From 10427b5288622764f1f7614dae88de06b9761026 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Mon, 25 Mar 2024 19:06:23 +0100 Subject: [PATCH] Conference, ActiveSpeaker, Camera --- Linphone/core/App.cpp | 5 + Linphone/core/CMakeLists.txt | 5 +- Linphone/core/call/CallCore.cpp | 5 + Linphone/core/call/CallCore.hpp | 1 + Linphone/core/call/CallGui.cpp | 2 - Linphone/core/camera/CameraGui.cpp | 195 +++++++-- Linphone/core/camera/CameraGui.hpp | 50 ++- Linphone/core/conference/ConferenceCore.cpp | 28 ++ Linphone/core/conference/ConferenceCore.hpp | 10 + Linphone/core/conference/ConferenceGui.cpp | 2 - .../core/conference/ConferenceInfoGui.cpp | 3 - .../participant/ParticipantDeviceCore.cpp | 11 +- .../participant/ParticipantDeviceCore.hpp | 1 + .../core/participant/ParticipantDeviceGui.cpp | 41 ++ .../core/participant/ParticipantDeviceGui.hpp | 42 ++ .../participant/ParticipantDeviceList.cpp | 397 +++++------------- .../participant/ParticipantDeviceList.hpp | 67 +-- .../participant/ParticipantDeviceProxy.cpp | 135 +++--- .../participant/ParticipantDeviceProxy.hpp | 69 ++- Linphone/model/conference/ConferenceModel.cpp | 12 +- Linphone/model/conference/ConferenceModel.hpp | 7 +- Linphone/model/tool/ToolModel.cpp | 40 ++ Linphone/model/tool/ToolModel.hpp | 5 + Linphone/tool/Utils.cpp | 13 +- Linphone/tool/thread/SafeConnection.hpp | 8 + Linphone/view/App/CallsWindow.qml | 165 +------- Linphone/view/App/Layout/MainLayout.qml | 3 +- Linphone/view/CMakeLists.txt | 6 + Linphone/view/Item/Call/WaitingRoom.qml | 4 +- Linphone/view/Item/Contact/Sticker.qml | 25 +- .../view/Layout/Call/ActiveSpeakerLayout.qml | 365 ++++++++++++++++ Linphone/view/Layout/Call/CallLayout.qml | 293 +++++++++++++ Linphone/view/Layout/Call/GridLayout.qml | 67 +++ 33 files changed, 1385 insertions(+), 697 deletions(-) create mode 100644 Linphone/core/participant/ParticipantDeviceGui.cpp create mode 100644 Linphone/core/participant/ParticipantDeviceGui.hpp create mode 100644 Linphone/view/Layout/Call/ActiveSpeakerLayout.qml create mode 100644 Linphone/view/Layout/Call/CallLayout.qml create mode 100644 Linphone/view/Layout/Call/GridLayout.qml diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 0c0edce81..6dba91742 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -50,6 +50,7 @@ #include "core/login/LoginPage.hpp" #include "core/notifier/Notifier.hpp" #include "core/participant/ParticipantDeviceCore.hpp" +#include "core/participant/ParticipantDeviceProxy.hpp" #include "core/participant/ParticipantGui.hpp" #include "core/participant/ParticipantProxy.hpp" #include "core/phone-number/PhoneNumber.hpp" @@ -217,6 +218,10 @@ void App::initCppInterfaces() { qmlRegisterType(Constants::MainQmlUri, 1, 0, "FPSCounter"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "TimeZoneProxy"); + + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantDeviceGui"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantDeviceProxy"); + LinphoneEnums::registerMetaTypes(); } diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index f3e187238..b15e7ee75 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -50,8 +50,9 @@ list(APPEND _LINPHONEAPP_SOURCES core/participant/ParticipantCore.cpp core/participant/ParticipantGui.cpp core/participant/ParticipantDeviceCore.cpp - # core/participant/ParticipantDeviceList.cpp - # core/participant/ParticipantDeviceProxy.cpp + core/participant/ParticipantDeviceGui.cpp + core/participant/ParticipantDeviceList.cpp + core/participant/ParticipantDeviceProxy.cpp core/participant/ParticipantList.cpp core/participant/ParticipantProxy.cpp ) diff --git a/Linphone/core/call/CallCore.cpp b/Linphone/core/call/CallCore.cpp index 41992c719..eaff051b4 100644 --- a/Linphone/core/call/CallCore.cpp +++ b/Linphone/core/call/CallCore.cpp @@ -380,10 +380,15 @@ ConferenceGui *CallCore::getConferenceGui() const { return mConference ? new ConferenceGui(mConference) : nullptr; } +QSharedPointer CallCore::getConferenceCore() const { + return mConference; +} + void CallCore::setConference(const QSharedPointer &conference) { if (mConference != conference) { mConference = conference; mIsConference = (mConference != nullptr); + qDebug() << "[CallCore] Set conference : " << mConference; emit conferenceChanged(); } } diff --git a/Linphone/core/call/CallCore.hpp b/Linphone/core/call/CallCore.hpp index a1f6b5c9e..d39ddc623 100644 --- a/Linphone/core/call/CallCore.hpp +++ b/Linphone/core/call/CallCore.hpp @@ -103,6 +103,7 @@ public: bool isConference() const; ConferenceGui *getConferenceGui() const; + QSharedPointer getConferenceCore() const; void setConference(const QSharedPointer &conference); QString getLocalSas(); diff --git a/Linphone/core/call/CallGui.cpp b/Linphone/core/call/CallGui.cpp index 293341953..6af8ac23b 100644 --- a/Linphone/core/call/CallGui.cpp +++ b/Linphone/core/call/CallGui.cpp @@ -24,7 +24,6 @@ DEFINE_ABSTRACT_OBJECT(CallGui) CallGui::CallGui(QSharedPointer core) { - qDebug() << "[CallGui] new" << this; App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); mCore = core; if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); @@ -32,7 +31,6 @@ CallGui::CallGui(QSharedPointer core) { CallGui::~CallGui() { mustBeInMainThread("~" + getClassName()); - qDebug() << "[CallGui] delete" << this; } CallCore *CallGui::getCore() const { diff --git a/Linphone/core/camera/CameraGui.cpp b/Linphone/core/camera/CameraGui.cpp index a71a1a13b..c41ca81c4 100644 --- a/Linphone/core/camera/CameraGui.cpp +++ b/Linphone/core/camera/CameraGui.cpp @@ -28,9 +28,14 @@ #include "core/App.hpp" #include "core/call/CallCore.hpp" #include "core/call/CallGui.hpp" +#include "core/participant/ParticipantDeviceCore.hpp" +#include "core/participant/ParticipantDeviceGui.hpp" DEFINE_ABSTRACT_OBJECT(CameraGui) +QMutex CameraGui::mPreviewCounterMutex; +int CameraGui::mPreviewCounter = 0; + // ============================================================================= CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) { mustBeInMainThread(getClassName()); @@ -45,38 +50,14 @@ CameraGui::CameraGui(QQuickItem *parent) : QQuickFramebufferObject(parent) { // TODO : Deactivate only if there are no previews to display (Could be open in settings and calls) CameraGui::~CameraGui() { mustBeInMainThread("~" + getClassName()); + mRefreshTimer.stop(); App::postModelSync([this]() { CoreModel::getInstance()->getCore()->enableVideoPreview(false); }); } QQuickFramebufferObject::Renderer *CameraGui::createRenderer() const { - QQuickFramebufferObject::Renderer *renderer = NULL; - // A renderer is mandatory, we cannot wait async. - switch (getSourceLocation()) { - case CorePreview: - App::postModelSync([this, &renderer]() { - auto coreModel = CoreModel::getInstance(); - if (coreModel) { - auto core = coreModel->getCore(); - if (!core) return; - core->enableVideoPreview(true); - renderer = (QQuickFramebufferObject::Renderer *)core->createNativePreviewWindowId(); - if (renderer) core->setNativePreviewWindowId(renderer); - } - }); - break; - case Call: - App::postModelSync([this, &renderer]() { - auto call = mCallGui->getCore()->getModel()->getMonitor(); - if (call) { - // qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to CallModel"; - renderer = (QQuickFramebufferObject::Renderer *)call->createNativeVideoWindowId(); - if (renderer) call->setNativeVideoWindowId(renderer); - } - }); - default: { - } - } + auto renderer = createRenderer(false); if (!renderer) { + qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to Dummy, " << getSourceLocation(); QTimer::singleShot(1, this, &CameraGui::isNotReady); renderer = new CameraDummy(); // Used to fill a renderer to avoid pushing a NULL. QTimer::singleShot(1000, this, &CameraGui::requestNewRenderer); @@ -84,6 +65,88 @@ QQuickFramebufferObject::Renderer *CameraGui::createRenderer() const { return renderer; } +QQuickFramebufferObject::Renderer *CameraGui::createRenderer(bool resetWindowId) const { + QQuickFramebufferObject::Renderer *renderer = NULL; + // A renderer is mandatory, we cannot wait async. + switch (getSourceLocation()) { + case CorePreview: + App::postModelSync([this, &renderer, resetWindowId]() { + qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to Preview"; + auto coreModel = CoreModel::getInstance(); + if (coreModel) { + auto core = coreModel->getCore(); + if (!core) return; + core->enableVideoPreview(true); + if (resetWindowId) { + renderer = (QQuickFramebufferObject::Renderer *)core->getNativePreviewWindowId(); + if (renderer) core->setNativePreviewWindowId(NULL); + } else { + renderer = (QQuickFramebufferObject::Renderer *)core->createNativePreviewWindowId(); + if (renderer) core->setNativePreviewWindowId(renderer); + } + } + }); + break; + case Call: + App::postModelSync([this, &renderer, resetWindowId]() { + auto call = mCallGui->getCore()->getModel()->getMonitor(); + if (call) { + qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to CallModel"; + if (resetWindowId) { + renderer = (QQuickFramebufferObject::Renderer *)call->getNativeVideoWindowId(); + if (renderer) call->setNativeVideoWindowId(NULL); + } else { + renderer = (QQuickFramebufferObject::Renderer *)call->createNativeVideoWindowId(); + if (renderer) call->setNativeVideoWindowId(renderer); + } + } + }); + break; + case Device: + App::postModelSync([this, &renderer, resetWindowId]() { + auto device = mParticipantDeviceGui->getCore()->getModel()->getMonitor(); + if (device) { + qInfo() << "[Camera] (" << mQmlName << ") Setting Camera to ParticipantDeviceModel"; + if (resetWindowId) { + } else { + renderer = (QQuickFramebufferObject::Renderer *)device->createNativeVideoWindowId(); + if (renderer) device->setNativeVideoWindowId(renderer); + } + } + }); + break; + default: { + } + } + + return renderer; +} + +void CameraGui::resetWindowId() const { + createRenderer(true); +} +void CameraGui::checkVideoDefinition() { /* + if (mWindowIdLocation == WindowIdLocation::CorePreview) { + auto videoDefinition = CoreManager::getInstance()->getSettingsModel()->getCurrentPreviewVideoDefinition(); + if (videoDefinition["width"] != mLastVideoDefinition["width"] || + videoDefinition["height"] != mLastVideoDefinition["height"]) { + mLastVideoDefinition = videoDefinition; + emit videoDefinitionChanged(); + } + }*/ +} + +QString CameraGui::getQmlName() const { + return mQmlName; +} + +void CameraGui::setQmlName(const QString &name) { + if (name != mQmlName) { + mQmlName = name; + emit qmlNameChanged(); + } +} + bool CameraGui::getIsReady() const { return mIsReady; } @@ -100,6 +163,21 @@ void CameraGui::isNotReady() { setIsReady(false); } +bool CameraGui::getIsPreview() const { + return mIsPreview; +} +void CameraGui::setIsPreview(bool status) { + if (mIsPreview != status) { + mIsPreview = status; + if (mIsPreview) activatePreview(); + else deactivatePreview(); + // updateWindowIdLocation(); + update(); + + emit isPreviewChanged(status); + } +} + CallGui *CameraGui::getCallGui() const { return mCallGui; } @@ -107,11 +185,70 @@ CallGui *CameraGui::getCallGui() const { void CameraGui::setCallGui(CallGui *callGui) { if (mCallGui != callGui) { mCallGui = callGui; + qDebug() << "Set Call " << mCallGui; emit callGuiChanged(mCallGui); + updateWindowIdLocation(); + } +} + +ParticipantDeviceGui *CameraGui::getParticipantDeviceGui() const { + return mParticipantDeviceGui; +} + +void CameraGui::setParticipantDeviceGui(ParticipantDeviceGui *deviceGui) { + if (mParticipantDeviceGui != deviceGui) { + mParticipantDeviceGui = deviceGui; + qDebug() << "Set Device " << mParticipantDeviceGui; + // setIsPreview(mParticipantDeviceGui->getCore()->isLocal()); + emit participantDeviceGuiChanged(mParticipantDeviceGui); + updateWindowIdLocation(); } } CameraGui::WindowIdLocation CameraGui::getSourceLocation() const { - if (mCallGui != nullptr) return Call; - else return CorePreview; + return mWindowIdLocation; +} + +void CameraGui::activatePreview() { + mPreviewCounterMutex.lock(); + setWindowIdLocation(WindowIdLocation::CorePreview); + if (++mPreviewCounter == 1) { + App::postModelSync([this]() { + auto coreModel = CoreModel::getInstance(); + coreModel->getCore()->enableVideoPreview(true); + }); + } + mPreviewCounterMutex.unlock(); +} + +void CameraGui::deactivatePreview() { + mPreviewCounterMutex.lock(); + setWindowIdLocation(WindowIdLocation::None); + if (--mPreviewCounter == 0) { + App::postModelSync([this]() { + auto coreModel = CoreModel::getInstance(); + coreModel->getCore()->enableVideoPreview(false); + }); + mPreviewCounterMutex.unlock(); + } +} +void CameraGui::setWindowIdLocation(const WindowIdLocation &location) { + if (mWindowIdLocation != location) { + qDebug() << "Update Window Id location from " << mWindowIdLocation << " to " << location; + resetWindowId(); // Location change: Reset old window ID. + mWindowIdLocation = location; + update(); + // if (mWindowIdLocation == WindowIdLocation::CorePreview) { + // mLastVideoDefinition = + // CoreManager::getInstance()->getSettingsModel()->getCurrentPreviewVideoDefinition(); emit + // videoDefinitionChanged(); mLastVideoDefinitionChecker.start(); + // } else mLastVideoDefinitionChecker.stop(); + } +} +void CameraGui::updateWindowIdLocation() { + bool useDefaultWindow = true; + if (mCallGui) setWindowIdLocation(WindowIdLocation::Call); + else if (mParticipantDeviceGui && !mParticipantDeviceGui->getCore()->isLocal()) + setWindowIdLocation(WindowIdLocation::Device); + else setWindowIdLocation(WindowIdLocation::CorePreview); } diff --git a/Linphone/core/camera/CameraGui.hpp b/Linphone/core/camera/CameraGui.hpp index 39764e2d8..42e10bb68 100644 --- a/Linphone/core/camera/CameraGui.hpp +++ b/Linphone/core/camera/CameraGui.hpp @@ -28,41 +28,83 @@ #include #include +#include "core/participant/ParticipantDeviceGui.hpp" // ============================================================================= class CallGui; class CameraGui : public QQuickFramebufferObject, public AbstractObject { Q_OBJECT - Q_PROPERTY(bool isReady READ getIsReady NOTIFY isReadyChanged) - Q_PROPERTY(CallGui *call READ getCallGui WRITE setCallGui NOTIFY callGuiChanged); + Q_PROPERTY(CallGui *call READ getCallGui WRITE setCallGui NOTIFY callGuiChanged) + Q_PROPERTY(ParticipantDeviceGui *participantDevice READ getParticipantDeviceGui WRITE setParticipantDeviceGui NOTIFY + participantDeviceGuiChanged) + Q_PROPERTY(bool isPreview READ getIsPreview WRITE setIsPreview NOTIFY isPreviewChanged) + Q_PROPERTY(bool isReady READ getIsReady WRITE setIsReady NOTIFY isReadyChanged) + // Q_PROPERTY(SoundPlayer * linphonePlayer READ getLinphonePlayer WRITE setLinphonePlayer NOTIFY + // linphonePlayerChanged) + Q_PROPERTY(QString qmlName READ getQmlName WRITE setQmlName NOTIFY qmlNameChanged) + + typedef enum { None = -1, CorePreview = 0, Call, Device, Player, Core } WindowIdLocation; public: CameraGui(QQuickItem *parent = Q_NULLPTR); virtual ~CameraGui(); QQuickFramebufferObject::Renderer *createRenderer() const override; + QQuickFramebufferObject::Renderer *createRenderer(bool resetWindowid) const; + + Q_INVOKABLE void resetWindowId() const; // const to be used from createRenderer() + void checkVideoDefinition(); + + static QMutex mPreviewCounterMutex; + static int mPreviewCounter; bool getIsReady() const; void setIsReady(bool isReady); void isReady(); void isNotReady(); + bool getIsPreview() const; + void setIsPreview(bool status); CallGui *getCallGui() const; void setCallGui(CallGui *callGui); - - typedef enum { None = -1, CorePreview = 0, Call, Device, Player, Core } WindowIdLocation; + ParticipantDeviceGui *getParticipantDeviceGui() const; + void setParticipantDeviceGui(ParticipantDeviceGui *participantDeviceGui); + QString getQmlName() const; + void setQmlName(const QString &name); WindowIdLocation getSourceLocation() const; + void setWindowIdLocation(const WindowIdLocation &location); + + void activatePreview(); + void deactivatePreview(); + void updateWindowIdLocation(); + void removeParticipantDeviceModel(); + void removeCallModel(); + void removeLinphonePlayer(); signals: void requestNewRenderer(); void isReadyChanged(bool isReady); void callGuiChanged(CallGui *callGui); + void isPreviewChanged(bool isPreview); + void isReadyChanged(); + void participantDeviceGuiChanged(ParticipantDeviceGui *participantDeviceGui); + void videoDefinitionChanged(); + // void linphonePlayerChanged(SoundPlayer * linphonePlayer); + void qmlNameChanged(); private: + bool mIsPreview = false; bool mIsReady = false; QTimer mRefreshTimer; int mMaxFps = 30; + QVariantMap mLastVideoDefinition; + QTimer mLastVideoDefinitionChecker; CallGui *mCallGui = nullptr; + ParticipantDeviceGui *mParticipantDeviceGui = nullptr; + QString mQmlName; + + WindowIdLocation mWindowIdLocation = None; + mutable bool mIsWindowIdSet = false; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/conference/ConferenceCore.cpp b/Linphone/core/conference/ConferenceCore.cpp index a2505c56a..26a656458 100644 --- a/Linphone/core/conference/ConferenceCore.cpp +++ b/Linphone/core/conference/ConferenceCore.cpp @@ -20,6 +20,7 @@ #include "ConferenceCore.hpp" #include "core/App.hpp" +#include "model/conference/ConferenceModel.hpp" #include "tool/Utils.hpp" #include "tool/thread/SafeConnection.hpp" @@ -35,6 +36,7 @@ ConferenceCore::ConferenceCore(const std::shared_ptr &conf App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); // Should be call from model Thread mustBeInLinphoneThread(getClassName()); + mConferenceModel = ConferenceModel::create(conference); mSubject = Utils::coreStringToAppString(conference->getSubject()); } ConferenceCore::~ConferenceCore() { @@ -45,6 +47,12 @@ ConferenceCore::~ConferenceCore() { void ConferenceCore::setSelf(QSharedPointer me) { mConferenceModelConnection = QSharedPointer>( new SafeConnection(me, mConferenceModel), &QObject::deleteLater); + mConferenceModelConnection->makeConnectToModel( + &ConferenceModel::activeSpeakerParticipantDevice, + [this](const std::shared_ptr &participantDevice) { + auto device = ParticipantDeviceCore::create(participantDevice); + mConferenceModelConnection->invokeToCore([this, device]() { setActiveSpeaker(device); }); + }); // mCallModelConnection->makeConnectToCore(&CallCore::lSetMicrophoneMuted, [this](bool isMuted) { // mCallModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); }); // }); @@ -79,3 +87,23 @@ void ConferenceCore::setIsReady(bool state) { isReadyChanged(); } } + +std::shared_ptr ConferenceCore::getModel() const { + return mConferenceModel; +} + +ParticipantDeviceCore *ConferenceCore::getActiveSpeaker() const { + return mActiveSpeaker.get(); +} + +ParticipantDeviceGui *ConferenceCore::getActiveSpeakerGui() const { + return new ParticipantDeviceGui(mActiveSpeaker); +} + +void ConferenceCore::setActiveSpeaker(const QSharedPointer &device) { + if (mActiveSpeaker != device) { + mActiveSpeaker = device; + qDebug() << "Changing active speaker to " << device->getAddress(); + emit activeSpeakerChanged(); + } +} diff --git a/Linphone/core/conference/ConferenceCore.hpp b/Linphone/core/conference/ConferenceCore.hpp index f84ca408f..753661424 100644 --- a/Linphone/core/conference/ConferenceCore.hpp +++ b/Linphone/core/conference/ConferenceCore.hpp @@ -21,6 +21,8 @@ #ifndef CONFERENCE_CORE_H_ #define CONFERENCE_CORE_H_ +#include "core/participant/ParticipantDeviceCore.hpp" +#include "core/participant/ParticipantDeviceGui.hpp" #include "model/conference/ConferenceModel.hpp" #include "tool/LinphoneEnums.hpp" #include "tool/thread/SafeConnection.hpp" @@ -38,6 +40,7 @@ public: // Q_PROPERTY(ParticipantModel* localParticipant READ getLocalParticipant NOTIFY localParticipantChanged) Q_PROPERTY(bool isReady MEMBER mIsReady WRITE setIsReady NOTIFY isReadyChanged) Q_PROPERTY(int participantDeviceCount READ getParticipantDeviceCount NOTIFY participantDeviceCountChanged) + Q_PROPERTY(ParticipantDeviceGui *activeSpeaker READ getActiveSpeakerGui NOTIFY activeSpeakerChanged) // Should be call from model Thread. Will be automatically in App thread after initialization static QSharedPointer create(const std::shared_ptr &conference); @@ -55,19 +58,26 @@ public: // std::list> // getParticipantList() const; // SDK exclude me. We want to get ALL participants. int getParticipantDeviceCount() const; + ParticipantDeviceCore *getActiveSpeaker() const; + ParticipantDeviceGui *getActiveSpeakerGui() const; + void setActiveSpeaker(const QSharedPointer &device); void setIsReady(bool state); + std::shared_ptr getModel() const; + //--------------------------------------------------------------------------- signals: void subjectChanged(); void isReadyChanged(); void participantDeviceCountChanged(); + void activeSpeakerChanged(); private: QSharedPointer> mConferenceModelConnection; std::shared_ptr mConferenceModel; + QSharedPointer mActiveSpeaker; bool mIsReady = false; QString mSubject; diff --git a/Linphone/core/conference/ConferenceGui.cpp b/Linphone/core/conference/ConferenceGui.cpp index 2bbd004ed..eb7ceb4fe 100644 --- a/Linphone/core/conference/ConferenceGui.cpp +++ b/Linphone/core/conference/ConferenceGui.cpp @@ -25,13 +25,11 @@ DEFINE_ABSTRACT_OBJECT(ConferenceGui) ConferenceGui::ConferenceGui() { - qDebug() << "[ConferenceGui] new" << this; mCore = ConferenceCore::create(nullptr); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); } ConferenceGui::ConferenceGui(QSharedPointer core) { - qDebug() << "[ConferenceGui] new" << this; App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); mCore = core; if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); diff --git a/Linphone/core/conference/ConferenceInfoGui.cpp b/Linphone/core/conference/ConferenceInfoGui.cpp index bb9db2330..5a8b233fb 100644 --- a/Linphone/core/conference/ConferenceInfoGui.cpp +++ b/Linphone/core/conference/ConferenceInfoGui.cpp @@ -25,13 +25,11 @@ DEFINE_ABSTRACT_OBJECT(ConferenceInfoGui) ConferenceInfoGui::ConferenceInfoGui() { - // qDebug() << "[ConferenceInfoGui] new" << this; mCore = ConferenceInfoCore::create(nullptr); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); } ConferenceInfoGui::ConferenceInfoGui(QSharedPointer core) { - // qDebug() << "[ConferenceInfoGui] new" << this; App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); mCore = core; if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); @@ -39,7 +37,6 @@ ConferenceInfoGui::ConferenceInfoGui(QSharedPointer core) { ConferenceInfoGui::~ConferenceInfoGui() { mustBeInMainThread("~" + getClassName()); - // qDebug() << "[ConferenceInfoGui] delete" << this; } ConferenceInfoCore *ConferenceInfoGui::getCore() const { diff --git a/Linphone/core/participant/ParticipantDeviceCore.cpp b/Linphone/core/participant/ParticipantDeviceCore.cpp index cab93804f..591b67e9d 100644 --- a/Linphone/core/participant/ParticipantDeviceCore.cpp +++ b/Linphone/core/participant/ParticipantDeviceCore.cpp @@ -20,6 +20,7 @@ #include "ParticipantDeviceCore.hpp" #include "core/App.hpp" +#include "model/tool/ToolModel.hpp" #include "tool/Utils.hpp" #include @@ -31,7 +32,7 @@ ParticipantDeviceCore::create(std::shared_ptr devic QSharedPointer(new ParticipantDeviceCore(device, isMe, parent), &QObject::deleteLater); sharedPointer->setSelf(sharedPointer); sharedPointer->moveToThread(App::getInstance()->thread()); - return nullptr; + return sharedPointer; } ParticipantDeviceCore::ParticipantDeviceCore(const std::shared_ptr &device, @@ -48,6 +49,8 @@ ParticipantDeviceCore::ParticipantDeviceCore(const std::shared_ptr(device); mParticipantDeviceModel->setSelf(mParticipantDeviceModel); mState = LinphoneEnums::fromLinphone(device->getState()); + qDebug() << "Address = " << Utils::coreStringToAppString(device->getAddress()->asStringUriOnly()); + mIsLocal = ToolModel::findAccount(device->getAddress()) != nullptr; // TODO set local // mCall = callModel; // if (mCall) connect(mCall, &CallModel::statusChanged, this, &ParticipantDeviceCore::onCallStatusChanged); mIsVideoEnabled = mParticipantDeviceModel->isVideoEnabled(); @@ -182,6 +185,10 @@ bool ParticipantDeviceCore::isLocal() const { return mIsLocal; } +std::shared_ptr ParticipantDeviceCore::getModel() const { + return mParticipantDeviceModel; +} + // void ParticipantDeviceCore::updateIsLocal() { // auto deviceAddress = mParticipantDeviceModel->getAddress(); // auto callAddress = mCall->getConferenceSharedModel()->getConference()->getMe()->getAddress(); @@ -247,4 +254,4 @@ void ParticipantDeviceCore::onStreamAvailabilityChanged( const std::shared_ptr &participantDevice, bool available, linphone::StreamType streamType) { -} \ No newline at end of file +} diff --git a/Linphone/core/participant/ParticipantDeviceCore.hpp b/Linphone/core/participant/ParticipantDeviceCore.hpp index e9a77a0c3..9443abb64 100644 --- a/Linphone/core/participant/ParticipantDeviceCore.hpp +++ b/Linphone/core/participant/ParticipantDeviceCore.hpp @@ -74,6 +74,7 @@ public: LinphoneEnums::ParticipantDeviceState getState() const; std::shared_ptr getDevice(); + std::shared_ptr getModel() const; void setPaused(bool paused); void setIsSpeaking(bool speaking); diff --git a/Linphone/core/participant/ParticipantDeviceGui.cpp b/Linphone/core/participant/ParticipantDeviceGui.cpp new file mode 100644 index 000000000..a4c13b94b --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceGui.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 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 . + */ + +#include "ParticipantDeviceGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(ParticipantDeviceGui) + +ParticipantDeviceGui::ParticipantDeviceGui(QObject *parent) : QObject(parent) { + mCore = ParticipantDeviceCore::create(nullptr); +} +ParticipantDeviceGui::ParticipantDeviceGui(QSharedPointer core) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; + if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); +} + +ParticipantDeviceGui::~ParticipantDeviceGui() { + mustBeInMainThread("~" + getClassName()); +} + +ParticipantDeviceCore *ParticipantDeviceGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/participant/ParticipantDeviceGui.hpp b/Linphone/core/participant/ParticipantDeviceGui.hpp new file mode 100644 index 000000000..0896d13a6 --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceGui.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 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 . + */ + +#ifndef PARTICIPANT_DEVICE_GUI_H_ +#define PARTICIPANT_DEVICE_GUI_H_ + +#include "ParticipantDeviceCore.hpp" +#include +#include + +class ParticipantDeviceGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(ParticipantDeviceCore *core READ getCore CONSTANT) + +public: + ParticipantDeviceGui(QSharedPointer core); + ParticipantDeviceGui(QObject *parent = nullptr); + ~ParticipantDeviceGui(); + ParticipantDeviceCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/participant/ParticipantDeviceList.cpp b/Linphone/core/participant/ParticipantDeviceList.cpp index 716d5720c..85580f3ab 100644 --- a/Linphone/core/participant/ParticipantDeviceList.cpp +++ b/Linphone/core/participant/ParticipantDeviceList.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Belledonne Communications SARL. + * Copyright (c) 2024 Belledonne Communications SARL. * * This file is part of linphone-desktop * (see https://www.linphone.org). @@ -20,21 +20,14 @@ #include "ParticipantDeviceList.hpp" #include "core/App.hpp" -#include "core/participant/ParticipantCore.hpp" +#include "core/participant/ParticipantDeviceCore.hpp" +#include "core/participant/ParticipantDeviceGui.hpp" #include #include DEFINE_ABSTRACT_OBJECT(ParticipantDeviceList) -QSharedPointer -ParticipantDeviceList::create(const std::shared_ptr &participant) { - auto model = QSharedPointer(new ParticipantDeviceList(participant), &QObject::deleteLater); - model->moveToThread(App::getInstance()->thread()); - model->setSelf(model); - return model; -} - QSharedPointer ParticipantDeviceList::create() { auto model = QSharedPointer(new ParticipantDeviceList(), &QObject::deleteLater); model->moveToThread(App::getInstance()->thread()); @@ -42,302 +35,112 @@ QSharedPointer ParticipantDeviceList::create() { return model; } -ParticipantDeviceList::ParticipantDeviceList(const std::shared_ptr &participant, QObject *parent) - : ListProxy(parent) { - std::list> devices = participant->getDevices(); - for (auto device : devices) { - auto deviceModel = ParticipantDeviceCore::create(device, isMe(device)); - // connect(this, &ParticipantDeviceList::securityLevelChanged, deviceModel.get(), - // &ParticipantDeviceCore::onSecurityLevelChanged); - connect(deviceModel.get(), &ParticipantDeviceCore::isSpeakingChanged, this, - &ParticipantDeviceList::onParticipantDeviceSpeaking); - mList << deviceModel; - } - mInitialized = true; +QSharedPointer +ParticipantDeviceList::create(const std::shared_ptr &conferenceModel) { + auto model = create(); + model->setConferenceModel(conferenceModel); + return model; } -ParticipantDeviceList::ParticipantDeviceList(QObject *parent) { - mustBeInMainThread(getClassName()); +ParticipantDeviceList::ParticipantDeviceList() { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); } -// ParticipantDeviceList::ParticipantDeviceList(CallModel *callModel, QObject *parent) : ProxyListModel(parent) { -// if (callModel && callModel->isConference()) { -// mCallModel = callModel; -// connect(mCallModel, &CallModel::conferenceModelChanged, this, &ParticipantDeviceList::onConferenceModelChanged); -// initConferenceModel(); -// } -// } - ParticipantDeviceList::~ParticipantDeviceList() { - mustBeInMainThread(getClassName()); +} + +QList> +ParticipantDeviceList::buildDevices(const std::shared_ptr &conferenceModel) const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + QList> devices; + auto lDevices = conferenceModel->getMonitor()->getParticipantDeviceList(); + bool haveMe = false; + for (auto device : lDevices) { + auto deviceCore = ParticipantDeviceCore::create(device); + devices << deviceCore; + if (deviceCore->isMe()) haveMe = true; + } + if (!haveMe) { + } + return devices; +} + +QSharedPointer ParticipantDeviceList::getMe() const { + if (mList.size() > 0) { + return mList[0].objectCast(); + } else return nullptr; +} + +void ParticipantDeviceList::setDevices(QList> devices) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + add(devices); + qDebug() << "[ParticipantDeviceList] : add " << devices.size() << " devices"; +} + +void ParticipantDeviceList::setConferenceModel(const std::shared_ptr &conferenceModel) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + mConferenceModel = conferenceModel; + qDebug() << "[ParticipantDeviceList] : set Conference " << mConferenceModel.get(); + if (mConferenceModelConnection->mCore.lock()) { // Unsure to get myself + auto oldConnect = mConferenceModelConnection->mCore; // Setself rebuild safepointer + setSelf(mConferenceModelConnection->mCore.mQData); // reset connections + oldConnect.unlock(); + } + beginResetModel(); + mList.clear(); + endResetModel(); + if (mConferenceModel) { + qDebug() << "[ParticipantDeviceList] : request devices"; + mConferenceModelConnection->invokeToModel([this]() { + qDebug() << "[ParticipantDeviceList] : build devices"; + auto devices = buildDevices(mConferenceModel); + mConferenceModelConnection->invokeToCore([this, devices]() { + qDebug() << "[ParticipantDeviceList] : set devices"; + setDevices(devices); + }); + }); + } } void ParticipantDeviceList::setSelf(QSharedPointer me) { -} - -void ParticipantDeviceList::initConferenceModel() { - // if (!mInitialized && mCallModel) { - // auto conferenceModel = mCallModel->getConferenceSharedModel(); - // if (conferenceModel) { - // updateDevices(conferenceModel->getConference()->getMe()->getDevices(), true); - // updateDevices(conferenceModel->getConference()->getParticipantDeviceList(), false); - - // qDebug() << "Conference have " << mList.size() << " devices"; - // connect(conferenceModel.get(), &ConferenceModel::activeSpeakerParticipantDevice, this, - // &ParticipantDeviceList::onActiveSpeakerParticipantDevice); - // connect(conferenceModel.get(), &ConferenceModel::participantAdded, this, - // &ParticipantDeviceList::onParticipantAdded); - // connect(conferenceModel.get(), &ConferenceModel::participantRemoved, this, - // &ParticipantDeviceList::onParticipantRemoved); - // connect(conferenceModel.get(), &ConferenceModel::participantDeviceAdded, this, - // &ParticipantDeviceList::onParticipantDeviceAdded); - // connect(conferenceModel.get(), &ConferenceModel::participantDeviceRemoved, this, - // &ParticipantDeviceList::onParticipantDeviceRemoved); - // connect(conferenceModel.get(), &ConferenceModel::conferenceStateChanged, this, - // &ParticipantDeviceList::onConferenceStateChanged); - // connect(conferenceModel.get(), &ConferenceModel::participantDeviceMediaCapabilityChanged, this, - // &ParticipantDeviceList::onParticipantDeviceMediaCapabilityChanged); - // connect(conferenceModel.get(), &ConferenceModel::participantDeviceMediaAvailabilityChanged, this, - // &ParticipantDeviceList::onParticipantDeviceMediaAvailabilityChanged); - // connect(conferenceModel.get(), &ConferenceModel::participantDeviceIsSpeakingChanged, this, - // &ParticipantDeviceList::onParticipantDeviceIsSpeakingChanged); - // mActiveSpeaker = get(conferenceModel->getConference()->getActiveSpeakerParticipantDevice()); - // mInitialized = true; - // } - // } -} - -void ParticipantDeviceList::updateDevices(std::shared_ptr participant) { - std::list> devices = participant->getDevices(); - bool meAdded = false; - beginResetModel(); - qDebug() << "Update devices from participant"; - mList.clear(); - for (auto device : devices) { - bool addMe = isMe(device); - auto deviceModel = ParticipantDeviceCore::create(device, addMe); - // connect(this, &ParticipantDeviceList::securityLevelChanged, deviceModel.get(), - // &ParticipantDeviceCore::onSecurityLevelChanged); - connect(deviceModel.get(), &ParticipantDeviceCore::isSpeakingChanged, this, - &ParticipantDeviceList::onParticipantDeviceSpeaking); - mList << deviceModel; - if (addMe) meAdded = true; - } - endResetModel(); - if (meAdded) emit meChanged(); -} - -void ParticipantDeviceList::updateDevices(const std::list> &devices, - const bool &isMe) { - for (auto device : devices) { - add(device); + if (mConferenceModelConnection) mConferenceModelConnection->disconnect(); + mConferenceModelConnection = QSharedPointer>( + new SafeConnection(me, mConferenceModel), &QObject::deleteLater); + if (mConferenceModel) { + mConferenceModelConnection->makeConnectToModel( + &ConferenceModel::participantDeviceAdded, + [this](const std::shared_ptr &device) { + auto deviceCore = ParticipantDeviceCore::create(device); + mConferenceModelConnection->invokeToCore([this, deviceCore]() { + qDebug() << "[ParticipantDeviceList] : add a device"; + this->add(deviceCore); + }); + }); + mConferenceModelConnection->makeConnectToModel( + &ConferenceModel::conferenceStateChanged, [this](linphone::Conference::State state) { + qDebug() << "[ParticipantDeviceList] new state = " << (int)state; + if (state == linphone::Conference::State::Created) { + qDebug() << "[ParticipantDeviceList] : build devices"; + auto devices = buildDevices(mConferenceModel); + mConferenceModelConnection->invokeToCore([this, devices]() { + qDebug() << "[ParticipantDeviceList] : set devices" << devices.size(); + setDevices(devices); + }); + } + }); + mConferenceModelConnection->makeConnectToCore( + &ParticipantDeviceList::lSetConferenceModel, + [this](const std::shared_ptr &conferenceModel) { + mConferenceModelConnection->invokeToCore( + [this, conferenceModel]() { setConferenceModel(conferenceModel); }); + }); } } -bool ParticipantDeviceList::add(const QSharedPointer &deviceToAdd) { - auto deviceToAddAddr = deviceToAdd->getAddress(); - int row = 0; - qDebug() << "Adding device " << deviceToAdd->getAddress(); - for (auto item : mList) { - auto deviceCore = item.objectCast(); - if (deviceCore == deviceToAdd) { - qDebug() << "Device already exist. Send video update event"; - // deviceCore->updateVideoEnabled(); - return false; - } else if (deviceToAddAddr == deviceCore->getAddress()) { // Address is the same (same device) but the model - // is using another linphone object. Replace it. - qDebug() << "Replacing device : Device exists but the model is using another linphone object."; - // deviceCore->updateVideoEnabled(); - removeRow(row); - break; - } - ++row; - } - bool addMe = isMe(deviceToAdd); - auto deviceModel = ParticipantDeviceCore::create(deviceToAdd, addMe); - // connect(this, &ParticipantDeviceList::securityLevelChanged, deviceModel.get(), - // &ParticipantDeviceCore::onSecurityLevelChanged); - connect(deviceModel.get(), &ParticipantDeviceCore::isSpeakingChanged, this, - &ParticipantDeviceList::onParticipantDeviceSpeaking); - ListProxy::add(deviceModel); - qDebug() << "Device added. Count=" << mList.count(); - QStringList debugDevices; - for (auto i : mList) { - auto item = i.objectCast(); - debugDevices.push_back(item->getAddress()); - } - qDebug() << debugDevices.join("\n"); - if (addMe) { - qDebug() << "Added a me device"; - emit meChanged(); - } else if (mList.size() == 1 || - (mList.size() == 2 && isMe(mList.front().objectCast()->getDevice()))) { - mActiveSpeaker = mList.back().objectCast(); - emit activeSpeakerChanged(); - } - return true; -} - -bool ParticipantDeviceList::remove(std::shared_ptr deviceToRemove) { - int row = 0; - for (auto item : mList) { - auto device = item.objectCast(); - if (device->getDevice() == deviceToRemove) { - // device->updateVideoEnabled(); - removeRow(row); - return true; - } else ++row; - } - return false; -} - -QSharedPointer -ParticipantDeviceList::get(std::shared_ptr deviceToGet, int *index) { - int row = 0; - for (auto item : mList) { - auto device = item.objectCast(); - if (device->getDevice() == deviceToGet) { - if (index) *index = row; - return device; - } else ++row; - } - return nullptr; -} - -QSharedPointer ParticipantDeviceList::getMe(int *index) const { - int row = 0; - for (auto item : mList) { - auto device = item.objectCast(); - if (device->isMe() && device->isLocal()) { - if (index) *index = row; - return device; - } else ++row; - } - return nullptr; -} - -ParticipantDeviceCore *ParticipantDeviceList::getActiveSpeakerModel() const { - return mActiveSpeaker.get(); -} - -bool ParticipantDeviceList::isMe(std::shared_ptr deviceToCheck) const { - // if (mCallModel) { - // auto devices = mCallModel->getConferenceSharedModel()->getConference()->getMe()->getDevices(); - // auto deviceToCheckAddress = deviceToCheck->getAddress(); - // for (auto device : devices) { - // if (deviceToCheckAddress == device->getAddress()) return true; - // } - // } - return false; -} - -bool ParticipantDeviceList::isMeAlone() const { - for (auto item : mList) { - auto device = item.objectCast(); - if (!isMe(device->getDevice())) return false; - } - return true; -} - -void ParticipantDeviceList::onConferenceModelChanged() { - if (!mInitialized) { - initConferenceModel(); - } -} - -void ParticipantDeviceList::onSecurityLevelChanged(std::shared_ptr device) { - emit securityLevelChanged(device); -} - -//---------------------------------------------------------------------------------------------------------- -void ParticipantDeviceList::onParticipantAdded(const std::shared_ptr &participant) { - std::list> devices = participant->getDevices(); - if (devices.size() == 0) - qDebug() << "Participant has no device. It will not be added : " - << participant->getAddress()->asString().c_str(); - else - for (auto device : devices) - add(device); -} - -void ParticipantDeviceList::onParticipantRemoved(const std::shared_ptr &participant) { - std::list> devices = participant->getDevices(); - for (auto device : devices) - remove(device); -} - -void ParticipantDeviceList::onParticipantDeviceAdded( - const std::shared_ptr &participantDevice) { - qDebug() << "Adding new device : " << mList.count(); - // auto conferenceModel = mCallModel->getConferenceSharedModel(); - std::list> devices; - for (int i = 0; i < 2; ++i) { - // if (i == 0) devices = conferenceModel->getConference()->getParticipantDeviceList(); // Active devices. - // else devices = conferenceModel->getConference()->getMe()->getDevices(); - for (auto realParticipantDevice : devices) { - if (realParticipantDevice == participantDevice) { - add(realParticipantDevice); - return; - } - } - } - - qDebug() << "No participant device found from linphone::ParticipantDevice at onParticipantDeviceAdded"; -} - -void ParticipantDeviceList::onParticipantDeviceRemoved( - const std::shared_ptr &participantDevice) { - qDebug() << "Removing participant device : " << mList.count(); - if (!remove(participantDevice)) - qDebug() << "No participant device found from linphone::ParticipantDevice at onParticipantDeviceRemoved"; -} - -void ParticipantDeviceList::onConferenceStateChanged(linphone::Conference::State newState) { - // if (newState == linphone::Conference::State::Created) { - // if (mCallModel && mCallModel->isConference()) { - // auto conferenceModel = mCallModel->getConferenceSharedModel(); - // updateDevices(conferenceModel->getConference()->getMe()->getDevices(), true); - // updateDevices(conferenceModel->getConference()->getParticipantDeviceList(), false); - // } - // emit conferenceCreated(); - // } -} - -void ParticipantDeviceList::onParticipantDeviceMediaCapabilityChanged( - const std::shared_ptr &participantDevice) { - // auto device = get(participantDevice); - // if (device) device->updateVideoEnabled(); - // else onParticipantDeviceAdded(participantDevice); - - // device = get(participantDevice); - // if (device && device->isMe()) { // Capability change for me. Update all videos. - // for (auto item : mList) { - // auto device = item.objectCast(); - // device->updateVideoEnabled(); - // } - // } -} - -void ParticipantDeviceList::onParticipantDeviceMediaAvailabilityChanged( - const std::shared_ptr &participantDevice) { - // auto device = get(participantDevice); - // if (device) device->updateVideoEnabled(); - // else onParticipantDeviceAdded(participantDevice); -} -void ParticipantDeviceList::onActiveSpeakerParticipantDevice( - const std::shared_ptr &participantDevice) { - // auto device = get(participantDevice); - // if (device) { - // mActiveSpeaker = device; - // emit activeSpeakerChanged(); - // } -} - -void ParticipantDeviceList::onParticipantDeviceIsSpeakingChanged( - const std::shared_ptr &participantDevice, bool isSpeaking) { - auto device = get(participantDevice); - if (device) emit participantSpeaking(device.get()); -} - -void ParticipantDeviceList::onParticipantDeviceSpeaking() { +QVariant ParticipantDeviceList::data(const QModelIndex &index, int role) const { + int row = index.row(); + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + if (role == Qt::DisplayRole) + return QVariant::fromValue(new ParticipantDeviceGui(mList[row].objectCast())); + return QVariant(); } diff --git a/Linphone/core/participant/ParticipantDeviceList.hpp b/Linphone/core/participant/ParticipantDeviceList.hpp index 4671e7c01..3a012890e 100644 --- a/Linphone/core/participant/ParticipantDeviceList.hpp +++ b/Linphone/core/participant/ParticipantDeviceList.hpp @@ -22,70 +22,39 @@ #define PARTICIPANT_DEVICE_LIST_H_ #include "../proxy/ListProxy.hpp" -#include "core/call/CallCore.hpp" -#include "core/participant/ParticipantDeviceCore.hpp" -#include -#include -#include +#include "ParticipantDeviceCore.hpp" +#include "model/conference/ConferenceModel.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/thread/SafeConnection.hpp" class ParticipantDeviceList : public ListProxy, public AbstractObject { Q_OBJECT public: - static QSharedPointer create(const std::shared_ptr &participant); static QSharedPointer create(); + static QSharedPointer create(const std::shared_ptr &conferenceModel); - ParticipantDeviceList(const std::shared_ptr &participant, QObject *parent = nullptr); - // ParticipantDeviceList(CallCore *callCore, QObject *parent = nullptr); - ParticipantDeviceList(QObject *parent = Q_NULLPTR); + ParticipantDeviceList(); ~ParticipantDeviceList(); + QList> + buildDevices(const std::shared_ptr &conferenceModel) const; + + QSharedPointer getMe() const; + + void setDevices(QList> devices); + void setConferenceModel(const std::shared_ptr &conferenceModel); + void setSelf(QSharedPointer me); - void initConferenceModel(); - void updateDevices(std::shared_ptr participant); - void updateDevices(const std::list> &devices, const bool &isMe); - - bool add(const QSharedPointer &deviceToAdd); - bool remove(std::shared_ptr deviceToAdd); - QSharedPointer get(std::shared_ptr deviceToGet, - int *index = nullptr); - QSharedPointer getMe(int *index = nullptr) const; - ParticipantDeviceCore *getActiveSpeakerModel() const; - - bool isMe(std::shared_ptr device) const; - bool isMeAlone() const; - -public slots: - void onActiveSpeakerParticipantDevice(const std::shared_ptr &participantDevice); - void onConferenceModelChanged(); - void onSecurityLevelChanged(std::shared_ptr device); - void onParticipantAdded(const std::shared_ptr &participant); - void onParticipantRemoved(const std::shared_ptr &participant); - void onParticipantDeviceAdded(const std::shared_ptr &participantDevice); - void onParticipantDeviceRemoved(const std::shared_ptr &participantDevice); - void onConferenceStateChanged(linphone::Conference::State newState); - void onParticipantDeviceMediaCapabilityChanged( - const std::shared_ptr &participantDevice); - void onParticipantDeviceMediaAvailabilityChanged( - const std::shared_ptr &participantDevice); - void onParticipantDeviceIsSpeakingChanged(const std::shared_ptr &device, - bool isSpeaking); - void onParticipantDeviceSpeaking(); + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; signals: - void activeSpeakerChanged(); - void securityLevelChanged(std::shared_ptr device); - void participantSpeaking(ParticipantDeviceCore *speakingDevice); - void conferenceCreated(); - void meChanged(); + void lSetConferenceModel(const std::shared_ptr &conferenceModel); private: - CallCore *mCallCore = nullptr; - QSharedPointer mActiveSpeaker; - // QList mActiveSpeakers;// First item is last speaker - bool mInitialized = false; - QSharedPointer> mModelConnection; + std::shared_ptr mConferenceModel; + QSharedPointer> mConferenceModelConnection; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/participant/ParticipantDeviceProxy.cpp b/Linphone/core/participant/ParticipantDeviceProxy.cpp index e1635c198..ab1185b2f 100644 --- a/Linphone/core/participant/ParticipantDeviceProxy.cpp +++ b/Linphone/core/participant/ParticipantDeviceProxy.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Belledonne Communications SARL. + * Copyright (c) 2024 Belledonne Communications SARL. * * This file is part of linphone-desktop * (see https://www.linphone.org). @@ -26,96 +26,71 @@ // ============================================================================= +DEFINE_ABSTRACT_OBJECT(ParticipantDeviceProxy) + ParticipantDeviceProxy::ParticipantDeviceProxy(QObject *parent) : SortFilterProxy(parent) { - mDeleteSourceModel = true; - mList = ParticipantDeviceList::create(); - setSourceModel(mList.get()); + mParticipants = ParticipantDeviceList::create(); + connect(mParticipants.get(), &ParticipantDeviceList::countChanged, this, &ParticipantDeviceProxy::meChanged); + + setSourceModel(mParticipants.get()); + sort(0); //, Qt::DescendingOrder); } ParticipantDeviceProxy::~ParticipantDeviceProxy() { + setSourceModel(nullptr); } -bool ParticipantDeviceProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - const QModelIndex index = mList->index(sourceRow, 0, sourceParent); - const ParticipantDeviceCore *device = index.data().value(); - return device && (isShowMe() /*|| !(device->isMe() && device->isLocal())*/); +CallGui *ParticipantDeviceProxy::getCurrentCall() const { + return mCurrentCall; } -bool ParticipantDeviceProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { - const ParticipantDeviceCore *deviceA = sourceModel()->data(left).value(); - const ParticipantDeviceCore *deviceB = sourceModel()->data(right).value(); - // 'me' at end (for grid). - return /*deviceB->isLocal() || !deviceA->isLocal() && deviceB->isMe() ||*/ left.row() < right.row(); -} -//--------------------------------------------------------------------------------- - -ParticipantDeviceCore *ParticipantDeviceProxy::getAt(int row) { - if (row >= 0) { - QModelIndex sourceIndex = mapToSource(this->index(row, 0)); - return sourceModel()->data(sourceIndex).value(); - } else return nullptr; -} - -ParticipantDeviceCore *ParticipantDeviceProxy::getActiveSpeakerModel() { - return mList->getActiveSpeakerModel(); -} - -CallModel *ParticipantDeviceProxy::getCallModel() const { - return mCallModel; -} - -ParticipantDeviceCore *ParticipantDeviceProxy::getMe() const { - return mList->getMe().get(); -} - -bool ParticipantDeviceProxy::isShowMe() const { - return mShowMe; -} - -void ParticipantDeviceProxy::connectTo(ParticipantDeviceList *model) { - connect(model, &ParticipantDeviceList::countChanged, this, &ParticipantDeviceProxy::onCountChanged); - connect(model, &ParticipantDeviceList::participantSpeaking, this, &ParticipantDeviceProxy::onParticipantSpeaking); - connect(model, &ParticipantDeviceList::conferenceCreated, this, &ParticipantDeviceProxy::conferenceCreated); - connect(model, &ParticipantDeviceList::meChanged, this, &ParticipantDeviceProxy::meChanged); - connect(model, &ParticipantDeviceList::activeSpeakerChanged, this, &ParticipantDeviceProxy::activeSpeakerChanged); -} -void ParticipantDeviceProxy::setCallModel(CallModel *callModel) { - setFilterType(1); - mCallModel = callModel; - deleteSourceModel(); - auto newSourceModel = new ParticipantDeviceList(mCallModel); - connectTo(newSourceModel); - setSourceModel(newSourceModel); - mDeleteSourceModel = true; - sort(0); - emit countChanged(); - emit meChanged(); -} - -// void ParticipantDeviceProxy::setParticipant(ParticipantCore *participantCore) { -// setFilterType(0); -// deleteSourceModel(); -// auto newSourceModel = participant->getParticipantDevices().get(); -// connectTo(newSourceModel); -// setSourceModel(newSourceModel); -// mDeleteSourceModel = false; -// sort(0); -// emit countChanged(); -// emit meChanged(); -// } - -void ParticipantDeviceProxy::setShowMe(const bool &show) { - if (mShowMe != show) { - mShowMe = show; - emit showMeChanged(); - invalidate(); +void ParticipantDeviceProxy::setCurrentCall(CallGui *call) { + qDebug() << "[ParticipantDeviceProxy] set current call " << this << " => " << call; + if (mCurrentCall != call) { + CallCore *callCore = nullptr; + if (mCurrentCall) { + callCore = mCurrentCall->getCore(); + if (callCore) callCore->disconnect(mParticipants.get()); + callCore = nullptr; + } + mCurrentCall = call; + if (mCurrentCall) callCore = mCurrentCall->getCore(); + if (callCore) { + connect(callCore, &CallCore::conferenceChanged, mParticipants.get(), [this]() { + auto conference = mCurrentCall->getCore()->getConferenceCore(); + qDebug() << "[ParticipantDeviceProxy] set conference " << this << " => " << conference; + mParticipants->setConferenceModel(conference ? conference->getModel() : nullptr); + // mParticipants->lSetConferenceModel(conference ? conference->getModel() : nullptr); + }); + auto conference = callCore->getConferenceCore(); + qDebug() << "[ParticipantDeviceProxy] set conference " << this << " => " << conference; + mParticipants->setConferenceModel(conference ? conference->getModel() : nullptr); + // mParticipants->lSetConferenceModel(conference ? conference->getModel() : nullptr); + } + emit currentCallChanged(); } } -void ParticipantDeviceProxy::onCountChanged() { - qDebug() << "Count changed : " << getCount(); +ParticipantDeviceGui *ParticipantDeviceProxy::getMe() const { + auto core = mParticipants->getMe(); + if (!core) return nullptr; + else { + return new ParticipantDeviceGui(core); + } } -void ParticipantDeviceProxy::onParticipantSpeaking(ParticipantDeviceCore *speakingDevice) { - emit participantSpeaking(speakingDevice); +void ParticipantDeviceProxy::setMe(ParticipantDeviceGui *me) { +} + +bool ParticipantDeviceProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { + return true; +} + +bool ParticipantDeviceProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { + auto deviceA = sourceModel()->data(left).value()->getCore(); + auto deviceB = sourceModel()->data(right).value()->getCore(); + // auto deviceB = getItemAt(right.row())->getCore(); + // return deviceB->isLocal() || !deviceA->isLocal() && deviceB->isMe() || left.row() < right.row(); + + return deviceB->isMe() || (!deviceB->isMe() && left.row() < right.row()); } diff --git a/Linphone/core/participant/ParticipantDeviceProxy.hpp b/Linphone/core/participant/ParticipantDeviceProxy.hpp index 436164464..29043b14e 100644 --- a/Linphone/core/participant/ParticipantDeviceProxy.hpp +++ b/Linphone/core/participant/ParticipantDeviceProxy.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Belledonne Communications SARL. + * Copyright (c) 2024 Belledonne Communications SARL. * * This file is part of linphone-desktop * (see https://www.linphone.org). @@ -21,64 +21,43 @@ #ifndef PARTICIPANT_DEVICE_PROXY_MODEL_H_ #define PARTICIPANT_DEVICE_PROXY_MODEL_H_ -#include -// ============================================================================= #include "../proxy/SortFilterProxy.hpp" -#include -#include -#include -#include +#include "core/call/CallGui.hpp" +#include "core/participant/ParticipantDeviceGui.hpp" +#include "tool/AbstractObject.hpp" class ParticipantDeviceList; -class ParticipantDeviceCore; -class ParticipantCore; -class CallModel; +class ParticipantDeviceGui; -class ParticipantDeviceProxy : public SortFilterProxy { +class ParticipantDeviceProxy : public SortFilterProxy, public AbstractObject { Q_OBJECT + Q_PROPERTY(CallGui *currentCall READ getCurrentCall WRITE setCurrentCall NOTIFY currentCallChanged) + Q_PROPERTY(ParticipantDeviceGui *me READ getMe WRITE setMe NOTIFY meChanged) public: - Q_PROPERTY(CallModel *callModel READ getCallModel WRITE setCallModel NOTIFY callModelChanged) - Q_PROPERTY(bool showMe READ isShowMe WRITE setShowMe NOTIFY showMeChanged) - Q_PROPERTY(ParticipantDeviceCore *me READ getMe NOTIFY meChanged) - Q_PROPERTY(ParticipantDeviceCore *activeSpeaker READ getActiveSpeakerModel NOTIFY activeSpeakerChanged) - - ParticipantDeviceProxy(QObject *parent = nullptr); + ParticipantDeviceProxy(QObject *parent = Q_NULLPTR); ~ParticipantDeviceProxy(); - Q_INVOKABLE ParticipantDeviceCore *getAt(int row); - ParticipantDeviceCore *getActiveSpeakerModel(); - ParticipantDeviceCore *getMe() const; + CallGui *getCurrentCall() const; + void setCurrentCall(CallGui *callGui); - CallModel *getCallModel() const; - bool isShowMe() const; - - void setCallModel(CallModel *callModel); - // void setParticipant(ParticipantCore *participantCore); - void setShowMe(const bool &show); - - void connectTo(ParticipantDeviceList *model); - -public slots: - void onCountChanged(); - void onParticipantSpeaking(ParticipantDeviceCore *speakingDevice); - -signals: - void activeSpeakerChanged(); - void callModelChanged(); - void showMeChanged(); - void meChanged(); - void participantSpeaking(ParticipantDeviceCore *speakingDevice); - void conferenceCreated(); + ParticipantDeviceGui *getMe() const; + void setMe(ParticipantDeviceGui *me); protected: - virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - CallModel *mCallModel; - bool mShowMe = true; +signals: + void lUpdate(); + void currentCallChanged(); + void meChanged(); - QSharedPointer mList; +private: + QString mSearchText; + CallGui *mCurrentCall = nullptr; + QSharedPointer mParticipants; + DECLARE_ABSTRACT_OBJECT }; #endif diff --git a/Linphone/model/conference/ConferenceModel.cpp b/Linphone/model/conference/ConferenceModel.cpp index e200a4f6d..2476a4cb6 100644 --- a/Linphone/model/conference/ConferenceModel.cpp +++ b/Linphone/model/conference/ConferenceModel.cpp @@ -24,13 +24,20 @@ #include "core/path/Paths.hpp" #include "model/core/CoreModel.hpp" +#include "tool/Utils.hpp" DEFINE_ABSTRACT_OBJECT(ConferenceModel) +std::shared_ptr ConferenceModel::create(const std::shared_ptr &conference) { + auto model = Utils::makeQObject_ptr(conference); + model->setSelf(model); + return model; +} + ConferenceModel::ConferenceModel(const std::shared_ptr &conference, QObject *parent) : ::Listener(conference, parent) { mustBeInLinphoneThread(getClassName()); - qDebug() << "[ConferenceModel] new" << this; + qDebug() << "[ConferenceModel] new" << this << conference.get(); } ConferenceModel::~ConferenceModel() { @@ -136,7 +143,8 @@ void ConferenceModel::onActiveSpeakerParticipantDevice( const std::shared_ptr &conference, const std::shared_ptr &participantDevice) { qDebug() << "onActiveSpeakerParticipantDevice: " << participantDevice->getAddress()->asString().c_str(); - emit activeSpeakerParticipantDevice(participantDevice); + + emit activeSpeakerParticipantDevice(conference->getActiveSpeakerParticipantDevice()); } void ConferenceModel::onParticipantAdded(const std::shared_ptr &conference, diff --git a/Linphone/model/conference/ConferenceModel.hpp b/Linphone/model/conference/ConferenceModel.hpp index 2948a10a8..3986189a6 100644 --- a/Linphone/model/conference/ConferenceModel.hpp +++ b/Linphone/model/conference/ConferenceModel.hpp @@ -35,6 +35,7 @@ class ConferenceModel : public ::Listener &conference, QObject *parent = nullptr); ~ConferenceModel(); + static std::shared_ptr create(const std::shared_ptr &conference); void terminate(); @@ -105,11 +106,11 @@ private: const std::shared_ptr &audioDevice) override; signals: - void activeSpeakerParticipantDevice(const std::shared_ptr &participantDevice); - void participantAdded(const std::shared_ptr &participant); + void activeSpeakerParticipantDevice(const std::shared_ptr &participantDevice); + void participantAdded(const std::shared_ptr &participant); void participantRemoved(const std::shared_ptr &participant); void participantAdminStatusChanged(const std::shared_ptr &participant); - void participantDeviceAdded(const std::shared_ptr &participantDevice); + void participantDeviceAdded(const std::shared_ptr &participantDevice); void participantDeviceRemoved(const std::shared_ptr &participantDevice); void participantDeviceStateChanged(const std::shared_ptr &conference, const std::shared_ptr &device, diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index 359d2345c..e8d2bb237 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -159,3 +159,43 @@ QSharedPointer ToolModel::createCall(const QString &sipAddress, // CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); */ } + +std::shared_ptr ToolModel::findAccount(const std::shared_ptr &address) { + std::shared_ptr account; + for (auto item : CoreModel::getInstance()->getCore()->getAccountList()) { + if (item->getContactAddress()->weakEqual(address)) { + account = item; + break; + } + } + return account; +} + +bool ToolModel::isMe(const QString &address) { + bool isMe = false; + auto linAddr = ToolModel::interpretUrl(address); + if (!CoreModel::getInstance()->getCore()->getDefaultAccount()) { + // for (auto &account : CoreModel::getInstance()->getCore()->getAccountList()) { + // if (account->getContactAddress()->weakEqual(linAddr)) return true; + // } + isMe = false; + } else { + auto accountAddr = CoreModel::getInstance()->getCore()->getDefaultAccount()->getContactAddress(); + isMe = linAddr && accountAddr ? accountAddr->weakEqual(linAddr) : false; + } + return isMe; +} + +bool ToolModel::isMe(const std::shared_ptr &address) { + auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount(); + if (!currentAccount) { // Default account is selected : Me is all local accounts. + return findAccount(address) != nullptr; + } else return address ? currentAccount->getContactAddress()->weakEqual(address) : false; +} +bool ToolModel::isLocal(const std::shared_ptr &conference, + const std::shared_ptr &device) { + auto deviceAddress = device->getAddress(); + auto callAddress = conference->getMe()->getAddress(); + auto gruuAddress = findAccount(callAddress)->getContactAddress(); + return deviceAddress->equal(gruuAddress); +} diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index c52778ba7..b159d5f9a 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -37,6 +37,11 @@ public: static std::shared_ptr interpretUrl(const QString &address); static std::shared_ptr makeLinphoneNumber(const QString &label, const QString &number); static std::shared_ptr findAudioDevice(const QString &id); + static std::shared_ptr findAccount(const std::shared_ptr &address); + static bool isMe(const QString &address); + static bool isMe(const std::shared_ptr &address); + static bool isLocal(const std::shared_ptr &conference, + const std::shared_ptr &device); static QString getDisplayName(const std::shared_ptr &address); static QString getDisplayName(QString address); diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 62a52b017..a2eecc146 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -1195,18 +1195,7 @@ int Utils::getYear(const QDate &date) { bool Utils::isMe(const QString &address) { bool isMe = false; - App::postModelSync([&isMe, address]() { - auto linAddr = ToolModel::interpretUrl(address); - if (!CoreModel::getInstance()->getCore()->getDefaultAccount()) { - // for (auto &account : CoreModel::getInstance()->getCore()->getAccountList()) { - // if (account->getContactAddress()->weakEqual(linAddr)) return true; - // } - isMe = false; - } else { - auto accountAddr = CoreModel::getInstance()->getCore()->getDefaultAccount()->getContactAddress(); - isMe = linAddr && accountAddr ? accountAddr->weakEqual(linAddr) : false; - } - }); + App::postModelSync([&isMe, address]() { isMe = ToolModel::isMe(address); }); return isMe; } // QDateTime dateTime(QDateTime::fromString(date, "yyyy-MM-dd hh:mm:ss")); diff --git a/Linphone/tool/thread/SafeConnection.hpp b/Linphone/tool/thread/SafeConnection.hpp index 710f2ae03..8f18eda7d 100644 --- a/Linphone/tool/thread/SafeConnection.hpp +++ b/Linphone/tool/thread/SafeConnection.hpp @@ -97,6 +97,14 @@ public: return connect(mCoreObject, signal, mModelObject, slot, Qt::DirectConnection); } + inline void disconnect() { + if (!tryLock()) // To avoid disconnections while being in call. + return; // Return to avoid to disconnect other connections than the pair. + QObject::disconnect(mModelObject, nullptr, mCoreObject, nullptr); + QObject::disconnect(mCoreObject, nullptr, mModelObject, nullptr); + unlock(); + } + template void invokeToModel(Func &&callable, Args &&...args) { if (!tryLock()) return; diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index 2a3101530..9bd0c81fa 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -28,11 +28,12 @@ Window { if (call && !conferenceInfo) middleItemStackView.replace(inCallItem) } Component.onCompleted: if (call && !conferenceInfo) middleItemStackView.replace(inCallItem) + property var callObj function joinConference(withVideo) { if (!conferenceInfo || conferenceInfo.core.uri.length === 0) UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La conférence n'a pas pu démarrer en raison d'une erreur d'uri.")) else { - var callObj = UtilsCpp.createCall(conferenceInfo.core.uri, withVideo) + callObj = UtilsCpp.createCall(conferenceInfo.core.uri, withVideo) } } @@ -358,6 +359,7 @@ Window { Layout.fillWidth: true Layout.fillHeight: true Layout.margins: 20 * DefaultStyle.dp + } OngoingCallRightPanel { id: rightPanel @@ -606,6 +608,10 @@ Window { onJoinConfRequested: mainWindow.joinConference(cameraEnabled) } } + + + + Component { id: inCallItem Control.Control { @@ -614,163 +620,18 @@ Window { // implicitHeight: parent.height Layout.fillHeight: true Layout.leftMargin: 10 * DefaultStyle.dp + Layout.rightMargin: 10 * DefaultStyle.dp Layout.alignment: Qt.AlignCenter + /* background: Rectangle { anchors.fill: parent color: DefaultStyle.grey_600 radius: 15 * DefaultStyle.dp - } - contentItem: Item { - id: centerItem - anchors.fill: parent - Text { - id: callTerminatedText - Connections { - target: mainWindow - onCallStateChanged: { - if (mainWindow.callState === LinphoneEnums.CallState.End) { - callTerminatedText.visible = true - } - } - } - visible: false - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 25 * DefaultStyle.dp - text: mainWindow.callTerminatedByUser ? qsTr("Vous avez terminé l'appel") : qsTr("Votre correspondant a terminé l'appel") - color: DefaultStyle.grey_0 - z: 1 - font { - pixelSize: 22 * DefaultStyle.dp - weight: 300 * DefaultStyle.dp - } - } - StackLayout { - id: centerLayout - currentIndex: 0 - anchors.fill: parent - Connections { - target: mainWindow - onCallStateChanged: { - if (mainWindow.callState === LinphoneEnums.CallState.Error) { - centerLayout.currentIndex = 1 - } - } - } - Sticker { - call: mainWindow.call - Layout.fillWidth: true - Layout.fillHeight: true - // visible: mainWindow.callState != LinphoneEnums.CallState.End - Connections { - target: mainWindow - onCallChanged: { - waitingTime.seconds = 0 - waitingTimer.restart() - console.log("call changed", call, waitingTime.seconds) - } - } - Timer { - id: waitingTimer - interval: 1000 - repeat: true - onTriggered: waitingTime.seconds += 1 - } - ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 30 * DefaultStyle.dp - visible: mainWindow.callState == LinphoneEnums.CallState.OutgoingInit - || mainWindow.callState == LinphoneEnums.CallState.OutgoingProgress - || mainWindow.callState == LinphoneEnums.CallState.OutgoingRinging - || mainWindow.callState == LinphoneEnums.CallState.OutgoingEarlyMedia - || mainWindow.callState == LinphoneEnums.CallState.IncomingReceived - BusyIndicator { - indicatorColor: DefaultStyle.main2_100 - Layout.alignment: Qt.AlignHCenter - } - Text { - id: waitingTime - property int seconds - text: UtilsCpp.formatElapsedTime(seconds) - color: DefaultStyle.grey_0 - Layout.alignment: Qt.AlignHCenter - horizontalAlignment: Text.AlignHCenter - font { - pixelSize: 30 * DefaultStyle.dp - weight: 300 * DefaultStyle.dp - } - Component.onCompleted: { - waitingTimer.restart() - } - } - } - } - ColumnLayout { - id: errorLayout - Layout.preferredWidth: parent.width - Layout.preferredHeight: parent.height - Layout.alignment: Qt.AlignCenter - Text { - text: qsTr(mainWindow.call.core.lastErrorMessage) - Layout.alignment: Qt.AlignCenter - color: DefaultStyle.grey_0 - font.pixelSize: 40 * DefaultStyle.dp - } - } - } - Sticker { - id: preview - visible: mainWindow.callState != LinphoneEnums.CallState.End - && mainWindow.callState != LinphoneEnums.CallState.Released - height: 180 * DefaultStyle.dp - width: 300 * DefaultStyle.dp - anchors.right: centerItem.right - anchors.bottom: centerItem.bottom - anchors.rightMargin: 10 * DefaultStyle.dp - anchors.bottomMargin: 10 * DefaultStyle.dp - AccountProxy{ - id: accounts - } - account: accounts.defaultAccount - enablePersonalCamera: mainWindow.call.core.cameraEnabled - - MovableMouseArea { - id: previewMouseArea - anchors.fill: parent - // visible: mainWindow.participantCount <= 2 - movableArea: centerItem - margin: 10 * DefaultStyle.dp - function resetPosition(){ - preview.anchors.right = centerItem.right - preview.anchors.bottom = centerItem.bottom - preview.anchors.rightMargin = previewMouseArea.margin - preview.anchors.bottomMargin = previewMouseArea.margin - } - onVisibleChanged: if(!visible){ - resetPosition() - } - drag.target: preview - onDraggingChanged: if(dragging) { - preview.anchors.right = undefined - preview.anchors.bottom = undefined - } - onRequestResetPosition: resetPosition() - } - } - property int previousWidth - Component.onCompleted: { - previousWidth = width - } - onWidthChanged: { - if (width < previousWidth) { - previewMouseArea.updatePosition(0, 0) - } else { - previewMouseArea.updatePosition(width - previousWidth, 0) - } - previousWidth = width - } + }*/ + contentItem: CallLayout{ + call: mainWindow.call + callTerminatedByUser: mainWindow.callTerminatedByUser } } } diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index b825ead09..b81b1749f 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -210,13 +210,14 @@ Item { background: Item{} Layout.preferredWidth: 24 * DefaultStyle.dp Layout.preferredHeight: 24 * DefaultStyle.dp + property var callObj contentItem: Image { width: 24 * DefaultStyle.dp height: 24 * DefaultStyle.dp source: AppIcons.phone } onClicked: { - var callObj = UtilsCpp.createCall(sipAddr) + callObj = UtilsCpp.createCall(sipAddr.text) } } } diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index e09b67165..5c389b28b 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -5,6 +5,10 @@ list(APPEND _LINPHONEAPP_QML_FILES view/App/Layout/LoginLayout.qml view/App/Layout/MainLayout.qml + view/Layout/Call/ActiveSpeakerLayout.qml + view/Layout/Call/CallLayout.qml + #view/Layout/Call/GridLayout.qml + view/Layout/Conference/IncallGrid.qml view/Layout/Contact/ContactLayout.qml view/Layout/Meeting/AddParticipantsLayout.qml @@ -86,6 +90,8 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Page/Main/ContactPage.qml view/Page/Main/MeetingPage.qml + + view/Tool/utils.js # Prototypes view/Prototype/PhoneNumberPrototype.qml diff --git a/Linphone/view/Item/Call/WaitingRoom.qml b/Linphone/view/Item/Call/WaitingRoom.qml index d0082a59a..3255208c2 100644 --- a/Linphone/view/Item/Call/WaitingRoom.qml +++ b/Linphone/view/Item/Call/WaitingRoom.qml @@ -28,7 +28,7 @@ RowLayout { id: accounts } account: accounts.defaultAccount - enablePersonalCamera: mainItem.cameraEnabled + previewEnabled: mainItem.cameraEnabled } RowLayout { Layout.alignment: Qt.AlignHCenter @@ -146,4 +146,4 @@ RowLayout { } } } -} \ No newline at end of file +} diff --git a/Linphone/view/Item/Contact/Sticker.qml b/Linphone/view/Item/Contact/Sticker.qml index 2c3276748..8065d3bcc 100644 --- a/Linphone/view/Item/Contact/Sticker.qml +++ b/Linphone/view/Item/Contact/Sticker.qml @@ -16,13 +16,19 @@ Item { width: 200 property CallGui call: null property AccountGui account: null - property bool enablePersonalCamera: false - onEnablePersonalCameraChanged: console.log ("enable camera", enablePersonalCamera) + property ParticipantDeviceGui participantDevice: null + property bool previewEnabled: false property color color: DefaultStyle.grey_600 property int radius: 15 * DefaultStyle.dp - property var peerAddressObj: call ? UtilsCpp.getDisplayName(call.core.peerAddress) : null - property string peerAddress: peerAddressObj ? peerAddressObj.value : "" + property var peerAddressObj: participantDevice && participantDevice.core + ? UtilsCpp.getDisplayName(participantDevice.core.address) + : call && call.core + ? UtilsCpp.getDisplayName(call.core.peerAddress) + : null + property string peerAddress:peerAddressObj ? peerAddressObj.value : "" property var identityAddress: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : null + property bool cameraEnabled: previewEnabled + property string qmlName Rectangle { id: background @@ -70,10 +76,7 @@ Item { interval: 1 onTriggered: {cameraLoader.active=false; cameraLoader.active=true;} } - active: mainItem.visible && call - ? call.core.remoteVideoEnabled && (mainItem.call.core.state != LinphoneEnums.CallState.End - && mainItem.call.core.state != LinphoneEnums.CallState.Released) - : mainItem.enablePersonalCamera + active: mainItem.visible && mainItem.cameraEnabled onActiveChanged: console.log("camera active", active) sourceComponent: cameraComponent } @@ -88,7 +91,9 @@ Item { anchors.fill: parent visible: isReady call: mainItem.call - + participantDevice: mainItem.participantDevice + isPreview: mainItem.previewEnabled + qmlName: mainItem.qmlName onRequestNewRenderer: { console.log("Request new renderer") resetTimer.restart() @@ -103,7 +108,7 @@ Item { anchors.leftMargin: 10 * DefaultStyle.dp anchors.bottomMargin: 10 * DefaultStyle.dp width: implicitWidth - text: mainItem.peerAddress.length != 0 + text: mainItem.peerAddress != '' ? mainItem.peerAddress : mainItem.account && mainItem.identityAddress ? mainItem.identityAddress.value diff --git a/Linphone/view/Layout/Call/ActiveSpeakerLayout.qml b/Linphone/view/Layout/Call/ActiveSpeakerLayout.qml new file mode 100644 index 000000000..37e239dc0 --- /dev/null +++ b/Linphone/view/Layout/Call/ActiveSpeakerLayout.qml @@ -0,0 +1,365 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Effects +import QtQml.Models +import QtQuick.Controls as Control +import Linphone +import EnumsToStringCpp 1.0 +import UtilsCpp 1.0 +import SettingsCpp 1.0 +// ============================================================================= + +Item{ + id: mainItem + property alias call: allDevices.currentCall + property ConferenceGui conference: call && call.core.conference || null + property var callState: call && call.core.state || undefined + + property ParticipantDeviceProxy participantDevices : ParticipantDeviceProxy { + id: allDevices + } + onCallChanged: { + waitingTime.seconds = 0 + waitingTimer.restart() + console.log("call changed", call, waitingTime.seconds) + } + RowLayout{ + anchors.fill: parent + spacing: 16 * DefaultStyle.dp + + Sticker { + id: activeSpeakerSticker + //call: mainItem.call + Layout.fillWidth: true + Layout.fillHeight: true + call: mainItem.call + participantDevice: mainItem.conference && mainItem.conference.core.activeSpeaker + property var address: participantDevice && participantDevice.core.address + onAddressChanged: console.log(address) + cameraEnabled: true + qmlName: 'AS' + + Timer { + id: waitingTimer + interval: 1000 + repeat: true + onTriggered: waitingTime.seconds += 1 + } + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 30 * DefaultStyle.dp + visible: mainItem.callState === LinphoneEnums.CallState.OutgoingInit + || mainItem.callState === LinphoneEnums.CallState.OutgoingProgress + || mainItem.callState === LinphoneEnums.CallState.OutgoingRinging + || mainItem.callState === LinphoneEnums.CallState.OutgoingEarlyMedia + || mainItem.callState === LinphoneEnums.CallState.IncomingReceived + BusyIndicator { + indicatorColor: DefaultStyle.main2_100 + Layout.alignment: Qt.AlignHCenter + } + Text { + id: waitingTime + property int seconds + text: UtilsCpp.formatElapsedTime(seconds) + color: DefaultStyle.grey_0 + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + font { + pixelSize: 30 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + Component.onCompleted: { + waitingTimer.restart() + } + } + } + } + ListView{ + Layout.fillHeight: true + Layout.preferredWidth: 300 * DefaultStyle.dp + Layout.rightMargin: 10 * DefaultStyle.dp + Layout.bottomMargin: 10 * DefaultStyle.dp + visible: allDevices.count > 2 + spacing: 15 * DefaultStyle.dp + model: allDevices + delegate: + Sticker { + visible: mainItem.callState != LinphoneEnums.CallState.End && mainItem.callState != LinphoneEnums.CallState.Released + && modelData.core.address != activeSpeakerSticker.address + onVisibleChanged: console.log(modelData.core.address) + height: visible ? 180 * DefaultStyle.dp : 0 + width: 300 * DefaultStyle.dp + qmlName: 'M_'+index + + participantDevice: modelData + cameraEnabled: visible + Component.onCompleted: console.log(modelData.core.address) + //previewEnabled: mainItem.call.core.cameraEnabled + } + } + } + Sticker { + id: preview + visible: allDevices.count <= 2 + height: 180 * DefaultStyle.dp + width: 300 * DefaultStyle.dp + anchors.right: mainItem.right + anchors.bottom: mainItem.bottom + anchors.rightMargin: 10 * DefaultStyle.dp + anchors.bottomMargin: 10 * DefaultStyle.dp + //participantDevice: allDevices.me + cameraEnabled: allDevices.count <= 2 + previewEnabled: true + qmlName: 'P' + + MovableMouseArea { + id: previewMouseArea + anchors.fill: parent + movableArea: mainItem + margin: 10 * DefaultStyle.dp + function resetPosition(){ + preview.anchors.right = mainItem.right + preview.anchors.bottom = mainItem.bottom + preview.anchors.rightMargin = previewMouseArea.margin + preview.anchors.bottomMargin = previewMouseArea.margin + } + onVisibleChanged: if(!visible){ + resetPosition() + } + drag.target: preview + onDraggingChanged: if(dragging) { + preview.anchors.right = undefined + preview.anchors.bottom = undefined + } + onRequestResetPosition: resetPosition() + } + } +} + /* + Sticker { + id: preview + visible: mainItem.callState != LinphoneEnums.CallState.End + && mainItem.callState != LinphoneEnums.CallState.Released + height: 180 * DefaultStyle.dp + width: 300 * DefaultStyle.dp + anchors.right: mainItem.right + anchors.bottom: mainItem.bottom + anchors.rightMargin: 10 * DefaultStyle.dp + anchors.bottomMargin: 10 * DefaultStyle.dp + AccountProxy{ + id: accounts + } + account: accounts.defaultAccount + previewEnabled: mainItem.call.core.cameraEnabled + + MovableMouseArea { + id: previewMouseArea + anchors.fill: parent + // visible: mainItem.participantCount <= 2 + movableArea: mainItem + margin: 10 * DefaultStyle.dp + function resetPosition(){ + preview.anchors.right = mainItem.right + preview.anchors.bottom = mainItem.bottom + preview.anchors.rightMargin = previewMouseArea.margin + preview.anchors.bottomMargin = previewMouseArea.margin + } + onVisibleChanged: if(!visible){ + resetPosition() + } + drag.target: preview + onDraggingChanged: if(dragging) { + preview.anchors.right = undefined + preview.anchors.bottom = undefined + } + onRequestResetPosition: resetPosition() + } + } + + property int previousWidth + Component.onCompleted: { + previousWidth = width + } + onWidthChanged: { + if (width < previousWidth) { + previewMouseArea.updatePosition(0, 0) + } else { + previewMouseArea.updatePosition(width - previousWidth, 0) + } + previousWidth = width + }*/ + +/* + +Item { + id: mainItem + property CallModel callModel + property bool isRightReducedLayout: false + property bool isLeftReducedLayout: false + property bool cameraEnabled: true + property bool isConference: callModel && callModel.isConference + property bool isConferenceReady: isConference && callModel.conferenceModel && callModel.conferenceModel.isReady + + property int participantCount: isConference ? allDevices.count + 1 : 2 // +me. allDevices==0 if !conference + + property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel { + id: allDevices + callModel: mainItem.callModel + showMe: false + + onConferenceCreated: cameraView.resetCamera() + } + + Sticker{ + id: cameraView + anchors.fill: parent + anchors.leftMargin: isRightReducedLayout || isLeftReducedLayout? 30 : 140 + anchors.rightMargin: isRightReducedLayout ? 10 : 140 + cameraQmlName: 'AS' + callModel: mainItem.callModel + currentDevice: isPreview + ? allDevices.me + : mainItem.isConference + ? allDevices.activeSpeaker + : null + deactivateCamera: !mainItem.cameraEnabled || (isPreview && callModel.pausedByUser) + ? true + : mainItem.isConference + ? (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused) ) + || (!(callModel && callModel.cameraEnabled) && mainItem.participantCount == 1) + || (currentDevice && !currentDevice.videoEnabled)// && mainItem.participantCount == 2) + || !mainItem.isConferenceReady + : (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused || !callModel.videoEnabled) ) + || currentDevice && !currentDevice.videoEnabled + isPreview: !preview.visible && mainItem.participantCount == 1 + onIsPreviewChanged: {cameraView.resetCamera() } + isCameraFromDevice: isPreview + isPaused: isPreview && callModel.pausedByUser + ? false + : mainItem.isConference + ? //callModel && callModel.pausedByUser && mainItem.participantCount != 2 || + (currentDevice && currentDevice.isPaused) + : callModel && !callModel.pausedByUser && (callModel.status === CallModel.CallStatusPaused) + + quickTransition: true + showCloseButton: false + showActiveSpeakerOverlay: false // This is an active speaker. We don't need to show the indicator. + showCustomButton: false + avatarStickerBackgroundColor: isPreview ? IncallStyle.container.avatar.stickerPreviewBackgroundColor.color : IncallStyle.container.avatar.stickerBackgroundColor.color + avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color + } + Item{// Need an item to not override Sticker internal states. States are needed for changing anchors. + id: preview + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: 30 + anchors.bottomMargin: 15 + + height: visible ? miniViews.cellHeight : 0 + width: 16 * height / 9 + + visible: mainItem.isConferenceReady && allDevices.count >= 1 + || (!mainItem.isConference && mainItem.callModel && mainItem.callModel.cameraEnabled)// use videoEnabled if we want to show the preview sticker + + Loader{ + anchors.fill: parent + anchors.margins: 3 + sourceComponent: + Sticker{ + id: previewSticker + cameraQmlName: 'AS_Preview' + deactivateCamera: !mainItem.cameraEnabled || !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled + currentDevice: allDevices.me + isPreview: true + callModel: mainItem.callModel + isCameraFromDevice: true + showCloseButton: false + showCustomButton: false + showAvatarBorder: true + avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerPreviewBackgroundColor.color + avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color + } + active: parent.visible + } + + MovableMouseArea{ + id: dragger + anchors.fill: parent + visible: mainItem.participantCount <= 2 + function resetPosition(){ + preview.anchors.right = mainItem.right + preview.anchors.bottom = mainItem.bottom + } + onVisibleChanged: if(!visible){ + resetPosition() + } + drag.target: preview + onDraggingChanged: if(dragging){ + preview.anchors.right = undefined + preview.anchors.bottom = undefined + } + onRequestResetPosition: resetPosition() + } + } + + Item{ + id: miniViewArea + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: preview.top + anchors.rightMargin: 30 + anchors.topMargin: 15 + anchors.bottomMargin: 0 +//--------------- + width: 16 * miniViews.cellHeight / 9 + visible: mainItem.isConferenceReady || !mainItem.isConference + property int heightLeft: parent.height - preview.height + onHeightLeftChanged: {Qt.callLater(miniViewArea.forceRefresh)} + function forceRefresh(){// Force a content refresh via margins. Qt is buggy when managing sizes in ListView. + ++miniViewArea.anchors.topMargin + --miniViewArea.anchors.topMargin + } + + ScrollableListView{ + id: miniViews + property int cellHeight: 150 + anchors.fill: parent + model : mainItem.isConference && mainItem.participantDevices.count > 1 ? mainItem.participantDevices : [] + spacing: 0 + verticalLayoutDirection: ListView.BottomToTop + fitCacheToContent: false + property int oldCount : 0// Count changed can be called without a change... (bug?). Use oldCount to avoid it. + onCountChanged: {if(oldCount != count){ oldCount = count ; Qt.callLater(miniViewArea.forceRefresh)}} + Component.onCompleted: {Qt.callLater(miniViewArea.forceRefresh)} + delegate:Item{ + height: visible ? miniViews.cellHeight + 15 : 0 + width: visible ? miniViews.width : 0 + visible: cameraView.currentDevice != modelData + clip:false + Sticker{ + id: miniView + anchors.fill: parent + anchors.topMargin: 3 + anchors.leftMargin: 3 + anchors.rightMargin: 3 + anchors.bottomMargin: 18 + cameraQmlName: 'S_'+index + deactivateCamera: (!mainItem.isConferenceReady || !mainItem.isConference) + && (index <0 || !mainItem.cameraEnabled || (!modelData.videoEnabled) || (callModel && callModel.pausedByUser) ) + currentDevice: modelData.isPreview ? null : modelData + callModel: modelData.isPreview ? null : mainItem.callModel + isCameraFromDevice: mainItem.isConference + isPaused: currentDevice && currentDevice.isPaused + showCloseButton: false + showCustomButton: false + showAvatarBorder: true + avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerBackgroundColor.color + avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color + } + } + } + } +} +*/ + diff --git a/Linphone/view/Layout/Call/CallLayout.qml b/Linphone/view/Layout/Call/CallLayout.qml new file mode 100644 index 000000000..dde2ecb01 --- /dev/null +++ b/Linphone/view/Layout/Call/CallLayout.qml @@ -0,0 +1,293 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Effects +import QtQml.Models +import QtQuick.Controls as Control +import Linphone +import EnumsToStringCpp 1.0 +import UtilsCpp 1.0 +import SettingsCpp 1.0 +// ============================================================================= + +Item { + id: mainItem + anchors.fill: parent + + property CallGui call + property bool callTerminatedByUser: false + readonly property var callState: call && call.core.state || undefined + onCallStateChanged: if (callState === LinphoneEnums.CallState.End) { + callTerminatedText.visible = true + }else if( callState === LinphoneEnums.CallState.Error) { + centerLayout.currentIndex = 1 + } + + Text { + id: callTerminatedText + visible: false + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 25 * DefaultStyle.dp + text: mainItem.callTerminatedByUser ? qsTr("Vous avez terminé l'appel") : qsTr("Votre correspondant a terminé l'appel") + color: DefaultStyle.grey_0 + z: 1 + font { + pixelSize: 22 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + StackLayout { + id: centerLayout + currentIndex: 0 + anchors.fill: parent + Loader{ + id: callLayout + Layout.fillWidth: true + Layout.fillHeight: true + sourceComponent:ActiveSpeakerLayout{ + Layout.fillWidth: true + Layout.fillHeight: true + call: mainItem.call + } + } + ColumnLayout { + id: userNotFoundLayout + Layout.preferredWidth: parent.width + Layout.preferredHeight: parent.height + Layout.alignment: Qt.AlignCenter + Text { + text: qsTr(mainItem.call.core.lastErrorMessage) + Layout.alignment: Qt.AlignCenter + color: DefaultStyle.grey_0 + font.pixelSize: 40 * DefaultStyle.dp + } + } + } +} + /* + Sticker { + id: preview + visible: mainItem.callState != LinphoneEnums.CallState.End + && mainItem.callState != LinphoneEnums.CallState.Released + height: 180 * DefaultStyle.dp + width: 300 * DefaultStyle.dp + anchors.right: mainItem.right + anchors.bottom: mainItem.bottom + anchors.rightMargin: 10 * DefaultStyle.dp + anchors.bottomMargin: 10 * DefaultStyle.dp + AccountProxy{ + id: accounts + } + account: accounts.defaultAccount + previewEnabled: mainItem.call.core.cameraEnabled + + MovableMouseArea { + id: previewMouseArea + anchors.fill: parent + // visible: mainItem.participantCount <= 2 + movableArea: mainItem + margin: 10 * DefaultStyle.dp + function resetPosition(){ + preview.anchors.right = mainItem.right + preview.anchors.bottom = mainItem.bottom + preview.anchors.rightMargin = previewMouseArea.margin + preview.anchors.bottomMargin = previewMouseArea.margin + } + onVisibleChanged: if(!visible){ + resetPosition() + } + drag.target: preview + onDraggingChanged: if(dragging) { + preview.anchors.right = undefined + preview.anchors.bottom = undefined + } + onRequestResetPosition: resetPosition() + } + } + + property int previousWidth + Component.onCompleted: { + previousWidth = width + } + onWidthChanged: { + if (width < previousWidth) { + previewMouseArea.updatePosition(0, 0) + } else { + previewMouseArea.updatePosition(width - previousWidth, 0) + } + previousWidth = width + }*/ + +/* + +Item { + id: mainItem + property CallModel callModel + property bool isRightReducedLayout: false + property bool isLeftReducedLayout: false + property bool cameraEnabled: true + property bool isConference: callModel && callModel.isConference + property bool isConferenceReady: isConference && callModel.conferenceModel && callModel.conferenceModel.isReady + + property int participantCount: isConference ? allDevices.count + 1 : 2 // +me. allDevices==0 if !conference + + property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel { + id: allDevices + callModel: mainItem.callModel + showMe: false + + onConferenceCreated: cameraView.resetCamera() + } + + Sticker{ + id: cameraView + anchors.fill: parent + anchors.leftMargin: isRightReducedLayout || isLeftReducedLayout? 30 : 140 + anchors.rightMargin: isRightReducedLayout ? 10 : 140 + cameraQmlName: 'AS' + callModel: mainItem.callModel + currentDevice: isPreview + ? allDevices.me + : mainItem.isConference + ? allDevices.activeSpeaker + : null + deactivateCamera: !mainItem.cameraEnabled || (isPreview && callModel.pausedByUser) + ? true + : mainItem.isConference + ? (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused) ) + || (!(callModel && callModel.cameraEnabled) && mainItem.participantCount == 1) + || (currentDevice && !currentDevice.videoEnabled)// && mainItem.participantCount == 2) + || !mainItem.isConferenceReady + : (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused || !callModel.videoEnabled) ) + || currentDevice && !currentDevice.videoEnabled + isPreview: !preview.visible && mainItem.participantCount == 1 + onIsPreviewChanged: {cameraView.resetCamera() } + isCameraFromDevice: isPreview + isPaused: isPreview && callModel.pausedByUser + ? false + : mainItem.isConference + ? //callModel && callModel.pausedByUser && mainItem.participantCount != 2 || + (currentDevice && currentDevice.isPaused) + : callModel && !callModel.pausedByUser && (callModel.status === CallModel.CallStatusPaused) + + quickTransition: true + showCloseButton: false + showActiveSpeakerOverlay: false // This is an active speaker. We don't need to show the indicator. + showCustomButton: false + avatarStickerBackgroundColor: isPreview ? IncallStyle.container.avatar.stickerPreviewBackgroundColor.color : IncallStyle.container.avatar.stickerBackgroundColor.color + avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color + } + Item{// Need an item to not override Sticker internal states. States are needed for changing anchors. + id: preview + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: 30 + anchors.bottomMargin: 15 + + height: visible ? miniViews.cellHeight : 0 + width: 16 * height / 9 + + visible: mainItem.isConferenceReady && allDevices.count >= 1 + || (!mainItem.isConference && mainItem.callModel && mainItem.callModel.cameraEnabled)// use videoEnabled if we want to show the preview sticker + + Loader{ + anchors.fill: parent + anchors.margins: 3 + sourceComponent: + Sticker{ + id: previewSticker + cameraQmlName: 'AS_Preview' + deactivateCamera: !mainItem.cameraEnabled || !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled + currentDevice: allDevices.me + isPreview: true + callModel: mainItem.callModel + isCameraFromDevice: true + showCloseButton: false + showCustomButton: false + showAvatarBorder: true + avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerPreviewBackgroundColor.color + avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color + } + active: parent.visible + } + + MovableMouseArea{ + id: dragger + anchors.fill: parent + visible: mainItem.participantCount <= 2 + function resetPosition(){ + preview.anchors.right = mainItem.right + preview.anchors.bottom = mainItem.bottom + } + onVisibleChanged: if(!visible){ + resetPosition() + } + drag.target: preview + onDraggingChanged: if(dragging){ + preview.anchors.right = undefined + preview.anchors.bottom = undefined + } + onRequestResetPosition: resetPosition() + } + } + + Item{ + id: miniViewArea + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: preview.top + anchors.rightMargin: 30 + anchors.topMargin: 15 + anchors.bottomMargin: 0 +//--------------- + width: 16 * miniViews.cellHeight / 9 + visible: mainItem.isConferenceReady || !mainItem.isConference + property int heightLeft: parent.height - preview.height + onHeightLeftChanged: {Qt.callLater(miniViewArea.forceRefresh)} + function forceRefresh(){// Force a content refresh via margins. Qt is buggy when managing sizes in ListView. + ++miniViewArea.anchors.topMargin + --miniViewArea.anchors.topMargin + } + + ScrollableListView{ + id: miniViews + property int cellHeight: 150 + anchors.fill: parent + model : mainItem.isConference && mainItem.participantDevices.count > 1 ? mainItem.participantDevices : [] + spacing: 0 + verticalLayoutDirection: ListView.BottomToTop + fitCacheToContent: false + property int oldCount : 0// Count changed can be called without a change... (bug?). Use oldCount to avoid it. + onCountChanged: {if(oldCount != count){ oldCount = count ; Qt.callLater(miniViewArea.forceRefresh)}} + Component.onCompleted: {Qt.callLater(miniViewArea.forceRefresh)} + delegate:Item{ + height: visible ? miniViews.cellHeight + 15 : 0 + width: visible ? miniViews.width : 0 + visible: cameraView.currentDevice != modelData + clip:false + Sticker{ + id: miniView + anchors.fill: parent + anchors.topMargin: 3 + anchors.leftMargin: 3 + anchors.rightMargin: 3 + anchors.bottomMargin: 18 + cameraQmlName: 'S_'+index + deactivateCamera: (!mainItem.isConferenceReady || !mainItem.isConference) + && (index <0 || !mainItem.cameraEnabled || (!modelData.videoEnabled) || (callModel && callModel.pausedByUser) ) + currentDevice: modelData.isPreview ? null : modelData + callModel: modelData.isPreview ? null : mainItem.callModel + isCameraFromDevice: mainItem.isConference + isPaused: currentDevice && currentDevice.isPaused + showCloseButton: false + showCustomButton: false + showAvatarBorder: true + avatarStickerBackgroundColor: IncallStyle.container.avatar.stickerBackgroundColor.color + avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color + } + } + } + } +} +*/ + diff --git a/Linphone/view/Layout/Call/GridLayout.qml b/Linphone/view/Layout/Call/GridLayout.qml new file mode 100644 index 000000000..3b2cb19bc --- /dev/null +++ b/Linphone/view/Layout/Call/GridLayout.qml @@ -0,0 +1,67 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQml.Models 2.12 +import QtGraphicalEffects 1.12 + +import Common 1.0 +import Common.Styles 1.0 +import Linphone 1.0 +import LinphoneEnums 1.0 + +import UtilsCpp 1.0 + +import App.Styles 1.0 + +import ConstantsCpp 1.0 +// Temp +import 'Incall.js' as Logic +import 'qrc:/ui/scripts/Utils/utils.js' as Utils + +// ============================================================================= + +Mosaic { + id: grid + property alias callModel: participantDevices.callModel + property bool cameraEnabled: true + property int participantCount: gridModel.count + + // On grid view, we limit the quality if there are enough participants// The vga mode has been activated from the factory rc + //onParticipantCountChanged: participantCount > ConstantsCpp.maxMosaicParticipants ? SettingsModel.setLimitedMosaicQuality() : SettingsModel.setHighMosaicQuality() + delegateModel: DelegateModel{ + id: gridModel + property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel { + id: participantDevices + showMe: true + } + model: participantDevices + delegate: Item{ + id: avatarCell + property ParticipantDeviceModel currentDevice: gridModel.participantDevices.getAt(index) + onCurrentDeviceChanged: { + if(index < 0) cameraView.enabled = false // this is a delegate destruction. We need to stop camera before Qt change its currentDevice (and then, let CameraView to delete wrong renderer) + } + + height: grid.cellHeight - 10 + width: grid.cellWidth - 10 + + Sticker{ + id: cameraView + anchors.fill: parent + + cameraQmlName: 'G_'+index + callModel: index >= 0 ? participantDevices.callModel : null // do this before to prioritize changing call on remove + deactivateCamera: index <0 || !grid.cameraEnabled || grid.callModel.pausedByUser + currentDevice: gridModel.participantDevices.getAt(index) + + isCameraFromDevice: true + isPaused: !isPreview && avatarCell.currentDevice && avatarCell.currentDevice.isPaused + showCloseButton: false + showCustomButton: false + avatarStickerBackgroundColor: isPreview? IncallStyle.container.avatar.stickerPreviewBackgroundColor.color : IncallStyle.container.avatar.stickerBackgroundColor.color + avatarBackgroundColor: IncallStyle.container.avatar.backgroundColor.color + + //onCloseRequested: participantDevices.showMe = false + } + } + } +}