Files
pilotclient/src/blackgui/guiapplication.cpp
Roland Winklmeier b78b40bbc0 Make widget style configurable with Fusion as default
Up to now the UI appearance on all platforms was aligned as much as
possible with stylesheets. Since the base widget styles still were
platform dependent defaults, there were many small differences in
details and controls. Some of them were even broken. Instead of
trying to tweak all platform specific styles, default to one on all
platforms. This guarantees that the UI is truly cross platform and
all styles and fixes cover all platforms at the same time.
For users who want to change the default style, they have now a gui
setting. But it is strongly recommended to stick with the default.

refs #683
2016-08-26 21:05:16 +01:00

558 lines
18 KiB
C++

/* 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/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/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 <QAction>
#include <QCloseEvent>
#include <QApplication>
#include <QCommandLineParser>
#include <QApplication>
#include <QDesktopServices>
#include <QDir>
#include <QEventLoop>
#include <QGuiApplication>
#include <QIcon>
#include <QKeySequence>
#include <QMenu>
#include <QMessageBox>
#include <QRegExp>
#include <QSplashScreen>
#include <QStyleFactory>
#include <QStringList>
#include <QStyle>
#include <QUrl>
#include <QWidget>
#include <QMainWindow>
#include <QtGlobal>
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<CGuiApplication *>(CApplication::instance());
}
const BlackMisc::CLogCategoryList &CGuiApplication::getLogCategories()
{
static const CLogCategoryList l(CApplication::getLogCategories().join({ CLogCategory::guiComponent() }));
return l;
}
CGuiApplication::CGuiApplication(const QString &applicationName, SwiftApplication application, const QPixmap &icon) :
CApplication(applicationName, application, false)
{
if (!sGui)
{
CGuiApplication::registerMetadata();
CApplication::init(false); // base class without metadata
this->setWindowIcon(icon);
sGui = this;
connect(&this->m_styleSheetUtility, &CStyleSheetUtility::styleSheetsChanged, this, &CGuiApplication::styleSheetsChanged);
reloadWidgetStyleFromSettings();
}
}
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<IMainWindowAccess *>(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 this->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 += "<table>\n";
}
if (!tableMode)
{
html += l;
html += "<br>";
}
else
{
// in table mode
if (lt.startsWith("-"))
{
if (pendingTr)
{
html += "</td></tr>\n";
}
html += "<tr><td>";
static const QRegExp reg("[ ]{2,}");
html += lt.replace(reg, "</td><td>");
pendingTr = true;
}
else
{
html += " ";
html += l.simplified();
}
}
}
html += "</table>\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(),
"<html><head/><body><h2>" + errorMessage + "</h2>" + helpText + "</body></html>");
}
else
{
CApplication::cmdLineErrorMessage(errorMessage);
}
}
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("file:///" + 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("file:///" + 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");
}
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; }
QAction *a = menu.addAction(w->style()->standardIcon(QStyle::SP_TitleBarMaxButton), "Fullscreen");
bool c = connect(a, &QAction::triggered, this, [a, w]()
{
w->showFullScreen();
});
Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed");
a = menu.addAction(w->style()->standardIcon(QStyle::SP_TitleBarMinButton), "Minimize");
c = connect(a, &QAction::triggered, this, [a, w]()
{
w->showMinimized();
});
Q_ASSERT_X(c, Q_FUNC_INFO, "Connect failed");
a = menu.addAction(w->style()->standardIcon(QStyle::SP_TitleBarNormalButton), "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;
}
bool CGuiApplication::reloadStyleSheets()
{
return m_styleSheetUtility.read();
}
bool CGuiApplication::updateFonts(const QString &fontFamily, const QString &fontSize, const QString &fontStyle, const QString &fontWeight, const QString &fontColor)
{
return m_styleSheetUtility.updateFonts(fontFamily, fontSize, fontStyle, fontWeight, fontColor);
}
QDialog::DialogCode CGuiApplication::showCloseDialog(QMainWindow *mainWindow, QCloseEvent *closeEvent)
{
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<QDialog::DialogCode>(this->m_closeDialog->exec());
if (c == QDialog::Rejected)
{
if (closeEvent) { closeEvent->ignore(); }
}
return c;
}
void CGuiApplication::cmdLineHelpMessage()
{
if (CBuildConfig::isRunningOnWindowsNtPlatform())
{
QMessageBox::information(nullptr, QGuiApplication::applicationDisplayName(),
"<html><head/><body><pre>" + this->m_parser.helpText() + "</pre></body></html>");
}
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::reloadWidgetStyleFromSettings()
{
auto widgetStyle = m_settingsWidgetStyle.get();
auto availableStyles = QStyleFactory::keys();
if (availableStyles.contains(widgetStyle))
{
QApplication::setStyle(QStyleFactory::create(widgetStyle));
}
}
} // ns