diff --git a/littlenavconnect.pro b/littlenavconnect.pro index 1aa3342..5f3911c 100644 --- a/littlenavconnect.pro +++ b/littlenavconnect.pro @@ -47,7 +47,7 @@ # ============================================================================= # Define program version here VERSION_NUMBER_TODO -VERSION_NUMBER=2.8.6 +VERSION_NUMBER=2.8.7 QT += core gui xml network svg @@ -191,7 +191,6 @@ message(-----------------------------------) # Files SOURCES +=\ - src/constants.cpp \ src/main.cpp \ src/mainwindow.cpp \ src/optionsdialog.cpp diff --git a/mainwindow.ui b/mainwindow.ui index ac01465..1621e6a 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -13,6 +13,10 @@ Little Navconnect + + + :/littlenavconnect/resources/icons/navconnect.svg:/littlenavconnect/resources/icons/navconnect.svg + @@ -41,6 +45,9 @@ true + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + @@ -51,7 +58,7 @@ 0 0 640 - 21 + 22 @@ -64,8 +71,9 @@ &Help - - + + + @@ -75,10 +83,20 @@ &Tools + + + + &Window + + + + + + @@ -98,7 +116,7 @@ false - + @@ -130,13 +148,13 @@ About &Qt - + :/littlenavconnect/resources/icons/help.svg:/littlenavconnect/resources/icons/help.svg - &Contents (Online) + &User Manual (Online) @@ -177,13 +195,13 @@ Stop Saving or Loading of Replay - + :/littlenavconnect/resources/icons/help.svg:/littlenavconnect/resources/icons/help.svg - &Contents (Offline, PDF) + &User Manual (Offline, PDF) @@ -220,6 +238,31 @@ Ctrl+Shift+X + + + true + + + true + + + &Minimize to System Tray + + + + + true + + + false + + + &Start minimized to System Tray + + + Startup minimized to the System Tray + + diff --git a/src/constants.cpp b/src/constants.cpp deleted file mode 100644 index a3635b4..0000000 --- a/src/constants.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/***************************************************************************** -* Copyright 2015-2020 Alexander Barthel alex@littlenavmap.org -* -* This program is free 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. -* -* This program 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 this program. If not, see . -*****************************************************************************/ - -#include "constants.h" diff --git a/src/constants.h b/src/constants.h index a8640cf..461c75a 100644 --- a/src/constants.h +++ b/src/constants.h @@ -30,6 +30,7 @@ const QLatin1String SETTINGS_OPTIONS_FETCH_AI_SHIP("Options/FetchAiShip"); const QLatin1String SETTINGS_OPTIONS_FETCH_AI_RADIUS("Options/FetchAiRadius"); const QLatin1String SETTINGS_ACTIONS_SHOW_PORT_CHANGE("Actions/ShowPortChange"); const QLatin1String SETTINGS_ACTIONS_SHOW_QUIT("Actions/ShowQuit"); +const QLatin1String SETTINGS_ACTIONS_SHOW_TRAY_HINT("Actions/ShowTrayHint"); const QLatin1String SETTINGS_OPTIONS_VERBOSE("Options/Verbose"); const QLatin1String SETTINGS_MAINWINDOW_WIDGET("MainWindow/Widget"); const QLatin1String SETTINGS_OPTIONS_RECONNECT_RATE("Options/ReconnectRate2"); diff --git a/src/main.cpp b/src/main.cpp index 54db05f..582f1d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* Copyright 2015-2020 Alexander Barthel alex@littlenavmap.org +* Copyright 2015-2023 Alexander Barthel alex@littlenavmap.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,16 +27,13 @@ #include "atools.h" #include "geo/calculations.h" #include "fs/fspaths.h" +#include "exception.h" #include #include - -#if defined(Q_OS_WIN32) #include #include -#endif - using atools::gui::Application; using atools::logging::LoggingHandler; using atools::logging::LoggingUtil; @@ -53,70 +50,88 @@ int main(int argc, char *argv[]) atools::geo::registerMetaTypes(); atools::fs::FsPaths::intitialize(); - using atools::gui::Application; - Application app(argc, argv); - Application::setWindowIcon(QIcon(":/littlenavconnect/resources/icons/navconnect.svg")); - Application::setApplicationName("Little Navconnect"); - Application::setOrganizationName("ABarthel"); - Application::setOrganizationDomain("littlenavmap.org"); - - Application::setApplicationVersion(VERSION_NUMBER_LITTLENAVCONNECT); - Application::setEmailAddresses({"alex@littlenavmap.org"}); - - // Initialize logging and force logfiles into the system or user temp directory - LoggingHandler::initializeForTemp(atools::settings::Settings::getOverloadedPath(":/littlenavconnect/resources/config/logging.cfg")); + int retval = 0; - Application::addReportPath(QObject::tr("Log files:"), LoggingHandler::getLogFiles()); - - Application::addReportPath(QObject::tr("Configuration:"), {Settings::getFilename()}); - - // Disable tooltip effects since these do not work well with tooltip updates while displaying - QApplication::setEffectEnabled(Qt::UI_FadeTooltip, false); - QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, false); + try + { + using atools::gui::Application; + Application app(argc, argv); + Application::setWindowIcon(QIcon(":/littlenavconnect/resources/icons/navconnect.svg")); + Application::setApplicationName("Little Navconnect"); + Application::setOrganizationName("ABarthel"); + Application::setOrganizationDomain("littlenavmap.org"); -#if QT_VERSION > QT_VERSION_CHECK(5, 10, 0) - QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); -#endif + Application::setApplicationVersion(VERSION_NUMBER_LITTLENAVCONNECT); + Application::setEmailAddresses({"alex@littlenavmap.org"}); - // Print some information which can be useful for debugging - LoggingUtil::logSystemInformation(); - qInfo().noquote().nospace() << "atools revision " << atools::gitRevision() << " " - << Application::applicationName() << " revision " << GIT_REVISION_LITTLENAVCONNECT; + // Initialize logging and force logfiles into the system or user temp directory + LoggingHandler::initializeForTemp(atools::settings::Settings::getOverloadedPath(":/littlenavconnect/resources/config/logging.cfg")); - LoggingUtil::logStandardPaths(); - qInfo() << "SSL supported" << QSslSocket::supportsSsl() - << "build library" << QSslSocket::sslLibraryBuildVersionString() - << "library" << QSslSocket::sslLibraryVersionString(); + Application::addReportPath(QObject::tr("Log files:"), LoggingHandler::getLogFiles()); - qInfo() << "Available styles" << QStyleFactory::keys(); + Application::addReportPath(QObject::tr("Configuration:"), {Settings::getFilename()}); - Settings::logSettingsInformation(); + // Disable tooltip effects since these do not work well with tooltip updates while displaying + QApplication::setEffectEnabled(Qt::UI_FadeTooltip, false); + QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, false); - // Load simulator paths ================================= - atools::fs::FsPaths::loadAllPaths(); - atools::fs::FsPaths::logAllPaths(); + // Avoid closing if options dialog is closed with main window hidden to tray + QApplication::setQuitOnLastWindowClosed(false); - // Load local and Qt system translations from various places - Translator::load(Settings::instance().valueStr(lnc::SETTINGS_OPTIONS_LANGUAGE, QString())); +#if QT_VERSION > QT_VERSION_CHECK(5, 10, 0) + QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); +#endif -#if defined(Q_OS_WIN32) - // Detect other running application instance - this is unsafe on Unix since shm can remain after crashes - QSharedMemory shared("ed1b2f62-a6b3-8c64-09b4-e4daa232ecf4"); // generated GUID - if(!shared.create(512, QSharedMemory::ReadWrite)) + // Print some information which can be useful for debugging + LoggingUtil::logSystemInformation(); + qInfo().noquote().nospace() << "atools revision " << atools::gitRevision() << " " + << Application::applicationName() << " revision " << GIT_REVISION_LITTLENAVCONNECT; + + LoggingUtil::logStandardPaths(); + qInfo() << "SSL supported" << QSslSocket::supportsSsl() + << "build library" << QSslSocket::sslLibraryBuildVersionString() + << "library" << QSslSocket::sslLibraryVersionString(); + + qInfo() << "Available styles" << QStyleFactory::keys(); + + Settings::logSettingsInformation(); + + // Load simulator paths ================================= + atools::fs::FsPaths::loadAllPaths(); + atools::fs::FsPaths::logAllPaths(); + + // Load local and Qt system translations from various places + Translator::load(Settings::instance().valueStr(lnc::SETTINGS_OPTIONS_LANGUAGE, QString())); + + // Detect other running application instance - this is unsafe on Unix since shared memory can remain after crashes + QSharedMemory shared("ed1b2f62-a6b3-8c64-09b4-e4daa232ecf4"); // generated GUID + if(!shared.create(64, QSharedMemory::ReadOnly)) + { + shared.detach(); + QMessageBox::critical(nullptr, QObject::tr("%1 - Error").arg(QCoreApplication::applicationName()), + QObject::tr("%1 is already running.").arg(QCoreApplication::applicationName())); + return 1; + } + + // Put in separate block to destroy main window before shutting down logging + MainWindow mainWindow; + mainWindow.showInitial(); + retval = QCoreApplication::exec(); + } + catch(atools::Exception& e) { - QMessageBox::critical(nullptr, QObject::tr("%1 - Error").arg(QCoreApplication::applicationName()), - QObject::tr("%1 is already running.").arg(QCoreApplication::applicationName())); - return 1; + ATOOLS_HANDLE_EXCEPTION(e); + // Does not return in case of fatal error + } + catch(...) + { + ATOOLS_HANDLE_UNKNOWN_EXCEPTION; + // Does not return in case of fatal error } -#endif - - MainWindow mainWindow; - - mainWindow.show(); - - int retval = app.exec(); qDebug() << "app.exec() done, retval is" << retval << (retval == 0 ? "(ok)" : "(error)"); + qInfo() << "About to shut down logging"; + atools::logging::LoggingHandler::shutdown(); return retval; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bfde76c..3565ead 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* Copyright 2015-2020 Alexander Barthel alex@littlenavmap.org +* Copyright 2015-2023 Alexander Barthel alex@littlenavmap.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,27 +22,28 @@ #include "fs/ns/navservercommon.h" #include "optionsdialog.h" -#include "settings/settings.h" -#include "gui/dialog.h" +#include "constants.h" +#include "fs/sc/datareaderthread.h" +#include "fs/sc/simconnecthandler.h" +#include "fs/sc/simconnectreply.h" +#include "fs/sc/xpconnecthandler.h" #include "geo/calculations.h" +#include "gui/dialog.h" #include "gui/helphandler.h" #include "gui/widgetstate.h" -#include "logging/logginghandler.h" #include "logging/loggingguiabort.h" -#include "fs/sc/simconnectreply.h" -#include "fs/sc/datareaderthread.h" -#include "constants.h" -#include "fs/sc/simconnecthandler.h" -#include "fs/sc/xpconnecthandler.h" +#include "logging/logginghandler.h" +#include "settings/settings.h" #include "util/htmlbuilder.h" #include "util/version.h" -#include #include #include -#include #include +#include #include +#include +#include using atools::settings::Settings; using atools::fs::sc::SimConnectData; @@ -76,8 +77,9 @@ MainWindow::MainWindow() atools::logging::LoggingGuiAbortHandler::setGuiAbortFunction(this); ui->setupUi(this); + ui->textEdit->clear(); - readSettings(); + restoreState(); // Update window title =================================================== // Remember original title @@ -120,33 +122,24 @@ MainWindow::MainWindow() parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption saveReplayOpt({"s", "save-replay"}, - QObject::tr("Save replay data to ."), - QObject::tr("file")); + QCommandLineOption saveReplayOpt({"s", "save-replay"}, QObject::tr("Save replay data to ."), QObject::tr("file")); parser.addOption(saveReplayOpt); - QCommandLineOption loadReplayOpt({"l", "load-replay"}, - QObject::tr("Load replay data from ."), - QObject::tr("file")); + QCommandLineOption loadReplayOpt({"l", "load-replay"}, QObject::tr("Load replay data from ."), QObject::tr("file")); parser.addOption(loadReplayOpt); - QCommandLineOption replaySpeedOpt({"r", "replay-speed"}, - QObject::tr("Use speed factor for replay."), - QObject::tr("speed")); + QCommandLineOption replaySpeedOpt({"r", "replay-speed"}, QObject::tr("Use speed factor for replay."), QObject::tr("speed")); parser.addOption(replaySpeedOpt); - QCommandLineOption replayWhazzupOpt({"w", "write-whazzup"}, - QObject::tr("Update whazzup file using VATSIM format during replay."), + QCommandLineOption replayWhazzupOpt({"w", "write-whazzup"}, QObject::tr("Update whazzup file using VATSIM format during replay."), QObject::tr("file")); parser.addOption(replayWhazzupOpt); - QCommandLineOption replayWhazzupUpdateOpt({"z", "write-whazzup-speed"}, - QObject::tr("Update whazzup file every during replay."), + QCommandLineOption replayWhazzupUpdateOpt({"z", "write-whazzup-speed"}, QObject::tr("Update whazzup file every during replay."), QObject::tr("seconds")); parser.addOption(replayWhazzupUpdateOpt); - QCommandLineOption showReplay({"g", "replay-gui"}, - QObject::tr("Show replay menu items.")); + QCommandLineOption showReplay({"g", "replay-gui"}, QObject::tr("Show replay menu items.")); parser.addOption(showReplay); // Process the actual command line arguments given by the user @@ -157,6 +150,7 @@ MainWindow::MainWindow() replayWhazzupUpdateSpeed = parser.value(replayWhazzupUpdateOpt).toInt(); writeReplayWhazzupFile = parser.value(replayWhazzupOpt); + // Add replay menu items if requested if(parser.isSet(showReplay)) { ui->menuTools->insertActions(ui->actionResetMessages, {ui->actionReplayFileLoad, ui->actionReplayFileSave, ui->actionReplayStop}); @@ -166,7 +160,7 @@ MainWindow::MainWindow() // Right align the help button QWidget *spacerWidget = new QWidget(ui->toolBar); spacerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - ui->toolBar->insertWidget(ui->actionContents, spacerWidget); + ui->toolBar->insertWidget(ui->actionHelp, spacerWidget); // Bind the log function to this class for category "gui" using namespace std::placeholders; @@ -176,14 +170,12 @@ MainWindow::MainWindow() helpHandler = new atools::gui::HelpHandler(this, aboutMessage, GIT_REVISION_LITTLENAVCONNECT); int defaultPort = Settings::instance().getAndStoreValue(lnc::SETTINGS_OPTIONS_DEFAULT_PORT, 51968).toInt(); - bool hideHostname = - Settings::instance().getAndStoreValue(lnc::SETTINGS_OPTIONS_HIDE_HOSTNAME, false).toBool(); + bool hideHostname = Settings::instance().getAndStoreValue(lnc::SETTINGS_OPTIONS_HIDE_HOSTNAME, false).toBool(); + // Create nav server but to not start it yet atools::fs::ns::NavServerOptions options = atools::fs::ns::NONE; - if(verbose) - options |= atools::fs::ns::VERBOSE; - if(hideHostname) - options |= atools::fs::ns::HIDE_HOST; + options.setFlag(atools::fs::ns::VERBOSE, verbose); + options.setFlag(atools::fs::ns::HIDE_HOST, hideHostname); navServer = new atools::fs::ns::NavServer(this, options, defaultPort); @@ -205,69 +197,77 @@ MainWindow::MainWindow() connect(ui->actionConnectFsx, &QAction::triggered, this, &MainWindow::simulatorSelectionTriggered); connect(ui->actionConnectXplane, &QAction::triggered, this, &MainWindow::simulatorSelectionTriggered); - - connect(ui->actionQuit, &QAction::triggered, this, &QMainWindow::close); + connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::closeFromTrayOrAction); if(parser.isSet(showReplay)) { - connect(ui->actionReplayFileLoad, &QAction::triggered, this, &MainWindow::loadReplayFileTriggered); - connect(ui->actionReplayFileSave, &QAction::triggered, this, &MainWindow::saveReplayFileTriggered); - connect(ui->actionReplayStop, &QAction::triggered, this, &MainWindow::stopReplay); + connect(ui->actionReplayFileLoad, &QAction::triggered, this, &MainWindow::loadReplayFileTriggered); + connect(ui->actionReplayFileSave, &QAction::triggered, this, &MainWindow::saveReplayFileTriggered); + connect(ui->actionReplayStop, &QAction::triggered, this, &MainWindow::stopReplay); } connect(ui->actionResetMessages, &QAction::triggered, this, &MainWindow::resetMessages); connect(ui->actionOptions, &QAction::triggered, this, &MainWindow::options); - connect(ui->actionContents, &QAction::triggered, this, &MainWindow::showOnlineHelp); - connect(ui->actionContentsOffline, &QAction::triggered, this, &MainWindow::showOfflineHelp); + connect(ui->actionHelp, &QAction::triggered, this, &MainWindow::showOnlineHelp); + connect(ui->actionHelpOffline, &QAction::triggered, this, &MainWindow::showOfflineHelp); connect(ui->actionAbout, &QAction::triggered, helpHandler, &atools::gui::HelpHandler::about); connect(ui->actionAboutQt, &QAction::triggered, helpHandler, &atools::gui::HelpHandler::aboutQt); // Log messages have to be redirected through a message so that QTextEdit::append is not called on // a thread context different than main - connect(this, &MainWindow::appendLogMessage, ui->textEdit, &QTextEdit::append); + connect(this, &MainWindow::appendLogMessage, ui->textEdit, &QTextEdit::append, Qt::QueuedConnection); - // Once visible start server and log messagess - connect(this, &MainWindow::windowShown, this, &MainWindow::mainWindowShown, Qt::QueuedConnection); + if(QSystemTrayIcon::isSystemTrayAvailable()) + // Create and show tray and menus + createTrayIcon(); + else + { + ui->actionMinimizeTray->setDisabled(true); + ui->actionStartMinimizeTray->setDisabled(true); + } } MainWindow::~MainWindow() { qDebug() << Q_FUNC_INFO; - navServer->stopServer(); + qDebug() << Q_FUNC_INFO << "delete trayIcon"; + delete trayIcon; + + qDebug() << Q_FUNC_INFO << "delete trayIconMenu"; + delete trayIconMenu; + qDebug() << Q_FUNC_INFO << "navServer stopped"; + navServer->stopServer(); + qDebug() << Q_FUNC_INFO << "delete navServer"; delete navServer; - qDebug() << Q_FUNC_INFO << "navServer deleted"; + qDebug() << Q_FUNC_INFO << "dataReader terminate"; dataReader->terminateThread(); - qDebug() << Q_FUNC_INFO << "dataReader terminated"; + qDebug() << Q_FUNC_INFO << "delete dataReader"; delete dataReader; - qDebug() << Q_FUNC_INFO << "dataReader deleted"; + qDebug() << Q_FUNC_INFO << "delete fsxConnectHandler"; delete fsxConnectHandler; - qDebug() << Q_FUNC_INFO << "fsxConnectHandler deleted"; + qDebug() << Q_FUNC_INFO << "delete xpConnectHandler"; delete xpConnectHandler; - qDebug() << Q_FUNC_INFO << "xpConnectHandler deleted"; + qDebug() << Q_FUNC_INFO << "reset logging"; atools::logging::LoggingHandler::setLogFunction(nullptr); - qDebug() << Q_FUNC_INFO << "logging reset"; + qDebug() << Q_FUNC_INFO << "delete helpHandler"; delete helpHandler; - qDebug() << Q_FUNC_INFO << "help handler deleted"; + qDebug() << Q_FUNC_INFO << "delete fsActionGroup"; delete simulatorActionGroup; - qDebug() << Q_FUNC_INFO << "fsActionGroup deleted"; + qDebug() << Q_FUNC_INFO << "delete ui"; delete ui; - qDebug() << Q_FUNC_INFO << "ui deleted"; atools::logging::LoggingGuiAbortHandler::resetGuiAbortFunction(); - - qDebug() << "MainWindow destructor about to shut down logging"; - atools::logging::LoggingHandler::shutdown(); } void MainWindow::showOnlineHelp() @@ -282,13 +282,10 @@ void MainWindow::showOfflineHelp() atools::fs::sc::ConnectHandler *MainWindow::handlerForSelection() { - atools::fs::sc::ConnectHandler *handler; if(ui->actionConnectFsx->isChecked()) - handler = fsxConnectHandler; + return fsxConnectHandler; else - handler = xpConnectHandler; - - return handler; + return xpConnectHandler; } void MainWindow::handlerChanged() @@ -305,8 +302,7 @@ void MainWindow::handlerChanged() } else { - qInfo(atools::fs::ns::gui).noquote().nospace() - << tr("Connecting to X-Plane using the Little Xpconnect plugin."); + qInfo(atools::fs::ns::gui).noquote().nospace() << tr("Connecting to X-Plane using the Little Xpconnect plugin."); Settings::instance().setValue(lnc::SETTINGS_OPTIONS_SIMULATOR_FSX, false); } Settings::syncSettings(); @@ -323,16 +319,14 @@ void MainWindow::simulatorSelectionTriggered() void MainWindow::saveReplayFileTriggered() { - QString filepath = atools::gui::Dialog(this).saveFileDialog( - tr("Save Replay"), tr("Replay Files (*.replay);;All Files (*)"), "replay", "Replay/", - QString(), "littlenavconnect.replay"); + QString filepath = atools::gui::Dialog(this).saveFileDialog(tr("Save Replay"), tr("Replay Files (*.replay);;All Files (*)"), + "replay", "Replay/", QString(), "littlenavconnect.replay"); if(!filepath.isEmpty()) { dataReader->terminateThread(); dataReader->setSaveReplayFilepath(filepath); dataReader->setLoadReplayFilepath(QString()); - dataReader->start(); } } @@ -347,7 +341,6 @@ void MainWindow::loadReplayFileTriggered() dataReader->terminateThread(); dataReader->setSaveReplayFilepath(QString()); dataReader->setLoadReplayFilepath(filepath); - dataReader->start(); } } @@ -357,7 +350,6 @@ void MainWindow::stopReplay() dataReader->terminateThread(); dataReader->setSaveReplayFilepath(QString()); dataReader->setLoadReplayFilepath(QString()); - dataReader->start(); } @@ -365,23 +357,18 @@ void MainWindow::options() { qDebug(atools::fs::ns::gui).noquote().nospace() << "MainWindow::options"; - OptionsDialog dialog; + OptionsDialog dialog(this); Settings& settings = Settings::instance(); unsigned int updateRateMs = settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_UPDATE_RATE, 500).toUInt(); int port = settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_DEFAULT_PORT, 51968).toInt(); - bool hideHostname = settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_HIDE_HOSTNAME, false).toBool(); - - bool fetchAiAircraft = settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_FETCH_AI_AIRCRAFT, true).toBool(); - bool fetchAiShip = settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_FETCH_AI_SHIP, true).toBool(); - int fetchAiRadiusNm = settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_FETCH_AI_RADIUS, 105).toInt(); dialog.setUpdateRate(updateRateMs); dialog.setPort(port); - dialog.setHideHostname(hideHostname); - dialog.setFetchAiAircraft(fetchAiAircraft); - dialog.setFetchAiShip(fetchAiShip); - dialog.setFetchAiRadius(fetchAiRadiusNm); + dialog.setHideHostname(settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_HIDE_HOSTNAME, false).toBool()); + dialog.setFetchAiAircraft(settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_FETCH_AI_AIRCRAFT, true).toBool()); + dialog.setFetchAiShip(settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_FETCH_AI_SHIP, true).toBool()); + dialog.setFetchAiRadius(settings.getAndStoreValue(lnc::SETTINGS_OPTIONS_FETCH_AI_RADIUS, 105).toInt()); int result = dialog.exec(); @@ -398,10 +385,8 @@ void MainWindow::options() Settings::syncSettings(); atools::fs::sc::Options options = atools::fs::sc::NO_OPTION; - if(dialog.isFetchAiAircraft()) - options |= atools::fs::sc::FETCH_AI_AIRCRAFT; - if(dialog.isFetchAiShip()) - options |= atools::fs::sc::FETCH_AI_BOAT; + options.setFlag(atools::fs::sc::FETCH_AI_AIRCRAFT, dialog.isFetchAiAircraft()); + options.setFlag(atools::fs::sc::FETCH_AI_BOAT, dialog.isFetchAiShip()); dataReader->setSimconnectOptions(options); dataReader->setAiFetchRadius(atools::geo::nmToKm(dialog.getAiFetchRadiusNm())); @@ -422,9 +407,8 @@ void MainWindow::options() if(navServer->hasConnections()) result2 = atools::gui::Dialog(this).showQuestionMsgBox( lnc::SETTINGS_ACTIONS_SHOW_PORT_CHANGE, - tr( - "There are still applications connected.\n" - "Really change the Network Port?"), + tr("There are still applications connected.\n" + "Really change the Network Port?"), tr("Do not &show this dialog again."), QMessageBox::Yes | QMessageBox::No, QMessageBox::No, QMessageBox::Yes); @@ -452,7 +436,7 @@ void MainWindow::resetMessages() void MainWindow::logGuiMessage(QtMsgType type, const QMessageLogContext& context, const QString& message) { - const static QString MESSAGEPATTERN("[%1] %2"); + const static QString MESSAGEPATTERN("[%1] %2
"); if(type == QtFatalMsg) // Fatal is a crash anyway - bail out to avoid follow up errors @@ -465,31 +449,42 @@ void MainWindow::logGuiMessage(QtMsgType type, const QMessageLogContext& context #endif - QString msg; + QString htmlMessage; if(context.category != nullptr && QString(context.category) == "gui") { // Define colors - QString style; switch(type) { case QtDebugMsg: - msg = atools::util::HtmlBuilder::textMessage(message, atools::util::html::NO_ENTITIES, Qt::darkGray); + htmlMessage = atools::util::HtmlBuilder::textMessage(message, atools::util::html::NO_ENTITIES, Qt::darkGray); break; + case QtWarningMsg: - msg = atools::util::HtmlBuilder::warningMessage(message); + htmlMessage = atools::util::HtmlBuilder::warningMessage(message); break; + case QtFatalMsg: case QtCriticalMsg: - msg = atools::util::HtmlBuilder::errorMessage(message); + htmlMessage = atools::util::HtmlBuilder::errorMessage(message); break; + case QtInfoMsg: - msg = atools::util::HtmlBuilder::textMessage(message, atools::util::html::NO_ENTITIES); + htmlMessage = atools::util::HtmlBuilder::textMessage(message, atools::util::html::NO_ENTITIES); break; } - QString now = QDateTime::currentDateTime().toString("yyyy-MM-dd h:mm:ss"); + // Add five last messages to tray + if(type != QtDebugMsg && trayIcon != nullptr) + { + QStringList tooltip = trayIcon->toolTip().split('\n'); + tooltip.append(tr("- %1").arg(atools::strToPlainText(message))); + if(tooltip.size() > 5) + tooltip.removeFirst(); + trayIcon->setToolTip(tooltip.join('\n')); + } + // Use a signal to update the text edit in the main thread context - emit appendLogMessage(MESSAGEPATTERN.arg(now).arg(msg)); + emit appendLogMessage(MESSAGEPATTERN.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd h:mm:ss")).arg(htmlMessage)); } } @@ -503,47 +498,35 @@ void MainWindow::postLogMessage(QString message, bool warning, bool error) qInfo(atools::fs::ns::gui).noquote().nospace() << message; } -void MainWindow::readSettings() +void MainWindow::showInitial() { - qDebug() << Q_FUNC_INFO; - - verbose = Settings::instance().getAndStoreValue(lnc::SETTINGS_OPTIONS_VERBOSE, false).toBool(); + if(ui->actionStartMinimizeTray->isChecked() && trayIcon != nullptr) + hide(); + else + show(); - atools::gui::WidgetState(lnc::SETTINGS_MAINWINDOW_WIDGET).restore(this); + QTimer::singleShot(0, this, &MainWindow::mainWindowShownDelayed); } -void MainWindow::writeSettings() +void MainWindow::restoreState() { qDebug() << Q_FUNC_INFO; - atools::gui::WidgetState widgetState(lnc::SETTINGS_MAINWINDOW_WIDGET); - widgetState.save(this); - widgetState.syncSettings(); + verbose = Settings::instance().getAndStoreValue(lnc::SETTINGS_OPTIONS_VERBOSE, false).toBool(); + + atools::gui::WidgetState(lnc::SETTINGS_MAINWINDOW_WIDGET).restore({this, ui->actionMinimizeTray, ui->actionStartMinimizeTray}); } -void MainWindow::closeEvent(QCloseEvent *event) +void MainWindow::saveState() { - // Catch all close events like Ctrl-Q or Menu/Exit or clicking on the - // close button on the window frame qDebug() << Q_FUNC_INFO; - if(navServer->hasConnections()) - { - int result = atools::gui::Dialog(this).showQuestionMsgBox(lnc::SETTINGS_ACTIONS_SHOW_QUIT, - tr("There are still applications connected.\n" - "Really Quit?"), - tr("Do not &show this dialog again."), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No, QMessageBox::Yes); - - if(result != QMessageBox::Yes) - event->ignore(); - } - - writeSettings(); + atools::gui::WidgetState widgetState(lnc::SETTINGS_MAINWINDOW_WIDGET); + widgetState.save({this, ui->actionMinimizeTray, ui->actionStartMinimizeTray}); + widgetState.syncSettings(); } -void MainWindow::mainWindowShown() +void MainWindow::mainWindowShownDelayed() { qDebug() << Q_FUNC_INFO; qDebug(atools::fs::ns::gui).noquote().nospace() << "MainWindow::mainWindowShown"; @@ -560,11 +543,10 @@ void MainWindow::mainWindowShown() qInfo(atools::fs::ns::gui).noquote().nospace() << QApplication::applicationName(); qInfo(atools::fs::ns::gui).noquote().nospace() << tr("Version %1 (revision %2)."). - arg(applicationVersion).arg(GIT_REVISION_LITTLENAVCONNECT); + arg(applicationVersion).arg(GIT_REVISION_LITTLENAVCONNECT); qInfo(atools::fs::ns::gui).noquote().nospace() - << tr("Data Version %1. Reply Version %2.").arg(SimConnectData::getDataVersion()).arg( - SimConnectReply::getReplyVersion()); + << tr("Data Version %1. Reply Version %2.").arg(SimConnectData::getDataVersion()).arg(SimConnectReply::getReplyVersion()); // Build the handler classes which are an abstraction to SimConnect and the Little Xpconnect shared memory fsxConnectHandler = new atools::fs::sc::SimConnectHandler(verbose); @@ -607,7 +589,7 @@ void MainWindow::mainWindowShown() ui->actionConnectFsx->setChecked(false); #endif - ui->menuTools->insertAction(ui->actionOptions, ui->toolBar->toggleViewAction()); + ui->menuWindow->addAction(ui->toolBar->toggleViewAction()); // Build the thread which will read the data from the interfaces dataReader = new atools::fs::sc::DataReaderThread(this, verbose); @@ -648,13 +630,135 @@ void MainWindow::mainWindowShown() qDebug(atools::fs::ns::gui).noquote().nospace() << "MainWindow::mainWindowShown exit"; } -void MainWindow::showEvent(QShowEvent *event) +void MainWindow::showEvent(QShowEvent *) +{ + updateTrayActions(); +} + +void MainWindow::hideEvent(QHideEvent *) +{ + // Use minimize to hide to tray + if(isMinimized() && ui->actionMinimizeTray->isChecked() && trayIcon != nullptr) + hide(); + updateTrayActions(); +} + +void MainWindow::closeEvent(QCloseEvent *event) { - if(firstStart) + // Catch all close events like Ctrl-Q or Menu/Exit or clicking on the + // close button on the window frame + qDebug() << Q_FUNC_INFO; + bool askClose = false; + if(windowCloseButtonClicked) + { + // Close button on window frame clicked ================================================ + + // Check if tray is valid in case OS does not support it + if(trayIcon != nullptr && trayIcon->isVisible() && ui->actionMinimizeTray->isChecked()) + { + // Show hint only once per session and not if startup option is checked + if(!trayHintShown && !ui->actionStartMinimizeTray->isChecked()) + { + atools::gui::Dialog(this).showInfoMsgBox(lnc::SETTINGS_ACTIONS_SHOW_TRAY_HINT, + tr("The program will keep running in the system tray.\n" + "Select \"Quit\" in the context menu of the system tray entry to terminate the program."), + tr("Do not &show this dialog again.")); + + // Show only once per session + trayHintShown = true; + } + } // else keep closing + else + // No tray - close as usual + askClose = true; + } + else + // From tray menu or close action + askClose = true; + + if(askClose) { - emit windowShown(); - firstStart = false; + if(askCloseApplication()) + // Required here since QApplication::quitOnLastWindowClosed() is set to false + QApplication::quit(); + else + event->ignore(); } - event->ignore(); + windowCloseButtonClicked = true; + saveState(); +} + +void MainWindow::closeFromTrayOrAction() +{ + // Signal for close event + windowCloseButtonClicked = false; + + close(); +} + +bool MainWindow::askCloseApplication() +{ + if(navServer->hasConnections()) + { + int result = atools::gui::Dialog(this).showQuestionMsgBox(lnc::SETTINGS_ACTIONS_SHOW_QUIT, + tr("There are still applications connected.\n" + "Really Quit?"), + tr("Do not &show this dialog again."), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No, QMessageBox::Yes); + + return result == QMessageBox::Yes; + } + + return true; +} + +void MainWindow::updateTrayActions() +{ + if(trayRestoreHideAction != nullptr) + trayRestoreHideAction->setText(isVisible() ? tr("&Hide Window") : tr("&Restore Window")); +} + +void MainWindow::showHideFromTray() +{ + setVisible(!isVisible()); +} + +void MainWindow::createTrayIcon() +{ + trayRestoreHideAction = new QAction(tr("&Restore"), this); // Text toggles depending on window state + + // Copy text and icon from main but not shortcuts + trayOptionsAction = new QAction(ui->actionOptions->icon(), ui->actionOptions->text(), this); + trayHelpAction = new QAction(ui->actionHelp->icon(), ui->actionHelp->text(), this); + tryQuitAction = new QAction(ui->actionQuit->icon(), ui->actionQuit->text(), this); + + // Context menu + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(trayRestoreHideAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(trayOptionsAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(trayHelpAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(tryQuitAction); + + // Create tray + trayIcon = new QSystemTrayIcon(QIcon(":/littlenavconnect/resources/icons/navconnect.svg"), this); + trayIcon->setContextMenu(trayIconMenu); + + connect(trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayActivated); + connect(trayRestoreHideAction, &QAction::triggered, this, &MainWindow::showHideFromTray); + connect(trayOptionsAction, &QAction::triggered, this, &MainWindow::options); + connect(trayHelpAction, &QAction::triggered, this, &MainWindow::showOnlineHelp); + connect(tryQuitAction, &QAction::triggered, this, &MainWindow::closeFromTrayOrAction); + + trayIcon->show(); +} + +void MainWindow::trayActivated(QSystemTrayIcon::ActivationReason reason) +{ + // Toggle main window visibility on click + if(reason == QSystemTrayIcon::Trigger) + setVisible(!isVisible()); } diff --git a/src/mainwindow.h b/src/mainwindow.h index d4e135a..9b96e4a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,5 +1,5 @@ /***************************************************************************** -* Copyright 2015-2020 Alexander Barthel alex@littlenavmap.org +* Copyright 2015-2023 Alexander Barthel alex@littlenavmap.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #define LITTLENAVCONNECT_MAINWINDOW_H #include +#include namespace Ui { class MainWindow; @@ -51,30 +52,32 @@ class MainWindow : public: MainWindow(); - virtual ~MainWindow(); + virtual ~MainWindow() override; + /* Connected from DataReaderThread::postLogMessage */ void postLogMessage(QString message, bool warning, bool error); + /* Shows or hides the window initally and calls mainWindowShownDelayed() later */ + void showInitial(); + signals: /* Append a log message to the gui log. */ void appendLogMessage(const QString& message); - /* Emitted when window is shown the first time */ - void windowShown(); - private: /* Loggin handler will send log messages of category gui to this method which will emit * appendLogMessage to ensure that the message is appended using the main thread context. */ void logGuiMessage(QtMsgType type, const QMessageLogContext& context, const QString& message); - virtual void showEvent(QShowEvent *event) override; + virtual void showEvent(QShowEvent *) override; + virtual void hideEvent(QHideEvent *) override; virtual void closeEvent(QCloseEvent *event) override; - void readSettings(); - void writeSettings(); + /* Initializes server after window or tray shown */ + void mainWindowShownDelayed(); - /* Window visible for the first time after startup */ - void mainWindowShown(); + void restoreState(); + void saveState(); /* Reset all "do not show again" messages */ void resetMessages(); @@ -82,16 +85,40 @@ class MainWindow : /* Options dialog */ void options(); + /* Methods calles from actions */ void saveReplayFileTriggered(); void loadReplayFileTriggered(); void stopReplay(); void showOnlineHelp(); void showOfflineHelp(); void simulatorSelectionTriggered(); + void handlerChanged(); + /* Creates the tray icon and menu */ + void createTrayIcon(); + + /* Clicked */ + void trayActivated(QSystemTrayIcon::ActivationReason reason); + + /* Returns true if shutdown can be continued */ + bool askCloseApplication(); + + /* Either from action quit or tray menu quit */ + void updateTrayActions(); + void showHideFromTray(); + void closeFromTrayOrAction(); + + bool trayHintShown = false, /* Show hint only once per session */ + windowCloseButtonClicked = true; /* Avoid close to tray notification in closeEvent */ + Ui::MainWindow *ui = nullptr; + /* Tray and menus */ + QSystemTrayIcon *trayIcon = nullptr; + QMenu *trayIconMenu = nullptr; + QAction *trayRestoreHideAction = nullptr, *tryQuitAction = nullptr, *trayHelpAction = nullptr, *trayOptionsAction = nullptr; + // Navserver that waits and accepts tcp connections. Starts a NavServerWorker in a thread for each connection. atools::fs::ns::NavServer *navServer = nullptr; @@ -106,13 +133,10 @@ class MainWindow : bool firstStart = true; // Used to emit the first windowShown signal bool verbose = false; - QString mainWindowTitle; + QString mainWindowTitle, writeReplayWhazzupFile, supportedLanguageOnlineHelp, aboutMessage; + QString saveReplayFile, loadReplayFile; int replaySpeed = 1, replayWhazzupUpdateSpeed = 15; - QString writeReplayWhazzupFile; - QString supportedLanguageOnlineHelp; - - QString aboutMessage; }; #endif // LITTLENAVCONNECT_MAINWINDOW_H diff --git a/src/optionsdialog.cpp b/src/optionsdialog.cpp index d0e1e1e..e669ea6 100644 --- a/src/optionsdialog.cpp +++ b/src/optionsdialog.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* Copyright 2015-2020 Alexander Barthel alex@littlenavmap.org +* Copyright 2015-2023 Alexander Barthel alex@littlenavmap.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/optionsdialog.h b/src/optionsdialog.h index 01c4d9c..a369f90 100644 --- a/src/optionsdialog.h +++ b/src/optionsdialog.h @@ -1,5 +1,5 @@ /***************************************************************************** -* Copyright 2015-2020 Alexander Barthel alex@littlenavmap.org +* Copyright 2015-2023 Alexander Barthel alex@littlenavmap.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,14 +24,17 @@ namespace Ui { class OptionsDialog; } +/* + * Options dialog. Passive and allows only to set and get option values + */ class OptionsDialog : public QDialog { Q_OBJECT public: - explicit OptionsDialog(QWidget *parent = 0); - ~OptionsDialog(); + explicit OptionsDialog(QWidget *parent); + virtual ~OptionsDialog() override; int getPort() const; unsigned int getUpdateRate() const;