diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 457009735..25d1f8423 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -41,17 +41,23 @@ #include "core/call/CallProxy.hpp" #include "core/camera/CameraGui.hpp" #include "core/fps-counter/FPSCounter.hpp" +#include "core/conference/ConferenceInfoGui.hpp" +#include "core/conference/ConferenceInfoProxy.hpp" #include "core/friend/FriendCore.hpp" #include "core/friend/FriendGui.hpp" -#include "core/friend/FriendInitialProxy.hpp" #include "core/logger/QtLogger.hpp" #include "core/login/LoginPage.hpp" #include "core/notifier/Notifier.hpp" +#include "core/participant/ParticipantDeviceCore.hpp" +#include "core/participant/ParticipantGui.hpp" +#include "core/participant/ParticipantProxy.hpp" #include "core/phone-number/PhoneNumber.hpp" #include "core/phone-number/PhoneNumberProxy.hpp" #include "core/search/MagicSearchProxy.hpp" #include "core/setting/SettingsCore.hpp" #include "core/singleapplication/singleapplication.h" +#include "core/timezone/TimeZone.hpp" +#include "core/timezone/TimeZoneProxy.hpp" #include "core/variant/VariantList.hpp" #include "model/object/VariantObject.hpp" #include "tool/Constants.hpp" @@ -184,7 +190,14 @@ void App::initCppInterfaces() { qmlRegisterType(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "VariantObject"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "VariantList"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantProxy"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantGui"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ConferenceInfoProxy"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ConferenceInfoGui"); + + qmlRegisterType(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "PhoneNumber", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountGui"); @@ -192,15 +205,14 @@ void App::initCppInterfaces() { qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallHistoryProxy"); - qmlRegisterType(Constants::MainQmlUri, 1, 0, "VariantList"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "FriendGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "MagicSearchProxy"); - qmlRegisterType(Constants::MainQmlUri, 1, 0, "FriendInitialProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CameraGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "FPSCounter"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "TimeZoneProxy"); LinphoneEnums::registerMetaTypes(); } @@ -208,12 +220,13 @@ void App::initCppInterfaces() { void App::clean() { // Wait 500ms to let time for log te be stored. - delete mNotifier; - mNotifier = nullptr; + // mNotifier destroyed in mEngine deletion as it is its parent delete mEngine; mEngine = nullptr; - mSettings.reset(); - mSettings = nullptr; + if (mSettings) { + mSettings.reset(); + mSettings = nullptr; + } mLinphoneThread->wait(250); qApp->processEvents(QEventLoop::AllEvents, 250); mLinphoneThread->exit(); diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index a293625c7..dc6383c7b 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -17,7 +17,6 @@ list(APPEND _LINPHONEAPP_SOURCES core/fps-counter/FPSCounter.cpp core/friend/FriendCore.cpp core/friend/FriendGui.cpp - core/friend/FriendInitialProxy.cpp core/logger/QtLogger.cpp core/login/LoginPage.cpp core/notifier/Notifier.cpp @@ -36,6 +35,24 @@ list(APPEND _LINPHONEAPP_SOURCES core/proxy/SortFilterProxy.cpp core/variant/VariantList.cpp + + core/conference/ConferenceCore.cpp + core/conference/ConferenceInfoCore.cpp + core/conference/ConferenceInfoGui.cpp + core/conference/ConferenceInfoList.cpp + core/conference/ConferenceInfoProxy.cpp + + core/timezone/TimeZoneList.cpp + core/timezone/TimeZoneProxy.cpp + core/timezone/TimeZone.cpp + + core/participant/ParticipantCore.cpp + core/participant/ParticipantGui.cpp + core/participant/ParticipantDeviceCore.cpp + # core/participant/ParticipantDeviceList.cpp + # core/participant/ParticipantDeviceProxy.cpp + core/participant/ParticipantList.cpp + core/participant/ParticipantProxy.cpp ) ## Single Application diff --git a/Linphone/core/call-history/CallHistoryCore.cpp b/Linphone/core/call-history/CallHistoryCore.cpp index ba806ae65..b3f061fc5 100644 --- a/Linphone/core/call-history/CallHistoryCore.cpp +++ b/Linphone/core/call-history/CallHistoryCore.cpp @@ -42,6 +42,7 @@ CallHistoryCore::CallHistoryCore(const std::shared_ptr &callL App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); // Should be call from model Thread mustBeInLinphoneThread(getClassName()); + mCallHistoryModel = std::make_shared(callLog); auto addr = callLog->getRemoteAddress()->clone(); addr->clean(); @@ -49,7 +50,6 @@ CallHistoryCore::CallHistoryCore(const std::shared_ptr &callL // mRemoteAddress->clean(); mStatus = LinphoneEnums::fromLinphone(callLog->getStatus()); mDate = QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000); - mCallHistoryModel = std::make_shared(callLog); mIsOutgoing = callLog->getDir() == linphone::Call::Dir::Outgoing; mDuration = QString::number(callLog->getDuration()); } diff --git a/Linphone/core/call-history/CallHistoryList.cpp b/Linphone/core/call-history/CallHistoryList.cpp index f65645928..9ec9b6cbe 100644 --- a/Linphone/core/call-history/CallHistoryList.cpp +++ b/Linphone/core/call-history/CallHistoryList.cpp @@ -22,7 +22,6 @@ #include "CallHistoryGui.hpp" #include "core/App.hpp" #include "model/object/VariantObject.hpp" -#include "tool/Utils.hpp" #include #include @@ -81,7 +80,7 @@ void CallHistoryList::setSelf(QSharedPointer me) { [this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); }); mModelConnection->makeConnectToModel(&CoreModel::callLogUpdated, [this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); }); - lUpdate(); + emit lUpdate(); } void CallHistoryList::removeAllEntries() { diff --git a/Linphone/core/call-history/CallHistoryProxy.cpp b/Linphone/core/call-history/CallHistoryProxy.cpp index cb55de7ea..838309c92 100644 --- a/Linphone/core/call-history/CallHistoryProxy.cpp +++ b/Linphone/core/call-history/CallHistoryProxy.cpp @@ -21,7 +21,6 @@ #include "CallHistoryProxy.hpp" #include "CallHistoryGui.hpp" #include "CallHistoryList.hpp" -#include "tool/Utils.hpp" DEFINE_ABSTRACT_OBJECT(CallHistoryProxy) diff --git a/Linphone/core/conference/ConferenceCore.cpp b/Linphone/core/conference/ConferenceCore.cpp new file mode 100644 index 000000000..f1d3319c1 --- /dev/null +++ b/Linphone/core/conference/ConferenceCore.cpp @@ -0,0 +1,481 @@ +// /* +// * 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 "CallCore.hpp" +// #include "core/App.hpp" +// #include "model/tool/ToolModel.hpp" +// #include "tool/Utils.hpp" +// #include "tool/thread/SafeConnection.hpp" + +// DEFINE_ABSTRACT_OBJECT(CallCore) + +// QVariant createDeviceVariant(const QString &id, const QString &name) { +// QVariantMap map; +// map.insert("id", id); +// map.insert("name", name); +// return map; +// } + +// QSharedPointer CallCore::create(const std::shared_ptr &call) { +// auto sharedPointer = QSharedPointer(new CallCore(call), &QObject::deleteLater); +// sharedPointer->setSelf(sharedPointer); +// sharedPointer->moveToThread(App::getInstance()->thread()); +// return sharedPointer; +// } + +// CallCore::CallCore(const std::shared_ptr &call) : QObject(nullptr) { +// qDebug() << "[CallCore] new" << this; +// App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); +// // Should be call from model Thread +// mustBeInLinphoneThread(getClassName()); +// mDir = LinphoneEnums::fromLinphone(call->getDir()); +// mCallModel = Utils::makeQObject_ptr(call); +// mCallModel->setSelf(mCallModel); +// mDuration = call->getDuration(); +// mMicrophoneMuted = call->getMicrophoneMuted(); +// mSpeakerMuted = call->getSpeakerMuted(); +// mCameraEnabled = call->cameraEnabled(); +// mState = LinphoneEnums::fromLinphone(call->getState()); +// mPeerAddress = Utils::coreStringToAppString(call->getRemoteAddress()->asStringUriOnly()); +// mStatus = LinphoneEnums::fromLinphone(call->getCallLog()->getStatus()); +// mTransferState = LinphoneEnums::fromLinphone(call->getTransferState()); +// auto token = Utils::coreStringToAppString(mCallModel->getAuthenticationToken()); +// auto localToken = mDir == LinphoneEnums::CallDir::Incoming ? token.left(2).toUpper() : token.right(2).toUpper(); +// auto remoteToken = mDir == LinphoneEnums::CallDir::Outgoing ? token.left(2).toUpper() : token.right(2).toUpper(); +// mEncryption = LinphoneEnums::fromLinphone(call->getParams()->getMediaEncryption()); +// auto tokenVerified = call->getAuthenticationTokenVerified(); +// mLocalSas = localToken; +// mRemoteSas = remoteToken; +// mIsSecured = (mEncryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) || +// mEncryption == LinphoneEnums::MediaEncryption::Srtp || +// mEncryption == LinphoneEnums::MediaEncryption::Dtls; +// mPaused = mState == LinphoneEnums::CallState::Pausing || mState == LinphoneEnums::CallState::Paused || +// mState == LinphoneEnums::CallState::PausedByRemote; +// mRemoteVideoEnabled = call->getRemoteParams() && call->getRemoteParams()->videoEnabled(); +// mRecording = call->getParams() && call->getParams()->isRecording(); +// mRemoteRecording = call->getRemoteParams() && call->getRemoteParams()->isRecording(); +// mSpeakerVolumeGain = mCallModel->getSpeakerVolumeGain(); +// // TODO : change this with settings value when settings done +// if (mSpeakerVolumeGain < 0) { +// call->setSpeakerVolumeGain(0.5); +// mSpeakerVolumeGain = 0.5; +// } +// mMicrophoneVolumeGain = call->getMicrophoneVolumeGain(); +// // TODO : change this with settings value when settings done +// if (mMicrophoneVolumeGain < 0) { +// call->setMicrophoneVolumeGain(0.5); +// mMicrophoneVolumeGain = 0.5; +// } +// mMicrophoneVolume = call->getRecordVolume(); +// mRecordable = mState == LinphoneEnums::CallState::StreamsRunning; +// } + +// CallCore::~CallCore() { +// qDebug() << "[CallCore] delete" << this; +// mustBeInMainThread("~" + getClassName()); +// emit mCallModel->removeListener(); +// } + +// void CallCore::setSelf(QSharedPointer me) { +// mCallModelConnection = QSharedPointer>( +// new SafeConnection(me, mCallModel), &QObject::deleteLater); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetMicrophoneMuted, [this](bool isMuted) { +// mCallModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::microphoneMutedChanged, [this](bool isMuted) { +// mCallModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(isMuted); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::remoteVideoEnabledChanged, [this](bool enabled) { +// mCallModelConnection->invokeToCore([this, enabled]() { setRemoteVideoEnabled(enabled); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetSpeakerMuted, [this](bool isMuted) { +// mCallModelConnection->invokeToModel([this, isMuted]() { mCallModel->setSpeakerMuted(isMuted); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::speakerMutedChanged, [this](bool isMuted) { +// mCallModelConnection->invokeToCore([this, isMuted]() { setSpeakerMuted(isMuted); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetCameraEnabled, [this](bool enabled) { +// mCallModelConnection->invokeToModel([this, enabled]() { mCallModel->setCameraEnabled(enabled); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lStartRecording, [this]() { +// mCallModelConnection->invokeToModel([this]() { mCallModel->startRecording(); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lStopRecording, [this]() { +// mCallModelConnection->invokeToModel([this]() { mCallModel->stopRecording(); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::recordingChanged, [this](bool recording) { +// mCallModelConnection->invokeToCore([this, recording]() { setRecording(recording); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lVerifyAuthenticationToken, [this](bool verified) { +// mCallModelConnection->invokeToModel( +// [this, verified]() { mCallModel->setAuthenticationTokenVerified(verified); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::authenticationTokenVerifiedChanged, [this](bool verified) { +// mCallModelConnection->invokeToCore([this, verified]() { setIsSecured(verified); }); +// }); +// mCallModelConnection->makeConnectToModel( +// &CallModel::remoteRecording, [this](const std::shared_ptr &call, bool recording) { +// mCallModelConnection->invokeToCore([this, recording]() { setRemoteRecording(recording); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::cameraEnabledChanged, [this](bool enabled) { +// mCallModelConnection->invokeToCore([this, enabled]() { setCameraEnabled(enabled); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::durationChanged, [this](int duration) { +// mCallModelConnection->invokeToCore([this, duration]() { setDuration(duration); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::microphoneVolumeChanged, [this](float volume) { +// mCallModelConnection->invokeToCore([this, volume]() { setMicrophoneVolume(volume); }); +// }); +// mCallModelConnection->makeConnectToModel( +// &CallModel::stateChanged, [this](linphone::Call::State state, const std::string &message) { +// mCallModelConnection->invokeToCore([this, state, message]() { +// setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message)); +// }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::statusChanged, [this](linphone::Call::Status status) { +// mCallModelConnection->invokeToCore([this, status]() { setStatus(LinphoneEnums::fromLinphone(status)); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::stateChanged, +// [this](linphone::Call::State state, const std::string &message) { +// mCallModelConnection->invokeToCore([this, state]() { +// setRecordable(state == linphone::Call::State::StreamsRunning); +// }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetPaused, [this](bool paused) { +// mCallModelConnection->invokeToModel([this, paused]() { mCallModel->setPaused(paused); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::pausedChanged, [this](bool paused) { +// mCallModelConnection->invokeToCore([this, paused]() { setPaused(paused); }); +// }); + +// mCallModelConnection->makeConnectToCore(&CallCore::lTransferCall, [this](const QString &address) { +// mCallModelConnection->invokeToModel( +// [this, address]() { mCallModel->transferTo(ToolModel::interpretUrl(address)); }); +// }); +// mCallModelConnection->makeConnectToModel( +// &CallModel::transferStateChanged, +// [this](const std::shared_ptr &call, linphone::Call::State state) { +// mCallModelConnection->invokeToCore([this, state]() { +// QString message; +// if (state == linphone::Call::State::Error) { +// message = "L'appel n'a pas pu être transféré."; +// } +// setTransferState(LinphoneEnums::fromLinphone(state), message); +// }); +// }); +// mCallModelConnection->makeConnectToModel( +// &CallModel::encryptionChanged, +// [this](const std::shared_ptr &call, bool on, const std::string &authenticationToken) { +// auto encryption = LinphoneEnums::fromLinphone(call->getCurrentParams()->getMediaEncryption()); +// auto tokenVerified = mCallModel->getAuthenticationTokenVerified(); +// auto token = Utils::coreStringToAppString(mCallModel->getAuthenticationToken()); +// mCallModelConnection->invokeToCore([this, call, encryption, tokenVerified, token]() { +// if (token.size() == 4) { +// auto localToken = +// mDir == LinphoneEnums::CallDir::Incoming ? token.left(2).toUpper() : token.right(2).toUpper(); +// auto remoteToken = +// mDir == LinphoneEnums::CallDir::Outgoing ? token.left(2).toUpper() : token.right(2).toUpper(); +// setLocalSas(localToken); +// setRemoteSas(remoteToken); +// } +// setEncryption(encryption); +// setIsSecured((encryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) || +// encryption == LinphoneEnums::MediaEncryption::Srtp || +// encryption == LinphoneEnums::MediaEncryption::Dtls); +// }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetSpeakerVolumeGain, [this](float gain) { +// mCallModelConnection->invokeToModel([this, gain]() { mCallModel->setSpeakerVolumeGain(gain); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::speakerVolumeGainChanged, [this](float gain) { +// mCallModelConnection->invokeToCore([this, gain]() { setSpeakerVolumeGain(gain); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetMicrophoneVolumeGain, [this](float gain) { +// mCallModelConnection->invokeToModel([this, gain]() { mCallModel->setMicrophoneVolumeGain(gain); }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::microphoneVolumeGainChanged, [this](float gain) { +// mCallModelConnection->invokeToCore([this, gain]() { setMicrophoneVolumeGain(gain); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetInputAudioDevice, [this](const QString &id) { +// mCallModelConnection->invokeToModel([this, id]() { +// if (auto device = ToolModel::findAudioDevice(id)) { +// mCallModel->setInputAudioDevice(device); +// } +// }); +// }); +// mCallModelConnection->makeConnectToModel(&CallModel::inputAudioDeviceChanged, [this](const std::string &id) { +// mCallModelConnection->invokeToCore([this, id]() {}); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lSetOutputAudioDevice, [this](const QString &id) { +// mCallModelConnection->invokeToModel([this, id]() { +// if (auto device = ToolModel::findAudioDevice(id)) { +// mCallModel->setOutputAudioDevice(device); +// } +// }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lAccept, [this](bool withVideo) { +// mCallModelConnection->invokeToModel([this, withVideo]() { mCallModel->accept(withVideo); }); +// }); +// mCallModelConnection->makeConnectToCore( +// &CallCore::lDecline, [this]() { mCallModelConnection->invokeToModel([this]() { mCallModel->decline(); }); }); +// mCallModelConnection->makeConnectToCore(&CallCore::lTerminate, [this]() { +// mCallModelConnection->invokeToModel([this]() { mCallModel->terminate(); }); +// }); +// mCallModelConnection->makeConnectToCore(&CallCore::lTerminateAllCalls, [this]() { +// mCallModelConnection->invokeToModel([this]() { mCallModel->terminateAllCalls(); }); +// }); +// } + +// QString CallCore::getPeerAddress() const { +// return mPeerAddress; +// } + +// LinphoneEnums::CallStatus CallCore::getStatus() const { +// return mStatus; +// } + +// void CallCore::setStatus(LinphoneEnums::CallStatus status) { +// mustBeInMainThread(log().arg(Q_FUNC_INFO)); +// if (mStatus != status) { +// mStatus = status; +// emit statusChanged(mStatus); +// } +// } + +// LinphoneEnums::CallDir CallCore::getDir() const { +// return mDir; +// } + +// void CallCore::setDir(LinphoneEnums::CallDir dir) { +// mustBeInMainThread(log().arg(Q_FUNC_INFO)); +// if (mDir != dir) { +// mDir = dir; +// emit dirChanged(mDir); +// } +// } + +// LinphoneEnums::CallState CallCore::getState() const { +// return mState; +// } + +// void CallCore::setState(LinphoneEnums::CallState state, const QString &message) { +// mustBeInMainThread(log().arg(Q_FUNC_INFO)); +// if (mState != state) { +// mState = state; +// if (state == LinphoneEnums::CallState::Error) setLastErrorMessage(message); +// emit stateChanged(mState); +// } +// } + +// QString CallCore::getLastErrorMessage() const { +// return mLastErrorMessage; +// } +// void CallCore::setLastErrorMessage(const QString &message) { +// if (mLastErrorMessage != message) { +// mLastErrorMessage = message; +// emit lastErrorMessageChanged(); +// } +// } + +// int CallCore::getDuration() { +// return mDuration; +// } + +// void CallCore::setDuration(int duration) { +// if (mDuration != duration) { +// mDuration = duration; +// emit durationChanged(mDuration); +// } +// } + +// bool CallCore::getSpeakerMuted() const { +// return mSpeakerMuted; +// } + +// void CallCore::setSpeakerMuted(bool isMuted) { +// if (mSpeakerMuted != isMuted) { +// mSpeakerMuted = isMuted; +// emit speakerMutedChanged(); +// } +// } + +// bool CallCore::getMicrophoneMuted() const { +// return mMicrophoneMuted; +// } + +// void CallCore::setMicrophoneMuted(bool isMuted) { +// if (mMicrophoneMuted != isMuted) { +// mMicrophoneMuted = isMuted; +// emit microphoneMutedChanged(); +// } +// } + +// bool CallCore::getCameraEnabled() const { +// return mCameraEnabled; +// } + +// void CallCore::setCameraEnabled(bool enabled) { +// if (mCameraEnabled != enabled) { +// mCameraEnabled = enabled; +// emit cameraEnabledChanged(); +// } +// } + +// bool CallCore::getPaused() const { +// return mPaused; +// } + +// void CallCore::setPaused(bool paused) { +// if (mPaused != paused) { +// mPaused = paused; +// emit pausedChanged(); +// } +// } + +// bool CallCore::isSecured() const { +// return mIsSecured; +// } + +// void CallCore::setIsSecured(bool secured) { +// if (mIsSecured != secured) { +// mIsSecured = secured; +// emit securityUpdated(); +// } +// } + +// QString CallCore::getLocalSas() { +// return mLocalSas; +// } + +// QString CallCore::getRemoteSas() { +// return mRemoteSas; +// } + +// void CallCore::setLocalSas(const QString &sas) { +// if (mLocalSas != sas) { +// mLocalSas = sas; +// emit localSasChanged(); +// } +// } + +// void CallCore::setRemoteSas(const QString &sas) { +// if (mRemoteSas != sas) { +// mRemoteSas = sas; +// emit remoteSasChanged(); +// } +// } + +// LinphoneEnums::MediaEncryption CallCore::getEncryption() const { +// return mEncryption; +// } + +// void CallCore::setEncryption(LinphoneEnums::MediaEncryption encryption) { +// if (mEncryption != encryption) { +// mEncryption = encryption; +// emit securityUpdated(); +// } +// } + +// bool CallCore::getRemoteVideoEnabled() const { +// return mRemoteVideoEnabled; +// } + +// void CallCore::setRemoteVideoEnabled(bool enabled) { +// if (mRemoteVideoEnabled != enabled) { +// mRemoteVideoEnabled = enabled; +// emit remoteVideoEnabledChanged(mRemoteVideoEnabled); +// } +// } + +// bool CallCore::getRecording() const { +// return mRecording; +// } +// void CallCore::setRecording(bool recording) { +// if (mRecording != recording) { +// mRecording = recording; +// emit recordingChanged(); +// } +// } + +// bool CallCore::getRemoteRecording() const { +// return mRemoteRecording; +// } +// void CallCore::setRemoteRecording(bool recording) { +// if (mRemoteRecording != recording) { +// mRemoteRecording = recording; +// emit remoteRecordingChanged(); +// } +// } + +// bool CallCore::getRecordable() const { +// return mRecordable; +// } +// void CallCore::setRecordable(bool recordable) { +// if (mRecordable != recordable) { +// mRecordable = recordable; +// emit recordableChanged(); +// } +// } + +// float CallCore::getSpeakerVolumeGain() const { +// return mSpeakerVolumeGain; +// } +// void CallCore::setSpeakerVolumeGain(float gain) { +// if (mSpeakerVolumeGain != gain) { +// mSpeakerVolumeGain = gain; +// emit speakerVolumeGainChanged(); +// } +// } + +// float CallCore::getMicrophoneVolume() const { +// return mMicrophoneVolume; +// } +// void CallCore::setMicrophoneVolume(float vol) { +// if (mMicrophoneVolume != vol) { +// mMicrophoneVolume = vol; +// emit microphoneVolumeChanged(); +// } +// } + +// float CallCore::getMicrophoneVolumeGain() const { +// return mMicrophoneVolumeGain; +// } +// void CallCore::setMicrophoneVolumeGain(float gain) { +// if (mMicrophoneVolumeGain != gain) { +// mMicrophoneVolumeGain = gain; +// emit microphoneVolumeGainChanged(); +// } +// } + +// LinphoneEnums::CallState CallCore::getTransferState() const { +// return mTransferState; +// } + +// void CallCore::setTransferState(LinphoneEnums::CallState state, const QString &message) { +// if (mTransferState != state) { +// mTransferState = state; +// if (state == LinphoneEnums::CallState::Error) setLastErrorMessage(message); +// emit transferStateChanged(); +// } +// } + +// std::shared_ptr CallCore::getModel() const { +// return mCallModel; +// } diff --git a/Linphone/core/conference/ConferenceCore.hpp b/Linphone/core/conference/ConferenceCore.hpp new file mode 100644 index 000000000..b2b8953ac --- /dev/null +++ b/Linphone/core/conference/ConferenceCore.hpp @@ -0,0 +1,224 @@ +// /* +// * 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 CONFERENCE_CORE_H_ +// #define CONFERENCE_CORE_H_ + +// #include "model/conference/ConferenceModel.hpp" +// #include "tool/LinphoneEnums.hpp" +// #include "tool/thread/SafeConnection.hpp" +// #include +// #include +// #include + +// class ConferenceCore : public QObject, public AbstractObject { +// Q_OBJECT + +// // Q_PROPERTY(QString peerDisplayName MEMBER mPeerDisplayName) +// Q_PROPERTY(LinphoneEnums::ConferenceStatus status READ getStatus NOTIFY statusChanged) +// Q_PROPERTY(LinphoneEnums::ConferenceDir dir READ getDir NOTIFY dirChanged) +// Q_PROPERTY(LinphoneEnums::ConferenceState state READ getState NOTIFY stateChanged) +// Q_PROPERTY(QString lastErrorMessage READ getLastErrorMessage NOTIFY lastErrorMessageChanged) +// Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged) +// Q_PROPERTY(bool speakerMuted READ getSpeakerMuted WRITE lSetSpeakerMuted NOTIFY speakerMutedChanged) +// Q_PROPERTY(bool microphoneMuted READ getMicrophoneMuted WRITE lSetMicrophoneMuted NOTIFY microphoneMutedChanged) +// Q_PROPERTY(bool cameraEnabled READ getCameraEnabled WRITE lSetCameraEnabled NOTIFY cameraEnabledChanged) +// Q_PROPERTY(bool paused READ getPaused WRITE lSetPaused NOTIFY pausedChanged) +// Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT) +// Q_PROPERTY(bool isSecured READ isSecured NOTIFY securityUpdated) +// Q_PROPERTY(LinphoneEnums::MediaEncryption encryption READ getEncryption NOTIFY securityUpdated) +// Q_PROPERTY(QString localSas READ getLocalSas WRITE setLocalSas MEMBER mLocalSas NOTIFY localSasChanged) +// Q_PROPERTY(QString remoteSas WRITE setRemoteSas MEMBER mRemoteSas NOTIFY remoteSasChanged) +// Q_PROPERTY( +// bool remoteVideoEnabled READ getRemoteVideoEnabled WRITE setRemoteVideoEnabled NOTIFY remoteVideoEnabledChanged) +// Q_PROPERTY(bool recording READ getRecording WRITE setRecording NOTIFY recordingChanged) +// Q_PROPERTY(bool remoteRecording READ getRemoteRecording WRITE setRemoteRecording NOTIFY remoteRecordingChanged) +// Q_PROPERTY(bool recordable READ getRecordable WRITE setRecordable NOTIFY recordableChanged) +// Q_PROPERTY( +// float speakerVolumeGain READ getSpeakerVolumeGain WRITE setSpeakerVolumeGain NOTIFY speakerVolumeGainChanged) +// Q_PROPERTY(float microphoneVolumeGain READ getMicrophoneVolumeGain WRITE setMicrophoneVolumeGain NOTIFY +// microphoneVolumeGainChanged) +// Q_PROPERTY(float microVolume READ getMicrophoneVolume WRITE setMicrophoneVolume NOTIFY microphoneVolumeChanged) +// Q_PROPERTY(LinphoneEnums::ConferenceState transferState READ getTransferState NOTIFY transferStateChanged) + +// public: +// // Should be call from model Thread. Will be automatically in App thread after initialization +// static QSharedPointer create(const std::shared_ptr &call); +// ConferenceCore(const std::shared_ptr &call); +// ~ConferenceCore(); +// void setSelf(QSharedPointer me); + +// QString getPeerAddress() const; + +// LinphoneEnums::ConferenceStatus getStatus() const; +// void setStatus(LinphoneEnums::ConferenceStatus status); + +// LinphoneEnums::ConferenceDir getDir() const; +// void setDir(LinphoneEnums::ConferenceDir dir); + +// LinphoneEnums::ConferenceState getState() const; +// void setState(LinphoneEnums::ConferenceState state, const QString &message); + +// QString getLastErrorMessage() const; +// void setLastErrorMessage(const QString &message); + +// int getDuration(); +// void setDuration(int duration); + +// bool getSpeakerMuted() const; +// void setSpeakerMuted(bool isMuted); + +// bool getMicrophoneMuted() const; +// void setMicrophoneMuted(bool isMuted); + +// bool getCameraEnabled() const; +// void setCameraEnabled(bool enabled); + +// bool getPaused() const; +// void setPaused(bool paused); + +// bool isSecured() const; +// void setIsSecured(bool secured); + +// QString getLocalSas(); +// void setLocalSas(const QString &sas); +// QString getRemoteSas(); +// void setRemoteSas(const QString &sas); + +// LinphoneEnums::MediaEncryption getEncryption() const; +// void setEncryption(LinphoneEnums::MediaEncryption encryption); + +// bool getRemoteVideoEnabled() const; +// void setRemoteVideoEnabled(bool enabled); + +// bool getRecording() const; +// void setRecording(bool recording); + +// bool getRemoteRecording() const; +// void setRemoteRecording(bool recording); + +// bool getRecordable() const; +// void setRecordable(bool recordable); + +// float getSpeakerVolumeGain() const; +// void setSpeakerVolumeGain(float gain); + +// float getMicrophoneVolumeGain() const; +// void setMicrophoneVolumeGain(float gain); + +// float getMicrophoneVolume() const; +// void setMicrophoneVolume(float vol); + +// QString getInputDeviceName() const; +// void setInputDeviceName(const QString &id); + +// LinphoneEnums::ConferenceState getTransferState() const; +// void setTransferState(LinphoneEnums::ConferenceState state, const QString &message); + +// std::shared_ptr getModel() const; + +// signals: +// void statusChanged(LinphoneEnums::ConferenceStatus status); +// void stateChanged(LinphoneEnums::ConferenceState state); +// void dirChanged(LinphoneEnums::ConferenceDir dir); +// void lastErrorMessageChanged(); +// void durationChanged(int duration); +// void speakerMutedChanged(); +// void microphoneMutedChanged(); +// void cameraEnabledChanged(); +// void pausedChanged(); +// void transferStateChanged(); +// void securityUpdated(); +// void localSasChanged(); +// void remoteSasChanged(); +// void remoteVideoEnabledChanged(bool remoteVideoEnabled); +// void recordingChanged(); +// void remoteRecordingChanged(); +// void recordableChanged(); +// void speakerVolumeGainChanged(); +// void microphoneVolumeChanged(); +// void microphoneVolumeGainChanged(); + +// // Linphone commands +// void lAccept(bool withVideo); // Accept an incoming call +// void lDecline(); // Decline an incoming call +// void lTerminate(); // Hangup a call +// void lTerminateAllConferences(); // Hangup all calls +// void lSetSpeakerMuted(bool muted); +// void lSetMicrophoneMuted(bool isMuted); +// void lSetCameraEnabled(bool enabled); +// void lSetPaused(bool paused); +// void lTransferConference(const QString &dest); +// void lStartRecording(); +// void lStopRecording(); +// void lVerifyAuthenticationToken(bool verified); +// void lSetSpeakerVolumeGain(float gain); +// void lSetMicrophoneVolumeGain(float gain); +// void lSetInputAudioDevice(const QString &id); +// void lSetOutputAudioDevice(const QString &id); + +// /* TODO +// Q_INVOKABLE void acceptWithVideo(); + +// Q_INVOKABLE void askForTransfer(); +// Q_INVOKABLE void askForAttendedTransfer(); +// Q_INVOKABLE bool transferTo(const QString &sipAddress); +// Q_INVOKABLE bool transferToAnother(const QString &peerAddress); + +// Q_INVOKABLE bool getRemoteVideoEnabled() const; +// Q_INVOKABLE void acceptVideoRequest(); +// Q_INVOKABLE void rejectVideoRequest(); + +// Q_INVOKABLE void takeSnapshot(); + +// Q_INVOKABLE void sendDtmf(const QString &dtmf); +// Q_INVOKABLE void verifyAuthenticationToken(bool verify); +// Q_INVOKABLE void updateStreams(); +// */ +// private: +// std::shared_ptr mConferenceModel; +// LinphoneEnums::ConferenceStatus mStatus; +// LinphoneEnums::ConferenceState mState; +// LinphoneEnums::ConferenceState mTransferState; +// LinphoneEnums::ConferenceDir mDir; +// LinphoneEnums::MediaEncryption mEncryption; +// QString mLastErrorMessage; +// QString mPeerAddress; +// bool mIsSecured; +// int mDuration = 0; +// bool mSpeakerMuted; +// bool mMicrophoneMuted; +// bool mCameraEnabled; +// bool mPaused = false; +// bool mRemoteVideoEnabled = false; +// bool mRecording = false; +// bool mRemoteRecording = false; +// bool mRecordable = false; +// QString mLocalSas; +// QString mRemoteSas; +// float mSpeakerVolumeGain; +// float mMicrophoneVolume; +// float mMicrophoneVolumeGain; +// QSharedPointer> mConferenceModelConnection; + +// DECLARE_ABSTRACT_OBJECT +// }; +// Q_DECLARE_METATYPE(ConferenceCore *) +// #endif diff --git a/Linphone/core/conference/ConferenceInfoCore.cpp b/Linphone/core/conference/ConferenceInfoCore.cpp new file mode 100644 index 000000000..68c6dcc20 --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoCore.cpp @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2021 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 "ConferenceInfoCore.hpp" + +#include "core/App.hpp" +#include "core/proxy/ListProxy.hpp" +#include "model/object/VariantObject.hpp" +#include "model/tool/ToolModel.hpp" +#include "tool/Utils.hpp" +#include "tool/thread/SafeConnection.hpp" + +DEFINE_ABSTRACT_OBJECT(ConferenceInfoCore) + +QSharedPointer +ConferenceInfoCore::create(std::shared_ptr conferenceInfo) { + auto sharedPointer = + QSharedPointer(new ConferenceInfoCore(conferenceInfo), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + if (isInLinphoneThread()) sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +ConferenceInfoCore::ConferenceInfoCore(std::shared_ptr conferenceInfo, QObject *parent) + : QObject(parent) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + mTimeZoneModel = QSharedPointer(new TimeZoneModel( + QTimeZone::systemTimeZone())); // Always return system timezone because this info is not stored in database. + + if (conferenceInfo) { + mustBeInLinphoneThread(getClassName()); + mConferenceInfoModel = Utils::makeQObject_ptr(conferenceInfo); + auto confSchedulerModel = mConferenceInfoModel->getConferenceScheduler(); + if (!confSchedulerModel) { + auto confScheduler = CoreModel::getInstance()->getCore()->createConferenceScheduler(); + confSchedulerModel = Utils::makeQObject_ptr(confScheduler); + mConferenceInfoModel->setConferenceScheduler(confSchedulerModel); + } + auto address = conferenceInfo->getUri(); + mUri = address && address->isValid() && !address->getDomain().empty() + ? Utils::coreStringToAppString(address->asStringUriOnly()) + : ""; + mDateTime = QDateTime::fromMSecsSinceEpoch(conferenceInfo->getDateTime() * 1000, Qt::LocalTime); + mDuration = conferenceInfo->getDuration(); + mEndDateTime = mDateTime.addSecs(mDuration * 60); + mOrganizerAddress = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->asStringUriOnly()); + mOrganizerName = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->getDisplayName()); + if (mOrganizerName.isEmpty()) { + mOrganizerName = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->getUsername()); + mOrganizerName.replace(".", " "); + } + mSubject = Utils::coreStringToAppString(conferenceInfo->getSubject()); + mDescription = Utils::coreStringToAppString(conferenceInfo->getDescription()); + mIsEnded = getDateTimeUtc().addSecs(mDuration * 60) < QDateTime::currentDateTimeUtc(); + + for (auto item : conferenceInfo->getParticipantInfos()) { + QVariantMap participant; + auto address = item->getAddress(); + auto name = Utils::coreStringToAppString(address->getDisplayName()); + if (name.isEmpty()) { + name = Utils::coreStringToAppString(address->getUsername()); + name.replace(".", " "); + } + participant["displayName"] = name; + participant["address"] = Utils::coreStringToAppString(address->asStringUriOnly()); + participant["role"] = (int)LinphoneEnums::fromLinphone(item->getRole()); + mParticipants.append(participant); + } + mConferenceInfoState = LinphoneEnums::fromLinphone(conferenceInfo->getState()); + } else { + auto defaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount(); + if (defaultAccount) { + auto accountAddress = defaultAccount->getContactAddress(); + if (accountAddress) { + auto cleanedClonedAddress = accountAddress->clone(); + cleanedClonedAddress->clean(); + mOrganizerAddress = Utils::coreStringToAppString(cleanedClonedAddress->asStringUriOnly()); + qDebug() << "set organizer address" << mOrganizerAddress; + } + } + } + + connect(this, &ConferenceInfoCore::endDateTimeChanged, + [this] { setDuration(mDateTime.secsTo(mEndDateTime) / 60.0); }); + connect(this, &ConferenceInfoCore::durationChanged, [this] { setEndDateTime(mDateTime.addSecs(mDuration * 60)); }); +} + +ConferenceInfoCore::ConferenceInfoCore(const ConferenceInfoCore &conferenceInfoCore) { + mDateTime = conferenceInfoCore.mDateTime; + mEndDateTime = conferenceInfoCore.mEndDateTime; + mDuration = conferenceInfoCore.mDuration; + mOrganizerAddress = conferenceInfoCore.mOrganizerAddress; + mOrganizerName = conferenceInfoCore.mOrganizerName; + mSubject = conferenceInfoCore.mSubject; + mDescription = conferenceInfoCore.mDescription; + mUri = conferenceInfoCore.mUri; + mParticipants = conferenceInfoCore.mParticipants; + mTimeZoneModel = conferenceInfoCore.mTimeZoneModel; + mIsScheduled = conferenceInfoCore.mIsScheduled; + mIsEnded = conferenceInfoCore.mIsEnded; + mInviteMode = conferenceInfoCore.mInviteMode; + mConferenceInfoState = conferenceInfoCore.mConferenceInfoState; +} + +ConferenceInfoCore::~ConferenceInfoCore() { + mustBeInMainThread("~" + getClassName()); + mCheckEndTimer.stop(); +} + +void ConferenceInfoCore::reset(const ConferenceInfoCore &conf) { + setDateTime(conf.getDateTimeSystem()); + setDuration(conf.getDuration()); + setOrganizerAddress(conf.getOrganizerAddress()); + setOrganizerName(conf.getOrganizerName()); + setSubject(conf.getSubject()); + setDescription(conf.getDescription()); + setUri(conf.getUri()); + resetParticipants(conf.getParticipants()); + setTimeZoneModel(conf.getTimeZoneModel()); + setIsScheduled(conf.isScheduled()); + setIsEnded(conf.isEnded()); + setInviteMode(conf.getInviteMode()); + setConferenceInfoState(conf.getConferenceInfoState()); +} + +void ConferenceInfoCore::setSelf(SafeSharedPointer me) { + setSelf(me.mQDataWeak.lock()); +} +void ConferenceInfoCore::setSelf(QSharedPointer me) { + if (me) { + if (mConferenceInfoModel) { + mCoreModelConnection = nullptr; + mConfInfoModelConnection = QSharedPointer>( + new SafeConnection(me, mConferenceInfoModel), + &QObject::deleteLater); + + mConfInfoModelConnection->makeConnectToModel(&ConferenceInfoModel::dateTimeChanged, + [this](const QDateTime &date) { + mConfInfoModelConnection->invokeToCore([this, date] { + setDateTime(date); + setIsEnded(computeIsEnded()); + }); + }); + mConfInfoModelConnection->makeConnectToModel(&ConferenceInfoModel::durationChanged, [this](int duration) { + mConfInfoModelConnection->invokeToCore([this, duration] { + setDuration(duration); + setIsEnded(computeIsEnded()); + }); + }); + mConfInfoModelConnection->makeConnectToModel( + &ConferenceInfoModel::stateChanged, [this](linphone::ConferenceScheduler::State state) { + qDebug() << "conf state changed" << LinphoneEnums::fromLinphone(state); + mConfInfoModelConnection->invokeToCore([this] {}); + }); + + mConfInfoModelConnection->makeConnectToCore(&ConferenceInfoCore::lDeleteConferenceInfo, + [this]() { mConferenceInfoModel->deleteConferenceInfo(); }); + mConfInfoModelConnection->makeConnectToModel(&ConferenceInfoModel::conferenceInfoDeleted, + &ConferenceInfoCore::removed); + + mConfInfoModelConnection->makeConnectToModel( + &ConferenceInfoModel::stateChanged, + [this](linphone::ConferenceScheduler::State state) { qDebug() << "conf state changed"; }); + mConfInfoModelConnection->makeConnectToModel( + &ConferenceInfoModel::invitationsSent, + [this](const std::list> &failedInvitations) { + qDebug() << "invitations sent"; + }); + } else { // Create + mCoreModelConnection = QSharedPointer>( + new SafeConnection(me, CoreModel::getInstance()), &QObject::deleteLater); + } + } +} + +//------------------------------------------------------------------------------------------------ + +// Note conferenceInfo->getDateTime uses UTC +QDateTime ConferenceInfoCore::getDateTimeUtc() const { + return getDateTimeSystem().toUTC(); +} + +QDateTime ConferenceInfoCore::getDateTimeSystem() const { + return mDateTime; +} + +int ConferenceInfoCore::getDuration() const { + return mDuration; +} + +QDateTime ConferenceInfoCore::getEndDateTime() const { + return mDateTime.addSecs(mDuration * 60); +} + +QDateTime ConferenceInfoCore::getEndDateTimeUtc() const { + return getEndDateTime().toUTC(); +} + +QString ConferenceInfoCore::getOrganizerName() const { + return mOrganizerName; +} + +QString ConferenceInfoCore::getOrganizerAddress() const { + return mOrganizerAddress; +} + +QString ConferenceInfoCore::getSubject() const { + return mSubject; +} + +QString ConferenceInfoCore::getDescription() const { + return mDescription; +} + +void ConferenceInfoCore::setDateTime(const QDateTime &date) { + if (date != mDateTime) { + mDateTime = date; + emit dateTimeChanged(); + } +} + +void ConferenceInfoCore::setEndDateTime(const QDateTime &date) { + if (date != mEndDateTime) { + mEndDateTime = date; + emit endDateTimeChanged(); + } +} + +void ConferenceInfoCore::setDuration(int duration) { + if (duration != mDuration) { + mDuration = duration; + emit durationChanged(); + } +} + +void ConferenceInfoCore::setSubject(const QString &subject) { + if (subject != mSubject) { + mSubject = subject; + emit subjectChanged(); + } +} + +void ConferenceInfoCore::setOrganizerName(const QString &organizer) { + if (organizer != mOrganizerName) { + mOrganizerName = organizer; + emit organizerNameChanged(); + } +} + +void ConferenceInfoCore::setOrganizerAddress(const QString &organizer) { + if (organizer != mOrganizerAddress) { + mOrganizerAddress = organizer; + emit organizerAddressChanged(); + } +} + +void ConferenceInfoCore::setUri(const QString &uri) { + if (uri != mUri) { + mUri = uri; + emit uriChanged(); + } +} + +void ConferenceInfoCore::setTimeZoneModel(TimeZoneModel *model) { + if (mTimeZoneModel->getDisplayName() != model->getDisplayName() || + mTimeZoneModel->getCountryName() != model->getCountryName() || + mTimeZoneModel->getOffsetFromUtc() != model->getOffsetFromUtc() || + mTimeZoneModel->getStandardTimeOffset() != model->getStandardTimeOffset() || + mTimeZoneModel->getTimeZone() != model->getTimeZone()) { + + mTimeZoneModel = QSharedPointer(model); + emit timeZoneModelChanged(); + } +} + +void ConferenceInfoCore::setDescription(const QString &description) { + if (description != mDescription) { + mDescription = description; + emit descriptionChanged(); + } +} + +QString ConferenceInfoCore::getUri() const { + return mUri; +} + +bool ConferenceInfoCore::isScheduled() const { + return mIsScheduled; +} + +bool ConferenceInfoCore::computeIsEnded() const { + return getEndDateTimeUtc() < QDateTime::currentDateTimeUtc(); +} + +bool ConferenceInfoCore::isEnded() const { + return mIsEnded; +} + +int ConferenceInfoCore::getInviteMode() const { + return mInviteMode; +} + +QVariantList ConferenceInfoCore::getParticipants() const { + return mParticipants; +} + +int ConferenceInfoCore::getParticipantCount() const { + return mParticipants.size(); +} + +void ConferenceInfoCore::addParticipant(const QString &address) { + for (auto &participant : mParticipants) { + auto map = participant.toMap(); + if (map["address"].toString() == address) return; + } + QVariantMap participant; + auto displayNameObj = Utils::getDisplayName(address); + if (displayNameObj) participant["displayName"] = displayNameObj->getValue(); + participant["address"] = address; + participant["role"] = (int)LinphoneEnums::ParticipantRole::Listener; + mParticipants.append(participant); + emit participantsChanged(); +} + +void ConferenceInfoCore::removeParticipant(const QString &address) { + for (int i = 0; i < mParticipants.size(); ++i) { + auto map = mParticipants[i].toMap(); + if (map["address"].toString() == address) { + mParticipants.remove(i); + emit participantsChanged(); + return; + } + } +} + +void ConferenceInfoCore::removeParticipant(const int &index) { + mParticipants.remove(index); + emit participantsChanged(); +} + +QString ConferenceInfoCore::getParticipantAddressAt(const int &index) { + if (index < 0 || index >= mParticipants.size()) return QString(); + auto map = mParticipants[index].toMap(); + return map["address"].toString(); +} + +void ConferenceInfoCore::clearParticipants() { + mParticipants.clear(); + emit participantsChanged(); +} + +void ConferenceInfoCore::resetParticipants(QVariantList participants) { + mParticipants = participants; +} + +void ConferenceInfoCore::resetParticipants(const QStringList &adresses) { + mParticipants.clear(); + for (auto &address : adresses) { + QVariantMap participant; + QString name; + auto nameObj = Utils::getDisplayName(address); + if (nameObj) name = nameObj->getValue().toString(); + participant["displayName"] = name; + participant["address"] = address; + participant["role"] = (int)LinphoneEnums::ParticipantRole::Listener; + mParticipants.append(participant); + } + emit participantsChanged(); +} + +int ConferenceInfoCore::getParticipantIndex(const QString &address) { + for (int i = 0; i < mParticipants.count(); ++i) { + auto map = mParticipants[i].toMap(); + if (map["address"].toString() == address) { + return i; + } + } + return -1; +} + +TimeZoneModel *ConferenceInfoCore::getTimeZoneModel() const { + return mTimeZoneModel.get(); +} + +// QString ConferenceInfoCore::getIcalendarString() const { +// return Utils::coreStringToAppString(mConferenceInfoModel->getIcalendarString()); +// } + +LinphoneEnums::ConferenceInfoState ConferenceInfoCore::getConferenceInfoState() const { + return mConferenceInfoState; +} + +// LinphoneEnums::ConferenceSchedulerState ConferenceInfoCore::getConferenceSchedulerState() const { +// return LinphoneEnums::fromLinphone(mLastConferenceSchedulerState); +// } + +//------------------------------------------------------------------------------------------------ +// Datetime is in Custom (Locale/UTC/System). Convert into UTC for conference info + +void ConferenceInfoCore::setIsScheduled(const bool &on) { + if (mIsScheduled != on) { + mIsScheduled = on; + emit isScheduledChanged(); + } +} + +void ConferenceInfoCore::setIsEnded(bool ended) { + if (mIsEnded != ended) { + mIsEnded = ended; + if (mIsEnded) mCheckEndTimer.stop(); // No need to run the timer. + else mCheckEndTimer.start(); + emit isEndedChanged(); + } +} + +void ConferenceInfoCore::setInviteMode(const int &mode) { + if (mode != mInviteMode) { + mInviteMode = mode; + emit inviteModeChanged(); + } +} + +void ConferenceInfoCore::setConferenceInfoState(LinphoneEnums::ConferenceInfoState state) { + if (state != mConferenceInfoState) { + mConferenceInfoState = state; + emit conferenceInfoStateChanged(); + } +} + +void ConferenceInfoCore::writeFromModel(const std::shared_ptr &model) { + mustBeInLinphoneThread(getClassName() + "::writeFromModel()"); + setDateTime(model->getDateTime()); + setDuration(model->getDuration()); + setSubject(model->getSubject()); + setOrganizerName(model->getOrganizerName()); + setOrganizerAddress(model->getOrganizerAddress()); + setDescription(model->getDescription()); + QStringList participantAddresses; + for (auto &infos : model->getParticipantInfos()) { + participantAddresses.append(Utils::coreStringToAppString(infos->getAddress()->asStringUriOnly())); + } + resetParticipants(participantAddresses); +} + +void ConferenceInfoCore::writeIntoModel(std::shared_ptr model) { + mustBeInLinphoneThread(getClassName() + "::writeIntoModel()"); + model->setDateTime(mDateTime); + model->setDuration(mDuration); + model->setSubject(mSubject); + model->setOrganizer(mOrganizerAddress); + model->setDescription(mDescription); + std::list> participantInfos; + for (auto &p : mParticipants) { + auto map = p.toMap(); + auto address = map["address"].toString(); + auto linAddr = ToolModel::interpretUrl(address); + auto infos = linphone::Factory::get()->createParticipantInfo(linAddr); + participantInfos.push_back(infos); + } + model->setParticipantInfos(participantInfos); +} + +void ConferenceInfoCore::save() { + mustBeInMainThread(getClassName() + "::save()"); + ConferenceInfoCore *thisCopy = new ConferenceInfoCore(*this); // Pointer to avoid multiple copies in lambdas + if (mConferenceInfoModel) { + mConfInfoModelConnection->invokeToModel([this, thisCopy]() { // Copy values to avoid concurrency + mustBeInLinphoneThread(getClassName() + "::save()"); + thisCopy->writeIntoModel(mConferenceInfoModel); + thisCopy->deleteLater(); + }); + } else { + mCoreModelConnection->invokeToModel([this, thisCopy]() { + auto linphoneConf = + CoreModel::getInstance()->getCore()->findConferenceInformationFromUri(ToolModel::interpretUrl(mUri)); + + if (linphoneConf == nullptr) { + linphoneConf = linphone::Factory::get()->createConferenceInfo(); + } + auto defaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount(); + if (defaultAccount) { + auto accountAddress = defaultAccount->getContactAddress(); + if (accountAddress) { + auto cleanedClonedAddress = accountAddress->clone(); + cleanedClonedAddress->clean(); + if (!linphoneConf->getOrganizer()) linphoneConf->setOrganizer(cleanedClonedAddress); + if (mOrganizerAddress.isEmpty()) + mOrganizerAddress = Utils::coreStringToAppString(accountAddress->asStringUriOnly()); + } + } + mConferenceInfoModel = Utils::makeQObject_ptr(linphoneConf); + // mConferenceInfoModel->createConferenceScheduler(); + auto confSchedulerModel = mConferenceInfoModel->getConferenceScheduler(); + if (!confSchedulerModel) { + auto confScheduler = CoreModel::getInstance()->getCore()->createConferenceScheduler(); + confSchedulerModel = Utils::makeQObject_ptr(confScheduler); + mConferenceInfoModel->setConferenceScheduler(confSchedulerModel); + } + thisCopy->writeIntoModel(mConferenceInfoModel); + thisCopy->deleteLater(); + confSchedulerModel->setInfo(linphoneConf); + mCoreModelConnection->invokeToCore([this]() { setSelf(mCoreModelConnection->mCore); }); + }); + } +} + +void ConferenceInfoCore::undo() { + if (mConferenceInfoModel) { + mConfInfoModelConnection->invokeToModel([this]() { + ConferenceInfoCore *conf = new ConferenceInfoCore(*this); + conf->writeFromModel(mConferenceInfoModel); + conf->moveToThread(App::getInstance()->thread()); + mConfInfoModelConnection->invokeToCore([this, conf]() mutable { + this->reset(*conf); + conf->deleteLater(); + }); + }); + } +} + +void ConferenceInfoCore::cancelConference() { + if (!mConferenceInfoModel) return; + mConferenceInfoModel->cancelConference(); +} + +//------------------------------------------------------------------------------------------------- + +// void ConferenceInfoCore::createConference(const int &securityLevel) { +// CoreModel::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = false; +// shared_ptr core = CoreManager::getInstance()->getCore(); +// static std::shared_ptr conference; +// qInfo() << "Conference creation of " << getSubject() << " at " << securityLevel << " security, organized by " +// << getOrganizer() << " for " << getDateTimeSystem().toString(); +// qInfo() << "Participants:"; +// for (auto p : mConferenceInfoModel->getParticipants()) +// qInfo() << "\t" << p->asString().c_str(); + +// mConferenceScheduler = ConferenceScheduler::create(); +// mConferenceScheduler->mSendInvite = mInviteMode; +// connect(mConferenceScheduler.get(), &ConferenceScheduler::invitationsSent, this, +// &ConferenceInfoCore::onInvitationsSent); +// connect(mConferenceScheduler.get(), &ConferenceScheduler::stateChanged, this, +// &ConferenceInfoCore::onConferenceSchedulerStateChanged); +// mConferenceScheduler->getConferenceScheduler()->setInfo(mConferenceInfoModel); +// } + +//------------------------------------------------------------------------------------------------- + +// void ConferenceInfoCore::onConferenceSchedulerStateChanged(linphone::ConferenceScheduler::State state) { +// qDebug() << "ConferenceInfoCore::onConferenceSchedulerStateChanged: " << (int)state; +// mLastConferenceSchedulerState = state; +// if (state == linphone::ConferenceScheduler::State::Ready) emit conferenceCreated(); +// else if (state == linphone::ConferenceScheduler::State::Error) emit conferenceCreationFailed(); +// emit conferenceInfoChanged(); +// } +void ConferenceInfoCore::onInvitationsSent(const std::list> &failedInvitations) { + qDebug() << "ConferenceInfoCore::onInvitationsSent"; + emit invitationsSent(); +} diff --git a/Linphone/core/conference/ConferenceInfoCore.hpp b/Linphone/core/conference/ConferenceInfoCore.hpp new file mode 100644 index 000000000..23f2dc602 --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoCore.hpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2022 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 CONFERENCE_INFO_CORE_H_ +#define CONFERENCE_INFO_CORE_H_ + +#include "core/timezone/TimeZone.hpp" +#include "model/conference/ConferenceInfoModel.hpp" +#include "tool/LinphoneEnums.hpp" +#include "tool/thread/SafeConnection.hpp" +#include + +#include +#include +#include +#include +#include + +class ParticipantListModel; +// class TimeZoneModel; + +class ConferenceInfoCore : public QObject, public AbstractObject { + Q_OBJECT + +public: + Q_PROPERTY(TimeZoneModel *timeZoneModel READ getTimeZoneModel WRITE setTimeZoneModel NOTIFY timeZoneModelChanged) + Q_PROPERTY(QDateTime dateTime READ getDateTimeSystem WRITE setDateTime NOTIFY dateTimeChanged) + Q_PROPERTY(QDateTime endDateTime READ getEndDateTime WRITE setEndDateTime NOTIFY endDateTimeChanged) + Q_PROPERTY(QDateTime dateTimeUtc READ getDateTimeUtc NOTIFY dateTimeChanged) + Q_PROPERTY(int duration READ getDuration WRITE setDuration NOTIFY durationChanged) + Q_PROPERTY( + QString organizerAddress READ getOrganizerAddress WRITE setOrganizerAddress NOTIFY organizerAddressChanged) + Q_PROPERTY(QString organizerName READ getOrganizerName WRITE setOrganizerName NOTIFY organizerNameChanged) + Q_PROPERTY(QString subject READ getSubject WRITE setSubject NOTIFY subjectChanged) + Q_PROPERTY(QString description READ getDescription WRITE setDescription NOTIFY descriptionChanged) + Q_PROPERTY(QString uri READ getUri NOTIFY uriChanged) + Q_PROPERTY(bool isScheduled READ isScheduled WRITE setIsScheduled NOTIFY isScheduledChanged) + Q_PROPERTY(bool isEnded READ isEnded WRITE setIsEnded NOTIFY isEndedChanged) + Q_PROPERTY(int inviteMode READ getInviteMode WRITE setInviteMode NOTIFY inviteModeChanged) + Q_PROPERTY(int participantCount READ getParticipantCount NOTIFY participantsChanged) + Q_PROPERTY(QVariantList participants READ getParticipants NOTIFY participantsChanged) + Q_PROPERTY(LinphoneEnums::ConferenceInfoState state READ getConferenceInfoState NOTIFY conferenceInfoStateChanged) + // Q_PROPERTY(LinphoneEnums::ConferenceSchedulerState conferenceSchedulerState READ getConferenceSchedulerState + // NOTIFY + // conferenceSchedulerStateChanged) + + static QSharedPointer create(std::shared_ptr conferenceInfo); + ConferenceInfoCore(std::shared_ptr conferenceInfo, QObject *parent = nullptr); + ConferenceInfoCore(const ConferenceInfoCore &conferenceInfoCore); + ~ConferenceInfoCore(); + void reset(const ConferenceInfoCore &contact); + + void setSelf(SafeSharedPointer me); + void setSelf(QSharedPointer me); + + QDateTime getDateTimeUtc() const; + QDateTime getDateTimeSystem() const; + int getDuration() const; + QDateTime getEndDateTime() const; + QDateTime getEndDateTimeUtc() const; + QString getOrganizerName() const; + QString getOrganizerAddress() const; + QString getSubject() const; + QString getDescription() const; + QString getUri() const; + bool isScheduled() const; + void setIsScheduled(const bool &on); + bool computeIsEnded() const; + bool isEnded() const; + void setIsEnded(bool ended); + int getInviteMode() const; + QVariantList getParticipants() const; + // Q_INVOKABLE QVariantList getAllParticipants() const; + int getParticipantCount() const; + TimeZoneModel *getTimeZoneModel() const; + // QString getIcalendarString() const; + LinphoneEnums::ConferenceInfoState getConferenceInfoState() const; + // LinphoneEnums::ConferenceSchedulerState getConferenceSchedulerState() const; + + void setDateTime(const QDateTime &date); + void setEndDateTime(const QDateTime &date); + void setDuration(int duration); + void setSubject(const QString &subject); + void setOrganizerName(const QString &organizer); + void setOrganizerAddress(const QString &organizer); + void setUri(const QString &uri); + void setTimeZoneModel(TimeZoneModel *model); + void setDescription(const QString &description); + void setInviteMode(const int &mode); + void setConferenceInfoState(LinphoneEnums::ConferenceInfoState state); + + Q_INVOKABLE void addParticipant(const QString &address); + Q_INVOKABLE void removeParticipant(const QString &address); + Q_INVOKABLE void removeParticipant(const int &index); + Q_INVOKABLE QString getParticipantAddressAt(const int &index); + Q_INVOKABLE void clearParticipants(); + void resetParticipants(QVariantList participants); + Q_INVOKABLE void resetParticipants(const QStringList &adresses); + Q_INVOKABLE int getParticipantIndex(const QString &address); + + void writeFromModel(const std::shared_ptr &model); + void writeIntoModel(std::shared_ptr model); + + Q_INVOKABLE void save(); + Q_INVOKABLE void cancelConference(); + Q_INVOKABLE void undo(); + + // Tools + // Q_INVOKABLE void resetConferenceInfo(); // Recreate a new conference info from factory + + // SCHEDULER + + // virtual void onConferenceSchedulerStateChanged(linphone::ConferenceScheduler::State state); + virtual void onInvitationsSent(const std::list> &failedInvitations); + +signals: + void dateTimeChanged(); + void endDateTimeChanged(); + void durationChanged(); + void organizerAddressChanged(); + void organizerNameChanged(); + void subjectChanged(); + void descriptionChanged(); + void participantsChanged(); + void uriChanged(); + void isScheduledChanged(); + void isEndedChanged(); + void inviteModeChanged(); + void conferenceInfoStateChanged(); + void timeZoneModelChanged(); + // void conferenceSchedulerStateChanged(); + + void invitationsSent(); + void removed(); + + // void lCreateConference(const int &securityLevel); + // void lCancelConference(); + void lDeleteConferenceInfo(); // Remove completly this conference info from DB + +private: + std::shared_ptr mConferenceInfoModel = nullptr; + QSharedPointer> mConfInfoModelConnection; + QSharedPointer> mConfSchedulerModelConnection; + QSharedPointer> mCoreModelConnection; + + QDateTime mDateTime; + QDateTime mEndDateTime; + int mDuration; + QString mOrganizerAddress; + QString mOrganizerName; + QString mSubject; + QString mDescription; + QString mUri; + QVariantList mParticipants; + QSharedPointer mTimeZoneModel; + LinphoneEnums::ConferenceInfoState mConferenceInfoState = + LinphoneEnums::ConferenceInfoState::ConferenceInfoStateNew; + bool mIsScheduled = true; + bool mIsEnded = false; + QTimer mCheckEndTimer; + int mInviteMode = 0; + // bool mRemoveRequested = false; // true if user has request its deletion from DB + // linphone::ConferenceScheduler::State mLastConferenceSchedulerState = + // linphone::ConferenceScheduler::State::Idle; // Workaround for missing getter in scheduler. + DECLARE_ABSTRACT_OBJECT +}; + +Q_DECLARE_METATYPE(ConferenceInfoCore *) + +#endif diff --git a/Linphone/core/conference/ConferenceInfoGui.cpp b/Linphone/core/conference/ConferenceInfoGui.cpp new file mode 100644 index 000000000..60c3cf9d3 --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoGui.cpp @@ -0,0 +1,47 @@ +/* + * 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 "ConferenceInfoGui.hpp" +#include "ConferenceInfoCore.hpp" +#include "core/App.hpp" + +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()); +} + +ConferenceInfoGui::~ConferenceInfoGui() { + mustBeInMainThread("~" + getClassName()); + qDebug() << "[ConferenceInfoGui] delete" << this; +} + +ConferenceInfoCore *ConferenceInfoGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/conference/ConferenceInfoGui.hpp b/Linphone/core/conference/ConferenceInfoGui.hpp new file mode 100644 index 000000000..a39a9c349 --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoGui.hpp @@ -0,0 +1,44 @@ +/* + * 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 CONFERENCE_INFO_GUI_H_ +#define CONFERENCE_INFO_GUI_H_ + +#include "tool/AbstractObject.hpp" +#include +#include + +class ConferenceInfoCore; + +class ConferenceInfoGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(ConferenceInfoCore *core READ getCore CONSTANT) + +public: + ConferenceInfoGui(); + ConferenceInfoGui(QSharedPointer core); + ~ConferenceInfoGui(); + ConferenceInfoCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/conference/ConferenceInfoList.cpp b/Linphone/core/conference/ConferenceInfoList.cpp new file mode 100644 index 000000000..4ec3a16ae --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoList.cpp @@ -0,0 +1,162 @@ +/* + * 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 "ConferenceInfoList.hpp" +#include "ConferenceInfoCore.hpp" +#include "ConferenceInfoGui.hpp" +#include "core/App.hpp" +#include "model/object/VariantObject.hpp" +#include "tool/Utils.hpp" +#include +#include + +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(ConferenceInfoList) + +QSharedPointer ConferenceInfoList::create() { + auto model = QSharedPointer(new ConferenceInfoList(), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + model->setSelf(model); + return model; +} + +ConferenceInfoList::ConferenceInfoList(QObject *parent) : ListProxy(parent) { + mustBeInMainThread(getClassName()); +} + +ConferenceInfoList::~ConferenceInfoList() { + mustBeInMainThread("~" + getClassName()); + mCoreModelConnection = nullptr; +} + +void ConferenceInfoList::setSelf(QSharedPointer me) { + mCoreModelConnection = QSharedPointer>( + new SafeConnection(me, CoreModel::getInstance()), &QObject::deleteLater); + + mCoreModelConnection->makeConnectToCore(&ConferenceInfoList::lUpdate, [this]() { + mCoreModelConnection->invokeToModel([this]() { + QList> *items = new QList>(); + mustBeInLinphoneThread(getClassName()); + std::list> conferenceInfos = + CoreModel::getInstance()->getCore()->getDefaultAccount()->getConferenceInformationList(); + for (auto conferenceInfo : conferenceInfos) { + auto confInfoCore = build(conferenceInfo); + if (confInfoCore) items->push_back(confInfoCore); + } + mCoreModelConnection->invokeToCore([this, items]() { + mustBeInMainThread(getClassName()); + resetData(); + add(*items); + delete items; + }); + }); + }); + // mCoreModelConnection->makeConnectToModel( + // &CoreModel::conferenceInfoReceived, + // [this](const std::shared_ptr core, + // const std::shared_ptr &conferenceInfo) { + // auto realConferenceInfo = CoreModel::getInstance()->getCore()->findConferenceInformationFromUri( + // conferenceInfo->getUri()->clone()); + // // auto realConferenceInfo = ConferenceInfoModel::findConferenceInfo(conferenceInfo); + // if (realConferenceInfo) { + // auto model = get(realConferenceInfo); + // if (model) { + // // model->setConferenceInfo(realConferenceInfo); + // } else { + // auto confInfo = build(realConferenceInfo); + // if (confInfo) add(confInfo); + // } + // } else + // qWarning() << "No ConferenceInfo have beend found for " << conferenceInfo->getUri()->asString().c_str(); + // }); + + mCoreModelConnection->makeConnectToModel(&CoreModel::conferenceInfoReceived, &ConferenceInfoList::lUpdate); + mCoreModelConnection->makeConnectToModel(&CoreModel::conferenceStateChanged, [this] { + qDebug() << "list: conf state changed"; + lUpdate(); + }); + mCoreModelConnection->makeConnectToModel( + &CoreModel::conferenceInfoReceived, + [this](const std::shared_ptr &core, + const std::shared_ptr &conferenceInfo) { + qDebug() << "info received" << conferenceInfo->getOrganizer()->asStringUriOnly() + << conferenceInfo->getSubject(); + }); + emit lUpdate(); +} + +QSharedPointer +ConferenceInfoList::get(std::shared_ptr conferenceInfo) const { + auto uri = Utils::coreStringToAppString(conferenceInfo->getUri()->asStringUriOnly()); + for (auto item : mList) { + auto model = item.objectCast(); + auto confUri = model->getUri(); + if (confUri == uri) { + return model; + } + } + return nullptr; +} + +QSharedPointer +ConferenceInfoList::build(const std::shared_ptr &conferenceInfo) const { + auto me = CoreModel::getInstance()->getCore()->getDefaultAccount()->getParams()->getIdentityAddress(); + qDebug() << "[CONFERENCEINFOLIST] looking for me " << me->asStringUriOnly(); + std::list> participants = conferenceInfo->getParticipantInfos(); + bool haveMe = conferenceInfo->getOrganizer()->weakEqual(me); + if (!haveMe) + haveMe = (std::find_if(participants.begin(), participants.end(), + [me](const std::shared_ptr &p) { + // qDebug() + // << "[CONFERENCEINFOLIST] participant " << p->getAddress()->asStringUriOnly(); + return me->weakEqual(p->getAddress()); + }) != participants.end()); + if (haveMe) { + auto conferenceModel = ConferenceInfoCore::create(conferenceInfo); + connect(conferenceModel.get(), &ConferenceInfoCore::removed, this, &ConferenceInfoList::lUpdate); + return conferenceModel; + } else return nullptr; +} + +void ConferenceInfoList::remove(const int &row) { + // beginRemoveRows(QModelIndex(), row, row); + auto item = mList[row].objectCast(); + emit item->lDeleteConferenceInfo(); + // endRemoveRows(); +} + +QHash ConferenceInfoList::roleNames() const { + QHash roles; + roles[Qt::DisplayRole] = "$modelData"; + roles[Qt::DisplayRole + 1] = "$sectionMonth"; + return roles; +} + +QVariant ConferenceInfoList::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 ConferenceInfoGui(mList[row].objectCast())); + } else if (role == Qt::DisplayRole + 1) { + return Utils::toDateMonthString(mList[row].objectCast()->getDateTimeUtc()); + } + return QVariant(); +} diff --git a/Linphone/core/conference/ConferenceInfoList.hpp b/Linphone/core/conference/ConferenceInfoList.hpp new file mode 100644 index 000000000..347d1b744 --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoList.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010-2020 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 CONFERENCE_INFO_LIST_H_ +#define CONFERENCE_INFO_LIST_H_ + +#include "../proxy/ListProxy.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/thread/SafeConnection.hpp" +#include + +class CoreModel; +class ConferenceInfoCore; + +class ConferenceInfoList : public ListProxy, public AbstractObject { + Q_OBJECT +public: + static QSharedPointer create(); + // Create a ConferenceInfoCore and make connections to List. + ConferenceInfoList(QObject *parent = Q_NULLPTR); + ~ConferenceInfoList(); + + void setSelf(QSharedPointer me); + + QSharedPointer get(std::shared_ptr conferenceInfo) const; + QSharedPointer build(const std::shared_ptr &conferenceInfo) const; + + void remove(const int &row); + + QHash roleNames() const override; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +signals: + void lUpdate(); + +private: + QSharedPointer> mCoreModelConnection; + DECLARE_ABSTRACT_OBJECT +}; +#endif // CONFERENCE_INFO_LIST_H_ diff --git a/Linphone/core/conference/ConferenceInfoProxy.cpp b/Linphone/core/conference/ConferenceInfoProxy.cpp new file mode 100644 index 000000000..24fb6a229 --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoProxy.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010-2020 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 "ConferenceInfoProxy.hpp" +#include "ConferenceInfoCore.hpp" +#include "ConferenceInfoGui.hpp" +#include "ConferenceInfoList.hpp" + +DEFINE_ABSTRACT_OBJECT(ConferenceInfoProxy) + +ConferenceInfoProxy::ConferenceInfoProxy(QObject *parent) : SortFilterProxy(parent) { + mList = ConferenceInfoList::create(); + setSourceModel(mList.get()); + connect(this, &ConferenceInfoProxy::searchTextChanged, [this] { invalidate(); }); +} + +ConferenceInfoProxy::~ConferenceInfoProxy() { + setSourceModel(nullptr); +} + +QString ConferenceInfoProxy::getSearchText() const { + return mSearchText; +} + +void ConferenceInfoProxy::setSearchText(const QString &search) { + mSearchText = search; + emit searchTextChanged(); +} + +bool ConferenceInfoProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { + const ConferenceInfoGui *gui = + sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent)).value(); + if (gui) { + auto ciCore = gui->getCore(); + assert(ciCore); + if (!ciCore->getSubject().contains(mSearchText)) return false; + if (ciCore->getDuration() == 0) return false; + QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); + if (mFilterType == 0) { + // auto res = ciCore->getEndDateTimeUtc() < currentDateTime; + return true; + } else if (mFilterType == 1) { + auto res = ciCore->getEndDateTimeUtc() >= currentDateTime; + return res; + // } else if (mFilterType == 2) { + // return !Utils::isMe(ciCore->getOrganizer()); + } else return mFilterType == -1; + } + return false; +} + +bool ConferenceInfoProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { + auto l = getItemAt(left.row())->getCore(); + auto r = getItemAt(right.row())->getCore(); + + return l->getDateTimeUtc() < r->getDateTimeUtc(); +} \ No newline at end of file diff --git a/Linphone/core/conference/ConferenceInfoProxy.hpp b/Linphone/core/conference/ConferenceInfoProxy.hpp new file mode 100644 index 000000000..0ff5b94c2 --- /dev/null +++ b/Linphone/core/conference/ConferenceInfoProxy.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 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 CONFERENCE_INFO_PROXY_H_ +#define CONFERENCE_INFO_PROXY_H_ + +#include "../proxy/SortFilterProxy.hpp" +#include "tool/AbstractObject.hpp" + +class ConferenceInfoList; + +class ConferenceInfoProxy : public SortFilterProxy, public AbstractObject { + + Q_OBJECT + Q_PROPERTY(QString searchText READ getSearchText WRITE setSearchText NOTIFY searchTextChanged) + +public: + ConferenceInfoProxy(QObject *parent = Q_NULLPTR); + ~ConferenceInfoProxy(); + + QString getSearchText() const; + void setSearchText(const QString &search); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + +signals: + void searchTextChanged(); + +private: + QString mSearchText; + QSharedPointer mList; + DECLARE_ABSTRACT_OBJECT +}; + +#endif // CONFERENCE_INFO_PROXY_H_ diff --git a/Linphone/core/friend/FriendCore.cpp b/Linphone/core/friend/FriendCore.cpp index e1f5db115..acb2108a8 100644 --- a/Linphone/core/friend/FriendCore.cpp +++ b/Linphone/core/friend/FriendCore.cpp @@ -459,6 +459,7 @@ void FriendCore::remove() { } void FriendCore::save() { // Save Values to model + mustBeInMainThread(getClassName() + "::save()"); if (mAddressList.size() > 0) { auto it = std::find_if(mAddressList.begin(), mAddressList.end(), [this](const QVariant &a) { return a.toMap()["address"].toString() == mDefaultAddress; @@ -472,9 +473,9 @@ void FriendCore::save() { // Save Values to model emit defaultAddressChanged(); } FriendCore *thisCopy = new FriendCore(*this); // Pointer to avoid multiple copies in lambdas - if (mFriendModel) { mFriendModelConnection->invokeToModel([this, thisCopy]() { // Copy values to avoid concurrency + mustBeInLinphoneThread(getClassName() + "::save()"); thisCopy->writeIntoModel(mFriendModel); thisCopy->deleteLater(); mFriendModelConnection->invokeToCore([this]() { saved(); }); @@ -491,18 +492,17 @@ void FriendCore::save() { // Save Values to model if (contact) break; } if (contact != nullptr) { - auto friendModel = Utils::makeQObject_ptr(contact); - friendModel->setSelf(friendModel); - thisCopy->writeIntoModel(friendModel); + mFriendModel = Utils::makeQObject_ptr(contact); + mFriendModel->setSelf(mFriendModel); + thisCopy->writeIntoModel(mFriendModel); thisCopy->deleteLater(); if (mFriendModelConnection) mFriendModelConnection->invokeToCore([this] { saved(); }); else mCoreModelConnection->invokeToCore([this] { saved(); }); } else { auto contact = core->createFriend(); - std::shared_ptr friendModel; - friendModel = Utils::makeQObject_ptr(contact); - friendModel->setSelf(friendModel); - thisCopy->writeIntoModel(friendModel); + mFriendModel = Utils::makeQObject_ptr(contact); + mFriendModel->setSelf(mFriendModel); + thisCopy->writeIntoModel(mFriendModel); thisCopy->deleteLater(); bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK); if (created) { diff --git a/Linphone/core/friend/FriendGui.cpp b/Linphone/core/friend/FriendGui.cpp index 1a81764ed..b54d7485c 100644 --- a/Linphone/core/friend/FriendGui.cpp +++ b/Linphone/core/friend/FriendGui.cpp @@ -24,6 +24,7 @@ DEFINE_ABSTRACT_OBJECT(FriendGui) FriendGui::FriendGui(QObject *parent) : QObject(parent) { + mustBeInMainThread(getClassName()); mCore = FriendCore::create(nullptr); } FriendGui::FriendGui(QSharedPointer core) { diff --git a/Linphone/core/friend/FriendInitialProxy.cpp b/Linphone/core/friend/FriendInitialProxy.cpp index e1f47d806..0f02b7264 100644 --- a/Linphone/core/friend/FriendInitialProxy.cpp +++ b/Linphone/core/friend/FriendInitialProxy.cpp @@ -21,7 +21,6 @@ #include "FriendInitialProxy.hpp" #include "FriendCore.hpp" #include "FriendGui.hpp" -#include "tool/Utils.hpp" DEFINE_ABSTRACT_OBJECT(FriendInitialProxy) diff --git a/Linphone/core/participant/ParticipantCore.cpp b/Linphone/core/participant/ParticipantCore.cpp new file mode 100644 index 000000000..2324a8ef8 --- /dev/null +++ b/Linphone/core/participant/ParticipantCore.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2021 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 + +#include "core/App.hpp" + +#include "ParticipantCore.hpp" +// #include "ParticipantDeviceList.hpp" +#include "model/participant/ParticipantModel.hpp" +#include "tool/Utils.hpp" + +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(ParticipantCore) + +QSharedPointer ParticipantCore::create(const std::shared_ptr &participant) { + auto sharedPointer = QSharedPointer(new ParticipantCore(participant), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +ParticipantCore::ParticipantCore(const std::shared_ptr &participant) : QObject(nullptr) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + mParticipantModel = Utils::makeQObject_ptr(participant); + mAdminStatus = participant->isAdmin(); + mSipAddress = Utils::coreStringToAppString(participant->getAddress()->asStringUriOnly()); + mCreationTime = QDateTime::fromSecsSinceEpoch(participant->getCreationTime()); + mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getDisplayName()); + if (mDisplayName.isEmpty()) mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getUsername()); + for (auto &device : participant->getDevices()) { + auto name = Utils::coreStringToAppString(device->getName()); + auto address = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly()); + QVariantMap map; + map.insert("name", name); + map.insert("address", address); + mParticipantDevices.append(map); + } + // App::getInstance()->mEngine->setObjectOwnership(mParticipantDevices.get(), + // QQmlEngine::CppOwnership); // Managed by QSharedPointer + // connect(this, &ParticipantCore::deviceSecurityLevelChanged, mParticipantDevices.get(), + // &ParticipantDeviceListModel::securityLevelChanged); +} + +ParticipantCore::~ParticipantCore() { + mustBeInMainThread("~" + getClassName()); +} + +void ParticipantCore::setSelf(QSharedPointer me) { + mParticipantConnection = QSharedPointer>( + new SafeConnection(me, mParticipantModel), &QObject::deleteLater); + mParticipantConnection->makeConnectToCore(&ParticipantCore::lStartInvitation, [this](const int &secs) { + QTimer::singleShot(secs * 1000, this, &ParticipantCore::onEndOfInvitation); + }); + // mParticipantConnection->makeConnectToModel(&ParticipantModel::) +} + +// FriendCore *ParticipantCore::getFriendCore() const { +// return nullptr; +// // return CoreModel::getInstance()->getContactsListModel()->findContactModelFromSipAddress(getSipAddress()).get(); +// } + +int ParticipantCore::getSecurityLevel() const { + return mSecurityLevel; +} + +int ParticipantCore::getDeviceCount() const { + return mParticipantDevices.size(); +} + +bool ParticipantCore::isMe() const { + return true; // Utils::isMe(getSipAddress()); +} + +QString ParticipantCore::getSipAddress() const { + return mSipAddress; +} +void ParticipantCore::setSipAddress(const QString &address) { + if (mSipAddress != address) { + mSipAddress = address; + emit sipAddressChanged(); + } +} + +void ParticipantCore::setDisplayName(const QString &name) { + if (mDisplayName != name) { + mDisplayName = name; + emit displayNameChanged(); + } +} + +QString ParticipantCore::getDisplayName() const { + return mDisplayName; +} + +QDateTime ParticipantCore::getCreationTime() const { + return mCreationTime; +} +void ParticipantCore::setCreationTime(const QDateTime &date) { + if (date != mCreationTime) { + mCreationTime = date; + emit creationTimeChanged(); + } +} + +bool ParticipantCore::getAdminStatus() const { + return mAdminStatus; +} + +bool ParticipantCore::isFocus() const { + return mIsFocus; +} + +void ParticipantCore::setAdminStatus(const bool &status) { + if (status != mAdminStatus) { + mAdminStatus = status; + emit adminStatusChanged(); + } +} + +void ParticipantCore::setIsFocus(const bool &focus) { + if (focus != mIsFocus) { + mIsFocus = focus; + emit isFocusChanged(); + } +} + +void ParticipantCore::setSecurityLevel(int level) { + if (level != mSecurityLevel) { + mSecurityLevel = level; + emit securityLevelChanged(); + } +} + +void ParticipantCore::onSecurityLevelChanged() { + emit securityLevelChanged(); +} +void ParticipantCore::onDeviceSecurityLevelChanged(std::shared_ptr device) { + emit deviceSecurityLevelChanged(device); +} + +// ParticipantDeviceProxyModel *ParticipantCore::getProxyDevices() { +// ParticipantDeviceProxyModel *devices = new ParticipantDeviceProxyModel(); +// devices->setParticipant(this); +// return devices; +// } + +QList ParticipantCore::getParticipantDevices() { + return mParticipantDevices; +} + +void ParticipantCore::onEndOfInvitation() { + emit invitationTimeout(this); +} diff --git a/Linphone/core/participant/ParticipantCore.hpp b/Linphone/core/participant/ParticipantCore.hpp new file mode 100644 index 000000000..52eb501b8 --- /dev/null +++ b/Linphone/core/participant/ParticipantCore.hpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021 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_CORE_H_ +#define PARTICIPANT_CORE_H_ + +#include "tool/thread/SafeConnection.hpp" +#include + +#include +#include +#include +#include +#include + +class FriendCore; +class ParticipantDeviceProxy; +class ParticipantDeviceList; +class ParticipantModel; + +class ParticipantCore : public QObject, public AbstractObject { + Q_OBJECT + + // Q_PROPERTY(FriendCore *friendCore READ getFriendCore CONSTANT) + Q_PROPERTY(QString sipAddress READ getSipAddress WRITE setSipAddress NOTIFY sipAddressChanged) + Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName NOTIFY displayNameChanged) + Q_PROPERTY(bool adminStatus READ getAdminStatus WRITE setAdminStatus NOTIFY adminStatusChanged) + Q_PROPERTY(bool isMe READ isMe CONSTANT) + Q_PROPERTY(QDateTime creationTime READ getCreationTime CONSTANT) + Q_PROPERTY(bool focus READ isFocus CONSTANT) + Q_PROPERTY(int securityLevel READ getSecurityLevel NOTIFY securityLevelChanged) + Q_PROPERTY(int deviceCount READ getDeviceCount NOTIFY deviceCountChanged) + Q_PROPERTY(QList devices READ getParticipantDevices NOTIFY deviceChanged) + + // Q_PROPERTY(bool inviting READ getInviting NOTIFY invitingChanged) + +public: + static QSharedPointer create(const std::shared_ptr &participant); + ParticipantCore(const std::shared_ptr &participant); + ~ParticipantCore(); + + void setSelf(QSharedPointer me); + + // FriendCore *getFriendCore() const; + QString getDisplayName() const; + QString getSipAddress() const; + QDateTime getCreationTime() const; + bool getAdminStatus() const; + bool isFocus() const; + int getSecurityLevel() const; + int getDeviceCount() const; + + bool isMe() const; + + void setSipAddress(const QString &address); + void setDisplayName(const QString &name); + void setCreationTime(const QDateTime &date); + void setAdminStatus(const bool &status); + void setIsFocus(const bool &focus); + void setSecurityLevel(int level); + + // Q_INVOKABLE ParticipantDeviceProxy *getProxyDevices(); + QList getParticipantDevices(); + +public slots: + void onSecurityLevelChanged(); + void onDeviceSecurityLevelChanged(std::shared_ptr device); + void onEndOfInvitation(); + +signals: + void securityLevelChanged(); + void deviceSecurityLevelChanged(std::shared_ptr device); + void sipAddressChanged(); + void updateAdminStatus( + const std::shared_ptr participant, + const bool &isAdmin); // Split in two signals in order to sequancialize execution between SDK and GUI + void adminStatusChanged(); + void isFocusChanged(); + void deviceCountChanged(); + void invitingChanged(); + void creationTimeChanged(); + void displayNameChanged(); + + void lStartInvitation(const int &secs); + + void invitationTimeout(ParticipantCore *model); + + void deviceChanged(); + +private: + std::shared_ptr mParticipantModel; + QSharedPointer> mParticipantConnection; + + // QSharedPointer mParticipantDevices; + QList mParticipantDevices; + + QString mDisplayName; + QString mSipAddress; + QDateTime mCreationTime; + bool mAdminStatus; + bool mIsFocus; + int mSecurityLevel; + + DECLARE_ABSTRACT_OBJECT +}; + +Q_DECLARE_METATYPE(QSharedPointer); + +#endif // PARTICIPANT_CORE_H_ diff --git a/Linphone/core/participant/ParticipantDeviceCore.cpp b/Linphone/core/participant/ParticipantDeviceCore.cpp new file mode 100644 index 000000000..cab93804f --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceCore.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2021 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 "ParticipantDeviceCore.hpp" +#include "core/App.hpp" +#include "tool/Utils.hpp" +#include + +DEFINE_ABSTRACT_OBJECT(ParticipantDeviceCore) + +QSharedPointer +ParticipantDeviceCore::create(std::shared_ptr device, const bool &isMe, QObject *parent) { + auto sharedPointer = + QSharedPointer(new ParticipantDeviceCore(device, isMe, parent), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return nullptr; +} + +ParticipantDeviceCore::ParticipantDeviceCore(const std::shared_ptr &device, + const bool &isMe, + QObject *parent) + : QObject(parent) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + mustBeInLinphoneThread(getClassName()); + mName = Utils::coreStringToAppString(device->getName()); + mDisplayName = Utils::coreStringToAppString(device->getAddress()->getDisplayName()); + mAddress = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly()); + mIsMuted = device->getIsMuted(); + mIsMe = isMe; + mParticipantDeviceModel = Utils::makeQObject_ptr(device); + mParticipantDeviceModel->setSelf(mParticipantDeviceModel); + mState = LinphoneEnums::fromLinphone(device->getState()); + // mCall = callModel; + // if (mCall) connect(mCall, &CallModel::statusChanged, this, &ParticipantDeviceCore::onCallStatusChanged); + mIsVideoEnabled = mParticipantDeviceModel->isVideoEnabled(); + // if (mCall && mParticipantDeviceModel) updateIsLocal(); +} + +ParticipantDeviceCore::~ParticipantDeviceCore() { + mParticipantDeviceModel->removeListener(); +} + +void ParticipantDeviceCore::setSelf(QSharedPointer me) { + mParticipantDeviceModelConnection = QSharedPointer>( + new SafeConnection(me, mParticipantDeviceModel), + &QObject::deleteLater); + mParticipantDeviceModelConnection->makeConnectToModel( + &ParticipantDeviceModel::isPausedChanged, [this](bool paused) { + mParticipantDeviceModelConnection->invokeToCore([this, paused] { setPaused(paused); }); + }); + mParticipantDeviceModelConnection->makeConnectToModel( + &ParticipantDeviceModel::isSpeakingChanged, [this](bool speaking) { + mParticipantDeviceModelConnection->invokeToCore([this, speaking] { setIsSpeaking(speaking); }); + }); + mParticipantDeviceModelConnection->makeConnectToModel(&ParticipantDeviceModel::isMutedChanged, [this](bool muted) { + mParticipantDeviceModelConnection->invokeToCore([this, muted] { setIsMuted(muted); }); + }); + mParticipantDeviceModelConnection->makeConnectToModel( + &ParticipantDeviceModel::stateChanged, [this](LinphoneEnums::ParticipantDeviceState state) { + mParticipantDeviceModelConnection->invokeToCore([this, state] { setState(state); }); + }); + mParticipantDeviceModelConnection->makeConnectToModel( + &ParticipantDeviceModel::streamCapabilityChanged, [this](linphone::StreamType) { + auto videoEnabled = mParticipantDeviceModel->isVideoEnabled(); + mParticipantDeviceModelConnection->invokeToCore([this, videoEnabled] { setIsVideoEnabled(videoEnabled); }); + }); + mParticipantDeviceModelConnection->makeConnectToModel( + &ParticipantDeviceModel::streamAvailabilityChanged, [this](linphone::StreamType) { + auto videoEnabled = mParticipantDeviceModel->isVideoEnabled(); + mParticipantDeviceModelConnection->invokeToCore([this, videoEnabled] { setIsVideoEnabled(videoEnabled); }); + }); +} + +QString ParticipantDeviceCore::getName() const { + return mName; +} + +QString ParticipantDeviceCore::getDisplayName() const { + return mDisplayName; +} + +int ParticipantDeviceCore::getSecurityLevel() const { + if (mParticipantDeviceModel) { + int security = (int)mParticipantDeviceModel->getSecurityLevel(); + return security; + } else return 0; +} + +time_t ParticipantDeviceCore::getTimeOfJoining() const { + return mParticipantDeviceModel ? mParticipantDeviceModel->getTimeOfJoining() : 0; +} + +QString ParticipantDeviceCore::getAddress() const { + return mAddress; +} + +bool ParticipantDeviceCore::getPaused() const { + return mIsPaused; +} + +bool ParticipantDeviceCore::getIsSpeaking() const { + return mIsSpeaking; +} + +bool ParticipantDeviceCore::getIsMuted() const { + return mIsMuted; +} + +LinphoneEnums::ParticipantDeviceState ParticipantDeviceCore::getState() const { + return mState; +} + +bool ParticipantDeviceCore::isVideoEnabled() const { + return mIsVideoEnabled; +} + +void ParticipantDeviceCore::setPaused(bool paused) { + if (mIsPaused != paused) { + mIsPaused = paused; + emit isPausedChanged(); + } +} + +void ParticipantDeviceCore::setIsSpeaking(bool speaking) { + if (mIsSpeaking != speaking) { + mIsSpeaking = speaking; + emit isSpeakingChanged(); + } +} + +void ParticipantDeviceCore::setIsMuted(bool muted) { + if (mIsMuted != muted) { + mIsMuted = muted; + emit isMutedChanged(); + } +} + +void ParticipantDeviceCore::setIsLocal(bool local) { + if (mIsLocal != local) { + mIsLocal = local; + emit isLocalChanged(); + } +} + +void ParticipantDeviceCore::setState(LinphoneEnums::ParticipantDeviceState state) { + if (mState != state) { + mState = state; + emit stateChanged(); + } +} + +void ParticipantDeviceCore::setIsVideoEnabled(bool enabled) { + if (mIsVideoEnabled != enabled) { + mIsVideoEnabled = enabled; + emit videoEnabledChanged(); + } +} + +bool ParticipantDeviceCore::isMe() const { + return mIsMe; +} + +bool ParticipantDeviceCore::isLocal() const { + return mIsLocal; +} + +// void ParticipantDeviceCore::updateIsLocal() { +// auto deviceAddress = mParticipantDeviceModel->getAddress(); +// auto callAddress = mCall->getConferenceSharedModel()->getConference()->getMe()->getAddress(); +// auto gruuAddress = +// CoreManager::getInstance()->getAccountSettingsModel()->findAccount(callAddress)->getContactAddress(); +// setIsLocal(deviceAddress->equal(gruuAddress)); +// } + +// void ParticipantDeviceCore::onSecurityLevelChanged(std::shared_ptr device) { +// if (!device || mParticipantDeviceModel && mParticipantDeviceModel->getAddress()->weakEqual(device)) +// emit securityLevelChanged(); +// } + +// void ParticipantDeviceCore::onCallStatusChanged() { +// if (mCall->getCall()->getState() == linphone::Call::State::StreamsRunning) { +// updateVideoEnabled(); +// } +// } + +//-------------------------------------------------------------------- +void ParticipantDeviceCore::onIsSpeakingChanged(const std::shared_ptr &participantDevice, + bool isSpeaking) { + setIsSpeaking(isSpeaking); +} +void ParticipantDeviceCore::onIsMuted(const std::shared_ptr &participantDevice, + bool isMuted) { + emit isMutedChanged(); +} +void ParticipantDeviceCore::onStateChanged(const std::shared_ptr &participantDevice, + linphone::ParticipantDevice::State state) { + switch (state) { + case linphone::ParticipantDevice::State::Joining: + break; + case linphone::ParticipantDevice::State::Present: + setPaused(false); + break; + case linphone::ParticipantDevice::State::Leaving: + break; + case linphone::ParticipantDevice::State::Left: + break; + case linphone::ParticipantDevice::State::ScheduledForJoining: + break; + case linphone::ParticipantDevice::State::ScheduledForLeaving: + break; + case linphone::ParticipantDevice::State::OnHold: + setPaused(true); + break; + case linphone::ParticipantDevice::State::Alerting: + break; + case linphone::ParticipantDevice::State::MutedByFocus: + break; + default: { + } + } + setState(LinphoneEnums::fromLinphone(state)); +} +void ParticipantDeviceCore::onStreamCapabilityChanged( + const std::shared_ptr &participantDevice, + linphone::MediaDirection direction, + linphone::StreamType streamType) { +} +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 new file mode 100644 index 000000000..e9a77a0c3 --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceCore.hpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 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_CORE_H_ +#define PARTICIPANT_DEVICE_CORE_H_ + +#include "model/participant/ParticipantDeviceModel.hpp" +#include "tool/LinphoneEnums.hpp" +#include "tool/thread/SafeConnection.hpp" +#include + +#include +#include +#include +#include + +class ParticipantDeviceCore : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(QString displayName READ getDisplayName CONSTANT) + Q_PROPERTY(QString name READ getName CONSTANT) + Q_PROPERTY(QString address READ getAddress CONSTANT) + Q_PROPERTY(int securityLevel READ getSecurityLevel NOTIFY securityLevelChanged) + Q_PROPERTY(time_t timeOfJoining READ getTimeOfJoining CONSTANT) + Q_PROPERTY(bool videoEnabled READ isVideoEnabled NOTIFY videoEnabledChanged) + Q_PROPERTY(bool isMe READ isMe CONSTANT) + Q_PROPERTY(bool isLocal READ isLocal WRITE setIsLocal NOTIFY + isLocalChanged) // Can change on call update. Not really used but it just in case as Object can be + // initialized with empty call/device. + Q_PROPERTY(bool isPaused READ getPaused WRITE setPaused NOTIFY isPausedChanged) + Q_PROPERTY(bool isSpeaking READ getIsSpeaking WRITE setIsSpeaking NOTIFY isSpeakingChanged) + Q_PROPERTY(bool isMuted READ getIsMuted NOTIFY isMutedChanged) + Q_PROPERTY(LinphoneEnums::ParticipantDeviceState state READ getState WRITE setState NOTIFY stateChanged) + +public: + // static QSharedPointer create(const std::shared_ptr &device); + static QSharedPointer + create(std::shared_ptr device, const bool &isMe = false, QObject *parent = nullptr); + + ParticipantDeviceCore(const std::shared_ptr &device, + const bool &isMe = false, + QObject *parent = nullptr); + virtual ~ParticipantDeviceCore(); + void setSelf(QSharedPointer me); + + QString getName() const; + QString getDisplayName() const; + QString getAddress() const; + int getSecurityLevel() const; + time_t getTimeOfJoining() const; + bool isVideoEnabled() const; + bool isMe() const; + bool isLocal() const; + bool getPaused() const; + bool getIsSpeaking() const; + bool getIsMuted() const; + LinphoneEnums::ParticipantDeviceState getState() const; + + std::shared_ptr getDevice(); + + void setPaused(bool paused); + void setIsSpeaking(bool speaking); + void setIsMuted(bool muted); + void setIsLocal(bool local); + void setIsVideoEnabled(bool enabled); + void setState(LinphoneEnums::ParticipantDeviceState state); + + virtual void onIsSpeakingChanged(const std::shared_ptr &participantDevice, + bool isSpeaking); + virtual void onIsMuted(const std::shared_ptr &participantDevice, bool isMuted); + virtual void onStateChanged(const std::shared_ptr &participantDevice, + linphone::ParticipantDevice::State state); + virtual void onStreamCapabilityChanged(const std::shared_ptr &participantDevice, + linphone::MediaDirection direction, + linphone::StreamType streamType); + virtual void onStreamAvailabilityChanged(const std::shared_ptr &participantDevice, + bool available, + linphone::StreamType streamType); + + // void updateIsLocal(); + + // public slots: + // void onSecurityLevelChanged(std::shared_ptr device); + // void onCallStatusChanged(); +signals: + void securityLevelChanged(); + void videoEnabledChanged(); + void isPausedChanged(); + void isSpeakingChanged(); + void isMutedChanged(); + void isLocalChanged(); + void stateChanged(); + +private: + QString mName; + QString mDisplayName; + QString mAddress; + bool mIsMe = false; + bool mIsLocal = false; + bool mIsVideoEnabled; + bool mIsMuted = false; + bool mIsPaused = false; + bool mIsSpeaking = false; + LinphoneEnums::ParticipantDeviceState mState; + + std::shared_ptr mParticipantDeviceModel; + QSharedPointer> mParticipantDeviceModelConnection; + + DECLARE_ABSTRACT_OBJECT +}; +Q_DECLARE_METATYPE(QSharedPointer) + +#endif // PARTICIPANT_CORE_H_ diff --git a/Linphone/core/participant/ParticipantDeviceList.cpp b/Linphone/core/participant/ParticipantDeviceList.cpp new file mode 100644 index 000000000..716d5720c --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceList.cpp @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2021 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 "ParticipantDeviceList.hpp" +#include "core/App.hpp" +#include "core/participant/ParticipantCore.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()); + model->setSelf(model); + 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; +} + +ParticipantDeviceList::ParticipantDeviceList(QObject *parent) { + mustBeInMainThread(getClassName()); +} + +// 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()); +} + +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); + } +} + +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() { +} diff --git a/Linphone/core/participant/ParticipantDeviceList.hpp b/Linphone/core/participant/ParticipantDeviceList.hpp new file mode 100644 index 000000000..4671e7c01 --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceList.hpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021 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_LIST_H_ +#define PARTICIPANT_DEVICE_LIST_H_ + +#include "../proxy/ListProxy.hpp" +#include "core/call/CallCore.hpp" +#include "core/participant/ParticipantDeviceCore.hpp" +#include +#include +#include + +class ParticipantDeviceList : public ListProxy, public AbstractObject { + Q_OBJECT + +public: + static QSharedPointer create(const std::shared_ptr &participant); + static QSharedPointer create(); + + ParticipantDeviceList(const std::shared_ptr &participant, QObject *parent = nullptr); + // ParticipantDeviceList(CallCore *callCore, QObject *parent = nullptr); + ParticipantDeviceList(QObject *parent = Q_NULLPTR); + ~ParticipantDeviceList(); + + 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(); + +signals: + void activeSpeakerChanged(); + void securityLevelChanged(std::shared_ptr device); + void participantSpeaking(ParticipantDeviceCore *speakingDevice); + void conferenceCreated(); + void meChanged(); + +private: + CallCore *mCallCore = nullptr; + QSharedPointer mActiveSpeaker; + // QList mActiveSpeakers;// First item is last speaker + bool mInitialized = false; + QSharedPointer> mModelConnection; + + DECLARE_ABSTRACT_OBJECT +}; +Q_DECLARE_METATYPE(QSharedPointer); + +#endif // PARTICIPANT_DEVICE_LIST_H_ diff --git a/Linphone/core/participant/ParticipantDeviceProxy.cpp b/Linphone/core/participant/ParticipantDeviceProxy.cpp new file mode 100644 index 000000000..e1635c198 --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceProxy.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021 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 "ParticipantDeviceProxy.hpp" +#include "ParticipantDeviceList.hpp" +#include "core/App.hpp" + +#include + +// ============================================================================= + +ParticipantDeviceProxy::ParticipantDeviceProxy(QObject *parent) : SortFilterProxy(parent) { + mDeleteSourceModel = true; + mList = ParticipantDeviceList::create(); + setSourceModel(mList.get()); +} + +ParticipantDeviceProxy::~ParticipantDeviceProxy() { +} + +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())*/); +} + +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::onCountChanged() { + qDebug() << "Count changed : " << getCount(); +} + +void ParticipantDeviceProxy::onParticipantSpeaking(ParticipantDeviceCore *speakingDevice) { + emit participantSpeaking(speakingDevice); +} diff --git a/Linphone/core/participant/ParticipantDeviceProxy.hpp b/Linphone/core/participant/ParticipantDeviceProxy.hpp new file mode 100644 index 000000000..436164464 --- /dev/null +++ b/Linphone/core/participant/ParticipantDeviceProxy.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 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_PROXY_MODEL_H_ +#define PARTICIPANT_DEVICE_PROXY_MODEL_H_ + +#include +// ============================================================================= +#include "../proxy/SortFilterProxy.hpp" +#include +#include +#include +#include + +class ParticipantDeviceList; +class ParticipantDeviceCore; +class ParticipantCore; +class CallModel; + +class ParticipantDeviceProxy : public SortFilterProxy { + Q_OBJECT + +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(); + + Q_INVOKABLE ParticipantDeviceCore *getAt(int row); + ParticipantDeviceCore *getActiveSpeakerModel(); + ParticipantDeviceCore *getMe() const; + + 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(); + +protected: + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + CallModel *mCallModel; + bool mShowMe = true; + + QSharedPointer mList; +}; + +#endif diff --git a/Linphone/core/participant/ParticipantGui.cpp b/Linphone/core/participant/ParticipantGui.cpp new file mode 100644 index 000000000..a2772a10d --- /dev/null +++ b/Linphone/core/participant/ParticipantGui.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 "ParticipantGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(ParticipantGui) + +ParticipantGui::ParticipantGui(QObject *parent) : QObject(parent) { + mCore = ParticipantCore::create(nullptr); +} +ParticipantGui::ParticipantGui(QSharedPointer core) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; + if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); +} + +ParticipantGui::~ParticipantGui() { + mustBeInMainThread("~" + getClassName()); +} + +ParticipantCore *ParticipantGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/participant/ParticipantGui.hpp b/Linphone/core/participant/ParticipantGui.hpp new file mode 100644 index 000000000..e357fe77c --- /dev/null +++ b/Linphone/core/participant/ParticipantGui.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_GUI_H_ +#define PARTICIPANT_GUI_H_ + +#include "ParticipantCore.hpp" +#include +#include + +class ParticipantGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(ParticipantCore *core READ getCore CONSTANT) + +public: + ParticipantGui(QSharedPointer core); + ParticipantGui(QObject *parent = nullptr); + ~ParticipantGui(); + ParticipantCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/participant/ParticipantList.cpp b/Linphone/core/participant/ParticipantList.cpp new file mode 100644 index 000000000..2cbdcafae --- /dev/null +++ b/Linphone/core/participant/ParticipantList.cpp @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2010-2020 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 "ParticipantList.hpp" +#include "core/App.hpp" +#include "core/participant/ParticipantGui.hpp" +#include "model/core/CoreModel.hpp" +#include "model/tool/ToolModel.hpp" +#include "tool/Utils.hpp" + +#include + +DEFINE_ABSTRACT_OBJECT(ParticipantList) + +QSharedPointer ParticipantList::create() { + auto model = QSharedPointer(new ParticipantList(), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + model->setSelf(model); + return model; +} + +// ParticipantList::ParticipantList(ChatRoomModel *chatRoomModel, QObject *parent) : ProxyListModel(parent) { +// if (chatRoomModel) { +// mChatRoomModel = chatRoomModel; + +// connect(mChatRoomModel, &ChatRoomModel::securityEvent, this, &ParticipantList::onSecurityEvent); +// connect(mChatRoomModel, &ChatRoomModel::conferenceJoined, this, &ParticipantList::onConferenceJoined); +// connect(mChatRoomModel, &ChatRoomModel::participantAdded, this, +// QOverload &>::of( +// &ParticipantList::onParticipantAdded)); +// connect(mChatRoomModel, &ChatRoomModel::participantRemoved, this, +// QOverload &>::of( +// &ParticipantList::onParticipantRemoved)); +// connect(mChatRoomModel, &ChatRoomModel::participantAdminStatusChanged, this, +// QOverload &>::of( +// &ParticipantList::onParticipantAdminStatusChanged)); +// connect(mChatRoomModel, &ChatRoomModel::participantDeviceAdded, this, +// &ParticipantList::onParticipantDeviceAdded); +// connect(mChatRoomModel, &ChatRoomModel::participantDeviceRemoved, this, +// &ParticipantList::onParticipantDeviceRemoved); +// connect(mChatRoomModel, &ChatRoomModel::participantRegistrationSubscriptionRequested, this, +// &ParticipantList::onParticipantRegistrationSubscriptionRequested); +// connect(mChatRoomModel, &ChatRoomModel::participantRegistrationUnsubscriptionRequested, this, +// &ParticipantList::onParticipantRegistrationUnsubscriptionRequested); + +// updateParticipants(); +// } +// } + +// ParticipantList::ParticipantList(ConferenceModel *conferenceModel, QObject *parent) : ListProxy(parent) { +// if (conferenceModel) { +// mConferenceModel = conferenceModel; + +// connect(mConferenceModel, &ConferenceModel::participantAdded, this, +// QOverload &>::of( +// &ParticipantList::onParticipantAdded)); +// connect(mConferenceModel, &ConferenceModel::participantRemoved, this, +// QOverload &>::of( +// &ParticipantList::onParticipantRemoved)); +// connect(mConferenceModel, &ConferenceModel::participantAdminStatusChanged, this, +// QOverload &>::of( +// &ParticipantList::onParticipantAdminStatusChanged)); +// connect(mConferenceModel, &ConferenceModel::conferenceStateChanged, this, +// &ParticipantList::onStateChanged); + +// updateParticipants(); +// } +// } + +ParticipantList::ParticipantList(QObject *parent) : ListProxy(parent) { +} + +ParticipantList::~ParticipantList() { + mList.clear(); + // mChatRoomModel = nullptr; + // mConferenceModel = nullptr; +} + +void ParticipantList::setSelf(QSharedPointer me) { + mModelConnection = QSharedPointer>( + new SafeConnection(me, mConferenceModel), &QObject::deleteLater); + + mModelConnection->makeConnectToCore(&ParticipantList::lUpdateParticipants, [this] { + mModelConnection->invokeToModel([this]() { + QList> *participantList = new QList>(); + mustBeInLinphoneThread(getClassName()); + std::list> participants; + participants = mConferenceModel->getMonitor()->getParticipantList(); + for (auto it : participants) { + auto model = ParticipantCore::create(it); + participantList->push_back(model); + } + mModelConnection->invokeToCore([this, participantList]() { + mustBeInMainThread(getClassName()); + resetData(); + add(*participantList); + delete participantList; + }); + }); + }); + + mModelConnection->makeConnectToModel(&ConferenceModel::participantAdded, &ParticipantList::lUpdateParticipants); + mModelConnection->makeConnectToModel(&ConferenceModel::participantRemoved, &ParticipantList::lUpdateParticipants); + emit lUpdateParticipants(); +} + +QVariant ParticipantList::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 ParticipantGui(mList[row].objectCast())); + } + return QVariant(); +} + +std::list> ParticipantList::getParticipants() const { + std::list> participants; + for (auto participant : mList) { + participants.push_back(ToolModel::interpretUrl(participant.objectCast()->getSipAddress())); + } + return participants; +} + +QString ParticipantList::addressesToString() const { + QStringList txt; + for (auto item : mList) { + auto participant = item.objectCast(); + if (participant) { + + // chat room yet. + txt << participant->getSipAddress(); + // txt << Utils::toDisplayString(Utils::coreStringToAppString(address->asStringUriOnly()), + // CoreModel::getInstance()->getSettingsModel()->getSipDisplayMode()); + } + } + if (txt.size() > 0) txt.removeFirst(); // Remove me + return txt.join(", "); +} + +QString ParticipantList::displayNamesToString() const { + QStringList txt; + for (auto item : mList) { + auto participant = item.objectCast(); + if (participant) { + QString displayName = participant->getSipAddress(); + if (displayName != "") txt << displayName; + } + } + if (txt.size() > 0) txt.removeFirst(); // Remove me + return txt.join(", "); +} + +QString ParticipantList::usernamesToString() const { + QStringList txt; + for (auto item : mList) { + auto participant = item.objectCast(); + if (participant) { + auto username = participant->getDisplayName(); + txt << username; + } + } + if (txt.size() > 0) txt.removeFirst(); // Remove me + return txt.join(", "); +} + +bool ParticipantList::contains(const QString &address) const { + auto testAddress = ToolModel::interpretUrl(address); + bool exists = false; + for (auto itParticipant = mList.begin(); !exists && itParticipant != mList.end(); ++itParticipant) + exists = testAddress->weakEqual( + ToolModel::interpretUrl(itParticipant->objectCast()->getSipAddress())); + return exists; +} + +// void ParticipantList::updateParticipants() { +// if (/*mChatRoomModel ||*/ mConferenceModel) { +// bool changed = false; +// mConferenceModel->getMonitor()->getParticipantList(); +// // auto dbParticipants = (/*mChatRoomModel ? mChatRoomModel->getParticipants() :*/ mConferenceModel->get()); +// // Remove left participants +// auto itParticipant = mList.begin(); +// while (itParticipant != mList.end()) { +// auto itDbParticipant = dbParticipants.begin(); +// while ( +// itDbParticipant != dbParticipants.end() && +// (itParticipant->objectCast()->getParticipant() && +// !(*itDbParticipant) +// ->getAddress() +// ->weakEqual(itParticipant->objectCast()->getParticipant()->getAddress()) || +// !itParticipant->objectCast()->getParticipant() && +// !(*itDbParticipant) +// ->getAddress() +// ->weakEqual( +// Utils::interpretUrl(itParticipant->objectCast()->getSipAddress())))) { +// ++itDbParticipant; +// } +// if (itDbParticipant == dbParticipants.end()) { +// int row = itParticipant - mList.begin(); +// if (!changed) emit layoutAboutToBeChanged(); +// beginRemoveRows(QModelIndex(), row, row); +// itParticipant = mList.erase(itParticipant); +// endRemoveRows(); +// changed = true; +// } else ++itParticipant; +// } +// // Add new +// for (auto dbParticipant : dbParticipants) { +// auto itParticipant = mList.begin(); +// while (itParticipant != mList.end() && +// ((itParticipant->objectCast()->getParticipant() && +// !dbParticipant->getAddress()->weakEqual( +// itParticipant->objectCast()->getParticipant()->getAddress())) + +// || (!itParticipant->objectCast()->getParticipant() && +// !dbParticipant->getAddress()->weakEqual( +// Utils::interpretUrl(itParticipant->objectCast()->getSipAddress()))))) { +// ++itParticipant; +// } +// if (itParticipant == mList.end()) { +// auto participant = QSharedPointer::create(dbParticipant); +// add(participant); +// changed = true; +// } else if (!itParticipant->objectCast()->getParticipant() || +// itParticipant->objectCast()->getParticipant() != dbParticipant) { +// itParticipant->objectCast()->setParticipant(dbParticipant); +// changed = true; +// } +// } +// if (changed) { +// emit layoutChanged(); +// emit participantsChanged(); +// emit countChanged(); +// } +// } +// } + +// void ParticipantList::add(QSharedPointer participant) { +// int row = mList.count(); +// connect(this, &ParticipantList::deviceSecurityLevelChanged, participant.get(), +// &ParticipantCore::onDeviceSecurityLevelChanged); +// connect(this, &ParticipantList::securityLevelChanged, participant.get(), &ParticipantCore::onSecurityLevelChanged); +// connect(participant.get(), &ParticipantCore::updateAdminStatus, this, &ParticipantList::setAdminStatus); +// ProxyListModel::add(participant); +// emit participantsChanged(); +// } + +// void ParticipantList::add(const std::shared_ptr &participant) { +// updateParticipants(); +// } + +// void ParticipantList::add(const std::shared_ptr &participantAddress) { +// add((mChatRoomModel ? mChatRoomModel->getChatRoom()->findParticipant(participantAddress->clone()) +// : mConferenceModel->getConference()->findParticipant(participantAddress))); +// } + +void ParticipantList::remove(ParticipantCore *participant) { + QString address = participant->getSipAddress(); + int index = 0; + bool found = false; + auto itParticipant = mList.begin(); + while (!found && itParticipant != mList.end()) { + if (itParticipant->objectCast()->getSipAddress() == address) found = true; + else { + ++itParticipant; + ++index; + } + } + if (found) { + mConferenceModel->removeParticipant(ToolModel::interpretUrl(address)); + } +} + +// const QSharedPointer +// ParticipantList::getParticipant(const std::shared_ptr &address) const { +// if (address) { +// auto itParticipant = +// std::find_if(mList.begin(), mList.end(), [address](const QSharedPointer &participant) { +// return +// participant.objectCast()->getParticipant()->getAddress()->weakEqual(address); +// }); +// if (itParticipant == mList.end()) return nullptr; +// else return itParticipant->objectCast(); +// } else return nullptr; +// } +// const QSharedPointer +// ParticipantList::getParticipant(const std::shared_ptr &pParticipant) const { +// if (pParticipant) { +// auto itParticipant = +// std::find_if(mList.begin(), mList.end(), [pParticipant](const QSharedPointer &participant) { +// return participant.objectCast()->getParticipant() == pParticipant; +// }); +// if (itParticipant == mList.end()) return nullptr; +// else return itParticipant->objectCast(); +// } else return nullptr; +// } + +//------------------------------------------------------------- + +void ParticipantList::setAdminStatus(const std::shared_ptr participant, const bool &isAdmin) { + // if (mChatRoomModel) mChatRoomModel->getChatRoom()->setParticipantAdminStatus(participant, isAdmin); + // if (mConferenceModel) mConferenceModel->getConference()->setParticipantAdminStatus(participant, isAdmin); +} + +void ParticipantList::onSecurityEvent(const std::shared_ptr &eventLog) { + auto address = eventLog->getParticipantAddress(); + if (address) { + // auto participant = getParticipant(address); + // if (participant) { + // emit participant->securityLevelChanged(); + // } + } else { + address = eventLog->getDeviceAddress(); + // Looping on all participant ensure to get all devices. Can be optimized if Device address is unique : Gain + // 2n operations. + if (address) emit deviceSecurityLevelChanged(address); + } +} + +void ParticipantList::onConferenceJoined() { + // updateParticipants(); +} + +void ParticipantList::onParticipantAdded(const std::shared_ptr &eventLog) { + qDebug() << "onParticipantAdded event: " << eventLog->getParticipantAddress()->asString().c_str(); + // add(eventLog->getParticipantAddress()); +} + +void ParticipantList::onParticipantAdded(const std::shared_ptr &participant) { + qDebug() << "onParticipantAdded part: " << participant->getAddress()->asString().c_str(); + // add(participant); +} + +void ParticipantList::onParticipantAdded(const std::shared_ptr &address) { + qDebug() << "onParticipantAdded addr: " << address->asString().c_str(); + // add(address); +} + +void ParticipantList::onParticipantRemoved(const std::shared_ptr &eventLog) { + onParticipantRemoved(eventLog->getParticipantAddress()); +} + +void ParticipantList::onParticipantRemoved(const std::shared_ptr &participant) { + // auto p = getParticipant(participant); + // if (p) remove(p.get()); +} + +void ParticipantList::onParticipantRemoved(const std::shared_ptr &address) { + // auto participant = getParticipant(address); + // if (participant) remove(participant.get()); +} + +void ParticipantList::onParticipantAdminStatusChanged(const std::shared_ptr &eventLog) { + onParticipantAdminStatusChanged(eventLog->getParticipantAddress()); +} +void ParticipantList::onParticipantAdminStatusChanged(const std::shared_ptr &participant) { + // auto p = getParticipant(participant); + // if (participant) emit p->adminStatusChanged(); // Request to participant to update its status from its data +} +void ParticipantList::onParticipantAdminStatusChanged(const std::shared_ptr &address) { + // auto participant = getParticipant(address); + // if (participant) + // emit participant->adminStatusChanged(); // Request to participant to update its status from its data +} +void ParticipantList::onParticipantDeviceAdded(const std::shared_ptr &eventLog) { + // auto participant = getParticipant(eventLog->getParticipantAddress()); + // if (participant) { + // emit participant->deviceCountChanged(); + // } +} +void ParticipantList::onParticipantDeviceRemoved(const std::shared_ptr &eventLog) { + // auto participant = getParticipant(eventLog->getParticipantAddress()); + // if (participant) { + // emit participant->deviceCountChanged(); + // } +} +void ParticipantList::onParticipantRegistrationSubscriptionRequested( + const std::shared_ptr &participantAddress) { +} +void ParticipantList::onParticipantRegistrationUnsubscriptionRequested( + const std::shared_ptr &participantAddress) { +} + +void ParticipantList::onStateChanged() { + // if (mConferenceModel) { + // if (mConferenceModel->getConference()->getState() == linphone::Conference::State::Created) { + // updateParticipants(); + // } + // } +} diff --git a/Linphone/core/participant/ParticipantList.hpp b/Linphone/core/participant/ParticipantList.hpp new file mode 100644 index 000000000..898356c9b --- /dev/null +++ b/Linphone/core/participant/ParticipantList.hpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2010-2020 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_LIST_H_ +#define PARTICIPANT_LIST_H_ + +#include "../proxy/ListProxy.hpp" +#include "core/participant/ParticipantCore.hpp" +#include "model/conference/ConferenceModel.hpp" + +class ConferenceModel; + +// ============================================================================= + +class ParticipantList : public ListProxy, public AbstractObject { + Q_OBJECT +public: + static QSharedPointer create(); + + // ParticipantList(ChatRoomModel *chatRoomModel, QObject *parent = Q_NULLPTR); + // ParticipantList(ConferenceModel *conferenceModel, QObject *parent = Q_NULLPTR); + ParticipantList(QObject *parent = Q_NULLPTR); + virtual ~ParticipantList(); + + void setSelf(QSharedPointer me); + + // Q_PROPERTY(ChatRoomModel *chatRoomModel READ getChatRoomModel CONSTANT) + Q_PROPERTY(QString addressesToString READ addressesToString NOTIFY participantsChanged) + Q_PROPERTY(QString displayNamesToString READ displayNamesToString NOTIFY participantsChanged) + Q_PROPERTY(QString usernamesToString READ usernamesToString NOTIFY participantsChanged) + + void reset(); + // void updateParticipants(); // Update list from Chat Room + // const QSharedPointer + // getParticipant(const std::shared_ptr &address) const; + // const QSharedPointer const QSharedPointer + // getParticipant(const std::shared_ptr &participant) const; + + Q_INVOKABLE void remove(ParticipantCore *participant); + std::list> getParticipants() const; + + Q_INVOKABLE QString addressesToString() const; + Q_INVOKABLE QString displayNamesToString() const; + Q_INVOKABLE QString usernamesToString() const; + + bool contains(const QString &address) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void setAdminStatus(const std::shared_ptr participant, const bool &isAdmin); + + void onSecurityEvent(const std::shared_ptr &eventLog); + void onConferenceJoined(); + void onParticipantAdded(const std::shared_ptr &participant); + void onParticipantAdded(const std::shared_ptr &eventLog); + void onParticipantAdded(const std::shared_ptr &address); + void onParticipantRemoved(const std::shared_ptr &participant); + void onParticipantRemoved(const std::shared_ptr &eventLog); + void onParticipantRemoved(const std::shared_ptr &address); + void onParticipantAdminStatusChanged(const std::shared_ptr &participant); + void onParticipantAdminStatusChanged(const std::shared_ptr &eventLog); + void onParticipantAdminStatusChanged(const std::shared_ptr &address); + void onParticipantDeviceAdded(const std::shared_ptr &eventLog); + void onParticipantDeviceRemoved(const std::shared_ptr &eventLog); + void + onParticipantRegistrationSubscriptionRequested(const std::shared_ptr &participantAddress); + void onParticipantRegistrationUnsubscriptionRequested( + const std::shared_ptr &participantAddress); + void onStateChanged(); + +signals: + void securityLevelChanged(); + void deviceSecurityLevelChanged(std::shared_ptr device); + void participantsChanged(); + + void lUpdateParticipants(); + +private: + std::shared_ptr mConferenceModel; + QSharedPointer> mModelConnection; + + // ChatRoomModel *mChatRoomModel = nullptr; + // ConferenceCore *mConferenceCore = nullptr; + + DECLARE_ABSTRACT_OBJECT +}; +Q_DECLARE_METATYPE(QSharedPointer); +#endif // PARTICIPANT_LIST_H_ diff --git a/Linphone/core/participant/ParticipantProxy.cpp b/Linphone/core/participant/ParticipantProxy.cpp new file mode 100644 index 000000000..f01c9005d --- /dev/null +++ b/Linphone/core/participant/ParticipantProxy.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2010-2020 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 "ParticipantProxy.hpp" + +// #include "core/conference/ConferenceCore.hpp" +#include "model/core/CoreModel.hpp" +#include "tool/Utils.hpp" + +#include "ParticipantList.hpp" +#include "core/participant/ParticipantCore.hpp" + +#include + +// ============================================================================= + +// ----------------------------------------------------------------------------- + +ParticipantProxy::ParticipantProxy(QObject *parent) : SortFilterProxy(parent) { + mList = ParticipantList::create(); + setSourceModel(mList.get()); + connect(this, &ParticipantProxy::chatRoomModelChanged, this, &ParticipantProxy::countChanged); + connect(this, &ParticipantProxy::conferenceModelChanged, this, &ParticipantProxy::countChanged); +} + +ParticipantProxy::~ParticipantProxy() { + setSourceModel(nullptr); +} + +// ChatRoomModel *ParticipantProxy::getChatRoomModel() const { +// return mChatRoomModel; +// } + +// ConferenceModel *ParticipantProxy::getConferenceModel() const { +// return mConferenceModel; +// } + +QStringList ParticipantProxy::getSipAddresses() const { + QStringList participants; + for (int i = 0; i < mList->rowCount(); ++i) + participants << mList->getAt(i)->getSipAddress(); + return participants; +} + +QVariantList ParticipantProxy::getParticipants() const { + QVariantList participants; + ParticipantList *list = qobject_cast(sourceModel()); + for (int i = 0; i < list->rowCount(); ++i) + participants << QVariant::fromValue(list->getAt(i).get()); + return participants; +} + +bool ParticipantProxy::getShowMe() const { + return mShowMe; +} + +// ----------------------------------------------------------------------------- + +// void ParticipantProxy::setChatRoomModel(ChatRoomModel *chatRoomModel) { +// if (!mChatRoomModel || mChatRoomModel != chatRoomModel) { +// mChatRoomModel = chatRoomModel; +// if (mChatRoomModel) { +// auto participants = mChatRoomModel->getParticipantList(); +// connect(participants, &ParticipantList::countChanged, this, &ParticipantProxy::countChanged); +// setSourceModel(participants); +// emit participantListChanged(); +// for (int i = 0; i < participants->getCount(); ++i) { +// auto participant = participants->getAt(i); +// connect(participant.get(), &ParticipantCore::invitationTimeout, this, &ParticipantProxy::removeModel); +// emit addressAdded(participant->getSipAddress()); +// } +// } else if (!sourceModel()) { +// auto model = new ParticipantList((ChatRoomModel *)nullptr, this); +// connect(model, &ParticipantList::countChanged, this, &ParticipantProxy::countChanged); +// setSourceModel(model); +// emit participantListChanged(); +// } +// sort(0); +// emit chatRoomModelChanged(); +// } +// } + +void ParticipantProxy::setConferenceModel(ConferenceModel *conferenceModel) { + // if (!mConferenceModel || mConferenceModel != conferenceModel) { + // mConferenceModel = conferenceModel; + // if (mConferenceModel) { + // auto participants = mConferenceModel->getParticipantList(); + // connect(participants, &ParticipantList::countChanged, this, &ParticipantProxy::countChanged); + // setSourceModel(participants); + // emit participantListChanged(); + // for (int i = 0; i < participants->getCount(); ++i) { + // auto participant = participants->getAt(i); + // connect(participant.get(), &ParticipantCore::invitationTimeout, this, &ParticipantProxy::removeModel); + // emit addressAdded(participant->getSipAddress()); + // } + // } else if (!sourceModel()) { + // auto model = new ParticipantList((ConferenceModel *)nullptr, this); + // connect(model, &ParticipantList::countChanged, this, &ParticipantProxy::countChanged); + // setSourceModel(model); + // emit participantListChanged(); + // } + // sort(0); + // emit conferenceModelChanged(); + // } +} + +void ParticipantProxy::setAddresses(ConferenceInfoModel *conferenceInfoModel) { + // if (conferenceInfoModel && conferenceInfoModel->getConferenceInfo()) + // for (auto address : conferenceInfoModel->getConferenceInfo()->getParticipants()) + // addAddress(QString::fromStdString(address->asString())); +} + +void ParticipantProxy::setShowMe(const bool &show) { + if (mShowMe != show) { + mShowMe = show; + emit showMeChanged(); + invalidate(); + } +} + +// void ParticipantProxy::addAddress(const QString &address) { +// if (!participantsModel->contains(address)) { +// QSharedPointer participant = QSharedPointer::create(nullptr); +// connect(participant.get(), &ParticipantCore::invitationTimeout, this, &ParticipantProxy::removeModel); +// participant->setSipAddress(address); +// participantsModel->add(participant); +// if (mChatRoomModel && mChatRoomModel->getChatRoom()) { // Invite and wait for its creation +// participant->startInvitation(); +// mChatRoomModel->getChatRoom()->addParticipant(Utils::interpretUrl(address)); +// } +// if (mConferenceModel && mConferenceModel->getConference()) { +// auto addressToInvite = Utils::interpretUrl(address); +// std::list> runningCallsToAdd; +// auto currentCalls = CoreManager::getInstance()->getCore()->getCalls(); +// auto haveCall = std::find_if(currentCalls.begin(), currentCalls.end(), +// [addressToInvite](const std::shared_ptr &call) { +// return call->getRemoteAddress()->weakEqual(addressToInvite); +// }); +// participant->startInvitation(); +// if (haveCall == currentCalls.end()) mConferenceModel->getConference()->addParticipant(addressToInvite); +// else { +// runningCallsToAdd.push_back(*haveCall); +// mConferenceModel->getConference()->addParticipants(runningCallsToAdd); +// } +// /* +// std::list> addressesToInvite; +// addressesToInvite.push_back(addressToInvite); +// auto callParameters = +// CoreManager::getInstance()->getCore()->createCallParams(mConferenceModel->getConference()->getCall()); +// mConferenceModel->getConference()->inviteParticipants(addressesToInvite, callParameters);*/ +// } +// emit countChanged(); +// emit addressAdded(address); +// } +// } + +void ParticipantProxy::removeParticipant(ParticipantCore *participant) { + if (participant) { + mList->remove(participant); + } +} + +// ----------------------------------------------------------------------------- + +bool ParticipantProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { + if (mShowMe) return true; + else { + const ParticipantCore *a = + sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent)).value(); + return !a->isMe(); + } +} + +bool ParticipantProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { + const ParticipantCore *a = sourceModel()->data(left).value(); + const ParticipantCore *b = sourceModel()->data(right).value(); + + return a->getCreationTime() > b->getCreationTime() || b->isMe(); +} diff --git a/Linphone/core/participant/ParticipantProxy.hpp b/Linphone/core/participant/ParticipantProxy.hpp new file mode 100644 index 000000000..a49ae7328 --- /dev/null +++ b/Linphone/core/participant/ParticipantProxy.hpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 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_PROXY_H_ +#define PARTICIPANT_PROXY_H_ + +#include "../proxy/SortFilterProxy.hpp" +#include + +class ParticipantCore; +class ChatRoomModel; +class ParticipantList; +class ConferenceModel; +class ConferenceInfoModel; +// ============================================================================= + +class QWindow; + +class ParticipantProxy : public SortFilterProxy { + + Q_OBJECT + + Q_PROPERTY(bool showMe READ getShowMe WRITE setShowMe NOTIFY showMeChanged) + +public: + ParticipantProxy(QObject *parent = Q_NULLPTR); + ~ParticipantProxy(); + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + // ChatRoomModel *getChatRoomModel() const; + // ConferenceModel *getConferenceModel() const; + Q_INVOKABLE QStringList getSipAddresses() const; + Q_INVOKABLE QVariantList getParticipants() const; + bool getShowMe() const; + + // void setChatRoomModel(ChatRoomModel *chatRoomModel); + void setConferenceModel(ConferenceModel *conferenceModel); + void setShowMe(const bool &show); + + // Q_INVOKABLE void addAddress(const QString &address); + Q_INVOKABLE void removeParticipant(ParticipantCore *participant); + Q_INVOKABLE void setAddresses(ConferenceInfoModel *conferenceInfoModel); + +signals: + void chatRoomModelChanged(); + void conferenceModelChanged(); + void participantListChanged(); + void countChanged(); + void showMeChanged(); + void addressAdded(QString sipAddress); + void addressRemoved(QString sipAddress); + +private: + // ChatRoomModel *mChatRoomModel = nullptr; + // ConferenceModel *mConferenceModel = nullptr; + bool mShowMe = true; + QSharedPointer mList; +}; + +#endif // PARTICIPANT_PROXY_H_ diff --git a/Linphone/core/timezone/TimeZone.cpp b/Linphone/core/timezone/TimeZone.cpp new file mode 100644 index 000000000..30677317a --- /dev/null +++ b/Linphone/core/timezone/TimeZone.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 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 "TimeZone.hpp" + +#include "core/App.hpp" + +#include + +// ============================================================================= + +TimeZoneModel::TimeZoneModel(const QTimeZone &timeZone, QObject *parent) : QObject(parent) { + App::getInstance()->mEngine->setObjectOwnership( + this, QQmlEngine::CppOwnership); // Avoid QML to destroy it when passing by Q_INVOKABLE + mTimeZone = timeZone; +} + +TimeZoneModel::~TimeZoneModel() { +} + +QTimeZone TimeZoneModel::getTimeZone() const { + return mTimeZone; +} + +int TimeZoneModel::getOffsetFromUtc() const { + return mTimeZone.offsetFromUtc(QDateTime::currentDateTime()); +} + +int TimeZoneModel::getStandardTimeOffset() const { + return mTimeZone.standardTimeOffset(QDateTime::currentDateTime()); +} + +QString TimeZoneModel::getCountryName() const { + return QLocale::countryToString(mTimeZone.country()); +} + +QString TimeZoneModel::getDisplayName() const { + return mTimeZone.displayName(QTimeZone::TimeType::GenericTime, QTimeZone::NameType::LongName); +} \ No newline at end of file diff --git a/Linphone/core/timezone/TimeZone.hpp b/Linphone/core/timezone/TimeZone.hpp new file mode 100644 index 000000000..81b750e51 --- /dev/null +++ b/Linphone/core/timezone/TimeZone.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 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 TIME_ZONE_MODEL_H_ +#define TIME_ZONE_MODEL_H_ + +#include +#include + +#include + +// ============================================================================= + +class TimeZoneModel : public QObject { + Q_OBJECT + Q_PROPERTY(QTimeZone timezone MEMBER mTimeZone CONSTANT) + Q_PROPERTY(int offsetFromUtc READ getOffsetFromUtc CONSTANT) + Q_PROPERTY(int standardTimeOffset READ getStandardTimeOffset CONSTANT) + Q_PROPERTY(QString countryName READ getCountryName CONSTANT) + Q_PROPERTY(QString displayName READ getDisplayName CONSTANT) + +public: + TimeZoneModel(const QTimeZone &timeZone, QObject *parent = nullptr); + virtual ~TimeZoneModel(); + + QTimeZone getTimeZone() const; + int getOffsetFromUtc() const; + int getStandardTimeOffset() const; + QString getCountryName() const; + QString getDisplayName() const; + +private: + QTimeZone mTimeZone; +}; +Q_DECLARE_METATYPE(TimeZoneModel *); +#endif diff --git a/Linphone/core/timezone/TimeZoneList.cpp b/Linphone/core/timezone/TimeZoneList.cpp new file mode 100644 index 000000000..b3da8ea81 --- /dev/null +++ b/Linphone/core/timezone/TimeZoneList.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 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 "TimeZoneList.hpp" +#include "core/App.hpp" + +#include + +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(TimeZoneList) + +using namespace std; + +QSharedPointer TimeZoneList::create() { + auto model = QSharedPointer(new TimeZoneList(), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + return model; +} + +TimeZoneList::TimeZoneList(QObject *parent) : ListProxy(parent) { + initTimeZones(); +} + +TimeZoneList::~TimeZoneList() { + mustBeInMainThread("~" + getClassName()); +} +// ----------------------------------------------------------------------------- + +void TimeZoneList::initTimeZones() { + resetData(); + for (auto id : QTimeZone::availableTimeZoneIds()) { + auto model = QSharedPointer::create(QTimeZone(id)); + if (std::find_if(mList.begin(), mList.end(), [id](const QSharedPointer &a) { + return a.objectCast()->getTimeZone() == QTimeZone(id); + }) == mList.end()) { + if (model->getCountryName().toUpper() != "DEFAULT") { + add(model); + } + } + } +} + +QHash TimeZoneList::roleNames() const { + QHash roles; + roles[Qt::DisplayRole] = "modelData"; + roles[Qt::DisplayRole + 1] = "timeZoneModel"; + return roles; +} + +QVariant TimeZoneList::data(const QModelIndex &index, int role) const { + int row = index.row(); + + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + auto timeZoneModel = getAt(row); + if (!timeZoneModel) return QVariant(); + if (role == Qt::DisplayRole) { + int offset = timeZoneModel->getStandardTimeOffset() / 3600; + int absOffset = std::abs(offset); + + return QStringLiteral("(GMT%1%2%3:00) %4 %5") + .arg(offset >= 0 ? "+" : "-") + .arg(absOffset < 10 ? "0" : "") + .arg(absOffset) + .arg(timeZoneModel->getCountryName()) + .arg(timeZoneModel->getTimeZone().comment().isEmpty() ? "" + : (" - " + timeZoneModel->getTimeZone().comment())); + } else { + return QVariant::fromValue(timeZoneModel.get()); + } + + return QVariant(); +} + +int TimeZoneList::get(const QTimeZone &timeZone) const { + auto it = find_if(mList.cbegin(), mList.cend(), [&timeZone](QSharedPointer item) { + return item.objectCast()->getTimeZone() == timeZone; + }); + if (it == mList.cend()) { + auto today = QDateTime::currentDateTime(); + it = find_if(mList.cbegin(), mList.cend(), [&timeZone, today](QSharedPointer item) { + auto tz = item.objectCast()->getTimeZone(); + return (timeZone.country() == QLocale::AnyCountry || tz.country() == timeZone.country()) && + tz.standardTimeOffset(today) == timeZone.standardTimeOffset(today); + }); + } + return it != mList.cend() ? int(distance(mList.cbegin(), it)) : 0; +} diff --git a/Linphone/core/timezone/TimeZoneList.hpp b/Linphone/core/timezone/TimeZoneList.hpp new file mode 100644 index 000000000..c2b54f824 --- /dev/null +++ b/Linphone/core/timezone/TimeZoneList.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 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 TIME_ZONE_LIST_MODEL_H_ +#define TIME_ZONE_LIST_MODEL_H_ + +#include "../proxy/ListProxy.hpp" +#include "core/timezone/TimeZone.hpp" +#include "tool/AbstractObject.hpp" +// ============================================================================= + +class TimeZoneList : public ListProxy, public AbstractObject { + Q_OBJECT + +public: + static QSharedPointer create(); + + TimeZoneList(QObject *parent = Q_NULLPTR); + ~TimeZoneList(); + + void initTimeZones(); + int get(const QTimeZone &timeZone = QTimeZone::systemTimeZone()) const; + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private: + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/timezone/TimeZoneProxy.cpp b/Linphone/core/timezone/TimeZoneProxy.cpp new file mode 100644 index 000000000..c8e18a86d --- /dev/null +++ b/Linphone/core/timezone/TimeZoneProxy.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 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 "TimeZoneProxy.hpp" +#include "TimeZoneList.hpp" +#include "core/timezone/TimeZone.hpp" + +// ----------------------------------------------------------------------------- + +TimeZoneProxy::TimeZoneProxy(QObject *parent) : SortFilterProxy(parent) { + mDeleteSourceModel = true; + mList = TimeZoneList::create(); + setSourceModel(mList.get()); + sort(0); +} + +// ----------------------------------------------------------------------------- + +bool TimeZoneProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { + auto test = sourceModel()->data(left); + auto l = getItemAt(left.row()); + auto r = getItemAt(right.row()); + if (!l || !r) return true; + auto timeA = l->getStandardTimeOffset() / 3600; + auto timeB = r->getStandardTimeOffset() / 3600; + + return timeA < timeB || (timeA == timeB && l->getCountryName() < r->getCountryName()); +} + +int TimeZoneProxy::getIndex(TimeZoneModel *model) const { + int index = 0; + index = mList->get(model ? model->getTimeZone() : QTimeZone::systemTimeZone()); + return mapFromSource(mList->index(index, 0)).row(); +} diff --git a/Linphone/core/timezone/TimeZoneProxy.hpp b/Linphone/core/timezone/TimeZoneProxy.hpp new file mode 100644 index 000000000..dbd7d294b --- /dev/null +++ b/Linphone/core/timezone/TimeZoneProxy.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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 TIME_ZONE_PROXY_MODEL_H_ +#define TIME_ZONE_PROXY_MODEL_H_ + +#include "../proxy/SortFilterProxy.hpp" + +// ============================================================================= + +class TimeZoneModel; +class TimeZoneList; + +class TimeZoneProxy : public SortFilterProxy { + Q_OBJECT +public: + TimeZoneProxy(QObject *parent = Q_NULLPTR); + Q_PROPERTY(int defaultIndex READ getIndex CONSTANT) + + Q_INVOKABLE int getIndex(TimeZoneModel *model = nullptr) const; + +protected: + QSharedPointer mList; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + +#endif diff --git a/Linphone/data/image/note.svg b/Linphone/data/image/note.svg new file mode 100644 index 000000000..a175c6ca2 --- /dev/null +++ b/Linphone/data/image/note.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/smiley-sad.svg b/Linphone/data/image/smiley-sad.svg new file mode 100644 index 000000000..8c61c87c5 --- /dev/null +++ b/Linphone/data/image/smiley-sad.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/smiley.svg b/Linphone/data/image/smiley.svg index cc0711829..8126d53e1 100644 --- a/Linphone/data/image/smiley.svg +++ b/Linphone/data/image/smiley.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/Linphone/data/image/user-rectangle.svg b/Linphone/data/image/user-rectangle.svg new file mode 100644 index 000000000..157097ebd --- /dev/null +++ b/Linphone/data/image/user-rectangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index c4f27bdfe..8f06d6128 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -5,6 +5,13 @@ list(APPEND _LINPHONEAPP_SOURCES model/call/CallModel.cpp model/call-history/CallHistoryModel.cpp + + model/conference/ConferenceInfoModel.cpp + model/conference/ConferenceModel.cpp + model/conference/ConferenceSchedulerModel.cpp + + model/participant/ParticipantDeviceModel.cpp + model/participant/ParticipantModel.cpp model/core/CoreModel.cpp diff --git a/Linphone/model/call-history/CallHistoryModel.cpp b/Linphone/model/call-history/CallHistoryModel.cpp index 734308278..a76d34164 100644 --- a/Linphone/model/call-history/CallHistoryModel.cpp +++ b/Linphone/model/call-history/CallHistoryModel.cpp @@ -23,7 +23,6 @@ #include #include "model/core/CoreModel.hpp" -#include "tool/Utils.hpp" DEFINE_ABSTRACT_OBJECT(CallHistoryModel) @@ -37,5 +36,6 @@ CallHistoryModel::~CallHistoryModel() { } void CallHistoryModel::removeCallHistory() { + mustBeInLinphoneThread(getClassName() + "::removeCallHistory"); CoreModel::getInstance()->getCore()->removeCallLog(callLog); } \ No newline at end of file diff --git a/Linphone/model/conference/ConferenceInfoModel.cpp b/Linphone/model/conference/ConferenceInfoModel.cpp new file mode 100644 index 000000000..f21e3a4cf --- /dev/null +++ b/Linphone/model/conference/ConferenceInfoModel.cpp @@ -0,0 +1,145 @@ +/* + * 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 "ConferenceInfoModel.hpp" + +#include + +#include "core/participant/ParticipantList.hpp" +#include "core/path/Paths.hpp" +#include "model/core/CoreModel.hpp" +#include "model/tool/ToolModel.hpp" +#include "tool/Utils.hpp" + +DEFINE_ABSTRACT_OBJECT(ConferenceInfoModel) + +ConferenceInfoModel::ConferenceInfoModel(const std::shared_ptr &conferenceInfo, + QObject *parent) + : mConferenceInfo(conferenceInfo) { + mustBeInLinphoneThread(getClassName()); +} + +ConferenceInfoModel::~ConferenceInfoModel() { + mustBeInLinphoneThread("~" + getClassName()); + if (mConferenceSchedulerModel) mConferenceSchedulerModel->removeListener(); +} + +void ConferenceInfoModel::createConferenceScheduler() { + mustBeInLinphoneThread(getClassName() + "::createConferenceScheduler()"); +} + +std::shared_ptr ConferenceInfoModel::getConferenceScheduler() const { + return mConferenceSchedulerModel; +} + +void ConferenceInfoModel::setConferenceScheduler(const std::shared_ptr &model) { + if (mConferenceSchedulerModel != model) { + if (mConferenceSchedulerModel) { + disconnect(mConferenceSchedulerModel.get(), &ConferenceSchedulerModel::stateChanged, this, nullptr); + disconnect(mConferenceSchedulerModel.get(), &ConferenceSchedulerModel::invitationsSent, this, nullptr); + mConferenceSchedulerModel->removeListener(); + } + mConferenceSchedulerModel = model; + connect(mConferenceSchedulerModel.get(), &ConferenceSchedulerModel::stateChanged, this, + &ConferenceInfoModel::stateChanged); + connect(mConferenceSchedulerModel.get(), &ConferenceSchedulerModel::invitationsSent, this, + &ConferenceInfoModel::invitationsSent); + mConferenceSchedulerModel->setSelf(mConferenceSchedulerModel); + } +} + +QDateTime ConferenceInfoModel::getDateTime() const { + return QDateTime::fromMSecsSinceEpoch(mConferenceInfo->getDateTime() * 1000, Qt::LocalTime); +} + +int ConferenceInfoModel::getDuration() const { + return mConferenceInfo->getDuration(); +} + +QDateTime ConferenceInfoModel::getEndTime() const { + return getDateTime().addSecs(mConferenceInfo->getDuration()); +} + +QString ConferenceInfoModel::getSubject() const { + return Utils::coreStringToAppString(mConferenceInfo->getSubject()); +} + +QString ConferenceInfoModel::getOrganizerName() const { + auto organizer = mConferenceInfo->getOrganizer(); + auto name = Utils::coreStringToAppString(organizer->getDisplayName()); + if (name.isEmpty()) name = ToolModel::getDisplayName(Utils::coreStringToAppString(organizer->asStringUriOnly())); + return name; +} + +QString ConferenceInfoModel::getOrganizerAddress() const { + return Utils::coreStringToAppString(mConferenceInfo->getOrganizer()->asStringUriOnly()); +} + +QString ConferenceInfoModel::getDescription() const { + return Utils::coreStringToAppString(mConferenceInfo->getSubject()); +} + +std::list> ConferenceInfoModel::getParticipantInfos() const { + return mConferenceInfo->getParticipantInfos(); +} + +void ConferenceInfoModel::setDateTime(const QDateTime &date) { + mConferenceInfo->setDateTime(date.toMSecsSinceEpoch() / 1000); // toMSecsSinceEpoch() is UTC + emit dateTimeChanged(date); +} + +void ConferenceInfoModel::setDuration(int duration) { + mConferenceInfo->setDuration(duration); + emit durationChanged(duration); +} + +void ConferenceInfoModel::setSubject(const QString &subject) { + mConferenceInfo->setSubject(Utils::appStringToCoreString(subject)); + emit subjectChanged(subject); +} + +void ConferenceInfoModel::setOrganizer(const QString &organizerAddress) { + auto linAddr = ToolModel::interpretUrl(organizerAddress); + if (linAddr) { + mConferenceInfo->setOrganizer(linAddr); + emit organizerChanged(organizerAddress); + } +} + +void ConferenceInfoModel::setDescription(const QString &description) { + mConferenceInfo->setDescription(Utils::appStringToCoreString(description)); + emit descriptionChanged(description); +} + +void ConferenceInfoModel::setParticipantInfos( + const std::list> &participantInfos) { + mConferenceInfo->setParticipantInfos(participantInfos); + emit participantsChanged(); +} + +void ConferenceInfoModel::deleteConferenceInfo() { + CoreModel::getInstance()->getCore()->deleteConferenceInformation(mConferenceInfo); + emit conferenceInfoDeleted(); +} + +void ConferenceInfoModel::cancelConference() { + if (!mConferenceSchedulerModel) return; + mConferenceSchedulerModel->cancelConference(mConferenceInfo); +} \ No newline at end of file diff --git a/Linphone/model/conference/ConferenceInfoModel.hpp b/Linphone/model/conference/ConferenceInfoModel.hpp new file mode 100644 index 000000000..5ef6fbdcb --- /dev/null +++ b/Linphone/model/conference/ConferenceInfoModel.hpp @@ -0,0 +1,79 @@ +/* + * 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 CONFERENCE_INFO_MODEL_H_ +#define CONFERENCE_INFO_MODEL_H_ + +#include "model/conference/ConferenceSchedulerModel.hpp" +#include "tool/AbstractObject.hpp" + +#include +#include +#include + +class ConferenceInfoModel : public QObject, public AbstractObject { + Q_OBJECT +public: + ConferenceInfoModel(const std::shared_ptr &conferenceInfo, QObject *parent = nullptr); + ~ConferenceInfoModel(); + + void createConferenceScheduler(); + + std::shared_ptr getConferenceScheduler() const; + void setConferenceScheduler(const std::shared_ptr &model); + QDateTime getDateTime() const; + int getDuration() const; + QDateTime getEndTime() const; + QString getSubject() const; + QString getOrganizerName() const; + QString getOrganizerAddress() const; + QString getDescription() const; + std::list> getParticipantInfos() const; + + void setDateTime(const QDateTime &date); + void setDuration(int duration); + void setSubject(const QString &subject); + void setOrganizer(const QString &organizerAddress); + void setDescription(const QString &description); + void setParticipantInfos(const std::list> &participantInfos); + void deleteConferenceInfo(); + void cancelConference(); + +signals: + void dateTimeChanged(const QDateTime &date); + void durationChanged(int duration); + void organizerChanged(const QString &organizer); + void subjectChanged(const QString &subject); + void descriptionChanged(const QString &description); + void participantsChanged(); + void conferenceInfoDeleted(); + void stateChanged(linphone::ConferenceScheduler::State state); + void invitationsSent(const std::list> &failedInvitations); + +private: + std::shared_ptr mConferenceInfo; + std::shared_ptr mConferenceSchedulerModel = nullptr; + DECLARE_ABSTRACT_OBJECT + + // LINPHONE + //-------------------------------------------------------------------------------- +}; + +#endif \ No newline at end of file diff --git a/Linphone/model/conference/ConferenceModel.cpp b/Linphone/model/conference/ConferenceModel.cpp new file mode 100644 index 000000000..e18fc885f --- /dev/null +++ b/Linphone/model/conference/ConferenceModel.cpp @@ -0,0 +1,218 @@ +/* + * 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 "ConferenceModel.hpp" + +#include + +#include "core/path/Paths.hpp" +#include "model/core/CoreModel.hpp" + +DEFINE_ABSTRACT_OBJECT(ConferenceModel) + +ConferenceModel::ConferenceModel(const std::shared_ptr &conference, QObject *parent) + : ::Listener(conference, parent) { + mustBeInLinphoneThread(getClassName()); +} + +ConferenceModel::~ConferenceModel() { + mustBeInLinphoneThread("~" + getClassName()); +} + +void ConferenceModel::terminate() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mMonitor->terminate(); +} +void ConferenceModel::setPaused(bool paused) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); +} + +void ConferenceModel::removeParticipant(std::shared_ptr p) { + mMonitor->removeParticipant(p); +} + +void ConferenceModel::removeParticipant(std::shared_ptr address) { + for (auto &p : mMonitor->getParticipantList()) { + if (address->asStringUriOnly() == p->getAddress()->asStringUriOnly()) { + mMonitor->removeParticipant(p); + } + } +} + +void ConferenceModel::setMicrophoneMuted(bool isMuted) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mMonitor->setMicrophoneMuted(isMuted); + emit microphoneMutedChanged(isMuted); +} + +void ConferenceModel::startRecording() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + // mMonitor->startRecording(mMonitor->getCurrentParams()->getRecordFile()); + // emit recordingChanged(mMonitor->getParams()->isRecording()); +} + +void ConferenceModel::stopRecording() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mMonitor->stopRecording(); + // emit recordingChanged(mMonitor->getParams()->isRecording()); + // TODO : display notification +} + +void ConferenceModel::setRecordFile(const std::string &path) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto core = CoreModel::getInstance()->getCore(); + // auto params = core->createCallParams(mMonitor); + // params->setRecordFile(path); + // mMonitor->update(params); +} + +// void ConferenceModel::setSpeakerVolumeGain(float gain) { +// mMonitor->setSpeakerVolumeGain(gain); +// emit speakerVolumeGainChanged(gain); +// } + +// float ConferenceModel::getSpeakerVolumeGain() const { +// auto gain = mMonitor->getSpeakerVolumeGain(); +// if (gain < 0) gain = CoreModel::getInstance()->getCore()->getPlaybackGainDb(); +// return gain; +// } + +// void ConferenceModel::setMicrophoneVolumeGain(float gain) { +// mMonitor->setMicrophoneVolumeGain(gain); +// emit microphoneVolumeGainChanged(gain); +// } + +// float ConferenceModel::getMicrophoneVolumeGain() const { +// auto gain = mMonitor->getMicrophoneVolumeGain(); +// return gain; +// } + +// float ConferenceModel::getMicrophoneVolume() const { +// auto volume = mMonitor->getRecordVolume(); +// return volume; +// } + +void ConferenceModel::setInputAudioDevice(const std::shared_ptr &device) { + mMonitor->setInputAudioDevice(device); + std::string deviceName; + if (device) deviceName = device->getDeviceName(); + emit inputAudioDeviceChanged(deviceName); +} + +std::shared_ptr ConferenceModel::getInputAudioDevice() const { + return mMonitor->getInputAudioDevice(); +} + +void ConferenceModel::setOutputAudioDevice(const std::shared_ptr &device) { + mMonitor->setOutputAudioDevice(device); + std::string deviceName; + if (device) deviceName = device->getDeviceName(); + emit outputAudioDeviceChanged(deviceName); +} + +std::shared_ptr ConferenceModel::getOutputAudioDevice() const { + return mMonitor->getOutputAudioDevice(); +} + +void ConferenceModel::onActiveSpeakerParticipantDevice( + const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) { + qDebug() << "onActiveSpeakerParticipantDevice: " << participantDevice->getAddress()->asString().c_str(); + emit activeSpeakerParticipantDevice(participantDevice); +} + +void ConferenceModel::onParticipantAdded(const std::shared_ptr &conference, + const std::shared_ptr &participant) { + qDebug() << "onParticipantAdded: " << participant->getAddress()->asString().c_str(); + emit participantAdded(participant); +} +void ConferenceModel::onParticipantRemoved(const std::shared_ptr &conference, + const std::shared_ptr &participant) { + qDebug() << "onParticipantRemoved"; + emit participantRemoved(participant); +} +void ConferenceModel::onParticipantDeviceAdded( + const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) { + qDebug() << "onParticipantDeviceAdded"; + qDebug() << "Me devices : " << conference->getMe()->getDevices().size(); + if (conference->getMe()->getDevices().size() > 1) + for (auto d : conference->getMe()->getDevices()) + qDebug() << "\t--> " << d->getAddress()->asString().c_str(); + emit participantDeviceAdded(participantDevice); +} +void ConferenceModel::onParticipantDeviceRemoved( + const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) { + qDebug() << "onParticipantDeviceRemoved: " << participantDevice->getAddress()->asString().c_str() << " isInConf?[" + << participantDevice->isInConference() << "]"; + qDebug() << "Me devices : " << conference->getMe()->getDevices().size(); + emit participantDeviceRemoved(participantDevice); +} +void ConferenceModel::onParticipantDeviceStateChanged(const std::shared_ptr &conference, + const std::shared_ptr &device, + linphone::ParticipantDevice::State state) { + qDebug() << "onParticipantDeviceStateChanged: " << device->getAddress()->asString().c_str() << " isInConf?[" + << device->isInConference() << "] " << (int)state; + emit participantDeviceStateChanged(conference, device, state); +} +void ConferenceModel::onParticipantAdminStatusChanged(const std::shared_ptr &conference, + const std::shared_ptr &participant) { + qDebug() << "onParticipantAdminStatusChanged"; + emit participantAdminStatusChanged(participant); +} +void ConferenceModel::onParticipantDeviceMediaCapabilityChanged( + const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) { + qDebug() << "onParticipantDeviceMediaCapabilityChanged: " + << (int)participantDevice->getStreamCapability(linphone::StreamType::Video) + << ". Device: " << participantDevice->getAddress()->asString().c_str(); + emit participantDeviceMediaCapabilityChanged(participantDevice); +} +void ConferenceModel::onParticipantDeviceMediaAvailabilityChanged( + const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) { + qDebug() << "onParticipantDeviceMediaAvailabilityChanged: " + << (int)participantDevice->getStreamAvailability(linphone::StreamType::Video) + << ". Device: " << participantDevice->getAddress()->asString().c_str(); + emit participantDeviceMediaAvailabilityChanged(participantDevice); +} +void ConferenceModel::onParticipantDeviceIsSpeakingChanged( + const std::shared_ptr &conference, + const std::shared_ptr &participantDevice, + bool isSpeaking) { + // qDebug() << "onParticipantDeviceIsSpeakingChanged: " << participantDevice->getAddress()->asString().c_str() << + // ". Speaking:" << isSpeaking; + emit participantDeviceIsSpeakingChanged(participantDevice, isSpeaking); +} +void ConferenceModel::onStateChanged(const std::shared_ptr &conference, + linphone::Conference::State newState) { + qDebug() << "onStateChanged:" << (int)newState; + emit conferenceStateChanged(newState); +} +void ConferenceModel::onSubjectChanged(const std::shared_ptr &conference, + const std::string &subject) { + qDebug() << "onSubjectChanged"; + emit subjectChanged(subject); +} +void ConferenceModel::onAudioDeviceChanged(const std::shared_ptr &conference, + const std::shared_ptr &audioDevice) { + qDebug() << "onAudioDeviceChanged is not yet implemented."; +} \ No newline at end of file diff --git a/Linphone/model/conference/ConferenceModel.hpp b/Linphone/model/conference/ConferenceModel.hpp new file mode 100644 index 000000000..70cc9bf23 --- /dev/null +++ b/Linphone/model/conference/ConferenceModel.hpp @@ -0,0 +1,135 @@ +/* + * 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 CONFERENCE_MODEL_H_ +#define CONFERENCE_MODEL_H_ + +#include "model/listener/Listener.hpp" +#include "tool/AbstractObject.hpp" + +#include +#include +#include + +class ConferenceModel : public ::Listener, + public linphone::ConferenceListener, + public AbstractObject { + Q_OBJECT +public: + ConferenceModel(const std::shared_ptr &conference, QObject *parent = nullptr); + ~ConferenceModel(); + + void terminate(); + + void setMicrophoneMuted(bool isMuted); + void startRecording(); + void stopRecording(); + void setRecordFile(const std::string &path); + void setInputAudioDevice(const std::shared_ptr &id); + std::shared_ptr getInputAudioDevice() const; + void setOutputAudioDevice(const std::shared_ptr &id); + std::shared_ptr getOutputAudioDevice() const; + + void setPaused(bool paused); + + void removeParticipant(std::shared_ptr p); + void removeParticipant(std::shared_ptr address); + +signals: + void microphoneMutedChanged(bool isMuted); + void speakerMutedChanged(bool isMuted); + void cameraEnabledChanged(bool enabled); + void durationChanged(int); + void microphoneVolumeChanged(float); + void pausedChanged(bool paused); + void remoteVideoEnabledChanged(bool remoteVideoEnabled); + void recordingChanged(bool recording); + void speakerVolumeGainChanged(float volume); + void microphoneVolumeGainChanged(float volume); + void inputAudioDeviceChanged(const std::string &id); + void outputAudioDeviceChanged(const std::string &id); + +private: + // LINPHONE LISTENERS + virtual void onActiveSpeakerParticipantDevice( + const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) override; + virtual void onParticipantAdded(const std::shared_ptr &conference, + const std::shared_ptr &participant) override; + virtual void onParticipantRemoved(const std::shared_ptr &conference, + const std::shared_ptr &participant) override; + virtual void + onParticipantAdminStatusChanged(const std::shared_ptr &conference, + const std::shared_ptr &participant) override; + virtual void + onParticipantDeviceAdded(const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) override; + virtual void + onParticipantDeviceRemoved(const std::shared_ptr &conference, + const std::shared_ptr &participantDevice) override; + virtual void onParticipantDeviceStateChanged(const std::shared_ptr &conference, + const std::shared_ptr &device, + linphone::ParticipantDevice::State state) override; + virtual void onParticipantDeviceMediaCapabilityChanged( + const std::shared_ptr &conference, + const std::shared_ptr &device) override; + virtual void onParticipantDeviceMediaAvailabilityChanged( + const std::shared_ptr &conference, + const std::shared_ptr &device) override; + virtual void + onParticipantDeviceIsSpeakingChanged(const std::shared_ptr &conference, + const std::shared_ptr &participantDevice, + bool isSpeaking) override; + virtual void onStateChanged(const std::shared_ptr &conference, + linphone::Conference::State newState) override; + virtual void onSubjectChanged(const std::shared_ptr &conference, + const std::string &subject) override; + virtual void onAudioDeviceChanged(const std::shared_ptr &conference, + const std::shared_ptr &audioDevice) override; + +signals: + 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 participantDeviceRemoved(const std::shared_ptr &participantDevice); + void participantDeviceStateChanged(const std::shared_ptr &conference, + const std::shared_ptr &device, + linphone::ParticipantDevice::State state); + void participantDeviceMediaCapabilityChanged( + const std::shared_ptr &participantDevice); + void participantDeviceMediaAvailabilityChanged( + const std::shared_ptr &participantDevice); + void participantDeviceIsSpeakingChanged(const std::shared_ptr &participantDevice, + bool isSpeaking); + void conferenceStateChanged(linphone::Conference::State newState); + void subjectChanged(const std::string &subject); + +private: + QTimer mDurationTimer; + QTimer mMicroVolumeTimer; + DECLARE_ABSTRACT_OBJECT + + // LINPHONE + //-------------------------------------------------------------------------------- +}; + +#endif diff --git a/Linphone/model/conference/ConferenceSchedulerModel.cpp b/Linphone/model/conference/ConferenceSchedulerModel.cpp new file mode 100644 index 000000000..a5baa661f --- /dev/null +++ b/Linphone/model/conference/ConferenceSchedulerModel.cpp @@ -0,0 +1,59 @@ +/* + * 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 "ConferenceSchedulerModel.hpp" + +#include + +#include "model/core/CoreModel.hpp" + +DEFINE_ABSTRACT_OBJECT(ConferenceSchedulerModel) + +ConferenceSchedulerModel::ConferenceSchedulerModel( + const std::shared_ptr &conferenceScheduler, QObject *parent) + : ::Listener(conferenceScheduler, parent) { + mustBeInLinphoneThread(getClassName()); + auto defaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount(); + assert(defaultAccount); + mMonitor->setAccount(CoreModel::getInstance()->getCore()->getDefaultAccount()); +} + +ConferenceSchedulerModel::~ConferenceSchedulerModel() { + mustBeInLinphoneThread("~" + getClassName()); +} + +void ConferenceSchedulerModel::setInfo(const std::shared_ptr &confInfo) { + mMonitor->setInfo(confInfo); +} + +void ConferenceSchedulerModel::cancelConference(const std::shared_ptr &confInfo) { + mMonitor->cancelConference(confInfo); +} + +void ConferenceSchedulerModel::onStateChanged(const std::shared_ptr &conferenceScheduler, + linphone::ConferenceScheduler::State state) { + emit stateChanged(state); +} + +void ConferenceSchedulerModel::onInvitationsSent( + const std::shared_ptr &conferenceScheduler, + const std::list> &failedInvitations) { + emit invitationsSent(failedInvitations); +} \ No newline at end of file diff --git a/Linphone/model/conference/ConferenceSchedulerModel.hpp b/Linphone/model/conference/ConferenceSchedulerModel.hpp new file mode 100644 index 000000000..a765fe870 --- /dev/null +++ b/Linphone/model/conference/ConferenceSchedulerModel.hpp @@ -0,0 +1,60 @@ +/* + * 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 CONFERENCE_SCHEDULER_MODEL_H_ +#define CONFERENCE_SCHEDULER_MODEL_H_ + +#include "model/listener/Listener.hpp" +#include "tool/AbstractObject.hpp" + +#include +#include +#include + +class ConferenceSchedulerModel + : public ::Listener, + public linphone::ConferenceSchedulerListener, + public AbstractObject { + Q_OBJECT +public: + ConferenceSchedulerModel(const std::shared_ptr &conferenceScheduler, + QObject *parent = nullptr); + ~ConferenceSchedulerModel(); + + void setInfo(const std::shared_ptr &confInfo); + void cancelConference(const std::shared_ptr &confInfo); + +signals: + void stateChanged(linphone::ConferenceScheduler::State state); + void invitationsSent(const std::list> &failedInvitations); + +private: + DECLARE_ABSTRACT_OBJECT + + //-------------------------------------------------------------------------------- + // LINPHONE + //-------------------------------------------------------------------------------- + virtual void onStateChanged(const std::shared_ptr &conferenceScheduler, + linphone::ConferenceScheduler::State state) override; + virtual void onInvitationsSent(const std::shared_ptr &conferenceScheduler, + const std::list> &failedInvitations) override; +}; + +#endif diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index eca6cd288..3d3264b29 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -220,8 +220,13 @@ void CoreModel::onConferenceInfoReceived(const std::shared_ptr & const std::shared_ptr &conferenceInfo) { emit conferenceInfoReceived(core, conferenceInfo); } +void CoreModel::onConferenceStateChanged(const std::shared_ptr &core, + const std::shared_ptr &conference, + linphone::Conference::State state) { + emit conferenceStateChanged(core, conference, state); +} void CoreModel::onConfiguringStatus(const std::shared_ptr &core, - linphone::ConfiguringState status, + linphone::Config::ConfiguringState status, const std::string &message) { emit configuringStatus(core, status, message); } diff --git a/Linphone/model/core/CoreModel.hpp b/Linphone/model/core/CoreModel.hpp index dd7ab9c0d..8be452323 100644 --- a/Linphone/model/core/CoreModel.hpp +++ b/Linphone/model/core/CoreModel.hpp @@ -108,8 +108,12 @@ private: virtual void onConferenceInfoReceived(const std::shared_ptr &core, const std::shared_ptr &conferenceInfo) override; + virtual void onConferenceStateChanged(const std::shared_ptr &core, + const std::shared_ptr &conference, + linphone::Conference::State state) override; + virtual void onConfiguringStatus(const std::shared_ptr &core, - linphone::ConfiguringState status, + linphone::Config::ConfiguringState status, const std::string &message) override; virtual void onDefaultAccountChanged(const std::shared_ptr &core, const std::shared_ptr &account) override; @@ -192,8 +196,11 @@ signals: linphone::ChatRoom::State state); void conferenceInfoReceived(const std::shared_ptr &core, const std::shared_ptr &conferenceInfo); + void conferenceStateChanged(const std::shared_ptr &core, + const std::shared_ptr &conference, + linphone::Conference::State state); void configuringStatus(const std::shared_ptr &core, - linphone::ConfiguringState status, + linphone::Config::ConfiguringState status, const std::string &message); void defaultAccountChanged(const std::shared_ptr &core, const std::shared_ptr &account); diff --git a/Linphone/model/participant/ParticipantDeviceModel.cpp b/Linphone/model/participant/ParticipantDeviceModel.cpp new file mode 100644 index 000000000..14a84f68a --- /dev/null +++ b/Linphone/model/participant/ParticipantDeviceModel.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2021 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 "ParticipantDeviceModel.hpp" + +#include "core/App.hpp" +#include "tool/Utils.hpp" +#include + +DEFINE_ABSTRACT_OBJECT(ParticipantDeviceModel) + +ParticipantDeviceModel::ParticipantDeviceModel(const std::shared_ptr &device, + QObject *parent) + : ::Listener(device, parent) { + mustBeInLinphoneThread(getClassName()); +} + +ParticipantDeviceModel::~ParticipantDeviceModel() { +} + +QString ParticipantDeviceModel::getName() const { + return Utils::coreStringToAppString(mMonitor->getName()); +} + +QString ParticipantDeviceModel::getDisplayName() const { + return Utils::coreStringToAppString(mMonitor->getAddress()->getDisplayName()); +} + +int ParticipantDeviceModel::getSecurityLevel() const { + return (int)mMonitor->getSecurityLevel(); +} + +time_t ParticipantDeviceModel::getTimeOfJoining() const { + return mMonitor->getTimeOfJoining(); +} + +QString ParticipantDeviceModel::getAddress() const { + return Utils::coreStringToAppString(mMonitor->getAddress()->asStringUriOnly()); +} + +bool ParticipantDeviceModel::getPaused() const { + return !mMonitor->isInConference() || mMonitor->getState() == linphone::ParticipantDevice::State::OnHold; +} + +bool ParticipantDeviceModel::getIsSpeaking() const { + return mMonitor->getIsSpeaking(); +} + +bool ParticipantDeviceModel::getIsMuted() const { + return mMonitor->getIsMuted(); +} + +LinphoneEnums::ParticipantDeviceState ParticipantDeviceModel::getState() const { + return LinphoneEnums::fromLinphone(mMonitor->getState()); +} + +bool ParticipantDeviceModel::isVideoEnabled() const { + return mMonitor->isInConference() && mMonitor->getStreamAvailability(linphone::StreamType::Video) && + (mMonitor->getStreamCapability(linphone::StreamType::Video) == linphone::MediaDirection::SendRecv || + mMonitor->getStreamCapability(linphone::StreamType::Video) == linphone::MediaDirection::SendOnly); +} + +// void ParticipantDeviceModel::updateIsLocal() { +// auto deviceAddress = mMonitor->getAddress(); +// auto callAddress = mCall->getConferenceSharedModel()->getConference()->getMe()->getAddress(); +// auto gruuAddress = +// CoreManager::getInstance()->getAccountSettingsModel()->findAccount(callAddress)->getContactAddress(); +// setIsLocal(deviceAddress->equal(gruuAddress)); +// } + +// void ParticipantDeviceModel::onSecurityLevelChanged(std::shared_ptr device) { +// if (!device || mMonitor && mMonitor->getAddress()->weakEqual(device)) emit securityLevelChanged(); +// } + +// void ParticipantDeviceModel::onCallStatusChanged() { +// if (mCall->getCall()->getState() == linphone::Call::State::StreamsRunning) { +// updateVideoEnabled(); +// } +// } + +//-------------------------------------------------------------------- +void ParticipantDeviceModel::onIsSpeakingChanged(const std::shared_ptr &participantDevice, + bool isSpeaking) { + emit isSpeakingChanged(isSpeaking); +} +void ParticipantDeviceModel::onIsMuted(const std::shared_ptr &participantDevice, + bool isMuted) { + emit isMutedChanged(isMuted); +} +void ParticipantDeviceModel::onStateChanged(const std::shared_ptr &participantDevice, + linphone::ParticipantDevice::State state) { + emit stateChanged(LinphoneEnums::fromLinphone(state)); +} +void ParticipantDeviceModel::onStreamCapabilityChanged( + const std::shared_ptr &participantDevice, + linphone::MediaDirection direction, + linphone::StreamType streamType) { + emit streamCapabilityChanged(streamType); +} +void ParticipantDeviceModel::onStreamAvailabilityChanged( + const std::shared_ptr &participantDevice, + bool available, + linphone::StreamType streamType) { + emit streamAvailabilityChanged(streamType); +} \ No newline at end of file diff --git a/Linphone/model/participant/ParticipantDeviceModel.hpp b/Linphone/model/participant/ParticipantDeviceModel.hpp new file mode 100644 index 000000000..9b799a6ad --- /dev/null +++ b/Linphone/model/participant/ParticipantDeviceModel.hpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 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_MODEL_H_ +#define PARTICIPANT_DEVICE_MODEL_H_ + +#include "model/listener/Listener.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/LinphoneEnums.hpp" +#include + +#include +#include +#include +#include + +class ParticipantDeviceModel : public ::Listener, + public linphone::ParticipantDeviceListener, + public AbstractObject { + Q_OBJECT + +public: + ParticipantDeviceModel(const std::shared_ptr &device, QObject *parent = nullptr); + virtual ~ParticipantDeviceModel(); + + QString getName() const; + QString getDisplayName() const; + QString getAddress() const; + int getSecurityLevel() const; + time_t getTimeOfJoining() const; + bool isVideoEnabled() const; + // bool isLocal() const; + bool getPaused() const; + bool getIsSpeaking() const; + bool getIsMuted() const; + LinphoneEnums::ParticipantDeviceState getState() const; + + virtual void onIsSpeakingChanged(const std::shared_ptr &participantDevice, + bool isSpeaking) override; + virtual void onIsMuted(const std::shared_ptr &participantDevice, + bool isMuted) override; + virtual void onStateChanged(const std::shared_ptr &participantDevice, + linphone::ParticipantDevice::State state) override; + virtual void onStreamCapabilityChanged(const std::shared_ptr &participantDevice, + linphone::MediaDirection direction, + linphone::StreamType streamType) override; + virtual void onStreamAvailabilityChanged(const std::shared_ptr &participantDevice, + bool available, + linphone::StreamType streamType) override; + + // void updateVideoEnabled(); + // void updateIsLocal(); + +signals: + // void securityLevelChanged(); + // void videoEnabledChanged(); + void isPausedChanged(bool paused); + void isSpeakingChanged(bool speaking); + void isMutedChanged(bool muted); + void stateChanged(LinphoneEnums::ParticipantDeviceState state); + void streamCapabilityChanged(linphone::StreamType streamType); + void streamAvailabilityChanged(linphone::StreamType streamType); + +private: + DECLARE_ABSTRACT_OBJECT +}; + +#endif // PARTICIPANT_DEVICE_MODEL_H_ diff --git a/Linphone/model/participant/ParticipantModel.cpp b/Linphone/model/participant/ParticipantModel.cpp new file mode 100644 index 000000000..d9f1447f2 --- /dev/null +++ b/Linphone/model/participant/ParticipantModel.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 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 "ParticipantModel.hpp" + +#include "tool/Utils.hpp" + +#include + +DEFINE_ABSTRACT_OBJECT(ParticipantModel) + +ParticipantModel::ParticipantModel(std::shared_ptr linphoneParticipant, QObject *parent) + : QObject(parent) { + assert(mParticipant); + mParticipant = linphoneParticipant; +} + +ParticipantModel::~ParticipantModel() { + mustBeInLinphoneThread("~" + getClassName()); +} + +int ParticipantModel::getSecurityLevel() const { + return (int)mParticipant->getSecurityLevel(); +} + +std::list> ParticipantModel::getDevices() const { + return mParticipant->getDevices(); +} + +int ParticipantModel::getDeviceCount() { + return mParticipant->getDevices().size(); +} + +QString ParticipantModel::getSipAddress() const { + return Utils::coreStringToAppString(mParticipant->getAddress()->asString()); +} + +QDateTime ParticipantModel::getCreationTime() const { + return QDateTime::fromSecsSinceEpoch(mParticipant->getCreationTime()); +} + +bool ParticipantModel::getAdminStatus() const { + return mParticipant->isAdmin(); +} + +bool ParticipantModel::isFocus() const { + return mParticipant->isFocus(); +} \ No newline at end of file diff --git a/Linphone/model/participant/ParticipantModel.hpp b/Linphone/model/participant/ParticipantModel.hpp new file mode 100644 index 000000000..ac1a8329c --- /dev/null +++ b/Linphone/model/participant/ParticipantModel.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 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_MODEL_H_ +#define PARTICIPANT_MODEL_H_ + +#include "tool/AbstractObject.hpp" +#include + +#include +#include +#include +#include + +class ParticipantDeviceProxyModel; +class ParticipantDeviceListModel; + +class ParticipantModel : public QObject, public AbstractObject { + Q_OBJECT + +public: + ParticipantModel(std::shared_ptr linphoneParticipant, QObject *parent = nullptr); + ~ParticipantModel(); + + QString getSipAddress() const; + QDateTime getCreationTime() const; + bool getAdminStatus() const; + bool isFocus() const; + int getSecurityLevel() const; + int getDeviceCount(); + std::list> getDevices() const; + +private: + std::shared_ptr mParticipant; + + DECLARE_ABSTRACT_OBJECT +}; + +#endif // PARTICIPANT_MODEL_H_ diff --git a/Linphone/tool/Constants.hpp b/Linphone/tool/Constants.hpp index 3c76b4f6b..4b141896b 100644 --- a/Linphone/tool/Constants.hpp +++ b/Linphone/tool/Constants.hpp @@ -37,7 +37,7 @@ public: //---------------------------------------------------------------------------------- - static constexpr char DefaultLocale[] = "en"; + static constexpr char DefaultLocale[] = "en_EN"; static constexpr char DefaultFont[] = "Noto Sans"; static constexpr int DefaultFontPointSize = 10; diff --git a/Linphone/tool/LinphoneEnums.cpp b/Linphone/tool/LinphoneEnums.cpp index eeab8adac..9ad0b4fb0 100644 --- a/Linphone/tool/LinphoneEnums.cpp +++ b/Linphone/tool/LinphoneEnums.cpp @@ -221,6 +221,14 @@ LinphoneEnums::ParticipantDeviceState LinphoneEnums::fromLinphone(const linphone return static_cast(state); } +linphone::Participant::Role LinphoneEnums::toLinphone(const LinphoneEnums::ParticipantRole &role) { + return static_cast(role); +} + +LinphoneEnums::ParticipantRole LinphoneEnums::fromLinphone(const linphone::Participant::Role &role) { + return static_cast(role); +} + linphone::Tunnel::Mode LinphoneEnums::toLinphone(const LinphoneEnums::TunnelMode &data) { return static_cast(data); } diff --git a/Linphone/tool/LinphoneEnums.hpp b/Linphone/tool/LinphoneEnums.hpp index 1a6a2cdc7..21d2a3eef 100644 --- a/Linphone/tool/LinphoneEnums.hpp +++ b/Linphone/tool/LinphoneEnums.hpp @@ -256,6 +256,15 @@ Q_ENUM_NS(ParticipantDeviceState) linphone::ParticipantDevice::State toLinphone(const LinphoneEnums::ParticipantDeviceState &state); LinphoneEnums::ParticipantDeviceState fromLinphone(const linphone::ParticipantDevice::State &state); +enum class ParticipantRole { + Speaker = int(linphone::Participant::Role::Speaker), + Listener = int(linphone::Participant::Role::Listener), + Unknown = int(linphone::Participant::Role::Unknown) +}; +Q_ENUM_NS(ParticipantRole) +linphone::Participant::Role toLinphone(const LinphoneEnums::ParticipantRole &role); +LinphoneEnums::ParticipantRole fromLinphone(const linphone::Participant::Role &role); + enum class RegistrationState { None = int(linphone::RegistrationState::None), Progress = int(linphone::RegistrationState::Progress), diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index af305228f..05dc10902 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -141,6 +141,12 @@ QQuickWindow *Utils::getMainWindow() { return win; } +void Utils::showInformationPopup(const QString &title, const QString &description, bool isSuccess) { + auto win = App::getInstance()->getMainWindow(); + QMetaObject::invokeMethod(win, "showInformationPopup", Q_ARG(QVariant, title), Q_ARG(QVariant, description), + Q_ARG(QVariant, isSuccess)); +} + VariantObject *Utils::haveAccount() { VariantObject *result = new VariantObject(); if (!result) return nullptr; @@ -274,6 +280,15 @@ QString Utils::generateLinphoneSipAddress(const QString &uri) { return ret; } +QString Utils::findAvatarByAddress(const QString &address) { + auto defaultFriendList = CoreModel::getInstance()->getCore()->getDefaultFriendList(); + if (!defaultFriendList) return QString(); + auto linphoneAddr = ToolModel::interpretUrl(address); + auto linFriend = CoreModel::getInstance()->getCore()->findFriend(linphoneAddr); + if (linFriend) return Utils::coreStringToAppString(linFriend->getPhoto()); + return QString(); +} + QString Utils::generateSavedFilename(const QString &from, const QString &to) { auto escape = [](const QString &str) { constexpr char ReservedCharacters[] = "[<|>|:|\"|/|\\\\|\\?|\\*|\\+|\\||_|-]+"; @@ -340,3 +355,842 @@ QString Utils::computeUserAgent() { .arg(qVersion()); */ } +QString Utils::getCountryName(const QLocale::Country &p_country) { + QString countryName; + switch (p_country) { + case QLocale::Afghanistan: + if ((countryName = QCoreApplication::translate("country", "Afghanistan")) == "Afghanistan") + countryName = ""; + break; + case QLocale::Albania: + if ((countryName = QCoreApplication::translate("country", "Albania")) == "Albania") countryName = ""; + break; + case QLocale::Algeria: + if ((countryName = QCoreApplication::translate("country", "Algeria")) == "Algeria") countryName = ""; + break; + case QLocale::AmericanSamoa: + if ((countryName = QCoreApplication::translate("country", "AmericanSamoa")) == "AmericanSamoa") + countryName = ""; + break; + case QLocale::Andorra: + if ((countryName = QCoreApplication::translate("country", "Andorra")) == "Andorra") countryName = ""; + break; + case QLocale::Angola: + if ((countryName = QCoreApplication::translate("country", "Angola")) == "Angola") countryName = ""; + break; + case QLocale::Anguilla: + if ((countryName = QCoreApplication::translate("country", "Anguilla")) == "Anguilla") countryName = ""; + break; + case QLocale::AntiguaAndBarbuda: + if ((countryName = QCoreApplication::translate("country", "AntiguaAndBarbuda")) == "AntiguaAndBarbuda") + countryName = ""; + break; + case QLocale::Argentina: + if ((countryName = QCoreApplication::translate("country", "Argentina")) == "Argentina") countryName = ""; + break; + case QLocale::Armenia: + if ((countryName = QCoreApplication::translate("country", "Armenia")) == "Armenia") countryName = ""; + break; + case QLocale::Aruba: + if ((countryName = QCoreApplication::translate("country", "Aruba")) == "Aruba") countryName = ""; + break; + case QLocale::Australia: + if ((countryName = QCoreApplication::translate("country", "Australia")) == "Australia") countryName = ""; + break; + case QLocale::Austria: + if ((countryName = QCoreApplication::translate("country", "Austria")) == "Austria") countryName = ""; + break; + case QLocale::Azerbaijan: + if ((countryName = QCoreApplication::translate("country", "Azerbaijan")) == "Azerbaijan") countryName = ""; + break; + case QLocale::Bahamas: + if ((countryName = QCoreApplication::translate("country", "Bahamas")) == "Bahamas") countryName = ""; + break; + case QLocale::Bahrain: + if ((countryName = QCoreApplication::translate("country", "Bahrain")) == "Bahrain") countryName = ""; + break; + case QLocale::Bangladesh: + if ((countryName = QCoreApplication::translate("country", "Bangladesh")) == "Bangladesh") countryName = ""; + break; + case QLocale::Barbados: + if ((countryName = QCoreApplication::translate("country", "Barbados")) == "Barbados") countryName = ""; + break; + case QLocale::Belarus: + if ((countryName = QCoreApplication::translate("country", "Belarus")) == "Belarus") countryName = ""; + break; + case QLocale::Belgium: + if ((countryName = QCoreApplication::translate("country", "Belgium")) == "Belgium") countryName = ""; + break; + case QLocale::Belize: + if ((countryName = QCoreApplication::translate("country", "Belize")) == "Belize") countryName = ""; + break; + case QLocale::Benin: + if ((countryName = QCoreApplication::translate("country", "Benin")) == "Benin") countryName = ""; + break; + case QLocale::Bermuda: + if ((countryName = QCoreApplication::translate("country", "Bermuda")) == "Bermuda") countryName = ""; + break; + case QLocale::Bhutan: + if ((countryName = QCoreApplication::translate("country", "Bhutan")) == "Bhutan") countryName = ""; + break; + case QLocale::Bolivia: + if ((countryName = QCoreApplication::translate("country", "Bolivia")) == "Bolivia") countryName = ""; + break; + case QLocale::BosniaAndHerzegowina: + if ((countryName = QCoreApplication::translate("country", "BosniaAndHerzegowina")) == + "BosniaAndHerzegowina") + countryName = ""; + break; + case QLocale::Botswana: + if ((countryName = QCoreApplication::translate("country", "Botswana")) == "Botswana") countryName = ""; + break; + case QLocale::Brazil: + if ((countryName = QCoreApplication::translate("country", "Brazil")) == "Brazil") countryName = ""; + break; + case QLocale::Brunei: + if ((countryName = QCoreApplication::translate("country", "Brunei")) == "Brunei") countryName = ""; + break; + case QLocale::Bulgaria: + if ((countryName = QCoreApplication::translate("country", "Bulgaria")) == "Bulgaria") countryName = ""; + break; + case QLocale::BurkinaFaso: + if ((countryName = QCoreApplication::translate("country", "BurkinaFaso")) == "BurkinaFaso") + countryName = ""; + break; + case QLocale::Burundi: + if ((countryName = QCoreApplication::translate("country", "Burundi")) == "Burundi") countryName = ""; + break; + case QLocale::Cambodia: + if ((countryName = QCoreApplication::translate("country", "Cambodia")) == "Cambodia") countryName = ""; + break; + case QLocale::Cameroon: + if ((countryName = QCoreApplication::translate("country", "Cameroon")) == "Cameroon") countryName = ""; + break; + case QLocale::Canada: + if ((countryName = QCoreApplication::translate("country", "Canada")) == "Canada") countryName = ""; + break; + case QLocale::CapeVerde: + if ((countryName = QCoreApplication::translate("country", "CapeVerde")) == "CapeVerde") countryName = ""; + break; + case QLocale::CaymanIslands: + if ((countryName = QCoreApplication::translate("country", "CaymanIslands")) == "CaymanIslands") + countryName = ""; + break; + case QLocale::CentralAfricanRepublic: + if ((countryName = QCoreApplication::translate("country", "CentralAfricanRepublic")) == + "CentralAfricanRepublic") + countryName = ""; + break; + case QLocale::Chad: + if ((countryName = QCoreApplication::translate("country", "Chad")) == "Chad") countryName = ""; + break; + case QLocale::Chile: + if ((countryName = QCoreApplication::translate("country", "Chile")) == "Chile") countryName = ""; + break; + case QLocale::China: + if ((countryName = QCoreApplication::translate("country", "China")) == "China") countryName = ""; + break; + case QLocale::Colombia: + if ((countryName = QCoreApplication::translate("country", "Colombia")) == "Colombia") countryName = ""; + break; + case QLocale::Comoros: + if ((countryName = QCoreApplication::translate("country", "Comoros")) == "Comoros") countryName = ""; + break; + case QLocale::PeoplesRepublicOfCongo: + if ((countryName = QCoreApplication::translate("country", "PeoplesRepublicOfCongo")) == + "PeoplesRepublicOfCongo") + countryName = ""; + break; + case QLocale::DemocraticRepublicOfCongo: + if ((countryName = QCoreApplication::translate("country", "DemocraticRepublicOfCongo")) == + "DemocraticRepublicOfCongo") + countryName = ""; + break; + case QLocale::CookIslands: + if ((countryName = QCoreApplication::translate("country", "CookIslands")) == "CookIslands") + countryName = ""; + break; + case QLocale::CostaRica: + if ((countryName = QCoreApplication::translate("country", "CostaRica")) == "CostaRica") countryName = ""; + break; + case QLocale::IvoryCoast: + if ((countryName = QCoreApplication::translate("country", "IvoryCoast")) == "IvoryCoast") countryName = ""; + break; + case QLocale::Croatia: + if ((countryName = QCoreApplication::translate("country", "Croatia")) == "Croatia") countryName = ""; + break; + case QLocale::Cuba: + if ((countryName = QCoreApplication::translate("country", "Cuba")) == "Cuba") countryName = ""; + break; + case QLocale::Cyprus: + if ((countryName = QCoreApplication::translate("country", "Cyprus")) == "Cyprus") countryName = ""; + break; + case QLocale::CzechRepublic: + if ((countryName = QCoreApplication::translate("country", "CzechRepublic")) == "CzechRepublic") + countryName = ""; + break; + case QLocale::Denmark: + if ((countryName = QCoreApplication::translate("country", "Denmark")) == "Denmark") countryName = ""; + break; + case QLocale::Djibouti: + if ((countryName = QCoreApplication::translate("country", "Djibouti")) == "Djibouti") countryName = ""; + break; + case QLocale::Dominica: + if ((countryName = QCoreApplication::translate("country", "Dominica")) == "Dominica") countryName = ""; + break; + case QLocale::DominicanRepublic: + if ((countryName = QCoreApplication::translate("country", "DominicanRepublic")) == "DominicanRepublic") + countryName = ""; + break; + case QLocale::Ecuador: + if ((countryName = QCoreApplication::translate("country", "Ecuador")) == "Ecuador") countryName = ""; + break; + case QLocale::Egypt: + if ((countryName = QCoreApplication::translate("country", "Egypt")) == "Egypt") countryName = ""; + break; + case QLocale::ElSalvador: + if ((countryName = QCoreApplication::translate("country", "ElSalvador")) == "ElSalvador") countryName = ""; + break; + case QLocale::EquatorialGuinea: + if ((countryName = QCoreApplication::translate("country", "EquatorialGuinea")) == "EquatorialGuinea") + countryName = ""; + break; + case QLocale::Eritrea: + if ((countryName = QCoreApplication::translate("country", "Eritrea")) == "Eritrea") countryName = ""; + break; + case QLocale::Estonia: + if ((countryName = QCoreApplication::translate("country", "Estonia")) == "Estonia") countryName = ""; + break; + case QLocale::Ethiopia: + if ((countryName = QCoreApplication::translate("country", "Ethiopia")) == "Ethiopia") countryName = ""; + break; + case QLocale::FalklandIslands: + if ((countryName = QCoreApplication::translate("country", "FalklandIslands")) == "FalklandIslands") + countryName = ""; + break; + case QLocale::FaroeIslands: + if ((countryName = QCoreApplication::translate("country", "FaroeIslands")) == "FaroeIslands") + countryName = ""; + break; + case QLocale::Fiji: + if ((countryName = QCoreApplication::translate("country", "Fiji")) == "Fiji") countryName = ""; + break; + case QLocale::Finland: + if ((countryName = QCoreApplication::translate("country", "Finland")) == "Finland") countryName = ""; + break; + case QLocale::France: + if ((countryName = QCoreApplication::translate("country", "France")) == "France") countryName = ""; + break; + case QLocale::FrenchGuiana: + if ((countryName = QCoreApplication::translate("country", "FrenchGuiana")) == "FrenchGuiana") + countryName = ""; + break; + case QLocale::FrenchPolynesia: + if ((countryName = QCoreApplication::translate("country", "FrenchPolynesia")) == "FrenchPolynesia") + countryName = ""; + break; + case QLocale::Gabon: + if ((countryName = QCoreApplication::translate("country", "Gabon")) == "Gabon") countryName = ""; + break; + case QLocale::Gambia: + if ((countryName = QCoreApplication::translate("country", "Gambia")) == "Gambia") countryName = ""; + break; + case QLocale::Georgia: + if ((countryName = QCoreApplication::translate("country", "Georgia")) == "Georgia") countryName = ""; + break; + case QLocale::Germany: + if ((countryName = QCoreApplication::translate("country", "Germany")) == "Germany") countryName = ""; + break; + case QLocale::Ghana: + if ((countryName = QCoreApplication::translate("country", "Ghana")) == "Ghana") countryName = ""; + break; + case QLocale::Gibraltar: + if ((countryName = QCoreApplication::translate("country", "Gibraltar")) == "Gibraltar") countryName = ""; + break; + case QLocale::Greece: + if ((countryName = QCoreApplication::translate("country", "Greece")) == "Greece") countryName = ""; + break; + case QLocale::Greenland: + if ((countryName = QCoreApplication::translate("country", "Greenland")) == "Greenland") countryName = ""; + break; + case QLocale::Grenada: + if ((countryName = QCoreApplication::translate("country", "Grenada")) == "Grenada") countryName = ""; + break; + case QLocale::Guadeloupe: + if ((countryName = QCoreApplication::translate("country", "Guadeloupe")) == "Guadeloupe") countryName = ""; + break; + case QLocale::Guam: + if ((countryName = QCoreApplication::translate("country", "Guam")) == "Guam") countryName = ""; + break; + case QLocale::Guatemala: + if ((countryName = QCoreApplication::translate("country", "Guatemala")) == "Guatemala") countryName = ""; + break; + case QLocale::Guinea: + if ((countryName = QCoreApplication::translate("country", "Guinea")) == "Guinea") countryName = ""; + break; + case QLocale::GuineaBissau: + if ((countryName = QCoreApplication::translate("country", "GuineaBissau")) == "GuineaBissau") + countryName = ""; + break; + case QLocale::Guyana: + if ((countryName = QCoreApplication::translate("country", "Guyana")) == "Guyana") countryName = ""; + break; + case QLocale::Haiti: + if ((countryName = QCoreApplication::translate("country", "Haiti")) == "Haiti") countryName = ""; + break; + case QLocale::Honduras: + if ((countryName = QCoreApplication::translate("country", "Honduras")) == "Honduras") countryName = ""; + break; + case QLocale::HongKong: + if ((countryName = QCoreApplication::translate("country", "HongKong")) == "HongKong") countryName = ""; + break; + case QLocale::Hungary: + if ((countryName = QCoreApplication::translate("country", "Hungary")) == "Hungary") countryName = ""; + break; + case QLocale::Iceland: + if ((countryName = QCoreApplication::translate("country", "Iceland")) == "Iceland") countryName = ""; + break; + case QLocale::India: + if ((countryName = QCoreApplication::translate("country", "India")) == "India") countryName = ""; + break; + case QLocale::Indonesia: + if ((countryName = QCoreApplication::translate("country", "Indonesia")) == "Indonesia") countryName = ""; + break; + case QLocale::Iran: + if ((countryName = QCoreApplication::translate("country", "Iran")) == "Iran") countryName = ""; + break; + case QLocale::Iraq: + if ((countryName = QCoreApplication::translate("country", "Iraq")) == "Iraq") countryName = ""; + break; + case QLocale::Ireland: + if ((countryName = QCoreApplication::translate("country", "Ireland")) == "Ireland") countryName = ""; + break; + case QLocale::Israel: + if ((countryName = QCoreApplication::translate("country", "Israel")) == "Israel") countryName = ""; + break; + case QLocale::Italy: + if ((countryName = QCoreApplication::translate("country", "Italy")) == "Italy") countryName = ""; + break; + case QLocale::Jamaica: + if ((countryName = QCoreApplication::translate("country", "Jamaica")) == "Jamaica") countryName = ""; + break; + case QLocale::Japan: + if ((countryName = QCoreApplication::translate("country", "Japan")) == "Japan") countryName = ""; + break; + case QLocale::Jordan: + if ((countryName = QCoreApplication::translate("country", "Jordan")) == "Jordan") countryName = ""; + break; + case QLocale::Kazakhstan: + if ((countryName = QCoreApplication::translate("country", "Kazakhstan")) == "Kazakhstan") countryName = ""; + break; + case QLocale::Kenya: + if ((countryName = QCoreApplication::translate("country", "Kenya")) == "Kenya") countryName = ""; + break; + case QLocale::Kiribati: + if ((countryName = QCoreApplication::translate("country", "Kiribati")) == "Kiribati") countryName = ""; + break; + case QLocale::DemocraticRepublicOfKorea: + if ((countryName = QCoreApplication::translate("country", "DemocraticRepublicOfKorea")) == + "DemocraticRepublicOfKorea") + countryName = ""; + break; + case QLocale::RepublicOfKorea: + if ((countryName = QCoreApplication::translate("country", "RepublicOfKorea")) == "RepublicOfKorea") + countryName = ""; + break; + case QLocale::Kuwait: + if ((countryName = QCoreApplication::translate("country", "Kuwait")) == "Kuwait") countryName = ""; + break; + case QLocale::Kyrgyzstan: + if ((countryName = QCoreApplication::translate("country", "Kyrgyzstan")) == "Kyrgyzstan") countryName = ""; + break; + case QLocale::Laos: + if ((countryName = QCoreApplication::translate("country", "Laos")) == "Laos") countryName = ""; + break; + case QLocale::Latvia: + if ((countryName = QCoreApplication::translate("country", "Latvia")) == "Latvia") countryName = ""; + break; + case QLocale::Lebanon: + if ((countryName = QCoreApplication::translate("country", "Lebanon")) == "Lebanon") countryName = ""; + break; + case QLocale::Lesotho: + if ((countryName = QCoreApplication::translate("country", "Lesotho")) == "Lesotho") countryName = ""; + break; + case QLocale::Liberia: + if ((countryName = QCoreApplication::translate("country", "Liberia")) == "Liberia") countryName = ""; + break; + case QLocale::Libya: + if ((countryName = QCoreApplication::translate("country", "Libya")) == "Libya") countryName = ""; + break; + case QLocale::Liechtenstein: + if ((countryName = QCoreApplication::translate("country", "Liechtenstein")) == "Liechtenstein") + countryName = ""; + break; + case QLocale::Lithuania: + if ((countryName = QCoreApplication::translate("country", "Lithuania")) == "Lithuania") countryName = ""; + break; + case QLocale::Luxembourg: + if ((countryName = QCoreApplication::translate("country", "Luxembourg")) == "Luxembourg") countryName = ""; + break; + case QLocale::Macau: + if ((countryName = QCoreApplication::translate("country", "Macau")) == "Macau") countryName = ""; + break; + case QLocale::Macedonia: + if ((countryName = QCoreApplication::translate("country", "Macedonia")) == "Macedonia") countryName = ""; + break; + case QLocale::Madagascar: + if ((countryName = QCoreApplication::translate("country", "Madagascar")) == "Madagascar") countryName = ""; + break; + case QLocale::Malawi: + if ((countryName = QCoreApplication::translate("country", "Malawi")) == "Malawi") countryName = ""; + break; + case QLocale::Malaysia: + if ((countryName = QCoreApplication::translate("country", "Malaysia")) == "Malaysia") countryName = ""; + break; + case QLocale::Maldives: + if ((countryName = QCoreApplication::translate("country", "Maldives")) == "Maldives") countryName = ""; + break; + case QLocale::Mali: + if ((countryName = QCoreApplication::translate("country", "Mali")) == "Mali") countryName = ""; + break; + case QLocale::Malta: + if ((countryName = QCoreApplication::translate("country", "Malta")) == "Malta") countryName = ""; + break; + case QLocale::MarshallIslands: + if ((countryName = QCoreApplication::translate("country", "MarshallIslands")) == "MarshallIslands") + countryName = ""; + break; + case QLocale::Martinique: + if ((countryName = QCoreApplication::translate("country", "Martinique")) == "Martinique") countryName = ""; + break; + case QLocale::Mauritania: + if ((countryName = QCoreApplication::translate("country", "Mauritania")) == "Mauritania") countryName = ""; + break; + case QLocale::Mauritius: + if ((countryName = QCoreApplication::translate("country", "Mauritius")) == "Mauritius") countryName = ""; + break; + case QLocale::Mayotte: + if ((countryName = QCoreApplication::translate("country", "Mayotte")) == "Mayotte") countryName = ""; + break; + case QLocale::Mexico: + if ((countryName = QCoreApplication::translate("country", "Mexico")) == "Mexico") countryName = ""; + break; + case QLocale::Micronesia: + if ((countryName = QCoreApplication::translate("country", "Micronesia")) == "Micronesia") countryName = ""; + break; + case QLocale::Moldova: + if ((countryName = QCoreApplication::translate("country", "Moldova")) == "Moldova") countryName = ""; + break; + case QLocale::Monaco: + if ((countryName = QCoreApplication::translate("country", "Monaco")) == "Monaco") countryName = ""; + break; + case QLocale::Mongolia: + if ((countryName = QCoreApplication::translate("country", "Mongolia")) == "Mongolia") countryName = ""; + break; + case QLocale::Montenegro: + if ((countryName = QCoreApplication::translate("country", "Montenegro")) == "Montenegro") countryName = ""; + break; + case QLocale::Montserrat: + if ((countryName = QCoreApplication::translate("country", "Montserrat")) == "Montserrat") countryName = ""; + break; + case QLocale::Morocco: + if ((countryName = QCoreApplication::translate("country", "Morocco")) == "Morocco") countryName = ""; + break; + case QLocale::Mozambique: + if ((countryName = QCoreApplication::translate("country", "Mozambique")) == "Mozambique") countryName = ""; + break; + case QLocale::Myanmar: + if ((countryName = QCoreApplication::translate("country", "Myanmar")) == "Myanmar") countryName = ""; + break; + case QLocale::Namibia: + if ((countryName = QCoreApplication::translate("country", "Namibia")) == "Namibia") countryName = ""; + break; + case QLocale::NauruCountry: + if ((countryName = QCoreApplication::translate("country", "NauruCountry")) == "NauruCountry") + countryName = ""; + break; + case QLocale::Nepal: + if ((countryName = QCoreApplication::translate("country", "Nepal")) == "Nepal") countryName = ""; + break; + case QLocale::Netherlands: + if ((countryName = QCoreApplication::translate("country", "Netherlands")) == "Netherlands") + countryName = ""; + break; + case QLocale::NewCaledonia: + if ((countryName = QCoreApplication::translate("country", "NewCaledonia")) == "NewCaledonia") + countryName = ""; + break; + case QLocale::NewZealand: + if ((countryName = QCoreApplication::translate("country", "NewZealand")) == "NewZealand") countryName = ""; + break; + case QLocale::Nicaragua: + if ((countryName = QCoreApplication::translate("country", "Nicaragua")) == "Nicaragua") countryName = ""; + break; + case QLocale::Niger: + if ((countryName = QCoreApplication::translate("country", "Niger")) == "Niger") countryName = ""; + break; + case QLocale::Nigeria: + if ((countryName = QCoreApplication::translate("country", "Nigeria")) == "Nigeria") countryName = ""; + break; + case QLocale::Niue: + if ((countryName = QCoreApplication::translate("country", "Niue")) == "Niue") countryName = ""; + break; + case QLocale::NorfolkIsland: + if ((countryName = QCoreApplication::translate("country", "NorfolkIsland")) == "NorfolkIsland") + countryName = ""; + break; + case QLocale::NorthernMarianaIslands: + if ((countryName = QCoreApplication::translate("country", "NorthernMarianaIslands")) == + "NorthernMarianaIslands") + countryName = ""; + break; + case QLocale::Norway: + if ((countryName = QCoreApplication::translate("country", "Norway")) == "Norway") countryName = ""; + break; + case QLocale::Oman: + if ((countryName = QCoreApplication::translate("country", "Oman")) == "Oman") countryName = ""; + break; + case QLocale::Pakistan: + if ((countryName = QCoreApplication::translate("country", "Pakistan")) == "Pakistan") countryName = ""; + break; + case QLocale::Palau: + if ((countryName = QCoreApplication::translate("country", "Palau")) == "Palau") countryName = ""; + break; + case QLocale::PalestinianTerritories: + if ((countryName = QCoreApplication::translate("country", "PalestinianTerritories")) == + "PalestinianTerritories") + countryName = ""; + break; + case QLocale::Panama: + if ((countryName = QCoreApplication::translate("country", "Panama")) == "Panama") countryName = ""; + break; + case QLocale::PapuaNewGuinea: + if ((countryName = QCoreApplication::translate("country", "PapuaNewGuinea")) == "PapuaNewGuinea") + countryName = ""; + break; + case QLocale::Paraguay: + if ((countryName = QCoreApplication::translate("country", "Paraguay")) == "Paraguay") countryName = ""; + break; + case QLocale::Peru: + if ((countryName = QCoreApplication::translate("country", "Peru")) == "Peru") countryName = ""; + break; + case QLocale::Philippines: + if ((countryName = QCoreApplication::translate("country", "Philippines")) == "Philippines") + countryName = ""; + break; + case QLocale::Poland: + if ((countryName = QCoreApplication::translate("country", "Poland")) == "Poland") countryName = ""; + break; + case QLocale::Portugal: + if ((countryName = QCoreApplication::translate("country", "Portugal")) == "Portugal") countryName = ""; + break; + case QLocale::PuertoRico: + if ((countryName = QCoreApplication::translate("country", "PuertoRico")) == "PuertoRico") countryName = ""; + break; + case QLocale::Qatar: + if ((countryName = QCoreApplication::translate("country", "Qatar")) == "Qatar") countryName = ""; + break; + case QLocale::Reunion: + if ((countryName = QCoreApplication::translate("country", "Reunion")) == "Reunion") countryName = ""; + break; + case QLocale::Romania: + if ((countryName = QCoreApplication::translate("country", "Romania")) == "Romania") countryName = ""; + break; + case QLocale::RussianFederation: + if ((countryName = QCoreApplication::translate("country", "RussianFederation")) == "RussianFederation") + countryName = ""; + break; + case QLocale::Rwanda: + if ((countryName = QCoreApplication::translate("country", "Rwanda")) == "Rwanda") countryName = ""; + break; + case QLocale::SaintHelena: + if ((countryName = QCoreApplication::translate("country", "SaintHelena")) == "SaintHelena") + countryName = ""; + break; + case QLocale::SaintKittsAndNevis: + if ((countryName = QCoreApplication::translate("country", "SaintKittsAndNevis")) == "SaintKittsAndNevis") + countryName = ""; + break; + case QLocale::SaintLucia: + if ((countryName = QCoreApplication::translate("country", "SaintLucia")) == "SaintLucia") countryName = ""; + break; + case QLocale::SaintPierreAndMiquelon: + if ((countryName = QCoreApplication::translate("country", "SaintPierreAndMiquelon")) == + "SaintPierreAndMiquelon") + countryName = ""; + break; + case QLocale::SaintVincentAndTheGrenadines: + if ((countryName = QCoreApplication::translate("country", "SaintVincentAndTheGrenadines")) == + "SaintVincentAndTheGrenadines") + countryName = ""; + break; + case QLocale::Samoa: + if ((countryName = QCoreApplication::translate("country", "Samoa")) == "Samoa") countryName = ""; + break; + case QLocale::SanMarino: + if ((countryName = QCoreApplication::translate("country", "SanMarino")) == "SanMarino") countryName = ""; + break; + case QLocale::SaoTomeAndPrincipe: + if ((countryName = QCoreApplication::translate("country", "SaoTomeAndPrincipe")) == "SaoTomeAndPrincipe") + countryName = ""; + break; + case QLocale::SaudiArabia: + if ((countryName = QCoreApplication::translate("country", "SaudiArabia")) == "SaudiArabia") + countryName = ""; + break; + case QLocale::Senegal: + if ((countryName = QCoreApplication::translate("country", "Senegal")) == "Senegal") countryName = ""; + break; + case QLocale::Serbia: + if ((countryName = QCoreApplication::translate("country", "Serbia")) == "Serbia") countryName = ""; + break; + case QLocale::Seychelles: + if ((countryName = QCoreApplication::translate("country", "Seychelles")) == "Seychelles") countryName = ""; + break; + case QLocale::SierraLeone: + if ((countryName = QCoreApplication::translate("country", "SierraLeone")) == "SierraLeone") + countryName = ""; + break; + case QLocale::Singapore: + if ((countryName = QCoreApplication::translate("country", "Singapore")) == "Singapore") countryName = ""; + break; + case QLocale::Slovakia: + if ((countryName = QCoreApplication::translate("country", "Slovakia")) == "Slovakia") countryName = ""; + break; + case QLocale::Slovenia: + if ((countryName = QCoreApplication::translate("country", "Slovenia")) == "Slovenia") countryName = ""; + break; + case QLocale::SolomonIslands: + if ((countryName = QCoreApplication::translate("country", "SolomonIslands")) == "SolomonIslands") + countryName = ""; + break; + case QLocale::Somalia: + if ((countryName = QCoreApplication::translate("country", "Somalia")) == "Somalia") countryName = ""; + break; + case QLocale::SouthAfrica: + if ((countryName = QCoreApplication::translate("country", "SouthAfrica")) == "SouthAfrica") + countryName = ""; + break; + case QLocale::Spain: + if ((countryName = QCoreApplication::translate("country", "Spain")) == "Spain") countryName = ""; + break; + case QLocale::SriLanka: + if ((countryName = QCoreApplication::translate("country", "SriLanka")) == "SriLanka") countryName = ""; + break; + case QLocale::Sudan: + if ((countryName = QCoreApplication::translate("country", "Sudan")) == "Sudan") countryName = ""; + break; + case QLocale::Suriname: + if ((countryName = QCoreApplication::translate("country", "Suriname")) == "Suriname") countryName = ""; + break; + case QLocale::Swaziland: + if ((countryName = QCoreApplication::translate("country", "Swaziland")) == "Swaziland") countryName = ""; + break; + case QLocale::Sweden: + if ((countryName = QCoreApplication::translate("country", "Sweden")) == "Sweden") countryName = ""; + break; + case QLocale::Switzerland: + if ((countryName = QCoreApplication::translate("country", "Switzerland")) == "Switzerland") + countryName = ""; + break; + case QLocale::Syria: + if ((countryName = QCoreApplication::translate("country", "Syria")) == "Syria") countryName = ""; + break; + case QLocale::Taiwan: + if ((countryName = QCoreApplication::translate("country", "Taiwan")) == "Taiwan") countryName = ""; + break; + case QLocale::Tajikistan: + if ((countryName = QCoreApplication::translate("country", "Tajikistan")) == "Tajikistan") countryName = ""; + break; + case QLocale::Tanzania: + if ((countryName = QCoreApplication::translate("country", "Tanzania")) == "Tanzania") countryName = ""; + break; + case QLocale::Thailand: + if ((countryName = QCoreApplication::translate("country", "Thailand")) == "Thailand") countryName = ""; + break; + case QLocale::Togo: + if ((countryName = QCoreApplication::translate("country", "Togo")) == "Togo") countryName = ""; + break; + case QLocale::TokelauCountry: + if ((countryName = QCoreApplication::translate("country", "Tokelau")) == "Tokelau") countryName = ""; + break; + case QLocale::Tonga: + if ((countryName = QCoreApplication::translate("country", "Tonga")) == "Tonga") countryName = ""; + break; + case QLocale::TrinidadAndTobago: + if ((countryName = QCoreApplication::translate("country", "TrinidadAndTobago")) == "TrinidadAndTobago") + countryName = ""; + break; + case QLocale::Tunisia: + if ((countryName = QCoreApplication::translate("country", "Tunisia")) == "Tunisia") countryName = ""; + break; + case QLocale::Turkey: + if ((countryName = QCoreApplication::translate("country", "Turkey")) == "Turkey") countryName = ""; + break; + case QLocale::Turkmenistan: + if ((countryName = QCoreApplication::translate("country", "Turkmenistan")) == "Turkmenistan") + countryName = ""; + break; + case QLocale::TurksAndCaicosIslands: + if ((countryName = QCoreApplication::translate("country", "TurksAndCaicosIslands")) == + "TurksAndCaicosIslands") + countryName = ""; + break; + case QLocale::TuvaluCountry: + if ((countryName = QCoreApplication::translate("country", "Tuvalu")) == "Tuvalu") countryName = ""; + break; + case QLocale::Uganda: + if ((countryName = QCoreApplication::translate("country", "Uganda")) == "Uganda") countryName = ""; + break; + case QLocale::Ukraine: + if ((countryName = QCoreApplication::translate("country", "Ukraine")) == "Ukraine") countryName = ""; + break; + case QLocale::UnitedArabEmirates: + if ((countryName = QCoreApplication::translate("country", "UnitedArabEmirates")) == "UnitedArabEmirates") + countryName = ""; + break; + case QLocale::UnitedKingdom: + if ((countryName = QCoreApplication::translate("country", "UnitedKingdom")) == "UnitedKingdom") + countryName = ""; + break; + case QLocale::UnitedStates: + if ((countryName = QCoreApplication::translate("country", "UnitedStates")) == "UnitedStates") + countryName = ""; + break; + case QLocale::Uruguay: + if ((countryName = QCoreApplication::translate("country", "Uruguay")) == "Uruguay") countryName = ""; + break; + case QLocale::Uzbekistan: + if ((countryName = QCoreApplication::translate("country", "Uzbekistan")) == "Uzbekistan") countryName = ""; + break; + case QLocale::Vanuatu: + if ((countryName = QCoreApplication::translate("country", "Vanuatu")) == "Vanuatu") countryName = ""; + break; + case QLocale::Venezuela: + if ((countryName = QCoreApplication::translate("country", "Venezuela")) == "Venezuela") countryName = ""; + break; + case QLocale::Vietnam: + if ((countryName = QCoreApplication::translate("country", "Vietnam")) == "Vietnam") countryName = ""; + break; + case QLocale::WallisAndFutunaIslands: + if ((countryName = QCoreApplication::translate("country", "WallisAndFutunaIslands")) == + "WallisAndFutunaIslands") + countryName = ""; + break; + case QLocale::Yemen: + if ((countryName = QCoreApplication::translate("country", "Yemen")) == "Yemen") countryName = ""; + break; + case QLocale::Zambia: + if ((countryName = QCoreApplication::translate("country", "Zambia")) == "Zambia") countryName = ""; + break; + case QLocale::Zimbabwe: + if ((countryName = QCoreApplication::translate("country", "Zimbabwe")) == "Zimbabwe") countryName = ""; + break; + default: { + countryName = QLocale::countryToString(p_country); + } + } + if (countryName == "") countryName = QLocale::countryToString(p_country); + return countryName; +} +QString Utils::toDateString(QDateTime date, const QString &format) { + return QLocale().toString(date, (!format.isEmpty() ? format : "ddd d MMMM yyyy")); +} + +QString Utils::toDateString(QDate date, const QString &format) { + return QLocale().toString(date, (!format.isEmpty() ? format : "ddd d, MMMM")); +} + +QString Utils::toDateDayString(const QDateTime &date) { + auto res = QLocale().toString(date, "d"); + return res; +} + +QString Utils::toDateHourString(const QDateTime &date) { + return QLocale().toString(date, "hh:mm"); +} + +QString Utils::toDateDayNameString(const QDateTime &date) { + return QLocale().toString(date, "ddd"); +} + +QString Utils::toDateMonthString(const QDateTime &date) { + return QLocale().toString(date, "MMMM"); +} + +bool Utils::isCurrentDay(QDateTime date) { + auto dateDayNum = date.date().day(); + auto currentDate = QDateTime::currentDateTime(); + auto currentDayNum = currentDate.date().day(); + auto daysTo = date.daysTo(currentDate); + return (dateDayNum == currentDayNum && daysTo == 0); +} + +bool Utils::isCurrentDay(QDate date) { + auto currentDate = QDate::currentDate(); + return date.month() == currentDate.month() && date.year() == currentDate.year() && date.day() == currentDate.day(); +} + +bool Utils::isCurrentMonth(QDate date) { + auto currentDate = QDate::currentDate(); + return date.month() == currentDate.month() && date.year() == currentDate.year(); +} + +bool Utils::isBeforeToday(QDate date) { + auto currentDate = QDate::currentDate(); + auto res = date.daysTo(currentDate) > 0; + return res; +} + +bool Utils::datesAreEqual(const QDate &a, const QDate &b) { + return a.month() == b.month() && a.year() == b.year() && a.day() == b.day(); +} + +QDateTime Utils::createDateTime(const QDate &date, int hour, int min) { + QTime time(hour, min); + return QDateTime(date, time); +} + +int Utils::secsTo(const QString &startTime, const QString &endTime) { + QDateTime startDate(QDateTime::fromString(startTime, "hh:mm")); + QDateTime endDate(QDateTime::fromString(endTime, "hh:mm")); + auto res = startDate.secsTo(endDate); + return res; +} + +QDateTime Utils::addSecs(QDateTime date, int secs) { + date = date.addSecs(secs); + return date; +} + +int Utils::getYear(const QDate &date) { + return date.year(); +} + +bool Utils::isMe(const QString &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; + } + return false; + } else { + auto accountAddr = CoreModel::getInstance()->getCore()->getDefaultAccount()->getContactAddress(); + return linAddr && accountAddr ? accountAddr->weakEqual(linAddr) : false; + } +} +// QDateTime dateTime(QDateTime::fromString(date, "yyyy-MM-dd hh:mm:ss")); + +// bool Utils::isMe(const QString &address) { +// return !address.isEmpty() ? isMe(Utils::interpretUrl(address)) : false; +// } + +// bool Utils::isMe(const std::shared_ptr &address) { +// if (!CoreModel::getInstance() +// ->getCore() +// ->getDefaultAccount()) { // Default account is selected : Me is all local accounts. +// return CoreModel::getInstance()->getAccountSettingsModel()->findAccount(address) != nullptr; +// } else +// return address ? CoreModel::getInstance()->getAccountSettingsModel()->getUsedSipAddress()->weakEqual(address) +// : false; +// } diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index ec2a46cfd..85aa4c84e 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -62,6 +62,8 @@ public: const QHash &headers = {}); Q_INVOKABLE static void openCallsWindow(CallGui *call); Q_INVOKABLE static QQuickWindow *getMainWindow(); + Q_INVOKABLE static void + showInformationPopup(const QString &title, const QString &description, bool isSuccess = true); Q_INVOKABLE static QQuickWindow *getCallsWindow(CallGui *callGui); Q_INVOKABLE static void closeCallsWindow(); Q_INVOKABLE static VariantObject *haveAccount(); @@ -74,8 +76,26 @@ public: Q_INVOKABLE static QStringList generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode); Q_INVOKABLE static int getRandomIndex(int size); Q_INVOKABLE static void copyToClipboard(const QString &text); - static QString generateSavedFilename(const QString &from, const QString &to); + Q_INVOKABLE static QString toDateString(QDateTime date, const QString &format = ""); + Q_INVOKABLE static QString toDateString(QDate date, const QString &format = ""); + Q_INVOKABLE static QString toDateDayString(const QDateTime &date); + Q_INVOKABLE static QString toDateHourString(const QDateTime &date); + Q_INVOKABLE static QString toDateDayNameString(const QDateTime &date); + Q_INVOKABLE static QString toDateMonthString(const QDateTime &date); + Q_INVOKABLE static bool isCurrentDay(QDateTime date); + Q_INVOKABLE static bool isCurrentDay(QDate date); + Q_INVOKABLE static bool isCurrentMonth(QDate date); + Q_INVOKABLE static bool isBeforeToday(QDate date); + Q_INVOKABLE static bool datesAreEqual(const QDate &a, const QDate &b); + Q_INVOKABLE static QDateTime createDateTime(const QDate &date, int hour, int min); + Q_INVOKABLE static int getYear(const QDate &date); + Q_INVOKABLE static int secsTo(const QString &start, const QString &end); + Q_INVOKABLE static QDateTime addSecs(QDateTime date, int secs); Q_INVOKABLE static QString generateLinphoneSipAddress(const QString &uri); + Q_INVOKABLE static QString findAvatarByAddress(const QString &address); + static QString generateSavedFilename(const QString &from, const QString &to); + Q_INVOKABLE static bool isMe(const QString &address); + static QString getCountryName(const QLocale::Country &p_country); static QString getApplicationProduct(); static QString getOsProduct(); diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index 0461ab735..9805aaed3 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -345,8 +345,9 @@ Item { id: callPage } ContactPage{} + Item{} //ConversationPage{} - //MeetingPage{} + MeetingPage{} } } } diff --git a/Linphone/view/App/Main.qml b/Linphone/view/App/Main.qml index 71e3259d8..9609b6f3c 100644 --- a/Linphone/view/App/Main.qml +++ b/Linphone/view/App/Main.qml @@ -24,6 +24,121 @@ Window { mainWindowStackView.currentItem.transferCallSucceed() } + function showInformationPopup(title, description, isSuccess) { + var infoPopup = popupComp.createObject(popupLayout, {"title": title, "description": description, "isSuccess": isSuccess}) + // informationPopup.title = title + // informationPopup.description = description + // informationPopup.isSuccess = isSuccess + // infoPopup.y = popupLayout.nextY - infoPopup.height + infoPopup.index = popupLayout.popupList.length + popupLayout.popupList.push(infoPopup) + infoPopup.open() + } + + Component { + id: popupComp + Popup { + id: informationPopup + property bool isSuccess: true + property string title + property string description + property int index + onAboutToShow: { + autoClosePopup.restart() + } + onAboutToHide: { + popupLayout.popupList.splice(informationPopup.index, 1) + } + closePolicy: Popup.NoAutoClose + x : parent.x + parent.width - width + // y : parent.y + parent.height - height + rightMargin: 20 * DefaultStyle.dp + bottomMargin: 20 * DefaultStyle.dp + padding: 20 * DefaultStyle.dp + underlineColor: informationPopup.isSuccess ? DefaultStyle.success_500main : DefaultStyle.danger_500main + radius: 0 + onHoveredChanged: { + if (hovered) autoClosePopup.stop() + else autoClosePopup.restart() + } + Timer { + id: autoClosePopup + interval: 5000 + onTriggered: { + informationPopup.close() + } + } + contentItem: RowLayout { + spacing: 15 * DefaultStyle.dp + EffectImage { + imageSource: informationPopup.isSuccess ? AppIcons.smiley : AppIcons.smileySad + colorizationColor: informationPopup.isSuccess ? DefaultStyle.success_500main : DefaultStyle.danger_500main + Layout.preferredWidth: 32 * DefaultStyle.dp + Layout.preferredHeight: 32 * DefaultStyle.dp + width: 32 * DefaultStyle.dp + height: 32 * DefaultStyle.dp + } + Rectangle { + Layout.preferredWidth: 1 * DefaultStyle.dp + Layout.preferredHeight: parent.height + color: DefaultStyle.main2_200 + } + ColumnLayout { + RowLayout { + Layout.fillWidth: true + Text { + Layout.fillWidth: true + text: informationPopup.title + color: informationPopup.isSuccess ? DefaultStyle.success_500main : DefaultStyle.danger_500main + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + Button { + Layout.preferredWidth: 20 * DefaultStyle.dp + Layout.preferredHeight: 20 * DefaultStyle.dp + Layout.alignment: Qt.AlignTop | Qt.AlignRight + visible: informationPopup.hovered || hovered + background: Item{} + icon.source: AppIcons.closeX + onClicked: informationPopup.close() + } + } + Text { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.maximumWidth: 300 * DefaultStyle.dp + text: informationPopup.description + wrapMode: Text.WordWrap + color: DefaultStyle.main2_500main + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + } + } + } + + ColumnLayout { + id: popupLayout + anchors.fill: parent + Layout.alignment: Qt.AlignBottom + property int nextY: mainWindow.height + property list popupList + property int popupCount: popupList.length + spacing: 15 + onPopupCountChanged: { + nextY = mainWindow.height + for(var i = 0; i < popupCount; ++i) { + popupList[i].y = nextY - popupList[i].height + nextY = nextY - popupList[i].height - 15 + } + } + } + AccountProxy { // TODO : change this so it does not display the main page for one second // when we fail trying to connect the first account (account is added and diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 61e676e82..ca21f9f89 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -2,10 +2,17 @@ list(APPEND _LINPHONEAPP_QML_FILES view/App/Main.qml view/App/CallsWindow.qml - view/App/Layout/ContactLayout.qml view/App/Layout/LoginLayout.qml view/App/Layout/MainLayout.qml + view/Layout/Conference/IncallGrid.qml + view/Layout/Contact/ContactLayout.qml + view/Layout/Meeting/AddParticipantsLayout.qml + + view/Layout/FormItemLayout.qml + view/Layout/Mosaic.qml + view/Layout/Section.qml + view/Item/Account/Accounts.qml view/Item/Call/CallContactsLists.qml @@ -22,9 +29,14 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/Contact/ContactEdition.qml view/Item/Contact/ContactsList.qml view/Item/Contact/Sticker.qml + + view/Item/Meeting/MeetingList.qml + view/Item/Meeting/NewMeeting.qml view/Item/BusyIndicator.qml view/Item/Button.qml + view/Item/Calendar.qml + view/Item/CalendarComboBox.qml view/Item/Carousel.qml view/Item/CheckBox.qml view/Item/ComboBox.qml @@ -45,9 +57,12 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/RoundedBackgroundControl.qml view/Item/SearchBar.qml view/Item/Slider.qml + view/Item/Switch.qml view/Item/TabBar.qml view/Item/Text.qml - view/Item/TextInput.qml + view/Item/TextArea.qml + view/Item/TextField.qml + view/Item/TimeComboBox.qml view/Item/ToolTip.qml view/Item/VerticalTabBar.qml view/Item/ZrtpTokenAuthenticationDialog.qml @@ -66,6 +81,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Page/Main/AbstractMainPage.qml view/Page/Main/CallPage.qml view/Page/Main/ContactPage.qml + view/Page/Main/MeetingPage.qml view/Tool/utils.js # Prototypes diff --git a/Linphone/view/Item/Button.qml b/Linphone/view/Item/Button.qml index 8221deac9..ab08874a5 100644 --- a/Linphone/view/Item/Button.qml +++ b/Linphone/view/Item/Button.qml @@ -15,6 +15,7 @@ Control.Button { property bool underline: false property bool shadowEnabled: false property var contentImageColor + property alias contentText: contentText hoverEnabled: true icon.width: width icon.height: height @@ -69,6 +70,7 @@ Control.Button { ? 1 : 2 + width: mainItem.width Text { id: contentText horizontalAlignment: Text.AlignHCenter @@ -76,10 +78,10 @@ Control.Button { Layout.alignment: Qt.AlignCenter Layout.fillWidth: true Layout.fillHeight: true - width: implicitWidth height: implicitHeight - wrapMode: Text.WordWrap + wrapMode: Text.WrapAnywhere text: mainItem.text + maximumLineCount: 1 color: inversedColors ? mainItem.color : DefaultStyle.grey_0 font { pixelSize: mainItem.textSize diff --git a/Linphone/view/Item/Calendar.qml b/Linphone/view/Item/Calendar.qml new file mode 100644 index 000000000..2ef6f2524 --- /dev/null +++ b/Linphone/view/Item/Calendar.qml @@ -0,0 +1,117 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick.Controls as Control +import QtQuick.Effects + +import Linphone +import ConstantsCpp 1.0 +import UtilsCpp 1.0 + +ListView { + id: mainItem + // width: 400 * DefaultStyle.dp + // height: 400 * DefaultStyle.dp + snapMode: ListView.SnapOneItem + orientation: Qt.Horizontal + clip: true + property int maxYears: 5 + readonly property var currentDate: new Date() + Layout.fillWidth: true + Layout.fillHeight: true + highlightMoveDuration: 100 + + property var selectedDate: new Date() + + model: Control.CalendarModel { + id: calendarModel + from: new Date() + to: new Date(2025, 12, 31) + } + + delegate: ColumnLayout { + width: mainItem.width + height: mainItem.height + RowLayout { + Layout.fillWidth: true + Text { + text: new Date(model.year, model.month, 15).toLocaleString(Qt.locale(ConstantsCpp.DefaultLocale), 'MMMM yyyy')// 15 because of timezones that can change the date for localeString + font { + pixelSize: 14 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + Item { + Layout.fillWidth: true + } + Button { + Layout.preferredWidth: 10 * DefaultStyle.dp + Layout.preferredHeight: 20 * DefaultStyle.dp + background: Item{} + icon.source: AppIcons.leftArrow + onClicked: if (mainItem.currentIndex > 0) --mainItem.currentIndex + } + Button { + Layout.preferredWidth: 20 * DefaultStyle.dp + Layout.preferredHeight: 20 * DefaultStyle.dp + background: Item{} + icon.source: AppIcons.rightArrow + onClicked: if (mainItem.currentIndex < mainItem.count) ++mainItem.currentIndex + } + } + Control.DayOfWeekRow { + locale: monthGrid.locale + Layout.column: 1 + Layout.fillWidth: true + delegate: Text { + text: model.shortName + color: DefaultStyle.main2_400 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + + Control.MonthGrid { + id: monthGrid + Layout.fillWidth: true + Layout.fillHeight: true + + year: model.year + month: model.month + // locale: Qt.locale("en_US") + delegate: Item { + property bool isSelectedDay: UtilsCpp.datesAreEqual(mainItem.selectedDate, model.date) + Rectangle { + anchors.centerIn: parent + width: 30 * DefaultStyle.dp + height: 30 * DefaultStyle.dp + radius: 50 * DefaultStyle.dp + color: isSelectedDay ? DefaultStyle.main1_500_main : "transparent" + } + Text { + anchors.centerIn: parent + text: monthGrid.locale.toString(model.date, "d") + color: isSelectedDay + ? DefaultStyle.grey_0 + : UtilsCpp.isCurrentDay(model.date) + ? DefaultStyle.main1_500_main + : UtilsCpp.isCurrentMonth(model.date) + ? DefaultStyle.main2_700 + : DefaultStyle.main2_400 + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + onClicked: (date) => { + if (UtilsCpp.isBeforeToday(date)) return; + mainItem.selectedDate = date + } + } + } +} \ No newline at end of file diff --git a/Linphone/view/Item/CalendarComboBox.qml b/Linphone/view/Item/CalendarComboBox.qml new file mode 100644 index 000000000..3cfa0f5d8 --- /dev/null +++ b/Linphone/view/Item/CalendarComboBox.qml @@ -0,0 +1,45 @@ +import QtQuick +import QtQuick.Controls as Control +import QtQuick.Effects +import Linphone + +ComboBox { + id: mainItem + property var selectedDate: calendar.selectedDate + property alias calendar: calendar + contentItem: Text { + text: Qt.formatDate(calendar.selectedDate, "ddd d, MMMM") + anchors.fill: parent + anchors.leftMargin: 15 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + font { + pixelSize: 14 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + popup: Control.Popup { + y: mainItem.height + width: 321 * DefaultStyle.dp + height: 270 * DefaultStyle.dp + closePolicy: Popup.NoAutoClose + background: Item { + Rectangle { + id: calendarBg + anchors.fill: parent + color: DefaultStyle.grey_0 + radius: 16 * DefaultStyle.dp + } + MultiEffect { + anchors.fill: calendarBg + source: calendarBg + shadowEnabled: true + shadowBlur: 1 + shadowOpacity: 0.1 + } + } + contentItem: Calendar { + id: calendar + } + } +} \ No newline at end of file diff --git a/Linphone/view/Item/Call/CallContactsLists.qml b/Linphone/view/Item/Call/CallContactsLists.qml index 23f289c67..b53a857a6 100644 --- a/Linphone/view/Item/Call/CallContactsLists.qml +++ b/Linphone/view/Item/Call/CallContactsLists.qml @@ -235,12 +235,26 @@ Item { onContactSelected: (contact) => { if (contact.core.allAddresses.length > 1) { startCallPopup.contact = contact - startCallPopup.open() + onSelectedContactChanged: { + if (selectedContact) { + if (selectedContact.core.allAddresses.length > 1) { + startCallPopup.selectedContact = selectedContact + startCallPopup.open() - } else { - mainItem.callButtonPressed(contact.core.defaultAddress) + } else { + mainItem.callButtonPressed(selectedContact.core.defaultAddress) + } } } + // onContactSelected: (contact) => { + // if (contact.core.allAddresses.length > 1) { + // startCallPopup.contact = contact + // startCallPopup.open() + + // } else { + // mainItem.callButtonPressed(contact.core.defaultAddress) + // } + // } } } ColumnLayout { @@ -264,20 +278,36 @@ Item { sourceFlags: LinphoneEnums.MagicSearchSource.All aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend } - onContactSelected: (contact) => { - if (contact.core.allAddresses.length > 1) { - startCallPopup.contact = contact - startCallPopup.open() + onSelectedContactChanged: { + if (selectedContact) { + if (selectedContact.core.allAddresses.length > 1) { + startCallPopup.selectedContact = selectedContact + startCallPopup.open() - } else { - var addressToCall = contact.core.defaultAddress.length === 0 - ? contact.core.phoneNumbers.length === 0 - ? "" - : contact.core.phoneNumbers[0].address - : contact.core.defaultAddress - if (addressToCall.length != 0) mainItem.callButtonPressed(addressToCall) + } else { + var addressToCall = selectedContact.core.defaultAddress.length === 0 + ? selectedContact.core.phoneNumbers.length === 0 + ? "" + : selectedContact.core.phoneNumbers[0].address + : selectedContact.core.defaultAddress + if (addressToCall.length != 0) mainItem.callButtonPressed(addressToCall) + } } } + // onContactSelected: (contact) => { + // if (contact.core.allAddresses.length > 1) { + // startCallPopup.contact = contact + // startCallPopup.open() + + // } else { + // var addressToCall = contact.core.defaultAddress.length === 0 + // ? contact.core.phoneNumbers.length === 0 + // ? "" + // : contact.core.phoneNumbers[0].address + // : contact.core.defaultAddress + // if (addressToCall.length != 0) mainItem.callButtonPressed(addressToCall) + // } + // } } } Item { diff --git a/Linphone/view/Item/ComboBox.qml b/Linphone/view/Item/ComboBox.qml index 4c457c01e..1a2a86e32 100644 --- a/Linphone/view/Item/ComboBox.qml +++ b/Linphone/view/Item/ComboBox.qml @@ -4,196 +4,198 @@ import QtQuick.Layouts 1.0 import QtQuick.Effects import Linphone -ColumnLayout { +Control.ComboBox { id: mainItem - property string label: "" // Usage : each item of the model list must be {text: ..., img: ...} // If string list, only text part of the delegate will be filled - property var model: [] - property alias combobox: combobox - readonly property string currentText: selectedItemText.text - property bool enableBackgroundColors: true - readonly property bool hasActiveFocus: combobox.activeFocus + // readonly property string currentText: selectedItemText.text + // Layout.preferredWidth: mainItem.width + // Layout.preferredHeight: mainItem.height + property alias listView: listView + property string constantImageSource + property int pixelSize: 14 * DefaultStyle.dp + property int weight: 400 * DefaultStyle.dp + property int leftMargin: 10 * DefaultStyle.dp - Text { - visible: label.length > 0 - verticalAlignment: Text.AlignVCenter - text: mainItem.label - color: combobox.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.main2_600 - font { - pixelSize: 13 * DefaultStyle.dp - weight: 700 * DefaultStyle.dp - } + onConstantImageSourceChanged: if (constantImageSource) selectedItemImg.source = constantImageSource + onCurrentIndexChanged: { + var item = model[currentIndex] + if (!item) item = model.getAt(currentIndex) + selectedItemText.text = item.text + ? item.text + : item + ? item + : "" + selectedItemImg.source = constantImageSource + ? constantImageSource + : item.img + ? item.img + : "" + console.log("const", constantImageSource, item.img) } - Control.ComboBox { - id: combobox - model: mainItem.model - Layout.preferredWidth: mainItem.width - background: Rectangle { - implicitWidth: mainItem.width - implicitHeight: 49 * DefaultStyle.dp - radius: 63 * DefaultStyle.dp - color: combobox.enabled ? DefaultStyle.grey_100 : DefaultStyle.grey_200 - border.color: combobox.enabled ? DefaultStyle.grey_200 : DefaultStyle.grey_400 - } - contentItem: Item { + background: Rectangle { + anchors.fill: mainItem + radius: 63 * DefaultStyle.dp + color: mainItem.enabled ? DefaultStyle.grey_100 : DefaultStyle.grey_200 + border.color: mainItem.enabled ? DefaultStyle.grey_200 : DefaultStyle.grey_400 + } + contentItem: Item { + Image { + id: selectedItemImg + source: mainItem.constantImageSource ? mainItem.constantImageSource : "" + visible: source != "" + sourceSize.width: 24 * DefaultStyle.dp + width: visible ? 24 * DefaultStyle.dp : 0 + fillMode: Image.PreserveAspectFit + anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.right: indicImage.right - Image { - id: selectedItemImg - visible: source != "" - sourceSize.width: 20 * DefaultStyle.dp - width: visible ? 20 * DefaultStyle.dp : 0 - fillMode: Image.PreserveAspectFit - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: visible ? 10 * DefaultStyle.dp : 0 - } + anchors.leftMargin: visible ? mainItem.leftMargin : 0 + } - Text { - id: selectedItemText - color: combobox.enabled ? DefaultStyle.main2_600 : DefaultStyle.grey_400 - elide: Text.ElideRight - maximumLineCount: 1 - font { - pixelSize: 14 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } - anchors.left: selectedItemImg.right - anchors.leftMargin: selectedItemImg.visible ? 5 * DefaultStyle.dp : 10 * DefaultStyle.dp - anchors.right: parent.right - anchors.rightMargin: 20 * DefaultStyle.dp - anchors.verticalCenter: parent.verticalCenter + Text { + id: selectedItemText + color: mainItem.enabled ? DefaultStyle.main2_600 : DefaultStyle.grey_400 + elide: Text.ElideRight + maximumLineCount: 2 + wrapMode: Text.WrapAnywhere + font { + pixelSize: mainItem.pixelSize + weight: mainItem.weight } + anchors.left: selectedItemImg.right + anchors.leftMargin: selectedItemImg.visible ? 5 * DefaultStyle.dp : 10 * DefaultStyle.dp + anchors.right: parent.right + anchors.rightMargin: 20 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + } - Component.onCompleted: { - var index = combobox.currentIndex < 0 ? 0 : combobox.currentIndex + Component.onCompleted: { + var index = mainItem.currentIndex < 0 ? 0 : mainItem.currentIndex + if (mainItem.model && mainItem.model[index]) { if (mainItem.model[index] && mainItem.model[index].img) { selectedItemImg.source = mainItem.model[index].img } - if (mainItem.model[index] && mainItem.model[index].text) + else if (mainItem.model[index] && mainItem.model[index].text) selectedItemText.text = mainItem.model[index].text - else if (mainItem.model[index]) + else selectedItemText.text = mainItem.model[index] } } + } - indicator: Image { - id: indicImage - anchors.right: parent.right - anchors.rightMargin: 10 * DefaultStyle.dp - anchors.verticalCenter: parent.verticalCenter - source: AppIcons.downArrow - } + indicator: Image { + id: indicImage + z: 1 + anchors.right: parent.right + anchors.rightMargin: 10 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + source: AppIcons.downArrow + } - popup: Control.Popup { - id: listPopup - y: combobox.height - 1 - width: combobox.width - implicitHeight: contentItem.implicitHeight - padding: 1 * DefaultStyle.dp + popup: Control.Popup { + id: popup + y: mainItem.height - 1 + width: mainItem.width + implicitHeight: contentItem.implicitHeight + padding: 1 * DefaultStyle.dp - contentItem: ListView { - id: listView - clip: true - implicitHeight: contentHeight - model: combobox.model - currentIndex: combobox.highlightedIndex >= 0 ? combobox.highlightedIndex : 0 - highlightFollowsCurrentItem: true - highlight: Rectangle { - width: listView.width - color: DefaultStyle.main2_300 - radius: 15 * DefaultStyle.dp - y: listView.currentItem? listView.currentItem.y : 0 - } + contentItem: ListView { + id: listView + clip: true + implicitHeight: contentHeight + height: contentHeight + model: mainItem.model + currentIndex: mainItem.highlightedIndex >= 0 ? mainItem.highlightedIndex : 0 + highlightFollowsCurrentItem: true + highlightMoveDuration: -1 + highlightMoveVelocity: -1 + highlight: Rectangle { + width: listView.width + color: DefaultStyle.main2_200 + radius: 15 * DefaultStyle.dp + y: listView.currentItem? listView.currentItem.y : 0 + } - delegate: Item { - width:combobox.width - height: combobox.height + delegate: Item { + width:mainItem.width + height: mainItem.height + // anchors.left: listView.left + // anchors.right: listView.right + + Image { + id: delegateImg + visible: source != "" + width: visible ? 20 * DefaultStyle.dp : 0 + sourceSize.width: 20 * DefaultStyle.dp + source: modelData.img ? modelData.img : "" + fillMode: Image.PreserveAspectFit anchors.left: parent.left + anchors.leftMargin: visible ? 10 * DefaultStyle.dp : 0 + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: modelData.text + ? modelData.text + : modelData + ? modelData + : "" + elide: Text.ElideRight + maximumLineCount: 1 + wrapMode: Text.WrapAnywhere + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + anchors.verticalCenter: parent.verticalCenter + anchors.left: delegateImg.right + anchors.leftMargin: delegateImg.visible ? 5 * DefaultStyle.dp : 10 * DefaultStyle.dp anchors.right: parent.right - - Image { - id: delegateImg - visible: source != "" - width: visible ? 20 * DefaultStyle.dp : 0 - sourceSize.width: 20 * DefaultStyle.dp - source: modelData.img ? modelData.img : "" - fillMode: Image.PreserveAspectFit - anchors.left: parent.left - anchors.leftMargin: visible ? 10 * DefaultStyle.dp : 0 - anchors.verticalCenter: parent.verticalCenter - } - - Text { - text: modelData.text - ? modelData.text - : modelData - ? modelData - : "" - elide: Text.ElideRight - maximumLineCount: 1 - font { - pixelSize: 14 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } - anchors.verticalCenter: parent.verticalCenter - anchors.left: delegateImg.right - anchors.leftMargin: delegateImg.visible ? 5 * DefaultStyle.dp : 10 * DefaultStyle.dp - anchors.right: parent.right - anchors.rightMargin: 20 * DefaultStyle.dp - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - Rectangle { - anchors.fill: parent - opacity: 0.1 - radius: 15 * DefaultStyle.dp - color: DefaultStyle.main2_500main - visible: parent.containsMouse - } - onPressed: { - combobox.state = "" - selectedItemText.text = modelData.text - ? modelData.text - : modelData - ? modelData - : "" - selectedItemImg.source = modelData.img ? modelData.img : "" - combobox.currentIndex = index - listPopup.close() - } - } + anchors.rightMargin: 20 * DefaultStyle.dp } - Control.ScrollIndicator.vertical: Control.ScrollIndicator { } - } - - onOpened: { - listView.positionViewAtIndex(listView.currentIndex, ListView.Center) - } - - background: Item { - implicitWidth: mainItem.width - implicitHeight: 30 * DefaultStyle.dp - Rectangle { - id: cboxBg + MouseArea { anchors.fill: parent - radius: 15 * DefaultStyle.dp + hoverEnabled: true + Rectangle { + anchors.fill: parent + opacity: 0.1 + radius: 15 * DefaultStyle.dp + color: DefaultStyle.main2_500main + visible: parent.containsMouse + } + onClicked: { + mainItem.currentIndex = index + popup.close() + } } - MultiEffect { - anchors.fill: cboxBg - source: cboxBg - shadowEnabled: true - shadowColor: DefaultStyle.grey_1000 - shadowBlur: 1 - shadowOpacity: 0.1 - } - } + } + + Control.ScrollIndicator.vertical: Control.ScrollIndicator { } } + + onOpened: { + listView.positionViewAtIndex(listView.currentIndex, ListView.Center) + } + + background: Item { + implicitWidth: mainItem.width + implicitHeight: 30 * DefaultStyle.dp + Rectangle { + id: cboxBg + anchors.fill: parent + radius: 15 * DefaultStyle.dp + } + MultiEffect { + anchors.fill: cboxBg + source: cboxBg + shadowEnabled: true + shadowColor: DefaultStyle.grey_1000 + shadowBlur: 1 + shadowOpacity: 0.1 + } + } } } diff --git a/Linphone/view/Item/Contact/Avatar.qml b/Linphone/view/Item/Contact/Avatar.qml index 55e1fae6d..48147bd57 100644 --- a/Linphone/view/Item/Contact/Avatar.qml +++ b/Linphone/view/Item/Contact/Avatar.qml @@ -26,7 +26,9 @@ StackView { property string displayNameVal: displayNameObj ? displayNameObj.value : "" property bool haveAvatar: (account && account.core.pictureUri ) || (contact && contact.core.pictureUri) - + || computedAvatarUri.length != 0 + property string computedAvatarUri: UtilsCpp.findAvatarByAddress(address) + onHaveAvatarChanged: replace(haveAvatar ? avatar : initials, StackView.Immediate) property bool secured: false @@ -134,7 +136,7 @@ StackView { anchors.centerIn: parent source: mainItem.account && mainItem.account.core.pictureUri || mainItem.contact && mainItem.contact.core.pictureUri - || "" + || computedAvatarUri mipmap: true } ShaderEffect { diff --git a/Linphone/view/Item/Contact/ContactEdition.qml b/Linphone/view/Item/Contact/ContactEdition.qml index f59e5bb09..2e8b20b2d 100644 --- a/Linphone/view/Item/Contact/ContactEdition.qml +++ b/Linphone/view/Item/Contact/ContactEdition.qml @@ -103,29 +103,37 @@ ColumnLayout { Layout.bottomMargin: 50 * DefaultStyle.dp ColumnLayout { spacing: 20 * DefaultStyle.dp - TextInput { + FormItemLayout { label: qsTr("Prénom") - initialText: contact.core.givenName - onEditingFinished: contact.core.givenName = text - backgroundColor: DefaultStyle.grey_0 + contentItem: TextField { + initialText: contact.core.givenName + onEditingFinished: contact.core.givenName = text + backgroundColor: DefaultStyle.grey_0 + } } - TextInput { + FormItemLayout { label: qsTr("Nom") - initialText: contact.core.familyName - onEditingFinished: contact.core.familyName = text - backgroundColor: DefaultStyle.grey_0 + contentItem: TextField { + initialText: contact.core.familyName + onEditingFinished: contact.core.familyName = text + backgroundColor: DefaultStyle.grey_0 + } } - TextInput { + FormItemLayout { label: qsTr("Entreprise") - initialText: contact.core.organization - onEditingFinished: contact.core.organization = text - backgroundColor: DefaultStyle.grey_0 + contentItem: TextField { + initialText: contact.core.organization + onEditingFinished: contact.core.organization = text + backgroundColor: DefaultStyle.grey_0 + } } - TextInput { + FormItemLayout { label: qsTr("Fonction") - initialText: contact.core.job - onEditingFinished: contact.core.job = text - backgroundColor: DefaultStyle.grey_0 + contentItem: TextField { + initialText: contact.core.job + onEditingFinished: contact.core.job = text + backgroundColor: DefaultStyle.grey_0 + } } Item{Layout.fillHeight: true} } @@ -141,13 +149,15 @@ ColumnLayout { model: mainItem.contact && mainItem.contact.core.addresses || [] } delegate: RowLayout { - TextInput { + FormItemLayout { label: modelData.label - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.setAddressAt(index, qsTr("Address SIP"), text) + contentItem: TextField { + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.setAddressAt(index, qsTr("Address SIP"), text) + } + initialText: modelData.address + backgroundColor: DefaultStyle.grey_0 } - initialText: modelData.address - backgroundColor: DefaultStyle.grey_0 } Button { Layout.preferredWidth: 24 * DefaultStyle.dp @@ -161,12 +171,14 @@ ColumnLayout { } } RowLayout { - TextInput { + FormItemLayout { label: qsTr("Adresse SIP") - backgroundColor: DefaultStyle.grey_0 - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.appendAddress(text) - setText("") + contentItem: TextField { + backgroundColor: DefaultStyle.grey_0 + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.appendAddress(text) + text = "" + } } } Item { @@ -180,13 +192,15 @@ ColumnLayout { model: mainItem.contact && mainItem.contact.core.phoneNumbers || [] } delegate: RowLayout { - TextInput { + FormItemLayout { label: modelData.label - initialText: modelData.address - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.setPhoneNumberAt(index, qsTr("Téléphone"), text) + contentItem: TextField { + initialText: modelData.address + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.setPhoneNumberAt(index, qsTr("Téléphone"), text) + } + backgroundColor: DefaultStyle.grey_0 } - backgroundColor: DefaultStyle.grey_0 } Button { Layout.preferredWidth: 24 * DefaultStyle.dp @@ -200,12 +214,14 @@ ColumnLayout { } } RowLayout { - TextInput { + FormItemLayout { label: qsTr("Phone") - backgroundColor: DefaultStyle.grey_0 - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.appendPhoneNumber(label, text) - setText("") + contentItem: TextField { + backgroundColor: DefaultStyle.grey_0 + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.appendPhoneNumber(label, text) + setText("") + } } } Item { diff --git a/Linphone/view/Item/Contact/ContactsList.qml b/Linphone/view/Item/Contact/ContactsList.qml index 1de6edeb7..e6a7c4088 100644 --- a/Linphone/view/Item/Contact/ContactsList.qml +++ b/Linphone/view/Item/Contact/ContactsList.qml @@ -18,6 +18,19 @@ ListView { property bool initialHeadersVisible: true property bool displayNameCapitalization: true property bool showOnlyFavourites: false + + property ConferenceInfoGui confInfoGui + + property bool multiSelectionEnabled: false + property list selectedContacts + property int selectedContactCount: selectedContacts.length + Component.onCompleted: { + if (confInfoGui) { + for(var i = 0; i < confInfoGui.core.participants.length; ++i) { + selectedContacts.push(confInfoGui.core.getParticipantAddressAt(i)); + } + } + } property int delegateLeftMargin: 0 currentIndex: -1 @@ -30,10 +43,11 @@ ListView { selectedContact = model.getAt(currentIndex) || null } - signal contactSelected(var contact) + // signal contactSelected(var contact) signal contactStarredChanged() signal contactDeletionRequested(FriendGui contact) - + signal contactAddedToSelection() + model: MagicSearchProxy { searchText: searchBarText.length === 0 ? "*" : searchBarText } @@ -89,13 +103,29 @@ ListView { maximumLineCount: 1 Layout.fillWidth: true } - RowLayout { - id: buttonsLayout - z: 1 - height: parent.height - children: mainItem.delegateButtons || [] + EffectImage { + id: isSelectedCheck + // visible: mainItem.multiSelectionEnabled && (mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1) + visible: mainItem.multiSelectionEnabled && (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1) + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + imageSource: AppIcons.check + colorizationColor: DefaultStyle.main1_500_main + Connections { + target: mainItem + // onParticipantsChanged: isSelectedCheck.visible = mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1 + onSelectedContactCountChanged: isSelectedCheck.visible = (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1) + } } + } + RowLayout { + z: 1 + height: parent.height + anchors.right: parent.right + anchors.rightMargin: 5 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + children: mainItem.delegateButtons || [] PopupButton { id: friendPopup z: 1 @@ -159,6 +189,7 @@ ListView { } } } + MouseArea { id: contactArea @@ -169,11 +200,29 @@ ListView { anchors.fill: contactArea opacity: 0.7 color: DefaultStyle.main2_100 - visible: contactArea.containsMouse || friendPopup.hovered || mainItem.currentIndex === index + visible: contactArea.containsMouse || friendPopup.hovered || (!mainItem.multiSelectionEnabled && mainItem.currentIndex === index) } onClicked: { mainItem.currentIndex = index - mainItem.contactSelected(modelData) + // mainItem.contactSelected(modelData) + // if (mainItem.multiSelectionEnabled && mainItem.confInfoGui) { + // var indexInSelection = mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) + // if (indexInSelection == -1) { + // mainItem.confInfoGui.core.addParticipant(modelData.core.defaultAddress) + // } else { + // mainItem.confInfoGui.core.removeParticipant(indexInSelection) + // } + // } + if (mainItem.multiSelectionEnabled) { + var indexInSelection = mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) + if (indexInSelection == -1) { + mainItem.selectedContacts.push(modelData.core.defaultAddress) + mainItem.contactAddedToSelection() + } + else { + mainItem.selectedContacts.splice(indexInSelection, 1) + } + } } } } diff --git a/Linphone/view/Item/Form/LoginForm.qml b/Linphone/view/Item/Form/LoginForm.qml index 4f734cca3..b4289175f 100644 --- a/Linphone/view/Item/Form/LoginForm.qml +++ b/Linphone/view/Item/Form/LoginForm.qml @@ -9,43 +9,51 @@ ColumnLayout { spacing: 15 * DefaultStyle.dp signal connectionSucceed() - TextInput { + FormItemLayout { id: username label: "Username" mandatory: true enableErrorText: true - - Binding on background.border.color { - when: errorText.opacity != 0 - value: DefaultStyle.danger_500main - } - Binding on textField.color { - when: errorText.opacity != 0 - value: DefaultStyle.danger_500main - } - } - - Item { - Layout.preferredHeight: password.implicitHeight - TextInput { - id: password - label: "Password" - mandatory: true - hidden: true - enableErrorText: true - - Binding on background.border.color { + contentItem: TextField { + id: usernameEdit + Layout.preferredWidth: 360 * DefaultStyle.dp + Layout.preferredHeight: 49 * DefaultStyle.dp + Binding on backgroundBorderColor { when: errorText.opacity != 0 value: DefaultStyle.danger_500main } - Binding on textField.color { + Binding on color { when: errorText.opacity != 0 value: DefaultStyle.danger_500main } } + } + Item { + Layout.preferredHeight: password.implicitHeight + FormItemLayout { + id: password + label: "Password" + mandatory: true + enableErrorText: true + contentItem: TextField { + id: passwordEdit + Layout.preferredWidth: 360 * DefaultStyle.dp + Layout.preferredHeight: 49 * DefaultStyle.dp + hidden: true + Binding on backgroundBorderColor { + when: errorText.opacity != 0 + value: DefaultStyle.danger_500main + } + Binding on color { + when: errorText.opacity != 0 + value: DefaultStyle.danger_500main + } + } + } ErrorText { - anchors.bottom: password.bottom + anchors.top: password.bottom + anchors.topMargin: 15 * DefaultStyle.dp id: errorText Connections { target: LoginPageCpp @@ -106,14 +114,14 @@ ColumnLayout { password.errorMessage = "" errorText.text = "" - if (username.text.length == 0 || password.text.length == 0) { - if (username.text.length == 0) + if (usernameEdit.text.length == 0 || passwordEdit.text.length == 0) { + if (usernameEdit.text.length == 0) username.errorMessage = qsTr("You must enter a username") - if (password.text.length == 0) + if (passwordEdit.text.length == 0) password.errorMessage = qsTr("You must enter a password") return } - LoginPageCpp.login(username.text, password.text) + LoginPageCpp.login(usernameEdit.text, passwordEdit.text) connectionButton.currentIndex = 1 } } diff --git a/Linphone/view/Item/Meeting/MeetingList.qml b/Linphone/view/Item/Meeting/MeetingList.qml new file mode 100644 index 000000000..cd2049c4f --- /dev/null +++ b/Linphone/view/Item/Meeting/MeetingList.qml @@ -0,0 +1,155 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick.Effects + +import Linphone +import QtQml +import UtilsCpp 1.0 + +ListView { + id: mainItem + height: contentHeight + visible: count > 0 + clip: true + + property string searchBarText + + property bool hoverEnabled: true + + property int delegateLeftMargin: 0 + currentIndex: -1 + + property var delegateButtons + + property ConferenceInfoGui selectedConference: model.getAt(currentIndex) || null + + onCountChanged: selectedConference = model.getAt(currentIndex) || null + onCurrentIndexChanged: selectedConference = model.getAt(currentIndex) || null + + signal conferenceSelected(var contact) + + model: ConferenceInfoProxy { + searchText: searchBarText.length === 0 ? "" : searchBarText + } + + section { + criteria: ViewSection.FullString + delegate: Text { + text: section + height: 29 * DefaultStyle.dp + font { + pixelSize: 20 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + property: '$sectionMonth' + } + + delegate: Item { + id: itemDelegate + height: 80 * DefaultStyle.dp + width: mainItem.width + property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null + property var previousDateTime: previousItem ? previousItem.core.dateTimeUtc : null + property var dateTime: $modelData.core.dateTime + property var endDateTime: $modelData.core.endDateTime + ColumnLayout { + id: dateDay + width: 32 * DefaultStyle.dp + height: 51 * DefaultStyle.dp + visible: !previousDateTime || previousDateTime != dateTime + anchors.verticalCenter: parent.verticalCenter + Text { + verticalAlignment: Text.AlignVCenter + Layout.preferredWidth: 32 * DefaultStyle.dp + Layout.preferredHeight: 19 * DefaultStyle.dp + // opacity: (!previousItem || !previousDateTime.startsWith(displayName[0])) ? 1 : 0 + text: UtilsCpp.toDateDayNameString(dateTime) + color: DefaultStyle.main2_500main + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + Rectangle { + id: dayNum + Layout.preferredWidth: 32 * DefaultStyle.dp + Layout.preferredHeight: 32 * DefaultStyle.dp + radius: 50 * DefaultStyle.dp + property var isCurrentDay: UtilsCpp.isCurrentDay(dateTime) + color: isCurrentDay ? DefaultStyle.main1_500_main : "transparent" + Text { + anchors.centerIn: parent + text: UtilsCpp.toDateDayString(dateTime) + color: dayNum.isCurrentDay ? DefaultStyle.grey_0 : DefaultStyle.main2_500main + font { + pixelSize: 20 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + } + } + Rectangle { + id: conferenceInfoDelegate + anchors.left: dateDay.visible ? dateDay.right : parent.left + anchors.leftMargin: 10 * DefaultStyle.dp + mainItem.delegateLeftMargin + anchors.rightMargin: 10 * DefaultStyle.dp + mainItem.delegateLeftMargin + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + radius: 10 * DefaultStyle.dp + // width: 265 * DefaultStyle.dp + height: 63 * DefaultStyle.dp + color: mainItem.currentIndex === index ? DefaultStyle.main2_200 : DefaultStyle.grey_0 + ColumnLayout { + anchors.fill: parent + anchors.left: parent.left + anchors.leftMargin: 15 * DefaultStyle.dp + spacing: 2 * DefaultStyle.dp + RowLayout { + spacing: 8 * DefaultStyle.dp + Image { + source: AppIcons.usersThree + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + Text { + text: $modelData.core.subject + font { + pixelSize: 13 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + } + Text { + text: UtilsCpp.toDateHourString(dateTime) + " - " + UtilsCpp.toDateHourString(endDateTime) + color: DefaultStyle.main2_500main + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + } + + MultiEffect { + source: conferenceInfoDelegate + anchors.fill: conferenceInfoDelegate + shadowEnabled: true + shadowBlur: 1.0 + shadowOpacity: 0.1 + } + + MouseArea { + id: confArea + hoverEnabled: mainItem.hoverEnabled + anchors.fill: dateDay.visible ? conferenceInfoDelegate : parent + cursorShape: Qt.PointingHandCursor + onClicked: { + mainItem.currentIndex = index + mainItem.conferenceSelected($modelData) + } + } + } +} diff --git a/Linphone/view/Item/Meeting/NewMeeting.qml b/Linphone/view/Item/Meeting/NewMeeting.qml new file mode 100644 index 000000000..29495cbf3 --- /dev/null +++ b/Linphone/view/Item/Meeting/NewMeeting.qml @@ -0,0 +1,377 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +ColumnLayout { + id: mainItem + spacing: 8 * DefaultStyle.dp + property ConferenceInfoGui conferenceInfoGui + signal addParticipantsRequested() + signal returnRequested() + RowLayout { + Button { + background: Item{} + icon.source: AppIcons.leftArrow + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + onClicked: mainItem.returnRequested() + } + Text { + text: qsTr("Nouvelle réunion") + color: DefaultStyle.main2_700 + font { + pixelSize: 22 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + Layout.fillWidth: true + } + Button { + topPadding: 6 * DefaultStyle.dp + bottomPadding: 6 * DefaultStyle.dp + leftPadding: 12 * DefaultStyle.dp + rightPadding: 12 * DefaultStyle.dp + text: qsTr("Créer") + textSize: 13 * DefaultStyle.dp + onClicked: { + if (mainItem.conferenceInfoGui.core.subject.length === 0) { + UtilsCpp.showInformationPopup(qsTr("Erreur lors de la création"), qsTr("La conférence doit contenir un sujet"), false) + } else if (mainItem.conferenceInfoGui.core.duration <= 0) { + UtilsCpp.showInformationPopup(qsTr("Erreur lors de la création"), qsTr("La fin de la conférence doit être plus récente que son début"), false) + } else if (mainItem.conferenceInfoGui.core.participantCount === 0) { + UtilsCpp.showInformationPopup(qsTr("Erreur lors de la création"), qsTr("La conférence doit contenir au moins un participant"), false) + } else { + mainItem.conferenceInfoGui.core.save() + mainItem.returnRequested() + } + } + } + } + component CheckableButton: Button { + id: checkableButton + checkable: true + autoExclusive: true + contentImageColor: checked ? DefaultStyle.grey_0 : DefaultStyle.main1_500_main + inversedColors: !checked + topPadding: 10 * DefaultStyle.dp + bottomPadding: 10 * DefaultStyle.dp + leftPadding: 16 * DefaultStyle.dp + rightPadding: 16 * DefaultStyle.dp + contentItem: RowLayout { + EffectImage { + imageSource: checkableButton.icon.source + colorizationColor: checkableButton.checked ? DefaultStyle.grey_0 : DefaultStyle.main1_500_main + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + } + Text { + text: checkableButton.text + color: checkableButton.checked ? DefaultStyle.grey_0 : DefaultStyle.main1_500_main + font { + pixelSize: 16 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + } + RowLayout { + Layout.fillWidth: true + Layout.topMargin: 20 * DefaultStyle.dp + Layout.bottomMargin: 20 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + spacing: 20 * DefaultStyle.dp + CheckableButton { + Layout.preferredWidth: 151 * DefaultStyle.dp + icon.source: AppIcons.usersThree + text: qsTr("Réunion") + checked: true + } + CheckableButton { + Layout.preferredWidth: 151 * DefaultStyle.dp + icon.source: AppIcons.slide + text: qsTr("Broadcast") + } + } + Section { + content: RowLayout { + EffectImage { + imageSource: AppIcons.usersThree + colorizationColor: DefaultStyle.main2_600 + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + TextInput { + id: confTitle + text: qsTr("Ajouter un titre") + color: DefaultStyle.main2_600 + font { + pixelSize: 20 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + onActiveFocusChanged: if(activeFocus==true) selectAll() + onEditingFinished: mainItem.conferenceInfoGui.core.subject = text + } + } + } + Section { + Layout.topMargin: 10 * DefaultStyle.dp + content: ColumnLayout { + spacing: 15 * DefaultStyle.dp + anchors.left: parent.left + anchors.right: parent.right + RowLayout { + Layout.fillWidth: true + + EffectImage { + imageSource: AppIcons.clock + colorizationColor: DefaultStyle.main2_600 + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + CalendarComboBox { + id: startDate + Layout.fillWidth: true + Layout.preferredHeight: 30 * DefaultStyle.dp + onSelectedDateChanged: { + mainItem.conferenceInfoGui.core.dateTime = UtilsCpp.createDateTime(selectedDate, startHour.selectedHour, startHour.selectedMin) + endDate.calendar.selectedDate = selectedDate + } + } + } + RowLayout { + Item { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + StackLayout { + currentIndex: allDaySwitch.position + RowLayout { + TimeComboBox { + id: startHour + onSelectedHourChanged: { + mainItem.conferenceInfoGui.core.dateTime = UtilsCpp.createDateTime(startDate.selectedDate, selectedHour, selectedMin) + console.log("selected hour", selectedHour, selectedMin) + endHour.selectedTimeString = Qt.formatDateTime(UtilsCpp.createDateTime(new Date(), selectedHour == 23 ? 0 : selectedHour + 1, selectedMin), "hh:mm") + } + onSelectedMinChanged: { + mainItem.conferenceInfoGui.core.dateTime = UtilsCpp.createDateTime(startDate.selectedDate, selectedHour, selectedMin) + console.log("selected min", selectedHour, selectedMin) + endHour.selectedTimeString = Qt.formatDateTime(UtilsCpp.createDateTime(new Date(), selectedHour == 23 ? 0 : selectedHour + 1, selectedMin), "hh:mm") + } + Layout.preferredWidth: 94 * DefaultStyle.dp + Layout.preferredHeight: 30 * DefaultStyle.dp + } + TimeComboBox { + id: endHour + property date startTime: new Date() + onSelectedHourChanged: mainItem.conferenceInfoGui.core.endDateTime = UtilsCpp.createDateTime(endDate.selectedDate, selectedHour, selectedMin) + onSelectedMinChanged: mainItem.conferenceInfoGui.core.endDateTime = UtilsCpp.createDateTime(endDate.selectedDate, selectedHour, selectedMin) + Layout.preferredWidth: 94 * DefaultStyle.dp + Layout.preferredHeight: 30 * DefaultStyle.dp + Component.onCompleted: selectedTimeString = Qt.formatDateTime(UtilsCpp.addSecs(startTime, 3600), "hh:mm") + } + Item { + Layout.fillWidth: true + } + Text { + property int durationSec: UtilsCpp.secsTo(startHour.selectedTime, endHour.selectedTime) + property int hour: durationSec/3600 + property int min: (durationSec - hour*3600)/60 + text: (hour > 0 ? hour + "h" : "") + (min > 0 ? min + "mn" : "") + font { + pixelSize: 14 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + } + CalendarComboBox { + id: endDate + Layout.fillWidth: true + Layout.preferredHeight: 30 * DefaultStyle.dp + onSelectedDateChanged: mainItem.conferenceInfoGui.core.endDateTime = UtilsCpp.createDateTime(selectedDate, 23, 59) + } + } + } + RowLayout { + Item { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + RowLayout { + Switch { + id: allDaySwitch + text: qsTr("Toute la journée") + } + } + } + + ComboBox { + id: timeZoneCbox + Layout.fillWidth: true + Layout.preferredHeight: 30 * DefaultStyle.dp + hoverEnabled: true + listView.implicitHeight: 152 * DefaultStyle.dp + constantImageSource: AppIcons.globe + weight: 700 * DefaultStyle.dp + leftMargin: 0 + currentIndex: mainItem.conferenceInfoGui ? model.getIndex(mainItem.conferenceInfoGui.core.timeZoneModel) : -1 + background: Rectangle { + visible: parent.hovered || parent.down + anchors.fill: parent + color: DefaultStyle.grey_100 + } + model: TimeZoneProxy{ + } + onCurrentIndexChanged: { + var modelIndex = timeZoneCbox.model.index(currentIndex, 0) + mainItem.conferenceInfoGui.core.timeZoneModel = timeZoneCbox.model.data(modelIndex, Qt.DisplayRole + 1) + } + } + + ComboBox { + id: repeaterCbox + enabled: false + Component.onCompleted: console.log("TODO : handle conf repetition") + constantImageSource: AppIcons.reloadArrow + Layout.fillWidth: true + Layout.preferredHeight: height + height: 30 * DefaultStyle.dp + width: 307 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + leftMargin: 0 + currentIndex: 0 + background: Rectangle { + visible: parent.hovered || parent.down + anchors.fill: parent + color: DefaultStyle.grey_100 + } + model: [ + {text: qsTr("Une fois")}, + {text: qsTr("Tous les jours")}, + {text: qsTr("Tous les jours de la semaine (Lun-Ven)")}, + {text: qsTr("Toutes les semaines")}, + {text: qsTr("Tous les mois")} + ] + } + + + } + } + Section { + content: RowLayout { + anchors.left: parent.left + anchors.right: parent.right + EffectImage { + imageSource: AppIcons.note + colorizationColor: DefaultStyle.main2_600 + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + TextArea { + id: descriptionEdit + Layout.fillWidth: true + Layout.preferredWidth: 275 * DefaultStyle.dp + leftPadding: 8 * DefaultStyle.dp + rightPadding: 8 * DefaultStyle.dp + hoverEnabled: true + placeholderText: qsTr("Ajouter une description") + placeholderTextColor: DefaultStyle.main2_600 + placeholderWeight: 700 * DefaultStyle.dp + color: DefaultStyle.main2_600 + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + background: Rectangle { + anchors.fill: parent + color: descriptionEdit.hovered || descriptionEdit.activeFocus ? DefaultStyle.grey_100 : "transparent" + radius: 4 * DefaultStyle.dp + } + onEditingFinished: mainItem.conferenceInfoGui.core.description = text + } + } + } + Section { + content: ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + Button { + id: addParticipantsButton + Layout.fillWidth: true + Layout.preferredHeight: 30 * DefaultStyle.dp + background: Rectangle { + anchors.fill: parent + color: addParticipantsButton.hovered ? DefaultStyle.grey_100 : "transparent" + radius: 4 * DefaultStyle.dp + } + contentItem: RowLayout { + EffectImage { + imageSource: AppIcons.usersThree + colorizationColor: DefaultStyle.main2_600 + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + Text { + Layout.fillWidth: true + text: qsTr("Ajouter des participants") + font { + pixelSize: 14 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + } + onClicked: mainItem.addParticipantsRequested() + } + ListView { + id: participantList + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: contentHeight + Layout.maximumHeight: 250 * DefaultStyle.dp + clip: true + model: mainItem.conferenceInfoGui.core.participants + delegate: Item { + height: 56 * DefaultStyle.dp + width: parent.width + RowLayout { + anchors.fill: parent + Avatar { + Layout.preferredWidth: 45 * DefaultStyle.dp + Layout.preferredHeight: 45 * DefaultStyle.dp + address: modelData.address + } + Text { + text: modelData.displayName + font.pixelSize: 14 * DefaultStyle.dp + font.capitalization: Font.Capitalize + } + Item { + Layout.fillWidth: true + } + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + Layout.rightMargin: 10 * DefaultStyle.dp + background: Item{} + icon.source: AppIcons.closeX + contentImageColor: DefaultStyle.main1_500_main + onClicked: mainItem.conferenceInfoGui.core.removeParticipant(index) + } + } + } + } + } + } + Switch { + text: qsTr("Send invitation to participants") + Component.onCompleted: { + console.log("TODO : handle send invitation to participants") + toggle() + } + } + Item { + Layout.fillHeight: true + } +} \ No newline at end of file diff --git a/Linphone/view/Item/PhoneNumberInput.qml b/Linphone/view/Item/PhoneNumberInput.qml index b46cf080f..15b335738 100644 --- a/Linphone/view/Item/PhoneNumberInput.qml +++ b/Linphone/view/Item/PhoneNumberInput.qml @@ -49,12 +49,11 @@ ColumnLayout { Layout.bottomMargin: 10 * DefaultStyle.dp color: DefaultStyle.main2_600 } - TextInput { + TextField { id: textField Layout.fillWidth: true placeholderText: mainItem.placeholderText - enableBackgroundColors: false - fillWidth: true + background: Item{} initialText: initialPhoneNumber validator: IntValidator{} } diff --git a/Linphone/view/Item/Popup.qml b/Linphone/view/Item/Popup.qml index de9dbcaba..43afa0d08 100644 --- a/Linphone/view/Item/Popup.qml +++ b/Linphone/view/Item/Popup.qml @@ -8,6 +8,7 @@ Control.Popup{ padding: 0 property color underlineColor property int radius: 16 * DefaultStyle.dp + property bool hovered: mouseArea.containsMouse background: Item{ Rectangle { visible: mainItem.underlineColor != undefined @@ -32,5 +33,10 @@ Control.Popup{ shadowBlur: 1.0 shadowOpacity: 0.1 } + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + } } } diff --git a/Linphone/view/Item/PopupButton.qml b/Linphone/view/Item/PopupButton.qml index 12c5b7484..64f28afce 100644 --- a/Linphone/view/Item/PopupButton.qml +++ b/Linphone/view/Item/PopupButton.qml @@ -6,25 +6,35 @@ import Linphone Button { id: mainItem property alias popup: popup + property var contentImageColor checked: popup.visible implicitWidth: 24 * DefaultStyle.dp implicitHeight: 24 * DefaultStyle.dp + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 + icon.source: AppIcons.more + icon.width: width + icon.height: height function close() { popup.close() } + background: Rectangle { anchors.fill: mainItem visible: mainItem.checked color: DefaultStyle.main2_300 radius: 40 * DefaultStyle.dp } - icon.source: AppIcons.more - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp + contentItem: EffectImage { + imageSource: mainItem.icon.source + imageWidth: mainItem.icon.width + imageHeight: mainItem.icon.height + colorizationColor: mainItem.contentImageColor + } onPressed: { if (popup.visible) popup.close() else popup.open() @@ -33,7 +43,7 @@ Button { id: popup x: - width y: mainItem.height - closePolicy: Popup.CloseOnPressOutsideParent |Popup.CloseOnPressOutside + closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnPressOutside parent: mainItem // Explicit define for coordinates references. onAboutToShow: { @@ -67,4 +77,4 @@ Button { } } } -} \ No newline at end of file +} diff --git a/Linphone/view/Item/Switch.qml b/Linphone/view/Item/Switch.qml new file mode 100644 index 000000000..f6231502e --- /dev/null +++ b/Linphone/view/Item/Switch.qml @@ -0,0 +1,41 @@ +import QtQuick 2.12 +import QtQuick.Controls as Control + +import Linphone + +Control.Switch { + id: mainItem + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + indicator: Rectangle { + implicitWidth: 32 * DefaultStyle.dp + implicitHeight: 20 * DefaultStyle.dp + x: mainItem.leftPadding + y: parent.height / 2 - height / 2 + radius: 10 * DefaultStyle.dp + color: mainItem.checked ? DefaultStyle.success_500main : DefaultStyle.main2_400 + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + property int margin: 4 * DefaultStyle.dp + x: mainItem.checked ? parent.width - width - margin : margin + width: 12 * DefaultStyle.dp + height: 12 * DefaultStyle.dp + radius: 10 * DefaultStyle.dp + color: DefaultStyle.grey_0 + Behavior on x { + NumberAnimation{duration: 100} + } + } + } + + contentItem: Text { + text: mainItem.text + font: mainItem.font + opacity: enabled ? 1.0 : 0.3 + verticalAlignment: Text.AlignVCenter + leftPadding: mainItem.indicator.width + mainItem.spacing + } +} \ No newline at end of file diff --git a/Linphone/view/Item/Test/ItemsTest.qml b/Linphone/view/Item/Test/ItemsTest.qml index 75e8d46ac..f6159bcc5 100644 --- a/Linphone/view/Item/Test/ItemsTest.qml +++ b/Linphone/view/Item/Test/ItemsTest.qml @@ -102,22 +102,22 @@ Window { text: "scaled text" } RowLayout { - TextInput { + TextField { label: "mandatory text input" placeholderText: "default text" - mandatory: true + // mandatory: true } - TextInput { + TextField { label: "password text input" placeholderText: "default text" hidden: true } - TextInput { + TextField { id: next label: "text input with long long looooooooooooooooooooooooooooooooooooooooooooooooooooooooong label" placeholderText: "long long long default text" } - TextInput { + TextField { label: "number text input" validator: IntValidator{} } diff --git a/Linphone/view/Item/TextArea.qml b/Linphone/view/Item/TextArea.qml new file mode 100644 index 000000000..de6221137 --- /dev/null +++ b/Linphone/view/Item/TextArea.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Controls as Control +import QtQuick.Layouts 1.0 +import Linphone + +TextEdit { + id: mainItem + + property string placeholderText + property int placeholderPixelSize: 14 * DefaultStyle.dp + property int placeholderWeight: 400 * DefaultStyle.dp + property color placeholderTextColor: color + property alias background: background.data + property bool hoverEnabled: false + property bool hovered: mouseArea.hoverEnabled && mouseArea.containsMouse + topPadding: 5 * DefaultStyle.dp + bottomPadding: 5 * DefaultStyle.dp + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: mainItem.hoverEnabled + // onPressed: mainItem.forceActiveFocus() + acceptedButtons: Qt.NoButton + cursorShape: mainItem.hovered ? Qt.PointingHandCursor : Qt.ArrowCursor + } + + Item { + id: background + anchors.fill: parent + z: -1 + } + + Text { + anchors.verticalCenter: mainItem.verticalCenter + text: mainItem.placeholderText + color: mainItem.placeholderTextColor + visible: mainItem.text.length == 0 && !mainItem.activeFocus + x: mainItem.leftPadding + font { + pixelSize: mainItem.placeholderPixelSize + weight: mainItem.placeholderWeight + } + } +} + diff --git a/Linphone/view/Item/TextField.qml b/Linphone/view/Item/TextField.qml new file mode 100644 index 000000000..4ca91c784 --- /dev/null +++ b/Linphone/view/Item/TextField.qml @@ -0,0 +1,115 @@ +import QtQuick +import QtQuick.Controls as Control +import QtQuick.Layouts 1.0 +import Linphone + +Control.TextField { + id: mainItem + width: 360 * DefaultStyle.dp + height: 49 * DefaultStyle.dp + leftPadding: 15 * DefaultStyle.dp + rightPadding: eyeButton.visible ? 5 * DefaultStyle.dp + eyeButton.width + eyeButton.rightMargin : 15 * DefaultStyle.dp + echoMode: (hidden && !eyeButton.checked) ? TextInput.Password : TextInput.Normal + color: DefaultStyle.main2_600 + font { + family: DefaultStyle.defaultFont + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + selectByMouse: true + + property bool controlIsDown: false + property bool hidden: false + property bool backgroundVisible: true + property color backgroundColor: DefaultStyle.grey_100 + property color backgroundBorderColor: DefaultStyle.grey_200 + property string initialText + property int pixelSize: 14 * DefaultStyle.dp + property int weight: 400 * DefaultStyle.dp + + Component.onCompleted: { + text = initialText + } + + function resetText() { + text = initialText + } + + signal enterPressed() + + background: Rectangle { + id: inputBackground + visible: mainItem.backgroundVisible + anchors.fill: parent + radius: 79 * DefaultStyle.dp + color: mainItem.backgroundColor + border.color: activeFocus + ? DefaultStyle.main1_500_main + : mainItem.backgroundBorderColor + } + cursorDelegate: Rectangle { + id: cursor + color: DefaultStyle.main1_500_main + width: 1 * DefaultStyle.dp + + SequentialAnimation { + loops: Animation.Infinite + running: mainItem.cursorVisible + + PropertyAction { + target: cursor + property: 'visible' + value: true + } + + PauseAnimation { + duration: 600 + } + + PropertyAction { + target: cursor + property: 'visible' + value: false + } + + PauseAnimation { + duration: 600 + } + + onStopped: { + cursor.visible = false + } + } + } + Keys.onPressed: (event) => { + if (event.jey == Qt.Key_Control) mainItem.controlIsDown = true + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { + enterPressed() + if (mainItem.controlIsDown) { + + } + } + } + Keys.onReleased: (event) => { + if (event.jey == Qt.Key_Control) mainItem.controlIsDown = false + } + + Button { + id: eyeButton + property int rightMargin: 15 * DefaultStyle.dp + z: 1 + visible: mainItem.hidden + checkable: true + background: Rectangle { + color: "transparent" + } + icon.source: eyeButton.checked ? AppIcons.eyeShow : AppIcons.eyeHide + width: 20 * DefaultStyle.dp + height: 20 * DefaultStyle.dp + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: rightMargin + } +} + diff --git a/Linphone/view/Item/TextInput.qml b/Linphone/view/Item/TextInput.qml index bbe66c2bc..e69de29bb 100644 --- a/Linphone/view/Item/TextInput.qml +++ b/Linphone/view/Item/TextInput.qml @@ -1,132 +0,0 @@ -import QtQuick -import QtQuick.Controls as Control -import QtQuick.Layouts -import Linphone - -ColumnLayout { - id: mainItem - - property string label: "" - property string errorMessage: "" - property string placeholderText: "" - property bool mandatory: false - property bool hidden: false - property int textInputWidth: 346 * DefaultStyle.dp - property var validator: RegularExpressionValidator{} - property bool fillWidth: false - property bool enableBackgroundColors: true - property color backgroundColor: DefaultStyle.grey_100 - property color backgroundBorderColor: DefaultStyle.grey_200 - property string initialText - - property bool enableErrorText: false - property bool errorTextVisible: errorText.opacity > 0 - - property alias textField: textField - property alias background: input - - readonly property string text: textField.text - readonly property bool hasActiveFocus: textField.activeFocus - - signal editingFinished() - - Component.onCompleted: { - setText(initialText) - } - - function setText(text) { - textField.text = text - } - function resetText() { - setText(initialText) - } - - Text { - visible: mainItem.label.length > 0 - verticalAlignment: Text.AlignVCenter - text: mainItem.label + (mainItem.mandatory ? "*" : "") - color: textField.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.main2_600 - elide: Text.ElideRight - wrapMode: Text.Wrap - maximumLineCount: 1 - font { - pixelSize: 13 * DefaultStyle.dp - family: DefaultStyle.defaultFont - weight: 700 * DefaultStyle.dp - } - Layout.preferredWidth: mainItem.textInputWidth - } - - Rectangle { - id: input - Component.onCompleted: { - if (mainItem.fillWidth) - Layout.fillWidth = true - } - Layout.preferredWidth: mainItem.textInputWidth - Layout.preferredHeight: 49 * DefaultStyle.dp - radius: 79 * DefaultStyle.dp - color: mainItem.backgroundColor - border.color: mainItem.errorTextVisible - ? DefaultStyle.danger_500main - : textField.activeFocus - ? DefaultStyle.main1_500_main - : mainItem.backgroundBorderColor - - Control.TextField { - id: textField - anchors.left: parent.left - anchors.leftMargin: 10 * DefaultStyle.dp - anchors.right: eyeButton.visible ? eyeButton.left : parent.right - anchors.rightMargin: eyeButton.visible ? 0 : 10 * DefaultStyle.dp - anchors.verticalCenter: parent.verticalCenter - placeholderText: mainItem.placeholderText - echoMode: (mainItem.hidden && !eyeButton.checked) ? TextInput.Password : TextInput.Normal - font.family: DefaultStyle.defaultFont - font { - pixelSize: 14 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } - color: mainItem.errorTextVisible ? DefaultStyle.danger_500main : DefaultStyle.main2_600 - selectByMouse: true - validator: mainItem.validator - background: Item { - opacity: 0. - } - cursorDelegate: Rectangle { - visible: textField.activeFocus - color: DefaultStyle.main1_500_main - width: 2 * DefaultStyle.dp - } - onEditingFinished: { - mainItem.editingFinished() - } - - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { - textField.focus = false - } - } - } - Button { - id: eyeButton - visible: mainItem.hidden - checkable: true - background: Rectangle { - color: "transparent" - } - icon.source: eyeButton.checked ? AppIcons.eyeShow : AppIcons.eyeHide - width: 20 * DefaultStyle.dp - height: 20 * DefaultStyle.dp - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 15 * DefaultStyle.dp - } - } - ErrorText { - id: errorText - visible: mainItem.enableErrorText - text: mainItem.errorMessage - Layout.preferredWidth: implicitWidth - } -} diff --git a/Linphone/view/Item/TimeComboBox.qml b/Linphone/view/Item/TimeComboBox.qml new file mode 100644 index 000000000..889feb8ff --- /dev/null +++ b/Linphone/view/Item/TimeComboBox.qml @@ -0,0 +1,83 @@ +import QtQuick +import Linphone +import UtilsCpp 1.0 + +ComboBox { + id: mainItem + property string selectedTimeString: Qt.formatDateTime(new Date(), "hh:mm") + property int selectedHour: input.hour*1 + property int selectedMin: input.min*1 + popup.width: 73 * DefaultStyle.dp + listView.model: 48 + listView.implicitHeight: 204 * DefaultStyle.dp + editable: true + popup.closePolicy: Popup.PressOutsideParent | Popup.CloseOnPressOutside + onCurrentTextChanged: input.text = currentText + popup.onOpened: { + input.forceActiveFocus() + } + contentItem: TextInput { + id: input + anchors.right: indicator.left + validator: IntValidator{} + // activeFocusOnPress: false + inputMask: "00:00" + verticalAlignment: TextInput.AlignVCenter + horizontalAlignment: TextInput.AlignHCenter + property string hour: text.split(":")[0] + property string min: text.split(":")[1] + color: DefaultStyle.main2_600 + onActiveFocusChanged: { + if (activeFocus) { + selectAll() + mainItem.popup.open() + } else { + listView.currentIndex = -1 + mainItem.selectedTimeString = Qt.formatDateTime(UtilsCpp.createDateTime(new Date(), hour, min), "hh:mm") + } + } + font { + pixelSize: 14 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + text: mainItem.selectedTimeString + Keys.onPressed: (event) => { + if (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) { + focus = false + } + } + onEditingFinished: { + console.log("set time", hour, min) + mainItem.selectedTimeString = Qt.formatDateTime(UtilsCpp.createDateTime(new Date(), hour, min), "hh:mm") + } + } + listView.delegate: Text { + id: hourDelegate + property int hour: modelData /2 + property int min: modelData%2 === 0 ? 0 : 30 + text: Qt.formatDateTime(UtilsCpp.createDateTime(new Date(), hour, min), "hh:mm") + width: mainItem.width + height: 25 * DefaultStyle.dp + verticalAlignment: TextInput.AlignVCenter + horizontalAlignment: TextInput.AlignHCenter + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + // mainItem.text = parent.text + mainItem.listView.currentIndex = index + mainItem.selectedTimeString = hourDelegate.text + mainItem.popup.close() + } + Rectangle { + visible: parent.containsMouse + color: DefaultStyle.main2_200 + } + } + } +} \ No newline at end of file diff --git a/Linphone/view/Layout/Conference/IncallGrid.qml b/Linphone/view/Layout/Conference/IncallGrid.qml new file mode 100644 index 000000000..3b2cb19bc --- /dev/null +++ b/Linphone/view/Layout/Conference/IncallGrid.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 + } + } + } +} diff --git a/Linphone/view/App/Layout/ContactLayout.qml b/Linphone/view/Layout/Contact/ContactLayout.qml similarity index 100% rename from Linphone/view/App/Layout/ContactLayout.qml rename to Linphone/view/Layout/Contact/ContactLayout.qml diff --git a/Linphone/view/Layout/FormItemLayout.qml b/Linphone/view/Layout/FormItemLayout.qml new file mode 100644 index 000000000..5a5fa4d4b --- /dev/null +++ b/Linphone/view/Layout/FormItemLayout.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Controls as Control +import QtQuick.Layouts 1.0 +import QtQuick.Effects +import Linphone + +ColumnLayout { + id: mainItem + property alias contentItem: contentItem.data + property string label: "" + property bool mandatory: false + + property string errorMessage: "" + property bool enableErrorText: false + property bool errorTextVisible: errorText.opacity > 0 + + Text { + visible: label.length > 0 + verticalAlignment: Text.AlignVCenter + text: mainItem.label + (mainItem.mandatory ? "*" : "") + color: contentItem.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.main2_600 + elide: Text.ElideRight + wrapMode: Text.Wrap + maximumLineCount: 1 + + font { + pixelSize: 13 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + + Item { + id: contentItem + Layout.preferredHeight: childrenRect.height + Layout.preferredWidth: childrenRect.width + } + + ErrorText { + id: errorText + visible: mainItem.enableErrorText + text: mainItem.errorMessage + Layout.preferredWidth: implicitWidth + } +} diff --git a/Linphone/view/Layout/Meeting/AddParticipantsLayout.qml b/Linphone/view/Layout/Meeting/AddParticipantsLayout.qml new file mode 100644 index 000000000..0e7d1cc9b --- /dev/null +++ b/Linphone/view/Layout/Meeting/AddParticipantsLayout.qml @@ -0,0 +1,114 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +ColumnLayout { + id: mainItem + property string title + property string validateButtonText + property string placeHolderText: qsTr("Rechercher des contacts") + property color titleColor: DefaultStyle.main2_700 + property ConferenceInfoGui conferenceInfoGui + signal returnRequested() + Layout.preferredWidth: 362 * DefaultStyle.dp + + RowLayout { + Layout.preferredWidth: 362 * DefaultStyle.dp + Button { + background: Item{} + icon.source: AppIcons.leftArrow + contentImageColor: DefaultStyle.main1_500_main + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + onClicked: mainItem.returnRequested() + } + Text { + text: mainItem.title + color: mainItem.titleColor + maximumLineCount: 1 + font { + pixelSize: 18 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + Layout.fillWidth: true + } + Button { + Layout.preferredWidth: 70 * DefaultStyle.dp + topPadding: 6 * DefaultStyle.dp + bottomPadding: 6 * DefaultStyle.dp + // leftPadding: 12 * DefaultStyle.dp + // rightPadding: 12 * DefaultStyle.dp + text: mainItem.validateButtonText + textSize: 13 * DefaultStyle.dp + onClicked: { + mainItem.conferenceInfoGui.core.resetParticipants(contactList.selectedContacts) + mainItem.returnRequested() + } + } + } + ListView { + id: participantList + Layout.fillWidth: true + // Layout.fillHeight: true + Layout.preferredHeight: contentHeight + Layout.maximumHeight: mainItem.height / 3 + width: mainItem.width + model: contactList.selectedContacts + clip: true + delegate: Item { + height: 56 * DefaultStyle.dp + width: participantList.width + RowLayout { + anchors.fill: parent + Avatar { + Layout.preferredWidth: 45 * DefaultStyle.dp + Layout.preferredHeight: 45 * DefaultStyle.dp + address: modelData + } + Text { + property var nameObj: UtilsCpp.getDisplayName(modelData) + text: nameObj ? nameObj.value : "" + font.pixelSize: 14 * DefaultStyle.dp + font.capitalization: Font.Capitalize + } + Item { + Layout.fillWidth: true + } + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + background: Item{} + icon.source: AppIcons.closeX + contentImageColor: DefaultStyle.main1_500_main + onClicked: contactList.selectedContacts.splice(index, 1) + } + } + } + } + SearchBar { + id: searchbar + Layout.fillWidth: true + placeholderText: mainItem.placeHolderText + } + Text { + text: qsTr("Contacts") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + ContactsList { + id: contactList + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: contentHeight + multiSelectionEnabled: true + contactMenuVisible: false + confInfoGui: mainItem.conferenceInfoGui + searchBarText: searchbar.text + onContactAddedToSelection: participantList.positionViewAtEnd() + } +} \ No newline at end of file diff --git a/Linphone/view/Layout/Mosaic.qml b/Linphone/view/Layout/Mosaic.qml new file mode 100644 index 000000000..43e8958f5 --- /dev/null +++ b/Linphone/view/Layout/Mosaic.qml @@ -0,0 +1,118 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.0 +import QtQml.Models 2.12 + +import Common 1.0 +import Common.Styles 1.0 + +// ============================================================================= +ColumnLayout{ + id: mainLayout + property alias delegateModel: grid.model + property alias cellHeight: grid.cellHeight + property alias cellWidth: grid.cellWidth + + function appendItem(item){ + mainLayout.delegateModel.model.append(item) + } + + function add(item){ + if( !grid.isLayoutWillChanged()) + appendItem(item) + else + bufferModels.append(item) + } + + function remove(index){ + if(mainLayout.delegateModel.model.count > index) + mainLayout.delegateModel.model.remove( index, 1) + } + + function get(index){ + return mainLayout.delegateModel.model.get(index) + } + + function tryToAdd(item){ + if( !grid.isLayoutWillChanged()) { + appendItem(item) + return true + }else + return false + } + + function clear(){ + if(mainLayout.delegateModel.model.clear) { + mainLayout.delegateModel.model.clear() + bufferModels.clear() + } + } + + + property int transitionCount : 0 + property var bufferModels : ListModel{} + + onWidthChanged: grid.updateLayout() + onHeightChanged: grid.updateLayout() + GridView{ + id: grid + property int margin: 10 + property int itemCount: model.count ? model.count :( model.length ? model.length : 0) + property int columns: 1 + property int rows: 1 + + function getBestLayout(itemCount){ + var availableW = mainLayout.width - grid.margin + var availableH = mainLayout.height - grid.margin + var bestSize = 0 + var bestC = 1, bestR = 1 + for(var R = 1 ; R <= itemCount ; ++R){ + for(var C = itemCount ; C >= 1 ; --C){ + if( R * C >= itemCount){// This is a good layout candidate + var estimatedSize = Math.min(availableW / C, availableH / R) + if(estimatedSize > bestSize // Size is better + || (estimatedSize == bestSize && Math.abs(bestR-bestC) > Math.abs(R - C) )){// Stickers are more homogenized + bestSize = estimatedSize + bestC = C + bestR = R + } + } + } + } + return [bestR, bestC] + } + + function updateLayout(){ + var bestLayout = getBestLayout(itemCount) + if( rows != bestLayout[0]) + rows = bestLayout[0] + if( columns != bestLayout[1]) + columns = bestLayout[1] + } + function updateCells(){ + cellWidth = Math.min(computedWidth, computedHeight) + cellHeight = Math.min(computedWidth, computedHeight) + } + onItemCountChanged: updateLayout() + property int computedWidth: (mainLayout.width - grid.margin ) / columns + property int computedHeight: (mainLayout.height - grid.margin ) / rows + onComputedHeightChanged: Qt.callLater(updateCells) + onComputedWidthChanged: Qt.callLater(updateCells) + cellWidth: Math.min(computedWidth, computedHeight) + cellHeight: Math.min(computedWidth, computedHeight) + + function isLayoutWillChanged(){ + var bestLayout = getBestLayout(itemCount+1) + return rows !== bestLayout[0] || columns !== bestLayout[1] + } + + Layout.preferredWidth: cellWidth * columns + Layout.preferredHeight: cellHeight * rows + Layout.alignment: Qt.AlignCenter + + interactive: false + model: DelegateModel{} + + onCountChanged: grid.updateLayout() + } +} \ No newline at end of file diff --git a/Linphone/view/Layout/Section.qml b/Linphone/view/Layout/Section.qml new file mode 100644 index 000000000..d5a99a5b4 --- /dev/null +++ b/Linphone/view/Layout/Section.qml @@ -0,0 +1,26 @@ +import QtQuick 2.15 +import QtQuick.Layouts + +import Linphone +/* +Layout with line separator used in several views +*/ + +ColumnLayout { + spacing: 15 * DefaultStyle.dp + property alias content: contentItem.data + implicitHeight: contentItem.implicitHeight + 1 * DefaultStyle.dp + spacing + Item { + id: contentItem + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + Layout.preferredWidth: childrenRect.width + // Layout.leftMargin: 8 * DefaultStyle.dp + } + Rectangle { + color: DefaultStyle.main2_200 + Layout.fillWidth: true + Layout.preferredHeight: 1 * DefaultStyle.dp + width: parent.width + } +} \ No newline at end of file diff --git a/Linphone/view/Page/Login/LoginPage.qml b/Linphone/view/Page/Login/LoginPage.qml index 36ec352bc..ecc42c63b 100644 --- a/Linphone/view/Page/Login/LoginPage.qml +++ b/Linphone/view/Page/Login/LoginPage.qml @@ -17,7 +17,7 @@ LoginLayout { visible: mainItem.showBackButton Layout.preferredHeight: 27 * DefaultStyle.dp Layout.preferredWidth: 27 * DefaultStyle.dp - icon.source: AppIcons.returnArrow + icon.source: AppIcons.leftArrow background: Rectangle { color: "transparent" } diff --git a/Linphone/view/Page/Login/RegisterCheckingPage.qml b/Linphone/view/Page/Login/RegisterCheckingPage.qml index 68d45acb6..a79b12c20 100644 --- a/Linphone/view/Page/Login/RegisterCheckingPage.qml +++ b/Linphone/view/Page/Login/RegisterCheckingPage.qml @@ -13,7 +13,7 @@ LoginLayout { Button { Layout.preferredHeight: 24 * DefaultStyle.dp Layout.preferredWidth: 24 * DefaultStyle.dp - icon.source: AppIcons.returnArrow + icon.source: AppIcons.leftArrow background: Rectangle { color: "transparent" } diff --git a/Linphone/view/Page/Login/RegisterPage.qml b/Linphone/view/Page/Login/RegisterPage.qml index b28b8da70..0d13c4af9 100644 --- a/Linphone/view/Page/Login/RegisterPage.qml +++ b/Linphone/view/Page/Login/RegisterPage.qml @@ -68,16 +68,19 @@ LoginLayout { Layout.topMargin: 20 * DefaultStyle.dp spacing: 15 * DefaultStyle.dp RowLayout { - TextInput { + FormItemLayout { label: qsTr("Username") mandatory: true - textInputWidth: 346 * DefaultStyle.dp + contentItem: TextField { + Layout.preferredWidth: 346 * DefaultStyle.dp + } } ComboBox { - label: " " + Layout.alignment: Qt.AlignBottom enabled: false model: [{text:"@sip.linphone.org"}] Layout.preferredWidth: 210 * DefaultStyle.dp + Layout.preferredHeight: 49 * DefaultStyle.dp } } PhoneNumberInput { @@ -85,15 +88,17 @@ LoginLayout { label: qsTr("Phone number") mandatory: true placeholderText: "Phone number" - textInputWidth: 346 * DefaultStyle.dp + Layout.preferredWidth: 346 * DefaultStyle.dp } RowLayout { ColumnLayout { - TextInput { + FormItemLayout { label: qsTr("Password") mandatory: true - hidden: true - textInputWidth: 346 * DefaultStyle.dp + contentItem: TextField { + hidden: true + Layout.preferredWidth: 346 * DefaultStyle.dp + } } Text { text: qsTr("The password must contain 6 characters minimum") @@ -104,11 +109,13 @@ LoginLayout { } } ColumnLayout { - TextInput { + FormItemLayout { label: qsTr("Confirm password") mandatory: true - hidden: true - textInputWidth: 346 * DefaultStyle.dp + contentItem: TextField { + hidden: true + Layout.preferredWidth: 346 * DefaultStyle.dp + } } Text { text: qsTr("The password must contain 6 characters minimum") @@ -162,34 +169,40 @@ LoginLayout { Layout.fillHeight: true spacing: 15 * DefaultStyle.dp RowLayout { - TextInput { + FormItemLayout { label: qsTr("Username") mandatory: true - textInputWidth: 346 * DefaultStyle.dp + contentItem: TextField { + Layout.preferredWidth: 346 * DefaultStyle.dp + } } ComboBox { // if we don't set a label this item is offset // due to the invisibility of the upper label - label: " " enabled: false model: [{text:"@sip.linphone.org"}] Layout.preferredWidth: 210 * DefaultStyle.dp + Layout.alignment: Qt.AlignBottom } } - TextInput { - id: emailInput + FormItemLayout { label: qsTr("Email") mandatory: true - textInputWidth: 346 * DefaultStyle.dp + contentItem: TextField { + id: emailInput + Layout.preferredWidth: 346 * DefaultStyle.dp + } } RowLayout { ColumnLayout { - TextInput { - id: pwdInput + FormItemLayout { label: qsTr("Password") mandatory: true - hidden: true - textInputWidth: 346 * DefaultStyle.dp + contentItem: TextField { + id: pwdInput + hidden: true + Layout.preferredWidth: 346 * DefaultStyle.dp + } } Text { text: qsTr("The password must contain 6 characters minimum") @@ -200,12 +213,14 @@ LoginLayout { } } ColumnLayout { - TextInput { - id: confirmPwdInput + FormItemLayout { label: qsTr("Confirm password") mandatory: true - hidden: true - textInputWidth: 346 * DefaultStyle.dp + contentItem: TextField { + id: confirmPwdInput + hidden: true + Layout.preferredWidth: 346 * DefaultStyle.dp + } } Text { text: qsTr("The password must contain 6 characters minimum") diff --git a/Linphone/view/Page/Login/SIPLoginPage.qml b/Linphone/view/Page/Login/SIPLoginPage.qml index 5ff87116a..c22d73c10 100644 --- a/Linphone/view/Page/Login/SIPLoginPage.qml +++ b/Linphone/view/Page/Login/SIPLoginPage.qml @@ -14,7 +14,7 @@ LoginLayout { Button { Layout.preferredHeight: 24 * DefaultStyle.dp Layout.preferredWidth: 24 * DefaultStyle.dp - icon.source: AppIcons.returnArrow + icon.source: AppIcons.leftArrow width: 24 * DefaultStyle.dp height: 24 * DefaultStyle.dp background: Item { @@ -133,34 +133,45 @@ LoginLayout { id: secondItem ColumnLayout { spacing: 10 * DefaultStyle.dp - TextInput { - id: username + FormItemLayout { label: qsTr("Username") mandatory: true - textInputWidth: 360 * DefaultStyle.dp + contentItem: TextField { + id: username + Layout.preferredWidth: 360 * DefaultStyle.dp + } } - TextInput { - id: password + FormItemLayout { label: qsTr("Password") mandatory: true - hidden: true - textInputWidth: 360 * DefaultStyle.dp + contentItem: TextField { + id: password + hidden: true + Layout.preferredWidth: 360 * DefaultStyle.dp + } } - TextInput { - id: domain + FormItemLayout { label: qsTr("Domain") mandatory: true - textInputWidth: 360 * DefaultStyle.dp + contentItem: TextField { + id: domain + Layout.preferredWidth: 360 * DefaultStyle.dp + } } - TextInput { - id: displayName + FormItemLayout { label: qsTr("Display Name") - textInputWidth: 360 * DefaultStyle.dp + contentItem: TextField { + id: displayName + Layout.preferredWidth: 360 * DefaultStyle.dp + } } - ComboBox { + FormItemLayout { label: qsTr("Transport") - model:[ "TCP", "UDP", "TLS", "DTLS"] - Layout.preferredWidth: 360 * DefaultStyle.dp + contentItem: ComboBox { + height: 49 * DefaultStyle.dp + width: 360 * DefaultStyle.dp + model:[ "TCP", "UDP", "TLS", "DTLS"] + } } ErrorText { diff --git a/Linphone/view/Page/Main/AbstractMainPage.qml b/Linphone/view/Page/Main/AbstractMainPage.qml index aee0d2105..2db1098f3 100644 --- a/Linphone/view/Page/Main/AbstractMainPage.qml +++ b/Linphone/view/Page/Main/AbstractMainPage.qml @@ -17,6 +17,7 @@ Item { property alias leftPanelContent: leftPanel.children property alias rightPanelStackView: rightPanelStackView property alias contactEditionComp: contactEditionComp + property alias rightPanel: rightPanel property bool showDefaultItem: true signal noItemButtonPressed() signal contactEditionClosed() @@ -231,6 +232,7 @@ Item { Layout.fillWidth: true Layout.fillHeight: true } + // We need this component here as it is used in multiple subPages (Call and Contact pages) Component { id: contactEditionComp ContactEdition { diff --git a/Linphone/view/Page/Main/CallPage.qml b/Linphone/view/Page/Main/CallPage.qml index 41babd5bc..01f81c050 100644 --- a/Linphone/view/Page/Main/CallPage.qml +++ b/Linphone/view/Page/Main/CallPage.qml @@ -317,7 +317,7 @@ AbstractMainPage { } Layout.preferredWidth: 24 * DefaultStyle.dp Layout.preferredHeight: 24 * DefaultStyle.dp - icon.source: AppIcons.returnArrow + icon.source: AppIcons.leftArrow onClicked: { console.debug("[CallPage]User: return to call history") listStackView.pop() diff --git a/Linphone/view/Page/Main/MeetingPage.qml b/Linphone/view/Page/Main/MeetingPage.qml new file mode 100644 index 000000000..2db4e9b24 --- /dev/null +++ b/Linphone/view/Page/Main/MeetingPage.qml @@ -0,0 +1,461 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +AbstractMainPage { + id: mainItem + noItemButtonText: qsTr("Créer une réunion") + emptyListText: qsTr("Aucune réunion") + newItemIconSource: AppIcons.plusCircle + // rightPanel.color: DefaultStyle.grey_0 + + // disable left panel contact list interaction while a contact is being edited + property bool leftPanelEnabled: true + property ConferenceInfoGui selectedConference + property int meetingListCount + + onSelectedConferenceChanged: { + if (selectedConference) { + if (!rightPanelStackView.currentItem || rightPanelStackView.currentItem.objectName != "meetingDetail") rightPanelStackView.replace(meetingDetail, Control.StackView.Immediate) + } else { + if (rightPanelStackView.currentItem && rightPanelStackView.currentItem.objectName === "meetingDetail") rightPanelStackView.clear() + } + } + + Connections { + target: leftPanelStackView + onCurrentItemChanged: { + mainItem.showDefaultItem = leftPanelStackView.currentItem.objectName == "listLayout" && mainItem.meetingListCount === 0 + } + } + onMeetingListCountChanged: showDefaultItem = leftPanelStackView.currentItem.objectName == "listLayout" && meetingListCount === 0 + + function createConference(name, address) { + var confInfoGui = Qt.createQmlObject('import Linphone + ConferenceInfoGui{ + }', mainItem) + leftPanelStackView.push(createConference, {"conferenceInfoGui": confInfoGui}) + } + + leftPanelContent: ColumnLayout { + id: leftPanel + Layout.fillWidth: true + Layout.fillHeight: true + property int sideMargin: 25 * DefaultStyle.dp + + ColumnLayout { + Layout.topMargin: 30 * DefaultStyle.dp + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + enabled: mainItem.leftPanelEnabled + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + Control.ScrollBar { + id: meetingsScrollbar + active: true + interactive: true + policy: Control.ScrollBar.AsNeeded + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + } + Control.StackView { + id: leftPanelStackView + initialItem: listLayout + anchors.fill: parent + } + Component { + id: listLayout + ColumnLayout { + spacing: 19 * DefaultStyle.dp + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + Text { + text: qsTr("Réunions") + color: DefaultStyle.main2_700 + font.pixelSize: 29 * DefaultStyle.dp + font.weight: 800 * DefaultStyle.dp + } + Item { + Layout.fillWidth: true + } + Button { + background: Item { + } + icon.source: AppIcons.plusCircle + Layout.preferredWidth: 30 * DefaultStyle.dp + Layout.preferredHeight: 30 * DefaultStyle.dp + width: 30 * DefaultStyle.dp + height: 30 * DefaultStyle.dp + onClicked: { + mainItem.createConference() + } + } + } + + SearchBar { + id: searchBar + Layout.rightMargin: leftPanel.sideMargin + Layout.fillWidth: true + placeholderText: qsTr("Rechercher une réunion") + } + Control.ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + rightPadding: leftPanel.sideMargin + contentWidth: width - leftPanel.sideMargin + contentHeight: content.height + clip: true + Control.ScrollBar.vertical: meetingsScrollbar + ColumnLayout { + id: content + width: parent.width + anchors.topMargin: 5 * DefaultStyle.dp + anchors.fill: parent + spacing: 15 * DefaultStyle.dp + Text { + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + text: mainItem.emptyListText + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + visible: mainItem.showDefaultItem + } + + MeetingList { + id: conferenceList + visible: count != 0 + hoverEnabled: mainItem.leftPanelEnabled + Layout.preferredWidth: parent.width + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: 20 * DefaultStyle.dp + searchBarText: searchBar.text + onSelectedConferenceChanged: { + mainItem.selectedConference = selectedConference + } + onCountChanged: { + mainItem.meetingListCount = count + } + } + } + } + } + } + Component { + id: createConference + NewMeeting { + onReturnRequested: leftPanelStackView.pop() + onAddParticipantsRequested: { + onClicked: leftPanelStackView.push(addParticipants, {"conferenceInfoGui": conferenceInfoGui}) + } + } + } + Component { + id: addParticipants + AddParticipantsLayout { + title: qsTr("Ajouter des participants") + validateButtonText: qsTr("Ajouter") + titleColor: DefaultStyle.main1_500_main + onReturnRequested: { + leftPanelStackView.pop() + } + } + } + } + } + } + + Component { + id: meetingDetail + RowLayout { + ColumnLayout { + Layout.alignment: Qt.AlignTop + Layout.preferredWidth: 393 * DefaultStyle.dp + Layout.fillWidth: false + Layout.fillHeight: true + Layout.leftMargin: 39 * DefaultStyle.dp + Layout.topMargin: 39 * DefaultStyle.dp + spacing: 25 * DefaultStyle.dp + Section { + content: RowLayout { + spacing: 8 * DefaultStyle.dp + Image { + source: AppIcons.usersThree + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + Text { + text: mainItem.selectedConference.core.subject + font { + pixelSize: 20 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + Item { + Layout.fillWidth: true + } + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + icon.source: AppIcons.pencil + contentImageColor: DefaultStyle.main1_500_main + background: Item{} + onClicked: mainItem.rightPanelStackView.replace() + } + PopupButton { + id: deletePopup + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + contentImageColor: DefaultStyle.main1_500_main + popup.contentItem: RowLayout { + Button { + background: Item{} + contentItem: RowLayout { + EffectImage { + imageSource: AppIcons.trashCan + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + colorizationColor: DefaultStyle.danger_500main + } + Text { + text: qsTr("Delete this meeting") + color: DefaultStyle.danger_500main + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + onClicked: { + if (UtilsCpp.isMe(mainItem.selectedConference.core.organizerAddress)) + mainItem.selectedConference.core.lDeleteConferenceInfo() + else + UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("Vous pouvez supprimer une conférence seulement si vous en êtes l'organisateur"), false) + // mainItem.contactDeletionRequested(mainItem.selectedConference) + deletePopup.close() + } + } + } + } + } + } + Section { + content: ColumnLayout { + spacing: 15 * DefaultStyle.dp + width: parent.width + RowLayout { + spacing: 8 * DefaultStyle.dp + Layout.fillWidth: true + Image { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + source: AppIcons.videoCamera + } + Button { + Layout.fillWidth: true + text: mainItem.selectedConference.core.uri + textSize: 14 * DefaultStyle.dp + textWeight: 400 * DefaultStyle.dp + underline: true + inversedColors: true + color: DefaultStyle.main2_600 + background: Item{} + onClicked: { + console.log("TODO: join conf", text) + } + } + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + background: Item{} + icon.source: AppIcons.shareNetwork + onClicked: UtilsCpp.copyToClipboard(confUri.text) + } + } + RowLayout { + spacing: 8 * DefaultStyle.dp + Image { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + source: AppIcons.clock + } + Text { + text: UtilsCpp.toDateString(mainItem.selectedConference.core.dateTimeUtc) + + " | " + UtilsCpp.toDateHourString(mainItem.selectedConference.core.dateTimeUtc) + + " - " + + UtilsCpp.toDateHourString(mainItem.selectedConference.core.endDateTime) + font { + pixelSize: 14 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + } + RowLayout { + spacing: 8 * DefaultStyle.dp + Image { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + source: AppIcons.globe + } + Text { + text: qsTr("Time zone: ") + mainItem.selectedConference.core.timeZoneModel.displayName + ", " + mainItem.selectedConference.core.timeZoneModel.countryName + font { + pixelSize: 14 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + } + } + } + Section { + visible: mainItem.selectedConference.core.description.length != 0 + content: RowLayout { + spacing: 8 * DefaultStyle.dp + EffectImage { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + imageSource: AppIcons.note + colorizationColor: DefaultStyle.main2_600 + } + Text { + text: mainItem.selectedConference.core.description + Layout.fillWidth: true + font { + pixelSize: 14 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + } + } + Section { + content: RowLayout { + spacing: 8 * DefaultStyle.dp + EffectImage { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + imageSource: AppIcons.userRectangle + colorizationColor: DefaultStyle.main2_600 + } + Avatar { + Layout.preferredWidth: 45 * DefaultStyle.dp + Layout.preferredHeight: 45 * DefaultStyle.dp + address: mainItem.selectedConference.core.organizerAddress + } + Text { + text: mainItem.selectedConference.core.organizerName + font { + pixelSize: 14 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + } + } + Section { + content: RowLayout { + Layout.preferredHeight: participantList.height + width: 393 * DefaultStyle.dp + spacing: 8 * DefaultStyle.dp + Image { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.topMargin: 20 * DefaultStyle.dp + source: AppIcons.usersTwo + } + ListView { + id: participantList + Layout.preferredHeight: Math.min(184 * DefaultStyle.dp, contentHeight) + Layout.fillWidth: true + model: mainItem.selectedConference.core.participants + clip: true + delegate: RowLayout { + height: 56 * DefaultStyle.dp + width: participantList.width + Avatar { + Layout.preferredWidth: 45 * DefaultStyle.dp + Layout.preferredHeight: 45 * DefaultStyle.dp + address: modelData.address + } + Text { + text: modelData.displayName + Layout.fillWidth: true + font { + pixelSize: 14 * DefaultStyle.dp + capitalization: Font.Capitalize + } + } + Text { + text: qsTr("Organizer") + visible: mainItem.selectedConference.core.organizerAddress === modelData.address + color: DefaultStyle.main2_400 + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + } + } + } + Button { + Layout.fillWidth: true + text: qsTr("Rejoindre la réunion") + topPadding: 11 * DefaultStyle.dp + bottomPadding: 11 * DefaultStyle.dp + onClicked: console.log("TODO: join conf", mainItem.selectedConference.core.subject) + } + } + } + } + Component { + id: meetingEdition + Section { + content: RowLayout { + spacing: 8 * DefaultStyle.dp + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + icon.source: AppIcons.leftArrow + contentImageColor: DefaultStyle.main1_500_main + background: Item{} + onClicked: mainItem.rightPanelStackView.pop() + } + Image { + source: AppIcons.usersThree + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + Text { + text: mainItem.selectedConference.core.subject + font { + pixelSize: 20 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + Item { + Layout.fillWidth: true + } + Button { + text: qsTr("Save") + onClicked: { + console.log("TODO : save meeting infos") + mainItem.rightPanelStackView.pop(meetingDetail, Control.StackView.Immediate) + } + } + } + } + } +} diff --git a/Linphone/view/Prototype/CallPrototype.qml b/Linphone/view/Prototype/CallPrototype.qml index 15624b2b0..cc70c140f 100644 --- a/Linphone/view/Prototype/CallPrototype.qml +++ b/Linphone/view/Prototype/CallPrototype.qml @@ -128,10 +128,10 @@ Window{ } } } - TextInput { + TextField { id: usernameToCall label: "Username to call" - textInputWidth: 250 + Layout.preferredWidth: 250 * DefaultStyle.dp } Button{ text: 'Call' diff --git a/Linphone/view/Prototype/FriendPrototype.qml b/Linphone/view/Prototype/FriendPrototype.qml index 4c92779e6..c072aa8a1 100644 --- a/Linphone/view/Prototype/FriendPrototype.qml +++ b/Linphone/view/Prototype/FriendPrototype.qml @@ -16,12 +16,12 @@ Window{ FriendGui{ id: contact } - TextInput{ + TextField{ placeholderText: 'Name' initialText: contact.core.givenName onTextChanged: contact.core.givenName = text } - TextInput{ + TextField{ placeholderText: 'Address' initialText: contact.core.address onTextChanged: contact.core.address = text @@ -63,7 +63,7 @@ Window{ Text{ text: modelData.core.address } - TextInput{ + TextField{ initialText: modelData.core.address onTextChanged: if(modelData.core.address != text){ modelData.core.address = text diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index fb8b8b48d..f35708992 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -8,9 +8,10 @@ QtObject { property string eyeHide: "image://internal/eye.svg" property string eyeShow: "image://internal/eye-slash.svg" property string downArrow: "image://internal/caret-down.svg" - property string returnArrow: "image://internal/caret-left.svg" + property string leftArrow: "image://internal/caret-left.svg" property string rightArrow: "image://internal/caret-right.svg" property string upArrow: "image://internal/caret-up.svg" + property string reloadArrow: "image://internal/arrow-clockwise.svg" property string info: "image://internal/info.svg" property string loginImage: "image://internal/login_image.svg" property string belledonne: "image://internal/belledonne.svg" @@ -62,6 +63,7 @@ QtObject { property string pause: "image://internal/pause.svg" property string play: "image://internal/play.svg" property string smiley: "image://internal/smiley.svg" + property string smileySad: "image://internal/smiley-sad.svg" property string trashCan: "image://internal/trash-simple.svg" property string copy: "image://internal/copy.svg" property string empty: "image://internal/empty.svg" @@ -75,5 +77,11 @@ QtObject { property string bellSlash: "image://internal/bell-simple-slash.svg" property string question: "image://internal/question.svg" property string settings: "image://internal/settings.svg" + property string clock: "image://internal/clock.svg" + property string note: "image://internal/note.svg" + property string userRectangle: "image://internal/user-rectangle.svg" + property string usersTwo: "image://internal/users.svg" + property string globe: "image://internal/globe-hemisphere-west.svg" + property string slide: "image://internal/slideshow.svg" } diff --git a/Linphone/view/Style/DefaultStyle.qml b/Linphone/view/Style/DefaultStyle.qml index 13685ab6c..024b150c3 100644 --- a/Linphone/view/Style/DefaultStyle.qml +++ b/Linphone/view/Style/DefaultStyle.qml @@ -29,7 +29,6 @@ QtObject { property color success_500main: "#4FAE80" property color info_500_main: "#4AA8FF" - property double dp: 1 @@ -39,7 +38,5 @@ QtObject { property color numericPadPressedButtonColor: "#EEF7F8" property color groupCallButtonColor: "#EEF7F8" - property color launchCallButtonColor: "#4FAE80" - property color callCheckedButtonColor: "#9AABB5" } diff --git a/external/linphone-sdk b/external/linphone-sdk index 8f3434663..f771d655e 160000 --- a/external/linphone-sdk +++ b/external/linphone-sdk @@ -1 +1 @@ -Subproject commit 8f34346639a69e44cc67a6363fc657f78bfbc28d +Subproject commit f771d655e30169e59d8b3736d4e2a30e52e29f18