diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..1a527999 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1 @@ +Checks: '-*,analyzer-cplusplus.NewDeleteLeaks' diff --git a/src/app/accountadapter.cpp b/src/app/accountadapter.cpp index 95cb7611..e022e70d 100644 --- a/src/app/accountadapter.cpp +++ b/src/app/accountadapter.cpp @@ -22,7 +22,7 @@ #include "appsettingsmanager.h" #include "qtutils.h" -#include "qmlregister.h" +#include "accountlistmodel.h" #include @@ -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::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* diff --git a/src/app/accountadapter.h b/src/app/accountadapter.h index 0407fc95..8ff685db 100644 --- a/src/app/accountadapter.h +++ b/src/app/accountadapter.h @@ -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 #include +#include // QML registration +#include // 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(), + qApp->property("SystemTray").value(), + qApp->property("LRCInstance").value()); + } + explicit AccountAdapter(AppSettingsManager* settingsManager, SystemTray* systemTray, LRCInstance* instance, @@ -100,9 +106,5 @@ private: AppSettingsManager* settingsManager_; SystemTray* systemTray_; - - QScopedPointer accountListModel_; - QScopedPointer deviceItemListModel_; - QScopedPointer moderatorListModel_; }; Q_DECLARE_METATYPE(AccountAdapter*) diff --git a/src/app/accountlistmodel.cpp b/src/app/accountlistmodel.cpp index d64760a0..b4a60e1a 100644 --- a/src/app/accountlistmodel.cpp +++ b/src/app/accountlistmodel.cpp @@ -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 diff --git a/src/app/accountlistmodel.h b/src/app/accountlistmodel.h index 67921a52..9ffd94ef 100644 --- a/src/app/accountlistmodel.h +++ b/src/app/accountlistmodel.h @@ -51,11 +51,9 @@ public: QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; - // reset the model when there's new account added Q_INVOKABLE void reset(); - void updateNotifications(); -protected: +private: using Role = AccountList::Role; }; diff --git a/src/app/avadapter.h b/src/app/avadapter.h index 306ce46d..ce5427fb 100644 --- a/src/app/avadapter.h +++ b/src/app/avadapter.h @@ -20,23 +20,31 @@ #include "qmladapterbase.h" #include "lrcinstance.h" +#include "qtutils.h" +#include "rendererinformationlistmodel.h" #include #include #include -#include - -#include "rendererinformationlistmodel.h" +#include // QML registration +#include // QML registration class AvAdapter final : public QmlAdapterBase { Q_OBJECT + QML_SINGLETON + QML_PROPERTY(bool, muteCamera) QML_RO_PROPERTY(QStringList, windowsNames) QML_RO_PROPERTY(QList, windowsIds) QML_RO_PROPERTY(QVariant, renderersInfoList) public: + static AvAdapter* create(QQmlEngine*, QJSEngine*) + { + return new AvAdapter(qApp->property("LRCInstance").value()); + } + explicit AvAdapter(LRCInstance* instance, QObject* parent = nullptr); ~AvAdapter() = default; diff --git a/src/app/avatarregistry.h b/src/app/avatarregistry.h index 2133f6c3..b224e20f 100644 --- a/src/app/avatarregistry.h +++ b/src/app/avatarregistry.h @@ -20,13 +20,22 @@ #include #include +#include // QML registration +#include // 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()); + } + explicit AvatarRegistry(LRCInstance* instance, QObject* parent = nullptr); ~AvatarRegistry() = default; diff --git a/src/app/calladapter.cpp b/src/app/calladapter.cpp index af49bdef..6ac9b3d5 100644 --- a/src/app/calladapter.cpp +++ b/src/app/calladapter.cpp @@ -26,8 +26,8 @@ #include "calladapter.h" #include "systemtray.h" -#include "qmlregister.h" #include "appsettingsmanager.h" +#include "pttlistener.h" #include #include @@ -45,19 +45,15 @@ CallAdapter::CallAdapter(AppSettingsManager* settingsManager, : QmlAdapterBase(instance, parent) , systemTray_(systemTray) , callInformationListModel_(std::make_unique()) - , 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(); 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_); diff --git a/src/app/calladapter.h b/src/app/calladapter.h index 0b00bc78..cefeee01 100644 --- a/src/app/calladapter.h +++ b/src/app/calladapter.h @@ -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 #include #include #include - -#include "callInformationListModel.h" +#include // QML registration +#include // 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(), + qApp->property("SystemTray").value(), + qApp->property("LRCInstance").value()); + } 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 overlayModel_; VectorString currentConfSubcalls_; std::unique_ptr callInformationListModel_; diff --git a/src/app/calloverlaymodel.h b/src/app/calloverlaymodel.h index a93145f9..934802bc 100644 --- a/src/app/calloverlaymodel.h +++ b/src/app/calloverlaymodel.h @@ -21,8 +21,6 @@ #include "lrcinstance.h" #include "qtutils.h" -#include "mainapplication.h" - #include "pttlistener.h" #include diff --git a/src/app/commoncomponents/ChangePttKeyPopup.qml b/src/app/commoncomponents/ChangePttKeyPopup.qml index 9cdd0bad..f5c17bf4 100644 --- a/src/app/commoncomponents/ChangePttKeyPopup.qml +++ b/src/app/commoncomponents/ChangePttKeyPopup.qml @@ -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; } } diff --git a/src/app/connectioninfolistmodel.cpp b/src/app/connectioninfolistmodel.cpp index 1d904c73..9ca1503e 100644 --- a/src/app/connectioninfolistmodel.cpp +++ b/src/app/connectioninfolistmodel.cpp @@ -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(); -} \ No newline at end of file +} diff --git a/src/app/connectioninfolistmodel.h b/src/app/connectioninfolistmodel.h index 4b049af1..47deb122 100644 --- a/src/app/connectioninfolistmodel.h +++ b/src/app/connectioninfolistmodel.h @@ -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 peerIds_; QMap>> peerData_; void aggregateData(); diff --git a/src/app/contactadapter.cpp b/src/app/contactadapter.cpp index 91d69956..b2e6f6b7 100644 --- a/src/app/contactadapter.cpp +++ b/src/app/contactadapter.cpp @@ -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() { diff --git a/src/app/contactadapter.h b/src/app/contactadapter.h index 80b67be4..b8931f3c 100644 --- a/src/app/contactadapter.h +++ b/src/app/contactadapter.h @@ -20,12 +20,12 @@ #include "qmladapterbase.h" #include "smartlistmodel.h" -#include "conversationlistmodel.h" -#include "connectioninfolistmodel.h" #include #include #include +#include // QML registration +#include // 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()); + } + 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_; QScopedPointer selectableProxyModel_; - QScopedPointer connectionInfoListModel_; QStringList defaultModerators_; diff --git a/src/app/conversationlistmodel.cpp b/src/app/conversationlistmodel.cpp index ac809463..b914f731 100644 --- a/src/app/conversationlistmodel.cpp +++ b/src/app/conversationlistmodel.cpp @@ -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 diff --git a/src/app/conversationsadapter.cpp b/src/app/conversationsadapter.cpp index 857ed5ee..8e9ecaaa 100644 --- a/src/app/conversationsadapter.cpp +++ b/src/app/conversationsadapter.cpp @@ -24,7 +24,9 @@ #include "qmlregister.h" #include "qtutils.h" +#ifdef Q_OS_LINUX #include "namedirectory.h" +#endif #include #include @@ -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()); diff --git a/src/app/conversationsadapter.h b/src/app/conversationsadapter.h index 59d970d5..0a1114f3 100644 --- a/src/app/conversationsadapter.h +++ b/src/app/conversationsadapter.h @@ -26,19 +26,36 @@ #include #include +#include // QML registration +#include // 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(), + qApp->property("LRCInstance").value(), + qApp->property("ConvListProxyModel").value(), + qApp->property("ConvSearchListProxyModel").value()); + } + 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 convSrcModel_; - QScopedPointer convModel_; + ConversationListProxyModel* convModel_; QScopedPointer searchSrcModel_; - QScopedPointer searchModel_; + SelectableListProxyModel* searchModel_; std::atomic_bool selectFirst_ {false}; }; diff --git a/src/app/currentaccount.h b/src/app/currentaccount.h index c3f97b6b..f2f5e733 100644 --- a/src/app/currentaccount.h +++ b/src/app/currentaccount.h @@ -24,6 +24,8 @@ #include #include +#include // QML registration +#include // 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(), + qApp->property("AppSettingsManager").value()); + } + explicit CurrentAccount(LRCInstance* lrcInstance, AppSettingsManager* settingsManager, QObject* parent = nullptr); diff --git a/src/app/currentaccounttomigrate.cpp b/src/app/currentaccounttomigrate.cpp index 21c01223..e670d72d 100644 --- a/src/app/currentaccounttomigrate.cpp +++ b/src/app/currentaccounttomigrate.cpp @@ -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(); -} \ No newline at end of file +} diff --git a/src/app/currentaccounttomigrate.h b/src/app/currentaccounttomigrate.h index 3d05720a..b1b075c7 100644 --- a/src/app/currentaccounttomigrate.h +++ b/src/app/currentaccounttomigrate.h @@ -19,15 +19,19 @@ #pragma once -#include - #include "qtutils.h" +#include +#include // QML registration +#include // 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()); + } + explicit CurrentAccountToMigrate(LRCInstance* lrcInstance, QObject* parent = nullptr); - ~CurrentAccountToMigrate(); + ~CurrentAccountToMigrate() = default; Q_INVOKABLE void removeCurrentAccountToMigrate(); diff --git a/src/app/currentcall.cpp b/src/app/currentcall.cpp index 2b5ecae8..7ef2af43 100644 --- a/src/app/currentcall.cpp +++ b/src/app/currentcall.cpp @@ -16,7 +16,8 @@ */ #include "currentcall.h" -#include "qmlregister.h" + +#include "callparticipantsmodel.h" #include #include @@ -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(); connect(lrcInstance_, &LRCInstance::currentAccountIdChanged, diff --git a/src/app/currentcall.h b/src/app/currentcall.h index 921e3a91..f9688747 100644 --- a/src/app/currentcall.h +++ b/src/app/currentcall.h @@ -19,14 +19,18 @@ #include "lrcinstance.h" #include "qtutils.h" -#include "callparticipantsmodel.h" #include #include +#include // QML registration +#include // 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()); + } + 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 participantsModel_; + CallParticipantsModel* participantsModel_; }; diff --git a/src/app/currentconversation.cpp b/src/app/currentconversation.cpp index 1b2354c2..8fa2b74c 100644 --- a/src/app/currentconversation.cpp +++ b/src/app/currentconversation.cpp @@ -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, diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h index b42b9c7f..44eb3234 100644 --- a/src/app/currentconversation.h +++ b/src/app/currentconversation.h @@ -23,6 +23,8 @@ #include #include +#include // QML registration +#include // 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()); + } + 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(); }; diff --git a/src/app/currentconversationmembers.h b/src/app/currentconversationmembers.h index e0485c23..ea8bb61e 100644 --- a/src/app/currentconversationmembers.h +++ b/src/app/currentconversationmembers.h @@ -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_; -}; \ No newline at end of file +}; diff --git a/src/app/imagedownloader.h b/src/app/imagedownloader.h index f5b71741..e0cf725f 100644 --- a/src/app/imagedownloader.h +++ b/src/app/imagedownloader.h @@ -18,16 +18,28 @@ #pragma once #include "networkmanager.h" +#include "connectivitymonitor.h" #include "qtutils.h" +#include // QML registration +#include // 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()); + } + 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); -}; \ No newline at end of file +}; diff --git a/src/app/mainapplication.cpp b/src/app/mainapplication.cpp index 144bfbc6..330412dc 100644 --- a/src/app/mainapplication.cpp +++ b/src/app/mainapplication.cpp @@ -26,6 +26,8 @@ #include "connectivitymonitor.h" #include "systemtray.h" #include "videoprovider.h" +#include "previewengine.h" +#include "conversationlistmodel.h" #include @@ -42,7 +44,6 @@ #include #include -#include #include #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); diff --git a/src/app/mainapplication.h b/src/app/mainapplication.h index 5c039e88..3bbba00a 100644 --- a/src/app/mainapplication.h +++ b/src/app/mainapplication.h @@ -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_; }; diff --git a/src/app/mainview/components/AddMemberPanel.qml b/src/app/mainview/components/AddMemberPanel.qml index 27826eb6..2874dc27 100644 --- a/src/app/mainview/components/AddMemberPanel.qml +++ b/src/app/mainview/components/AddMemberPanel.qml @@ -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 diff --git a/src/app/mainview/components/ConversationExtrasPanel.qml b/src/app/mainview/components/ConversationExtrasPanel.qml index e319f4e7..5fa4d1be 100644 --- a/src/app/mainview/components/ConversationExtrasPanel.qml +++ b/src/app/mainview/components/ConversationExtrasPanel.qml @@ -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(); } } diff --git a/src/app/mainview/components/ConversationListView.qml b/src/app/mainview/components/ConversationListView.qml index 9a23671c..d0b1fdcd 100644 --- a/src/app/mainview/components/ConversationListView.qml +++ b/src/app/mainview/components/ConversationListView.qml @@ -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 diff --git a/src/app/mainview/components/PluginHandlerPicker.qml b/src/app/mainview/components/PluginHandlerPicker.qml index ce21b7e8..c0a4e406 100644 --- a/src/app/mainview/components/PluginHandlerPicker.qml +++ b/src/app/mainview/components/PluginHandlerPicker.qml @@ -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); } } diff --git a/src/app/mainview/components/SidePanel.qml b/src/app/mainview/components/SidePanel.qml index 0037f7eb..01ce3468 100644 --- a/src/app/mainview/components/SidePanel.qml +++ b/src/app/mainview/components/SidePanel.qml @@ -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 diff --git a/src/app/mainview/components/SwarmDetailsPanel.qml b/src/app/mainview/components/SwarmDetailsPanel.qml index 2f0d5a59..ef439fe1 100644 --- a/src/app/mainview/components/SwarmDetailsPanel.qml +++ b/src/app/mainview/components/SwarmDetailsPanel.qml @@ -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 diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp index 823300e0..9bc034e6 100644 --- a/src/app/messagesadapter.cpp +++ b/src/app/messagesadapter.cpp @@ -26,6 +26,7 @@ #include "appsettingsmanager.h" #include "qtutils.h" #include "messageparser.h" +#include "previewengine.h" #include diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h index e333e66b..1deb1608 100644 --- a/src/app/messagesadapter.h +++ b/src/app/messagesadapter.h @@ -20,16 +20,18 @@ #include "lrcinstance.h" #include "qmladapterbase.h" + #include "previewengine.h" +#include "messageparser.h" +#include "appsettingsmanager.h" #include #include #include #include - -class AppSettingsManager; -class MessageParser; +#include // QML registration +#include // 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(), + qApp->property("PreviewEngine").value(), + qApp->property("LRCInstance").value()); + } + 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, diff --git a/src/app/pluginadapter.cpp b/src/app/pluginadapter.cpp index bcdd53c5..ab0864e0 100644 --- a/src/app/pluginadapter.cpp +++ b/src/app/pluginadapter.cpp @@ -24,7 +24,8 @@ #include "networkmanager.h" #include "lrcinstance.h" #include "appsettingsmanager.h" -#include "qmlregister.h" + +#include "api/pluginmodel.h" #include #include @@ -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(); + pluginStoreListModel_ = qApp->property("PluginStoreListModel").value(); + updateHandlersListCount(); connect(&lrcInstance_->pluginModel(), &lrc::api::PluginModel::modelUpdated, diff --git a/src/app/pluginadapter.h b/src/app/pluginadapter.h index da93da95..38e8c62a 100644 --- a/src/app/pluginadapter.h +++ b/src/app/pluginadapter.h @@ -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 #include #include +#include // QML registration +#include // 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(), + qApp->property("AppSettingsManager").value()); + } + 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_; diff --git a/src/app/positionmanager.h b/src/app/positionmanager.h index 42c9ca35..88cc821c 100644 --- a/src/app/positionmanager.h +++ b/src/app/positionmanager.h @@ -27,16 +27,27 @@ #include #include #include +#include // QML registration +#include // 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(), + qApp->property("SystemTray").value(), + qApp->property("LRCInstance").value()); + } + explicit PositionManager(AppSettingsManager* settingsManager, SystemTray* systemTray, LRCInstance* instance, diff --git a/src/app/previewengine.cpp b/src/app/previewengine.cpp index 8aa570eb..6d331026 100644 --- a/src/app/previewengine.cpp +++ b/src/app/previewengine.cpp @@ -17,32 +17,72 @@ #include "previewengine.h" +#include "htmlparser.h" + #include #include -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& tags, const QString& value); + QString getTitle(const QList& metaTags); + QString getDescription(const QList& metaTags); + QString getImage(const QList& 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& tags, const QString& value) +PreviewEngine::Parser::getTagContent(const QList& 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& tags, const QString& value) } QString -PreviewEngine::getTitle(const QList& metaTags) +PreviewEngine::Parser::getTitle(const QList& metaTags) { // Try with opengraph/twitter props QString title = getTagContent(metaTags, "title"); @@ -73,7 +113,7 @@ PreviewEngine::getTitle(const QList& metaTags) } QString -PreviewEngine::getDescription(const QList& metaTags) +PreviewEngine::Parser::getDescription(const QList& metaTags) { // Try with og/twitter props QString desc = getTagContent(metaTags, "description"); @@ -84,7 +124,7 @@ PreviewEngine::getDescription(const QList& metaTags) } QString -PreviewEngine::getImage(const QList& metaTags) +PreviewEngine::Parser::getImage(const QList& metaTags) { // Try with og/twitter props QString image = getTagContent(metaTags, "image"); @@ -101,25 +141,25 @@ PreviewEngine::getImage(const QList& 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 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 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" diff --git a/src/app/previewengine.h b/src/app/previewengine.h index 0ba009e1..660a960c 100644 --- a/src/app/previewengine.h +++ b/src/app/previewengine.h @@ -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& tags, const QString& value); - QString getTitle(const QList& metaTags); - QString getDescription(const QList& metaTags); - QString getImage(const QList& metaTags); - - static const QRegularExpression newlineRe; - - QThread* thread_; + class Parser; + QScopedPointer parser_; + QThread* parserThread_; }; diff --git a/src/app/qmlregister.cpp b/src/app/qmlregister.cpp index a0d1c816..96480c2b 100644 --- a/src/app/qmlregister.cpp +++ b/src/app/qmlregister.cpp @@ -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(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(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(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(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(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(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(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(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(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(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 diff --git a/src/app/qmlregister.h b/src/app/qmlregister.h index 510a010a..3e0bc38a 100644 --- a/src/app/qmlregister.h +++ b/src/app/qmlregister.h @@ -46,6 +46,9 @@ Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") } // namespace dummy // clang-format off +#define QML_REGISTERSINGLETON_TYPE(NS, T) \ + qmlRegisterSingletonType(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::type; \ @@ -67,6 +70,7 @@ void registerTypes(QQmlEngine* engine, SystemTray* systemTray, AppSettingsManager* appSettingsManager, ConnectivityMonitor* connectivityMonitor, + PreviewEngine* previewEngine, ScreenInfo* screenInfo, QObject* app); } diff --git a/src/app/settingsview/components/CallSettingsPage.qml b/src/app/settingsview/components/CallSettingsPage.qml index b637c979..431d554f 100644 --- a/src/app/settingsview/components/CallSettingsPage.qml +++ b/src/app/settingsview/components/CallSettingsPage.qml @@ -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); }); } } diff --git a/src/app/settingsview/components/ConnectionMonitoringTable.qml b/src/app/settingsview/components/ConnectionMonitoringTable.qml index 1337a74a..9819cc46 100644 --- a/src/app/settingsview/components/ConnectionMonitoringTable.qml +++ b/src/app/settingsview/components/ConnectionMonitoringTable.qml @@ -14,13 +14,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + 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; } } diff --git a/src/app/tipsmodel.h b/src/app/tipsmodel.h index 4cac64bc..fa3af435 100644 --- a/src/app/tipsmodel.h +++ b/src/app/tipsmodel.h @@ -17,12 +17,14 @@ */ #pragma once -#include "lrcinstance.h" #include "appsettingsmanager.h" -#include "qtutils.h" + +#include "typedefs.h" #include #include +#include // QML registration +#include // 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()); + } + 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_; -}; \ No newline at end of file +}; diff --git a/src/app/utilsadapter.h b/src/app/utilsadapter.h index 2f1b6abb..5cb5c5a8 100644 --- a/src/app/utilsadapter.h +++ b/src/app/utilsadapter.h @@ -30,12 +30,15 @@ #include #include +#include // QML registration +#include // QML registration #if __has_include() #include #endif #if defined(WIN32) && __has_include() +#define _SILENCE_CLANG_COROUTINE_MESSAGE #include #define WATCHSYSTEMTHEME __has_include() @@ -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(), + qApp->property("SystemTray").value(), + qApp->property("LRCInstance").value()); + } + explicit UtilsAdapter(AppSettingsManager* settingsManager, SystemTray* systemTray, LRCInstance* instance, diff --git a/src/app/videodevices.cpp b/src/app/videodevices.cpp index 133b1172..a986c4f4 100644 --- a/src/app/videodevices.cpp +++ b/src/app/videodevices.cpp @@ -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) diff --git a/src/app/videodevices.h b/src/app/videodevices.h index 5232e927..546381d7 100644 --- a/src/app/videodevices.h +++ b/src/app/videodevices.h @@ -21,9 +21,9 @@ #include "lrcinstance.h" #include "qtutils.h" -#include "api/devicemodel.h" - #include +#include // QML registration +#include // 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()); + } + explicit VideoDevices(LRCInstance* lrcInstance, QObject* parent = nullptr); ~VideoDevices() = default; diff --git a/src/app/wizardview/WizardView.qml b/src/app/wizardview/WizardView.qml index 2a1f9b73..995de991 100644 --- a/src/app/wizardview/WizardView.qml +++ b/src/app/wizardview/WizardView.qml @@ -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 diff --git a/src/app/wizardviewstepmodel.cpp b/src/app/wizardviewstepmodel.cpp index fc48c1ad..08038f61 100644 --- a/src/app/wizardviewstepmodel.cpp +++ b/src/app/wizardviewstepmodel.cpp @@ -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_); } } diff --git a/src/app/wizardviewstepmodel.h b/src/app/wizardviewstepmodel.h index c21decea..4928325e 100644 --- a/src/app/wizardviewstepmodel.h +++ b/src/app/wizardviewstepmodel.h @@ -18,11 +18,13 @@ #pragma once +#include "qtutils.h" + #include #include #include - -#include "qtutils.h" +#include // QML registration +#include // 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(), + qApp->property("AppSettingsManager") + .value()); + } + 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_; }; diff --git a/tests/qml/main.cpp b/tests/qml/main.cpp index a604a5c7..d25e81ce 100644 --- a/tests/qml/main.cpp +++ b/tests/qml/main.cpp @@ -136,6 +136,7 @@ public Q_SLOTS: systemTray_.get(), settingsManager_.get(), connectivityMonitor_.get(), + previewEngine_.get(), &screenInfo_, this); diff --git a/tests/qml/src/TestWrapper.qml b/tests/qml/src/TestWrapper.qml index 3d22ce6c..d8f4510d 100644 --- a/tests/qml/src/TestWrapper.qml +++ b/tests/qml/src/TestWrapper.qml @@ -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 } } diff --git a/tests/qml/src/tst_CallMessageDelegate.qml b/tests/qml/src/tst_CallMessageDelegate.qml index 4d68c32a..a7b022b7 100644 --- a/tests/qml/src/tst_CallMessageDelegate.qml +++ b/tests/qml/src/tst_CallMessageDelegate.qml @@ -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) + } } } -} \ No newline at end of file +} diff --git a/tests/qml/src/tst_OngoingCallPage.qml b/tests/qml/src/tst_OngoingCallPage.qml index 62f9a375..0485d61c 100644 --- a/tests/qml/src/tst_OngoingCallPage.qml +++ b/tests/qml/src/tst_OngoingCallPage.qml @@ -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) + } } } -} \ No newline at end of file +} diff --git a/tests/qml/src/tst_WelcomePage.qml b/tests/qml/src/tst_WelcomePage.qml index d7cc9588..3acefa89 100644 --- a/tests/qml/src/tst_WelcomePage.qml +++ b/tests/qml/src/tst_WelcomePage.qml @@ -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")