mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 11:28:07 +00:00
Replace postAsync functions by SafeConnection in order to make more robust thread connections and guarantee to have always caller/callee while processing signals.
Introduce ObjectCore, ObjectGui, ObjectModel. Simulate addAccount signals on adding account (in waiting for SDK callback).
This commit is contained in:
parent
3fad8ee49e
commit
cd82964b23
44 changed files with 1202 additions and 316 deletions
|
|
@ -30,8 +30,10 @@
|
|||
#include <QQmlFileSelector>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/account/Account.hpp"
|
||||
#include "core/account/AccountCore.hpp"
|
||||
#include "core/account/AccountProxy.hpp"
|
||||
#include "core/call/CallCore.hpp"
|
||||
#include "core/call/CallGui.hpp"
|
||||
#include "core/logger/QtLogger.hpp"
|
||||
#include "core/login/LoginPage.hpp"
|
||||
#include "core/notifier/Notifier.hpp"
|
||||
|
|
@ -128,8 +130,10 @@ void App::initCppInterfaces() {
|
|||
|
||||
qmlRegisterUncreatableType<PhoneNumber>(Constants::MainQmlUri, 1, 0, "PhoneNumber", QLatin1String("Uncreatable"));
|
||||
qmlRegisterType<AccountProxy>(Constants::MainQmlUri, 1, 0, "AccountProxy");
|
||||
qmlRegisterUncreatableType<Account>(Constants::MainQmlUri, 1, 0, "Account", QLatin1String("Uncreatable"));
|
||||
qmlRegisterUncreatableType<Call>(Constants::MainQmlUri, 1, 0, "Call", QLatin1String("Uncreatable"));
|
||||
qmlRegisterType<AccountGui>(Constants::MainQmlUri, 1, 0, "AccountGui");
|
||||
qmlRegisterUncreatableType<AccountCore>(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable"));
|
||||
qmlRegisterType<CallGui>(Constants::MainQmlUri, 1, 0, "CallGui");
|
||||
qmlRegisterUncreatableType<CallCore>(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable"));
|
||||
|
||||
LinphoneEnums::registerMetaTypes();
|
||||
}
|
||||
|
|
@ -167,3 +171,15 @@ void App::createCommandParser() {
|
|||
{"qt-logs-only", tr("commandLineOptionQtLogsOnly")},
|
||||
});
|
||||
}
|
||||
|
||||
bool App::notify(QObject *receiver, QEvent *event) {
|
||||
bool done = true;
|
||||
try {
|
||||
done = QApplication::notify(receiver, event);
|
||||
} catch (const std::exception &ex) {
|
||||
qCritical() << "[App] Exception has been catch in notify";
|
||||
} catch (...) {
|
||||
qCritical() << "[App] Generic exeption has been catch in notify";
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ public:
|
|||
void onLoggerInitialized();
|
||||
|
||||
QQmlApplicationEngine *mEngine = nullptr;
|
||||
bool notify(QObject *receiver, QEvent *event);
|
||||
|
||||
private:
|
||||
void createCommandParser();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
list(APPEND _LINPHONEAPP_SOURCES
|
||||
core/account/Account.cpp
|
||||
core/account/AccountCore.cpp
|
||||
core/account/AccountGui.cpp
|
||||
core/account/AccountList.cpp
|
||||
core/account/AccountProxy.cpp
|
||||
core/App.cpp
|
||||
core/call/Call.cpp
|
||||
core/call/CallCore.cpp
|
||||
core/call/CallGui.cpp
|
||||
core/logger/QtLogger.cpp
|
||||
core/login/LoginPage.cpp
|
||||
core/notifier/Notifier.cpp
|
||||
|
|
|
|||
|
|
@ -1,82 +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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Account.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(Account)
|
||||
|
||||
Account::Account(const std::shared_ptr<linphone::Account> &account) : QObject(nullptr) {
|
||||
// Should be call from model Thread
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
auto address = account->getContactAddress();
|
||||
mContactAddress = address ? Utils::coreStringToAppString(account->getContactAddress()->asString()) : "";
|
||||
auto params = account->getParams();
|
||||
auto identityAddress = params->getIdentityAddress();
|
||||
mIdentityAddress = identityAddress ? Utils::coreStringToAppString(identityAddress->asString()) : "";
|
||||
mPictureUri = Utils::coreStringToAppString(params->getPictureUri());
|
||||
mRegistrationState = LinphoneEnums::fromLinphone(account->getState());
|
||||
|
||||
mAccountModel = Utils::makeQObject_ptr<AccountModel>(account); // OK
|
||||
connect(mAccountModel.get(), &AccountModel::registrationStateChanged, this, &Account::onRegistrationStateChanged);
|
||||
connect(this, &Account::requestSetPictureUri, mAccountModel.get(), &AccountModel::setPictureUri,
|
||||
Qt::QueuedConnection);
|
||||
connect(mAccountModel.get(), &AccountModel::pictureUriChanged, this, &Account::onPictureUriChanged,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
Account::~Account() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
emit mAccountModel->removeListener();
|
||||
}
|
||||
|
||||
QString Account::getContactAddress() const {
|
||||
return mContactAddress;
|
||||
}
|
||||
|
||||
QString Account::getIdentityAddress() const {
|
||||
return mIdentityAddress;
|
||||
}
|
||||
|
||||
QString Account::getPictureUri() const {
|
||||
return mPictureUri;
|
||||
}
|
||||
|
||||
LinphoneEnums::RegistrationState Account::getRegistrationState() const {
|
||||
return mRegistrationState;
|
||||
}
|
||||
|
||||
void Account::onRegistrationStateChanged(const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
const std::string &message) {
|
||||
mRegistrationState = LinphoneEnums::fromLinphone(state);
|
||||
emit registrationStateChanged(Utils::coreStringToAppString(message));
|
||||
}
|
||||
|
||||
void Account::setPictureUri(const QString &uri) {
|
||||
if (uri != mPictureUri) {
|
||||
emit requestSetPictureUri(Utils::appStringToCoreString(uri));
|
||||
}
|
||||
}
|
||||
|
||||
void Account::onPictureUriChanged(std::string uri) {
|
||||
mPictureUri = Utils::coreStringToAppString(uri);
|
||||
emit pictureUriChanged();
|
||||
}
|
||||
126
Linphone/core/account/AccountCore.cpp
Normal file
126
Linphone/core/account/AccountCore.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AccountCore.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "model/core/CoreModel.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(AccountCore)
|
||||
|
||||
QSharedPointer<AccountCore> AccountCore::create(const std::shared_ptr<linphone::Account> &account) {
|
||||
auto model = QSharedPointer<AccountCore>(new AccountCore(account), &QObject::deleteLater);
|
||||
model->moveToThread(App::getInstance()->thread());
|
||||
model->setSelf(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
AccountCore::AccountCore(const std::shared_ptr<linphone::Account> &account) : QObject(nullptr) {
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
// Should be call from model Thread
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
// Init data
|
||||
auto address = account->getContactAddress();
|
||||
mContactAddress = address ? Utils::coreStringToAppString(account->getContactAddress()->asString()) : "";
|
||||
auto params = account->getParams();
|
||||
auto identityAddress = params->getIdentityAddress();
|
||||
mIdentityAddress = identityAddress ? Utils::coreStringToAppString(identityAddress->asString()) : "";
|
||||
mPictureUri = Utils::coreStringToAppString(params->getPictureUri());
|
||||
mRegistrationState = LinphoneEnums::fromLinphone(account->getState());
|
||||
mIsDefaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount() == account;
|
||||
|
||||
// Add listener
|
||||
mAccountModel = Utils::makeQObject_ptr<AccountModel>(account); // OK
|
||||
mAccountModel->setSelf(mAccountModel);
|
||||
}
|
||||
|
||||
AccountCore::~AccountCore() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
emit mAccountModel->removeListener();
|
||||
}
|
||||
|
||||
void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
|
||||
mAccountModelConnection = QSharedPointer<SafeConnection>(
|
||||
new SafeConnection(me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(mAccountModel)));
|
||||
mAccountModelConnection->makeConnect(mAccountModel.get(), &AccountModel::registrationStateChanged,
|
||||
[this](const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state, const std::string &message) {
|
||||
mAccountModelConnection->invokeToCore([this, account, state, message]() {
|
||||
this->onRegistrationStateChanged(account, state, message);
|
||||
});
|
||||
});
|
||||
// From Model
|
||||
mAccountModelConnection->makeConnect(
|
||||
mAccountModel.get(), &AccountModel::defaultAccountChanged, [this](bool isDefault) {
|
||||
mAccountModelConnection->invokeToCore([this, isDefault]() { this->onDefaultAccountChanged(isDefault); });
|
||||
});
|
||||
|
||||
mAccountModelConnection->makeConnect(mAccountModel.get(), &AccountModel::pictureUriChanged, [this](QString uri) {
|
||||
mAccountModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); });
|
||||
});
|
||||
|
||||
// From GUI
|
||||
mAccountModelConnection->makeConnect(this, &AccountCore::lSetPictureUri, [this](QString uri) {
|
||||
mAccountModelConnection->invokeToModel([this, uri]() { mAccountModel->setPictureUri(uri); });
|
||||
});
|
||||
mAccountModelConnection->makeConnect(this, &AccountCore::lSetDefaultAccount, [this]() {
|
||||
mAccountModelConnection->invokeToModel([this]() { mAccountModel->setDefault(); });
|
||||
});
|
||||
}
|
||||
|
||||
QString AccountCore::getContactAddress() const {
|
||||
return mContactAddress;
|
||||
}
|
||||
|
||||
QString AccountCore::getIdentityAddress() const {
|
||||
return mIdentityAddress;
|
||||
}
|
||||
|
||||
QString AccountCore::getPictureUri() const {
|
||||
return mPictureUri;
|
||||
}
|
||||
|
||||
LinphoneEnums::RegistrationState AccountCore::getRegistrationState() const {
|
||||
return mRegistrationState;
|
||||
}
|
||||
|
||||
bool AccountCore::getIsDefaultAccount() const {
|
||||
return mIsDefaultAccount;
|
||||
}
|
||||
|
||||
void AccountCore::onRegistrationStateChanged(const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
const std::string &message) {
|
||||
mRegistrationState = LinphoneEnums::fromLinphone(state);
|
||||
emit registrationStateChanged(Utils::coreStringToAppString(message));
|
||||
}
|
||||
|
||||
void AccountCore::onDefaultAccountChanged(bool isDefault) {
|
||||
if (mIsDefaultAccount != isDefault) {
|
||||
mIsDefaultAccount = isDefault;
|
||||
emit defaultAccountChanged(mIsDefaultAccount);
|
||||
}
|
||||
}
|
||||
|
||||
void AccountCore::onPictureUriChanged(QString uri) {
|
||||
mPictureUri = uri;
|
||||
emit pictureUriChanged();
|
||||
}
|
||||
|
|
@ -18,54 +18,63 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACCOUNT_H_
|
||||
#define ACCOUNT_H_
|
||||
#ifndef ACCOUNT_CORE_H_
|
||||
#define ACCOUNT_CORE_H_
|
||||
|
||||
#include "model/account/AccountModel.hpp"
|
||||
#include "tool/LinphoneEnums.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
class Account : public QObject, public AbstractObject {
|
||||
class AccountCore : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString contactAddress READ getContactAddress CONSTANT)
|
||||
Q_PROPERTY(QString identityAddress READ getIdentityAddress CONSTANT)
|
||||
Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
|
||||
Q_PROPERTY(QString pictureUri READ getPictureUri WRITE lSetPictureUri NOTIFY pictureUriChanged)
|
||||
Q_PROPERTY(
|
||||
LinphoneEnums::RegistrationState registrationState READ getRegistrationState NOTIFY registrationStateChanged)
|
||||
Q_PROPERTY(bool isDefaultAccount READ getIsDefaultAccount NOTIFY defaultAccountChanged)
|
||||
|
||||
public:
|
||||
static QSharedPointer<AccountCore> create(const std::shared_ptr<linphone::Account> &account);
|
||||
// Should be call from model Thread. Will be automatically in App thread after initialization
|
||||
Account(const std::shared_ptr<linphone::Account> &account);
|
||||
~Account();
|
||||
AccountCore(const std::shared_ptr<linphone::Account> &account);
|
||||
~AccountCore();
|
||||
void setSelf(QSharedPointer<AccountCore> me);
|
||||
|
||||
QString getContactAddress() const;
|
||||
QString getIdentityAddress() const;
|
||||
QString getPictureUri() const;
|
||||
LinphoneEnums::RegistrationState getRegistrationState() const;
|
||||
bool getIsDefaultAccount() const;
|
||||
|
||||
void setPictureUri(const QString &uri);
|
||||
|
||||
void onPictureUriChanged(std::string uri);
|
||||
void onPictureUriChanged(QString uri);
|
||||
void onRegistrationStateChanged(const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
const std::string &message);
|
||||
|
||||
void onDefaultAccountChanged(bool isDefault);
|
||||
|
||||
signals:
|
||||
void pictureUriChanged();
|
||||
void registrationStateChanged(const QString &message);
|
||||
void defaultAccountChanged(bool isDefault);
|
||||
|
||||
// Account requests
|
||||
void requestSetPictureUri(std::string pictureUri);
|
||||
void lSetPictureUri(QString pictureUri);
|
||||
void lSetDefaultAccount();
|
||||
|
||||
private:
|
||||
QString mContactAddress;
|
||||
QString mIdentityAddress;
|
||||
QString mPictureUri;
|
||||
bool mIsDefaultAccount = false;
|
||||
LinphoneEnums::RegistrationState mRegistrationState;
|
||||
std::shared_ptr<AccountModel> mAccountModel;
|
||||
QSharedPointer<SafeConnection> mAccountModelConnection;
|
||||
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
40
Linphone/core/account/AccountGui.cpp
Normal file
40
Linphone/core/account/AccountGui.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AccountGui.hpp"
|
||||
#include "core/App.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(AccountGui)
|
||||
|
||||
AccountGui::AccountGui(QSharedPointer<AccountCore> core) {
|
||||
mustBeInMainThread(getClassName());
|
||||
qDebug() << "[AccountGui] new" << this;
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
|
||||
mCore = core;
|
||||
}
|
||||
|
||||
AccountGui::~AccountGui() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
qDebug() << "[AccountGui] delete" << this;
|
||||
}
|
||||
|
||||
AccountCore *AccountGui::getCore() const {
|
||||
return mCore.get();
|
||||
}
|
||||
41
Linphone/core/account/AccountGui.hpp
Normal file
41
Linphone/core/account/AccountGui.hpp
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACCOUNT_GUI_H_
|
||||
#define ACCOUNT_GUI_H_
|
||||
|
||||
#include "AccountCore.hpp"
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class AccountGui : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(AccountCore *core READ getCore CONSTANT)
|
||||
|
||||
public:
|
||||
AccountGui(QSharedPointer<AccountCore> core);
|
||||
~AccountGui();
|
||||
AccountCore *getCore() const;
|
||||
QSharedPointer<AccountCore> mCore;
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -19,7 +19,8 @@
|
|||
*/
|
||||
|
||||
#include "AccountList.hpp"
|
||||
#include "Account.hpp"
|
||||
#include "AccountCore.hpp"
|
||||
#include "AccountGui.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include <QSharedPointer>
|
||||
#include <linphone++/linphone.hh>
|
||||
|
|
@ -28,26 +29,80 @@
|
|||
|
||||
DEFINE_ABSTRACT_OBJECT(AccountList)
|
||||
|
||||
QSharedPointer<AccountList> AccountList::create() {
|
||||
auto model = QSharedPointer<AccountList>(new AccountList(), &QObject::deleteLater);
|
||||
model->moveToThread(App::getInstance()->thread());
|
||||
model->setSelf(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
AccountList::AccountList(QObject *parent) : ListProxy(parent) {
|
||||
qDebug() << "[AccountList] new" << this;
|
||||
mustBeInMainThread(getClassName());
|
||||
App::postModelAsync([=]() {
|
||||
QList<QSharedPointer<Account>> accounts;
|
||||
// Model thread.
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
auto linphoneAccounts = CoreModel::getInstance()->getCore()->getAccountList();
|
||||
for (auto it : linphoneAccounts) {
|
||||
auto model = QSharedPointer<Account>(new Account(it), &QObject::deleteLater);
|
||||
model->moveToThread(this->thread());
|
||||
accounts.push_back(model);
|
||||
}
|
||||
// Invoke for adding stuffs in caller thread
|
||||
QMetaObject::invokeMethod(this, [this, accounts]() {
|
||||
mustBeInMainThread(getClassName());
|
||||
add(accounts);
|
||||
});
|
||||
});
|
||||
connect(CoreModel::getInstance().get(), &CoreModel::accountAdded, this, &AccountList::lUpdate);
|
||||
}
|
||||
|
||||
AccountList::~AccountList() {
|
||||
qDebug() << "[AccountList] delete" << this;
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
if (mModelConnection) mModelConnection->deleteLater();
|
||||
}
|
||||
|
||||
void AccountList::setSelf(QSharedPointer<AccountList> me) {
|
||||
mModelConnection = QSharedPointer<SafeConnection>(
|
||||
new SafeConnection(me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(CoreModel::getInstance())),
|
||||
&QObject::deleteLater);
|
||||
mModelConnection->makeConnect(this, &AccountList::lUpdate, [this]() {
|
||||
mModelConnection->invokeToModel([this]() {
|
||||
QList<QSharedPointer<AccountCore>> accounts;
|
||||
// Model thread.
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
auto linphoneAccounts = CoreModel::getInstance()->getCore()->getAccountList();
|
||||
for (auto it : linphoneAccounts) {
|
||||
auto model = AccountCore::create(it);
|
||||
accounts.push_back(model);
|
||||
}
|
||||
mModelConnection->invokeToCore([this, accounts]() {
|
||||
mustBeInMainThread(getClassName());
|
||||
clearData();
|
||||
add(accounts);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
lUpdate();
|
||||
}
|
||||
|
||||
AccountGui *AccountList::getDefaultAccount() const {
|
||||
for (auto it : mList) {
|
||||
auto account = it.objectCast<AccountCore>();
|
||||
if (account->getIsDefaultAccount()) return new AccountGui(account);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
/*
|
||||
void AccountList::update() {
|
||||
App::postModelAsync([=]() {
|
||||
QList<QSharedPointer<AccountCore>> accounts;
|
||||
// Model thread.
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
auto linphoneAccounts = CoreModel::getInstance()->getCore()->getAccountList();
|
||||
for (auto it : linphoneAccounts) {
|
||||
auto model = AccountCore::create(it);
|
||||
accounts.push_back(model);
|
||||
}
|
||||
// Invoke for adding stuffs in caller thread
|
||||
QMetaObject::invokeMethod(this, [this, accounts]() {
|
||||
mustBeInMainThread(getClassName());
|
||||
clearData();
|
||||
add(accounts);
|
||||
});
|
||||
});
|
||||
}
|
||||
*/
|
||||
QVariant AccountList::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 AccountGui(mList[row].objectCast<AccountCore>()));
|
||||
return QVariant();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,15 +23,28 @@
|
|||
|
||||
#include "../proxy/ListProxy.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
#include <QLocale>
|
||||
|
||||
class AccountGui;
|
||||
// =============================================================================
|
||||
|
||||
class AccountList : public ListProxy, public AbstractObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static QSharedPointer<AccountList> create();
|
||||
AccountList(QObject *parent = Q_NULLPTR);
|
||||
~AccountList();
|
||||
|
||||
void setSelf(QSharedPointer<AccountList> me);
|
||||
|
||||
AccountGui *getDefaultAccount() const;
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
signals:
|
||||
void lUpdate();
|
||||
|
||||
private:
|
||||
QSharedPointer<SafeConnection> mModelConnection;
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,15 +19,19 @@
|
|||
*/
|
||||
|
||||
#include "AccountProxy.hpp"
|
||||
#include "Account.hpp"
|
||||
#include "AccountGui.hpp"
|
||||
#include "AccountList.hpp"
|
||||
|
||||
AccountProxy::AccountProxy(QObject *parent) : SortFilterProxy(parent) {
|
||||
setSourceModel(new AccountList(this));
|
||||
qDebug() << "[AccountProxy] new" << this;
|
||||
mList = AccountList::create();
|
||||
connect(mList.get(), &AccountList::countChanged, this, &AccountProxy::defaultAccountChanged);
|
||||
setSourceModel(mList.get());
|
||||
sort(0);
|
||||
}
|
||||
|
||||
AccountProxy::~AccountProxy() {
|
||||
qDebug() << "[AccountProxy] delete" << this;
|
||||
}
|
||||
|
||||
QString AccountProxy::getFilterText() const {
|
||||
|
|
@ -42,6 +46,13 @@ void AccountProxy::setFilterText(const QString &filter) {
|
|||
}
|
||||
}
|
||||
|
||||
AccountGui *AccountProxy::getDefaultAccount() const {
|
||||
return dynamic_cast<AccountList *>(sourceModel())->getDefaultAccount();
|
||||
}
|
||||
|
||||
void AccountProxy::setDefaultAccount(AccountGui *account) {
|
||||
}
|
||||
|
||||
bool AccountProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
|
||||
bool show = (mFilterText.isEmpty() || mFilterText == "*");
|
||||
if (!show) {
|
||||
|
|
@ -50,8 +61,8 @@ bool AccountProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourcePare
|
|||
QRegularExpression::UseUnicodePropertiesOption);
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
auto model = sourceModel()->data(index);
|
||||
auto account = model.value<Account *>();
|
||||
show = account->getIdentityAddress().contains(search);
|
||||
auto account = model.value<AccountGui *>();
|
||||
show = account->getCore()->getIdentityAddress().contains(search);
|
||||
}
|
||||
|
||||
return show;
|
||||
|
|
@ -61,5 +72,6 @@ bool AccountProxy::lessThan(const QModelIndex &left, const QModelIndex &right) c
|
|||
auto l = sourceModel()->data(left);
|
||||
auto r = sourceModel()->data(right);
|
||||
|
||||
return l.value<Account *>()->getIdentityAddress() < r.value<Account *>()->getIdentityAddress();
|
||||
return l.value<AccountGui *>()->getCore()->getIdentityAddress() <
|
||||
r.value<AccountGui *>()->getCore()->getIdentityAddress();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
#define ACCOUNT_PROXY_H_
|
||||
|
||||
#include "../proxy/SortFilterProxy.hpp"
|
||||
#include "core/account/AccountGui.hpp"
|
||||
#include "core/account/AccountList.hpp"
|
||||
|
||||
// =============================================================================
|
||||
|
||||
|
|
@ -29,6 +31,7 @@ class AccountProxy : public SortFilterProxy {
|
|||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString filterText READ getFilterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
Q_PROPERTY(AccountGui *defaultAccount READ getDefaultAccount WRITE setDefaultAccount NOTIFY defaultAccountChanged)
|
||||
|
||||
public:
|
||||
AccountProxy(QObject *parent = Q_NULLPTR);
|
||||
|
|
@ -37,14 +40,19 @@ public:
|
|||
QString getFilterText() const;
|
||||
void setFilterText(const QString &filter);
|
||||
|
||||
AccountGui *getDefaultAccount() const;
|
||||
void setDefaultAccount(AccountGui *account);
|
||||
|
||||
signals:
|
||||
void filterTextChanged();
|
||||
void defaultAccountChanged();
|
||||
|
||||
protected:
|
||||
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
QString mFilterText;
|
||||
QSharedPointer<AccountList> mList;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,81 +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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Call.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(Call)
|
||||
|
||||
Call::Call(const std::shared_ptr<linphone::Call> &call) : QObject(nullptr) {
|
||||
// Should be call from model Thread
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
mCallModel = Utils::makeQObject_ptr<CallModel>(call);
|
||||
connect(mCallModel.get(), &CallModel::stateChanged, this, &Call::onStateChanged);
|
||||
connect(this, &Call::lAccept, mCallModel.get(), &CallModel::accept);
|
||||
connect(this, &Call::lDecline, mCallModel.get(), &CallModel::decline);
|
||||
connect(this, &Call::lTerminate, mCallModel.get(), &CallModel::terminate);
|
||||
mCallModel->setSelf(mCallModel);
|
||||
mState = LinphoneEnums::fromLinphone(call->getState());
|
||||
}
|
||||
|
||||
Call::~Call() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
emit mCallModel->removeListener();
|
||||
}
|
||||
|
||||
LinphoneEnums::CallStatus Call::getStatus() const {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
void Call::setStatus(LinphoneEnums::CallStatus status) {
|
||||
mustBeInMainThread(log().arg(Q_FUNC_INFO));
|
||||
if (mStatus != status) {
|
||||
mStatus = status;
|
||||
emit statusChanged(mStatus);
|
||||
}
|
||||
}
|
||||
|
||||
LinphoneEnums::CallState Call::getState() const {
|
||||
return mState;
|
||||
}
|
||||
|
||||
void Call::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);
|
||||
}
|
||||
}
|
||||
|
||||
void Call::onStateChanged(linphone::Call::State state, const std::string &message) {
|
||||
setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message));
|
||||
}
|
||||
|
||||
QString Call::getLastErrorMessage() const {
|
||||
return mLastErrorMessage;
|
||||
}
|
||||
void Call::setLastErrorMessage(const QString &message) {
|
||||
if (mLastErrorMessage != message) {
|
||||
mLastErrorMessage = message;
|
||||
emit lastErrorMessageChanged();
|
||||
}
|
||||
}
|
||||
132
Linphone/core/call/CallCore.cpp
Normal file
132
Linphone/core/call/CallCore.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CallCore.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "model/object/VariantObject.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(CallCore)
|
||||
|
||||
QSharedPointer<CallCore> CallCore::create(const std::shared_ptr<linphone::Call> &call) {
|
||||
auto sharedPointer = QSharedPointer<CallCore>(new CallCore(call), &QObject::deleteLater);
|
||||
sharedPointer->setSelf(sharedPointer);
|
||||
sharedPointer->moveToThread(App::getInstance()->thread());
|
||||
return sharedPointer;
|
||||
}
|
||||
|
||||
CallCore::CallCore(const std::shared_ptr<linphone::Call> &call) : QObject(nullptr) {
|
||||
qDebug() << "[CallCore] new" << this;
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
// Should be call from model Thread
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
mDuration = call->getDuration();
|
||||
mMicrophoneMuted = call->getMicrophoneMuted();
|
||||
mCallModel = Utils::makeQObject_ptr<CallModel>(call);
|
||||
connect(mCallModel.get(), &CallModel::stateChanged, this, &CallCore::onStateChanged);
|
||||
connect(this, &CallCore::lAccept, mCallModel.get(), &CallModel::accept);
|
||||
connect(this, &CallCore::lDecline, mCallModel.get(), &CallModel::decline);
|
||||
connect(this, &CallCore::lTerminate, mCallModel.get(), &CallModel::terminate);
|
||||
mCallModel->setSelf(mCallModel);
|
||||
mState = LinphoneEnums::fromLinphone(call->getState());
|
||||
}
|
||||
|
||||
CallCore::~CallCore() {
|
||||
qDebug() << "[CallCore] delete" << this;
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
emit mCallModel->removeListener();
|
||||
}
|
||||
|
||||
void CallCore::setSelf(QSharedPointer<CallCore> me) {
|
||||
mAccountModelConnection = QSharedPointer<SafeConnection>(
|
||||
new SafeConnection(me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(mCallModel)),
|
||||
&QObject::deleteLater);
|
||||
mAccountModelConnection->makeConnect(this, &CallCore::lSetMicrophoneMuted, [this](bool isMuted) {
|
||||
mAccountModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); });
|
||||
});
|
||||
mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::microphoneMutedChanged, [this](bool isMuted) {
|
||||
mAccountModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(isMuted); });
|
||||
});
|
||||
mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::durationChanged, [this](int duration) {
|
||||
mAccountModelConnection->invokeToCore([this, duration]() { setDuration(duration); });
|
||||
});
|
||||
}
|
||||
|
||||
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::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);
|
||||
}
|
||||
}
|
||||
|
||||
void CallCore::onStateChanged(linphone::Call::State state, const std::string &message) {
|
||||
setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message));
|
||||
}
|
||||
|
||||
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::getMicrophoneMuted() const {
|
||||
return mMicrophoneMuted;
|
||||
}
|
||||
|
||||
void CallCore::setMicrophoneMuted(bool isMuted) {
|
||||
if (mMicrophoneMuted != isMuted) {
|
||||
mMicrophoneMuted = isMuted;
|
||||
emit microphoneMutedChanged();
|
||||
}
|
||||
}
|
||||
|
|
@ -18,8 +18,8 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CALL_H_
|
||||
#define CALL_H_
|
||||
#ifndef CALL_CORE_H_
|
||||
#define CALL_CORE_H_
|
||||
|
||||
#include "model/call/CallModel.hpp"
|
||||
#include "tool/LinphoneEnums.hpp"
|
||||
|
|
@ -27,17 +27,23 @@
|
|||
#include <QSharedPointer>
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
class Call : public QObject, public AbstractObject {
|
||||
class SafeConnection;
|
||||
|
||||
class CallCore : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(LinphoneEnums::CallStatus status READ getStatus NOTIFY statusChanged)
|
||||
Q_PROPERTY(LinphoneEnums::CallState state READ getState NOTIFY stateChanged)
|
||||
Q_PROPERTY(QString lastErrorMessage READ getLastErrorMessage NOTIFY lastErrorMessageChanged)
|
||||
Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged);
|
||||
Q_PROPERTY(bool microphoneMuted READ getMicrophoneMuted WRITE lSetMicrophoneMuted NOTIFY microphoneMutedChanged)
|
||||
|
||||
public:
|
||||
// Should be call from model Thread. Will be automatically in App thread after initialization
|
||||
Call(const std::shared_ptr<linphone::Call> &call);
|
||||
~Call();
|
||||
static QSharedPointer<CallCore> create(const std::shared_ptr<linphone::Call> &call);
|
||||
CallCore(const std::shared_ptr<linphone::Call> &call);
|
||||
~CallCore();
|
||||
void setSelf(QSharedPointer<CallCore> me);
|
||||
|
||||
LinphoneEnums::CallStatus getStatus() const;
|
||||
void setStatus(LinphoneEnums::CallStatus status);
|
||||
|
|
@ -49,43 +55,56 @@ public:
|
|||
QString getLastErrorMessage() const;
|
||||
void setLastErrorMessage(const QString &message);
|
||||
|
||||
int getDuration();
|
||||
void setDuration(int duration);
|
||||
|
||||
bool getMicrophoneMuted() const;
|
||||
void setMicrophoneMuted(bool isMuted);
|
||||
|
||||
signals:
|
||||
void statusChanged(LinphoneEnums::CallStatus status);
|
||||
void stateChanged(LinphoneEnums::CallState state);
|
||||
void lastErrorMessageChanged();
|
||||
void durationChanged(int duration);
|
||||
void microphoneMutedChanged();
|
||||
|
||||
// Linphone commands
|
||||
void lAccept(bool withVideo); // Accept an incoming call
|
||||
void lDecline(); // Decline an incoming call
|
||||
void lTerminate(); // Hangup a call
|
||||
/* 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 startRecording();
|
||||
Q_INVOKABLE void stopRecording();
|
||||
|
||||
Q_INVOKABLE void sendDtmf(const QString &dtmf);
|
||||
Q_INVOKABLE void verifyAuthenticationToken(bool verify);
|
||||
Q_INVOKABLE void updateStreams();
|
||||
Q_INVOKABLE void toggleSpeakerMute();
|
||||
*/
|
||||
void lSetMicrophoneMuted(bool isMuted);
|
||||
|
||||
/* 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 startRecording();
|
||||
Q_INVOKABLE void stopRecording();
|
||||
|
||||
Q_INVOKABLE void sendDtmf(const QString &dtmf);
|
||||
Q_INVOKABLE void verifyAuthenticationToken(bool verify);
|
||||
Q_INVOKABLE void updateStreams();
|
||||
Q_INVOKABLE void toggleSpeakerMute();
|
||||
*/
|
||||
private:
|
||||
std::shared_ptr<CallModel> mCallModel;
|
||||
LinphoneEnums::CallStatus mStatus;
|
||||
LinphoneEnums::CallState mState;
|
||||
QString mLastErrorMessage;
|
||||
int mDuration = 0;
|
||||
bool mMicrophoneMuted;
|
||||
QSharedPointer<SafeConnection> mAccountModelConnection;
|
||||
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
Q_DECLARE_METATYPE(Call *)
|
||||
Q_DECLARE_METATYPE(CallCore *)
|
||||
#endif
|
||||
40
Linphone/core/call/CallGui.cpp
Normal file
40
Linphone/core/call/CallGui.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CallGui.hpp"
|
||||
#include "core/App.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(CallGui)
|
||||
|
||||
CallGui::CallGui(QSharedPointer<CallCore> core) {
|
||||
mustBeInMainThread(getClassName());
|
||||
qDebug() << "[CallGui] new" << this;
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
|
||||
mCore = core;
|
||||
}
|
||||
|
||||
CallGui::~CallGui() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
qDebug() << "[CallGui] delete" << this;
|
||||
}
|
||||
|
||||
CallCore *CallGui::getCore() const {
|
||||
return mCore.get();
|
||||
}
|
||||
41
Linphone/core/call/CallGui.hpp
Normal file
41
Linphone/core/call/CallGui.hpp
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CALL_GUI_H_
|
||||
#define CALL_GUI_H_
|
||||
|
||||
#include "CallCore.hpp"
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class CallGui : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(CallCore *core READ getCore CONSTANT)
|
||||
|
||||
public:
|
||||
CallGui(QSharedPointer<CallCore> core);
|
||||
~CallGui();
|
||||
CallCore *getCore() const;
|
||||
QSharedPointer<CallCore> mCore;
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
#include "Notifier.hpp"
|
||||
|
||||
#include "core/App.hpp"
|
||||
#include "core/call/Call.hpp"
|
||||
#include "core/call/CallGui.hpp"
|
||||
#include "tool/LinphoneEnums.hpp"
|
||||
#include "tool/providers/ImageProvider.hpp"
|
||||
|
||||
|
|
@ -274,20 +274,23 @@ void Notifier::deleteNotification(QVariant notification) {
|
|||
|
||||
void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
auto model = new Call(call);
|
||||
model->moveToThread(this->thread());
|
||||
App::postCoreAsync([this, model]() {
|
||||
auto model = CallCore::create(call);
|
||||
auto gui = new CallGui(model);
|
||||
gui->moveToThread(App::getInstance()->thread());
|
||||
App::postCoreAsync([this, gui]() {
|
||||
mustBeInMainThread(getClassName());
|
||||
QVariantMap map;
|
||||
map["call"].setValue(model);
|
||||
|
||||
map["call"].setValue(gui);
|
||||
CREATE_NOTIFICATION(Notifier::ReceivedCall, map)
|
||||
|
||||
QObject::connect(
|
||||
model, &Call::statusChanged, notification, [this, notification](LinphoneEnums::CallStatus status) {
|
||||
gui->getCore(), &CallCore::statusChanged, notification,
|
||||
[this, notification](LinphoneEnums::CallStatus status) {
|
||||
qInfo() << log().arg("Delete notification on call status : %1").arg(LinphoneEnums::toString(status));
|
||||
deleteNotification(QVariant::fromValue(notification));
|
||||
});
|
||||
QObject::connect(model, &Call::destroyed, notification,
|
||||
QObject::connect(gui->getCore(), &CallCore::destroyed, notification,
|
||||
[this, notification]() { deleteNotification(QVariant::fromValue(notification)); });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "core/call/Call.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <linphone++/linphone.hh>
|
||||
// =============================================================================
|
||||
|
||||
class QMutex;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
model/logger/LoggerModel.cpp
|
||||
model/logger/LoggerListener.cpp
|
||||
|
||||
model/object/SafeObject.cpp
|
||||
model/object/VariantObject.cpp
|
||||
|
||||
model/setting/SettingsModel.cpp
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ bool AccountManager::login(QString username, QString password) {
|
|||
connect(mAccountModel.get(), &AccountModel::registrationStateChanged, this,
|
||||
&AccountManager::onRegistrationStateChanged);
|
||||
core->addAccount(account);
|
||||
emit CoreModel::getInstance()->accountAdded();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,16 @@
|
|||
|
||||
#include <QDebug>
|
||||
|
||||
#include "model/core/CoreModel.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(AccountModel)
|
||||
|
||||
AccountModel::AccountModel(const std::shared_ptr<linphone::Account> &account, QObject *parent)
|
||||
: ::Listener<linphone::Account, linphone::AccountListener>(account, parent) {
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
connect(CoreModel::getInstance().get(), &CoreModel::defaultAccountChanged, this,
|
||||
&AccountModel::onDefaultAccountChanged);
|
||||
}
|
||||
|
||||
AccountModel::~AccountModel() {
|
||||
|
|
@ -39,11 +44,22 @@ void AccountModel::onRegistrationStateChanged(const std::shared_ptr<linphone::Ac
|
|||
emit registrationStateChanged(account, state, message);
|
||||
}
|
||||
|
||||
void AccountModel::setPictureUri(std::string uri) {
|
||||
void AccountModel::setPictureUri(QString uri) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
auto account = std::dynamic_pointer_cast<linphone::Account>(mMonitor);
|
||||
auto params = account->getParams()->clone();
|
||||
params->setPictureUri(uri);
|
||||
params->setPictureUri(Utils::appStringToCoreString(uri));
|
||||
account->setParams(params);
|
||||
emit pictureUriChanged(uri);
|
||||
}
|
||||
|
||||
void AccountModel::onDefaultAccountChanged() {
|
||||
emit defaultAccountChanged(CoreModel::getInstance()->getCore()->getDefaultAccount() == mMonitor);
|
||||
}
|
||||
|
||||
void AccountModel::setDefault() {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
auto core = CoreModel::getInstance()->getCore();
|
||||
core->setDefaultAccount(mMonitor);
|
||||
emit CoreModel::getInstance()->defaultAccountChanged();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,15 +38,18 @@ public:
|
|||
virtual void onRegistrationStateChanged(const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
const std::string &message) override;
|
||||
void onDefaultAccountChanged();
|
||||
|
||||
void setPictureUri(std::string uri);
|
||||
void setPictureUri(QString uri);
|
||||
void setDefault();
|
||||
|
||||
signals:
|
||||
void registrationStateChanged(const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
const std::string &message);
|
||||
void defaultAccountChanged(bool isDefault);
|
||||
|
||||
void pictureUriChanged(std::string uri);
|
||||
void pictureUriChanged(QString uri);
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ DEFINE_ABSTRACT_OBJECT(CallModel)
|
|||
CallModel::CallModel(const std::shared_ptr<linphone::Call> &call, QObject *parent)
|
||||
: ::Listener<linphone::Call, linphone::CallListener>(call, parent) {
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
mDurationTimer.setInterval(1000);
|
||||
mDurationTimer.setSingleShot(false);
|
||||
connect(&mDurationTimer, &QTimer::timeout, this, [this]() { this->durationChanged(mMonitor->getDuration()); });
|
||||
mDurationTimer.start();
|
||||
}
|
||||
|
||||
CallModel::~CallModel() {
|
||||
|
|
@ -65,6 +69,11 @@ void CallModel::terminate() {
|
|||
mMonitor->terminate();
|
||||
}
|
||||
|
||||
void CallModel::setMicrophoneMuted(bool isMuted) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
mMonitor->setMicrophoneMuted(isMuted);
|
||||
emit microphoneMutedChanged(isMuted);
|
||||
}
|
||||
void CallModel::onDtmfReceived(const std::shared_ptr<linphone::Call> &call, int dtmf) {
|
||||
emit dtmfReceived(call, dtmf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "tool/AbstractObject.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
class CallModel : public ::Listener<linphone::Call, linphone::CallListener>,
|
||||
|
|
@ -39,7 +40,14 @@ public:
|
|||
void decline();
|
||||
void terminate();
|
||||
|
||||
void setMicrophoneMuted(bool isMuted);
|
||||
|
||||
signals:
|
||||
void microphoneMutedChanged(bool isMuted);
|
||||
void durationChanged(int);
|
||||
|
||||
private:
|
||||
QTimer mDurationTimer;
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -139,6 +139,10 @@ void CoreModel::setPathAfterStart() {
|
|||
|
||||
//---------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void CoreModel::onAccountAdded() {
|
||||
emit accountAdded();
|
||||
}
|
||||
|
||||
void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
|
||||
const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public:
|
|||
|
||||
signals:
|
||||
void loggerInitialized();
|
||||
void defaultAccountChanged();
|
||||
|
||||
private:
|
||||
QString mConfigPath;
|
||||
|
|
@ -71,6 +72,7 @@ private:
|
|||
//--------------------------------------------------------------------------------
|
||||
// LINPHONE
|
||||
//--------------------------------------------------------------------------------
|
||||
virtual void onAccountAdded(); // override (Not yet implemented by SDK)
|
||||
virtual void onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
|
||||
const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
|
|
@ -152,6 +154,7 @@ private:
|
|||
const std::string &url) override;
|
||||
|
||||
signals:
|
||||
void accountAdded();
|
||||
void accountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
|
||||
const std::shared_ptr<linphone::Account> &account,
|
||||
linphone::RegistrationState state,
|
||||
|
|
|
|||
46
Linphone/model/object/SafeObject.cpp
Normal file
46
Linphone/model/object/SafeObject.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SafeObject.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTest>
|
||||
|
||||
#include "core/App.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(SafeObject)
|
||||
|
||||
SafeObject::SafeObject(QObject *parent) {
|
||||
}
|
||||
SafeObject::SafeObject(QVariant defaultValue, QObject *parent) : mValue(defaultValue) {
|
||||
}
|
||||
SafeObject::~SafeObject() {
|
||||
}
|
||||
|
||||
QVariant SafeObject::getValue() const {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
void SafeObject::onSetValue(QVariant value) {
|
||||
if (value != mValue) {
|
||||
mValue = value;
|
||||
emit valueChanged(mValue);
|
||||
}
|
||||
}
|
||||
49
Linphone/model/object/SafeObject.hpp
Normal file
49
Linphone/model/object/SafeObject.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SAFE_OBJECT_H_
|
||||
#define SAFE_OBJECT_H_
|
||||
|
||||
#include "tool/AbstractObject.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
class SafeObject : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SafeObject(QObject *parent = nullptr);
|
||||
SafeObject(QVariant defaultValue, QObject *parent = nullptr);
|
||||
~SafeObject();
|
||||
|
||||
QVariant getValue() const;
|
||||
void onSetValue(QVariant value);
|
||||
signals:
|
||||
void requestValue();
|
||||
void setValue(QVariant value);
|
||||
void valueChanged(QVariant value);
|
||||
|
||||
private:
|
||||
QVariant mValue;
|
||||
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -26,39 +26,29 @@
|
|||
#include "core/App.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(VariantObject)
|
||||
|
||||
VariantObject::VariantObject(QObject *parent) {
|
||||
mThreadLocation = false;
|
||||
mustBeInMainThread(getClassName());
|
||||
mCoreObject = nullptr;
|
||||
VariantObject::VariantObject(QObject *parent) : VariantObject(QVariant()) {
|
||||
}
|
||||
VariantObject::VariantObject(QVariant defaultValue, QObject *parent) {
|
||||
mCoreObject = QSharedPointer<SafeObject>::create(defaultValue);
|
||||
mModelObject = QSharedPointer<SafeObject>::create();
|
||||
mModelObject->moveToThread(CoreModel::getInstance()->thread());
|
||||
|
||||
VariantObject::VariantObject(QVariant value, QObject *parent) : mValue(value) {
|
||||
mThreadLocation = true;
|
||||
mustBeInMainThread(getClassName());
|
||||
connect(this, &VariantObject::valueUpdated, this, &VariantObject::setValue);
|
||||
mCoreObject = new VariantObject(nullptr);
|
||||
mCoreObject->moveToThread(CoreModel::getInstance()->thread());
|
||||
connect(mCoreObject, &VariantObject::valueChanged, this, &VariantObject::setValue);
|
||||
connect(mCoreObject, &VariantObject::valueChanged, mCoreObject, &QObject::deleteLater);
|
||||
mConnection = QSharedPointer<SafeConnection>(
|
||||
new SafeConnection(mCoreObject.objectCast<QObject>(), mModelObject.objectCast<QObject>()),
|
||||
&QObject::deleteLater);
|
||||
|
||||
mConnection->makeConnect(mCoreObject.get(), &SafeObject::setValue, &SafeObject::onSetValue);
|
||||
mConnection->makeConnect(mModelObject.get(), &SafeObject::setValue, &SafeObject::onSetValue);
|
||||
connect(mCoreObject.get(), &SafeObject::valueChanged, this, &VariantObject::valueChanged);
|
||||
}
|
||||
|
||||
VariantObject::~VariantObject() {
|
||||
if (mThreadLocation) mustBeInMainThread("~" + getClassName());
|
||||
else mustBeInLinphoneThread("~" + getClassName());
|
||||
}
|
||||
|
||||
QVariant VariantObject::getValue() const {
|
||||
if (mThreadLocation) mustBeInMainThread(QString(gClassName) + " : " + Q_FUNC_INFO);
|
||||
else mustBeInLinphoneThread(QString(gClassName) + " : " + Q_FUNC_INFO);
|
||||
return mValue;
|
||||
return mCoreObject->getValue();
|
||||
}
|
||||
|
||||
void VariantObject::setValue(QVariant value) {
|
||||
if (mThreadLocation) mustBeInMainThread(QString(gClassName) + " : " + Q_FUNC_INFO);
|
||||
else mustBeInLinphoneThread(QString(gClassName) + " : " + Q_FUNC_INFO);
|
||||
if (value != mValue) {
|
||||
mValue = value;
|
||||
emit valueChanged(mValue);
|
||||
}
|
||||
void VariantObject::requestValue() {
|
||||
emit mCoreObject->requestValue();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,35 +24,41 @@
|
|||
#include "tool/AbstractObject.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariant>
|
||||
|
||||
// Store the VariantObject on a propery and use value.
|
||||
// Do not use direcly teh value like VariantObject.value : in this case if value change, VariantObject will be
|
||||
// reevaluated.
|
||||
#include "SafeObject.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
|
||||
class VariantObject : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariant value READ getValue NOTIFY valueChanged)
|
||||
public:
|
||||
Q_PROPERTY(QVariant value READ getValue WRITE setValue NOTIFY valueChanged)
|
||||
|
||||
VariantObject(QObject *parent = nullptr);
|
||||
VariantObject(QVariant value, QObject *parent = nullptr);
|
||||
VariantObject(QVariant defaultValue, QObject *parent = nullptr);
|
||||
~VariantObject();
|
||||
|
||||
template <typename Func, typename... Args>
|
||||
void makeRequest(Func &&callable, Args &&...args) {
|
||||
mConnection->makeConnect(mCoreObject.get(), &SafeObject::requestValue, [this, callable, args...]() {
|
||||
mConnection->invokeToModel([this, callable, args...]() { mModelObject->setValue(callable(args...)); });
|
||||
});
|
||||
}
|
||||
template <typename SenderClass>
|
||||
void makeUpdate(const typename QtPrivate::FunctionPointer<SenderClass>::Object *sender, SenderClass signal) {
|
||||
mConnection->makeConnect(sender, signal,
|
||||
[this]() { mConnection->invokeToCore([this]() { mCoreObject->requestValue(); }); });
|
||||
}
|
||||
|
||||
QVariant getValue() const;
|
||||
void setValue(QVariant value);
|
||||
|
||||
// mCoreObject must be used to request update value : this object will be not be deleted by GUI so it is safe to use
|
||||
// inside model thread. call emit updateValue() from coreObject to set value from model.
|
||||
VariantObject *mCoreObject; // Ensure to use DeleteLater() after updating value
|
||||
void requestValue();
|
||||
|
||||
QSharedPointer<SafeObject> mCoreObject, mModelObject;
|
||||
QSharedPointer<SafeConnection> mConnection;
|
||||
signals:
|
||||
void valueChanged(QVariant value);
|
||||
void valueUpdated(QVariant value);
|
||||
|
||||
private:
|
||||
QVariant mValue;
|
||||
bool mThreadLocation = true; // true=Core, false=Model
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ QString ToolModel::getDisplayName(QString address) {
|
|||
return displayName.isEmpty() ? address : displayName;
|
||||
}
|
||||
|
||||
Call *ToolModel::startAudioCall(const QString &sipAddress,
|
||||
const QString &prepareTransfertAddress,
|
||||
const QHash<QString, QString> &headers) {
|
||||
QSharedPointer<CallCore> ToolModel::startAudioCall(const QString &sipAddress,
|
||||
const QString &prepareTransfertAddress,
|
||||
const QHash<QString, QString> &headers) {
|
||||
bool waitRegistrationForCall = true; // getSettingsModel()->getWaitRegistrationForCall()
|
||||
std::shared_ptr<linphone::Core> core = CoreModel::getInstance()->getCore();
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ Call *ToolModel::startAudioCall(const QString &sipAddress,
|
|||
if (core->getDefaultAccount()) params->setAccount(core->getDefaultAccount());
|
||||
// CallModel::setRecordFile(params, Utils::coreStringToAppString(address->getUsername()));
|
||||
auto call = core->inviteAddressWithParams(address, params);
|
||||
return call ? new Call(call) : nullptr;
|
||||
return call ? CallCore::create(call) : nullptr;
|
||||
|
||||
/* TODO transfer
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
#ifndef TOOL_MODEL_H_
|
||||
#define TOOL_MODEL_H_
|
||||
|
||||
#include "core/call/Call.hpp"
|
||||
#include "core/call/CallCore.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
|
||||
#include <QHash>
|
||||
|
|
@ -39,9 +39,9 @@ public:
|
|||
static QString getDisplayName(const std::shared_ptr<const linphone::Address> &address);
|
||||
static QString getDisplayName(QString address);
|
||||
|
||||
static Call *startAudioCall(const QString &sipAddress,
|
||||
const QString &prepareTransfertAddress = "",
|
||||
const QHash<QString, QString> &headers = {});
|
||||
static QSharedPointer<CallCore> startAudioCall(const QString &sipAddress,
|
||||
const QString &prepareTransfertAddress = "",
|
||||
const QHash<QString, QString> &headers = {});
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
tool/Utils.cpp
|
||||
|
||||
tool/LinphoneEnums.cpp
|
||||
tool/thread/SafeSharedPointer.hpp
|
||||
tool/thread/SafeConnection.cpp
|
||||
tool/thread/Thread.cpp
|
||||
tool/providers/ImageProvider.cpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
#include "Utils.hpp"
|
||||
|
||||
#include "core/App.hpp"
|
||||
#include "model/call/CallModel.hpp"
|
||||
#include "core/call/CallGui.hpp"
|
||||
#include "model/object/VariantObject.hpp"
|
||||
#include "model/tool/ToolModel.hpp"
|
||||
|
||||
|
|
@ -42,10 +42,11 @@ char *Utils::rstrstr(const char *a, const char *b) {
|
|||
|
||||
VariantObject *Utils::getDisplayName(const QString &address) {
|
||||
VariantObject *data = new VariantObject(address); // Scope : GUI
|
||||
App::postModelAsync([coreObject = data->mCoreObject, address]() mutable {
|
||||
data->makeRequest([address]() {
|
||||
QString displayName = ToolModel::getDisplayName(address);
|
||||
coreObject->setValue(displayName);
|
||||
return displayName;
|
||||
});
|
||||
data->requestValue();
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -53,16 +54,27 @@ VariantObject *Utils::startAudioCall(const QString &sipAddress,
|
|||
const QString &prepareTransfertAddress,
|
||||
const QHash<QString, QString> &headers) {
|
||||
VariantObject *data = new VariantObject(QVariant()); // Scope : GUI
|
||||
qDebug() << "Calling " << sipAddress;
|
||||
App::postModelAsync([coreObject = data->mCoreObject, sipAddress, prepareTransfertAddress, headers]() mutable {
|
||||
|
||||
data->makeRequest([sipAddress, prepareTransfertAddress, headers]() {
|
||||
auto call = ToolModel::startAudioCall(sipAddress, prepareTransfertAddress, headers);
|
||||
if (call && coreObject) {
|
||||
call->moveToThread(App::getInstance()->thread());
|
||||
coreObject->setValue(QVariant::fromValue(call));
|
||||
// App::postCoreAsync([data, call]() { data->setValue(QVariant::fromValue(call)); });
|
||||
// emit coreObject->valueChanged(call);
|
||||
}
|
||||
if (call) {
|
||||
return QVariant::fromValue(new CallGui(call));
|
||||
} else return QVariant();
|
||||
});
|
||||
data->requestValue();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
VariantObject *Utils::haveAccount() {
|
||||
VariantObject *result = new VariantObject();
|
||||
|
||||
// Using connect ensure to have sender() and receiver() alive.
|
||||
result->makeRequest([]() {
|
||||
// Model
|
||||
return CoreModel::getInstance()->getCore()->getAccountList().size() > 0;
|
||||
});
|
||||
result->makeUpdate(CoreModel::getInstance().get(), &CoreModel::accountAdded);
|
||||
result->requestValue();
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public:
|
|||
Q_INVOKABLE static VariantObject *startAudioCall(const QString &sipAddress,
|
||||
const QString &prepareTransfertAddress = "",
|
||||
const QHash<QString, QString> &headers = {});
|
||||
Q_INVOKABLE static VariantObject *haveAccount();
|
||||
|
||||
static inline QString coreStringToAppString(const std::string &str) {
|
||||
if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(str);
|
||||
|
|
|
|||
29
Linphone/tool/thread/SafeConnection.cpp
Normal file
29
Linphone/tool/thread/SafeConnection.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SafeConnection.hpp"
|
||||
|
||||
SafeConnection::SafeConnection(SafeSharedPointer<QObject> core, SafeSharedPointer<QObject> model)
|
||||
: mCore(core), mModel(model) {
|
||||
}
|
||||
SafeConnection::~SafeConnection() {
|
||||
if (mCore.mCountRef != 0 || mModel.mCountRef != 0)
|
||||
qCritical() << "[SafeConnection] Destruction while still having references";
|
||||
}
|
||||
117
Linphone/tool/thread/SafeConnection.hpp
Normal file
117
Linphone/tool/thread/SafeConnection.hpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SAFE_CONNECTION_H_
|
||||
#define SAFE_CONNECTION_H_
|
||||
|
||||
#include "SafeSharedPointer.hpp"
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
// Use this class to protect sender/receiver from being deleted while running a
|
||||
// signal/call
|
||||
|
||||
/*
|
||||
* ObjectGui : mainAcces for GUI. Its memory is managed by JavaScript. It contains a QSharedPointer of ObjectCore.
|
||||
* ObjectCore : memory is CPP managed by QSharedPointer.
|
||||
* ObjectModel: memory is managed by shared pointers and is running on Model thread.
|
||||
*
|
||||
* => ObjectGUI have QSharedPointer<ObjectCore>
|
||||
* ObjectCore have std::shared_ptr<ObjectModel> and SafeConnection.
|
||||
*
|
||||
* Need:
|
||||
* - static QSharedPointer<ObjectCore> create(Args...args); => It set self and moveToThread.
|
||||
* - In GUI constructor : App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
|
||||
* - In Core/model : App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
* - void setSelf(QSharedPointer<ObjectCore> me); => instantiate SafeConnection with :
|
||||
* me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(<Model_stored_in_ObjectCore>));
|
||||
*
|
||||
* Set connections in setSelf:
|
||||
* - From model
|
||||
* mSafeConnection->makeConnect( mModel.get()
|
||||
* , &ObjectModel::signal, [this](params) {
|
||||
* mSafeConnection->invokeToCore([this, params]() { this->slot(params); });
|
||||
* });
|
||||
*
|
||||
*- From GUI
|
||||
* mSafeConnection->makeConnect(this, &ObjectCore::lSignal, [this](params) {
|
||||
* mSafeConnection->invokeToModel([this, params]() { mModel->slot(params); });
|
||||
* });
|
||||
*
|
||||
* - Direct call can be call with only invokeToModel/invokeToCore
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class SafeConnection : public QObject {
|
||||
public:
|
||||
SafeConnection(SafeSharedPointer<QObject> a, SafeSharedPointer<QObject> b);
|
||||
~SafeConnection();
|
||||
SafeSharedPointer<QObject> mCore, mModel;
|
||||
QMutex mLocker;
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
static inline QMetaObject::Connection makeConnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,
|
||||
Func1 signal,
|
||||
Func2 slot,
|
||||
Qt::ConnectionType type = Qt::AutoConnection) {
|
||||
return connect(sender, signal, sender, slot, type);
|
||||
}
|
||||
|
||||
template <typename Func, typename... Args>
|
||||
void invokeToModel(Func &&callable, Args &&...args) {
|
||||
if (!tryLock()) return;
|
||||
auto model = mModel.get();
|
||||
QMetaObject::invokeMethod(model, [&, model, callable, args...]() { // Is async
|
||||
QMetaObject::invokeMethod(model, callable, args...); // Is Sync
|
||||
unlock();
|
||||
});
|
||||
}
|
||||
|
||||
// Will running call in Core
|
||||
template <typename Func, typename... Args>
|
||||
void invokeToCore(Func &&callable, Args &&...args) {
|
||||
if (!tryLock()) return;
|
||||
QMetaObject::invokeMethod(mCore.get(), [&, callable, args...]() { // Is async
|
||||
QMetaObject::invokeMethod(mCore.get(), callable, args...); // Is Sync
|
||||
unlock();
|
||||
});
|
||||
}
|
||||
|
||||
bool tryLock() {
|
||||
mLocker.lock();
|
||||
if (!mCore.lock() || !mModel.lock()) { // Direct locking
|
||||
mCore.reset();
|
||||
mModel.reset();
|
||||
mLocker.unlock();
|
||||
return false;
|
||||
}
|
||||
mLocker.unlock();
|
||||
return true;
|
||||
}
|
||||
void unlock() {
|
||||
mLocker.lock();
|
||||
mCore.unlock();
|
||||
mModel.unlock();
|
||||
mLocker.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
78
Linphone/tool/thread/SafeSharedPointer.hpp
Normal file
78
Linphone/tool/thread/SafeSharedPointer.hpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SAFE_SHARED_POINTER_H_
|
||||
#define SAFE_SHARED_POINTER_H_
|
||||
|
||||
#include <QDebug>
|
||||
#include <QSharedPointer>
|
||||
|
||||
// Store a Qt/Std shared pointer
|
||||
template <typename A>
|
||||
class SafeSharedPointer {
|
||||
public:
|
||||
QSharedPointer<A> mQData;
|
||||
QWeakPointer<A> mQDataWeak;
|
||||
std::shared_ptr<A> mStdData;
|
||||
std::weak_ptr<A> mStdDataWeak;
|
||||
int mCountRef = 0;
|
||||
|
||||
SafeSharedPointer(QSharedPointer<A> p) : mQDataWeak(p) {
|
||||
}
|
||||
SafeSharedPointer(std::shared_ptr<A> p) : mStdDataWeak(p) {
|
||||
}
|
||||
|
||||
bool lock() {
|
||||
if (mCountRef == 0) {
|
||||
if (!mQDataWeak.isNull()) {
|
||||
mQData = mQDataWeak.lock();
|
||||
if (mQData) ++mCountRef;
|
||||
return !mQData.isNull();
|
||||
} else if (!mStdDataWeak.expired()) {
|
||||
mStdData = mStdDataWeak.lock();
|
||||
if (mStdData) ++mCountRef;
|
||||
return mStdData != nullptr;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
++mCountRef;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
void unlock() {
|
||||
if (mCountRef == 0) qWarning() << "[SafeConnection] too much unlocking";
|
||||
else if (--mCountRef == 0) {
|
||||
mQData = nullptr;
|
||||
mStdData = nullptr;
|
||||
}
|
||||
}
|
||||
void reset() {
|
||||
mQData = nullptr;
|
||||
mStdData = nullptr;
|
||||
mCountRef = 0;
|
||||
}
|
||||
A *get() {
|
||||
if (mQData) return mQData.get();
|
||||
if (mStdData) return mStdData.get();
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -84,12 +84,5 @@ Window {
|
|||
MainLayout {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: ongoingCallPage
|
||||
OngoingCallPage {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
|||
view/Prototype/PhoneNumberPrototype.qml
|
||||
view/Prototype/AccountsPrototype.qml
|
||||
view/Prototype/CallPrototype.qml
|
||||
view/Prototype/ItemPrototype.qml
|
||||
)
|
||||
|
||||
list(APPEND _LINPHONEAPP_QML_SINGLETONS
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Notification {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
readonly property var call: notificationData && notificationData.call
|
||||
property var state: call.state
|
||||
property var state: call.core.state
|
||||
onStateChanged:{
|
||||
if(state != LinphoneEnums.CallState.IncomingReceived){
|
||||
close()
|
||||
|
|
@ -35,7 +35,7 @@ Notification {
|
|||
text: 'Accept'
|
||||
Layout.rightMargin: 20
|
||||
onClicked: {
|
||||
notification.call.lAccept()
|
||||
notification.call.core.lAccept(true)
|
||||
}
|
||||
}
|
||||
Item{
|
||||
|
|
@ -46,7 +46,7 @@ Notification {
|
|||
text: 'Reject'
|
||||
Layout.rightMargin: 20
|
||||
onClicked: {
|
||||
notification.call.lDecline()
|
||||
notification.call.core.lDecline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,25 +12,79 @@ Window{
|
|||
onWidthChanged: console.log(width)
|
||||
property var callVarObject
|
||||
property var call: callVarObject ? callVarObject.value : null
|
||||
property var callState: call && call.state
|
||||
onCallStateChanged: console.log("State:" +callState)
|
||||
property var callState: call && call.core.state
|
||||
onCallStateChanged: {
|
||||
console.log("State:" +callState)
|
||||
if(callState == LinphoneEnums.CallState.Released)
|
||||
callVarObject = undefined
|
||||
}
|
||||
visible: true
|
||||
onCallChanged: console.log('New Call:' +call)
|
||||
onClosing: {
|
||||
accountStatus.defaultAccount = accountStatus
|
||||
accountLayout.accounts = null
|
||||
gc()
|
||||
}
|
||||
Component.onDestruction: gc()
|
||||
ColumnLayout{
|
||||
anchors.fill: parent
|
||||
RowLayout {
|
||||
id: accountLayout
|
||||
Layout.fillWidth: true
|
||||
LoginForm{
|
||||
property AccountProxy accounts: AccountProxy{id: accountProxy}
|
||||
property var haveAccountVar: UtilsCpp.haveAccount()
|
||||
property var haveAccount: haveAccountVar ? haveAccountVar.value : false
|
||||
onHaveAccountChanged: {
|
||||
console.log("HaveAccount: " +haveAccount)
|
||||
logStack.replace(haveAccount ? accountListComponent : loginComponent)
|
||||
}
|
||||
Control.StackView{
|
||||
id: logStack
|
||||
Layout.preferredHeight: 250
|
||||
Layout.preferredWidth: 250
|
||||
//initialItem: loginComponent
|
||||
}
|
||||
Component{
|
||||
id: accountListComponent
|
||||
ListView{
|
||||
id: accountList
|
||||
Layout.fillHeight: true
|
||||
model: AccountProxy{}
|
||||
delegate:Rectangle{
|
||||
color: "#11111111"
|
||||
height: 20
|
||||
width: accountList.width
|
||||
Text{
|
||||
|
||||
text: modelData.core.identityAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Component{
|
||||
id: loginComponent
|
||||
LoginForm{}
|
||||
}
|
||||
Rectangle{
|
||||
id: accountStatus
|
||||
Layout.preferredWidth: 50
|
||||
Layout.preferredHeight: 50
|
||||
property var defaultAccount: accountProxy.defaultAccount
|
||||
property var state: accountProxy.count > 0 && defaultAccount? defaultAccount.registrationState : LoginPageCpp.registrationState
|
||||
onStateChanged: console.log("State:"+state)
|
||||
|
||||
color: LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.Ok
|
||||
color: state === LinphoneEnums.RegistrationState.Ok
|
||||
? 'green'
|
||||
: LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.Failed || LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.None
|
||||
: state === LinphoneEnums.RegistrationState.Failed || state === LinphoneEnums.RegistrationState.None
|
||||
? 'red'
|
||||
: 'orange'
|
||||
MouseArea{
|
||||
anchors.fill: parent
|
||||
onClicked:{
|
||||
logStack.replace(loginComponent)
|
||||
gc()
|
||||
}
|
||||
}
|
||||
}
|
||||
TextInput {
|
||||
id: usernameToCall
|
||||
|
|
@ -41,6 +95,7 @@ Window{
|
|||
text: 'Call'
|
||||
onClicked: {
|
||||
mainItem.callVarObject = UtilsCpp.startAudioCall(usernameToCall.inputText + "@sip.linphone.org")
|
||||
proto.component1 = comp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,10 +103,10 @@ Window{
|
|||
Rectangle{
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 50
|
||||
color: call
|
||||
? call.state === LinphoneEnums.CallState.StreamsRunning
|
||||
color: call
|
||||
? call.core.state === LinphoneEnums.CallState.StreamsRunning
|
||||
? 'green'
|
||||
: call.state === LinphoneEnums.CallState.Released
|
||||
: call.core.state === LinphoneEnums.CallState.Released
|
||||
? 'pink'
|
||||
: 'orange'
|
||||
: 'red'
|
||||
|
|
@ -68,13 +123,26 @@ Window{
|
|||
}
|
||||
Text{
|
||||
id: errorMessageText
|
||||
text: mainItem.call ? mainItem.call.lastErrorMessage : ''
|
||||
text: mainItem.call ? mainItem.call.core.lastErrorMessage : ''
|
||||
color: 'red'
|
||||
}
|
||||
ItemPrototype{
|
||||
id: proto
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Item{
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
Component{
|
||||
id: comp
|
||||
Rectangle{
|
||||
width: 100
|
||||
height: width
|
||||
color: 'pink'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
54
Linphone/view/Prototype/ItemPrototype.qml
Normal file
54
Linphone/view/Prototype/ItemPrototype.qml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls as Control
|
||||
import Linphone
|
||||
import UtilsCpp 1.0
|
||||
|
||||
Rectangle{
|
||||
id: mainItem
|
||||
property Component component1: comp1
|
||||
property Component component2: comp2
|
||||
property bool step: false
|
||||
color: 'black'
|
||||
onStepChanged: {
|
||||
stack.replace(step ? component1 : component2)
|
||||
}
|
||||
Timer{
|
||||
id: delay
|
||||
interval: 1000
|
||||
onTriggered: mainItem.step = !mainItem.step
|
||||
repeat: true
|
||||
running: true
|
||||
}
|
||||
Control.StackView{
|
||||
id: stack
|
||||
anchors.fill: parent
|
||||
/*
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: parent.width/2
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: parent.width/2
|
||||
anchors.bottom: parent.bottom*/
|
||||
initialItem : Rectangle{width: 100
|
||||
height: width
|
||||
color: 'orange'}
|
||||
}
|
||||
Component{
|
||||
id: comp1
|
||||
Rectangle{
|
||||
width: 100
|
||||
height: width
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
|
||||
Component{
|
||||
id: comp2
|
||||
Rectangle{
|
||||
width: 100
|
||||
height: width
|
||||
color: 'green'
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue