1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2024-10-28 00:39:36 +01:00

qmlregister: improve singleton registration

- better manage QML interop object lifetimes
- allow intellisense to pick up QML registered symbols
- fix for PreviewEngine threading

Change-Id: I416cdede70b155dc34fc3ee94f428ae2128c8950
This commit is contained in:
Andreas Traczyk 2023-11-16 15:27:07 -05:00
parent c8b52262bc
commit 35f850289f
58 changed files with 658 additions and 491 deletions

1
.clang-tidy Normal file
View file

@ -0,0 +1 @@
Checks: '-*,analyzer-cplusplus.NewDeleteLeaks'

View file

@ -22,7 +22,7 @@
#include "appsettingsmanager.h"
#include "qtutils.h"
#include "qmlregister.h"
#include "accountlistmodel.h"
#include <QtConcurrent/QtConcurrent>
@ -33,14 +33,7 @@ AccountAdapter::AccountAdapter(AppSettingsManager* settingsManager,
: QmlAdapterBase(instance, parent)
, settingsManager_(settingsManager)
, systemTray_(systemTray)
, accountListModel_(new AccountListModel(instance))
, deviceItemListModel_(new DeviceItemListModel(instance, parent))
, moderatorListModel_(new ModeratorListModel(instance, parent))
{
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, accountListModel_.get(), "AccountListModel");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, deviceItemListModel_.get(), "DeviceItemListModel");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, moderatorListModel_.get(), "ModeratorListModel");
connect(&lrcInstance_->accountModel(),
&AccountModel::accountStatusChanged,
this,
@ -53,8 +46,13 @@ AccountAdapter::AccountAdapter(AppSettingsManager* settingsManager,
connect(systemTray_,
&SystemTray::countChanged,
accountListModel_.get(),
qApp->property("AccountListModel").value<AccountListModel*>(),
&AccountListModel::updateNotifications);
// Switch account to the specified index when an account is added.
connect(this, &AccountAdapter::accountAdded, this, [this](const QString&, int index) {
changeAccount(index);
});
}
AccountModel*

View file

@ -20,21 +20,20 @@
#include "qmladapterbase.h"
#include "accountlistmodel.h"
#include "deviceitemlistmodel.h"
#include "moderatorlistmodel.h"
#include "systemtray.h"
#include "lrcinstance.h"
#include "utils.h"
#include <QSettings>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class AppSettingsManager;
class AccountAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
Q_PROPERTY(lrc::api::AccountModel* model READ getModel NOTIFY modelChanged)
@ -45,6 +44,13 @@ Q_SIGNALS:
void modelChanged();
public:
static AccountAdapter* create(QQmlEngine*, QJSEngine*)
{
return new AccountAdapter(qApp->property("AppSettingsManager").value<AppSettingsManager*>(),
qApp->property("SystemTray").value<SystemTray*>(),
qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit AccountAdapter(AppSettingsManager* settingsManager,
SystemTray* systemTray,
LRCInstance* instance,
@ -100,9 +106,5 @@ private:
AppSettingsManager* settingsManager_;
SystemTray* systemTray_;
QScopedPointer<AccountListModel> accountListModel_;
QScopedPointer<DeviceItemListModel> deviceItemListModel_;
QScopedPointer<ModeratorListModel> moderatorListModel_;
};
Q_DECLARE_METATYPE(AccountAdapter*)

View file

@ -20,11 +20,8 @@
#include "accountlistmodel.h"
#include "lrcinstance.h"
#include "utils.h"
#include "api/account.h"
#include "api/contact.h"
#include "api/conversation.h"
#include <QDateTime>

View file

@ -51,11 +51,9 @@ public:
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
// reset the model when there's new account added
Q_INVOKABLE void reset();
void updateNotifications();
protected:
private:
using Role = AccountList::Role;
};

View file

@ -20,23 +20,31 @@
#include "qmladapterbase.h"
#include "lrcinstance.h"
#include "qtutils.h"
#include "rendererinformationlistmodel.h"
#include <QObject>
#include <QVariant>
#include <QString>
#include <qtutils.h>
#include "rendererinformationlistmodel.h"
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class AvAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
QML_PROPERTY(bool, muteCamera)
QML_RO_PROPERTY(QStringList, windowsNames)
QML_RO_PROPERTY(QList<QVariant>, windowsIds)
QML_RO_PROPERTY(QVariant, renderersInfoList)
public:
static AvAdapter* create(QQmlEngine*, QJSEngine*)
{
return new AvAdapter(qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit AvAdapter(LRCInstance* instance, QObject* parent = nullptr);
~AvAdapter() = default;

View file

@ -20,13 +20,22 @@
#include <QObject>
#include <QMap>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class LRCInstance;
class AvatarRegistry : public QObject
{
Q_OBJECT
QML_SINGLETON
public:
static AvatarRegistry* create(QQmlEngine*, QJSEngine*)
{
return new AvatarRegistry(qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit AvatarRegistry(LRCInstance* instance, QObject* parent = nullptr);
~AvatarRegistry() = default;

View file

@ -26,8 +26,8 @@
#include "calladapter.h"
#include "systemtray.h"
#include "qmlregister.h"
#include "appsettingsmanager.h"
#include "pttlistener.h"
#include <api/callmodel.h>
#include <api/callparticipantsmodel.h>
@ -45,19 +45,15 @@ CallAdapter::CallAdapter(AppSettingsManager* settingsManager,
: QmlAdapterBase(instance, parent)
, systemTray_(systemTray)
, callInformationListModel_(std::make_unique<CallInformationListModel>())
, listener_(new PTTListener(settingsManager, this))
{
// Expose the Push-to-talk listener to QML as a singleton
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, listener_, "PttListener");
// Get the PTTListener instance.
listener_ = qApp->property("PTTListener").value<PTTListener*>();
set_callInformationList(QVariant::fromValue(callInformationListModel_.get()));
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &CallAdapter::updateAdvancedInformation);
overlayModel_.reset(new CallOverlayModel(lrcInstance_, listener_, this));
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, overlayModel_.get(), "CallOverlayModel");
accountId_ = lrcInstance_->get_currentAccountId();
connectCallModel(accountId_);

View file

@ -23,32 +23,34 @@
#include "lrcinstance.h"
#include "qmladapterbase.h"
#include "screensaver.h"
#include "calloverlaymodel.h"
#ifdef HAVE_GLOBAL_PTT
#include "pttlistener.h"
#endif
#include "callInformationListModel.h"
#include <QObject>
#include <QString>
#include <QVariant>
#include <QSystemTrayIcon>
#include "callInformationListModel.h"
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class SystemTray;
class AppSettingsManager;
class PTTListener;
class CallAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
QML_PROPERTY(bool, hasCall)
QML_RO_PROPERTY(QVariant, callInformationList)
public:
QTimer* timer;
enum MuteStates { UNMUTED, LOCAL_MUTED, MODERATOR_MUTED, BOTH_MUTED };
Q_ENUM(MuteStates)
static CallAdapter* create(QQmlEngine*, QJSEngine*)
{
return new CallAdapter(qApp->property("AppSettingsManager").value<AppSettingsManager*>(),
qApp->property("SystemTray").value<SystemTray*>(),
qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit CallAdapter(AppSettingsManager* settingsManager,
SystemTray* systemTray,
@ -56,7 +58,10 @@ public:
QObject* parent = nullptr);
~CallAdapter();
public:
QTimer* timer;
enum MuteStates { UNMUTED, LOCAL_MUTED, MODERATOR_MUTED, BOTH_MUTED };
Q_ENUM(MuteStates)
Q_INVOKABLE void startTimerInformation();
Q_INVOKABLE void stopTimerInformation();
Q_INVOKABLE void placeAudioOnlyCall();
@ -131,7 +136,6 @@ private:
ScreenSaver screenSaver;
SystemTray* systemTray_;
QScopedPointer<CallOverlayModel> overlayModel_;
VectorString currentConfSubcalls_;
std::unique_ptr<CallInformationListModel> callInformationListModel_;

View file

@ -21,8 +21,6 @@
#include "lrcinstance.h"
#include "qtutils.h"
#include "mainapplication.h"
#include "pttlistener.h"
#include <QAbstractListModel>

View file

@ -37,7 +37,7 @@ BaseModalDialog {
button2Role: DialogButtonBox.RejectRole
button1.onClicked: {
if (!(pressedKey === Qt.Key_unknown)){
PttListener.setPttKey(pressedKey);
PTTListener.setPttKey(pressedKey);
choiceMade(pressedKey);
}
close();
@ -102,7 +102,7 @@ BaseModalDialog {
id: keyItem
Keys.onPressed: (event)=>{
keyLabel.text = PttListener.keyToString(event.key);
keyLabel.text = PTTListener.keyToString(event.key);
pressedKey = event.key;
}
}

View file

@ -126,10 +126,6 @@ ConnectionInfoListModel::roleNames() const
void
ConnectionInfoListModel::update()
{
const auto accountId = lrcInstance_->get_currentAccountId();
if (accountId.isEmpty()) {
return;
}
aggregateData();
}
@ -224,4 +220,4 @@ ConnectionInfoListModel::resetData()
peerIds_.clear();
peerData_.clear();
endResetModel();
}
}

View file

@ -41,8 +41,10 @@ enum Role {
Q_ENUM_NS(Role)
} // namespace ConnectionInfoList
class ConnectionInfoListModel : public AbstractListModelBase
class ConnectionInfoListModel final : public AbstractListModelBase
{
Q_OBJECT
public:
explicit ConnectionInfoListModel(LRCInstance* instance, QObject* parent = nullptr);
@ -56,7 +58,6 @@ private:
using Role = ConnectionInfoList::Role;
VectorMapStringString connectionInfoList_;
QVector<QString> peerIds_;
QMap<QString, QMap<QString, QMap<QString, QVariant>>> peerData_;
void aggregateData();

View file

@ -21,16 +21,10 @@
#include "contactadapter.h"
#include "lrcinstance.h"
#include "qmlregister.h"
ContactAdapter::ContactAdapter(LRCInstance* instance, QObject* parent)
: QmlAdapterBase(instance, parent)
, connectionInfoListModel_(new ConnectionInfoListModel(lrcInstance_, this))
{
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS,
connectionInfoListModel_.get(),
"ConnectionInfoListModel");
selectableProxyModel_.reset(new SelectableProxyModel(this));
if (lrcInstance_) {
connect(lrcInstance_,
@ -252,12 +246,6 @@ ContactAdapter::removeContact(const QString& peerUri, bool banContact)
accInfo.contactModel->removeContact(peerUri, banContact);
}
void
ContactAdapter::updateConnectionInfo()
{
connectionInfoListModel_->update();
}
void
ContactAdapter::connectSignals()
{

View file

@ -20,12 +20,12 @@
#include "qmladapterbase.h"
#include "smartlistmodel.h"
#include "conversationlistmodel.h"
#include "connectioninfolistmodel.h"
#include <QObject>
#include <QSortFilterProxyModel>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class LRCInstance;
@ -80,8 +80,14 @@ private:
class ContactAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
public:
static ContactAdapter* create(QQmlEngine*, QJSEngine*)
{
return new ContactAdapter(qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit ContactAdapter(LRCInstance* instance, QObject* parent = nullptr);
~ContactAdapter() = default;
@ -91,7 +97,6 @@ public:
Q_INVOKABLE void setSearchFilter(const QString& filter);
Q_INVOKABLE void contactSelected(int index);
Q_INVOKABLE void removeContact(const QString& peerUri, bool banContact);
Q_INVOKABLE void updateConnectionInfo();
void connectSignals();
@ -106,7 +111,6 @@ private:
SmartListModel::Type listModeltype_;
QScopedPointer<SmartListModel> smartListModel_;
QScopedPointer<SelectableProxyModel> selectableProxyModel_;
QScopedPointer<ConnectionInfoListModel> connectionInfoListModel_;
QStringList defaultModerators_;

View file

@ -55,10 +55,15 @@ ConversationListModel::ConversationListModel(LRCInstance* instance, QObject* par
&ConversationListModel::endRemoveRows,
Qt::DirectConnection);
connect(model_, &ConversationModel::dataChanged, this, [this](int position) {
const auto index = createIndex(position, 0);
Q_EMIT ConversationListModel::dataChanged(index, index);
}, Qt::QueuedConnection);
connect(
model_,
&ConversationModel::dataChanged,
this,
[this](int position) {
const auto index = createIndex(position, 0);
Q_EMIT ConversationListModel::dataChanged(index, index);
},
Qt::QueuedConnection);
}
int

View file

@ -24,7 +24,9 @@
#include "qmlregister.h"
#include "qtutils.h"
#ifdef Q_OS_LINUX
#include "namedirectory.h"
#endif
#include <QApplication>
#include <QJsonObject>
@ -33,18 +35,21 @@ using namespace lrc::api;
ConversationsAdapter::ConversationsAdapter(SystemTray* systemTray,
LRCInstance* instance,
ConversationListProxyModel* convProxyModel,
SelectableListProxyModel* searchProxyModel,
QObject* parent)
: QmlAdapterBase(instance, parent)
, systemTray_(systemTray)
, convSrcModel_(new ConversationListModel(lrcInstance_))
, convModel_(new ConversationListProxyModel(convSrcModel_.get()))
, convModel_(convProxyModel)
, searchSrcModel_(new SearchResultsListModel(lrcInstance_))
, searchModel_(new SelectableListProxyModel(searchSrcModel_.get()))
, searchModel_(searchProxyModel)
{
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, convModel_.get(), "ConversationListModel");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, searchModel_.get(), "SearchResultsListModel");
convModel_->bindSourceModel(convSrcModel_.get());
searchModel_->bindSourceModel(searchSrcModel_.get());
new SelectableListProxyGroupModel({convModel_.data(), searchModel_.data()}, this);
set_convListProxyModel(QVariant::fromValue(convModel_));
set_searchListProxyModel(QVariant::fromValue(searchModel_));
// this will trigger when the invite filter tab is selected
connect(this, &ConversationsAdapter::filterRequestsChanged, [this]() {
@ -112,32 +117,27 @@ ConversationsAdapter::ConversationsAdapter(SystemTray* systemTray,
connect(&lrcInstance_->behaviorController(),
&BehaviorController::newUnreadInteraction,
this,
&ConversationsAdapter::onNewUnreadInteraction,
Qt::UniqueConnection);
&ConversationsAdapter::onNewUnreadInteraction);
connect(&lrcInstance_->behaviorController(),
&BehaviorController::newReadInteraction,
this,
&ConversationsAdapter::onNewReadInteraction,
Qt::UniqueConnection);
&ConversationsAdapter::onNewReadInteraction);
connect(&lrcInstance_->behaviorController(),
&BehaviorController::newTrustRequest,
this,
&ConversationsAdapter::onNewTrustRequest,
Qt::UniqueConnection);
&ConversationsAdapter::onNewTrustRequest);
connect(&lrcInstance_->behaviorController(),
&BehaviorController::trustRequestTreated,
this,
&ConversationsAdapter::onTrustRequestTreated,
Qt::UniqueConnection);
&ConversationsAdapter::onTrustRequestTreated);
connect(lrcInstance_,
&LRCInstance::currentAccountIdChanged,
this,
&ConversationsAdapter::onCurrentAccountIdChanged,
Qt::UniqueConnection);
&ConversationsAdapter::onCurrentAccountIdChanged);
connectConversationModel();
}
@ -168,7 +168,6 @@ ConversationsAdapter::onNewUnreadInteraction(const QString& accountId,
if (interaction.authorUri == accountInfo.profileInfo.uri)
return;
auto from = accountInfo.contactModel->bestNameForContact(interaction.authorUri);
auto to = lrcInstance_->accountModel().bestNameForAccount(accountId);
auto body_ = interaction.body;
if (interaction.type == interaction::Type::DATA_TRANSFER) {
@ -180,6 +179,7 @@ ConversationsAdapter::onNewUnreadInteraction(const QString& accountId,
if (preferences["ignoreNotifications"] == "true")
return;
#ifdef Q_OS_LINUX
auto to = lrcInstance_->accountModel().bestNameForAccount(accountId);
auto contactPhoto = Utils::contactPhoto(lrcInstance_,
interaction.authorUri,
QSize(50, 50),
@ -599,75 +599,49 @@ void
ConversationsAdapter::connectConversationModel()
{
// Signal connections
auto currentConversationModel = lrcInstance_->getCurrentConversationModel();
if (currentConversationModel == nullptr) {
auto model = lrcInstance_->getCurrentConversationModel();
if (!model) {
return;
}
QObject::connect(currentConversationModel,
&ConversationModel::modelChanged,
this,
&ConversationsAdapter::onModelChanged,
Qt::UniqueConnection);
auto connectObjectSignal = [this](auto obj, auto signal, auto slot) {
connect(obj, signal, this, slot, Qt::UniqueConnection);
};
QObject::connect(lrcInstance_->getCurrentContactModel(),
&ContactModel::profileUpdated,
this,
&ConversationsAdapter::onProfileUpdated,
Qt::UniqueConnection);
connectObjectSignal(model,
&ConversationModel::modelChanged,
&ConversationsAdapter::onModelChanged);
connectObjectSignal(model,
&ConversationModel::profileUpdated,
&ConversationsAdapter::onProfileUpdated);
connectObjectSignal(model,
&ConversationModel::conversationUpdated,
&ConversationsAdapter::onConversationUpdated);
connectObjectSignal(model,
&ConversationModel::conversationRemoved,
&ConversationsAdapter::onConversationRemoved);
connectObjectSignal(model,
&ConversationModel::filterChanged,
&ConversationsAdapter::onFilterChanged);
connectObjectSignal(model,
&ConversationModel::conversationCleared,
&ConversationsAdapter::onConversationCleared);
connectObjectSignal(model,
&ConversationModel::searchStatusChanged,
&ConversationsAdapter::onSearchStatusChanged);
connectObjectSignal(model,
&ConversationModel::searchResultUpdated,
&ConversationsAdapter::onSearchResultUpdated);
connectObjectSignal(model,
&ConversationModel::searchResultEnded,
&ConversationsAdapter::onSearchResultEnded);
connectObjectSignal(model,
&ConversationModel::conversationReady,
&ConversationsAdapter::onConversationReady);
QObject::connect(currentConversationModel,
&ConversationModel::conversationUpdated,
this,
&ConversationsAdapter::onConversationUpdated,
Qt::UniqueConnection);
QObject::connect(currentConversationModel,
&ConversationModel::conversationRemoved,
this,
&ConversationsAdapter::onConversationRemoved,
Qt::UniqueConnection);
QObject::connect(currentConversationModel,
&ConversationModel::filterChanged,
this,
&ConversationsAdapter::onFilterChanged,
Qt::UniqueConnection);
QObject::connect(currentConversationModel,
&ConversationModel::conversationCleared,
this,
&ConversationsAdapter::onConversationCleared,
Qt::UniqueConnection);
QObject::connect(currentConversationModel,
&ConversationModel::searchStatusChanged,
this,
&ConversationsAdapter::onSearchStatusChanged,
Qt::UniqueConnection);
QObject::connect(currentConversationModel,
&ConversationModel::searchResultUpdated,
this,
&ConversationsAdapter::onSearchResultUpdated,
Qt::UniqueConnection);
QObject::connect(currentConversationModel,
&ConversationModel::searchResultEnded,
this,
&ConversationsAdapter::onSearchResultEnded,
Qt::UniqueConnection);
QObject::connect(currentConversationModel,
&ConversationModel::conversationReady,
this,
&ConversationsAdapter::onConversationReady,
Qt::UniqueConnection);
QObject::connect(lrcInstance_->getCurrentContactModel(),
&ContactModel::bannedStatusChanged,
this,
&ConversationsAdapter::onBannedStatusChanged,
Qt::UniqueConnection);
connectObjectSignal(lrcInstance_->getCurrentContactModel(),
&ContactModel::bannedStatusChanged,
&ConversationsAdapter::onBannedStatusChanged);
convSrcModel_.reset(new ConversationListModel(lrcInstance_));
convModel_->bindSourceModel(convSrcModel_.get());

View file

@ -26,19 +26,36 @@
#include <QObject>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class SystemTray;
class ConversationsAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
QML_PROPERTY(bool, filterRequests)
QML_PROPERTY(int, totalUnreadMessageCount)
QML_PROPERTY(int, pendingRequestCount)
QML_RO_PROPERTY(QVariant, convListProxyModel)
QML_RO_PROPERTY(QVariant, searchListProxyModel)
public:
static ConversationsAdapter* create(QQmlEngine*, QJSEngine*)
{
return new ConversationsAdapter(
qApp->property("SystemTray").value<SystemTray*>(),
qApp->property("LRCInstance").value<LRCInstance*>(),
qApp->property("ConvListProxyModel").value<ConversationListProxyModel*>(),
qApp->property("ConvSearchListProxyModel").value<SelectableListProxyModel*>());
}
explicit ConversationsAdapter(SystemTray* systemTray,
LRCInstance* instance,
ConversationListProxyModel* convProxyModel,
SelectableListProxyModel* searchProxyModel,
QObject* parent = nullptr);
~ConversationsAdapter() = default;
@ -46,9 +63,9 @@ public:
void connectConversationModel();
Q_INVOKABLE QString createSwarm(const QString& title,
const QString& description,
const QString& avatar,
const VectorString& participants);
const QString& description,
const QString& avatar,
const VectorString& participants);
Q_INVOKABLE void setFilter(const QString& filterString);
Q_INVOKABLE void setFilterAndSelect(const QString& filterString);
Q_INVOKABLE void ignoreFiltering(const QVariant& hightlighted);
@ -107,9 +124,9 @@ private:
SystemTray* systemTray_;
QScopedPointer<ConversationListModel> convSrcModel_;
QScopedPointer<ConversationListProxyModel> convModel_;
ConversationListProxyModel* convModel_;
QScopedPointer<SearchResultsListModel> searchSrcModel_;
QScopedPointer<SelectableListProxyModel> searchModel_;
SelectableListProxyModel* searchModel_;
std::atomic_bool selectFirst_ {false};
};

View file

@ -24,6 +24,8 @@
#include <QObject>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
#define ACCOUNT_CONFIG_SETTINGS_PROPERTY_BASE(type, prop) \
PROPERTY_GETTER_BASE(type, prop) \
@ -97,7 +99,7 @@ private: \
class CurrentAccount final : public QObject
{
Q_OBJECT
// Basic settings
QML_SINGLETON
QML_RO_PROPERTY(QString, id)
QML_RO_PROPERTY(QString, uri)
@ -194,6 +196,12 @@ class CurrentAccount final : public QObject
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(QJsonObject, uiCustomization)
public:
static CurrentAccount* create(QQmlEngine*, QJSEngine*)
{
return new CurrentAccount(qApp->property("LRCInstance").value<LRCInstance*>(),
qApp->property("AppSettingsManager").value<AppSettingsManager*>());
}
explicit CurrentAccount(LRCInstance* lrcInstance,
AppSettingsManager* settingsManager,
QObject* parent = nullptr);

View file

@ -58,8 +58,6 @@ CurrentAccountToMigrate::CurrentAccountToMigrate(LRCInstance* instance, QObject*
&CurrentAccountToMigrate::slotAccountRemoved);
}
CurrentAccountToMigrate::~CurrentAccountToMigrate() {}
void
CurrentAccountToMigrate::removeCurrentAccountToMigrate()
{
@ -133,4 +131,4 @@ CurrentAccountToMigrate::slotAccountRemoved(const QString& accountId)
Q_EMIT allMigrationsFinished();
else
Q_EMIT currentAccountToMigrateRemoved();
}
}

View file

@ -19,15 +19,19 @@
#pragma once
#include <QObject>
#include "qtutils.h"
#include <QObject>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class LRCInstance;
class CurrentAccountToMigrate : public QObject
{
Q_OBJECT
QML_SINGLETON
QML_RO_PROPERTY(int, accountToMigrateListSize)
QML_RO_PROPERTY(QString, accountId)
QML_RO_PROPERTY(QString, managerUsername)
@ -36,8 +40,13 @@ class CurrentAccountToMigrate : public QObject
QML_RO_PROPERTY(QString, alias)
public:
static CurrentAccountToMigrate* create(QQmlEngine*, QJSEngine*)
{
return new CurrentAccountToMigrate(qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit CurrentAccountToMigrate(LRCInstance* lrcInstance, QObject* parent = nullptr);
~CurrentAccountToMigrate();
~CurrentAccountToMigrate() = default;
Q_INVOKABLE void removeCurrentAccountToMigrate();

View file

@ -16,7 +16,8 @@
*/
#include "currentcall.h"
#include "qmlregister.h"
#include "callparticipantsmodel.h"
#include <api/callparticipantsmodel.h>
#include <api/devicemodel.h>
@ -25,8 +26,7 @@ CurrentCall::CurrentCall(LRCInstance* lrcInstance, QObject* parent)
: QObject(parent)
, lrcInstance_(lrcInstance)
{
participantsModel_.reset(new CallParticipantsModel(lrcInstance_, this));
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, participantsModel_.get(), "CallParticipantsModel");
participantsModel_ = qApp->property("CallParticipantsModel").value<CallParticipantsModel*>();
connect(lrcInstance_,
&LRCInstance::currentAccountIdChanged,

View file

@ -19,14 +19,18 @@
#include "lrcinstance.h"
#include "qtutils.h"
#include "callparticipantsmodel.h"
#include <QObject>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class CallParticipantsModel;
class CurrentCall final : public QObject
{
Q_OBJECT
QML_SINGLETON
QML_RO_PROPERTY(QString, id)
QML_RO_PROPERTY(QStringList, uris)
@ -55,6 +59,11 @@ class CurrentCall final : public QObject
QML_PROPERTY(bool, flipSelf)
public:
static CurrentCall* create(QQmlEngine*, QJSEngine*)
{
return new CurrentCall(qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit CurrentCall(LRCInstance* lrcInstance, QObject* parent = nullptr);
~CurrentCall() = default;
Q_INVOKABLE QVariantList getConferencesInfos() const;
@ -85,5 +94,5 @@ private Q_SLOTS:
private:
LRCInstance* lrcInstance_;
QScopedPointer<CallParticipantsModel> participantsModel_;
CallParticipantsModel* participantsModel_;
};

View file

@ -24,7 +24,9 @@ CurrentConversation::CurrentConversation(LRCInstance* lrcInstance, QObject* pare
: QObject(parent)
, lrcInstance_(lrcInstance)
{
uris_ = new CurrentConversationMembers(lrcInstance, this);
membersModel_ = new CurrentConversationMembers(lrcInstance, this);
set_members(QVariant::fromValue(membersModel_));
// whenever the account changes, reconnect the new conversation model
// for updates to the conversation and call state/id
connect(lrcInstance_,
@ -55,7 +57,7 @@ CurrentConversation::updateData()
// If the conversation is empty, clear the id and return.
if (convId.isEmpty()) {
set_id();
uris_->setMembers({}, {}, {});
membersModel_->setMembers({}, {}, {});
return;
}
@ -95,7 +97,7 @@ CurrentConversation::updateData()
for (const auto& banned : bannedUris)
uris.push_back(banned);
}
uris_->setMembers(accountId, convId, uris);
membersModel_->setMembers(accountId, convId, uris);
set_isSwarm(convInfo.isSwarm());
set_isLegacy(convInfo.isLegacy());
set_isCoreDialog(convInfo.isCoreDialog());
@ -202,12 +204,6 @@ CurrentConversation::setInfo(const QString& key, const QString& value)
accInfo.conversationModel->updateConversationInfos(convId, infos);
}
CurrentConversationMembers*
CurrentConversation::uris() const
{
return uris_;
}
void
CurrentConversation::onConversationUpdated(const QString& convId)
{
@ -266,16 +262,22 @@ CurrentConversation::updateConversationPreferences(const QString& convId)
void
CurrentConversation::connectModel()
{
uris_->setMembers({}, {}, {});
membersModel_->setMembers({}, {}, {});
auto convModel = lrcInstance_->getCurrentConversationModel();
if (!convModel)
return;
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::conversationUpdated,
this,
&CurrentConversation::onConversationUpdated,
Qt::UniqueConnection);
auto connectObjectSignal = [this](auto obj, auto signal, auto slot) {
connect(obj, signal, this, slot, Qt::UniqueConnection);
};
connectObjectSignal(convModel,
&ConversationModel::conversationUpdated,
&CurrentConversation::onConversationUpdated);
connectObjectSignal(convModel,
&ConversationModel::profileUpdated,
&CurrentConversation::updateProfile);
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::profileUpdated,
this,

View file

@ -23,6 +23,8 @@
#include <QObject>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
// an adapter object to expose a conversation::Info struct
// as a group of observable properties
@ -30,6 +32,8 @@
class CurrentConversation final : public QObject
{
Q_OBJECT
QML_SINGLETON
QML_PROPERTY(QString, id)
QML_PROPERTY(QString, title)
QML_PROPERTY(QString, description)
@ -56,10 +60,17 @@ class CurrentConversation final : public QObject
QML_PROPERTY(QStringList, backendErrors)
QML_PROPERTY(QString, lastSelfMessageId)
QML_RO_PROPERTY(bool, hasCall)
QML_RO_PROPERTY(QVariant, members)
public:
static CurrentConversation* create(QQmlEngine*, QJSEngine*)
{
return new CurrentConversation(qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr);
~CurrentConversation() = default;
Q_INVOKABLE void scrollToMsg(const QString& msgId);
Q_INVOKABLE void setPreference(const QString& key, const QString& value);
Q_INVOKABLE QString getPreference(const QString& key) const;
@ -88,7 +99,7 @@ Q_SIGNALS:
private:
LRCInstance* lrcInstance_;
CurrentConversationMembers* uris_;
CurrentConversationMembers* membersModel_;
void connectModel();
};

View file

@ -41,7 +41,7 @@ Q_ENUM_NS(Role)
class CurrentConversationMembers : public QAbstractListModel
{
Q_OBJECT
QML_PROPERTY(int, count)
QML_RO_PROPERTY(int, count)
public:
explicit CurrentConversationMembers(LRCInstance* lrcInstance, QObject* parent = nullptr);
@ -56,4 +56,4 @@ private:
QString accountId_;
QString convId_;
QStringList members_;
};
};

View file

@ -18,16 +18,28 @@
#pragma once
#include "networkmanager.h"
#include "connectivitymonitor.h"
#include "qtutils.h"
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class ImageDownloader : public NetworkManager
{
Q_OBJECT
QML_SINGLETON
QML_PROPERTY(QString, cachePath)
public:
static ImageDownloader* create(QQmlEngine*, QJSEngine*)
{
return new ImageDownloader(
qApp->property("ConnectivityMonitor").value<ConnectivityMonitor*>());
}
explicit ImageDownloader(ConnectivityMonitor* cm, QObject* parent = nullptr);
~ImageDownloader() = default;
// Download an image and call onDownloadImageFinished when done
Q_INVOKABLE void downloadImage(const QUrl& url, const QString& localPath);
@ -39,4 +51,4 @@ Q_SIGNALS:
private Q_SLOTS:
// Saves the image to the localPath and emits the appropriate signal
void onDownloadImageFinished(const QByteArray& reply, const QString& localPath);
};
};

View file

@ -26,6 +26,8 @@
#include "connectivitymonitor.h"
#include "systemtray.h"
#include "videoprovider.h"
#include "previewengine.h"
#include "conversationlistmodel.h"
#include <QWKQuick/qwkquickglobal.h>
@ -42,7 +44,6 @@
#include <QQuickWindow>
#include <QLoggingCategory>
#include <locale.h>
#include <thread>
#ifdef Q_OS_WIN
@ -188,6 +189,7 @@ MainApplication::init()
connectivityMonitor_ = new ConnectivityMonitor(this);
settingsManager_ = new AppSettingsManager(this);
systemTray_ = new SystemTray(settingsManager_, this);
previewEngine_ = new PreviewEngine(connectivityMonitor_, this);
// These should should be QueuedConnection to ensure that the
// they are executed after the QML engine has been initialized,
@ -407,6 +409,7 @@ MainApplication::initQmlLayer()
systemTray_,
settingsManager_,
connectivityMonitor_,
previewEngine_,
&screenInfo_,
this);

View file

@ -35,6 +35,7 @@
class ConnectivityMonitor;
class AppSettingsManager;
class SystemTray;
class PreviewEngine;
// Provides information about the screen the app is displayed on
class ScreenInfo : public QObject
@ -120,6 +121,7 @@ private:
ConnectivityMonitor* connectivityMonitor_;
SystemTray* systemTray_;
AppSettingsManager* settingsManager_;
PreviewEngine* previewEngine_;
ScreenInfo screenInfo_;
};

View file

@ -57,8 +57,8 @@ Rectangle {
Layout.leftMargin: 4
Layout.rightMargin: 4
// Reset the model if visible or the CurrentConversationMembers.count changes (0 or greater)
model: visible && CurrentConversationMembers.count >= 0 ? ContactAdapter.getContactSelectableModel(type) : null
// Reset the model if visible or the current conv member count changes (0 or greater)
model: visible && CurrentConversation.members.count >= 0 ? ContactAdapter.getContactSelectableModel(type) : null
delegate: ContactPickerItemDelegate {
id: contactPickerItemDelegate

View file

@ -65,12 +65,12 @@ StackLayout {
}
Connections {
target: CurrentConversationMembers
target: CurrentConversation.members
function onCountChanged() {
// Close the panel if there are 8 or more members in the
// conversation AND the "Add Member" panel is currently open.
if (CurrentConversationMembers.count >= 8 && isOpen(ChatView.AddMemberPanel)) {
if (CurrentConversation.members.count >= 8 && isOpen(ChatView.AddMemberPanel)) {
closePanel();
}
}

View file

@ -70,28 +70,6 @@ JamiListView {
onCountChanged: positionViewAtBeginning()
add: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1.0
duration: JamiTheme.smartListTransitionDuration
}
}
displaced: Transition {
NumberAnimation {
properties: "x,y"
easing.type: Easing.OutCubic
duration: JamiTheme.smartListTransitionDuration
}
NumberAnimation {
property: "opacity"
to: 1.0
duration: JamiTheme.smartListTransitionDuration * (1 - from)
}
}
Behavior on opacity {
NumberAnimation {
easing.type: Easing.OutCubic

View file

@ -57,7 +57,7 @@ Popup {
if (isCall) {
pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id);
} else {
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0];
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.members[0];
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId);
}
}
@ -69,7 +69,7 @@ Popup {
pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id);
} else {
var accountId = LRCInstance.currentAccountId;
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0];
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.members[0];
PluginModel.toggleChatHandler(handlerId, accountId, peerId, !isLoaded);
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId);
}
@ -124,7 +124,7 @@ Popup {
if (isCall) {
return PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id);
} else {
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0];
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.members[0];
return PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId);
}
}

View file

@ -378,7 +378,7 @@ SidePanelBase {
return parent.height;
}
model: SearchResultsListModel
model: ConversationsAdapter.searchListProxyModel
headerLabel: JamiStrings.searchResults
headerVisible: true
}
@ -389,7 +389,7 @@ SidePanelBase {
Layout.fillWidth: true
Layout.fillHeight: true
model: ConversationListModel
model: ConversationsAdapter.convListProxyModel
headerLabel: JamiStrings.conversations
headerVisible: count && searchResultsListView.visible
}
@ -411,7 +411,7 @@ SidePanelBase {
Layout.preferredWidth: parent.width
Layout.fillHeight: true
model: ConversationListModel
model: ConversationsAdapter.convListProxyModel
delegate: SmartListItemDelegate {
interactive: false

View file

@ -205,7 +205,7 @@ Rectangle {
objectName: "members"
visible: !CurrentConversation.isCoreDialog
labelText: {
var membersNb = CurrentConversationMembers.count;
var membersNb = CurrentConversation.members.count;
if (membersNb > 1)
return JamiStrings.members.arg(membersNb);
return JamiStrings.member;
@ -581,7 +581,7 @@ Rectangle {
}
}
model: CurrentConversationMembers
model: CurrentConversation.members
delegate: ItemDelegate {
id: member

View file

@ -26,6 +26,7 @@
#include "appsettingsmanager.h"
#include "qtutils.h"
#include "messageparser.h"
#include "previewengine.h"
#include <api/datatransfermodel.h>

View file

@ -20,16 +20,18 @@
#include "lrcinstance.h"
#include "qmladapterbase.h"
#include "previewengine.h"
#include "messageparser.h"
#include "appsettingsmanager.h"
#include <QObject>
#include <QString>
#include <QTimer>
#include <QSortFilterProxyModel>
class AppSettingsManager;
class MessageParser;
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class FilteredMsgListModel final : public QSortFilterProxyModel
{
@ -63,6 +65,8 @@ public:
class MessagesAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
QML_RO_PROPERTY(QVariant, messageListModel)
QML_PROPERTY(QString, replyToId)
QML_PROPERTY(QString, editId)
@ -71,23 +75,19 @@ class MessagesAdapter final : public QmlAdapterBase
QML_PROPERTY(QString, searchbarPrompt)
public:
static MessagesAdapter* create(QQmlEngine*, QJSEngine*)
{
return new MessagesAdapter(qApp->property("AppSettingsManager").value<AppSettingsManager*>(),
qApp->property("PreviewEngine").value<PreviewEngine*>(),
qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit MessagesAdapter(AppSettingsManager* settingsManager,
PreviewEngine* previewEngine,
LRCInstance* instance,
QObject* parent = nullptr);
~MessagesAdapter() = default;
Q_SIGNALS:
void newInteraction(const QString& id, int type);
void newMessageBarPlaceholderText(QString& placeholderText);
void newFilePasted(QString filePath);
void newTextPasted();
void moreMessagesLoaded(qint32 loadingRequestId);
void timestampUpdated();
void fileCopied(const QString& dest);
void messageParsed(const QString& msgId, const QString& msg);
protected:
Q_INVOKABLE bool isDocument(const interaction::Type& type);
Q_INVOKABLE void loadMoreMessages();
Q_INVOKABLE void connectConversationModel();
@ -149,6 +149,15 @@ protected:
inline MessageListModel* getMsgListSourceModel() const;
Q_SIGNALS:
void newInteraction(const QString& id, int type);
void newFilePasted(const QString& filePath);
void newTextPasted();
void moreMessagesLoaded(qint32 loadingRequestId);
void timestampUpdated();
void fileCopied(const QString& dest);
void messageParsed(const QString& msgId, const QString& msg);
private Q_SLOTS:
void onNewInteraction(const QString& convUid,
const QString& interactionId,

View file

@ -24,7 +24,8 @@
#include "networkmanager.h"
#include "lrcinstance.h"
#include "appsettingsmanager.h"
#include "qmlregister.h"
#include "api/pluginmodel.h"
#include <QJsonArray>
#include <QJsonDocument>
@ -35,8 +36,7 @@
PluginAdapter::PluginAdapter(LRCInstance* instance,
AppSettingsManager* settingsManager,
QObject* parent,
QString baseUrl)
QObject* parent)
: QmlAdapterBase(instance, parent)
, pluginStoreListModel_(new PluginStoreListModel(instance, this))
, pluginVersionManager_(new PluginVersionManager(instance, settingsManager, this))
@ -44,8 +44,9 @@ PluginAdapter::PluginAdapter(LRCInstance* instance,
, lrcInstance_(instance)
, settingsManager_(settingsManager)
{
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, pluginStoreListModel_, "PluginStoreListModel");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, pluginListModel_, "PluginListModel")
pluginListModel_ = qApp->property("PluginListModel").value<PluginListModel*>();
pluginStoreListModel_ = qApp->property("PluginStoreListModel").value<PluginStoreListModel*>();
updateHandlersListCount();
connect(&lrcInstance_->pluginModel(),
&lrc::api::PluginModel::modelUpdated,

View file

@ -19,33 +19,37 @@
#pragma once
#include "qmladapterbase.h"
#include "pluginlistmodel.h"
#include "pluginhandlerlistmodel.h"
#include "pluginlistpreferencemodel.h"
#include "appsettingsmanager.h"
#include "pluginversionmanager.h"
#include "pluginstorelistmodel.h"
#include "preferenceitemlistmodel.h"
#include <QObject>
#include <QSortFilterProxyModel>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class PluginVersionManager;
class PluginListModel;
class PluginStoreListModel;
class PluginVersionManager;
class AppSettingsManager;
class PluginAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
QML_PROPERTY(int, callMediaHandlersListCount)
QML_PROPERTY(int, chatHandlersListCount)
public:
static PluginAdapter* create(QQmlEngine*, QJSEngine*)
{
return new PluginAdapter(qApp->property("LRCInstance").value<LRCInstance*>(),
qApp->property("AppSettingsManager").value<AppSettingsManager*>());
}
explicit PluginAdapter(LRCInstance* instance,
AppSettingsManager* settingsManager,
QObject* parent = nullptr,
QString baseUrl = "https://plugins.jami.net");
QObject* parent = nullptr);
~PluginAdapter() = default;
Q_INVOKABLE void getPluginsFromStore();
@ -73,9 +77,9 @@ Q_SIGNALS:
private:
void updateHandlersListCount();
PluginListModel* pluginListModel_;
PluginStoreListModel* pluginStoreListModel_;
PluginVersionManager* pluginVersionManager_;
PluginListModel* pluginListModel_;
std::unique_ptr<PluginHandlerListModel> pluginHandlerListModel_;

View file

@ -27,16 +27,27 @@
#include <QMutex>
#include <QObject>
#include <QString>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class PositionManager : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
// map of elements : map key and isUnpin
QML_PROPERTY(QVariantMap, mapStatus)
QML_PROPERTY(bool, mapAutoOpening)
QML_PROPERTY(int, positionShareConvIdsCount)
QML_PROPERTY(int, sharingUrisCount)
public:
static PositionManager* create(QQmlEngine*, QJSEngine*)
{
return new PositionManager(qApp->property("AppSettingsManager").value<AppSettingsManager*>(),
qApp->property("SystemTray").value<SystemTray*>(),
qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit PositionManager(AppSettingsManager* settingsManager,
SystemTray* systemTray,
LRCInstance* instance,

View file

@ -17,32 +17,72 @@
#include "previewengine.h"
#include "htmlparser.h"
#include <QRegularExpression>
#include <QThread>
const QRegularExpression PreviewEngine::newlineRe("\\r?\\n");
class PreviewEngine::Parser : public QObject
{
Q_OBJECT
public:
explicit Parser(QObject* parent = nullptr);
~Parser() = default;
Q_SIGNAL void infoReady(const QString& id, const QVariantMap& info);
public:
Q_SLOT void processHTML(const QString& id, const QString& link, const QString& data);
private:
// An instance of HtmlParser used to parse HTML.
HtmlParser* htmlParser_;
QString getTagContent(const QList<QString>& tags, const QString& value);
QString getTitle(const QList<QString>& metaTags);
QString getDescription(const QList<QString>& metaTags);
QString getImage(const QList<QString>& metaTags);
static const QRegularExpression newlineRe;
};
const QRegularExpression PreviewEngine::Parser::newlineRe("\\r?\\n");
PreviewEngine::PreviewEngine(ConnectivityMonitor* cm, QObject* parent)
: NetworkManager(cm, parent)
, htmlParser_(new HtmlParser(this))
, parser_(new PreviewEngine::Parser)
{
// Run this object in a separate thread.
thread_ = new QThread();
moveToThread(thread_);
thread_->start();
parserThread_ = new QThread();
parser_->moveToThread(parserThread_);
// Connect on a queued connection to avoid blocking caller thread.
connect(this, &PreviewEngine::parseLink, this, &PreviewEngine::onParseLink, Qt::QueuedConnection);
connect(this, &PreviewEngine::parseLink, this, &PreviewEngine::onParseLink);
connect(this, &PreviewEngine::htmlReady, parser_.get(), &Parser::processHTML);
connect(parser_.get(), &Parser::infoReady, this, &PreviewEngine::infoReady);
parserThread_->start();
}
PreviewEngine::~PreviewEngine()
{
thread_->quit();
thread_->wait();
parserThread_->quit();
parserThread_->wait();
}
void
PreviewEngine::onParseLink(const QString& id, const QString& link)
{
sendGetRequest(link,
[this, id, link](const QByteArray& html) { Q_EMIT htmlReady(id, link, html); });
}
PreviewEngine::Parser::Parser(QObject* parent)
: QObject(parent)
, htmlParser_(new HtmlParser(this))
{}
QString
PreviewEngine::getTagContent(const QList<QString>& tags, const QString& value)
PreviewEngine::Parser::getTagContent(const QList<QString>& tags, const QString& value)
{
Q_FOREACH (auto tag, tags) {
const QRegularExpression re("(property|name)=\"(og:|twitter:|)" + value
@ -56,7 +96,7 @@ PreviewEngine::getTagContent(const QList<QString>& tags, const QString& value)
}
QString
PreviewEngine::getTitle(const QList<QString>& metaTags)
PreviewEngine::Parser::getTitle(const QList<QString>& metaTags)
{
// Try with opengraph/twitter props
QString title = getTagContent(metaTags, "title");
@ -73,7 +113,7 @@ PreviewEngine::getTitle(const QList<QString>& metaTags)
}
QString
PreviewEngine::getDescription(const QList<QString>& metaTags)
PreviewEngine::Parser::getDescription(const QList<QString>& metaTags)
{
// Try with og/twitter props
QString desc = getTagContent(metaTags, "description");
@ -84,7 +124,7 @@ PreviewEngine::getDescription(const QList<QString>& metaTags)
}
QString
PreviewEngine::getImage(const QList<QString>& metaTags)
PreviewEngine::Parser::getImage(const QList<QString>& metaTags)
{
// Try with og/twitter props
QString image = getTagContent(metaTags, "image");
@ -101,25 +141,25 @@ PreviewEngine::getImage(const QList<QString>& metaTags)
}
void
PreviewEngine::onParseLink(const QString& messageId, const QString& link)
PreviewEngine::Parser::processHTML(const QString& id, const QString& link, const QString& data)
{
sendGetRequest(QUrl(link), [this, messageId, link](const QByteArray& html) {
htmlParser_->parseHtmlString(html);
auto tagsNodes = htmlParser_->getTagsNodes({TidyTag_META});
auto metaTagNodes = tagsNodes[TidyTag_META];
QList<QString> metaTags;
Q_FOREACH (auto tag, metaTagNodes) {
metaTags.append(htmlParser_->getNodeText(tag));
}
QString domain = QUrl(link).host();
if (domain.isEmpty()) {
domain = link;
}
Q_EMIT infoReady(messageId,
{{"title", getTitle(metaTags)},
{"description", getDescription(metaTags)},
{"image", getImage(metaTags)},
{"url", link},
{"domain", domain}});
});
htmlParser_->parseHtmlString(data);
auto tagsNodes = htmlParser_->getTagsNodes({TidyTag_META});
auto metaTagNodes = tagsNodes[TidyTag_META];
QList<QString> metaTags;
Q_FOREACH (auto tag, metaTagNodes) {
metaTags.append(htmlParser_->getNodeText(tag));
}
QString domain = QUrl(link).host();
if (domain.isEmpty()) {
domain = link;
}
Q_EMIT infoReady(id,
{{"title", getTitle(metaTags)},
{"description", getDescription(metaTags)},
{"image", getImage(metaTags)},
{"url", link},
{"domain", domain}});
}
#include "previewengine.moc"

View file

@ -19,8 +19,6 @@
#include "networkmanager.h"
#include "htmlparser.h"
class PreviewEngine final : public NetworkManager
{
Q_OBJECT
@ -30,21 +28,14 @@ public:
~PreviewEngine();
Q_SIGNALS:
void parseLink(const QString& messageId, const QString& link);
void infoReady(const QString& messageId, const QVariantMap& info);
void parseLink(const QString& id, const QString& link);
void infoReady(const QString& id, const QVariantMap& info);
private:
Q_SLOT void onParseLink(const QString& messageId, const QString& link);
Q_SLOT void onParseLink(const QString& id, const QString& link);
Q_SIGNAL void htmlReady(const QString& id, const QString& link, const QByteArray& data);
// An instance of HtmlParser used to parse HTML.
HtmlParser* htmlParser_;
QString getTagContent(const QList<QString>& tags, const QString& value);
QString getTitle(const QList<QString>& metaTags);
QString getDescription(const QList<QString>& metaTags);
QString getImage(const QList<QString>& metaTags);
static const QRegularExpression newlineRe;
QThread* thread_;
class Parser;
QScopedPointer<Parser> parser_;
QThread* parserThread_;
};

View file

@ -27,7 +27,6 @@
#include "positionmanager.h"
#include "tipsmodel.h"
#include "connectivitymonitor.h"
#include "previewengine.h"
#include "imagedownloader.h"
#include "utilsadapter.h"
#include "conversationsadapter.h"
@ -36,7 +35,8 @@
#include "currentaccount.h"
#include "videodevices.h"
#include "currentaccounttomigrate.h"
#include "pttlistener.h"
#include "calloverlaymodel.h"
#include "accountlistmodel.h"
#include "mediacodeclistmodel.h"
#include "audiodevicemodel.h"
@ -47,7 +47,10 @@
#include "smartlistmodel.h"
#include "filestosendlistmodel.h"
#include "callInformationListModel.h"
#include "rendererinformationlistmodel.h"
#include "connectioninfolistmodel.h"
#include "callparticipantsmodel.h"
#include "pluginlistmodel.h"
#include "pluginstorelistmodel.h"
#include "qrimageprovider.h"
#include "avatarimageprovider.h"
@ -75,6 +78,7 @@
// clang-format off
// TODO: remove this
#define QML_REGISTERSINGLETONTYPE_WITH_INSTANCE(T) \
QQmlEngine::setObjectOwnership(&T::instance(), QQmlEngine::CppOwnership); \
qmlRegisterSingletonType<T>(NS_MODELS, MODULE_VER_MAJ, MODULE_VER_MIN, #T, \
[](QQmlEngine* e, QJSEngine* se) -> QObject* { \
Q_UNUSED(e); Q_UNUSED(se); \
@ -97,6 +101,10 @@
MODULE_VER_MAJ, MODULE_VER_MIN, #T, \
"Don't try to add to a qml definition of " #T);
#define REG_QML_SINGLETON qmlRegisterSingletonType
#define REG_MODEL NS_MODELS, MODULE_VER_MAJ, MODULE_VER_MIN
#define CREATE(Obj) [=](QQmlEngine*, QJSEngine*) -> QObject* { return Obj; }
namespace Utils {
/*!
@ -108,65 +116,103 @@ registerTypes(QQmlEngine* engine,
SystemTray* systemTray,
AppSettingsManager* settingsManager,
ConnectivityMonitor* connectivityMonitor,
PreviewEngine* previewEngine,
ScreenInfo* screenInfo,
QObject* app)
{
// setup the adapters (their lifetimes are that of MainApplication)
auto avatarRegistry = new AvatarRegistry(lrcInstance, engine);
auto callAdapter = new CallAdapter(settingsManager, systemTray, lrcInstance, engine);
auto previewEngine = new PreviewEngine(connectivityMonitor, engine);
auto imageDownloader = new ImageDownloader(connectivityMonitor, engine);
auto messagesAdapter = new MessagesAdapter(settingsManager, previewEngine, lrcInstance, engine);
auto positionManager = new PositionManager(settingsManager, systemTray, lrcInstance, engine);
auto conversationsAdapter = new ConversationsAdapter(systemTray, lrcInstance, engine);
auto avAdapter = new AvAdapter(lrcInstance, engine);
auto contactAdapter = new ContactAdapter(lrcInstance, engine);
auto accountAdapter = new AccountAdapter(settingsManager, systemTray, lrcInstance, engine);
auto utilsAdapter = new UtilsAdapter(settingsManager, systemTray, lrcInstance, engine);
auto pluginAdapter = new PluginAdapter(lrcInstance, settingsManager, engine);
auto currentCall = new CurrentCall(lrcInstance, engine);
auto currentConversation = new CurrentConversation(lrcInstance, engine);
auto currentAccount = new CurrentAccount(lrcInstance, settingsManager, engine);
auto tipsModel = new TipsModel(settingsManager, engine);
auto videoDevices = new VideoDevices(lrcInstance, engine);
auto currentAccountToMigrate = new CurrentAccountToMigrate(lrcInstance, engine);
auto wizardViewStepModel = new WizardViewStepModel(lrcInstance, accountAdapter, settingsManager, engine);
/* Used in ContactAdapter */
auto connectionInfoListModel = new ConnectionInfoListModel(lrcInstance, app);
qApp->setProperty("ConnectionInfoListModel", QVariant::fromValue(connectionInfoListModel));
QQmlEngine::setObjectOwnership(connectionInfoListModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<ConnectionInfoListModel>(REG_MODEL, "ConnectionInfoListModel", CREATE(connectionInfoListModel));
/* Used in AccountAdapter */
auto accountListModel = new AccountListModel(lrcInstance, app);
qApp->setProperty("AccountListModel", QVariant::fromValue(accountListModel));
QQmlEngine::setObjectOwnership(accountListModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<AccountListModel>(REG_MODEL, "AccountListModel", CREATE(accountListModel));
auto deviceItemListModel = new DeviceItemListModel(lrcInstance, app);
qApp->setProperty("DeviceItemListModel", QVariant::fromValue(deviceItemListModel));
QQmlEngine::setObjectOwnership(deviceItemListModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<DeviceItemListModel>(REG_MODEL, "DeviceItemListModel", CREATE(deviceItemListModel));
auto moderatorListModel = new ModeratorListModel(lrcInstance, app);
qApp->setProperty("ModeratorListModel", QVariant::fromValue(moderatorListModel));
QQmlEngine::setObjectOwnership(moderatorListModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<ModeratorListModel>(REG_MODEL, "ModeratorListModel", CREATE(moderatorListModel));
/* Used in CallAdapter */
auto pttListener = new PTTListener(settingsManager, app);
qApp->setProperty("PTTListener", QVariant::fromValue(pttListener));
QQmlEngine::setObjectOwnership(pttListener, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<PTTListener>(REG_MODEL, "PTTListener", CREATE(pttListener));
auto callOverlayModel = new CallOverlayModel(lrcInstance, pttListener, app);
qApp->setProperty("CallOverlayModel", QVariant::fromValue(callOverlayModel));
QQmlEngine::setObjectOwnership(callOverlayModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<CallOverlayModel>(REG_MODEL, "CallOverlayModel", CREATE(callOverlayModel));
/* Used in CurrentCall */
auto callParticipantsModel = new CallParticipantsModel(lrcInstance, app);
qApp->setProperty("CallParticipantsModel", QVariant::fromValue(callParticipantsModel));
QQmlEngine::setObjectOwnership(callParticipantsModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<CallParticipantsModel>(REG_MODEL, "CallParticipantsModel", CREATE(callParticipantsModel));
/* Used in ConversationsAdapter */
auto convListProxyModel = new ConversationListProxyModel(nullptr, app);
qApp->setProperty("ConvListProxyModel", QVariant::fromValue(convListProxyModel));
auto searchProxyListModel = new SelectableListProxyModel(nullptr, app);
qApp->setProperty("ConvSearchListProxyModel", QVariant::fromValue(searchProxyListModel));
// This causes mutually exclusive selection between the two proxy models.
new SelectableListProxyGroupModel({convListProxyModel, searchProxyListModel}, app);
/* Used in PluginManager */
auto pluginListModel = new PluginListModel(lrcInstance, app);
qApp->setProperty("PluginListModel", QVariant::fromValue(pluginListModel));
QQmlEngine::setObjectOwnership(pluginListModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<PluginListModel>(REG_MODEL, "PluginListModel", CREATE(pluginListModel));
auto pluginStoreListModel = new PluginStoreListModel(lrcInstance, app);
qApp->setProperty("PluginStoreListModel", QVariant::fromValue(pluginStoreListModel));
QQmlEngine::setObjectOwnership(pluginStoreListModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<PluginStoreListModel>(REG_MODEL, "PluginStoreListModel", CREATE(pluginStoreListModel));
// Register app-level objects that are used by QML created objects.
// These MUST be set prior to loading the initial QML file, in order to
// be available to the QML adapter class factory creation methods.
qApp->setProperty("LRCInstance", QVariant::fromValue(lrcInstance));
qApp->setProperty("SystemTray", QVariant::fromValue(systemTray));
qApp->setProperty("AppSettingsManager", QVariant::fromValue(settingsManager));
qApp->setProperty("ConnectivityMonitor", QVariant::fromValue(connectivityMonitor));
qApp->setProperty("PreviewEngine", QVariant::fromValue(previewEngine));
// qml adapter registration
QML_REGISTERSINGLETONTYPE_POBJECT(NS_HELPERS, avatarRegistry, "AvatarRegistry");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, callAdapter, "CallAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, tipsModel, "TipsModel");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, messagesAdapter, "MessagesAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, positionManager, "PositionManager");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, conversationsAdapter, "ConversationsAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, avAdapter, "AvAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, contactAdapter, "ContactAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, accountAdapter, "AccountAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, utilsAdapter, "UtilsAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentCall, "CurrentCall");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation, "CurrentConversation");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation->uris(), "CurrentConversationMembers");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccount, "CurrentAccount");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, videoDevices, "VideoDevices");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccountToMigrate, "CurrentAccountToMigrate")
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, wizardViewStepModel, "WizardViewStepModel")
QML_REGISTERSINGLETONTYPE_POBJECT(NS_HELPERS, imageDownloader, "ImageDownloader")
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, AvatarRegistry);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, AccountAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CallAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, MessagesAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, ConversationsAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, ContactAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, UtilsAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, PositionManager);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, AvAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, PluginAdapter);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CurrentAccount);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CurrentConversation);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CurrentCall);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, TipsModel);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, VideoDevices);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CurrentAccountToMigrate);
QML_REGISTERSINGLETON_TYPE(NS_MODELS, WizardViewStepModel);
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, ImageDownloader);
// TODO: remove these
QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, AVModel, &lrcInstance->avModel())
QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, PluginModel, &lrcInstance->pluginModel())
QML_REGISTERSINGLETONTYPE_CUSTOM(NS_HELPERS, AppVersionManager, lrcInstance->getAppVersionManager())
// Hack for QtCreator autocomplete (part 2)
// https://bugreports.qt.io/browse/QTCREATORBUG-20569
// Use a dummy object to register the import namespace.
// This occurs when we register from within MainApplication
QML_REGISTERNAMESPACE(NS_MODELS, dummy::staticMetaObject, "");
QML_REGISTERNAMESPACE(NS_ADAPTERS, dummy::staticMetaObject, "");
QML_REGISTERNAMESPACE(NS_CONSTANTS, dummy::staticMetaObject, "");
QML_REGISTERNAMESPACE(NS_HELPERS, dummy::staticMetaObject, "");
QML_REGISTERNAMESPACE(NS_ENUMS, dummy::staticMetaObject, "");
QML_REGISTERSINGLETONTYPE_WITH_INSTANCE(NameDirectory); // C++ singleton
// QAbstractListModels
QML_REGISTERTYPE(NS_MODELS, BannedListModel);
@ -176,10 +222,7 @@ registerTypes(QQmlEngine* engine,
QML_REGISTERTYPE(NS_MODELS, PreferenceItemListModel);
QML_REGISTERTYPE(NS_MODELS, PluginListPreferenceModel);
QML_REGISTERTYPE(NS_MODELS, FilesToSendListModel);
QML_REGISTERTYPE(NS_MODELS, SmartListModel);
QML_REGISTERTYPE(NS_MODELS, MessageListModel);
QML_REGISTERTYPE(NS_MODELS, CallInformationListModel);
QML_REGISTERTYPE(NS_MODELS, RendererInformationListModel);
// Roles & type enums for models
QML_REGISTERNAMESPACE(NS_MODELS, AccountList::staticMetaObject, "AccountList");
@ -201,10 +244,6 @@ registerTypes(QQmlEngine* engine,
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, lrcInstance, "LRCInstance")
QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, settingsManager, "AppSettingsManager")
// C++ singletons
// TODO: remove this
QML_REGISTERSINGLETONTYPE_WITH_INSTANCE(NameDirectory);
// Lrc namespaces, models, and singletons
QML_REGISTERNAMESPACE(NS_MODELS, lrc::api::staticMetaObject, "Lrc");
QML_REGISTERNAMESPACE(NS_MODELS, lrc::api::account::staticMetaObject, "Account");
@ -239,13 +278,7 @@ registerTypes(QQmlEngine* engine,
QML_REGISTERUNCREATABLE(NS_ENUMS, VideoFormatFpsModel)
engine->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance));
engine->addImageProvider(QLatin1String("avatarimage"),
new AvatarImageProvider(lrcInstance));
engine->setObjectOwnership(&lrcInstance->avModel(), QQmlEngine::CppOwnership);
engine->setObjectOwnership(&lrcInstance->pluginModel(), QQmlEngine::CppOwnership);
engine->setObjectOwnership(lrcInstance->getAppVersionManager(), QQmlEngine::CppOwnership);
engine->setObjectOwnership(&NameDirectory::instance(), QQmlEngine::CppOwnership);
engine->addImageProvider(QLatin1String("avatarimage"), new AvatarImageProvider(lrcInstance));
}
// clang-format on
} // namespace Utils

View file

@ -46,6 +46,9 @@ Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
} // namespace dummy
// clang-format off
#define QML_REGISTERSINGLETON_TYPE(NS, T) \
qmlRegisterSingletonType<T>(NS, MODULE_VER_MAJ, MODULE_VER_MIN, #T, T::create);
#define QML_REGISTERSINGLETONTYPE_POBJECT(NS, I, N) \
QQmlEngine::setObjectOwnership(I, QQmlEngine::CppOwnership); \
{ using T = std::remove_reference<decltype(*I)>::type; \
@ -67,6 +70,7 @@ void registerTypes(QQmlEngine* engine,
SystemTray* systemTray,
AppSettingsManager* appSettingsManager,
ConnectivityMonitor* connectivityMonitor,
PreviewEngine* previewEngine,
ScreenInfo* screenInfo,
QObject* app);
}

View file

@ -34,7 +34,7 @@ SettingsPageBase {
property bool isSIP: CurrentAccount.type === Profile.Type.SIP
property int itemWidth: 132
property string key: PttListener.keyToString(PttListener.getCurrentKey())
property string key: PTTListener.keyToString(PTTListener.getCurrentKey())
title: JamiStrings.callSettingsTitle
function updateAndShowModeratorsSlot() {
@ -437,7 +437,7 @@ SettingsPageBase {
onClicked: {
var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/ChangePttKeyPopup.qml");
dlg.choiceMade.connect(function (chosenKey) {
keyLabel.text = PttListener.keyToString(chosenKey);
keyLabel.text = PTTListener.keyToString(chosenKey);
});
}
}

View file

@ -14,13 +14,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
import net.jami.Enums 1.1
import net.jami.Models 1.1
import "../../commoncomponents"
import "../js/logviewwindowcreation.js" as LogViewWindowCreation
@ -109,16 +112,14 @@ ListView {
model: ConnectionInfoListModel
Component.onCompleted: {
ContactAdapter.updateConnectionInfo();
}
Component.onCompleted: ConnectionInfoListModel.update()
Timer {
interval: 1000
running: root.visible
repeat: true
onTriggered: {
ContactAdapter.updateConnectionInfo();
ConnectionInfoListModel.update();
listview.rota = listview.rota + 5;
}
}

View file

@ -17,12 +17,14 @@
*/
#pragma once
#include "lrcinstance.h"
#include "appsettingsmanager.h"
#include "qtutils.h"
#include "typedefs.h"
#include <QAbstractListModel>
#include <QObject>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
#define TIPS_ROLES \
X(TipId) \
@ -44,8 +46,14 @@ Q_ENUM_NS(Role)
class TipsModel : public QAbstractListModel
{
Q_OBJECT
QML_SINGLETON
public:
static TipsModel* create(QQmlEngine*, QJSEngine*)
{
return new TipsModel(qApp->property("AppSettingsManager").value<AppSettingsManager*>());
}
TipsModel(AppSettingsManager* sm, QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@ -58,4 +66,4 @@ public Q_SLOTS:
private:
VectorMapStringString tips_;
AppSettingsManager* settingsManager_;
};
};

View file

@ -30,12 +30,15 @@
#include <QApplication>
#include <QObject>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
#if __has_include(<gio/gio.h>)
#include <gio/gio.h>
#endif
#if defined(WIN32) && __has_include(<winrt/Windows.Foundation.h>)
#define _SILENCE_CLANG_COROUTINE_MESSAGE
#include <winrt/Windows.Foundation.h>
#define WATCHSYSTEMTHEME __has_include(<winrt/Windows.UI.ViewManagement.h>)
@ -67,9 +70,18 @@ bool readAppsUseLightThemeRegistry(bool getValue);
class UtilsAdapter final : public QmlAdapterBase
{
Q_OBJECT
QML_SINGLETON
QML_PROPERTY(QStringList, logList)
QML_RO_PROPERTY(bool, isRTL)
public:
static UtilsAdapter* create(QQmlEngine*, QJSEngine*)
{
return new UtilsAdapter(qApp->property("AppSettingsManager").value<AppSettingsManager*>(),
qApp->property("SystemTray").value<SystemTray*>(),
qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit UtilsAdapter(AppSettingsManager* settingsManager,
SystemTray* systemTray,
LRCInstance* instance,

View file

@ -18,7 +18,8 @@
#include "videodevices.h"
// VideoInputDeviceModel
#include "api/devicemodel.h"
VideoInputDeviceModel::VideoInputDeviceModel(LRCInstance* lrcInstance,
VideoDevices* videoDeviceInstance)
: QAbstractListModel(videoDeviceInstance)
@ -124,7 +125,6 @@ VideoFormatResolutionModel::getCurrentIndex() const
return resultList.size() > 0 ? resultList[0].row() : 0;
}
// VideoFormatFpsModel
VideoFormatFpsModel::VideoFormatFpsModel(LRCInstance* lrcInstance, VideoDevices* videoDeviceInstance)
: QAbstractListModel(videoDeviceInstance)
, lrcInstance_(lrcInstance)
@ -177,7 +177,6 @@ VideoFormatFpsModel::getCurrentIndex() const
return resultList.size() > 0 ? resultList[0].row() : 0;
}
// VideoDevices
VideoDevices::VideoDevices(LRCInstance* lrcInstance, QObject* parent)
: QObject(parent)
, lrcInstance_(lrcInstance)

View file

@ -21,9 +21,9 @@
#include "lrcinstance.h"
#include "qtutils.h"
#include "api/devicemodel.h"
#include <QObject>
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class VideoDevices;
@ -115,6 +115,8 @@ private:
class VideoDevices : public QObject
{
Q_OBJECT
QML_SINGLETON
QML_RO_PROPERTY(int, listSize)
QML_RO_PROPERTY(QString, defaultChannel)
@ -130,6 +132,11 @@ class VideoDevices : public QObject
QML_RO_PROPERTY(QVariant, sharingFpsSourceModel)
public:
static VideoDevices* create(QQmlEngine*, QJSEngine*)
{
return new VideoDevices(qApp->property("LRCInstance").value<LRCInstance*>());
}
explicit VideoDevices(LRCInstance* lrcInstance, QObject* parent = nullptr);
~VideoDevices() = default;

View file

@ -50,6 +50,31 @@ BaseView {
}
}
// Handle the end of the wizard account creation process.
Connections {
target: WizardViewStepModel
function onCreateAccountRequested(creationOption) {
switch (creationOption) {
case WizardViewStepModel.AccountCreationOption.CreateJamiAccount:
case WizardViewStepModel.AccountCreationOption.CreateRendezVous:
case WizardViewStepModel.AccountCreationOption.ImportFromBackup:
case WizardViewStepModel.AccountCreationOption.ImportFromDevice:
AccountAdapter.createJamiAccount(WizardViewStepModel.accountCreationInfo);
break;
case WizardViewStepModel.AccountCreationOption.ConnectToAccountManager:
AccountAdapter.createJAMSAccount(WizardViewStepModel.accountCreationInfo);
break;
case WizardViewStepModel.AccountCreationOption.CreateSipAccount:
AccountAdapter.createSIPAccount(WizardViewStepModel.accountCreationInfo);
break;
default:
print("Bad account creation option: " + creationOption);
WizardViewStepModel.closeWizardView();
break;
}
}
}
Connections {
target: WizardViewStepModel

View file

@ -18,26 +18,23 @@
#include "wizardviewstepmodel.h"
#include "accountadapter.h"
#include "appsettingsmanager.h"
#include "lrcinstance.h"
#include "api/accountmodel.h"
WizardViewStepModel::WizardViewStepModel(LRCInstance* lrcInstance,
AccountAdapter* accountAdapter,
AppSettingsManager* appSettingsManager,
QObject* parent)
: QObject(parent)
, lrcInstance_(lrcInstance)
, accountAdapter_(accountAdapter)
, appSettingsManager_(appSettingsManager)
{
reset();
connect(accountAdapter_,
&AccountAdapter::accountAdded,
connect(&lrcInstance_->accountModel(),
&AccountModel::accountAdded,
this,
[this](QString accountId, int index) {
accountAdapter_->changeAccount(index);
[this](const QString& accountId) {
auto accountCreationOption = get_accountCreationOption();
if (accountCreationOption == AccountCreationOption::ConnectToAccountManager
|| accountCreationOption == AccountCreationOption::CreateSipAccount) {
@ -66,30 +63,8 @@ WizardViewStepModel::startAccountCreationFlow(AccountCreationOption accountCreat
void
WizardViewStepModel::nextStep()
{
auto accountCreationOption = get_accountCreationOption();
if (get_mainStep() == MainSteps::Initial
|| accountCreationOption == AccountCreationOption::None) {
return;
}
switch (accountCreationOption) {
case AccountCreationOption::CreateJamiAccount:
case AccountCreationOption::CreateRendezVous:
case AccountCreationOption::ImportFromBackup:
case AccountCreationOption::ImportFromDevice: {
accountAdapter_->createJamiAccount(get_accountCreationInfo());
break;
}
case AccountCreationOption::ConnectToAccountManager: {
accountAdapter_->createJAMSAccount(get_accountCreationInfo());
break;
}
case AccountCreationOption::CreateSipAccount: {
accountAdapter_->createSIPAccount(get_accountCreationInfo());
break;
}
default:
return;
if (mainStep_ != MainSteps::Initial) {
Q_EMIT createAccountRequested(accountCreationOption_);
}
}

View file

@ -18,11 +18,13 @@
#pragma once
#include "qtutils.h"
#include <QObject>
#include <QVariant>
#include <QMap>
#include "qtutils.h"
#include <QQmlEngine> // QML registration
#include <QApplication> // QML registration
class AccountAdapter;
class LRCInstance;
@ -31,6 +33,7 @@ class AppSettingsManager;
class WizardViewStepModel : public QObject
{
Q_OBJECT
QML_SINGLETON
Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
public:
@ -54,12 +57,17 @@ public:
QML_PROPERTY(MainSteps, mainStep)
QML_PROPERTY(AccountCreationOption, accountCreationOption)
QML_PROPERTY(QVariantMap, accountCreationInfo)
public:
static WizardViewStepModel* create(QQmlEngine*, QJSEngine*)
{
return new WizardViewStepModel(qApp->property("LRCInstance").value<LRCInstance*>(),
qApp->property("AppSettingsManager")
.value<AppSettingsManager*>());
}
explicit WizardViewStepModel(LRCInstance* lrcInstance,
AccountAdapter* accountAdapter,
AppSettingsManager* appSettingsManager,
QObject* parent = nullptr);
@ -70,11 +78,11 @@ public:
Q_SIGNALS:
void accountIsReady(QString accountId);
void closeWizardView();
void createAccountRequested(AccountCreationOption);
private:
void reset();
LRCInstance* lrcInstance_;
AccountAdapter* accountAdapter_;
AppSettingsManager* appSettingsManager_;
};

View file

@ -136,6 +136,7 @@ public Q_SLOTS:
systemTray_.get(),
settingsManager_.get(),
connectivityMonitor_.get(),
previewEngine_.get(),
&screenInfo_,
this);

View file

@ -17,6 +17,7 @@
import QtQuick
import QtQuick.Controls
import QtTest
import "../../../src/app/"
@ -24,8 +25,18 @@ import "../../../src/app/"
// each UUT from having to manage its own top level app management objects
// (currently ViewManager, ViewCoordinator, and ApplicationWindow).
Item {
// This will be our UUT.
required default property var uut
id: tw
width: childrenRect.width
height: childrenRect.height
// A binding to the windowShown property
Binding {
tw.appWindow: uut.Window.window
when: QTestRootObject.windowShown
}
Component.onCompleted: viewCoordinator.init(this)
// These are our fake app management objects. The caveat is that they
// must be maintained in sync with the actual objects in the app for now.
@ -33,7 +44,7 @@ Item {
// sync them.
property ViewManager viewManager: ViewManager {}
property ViewCoordinator viewCoordinator: ViewCoordinator {}
property ApplicationWindow appWindow: ApplicationWindow {
property QtObject appWindow: QtObject {
property bool useFrameless: false
}
}

View file

@ -28,17 +28,20 @@ import "../../../src/app/mainview"
import "../../../src/app/mainview/components"
import "../../../src/app/commoncomponents"
CallMessageDelegate {
id: uut
type: Interaction.Type.CALL
TestWrapper {
CallMessageDelegate {
id: uut
TestCase {
name: "Check basic visibility for header buttons"
function test_checkBasicVisibility() {
var moreButton = findChild(uut, "more")
var replyButton = findChild(uut, "reply")
compare(moreButton.visible, false)
compare(replyButton.visible, false)
type: Interaction.Type.CALL
TestCase {
name: "Check basic visibility for option buttons"
function test_checkOptionButtonsVisibility() {
var moreButton = findChild(uut, "more")
var replyButton = findChild(uut, "reply")
compare(moreButton.visible, false)
compare(replyButton.visible, false)
}
}
}
}
}

View file

@ -25,67 +25,59 @@ import net.jami.Constants 1.1
import "../../../src/app/"
import "../../../src/app/mainview/components"
OngoingCallPage {
id: uut
TestWrapper {
OngoingCallPage {
id: uut
width: 800
height: 600
width: 800
height: 600
property QtObject appWindow
property ViewManager viewManager: ViewManager {}
property ViewCoordinator viewCoordinator: ViewCoordinator {}
TestCase {
name: "Check basic visibility of action bar during a call"
when: windowShown // Mouse events can only be handled
// after the window has been shown.
TestCase {
name: "Check basic visibility of action bar during a call"
when: windowShown // Mouse events can only be handled
// after the window has been shown.
property var mainOverlay
property var callOverlay
property var mainOverlay
function initTestCase() {
mainOverlay = findChild(uut, "mainOverlay")
function initTestCase() {
callOverlay = findChild(uut, "callOverlay")
mainOverlay = findChild(callOverlay, "mainOverlay")
// The CallActionBar on the OngoingCallPage starts out invisible and
// is made visible whenever the user moves their mouse.
// This is implemented via an event filter in the CallOverlayModel
// class. The event filter is created when the MainOverlay becomes
// visible. In the actual Jami application, this happens when a call
// is started, but we need to toggle the visiblity manually here
// because the MainOverlay is visible at the beginning of the test.
mainOverlay.visible = false
mainOverlay.visible = true
}
// The CallActionBar on the OngoingCallPage starts out invisible and
// is made visible whenever the user moves their mouse.
// This is implemented via an event filter in the CallOverlayModel
// class. The event filter is created when the MainOverlay becomes
// visible. In the actual Jami application, this happens when a call
// is started, but we need to toggle the visiblity manually here
// because the MainOverlay is visible at the beginning of the test.
appWindow = uut.Window.window
mainOverlay.visible = false
mainOverlay.visible = true
function test_checkCallActionBarVisibility() {
var callActionBar = findChild(mainOverlay, "callActionBar")
// Calling mouseMove() will generate warnings if we don't call init first.
viewCoordinator.init(uut)
}
// The primary and secondary actions in the CallActionBar are currently being added
// one by one (not using a loop) to CallOverlayModel in the Component.onCompleted
// block of CallActionBar.qml. The two lines below are meant as a sanity check
// that no action has been forgotten.
compare(callActionBar.primaryActions.length, CallOverlayModel.primaryModel().rowCount())
compare(callActionBar.secondaryActions.length, CallOverlayModel.secondaryModel().rowCount())
function test_checkBasicVisibility() {
var callActionBar = findChild(mainOverlay, "callActionBar")
compare(callActionBar.visible, false)
mouseMove(uut)
// The primary and secondary actions in the CallActionBar are currently being added
// one by one (not using a loop) to CallOverlayModel in the Component.onCompleted
// block of CallActionBar.qml. The two lines below are meant as a sanity check
// that no action has been forgotten.
compare(callActionBar.primaryActions.length, CallOverlayModel.primaryModel().rowCount())
compare(callActionBar.secondaryActions.length, CallOverlayModel.secondaryModel().rowCount())
// We need to wait for the fade-in animation of the CallActionBar to be completed
// before we check that it's visible.
var waitTime = JamiTheme.overlayFadeDuration + 100
// Make sure we have time to check that the CallActioinBar is visible before it fades out:
verify(waitTime + 100 < JamiTheme.overlayFadeDelay)
// Note: The CallActionBar is supposed to stay visible for a few seconds. If the above
// check fails, then this means that either overlayFadeDuration or overlayFadeDelay
// got changed to a value that's way too high/low.
compare(callActionBar.visible, false)
mouseMove(uut)
// We need to wait for the fade-in animation of the CallActionBar to be completed
// before we check that it's visible.
var waitTime = JamiTheme.overlayFadeDuration + 100
// Make sure we have time to check that the CallActioinBar is visible before it fades out:
verify(waitTime + 100 < JamiTheme.overlayFadeDelay)
// Note: The CallActionBar is supposed to stay visible for a few seconds. If the above
// check fails, then this means that either overlayFadeDuration or overlayFadeDelay
// got changed to a value that's way too high/low.
wait(waitTime)
compare(callActionBar.visible, true)
wait(waitTime)
compare(callActionBar.visible, true)
}
}
}
}
}

View file

@ -24,9 +24,12 @@ import "../../../src/app/"
import "../../../src/app/mainview/components"
TestWrapper {
uut: WelcomePage {
WelcomePage {
id: uut
TestCase {
name: "Open 'About Jami' popup"
when: windowShown
function test_openAboutPopup() {
var aboutJamiButton = findChild(uut, "aboutJami")