/* Copyright (C) 2016 * swift project Community / Contributors * * This file is part of swift Project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, * including this file, may be copied, modified, propagated, or distributed except according to the terms * contained in the LICENSE file. */ #include "blackconfig/buildconfig.h" #include "blackcore/context/contextnetwork.h" #include "blackcore/data/globalsetup.h" #include "blackcore/data/updateinfo.h" #include "blackgui/components/applicationclosedialog.h" #include "blackgui/guiapplication.h" #include "blackgui/guiutility.h" #include "blackgui/registermetadata.h" #include "blackmisc/directoryutils.h" #include "blackmisc/datacache.h" #include "blackmisc/logcategory.h" #include "blackmisc/logcategorylist.h" #include "blackmisc/logmessage.h" #include "blackmisc/metadatautils.h" #include "blackmisc/registermetadata.h" #include "blackmisc/settingscache.h" #include "blackmisc/verify.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace BlackConfig; using namespace BlackMisc; using namespace BlackMisc::Network; using namespace BlackGui::Components; using namespace BlackCore::Data; BlackGui::CGuiApplication *sGui = nullptr; // set by constructor namespace BlackGui { CGuiApplication *CGuiApplication::instance() { return qobject_cast(CApplication::instance()); } const BlackMisc::CLogCategoryList &CGuiApplication::getLogCategories() { static const CLogCategoryList l(CApplication::getLogCategories().join({ CLogCategory::guiComponent() })); return l; } CGuiApplication::CGuiApplication(const QString &applicationName, CApplicationInfo::Application application, const QPixmap &icon) : CApplication(applicationName, application, false) { if (!sGui) { CGuiApplication::registerMetadata(); CApplication::init(false); // base class without metadata this->setWindowIcon(icon); this->settingsChanged(); sGui = this; connect(&this->m_styleSheetUtility, &CStyleSheetUtility::styleSheetsChanged, this, &CGuiApplication::styleSheetsChanged); } } CGuiApplication::~CGuiApplication() { sGui = nullptr; } void CGuiApplication::registerMetadata() { CApplication::registerMetadata(); BlackGui::registerMetadata(); } void CGuiApplication::addWindowModeOption() { this->m_cmdWindowMode = QCommandLineOption(QStringList() << "w" << "window", QCoreApplication::translate("main", "Windows: (n)ormal, (f)rameless, (t)ool."), "windowtype"); this->addParserOption(this->m_cmdWindowMode); } void CGuiApplication::addWindowStateOption() { this->m_cmdWindowStateMinimized = QCommandLineOption({{"m", "minimized"}, QCoreApplication::translate("main", "Start minimized in system tray.")}); this->addParserOption(this->m_cmdWindowStateMinimized); } Qt::WindowState CGuiApplication::getWindowState() const { if (this->m_cmdWindowStateMinimized.valueName() == "empty") { return Qt::WindowNoState; } if (this->m_parser.isSet(this->m_cmdWindowStateMinimized)) { return Qt::WindowMinimized; } return Qt::WindowNoState; } CEnableForFramelessWindow::WindowMode CGuiApplication::getWindowMode() const { if (this->isParserOptionSet(m_cmdWindowMode)) { const QString v(this->getParserValue(this->m_cmdWindowMode)); return CEnableForFramelessWindow::stringToWindowMode(v); } else { return CEnableForFramelessWindow::WindowNormal; } } void CGuiApplication::splashScreen(const QString &resource) { if (this->m_splashScreen) { // delete old one this->m_splashScreen.reset(); } if (!resource.isEmpty()) { const QPixmap pm(resource); this->splashScreen(pm); } } void CGuiApplication::splashScreen(const QPixmap &pixmap) { if (this->m_splashScreen) { // delete old one this->m_splashScreen.reset(); } this->m_splashScreen.reset(new QSplashScreen(pixmap.scaled(256, 256))); this->m_splashScreen->show(); this->processEventsToRefreshGui(); } void CGuiApplication::processEventsToRefreshGui() const { QCoreApplication::processEvents(QEventLoop::AllEvents, 100); } QWidget *CGuiApplication::mainApplicationWindow() { return CGuiUtility::mainApplicationWindow(); } IMainWindowAccess *CGuiApplication::mainWindowAccess() { IMainWindowAccess *m = qobject_cast(mainApplicationWindow()); return m; } void CGuiApplication::initMainApplicationWindow(QWidget *mainWindow) { if (!mainWindow) { return; } if (this->m_uiSetupCompleted) { return; } this->m_uiSetupCompleted = true; const QString name(this->getApplicationNameVersionBetaDev()); mainWindow->setObjectName(QCoreApplication::applicationName()); mainWindow->setWindowTitle(name); mainWindow->setWindowIcon(m_windowIcon); mainWindow->setWindowIconText(name); emit uiObjectTreeReady(); } void CGuiApplication::setWindowIcon(const QPixmap &icon) { instance()->m_windowIcon = icon; QApplication::setWindowIcon(icon); } void CGuiApplication::exit(int retcode) { CApplication::exit(retcode); } void CGuiApplication::highDpiScreenSupport() { qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1"); } void CGuiApplication::ps_startupCompleted() { CApplication::ps_startupCompleted(); if (this->m_splashScreen) { this->m_splashScreen->close(); this->m_splashScreen.reset(); } } QString CGuiApplication::beautifyHelpMessage(const QString &helpText) { // just formatting Qt help message into HTML table if (helpText.isEmpty()) { return ""; } const QStringList lines(helpText.split('\n')); QString html; bool tableMode = false; bool pendingTr = false; for (const QString &l : lines) { QString lt(l.trimmed()); if (!tableMode && lt.startsWith("-")) { tableMode = true; html += "\n"; } if (!tableMode) { html += l.toHtmlEscaped(); html += "
"; } else { // in table mode if (lt.startsWith("-")) { if (pendingTr) { html += "\n"; } html += "
"; static const QRegExp reg("[ ]{2,}"); html += lt.replace(reg, ""); pendingTr = true; } else { html += " "; html += l.simplified().toHtmlEscaped(); } } } html += "
\n"; return html; } void CGuiApplication::cmdLineErrorMessage(const QString &errorMessage) const { if (CBuildConfig::isRunningOnWindowsNtPlatform()) { const QString helpText(beautifyHelpMessage(this->m_parser.helpText())); QMessageBox::warning(nullptr, QGuiApplication::applicationDisplayName(), "

