summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorDeepBlueV7.X <nicolas.werner@hotmail.de>2022-01-30 19:18:32 +0000
committerGitHub <noreply@github.com>2022-01-30 19:18:32 +0000
commitb706e272e55bb98bc2c09813339372d7e34c44a6 (patch)
tree06d017ce059044b25862360d8702a9ef727dca17 /src
parentMerge pull request #909 from tastytea/rename-manpage (diff)
parentFix list items being hoverable through between settings and new room buttons (diff)
downloadnheko-b706e272e55bb98bc2c09813339372d7e34c44a6.tar.xz
Merge pull request #893 from Nheko-Reborn/qml-root
Qml root
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp4
-rw-r--r--src/ChatPage.cpp53
-rw-r--r--src/ChatPage.h13
-rw-r--r--src/LoginPage.cpp446
-rw-r--r--src/LoginPage.h156
-rw-r--r--src/MainWindow.cpp397
-rw-r--r--src/MainWindow.h66
-rw-r--r--src/RegisterPage.cpp453
-rw-r--r--src/RegisterPage.h109
-rw-r--r--src/TrayIcon.cpp7
-rw-r--r--src/TrayIcon.h2
-rw-r--r--src/UserSettingsPage.cpp40
-rw-r--r--src/UserSettingsPage.h3
-rw-r--r--src/Utils.cpp16
-rw-r--r--src/Utils.h2
-rw-r--r--src/WelcomePage.cpp88
-rw-r--r--src/WelcomePage.h26
-rw-r--r--src/main.cpp7
-rw-r--r--src/timeline/InputBar.cpp6
-rw-r--r--src/timeline/RoomlistModel.cpp5
-rw-r--r--src/timeline/TimelineModel.cpp4
-rw-r--r--src/timeline/TimelineViewManager.cpp213
-rw-r--r--src/timeline/TimelineViewManager.h32
-rw-r--r--src/ui/DropShadow.cpp108
-rw-r--r--src/ui/DropShadow.h25
-rw-r--r--src/ui/FlatButton.cpp730
-rw-r--r--src/ui/FlatButton.h198
-rw-r--r--src/ui/Label.cpp33
-rw-r--r--src/ui/Label.h30
-rw-r--r--src/ui/LoadingIndicator.cpp84
-rw-r--r--src/ui/LoadingIndicator.h49
-rw-r--r--src/ui/Menu.h26
-rw-r--r--src/ui/NhekoGlobalObject.cpp2
-rw-r--r--src/ui/OverlayModal.cpp62
-rw-r--r--src/ui/OverlayModal.h40
-rw-r--r--src/ui/OverlayWidget.cpp79
-rw-r--r--src/ui/OverlayWidget.h26
-rw-r--r--src/ui/Painter.h154
-rw-r--r--src/ui/RaisedButton.cpp92
-rw-r--r--src/ui/RaisedButton.h32
-rw-r--r--src/ui/Ripple.cpp116
-rw-r--r--src/ui/Ripple.h154
-rw-r--r--src/ui/RippleOverlay.cpp67
-rw-r--r--src/ui/RippleOverlay.h62
-rw-r--r--src/ui/SnackBar.cpp136
-rw-r--r--src/ui/SnackBar.h98
-rw-r--r--src/ui/TextLabel.cpp123
-rw-r--r--src/ui/TextLabel.h60
-rw-r--r--src/ui/UIA.cpp7
49 files changed, 714 insertions, 4027 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp

