/*
Copyright © 2014-2019 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see .
*/
#include "nexus.h"
#include "persistence/settings.h"
#include "src/core/core.h"
#include "src/core/coreav.h"
#include "src/model/groupinvite.h"
#include "src/model/status.h"
#include "src/persistence/profile.h"
#include "src/widget/widget.h"
#include "video/camerasource.h"
#include "widget/gui.h"
#include "widget/loginscreen.h"
#include
#include
#include
#include
#include
#include
#include "audio/audio.h"
#include
#ifdef Q_OS_MAC
#include
#include
#include
#include
#endif
/**
* @class Nexus
*
* This class is in charge of connecting various systems together
* and forwarding signals appropriately to the right objects,
* it is in charge of starting the GUI and the Core.
*/
Q_DECLARE_OPAQUE_POINTER(ToxAV*)
static Nexus* nexus{nullptr};
Nexus::Nexus(QObject* parent)
: QObject(parent)
, profile{nullptr}
, widget{nullptr}
{}
Nexus::~Nexus()
{
delete widget;
widget = nullptr;
delete profile;
profile = nullptr;
emit saveGlobal();
#ifdef Q_OS_MAC
delete globalMenuBar;
#endif
}
/**
* @brief Sets up invariants and calls showLogin
*
* Hides the login screen and shows the GUI for the given profile.
* Will delete the current GUI, if it exists.
*/
void Nexus::start()
{
qDebug() << "Starting up";
// Setup the environment
qRegisterMetaType("Status::Status");
qRegisterMetaType("vpx_image");
qRegisterMetaType("uint8_t");
qRegisterMetaType("uint16_t");
qRegisterMetaType("uint32_t");
qRegisterMetaType("const int16_t*");
qRegisterMetaType("int32_t");
qRegisterMetaType("int64_t");
qRegisterMetaType("size_t");
qRegisterMetaType("QPixmap");
qRegisterMetaType("Profile*");
qRegisterMetaType("ToxAV*");
qRegisterMetaType("ToxFile");
qRegisterMetaType("ToxFile::FileDirection");
qRegisterMetaType>("std::shared_ptr");
qRegisterMetaType("ToxPk");
qRegisterMetaType("ToxId");
qRegisterMetaType("GroupId");
qRegisterMetaType("ContactId");
qRegisterMetaType("GroupInvite");
qRegisterMetaType("ReceiptNum");
qRegisterMetaType("RowId");
qApp->setQuitOnLastWindowClosed(false);
#ifdef Q_OS_MAC
// TODO: still needed?
globalMenuBar = new QMenuBar();
dockMenu = new QMenu(globalMenuBar);
viewMenu = globalMenuBar->addMenu(QString());
windowMenu = globalMenuBar->addMenu(QString());
globalMenuBar->addAction(windowMenu->menuAction());
fullscreenAction = viewMenu->addAction(QString());
fullscreenAction->setShortcut(QKeySequence::FullScreen);
connect(fullscreenAction, &QAction::triggered, this, &Nexus::toggleFullscreen);
minimizeAction = windowMenu->addAction(QString());
minimizeAction->setShortcut(Qt::CTRL + Qt::Key_M);
connect(minimizeAction, &QAction::triggered, [this]() {
minimizeAction->setEnabled(false);
QApplication::focusWindow()->showMinimized();
});
windowMenu->addSeparator();
frontAction = windowMenu->addAction(QString());
connect(frontAction, &QAction::triggered, this, &Nexus::bringAllToFront);
QAction* quitAction = new QAction(globalMenuBar);
quitAction->setMenuRole(QAction::QuitRole);
connect(quitAction, &QAction::triggered, qApp, &QApplication::quit);
retranslateUi();
#endif
showMainGUI();
}
/**
* @brief Hides the main GUI, delete the profile, and shows the login screen
*/
int Nexus::showLogin(const QString& profileName)
{
delete widget;
widget = nullptr;
delete profile;
profile = nullptr;
LoginScreen loginScreen{profileName};
connectLoginScreen(loginScreen);
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
// TODO(kriby): Move core out of profile
// This is awkward because the core is in the profile
// The connection order ensures profile will be ready for bootstrap for now
connect(this, &Nexus::currentProfileChanged, this, &Nexus::bootstrapWithProfile);
int returnval = loginScreen.exec();
if (returnval == QDialog::Rejected) {
// Kriby: This will terminate the main application loop, necessary until we refactor
// away the split startup/return to login behavior.
qApp->quit();
}
disconnect(this, &Nexus::currentProfileChanged, this, &Nexus::bootstrapWithProfile);
return returnval;
}
void Nexus::bootstrapWithProfile(Profile* p)
{
// kriby: This is a hack until a proper controller is written
profile = p;
if (profile) {
audioControl = std::unique_ptr(Audio::makeAudio(*settings));
assert(audioControl != nullptr);
profile->getCore().getAv()->setAudio(*audioControl);
start();
}
}
void Nexus::setSettings(Settings* settings)
{
if (this->settings) {
QObject::disconnect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal);
}
this->settings = settings;
if (this->settings) {
QObject::connect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal);
}
}
void Nexus::connectLoginScreen(const LoginScreen& loginScreen)
{
// TODO(kriby): Move connect sequences to a controller class object instead
// Nexus -> LoginScreen
QObject::connect(this, &Nexus::profileLoaded, &loginScreen, &LoginScreen::onProfileLoaded);
QObject::connect(this, &Nexus::profileLoadFailed, &loginScreen, &LoginScreen::onProfileLoadFailed);
// LoginScreen -> Nexus
QObject::connect(&loginScreen, &LoginScreen::createNewProfile, this, &Nexus::onCreateNewProfile);
QObject::connect(&loginScreen, &LoginScreen::loadProfile, this, &Nexus::onLoadProfile);
// LoginScreen -> Settings
QObject::connect(&loginScreen, &LoginScreen::autoLoginChanged, settings, &Settings::setAutoLogin);
QObject::connect(&loginScreen, &LoginScreen::autoLoginChanged, settings, &Settings::saveGlobal);
// Settings -> LoginScreen
QObject::connect(settings, &Settings::autoLoginChanged, &loginScreen,
&LoginScreen::onAutoLoginChanged);
}
void Nexus::showMainGUI()
{
// TODO(kriby): Rewrite as view-model connect sequence only, add to a controller class object
assert(profile);
// Create GUI
widget = new Widget(*profile, *audioControl);
// Start GUI
widget->init();
GUI::getInstance();
// Zetok protection
// There are small instants on startup during which no
// profile is loaded but the GUI could still receive events,
// e.g. between two modal windows. Disable the GUI to prevent that.
GUI::setEnabled(false);
// Connections
connect(profile, &Profile::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded);
connect(profile, &Profile::coreChanged, widget, &Widget::onCoreChanged);
connect(profile, &Profile::failedToStart, widget, &Widget::onFailedToStartCore,
Qt::BlockingQueuedConnection);
connect(profile, &Profile::badProxy, widget, &Widget::onBadProxyCore, Qt::BlockingQueuedConnection);
profile->startCore();
GUI::setEnabled(true);
}
/**
* @brief Returns the singleton instance.
*/
Nexus& Nexus::getInstance()
{
if (!nexus)
nexus = new Nexus;
return *nexus;
}
void Nexus::destroyInstance()
{
delete nexus;
nexus = nullptr;
}
/**
* @brief Get core instance.
* @return nullptr if not started, core instance otherwise.
*/
Core* Nexus::getCore()
{
Nexus& nexus = getInstance();
if (!nexus.profile)
return nullptr;
return &nexus.profile->getCore();
}
/**
* @brief Get current user profile.
* @return nullptr if not started, profile otherwise.
*/
Profile* Nexus::getProfile()
{
return getInstance().profile;
}
/**
* @brief Creates a new profile and replaces the current one.
* @param name New username
* @param pass New password
*/
void Nexus::onCreateNewProfile(const QString& name, const QString& pass)
{
setProfile(Profile::createProfile(name, pass, *settings, parser));
parser = nullptr; // only apply cmdline proxy settings once
}
/**
* Loads an existing profile and replaces the current one.
*/
void Nexus::onLoadProfile(const QString& name, const QString& pass)
{
setProfile(Profile::loadProfile(name, pass, *settings, parser));
parser = nullptr; // only apply cmdline proxy settings once
}
/**
* Changes the loaded profile and notifies listeners.
* @param p
*/
void Nexus::setProfile(Profile* p)
{
if (!p) {
emit profileLoadFailed();
// Warnings are issued during respective createNew/load calls
return;
} else {
emit profileLoaded();
}
emit currentProfileChanged(p);
}
void Nexus::setParser(QCommandLineParser* parser)
{
this->parser = parser;
}
/**
* @brief Get desktop GUI widget.
* @return nullptr if not started, desktop widget otherwise.
*/
Widget* Nexus::getDesktopGUI()
{
return getInstance().widget;
}
#ifdef Q_OS_MAC
void Nexus::retranslateUi()
{
viewMenu->menuAction()->setText(tr("View", "OS X Menu bar"));
windowMenu->menuAction()->setText(tr("Window", "OS X Menu bar"));
minimizeAction->setText(tr("Minimize", "OS X Menu bar"));
frontAction->setText((tr("Bring All to Front", "OS X Menu bar")));
}
void Nexus::onWindowStateChanged(Qt::WindowStates state)
{
minimizeAction->setEnabled(QApplication::activeWindow() != nullptr);
if (QApplication::activeWindow() != nullptr && sender() == QApplication::activeWindow()) {
if (state & Qt::WindowFullScreen)
minimizeAction->setEnabled(false);
if (state & Qt::WindowFullScreen)
fullscreenAction->setText(tr("Exit Fullscreen"));
else
fullscreenAction->setText(tr("Enter Fullscreen"));
updateWindows();
}
updateWindowsStates();
}
void Nexus::updateWindows()
{
updateWindowsArg(nullptr);
}
void Nexus::updateWindowsArg(QWindow* closedWindow)
{
QWindowList windowList = QApplication::topLevelWindows();
delete windowActions;
windowActions = new QActionGroup(this);
windowMenu->addSeparator();
QAction* dockLast;
if (!dockMenu->actions().isEmpty())
dockLast = dockMenu->actions().first();
else
dockLast = nullptr;
QWindow* activeWindow;
if (QApplication::activeWindow())
activeWindow = QApplication::activeWindow()->windowHandle();
else
activeWindow = nullptr;
for (int i = 0; i < windowList.size(); ++i) {
if (closedWindow == windowList[i])
continue;
QAction* action = windowActions->addAction(windowList[i]->title());
action->setCheckable(true);
action->setChecked(windowList[i] == activeWindow);
connect(action, &QAction::triggered, [=] { onOpenWindow(windowList[i]); });
windowMenu->addAction(action);
dockMenu->insertAction(dockLast, action);
}
if (dockLast && !dockLast->isSeparator())
dockMenu->insertSeparator(dockLast);
}
void Nexus::updateWindowsClosed()
{
updateWindowsArg(static_cast(sender())->windowHandle());
}
void Nexus::updateWindowsStates()
{
bool exists = false;
QWindowList windowList = QApplication::topLevelWindows();
for (QWindow* window : windowList) {
if (!(window->windowState() & Qt::WindowMinimized)) {
exists = true;
break;
}
}
frontAction->setEnabled(exists);
}
void Nexus::onOpenWindow(QObject* object)
{
QWindow* window = static_cast(object);
if (window->windowState() & QWindow::Minimized)
window->showNormal();
window->raise();
window->requestActivate();
}
void Nexus::toggleFullscreen()
{
QWidget* window = QApplication::activeWindow();
if (window->isFullScreen())
window->showNormal();
else
window->showFullScreen();
}
void Nexus::bringAllToFront()
{
QWindowList windowList = QApplication::topLevelWindows();
QWindow* focused = QApplication::focusWindow();
for (QWindow* window : windowList)
window->raise();
focused->raise();
}
#endif