" + errorMessage + "


" + helpText + ""); } else { CApplication::cmdLineErrorMessage(errorMessage); } } void CGuiApplication::cmdLineErrorMessage(const CStatusMessageList &msgs) const { if (msgs.isEmpty()) { return; } if (!msgs.hasErrorMessages()) { return; } if (CBuildConfig::isRunningOnWindowsNtPlatform()) { static const CPropertyIndexList propertiesSingle({ CStatusMessage::IndexMessage }); static const CPropertyIndexList propertiesMulti({ CStatusMessage::IndexSeverityAsString, CStatusMessage::IndexMessage }); const QString helpText(CGuiApplication::beautifyHelpMessage(this->m_parser.helpText())); const QString msgsHtml = msgs.toHtml(msgs.size() > 1 ? propertiesMulti : propertiesSingle); QMessageBox::critical(nullptr, QGuiApplication::applicationDisplayName(), "" + msgsHtml + "

" + helpText + ""); } else { CApplication::cmdLineErrorMessage(msgs); } } bool CGuiApplication::displayInStatusBar(const CStatusMessage &message) { IMainWindowAccess *m = mainWindowAccess(); BLACK_VERIFY_X(m, Q_FUNC_INFO, "No access interface"); if (!m) { return false; } return m->displayInStatusBar(message); } bool CGuiApplication::displayInOverlayWindow(const CStatusMessage &message) { IMainWindowAccess *m = mainWindowAccess(); BLACK_VERIFY_X(m, Q_FUNC_INFO, "No access interface"); if (!m) { return false; } return m->displayInOverlayWindow(message); } bool CGuiApplication::displayTextInConsole(const QString &text) { IMainWindowAccess *m = mainWindowAccess(); BLACK_VERIFY_X(m, Q_FUNC_INFO, "No access interface"); if (!m) { return false; } return m->displayTextInConsole(text); } void CGuiApplication::addMenuForSettingsAndCache(QMenu &menu) { QMenu *sm = menu.addMenu(CIcons::appSettings16(), "Settings"); sm->setIcon(CIcons::appSettings16()); QAction *a = sm->addAction(CIcons::disk16(), "Settings directory"); bool c = connect(a, &QAction::triggered, this, [a, this]() { const QString path(QDir::toNativeSeparators(CSettingsCache::persistentStore())); if (QDir(path).exists()) { QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = sm->addAction("Reset settings"); c = connect(a, &QAction::triggered, this, [this]() { CSettingsCache::instance()->clearAllValues(); this->displayTextInConsole("Cleared all settings!"); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = sm->addAction("List settings files"); c = connect(a, &QAction::triggered, this, [this]() { const QStringList files(CSettingsCache::instance()->enumerateStore()); this->displayTextInConsole(files.join("\n")); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); sm = menu.addMenu("Cache"); sm->setIcon(CIcons::appSettings16()); a = sm->addAction(CIcons::disk16(), "Cache directory"); c = connect(a, &QAction::triggered, this, [this]() { const QString path(QDir::toNativeSeparators(CDataCache::persistentStore())); if (QDir(path).exists()) { QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = sm->addAction("Reset cache"); c = connect(a, &QAction::triggered, this, [this]() { const QStringList files = CApplication::clearCaches(); this->displayTextInConsole("Cleared caches! " + QString::number(files.size()) + " files"); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = sm->addAction("List cache files"); c = connect(a, &QAction::triggered, this, [this]() { const QStringList files(CDataCache::instance()->enumerateStore()); this->displayTextInConsole(files.join("\n")); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = menu.addAction(CIcons::disk16(), "Log directory"); c = connect(a, &QAction::triggered, this, [this]() { const QString path(QDir::toNativeSeparators(CDirectoryUtils::getLogDirectory())); if (QDir(path).exists()) { QDesktopServices::openUrl(QUrl("file:///" + path)); } }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); } void CGuiApplication::addMenuForStyleSheets(QMenu &menu) { QMenu *sm = menu.addMenu("Style sheet"); QAction *a = sm->addAction(CIcons::refresh16(), "Reload"); bool c = connect(a, &QAction::triggered, this, [a, this]() { this->reloadStyleSheets(); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); } void CGuiApplication::addMenuFile(QMenu &menu) { addMenuForSettingsAndCache(menu); addMenuForStyleSheets(menu); menu.addSeparator(); QAction *a = menu.addAction("E&xit"); a->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q)); bool c = connect(a, &QAction::triggered, this, [a, this]() { // a close event might already trigger a shutdown this->mainApplicationWindow()->close(); this->gracefulShutdown(); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); } void CGuiApplication::addMenuInternals(QMenu &menu) { QMenu *sm = menu.addMenu("Templates"); QAction *a = sm->addAction("JSON bootstrap"); bool c = connect(a, &QAction::triggered, this, [a, this]() { this->displayTextInConsole(this->getGlobalSetup().toJsonString()); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = sm->addAction("JSON update info"); c = connect(a, &QAction::triggered, this, [a, this]() { this->displayTextInConsole(this->getUpdateInfo().toJsonString()); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = menu.addAction("Metadata (slow)"); c = connect(a, &QAction::triggered, this, [a, this]() { this->displayTextInConsole(getAllUserMetatypesTypes()); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = menu.addAction("Setup"); c = connect(a, &QAction::triggered, this, [a, this]() { this->displayTextInConsole(this->getGlobalSetup().convertToQString("\n", true)); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = menu.addAction("Compile info"); c = connect(a, &QAction::triggered, this, [a, this]() { this->displayTextInConsole(this->getInfoString("\n")); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); } void CGuiApplication::addMenuWindow(QMenu &menu) { QWidget *w = mainApplicationWindow(); if (!w) { return; } const QSize iconSize = CIcons::empty16().size(); QPixmap icon = w->style()->standardIcon(QStyle::SP_TitleBarMaxButton).pixmap(iconSize); QAction *a = menu.addAction(icon.scaled(iconSize), "Fullscreen"); bool c = connect(a, &QAction::triggered, this, [a, w]() { w->showFullScreen(); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); icon = w->style()->standardIcon(QStyle::SP_TitleBarMinButton).pixmap(iconSize); a = menu.addAction(icon.scaled(iconSize), "Minimize"); c = connect(a, &QAction::triggered, this, [a, w]() { w->showMinimized(); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); icon = w->style()->standardIcon(QStyle::SP_TitleBarNormalButton).pixmap(iconSize); a = menu.addAction(icon.scaled(iconSize), "Normal"); c = connect(a, &QAction::triggered, this, [a, w]() { w->showNormal(); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); a = menu.addAction("Toggle stay on top"); c = connect(a, &QAction::triggered, this, [a, w]() { if (CGuiUtility::toggleStayOnTop(w)) { CLogMessage(w).info("Window on top"); } else { CLogMessage(w).info("Window not always on top"); } }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); } void CGuiApplication::addMenuHelp(QMenu &menu) { QWidget *w = mainApplicationWindow(); if (!w) { return; } const CGlobalSetup gs = this->getGlobalSetup(); const CUrl helpPage = gs.getHelpPageUrl(); QAction *a = menu.addAction(w->style()->standardIcon(QStyle::SP_TitleBarContextHelpButton), "Online help"); bool c = connect(a, &QAction::triggered, this, [helpPage]() { QDesktopServices::openUrl(helpPage); }); Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed"); } const CStyleSheetUtility &CGuiApplication::getStyleSheetUtility() const { return this->m_styleSheetUtility; } QString CGuiApplication::getWidgetStyle() const { QString currentWidgetStyle(QApplication::style()->metaObject()->className()); if (currentWidgetStyle.startsWith('Q')) { currentWidgetStyle.remove(0, 1); } return currentWidgetStyle.replace("Style", ""); } bool CGuiApplication::reloadStyleSheets() { return m_styleSheetUtility.read(); } bool CGuiApplication::updateFont(const QString &fontFamily, const QString &fontSize, const QString &fontStyle, const QString &fontWeight, const QString &fontColor) { return m_styleSheetUtility.updateFont(fontFamily, fontSize, fontStyle, fontWeight, fontColor); } bool CGuiApplication::resetFont() { return m_styleSheetUtility.resetFont(); } QDialog::DialogCode CGuiApplication::showCloseDialog(QMainWindow *mainWindow, QCloseEvent *closeEvent) { const bool needsDialog = this->hasUnsavedSettings(); if (!needsDialog) { return QDialog::Accepted; } if (!this->m_closeDialog) { this->m_closeDialog = new CApplicationCloseDialog(mainWindow); if (mainWindow && !mainWindow->windowTitle().isEmpty()) { this->setSettingsAutoSave(false); // will be handled by dialog this->m_closeDialog->setWindowTitle(mainWindow->windowTitle()); this->m_closeDialog->setModal(true); } } const QDialog::DialogCode c = static_cast(this->m_closeDialog->exec()); if (c == QDialog::Rejected) { if (closeEvent) { closeEvent->ignore(); } } return c; } void CGuiApplication::cmdLineHelpMessage() { if (CBuildConfig::isRunningOnWindowsNtPlatform()) { const QString helpText(CGuiApplication::beautifyHelpMessage(this->m_parser.helpText())); QMessageBox::information(nullptr, QGuiApplication::applicationDisplayName(), "" + helpText + ""); } else { CApplication::cmdLineHelpMessage(); } } void CGuiApplication::cmdLineVersionMessage() const { if (CBuildConfig::isRunningOnWindowsNtPlatform()) { QMessageBox::information(nullptr, QGuiApplication::applicationDisplayName(), QGuiApplication::applicationDisplayName() + ' ' + QCoreApplication::applicationVersion()); } else { CApplication::cmdLineVersionMessage(); } } bool CGuiApplication::parsingHookIn() { return true; } void CGuiApplication::settingsChanged() { // changing widget style is slow, so I try to prevent setting it when nothing changed const QString widgetStyle = m_guiSettings.get().getWidgetStyle(); const QString currentWidgetStyle(this->getWidgetStyle()); if (!(currentWidgetStyle.length() == widgetStyle.length() && currentWidgetStyle.startsWith(widgetStyle, Qt::CaseInsensitive))) { const auto availableStyles = QStyleFactory::keys(); if (availableStyles.contains(widgetStyle)) { // changing style freezes the application, so it must not be done in flight mode if (this->getIContextNetwork() && this->getIContextNetwork()->isConnected()) { CLogMessage(this).validationError("Cannot change style while connected to network"); } else { QApplication::setStyle(QStyleFactory::create(widgetStyle)); } } } } } // ns