index d1723d98..b55d53a6 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", @@ -391,6 +391,7 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> 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<std::pair<std::string, bool>> 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/ChatPage.cpp b/src/ChatPage.cpp
index bfaa6389..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" @@ -44,8 +43,8 @@ Q_DECLARE_METATYPE(mtx::presence::PresenceState) Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription) Q_DECLARE_METATYPE(SecretsToDecrypt) -ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) - : QWidget(parent) +ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent) + : QObject(parent) , isConnected_(true) , userSettings_{userSettings} , notificationsManager(this) @@ -61,14 +60,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>(); qRegisterMetaType<SecretsToDecrypt>(); - 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 +147,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) [this](const QString &roomid, const QString &eventid) { Q_UNUSED(eventid) view_manager_->rooms()->setCurrentRoom(roomid); - activateWindow(); + MainWindow::instance()->requestActivate(); }); connect(&notificationsManager, &NotificationsManager::sendNotificationReply, @@ -162,17 +155,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> 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( this, &ChatPage::initializeViews, @@ -183,8 +168,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *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); @@ -201,7 +184,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> 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 +314,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 +327,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 +673,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 +759,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) @@ -787,6 +770,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))); @@ -800,7 +785,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 +810,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 +834,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) @@ -1064,8 +1049,6 @@ ChatPage::initiateLogout() emit loggedOut(); }); - - emit showOverlayProgressBar(); } template<typename T> @@ -1083,7 +1066,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 +1098,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 +1192,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 +1378,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..e4b9e4e8 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h
@@ -8,7 +8,6 @@ #include <atomic> #include <optional> -#include <stack> #include <variant> #include <mtx/common.hpp> @@ -18,17 +17,15 @@ #include <mtx/events/presence.hpp> #include <mtx/secret_storage.hpp> -#include <QHBoxLayout> #include <QMap> #include <QPoint> +#include <QSharedPointer> #include <QTimer> -#include <QWidget> #include "CacheCryptoStructs.h" #include "CacheStructs.h" #include "notifications/Manager.h" -class OverlayModal; class TimelineViewManager; class UserSettings; class NotificationsManager; @@ -51,12 +48,12 @@ struct Rooms; using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>; -class ChatPage : public QWidget +class ChatPage : public QObject { Q_OBJECT public: - ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr); + ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent = nullptr); // Initialize all the components of the UI. void bootstrap(QString userid, QString homeserver, QString token); @@ -112,7 +109,6 @@ signals: void showNotification(const QString &msg); void showLoginPage(const QString &msg); void showUserSettingsPage(); - void showOverlayProgressBar(); void ownProfileOk(); void setUserDisplayName(const QString &name); @@ -143,7 +139,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); @@ -201,8 +196,6 @@ private: template<typename T> void connectCallMessage(); - QHBoxLayout *topLayout_; - TimelineViewManager *view_manager_; QTimer connectivityTimer_; diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp
index 4c28f364..6bed446e 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp
@@ -5,11 +5,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include <QDesktopServices> -#include <QFontMetrics> -#include <QLabel> -#include <QPainter> -#include <QStyleOption> -#include <QtMath> #include <mtx/identifiers.hpp> #include <mtx/requests.hpp> @@ -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>("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>("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<void (LoginPage::*)(QLabel *, const QString &)>(&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("")); - - User user; + clearErrors(); - 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(); - } + homeserverValid_ = false; + emit homeserverChanged(); + User user; try { - user = parse<User>(matrixid_input_->text().toStdString()); + user = parse<User>(mxid_.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; } - 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(); - } + 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 { + nhlog::net()->debug("hostname: {}", user.hostname()); + } - 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<User>(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) { @@ -318,107 +171,78 @@ 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); }); }); } 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_; - - 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); + passwordSupported_ = passwordSupported; + ssoSupported_ = ssoSupported; - 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("")); - User user; + clearErrors(); - 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(); - } + User user; try { - user = parse<User>(matrixid_input_->text().toStdString()); + user = parse<User>(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..9a1b9653 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h
@@ -6,16 +6,7 @@ #pragma once -#include <QWidget> - -class FlatButton; -class LoadingIndicator; -class OverlayModal; -class RaisedButton; -class TextField; -class QLabel; -class QVBoxLayout; -class QHBoxLayout; +#include <QObject> namespace mtx { namespace responses { @@ -23,24 +14,77 @@ 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(QObject *parent = nullptr); + + Q_INVOKABLE QString initialDeviceName() const + { + return QString::fromStdString(initialDeviceName_()); + } - LoginPage(QWidget *parent = nullptr); + 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_; } - void reset(); + 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(); + } + } + + 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 backButtonClicked(); - void loggingIn(); + void loggingInChanged(); void errorOccurred(); //! Used to trigger the corresponding slot outside of the main thread. @@ -48,28 +92,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,48 +120,28 @@ private slots: private: void checkHomeserverVersion(); - std::string initialDeviceName() + void onMatrixIdEntered(); + void clearErrors() { -#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 + 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_; + + QString mxidError_; + QString error_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; + bool passwordSupported_ = true; + bool ssoSupported_ = false; - TextField *matrixid_input_; - TextField *password_input_; - TextField *deviceName_; - TextField *serverInput_; - 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 5bfce89e..83504d86 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp
@@ -5,91 +5,87 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include <QApplication> -#include <QLayout> #include <QMessageBox> -#include <QPluginLoader> -#include <QShortcut> #include <mtx/requests.hpp> #include <mtx/responses/login.hpp> +#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 "ui/LoadingIndicator.h" -#include "ui/OverlayModal.h" -#include "ui/SnackBar.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/MxcAnimatedImage.h" +#include "ui/MxcMediaProxy.h" +#include "ui/NhekoCursorShape.h" +#include "ui/NhekoDropArea.h" +#include "ui/NhekoGlobalObject.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<DeviceInfo>) +Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>) + 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); - - 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); + chat_page_ = new ChatPage(userSettings_, this); + registerQmlTypes(); - // Initialize sliding widget manager. - pageStack_ = new QStackedWidget(this); - pageStack_->addWidget(welcome_page_); - pageStack_->addWidget(login_page_); - pageStack_->addWidget(register_page_); - pageStack_->addWidget(chat_page_); + setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color()); + setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml"))); - setCentralWidget(pageStack_); - - 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())); + trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this); - connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage); - connect( - chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); + 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) { - login_page_->showError(msg); - showLoginPage(); - }); + 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_, @@ -97,20 +93,6 @@ MainWindow::MainWindow(QWidget *parent) this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - 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(); - }); - - connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); - - QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); - connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); - trayIcon_->setVisible(userSettings_->tray()); // load cache on event loop @@ -133,12 +115,172 @@ MainWindow::MainWindow(QWidget *parent) user_id.toStdString()); } + nhlog::ui()->info("User already signed in, showing chat page"); showChatPage(); } }); } void +MainWindow::registerQmlTypes() +{ + qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationDone>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationKey>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationMac>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationReady>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationStart>(); + qRegisterMetaType<CombinedImagePackModel *>(); + qRegisterMetaType<mtx::events::collections::TimelineEvents>(); + qRegisterMetaType<std::vector<DeviceInfo>>(); + + qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>(); + + 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<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); + qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); + qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea"); + qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape"); + qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage"); + qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia"); + qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel"); + qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login"); + qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration"); + qmlRegisterUncreatableType<DeviceVerificationFlow>( + "im.nheko", + 1, + 0, + "DeviceVerificationFlow", + QStringLiteral("Can't create verification flow from QML!")); + qmlRegisterUncreatableType<UserProfile>( + "im.nheko", + 1, + 0, + "UserProfileModel", + QStringLiteral("UserProfile needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType<MemberList>( + "im.nheko", + 1, + 0, + "MemberList", + QStringLiteral("MemberList needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType<RoomSettings>( + "im.nheko", + 1, + 0, + "RoomSettingsModel", + QStringLiteral("Room Settings needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType<TimelineModel>( + "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType<ImagePackListModel>( + "im.nheko", + 1, + 0, + "ImagePackListModel", + QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType<SingleImagePackModel>( + "im.nheko", + 1, + 0, + "SingleImagePackModel", + QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType<InviteesModel>( + "im.nheko", + 1, + 0, + "InviteesModel", + QStringLiteral("InviteesModel needs to be instantiated on the C++ side")); + qmlRegisterUncreatableType<ReadReceiptsProxy>( + "im.nheko", + 1, + 0, + "ReadReceiptsProxy", + QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side")); + + qmlRegisterSingletonType<Clipboard>( + "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new Clipboard(); + }); + qmlRegisterSingletonType<Nheko>( + "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new Nheko(); + }); + qmlRegisterSingletonType<UserSettingsModel>( + "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new UserSettingsModel(); + }); + + qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", userSettings_.data()); + + qRegisterMetaType<mtx::events::collections::TimelineEvents>(); + qRegisterMetaType<std::vector<DeviceInfo>>(); + + qmlRegisterUncreatableType<FilteredCommunitiesModel>( + "im.nheko", + 1, + 0, + "FilteredCommunitiesModel", + QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel")); + + qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel"); + qmlRegisterUncreatableType<emoji::Emoji>( + "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<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel"); + + qmlRegisterSingletonType<SelfVerificationStatus>( + "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()); + + QObject::connect(engine(), &QQmlEngine::quit, &QGuiApplication::quit); +} + +void MainWindow::setWindowTitle(int notificationCount) { QString name = QStringLiteral("nheko"); @@ -148,20 +290,19 @@ 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) { - emit focusChanged(true); - } else if (type == QEvent::WindowDeactivate) { - emit focusChanged(false); + + if (type == QEvent::Close) { + closeEvent(static_cast<QCloseEvent *>(event)); } - return QMainWindow::event(event); + return QQuickView::event(event); } void @@ -189,31 +330,6 @@ MainWindow::saveCurrentWindowSize() } void -MainWindow::removeOverlayProgressBar() -{ - QTimer *timer = new QTimer(this); - timer->setSingleShot(true); - - 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); - }); - - timer->start(50); -} - -void MainWindow::showChatPage() { auto userid = QString::fromStdString(http::client()->user_id().to_string()); @@ -227,19 +343,13 @@ MainWindow::showChatPage() userSettings_.data()->setDeviceId(device_id); userSettings_.data()->setHomeserver(homeserver); - showOverlayProgressBar(); - - pageStack_->setCurrentWidget(chat_page_); - - pageStack_->removeWidget(welcome_page_); - pageStack_->removeWidget(login_page_); - pageStack_->removeWidget(register_page_); - - 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 +357,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; @@ -290,22 +400,10 @@ 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<void(const mtx::requests::CreateRoom &request)> callback) { - auto dialog = new dialogs::CreateRoom(this); + auto dialog = new dialogs::CreateRoom(nullptr); connect(dialog, &dialogs::CreateRoom::createRoom, this, @@ -314,76 +412,19 @@ MainWindow::openCreateRoomDialog( showDialog(dialog); } -void -MainWindow::showTransparentOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags) -{ - modal_->setWidget(content); - modal_->setColor(QColor(30, 30, 30, 150)); - modal_->setDismissible(true); - modal_->setContentAlignment(flags); - modal_->raise(); - modal_->show(); -} - -void -MainWindow::showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags) -{ - 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(); -} - bool MainWindow::pageSupportsTray() const { - return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible(); -} - -void -MainWindow::hideOverlay() -{ - if (modal_) - modal_->hide(); + 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(); -} - -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_); + utils::centerWidget(dialog, this); + dialog->window()->windowHandle()->setTransientParent(this); } diff --git a/src/MainWindow.h b/src/MainWindow.h
index 458eb054..7bc94328 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h
@@ -8,26 +8,21 @@ #include <functional> -#include <QMainWindow> +#include <QQuickView> #include <QSharedPointer> -#include <QStackedWidget> #include <QSystemTrayIcon> #include "UserSettingsPage.h" -#include "ui/OverlayModal.h" #include "jdenticoninterface.h" class ChatPage; class RegisterPage; -class LoginPage; class WelcomePage; -class LoadingIndicator; -class OverlayModal; -class SnackBar; class TrayIcon; class UserSettings; +class MxcImageProvider; namespace mtx { namespace requests { @@ -42,17 +37,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(); @@ -61,69 +51,51 @@ public: openCreateRoomDialog(std::function<void(const mtx::requests::CreateRoom &request)> callback); void openJoinRoomDialog(std::function<void(const QString &room_id)> callback); - void hideOverlay(); - void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter); - void - showTransparentOverlayModal(QWidget *content, - QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop | Qt::AlignHCenter); + MxcImageProvider *imageProvider() { return imgProvider; } + + //! Show the chat page and start communicating with the given access token. + void showChatPage(); protected: - void closeEvent(QCloseEvent *event) override; + void closeEvent(QCloseEvent *event); bool event(QEvent *event) override; 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 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(); - virtual void setWindowTitle(int notificationCount); signals: - void focusChanged(const bool focused); void reload(); void secretsChanged(); + void showNotification(QString msg); + + void switchToChatPage(); + void switchToWelcomePage(); + void switchToLoginPage(QString error); + 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; + void registerQmlTypes(); + static MainWindow *instance_; //! The initial welcome screen. WelcomePage *welcome_page_; - //! The login screen. - 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> 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/RegisterPage.cpp b/src/RegisterPage.cpp
index d089ac96..5b2ebc78 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp
@@ -4,312 +4,83 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -#include <QInputDialog> -#include <QLabel> -#include <QMetaType> -#include <QPainter> -#include <QStyleOption> -#include <QTimer> -#include <QtMath> - +#include <mtx/responses/common.hpp> #include <mtx/responses/register.hpp> #include <mtx/responses/well-known.hpp> #include <mtxclient/http/client.hpp> #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" - -Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized) -Q_DECLARE_METATYPE(mtx::user_interactive::Auth) - -RegisterPage::RegisterPage(QWidget *parent) - : QWidget(parent) -{ - qRegisterMetaType<mtx::user_interactive::Unauthorized>(); - qRegisterMetaType<mtx::user_interactive::Auth>(); - top_layout_ = new QVBoxLayout(); - - 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); -} - -void -RegisterPage::onBackButtonClicked() +RegisterPage::RegisterPage(QObject *parent) + : QObject(parent) { - emit backButtonClicked(); + connect(this, &RegisterPage::registerOk, this, [] { MainWindow::instance()->showChatPage(); }); } void -RegisterPage::showError(const QString &msg) +RegisterPage::setError(QString err) { - 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); + registrationError_ = err; + emit errorChanged(); + registering_ = false; + emit registeringChanged(); } - 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(); -} - -bool -RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg) +RegisterPage::setHsError(QString err) { - if (t_field->isValid()) { - label->hide(); - return true; - } else { - showError(label, msg); - return false; - } + hsError_ = err; + emit hsErrorChanged(); + lookingUpHs_ = false; + emit lookingUpHsChanged(); } -bool -RegisterPage::checkUsername() +QString +RegisterPage::initialDeviceName() const { - 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")); + return QString::fromStdString(LoginPage::initialDeviceName_()); } void -RegisterPage::onRegisterButtonClicked() +RegisterPage::setServer(QString server) { - if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) { - auto server = server_input_->text().toStdString(); - - http::client()->set_server(server); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); + if (server == lastServer) + return; - // 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 + lastServer = server; - emit wellKnownLookup(); + http::client()->set_server(server.toStdString()); + http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation()); - emit registering(); - } -} + hsError_.clear(); + emit hsErrorChanged(); + supported_ = false; + lookingUpHs_ = true; + emit lookingUpHsChanged(); -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 +90,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) +{ + usernameAvailable_ = usernameUnavailable_ = false; + usernameError_.clear(); + lookingUpUsername_ = true; + emit lookingUpUsernameChanged(); + + 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 { + } + + usernameAvailable_ = false; + usernameUnavailable_ = true; + } else { + usernameAvailable_ = available.available; + usernameUnavailable_ = !available.available; + } + lookingUpUsername_ = false; + emit lookingUpUsernameChanged(); + }); +} + +void +RegisterPage::startRegistration(QString username, QString password, QString devicename) { // 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(); + if (!username.isEmpty() && !password.isEmpty() && usernameAvailable_ && supported_) { + registrationError_.clear(); + emit errorChanged(); + registering_ = true; + emit registeringChanged(); + connect(UIA::instance(), &UIA::error, this, [this](QString msg) { - showError(msg); + setError(msg); disconnect(UIA::instance(), &UIA::error, this, nullptr); }); http::client()->registration( - username, - password, + username.toStdString(), + password.toStdString(), ::UIA::instance()->genericHandler(QStringLiteral("Registration")), - registrationCb()); - } -} - -mtx::http::Callback<mtx::responses::Register> -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; - } + [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { + registering_ = false; + emit registeringChanged(); - // 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<int>(err->status_code), - err->matrix_error.error); - showError(QString::fromStdString(err->matrix_error.error)); - } - return; - } + 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; + } - nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", - static_cast<int>(err->status_code), - err->matrix_error.error); + // 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<int>(err->status_code), + err->matrix_error.error); + setError(QString::fromStdString(err->matrix_error.error)); + disconnect(UIA::instance(), &UIA::error, this, nullptr); + return; + } - showError(QString::fromStdString(err->matrix_error.error)); - }; -} + nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", + static_cast<int>(err->status_code), + err->matrix_error.error); -void -RegisterPage::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.initFrom(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + 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..67e2a22e 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h
@@ -6,88 +6,69 @@ #pragma once -#include <QWidget> - -#include <memory> +#include <QObject> +#include <QString> #include <mtx/user_interactive.hpp> #include <mtxclient/http/client.hpp> -class FlatButton; -class RaisedButton; -class TextField; -class QLabel; -class QVBoxLayout; -class QHBoxLayout; - -class RegisterPage : public QWidget +class RegisterPage : public QObject { Q_OBJECT + 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) + public: - RegisterPage(QWidget *parent = nullptr); + RegisterPage(QObject *parent = nullptr); -protected: - void paintEvent(QPaintEvent *event) override; + 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; -signals: - void backButtonClicked(); - void errorOccurred(); + 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_; } - //! Used to trigger the corresponding slot outside of the main thread. - void serverError(const QString &err); + QString error() const { return registrationError_; } + QString usernameError() const { return usernameError_; } + QString hsError() const { return hsError_; } - 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); +signals: + void errorChanged(); + void hsErrorChanged(); - bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg); - bool checkUsername(); - bool checkPassword(); - bool checkPasswordConfirmation(); - bool checkServer(); + void registeringChanged(); + void lookingUpHsChanged(); + void lookingUpUsernameChanged(); - void doWellKnownLookup(); - void doVersionsCheck(); - void doRegistration(); - mtx::http::Callback<mtx::responses::Register> registrationCb(); + void registerOk(); private: - QVBoxLayout *top_layout_; - - QHBoxLayout *back_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; + void versionsCheck(); - 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_; + void setHsError(QString err); + void setError(QString err); - FlatButton *back_button_; - RaisedButton *register_button_; + QString registrationError_, hsError_, usernameError_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; + bool registering_; + bool supported_; + bool lookingUpHs_; + bool lookingUpUsername_; + bool usernameAvailable_; + bool usernameUnavailable_; - TextField *username_input_; - TextField *password_input_; - TextField *password_confirmation_; - TextField *server_input_; - TextField *registration_token_input_; + QString lastServer; }; 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 <QMenu> #include <QPainter> #include <QTimer> +#include <QWindow> #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..a0aa8f84 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp
@@ -5,25 +5,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include <QApplication> -#include <QComboBox> #include <QCoreApplication> #include <QFileDialog> -#include <QFontComboBox> -#include <QFormLayout> +#include <QFontDatabase> #include <QInputDialog> -#include <QLabel> -#include <QLineEdit> #include <QMessageBox> -#include <QPainter> -#include <QPushButton> -#include <QResizeEvent> -#include <QScrollArea> -#include <QScroller> -#include <QSpinBox> #include <QStandardPaths> #include <QString> #include <QTextStream> -#include <QtQml> #include <mtx/secret_storage.hpp> #include "Cache.h" @@ -33,8 +22,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" @@ -1518,7 +1506,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 +1588,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 +1600,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 +1610,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 +1618,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 +1626,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 +1636,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 +1665,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/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 <QAbstractListModel> -#include <QFontDatabase> -#include <QFrame> #include <QProcessEnvironment> #include <QSettings> #include <QSharedPointer> -#include <QWidget> #include "JdenticonProvider.h" #include <optional> 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 <QStringBuilder> #include <QTextBoundaryFinder> #include <QTextDocument> +#include <QWindow> #include <QXmlStreamReader> #include <array> @@ -770,20 +771,17 @@ utils::luminance(const QColor &col) } void -utils::centerWidget(QWidget *widget, QWidget *parent) +utils::centerWidget(QWidget *widget, QWindow *parent) { - 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()); + widget->window()->windowHandle()->setTransientParent(parent); return; } - // Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry())); + auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint { + return QPoint(hostRect.center().x() - (childRect.width() * 0.5), + hostRect.center().y() - (childRect.height() * 0.5)); + }; 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); 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 <siderisk@auth.gr> -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include <QLabel> -#include <QLayout> -#include <QPainter> -#include <QStyleOption> - -#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 <QWidget> - -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/main.cpp b/src/main.cpp
index 2ae631cf..24fc8415 100644 --- a/src/main.cpp +++ b/src/main.cpp
@@ -17,6 +17,7 @@ #include <QLibraryInfo> #include <QMessageBox> #include <QPoint> +#include <QQuickView> #include <QScreen> #include <QStandardPaths> #include <QTranslator> @@ -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,9 +298,10 @@ 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(); @@ -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..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 (!ChatPage::instance()->isActiveWindow()) + if (MainWindow::instance() != QGuiApplication::focusWindow()) 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..0abd102b 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp
@@ -5,10 +5,10 @@ #include "TimelineViewManager.h" +#include <QApplication> #include <QDropEvent> #include <QFileDialog> #include <QMetaType> -#include <QPalette> #include <QQmlContext> #include <QQmlEngine> #include <QStandardPaths> @@ -45,10 +45,6 @@ #include "ui/NhekoGlobalObject.h" #include "ui/UIA.h" -Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) -Q_DECLARE_METATYPE(std::vector<DeviceInfo>) -Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>) - namespace msgs = mtx::events::msg; namespace { @@ -102,19 +98,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 +109,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<mtx::events::msg::KeyVerificationAccept>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationDone>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationKey>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationMac>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationReady>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationStart>(); - qRegisterMetaType<CombinedImagePackModel *>(); - - qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>(); - - 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<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); - qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); - qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea"); - qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape"); - qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage"); - qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia"); - qmlRegisterUncreatableType<DeviceVerificationFlow>( - "im.nheko", - 1, - 0, - "DeviceVerificationFlow", - QStringLiteral("Can't create verification flow from QML!")); - qmlRegisterUncreatableType<UserProfile>( - "im.nheko", - 1, - 0, - "UserProfileModel", - QStringLiteral("UserProfile needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<MemberList>( - "im.nheko", - 1, - 0, - "MemberList", - QStringLiteral("MemberList needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<RoomSettings>( - "im.nheko", - 1, - 0, - "RoomSettingsModel", - QStringLiteral("Room Settings needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<TimelineModel>( - "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<ImagePackListModel>( - "im.nheko", - 1, - 0, - "ImagePackListModel", - QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<SingleImagePackModel>( - "im.nheko", - 1, - 0, - "SingleImagePackModel", - QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<InviteesModel>( - "im.nheko", - 1, - 0, - "InviteesModel", - QStringLiteral("InviteesModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<ReadReceiptsProxy>( - "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<RoomlistModel>( "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { auto ptr = new FilteredRoomlistModel(self->rooms_); @@ -247,79 +133,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<Clipboard>( - "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Clipboard(); - }); - qmlRegisterSingletonType<Nheko>( - "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Nheko(); - }); - qmlRegisterSingletonType<UserSettingsModel>( - "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<SelfVerificationStatus>( - "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = new SelfVerificationStatus(); - QObject::connect(ChatPage::instance(), - &ChatPage::initializeEmptyViews, - ptr, - &SelfVerificationStatus::invalidate); - return ptr; - }); - - qRegisterMetaType<mtx::events::collections::TimelineEvents>(); - qRegisterMetaType<std::vector<DeviceInfo>>(); - qmlRegisterUncreatableType<FilteredCommunitiesModel>( - "im.nheko", - 1, - 0, - "FilteredCommunitiesModel", - QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel")); - - qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel"); - qmlRegisterUncreatableType<emoji::Emoji>( - "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<RoomDirectoryModel>("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_, @@ -336,6 +158,16 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par isInitialSync_ = true; emit initialSyncChanged(true); }); + connect(qobject_cast<QApplication *>(QApplication::instance()), + &QApplication::focusWindowChanged, + this, + &TimelineViewManager::focusChanged); +} + +bool +TimelineViewManager::isWindowFocused() const +{ + return MainWindow::instance() == QApplication::focusWindow(); } void @@ -379,7 +211,8 @@ void TimelineViewManager::setVideoCallItem() { WebRTCSession::instance().setVideoItem( - view->rootObject()->findChild<QQuickItem *>(QStringLiteral("videoCallItem"))); + MainWindow::instance()->rootObject()->findChild<QQuickItem *>( + QStringLiteral("videoCallItem"))); } void @@ -401,7 +234,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 +272,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; @@ -591,12 +424,6 @@ TimelineViewManager::completerFor(QString completerName, QString roomId) } 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..13ab5dbb 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h
@@ -8,8 +8,6 @@ #include <QHash> #include <QQuickItem> #include <QQuickTextDocument> -#include <QQuickView> -#include <QQuickWidget> #include <QWidget> #include <mtx/common.hpp> @@ -43,23 +41,19 @@ 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); - 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(); } 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); @@ -98,14 +92,8 @@ public slots: void updateReadReceipts(const QString &room_id, const std::vector<QString> &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); - void focusTimeline(); void updateColorPalette(); void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody); @@ -122,26 +110,12 @@ 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; + bool isInitialSync_ = true; RoomlistModel *rooms_ = nullptr; CommunitiesModel *communities_ = nullptr; // don't move this above the rooms_ - CallManager *callManager_ = nullptr; VerificationManager *verificationManager_ = nullptr; PresenceEmitter *presenceEmitter = nullptr; 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 <QLinearGradient> -#include <QPainter> - -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 <QColor> - -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 <QEventTransition> -#include <QFontDatabase> -#include <QIcon> -#include <QMouseEvent> -#include <QPaintEvent> -#include <QPainter> -#include <QPainterPath> -#include <QResizeEvent> -#include <QSignalTransition> - -#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<qreal>(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<qreal>(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<QFocusEvent *>(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 <QPushButton> -#include <QStateMachine> - -#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 <siderisk@auth.gr> -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "Label.h" -#include <QMouseEvent> - -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 <QLabel> - -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 <QPaintEvent> -#include <QPainter> -#include <QTimer> - -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 <QColor> -#include <QWidget> - -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 <QMenu> - -#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/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/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 <siderisk@auth.gr> -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include <QPainter> -#include <QVBoxLayout> - -#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 <siderisk@auth.gr> -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// SPDX-FileCopyrightText: 2022 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include <QKeyEvent> -#include <QMouseEvent> -#include <QPaintEvent> -#include <QVBoxLayout> - -#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<Qt::AlignmentFlag> 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/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 <QPainter> -#include <QStyleOption> - -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 <QEvent> -#include <QWidget> - -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; -}; 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 <QFontMetrics> -#include <QPaintDevice> -#include <QPainter> -#include <QPainterPath> -#include <QtGlobal> - -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 <QEventTransition> -#include <QPropertyAnimation> - -#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 <QGraphicsDropShadowEffect> -#include <QState> -#include <QStateMachine> - -#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 &center, 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 &center, 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 <QBrush> -#include <QEasingCurve> -#include <QParallelAnimationGroup> -#include <QPoint> -#include <QPropertyAnimation> - -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 &center, QObject *parent = nullptr); - Ripple(const QPoint &center, 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 <QPainter> - -#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 <QPainterPath> - -#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<Ripple *> 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 <QPainter> - -#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 <QCoreApplication> -#include <QPaintEvent> -#include <QPropertyAnimation> -#include <QTimer> -#include <deque> - -#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<QString> 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 <QAbstractTextDocumentLayout> -#include <QDesktopServices> -#include <QEvent> -#include <QWheelEvent> - -#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<User>(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 <QSize> -#include <QString> -#include <QTextBrowser> -#include <QUrl> - -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; -}; 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 <mtx/responses/common.hpp> #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]() {