1 // SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
2 // SPDX-FileCopyrightText: 2021 Nheko Contributors
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5
6 #include <QApplication>
7 #include <QLayout>
8 #include <QMessageBox>
9 #include <QPluginLoader>
10 #include <QShortcut>
11
12 #include <mtx/requests.hpp>
13 #include <mtx/responses/login.hpp>
14
15 #include "Cache.h"
16 #include "Cache_p.h"
17 #include "ChatPage.h"
18 #include "Config.h"
19 #include "JdenticonProvider.h"
20 #include "Logging.h"
21 #include "LoginPage.h"
22 #include "MainWindow.h"
23 #include "MatrixClient.h"
24 #include "MemberList.h"
25 #include "RegisterPage.h"
26 #include "TrayIcon.h"
27 #include "UserSettingsPage.h"
28 #include "Utils.h"
29 #include "WelcomePage.h"
30 #include "ui/LoadingIndicator.h"
31 #include "ui/OverlayModal.h"
32 #include "ui/SnackBar.h"
33 #include "voip/WebRTCSession.h"
34
35 #include "dialogs/CreateRoom.h"
36
37 MainWindow *MainWindow::instance_ = nullptr;
38
MainWindow(QWidget * parent)39 MainWindow::MainWindow(QWidget *parent)
40 : QMainWindow(parent)
41 , userSettings_{UserSettings::instance()}
42 {
43 instance_ = this;
44
45 setWindowTitle(0);
46 setObjectName("MainWindow");
47
48 modal_ = new OverlayModal(this);
49
50 restoreWindowSize();
51
52 QFont font;
53 font.setStyleStrategy(QFont::PreferAntialias);
54 setFont(font);
55
56 trayIcon_ = new TrayIcon(":/logos/nheko.svg", this);
57
58 welcome_page_ = new WelcomePage(this);
59 login_page_ = new LoginPage(this);
60 register_page_ = new RegisterPage(this);
61 chat_page_ = new ChatPage(userSettings_, this);
62 userSettingsPage_ = new UserSettingsPage(userSettings_, this);
63
64 // Initialize sliding widget manager.
65 pageStack_ = new QStackedWidget(this);
66 pageStack_->addWidget(welcome_page_);
67 pageStack_->addWidget(login_page_);
68 pageStack_->addWidget(register_page_);
69 pageStack_->addWidget(chat_page_);
70 pageStack_->addWidget(userSettingsPage_);
71
72 setCentralWidget(pageStack_);
73
74 connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
75 connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
76
77 connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
78 connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
79 connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
__anon01415c920102() 80 connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
81 connect(
__anon01415c920202() 82 register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
83 connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
84
85 connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
86 connect(
87 chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
88 connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
89 connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
__anon01415c920302(const QString &msg) 90 connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
91 login_page_->showError(msg);
92 showLoginPage();
93 });
94
__anon01415c920402() 95 connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() {
96 pageStack_->setCurrentWidget(chat_page_);
97 });
98
99 connect(userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
100 connect(
101 userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged);
102 connect(trayIcon_,
103 SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
104 this,
105 SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
106
107 connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
108
109 connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
110
111 connect(chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
112
__anon01415c920502(const mtx::responses::Login &res) 113 connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
114 http::client()->set_user(res.user_id);
115 showChatPage();
116 });
117
118 connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
119
120 QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
121 connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
122
123 trayIcon_->setVisible(userSettings_->tray());
124
125 // load cache on event loop
__anon01415c920602null126 QTimer::singleShot(0, this, [this] {
127 if (hasActiveUser()) {
128 QString token = userSettings_->accessToken();
129 QString home_server = userSettings_->homeserver();
130 QString user_id = userSettings_->userId();
131 QString device_id = userSettings_->deviceId();
132
133 http::client()->set_access_token(token.toStdString());
134 http::client()->set_server(home_server.toStdString());
135 http::client()->set_device_id(device_id.toStdString());
136
137 try {
138 using namespace mtx::identifiers;
139 http::client()->set_user(parse<User>(user_id.toStdString()));
140 } catch (const std::invalid_argument &) {
141 nhlog::ui()->critical("bootstrapped with invalid user_id: {}",
142 user_id.toStdString());
143 }
144
145 showChatPage();
146 }
147 });
148 }
149
150 void
setWindowTitle(int notificationCount)151 MainWindow::setWindowTitle(int notificationCount)
152 {
153 QString name = "nheko";
154
155 if (!userSettings_.data()->profile().isEmpty())
156 name += " | " + userSettings_.data()->profile();
157 if (notificationCount > 0) {
158 name.append(QString{" (%1)"}.arg(notificationCount));
159 }
160 QMainWindow::setWindowTitle(name);
161 }
162
163 bool
event(QEvent * event)164 MainWindow::event(QEvent *event)
165 {
166 auto type = event->type();
167 if (type == QEvent::WindowActivate) {
168 emit focusChanged(true);
169 } else if (type == QEvent::WindowDeactivate) {
170 emit focusChanged(false);
171 }
172
173 return QMainWindow::event(event);
174 }
175
176 void
restoreWindowSize()177 MainWindow::restoreWindowSize()
178 {
179 int savedWidth = userSettings_->qsettings()->value("window/width").toInt();
180 int savedheight = userSettings_->qsettings()->value("window/height").toInt();
181
182 nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight);
183
184 if (savedWidth == 0 || savedheight == 0)
185 resize(conf::window::width, conf::window::height);
186 else
187 resize(savedWidth, savedheight);
188 }
189
190 void
saveCurrentWindowSize()191 MainWindow::saveCurrentWindowSize()
192 {
193 auto settings = userSettings_->qsettings();
194 QSize current = size();
195
196 settings->setValue("window/width", current.width());
197 settings->setValue("window/height", current.height());
198 }
199
200 void
removeOverlayProgressBar()201 MainWindow::removeOverlayProgressBar()
202 {
203 QTimer *timer = new QTimer(this);
204 timer->setSingleShot(true);
205
206 connect(timer, &QTimer::timeout, [this, timer]() {
207 timer->deleteLater();
208
209 if (modal_)
210 modal_->hide();
211
212 if (spinner_)
213 spinner_->stop();
214 });
215
216 // FIXME: Snackbar doesn't work if it's initialized in the constructor.
217 QTimer::singleShot(0, this, [this]() {
218 snackBar_ = new SnackBar(this);
219 connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
220 });
221
222 timer->start(50);
223 }
224
225 void
showChatPage()226 MainWindow::showChatPage()
227 {
228 auto userid = QString::fromStdString(http::client()->user_id().to_string());
229 auto device_id = QString::fromStdString(http::client()->device_id());
230 auto homeserver = QString::fromStdString(http::client()->server() + ":" +
231 std::to_string(http::client()->port()));
232 auto token = QString::fromStdString(http::client()->access_token());
233
234 userSettings_.data()->setUserId(userid);
235 userSettings_.data()->setAccessToken(token);
236 userSettings_.data()->setDeviceId(device_id);
237 userSettings_.data()->setHomeserver(homeserver);
238
239 showOverlayProgressBar();
240
241 pageStack_->setCurrentWidget(chat_page_);
242
243 pageStack_->removeWidget(welcome_page_);
244 pageStack_->removeWidget(login_page_);
245 pageStack_->removeWidget(register_page_);
246
247 login_page_->reset();
248 chat_page_->bootstrap(userid, homeserver, token);
249 connect(cache::client(),
250 &Cache::databaseReady,
251 userSettingsPage_,
252 &UserSettingsPage::updateSecretStatus);
253 connect(cache::client(),
254 &Cache::secretChanged,
255 userSettingsPage_,
256 &UserSettingsPage::updateSecretStatus);
257 emit reload();
258 }
259
260 void
closeEvent(QCloseEvent * event)261 MainWindow::closeEvent(QCloseEvent *event)
262 {
263 if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
264 if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
265 QMessageBox::Yes) {
266 event->ignore();
267 return;
268 }
269 }
270
271 if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && userSettings_->tray()) {
272 event->ignore();
273 hide();
274 }
275 }
276
277 void
iconActivated(QSystemTrayIcon::ActivationReason reason)278 MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
279 {
280 switch (reason) {
281 case QSystemTrayIcon::Trigger:
282 if (!isVisible()) {
283 show();
284 } else {
285 hide();
286 }
287 break;
288 default:
289 break;
290 }
291 }
292
293 bool
hasActiveUser()294 MainWindow::hasActiveUser()
295 {
296 auto settings = userSettings_->qsettings();
297 QString prefix;
298 if (userSettings_->profile() != "")
299 prefix = "profile/" + userSettings_->profile() + "/";
300
301 return settings->contains(prefix + "auth/access_token") &&
302 settings->contains(prefix + "auth/home_server") &&
303 settings->contains(prefix + "auth/user_id");
304 }
305
306 void
showOverlayProgressBar()307 MainWindow::showOverlayProgressBar()
308 {
309 spinner_ = new LoadingIndicator(this);
310 spinner_->setFixedHeight(100);
311 spinner_->setFixedWidth(100);
312 spinner_->setObjectName("ChatPageLoadSpinner");
313 spinner_->start();
314
315 showSolidOverlayModal(spinner_);
316 }
317
318 void
openCreateRoomDialog(std::function<void (const mtx::requests::CreateRoom & request)> callback)319 MainWindow::openCreateRoomDialog(
320 std::function<void(const mtx::requests::CreateRoom &request)> callback)
321 {
322 auto dialog = new dialogs::CreateRoom(this);
323 connect(dialog,
324 &dialogs::CreateRoom::createRoom,
325 this,
326 [callback](const mtx::requests::CreateRoom &request) { callback(request); });
327
328 showDialog(dialog);
329 }
330
331 void
showTransparentOverlayModal(QWidget * content,QFlags<Qt::AlignmentFlag> flags)332 MainWindow::showTransparentOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
333 {
334 modal_->setWidget(content);
335 modal_->setColor(QColor(30, 30, 30, 150));
336 modal_->setDismissible(true);
337 modal_->setContentAlignment(flags);
338 modal_->raise();
339 modal_->show();
340 }
341
342 void
showSolidOverlayModal(QWidget * content,QFlags<Qt::AlignmentFlag> flags)343 MainWindow::showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
344 {
345 modal_->setWidget(content);
346 modal_->setColor(QColor(30, 30, 30));
347 modal_->setDismissible(false);
348 modal_->setContentAlignment(flags);
349 modal_->raise();
350 modal_->show();
351 }
352
353 bool
hasActiveDialogs() const354 MainWindow::hasActiveDialogs() const
355 {
356 return !modal_ && modal_->isVisible();
357 }
358
359 bool
pageSupportsTray() const360 MainWindow::pageSupportsTray() const
361 {
362 return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible();
363 }
364
365 void
hideOverlay()366 MainWindow::hideOverlay()
367 {
368 if (modal_)
369 modal_->hide();
370 }
371
372 inline void
showDialog(QWidget * dialog)373 MainWindow::showDialog(QWidget *dialog)
374 {
375 utils::centerWidget(dialog, this);
376 dialog->raise();
377 dialog->show();
378 }
379
380 void
showWelcomePage()381 MainWindow::showWelcomePage()
382 {
383 removeOverlayProgressBar();
384 pageStack_->addWidget(welcome_page_);
385 pageStack_->setCurrentWidget(welcome_page_);
386 }
387
388 void
showLoginPage()389 MainWindow::showLoginPage()
390 {
391 if (modal_)
392 modal_->hide();
393
394 pageStack_->addWidget(login_page_);
395 pageStack_->setCurrentWidget(login_page_);
396 }
397
398 void
showRegisterPage()399 MainWindow::showRegisterPage()
400 {
401 pageStack_->addWidget(register_page_);
402 pageStack_->setCurrentWidget(register_page_);
403 }
404
405 void
showUserSettingsPage()406 MainWindow::showUserSettingsPage()
407 {
408 pageStack_->setCurrentWidget(userSettingsPage_);
409 }
410