/* Copyright © 2014-2015 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/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 #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} , loginScreen{nullptr} , running{true} , quitOnLastWindowClosed{true} { } Nexus::~Nexus() { delete widget; widget = nullptr; delete loginScreen; loginScreen = nullptr; delete profile; profile = nullptr; Settings::getInstance().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"); qRegisterMetaType("vpx_image"); qRegisterMetaType("uint8_t"); qRegisterMetaType("uint16_t"); qRegisterMetaType("uint32_t"); qRegisterMetaType("const int16_t*"); qRegisterMetaType("int32_t"); qRegisterMetaType("int64_t"); qRegisterMetaType("QPixmap"); qRegisterMetaType("Profile*"); qRegisterMetaType("ToxAV*"); qRegisterMetaType("ToxFile"); qRegisterMetaType("ToxFile::FileDirection"); qRegisterMetaType>("std::shared_ptr"); qRegisterMetaType("ToxPk"); qRegisterMetaType("ToxId"); loginScreen = new LoginScreen(); // We need this LastWindowClosed dance because the LoginScreen may be shown // and closed in a processEvents() loop before the start of the real // exec() event loop, meaning we wouldn't receive the onLastWindowClosed, // and so we wouldn't have a chance to tell the processEvents() loop to quit. qApp->setQuitOnLastWindowClosed(false); connect(qApp, &QApplication::lastWindowClosed, this, &Nexus::onLastWindowClosed); connect(loginScreen, &LoginScreen::closed, this, &Nexus::onLastWindowClosed); #ifdef Q_OS_MAC globalMenuBar = new QMenuBar(0); 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); windowMapper = new QSignalMapper(this); connect(windowMapper, SIGNAL(mapped(QObject*)), this, SLOT(onOpenWindow(QObject*))); connect(loginScreen, &LoginScreen::windowStateChanged, this, &Nexus::onWindowStateChanged); retranslateUi(); #endif if (profile) showMainGUI(); else showLogin(); } /** * @brief Hides the main GUI, delete the profile, and shows the login screen */ void Nexus::showLogin() { delete widget; widget = nullptr; delete profile; profile = nullptr; loginScreen->reset(); loginScreen->move(QApplication::desktop()->screen()->rect().center() - loginScreen->rect().center()); loginScreen->show(); quitOnLastWindowClosed = true; } void Nexus::showMainGUI() { assert(profile); quitOnLastWindowClosed = false; loginScreen->close(); // Create GUI widget = Widget::getInstance(); // 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 Core* core = profile->getCore(); connect(core, &Core::connected, widget, &Widget::onConnected); connect(core, &Core::disconnected, widget, &Widget::onDisconnected); connect(core, &Core::failedToStart, widget, &Widget::onFailedToStartCore, Qt::BlockingQueuedConnection); connect(core, &Core::badProxy, widget, &Widget::onBadProxyCore, Qt::BlockingQueuedConnection); connect(core, &Core::statusSet, widget, &Widget::onStatusSet); connect(core, &Core::usernameSet, widget, &Widget::setUsername); connect(core, &Core::statusMessageSet, widget, &Widget::setStatusMessage); connect(core, &Core::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded); connect(core, &Core::friendAdded, widget, &Widget::addFriend); connect(core, &Core::friendshipChanged, widget, &Widget::onFriendshipChanged); connect(core, &Core::failedToAddFriend, widget, &Widget::addFriendFailed); connect(core, &Core::friendUsernameChanged, widget, &Widget::onFriendUsernameChanged); connect(core, &Core::friendStatusChanged, widget, &Widget::onFriendStatusChanged); connect(core, &Core::friendStatusMessageChanged, widget, &Widget::onFriendStatusMessageChanged); connect(core, &Core::friendRequestReceived, widget, &Widget::onFriendRequestReceived); connect(core, &Core::friendMessageReceived, widget, &Widget::onFriendMessageReceived); connect(core, &Core::receiptRecieved, widget, &Widget::onReceiptRecieved); connect(core, &Core::groupInviteReceived, widget, &Widget::onGroupInviteReceived); connect(core, &Core::groupMessageReceived, widget, &Widget::onGroupMessageReceived); connect(core, &Core::groupNamelistChanged, widget, &Widget::onGroupNamelistChanged); connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged); connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying); connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated); connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged); connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult); connect(core, &Core::groupSentResult, widget, &Widget::onGroupSendResult); connect(widget, &Widget::statusSet, core, &Core::setStatus); connect(widget, &Widget::friendRequested, core, &Core::requestFriendship); connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest); profile->startCore(); } /** * @brief Calls QApplication::quit(), and causes Nexus::isRunning() to return false */ void Nexus::quit() { running = false; qApp->quit(); } /** * @brief Returns true until Nexus::quit is called. * * Any blocking processEvents() loop should check this as a return condition, * since the application can not quit until control is returned to the event loop. */ bool Nexus::isRunning() { return running; } /** * @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 Unload the current profile, if any, and replaces it. * @param profile Profile to set. */ void Nexus::setProfile(Profile* profile) { getInstance().profile = profile; if (profile) Settings::getInstance().loadPersonal(profile); } /** * @brief Get desktop GUI widget. * @return nullptr if not started, desktop widget otherwise. */ Widget* Nexus::getDesktopGUI() { return getInstance().widget; } QString Nexus::getSupportedImageFilter() { QString res; for (auto type : QImageReader::supportedImageFormats()) res += QString("*.%1 ").arg(QString(type)); return tr("Images (%1)", "filetype filter").arg(res.left(res.size() - 1)); } /** * @brief Dangerous way to find out if a path is writable. * @param filepath Path to file which should be deleted. * @return True, if file writeable, false otherwise. */ bool Nexus::tryRemoveFile(const QString& filepath) { QFile tmp(filepath); bool writable = tmp.open(QIODevice::WriteOnly); tmp.remove(); return writable; } /** * @brief Calls showLogin asynchronously, so we can safely logout from within the main GUI */ void Nexus::showLoginLater() { GUI::setEnabled(false); QMetaObject::invokeMethod(&getInstance(), "showLogin", Qt::QueuedConnection); } void Nexus::onLastWindowClosed() { if (quitOnLastWindowClosed) quit(); } #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, SIGNAL(triggered()), windowMapper, SLOT(map())); windowMapper->setMapping(action, 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