From fb4ee6b5797068875cb498e1d4488c8f23612c23 Mon Sep 17 00:00:00 2001 From: Gaelle Braud Date: Tue, 13 Feb 2024 17:10:00 +0100 Subject: [PATCH] audio settings video settings app settings in settings file --- Linphone/core/App.cpp | 104 ++-- Linphone/core/App.hpp | 11 +- Linphone/core/CMakeLists.txt | 2 +- Linphone/core/call/CallCore.cpp | 87 ++- Linphone/core/call/CallCore.hpp | 27 + Linphone/core/setting/Settings.cpp | 45 -- Linphone/core/setting/Settings.hpp | 36 -- Linphone/core/setting/SettingsCore.cpp | 111 ++++ Linphone/core/setting/SettingsCore.hpp | 74 +++ Linphone/model/call/CallModel.cpp | 55 ++ Linphone/model/call/CallModel.hpp | 15 + Linphone/model/setting/SettingsModel.cpp | 19 + Linphone/model/setting/SettingsModel.hpp | 8 + Linphone/model/tool/ToolModel.cpp | 12 + Linphone/model/tool/ToolModel.hpp | 1 + Linphone/tool/Utils.cpp | 8 - Linphone/tool/Utils.hpp | 12 +- Linphone/view/App/CallsWindow.qml | 533 ++++++++++++------ Linphone/view/App/Main.qml | 5 +- Linphone/view/CMakeLists.txt | 1 + Linphone/view/Item/Carousel.qml | 2 +- Linphone/view/Item/ComboBox.qml | 13 +- Linphone/view/Item/EffectImage.qml | 2 +- Linphone/view/Item/MovableMouseArea.qml | 2 +- Linphone/view/Item/PhoneNumberComboBox.qml | 2 +- Linphone/view/Item/PopupButton.qml | 1 + .../view/Item/RoundedBackgroundControl.qml | 20 +- Linphone/view/Item/Slider.qml | 53 ++ 28 files changed, 902 insertions(+), 359 deletions(-) delete mode 100644 Linphone/core/setting/Settings.cpp delete mode 100644 Linphone/core/setting/Settings.hpp create mode 100644 Linphone/core/setting/SettingsCore.cpp create mode 100644 Linphone/core/setting/SettingsCore.hpp create mode 100644 Linphone/view/Item/Slider.qml diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index ea00cbd8..00f7175a 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -49,6 +49,7 @@ #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/variant/VariantList.hpp" #include "model/object/VariantObject.hpp" @@ -67,6 +68,9 @@ App::App(int &argc, char *argv[]) init(); } +App::~App() { +} + App *App::getInstance() { return dynamic_cast(QApplication::instance()); } @@ -81,8 +85,53 @@ Notifier *App::getNotifier() const { void App::init() { // Core. Manage the logger so it must be instantiate at first. auto coreModel = CoreModel::create("", mLinphoneThread); - connect(mLinphoneThread, &QThread::started, coreModel.get(), &CoreModel::start); - mFirstLaunch = mSettings.value("firstLaunch", 1).toInt(); + connect( + mLinphoneThread, &QThread::started, coreModel.get(), + [this, coreModel]() mutable { + coreModel->start(); + auto settings = Settings::create(); + QMetaObject::invokeMethod(App::getInstance()->thread(), [this, settings]() mutable { + mSettings = settings; + settings.reset(); + + // QML + mEngine = new QQmlApplicationEngine(this); + // Provide `+custom` folders for custom components and `5.9` for old components. + QStringList selectors("custom"); + const QVersionNumber &version = QLibraryInfo::version(); + if (version.majorVersion() == 5 && version.minorVersion() == 9) selectors.push_back("5.9"); + auto selector = new QQmlFileSelector(mEngine, mEngine); + selector->setExtraSelectors(selectors); + qInfo() << log().arg("Activated selectors:") << selector->selector()->allSelectors(); + + mEngine->addImportPath(":/"); + mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath()); + initCppInterfaces(); + mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider()); + mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider()); + + // Enable notifications. + mNotifier = new Notifier(mEngine); + + const QUrl url(u"qrc:/Linphone/view/App/Main.qml"_qs); + QObject::connect( + mEngine, &QQmlApplicationEngine::objectCreated, this, + [this, url](QObject *obj, const QUrl &objUrl) { + if (url == objUrl) { + if (!obj) { + qCritical() << log().arg("Main.qml couldn't be load. The app will exit"); + exit(-1); + } + mMainWindow = qobject_cast(obj); + Q_ASSERT(mMainWindow); + } + }, + Qt::QueuedConnection); + mEngine->load(url); + }); + coreModel.reset(); + }, + Qt::SingleShotConnection); // Console Commands createCommandParser(); mParser->parse(this->arguments()); @@ -102,40 +151,6 @@ void App::init() { qInfo() << log().arg("Display server : %1").arg(platformName()); - // QML - mEngine = new QQmlApplicationEngine(this); - // Provide `+custom` folders for custom components and `5.9` for old components. - QStringList selectors("custom"); - const QVersionNumber &version = QLibraryInfo::version(); - if (version.majorVersion() == 5 && version.minorVersion() == 9) selectors.push_back("5.9"); - auto selector = new QQmlFileSelector(mEngine, mEngine); - selector->setExtraSelectors(selectors); - qInfo() << log().arg("Activated selectors:") << selector->selector()->allSelectors(); - - mEngine->addImportPath(":/"); - mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath()); - initCppInterfaces(); - mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider()); - mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider()); - - // Enable notifications. - mNotifier = new Notifier(mEngine); - - const QUrl url(u"qrc:/Linphone/view/App/Main.qml"_qs); - QObject::connect( - mEngine, &QQmlApplicationEngine::objectCreated, this, - [this, url](QObject *obj, const QUrl &objUrl) { - if (url == objUrl) { - if (!obj) { - qCritical() << log().arg("Main.qml couldn't be load. The app will exit"); - exit(-1); - } - mMainWindow = qobject_cast(obj); - Q_ASSERT(mMainWindow); - } - }, - Qt::QueuedConnection); - mEngine->load(url); // mEngine->load(u"qrc:/Linphone/view/Prototype/CameraPrototype.qml"_qs); } @@ -152,6 +167,10 @@ void App::initCppInterfaces() { "EnumsToStringCpp", 1, 0, "EnumsToStringCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * { return new EnumsToString(engine); }); + qmlRegisterSingletonType( + "SettingsCpp", 1, 0, "SettingsCpp", + [this](QQmlEngine *engine, QJSEngine *) -> QObject * { return mSettings.get(); }); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "VariantObject"); @@ -180,6 +199,8 @@ void App::clean() { mNotifier = nullptr; delete mEngine; mEngine = nullptr; + mSettings.reset(); + mSettings = nullptr; mLinphoneThread->wait(250); qApp->processEvents(QEventLoop::AllEvents, 250); mLinphoneThread->exit(); @@ -267,17 +288,6 @@ void App::closeCallsWindow() { } } -void App::setFirstLaunch(bool first) { - if (mFirstLaunch != first) { - mFirstLaunch = first; - mSettings.setValue("firstLaunch", first); - } -} - -bool App::getFirstLaunch() const { - return mFirstLaunch; -} - QQuickWindow *App::getMainWindow() { return mMainWindow; } diff --git a/Linphone/core/App.hpp b/Linphone/core/App.hpp index 04f73a0a..8e7af8a0 100644 --- a/Linphone/core/App.hpp +++ b/Linphone/core/App.hpp @@ -20,9 +20,9 @@ #include #include -#include #include +#include "core/setting/SettingsCore.hpp" #include "core/singleapplication/singleapplication.h" #include "model/core/CoreModel.hpp" #include "tool/AbstractObject.hpp" @@ -35,6 +35,7 @@ class QQuickWindow; class App : public SingleApplication, public AbstractObject { public: App(int &argc, char *argv[]); + ~App(); static App *getInstance(); Notifier *getNotifier() const; @@ -94,9 +95,6 @@ public: QQuickWindow *getCallsWindow(QVariant callGui); void closeCallsWindow(); - bool getFirstLaunch() const; - void setFirstLaunch(bool first); - QQuickWindow *getMainWindow(); QQmlApplicationEngine *mEngine = nullptr; @@ -112,10 +110,7 @@ private: Notifier *mNotifier = nullptr; QQuickWindow *mMainWindow = nullptr; QQuickWindow *mCallsWindow = nullptr; - QSettings mSettings; - bool mFirstLaunch = true; - // TODO : changer ce count lorsqu'on aura liste d'appels - int callsCount = 0; + QSharedPointer mSettings; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index 7dea98ff..5ddc5994 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -28,7 +28,7 @@ list(APPEND _LINPHONEAPP_SOURCES core/search/MagicSearchList.cpp core/search/MagicSearchProxy.cpp - core/setting/Settings.cpp + core/setting/SettingsCore.cpp core/proxy/ListProxy.cpp core/proxy/Proxy.cpp diff --git a/Linphone/core/call/CallCore.cpp b/Linphone/core/call/CallCore.cpp index 89602052..a4214e09 100644 --- a/Linphone/core/call/CallCore.cpp +++ b/Linphone/core/call/CallCore.cpp @@ -26,6 +26,13 @@ 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); @@ -45,16 +52,15 @@ CallCore::CallCore(const std::shared_ptr &call) : QObject(nullpt mMicrophoneMuted = call->getMicrophoneMuted(); mSpeakerMuted = call->getSpeakerMuted(); mCameraEnabled = call->cameraEnabled(); - mDuration = call->getDuration(); mState = LinphoneEnums::fromLinphone(call->getState()); - mPeerAddress = Utils::coreStringToAppString(mCallModel->getRemoteAddress()->asStringUriOnly()); + 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 = mCallModel->getAuthenticationTokenVerified(); + auto tokenVerified = call->getAuthenticationTokenVerified(); mLocalSas = localToken; mRemoteSas = remoteToken; mIsSecured = (mEncryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) || @@ -65,6 +71,19 @@ CallCore::CallCore(const std::shared_ptr &call) : QObject(nullpt 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; } @@ -121,6 +140,9 @@ void CallCore::setSelf(QSharedPointer me) { 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]() { @@ -179,6 +201,35 @@ void CallCore::setSelf(QSharedPointer me) { 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); }); }); @@ -383,6 +434,36 @@ void CallCore::setRecordable(bool recordable) { } } +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; } diff --git a/Linphone/core/call/CallCore.hpp b/Linphone/core/call/CallCore.hpp index 7289af16..27530812 100644 --- a/Linphone/core/call/CallCore.hpp +++ b/Linphone/core/call/CallCore.hpp @@ -51,6 +51,11 @@ class CallCore : public QObject, public AbstractObject { 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::CallState transferState READ getTransferState NOTIFY transferStateChanged) public: @@ -112,6 +117,18 @@ public: 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::CallState getTransferState() const; void setTransferState(LinphoneEnums::CallState state, const QString &message); @@ -135,6 +152,9 @@ signals: void recordingChanged(); void remoteRecordingChanged(); void recordableChanged(); + void speakerVolumeGainChanged(); + void microphoneVolumeChanged(); + void microphoneVolumeGainChanged(); // Linphone commands void lAccept(bool withVideo); // Accept an incoming call @@ -149,6 +169,10 @@ signals: 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(); @@ -189,6 +213,9 @@ private: bool mRecordable = false; QString mLocalSas; QString mRemoteSas; + float mSpeakerVolumeGain; + float mMicrophoneVolume; + float mMicrophoneVolumeGain; QSharedPointer> mCallModelConnection; DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/core/setting/Settings.cpp b/Linphone/core/setting/Settings.cpp deleted file mode 100644 index 63273598..00000000 --- a/Linphone/core/setting/Settings.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "Settings.hpp" - -#include - -#include "core/path/Paths.hpp" - -// ============================================================================= - -Settings::Settings(QObject *parent) : QObject(parent) { -} - -Settings::~Settings() { -} - -QString Settings::getConfigPath(const QCommandLineParser &parser) { - QString filePath = parser.isSet("config") ? parser.value("config") : ""; - QString configPath; - if (!QUrl(filePath).isRelative()) { - // configPath = FileDownloader::synchronousDownload(filePath, - // Utils::coreStringToAppString(Paths::getConfigDirPath(false)), true)); - } - if (configPath == "") configPath = Paths::getConfigFilePath(filePath, false); - if (configPath == "" && !filePath.isEmpty()) configPath = Paths::getConfigFilePath("", false); - return configPath; -} diff --git a/Linphone/core/setting/Settings.hpp b/Linphone/core/setting/Settings.hpp deleted file mode 100644 index 69c07516..00000000 --- a/Linphone/core/setting/Settings.hpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 SETTINGS_H_ -#define SETTINGS_H_ - -#include -#include -#include - -class Settings : public QObject { - Q_OBJECT -public: - Settings(QObject *parent = Q_NULLPTR); - virtual ~Settings(); - - QString getConfigPath(const QCommandLineParser &parser = QCommandLineParser()); -}; -#endif diff --git a/Linphone/core/setting/SettingsCore.cpp b/Linphone/core/setting/SettingsCore.cpp new file mode 100644 index 00000000..27fb6598 --- /dev/null +++ b/Linphone/core/setting/SettingsCore.cpp @@ -0,0 +1,111 @@ +/* + * 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 "SettingsCore.hpp" +#include "core/App.hpp" +#include "core/path/Paths.hpp" +#include "model/tool/ToolModel.hpp" +#include "tool/Utils.hpp" + +#include +#include + +DEFINE_ABSTRACT_OBJECT(Settings) + +// ============================================================================= + +QSharedPointer Settings::create() { + auto sharedPointer = QSharedPointer(new Settings(), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +Settings::Settings(QObject *parent) : QObject(parent) { + mustBeInLinphoneThread(getClassName()); + mSettingsModel = Utils::makeQObject_ptr(); + auto core = CoreModel::getInstance()->getCore(); + for (auto &device : core->getExtendedAudioDevices()) { + auto core = CoreModel::getInstance()->getCore(); + if (device->hasCapability(linphone::AudioDevice::Capabilities::CapabilityRecord)) { + mInputAudioDevices.append(Utils::coreStringToAppString(device->getId())); + // mInputAudioDevices.append(createDeviceVariant(Utils::coreStringToAppString(device->getId()), + // Utils::coreStringToAppString(device->getDeviceName()))); + } + if (device->hasCapability(linphone::AudioDevice::Capabilities::CapabilityPlay)) { + mOutputAudioDevices.append(Utils::coreStringToAppString(device->getId())); + // mOutputAudioDevices.append(createDeviceVariant(Utils::coreStringToAppString(device->getId()), + // Utils::coreStringToAppString(device->getDeviceName()))); + } + } + for (auto &device : core->getVideoDevicesList()) { + mVideoDevices.append(Utils::coreStringToAppString(device)); + } +} + +Settings::~Settings() { +} + +void Settings::setSelf(QSharedPointer me) { + mustBeInLinphoneThread(getClassName()); + mSettingsModelConnection = QSharedPointer>( + new SafeConnection(me, mSettingsModel), &QObject::deleteLater); + mSettingsModelConnection->makeConnectToCore(&Settings::lSetVideoDevice, [this](const QString &id) { + mSettingsModelConnection->invokeToModel( + [this, id]() { mSettingsModel->setVideoDevice(Utils::appStringToCoreString(id)); }); + }); +} + +QString Settings::getConfigPath(const QCommandLineParser &parser) { + QString filePath = parser.isSet("config") ? parser.value("config") : ""; + QString configPath; + if (!QUrl(filePath).isRelative()) { + // configPath = FileDownloader::synchronousDownload(filePath, + // Utils::coreStringToAppString(Paths::getConfigDirPath(false)), true)); + } + if (configPath == "") configPath = Paths::getConfigFilePath(filePath, false); + if (configPath == "" && !filePath.isEmpty()) configPath = Paths::getConfigFilePath("", false); + return configPath; +} + +QStringList Settings::getInputAudioDevicesList() const { + return mInputAudioDevices; +} + +QStringList Settings::getOutputAudioDevicesList() const { + return mOutputAudioDevices; +} + +QStringList Settings::getVideoDevicesList() const { + return mVideoDevices; +} + +bool Settings::getFirstLaunch() const { + auto val = mAppSettings.value("firstLaunch", 1).toInt(); + return val; +} + +void Settings::setFirstLaunch(bool first) { + auto firstLaunch = getFirstLaunch(); + if (firstLaunch != first) { + mAppSettings.setValue("firstLaunch", (int)first); + mAppSettings.sync(); + } +} \ No newline at end of file diff --git a/Linphone/core/setting/SettingsCore.hpp b/Linphone/core/setting/SettingsCore.hpp new file mode 100644 index 00000000..9bfaacf6 --- /dev/null +++ b/Linphone/core/setting/SettingsCore.hpp @@ -0,0 +1,74 @@ +/* + * 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 SETTINGS_H_ +#define SETTINGS_H_ + +#include "model/setting/SettingsModel.hpp" +#include "tool/thread/SafeConnection.hpp" + +#include +#include +#include +#include + +class Settings : public QObject, public AbstractObject { + Q_OBJECT + Q_PROPERTY(QStringList inputAudioDevicesList READ getInputAudioDevicesList NOTIFY inputAudioDeviceChanged) + Q_PROPERTY(QStringList outputAudioDevicesList READ getOutputAudioDevicesList NOTIFY outputAudioDeviceChanged) + Q_PROPERTY(QStringList videoDevicesList READ getVideoDevicesList NOTIFY videoDeviceChanged) +public: + static QSharedPointer create(); + Settings(QObject *parent = Q_NULLPTR); + virtual ~Settings(); + + void setSelf(QSharedPointer me); + + QString getConfigPath(const QCommandLineParser &parser = QCommandLineParser()); + + QStringList getInputAudioDevicesList() const; + + QStringList getOutputAudioDevicesList() const; + + QStringList getVideoDevicesList() const; + + Q_INVOKABLE void setFirstLaunch(bool first); + Q_INVOKABLE bool getFirstLaunch() const; + +signals: + void inputAudioDeviceChanged(const QString &id); + void outputAudioDeviceChanged(const QString &id); + void videoDeviceChanged(); + + void lSetInputAudioDevice(const QString &device); + void lSetOutputAudioDevice(const QString &device); + void lSetVideoDevice(const QString &device); + +private: + std::shared_ptr mSettingsModel; + QStringList mInputAudioDevices; + QStringList mOutputAudioDevices; + QStringList mVideoDevices; + QSettings mAppSettings; + QSharedPointer> mSettingsModelConnection; + + DECLARE_ABSTRACT_OBJECT +}; +#endif diff --git a/Linphone/model/call/CallModel.cpp b/Linphone/model/call/CallModel.cpp index aec8e6af..20e5969f 100644 --- a/Linphone/model/call/CallModel.cpp +++ b/Linphone/model/call/CallModel.cpp @@ -35,6 +35,13 @@ CallModel::CallModel(const std::shared_ptr &call, QObject *paren mDurationTimer.setSingleShot(false); connect(&mDurationTimer, &QTimer::timeout, this, [this]() { this->durationChanged(mMonitor->getDuration()); }); mDurationTimer.start(); + + mMicroVolumeTimer.setInterval(50); + mMicroVolumeTimer.setSingleShot(false); + connect(&mMicroVolumeTimer, &QTimer::timeout, this, + [this]() { this->microphoneVolumeChanged(Utils::computeVu(mMonitor->getRecordVolume())); }); + mMicroVolumeTimer.start(); + connect(this, &CallModel::stateChanged, this, [this] { auto state = mMonitor->getState(); if (state == linphone::Call::State::Paused) setPaused(true); @@ -144,6 +151,54 @@ void CallModel::setRecordFile(const std::string &path) { mMonitor->update(params); } +void CallModel::setSpeakerVolumeGain(float gain) { + mMonitor->setSpeakerVolumeGain(gain); + emit speakerVolumeGainChanged(gain); +} + +float CallModel::getSpeakerVolumeGain() const { + auto gain = mMonitor->getSpeakerVolumeGain(); + if (gain < 0) gain = CoreModel::getInstance()->getCore()->getPlaybackGainDb(); + return gain; +} + +void CallModel::setMicrophoneVolumeGain(float gain) { + mMonitor->setMicrophoneVolumeGain(gain); + emit microphoneVolumeGainChanged(gain); +} + +float CallModel::getMicrophoneVolumeGain() const { + auto gain = mMonitor->getMicrophoneVolumeGain(); + return gain; +} + +float CallModel::getMicrophoneVolume() const { + auto volume = mMonitor->getRecordVolume(); + return volume; +} + +void CallModel::setInputAudioDevice(const std::shared_ptr &device) { + mMonitor->setInputAudioDevice(device); + std::string deviceName; + if (device) deviceName = device->getDeviceName(); + emit inputAudioDeviceChanged(deviceName); +} + +std::shared_ptr CallModel::getInputAudioDevice() const { + return mMonitor->getInputAudioDevice(); +} + +void CallModel::setOutputAudioDevice(const std::shared_ptr &device) { + mMonitor->setOutputAudioDevice(device); + std::string deviceName; + if (device) deviceName = device->getDeviceName(); + emit outputAudioDeviceChanged(deviceName); +} + +std::shared_ptr CallModel::getOutputAudioDevice() const { + return mMonitor->getOutputAudioDevice(); +} + std::string CallModel::getRecordFile() const { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return mMonitor->getParams()->getRecordFile(); diff --git a/Linphone/model/call/CallModel.hpp b/Linphone/model/call/CallModel.hpp index 64a29708..5d4d45b3 100644 --- a/Linphone/model/call/CallModel.hpp +++ b/Linphone/model/call/CallModel.hpp @@ -46,11 +46,20 @@ public: void startRecording(); void stopRecording(); void setRecordFile(const std::string &path); + void setSpeakerVolumeGain(float gain); + void setMicrophoneVolumeGain(float gain); + 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 transferTo(const std::shared_ptr &address); void terminateAllCalls(); + float getMicrophoneVolumeGain() const; + float getMicrophoneVolume() const; + float getSpeakerVolumeGain() const; std::string getRecordFile() const; std::shared_ptr getRemoteAddress(); bool getAuthenticationTokenVerified() const; @@ -62,13 +71,19 @@ signals: 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 authenticationTokenVerifiedChanged(bool verified); + void speakerVolumeGainChanged(float volume); + void microphoneVolumeGainChanged(float volume); + void inputAudioDeviceChanged(const std::string &id); + void outputAudioDeviceChanged(const std::string &id); private: QTimer mDurationTimer; + QTimer mMicroVolumeTimer; DECLARE_ABSTRACT_OBJECT //-------------------------------------------------------------------------------- diff --git a/Linphone/model/setting/SettingsModel.cpp b/Linphone/model/setting/SettingsModel.cpp index 0436d276..d24dad3a 100644 --- a/Linphone/model/setting/SettingsModel.cpp +++ b/Linphone/model/setting/SettingsModel.cpp @@ -19,6 +19,8 @@ */ #include "SettingsModel.hpp" +#include "model/core/CoreModel.hpp" + // ============================================================================= DEFINE_ABSTRACT_OBJECT(SettingsModel) @@ -41,3 +43,20 @@ std::string SettingsModel::getEntryFullName(const std::string §ion, const st mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return isReadOnly(section, name) ? name + "/readonly" : name; } + +std::list SettingsModel::getVideoDevices() const { + auto core = CoreModel::getInstance()->getCore(); + return core->getVideoDevicesList(); +} + +std::string SettingsModel::getVideoDevice() { + return CoreModel::getInstance()->getCore()->getVideoDevice(); +} + +void SettingsModel::setVideoDevice(const std::string &id) { + auto core = CoreModel::getInstance()->getCore(); + if (core->getVideoDevice() != id) { + CoreModel::getInstance()->getCore()->setVideoDevice(id); + emit videoDeviceChanged(); + } +} \ No newline at end of file diff --git a/Linphone/model/setting/SettingsModel.hpp b/Linphone/model/setting/SettingsModel.hpp index 2b154340..c1ba906d 100644 --- a/Linphone/model/setting/SettingsModel.hpp +++ b/Linphone/model/setting/SettingsModel.hpp @@ -30,6 +30,7 @@ class SettingsModel : public QObject, public AbstractObject { Q_OBJECT + public: SettingsModel(QObject *parent = Q_NULLPTR); virtual ~SettingsModel(); @@ -39,10 +40,17 @@ public: getEntryFullName(const std::string §ion, const std::string &name) const; // Return the full name of the entry : 'name/readonly' or 'name' + std::list getVideoDevices() const; + void setVideoDevice(const std::string &id); + std::string getVideoDevice(); + static const std::string UiSection; std::shared_ptr mConfig; +signals: + void videoDeviceChanged(); + private: DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index f32efb87..359d2345 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -56,6 +56,18 @@ std::shared_ptr ToolModel::makeLinphoneNumber(const return linphoneNumber; } +std::shared_ptr ToolModel::findAudioDevice(const QString &id) { + std::string devId = Utils::appStringToCoreString(id); + auto devices = CoreModel::getInstance()->getCore()->getExtendedAudioDevices(); + auto audioDevice = + find_if(devices.cbegin(), devices.cend(), + [&](const std::shared_ptr &audioItem) { return audioItem->getId() == devId; }); + if (audioDevice != devices.cend()) { + return *audioDevice; + } + return nullptr; +} + QString ToolModel::getDisplayName(const std::shared_ptr &address) { QString displayName; if (address) { diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index 217cafc8..c52778ba 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -36,6 +36,7 @@ public: static std::shared_ptr interpretUrl(const QString &address); static std::shared_ptr makeLinphoneNumber(const QString &label, const QString &number); + static std::shared_ptr findAudioDevice(const QString &id); static QString getDisplayName(const std::shared_ptr &address); static QString getDisplayName(QString address); diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 03a17543..1d99dd37 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -120,14 +120,6 @@ VariantObject *Utils::createCall(const QString &sipAddress, return data; } -void Utils::setFirstLaunch(bool first) { - App::getInstance()->setFirstLaunch(first); -} - -bool Utils::getFirstLaunch() { - return App::getInstance()->getFirstLaunch(); -} - void Utils::openCallsWindow(CallGui *call) { if (call) App::getInstance()->getCallsWindow(QVariant::fromValue(call))->show(); } diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 354d0503..1e3bf278 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -60,8 +60,6 @@ public: bool withVideo = false, const QString &prepareTransfertAddress = "", const QHash &headers = {}); - Q_INVOKABLE static void setFirstLaunch(bool first); - Q_INVOKABLE static bool getFirstLaunch(); Q_INVOKABLE static void openCallsWindow(CallGui *call); Q_INVOKABLE static QQuickWindow *getMainWindow(); Q_INVOKABLE static QQuickWindow *getCallsWindow(CallGui *callGui); @@ -98,6 +96,16 @@ public: static std::shared_ptr makeQObject_ptr(Args &&...args) { return std::shared_ptr(new T(args...), [](T *obj) { obj->deleteLater(); }); } + + static inline float computeVu(float volume) { + constexpr float VuMin = -20.f; + constexpr float VuMax = 4.f; + + if (volume < VuMin) return 0.f; + if (volume > VuMax) return 1.f; + + return (volume - VuMin) / (VuMax - VuMin); + } }; #endif // UTILS_H_ diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index 567dbd32..02a10852 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -5,6 +5,7 @@ import QtQuick.Controls as Control import Linphone import EnumsToStringCpp 1.0 import UtilsCpp 1.0 +import SettingsCpp 1.0 Window { id: mainWindow @@ -277,7 +278,8 @@ Window { bottomPadding: 8 * DefaultStyle.dp leftPadding: 10 * DefaultStyle.dp rightPadding: 10 * DefaultStyle.dp - visible: mainWindow.call.core.isSecured + width: 269 * DefaultStyle.dp + visible: mainWindow.call && mainWindow.call.core.isSecured background: Rectangle { anchors.fill: parent border.color: DefaultStyle.info_500_main @@ -482,198 +484,341 @@ Window { weight: 800 * DefaultStyle.dp } } - contentItem: Control.StackView { + Control.StackView { id: rightPanelStack - } - Component { - id: contactsListPanel - CallContactsLists { - sideMargin: 10 * DefaultStyle.dp - topMargin: 15 * DefaultStyle.dp - groupCallVisible: false - searchBarColor: DefaultStyle.grey_0 - searchBarBorderColor: DefaultStyle.grey_200 - onCallButtonPressed: (address) => { - mainWindow.call.core.lTransferCall(address) + width: parent.width + height: parent.height + initialItem: callsListPanel + Component { + id: contactsListPanel + CallContactsLists { + sideMargin: 10 * DefaultStyle.dp + topMargin: 15 * DefaultStyle.dp + groupCallVisible: false + searchBarColor: DefaultStyle.grey_0 + searchBarBorderColor: DefaultStyle.grey_200 + onCallButtonPressed: (address) => { + mainWindow.call.core.lTransferCall(address) + } + Control.StackView.onActivated: rightPanelTitle.text = qsTr("Transfert d'appel") } - Control.StackView.onActivated: rightPanelTitle.text = qsTr("Transfert d'appel") } - } - Component { - id: dialerPanel - ColumnLayout { - Control.StackView.onActivated: rightPanelTitle.text = qsTr("Dialer") - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - SearchBar { - id: dialerTextInput - Layout.fillWidth: true - Layout.leftMargin: 10 * DefaultStyle.dp - Layout.rightMargin: 10 * DefaultStyle.dp - magnifierVisible: false - color: DefaultStyle.grey_0 - borderColor: DefaultStyle.grey_200 - placeholderText: "" - numericPad: numPad - numericPadButton.visible: false - } - Item { - Layout.fillWidth: true - Layout.preferredHeight: numPad.height - Layout.topMargin: 10 * DefaultStyle.dp - property var callObj - NumericPad { - id: numPad - width: parent.width - visible: parent.visible - closeButtonVisible: false - onLaunchCall: { - callObj = UtilsCpp.createCall(dialerTextInput.text) + Component { + id: dialerPanel + ColumnLayout { + Control.StackView.onActivated: rightPanelTitle.text = qsTr("Dialer") + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + SearchBar { + id: dialerTextInput + Layout.fillWidth: true + Layout.leftMargin: 10 * DefaultStyle.dp + Layout.rightMargin: 10 * DefaultStyle.dp + magnifierVisible: false + color: DefaultStyle.grey_0 + borderColor: DefaultStyle.grey_200 + placeholderText: "" + numericPad: numPad + numericPadButton.visible: false + } + Item { + Layout.fillWidth: true + Layout.preferredHeight: numPad.height + Layout.topMargin: 10 * DefaultStyle.dp + property var callObj + NumericPad { + id: numPad + width: parent.width + visible: parent.visible + closeButtonVisible: false + onLaunchCall: { + callObj = UtilsCpp.createCall(dialerTextInput.text + "@sip.linphone.org") + } } } } } - } - Component { - id: callsListPanel - Control.Control { - Control.StackView.onActivated: rightPanelTitle.text = qsTr("Liste d'appel") - // width: callList.width - // height: callList.height - // padding: 15 * DefaultStyle.dp - topPadding: 15 * DefaultStyle.dp - bottomPadding: 15 * DefaultStyle.dp - leftPadding: 15 * DefaultStyle.dp - rightPadding: 15 * DefaultStyle.dp - - background: Rectangle { - anchors.fill: parent - anchors.leftMargin: 10 * DefaultStyle.dp - anchors.rightMargin: 10 * DefaultStyle.dp - anchors.topMargin: 10 * DefaultStyle.dp - anchors.bottomMargin: 10 * DefaultStyle.dp - color: DefaultStyle.main2_0 - radius: 15 * DefaultStyle.dp - } + Component { + id: callsListPanel + ColumnLayout { + RoundedBackgroundControl { + Control.StackView.onActivated: rightPanelTitle.text = qsTr("Liste d'appel") + Layout.fillWidth: true + // height: Math.min(callList.height + topPadding + bottomPadding, rightPanelStack.height) + onHeightChanged: console.log("calls list height changed", height) + height: Math.min(callList.height + topPadding + bottomPadding, rightPanelStack.height) - contentItem: ListView { - id: callList - model: callsModel - height: contentHeight - spacing: 15 * DefaultStyle.dp + topPadding: 15 * DefaultStyle.dp + bottomPadding: 15 * DefaultStyle.dp + leftPadding: 15 * DefaultStyle.dp + rightPadding: 15 * DefaultStyle.dp + backgroundColor: DefaultStyle.main2_0 + + contentItem: ListView { + id: callList + model: callsModel + height: Math.min(contentHeight, rightPanelStack.height) + spacing: 15 * DefaultStyle.dp - onCountChanged: forceLayout() + onCountChanged: forceLayout() - delegate: Item { - id: callDelegate - width: callList.width - height: 45 * DefaultStyle.dp + delegate: Item { + id: callDelegate + width: callList.width + height: 45 * DefaultStyle.dp - RowLayout { - id: delegateContent - anchors.fill: parent - anchors.leftMargin: 10 * DefaultStyle.dp - anchors.rightMargin: 10 * DefaultStyle.dp - Avatar { - id: delegateAvatar - address: modelData.core.peerAddress - Layout.preferredWidth: 45 * DefaultStyle.dp - Layout.preferredHeight: 45 * DefaultStyle.dp + RowLayout { + id: delegateContent + anchors.fill: parent + anchors.leftMargin: 10 * DefaultStyle.dp + anchors.rightMargin: 10 * DefaultStyle.dp + Avatar { + id: delegateAvatar + address: modelData.core.peerAddress + Layout.preferredWidth: 45 * DefaultStyle.dp + Layout.preferredHeight: 45 * DefaultStyle.dp + } + Text { + id: delegateName + property var remoteAddress: UtilsCpp.getDisplayName(modelData.core.peerAddress) + text: remoteAddress ? remoteAddress.value : "" + Connections { + target: modelData.core + } + } + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + Text { + id: callStateText + text: modelData.core.state === LinphoneEnums.CallState.Paused + || modelData.core.state === LinphoneEnums.CallState.PausedByRemote + ? qsTr("Appel en pause") : qsTr("Appel en cours") + } + PopupButton { + id: listCallOptionsButton + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + Layout.alignment: Qt.AlignRight + + popup.contentItem: ColumnLayout { + spacing: 0 + Control.Button { + background: Item {} + contentItem: RowLayout { + Image { + source: modelData.core.state === LinphoneEnums.CallState.Paused + || modelData.core.state === LinphoneEnums.CallState.PausedByRemote + ? AppIcons.phone : AppIcons.pause + sourceSize.width: 32 * DefaultStyle.dp + sourceSize.height: 32 * DefaultStyle.dp + Layout.preferredWidth: 32 * DefaultStyle.dp + Layout.preferredHeight: 32 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + } + Text { + text: modelData.core.state === LinphoneEnums.CallState.Paused + || modelData.core.state === LinphoneEnums.CallState.PausedByRemote + ? qsTr("Reprendre l'appel") : qsTr("Mettre en pause") + color: DefaultStyle.main2_500main + Layout.preferredWidth: metrics.width + } + TextMetrics { + id: metrics + text: qsTr("Reprendre l'appel") + } + Item { + Layout.fillWidth: true + } + } + onClicked: modelData.core.lSetPaused(!modelData.core.paused) + } + Control.Button { + background: Item {} + contentItem: RowLayout { + EffectImage { + source: AppIcons.endCall + colorizationColor: DefaultStyle.danger_500main + width: 32 * DefaultStyle.dp + height: 32 * DefaultStyle.dp + } + Text { + color: DefaultStyle.danger_500main + text: qsTr("Terminer l'appel") + } + Item { + Layout.fillWidth: true + } + } + onClicked: mainWindow.endCall(modelData) + } + } + } + } + + // MouseArea{ + // anchors.fill: delegateLayout + // onClicked: { + // callsModel.currentCall = modelData + // } + // } } - Text { - id: delegateName - property var remoteAddress: UtilsCpp.getDisplayName(modelData.core.peerAddress) - text: remoteAddress ? remoteAddress.value : "" - Connections { - target: modelData.core + } + } + Item { + Layout.fillHeight: true + } + } + } + Component { + id: settingsPanel + ColumnLayout { + RoundedBackgroundControl { + Layout.alignment: Qt.AlignHCenter + Control.StackView.onActivated: { + rightPanelTitle.text = qsTr("Paramètres") + } + backgroundColor: DefaultStyle.main2_0 + height: contentItem.implicitHeight + topPadding + bottomPadding + Layout.fillWidth: true + topPadding: 25 * DefaultStyle.dp + bottomPadding: 25 * DefaultStyle.dp + leftPadding: 25 * DefaultStyle.dp + rightPadding: 25 * DefaultStyle.dp + contentItem: ColumnLayout { + spacing: 10 * DefaultStyle.dp + + RowLayout { + Layout.fillWidth: true + EffectImage { + source: AppIcons.speaker + colorizationColor: DefaultStyle.main1_500_main + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + imageWidth: 24 * DefaultStyle.dp + imageHeight: 24 * DefaultStyle.dp + } + Text { + text: qsTr("Haut-parleurs") + Layout.fillWidth: true } } - Item { - Layout.fillHeight: true + ComboBox { Layout.fillWidth: true + Layout.preferredWidth: parent.width + model: SettingsCpp.outputAudioDevicesList + onCurrentTextChanged: { + mainWindow.call.core.lSetOutputAudioDevice(currentText) + } } - Text { - id: callStateText - text: mainWindow.call.core.state === LinphoneEnums.CallState.Paused - || mainWindow.call.core.state === LinphoneEnums.CallState.PausedByRemote - ? qsTr("Appel en pause") : qsTr("Appel en cours") + Slider { + id: speakerVolume + Layout.fillWidth: true + from: 0.0 + to: 1.0 + value: mainWindow.call && mainWindow.call.core.speakerVolumeGain + onMoved: { + mainWindow.call.core.lSetSpeakerVolumeGain(value) + } } - PopupButton { - id: listCallOptionsButton - Layout.preferredWidth: 24 * DefaultStyle.dp - Layout.preferredHeight: 24 * DefaultStyle.dp - Layout.alignment: Qt.AlignRight + RowLayout { + Layout.fillWidth: true + EffectImage { + source: AppIcons.microphone + colorizationColor: DefaultStyle.main1_500_main + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + imageWidth: 24 * DefaultStyle.dp + imageHeight: 24 * DefaultStyle.dp + } + Text { + text: qsTr("Microphone") + Layout.fillWidth: true + } + } + ComboBox { + Layout.fillWidth: true + Layout.preferredWidth: parent.width + model: SettingsCpp.inputAudioDevicesList + onCurrentTextChanged: { + mainWindow.call.core.lSetInputAudioDevice(currentText) + } + } + Slider { + id: microVolume + Layout.fillWidth: true + from: 0.0 + to: 1.0 + value: mainWindow.call && mainWindow.call.core.microphoneVolumeGain + onMoved: { + mainWindow.call.core.lSetMicrophoneVolumeGain(value) + } + } + Timer { + interval: 50 + repeat: true + running: mainWindow.call || false + onTriggered: audioTestSlider.value = (mainWindow.call && mainWindow.call.core.microVolume) + } + Slider { + id: audioTestSlider + Layout.fillWidth: true + enabled: false + Layout.preferredHeight: 10 * DefaultStyle.dp - popup.contentItem: ColumnLayout { - spacing: 0 - Button { - leftPadding: 0 - topPadding: 5 * DefaultStyle.dp - bottomPadding: 5 * DefaultStyle.dp - background: Item {} - contentItem: RowLayout { - Image { - source: modelData.core.paused - ? AppIcons.phone : AppIcons.pause - sourceSize.width: 32 * DefaultStyle.dp - sourceSize.height: 32 * DefaultStyle.dp - Layout.preferredWidth: 32 * DefaultStyle.dp - Layout.preferredHeight: 32 * DefaultStyle.dp - fillMode: Image.PreserveAspectFit - } - Text { - text: modelData.core.paused - ? qsTr("Reprendre l'appel") : qsTr("Mettre en pause") - color: DefaultStyle.main2_500main - Layout.preferredWidth: metrics.width - } - TextMetrics { - id: metrics - text: qsTr("Reprendre l'appel") - } - Item { - Layout.fillWidth: true - } - } - onClicked: modelData.core.lSetPaused(!modelData.core.paused) - } - Button { - leftPadding: 0 - topPadding: 5 * DefaultStyle.dp - bottomPadding: 5 * DefaultStyle.dp - background: Item {} - contentItem: RowLayout { - EffectImage { - source: AppIcons.endCall - colorizationColor: DefaultStyle.danger_500main - width: 32 * DefaultStyle.dp - height: 32 * DefaultStyle.dp - } - Text { - color: DefaultStyle.danger_500main - text: qsTr("Terminer l'appel") - } - Item { - Layout.fillWidth: true - } - } - onClicked: { - mainWindow.endCall(modelData) - mainWindow.callTerminatedByUser = true + background: Rectangle { + x: audioTestSlider.leftPadding + y: audioTestSlider.topPadding + audioTestSlider.availableHeight / 2 - height / 2 + implicitWidth: 200 * DefaultStyle.dp + implicitHeight: 10 * DefaultStyle.dp + width: audioTestSlider.availableWidth + height: implicitHeight + radius: 2 * DefaultStyle.dp + color: "#D9D9D9" + + Rectangle { + width: audioTestSlider.visualPosition * parent.width + height: parent.height + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "#6FF88D" } + GradientStop { position: 1.0; color: "#00D916" } } + radius: 2 * DefaultStyle.dp } } + handle: Item {visible: false} + } + RowLayout { + Layout.fillWidth: true + EffectImage { + source: AppIcons.videoCamera + colorizationColor: DefaultStyle.main1_500_main + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + imageWidth: 24 * DefaultStyle.dp + imageHeight: 24 * DefaultStyle.dp + } + Text { + text: qsTr("Caméra") + Layout.fillWidth: true + } + } + ComboBox { + Layout.fillWidth: true + Layout.preferredWidth: parent.width + model: SettingsCpp.videoDevicesList + onCurrentTextChanged: { + SettingsCpp.lSetVideoDevice(currentText) + } } } - - // MouseArea{ - // anchors.fill: delegateLayout - // onClicked: { - // callsModel.currentCall = modelData - // } - // } + } + Item { + Layout.fillHeight: true } } } @@ -845,9 +990,7 @@ Window { Button { id: callListButton Layout.fillWidth: true - background: Item { - visible: false - } + background: Item {} contentItem: RowLayout { Image { Layout.preferredWidth: 24 * DefaultStyle.dp @@ -868,9 +1011,7 @@ Window { Button { id: dialerButton Layout.fillWidth: true - background: Item { - visible: false - } + background: Item {} contentItem: RowLayout { Image { Layout.preferredWidth: 24 * DefaultStyle.dp @@ -893,9 +1034,7 @@ Window { Layout.fillWidth: true enabled: mainWindow.call.core.recordable checkable: true - background: Item { - visible: false - } + background: Item {} contentItem: RowLayout { EffectImage { Layout.preferredWidth: 24 * DefaultStyle.dp @@ -914,29 +1053,47 @@ Window { mainWindow.call.core.recording ? mainWindow.call.core.lStopRecording() : mainWindow.call.core.lStartRecording() } } - Button { + Control.Button { id: speakerButton Layout.fillWidth: true checkable: true - background: Item { - visible: false - } + background: Item {} contentItem: RowLayout { EffectImage { Layout.preferredWidth: 24 * DefaultStyle.dp Layout.preferredHeight: 24 * DefaultStyle.dp fillMode: Image.PreserveAspectFit - source: mainWindow.call.core.speakerMuted ? AppIcons.speakerSlash : AppIcons.speaker - colorizationColor: mainWindow.call.core.speakerMuted ? DefaultStyle.danger_500main : undefined + source: AppIcons.recordFill + colorizationColor: mainWindow.call.core.recording ? DefaultStyle.danger_500main : undefined } Text { - text: mainWindow.call.core.speakerMuted ? qsTr("Activer le son") : qsTr("Désactiver le son") - color: mainWindow.call.core.speakerMuted ? DefaultStyle.danger_500main : DefaultStyle.main2_600 + color: mainWindow.call.core.recording ? DefaultStyle.danger_500main : DefaultStyle.main2_600 + text: mainWindow.call.core.recording ? qsTr("Terminer l'enregistrement") : qsTr("Enregistrer l'appel") } } onClicked: { - mainWindow.call.core.lSetSpeakerMuted(!mainWindow.call.core.speakerMuted) + mainWindow.call.core.recording ? mainWindow.call.core.lStopRecording() : mainWindow.call.core.lStartRecording() + } + } + Control.Button { + id: settingsButton + Layout.fillWidth: true + background: Item{} + contentItem: RowLayout { + Image { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + source: AppIcons.settings + } + Text { + text: qsTr("Paramètres") + } + } + onClicked: { + rightPanel.visible = true + rightPanel.replace(settingsPanel) + moreOptionsButton.close() } } } diff --git a/Linphone/view/App/Main.qml b/Linphone/view/App/Main.qml index 74e9cd41..e1912d4c 100644 --- a/Linphone/view/App/Main.qml +++ b/Linphone/view/App/Main.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts 1.3 import QtQuick.Controls import Linphone import UtilsCpp 1.0 +import SettingsCpp 1.0 Window { id: mainWindow @@ -35,14 +36,14 @@ Window { StackView { id: mainWindowStackView anchors.fill: parent - initialItem: accountProxy.haveAccount ? mainPage : UtilsCpp.getFirstLaunch() ? welcomePage : loginPage + initialItem: accountProxy.haveAccount ? mainPage : SettingsCpp.getFirstLaunch() ? welcomePage : loginPage } Component { id: welcomePage WelcomePage { onStartButtonPressed: { mainWindowStackView.replace(loginPage)// Replacing the first item will destroy the old. - UtilsCpp.setFirstLaunch(false) + SettingsCpp.setFirstLaunch(false) } } } diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index b693f480..de7cd270 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -44,6 +44,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/RectangleTest.qml view/Item/RoundedBackgroundControl.qml view/Item/SearchBar.qml + view/Item/Slider.qml view/Item/TabBar.qml view/Item/Text.qml view/Item/TextInput.qml diff --git a/Linphone/view/Item/Carousel.qml b/Linphone/view/Item/Carousel.qml index 7acf8da4..6c2ac0a3 100644 --- a/Linphone/view/Item/Carousel.qml +++ b/Linphone/view/Item/Carousel.qml @@ -70,7 +70,7 @@ ColumnLayout { radius: 30 * DefaultStyle.dp color: DefaultStyle.main1_500_main z: 1 - x: carouselButton.itemAt(mainItem.currentIndex).x + x: mainItem.currentIndex >= 0 && carouselButton.itemAt(mainItem.currentIndex) ? carouselButton.itemAt(mainItem.currentIndex).x : 0 Behavior on x { NumberAnimation {duration: 100}} } RowLayout { diff --git a/Linphone/view/Item/ComboBox.qml b/Linphone/view/Item/ComboBox.qml index 6522f912..4c457c01 100644 --- a/Linphone/view/Item/ComboBox.qml +++ b/Linphone/view/Item/ComboBox.qml @@ -10,6 +10,7 @@ ColumnLayout { // 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 @@ -54,6 +55,7 @@ ColumnLayout { 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 @@ -67,13 +69,13 @@ ColumnLayout { Component.onCompleted: { var index = combobox.currentIndex < 0 ? 0 : combobox.currentIndex - if (mainItem.model[index].img) { - selectedItemImg.source = mainItem.model[0].img + if (mainItem.model[index] && mainItem.model[index].img) { + selectedItemImg.source = mainItem.model[index].img } - if (mainItem.model[index].text) - selectedItemText.text = mainItem.model[0].text + if (mainItem.model[index] && mainItem.model[index].text) + selectedItemText.text = mainItem.model[index].text else if (mainItem.model[index]) - selectedItemText.text = mainItem.model[0] + selectedItemText.text = mainItem.model[index] } } @@ -132,6 +134,7 @@ ColumnLayout { ? modelData : "" elide: Text.ElideRight + maximumLineCount: 1 font { pixelSize: 14 * DefaultStyle.dp weight: 400 * DefaultStyle.dp diff --git a/Linphone/view/Item/EffectImage.qml b/Linphone/view/Item/EffectImage.qml index 325e315d..2033517f 100644 --- a/Linphone/view/Item/EffectImage.qml +++ b/Linphone/view/Item/EffectImage.qml @@ -20,7 +20,7 @@ Loader { Image { id: image visible: !effect2.enabled - source: mainItem.source + source: mainItem.source ? mainItem.source : "" fillMode: mainItem.fillMode sourceSize.width: width sourceSize.height: height diff --git a/Linphone/view/Item/MovableMouseArea.qml b/Linphone/view/Item/MovableMouseArea.qml index a2fcee57..630a8cca 100644 --- a/Linphone/view/Item/MovableMouseArea.qml +++ b/Linphone/view/Item/MovableMouseArea.qml @@ -29,7 +29,6 @@ MouseArea{ preventStealing: true propagateComposedEvents: true hoverEnabled: true - onMScaleChanged: updateScale() function updateScale(){// Avoid scaling if leading outside movableArea. drag.target.height = Math.max(0, Math.min(movableArea.height, heightOrigin * mScale)) @@ -42,6 +41,7 @@ MouseArea{ drag.target.x = Math.max(parentTLBounds.x + margin, Math.min(parentBRBounds.x - drag.target.width - margin, drag.target.x + x - margin)) drag.target.y = Math.max(parentTLBounds.y + margin, Math.min(parentBRBounds.y - drag.target.height - margin, drag.target.y + y - margin)) } + onMScaleChanged: updateScale() onPositionChanged: (mouse) => { if(dragging){ updatePosition(mouse.x - xClicked, mouse.y - yClicked) diff --git a/Linphone/view/Item/PhoneNumberComboBox.qml b/Linphone/view/Item/PhoneNumberComboBox.qml index ad9bb452..aa3aefa5 100644 --- a/Linphone/view/Item/PhoneNumberComboBox.qml +++ b/Linphone/view/Item/PhoneNumberComboBox.qml @@ -116,7 +116,7 @@ ColumnLayout { width: listView.width height: listView.height color: DefaultStyle.main2_300 - radius: 15 * DefaultStyle.dp + // radius: 15 * DefaultStyle.dp y: listView.currentItem? listView.currentItem.y : 0 } diff --git a/Linphone/view/Item/PopupButton.qml b/Linphone/view/Item/PopupButton.qml index d3b61705..cc370398 100644 --- a/Linphone/view/Item/PopupButton.qml +++ b/Linphone/view/Item/PopupButton.qml @@ -36,6 +36,7 @@ Button { closePolicy: Popup.CloseOnPressOutsideParent |Popup.CloseOnPressOutside onAboutToShow: { + if (mainApplicationWindow == undefined) return; var coord = mapToGlobal(mainItem.x, mainItem.y) if (coord.y + popup.height >= mainApplicationWindow.height) { y = -popup.height diff --git a/Linphone/view/Item/RoundedBackgroundControl.qml b/Linphone/view/Item/RoundedBackgroundControl.qml index 83f774ff..a4810465 100644 --- a/Linphone/view/Item/RoundedBackgroundControl.qml +++ b/Linphone/view/Item/RoundedBackgroundControl.qml @@ -3,17 +3,17 @@ import QtQuick.Controls as Control import Linphone Control.Control { - width: 360 * DefaultStyle.dp - property color backgroundColor - topPadding: 10 * DefaultStyle.dp - bottomPadding: 10 * DefaultStyle.dp - leftPadding: 10 * DefaultStyle.dp - rightPadding: 10 * DefaultStyle.dp + id: mainItem + // width: 360 * DefaultStyle.dp + property color backgroundColor: DefaultStyle.grey_0 + padding: 10 * DefaultStyle.dp background: Rectangle { - // anchors.fill: parent - width: parent.width - height: parent.height + anchors.fill: parent radius: 15 * DefaultStyle.dp - color: mainItem.backgroundColor ? mainItem.backgroundColor : DefaultStyle.grey_0 + color: mainItem.backgroundColor + anchors.leftMargin: 10 * DefaultStyle.dp + anchors.rightMargin: 10 * DefaultStyle.dp + anchors.topMargin: 10 * DefaultStyle.dp + anchors.bottomMargin: 10 * DefaultStyle.dp } } \ No newline at end of file diff --git a/Linphone/view/Item/Slider.qml b/Linphone/view/Item/Slider.qml new file mode 100644 index 00000000..c97ceb0c --- /dev/null +++ b/Linphone/view/Item/Slider.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone + +Control.Slider { + id: mainItem + + background: Rectangle { + x: mainItem.leftPadding + y: mainItem.topPadding + mainItem.availableHeight / 2 - height / 2 + implicitWidth: 200 * DefaultStyle.dp + implicitHeight: 4 * DefaultStyle.dp + width: mainItem.availableWidth + height: implicitHeight + radius: 30 * DefaultStyle.dp + // TODO : change the colors when mockup indicates their names + color: "#D9D9D9" + + Rectangle { + width: mainItem.visualPosition * parent.width + height: parent.height + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "#FF9E79" } + GradientStop { position: 1.0; color: "#FE5E00" } + } + radius: 40 * DefaultStyle.dp + } + } + + handle: Item { + x: mainItem.leftPadding + mainItem.visualPosition * (mainItem.availableWidth - width) + y: mainItem.topPadding + mainItem.availableHeight / 2 - height / 2 + implicitWidth: 16 * DefaultStyle.dp + implicitHeight: 16 * DefaultStyle.dp + Rectangle { + id: handleRect + anchors.fill: parent + radius: 30 * DefaultStyle.dp + color: DefaultStyle.grey_0 + } + MultiEffect { + source: handleRect + anchors.fill: handleRect + shadowEnabled: true + shadowColor: DefaultStyle.grey_1000 + shadowBlur: 1 + shadowOpacity: 0.1 + } + } +} \ No newline at end of file