From b106eafb0edab2e596a997592e75a81396f8f36c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 12 Jan 2022 19:09:46 +0100 Subject: [PATCH 01/17] Initial qml root window --- resources/qml/PrivacyScreen.qml | 7 +- resources/qml/Root.qml | 23 +- resources/qml/components/FlatButton.qml | 4 +- resources/qml/pages/WelcomePage.qml | 76 ++++++ resources/res.qrc | 1 + src/Cache.cpp | 2 +- src/ChatPage.cpp | 40 ++- src/ChatPage.h | 4 +- src/MainWindow.cpp | 349 +++++++++++++++++------- src/MainWindow.h | 30 +- src/TrayIcon.cpp | 7 +- src/TrayIcon.h | 2 +- src/UserSettingsPage.cpp | 24 +- src/main.cpp | 9 +- src/timeline/InputBar.cpp | 6 +- src/timeline/RoomlistModel.cpp | 5 +- src/timeline/TimelineModel.cpp | 4 +- src/timeline/TimelineViewManager.cpp | 202 +------------- src/timeline/TimelineViewManager.h | 19 -- src/ui/NhekoGlobalObject.cpp | 2 +- src/ui/UIA.cpp | 7 +- 21 files changed, 426 insertions(+), 397 deletions(-) create mode 100644 resources/qml/pages/WelcomePage.qml diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml index e6286bc6..6ad2a557 100644 --- a/resources/qml/PrivacyScreen.qml +++ b/resources/qml/PrivacyScreen.qml @@ -5,6 +5,7 @@ import QtGraphicalEffects 1.0 import QtQuick 2.12 +import QtQuick.Window 2.2 import im.nheko 1.0 Item { @@ -15,7 +16,7 @@ Item { Connections { function onFocusChanged() { - if (TimelineManager.isWindowFocused) { + if (MainWindow.active) { screenSaverTimer.stop(); screenSaver.state = "Invisible"; } else { @@ -25,14 +26,14 @@ Item { } } - target: TimelineManager + target: MainWindow } Timer { id: screenSaverTimer interval: screenTimeout * 1000 - running: true + running: !MainWindow.active onTriggered: { screenSaver.state = "Visible"; } diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index e4b164e4..004169e1 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -17,10 +17,12 @@ import QtQuick.Window 2.15 import im.nheko 1.0 import im.nheko.EmojiModel 1.0 -Page { +Pane { id: timelineRoot palette: Nheko.colors + background: null + padding: 0 FontMetrics { id: fontMetrics @@ -157,7 +159,6 @@ Page { sequence: "Ctrl+K" onActivated: { var quickSwitch = quickSwitcherComponent.createObject(timelineRoot); - TimelineManager.focusTimeline(); quickSwitch.open(); } } @@ -165,7 +166,6 @@ Page { Shortcut { // Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit sequences: ["Alt+A", "Ctrl+Shift+A"] - context: Qt.ApplicationShortcut onActivated: Rooms.nextRoomWithActivity() } @@ -366,9 +366,24 @@ Page { id: mainWindow anchors.fill: parent - initialItem: ChatPage { + initialItem: WelcomePage { //anchors.fill: parent } } + Component { + id: chatPage + + ChatPage { + } + } + + Connections { + function onSwitchToChatPage() { + console.log("AAAA"); + mainWindow.replace(chatPage); + } + target: MainWindow + } + } diff --git a/resources/qml/components/FlatButton.qml b/resources/qml/components/FlatButton.qml index 8ca3f104..72184d28 100644 --- a/resources/qml/components/FlatButton.qml +++ b/resources/qml/components/FlatButton.qml @@ -12,7 +12,7 @@ import im.nheko 1.0 Button { id: control - implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.5) + implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70) implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight) hoverEnabled: true @@ -42,7 +42,7 @@ Button { background: Rectangle { //height: control.contentItem.implicitHeight * 2 //width: control.contentItem.implicitWidth * 2 - radius: height / 6 + radius: height / 8 color: Qt.lighter(Nheko.colors.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) } diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml new file mode 100644 index 00000000..d95b6104 --- /dev/null +++ b/resources/qml/pages/WelcomePage.qml @@ -0,0 +1,76 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.15 +import im.nheko 1.0 +import "../components/" + +ColumnLayout { + FontMetrics { + id: fontMetrics + } + + Shortcut { + sequence: StandardKey.Quit + onActivated: Qt.quit() + } + + Item { + Layout.fillHeight: true + } + + Image { + Layout.alignment: Qt.AlignHCenter + source: "qrc:/logos/splash.png" + height: 256 + width: 256 + } + + Label { + Layout.margins: Nheko.paddingLarge + Layout.bottomMargin: 0 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.") + color: Nheko.colors.text + font.pointSize: fontMetrics.font.pointSize*2 + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + } + Label { + Layout.margins: Nheko.paddingLarge + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Enjoy your stay!") + color: Nheko.colors.text + font.pointSize: fontMetrics.font.pointSize*1.5 + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + } + + RowLayout { + Item { + Layout.fillWidth: true + } + FlatButton { + Layout.margins: Nheko.paddingLarge + Layout.alignment: Qt.AlignHCenter + text: qsTr("REGISTER") + onClicked: { + } + } + FlatButton { + Layout.margins: Nheko.paddingLarge + Layout.alignment: Qt.AlignHCenter + text: qsTr("LOGIN") + onClicked: { + } + } + Item { + Layout.fillWidth: true + } +} + Item { + Layout.fillHeight: true + } +} diff --git a/resources/res.qrc b/resources/res.qrc index a2ee393f..0222619b 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -110,6 +110,7 @@ qml/TypingIndicator.qml qml/NotificationWarning.qml qml/pages/UserSettingsPage.qml + qml/pages/WelcomePage.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml diff --git a/src/Cache.cpp b/src/Cache.cpp index d1723d98..c15d2f4b 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -325,7 +325,7 @@ static void fatalSecretError() { QMessageBox::critical( - ChatPage::instance(), + nullptr, QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), QCoreApplication::translate( "SecretStorage", diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index bfaa6389..fc90e6c7 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -44,8 +44,8 @@ Q_DECLARE_METATYPE(mtx::presence::PresenceState) Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription) Q_DECLARE_METATYPE(SecretsToDecrypt) -ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) - : QWidget(parent) +ChatPage::ChatPage(QSharedPointer userSettings, QObject *parent) + : QObject(parent) , isConnected_(true) , userSettings_{userSettings} , notificationsManager(this) @@ -61,14 +61,8 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) qRegisterMetaType(); qRegisterMetaType(); - topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setContentsMargins(0, 0, 0, 0); - view_manager_ = new TimelineViewManager(callManager_, this); - topLayout_->addWidget(view_manager_->getWidget()); - connect(this, &ChatPage::downloadedSecrets, this, @@ -154,7 +148,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) [this](const QString &roomid, const QString &eventid) { Q_UNUSED(eventid) view_manager_->rooms()->setCurrentRoom(roomid); - activateWindow(); + MainWindow::instance()->requestActivate(); }); connect(¬ificationsManager, &NotificationsManager::sendNotificationReply, @@ -162,15 +156,13 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) [this](const QString &roomid, const QString &eventid, const QString &body) { view_manager_->rooms()->setCurrentRoom(roomid); view_manager_->queueReply(roomid, eventid, body); - activateWindow(); + MainWindow::instance()->requestActivate(); }); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() { // ensure the qml context is shutdown before we destroy all other singletons // Otherwise Qml will try to access the room list or settings, after they have been // destroyed - topLayout_->removeWidget(view_manager_->getWidget()); - delete view_manager_->getWidget(); }); connect( @@ -201,7 +193,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) // TODO: Replace this once we have proper pushrules support. This is a horrible hack if (prevNotificationCount < notificationCount) { if (userSettings_->hasAlertOnNotification()) - QApplication::alert(this); + MainWindow::instance()->alert(0); } prevNotificationCount = notificationCount; @@ -331,7 +323,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) } else if (cacheVersion == cache::CacheVersion::Older) { if (!cache::runMigrations()) { QMessageBox::critical( - this, + nullptr, tr("Cache migration failed!"), tr("Migrating the cache to the current version failed. " "This can have different reasons. Please open an " @@ -344,7 +336,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) return; } else if (cacheVersion == cache::CacheVersion::Newer) { QMessageBox::critical( - this, + nullptr, tr("Incompatible cache version"), tr("The cache on your disk is newer than this version of Nheko " "supports. Please update Nheko or clear your cache.")); @@ -690,7 +682,7 @@ ChatPage::joinRoomVia(const std::string &room_id, if (promptForConfirmation && QMessageBox::Yes != QMessageBox::question( - this, + nullptr, tr("Confirm join"), tr("Do you really want to join %1?").arg(QString::fromStdString(room_id)))) return; @@ -776,7 +768,7 @@ ChatPage::inviteUser(QString userid, QString reason) { auto room = currentRoom(); - if (QMessageBox::question(this, + if (QMessageBox::question(nullptr, tr("Confirm invite"), tr("Do you really want to invite %1 (%2)?") .arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes) @@ -800,7 +792,7 @@ ChatPage::kickUser(QString userid, QString reason) { auto room = currentRoom(); - if (QMessageBox::question(this, + if (QMessageBox::question(nullptr, tr("Confirm kick"), tr("Do you really want to kick %1 (%2)?") .arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes) @@ -825,7 +817,7 @@ ChatPage::banUser(QString userid, QString reason) auto room = currentRoom(); if (QMessageBox::question( - this, + nullptr, tr("Confirm ban"), tr("Do you really want to ban %1 (%2)?").arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes) @@ -849,7 +841,7 @@ ChatPage::unbanUser(QString userid, QString reason) { auto room = currentRoom(); - if (QMessageBox::question(this, + if (QMessageBox::question(nullptr, tr("Confirm unban"), tr("Do you really want to unban %1 (%2)?") .arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes) @@ -1083,7 +1075,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio const SecretsToDecrypt &secrets) { QString text = QInputDialog::getText( - ChatPage::instance(), + nullptr, QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"), keyDesc.name.empty() ? QCoreApplication::translate( @@ -1115,7 +1107,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio if (!decryptionKey) { QMessageBox::information( - ChatPage::instance(), + nullptr, QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"), QCoreApplication::translate("CrossSigningSecrets", "Failed to decrypt secrets with the " @@ -1209,7 +1201,7 @@ ChatPage::startChat(QString userid) if (QMessageBox::Yes != QMessageBox::question( - this, + nullptr, tr("Confirm invite"), tr("Do you really want to start a private chat with %1?").arg(userid))) return; @@ -1395,7 +1387,7 @@ ChatPage::handleMatrixUri(const QUrl &uri) bool ChatPage::isRoomActive(const QString &room_id) { - return isActiveWindow() && currentRoom() == room_id; + return MainWindow::instance()->isActive() && currentRoom() == room_id; } QString diff --git a/src/ChatPage.h b/src/ChatPage.h index ae55c923..5e3b509d 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -51,12 +51,12 @@ struct Rooms; using SecretsToDecrypt = std::map; -class ChatPage : public QWidget +class ChatPage : public QObject { Q_OBJECT public: - ChatPage(QSharedPointer userSettings, QWidget *parent = nullptr); + ChatPage(QSharedPointer userSettings, QObject *parent = nullptr); // Initialize all the components of the UI. void bootstrap(QString userid, QString homeserver, QString token); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5bfce89e..5e7fe6ce 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -13,77 +13,108 @@ #include #include +#include "BlurhashProvider.h" #include "Cache.h" #include "Cache_p.h" #include "ChatPage.h" +#include "Clipboard.h" +#include "ColorImageProvider.h" +#include "CombinedImagePackModel.h" +#include "CompletionProxyModel.h" #include "Config.h" +#include "EventAccessors.h" +#include "ImagePackListModel.h" +#include "InviteesModel.h" #include "JdenticonProvider.h" #include "Logging.h" #include "LoginPage.h" #include "MainWindow.h" #include "MatrixClient.h" #include "MemberList.h" +#include "MxcImageProvider.h" +#include "ReadReceiptsModel.h" #include "RegisterPage.h" +#include "RoomDirectoryModel.h" +#include "RoomsModel.h" +#include "SingleImagePackModel.h" #include "TrayIcon.h" #include "UserSettingsPage.h" +#include "UsersModel.h" #include "Utils.h" #include "WelcomePage.h" +#include "emoji/EmojiModel.h" +#include "emoji/Provider.h" +#include "encryption/DeviceVerificationFlow.h" +#include "encryption/SelfVerificationStatus.h" +#include "timeline/DelegateChooser.h" +#include "timeline/TimelineViewManager.h" #include "ui/LoadingIndicator.h" +#include "ui/MxcAnimatedImage.h" +#include "ui/MxcMediaProxy.h" +#include "ui/NhekoCursorShape.h" +#include "ui/NhekoDropArea.h" +#include "ui/NhekoGlobalObject.h" #include "ui/OverlayModal.h" #include "ui/SnackBar.h" +#include "ui/UIA.h" #include "voip/WebRTCSession.h" #include "dialogs/CreateRoom.h" +Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) +Q_DECLARE_METATYPE(std::vector) +Q_DECLARE_METATYPE(std::vector) + MainWindow *MainWindow::instance_ = nullptr; -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) +MainWindow::MainWindow(QWindow *parent) + : QQuickView(parent) , userSettings_{UserSettings::instance()} { instance_ = this; - QMainWindow::setWindowTitle(0); + MainWindow::setWindowTitle(0); setObjectName(QStringLiteral("MainWindow")); - - modal_ = new OverlayModal(this); - + setResizeMode(QQuickView::SizeRootObjectToView); + setMinimumHeight(400); + setMinimumWidth(400); restoreWindowSize(); - QFont font; - font.setStyleStrategy(QFont::PreferAntialias); - setFont(font); + chat_page_ = new ChatPage(userSettings_, this); + registerQmlTypes(); + + setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color()); + setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml"))); + // modal_ = new OverlayModal(this); + + + // QFont font; + // font.setStyleStrategy(QFont::PreferAntialias); + // setFont(font); trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this); - welcome_page_ = new WelcomePage(this); - login_page_ = new LoginPage(this); - register_page_ = new RegisterPage(this); - chat_page_ = new ChatPage(userSettings_, this); + // welcome_page_ = new WelcomePage(this); + // login_page_ = new LoginPage(this); + // register_page_ = new RegisterPage(this); - // Initialize sliding widget manager. - pageStack_ = new QStackedWidget(this); - pageStack_->addWidget(welcome_page_); - pageStack_->addWidget(login_page_); - pageStack_->addWidget(register_page_); - pageStack_->addWidget(chat_page_); + //// Initialize sliding widget manager. - setCentralWidget(pageStack_); + // connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); + // connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); - connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); - connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); - - connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar); - connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar); - connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); - connect( - register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); - connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + // connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + // connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar); + // connect(register_page_, &RegisterPage::registering, this, + // &MainWindow::showOverlayProgressBar); connect(login_page_, &LoginPage::errorOccurred, this, + // [this]() { removeOverlayProgressBar(); }); connect( + // register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); + // }); + // connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage); - connect( - chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); + // connect( + // chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { @@ -101,15 +132,12 @@ MainWindow::MainWindow(QWidget *parent) connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged); - connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { - http::client()->set_user(res.user_id); - showChatPage(); - }); + // connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { + // http::client()->set_user(res.user_id); + // showChatPage(); + // }); - connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); - - QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); - connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); + // connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); trayIcon_->setVisible(userSettings_->tray()); @@ -133,11 +161,167 @@ MainWindow::MainWindow(QWidget *parent) user_id.toStdString()); } + nhlog::ui()->info("User already signed in, showing chat page"); showChatPage(); } }); } +void +MainWindow::registerQmlTypes() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType>(); + + qRegisterMetaType>(); + + qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, + "im.nheko", + 1, + 0, + "MtxEvent", + QStringLiteral("Can't instantiate enum!")); + qmlRegisterUncreatableMetaObject( + olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!")); + qmlRegisterUncreatableMetaObject(crypto::staticMetaObject, + "im.nheko", + 1, + 0, + "Crypto", + QStringLiteral("Can't instantiate enum!")); + qmlRegisterUncreatableMetaObject(verification::staticMetaObject, + "im.nheko", + 1, + 0, + "VerificationStatus", + QStringLiteral("Can't instantiate enum!")); + + qmlRegisterType("im.nheko", 1, 0, "DelegateChoice"); + qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); + qmlRegisterType("im.nheko", 1, 0, "NhekoDropArea"); + qmlRegisterType("im.nheko", 1, 0, "CursorShape"); + qmlRegisterType("im.nheko", 1, 0, "MxcAnimatedImage"); + qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); + qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "DeviceVerificationFlow", + QStringLiteral("Can't create verification flow from QML!")); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "UserProfileModel", + QStringLiteral("UserProfile needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "MemberList", + QStringLiteral("MemberList needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "RoomSettingsModel", + QStringLiteral("Room Settings needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType( + "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "ImagePackListModel", + QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "SingleImagePackModel", + QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "InviteesModel", + QStringLiteral("InviteesModel needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "ReadReceiptsProxy", + QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side")); + + qmlRegisterSingletonType( + "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new Clipboard(); + }); + qmlRegisterSingletonType( + "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new Nheko(); + }); + qmlRegisterSingletonType( + "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new UserSettingsModel(); + }); + + qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", userSettings_.data()); + + qRegisterMetaType(); + qRegisterMetaType>(); + + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "FilteredCommunitiesModel", + QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel")); + + qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel"); + qmlRegisterUncreatableType( + "im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models")); + qmlRegisterUncreatableMetaObject(emoji::staticMetaObject, + "im.nheko.EmojiModel", + 1, + 0, + "EmojiCategory", + QStringLiteral("Error: Only enums")); + + qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); + + qmlRegisterSingletonType( + "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * { + auto ptr = new SelfVerificationStatus(); + QObject::connect(ChatPage::instance(), + &ChatPage::initializeEmptyViews, + ptr, + &SelfVerificationStatus::invalidate); + return ptr; + }); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", this); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance()); + qmlRegisterSingletonInstance( + "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager()); + + imgProvider = new MxcImageProvider(); + engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider); + engine()->addImageProvider(QStringLiteral("colorimage"), new ColorImageProvider()); + engine()->addImageProvider(QStringLiteral("blurhash"), new BlurhashProvider()); + if (JdenticonProvider::isAvailable()) + engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider()); +} + void MainWindow::setWindowTitle(int notificationCount) { @@ -148,20 +332,23 @@ MainWindow::setWindowTitle(int notificationCount) if (notificationCount > 0) { name.append(QString{QStringLiteral(" (%1)")}.arg(notificationCount)); } - QMainWindow::setWindowTitle(name); + QQuickView::setTitle(name); } bool MainWindow::event(QEvent *event) { auto type = event->type(); - if (type == QEvent::WindowActivate) { + + if (type == QEvent::Close) { + closeEvent(static_cast(event)); + } else if (type == QEvent::WindowActivate) { emit focusChanged(true); } else if (type == QEvent::WindowDeactivate) { emit focusChanged(false); } - return QMainWindow::event(event); + return QQuickView::event(event); } void @@ -196,19 +383,13 @@ MainWindow::removeOverlayProgressBar() connect(timer, &QTimer::timeout, this, [this, timer]() { timer->deleteLater(); - - if (modal_) - modal_->hide(); - - if (spinner_) - spinner_->stop(); }); // FIXME: Snackbar doesn't work if it's initialized in the constructor. - QTimer::singleShot(0, this, [this]() { - snackBar_ = new SnackBar(this); - connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage); - }); + // QTimer::singleShot(0, this, [this]() { + // snackBar_ = new SnackBar(this); + // connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage); + //}); timer->start(50); } @@ -229,17 +410,14 @@ MainWindow::showChatPage() showOverlayProgressBar(); - pageStack_->setCurrentWidget(chat_page_); - - pageStack_->removeWidget(welcome_page_); - pageStack_->removeWidget(login_page_); - pageStack_->removeWidget(register_page_); - - login_page_->reset(); + // login_page_->reset(); chat_page_->bootstrap(userid, homeserver, token); connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged); connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged); + emit reload(); + nhlog::ui()->info("Switching to chat page"); + emit switchToChatPage(); } void @@ -247,7 +425,7 @@ MainWindow::closeEvent(QCloseEvent *event) { if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { if (QMessageBox::question( - this, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) != + nullptr, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) != QMessageBox::Yes) { event->ignore(); return; @@ -292,20 +470,13 @@ MainWindow::hasActiveUser() void MainWindow::showOverlayProgressBar() { - spinner_ = new LoadingIndicator(this); - spinner_->setFixedHeight(100); - spinner_->setFixedWidth(100); - spinner_->setObjectName(QStringLiteral("ChatPageLoadSpinner")); - spinner_->start(); - - showSolidOverlayModal(spinner_); } void MainWindow::openCreateRoomDialog( std::function callback) { - auto dialog = new dialogs::CreateRoom(this); + auto dialog = new dialogs::CreateRoom(nullptr); connect(dialog, &dialogs::CreateRoom::createRoom, this, @@ -315,50 +486,36 @@ MainWindow::openCreateRoomDialog( } void -MainWindow::showTransparentOverlayModal(QWidget *content, QFlags flags) -{ - modal_->setWidget(content); - modal_->setColor(QColor(30, 30, 30, 150)); - modal_->setDismissible(true); - modal_->setContentAlignment(flags); - modal_->raise(); - modal_->show(); -} +MainWindow::showTransparentOverlayModal(QWidget *, QFlags) +{} void -MainWindow::showSolidOverlayModal(QWidget *content, QFlags flags) +MainWindow::showSolidOverlayModal(QWidget *, QFlags) { - modal_->setWidget(content); - modal_->setColor(QColor(30, 30, 30)); - modal_->setDismissible(false); - modal_->setContentAlignment(flags); - modal_->raise(); - modal_->show(); } bool MainWindow::hasActiveDialogs() const { - return modal_ && modal_->isVisible(); + return false; } bool MainWindow::pageSupportsTray() const { - return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible(); + return false; //! welcome_page_->isVisible() && !login_page_->isVisible() && + //! !register_page_->isVisible(); } void MainWindow::hideOverlay() { - if (modal_) - modal_->hide(); } inline void MainWindow::showDialog(QWidget *dialog) { - utils::centerWidget(dialog, this); + // utils::centerWidget(dialog, this); dialog->raise(); dialog->show(); } @@ -367,23 +524,13 @@ void MainWindow::showWelcomePage() { removeOverlayProgressBar(); - pageStack_->addWidget(welcome_page_); - pageStack_->setCurrentWidget(welcome_page_); } void MainWindow::showLoginPage() { - if (modal_) - modal_->hide(); - - pageStack_->addWidget(login_page_); - pageStack_->setCurrentWidget(login_page_); } void MainWindow::showRegisterPage() -{ - pageStack_->addWidget(register_page_); - pageStack_->setCurrentWidget(register_page_); -} +{} diff --git a/src/MainWindow.h b/src/MainWindow.h index 458eb054..04311e12 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -8,7 +8,7 @@ #include -#include +#include #include #include #include @@ -28,6 +28,7 @@ class OverlayModal; class SnackBar; class TrayIcon; class UserSettings; +class MxcImageProvider; namespace mtx { namespace requests { @@ -42,17 +43,12 @@ class MemberList; class ReCaptcha; } -class MainWindow : public QMainWindow +class MainWindow : public QQuickView { Q_OBJECT - Q_PROPERTY(int x READ x CONSTANT) - Q_PROPERTY(int y READ y CONSTANT) - Q_PROPERTY(int width READ width CONSTANT) - Q_PROPERTY(int height READ height CONSTANT) - public: - explicit MainWindow(QWidget *parent = nullptr); + explicit MainWindow(QWindow *parent = nullptr); static MainWindow *instance() { return instance_; } void saveCurrentWindowSize(); @@ -67,8 +63,10 @@ public: showTransparentOverlayModal(QWidget *content, QFlags flags = Qt::AlignTop | Qt::AlignHCenter); + MxcImageProvider *imageProvider() { return imgProvider; } + protected: - void closeEvent(QCloseEvent *event) override; + void closeEvent(QCloseEvent *event); bool event(QEvent *event) override; private slots: @@ -97,6 +95,9 @@ signals: void reload(); void secretsChanged(); + void switchToChatPage(); + void switchToWelcomePage(); + private: void showDialog(QWidget *dialog); bool hasActiveUser(); @@ -106,6 +107,8 @@ private: //! Check if the current page supports the "minimize to tray" functionality. bool pageSupportsTray() const; + void registerQmlTypes(); + static MainWindow *instance_; //! The initial welcome screen. @@ -114,16 +117,11 @@ private: LoginPage *login_page_; //! The register page. RegisterPage *register_page_; - //! A stacked widget that handles the transitions between widgets. - QStackedWidget *pageStack_; //! The main chat area. ChatPage *chat_page_; QSharedPointer userSettings_; //! Tray icon that shows the unread message count. TrayIcon *trayIcon_; - //! Notifications display. - SnackBar *snackBar_ = nullptr; - //! Overlay modal used to project other widgets. - OverlayModal *modal_ = nullptr; - LoadingIndicator *spinner_ = nullptr; + + MxcImageProvider *imgProvider = nullptr; }; diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp index d83156a4..28da9558 100644 --- a/src/TrayIcon.cpp +++ b/src/TrayIcon.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "TrayIcon.h" @@ -100,7 +101,7 @@ MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State s return result; } -TrayIcon::TrayIcon(const QString &filename, QWidget *parent) +TrayIcon::TrayIcon(const QString &filename, QWindow *parent) : QSystemTrayIcon(parent) { #if defined(Q_OS_MAC) || defined(Q_OS_WIN) @@ -110,13 +111,13 @@ TrayIcon::TrayIcon(const QString &filename, QWidget *parent) setIcon(QIcon(icon_)); #endif - QMenu *menu = new QMenu(parent); + QMenu *menu = new QMenu(); setContextMenu(menu); viewAction_ = new QAction(tr("Show"), this); quitAction_ = new QAction(tr("Quit"), this); - connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show())); + connect(viewAction_, &QAction::triggered, parent, &QWindow::show); connect(quitAction_, &QAction::triggered, this, QApplication::quit); menu->addAction(viewAction_); diff --git a/src/TrayIcon.h b/src/TrayIcon.h index 17bf5eff..554a4a0a 100644 --- a/src/TrayIcon.h +++ b/src/TrayIcon.h @@ -40,7 +40,7 @@ class TrayIcon : public QSystemTrayIcon { Q_OBJECT public: - TrayIcon(const QString &filename, QWidget *parent); + TrayIcon(const QString &filename, QWindow *parent); public slots: void setUnreadCount(int count); diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index c43733fb..e973fc1f 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -1518,7 +1518,7 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); auto filepath = QFileDialog::getOpenFileName( - MainWindow::instance(), tr("Select a file"), homeFolder, tr("All Files (*)")); + nullptr, tr("Select a file"), homeFolder, tr("All Files (*)")); if (!filepath.isEmpty()) { i->setRingtone(filepath); i->setRingtone(filepath); @@ -1600,11 +1600,11 @@ UserSettingsModel::importSessionKeys() { const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); const QString fileName = QFileDialog::getOpenFileName( - MainWindow::instance(), tr("Open Sessions File"), homeFolder, QLatin1String("")); + nullptr, tr("Open Sessions File"), homeFolder, QLatin1String("")); QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { - QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString()); + QMessageBox::warning(nullptr, tr("Error"), file.errorString()); return; } @@ -1612,7 +1612,7 @@ UserSettingsModel::importSessionKeys() auto payload = std::string(bin.data(), bin.size()); bool ok; - auto password = QInputDialog::getText(MainWindow::instance(), + auto password = QInputDialog::getText(nullptr, tr("File Password"), tr("Enter the passphrase to decrypt the file:"), QLineEdit::Password, @@ -1622,8 +1622,7 @@ UserSettingsModel::importSessionKeys() return; if (password.isEmpty()) { - QMessageBox::warning( - MainWindow::instance(), tr("Error"), tr("The password cannot be empty")); + QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty")); return; } @@ -1631,7 +1630,7 @@ UserSettingsModel::importSessionKeys() auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString()); cache::importSessionKeys(std::move(sessions)); } catch (const std::exception &e) { - QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what()); + QMessageBox::warning(nullptr, tr("Error"), e.what()); } } void @@ -1639,7 +1638,7 @@ UserSettingsModel::exportSessionKeys() { // Open password dialog. bool ok; - auto password = QInputDialog::getText(MainWindow::instance(), + auto password = QInputDialog::getText(nullptr, tr("File Password"), tr("Enter passphrase to encrypt your session keys:"), QLineEdit::Password, @@ -1649,19 +1648,18 @@ UserSettingsModel::exportSessionKeys() return; if (password.isEmpty()) { - QMessageBox::warning( - MainWindow::instance(), tr("Error"), tr("The password cannot be empty")); + QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty")); return; } // Open file dialog to save the file. const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); const QString fileName = QFileDialog::getSaveFileName( - MainWindow::instance(), tr("File to save the exported session keys"), homeFolder); + nullptr, tr("File to save the exported session keys"), homeFolder); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString()); + QMessageBox::warning(nullptr, tr("Error"), file.errorString()); return; } @@ -1679,7 +1677,7 @@ UserSettingsModel::exportSessionKeys() out << prefix << newline << b64 << newline << suffix << newline; file.close(); } catch (const std::exception &e) { - QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what()); + QMessageBox::warning(nullptr, tr("Error"), e.what()); } } void diff --git a/src/main.cpp b/src/main.cpp index 2ae631cf..af8a46a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -279,6 +280,7 @@ main(int argc, char *argv[]) font.setPointSizeF(settings.lock()->fontSize()); app.setFont(font); + settings.lock()->applyTheme(); if (QLocale().language() == QLocale::C) QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom)); @@ -296,15 +298,16 @@ main(int argc, char *argv[]) app.installTranslator(&appTranslator); MainWindow w; + // QQuickView w; // Move the MainWindow to the center - w.move(screenCenter(w.width(), w.height())); + // w.move(screenCenter(w.width(), w.height())); if (!(settings.lock()->startInTray() && settings.lock()->tray())) w.show(); QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() { - w.saveCurrentWindowSize(); + // w.saveCurrentWindowSize(); if (http::client() != nullptr) { nhlog::net()->debug("shutting down all I/O threads & open connections"); http::client()->close(true); @@ -314,7 +317,7 @@ main(int argc, char *argv[]) QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() { w.show(); w.raise(); - w.activateWindow(); + w.requestActivate(); }); // It seems like handling the message in a blocking manner is a no-go. I have no idea how to diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 5284ce0e..18e224b2 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -266,8 +266,8 @@ void InputBar::openFileSelection() { const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - const auto fileName = QFileDialog::getOpenFileName( - ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)")); + const auto fileName = + QFileDialog::getOpenFileName(nullptr, tr("Select a file"), homeFolder, tr("All Files (*)")); if (fileName.isEmpty()) return; @@ -659,7 +659,7 @@ InputBar::command(const QString &command, QString args) void InputBar::showPreview(const QMimeData &source, const QString &path, const QStringList &formats) { - auto *previewDialog_ = new dialogs::PreviewUploadOverlay(ChatPage::instance()); + auto *previewDialog_ = new dialogs::PreviewUploadOverlay(nullptr); previewDialog_->setAttribute(Qt::WA_DeleteOnClose); // Force SVG to _not_ be handled as an image, but as raw data diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index eb453462..aa81f501 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -8,6 +8,7 @@ #include "Cache_p.h" #include "ChatPage.h" #include "Logging.h" +#include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" #include "TimelineModel.h" @@ -275,7 +276,7 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) connect(newRoom.data(), &TimelineModel::newEncryptedImage, - manager->imageProvider(), + MainWindow::instance()->imageProvider(), &MxcImageProvider::addEncryptionInfo); connect(newRoom.data(), &TimelineModel::forwardToRoom, @@ -509,7 +510,7 @@ RoomlistModel::sync(const mtx::responses::Sync &sync_) // room_model->addEvents(room.timeline); connect(room_model.data(), &TimelineModel::newCallEvent, - manager->callManager(), + ChatPage::instance()->callManager(), &CallManager::syncEvent, Qt::UniqueConnection); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 7c9df403..e769fa40 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1031,7 +1031,7 @@ TimelineModel::setCurrentIndex(int index) if (index != oldIndex) emit currentIndexChanged(index); - if (!ChatPage::instance()->isActiveWindow()) + if (!MainWindow::instance()->isActive()) return; if (!currentId.startsWith('m')) { @@ -1495,7 +1495,7 @@ TimelineModel::saveMedia(const QString &eventId) const const QString openLocation = downloadsFolder + "/" + originalFilename; const QString filename = - QFileDialog::getSaveFileName(manager_->getWidget(), dialogTitle, openLocation, filterString); + QFileDialog::getSaveFileName(nullptr, dialogTitle, openLocation, filterString); if (filename.isEmpty()) return false; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index e689e2fa..5d2a4443 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -45,10 +44,6 @@ #include "ui/NhekoGlobalObject.h" #include "ui/UIA.h" -Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) -Q_DECLARE_METATYPE(std::vector) -Q_DECLARE_METATYPE(std::vector) - namespace msgs = mtx::events::msg; namespace { @@ -102,19 +97,6 @@ void TimelineViewManager::updateColorPalette() { userColors.clear(); - - if (ChatPage::instance()->userSettings()->theme() == QLatin1String("light")) { - view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette()); - view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"), - QPalette()); - } else if (ChatPage::instance()->userSettings()->theme() == QLatin1String("dark")) { - view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette()); - view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"), - QPalette()); - } else { - view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette()); - view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"), nullptr); - } } QColor @@ -126,112 +108,15 @@ TimelineViewManager::userColor(QString id, QColor background) return userColors.value(idx); } -TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent) +TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent) : QObject(parent) - , imgProvider(new MxcImageProvider()) - , colorImgProvider(new ColorImageProvider()) - , blurhashProvider(new BlurhashProvider()) - , jdenticonProvider(new JdenticonProvider()) , rooms_(new RoomlistModel(this)) , communities_(new CommunitiesModel(this)) - , callManager_(callManager) , verificationManager_(new VerificationManager(this)) , presenceEmitter(new PresenceEmitter(this)) { - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - - qRegisterMetaType>(); - - qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, - "im.nheko", - 1, - 0, - "MtxEvent", - QStringLiteral("Can't instantiate enum!")); - qmlRegisterUncreatableMetaObject( - olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!")); - qmlRegisterUncreatableMetaObject(crypto::staticMetaObject, - "im.nheko", - 1, - 0, - "Crypto", - QStringLiteral("Can't instantiate enum!")); - qmlRegisterUncreatableMetaObject(verification::staticMetaObject, - "im.nheko", - 1, - 0, - "VerificationStatus", - QStringLiteral("Can't instantiate enum!")); - - qmlRegisterType("im.nheko", 1, 0, "DelegateChoice"); - qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); - qmlRegisterType("im.nheko", 1, 0, "NhekoDropArea"); - qmlRegisterType("im.nheko", 1, 0, "CursorShape"); - qmlRegisterType("im.nheko", 1, 0, "MxcAnimatedImage"); - qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "DeviceVerificationFlow", - QStringLiteral("Can't create verification flow from QML!")); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "UserProfileModel", - QStringLiteral("UserProfile needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "MemberList", - QStringLiteral("MemberList needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "RoomSettingsModel", - QStringLiteral("Room Settings needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType( - "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "ImagePackListModel", - QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "SingleImagePackModel", - QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "InviteesModel", - QStringLiteral("InviteesModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "ReadReceiptsProxy", - QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side")); - static auto self = this; - qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", MainWindow::instance()); qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self); - qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance()); qmlRegisterSingletonType( "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { auto ptr = new FilteredRoomlistModel(self->rooms_); @@ -247,79 +132,15 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par return ptr; }); qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_); - qmlRegisterSingletonInstance( - "im.nheko", 1, 0, "Settings", ChatPage::instance()->userSettings().data()); - qmlRegisterSingletonInstance( - "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager()); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Clipboard(); - }); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Nheko(); - }); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new UserSettingsModel(); - }); qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_); qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = new SelfVerificationStatus(); - QObject::connect(ChatPage::instance(), - &ChatPage::initializeEmptyViews, - ptr, - &SelfVerificationStatus::invalidate); - return ptr; - }); - qRegisterMetaType(); - qRegisterMetaType>(); - - qmlRegisterUncreatableType( - "im.nheko", - 1, - 0, - "FilteredCommunitiesModel", - QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel")); - - qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel"); - qmlRegisterUncreatableType( - "im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models")); - qmlRegisterUncreatableMetaObject(emoji::staticMetaObject, - "im.nheko.EmojiModel", - 1, - 0, - "EmojiCategory", - QStringLiteral("Error: Only enums")); - - qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); - -#ifdef USE_QUICK_VIEW - view = new QQuickView(parent); - container = QWidget::createWindowContainer(view, parent); -#else - view = new QQuickWidget(parent); - container = view; - view->setResizeMode(QQuickWidget::SizeRootObjectToView); - container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) { - nhlog::ui()->debug("Status changed to {}", status); - }); -#endif - container->setMinimumSize(200, 200); updateColorPalette(); - view->engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider); - view->engine()->addImageProvider(QStringLiteral("colorimage"), colorImgProvider); - view->engine()->addImageProvider(QStringLiteral("blurhash"), blurhashProvider); - if (JdenticonProvider::isAvailable()) - view->engine()->addImageProvider(QStringLiteral("jdenticon"), jdenticonProvider); - view->setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml"))); - connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette); + connect(UserSettings::instance().get(), + &UserSettings::themeChanged, + this, + &TimelineViewManager::updateColorPalette); connect(parent, &ChatPage::receivedRoomDeviceVerificationRequest, verificationManager_, @@ -379,7 +200,8 @@ void TimelineViewManager::setVideoCallItem() { WebRTCSession::instance().setVideoItem( - view->rootObject()->findChild(QStringLiteral("videoCallItem"))); + MainWindow::instance()->rootObject()->findChild( + QStringLiteral("videoCallItem"))); } void @@ -401,7 +223,7 @@ TimelineViewManager::showEvent(const QString &room_id, const QString &event_id) if (auto room = rooms_->getRoomById(room_id)) { if (rooms_->currentRoom() != room) { rooms_->setCurrentRoom(room_id); - container->setFocus(); + MainWindow::instance()->requestActivate(); nhlog::ui()->info("Activated room {}", room_id.toStdString()); } @@ -439,7 +261,7 @@ TimelineViewManager::saveMedia(QString mxcUrl) QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); const QString openLocation = downloadsFolder + "/" + mxcUrl.splitRef(u'/').constLast(); - const QString filename = QFileDialog::getSaveFileName(getWidget(), {}, openLocation); + const QString filename = QFileDialog::getSaveFileName(nullptr, {}, openLocation); if (filename.isEmpty()) return; @@ -590,12 +412,6 @@ TimelineViewManager::completerFor(QString completerName, QString roomId) return nullptr; } -void -TimelineViewManager::focusTimeline() -{ - getWidget()->setFocus(); -} - void TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId) diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 455702f4..424d78d6 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -8,8 +8,6 @@ #include #include #include -#include -#include #include #include @@ -48,12 +46,9 @@ class TimelineViewManager : public QObject public: TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); - QWidget *getWidget() const { return container; } void sync(const mtx::responses::Sync &sync_); - MxcImageProvider *imageProvider() { return imgProvider; } - CallManager *callManager() { return callManager_; } VerificationManager *verificationManager() { return verificationManager_; } void clearAll() { rooms_->clear(); } @@ -105,7 +100,6 @@ public slots: } void showEvent(const QString &room_id, const QString &event_id); - void focusTimeline(); void updateColorPalette(); void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody); @@ -122,18 +116,6 @@ public slots: RoomlistModel *rooms() { return rooms_; } private: -#ifdef USE_QUICK_VIEW - QQuickView *view; -#else - QQuickWidget *view; -#endif - QWidget *container; - - MxcImageProvider *imgProvider; - ColorImageProvider *colorImgProvider; - BlurhashProvider *blurhashProvider; - JdenticonProvider *jdenticonProvider; - bool isInitialSync_ = true; bool isWindowFocused_ = false; @@ -141,7 +123,6 @@ private: CommunitiesModel *communities_ = nullptr; // don't move this above the rooms_ - CallManager *callManager_ = nullptr; VerificationManager *verificationManager_ = nullptr; PresenceEmitter *presenceEmitter = nullptr; diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index 450cb0d0..3d8d9959 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -139,5 +139,5 @@ Nheko::openCreateRoomDialog() const void Nheko::reparent(QWindow *win) const { - win->setTransientParent(MainWindow::instance()->windowHandle()); + win->setTransientParent(MainWindow::instance()); } diff --git a/src/ui/UIA.cpp b/src/ui/UIA.cpp index 291d0a9f..9f28ca6a 100644 --- a/src/ui/UIA.cpp +++ b/src/ui/UIA.cpp @@ -13,7 +13,6 @@ #include #include "Logging.h" -#include "MainWindow.h" #include "dialogs/FallbackAuth.h" #include "dialogs/ReCaptcha.h" @@ -71,7 +70,7 @@ UIA::genericHandler(QString context) emit phoneNumber(); } else if (current_stage == mtx::user_interactive::auth_types::recaptcha) { auto captchaDialog = - new dialogs::ReCaptcha(QString::fromStdString(u.session), MainWindow::instance()); + new dialogs::ReCaptcha(QString::fromStdString(u.session), nullptr); captchaDialog->setWindowTitle(context); connect( @@ -95,7 +94,7 @@ UIA::genericHandler(QString context) } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { bool ok; QString token = - QInputDialog::getText(MainWindow::instance(), + QInputDialog::getText(nullptr, context, tr("Please enter a valid registration token."), QLineEdit::Normal, @@ -113,7 +112,7 @@ UIA::genericHandler(QString context) // use fallback auto dialog = new dialogs::FallbackAuth(QString::fromStdString(current_stage), QString::fromStdString(u.session), - MainWindow::instance()); + nullptr); dialog->setWindowTitle(context); connect(dialog, &dialogs::FallbackAuth::confirmation, this, [h, u, dialog]() { From aaae72a4f2936df84dbb94052c0d303a1fcc33a9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 13 Jan 2022 04:16:11 +0100 Subject: [PATCH 02/17] Fix focus and qml parenting with qml root --- resources/qml/PrivacyScreen.qml | 2 +- .../qml/device-verification/DeviceVerification.qml | 1 - resources/qml/dialogs/ImagePackEditorDialog.qml | 2 -- resources/qml/dialogs/ImagePackSettingsDialog.qml | 1 - resources/qml/dialogs/InputDialog.qml | 1 - resources/qml/dialogs/InviteDialog.qml | 1 - resources/qml/dialogs/JoinRoomDialog.qml | 1 - resources/qml/dialogs/PhoneNumberInputDialog.qml | 1 - resources/qml/dialogs/RawMessageDialog.qml | 1 - resources/qml/dialogs/ReadReceipts.qml | 1 - resources/qml/dialogs/RoomDirectory.qml | 1 - resources/qml/dialogs/RoomMembers.qml | 1 - resources/qml/dialogs/RoomSettings.qml | 1 - resources/qml/dialogs/UserProfile.qml | 4 ---- src/ChatPage.cpp | 2 -- src/ChatPage.h | 1 - src/MainWindow.cpp | 6 ------ src/MainWindow.h | 1 - src/timeline/TimelineModel.cpp | 2 +- src/timeline/TimelineViewManager.cpp | 11 +++++++++++ src/timeline/TimelineViewManager.h | 11 ++--------- 21 files changed, 15 insertions(+), 38 deletions(-) diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml index 6ad2a557..fb3818df 100644 --- a/resources/qml/PrivacyScreen.qml +++ b/resources/qml/PrivacyScreen.qml @@ -26,7 +26,7 @@ Item { } } - target: MainWindow + target: TimelineManager } Timer { diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index c00a0bdb..90dc9ac4 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -21,7 +21,6 @@ ApplicationWindow { minimumHeight: stack.implicitHeight width: stack.implicitWidth flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(dialog) StackView { id: stack diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 3ba04d94..9c46a490 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -12,8 +12,6 @@ import QtQuick.Layouts 1.12 import im.nheko 1.0 ApplicationWindow { - //Component.onCompleted: Nheko.reparent(win) - id: win property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml index fa079855..18c32c41 100644 --- a/resources/qml/dialogs/ImagePackSettingsDialog.qml +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -28,7 +28,6 @@ ApplicationWindow { color: Nheko.colors.base modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(win) Component { id: packEditor diff --git a/resources/qml/dialogs/InputDialog.qml b/resources/qml/dialogs/InputDialog.qml index 63ca3181..cf1474dc 100644 --- a/resources/qml/dialogs/InputDialog.qml +++ b/resources/qml/dialogs/InputDialog.qml @@ -18,7 +18,6 @@ ApplicationWindow { modality: Qt.NonModal flags: Qt.Dialog - Component.onCompleted: Nheko.reparent(inputDialog) width: 350 height: fontMetrics.lineSpacing * 7 diff --git a/resources/qml/dialogs/InviteDialog.qml b/resources/qml/dialogs/InviteDialog.qml index 917bc856..e7dd4e3a 100644 --- a/resources/qml/dialogs/InviteDialog.qml +++ b/resources/qml/dialogs/InviteDialog.qml @@ -37,7 +37,6 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(inviteDialogRoot) Shortcut { sequence: "Ctrl+Enter" diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml index 9ce6bcf1..e49f538d 100644 --- a/resources/qml/dialogs/JoinRoomDialog.qml +++ b/resources/qml/dialogs/JoinRoomDialog.qml @@ -17,7 +17,6 @@ ApplicationWindow { flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint palette: Nheko.colors color: Nheko.colors.window - Component.onCompleted: Nheko.reparent(joinRoomRoot) width: 350 height: fontMetrics.lineSpacing * 7 diff --git a/resources/qml/dialogs/PhoneNumberInputDialog.qml b/resources/qml/dialogs/PhoneNumberInputDialog.qml index 399b11d5..9c36c98f 100644 --- a/resources/qml/dialogs/PhoneNumberInputDialog.qml +++ b/resources/qml/dialogs/PhoneNumberInputDialog.qml @@ -19,7 +19,6 @@ ApplicationWindow { modality: Qt.NonModal flags: Qt.Dialog - Component.onCompleted: Nheko.reparent(inputDialog) width: 350 height: fontMetrics.lineSpacing * 7 diff --git a/resources/qml/dialogs/RawMessageDialog.qml b/resources/qml/dialogs/RawMessageDialog.qml index 34104394..774b078b 100644 --- a/resources/qml/dialogs/RawMessageDialog.qml +++ b/resources/qml/dialogs/RawMessageDialog.qml @@ -17,7 +17,6 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(rawMessageRoot) Shortcut { sequence: StandardKey.Cancel diff --git a/resources/qml/dialogs/ReadReceipts.qml b/resources/qml/dialogs/ReadReceipts.qml index aced4374..da87996e 100644 --- a/resources/qml/dialogs/ReadReceipts.qml +++ b/resources/qml/dialogs/ReadReceipts.qml @@ -22,7 +22,6 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(readReceiptsRoot) Shortcut { sequence: StandardKey.Cancel diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml index f458ac51..99da63bb 100644 --- a/resources/qml/dialogs/RoomDirectory.qml +++ b/resources/qml/dialogs/RoomDirectory.qml @@ -22,7 +22,6 @@ ApplicationWindow { color: Nheko.colors.window modality: Qt.WindowModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(roomDirectoryWindow) title: qsTr("Explore Public Rooms") Shortcut { diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml index 89cce414..55d5488b 100644 --- a/resources/qml/dialogs/RoomMembers.qml +++ b/resources/qml/dialogs/RoomMembers.qml @@ -24,7 +24,6 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(roomMembersRoot) Shortcut { sequence: StandardKey.Cancel diff --git a/resources/qml/dialogs/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml index c9f2b1a1..48d2e2b7 100644 --- a/resources/qml/dialogs/RoomSettings.qml +++ b/resources/qml/dialogs/RoomSettings.qml @@ -23,7 +23,6 @@ ApplicationWindow { color: Nheko.colors.window modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(roomSettingsDialog) title: qsTr("Room Settings") Shortcut { diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 29ce2c3f..73c4e68b 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -13,9 +13,6 @@ import QtQuick.Window 2.13 import im.nheko 1.0 ApplicationWindow { - // this does not work in ApplicationWindow, just in Window - //transientParent: Nheko.mainwindow() - id: userProfileDialog property var profile @@ -29,7 +26,6 @@ ApplicationWindow { title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - Component.onCompleted: Nheko.reparent(userProfileDialog) Shortcut { sequence: StandardKey.Cancel diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index fc90e6c7..f30e0466 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -175,8 +175,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QObject *parent) &ChatPage::initializeEmptyViews, view_manager_, &TimelineViewManager::initializeRoomlist); - connect( - this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged); connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) { view_manager_->sync(sync); diff --git a/src/ChatPage.h b/src/ChatPage.h index 5e3b509d..4b7351a5 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -143,7 +143,6 @@ signals: void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state); void themeChanged(); void decryptSidebarChanged(); - void chatFocusChanged(const bool focused); //! Signals for device verificaiton void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5e7fe6ce..03a99b0f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -130,8 +130,6 @@ MainWindow::MainWindow(QWindow *parent) connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); - connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged); - // connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { // http::client()->set_user(res.user_id); // showChatPage(); @@ -342,10 +340,6 @@ MainWindow::event(QEvent *event) if (type == QEvent::Close) { closeEvent(static_cast(event)); - } else if (type == QEvent::WindowActivate) { - emit focusChanged(true); - } else if (type == QEvent::WindowDeactivate) { - emit focusChanged(false); } return QQuickView::event(event); diff --git a/src/MainWindow.h b/src/MainWindow.h index 04311e12..ea919f4d 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -91,7 +91,6 @@ private slots: virtual void setWindowTitle(int notificationCount); signals: - void focusChanged(const bool focused); void reload(); void secretsChanged(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index e769fa40..6b380f79 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1031,7 +1031,7 @@ TimelineModel::setCurrentIndex(int index) if (index != oldIndex) emit currentIndexChanged(index); - if (!MainWindow::instance()->isActive()) + if (MainWindow::instance() != QGuiApplication::focusWindow()) return; if (!currentId.startsWith('m')) { diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 5d2a4443..0abd102b 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -5,6 +5,7 @@ #include "TimelineViewManager.h" +#include #include #include #include @@ -157,6 +158,16 @@ TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent) isInitialSync_ = true; emit initialSyncChanged(true); }); + connect(qobject_cast(QApplication::instance()), + &QApplication::focusWindowChanged, + this, + &TimelineViewManager::focusChanged); +} + +bool +TimelineViewManager::isWindowFocused() const +{ + return MainWindow::instance() == QApplication::focusWindow(); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 424d78d6..9419f224 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -41,8 +41,7 @@ class TimelineViewManager : public QObject Q_PROPERTY( bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) - Q_PROPERTY( - bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged) + Q_PROPERTY(bool isWindowFocused READ isWindowFocused NOTIFY focusChanged) public: TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); @@ -54,7 +53,7 @@ public: void clearAll() { rooms_->clear(); } Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } - bool isWindowFocused() const { return isWindowFocused_; } + bool isWindowFocused() const; Q_INVOKABLE void openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId); Q_INVOKABLE void openImagePackSettings(QString roomid); Q_INVOKABLE void saveMedia(QString mxcUrl); @@ -93,11 +92,6 @@ public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); void receivedSessionKey(const std::string &room_id, const std::string &session_id); void initializeRoomlist(); - void chatFocusChanged(bool focused) - { - isWindowFocused_ = focused; - emit focusChanged(); - } void showEvent(const QString &room_id, const QString &event_id); @@ -117,7 +111,6 @@ public slots: private: bool isInitialSync_ = true; - bool isWindowFocused_ = false; RoomlistModel *rooms_ = nullptr; CommunitiesModel *communities_ = nullptr; From 4a80fdc951b57647ce9404bbbfe306b459c84f57 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 24 Jan 2022 00:41:55 +0100 Subject: [PATCH 03/17] Functional login page --- resources/qml/MatrixTextField.qml | 135 +++++- resources/qml/QuickSwitcher.qml | 4 +- resources/qml/Root.qml | 27 +- .../qml/dialogs/ImagePackEditorDialog.qml | 50 +- resources/qml/dialogs/RoomDirectory.qml | 2 - resources/qml/pages/LoginPage.qml | 178 +++++++ resources/qml/pages/WelcomePage.qml | 48 +- resources/res.qrc | 1 + src/LoginPage.cpp | 438 +++++------------- src/LoginPage.h | 132 +++--- src/MainWindow.cpp | 37 +- src/MainWindow.h | 13 +- src/timeline/TimelineViewManager.h | 2 +- 13 files changed, 573 insertions(+), 494 deletions(-) create mode 100644 resources/qml/pages/LoginPage.qml diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml index af077124..655d53f1 100644 --- a/resources/qml/MatrixTextField.qml +++ b/resources/qml/MatrixTextField.qml @@ -8,29 +8,131 @@ import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import im.nheko 1.0 -TextField { - id: input - property alias backgroundColor: backgroundRect.color +ColumnLayout { + id: c + property color backgroundColor: Nheko.colors.base + property alias color: labelC.color + property alias textPadding: input.padding + property alias text: input.text + property alias label: labelC.text + property alias placeholderText: input.placeholderText + property alias font: input.font + property alias echoMode: input.echoMode + property alias selectByMouse: input.selectByMouse - palette: Nheko.colors - color: Nheko.colors.text + signal textEdited + signal accepted + signal editingFinished + + function forceActiveFocus() { + input.forceActiveFocus(); + } + + ToolTip.delay: Nheko.tooltipDelay + ToolTip.visible: hover.hovered + + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: labelC.contentHeight + Layout.margins: input.padding + Layout.bottomMargin: Nheko.paddingSmall + visible: labelC.text + + z: 1 + + Label { + id: labelC + + y: contentHeight + input.padding + Nheko.paddingSmall + enabled: false + + palette: Nheko.colors + color: Nheko.colors.text + font.pixelSize: input.font.pixelSize + font.weight: Font.DemiBold + font.letterSpacing: input.font.pixelSize * 0.02 + width: parent.width + + state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : "" + + states: State { + name: "focused" + + PropertyChanges { + target: labelC + y: 0 + } + + PropertyChanges { + target: input + opacity: 1 + } + + } + + transitions: Transition { + from: "" + to: "focused" + reversible: true + + NumberAnimation { + target: labelC + properties: "y" + duration: 210 + easing.type: Easing.InCubic + alwaysRunToEnd: true + } + + NumberAnimation { + target: input + properties: "opacity" + duration: 210 + easing.type: Easing.InCubic + alwaysRunToEnd: true + } + + } + } + } + + TextField { + id: input + Layout.fillWidth: true + + palette: Nheko.colors + color: labelC.color + opacity: labelC.text ? 0 : 1 + + onTextEdited: c.textEdited() + onAccepted: c.accepted() + onEditingFinished: c.editingFinished() + + + background: Rectangle { + id: backgroundRect + + color: labelC.text ? "transparent" : backgroundColor + } + + } Rectangle { id: blueBar - anchors.top: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter + Layout.fillWidth: true + color: Nheko.colors.highlight height: 1 - width: parent.width Rectangle { id: blackBar - anchors.verticalCenter: blueBar.verticalCenter + anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - height: parent.height + 1 + height: parent.height*2 width: 0 color: Nheko.colors.text @@ -50,11 +152,12 @@ TextField { to: "focused" reversible: true + NumberAnimation { target: blackBar properties: "width" - duration: 500 - easing.type: Easing.InOutQuad + duration: 310 + easing.type: Easing.InCubic alwaysRunToEnd: true } @@ -64,10 +167,8 @@ TextField { } - background: Rectangle { - id: backgroundRect - - color: Nheko.colors.base + HoverHandler { + id: hover + enabled: c.ToolTip.text } - } diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index 174951a0..6d217c72 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -11,7 +11,6 @@ Popup { id: quickSwitcher property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4) - property int textMargin: Math.round(textHeight / 8) background: null width: Math.round(parent.width / 2) @@ -34,7 +33,6 @@ Popup { anchors.fill: parent font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6) - padding: textMargin color: Nheko.colors.text onTextEdited: { completerPopup.completer.searchString = text; @@ -60,7 +58,7 @@ Popup { id: completerPopup x: roomTextInput.x - y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin + y: roomTextInput.y + roomTextInput.height visible: roomTextInput.length > 0 width: parent.width completerName: "room" diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 004169e1..f3976cc0 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -155,6 +155,11 @@ Pane { } + Shortcut { + sequence: StandardKey.Quit + onActivated: Qt.quit() + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -366,8 +371,13 @@ Pane { id: mainWindow anchors.fill: parent - initialItem: WelcomePage { - //anchors.fill: parent + initialItem: welcomePage + } + + Component { + id: welcomePage + + WelcomePage { } } @@ -378,10 +388,19 @@ Pane { } } + Component { + id: loginPage + + LoginPage { + } + } + Connections { function onSwitchToChatPage() { - console.log("AAAA"); - mainWindow.replace(chatPage); + mainWindow.replace(null, chatPage); + } + function onSwitchToLoginPage(error) { + mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition); } target: MainWindow } diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 9c46a490..eb420fce 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -173,39 +173,37 @@ ApplicationWindow { } } - MatrixText { - visible: imagePack.roomid - text: qsTr("State key") - } - MatrixTextField { + id: statekeyField + visible: imagePack.roomid Layout.fillWidth: true + Layout.columnSpan: 2 + label: qsTr("State key") text: imagePack.statekey onTextEdited: imagePack.statekey = text } - MatrixText { - text: qsTr("Packname") - } - MatrixTextField { Layout.fillWidth: true + Layout.columnSpan: 2 + label: qsTr("Packname") text: imagePack.packname onTextEdited: imagePack.packname = text } - MatrixText { - text: qsTr("Attribution") - } - MatrixTextField { Layout.fillWidth: true + Layout.columnSpan: 2 + label: qsTr("Attribution") text: imagePack.attribution onTextEdited: imagePack.attribution = text } MatrixText { + Layout.margins: statekeyField.textPadding + font.weight: Font.DemiBold + font.letterSpacing: font.pixelSize * 0.02 text: qsTr("Use as Emoji") } @@ -216,6 +214,9 @@ ApplicationWindow { } MatrixText { + Layout.margins: statekeyField.textPadding + font.weight: Font.DemiBold + font.letterSpacing: font.pixelSize * 0.02 text: qsTr("Use as Sticker") } @@ -251,27 +252,28 @@ ApplicationWindow { Layout.alignment: Qt.AlignHCenter } - MatrixText { - text: qsTr("Shortcode") - } - MatrixTextField { Layout.fillWidth: true + Layout.columnSpan: 2 + label: qsTr("Shortcode") text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode) } - MatrixText { - text: qsTr("Body") - } - MatrixTextField { + id: bodyField + Layout.fillWidth: true + Layout.columnSpan: 2 + label: qsTr("Body") text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body) onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body) } MatrixText { + Layout.margins: bodyField.textPadding + font.weight: Font.DemiBold + font.letterSpacing: font.pixelSize * 0.02 text: qsTr("Use as Emoji") } @@ -282,6 +284,9 @@ ApplicationWindow { } MatrixText { + Layout.margins: bodyField.textPadding + font.weight: Font.DemiBold + font.letterSpacing: font.pixelSize * 0.02 text: qsTr("Use as Sticker") } @@ -292,6 +297,9 @@ ApplicationWindow { } MatrixText { + Layout.margins: bodyField.textPadding + font.weight: Font.DemiBold + font.letterSpacing: font.pixelSize * 0.02 text: qsTr("Remove from pack") } diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml index 99da63bb..36c29a0b 100644 --- a/resources/qml/dialogs/RoomDirectory.qml +++ b/resources/qml/dialogs/RoomDirectory.qml @@ -188,7 +188,6 @@ ApplicationWindow { Layout.fillWidth: true selectByMouse: true font.pixelSize: fontMetrics.font.pixelSize - padding: Nheko.paddingMedium color: Nheko.colors.text placeholderText: qsTr("Search for public rooms") onTextChanged: searchTimer.restart() @@ -199,7 +198,6 @@ ApplicationWindow { Layout.minimumWidth: 0.3 * header.width Layout.maximumWidth: 0.3 * header.width - padding: Nheko.paddingMedium color: Nheko.colors.text placeholderText: qsTr("Choose custom homeserver") onTextChanged: publicRooms.setMatrixServer(text) diff --git a/resources/qml/pages/LoginPage.qml b/resources/qml/pages/LoginPage.qml new file mode 100644 index 00000000..0beb2bdc --- /dev/null +++ b/resources/qml/pages/LoginPage.qml @@ -0,0 +1,178 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.15 +import im.nheko 1.0 +import "../components/" +import "../ui/" +import "../" + +Item { + id: loginPage + property int maxExpansion: 800 + + property string error: login.error + + Login { + id: login + } + + ScrollView { + id: scroll + + clip: false + palette: Nheko.colors + ScrollBar.horizontal.visible: false + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + height: Math.min(loginPage.height, col.implicitHeight) + anchors.margins: Nheko.paddingLarge + + contentWidth: availableWidth + + ColumnLayout { + id: col + + spacing: Nheko.paddingMedium + + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2) + + Image { + Layout.alignment: Qt.AlignHCenter + source: "qrc:/logos/login.png" + height: 128 + width: 128 + } + + RowLayout { + spacing: Nheko.paddingLarge + + Layout.fillWidth: true + MatrixTextField { + id: matrixIdLabel + label: qsTr("Matrix ID") + placeholderText: qsTr("e.g @joe:matrix.org") + onEditingFinished: login.mxid = text + + ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.") + Keys.forwardTo: [pwBtn, ssoBtn] + } + + + Spinner { + height: matrixIdLabel.height/2 + Layout.alignment: Qt.AlignBottom + + visible: running + running: login.lookingUpHs + foreground: Nheko.colors.mid + } + } + + MatrixText { + textFormat: Text.PlainText + color: Nheko.theme.error + text: login.mxidError + visible: text + } + + MatrixTextField { + id: passwordLabel + Layout.fillWidth: true + label: qsTr("Password") + echoMode: TextInput.Password + ToolTip.text: qsTr("Your password.") + visible: login.passwordSupported + Keys.forwardTo: [pwBtn, ssoBtn] + } + + MatrixTextField { + id: deviceNameLabel + Layout.fillWidth: true + label: qsTr("Device name") + placeholderText: login.initialDeviceName() + ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.") + Keys.forwardTo: [pwBtn, ssoBtn] + } + + MatrixTextField { + id: hsLabel + enabled: visible + visible: login.homeserverNeeded + + Layout.fillWidth: true + label: qsTr("Homeserver address") + placeholderText: qsTr("server.my:8787") + text: login.homeserver + onEditingFinished: login.homeserver = text + ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787") + Keys.forwardTo: [pwBtn, ssoBtn] + } + + Item { + height: Nheko.avatarSize + Layout.fillWidth: true + + Spinner { + height: parent.height + anchors.centerIn: parent + + visible: running + running: login.loggingIn + foreground: Nheko.colors.mid + } + } + + MatrixText { + textFormat: Text.PlainText + color: Nheko.theme.error + text: loginPage.error + visible: text + } + + FlatButton { + id: pwBtn + visible: login.passwordSupported + enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text + Layout.alignment: Qt.AlignHCenter + text: qsTr("LOGIN") + function pwLogin() { + login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text) + } + onClicked: pwBtn.pwLogin() + Keys.onEnterPressed: pwBtn.pwLogin() + Keys.onReturnPressed: pwBtn.pwLogin() + Keys.enabled: pwBtn.enabled && login.passwordSupported + } + FlatButton { + id: ssoBtn + visible: login.ssoSupported + enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text + Layout.alignment: Qt.AlignHCenter + text: qsTr("SSO LOGIN") + function ssoLogin() { + login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text) + } + onClicked: ssoBtn.ssoLogin() + Keys.onEnterPressed: ssoBtn.ssoLogin() + Keys.onReturnPressed: ssoBtn.ssoLogin() + Keys.enabled: ssoBtn.enabled && !login.passwordSupported + } + + } + } + + ImageButton { + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: Nheko.paddingMedium + width: Nheko.avatarSize + height: Nheko.avatarSize + image: ":/icons/icons/ui/angle-arrow-left.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Back") + onClicked: mainWindow.pop() + } +} diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml index d95b6104..43050d8e 100644 --- a/resources/qml/pages/WelcomePage.qml +++ b/resources/qml/pages/WelcomePage.qml @@ -6,15 +6,6 @@ import im.nheko 1.0 import "../components/" ColumnLayout { - FontMetrics { - id: fontMetrics - } - - Shortcut { - sequence: StandardKey.Quit - onActivated: Qt.quit() - } - Item { Layout.fillHeight: true } @@ -49,27 +40,28 @@ ColumnLayout { } RowLayout { - Item { - Layout.fillWidth: true - } - FlatButton { - Layout.margins: Nheko.paddingLarge - Layout.alignment: Qt.AlignHCenter - text: qsTr("REGISTER") - onClicked: { + Item { + Layout.fillWidth: true + } + FlatButton { + Layout.margins: Nheko.paddingLarge + Layout.alignment: Qt.AlignHCenter + text: qsTr("REGISTER") + onClicked: { + } + } + FlatButton { + Layout.margins: Nheko.paddingLarge + Layout.alignment: Qt.AlignHCenter + text: qsTr("LOGIN") + onClicked: { + mainWindow.push(loginPage); + } + } + Item { + Layout.fillWidth: true } } - FlatButton { - Layout.margins: Nheko.paddingLarge - Layout.alignment: Qt.AlignHCenter - text: qsTr("LOGIN") - onClicked: { - } - } - Item { - Layout.fillWidth: true - } -} Item { Layout.fillHeight: true } diff --git a/resources/res.qrc b/resources/res.qrc index 0222619b..efc0e74a 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -111,6 +111,7 @@ qml/NotificationWarning.qml qml/pages/UserSettingsPage.qml qml/pages/WelcomePage.qml + qml/pages/LoginPage.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index 4c28f364..cfc600ae 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -5,11 +5,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include -#include -#include -#include -#include -#include #include #include @@ -18,247 +13,94 @@ #include "Config.h" #include "Logging.h" #include "LoginPage.h" +#include "MainWindow.h" #include "MatrixClient.h" #include "SSOHandler.h" #include "UserSettingsPage.h" -#include "ui/FlatButton.h" -#include "ui/LoadingIndicator.h" -#include "ui/OverlayModal.h" -#include "ui/RaisedButton.h" -#include "ui/TextField.h" Q_DECLARE_METATYPE(LoginPage::LoginMethod) using namespace mtx::identifiers; -LoginPage::LoginPage(QWidget *parent) - : QWidget(parent) +LoginPage::LoginPage(QObject *parent) + : QObject(parent) , inferredServerAddress_() { - qRegisterMetaType("LoginPage::LoginMethod"); - - top_layout_ = new QVBoxLayout(); - - top_bar_layout_ = new QHBoxLayout(); - top_bar_layout_->setSpacing(0); - top_bar_layout_->setContentsMargins(0, 0, 0, 0); - - back_button_ = new FlatButton(this); - back_button_->setMinimumSize(QSize(30, 30)); - - top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - top_bar_layout_->addStretch(1); - - QIcon icon; - icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg")); - - back_button_->setIcon(icon); - back_button_->setIconSize(QSize(32, 32)); - - QIcon logo; - logo.addFile(QStringLiteral(":/logos/login.png")); - - logo_ = new QLabel(this); - logo_->setPixmap(logo.pixmap(128)); - - logo_layout_ = new QHBoxLayout(); - logo_layout_->setContentsMargins(0, 0, 0, 20); - logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); - - form_wrapper_ = new QHBoxLayout(); - form_widget_ = new QWidget(); - form_widget_->setMinimumSize(QSize(350, 200)); - - form_layout_ = new QVBoxLayout(); - form_layout_->setSpacing(20); - form_layout_->setContentsMargins(0, 0, 0, 30); - form_widget_->setLayout(form_layout_); - - form_wrapper_->addStretch(1); - form_wrapper_->addWidget(form_widget_); - form_wrapper_->addStretch(1); - - matrixid_input_ = new TextField(this); - matrixid_input_->setLabel(tr("Matrix ID")); - matrixid_input_->setRegexp(QRegularExpression(QStringLiteral("@.+?:.{3,}"))); - matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); - matrixid_input_->setToolTip( - tr("Your login name. A mxid should start with @ followed by the user id. After the user " - "id you need to include your server name after a :.\nYou can also put your homeserver " - "address there, if your server doesn't support .well-known lookup.\nExample: " - "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a " - "field to enter the server manually.")); - - spinner_ = new LoadingIndicator(this); - spinner_->setFixedHeight(40); - spinner_->setFixedWidth(40); - spinner_->hide(); - - errorIcon_ = new QLabel(this); - errorIcon_->setPixmap(QPixmap(QStringLiteral(":/icons/icons/error.png"))); - errorIcon_->hide(); - - matrixidLayout_ = new QHBoxLayout(); - matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); - - QFont font; - - error_matrixid_label_ = new QLabel(this); - error_matrixid_label_->setFont(font); - error_matrixid_label_->setWordWrap(true); - - password_input_ = new TextField(this); - password_input_->setLabel(tr("Password")); - password_input_->setEchoMode(QLineEdit::Password); - password_input_->setToolTip(tr("Your password.")); - - deviceName_ = new TextField(this); - deviceName_->setLabel(tr("Device name")); - deviceName_->setToolTip( - tr("A name for this device, which will be shown to others, when verifying your devices. " - "If none is provided a default is used.")); - - serverInput_ = new TextField(this); - serverInput_->setLabel(tr("Homeserver address")); - serverInput_->setPlaceholderText(tr("server.my:8787")); - serverInput_->setToolTip(tr("The address that can be used to contact you homeservers " - "client API.\nExample: https://server.my:8787")); - serverInput_->hide(); - - serverLayout_ = new QHBoxLayout(); - serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); - - form_layout_->addLayout(matrixidLayout_); - form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter); - form_layout_->addWidget(password_input_); - form_layout_->addWidget(deviceName_, Qt::AlignHCenter); - form_layout_->addLayout(serverLayout_); - - error_matrixid_label_->hide(); - - button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(20); - button_layout_->setContentsMargins(0, 0, 0, 30); - - login_button_ = new RaisedButton(tr("LOGIN"), this); - login_button_->setMinimumSize(150, 65); - login_button_->setFontSize(20); - login_button_->setCornerRadius(3); - - sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this); - sso_login_button_->setMinimumSize(150, 65); - sso_login_button_->setFontSize(20); - sso_login_button_->setCornerRadius(3); - sso_login_button_->setVisible(false); - - button_layout_->addStretch(1); - button_layout_->addWidget(login_button_); - button_layout_->addWidget(sso_login_button_); - button_layout_->addStretch(1); - - error_label_ = new QLabel(this); - error_label_->setFont(font); - error_label_->setWordWrap(true); - - top_layout_->addLayout(top_bar_layout_); - top_layout_->addStretch(1); - top_layout_->addLayout(logo_layout_); - top_layout_->addLayout(form_wrapper_); - top_layout_->addStretch(1); - top_layout_->addLayout(button_layout_); - top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); - top_layout_->addStretch(1); - - setLayout(top_layout_); + [[maybe_unused]] static auto ignored = + qRegisterMetaType("LoginPage::LoginMethod"); connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection); connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection); + connect( + this, + &LoginPage::loginOk, + this, + [this](const mtx::responses::Login &res) { + loggingIn_ = false; + emit loggingInChanged(); - connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(login_button_, &RaisedButton::clicked, this, [this]() { - onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO); - }); - connect(sso_login_button_, &RaisedButton::clicked, this, [this]() { - onLoginButtonClicked(LoginMethod::SSO); - }); - connect(this, - &LoginPage::showErrorMessage, - this, - static_cast(&LoginPage::showError), - Qt::QueuedConnection); - connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); - connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); + http::client()->set_user(res.user_id); + MainWindow::instance()->showChatPage(); + }, + Qt::QueuedConnection); } void LoginPage::showError(const QString &msg) { - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - error_label_->setFixedHeight((int)qCeil(width / 200.0) * height); - error_label_->setText(msg); + loggingIn_ = false; + emit loggingInChanged(); + + error_ = msg; + emit errorOccurred(); } void -LoginPage::showError(QLabel *label, const QString &msg) +LoginPage::setHomeserver(QString hs) { - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - label->setFixedHeight((int)qCeil(width / 200.0) * height); - label->setText(msg); + if (hs != homeserver_) { + homeserver_ = hs; + homeserverValid_ = false; + emit homeserverChanged(); + http::client()->set_server(hs.toStdString()); + checkHomeserverVersion(); + } } void LoginPage::onMatrixIdEntered() { - error_label_->setText(QLatin1String("")); + clearErrors(); + + homeserverValid_ = false; + emit homeserverChanged(); User user; + try { + user = parse(mxid_.toStdString()); + } catch (const std::exception &) { + mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"); + emit mxidErrorChanged(); + return; + } - if (!matrixid_input_->isValid()) { - error_matrixid_label_->show(); - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + if (user.hostname().empty() || user.localpart().empty()) { + mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"); + emit mxidErrorChanged(); return; } else { - error_matrixid_label_->setText(QLatin1String("")); - error_matrixid_label_->hide(); + nhlog::net()->debug("hostname: {}", user.hostname()); } - try { - user = parse(matrixid_input_->text().toStdString()); - } catch (const std::exception &) { - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } - - QString homeServer = QString::fromStdString(user.hostname()); - if (homeServer != inferredServerAddress_) { - serverInput_->hide(); - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - if (serverInput_->isVisible()) { - matrixidLayout_->removeWidget(spinner_); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); - } else { - serverLayout_->removeWidget(spinner_); - matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); - } - - inferredServerAddress_ = homeServer; - serverInput_->setText(homeServer); + if (user.hostname() != inferredServerAddress_.toStdString()) { + homeserverNeeded_ = false; + lookingUpHs_ = true; + emit lookingUpHsChanged(); http::client()->set_server(user.hostname()); http::client()->verify_certificates( !UserSettings::instance()->disableCertificateValidation()); + homeserver_ = QString::fromStdString(user.hostname()); + emit homeserverChanged(); http::client()->well_known( [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { @@ -286,6 +128,7 @@ LoginPage::onMatrixIdEntered() nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); http::client()->set_server(res.homeserver.base_url); + emit homeserverChanged(); checkHomeserverVersion(); }); } @@ -294,6 +137,16 @@ LoginPage::onMatrixIdEntered() void LoginPage::checkHomeserverVersion() { + clearErrors(); + + try { + User user = parse(mxid_.toStdString()); + } catch (const std::exception &) { + mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"); + emit mxidErrorChanged(); + return; + } + http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) { if (err) { if (err->status_code == 404) { @@ -332,93 +185,64 @@ LoginPage::checkHomeserverVersion() }); } -void -LoginPage::onServerAddressEntered() -{ - error_label_->setText(QLatin1String("")); - http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation()); - http::client()->set_server(serverInput_->text().toStdString()); - checkHomeserverVersion(); - - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); -} - void LoginPage::versionError(const QString &error) { - showError(error_label_, error); - serverInput_->show(); + showError(error); - spinner_->stop(); - serverLayout_->removeWidget(spinner_); - serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); - errorIcon_->show(); - matrixidLayout_->removeWidget(spinner_); + homeserverNeeded_ = true; + lookingUpHs_ = false; + homeserverValid_ = false; + emit lookingUpHsChanged(); + emit versionLookedUp(); } void -LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_) +LoginPage::versionOk(bool passwordSupported, bool ssoSupported) { - passwordSupported = passwordSupported_; - ssoSupported = ssoSupported_; + passwordSupported_ = passwordSupported; + ssoSupported_ = ssoSupported; - serverLayout_->removeWidget(spinner_); - matrixidLayout_->removeWidget(spinner_); - spinner_->stop(); - - password_input_->setVisible(passwordSupported); - password_input_->setEnabled(passwordSupported); - sso_login_button_->setVisible(ssoSupported); - login_button_->setVisible(passwordSupported); - - if (serverInput_->isVisible()) - serverInput_->hide(); + lookingUpHs_ = false; + homeserverValid_ = true; + emit homeserverChanged(); + emit lookingUpHsChanged(); + emit versionLookedUp(); } void -LoginPage::onLoginButtonClicked(LoginMethod loginMethod) +LoginPage::onLoginButtonClicked(LoginMethod loginMethod, + QString userid, + QString password, + QString deviceName) { - error_label_->setText(QLatin1String("")); + clearErrors(); + User user; - if (!matrixid_input_->isValid()) { - error_matrixid_label_->show(); - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } else { - error_matrixid_label_->setText(QLatin1String("")); - error_matrixid_label_->hide(); - } - try { - user = parse(matrixid_input_->text().toStdString()); + user = parse(userid.toStdString()); } catch (const std::exception &) { - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"); + emit mxidErrorChanged(); return; } if (loginMethod == LoginMethod::Password) { - if (password_input_->text().isEmpty()) - return showError(error_label_, tr("Empty password")); + if (password.isEmpty()) + return showError(tr("Empty password")); http::client()->login( user.localpart(), - password_input_->text().toStdString(), - deviceName_->text().trimmed().isEmpty() ? initialDeviceName() - : deviceName_->text().toStdString(), + password.toStdString(), + deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString(), [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { if (err) { auto error = err->matrix_error.error; if (error.empty()) error = err->parse_error; - showErrorMessage(error_label_, QString::fromStdString(error)); - emit errorOccurred(); + showError(QString::fromStdString(error)); return; } @@ -432,34 +256,33 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod) }); } else { auto sso = new SSOHandler(); - connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) { - mtx::requests::Login req{}; - req.token = token; - req.type = mtx::user_interactive::auth_types::token; - req.device_id = deviceName_->text().trimmed().isEmpty() - ? initialDeviceName() - : deviceName_->text().toStdString(); - http::client()->login( - req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { - if (err) { - showErrorMessage(error_label_, - QString::fromStdString(err->matrix_error.error)); - emit errorOccurred(); - return; - } + connect( + sso, &SSOHandler::ssoSuccess, this, [this, sso, userid, deviceName](std::string token) { + mtx::requests::Login req{}; + req.token = token; + req.type = mtx::user_interactive::auth_types::token; + req.device_id = + deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString(); + http::client()->login( + req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { + if (err) { + showError(QString::fromStdString(err->matrix_error.error)); + emit errorOccurred(); + return; + } - if (res.well_known) { - http::client()->set_server(res.well_known->homeserver.base_url); - nhlog::net()->info("Login requested to user server: " + - res.well_known->homeserver.base_url); - } + if (res.well_known) { + http::client()->set_server(res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } - emit loginOk(res); - }); - sso->deleteLater(); - }); + emit loginOk(res); + }); + sso->deleteLater(); + }); connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() { - showErrorMessage(error_label_, tr("SSO login failed")); + showError(tr("SSO login failed")); emit errorOccurred(); sso->deleteLater(); }); @@ -468,37 +291,6 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod) QString::fromStdString(http::client()->login_sso_redirect(sso->url()))); } - emit loggingIn(); -} - -void -LoginPage::reset() -{ - matrixid_input_->clear(); - password_input_->clear(); - password_input_->show(); - serverInput_->clear(); - - spinner_->stop(); - errorIcon_->hide(); - serverLayout_->removeWidget(spinner_); - serverLayout_->removeWidget(errorIcon_); - matrixidLayout_->removeWidget(spinner_); - - inferredServerAddress_.clear(); -} - -void -LoginPage::onBackButtonClicked() -{ - emit backButtonClicked(); -} - -void -LoginPage::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.initFrom(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + loggingIn_ = true; + emit loggingInChanged(); } diff --git a/src/LoginPage.h b/src/LoginPage.h index fbfd8710..a613bb48 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -6,16 +6,7 @@ #pragma once -#include - -class FlatButton; -class LoadingIndicator; -class OverlayModal; -class RaisedButton; -class TextField; -class QLabel; -class QVBoxLayout; -class QHBoxLayout; +#include namespace mtx { namespace responses { @@ -23,24 +14,61 @@ struct Login; } } -class LoginPage : public QWidget +class LoginPage : public QObject { Q_OBJECT + Q_PROPERTY(QString mxid READ mxid WRITE setMxid NOTIFY matrixIdChanged) + Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) + + Q_PROPERTY(QString mxidError READ mxidError NOTIFY mxidErrorChanged) + Q_PROPERTY(QString error READ error NOTIFY errorOccurred) + Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged) + Q_PROPERTY(bool homeserverValid READ homeserverValid NOTIFY lookingUpHsChanged) + Q_PROPERTY(bool loggingIn READ loggingIn NOTIFY loggingInChanged) + Q_PROPERTY(bool passwordSupported READ passwordSupported NOTIFY versionLookedUp) + Q_PROPERTY(bool ssoSupported READ ssoSupported NOTIFY versionLookedUp) + Q_PROPERTY(bool homeserverNeeded READ homeserverNeeded NOTIFY versionLookedUp) + public: enum class LoginMethod { Password, SSO, }; + Q_ENUM(LoginMethod) - LoginPage(QWidget *parent = nullptr); + LoginPage(QObject *parent = nullptr); - void reset(); + Q_INVOKABLE QString initialDeviceName() const + { + return QString::fromStdString(initialDeviceName_()); + } + bool lookingUpHs() const { return lookingUpHs_; } + bool loggingIn() const { return loggingIn_; } + bool passwordSupported() const { return passwordSupported_; } + bool ssoSupported() const { return ssoSupported_; } + bool homeserverNeeded() const { return homeserverNeeded_; } + bool homeserverValid() const { return homeserverValid_; } + + QString homeserver() { return homeserver_; } + QString mxid() { return mxid_; } + + QString error() { return error_; } + QString mxidError() { return mxidError_; } + + void setHomeserver(QString hs); + void setMxid(QString id) + { + if (id != mxid_) { + mxid_ = id; + emit matrixIdChanged(); + onMatrixIdEntered(); + } + } signals: - void backButtonClicked(); - void loggingIn(); + void loggingInChanged(); void errorOccurred(); //! Used to trigger the corresponding slot outside of the main thread. @@ -48,28 +76,26 @@ signals: void versionOkCb(bool passwordSupported, bool ssoSupported); void loginOk(const mtx::responses::Login &res); - void showErrorMessage(QLabel *label, const QString &msg); -protected: - void paintEvent(QPaintEvent *event) override; + void onServerAddressEntered(); + + void matrixIdChanged(); + void homeserverChanged(); + + void mxidErrorChanged(); + void lookingUpHsChanged(); + void versionLookedUp(); + void versionLookupFinished(); public slots: // Displays errors produced during the login. void showError(const QString &msg); - void showError(QLabel *label, const QString &msg); - -private slots: - // Callback for the back button. - void onBackButtonClicked(); // Callback for the login button. - void onLoginButtonClicked(LoginMethod loginMethod); - - // Callback for probing the server found in the mxid - void onMatrixIdEntered(); - - // Callback for probing the manually entered server - void onServerAddressEntered(); + void onLoginButtonClicked(LoginMethod loginMethod, + QString userid, + QString password, + QString deviceName); // Callback for errors produced during server probing void versionError(const QString &error_message); @@ -78,7 +104,8 @@ private slots: private: void checkHomeserverVersion(); - std::string initialDeviceName() + void onMatrixIdEntered(); + std::string initialDeviceName_() const { #if defined(Q_OS_MAC) return "Nheko on macOS"; @@ -92,34 +119,27 @@ private: return "Nheko"; #endif } + void clearErrors() + { + error_.clear(); + mxidError_.clear(); + emit errorOccurred(); + emit mxidErrorChanged(); + } - QVBoxLayout *top_layout_; - - QHBoxLayout *top_bar_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; - - QLabel *logo_; - QLabel *error_label_; - QLabel *error_matrixid_label_; - - QHBoxLayout *serverLayout_; - QHBoxLayout *matrixidLayout_; - LoadingIndicator *spinner_; - QLabel *errorIcon_; QString inferredServerAddress_; - FlatButton *back_button_; - RaisedButton *login_button_, *sso_login_button_; + QString mxid_; + QString homeserver_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; + QString mxidError_; + QString error_; - TextField *matrixid_input_; - TextField *password_input_; - TextField *deviceName_; - TextField *serverInput_; - bool passwordSupported = true; - bool ssoSupported = false; + bool passwordSupported_ = true; + bool ssoSupported_ = false; + + bool lookingUpHs_ = false; + bool loggingIn_ = false; + bool homeserverNeeded_ = false; + bool homeserverValid_ = false; }; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 03a99b0f..3efdc23f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -87,7 +87,6 @@ MainWindow::MainWindow(QWindow *parent) setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml"))); // modal_ = new OverlayModal(this); - // QFont font; // font.setStyleStrategy(QFont::PreferAntialias); // setFont(font); @@ -95,31 +94,19 @@ MainWindow::MainWindow(QWindow *parent) trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this); // welcome_page_ = new WelcomePage(this); - // login_page_ = new LoginPage(this); // register_page_ = new RegisterPage(this); //// Initialize sliding widget manager. - // connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); // connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); - // connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - // connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar); - // connect(register_page_, &RegisterPage::registering, this, - // &MainWindow::showOverlayProgressBar); connect(login_page_, &LoginPage::errorOccurred, this, - // [this]() { removeOverlayProgressBar(); }); connect( - // register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); - // }); - // connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - - connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage); + connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); }); // connect( // chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { - login_page_->showError(msg); - showLoginPage(); + switchToLoginPage(msg); }); connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible); @@ -210,6 +197,7 @@ MainWindow::registerQmlTypes() qmlRegisterType("im.nheko", 1, 0, "MxcAnimatedImage"); qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); + qmlRegisterType("im.nheko", 1, 0, "Login"); qmlRegisterUncreatableType( "im.nheko", 1, @@ -375,9 +363,7 @@ MainWindow::removeOverlayProgressBar() QTimer *timer = new QTimer(this); timer->setSingleShot(true); - connect(timer, &QTimer::timeout, this, [this, timer]() { - timer->deleteLater(); - }); + connect(timer, &QTimer::timeout, this, [this, timer]() { timer->deleteLater(); }); // FIXME: Snackbar doesn't work if it's initialized in the constructor. // QTimer::singleShot(0, this, [this]() { @@ -404,7 +390,6 @@ MainWindow::showChatPage() showOverlayProgressBar(); - // login_page_->reset(); chat_page_->bootstrap(userid, homeserver, token); connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged); connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged); @@ -463,8 +448,7 @@ MainWindow::hasActiveUser() void MainWindow::showOverlayProgressBar() -{ -} +{} void MainWindow::openCreateRoomDialog( @@ -485,8 +469,7 @@ MainWindow::showTransparentOverlayModal(QWidget *, QFlags) void MainWindow::showSolidOverlayModal(QWidget *, QFlags) -{ -} +{} bool MainWindow::hasActiveDialogs() const @@ -503,8 +486,7 @@ MainWindow::pageSupportsTray() const void MainWindow::hideOverlay() -{ -} +{} inline void MainWindow::showDialog(QWidget *dialog) @@ -520,11 +502,6 @@ MainWindow::showWelcomePage() removeOverlayProgressBar(); } -void -MainWindow::showLoginPage() -{ -} - void MainWindow::showRegisterPage() {} diff --git a/src/MainWindow.h b/src/MainWindow.h index ea919f4d..5a351893 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -20,7 +20,6 @@ class ChatPage; class RegisterPage; -class LoginPage; class WelcomePage; class LoadingIndicator; @@ -65,6 +64,9 @@ public: MxcImageProvider *imageProvider() { return imgProvider; } + //! Show the chat page and start communicating with the given access token. + void showChatPage(); + protected: void closeEvent(QCloseEvent *event); bool event(QEvent *event) override; @@ -76,15 +78,9 @@ private slots: //! Show the welcome page in the main window. void showWelcomePage(); - //! Show the login page in the main window. - void showLoginPage(); - //! Show the register page in the main window. void showRegisterPage(); - //! Show the chat page and start communicating with the given access token. - void showChatPage(); - void showOverlayProgressBar(); void removeOverlayProgressBar(); @@ -96,6 +92,7 @@ signals: void switchToChatPage(); void switchToWelcomePage(); + void switchToLoginPage(QString error); private: void showDialog(QWidget *dialog); @@ -112,8 +109,6 @@ private: //! The initial welcome screen. WelcomePage *welcome_page_; - //! The login screen. - LoginPage *login_page_; //! The register page. RegisterPage *register_page_; //! The main chat area. diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 9419f224..13ab5dbb 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -110,7 +110,7 @@ public slots: RoomlistModel *rooms() { return rooms_; } private: - bool isInitialSync_ = true; + bool isInitialSync_ = true; RoomlistModel *rooms_ = nullptr; CommunitiesModel *communities_ = nullptr; From 573624a490201920469503de0f9eeb1d67bcf5cd Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 25 Jan 2022 11:09:54 +0100 Subject: [PATCH 04/17] Remove some unused functions --- src/ChatPage.cpp | 2 ++ src/MainWindow.cpp | 41 ----------------------------------------- src/MainWindow.h | 12 ------------ 3 files changed, 2 insertions(+), 53 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index f30e0466..fe6b08eb 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -777,6 +777,8 @@ ChatPage::inviteUser(QString userid, QString reason) userid.toStdString(), [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { if (err) { + nhlog::net()->error( + "Failed to invite {} to {}: {}", userid.toStdString(), room.toStdString(), *err); emit showNotification( tr("Failed to invite %1 to %2: %3") .arg(userid, room, QString::fromStdString(err->matrix_error.error))); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 3efdc23f..d3395c68 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -357,23 +357,6 @@ MainWindow::saveCurrentWindowSize() settings->setValue(QStringLiteral("window/height"), current.height()); } -void -MainWindow::removeOverlayProgressBar() -{ - QTimer *timer = new QTimer(this); - timer->setSingleShot(true); - - connect(timer, &QTimer::timeout, this, [this, timer]() { timer->deleteLater(); }); - - // FIXME: Snackbar doesn't work if it's initialized in the constructor. - // QTimer::singleShot(0, this, [this]() { - // snackBar_ = new SnackBar(this); - // connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage); - //}); - - timer->start(50); -} - void MainWindow::showChatPage() { @@ -388,8 +371,6 @@ MainWindow::showChatPage() userSettings_.data()->setDeviceId(device_id); userSettings_.data()->setHomeserver(homeserver); - showOverlayProgressBar(); - chat_page_->bootstrap(userid, homeserver, token); connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged); connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged); @@ -446,10 +427,6 @@ MainWindow::hasActiveUser() settings->contains(prefix + "auth/user_id"); } -void -MainWindow::showOverlayProgressBar() -{} - void MainWindow::openCreateRoomDialog( std::function callback) @@ -463,14 +440,6 @@ MainWindow::openCreateRoomDialog( showDialog(dialog); } -void -MainWindow::showTransparentOverlayModal(QWidget *, QFlags) -{} - -void -MainWindow::showSolidOverlayModal(QWidget *, QFlags) -{} - bool MainWindow::hasActiveDialogs() const { @@ -484,10 +453,6 @@ MainWindow::pageSupportsTray() const //! !register_page_->isVisible(); } -void -MainWindow::hideOverlay() -{} - inline void MainWindow::showDialog(QWidget *dialog) { @@ -496,12 +461,6 @@ MainWindow::showDialog(QWidget *dialog) dialog->show(); } -void -MainWindow::showWelcomePage() -{ - removeOverlayProgressBar(); -} - void MainWindow::showRegisterPage() {} diff --git a/src/MainWindow.h b/src/MainWindow.h index 5a351893..02213cdf 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -56,12 +56,6 @@ public: openCreateRoomDialog(std::function callback); void openJoinRoomDialog(std::function callback); - void hideOverlay(); - void showSolidOverlayModal(QWidget *content, QFlags flags = Qt::AlignCenter); - void - showTransparentOverlayModal(QWidget *content, - QFlags flags = Qt::AlignTop | Qt::AlignHCenter); - MxcImageProvider *imageProvider() { return imgProvider; } //! Show the chat page and start communicating with the given access token. @@ -75,15 +69,9 @@ private slots: //! Handle interaction with the tray icon. void iconActivated(QSystemTrayIcon::ActivationReason reason); - //! Show the welcome page in the main window. - void showWelcomePage(); - //! Show the register page in the main window. void showRegisterPage(); - void showOverlayProgressBar(); - void removeOverlayProgressBar(); - virtual void setWindowTitle(int notificationCount); signals: From f28013dc181e279b6659484c7b4a46746fbd2eb3 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 28 Jan 2022 15:24:56 +0100 Subject: [PATCH 05/17] Port registration to Qml --- resources/qml/MatrixTextField.qml | 8 + resources/qml/QuickSwitcher.qml | 2 +- resources/qml/Root.qml | 7 + resources/qml/pages/LoginPage.qml | 6 +- resources/qml/pages/RegistrationPage.qml | 215 ++++++++++ resources/qml/pages/WelcomePage.qml | 1 + resources/res.qrc | 1 + src/LoginPage.h | 30 +- src/MainWindow.cpp | 26 +- src/MainWindow.h | 3 - src/RegisterPage.cpp | 475 +++++++---------------- src/RegisterPage.h | 113 +++--- 12 files changed, 444 insertions(+), 443 deletions(-) create mode 100644 resources/qml/pages/RegistrationPage.qml diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml index 655d53f1..05f2c82f 100644 --- a/resources/qml/MatrixTextField.qml +++ b/resources/qml/MatrixTextField.qml @@ -21,6 +21,14 @@ ColumnLayout { property alias echoMode: input.echoMode property alias selectByMouse: input.selectByMouse + Timer { + id: timer + interval: 350 + onTriggered: editingFinished() + } + + onTextChanged: timer.restart() + signal textEdited signal accepted signal editingFinished diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index 6d217c72..8747c47d 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -58,7 +58,7 @@ Popup { id: completerPopup x: roomTextInput.x - y: roomTextInput.y + roomTextInput.height + y: roomTextInput.y + quickSwitcher.textHeight visible: roomTextInput.length > 0 width: parent.width completerName: "room" diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index f3976cc0..1969c613 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -395,6 +395,13 @@ Pane { } } + Component { + id: registerPage + + RegistrationPage { + } + } + Connections { function onSwitchToChatPage() { mainWindow.replace(null, chatPage); diff --git a/resources/qml/pages/LoginPage.qml b/resources/qml/pages/LoginPage.qml index 0beb2bdc..4d3a52b3 100644 --- a/resources/qml/pages/LoginPage.qml +++ b/resources/qml/pages/LoginPage.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 @@ -9,7 +13,7 @@ import "../" Item { id: loginPage - property int maxExpansion: 800 + property int maxExpansion: 400 property string error: login.error diff --git a/resources/qml/pages/RegistrationPage.qml b/resources/qml/pages/RegistrationPage.qml new file mode 100644 index 00000000..44836ccb --- /dev/null +++ b/resources/qml/pages/RegistrationPage.qml @@ -0,0 +1,215 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.15 +import im.nheko 1.0 +import "../components/" +import "../ui/" +import "../" + +Item { + id: registrationPage + property int maxExpansion: 400 + + property string error: regis.error + + Registration { + id: regis + } + + ScrollView { + id: scroll + + clip: false + palette: Nheko.colors + ScrollBar.horizontal.visible: false + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + height: Math.min(registrationPage.height, col.implicitHeight) + anchors.margins: Nheko.paddingLarge + + contentWidth: availableWidth + + ColumnLayout { + id: col + + spacing: Nheko.paddingMedium + + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(registrationPage.maxExpansion, scroll.width- Nheko.paddingLarge*2) + + Image { + Layout.alignment: Qt.AlignHCenter + source: "qrc:/logos/login.png" + height: 128 + width: 128 + } + + RowLayout { + spacing: Nheko.paddingLarge + + Layout.fillWidth: true + MatrixTextField { + id: hsLabel + label: qsTr("Homeserver") + placeholderText: qsTr("your.server") + onEditingFinished: regis.setServer(text) + + ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.") + } + + + Spinner { + height: hsLabel.height/2 + Layout.alignment: Qt.AlignBottom + + visible: running + running: regis.lookingUpHs + foreground: Nheko.colors.mid + } + } + + MatrixText { + textFormat: Text.PlainText + color: Nheko.theme.error + text: regis.hsError + visible: text + } + + RowLayout { + spacing: Nheko.paddingLarge + + visible: regis.supported + + Layout.fillWidth: true + MatrixTextField { + id: usernameLabel + Layout.fillWidth: true + label: qsTr("Username") + ToolTip.text: qsTr("The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.") + onEditingFinished: regis.checkUsername(text) + } + Spinner { + height: usernameLabel.height/2 + Layout.alignment: Qt.AlignBottom + + visible: running + running: regis.lookingUpUsername + foreground: Nheko.colors.mid + } + + Image { + width: usernameLabel.height/2 + height: width + Layout.preferredHeight: usernameLabel.height/2 + Layout.preferredWidth: usernameLabel.height/2 + Layout.alignment: Qt.AlignBottom + source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?"+Nheko.theme.error) + visible: regis.usernameAvailable || regis.usernameUnavailable + ToolTip.visible: ma.hovered + ToolTip.text: qsTr("Back") + sourceSize.height: height * Screen.devicePixelRatio + sourceSize.width: width * Screen.devicePixelRatio + HoverHandler { + id: ma + } + } + } + + MatrixText { + textFormat: Text.PlainText + color: Nheko.theme.error + text: regis.usernameError + visible: text + } + + + MatrixTextField { + visible: regis.supported + id: passwordLabel + Layout.fillWidth: true + label: qsTr("Password") + echoMode: TextInput.Password + ToolTip.text: qsTr("Please choose a secure password. The exact requirements for password strength may depend on your server.") + } + + MatrixTextField { + visible: regis.supported + id: passwordConfirmationLabel + Layout.fillWidth: true + label: qsTr("Password confirmation") + echoMode: TextInput.Password + } + + MatrixText { + visible: regis.supported + textFormat: Text.PlainText + color: Nheko.theme.error + text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : "" + } + + MatrixTextField { + visible: regis.supported + id: deviceNameLabel + Layout.fillWidth: true + label: qsTr("Device name") + placeholderText: regis.initialDeviceName() + ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.") + } + + Item { + height: Nheko.avatarSize + Layout.fillWidth: true + + Spinner { + height: parent.height + anchors.centerIn: parent + + visible: running + running: regis.registering + foreground: Nheko.colors.mid + } + } + + MatrixText { + textFormat: Text.PlainText + color: Nheko.theme.error + text: registrationPage.error + visible: text + } + + FlatButton { + id: regisBtn + visible: regis.supported + enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text + Layout.alignment: Qt.AlignHCenter + text: qsTr("REGISTER") + function register() { + regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text) + } + onClicked: regisBtn.register() + Keys.onEnterPressed: regisBtn.register() + Keys.onReturnPressed: regisBtn.register() + Keys.enabled: regisBtn.enabled && regis.supported + } + } + } + + ImageButton { + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: Nheko.paddingMedium + width: Nheko.avatarSize + height: Nheko.avatarSize + image: ":/icons/icons/ui/angle-arrow-left.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Back") + onClicked: mainWindow.pop() + } +} + diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml index 43050d8e..627d8b1c 100644 --- a/resources/qml/pages/WelcomePage.qml +++ b/resources/qml/pages/WelcomePage.qml @@ -48,6 +48,7 @@ ColumnLayout { Layout.alignment: Qt.AlignHCenter text: qsTr("REGISTER") onClicked: { + mainWindow.push(registerPage); } } FlatButton { diff --git a/resources/res.qrc b/resources/res.qrc index efc0e74a..f7f2a796 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -112,6 +112,7 @@ qml/pages/UserSettingsPage.qml qml/pages/WelcomePage.qml qml/pages/LoginPage.qml + qml/pages/RegistrationPage.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml diff --git a/src/LoginPage.h b/src/LoginPage.h index a613bb48..9a1b9653 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -67,6 +67,22 @@ public: onMatrixIdEntered(); } } + + static std::string initialDeviceName_() + { +#if defined(Q_OS_MAC) + return "Nheko on macOS"; +#elif defined(Q_OS_LINUX) + return "Nheko on Linux"; +#elif defined(Q_OS_WIN) + return "Nheko on Windows"; +#elif defined(Q_OS_FREEBSD) + return "Nheko on FreeBSD"; +#else + return "Nheko"; +#endif + } + signals: void loggingInChanged(); void errorOccurred(); @@ -105,20 +121,6 @@ public slots: private: void checkHomeserverVersion(); void onMatrixIdEntered(); - std::string initialDeviceName_() const - { -#if defined(Q_OS_MAC) - return "Nheko on macOS"; -#elif defined(Q_OS_LINUX) - return "Nheko on Linux"; -#elif defined(Q_OS_WIN) - return "Nheko on Windows"; -#elif defined(Q_OS_FREEBSD) - return "Nheko on FreeBSD"; -#else - return "Nheko"; -#endif - } void clearErrors() { error_.clear(); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index d3395c68..1c915fba 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -85,24 +85,10 @@ MainWindow::MainWindow(QWindow *parent) setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color()); setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml"))); - // modal_ = new OverlayModal(this); - - // QFont font; - // font.setStyleStrategy(QFont::PreferAntialias); - // setFont(font); trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this); - // welcome_page_ = new WelcomePage(this); - // register_page_ = new RegisterPage(this); - - //// Initialize sliding widget manager. - - // connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); - connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); }); - // connect( - // chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { @@ -117,13 +103,6 @@ MainWindow::MainWindow(QWindow *parent) connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); - // connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { - // http::client()->set_user(res.user_id); - // showChatPage(); - // }); - - // connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); - trayIcon_->setVisible(userSettings_->tray()); // load cache on event loop @@ -198,6 +177,7 @@ MainWindow::registerQmlTypes() qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); qmlRegisterType("im.nheko", 1, 0, "Login"); + qmlRegisterType("im.nheko", 1, 0, "Registration"); qmlRegisterUncreatableType( "im.nheko", 1, @@ -460,7 +440,3 @@ MainWindow::showDialog(QWidget *dialog) dialog->raise(); dialog->show(); } - -void -MainWindow::showRegisterPage() -{} diff --git a/src/MainWindow.h b/src/MainWindow.h index 02213cdf..33e16271 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -69,9 +69,6 @@ private slots: //! Handle interaction with the tray icon. void iconActivated(QSystemTrayIcon::ActivationReason reason); - //! Show the register page in the main window. - void showRegisterPage(); - virtual void setWindowTitle(int notificationCount); signals: diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index d089ac96..f94e1412 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -4,312 +4,81 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -#include -#include -#include -#include -#include -#include -#include - +#include #include #include #include #include "Config.h" #include "Logging.h" +#include "LoginPage.h" #include "MainWindow.h" #include "MatrixClient.h" #include "RegisterPage.h" -#include "ui/FlatButton.h" -#include "ui/RaisedButton.h" -#include "ui/TextField.h" #include "ui/UIA.h" -#include "dialogs/FallbackAuth.h" -#include "dialogs/ReCaptcha.h" +RegisterPage::RegisterPage(QObject *parent) + : QObject(parent) +{} -Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized) -Q_DECLARE_METATYPE(mtx::user_interactive::Auth) - -RegisterPage::RegisterPage(QWidget *parent) - : QWidget(parent) +void +RegisterPage::setError(QString err) { - qRegisterMetaType(); - qRegisterMetaType(); - top_layout_ = new QVBoxLayout(); + registrationError_ = err; + emit errorChanged(); + registering_ = false; + emit registeringChanged(); +} +void +RegisterPage::setHsError(QString err) +{ + hsError_ = err; + emit hsErrorChanged(); + lookingUpHs_ = false; + emit lookingUpHsChanged(); +} - back_layout_ = new QHBoxLayout(); - back_layout_->setSpacing(0); - back_layout_->setContentsMargins(5, 5, -1, -1); - - back_button_ = new FlatButton(this); - back_button_->setMinimumSize(QSize(30, 30)); - - QIcon icon; - icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg")); - - back_button_->setIcon(icon); - back_button_->setIconSize(QSize(32, 32)); - - back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - back_layout_->addStretch(1); - - QIcon logo; - logo.addFile(QStringLiteral(":/logos/register.png")); - - logo_ = new QLabel(this); - logo_->setPixmap(logo.pixmap(128)); - - logo_layout_ = new QHBoxLayout(); - logo_layout_->setContentsMargins(0, 0, 0, 0); - logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); - - form_wrapper_ = new QHBoxLayout(); - form_widget_ = new QWidget(); - form_widget_->setMinimumSize(QSize(350, 300)); - - form_layout_ = new QVBoxLayout(); - form_layout_->setSpacing(20); - form_layout_->setContentsMargins(0, 0, 0, 40); - form_widget_->setLayout(form_layout_); - - form_wrapper_->addStretch(1); - form_wrapper_->addWidget(form_widget_); - form_wrapper_->addStretch(1); - - username_input_ = new TextField(); - username_input_->setLabel(tr("Username")); - username_input_->setRegexp(QRegularExpression(QStringLiteral("[a-z0-9._=/-]+"))); - username_input_->setToolTip(tr("The username must not be empty, and must contain only the " - "characters a-z, 0-9, ., _, =, -, and /.")); - - password_input_ = new TextField(); - password_input_->setLabel(tr("Password")); - password_input_->setRegexp(QRegularExpression(QStringLiteral("^.{8,}$"))); - password_input_->setEchoMode(QLineEdit::Password); - password_input_->setToolTip(tr("Please choose a secure password. The exact requirements " - "for password strength may depend on your server.")); - - password_confirmation_ = new TextField(); - password_confirmation_->setLabel(tr("Password confirmation")); - password_confirmation_->setEchoMode(QLineEdit::Password); - - server_input_ = new TextField(); - server_input_->setLabel(tr("Homeserver")); - server_input_->setRegexp(QRegularExpression(QStringLiteral(".+"))); - server_input_->setToolTip( - tr("A server that allows registration. Since matrix is decentralized, you need to first " - "find a server you can register on or host your own.")); - - error_username_label_ = new QLabel(this); - error_username_label_->setWordWrap(true); - error_username_label_->hide(); - - error_password_label_ = new QLabel(this); - error_password_label_->setWordWrap(true); - error_password_label_->hide(); - - error_password_confirmation_label_ = new QLabel(this); - error_password_confirmation_label_->setWordWrap(true); - error_password_confirmation_label_->hide(); - - error_server_label_ = new QLabel(this); - error_server_label_->setWordWrap(true); - error_server_label_->hide(); - - form_layout_->addWidget(username_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_username_label_, Qt::AlignHCenter); - form_layout_->addWidget(password_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_password_label_, Qt::AlignHCenter); - form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter); - form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter); - form_layout_->addWidget(server_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_server_label_, Qt::AlignHCenter); - - button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(0); - button_layout_->setContentsMargins(0, 0, 0, 0); - - error_label_ = new QLabel(this); - error_label_->setWordWrap(true); - - register_button_ = new RaisedButton(tr("REGISTER"), this); - register_button_->setMinimumSize(350, 65); - register_button_->setFontSize(conf::btn::fontSize); - register_button_->setCornerRadius(conf::btn::cornerRadius); - - button_layout_->addStretch(1); - button_layout_->addWidget(register_button_); - button_layout_->addStretch(1); - - top_layout_->addLayout(back_layout_); - top_layout_->addLayout(logo_layout_); - top_layout_->addLayout(form_wrapper_); - top_layout_->addStretch(1); - top_layout_->addLayout(button_layout_); - top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); - top_layout_->addStretch(1); - setLayout(top_layout_); - - connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked())); - - connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername); - connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword); - connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(password_confirmation_, - &TextField::editingFinished, - this, - &RegisterPage::checkPasswordConfirmation); - connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer); - - connect( - this, - &RegisterPage::serverError, - this, - [this](const QString &msg) { - server_input_->setValid(false); - showError(error_server_label_, msg); - }, - Qt::QueuedConnection); - - connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup); - connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck); - connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration); +QString +RegisterPage::initialDeviceName() const +{ + return QString::fromStdString(LoginPage::initialDeviceName_()); } void -RegisterPage::onBackButtonClicked() +RegisterPage::setServer(QString server) { - emit backButtonClicked(); -} + if (server == lastServer) + return; -void -RegisterPage::showError(const QString &msg) -{ - emit errorOccurred(); - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - error_label_->setFixedHeight(qCeil(width / 200.0) * height); - error_label_->setText(msg); -} + lastServer = server; -void -RegisterPage::showError(QLabel *label, const QString &msg) -{ - emit errorOccurred(); - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - label->setFixedHeight((int)qCeil(width / 200.0) * height); - label->setText(msg); - label->show(); -} + http::client()->set_server(server.toStdString()); + http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation()); -bool -RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg) -{ - if (t_field->isValid()) { - label->hide(); - return true; - } else { - showError(label, msg); - return false; - } -} + hsError_.clear(); + emit hsErrorChanged(); + supported_ = false; + lookingUpHs_ = true; + emit lookingUpHsChanged(); -bool -RegisterPage::checkUsername() -{ - return checkOneField(error_username_label_, - username_input_, - tr("The username must not be empty, and must contain only the " - "characters a-z, 0-9, ., _, =, -, and /.")); -} - -bool -RegisterPage::checkPassword() -{ - return checkOneField( - error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)")); -} - -bool -RegisterPage::checkPasswordConfirmation() -{ - if (password_input_->text() == password_confirmation_->text()) { - error_password_confirmation_label_->hide(); - password_confirmation_->setValid(true); - return true; - } else { - showError(error_password_confirmation_label_, tr("Passwords don't match")); - password_confirmation_->setValid(false); - return false; - } -} - -bool -RegisterPage::checkServer() -{ - // This doesn't check that the server is reachable, - // just that the input is not obviously wrong. - return checkOneField(error_server_label_, server_input_, tr("Invalid server name")); -} - -void -RegisterPage::onRegisterButtonClicked() -{ - if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) { - auto server = server_input_->text().toStdString(); - - http::client()->set_server(server); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); - - // This starts a chain of `emit`s which ends up doing the - // registration. Signals are used rather than normal function - // calls so that the dialogs used in UIA work correctly. - // - // The sequence of events looks something like this: - // - // doKnownLookup - // v - // doVersionsCheck - // v - // doRegistration -> loops the UIAHandler until complete - - emit wellKnownLookup(); - - emit registering(); - } -} - -void -RegisterPage::doWellKnownLookup() -{ http::client()->well_known( [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { if (err) { if (err->status_code == 404) { nhlog::net()->info("Autodiscovery: No .well-known."); // Check that the homeserver can be reached - emit versionsCheck(); + versionsCheck(); return; } if (!err->parse_error.empty()) { - emit serverError(tr("Autodiscovery failed. Received malformed response.")); + setHsError(tr("Autodiscovery failed. Received malformed response.")); nhlog::net()->error("Autodiscovery failed. Received malformed response."); + emit hsErrorChanged(); return; } - emit serverError(tr("Autodiscovery failed. Unknown error when " - "requesting .well-known.")); + setHsError(tr("Autodiscovery failed. Unknown error when requesting .well-known.")); nhlog::net()->error("Autodiscovery failed. Unknown error when " "requesting .well-known. {} {}", err->status_code, @@ -319,98 +88,140 @@ RegisterPage::doWellKnownLookup() nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); http::client()->set_server(res.homeserver.base_url); + emit hsErrorChanged(); // Check that the homeserver can be reached - emit versionsCheck(); + versionsCheck(); }); } void -RegisterPage::doVersionsCheck() +RegisterPage::versionsCheck() { // Make a request to /_matrix/client/versions to check the address // given is a Matrix homeserver. http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) { if (err) { if (err->status_code == 404) { - emit serverError(tr("The required endpoints were not found. Possibly " - "not a Matrix server.")); + setHsError( + tr("The required endpoints were not found. Possibly not a Matrix server.")); + emit hsErrorChanged(); return; } if (!err->parse_error.empty()) { - emit serverError(tr("Received malformed response. Make sure the homeserver " - "domain is valid.")); + setHsError( + tr("Received malformed response. Make sure the homeserver domain is valid.")); + emit hsErrorChanged(); return; } - emit serverError(tr("An unknown error occured. Make sure the " - "homeserver domain is valid.")); + setHsError(tr("An unknown error occured. Make sure the homeserver domain is valid.")); + emit hsErrorChanged(); return; } - // Attempt registration without an `auth` dict - emit registration(); + http::client()->registration( + [this](const mtx::responses::Register &, mtx::http::RequestErr e) { + nhlog::net()->debug("Registration check: {}", e); + + if (!e) { + setHsError(tr("Server does not support querying registration flows!")); + emit hsErrorChanged(); + return; + } + if (e->status_code != 401) { + setHsError(tr("Server does not support registration.")); + emit hsErrorChanged(); + return; + } + + supported_ = true; + lookingUpHs_ = false; + emit lookingUpHsChanged(); + }); }); } void -RegisterPage::doRegistration() +RegisterPage::checkUsername(QString name) { - // These inputs should still be alright, but check just in case - if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { - auto username = username_input_->text().toStdString(); - auto password = password_input_->text().toStdString(); - connect(UIA::instance(), &UIA::error, this, [this](QString msg) { - showError(msg); - disconnect(UIA::instance(), &UIA::error, this, nullptr); - }); - http::client()->registration( - username, - password, - ::UIA::instance()->genericHandler(QStringLiteral("Registration")), - registrationCb()); - } -} + usernameAvailable_ = usernameUnavailable_ = false; + usernameError_.clear(); + lookingUpUsername_ = true; + emit lookingUpUsernameChanged(); -mtx::http::Callback -RegisterPage::registrationCb() -{ - // Return a function to be used as the callback when an attempt at - // registration is made. - return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { - if (!err) { - http::client()->set_user(res.user_id); - http::client()->set_access_token(res.access_token); - emit registerOk(); - disconnect(UIA::instance(), &UIA::error, this, nullptr); - return; - } + http::client()->register_username_available( + name.toStdString(), + [this](const mtx::responses::Available &available, mtx::http::RequestErr e) { + if (e) { + if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_INVALID_USERNAME) { + usernameError_ = tr("Invalid username."); + } else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_USER_IN_USE) { + usernameError_ = tr("Name already in use."); + } else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_EXCLUSIVE) { + usernameError_ = tr("Part of the reserved namespace."); + } else { + } - // The server requires registration flows. - if (err->status_code == 401) { - if (err->matrix_error.unauthorized.flows.empty()) { - nhlog::net()->warn("failed to retrieve registration flows: " - "status_code({}), matrix_error({}) ", - static_cast(err->status_code), - err->matrix_error.error); - showError(QString::fromStdString(err->matrix_error.error)); - } - return; - } - - nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", - static_cast(err->status_code), - err->matrix_error.error); - - showError(QString::fromStdString(err->matrix_error.error)); - }; + usernameAvailable_ = false; + usernameUnavailable_ = true; + } else { + usernameAvailable_ = available.available; + usernameUnavailable_ = !available.available; + } + lookingUpUsername_ = false; + emit lookingUpUsernameChanged(); + }); } void -RegisterPage::paintEvent(QPaintEvent *) +RegisterPage::startRegistration(QString username, QString password, QString devicename) { - QStyleOption opt; - opt.initFrom(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + // These inputs should still be alright, but check just in case + if (!username.isEmpty() && !password.isEmpty() && usernameAvailable_ && supported_) { + registrationError_.clear(); + emit errorChanged(); + registering_ = true; + emit registeringChanged(); + + connect(UIA::instance(), &UIA::error, this, [this](QString msg) { + setError(msg); + disconnect(UIA::instance(), &UIA::error, this, nullptr); + }); + http::client()->registration( + username.toStdString(), + password.toStdString(), + ::UIA::instance()->genericHandler(QStringLiteral("Registration")), + [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { + registering_ = false; + emit registeringChanged(); + + if (!err) { + http::client()->set_user(res.user_id); + http::client()->set_access_token(res.access_token); + MainWindow::instance()->showChatPage(); + disconnect(UIA::instance(), &UIA::error, this, nullptr); + return; + } + + // The server requires registration flows. + if (err->status_code == 401 && err->matrix_error.unauthorized.flows.empty()) { + nhlog::net()->warn("failed to retrieve registration flows: " + "status_code({}), matrix_error({}) ", + static_cast(err->status_code), + err->matrix_error.error); + setError(QString::fromStdString(err->matrix_error.error)); + disconnect(UIA::instance(), &UIA::error, this, nullptr); + return; + } + + nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", + static_cast(err->status_code), + err->matrix_error.error); + + setError(QString::fromStdString(err->matrix_error.error)); + disconnect(UIA::instance(), &UIA::error, this, nullptr); + }, + devicename.isEmpty() ? LoginPage::initialDeviceName_() : devicename.toStdString()); + } } diff --git a/src/RegisterPage.h b/src/RegisterPage.h index f76313c8..9f32e820 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -6,88 +6,67 @@ #pragma once -#include - -#include +#include +#include #include #include -class FlatButton; -class RaisedButton; -class TextField; -class QLabel; -class QVBoxLayout; -class QHBoxLayout; - -class RegisterPage : public QWidget +class RegisterPage : public QObject { Q_OBJECT -public: - RegisterPage(QWidget *parent = nullptr); + Q_PROPERTY(QString error READ error NOTIFY errorChanged) + Q_PROPERTY(QString hsError READ hsError NOTIFY hsErrorChanged) + Q_PROPERTY(QString usernameError READ usernameError NOTIFY lookingUpUsernameChanged) + Q_PROPERTY(bool registering READ registering NOTIFY registeringChanged) + Q_PROPERTY(bool supported READ supported NOTIFY lookingUpHsChanged) + Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged) + Q_PROPERTY(bool lookingUpUsername READ lookingUpUsername NOTIFY lookingUpUsernameChanged) + Q_PROPERTY(bool usernameAvailable READ usernameAvailable NOTIFY lookingUpUsernameChanged) + Q_PROPERTY(bool usernameUnavailable READ usernameUnavailable NOTIFY lookingUpUsernameChanged) -protected: - void paintEvent(QPaintEvent *event) override; +public: + RegisterPage(QObject *parent = nullptr); + + Q_INVOKABLE void setServer(QString server); + Q_INVOKABLE void checkUsername(QString name); + Q_INVOKABLE void startRegistration(QString username, QString password, QString deviceName); + Q_INVOKABLE QString initialDeviceName() const; + + bool registering() const { return registering_; } + bool supported() const { return supported_; } + bool lookingUpHs() const { return lookingUpHs_; } + bool lookingUpUsername() const { return lookingUpUsername_; } + bool usernameAvailable() const { return usernameAvailable_; } + bool usernameUnavailable() const { return usernameUnavailable_; } + + QString error() const { return registrationError_; } + QString usernameError() const { return usernameError_; } + QString hsError() const { return hsError_; } signals: - void backButtonClicked(); - void errorOccurred(); + void errorChanged(); + void hsErrorChanged(); - //! Used to trigger the corresponding slot outside of the main thread. - void serverError(const QString &err); - - void wellKnownLookup(); - void versionsCheck(); - void registration(); - - void registering(); - void registerOk(); - -private slots: - void onBackButtonClicked(); - void onRegisterButtonClicked(); - - // function for showing different errors - void showError(const QString &msg); - void showError(QLabel *label, const QString &msg); - - bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg); - bool checkUsername(); - bool checkPassword(); - bool checkPasswordConfirmation(); - bool checkServer(); - - void doWellKnownLookup(); - void doVersionsCheck(); - void doRegistration(); - mtx::http::Callback registrationCb(); + void registeringChanged(); + void lookingUpHsChanged(); + void lookingUpUsernameChanged(); private: - QVBoxLayout *top_layout_; + void versionsCheck(); - QHBoxLayout *back_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; + void setHsError(QString err); + void setError(QString err); - QLabel *logo_; - QLabel *error_label_; - QLabel *error_username_label_; - QLabel *error_password_label_; - QLabel *error_password_confirmation_label_; - QLabel *error_server_label_; - QLabel *error_registration_token_label_; + QString registrationError_, hsError_, usernameError_; - FlatButton *back_button_; - RaisedButton *register_button_; + bool registering_; + bool supported_; + bool lookingUpHs_; + bool lookingUpUsername_; + bool usernameAvailable_; + bool usernameUnavailable_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; - - TextField *username_input_; - TextField *password_input_; - TextField *password_confirmation_; - TextField *server_input_; - TextField *registration_token_input_; + QString lastServer; }; From 6a3d55b47fe496db7ce02a73d659ed671084df08 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 28 Jan 2022 15:42:00 +0100 Subject: [PATCH 06/17] Bump mtxclient --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4581df94..67f8854d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,7 +414,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 6a7eaa5006b1a18e132be7655e490d9819158dca + GIT_TAG 9781553b0186f2db9036d2abbde83c28828eb2b9 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 946ca7b6..700b6d02 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -191,7 +191,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 6a7eaa5006b1a18e132be7655e490d9819158dca + - commit: 9781553b0186f2db9036d2abbde83c28828eb2b9 #tag: v0.6.1 type: git url: https://github.com/Nheko-Reborn/mtxclient.git From 2e7d26bcc010f6ad1908461a34361b64f56d53be Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 29 Jan 2022 17:27:28 +0100 Subject: [PATCH 07/17] Fix secrets not loading after registration --- src/Cache.cpp | 2 ++ src/RegisterPage.cpp | 6 ++++-- src/RegisterPage.h | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index c15d2f4b..b55d53a6 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -391,6 +391,7 @@ Cache::loadSecrets(std::vector> toLoad) &QKeychain::ReadPasswordJob::finished, this, [this, name, toLoad, job](QKeychain::Job *) mutable { + nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first); const QString secret = job->textData(); if (job->error() && job->error() != QKeychain::Error::EntryNotFound) { nhlog::db()->error("Restoring secret '{}' failed ({}): {}", @@ -413,6 +414,7 @@ Cache::loadSecrets(std::vector> toLoad) // You can't start a job from the finish signal of a job. QTimer::singleShot(0, this, [this, toLoad] { loadSecrets(toLoad); }); }); + nhlog::db()->debug("Reading '{}'", name_); job->start(); } diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index f94e1412..5b2ebc78 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -19,7 +19,9 @@ RegisterPage::RegisterPage(QObject *parent) : QObject(parent) -{} +{ + connect(this, &RegisterPage::registerOk, this, [] { MainWindow::instance()->showChatPage(); }); +} void RegisterPage::setError(QString err) @@ -199,7 +201,7 @@ RegisterPage::startRegistration(QString username, QString password, QString devi if (!err) { http::client()->set_user(res.user_id); http::client()->set_access_token(res.access_token); - MainWindow::instance()->showChatPage(); + emit registerOk(); disconnect(UIA::instance(), &UIA::error, this, nullptr); return; } diff --git a/src/RegisterPage.h b/src/RegisterPage.h index 9f32e820..67e2a22e 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -53,6 +53,8 @@ signals: void lookingUpHsChanged(); void lookingUpUsernameChanged(); + void registerOk(); + private: void versionsCheck(); From a0a35c8786847c123bc3fbb514077c49c34d841e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 29 Jan 2022 17:30:56 +0100 Subject: [PATCH 08/17] keep the RegisterPage name So we don't have to retranslate. --- resources/qml/Root.qml | 2 +- .../qml/pages/{RegistrationPage.qml => RegisterPage.qml} | 0 resources/qml/pages/WelcomePage.qml | 4 ++++ resources/res.qrc | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) rename resources/qml/pages/{RegistrationPage.qml => RegisterPage.qml} (100%) diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 1969c613..e3be440d 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -398,7 +398,7 @@ Pane { Component { id: registerPage - RegistrationPage { + RegisterPage { } } diff --git a/resources/qml/pages/RegistrationPage.qml b/resources/qml/pages/RegisterPage.qml similarity index 100% rename from resources/qml/pages/RegistrationPage.qml rename to resources/qml/pages/RegisterPage.qml diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml index 627d8b1c..e1ecc31d 100644 --- a/resources/qml/pages/WelcomePage.qml +++ b/resources/qml/pages/WelcomePage.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 diff --git a/resources/res.qrc b/resources/res.qrc index f7f2a796..5b49d596 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -112,7 +112,7 @@ qml/pages/UserSettingsPage.qml qml/pages/WelcomePage.qml qml/pages/LoginPage.qml - qml/pages/RegistrationPage.qml + qml/pages/RegisterPage.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml From f7fd43e3f24d5b26fad2112670e3c44bbf255359 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 29 Jan 2022 17:38:59 +0100 Subject: [PATCH 09/17] Connect quit --- src/MainWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 1c915fba..ab2de2ba 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -286,6 +286,8 @@ MainWindow::registerQmlTypes() engine()->addImageProvider(QStringLiteral("blurhash"), new BlurhashProvider()); if (JdenticonProvider::isAvailable()) engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider()); + + QObject::connect(engine(), &QQmlEngine::quit, &QGuiApplication::quit); } void From c5c892b873963cca5f725e3d28312dc096b68253 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 29 Jan 2022 17:41:14 +0100 Subject: [PATCH 10/17] Remove unnecessary qml engine shutdown workaround --- src/ChatPage.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index fe6b08eb..808489d7 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -159,12 +159,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QObject *parent) MainWindow::instance()->requestActivate(); }); - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() { - // ensure the qml context is shutdown before we destroy all other singletons - // Otherwise Qml will try to access the room list or settings, after they have been - // destroyed - }); - connect( this, &ChatPage::initializeViews, From 909c11d2bb9cddde3d8dc8e5ca2404b11fdd801b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 29 Jan 2022 18:03:25 +0100 Subject: [PATCH 11/17] Get rid of some shadowing --- src/LoginPage.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index cfc600ae..6bed446e 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -171,16 +171,16 @@ LoginPage::checkHomeserverVersion() if (err || flows.flows.empty()) emit versionOkCb(true, false); - bool ssoSupported_ = false; - bool passwordSupported_ = false; + bool ssoSupported = false; + bool passwordSupported = false; for (const auto &flow : flows.flows) { if (flow.type == mtx::user_interactive::auth_types::sso) { - ssoSupported_ = true; + ssoSupported = true; } else if (flow.type == mtx::user_interactive::auth_types::password) { - passwordSupported_ = true; + passwordSupported = true; } } - emit versionOkCb(passwordSupported_, ssoSupported_); + emit versionOkCb(passwordSupported, ssoSupported); }); }); } From 1d6d9e65af7b78cb9fa1b68238f1b488b085ddfd Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 29 Jan 2022 18:10:42 +0100 Subject: [PATCH 12/17] Store window size on exit again --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index af8a46a9..24fc8415 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -307,7 +307,7 @@ main(int argc, char *argv[]) w.show(); QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() { - // w.saveCurrentWindowSize(); + w.saveCurrentWindowSize(); if (http::client() != nullptr) { nhlog::net()->debug("shutting down all I/O threads & open connections"); http::client()->close(true); From 392a4be858a9ba1d1870041f726e05501bf65c4e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 29 Jan 2022 19:07:27 +0100 Subject: [PATCH 13/17] Fix centering dialogs --- src/MainWindow.cpp | 14 +++++--------- src/MainWindow.h | 2 -- src/Utils.cpp | 16 +++++++--------- src/Utils.h | 2 +- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ab2de2ba..a4661da7 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -422,23 +422,19 @@ MainWindow::openCreateRoomDialog( showDialog(dialog); } -bool -MainWindow::hasActiveDialogs() const -{ - return false; -} - bool MainWindow::pageSupportsTray() const { - return false; //! welcome_page_->isVisible() && !login_page_->isVisible() && - //! !register_page_->isVisible(); + return !http::client()->access_token().empty(); } inline void MainWindow::showDialog(QWidget *dialog) { - // utils::centerWidget(dialog, this); + dialog->setWindowFlags(Qt::WindowType::Dialog | Qt::WindowType::WindowCloseButtonHint | + Qt::WindowType::WindowTitleHint); dialog->raise(); dialog->show(); + utils::centerWidget(dialog, this); + dialog->window()->windowHandle()->setTransientParent(this); } diff --git a/src/MainWindow.h b/src/MainWindow.h index 33e16271..1258bafb 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -83,8 +83,6 @@ private: void showDialog(QWidget *dialog); bool hasActiveUser(); void restoreWindowSize(); - //! Check if there is an open dialog. - bool hasActiveDialogs() const; //! Check if the current page supports the "minimize to tray" functionality. bool pageSupportsTray() const; diff --git a/src/Utils.cpp b/src/Utils.cpp index a9cfde22..0ac37d8e 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -770,20 +771,17 @@ utils::luminance(const QColor &col) } void -utils::centerWidget(QWidget *widget, QWidget *parent) +utils::centerWidget(QWidget *widget, QWindow *parent) { + if (parent) { + widget->window()->windowHandle()->setTransientParent(parent); + return; + } + auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint { return QPoint(hostRect.center().x() - (childRect.width() * 0.5), hostRect.center().y() - (childRect.height() * 0.5)); }; - - if (parent) { - widget->move(parent->window()->frameGeometry().topLeft() + - parent->window()->rect().center() - widget->rect().center()); - return; - } - - // Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry())); widget->move(findCenter(QGuiApplication::primaryScreen()->geometry())); } diff --git a/src/Utils.h b/src/Utils.h index 87ce1c34..0b6034ac 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -290,7 +290,7 @@ luminance(const QColor &col); //! Center a widget in relation to another widget. void -centerWidget(QWidget *widget, QWidget *parent); +centerWidget(QWidget *widget, QWindow *parent); void restoreCombobox(QComboBox *combo, const QString &value); From f44d8e916beb3e1059ba31e149de0cffbcaa02fd Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 30 Jan 2022 13:16:36 +0100 Subject: [PATCH 14/17] Remove a few now unused files --- CMakeLists.txt | 21 -- src/ChatPage.cpp | 3 - src/ChatPage.h | 2 - src/MainWindow.cpp | 6 - src/MainWindow.h | 5 - src/UserSettingsPage.cpp | 3 +- src/WelcomePage.cpp | 88 ----- src/WelcomePage.h | 26 -- src/ui/DropShadow.cpp | 108 ------ src/ui/DropShadow.h | 25 -- src/ui/FlatButton.cpp | 730 ------------------------------------ src/ui/FlatButton.h | 198 ---------- src/ui/Label.cpp | 33 -- src/ui/Label.h | 30 -- src/ui/LoadingIndicator.cpp | 84 ----- src/ui/LoadingIndicator.h | 49 --- src/ui/Menu.h | 26 -- src/ui/OverlayModal.cpp | 62 --- src/ui/OverlayModal.h | 40 -- src/ui/Painter.h | 154 -------- src/ui/RaisedButton.cpp | 92 ----- src/ui/RaisedButton.h | 32 -- src/ui/Ripple.cpp | 116 ------ src/ui/Ripple.h | 154 -------- src/ui/RippleOverlay.cpp | 67 ---- src/ui/RippleOverlay.h | 62 --- src/ui/SnackBar.cpp | 136 ------- src/ui/SnackBar.h | 98 ----- src/ui/TextLabel.cpp | 123 ------ src/ui/TextLabel.h | 60 --- 30 files changed, 1 insertion(+), 2632 deletions(-) delete mode 100644 src/WelcomePage.cpp delete mode 100644 src/WelcomePage.h delete mode 100644 src/ui/DropShadow.cpp delete mode 100644 src/ui/DropShadow.h delete mode 100644 src/ui/FlatButton.cpp delete mode 100644 src/ui/FlatButton.h delete mode 100644 src/ui/Label.cpp delete mode 100644 src/ui/Label.h delete mode 100644 src/ui/LoadingIndicator.cpp delete mode 100644 src/ui/LoadingIndicator.h delete mode 100644 src/ui/Menu.h delete mode 100644 src/ui/OverlayModal.cpp delete mode 100644 src/ui/OverlayModal.h delete mode 100644 src/ui/Painter.h delete mode 100644 src/ui/RaisedButton.cpp delete mode 100644 src/ui/RaisedButton.h delete mode 100644 src/ui/Ripple.cpp delete mode 100644 src/ui/Ripple.h delete mode 100644 src/ui/RippleOverlay.cpp delete mode 100644 src/ui/RippleOverlay.h delete mode 100644 src/ui/SnackBar.cpp delete mode 100644 src/ui/SnackBar.h delete mode 100644 src/ui/TextLabel.cpp delete mode 100644 src/ui/TextLabel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 67f8854d..d2d975f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,24 +331,14 @@ set(SRC_FILES src/timeline/RoomlistModel.cpp # UI components - src/ui/DropShadow.cpp - src/ui/FlatButton.cpp - src/ui/Label.cpp - src/ui/LoadingIndicator.cpp src/ui/MxcAnimatedImage.cpp src/ui/MxcMediaProxy.cpp src/ui/NhekoCursorShape.cpp src/ui/NhekoDropArea.cpp src/ui/NhekoGlobalObject.cpp - src/ui/OverlayModal.cpp src/ui/OverlayWidget.cpp - src/ui/RaisedButton.cpp - src/ui/Ripple.cpp - src/ui/RippleOverlay.cpp src/ui/RoomSettings.cpp - src/ui/SnackBar.cpp src/ui/TextField.cpp - src/ui/TextLabel.cpp src/ui/Theme.cpp src/ui/ThemeManager.cpp src/ui/ToggleButton.cpp @@ -395,7 +385,6 @@ set(SRC_FILES src/RoomDirectoryModel.cpp src/RoomsModel.cpp src/Utils.cpp - src/WelcomePage.cpp src/main.cpp third_party/blurhash/blurhash.cpp @@ -538,23 +527,14 @@ qt5_wrap_cpp(MOC_HEADERS src/timeline/RoomlistModel.h # UI components - src/ui/FlatButton.h - src/ui/Label.h - src/ui/LoadingIndicator.h src/ui/MxcAnimatedImage.h src/ui/MxcMediaProxy.h - src/ui/Menu.h src/ui/NhekoCursorShape.h src/ui/NhekoDropArea.h src/ui/NhekoGlobalObject.h src/ui/OverlayWidget.h - src/ui/RaisedButton.h - src/ui/Ripple.h - src/ui/RippleOverlay.h src/ui/RoomSettings.h - src/ui/SnackBar.h src/ui/TextField.h - src/ui/TextLabel.h src/ui/Theme.h src/ui/ThemeManager.h src/ui/ToggleButton.h @@ -596,7 +576,6 @@ qt5_wrap_cpp(MOC_HEADERS src/UsersModel.h src/RoomDirectoryModel.h src/RoomsModel.h - src/WelcomePage.h src/ReadReceiptsModel.h ) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 808489d7..cdaf7260 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -22,7 +22,6 @@ #include "Utils.h" #include "encryption/DeviceVerificationFlow.h" #include "encryption/Olm.h" -#include "ui/OverlayModal.h" #include "ui/Theme.h" #include "ui/UserProfile.h" #include "voip/CallManager.h" @@ -1050,8 +1049,6 @@ ChatPage::initiateLogout() emit loggedOut(); }); - - emit showOverlayProgressBar(); } template diff --git a/src/ChatPage.h b/src/ChatPage.h index 4b7351a5..70bbb6c2 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -28,7 +28,6 @@ #include "CacheStructs.h" #include "notifications/Manager.h" -class OverlayModal; class TimelineViewManager; class UserSettings; class NotificationsManager; @@ -112,7 +111,6 @@ signals: void showNotification(const QString &msg); void showLoginPage(const QString &msg); void showUserSettingsPage(); - void showOverlayProgressBar(); void ownProfileOk(); void setUserDisplayName(const QString &name); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index a4661da7..ce9dd945 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -41,21 +41,17 @@ #include "UserSettingsPage.h" #include "UsersModel.h" #include "Utils.h" -#include "WelcomePage.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" #include "encryption/DeviceVerificationFlow.h" #include "encryption/SelfVerificationStatus.h" #include "timeline/DelegateChooser.h" #include "timeline/TimelineViewManager.h" -#include "ui/LoadingIndicator.h" #include "ui/MxcAnimatedImage.h" #include "ui/MxcMediaProxy.h" #include "ui/NhekoCursorShape.h" #include "ui/NhekoDropArea.h" #include "ui/NhekoGlobalObject.h" -#include "ui/OverlayModal.h" -#include "ui/SnackBar.h" #include "ui/UIA.h" #include "voip/WebRTCSession.h" @@ -101,8 +97,6 @@ MainWindow::MainWindow(QWindow *parent) this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); - trayIcon_->setVisible(userSettings_->tray()); // load cache on event loop diff --git a/src/MainWindow.h b/src/MainWindow.h index 1258bafb..80ade988 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -10,11 +10,9 @@ #include #include -#include #include #include "UserSettingsPage.h" -#include "ui/OverlayModal.h" #include "jdenticoninterface.h" @@ -22,9 +20,6 @@ class ChatPage; class RegisterPage; class WelcomePage; -class LoadingIndicator; -class OverlayModal; -class SnackBar; class TrayIcon; class UserSettings; class MxcImageProvider; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index e973fc1f..388aa241 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -33,8 +33,7 @@ #include "UserSettingsPage.h" #include "Utils.h" #include "encryption/Olm.h" -#include "ui/FlatButton.h" -#include "ui/ToggleButton.h" +#include "ui/Theme.h" #include "voip/CallDevices.h" #include "config/nheko.h" diff --git a/src/WelcomePage.cpp b/src/WelcomePage.cpp deleted file mode 100644 index 5d540f4e..00000000 --- a/src/WelcomePage.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include - -#include "Config.h" -#include "WelcomePage.h" -#include "ui/RaisedButton.h" -#include "ui/TextLabel.h" - -WelcomePage::WelcomePage(QWidget *parent) - : QWidget(parent) -{ - auto topLayout_ = new QVBoxLayout(this); - topLayout_->setSpacing(20); - topLayout_->setAlignment(Qt::AlignCenter); - - QFont headingFont; - headingFont.setPointSizeF(headingFont.pointSizeF() * 2); - QFont subTitleFont; - subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5); - - QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})}; - - auto logo_ = new QLabel(this); - logo_->setPixmap(icon.pixmap(256)); - logo_->setAlignment(Qt::AlignCenter); - - QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol.")); - QString main(tr("Enjoy your stay!")); - - auto intoTxt_ = new TextLabel(heading, this); - intoTxt_->setFont(headingFont); - intoTxt_->setAlignment(Qt::AlignCenter); - - auto subTitle = new TextLabel(main, this); - subTitle->setFont(subTitleFont); - subTitle->setAlignment(Qt::AlignCenter); - - topLayout_->addStretch(1); - topLayout_->addWidget(logo_); - topLayout_->addWidget(intoTxt_); - topLayout_->addWidget(subTitle); - - auto btnLayout_ = new QHBoxLayout(); - btnLayout_->setSpacing(20); - btnLayout_->setContentsMargins(0, 20, 0, 20); - - const int fontHeight = QFontMetrics{subTitleFont}.height(); - const int buttonHeight = fontHeight * 2.5; - const int buttonWidth = fontHeight * 8; - - auto registerBtn = new RaisedButton(tr("REGISTER"), this); - registerBtn->setMinimumSize(buttonWidth, buttonHeight); - registerBtn->setFontSize(subTitleFont.pointSizeF()); - registerBtn->setCornerRadius(conf::btn::cornerRadius); - - auto loginBtn = new RaisedButton(tr("LOGIN"), this); - loginBtn->setMinimumSize(buttonWidth, buttonHeight); - loginBtn->setFontSize(subTitleFont.pointSizeF()); - loginBtn->setCornerRadius(conf::btn::cornerRadius); - - btnLayout_->addStretch(1); - btnLayout_->addWidget(registerBtn); - btnLayout_->addWidget(loginBtn); - btnLayout_->addStretch(1); - - topLayout_->addLayout(btnLayout_); - topLayout_->addStretch(1); - - connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister); - connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin); -} - -void -WelcomePage::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.initFrom(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} diff --git a/src/WelcomePage.h b/src/WelcomePage.h deleted file mode 100644 index 9d5da8ba..00000000 --- a/src/WelcomePage.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -class WelcomePage : public QWidget -{ - Q_OBJECT - -public: - explicit WelcomePage(QWidget *parent = nullptr); - -protected: - void paintEvent(QPaintEvent *) override; - -signals: - // Notify that the user wants to login in. - void userLogin(); - - // Notify that the user wants to register. - void userRegister(); -}; diff --git a/src/ui/DropShadow.cpp b/src/ui/DropShadow.cpp deleted file mode 100644 index 039d6558..00000000 --- a/src/ui/DropShadow.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "DropShadow.h" - -#include -#include - -void -DropShadow::draw(QPainter &painter, - qint16 margin, - qreal radius, - QColor start, - QColor end, - qreal startPosition, - qreal endPosition0, - qreal endPosition1, - qreal width, - qreal height) -{ - painter.setPen(Qt::NoPen); - - QLinearGradient gradient; - gradient.setColorAt(startPosition, start); - gradient.setColorAt(endPosition0, end); - - // Right - QPointF right0(width - margin, height / 2); - QPointF right1(width, height / 2); - gradient.setStart(right0); - gradient.setFinalStop(right1); - painter.setBrush(QBrush(gradient)); - // Deprecated in 5.13: painter.drawRoundRect( - // QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - - // margin)), 0.0, 0.0); - painter.drawRoundedRect( - QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)), 0.0, 0.0); - - // Left - QPointF left0(margin, height / 2); - QPointF left1(0, height / 2); - gradient.setStart(left0); - gradient.setFinalStop(left1); - painter.setBrush(QBrush(gradient)); - painter.drawRoundedRect( - QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0); - - // Top - QPointF top0(width / 2, margin); - QPointF top1(width / 2, 0); - gradient.setStart(top0); - gradient.setFinalStop(top1); - painter.setBrush(QBrush(gradient)); - painter.drawRoundedRect(QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0); - - // Bottom - QPointF bottom0(width / 2, height - margin); - QPointF bottom1(width / 2, height); - gradient.setStart(bottom0); - gradient.setFinalStop(bottom1); - painter.setBrush(QBrush(gradient)); - painter.drawRoundedRect( - QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)), 0.0, 0.0); - - // BottomRight - QPointF bottomright0(width - margin, height - margin); - QPointF bottomright1(width, height); - gradient.setStart(bottomright0); - gradient.setFinalStop(bottomright1); - gradient.setColorAt(endPosition1, end); - painter.setBrush(QBrush(gradient)); - painter.drawRoundedRect(QRectF(bottomright0, bottomright1), 0.0, 0.0); - - // BottomLeft - QPointF bottomleft0(margin, height - margin); - QPointF bottomleft1(0, height); - gradient.setStart(bottomleft0); - gradient.setFinalStop(bottomleft1); - gradient.setColorAt(endPosition1, end); - painter.setBrush(QBrush(gradient)); - painter.drawRoundedRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0); - - // TopLeft - QPointF topleft0(margin, margin); - QPointF topleft1(0, 0); - gradient.setStart(topleft0); - gradient.setFinalStop(topleft1); - gradient.setColorAt(endPosition1, end); - painter.setBrush(QBrush(gradient)); - painter.drawRoundedRect(QRectF(topleft0, topleft1), 0.0, 0.0); - - // TopRight - QPointF topright0(width - margin, margin); - QPointF topright1(width, 0); - gradient.setStart(topright0); - gradient.setFinalStop(topright1); - gradient.setColorAt(endPosition1, end); - painter.setBrush(QBrush(gradient)); - painter.drawRoundedRect(QRectF(topright0, topright1), 0.0, 0.0); - - // Widget - painter.setBrush(QBrush(QColor(0xff, 0xff, 0xff))); - painter.setRenderHint(QPainter::Antialiasing); - painter.drawRoundedRect( - QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)), radius, radius); -} diff --git a/src/ui/DropShadow.h b/src/ui/DropShadow.h deleted file mode 100644 index 1810a1fe..00000000 --- a/src/ui/DropShadow.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -class QPainter; - -class DropShadow -{ -public: - static void draw(QPainter &painter, - qint16 margin, - qreal radius, - QColor start, - QColor end, - qreal startPosition, - qreal endPosition0, - qreal endPosition1, - qreal width, - qreal height); -}; diff --git a/src/ui/FlatButton.cpp b/src/ui/FlatButton.cpp deleted file mode 100644 index da322378..00000000 --- a/src/ui/FlatButton.cpp +++ /dev/null @@ -1,730 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "FlatButton.h" -#include "Ripple.h" -#include "RippleOverlay.h" -#include "ThemeManager.h" - -// The ampersand is automatically set in QPushButton or QCheckbx -// by KDEPlatformTheme plugin in Qt5. -// [https://bugs.kde.org/show_bug.cgi?id=337491] -// -// A workaroud is to add -// -// [Development] -// AutoCheckAccelerators=false -// -// to ~/.config/kdeglobals -static QString -removeKDEAccelerators(QString text) -{ - return text.remove(QChar('&')); -} - -void -FlatButton::init() -{ - ripple_overlay_ = new RippleOverlay(this); - state_machine_ = new FlatButtonStateMachine(this); - role_ = ui::Role::Default; - ripple_style_ = ui::RippleStyle::PositionedRipple; - icon_placement_ = ui::ButtonIconPlacement::LeftIcon; - overlay_style_ = ui::OverlayStyle::GrayOverlay; - bg_mode_ = Qt::TransparentMode; - fixed_ripple_radius_ = 64; - corner_radius_ = 3; - base_opacity_ = 0.13; - font_size_ = 10; // 10.5; - use_fixed_ripple_radius_ = false; - - setStyle(&ThemeManager::instance()); - setAttribute(Qt::WA_Hover); - setMouseTracking(true); - setCursor(QCursor(Qt::PointingHandCursor)); - - QPainterPath path; - path.addRoundedRect(rect(), corner_radius_, corner_radius_); - - ripple_overlay_->setClipPath(path); - ripple_overlay_->setClipping(true); - - state_machine_->setupProperties(); - state_machine_->startAnimations(); -} - -FlatButton::FlatButton(QWidget *parent, ui::ButtonPreset preset) - : QPushButton(parent) -{ - init(); - applyPreset(preset); -} - -FlatButton::FlatButton(const QString &text, QWidget *parent, ui::ButtonPreset preset) - : QPushButton(text, parent) -{ - init(); - applyPreset(preset); -} - -FlatButton::FlatButton(const QString &text, ui::Role role, QWidget *parent, ui::ButtonPreset preset) - : QPushButton(text, parent) -{ - init(); - applyPreset(preset); - setRole(role); -} - -void -FlatButton::applyPreset(ui::ButtonPreset preset) -{ - switch (preset) { - case ui::ButtonPreset::FlatPreset: - setOverlayStyle(ui::OverlayStyle::NoOverlay); - break; - case ui::ButtonPreset::CheckablePreset: - setOverlayStyle(ui::OverlayStyle::NoOverlay); - setCheckable(true); - break; - default: - break; - } -} - -void -FlatButton::setRole(ui::Role role) -{ - role_ = role; - state_machine_->setupProperties(); -} - -ui::Role -FlatButton::role() const -{ - return role_; -} - -void -FlatButton::setForegroundColor(const QColor &color) -{ - foreground_color_ = color; - emit foregroundColorChanged(); -} - -QColor -FlatButton::foregroundColor() const -{ - if (!foreground_color_.isValid()) { - if (bg_mode_ == Qt::OpaqueMode) { - return ThemeManager::instance().themeColor(QStringLiteral("BrightWhite")); - } - - switch (role_) { - case ui::Role::Primary: - return ThemeManager::instance().themeColor(QStringLiteral("Blue")); - case ui::Role::Secondary: - return ThemeManager::instance().themeColor(QStringLiteral("Gray")); - case ui::Role::Default: - default: - return ThemeManager::instance().themeColor(QStringLiteral("Black")); - } - } - - return foreground_color_; -} - -void -FlatButton::setBackgroundColor(const QColor &color) -{ - background_color_ = color; - emit backgroundColorChanged(); -} - -QColor -FlatButton::backgroundColor() const -{ - if (!background_color_.isValid()) { - switch (role_) { - case ui::Role::Primary: - return ThemeManager::instance().themeColor(QStringLiteral("Blue")); - case ui::Role::Secondary: - return ThemeManager::instance().themeColor(QStringLiteral("Gray")); - case ui::Role::Default: - default: - return ThemeManager::instance().themeColor(QStringLiteral("Black")); - } - } - - return background_color_; -} - -void -FlatButton::setOverlayColor(const QColor &color) -{ - overlay_color_ = color; - setOverlayStyle(ui::OverlayStyle::TintedOverlay); - emit overlayColorChanged(); -} - -QColor -FlatButton::overlayColor() const -{ - if (!overlay_color_.isValid()) { - return foregroundColor(); - } - - return overlay_color_; -} - -void -FlatButton::setDisabledForegroundColor(const QColor &color) -{ - disabled_color_ = color; - emit disabledForegroundColorChanged(); -} - -QColor -FlatButton::disabledForegroundColor() const -{ - if (!disabled_color_.isValid()) { - return ThemeManager::instance().themeColor(QStringLiteral("FadedWhite")); - } - - return disabled_color_; -} - -void -FlatButton::setDisabledBackgroundColor(const QColor &color) -{ - disabled_background_color_ = color; - emit disabledBackgroundColorChanged(); -} - -QColor -FlatButton::disabledBackgroundColor() const -{ - if (!disabled_background_color_.isValid()) { - return ThemeManager::instance().themeColor(QStringLiteral("FadedWhite")); - } - - return disabled_background_color_; -} - -void -FlatButton::setFontSize(qreal size) -{ - font_size_ = size; - - QFont f(font()); - f.setPointSizeF(size); - setFont(f); - - emit fontSizeChanged(); - - update(); -} - -qreal -FlatButton::fontSize() const -{ - return font_size_; -} - -void -FlatButton::setOverlayStyle(ui::OverlayStyle style) -{ - overlay_style_ = style; - update(); -} - -ui::OverlayStyle -FlatButton::overlayStyle() const -{ - return overlay_style_; -} - -void -FlatButton::setRippleStyle(ui::RippleStyle style) -{ - ripple_style_ = style; -} - -ui::RippleStyle -FlatButton::rippleStyle() const -{ - return ripple_style_; -} - -void -FlatButton::setIconPlacement(ui::ButtonIconPlacement placement) -{ - icon_placement_ = placement; - update(); -} - -ui::ButtonIconPlacement -FlatButton::iconPlacement() const -{ - return icon_placement_; -} - -void -FlatButton::setCornerRadius(qreal radius) -{ - corner_radius_ = radius; - updateClipPath(); - update(); -} - -qreal -FlatButton::cornerRadius() const -{ - return corner_radius_; -} - -void -FlatButton::setBackgroundMode(Qt::BGMode mode) -{ - bg_mode_ = mode; - state_machine_->setupProperties(); -} - -Qt::BGMode -FlatButton::backgroundMode() const -{ - return bg_mode_; -} - -void -FlatButton::setBaseOpacity(qreal opacity) -{ - base_opacity_ = opacity; - state_machine_->setupProperties(); -} - -qreal -FlatButton::baseOpacity() const -{ - return base_opacity_; -} - -void -FlatButton::setCheckable(bool value) -{ - state_machine_->updateCheckedStatus(); - state_machine_->setCheckedOverlayProgress(0); - - QPushButton::setCheckable(value); -} - -void -FlatButton::setHasFixedRippleRadius(bool value) -{ - use_fixed_ripple_radius_ = value; -} - -bool -FlatButton::hasFixedRippleRadius() const -{ - return use_fixed_ripple_radius_; -} - -void -FlatButton::setFixedRippleRadius(qreal radius) -{ - fixed_ripple_radius_ = radius; - setHasFixedRippleRadius(true); -} - -QSize -FlatButton::sizeHint() const -{ - ensurePolished(); - - QSize label(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text()))); - - int w = 20 + label.width(); - int h = label.height(); - - if (!icon().isNull()) { - w += iconSize().width() + FlatButton::IconPadding; - h = qMax(h, iconSize().height()); - } - - return QSize(w, 20 + h); -} - -void -FlatButton::checkStateSet() -{ - state_machine_->updateCheckedStatus(); - QPushButton::checkStateSet(); -} - -void -FlatButton::mousePressEvent(QMouseEvent *event) -{ - if (ui::RippleStyle::NoRipple != ripple_style_) { - QPoint pos; - qreal radiusEndValue; - - if (ui::RippleStyle::CenteredRipple == ripple_style_) { - pos = rect().center(); - } else { - pos = event->pos(); - } - - if (use_fixed_ripple_radius_) { - radiusEndValue = fixed_ripple_radius_; - } else { - radiusEndValue = static_cast(width()) / 2; - } - - Ripple *ripple = new Ripple(pos); - - ripple->setRadiusEndValue(radiusEndValue); - ripple->setOpacityStartValue(0.35); - ripple->setColor(foregroundColor()); - ripple->radiusAnimation()->setDuration(250); - ripple->opacityAnimation()->setDuration(250); - - ripple_overlay_->addRipple(ripple); - } - - QPushButton::mousePressEvent(event); -} - -void -FlatButton::mouseReleaseEvent(QMouseEvent *event) -{ - QPushButton::mouseReleaseEvent(event); - state_machine_->updateCheckedStatus(); -} - -void -FlatButton::resizeEvent(QResizeEvent *event) -{ - QPushButton::resizeEvent(event); - updateClipPath(); -} - -void -FlatButton::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - const qreal cr = corner_radius_; - - if (cr > 0) { - QPainterPath path; - path.addRoundedRect(rect(), cr, cr); - - painter.setClipPath(path); - painter.setClipping(true); - } - - paintBackground(&painter); - - painter.setOpacity(1); - painter.setClipping(false); - - paintForeground(&painter); -} - -void -FlatButton::paintBackground(QPainter *painter) -{ - const qreal overlayOpacity = state_machine_->overlayOpacity(); - const qreal checkedProgress = state_machine_->checkedOverlayProgress(); - - if (Qt::OpaqueMode == bg_mode_) { - QBrush brush; - brush.setStyle(Qt::SolidPattern); - - if (isEnabled()) { - brush.setColor(backgroundColor()); - } else { - brush.setColor(disabledBackgroundColor()); - } - - painter->setOpacity(1); - painter->setBrush(brush); - painter->setPen(Qt::NoPen); - painter->drawRect(rect()); - } - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - painter->setPen(Qt::NoPen); - - if (!isEnabled()) { - return; - } - - if ((ui::OverlayStyle::NoOverlay != overlay_style_) && (overlayOpacity > 0)) { - if (ui::OverlayStyle::TintedOverlay == overlay_style_) { - brush.setColor(overlayColor()); - } else { - brush.setColor(Qt::gray); - } - - painter->setOpacity(overlayOpacity); - painter->setBrush(brush); - painter->drawRect(rect()); - } - - if (isCheckable() && checkedProgress > 0) { - const qreal q = Qt::TransparentMode == bg_mode_ ? 0.45 : 0.7; - brush.setColor(foregroundColor()); - painter->setOpacity(q * checkedProgress); - painter->setBrush(brush); - QRect r(rect()); - r.setHeight(static_cast(r.height()) * checkedProgress); - painter->drawRect(r); - } -} - -#define COLOR_INTERPOLATE(CH) (1 - progress) * source.CH() + progress *dest.CH() - -void -FlatButton::paintForeground(QPainter *painter) -{ - if (isEnabled()) { - painter->setPen(foregroundColor()); - const qreal progress = state_machine_->checkedOverlayProgress(); - - if (isCheckable() && progress > 0) { - QColor source = foregroundColor(); - QColor dest = Qt::TransparentMode == bg_mode_ ? Qt::white : backgroundColor(); - if (qFuzzyCompare(1, progress)) { - painter->setPen(dest); - } else { - painter->setPen(QColor(COLOR_INTERPOLATE(red), - COLOR_INTERPOLATE(green), - COLOR_INTERPOLATE(blue), - COLOR_INTERPOLATE(alpha))); - } - } - } else { - painter->setPen(disabledForegroundColor()); - } - - if (icon().isNull()) { - painter->drawText(rect(), Qt::AlignCenter, removeKDEAccelerators(text())); - return; - } - - QSize textSize(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text()))); - QSize base(size() - textSize); - - const int iw = iconSize().width() + IconPadding; - QPoint pos((base.width() - iw) / 2, 0); - - QRect textGeometry(pos + QPoint(0, base.height() / 2), textSize); - QRect iconGeometry(pos + QPoint(0, (height() - iconSize().height()) / 2), iconSize()); - - /* if (ui::LeftIcon == icon_placement_) { */ - /* textGeometry.translate(iw, 0); */ - /* } else { */ - /* iconGeometry.translate(textSize.width() + IconPadding, 0); */ - /* } */ - - painter->drawText(textGeometry, Qt::AlignCenter, removeKDEAccelerators(text())); - - QPixmap pixmap = icon().pixmap(iconSize()); - QPainter icon(&pixmap); - icon.setCompositionMode(QPainter::CompositionMode_SourceIn); - icon.fillRect(pixmap.rect(), painter->pen().color()); - painter->drawPixmap(iconGeometry, pixmap); -} - -void -FlatButton::updateClipPath() -{ - const qreal radius = corner_radius_; - - QPainterPath path; - path.addRoundedRect(rect(), radius, radius); - ripple_overlay_->setClipPath(path); -} - -FlatButtonStateMachine::FlatButtonStateMachine(FlatButton *parent) - : QStateMachine(parent) - , button_(parent) - , top_level_state_(new QState(QState::ParallelStates)) - , config_state_(new QState(top_level_state_)) - , checkable_state_(new QState(top_level_state_)) - , checked_state_(new QState(checkable_state_)) - , unchecked_state_(new QState(checkable_state_)) - , neutral_state_(new QState(config_state_)) - , neutral_focused_state_(new QState(config_state_)) - , hovered_state_(new QState(config_state_)) - , hovered_focused_state_(new QState(config_state_)) - , pressed_state_(new QState(config_state_)) - , overlay_opacity_(0) - , checked_overlay_progress_(parent->isChecked() ? 1 : 0) - , was_checked_(false) -{ - Q_ASSERT(parent); - - parent->installEventFilter(this); - - config_state_->setInitialState(neutral_state_); - addState(top_level_state_); - setInitialState(top_level_state_); - - checkable_state_->setInitialState(parent->isChecked() ? checked_state_ : unchecked_state_); - QSignalTransition *transition; - QPropertyAnimation *animation; - - transition = new QSignalTransition(this, SIGNAL(buttonChecked())); - transition->setTargetState(checked_state_); - unchecked_state_->addTransition(transition); - - animation = new QPropertyAnimation(this, "checkedOverlayProgress", this); - animation->setDuration(200); - transition->addAnimation(animation); - - transition = new QSignalTransition(this, SIGNAL(buttonUnchecked())); - transition->setTargetState(unchecked_state_); - checked_state_->addTransition(transition); - - animation = new QPropertyAnimation(this, "checkedOverlayProgress", this); - animation->setDuration(200); - transition->addAnimation(animation); - - addTransition(button_, QEvent::FocusIn, neutral_state_, neutral_focused_state_); - addTransition(button_, QEvent::FocusOut, neutral_focused_state_, neutral_state_); - addTransition(button_, QEvent::Enter, neutral_state_, hovered_state_); - addTransition(button_, QEvent::Leave, hovered_state_, neutral_state_); - addTransition(button_, QEvent::Enter, neutral_focused_state_, hovered_focused_state_); - addTransition(button_, QEvent::Leave, hovered_focused_state_, neutral_focused_state_); - addTransition(button_, QEvent::FocusIn, hovered_state_, hovered_focused_state_); - addTransition(button_, QEvent::FocusOut, hovered_focused_state_, hovered_state_); - addTransition(this, SIGNAL(buttonPressed()), hovered_state_, pressed_state_); - addTransition(button_, QEvent::Leave, pressed_state_, neutral_focused_state_); - addTransition(button_, QEvent::FocusOut, pressed_state_, hovered_state_); -} - -void -FlatButtonStateMachine::setOverlayOpacity(qreal opacity) -{ - overlay_opacity_ = opacity; - emit overlayOpacityChanged(); - button_->update(); -} - -void -FlatButtonStateMachine::setCheckedOverlayProgress(qreal opacity) -{ - checked_overlay_progress_ = opacity; - emit checkedOverlayProgressChanged(); - button_->update(); -} - -void -FlatButtonStateMachine::startAnimations() -{ - start(); -} - -void -FlatButtonStateMachine::setupProperties() -{ - QColor overlayColor; - - if (Qt::TransparentMode == button_->backgroundMode()) { - overlayColor = button_->backgroundColor(); - } else { - overlayColor = button_->foregroundColor(); - } - - const qreal baseOpacity = button_->baseOpacity(); - - neutral_state_->assignProperty(this, "overlayOpacity", 0); - neutral_focused_state_->assignProperty(this, "overlayOpacity", 0); - hovered_state_->assignProperty(this, "overlayOpacity", baseOpacity); - hovered_focused_state_->assignProperty(this, "overlayOpacity", baseOpacity); - pressed_state_->assignProperty(this, "overlayOpacity", baseOpacity); - checked_state_->assignProperty(this, "checkedOverlayProgress", 1); - unchecked_state_->assignProperty(this, "checkedOverlayProgress", 0); - - button_->update(); -} - -void -FlatButtonStateMachine::updateCheckedStatus() -{ - const bool checked = button_->isChecked(); - if (was_checked_ != checked) { - was_checked_ = checked; - if (checked) { - emit buttonChecked(); - } else { - emit buttonUnchecked(); - } - } -} - -bool -FlatButtonStateMachine::eventFilter(QObject *watched, QEvent *event) -{ - if (QEvent::FocusIn == event->type()) { - QFocusEvent *focusEvent = static_cast(event); - if (focusEvent && Qt::MouseFocusReason == focusEvent->reason()) { - emit buttonPressed(); - return true; - } - } - - return QStateMachine::eventFilter(watched, event); -} - -void -FlatButtonStateMachine::addTransition(QObject *object, - const char *signal, - QState *fromState, - QState *toState) -{ - addTransition(new QSignalTransition(object, signal), fromState, toState); -} - -void -FlatButtonStateMachine::addTransition(QObject *object, - QEvent::Type eventType, - QState *fromState, - QState *toState) -{ - addTransition(new QEventTransition(object, eventType), fromState, toState); -} - -void -FlatButtonStateMachine::addTransition(QAbstractTransition *transition, - QState *fromState, - QState *toState) -{ - transition->setTargetState(toState); - - QPropertyAnimation *animation; - - animation = new QPropertyAnimation(this, "overlayOpacity", this); - animation->setDuration(150); - transition->addAnimation(animation); - - fromState->addTransition(transition); -} diff --git a/src/ui/FlatButton.h b/src/ui/FlatButton.h deleted file mode 100644 index e6215024..00000000 --- a/src/ui/FlatButton.h +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -#include "Theme.h" - -class RippleOverlay; -class FlatButton; - -class FlatButtonStateMachine : public QStateMachine -{ - Q_OBJECT - - Q_PROPERTY( - qreal overlayOpacity WRITE setOverlayOpacity READ overlayOpacity NOTIFY overlayOpacityChanged) - Q_PROPERTY(qreal checkedOverlayProgress WRITE setCheckedOverlayProgress READ - checkedOverlayProgress NOTIFY checkedOverlayProgressChanged) - -public: - explicit FlatButtonStateMachine(FlatButton *parent); - - void setOverlayOpacity(qreal opacity); - void setCheckedOverlayProgress(qreal opacity); - - inline qreal overlayOpacity() const; - inline qreal checkedOverlayProgress() const; - - void startAnimations(); - void setupProperties(); - void updateCheckedStatus(); - -signals: - void buttonPressed(); - void buttonChecked(); - void buttonUnchecked(); - - void overlayOpacityChanged(); - void checkedOverlayProgressChanged(); - -protected: - bool eventFilter(QObject *watched, QEvent *event) override; - -private: - void addTransition(QObject *object, const char *signal, QState *fromState, QState *toState); - void addTransition(QObject *object, QEvent::Type eventType, QState *fromState, QState *toState); - void addTransition(QAbstractTransition *transition, QState *fromState, QState *toState); - - FlatButton *const button_; - - QState *const top_level_state_; - QState *const config_state_; - QState *const checkable_state_; - QState *const checked_state_; - QState *const unchecked_state_; - QState *const neutral_state_; - QState *const neutral_focused_state_; - QState *const hovered_state_; - QState *const hovered_focused_state_; - QState *const pressed_state_; - - qreal overlay_opacity_; - qreal checked_overlay_progress_; - - bool was_checked_; -}; - -inline qreal -FlatButtonStateMachine::overlayOpacity() const -{ - return overlay_opacity_; -} - -inline qreal -FlatButtonStateMachine::checkedOverlayProgress() const -{ - return checked_overlay_progress_; -} - -class FlatButton : public QPushButton -{ - Q_OBJECT - - Q_PROPERTY(QColor foregroundColor WRITE setForegroundColor READ foregroundColor NOTIFY - foregroundColorChanged) - Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor NOTIFY - backgroundColorChanged) - Q_PROPERTY( - QColor overlayColor WRITE setOverlayColor READ overlayColor NOTIFY overlayColorChanged) - Q_PROPERTY(QColor disabledForegroundColor WRITE setDisabledForegroundColor READ - disabledForegroundColor NOTIFY disabledForegroundColorChanged) - Q_PROPERTY(QColor disabledBackgroundColor WRITE setDisabledBackgroundColor READ - disabledBackgroundColor NOTIFY disabledBackgroundColorChanged) - Q_PROPERTY(qreal fontSize WRITE setFontSize READ fontSize NOTIFY fontSizeChanged) - -public: - explicit FlatButton(QWidget *parent = nullptr, - ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset); - explicit FlatButton(const QString &text, - QWidget *parent = nullptr, - ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset); - FlatButton(const QString &text, - ui::Role role, - QWidget *parent = nullptr, - ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset); - - void applyPreset(ui::ButtonPreset preset); - - void setBackgroundColor(const QColor &color); - void setBackgroundMode(Qt::BGMode mode); - void setBaseOpacity(qreal opacity); - void setCheckable(bool value); - void setCornerRadius(qreal radius); - void setDisabledBackgroundColor(const QColor &color); - void setDisabledForegroundColor(const QColor &color); - void setFixedRippleRadius(qreal radius); - void setFontSize(qreal size); - void setForegroundColor(const QColor &color); - void setHasFixedRippleRadius(bool value); - void setIconPlacement(ui::ButtonIconPlacement placement); - void setOverlayColor(const QColor &color); - void setOverlayStyle(ui::OverlayStyle style); - void setRippleStyle(ui::RippleStyle style); - void setRole(ui::Role role); - - QColor foregroundColor() const; - QColor backgroundColor() const; - QColor overlayColor() const; - QColor disabledForegroundColor() const; - QColor disabledBackgroundColor() const; - - qreal fontSize() const; - qreal cornerRadius() const; - qreal baseOpacity() const; - - bool hasFixedRippleRadius() const; - - ui::Role role() const; - ui::OverlayStyle overlayStyle() const; - ui::RippleStyle rippleStyle() const; - ui::ButtonIconPlacement iconPlacement() const; - - Qt::BGMode backgroundMode() const; - - QSize sizeHint() const override; - -protected: - int IconPadding = 0; - - void checkStateSet() override; - void mousePressEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - void paintEvent(QPaintEvent *event) override; - - virtual void paintBackground(QPainter *painter); - virtual void paintForeground(QPainter *painter); - virtual void updateClipPath(); - - void init(); - -signals: - void foregroundColorChanged(); - void backgroundColorChanged(); - void overlayColorChanged(); - void disabledForegroundColorChanged(); - void disabledBackgroundColorChanged(); - void fontSizeChanged(); - -private: - RippleOverlay *ripple_overlay_; - FlatButtonStateMachine *state_machine_; - - ui::Role role_; - ui::RippleStyle ripple_style_; - ui::ButtonIconPlacement icon_placement_; - ui::OverlayStyle overlay_style_; - - Qt::BGMode bg_mode_; - - QColor background_color_; - QColor foreground_color_; - QColor overlay_color_; - QColor disabled_color_; - QColor disabled_background_color_; - - qreal fixed_ripple_radius_; - qreal corner_radius_; - qreal base_opacity_; - qreal font_size_; - - bool use_fixed_ripple_radius_; -}; diff --git a/src/ui/Label.cpp b/src/ui/Label.cpp deleted file mode 100644 index 40d2cfa4..00000000 --- a/src/ui/Label.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "Label.h" -#include - -Label::Label(QWidget *parent, Qt::WindowFlags f) - : QLabel(parent, f) -{} - -Label::Label(const QString &text, QWidget *parent, Qt::WindowFlags f) - : QLabel(text, parent, f) -{} - -void -Label::mousePressEvent(QMouseEvent *e) -{ - pressPosition_ = e->pos(); - emit pressed(e); - QLabel::mousePressEvent(e); -} - -void -Label::mouseReleaseEvent(QMouseEvent *e) -{ - emit released(e); - if (pressPosition_ == e->pos()) - emit clicked(e); - QLabel::mouseReleaseEvent(e); -} diff --git a/src/ui/Label.h b/src/ui/Label.h deleted file mode 100644 index 034e3c6c..00000000 --- a/src/ui/Label.h +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -class Label : public QLabel -{ - Q_OBJECT - -public: - explicit Label(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); - explicit Label(const QString &text, - QWidget *parent = Q_NULLPTR, - Qt::WindowFlags f = Qt::WindowFlags()); - -signals: - void clicked(QMouseEvent *e); - void pressed(QMouseEvent *e); - void released(QMouseEvent *e); - -protected: - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - - QPoint pressPosition_; -}; diff --git a/src/ui/LoadingIndicator.cpp b/src/ui/LoadingIndicator.cpp deleted file mode 100644 index 151f0750..00000000 --- a/src/ui/LoadingIndicator.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "LoadingIndicator.h" - -#include -#include -#include - -LoadingIndicator::LoadingIndicator(QWidget *parent) - : QWidget(parent) - , interval_(70) - , angle_(0) - , color_(Qt::black) -{ - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setFocusPolicy(Qt::NoFocus); - - timer_ = new QTimer(this); - connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout())); -} - -void -LoadingIndicator::paintEvent(QPaintEvent *e) -{ - Q_UNUSED(e) - - if (!timer_->isActive()) - return; - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - int width = qMin(this->width(), this->height()); - - int outerRadius = (width - 4) * 0.5f; - int innerRadius = outerRadius * 0.78f; - - int capsuleRadius = (outerRadius - innerRadius) / 2; - - for (int i = 0; i < 8; ++i) { - QColor color = color_; - - color.setAlphaF(1.0f - (i / 8.0f)); - - painter.setPen(Qt::NoPen); - painter.setBrush(color); - - qreal radius = capsuleRadius * (1.0f - (i / 16.0f)); - - painter.save(); - - painter.translate(rect().center()); - painter.rotate(angle_ - i * 45.0f); - - QPointF center = QPointF(-capsuleRadius, -innerRadius); - painter.drawEllipse(center, radius * 2, radius * 2); - - painter.restore(); - } -} - -void -LoadingIndicator::start() -{ - timer_->start(interval_); - show(); -} - -void -LoadingIndicator::stop() -{ - timer_->stop(); - hide(); -} - -void -LoadingIndicator::onTimeout() -{ - angle_ = (angle_ + 45) % 360; - repaint(); -} diff --git a/src/ui/LoadingIndicator.h b/src/ui/LoadingIndicator.h deleted file mode 100644 index 6d3f2a89..00000000 --- a/src/ui/LoadingIndicator.h +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -class QPainter; -class QTimer; -class QPaintEvent; -class LoadingIndicator : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) - -public: - LoadingIndicator(QWidget *parent = nullptr); - - void paintEvent(QPaintEvent *e) override; - - void start(); - void stop(); - - QColor color() { return color_; } - void setColor(QColor color) - { - color_ = color; - emit colorChanged(); - } - - int interval() { return interval_; } - void setInterval(int interval) { interval_ = interval; } - -private slots: - void onTimeout(); - -signals: - void colorChanged(); - -private: - int interval_; - int angle_; - - QColor color_; - QTimer *timer_; -}; diff --git a/src/ui/Menu.h b/src/ui/Menu.h deleted file mode 100644 index a666229a..00000000 --- a/src/ui/Menu.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "Config.h" - -class Menu : public QMenu -{ - Q_OBJECT -public: - Menu(QWidget *parent = nullptr) - : QMenu(parent){}; - -protected: - void leaveEvent(QEvent *e) override - { - hide(); - - QMenu::leaveEvent(e); - } -}; diff --git a/src/ui/OverlayModal.cpp b/src/ui/OverlayModal.cpp deleted file mode 100644 index 88334ce8..00000000 --- a/src/ui/OverlayModal.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include - -#include "OverlayModal.h" - -OverlayModal::OverlayModal(QWidget *parent) - : OverlayWidget(parent) - , color_{QColor(30, 30, 30, 170)} -{ - layout_ = new QVBoxLayout(this); - layout_->setSpacing(0); - layout_->setContentsMargins(10, 40, 10, 20); - setContentAlignment(Qt::AlignCenter); -} - -void -OverlayModal::setWidget(QWidget *widget) -{ - // Delete the previous widget - if (layout_->count() > 0) { - QLayoutItem *item; - while ((item = layout_->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } - } - - layout_->addWidget(widget); - content_ = widget; - content_->setFocus(); -} - -void -OverlayModal::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QPainter painter(this); - painter.fillRect(rect(), color_); -} - -void -OverlayModal::mousePressEvent(QMouseEvent *e) -{ - if (isDismissible_ && content_ && !content_->geometry().contains(e->pos())) - hide(); -} - -void -OverlayModal::keyPressEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Escape) { - event->accept(); - hide(); - } -} diff --git a/src/ui/OverlayModal.h b/src/ui/OverlayModal.h deleted file mode 100644 index 5e7ecc20..00000000 --- a/src/ui/OverlayModal.h +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include "OverlayWidget.h" - -class OverlayModal : public OverlayWidget -{ -public: - OverlayModal(QWidget *parent); - - void setColor(QColor color) { color_ = color; } - void setDismissible(bool state) { isDismissible_ = state; } - - void setContentAlignment(QFlags flag) { layout_->setAlignment(flag); } - void setWidget(QWidget *widget); - -protected: - void paintEvent(QPaintEvent *event) override; - void keyPressEvent(QKeyEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; - -private: - QWidget *content_; - QVBoxLayout *layout_; - - QColor color_; - - //! Decides whether or not the modal can be removed by clicking into it. - bool isDismissible_ = true; -}; diff --git a/src/ui/Painter.h b/src/ui/Painter.h deleted file mode 100644 index 5a7dae3e..00000000 --- a/src/ui/Painter.h +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -class Painter : public QPainter -{ -public: - explicit Painter(QPaintDevice *device) - : QPainter(device) - {} - - void drawTextLeft(int x, int y, const QString &text) - { - QFontMetrics m(fontMetrics()); - drawText(x, y + m.ascent(), text); - } - - void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) - { - QFontMetrics m(fontMetrics()); - if (textWidth < 0) { - textWidth = m.horizontalAdvance(text); - } - drawText((outerw - x - textWidth), y + m.ascent(), text); - } - - void drawPixmapLeft(int x, int y, const QPixmap &pix, const QRect &from) - { - drawPixmap(QPoint(x, y), pix, from); - } - - void drawPixmapLeft(const QPoint &p, const QPixmap &pix, const QRect &from) - { - return drawPixmapLeft(p.x(), p.y(), pix, from); - } - - void drawPixmapLeft(int x, int y, int w, int h, const QPixmap &pix, const QRect &from) - { - drawPixmap(QRect(x, y, w, h), pix, from); - } - - void drawPixmapLeft(const QRect &r, const QPixmap &pix, const QRect &from) - { - return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), pix, from); - } - - void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix) - { - Q_UNUSED(outerw); - drawPixmap(QPoint(x, y), pix); - } - - void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix) - { - return drawPixmapLeft(p.x(), p.y(), outerw, pix); - } - - void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from) - { - drawPixmap(QPoint((outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from); - } - - void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) - { - return drawPixmapRight(p.x(), p.y(), outerw, pix, from); - } - void - drawPixmapRight(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from) - { - drawPixmap(QRect((outerw - x - w), y, w, h), pix, from); - } - - void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) - { - return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from); - } - - void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix) - { - drawPixmap(QPoint((outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix); - } - - void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix) - { - return drawPixmapRight(p.x(), p.y(), outerw, pix); - } - - void drawAvatar(const QPixmap &pix, int w, int h, int d) - { - QPainterPath pp; - pp.addEllipse((w - d) / 2, (h - d) / 2, d, d); - - QRect region((w - d) / 2, (h - d) / 2, d, d); - - setClipPath(pp); - drawPixmap(region, pix); - } - - void drawLetterAvatar(const QString &c, - const QColor &penColor, - const QColor &brushColor, - int w, - int h, - int d) - { - QRect region((w - d) / 2, (h - d) / 2, d, d); - - setPen(Qt::NoPen); - setBrush(brushColor); - - drawEllipse(region.center(), d / 2, d / 2); - - setBrush(Qt::NoBrush); - drawEllipse(region.center(), d / 2, d / 2); - - setPen(penColor); - drawText(region.translated(0, -1), Qt::AlignCenter, c); - } -}; - -class PainterHighQualityEnabler -{ -public: - PainterHighQualityEnabler(Painter &p) - : _painter(p) - { - hints_ = - QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing; - - _painter.setRenderHints(hints_); - } - - ~PainterHighQualityEnabler() - { - if (hints_) - _painter.setRenderHints(hints_, false); - } - - PainterHighQualityEnabler(const PainterHighQualityEnabler &other) = delete; - PainterHighQualityEnabler &operator=(const PainterHighQualityEnabler &other) = delete; - -private: - Painter &_painter; - QPainter::RenderHints hints_ = {}; -}; diff --git a/src/ui/RaisedButton.cpp b/src/ui/RaisedButton.cpp deleted file mode 100644 index 491ab573..00000000 --- a/src/ui/RaisedButton.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include - -#include "RaisedButton.h" - -void -RaisedButton::init() -{ - shadow_state_machine_ = new QStateMachine(this); - normal_state_ = new QState; - pressed_state_ = new QState; - effect_ = new QGraphicsDropShadowEffect; - - effect_->setBlurRadius(7); - effect_->setOffset(QPointF(0, 2)); - effect_->setColor(QColor(0, 0, 0, 75)); - - setBackgroundMode(Qt::OpaqueMode); - setMinimumHeight(42); - setGraphicsEffect(effect_); - setBaseOpacity(0.3); - - shadow_state_machine_->addState(normal_state_); - shadow_state_machine_->addState(pressed_state_); - - normal_state_->assignProperty(effect_, "offset", QPointF(0, 2)); - normal_state_->assignProperty(effect_, "blurRadius", 7); - - pressed_state_->assignProperty(effect_, "offset", QPointF(0, 5)); - pressed_state_->assignProperty(effect_, "blurRadius", 29); - - QAbstractTransition *transition; - - transition = new QEventTransition(this, QEvent::MouseButtonPress); - transition->setTargetState(pressed_state_); - normal_state_->addTransition(transition); - - transition = new QEventTransition(this, QEvent::MouseButtonDblClick); - transition->setTargetState(pressed_state_); - normal_state_->addTransition(transition); - - transition = new QEventTransition(this, QEvent::MouseButtonRelease); - transition->setTargetState(normal_state_); - pressed_state_->addTransition(transition); - - QPropertyAnimation *animation; - - animation = new QPropertyAnimation(effect_, "offset", this); - animation->setDuration(100); - shadow_state_machine_->addDefaultAnimation(animation); - - animation = new QPropertyAnimation(effect_, "blurRadius", this); - animation->setDuration(100); - shadow_state_machine_->addDefaultAnimation(animation); - - shadow_state_machine_->setInitialState(normal_state_); - shadow_state_machine_->start(); -} - -RaisedButton::RaisedButton(QWidget *parent) - : FlatButton(parent) -{ - init(); -} - -RaisedButton::RaisedButton(const QString &text, QWidget *parent) - : FlatButton(parent) -{ - init(); - setText(text); -} - -bool -RaisedButton::event(QEvent *event) -{ - if (QEvent::EnabledChange == event->type()) { - if (isEnabled()) { - shadow_state_machine_->start(); - effect_->setEnabled(true); - } else { - shadow_state_machine_->stop(); - effect_->setEnabled(false); - } - } - - return FlatButton::event(event); -} diff --git a/src/ui/RaisedButton.h b/src/ui/RaisedButton.h deleted file mode 100644 index 7464c207..00000000 --- a/src/ui/RaisedButton.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -#include "FlatButton.h" - -class RaisedButton : public FlatButton -{ - Q_OBJECT - -public: - explicit RaisedButton(QWidget *parent = nullptr); - explicit RaisedButton(const QString &text, QWidget *parent = nullptr); - -protected: - bool event(QEvent *event) override; - -private: - void init(); - - QStateMachine *shadow_state_machine_; - QState *normal_state_; - QState *pressed_state_; - QGraphicsDropShadowEffect *effect_; -}; diff --git a/src/ui/Ripple.cpp b/src/ui/Ripple.cpp deleted file mode 100644 index 84c0b394..00000000 --- a/src/ui/Ripple.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "Ripple.h" -#include "RippleOverlay.h" - -Ripple::Ripple(const QPoint ¢er, QObject *parent) - : QParallelAnimationGroup(parent) - , overlay_(nullptr) - , radius_anim_(animate("radius")) - , opacity_anim_(animate("opacity")) - , radius_(0) - , opacity_(0) - , center_(center) -{ - init(); -} - -Ripple::Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent) - : QParallelAnimationGroup(parent) - , overlay_(overlay) - , radius_anim_(animate("radius")) - , opacity_anim_(animate("opacity")) - , radius_(0) - , opacity_(0) - , center_(center) -{ - init(); -} - -void -Ripple::setRadius(qreal radius) -{ - Q_ASSERT(overlay_); - - if (radius_ == radius) - return; - - radius_ = radius; - overlay_->update(); - - emit radiusChanged(); -} - -void -Ripple::setOpacity(qreal opacity) -{ - Q_ASSERT(overlay_); - - if (opacity_ == opacity) - return; - - opacity_ = opacity; - overlay_->update(); - - emit opacityChanged(); -} - -void -Ripple::setColor(const QColor &color) -{ - if (brush_.color() == color) - return; - - brush_.setColor(color); - - if (overlay_) - overlay_->update(); -} - -void -Ripple::setBrush(const QBrush &brush) -{ - brush_ = brush; - - if (overlay_) - overlay_->update(); -} - -void -Ripple::destroy() -{ - Q_ASSERT(overlay_); - - overlay_->removeRipple(this); -} - -QPropertyAnimation * -Ripple::animate(const QByteArray &property, const QEasingCurve &easing, int duration) -{ - QPropertyAnimation *animation = new QPropertyAnimation; - animation->setTargetObject(this); - animation->setPropertyName(property); - animation->setEasingCurve(easing); - animation->setDuration(duration); - - addAnimation(animation); - - return animation; -} - -void -Ripple::init() -{ - setOpacityStartValue(0.5); - setOpacityEndValue(0); - setRadiusStartValue(0); - setRadiusEndValue(300); - - brush_.setColor(Qt::black); - brush_.setStyle(Qt::SolidPattern); - - connect(this, SIGNAL(finished()), this, SLOT(destroy())); -} diff --git a/src/ui/Ripple.h b/src/ui/Ripple.h deleted file mode 100644 index 43d291cb..00000000 --- a/src/ui/Ripple.h +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -class RippleOverlay; - -class Ripple : public QParallelAnimationGroup -{ - Q_OBJECT - - Q_PROPERTY(qreal radius WRITE setRadius READ radius NOTIFY radiusChanged) - Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity NOTIFY opacityChanged) - -public: - explicit Ripple(const QPoint ¢er, QObject *parent = nullptr); - Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent = nullptr); - - inline void setOverlay(RippleOverlay *overlay); - - void setRadius(qreal radius); - void setOpacity(qreal opacity); - void setColor(const QColor &color); - void setBrush(const QBrush &brush); - - inline qreal radius() const; - inline qreal opacity() const; - inline QColor color() const; - inline QBrush brush() const; - inline QPoint center() const; - - inline QPropertyAnimation *radiusAnimation() const; - inline QPropertyAnimation *opacityAnimation() const; - - inline void setOpacityStartValue(qreal value); - inline void setOpacityEndValue(qreal value); - inline void setRadiusStartValue(qreal value); - inline void setRadiusEndValue(qreal value); - inline void setDuration(int msecs); - -protected slots: - void destroy(); - -signals: - void radiusChanged(); - void opacityChanged(); - -private: - Q_DISABLE_COPY(Ripple) - - QPropertyAnimation *animate(const QByteArray &property, - const QEasingCurve &easing = QEasingCurve::OutQuad, - int duration = 800); - - void init(); - - RippleOverlay *overlay_; - - QPropertyAnimation *const radius_anim_; - QPropertyAnimation *const opacity_anim_; - - qreal radius_; - qreal opacity_; - - QPoint center_; - QBrush brush_; -}; - -inline void -Ripple::setOverlay(RippleOverlay *overlay) -{ - overlay_ = overlay; -} - -inline qreal -Ripple::radius() const -{ - return radius_; -} - -inline qreal -Ripple::opacity() const -{ - return opacity_; -} - -inline QColor -Ripple::color() const -{ - return brush_.color(); -} - -inline QBrush -Ripple::brush() const -{ - return brush_; -} - -inline QPoint -Ripple::center() const -{ - return center_; -} - -inline QPropertyAnimation * -Ripple::radiusAnimation() const -{ - return radius_anim_; -} - -inline QPropertyAnimation * -Ripple::opacityAnimation() const -{ - return opacity_anim_; -} - -inline void -Ripple::setOpacityStartValue(qreal value) -{ - opacity_anim_->setStartValue(value); -} - -inline void -Ripple::setOpacityEndValue(qreal value) -{ - opacity_anim_->setEndValue(value); -} - -inline void -Ripple::setRadiusStartValue(qreal value) -{ - radius_anim_->setStartValue(value); -} - -inline void -Ripple::setRadiusEndValue(qreal value) -{ - radius_anim_->setEndValue(value); -} - -inline void -Ripple::setDuration(int msecs) -{ - radius_anim_->setDuration(msecs); - opacity_anim_->setDuration(msecs); -} diff --git a/src/ui/RippleOverlay.cpp b/src/ui/RippleOverlay.cpp deleted file mode 100644 index 97ad8662..00000000 --- a/src/ui/RippleOverlay.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include - -#include "Ripple.h" -#include "RippleOverlay.h" - -RippleOverlay::RippleOverlay(QWidget *parent) - : OverlayWidget(parent) - , use_clip_(false) -{ - setAttribute(Qt::WA_TransparentForMouseEvents); - setAttribute(Qt::WA_NoSystemBackground); -} - -void -RippleOverlay::addRipple(Ripple *ripple) -{ - ripple->setOverlay(this); - ripples_.push_back(ripple); - ripple->start(); -} - -void -RippleOverlay::addRipple(const QPoint &position, qreal radius) -{ - Ripple *ripple = new Ripple(position); - ripple->setRadiusEndValue(radius); - addRipple(ripple); -} - -void -RippleOverlay::removeRipple(Ripple *ripple) -{ - if (ripples_.removeOne(ripple)) - delete ripple; -} - -void -RippleOverlay::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - painter.setPen(Qt::NoPen); - - if (use_clip_) - painter.setClipPath(clip_path_); - - for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); ++it) - paintRipple(&painter, *it); -} - -void -RippleOverlay::paintRipple(QPainter *painter, Ripple *ripple) -{ - const qreal radius = ripple->radius(); - const QPointF center = ripple->center(); - - painter->setOpacity(ripple->opacity()); - painter->setBrush(ripple->brush()); - painter->drawEllipse(center, radius, radius); -} diff --git a/src/ui/RippleOverlay.h b/src/ui/RippleOverlay.h deleted file mode 100644 index f70d00a7..00000000 --- a/src/ui/RippleOverlay.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "OverlayWidget.h" - -class Ripple; - -class RippleOverlay : public OverlayWidget -{ - Q_OBJECT - -public: - explicit RippleOverlay(QWidget *parent = nullptr); - - void addRipple(Ripple *ripple); - void addRipple(const QPoint &position, qreal radius = 300); - - void removeRipple(Ripple *ripple); - - inline void setClipping(bool enable); - inline bool hasClipping() const; - - inline void setClipPath(const QPainterPath &path); - -protected: - void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; - -private: - Q_DISABLE_COPY(RippleOverlay) - - void paintRipple(QPainter *painter, Ripple *ripple); - - QList ripples_; - QPainterPath clip_path_; - bool use_clip_; -}; - -inline void -RippleOverlay::setClipping(bool enable) -{ - use_clip_ = enable; - update(); -} - -inline bool -RippleOverlay::hasClipping() const -{ - return use_clip_; -} - -inline void -RippleOverlay::setClipPath(const QPainterPath &path) -{ - clip_path_ = path; - update(); -} diff --git a/src/ui/SnackBar.cpp b/src/ui/SnackBar.cpp deleted file mode 100644 index 50c3d3f9..00000000 --- a/src/ui/SnackBar.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include - -#include "SnackBar.h" - -constexpr int STARTING_OFFSET = 1; -constexpr int BOX_PADDING = 10; -constexpr double MIN_WIDTH = 400.0; -constexpr double MIN_WIDTH_PERCENTAGE = 0.3; - -SnackBar::SnackBar(QWidget *parent) - : OverlayWidget(parent) - , offset_anim(this, "offset", this) -{ - QFont font; - font.setPointSizeF(font.pointSizeF() * 1.2); - font.setWeight(QFont::Weight::Thin); - setFont(font); - - boxHeight_ = QFontMetrics(font).height() * 2; - offset_ = STARTING_OFFSET; - position_ = SnackBarPosition::Top; - - hideTimer_.setSingleShot(true); - - offset_anim.setStartValue(1.0); - offset_anim.setEndValue(0.0); - offset_anim.setDuration(100); - offset_anim.setEasingCurve(QEasingCurve::OutCubic); - - connect(this, &SnackBar::offsetChanged, this, [this]() mutable { repaint(); }); - connect( - &offset_anim, &QPropertyAnimation::finished, this, [this]() { hideTimer_.start(10000); }); - - connect(&hideTimer_, SIGNAL(timeout()), this, SLOT(hideMessage())); - - hide(); -} - -void -SnackBar::start() -{ - if (messages_.empty()) - return; - - show(); - raise(); - - offset_anim.start(); -} - -void -SnackBar::hideMessage() -{ - stopTimers(); - hide(); - - if (!messages_.empty()) - // Moving on to the next message. - messages_.pop_front(); - - // Resetting the starting position of the widget. - offset_ = STARTING_OFFSET; - - if (!messages_.empty()) - start(); -} - -void -SnackBar::stopTimers() -{ - hideTimer_.stop(); -} - -void -SnackBar::showMessage(const QString &msg) -{ - messages_.push_back(msg); - - // There is already an active message. - if (isVisible()) - return; - - start(); -} - -void -SnackBar::mousePressEvent(QMouseEvent *) -{ - hideMessage(); -} - -void -SnackBar::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event) - - if (messages_.empty()) - return; - - auto message_ = messages_.front(); - - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(bgColor_); - p.setBrush(brush); - - QRect r(0, 0, std::max(MIN_WIDTH, width() * MIN_WIDTH_PERCENTAGE), boxHeight_); - - p.setPen(Qt::white); - QRect br = p.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_); - - p.setPen(Qt::NoPen); - r = br.united(r).adjusted(-BOX_PADDING, -BOX_PADDING, BOX_PADDING, BOX_PADDING); - - const qreal s = 1 - offset_; - - if (position_ == SnackBarPosition::Bottom) - p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2, - height() - BOX_PADDING - s * (r.height())); - else - p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2, - s * (r.height()) - 2 * BOX_PADDING); - - br.moveCenter(r.center()); - p.drawRoundedRect(r.adjusted(0, 0, 0, 4), 4, 4); - p.setPen(textColor_); - p.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_); -} diff --git a/src/ui/SnackBar.h b/src/ui/SnackBar.h deleted file mode 100644 index caac68aa..00000000 --- a/src/ui/SnackBar.h +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -#include "OverlayWidget.h" - -enum class SnackBarPosition -{ - Bottom, - Top, -}; - -class SnackBar : public OverlayWidget -{ - Q_OBJECT - - Q_PROPERTY( - QColor bgColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged) - Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged) - Q_PROPERTY(double offset READ offset WRITE setOffset NOTIFY offsetChanged) - -public: - explicit SnackBar(QWidget *parent); - - QColor backgroundColor() const { return bgColor_; } - void setBackgroundColor(const QColor &color) - { - bgColor_ = color; - update(); - emit backgroundColorChanged(); - } - - QColor textColor() const { return textColor_; } - void setTextColor(const QColor &color) - { - textColor_ = color; - update(); - emit textColorChanged(); - } - void setPosition(SnackBarPosition pos) - { - position_ = pos; - update(); - } - - double offset() { return offset_; } - void setOffset(double offset) - { - if (offset != offset_) { - offset_ = offset; - emit offsetChanged(); - } - } - -public slots: - void showMessage(const QString &msg); - -signals: - void offsetChanged(); - void backgroundColorChanged(); - void textColorChanged(); - -protected: - void paintEvent(QPaintEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; - -private slots: - void hideMessage(); - -private: - void stopTimers(); - void start(); - - QColor bgColor_; - QColor textColor_; - - qreal bgOpacity_; - qreal offset_; - - std::deque messages_; - - QTimer hideTimer_; - - double boxHeight_; - - QPropertyAnimation offset_anim; - - SnackBarPosition position_; -}; diff --git a/src/ui/TextLabel.cpp b/src/ui/TextLabel.cpp deleted file mode 100644 index 246d9d6b..00000000 --- a/src/ui/TextLabel.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "ui/TextLabel.h" - -#include -#include -#include -#include - -#include "Utils.h" - -bool -ContextMenuFilter::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::MouseButtonPress) { - emit contextMenuIsOpening(); - return true; - } - - return QObject::eventFilter(obj, event); -} - -TextLabel::TextLabel(QWidget *parent) - : TextLabel(QString(), parent) -{} - -TextLabel::TextLabel(const QString &text, QWidget *parent) - : QTextBrowser(parent) -{ - document()->setDefaultStyleSheet(QStringLiteral("a {color: %1; }").arg(utils::linkColor())); - - setText(text); - setOpenExternalLinks(true); - - // Make it look and feel like an ordinary label. - setReadOnly(true); - setFrameStyle(QFrame::NoFrame); - QPalette pal = palette(); - pal.setColor(QPalette::Base, Qt::transparent); - setPalette(pal); - - // Wrap anywhere but prefer words, adjust minimum height on the fly. - setLineWrapMode(QTextEdit::WidgetWidth); - setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - connect(document()->documentLayout(), - &QAbstractTextDocumentLayout::documentSizeChanged, - this, - &TextLabel::adjustHeight); - document()->setDocumentMargin(0); - - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - setFixedHeight(0); - - connect(this, &TextLabel::linkActivated, this, &TextLabel::handleLinkActivation); - - auto filter = new ContextMenuFilter(this); - installEventFilter(filter); - connect(filter, &ContextMenuFilter::contextMenuIsOpening, this, [this]() { - contextMenuRequested_ = true; - }); -} - -void -TextLabel::focusOutEvent(QFocusEvent *e) -{ - QTextBrowser::focusOutEvent(e); - - // We keep the selection available for the context menu. - if (contextMenuRequested_) { - contextMenuRequested_ = false; - return; - } - - QTextCursor cursor = textCursor(); - cursor.clearSelection(); - setTextCursor(cursor); -} - -void -TextLabel::mousePressEvent(QMouseEvent *e) -{ - link_ = (e->button() & Qt::LeftButton) ? anchorAt(e->pos()) : QString(); - QTextBrowser::mousePressEvent(e); -} - -void -TextLabel::mouseReleaseEvent(QMouseEvent *e) -{ - if (e->button() & Qt::LeftButton && !link_.isEmpty() && anchorAt(e->pos()) == link_) { - emit linkActivated(link_); - return; - } - - QTextBrowser::mouseReleaseEvent(e); -} - -void -TextLabel::wheelEvent(QWheelEvent *event) -{ - event->ignore(); -} - -void -TextLabel::handleLinkActivation(const QUrl &url) -{ - auto parts = url.toString().split('/'); - auto defaultHandler = [](const QUrl &url) { QDesktopServices::openUrl(url); }; - - if (url.host() != QLatin1String("matrix.to") || parts.isEmpty()) - return defaultHandler(url); - - try { - using namespace mtx::identifiers; - parse(parts.last().toStdString()); - } catch (const std::exception &) { - return defaultHandler(url); - } - - emit userProfileTriggered(parts.last()); -} diff --git a/src/ui/TextLabel.h b/src/ui/TextLabel.h deleted file mode 100644 index 2c6f4aa3..00000000 --- a/src/ui/TextLabel.h +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include - -class QMouseEvent; -class QFocusEvent; -class QWheelEvent; - -class ContextMenuFilter : public QObject -{ - Q_OBJECT - -public: - explicit ContextMenuFilter(QWidget *parent) - : QObject(parent) - {} - -signals: - void contextMenuIsOpening(); - -protected: - bool eventFilter(QObject *obj, QEvent *event) override; -}; - -class TextLabel : public QTextBrowser -{ - Q_OBJECT - -public: - TextLabel(const QString &text, QWidget *parent = nullptr); - TextLabel(QWidget *parent = nullptr); - - void wheelEvent(QWheelEvent *event) override; - void clearLinks() { link_.clear(); } - -protected: - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void focusOutEvent(QFocusEvent *e) override; - -private slots: - void adjustHeight(const QSizeF &size) { setFixedHeight(size.height()); } - void handleLinkActivation(const QUrl &link); - -signals: - void userProfileTriggered(const QString &user_id); - void linkActivated(const QUrl &link); - -private: - QString link_; - bool contextMenuRequested_ = false; -}; From b4278ffaa843afe0fe6de63339595662da6d1fa3 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 30 Jan 2022 13:31:39 +0100 Subject: [PATCH 15/17] Remove a few unused includes --- src/ChatPage.h | 6 +----- src/MainWindow.cpp | 3 --- src/UserSettingsPage.cpp | 13 +------------ src/UserSettingsPage.h | 3 --- 4 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/ChatPage.h b/src/ChatPage.h index 70bbb6c2..e4b9e4e8 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -18,11 +17,10 @@ #include #include -#include #include #include +#include #include -#include #include "CacheCryptoStructs.h" #include "CacheStructs.h" @@ -198,8 +196,6 @@ private: template void connectCallMessage(); - QHBoxLayout *topLayout_; - TimelineViewManager *view_manager_; QTimer connectivityTimer_; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ce9dd945..64587075 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -5,10 +5,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include -#include #include -#include -#include #include #include diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 388aa241..a0aa8f84 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -5,25 +5,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include -#include #include #include -#include -#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include #include #include #include -#include #include #include "Cache.h" diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index a44e0030..e9b8763d 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -7,12 +7,9 @@ #pragma once #include -#include -#include #include #include #include -#include #include "JdenticonProvider.h" #include From 2613275461fcf46ceb401b99fc6179bd3b1445f2 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 30 Jan 2022 19:14:33 +0100 Subject: [PATCH 16/17] Reimplement error notifications --- CMakeLists.txt | 2 - resources/langs/nheko_de.ts | 2 +- resources/qml/Root.qml | 7 +++ resources/qml/ui/Snackbar.qml | 98 +++++++++++++++++++++++++++++++++++ resources/res.qrc | 1 + src/MainWindow.cpp | 5 +- src/MainWindow.h | 2 + src/ui/OverlayWidget.cpp | 79 ---------------------------- src/ui/OverlayWidget.h | 26 ---------- 9 files changed, 111 insertions(+), 111 deletions(-) create mode 100644 resources/qml/ui/Snackbar.qml delete mode 100644 src/ui/OverlayWidget.cpp delete mode 100644 src/ui/OverlayWidget.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d2d975f9..7dd4d245 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -336,7 +336,6 @@ set(SRC_FILES src/ui/NhekoCursorShape.cpp src/ui/NhekoDropArea.cpp src/ui/NhekoGlobalObject.cpp - src/ui/OverlayWidget.cpp src/ui/RoomSettings.cpp src/ui/TextField.cpp src/ui/Theme.cpp @@ -532,7 +531,6 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/NhekoCursorShape.h src/ui/NhekoDropArea.h src/ui/NhekoGlobalObject.h - src/ui/OverlayWidget.h src/ui/RoomSettings.h src/ui/TextField.h src/ui/Theme.h diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index 29a04355..c499874b 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -319,7 +319,7 @@ Failed to kick %1 from %2: %3 - Kontte %1 nicht aus %2 entfernen: %3 + Konnte %1 nicht aus %2 entfernen: %3 diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index e3be440d..88d3e7c6 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -9,6 +9,7 @@ import "./dialogs" import "./emoji" import "./pages" import "./voip" +import "./ui" import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -402,6 +403,8 @@ Pane { } } + Snackbar { id: snackbar } + Connections { function onSwitchToChatPage() { mainWindow.replace(null, chatPage); @@ -409,6 +412,10 @@ Pane { function onSwitchToLoginPage(error) { mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition); } + function onShowNotification(msg) { + snackbar.showNotification(msg); + console.log("New snack: " + msg); + } target: MainWindow } diff --git a/resources/qml/ui/Snackbar.qml b/resources/qml/ui/Snackbar.qml new file mode 100644 index 00000000..80c0d888 --- /dev/null +++ b/resources/qml/ui/Snackbar.qml @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import im.nheko 1.0 + +Popup { + id: snackbar + + property var messages: [] + property string currentMessage: "" + + function showNotification(msg) { + messages.push(msg); + currentMessage = messages[0]; + if (!visible) { + open(); + dismissTimer.start(); + } + } + + Timer { + id: dismissTimer + interval: 10000 + onTriggered: snackbar.close() + } + + onAboutToHide: { + messages.shift(); + } + onClosed: { + if (messages.length > 0) { + currentMessage = messages[0]; + open(); + dismissTimer.restart(); + } + } + + parent: Overlay.overlay + opacity: 0 + y: -100 + x: (parent.width - width)/2 + padding: Nheko.paddingLarge + + contentItem: Label { + color: Nheko.colors.light + width: Math.max(Overlay.overlay? Overlay.overlay.width/2 : 0, 400) + text: snackbar.currentMessage + font.bold: true + } + + background: Rectangle { + radius: Nheko.paddingLarge + color: Nheko.colors.dark + opacity: 0.8 + } + + enter: Transition { + NumberAnimation { + target: snackbar + property: "opacity" + from: 0.0 + to: 1.0 + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + target: snackbar + properties: "y" + from: -100 + to: 100 + duration: 1000 + easing.type: Easing.OutCubic + } + } + exit: Transition { + NumberAnimation { + target: snackbar + property: "opacity" + from: 1.0 + to: 0.0 + duration: 300 + easing.type: Easing.InCubic + } + NumberAnimation { + target: snackbar + properties: "y" + to: -100 + from: 100 + duration: 300 + easing.type: Easing.InCubic + } + } +} + + diff --git a/resources/res.qrc b/resources/res.qrc index 5b49d596..2fba5f4c 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -157,6 +157,7 @@ qml/ui/NhekoSlider.qml qml/ui/Ripple.qml qml/ui/Spinner.qml + qml/ui/Snackbar.qml qml/ui/animations/BlinkAnimation.qml qml/ui/media/MediaControls.qml qml/voip/ActiveCallBar.qml diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 64587075..83504d86 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -84,9 +84,8 @@ MainWindow::MainWindow(QWindow *parent) connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); }); connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); - connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { - switchToLoginPage(msg); - }); + connect(chat_page_, &ChatPage::showLoginPage, this, &MainWindow::switchToLoginPage); + connect(chat_page_, &ChatPage::showNotification, this, &MainWindow::showNotification); connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible); connect(trayIcon_, diff --git a/src/MainWindow.h b/src/MainWindow.h index 80ade988..7bc94328 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -70,6 +70,8 @@ signals: void reload(); void secretsChanged(); + void showNotification(QString msg); + void switchToChatPage(); void switchToWelcomePage(); void switchToLoginPage(QString error); diff --git a/src/ui/OverlayWidget.cpp b/src/ui/OverlayWidget.cpp deleted file mode 100644 index b755a44c..00000000 --- a/src/ui/OverlayWidget.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "OverlayWidget.h" - -#include -#include - -OverlayWidget::OverlayWidget(QWidget *parent) - : QWidget(parent) -{ - if (parent) { - parent->installEventFilter(this); - setGeometry(overlayGeometry()); - raise(); - } -} - -bool -OverlayWidget::event(QEvent *event) -{ - if (!parent()) - return QWidget::event(event); - - switch (event->type()) { - case QEvent::ParentChange: { - parent()->installEventFilter(this); - setGeometry(overlayGeometry()); - break; - } - case QEvent::ParentAboutToChange: { - parent()->removeEventFilter(this); - break; - } - default: - break; - } - - return QWidget::event(event); -} - -bool -OverlayWidget::eventFilter(QObject *obj, QEvent *event) -{ - switch (event->type()) { - case QEvent::Move: - case QEvent::Resize: - setGeometry(overlayGeometry()); - break; - default: - break; - } - - return QWidget::eventFilter(obj, event); -} - -QRect -OverlayWidget::overlayGeometry() const -{ - QWidget *widget = parentWidget(); - - if (!widget) - return QRect(); - - return widget->rect(); -} - -void -OverlayWidget::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QStyleOption opt; - opt.initFrom(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} diff --git a/src/ui/OverlayWidget.h b/src/ui/OverlayWidget.h deleted file mode 100644 index 19ad0cc6..00000000 --- a/src/ui/OverlayWidget.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -class QPainter; - -class OverlayWidget : public QWidget -{ - Q_OBJECT - -public: - explicit OverlayWidget(QWidget *parent = nullptr); - -protected: - bool event(QEvent *event) override; - bool eventFilter(QObject *obj, QEvent *event) override; - - QRect overlayGeometry() const; - void paintEvent(QPaintEvent *event) override; -}; From f76d679ef63338685cfcfc4ecd7c882b6fb94e34 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 30 Jan 2022 20:09:58 +0100 Subject: [PATCH 17/17] Fix list items being hoverable through between settings and new room buttons --- resources/qml/ChatPage.qml | 1 + resources/qml/RoomList.qml | 27 ++- resources/qml/TopBar.qml | 436 +++++++++++++++++++------------------ 3 files changed, 235 insertions(+), 229 deletions(-) diff --git a/resources/qml/ChatPage.qml b/resources/qml/ChatPage.qml index 33db1b1a..e3aa3e48 100644 --- a/resources/qml/ChatPage.qml +++ b/resources/qml/ChatPage.qml @@ -97,6 +97,7 @@ Rectangle { implicitHeight: chatPage.height collapsed: parent.collapsed + anchors.fill: parent } Binding { diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 6e7b683f..da205950 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -385,7 +385,7 @@ Page { header: ColumnLayout { spacing: 0 - Rectangle { + Pane { id: userInfoPanel function openUserProfile() { @@ -396,12 +396,15 @@ Page { userProfile.show(); } - color: Nheko.colors.window + Layout.fillWidth: true Layout.alignment: Qt.AlignBottom - Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium + //Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium + padding: Nheko.paddingMedium Layout.minimumHeight: 40 + background: Rectangle {color: Nheko.colors.window} + InputDialog { id: statusDialog @@ -442,14 +445,12 @@ Page { gesturePolicy: TapHandler.ReleaseWithinBounds } - RowLayout { + contentItem: RowLayout { id: userInfoGrid property var profile: Nheko.currentUser spacing: Nheko.paddingMedium - anchors.fill: parent - anchors.margins: Nheko.paddingMedium Avatar { id: avatar @@ -614,19 +615,17 @@ Page { Layout.fillWidth: true } - Rectangle { - color: Nheko.colors.window + Pane { Layout.fillWidth: true Layout.alignment: Qt.AlignBottom - Layout.preferredHeight: buttonRow.implicitHeight Layout.minimumHeight: 40 - RowLayout { - id: buttonRow + horizontalPadding: Nheko.paddingMedium + verticalPadding: 0 - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: Nheko.paddingMedium + background: Rectangle {color: Nheko.colors.window} + contentItem: RowLayout { + id: buttonRow ImageButton { Layout.fillWidth: true diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 1c0115e2..c9a8d0d2 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -12,7 +12,7 @@ import im.nheko 1.0 import "./delegates" -Rectangle { +Pane { id: topBar property bool showBackButton: false @@ -28,7 +28,11 @@ Rectangle { Layout.fillWidth: true implicitHeight: topLayout.height + Nheko.paddingMedium * 2 z: 3 - color: Nheko.colors.window + + padding: 0 + background: Rectangle { + color: Nheko.colors.window + } TapHandler { onSingleTapped: { @@ -65,248 +69,250 @@ Rectangle { grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything } - GridLayout { - id: topLayout + contentItem: Item { + GridLayout { + id: topLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: Nheko.paddingMedium - anchors.verticalCenter: parent.verticalCenter - columnSpacing: Nheko.paddingSmall - rowSpacing: Nheko.paddingSmall + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: Nheko.paddingMedium + anchors.verticalCenter: parent.verticalCenter + columnSpacing: Nheko.paddingSmall + rowSpacing: Nheko.paddingSmall - ImageButton { - id: backToRoomsButton + ImageButton { + id: backToRoomsButton - Layout.column: 0 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium - Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - visible: showBackButton - image: ":/icons/icons/ui/angle-arrow-left.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Back to room list") - onClicked: Rooms.resetCurrentRoom() - } - - Avatar { - Layout.column: 1 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - width: Nheko.avatarSize - height: Nheko.avatarSize - url: avatarUrl.replace("mxc://", "image://MxcImage/") - roomid: roomId - userid: isDirect ? directChatOtherUserId : "" - displayName: roomName - enabled: false - } - - Label { - Layout.fillWidth: true - Layout.column: 2 - Layout.row: 0 - color: Nheko.colors.text - font.pointSize: fontMetrics.font.pointSize * 1.1 - text: roomName - maximumLineCount: 1 - elide: Text.ElideRight - textFormat: Text.RichText - } - - MatrixText { - id: roomTopicC - Layout.fillWidth: true - Layout.column: 2 - Layout.row: 1 - Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines - selectByMouse: false - enabled: false - clip: true - text: roomTopic - } - - EncryptionIndicator { - Layout.column: 3 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium - Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - sourceSize.height: Layout.preferredHeight * Screen.devicePixelRatio - sourceSize.width: Layout.preferredWidth * Screen.devicePixelRatio - visible: isEncrypted - encrypted: isEncrypted - trust: trustlevel - ToolTip.text: { - if (!encrypted) - return qsTr("This room is not encrypted!"); - - switch (trust) { - case Crypto.Verified: - return qsTr("This room contains only verified devices."); - case Crypto.TOFU: - return qsTr("This room contains verified devices and devices which have never changed their master key."); - default: - return qsTr("This room contains unverified devices!"); - } + Layout.column: 0 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium + Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium + visible: showBackButton + image: ":/icons/icons/ui/angle-arrow-left.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Back to room list") + onClicked: Rooms.resetCurrentRoom() } - } - ImageButton { - id: pinButton + Avatar { + Layout.column: 1 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + width: Nheko.avatarSize + height: Nheko.avatarSize + url: avatarUrl.replace("mxc://", "image://MxcImage/") + roomid: roomId + userid: isDirect ? directChatOtherUserId : "" + displayName: roomName + enabled: false + } - property bool pinsShown: !Settings.hiddenPins.includes(roomId) + Label { + Layout.fillWidth: true + Layout.column: 2 + Layout.row: 0 + color: Nheko.colors.text + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: roomName + maximumLineCount: 1 + elide: Text.ElideRight + textFormat: Text.RichText + } - visible: !!room && room.pinnedMessages.length > 0 - Layout.column: 4 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium - Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Show or hide pinned messages") - onClicked: { - var ps = Settings.hiddenPins; - if (pinsShown) { - ps.push(roomId); - } else { - const index = ps.indexOf(roomId); - if (index > -1) { - ps.splice(index, 1); + MatrixText { + id: roomTopicC + Layout.fillWidth: true + Layout.column: 2 + Layout.row: 1 + Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines + selectByMouse: false + enabled: false + clip: true + text: roomTopic + } + + EncryptionIndicator { + Layout.column: 3 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium + Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium + sourceSize.height: Layout.preferredHeight * Screen.devicePixelRatio + sourceSize.width: Layout.preferredWidth * Screen.devicePixelRatio + visible: isEncrypted + encrypted: isEncrypted + trust: trustlevel + ToolTip.text: { + if (!encrypted) + return qsTr("This room is not encrypted!"); + + switch (trust) { + case Crypto.Verified: + return qsTr("This room contains only verified devices."); + case Crypto.TOFU: + return qsTr("This room contains verified devices and devices which have never changed their master key."); + default: + return qsTr("This room contains unverified devices!"); } } - Settings.hiddenPins = ps; } - } + ImageButton { + id: pinButton - ImageButton { - id: roomOptionsButton + property bool pinsShown: !Settings.hiddenPins.includes(roomId) - visible: !!room - Layout.column: 5 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium - Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - image: ":/icons/icons/ui/options.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Room options") - onClicked: roomOptionsMenu.open(roomOptionsButton) - - Platform.Menu { - id: roomOptionsMenu - - Platform.MenuItem { - visible: room ? room.permissions.canInvite() : false - text: qsTr("Invite users") - onTriggered: TimelineManager.openInviteUsers(roomId) - } - - Platform.MenuItem { - text: qsTr("Members") - onTriggered: TimelineManager.openRoomMembers(room) - } - - Platform.MenuItem { - text: qsTr("Leave room") - onTriggered: TimelineManager.openLeaveRoomDialog(roomId) - } - - Platform.MenuItem { - text: qsTr("Settings") - onTriggered: TimelineManager.openRoomSettings(roomId) + visible: !!room && room.pinnedMessages.length > 0 + Layout.column: 4 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium + Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium + image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Show or hide pinned messages") + onClicked: { + var ps = Settings.hiddenPins; + if (pinsShown) { + ps.push(roomId); + } else { + const index = ps.indexOf(roomId); + if (index > -1) { + ps.splice(index, 1); + } + } + Settings.hiddenPins = ps; } } - } + ImageButton { + id: roomOptionsButton - ScrollView { - id: pinnedMessages + visible: !!room + Layout.column: 5 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium + Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium + image: ":/icons/icons/ui/options.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Room options") + onClicked: roomOptionsMenu.open(roomOptionsButton) - Layout.row: 2 - Layout.column: 2 - Layout.columnSpan: 3 + Platform.Menu { + id: roomOptionsMenu - Layout.fillWidth: true - Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) - - visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId) - clip: true - - palette: Nheko.colors - ScrollBar.horizontal.visible: false - - ListView { - - spacing: Nheko.paddingSmall - model: room ? room.pinnedMessages : undefined - delegate: RowLayout { - required property string modelData - - width: ListView.view.width - height: implicitHeight - - Reply { - property var e: room ? room.getDump(modelData, "") : {} - Layout.fillWidth: true - Layout.preferredHeight: height - - userColor: TimelineManager.userColor(e.userId, Nheko.colors.window) - blurhash: e.blurhash ?? "" - body: e.body ?? "" - formattedBody: e.formattedBody ?? "" - eventId: e.eventId ?? "" - filename: e.filename ?? "" - filesize: e.filesize ?? "" - proportionalHeight: e.proportionalHeight ?? 1 - type: e.type ?? MtxEvent.UnknownMessage - typeString: e.typeString ?? "" - url: e.url ?? "" - originalWidth: e.originalWidth ?? 0 - isOnlyEmoji: e.isOnlyEmoji ?? false - userId: e.userId ?? "" - userName: e.userName ?? "" - encryptionError: e.encryptionError ?? "" + Platform.MenuItem { + visible: room ? room.permissions.canInvite() : false + text: qsTr("Invite users") + onTriggered: TimelineManager.openInviteUsers(roomId) } - ImageButton { - id: deletePinButton - - Layout.preferredHeight: 16 - Layout.preferredWidth: 16 - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - visible: room.permissions.canChange(MtxEvent.PinnedEvents) - - hoverEnabled: true - image: ":/icons/icons/ui/dismiss.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Unpin") - - onClicked: room.unpin(modelData) + Platform.MenuItem { + text: qsTr("Members") + onTriggered: TimelineManager.openRoomMembers(room) } + + Platform.MenuItem { + text: qsTr("Leave room") + onTriggered: TimelineManager.openLeaveRoomDialog(roomId) + } + + Platform.MenuItem { + text: qsTr("Settings") + onTriggered: TimelineManager.openRoomSettings(roomId) + } + } + } - ScrollHelper { - flickable: parent - anchors.fill: parent - enabled: !Settings.mobileMode + ScrollView { + id: pinnedMessages + + Layout.row: 2 + Layout.column: 2 + Layout.columnSpan: 3 + + Layout.fillWidth: true + Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) + + visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId) + clip: true + + palette: Nheko.colors + ScrollBar.horizontal.visible: false + + ListView { + + spacing: Nheko.paddingSmall + model: room ? room.pinnedMessages : undefined + delegate: RowLayout { + required property string modelData + + width: ListView.view.width + height: implicitHeight + + Reply { + property var e: room ? room.getDump(modelData, "") : {} + Layout.fillWidth: true + Layout.preferredHeight: height + + userColor: TimelineManager.userColor(e.userId, Nheko.colors.window) + blurhash: e.blurhash ?? "" + body: e.body ?? "" + formattedBody: e.formattedBody ?? "" + eventId: e.eventId ?? "" + filename: e.filename ?? "" + filesize: e.filesize ?? "" + proportionalHeight: e.proportionalHeight ?? 1 + type: e.type ?? MtxEvent.UnknownMessage + typeString: e.typeString ?? "" + url: e.url ?? "" + originalWidth: e.originalWidth ?? 0 + isOnlyEmoji: e.isOnlyEmoji ?? false + userId: e.userId ?? "" + userName: e.userName ?? "" + encryptionError: e.encryptionError ?? "" + } + + ImageButton { + id: deletePinButton + + Layout.preferredHeight: 16 + Layout.preferredWidth: 16 + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + visible: room.permissions.canChange(MtxEvent.PinnedEvents) + + hoverEnabled: true + image: ":/icons/icons/ui/dismiss.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Unpin") + + onClicked: room.unpin(modelData) + } + } + + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } } } } - } - CursorShape { - anchors.fill: parent - anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0 - cursorShape: Qt.PointingHandCursor + CursorShape { + anchors.fill: parent + anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0 + cursorShape: Qt.PointingHandCursor + } } }