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
+
+
@@ -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;