Add native macOS settings and device integration
This commit is contained in:
+131
-1
@@ -9,6 +9,7 @@
|
|||||||
#include <QAbstractItemView>
|
#include <QAbstractItemView>
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QCloseEvent>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@@ -21,10 +22,13 @@
|
|||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QPlainTextEdit>
|
#include <QPlainTextEdit>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <QSettings>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QStatusBar>
|
#include <QStatusBar>
|
||||||
@@ -365,6 +369,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
, m_rinex(new RinexNavigation(this))
|
, m_rinex(new RinexNavigation(this))
|
||||||
{
|
{
|
||||||
buildUi();
|
buildUi();
|
||||||
|
buildMenus();
|
||||||
|
|
||||||
m_locationTimeoutTimer = new QTimer(this);
|
m_locationTimeoutTimer = new QTimer(this);
|
||||||
m_locationTimeoutTimer->setSingleShot(true);
|
m_locationTimeoutTimer->setSingleShot(true);
|
||||||
@@ -374,11 +379,18 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
m_uiRefreshTimer->setSingleShot(true);
|
m_uiRefreshTimer->setSingleShot(true);
|
||||||
m_uiRefreshTimer->setInterval(UiRefreshIntervalMs);
|
m_uiRefreshTimer->setInterval(UiRefreshIntervalMs);
|
||||||
|
|
||||||
|
m_portRefreshTimer = new QTimer(this);
|
||||||
|
m_portRefreshTimer->setInterval(2000);
|
||||||
|
|
||||||
connect(m_serial, &SerialReader::lineReceived, this, &MainWindow::handleSerialLine);
|
connect(m_serial, &SerialReader::lineReceived, this, &MainWindow::handleSerialLine);
|
||||||
connect(m_serial, &SerialReader::statusChanged, this, &MainWindow::handleSerialStatus);
|
connect(m_serial, &SerialReader::statusChanged, this, &MainWindow::handleSerialStatus);
|
||||||
connect(m_serial, &SerialReader::errorOccurred, this, &MainWindow::handleSerialError);
|
connect(m_serial, &SerialReader::errorOccurred, this, &MainWindow::handleSerialError);
|
||||||
connect(m_locationTimeoutTimer, &QTimer::timeout, this, &MainWindow::handleLocationTimeout);
|
connect(m_locationTimeoutTimer, &QTimer::timeout, this, &MainWindow::handleLocationTimeout);
|
||||||
connect(m_uiRefreshTimer, &QTimer::timeout, this, &MainWindow::flushUiUpdates);
|
connect(m_uiRefreshTimer, &QTimer::timeout, this, &MainWindow::flushUiUpdates);
|
||||||
|
connect(m_portRefreshTimer, &QTimer::timeout, this, [this] {
|
||||||
|
if (!m_serial->isOpen()) refreshSerialPorts();
|
||||||
|
});
|
||||||
|
m_portRefreshTimer->start();
|
||||||
connect(m_rinex, &RinexNavigation::satelliteMotionUpdated,
|
connect(m_rinex, &RinexNavigation::satelliteMotionUpdated,
|
||||||
m_globe, &GlobeWidget::setSatelliteMotion);
|
m_globe, &GlobeWidget::setSatelliteMotion);
|
||||||
connect(m_rinex, &RinexNavigation::statusChanged, this,
|
connect(m_rinex, &RinexNavigation::statusChanged, this,
|
||||||
@@ -399,9 +411,16 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
rinexRefreshTimer->start();
|
rinexRefreshTimer->start();
|
||||||
|
|
||||||
resize(1480, 900);
|
resize(1480, 900);
|
||||||
|
loadSettings();
|
||||||
setWindowTitle(QStringLiteral("Canli Konum Haritasi"));
|
setWindowTitle(QStringLiteral("Canli Konum Haritasi"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::closeEvent(QCloseEvent *event)
|
||||||
|
{
|
||||||
|
saveSettings();
|
||||||
|
QMainWindow::closeEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::toggleSerial()
|
void MainWindow::toggleSerial()
|
||||||
{
|
{
|
||||||
if (m_serial->isOpen()) {
|
if (m_serial->isOpen()) {
|
||||||
@@ -475,6 +494,14 @@ void MainWindow::refreshSerialPorts()
|
|||||||
const QString configuredPort = qEnvironmentVariable("LOCATION_PLOTTER_PORT").trimmed();
|
const QString configuredPort = qEnvironmentVariable("LOCATION_PLOTTER_PORT").trimmed();
|
||||||
const QStringList detectedPorts = detectedSerialPorts();
|
const QStringList detectedPorts = detectedSerialPorts();
|
||||||
|
|
||||||
|
if (m_portsInitialized && detectedPorts == m_lastDetectedPorts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool deviceAdded = m_portsInitialized
|
||||||
|
&& detectedPorts.size() > m_lastDetectedPorts.size();
|
||||||
|
m_lastDetectedPorts = detectedPorts;
|
||||||
|
m_portsInitialized = true;
|
||||||
|
|
||||||
m_portCombo->clear();
|
m_portCombo->clear();
|
||||||
for (const QString &port : detectedPorts) {
|
for (const QString &port : detectedPorts) {
|
||||||
m_portCombo->addItem(port);
|
m_portCombo->addItem(port);
|
||||||
@@ -495,6 +522,106 @@ void MainWindow::refreshSerialPorts()
|
|||||||
m_portCombo->addItem(selectedPort);
|
m_portCombo->addItem(selectedPort);
|
||||||
}
|
}
|
||||||
m_portCombo->setCurrentText(selectedPort);
|
m_portCombo->setCurrentText(selectedPort);
|
||||||
|
if (deviceAdded) {
|
||||||
|
statusBar()->showMessage(QStringLiteral("Yeni USB UART aygiti algilandi: %1")
|
||||||
|
.arg(selectedPort), 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::buildMenus()
|
||||||
|
{
|
||||||
|
auto *fileMenu = menuBar()->addMenu(QStringLiteral("Dosya"));
|
||||||
|
QAction *openAction = fileMenu->addAction(QStringLiteral("Senaryo Logu Ac..."));
|
||||||
|
openAction->setObjectName(QStringLiteral("openLogAction"));
|
||||||
|
openAction->setShortcut(QKeySequence::Open);
|
||||||
|
connect(openAction, &QAction::triggered, this, &MainWindow::openTelemetryLog);
|
||||||
|
fileMenu->addSeparator();
|
||||||
|
QAction *quitAction = fileMenu->addAction(QStringLiteral("Cikis"));
|
||||||
|
quitAction->setShortcut(QKeySequence::Quit);
|
||||||
|
connect(quitAction, &QAction::triggered, this, &QWidget::close);
|
||||||
|
|
||||||
|
auto *connectionMenu = menuBar()->addMenu(QStringLiteral("Baglanti"));
|
||||||
|
QAction *listenAction = connectionMenu->addAction(QStringLiteral("Seri Baglantiyi Ac / Kapat"));
|
||||||
|
listenAction->setObjectName(QStringLiteral("toggleSerialAction"));
|
||||||
|
listenAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+K")));
|
||||||
|
connect(listenAction, &QAction::triggered, this, &MainWindow::toggleSerial);
|
||||||
|
QAction *portsAction = connectionMenu->addAction(QStringLiteral("Portlari Yenile"));
|
||||||
|
portsAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+Shift+R")));
|
||||||
|
connect(portsAction, &QAction::triggered, this, [this] {
|
||||||
|
m_portsInitialized = false;
|
||||||
|
refreshSerialPorts();
|
||||||
|
});
|
||||||
|
QAction *rinexAction = connectionMenu->addAction(QStringLiteral("RINEX Guncelle"));
|
||||||
|
rinexAction->setObjectName(QStringLiteral("rinexUpdateAction"));
|
||||||
|
rinexAction->setShortcut(QKeySequence::Refresh);
|
||||||
|
connect(rinexAction, &QAction::triggered, m_rinex, &RinexNavigation::refresh);
|
||||||
|
|
||||||
|
auto *viewMenu = menuBar()->addMenu(QStringLiteral("Gorunum"));
|
||||||
|
QAction *fitAction = viewMenu->addAction(QStringLiteral("Rotayi Haritaya Sigdir"));
|
||||||
|
fitAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+0")));
|
||||||
|
connect(fitAction, &QAction::triggered, m_map, &MapWidget::fitToPoints);
|
||||||
|
QAction *zoomInAction = viewMenu->addAction(QStringLiteral("Yaklastir"));
|
||||||
|
zoomInAction->setShortcut(QKeySequence::ZoomIn);
|
||||||
|
connect(zoomInAction, &QAction::triggered, m_map, &MapWidget::zoomIn);
|
||||||
|
QAction *zoomOutAction = viewMenu->addAction(QStringLiteral("Uzaklastir"));
|
||||||
|
zoomOutAction->setShortcut(QKeySequence::ZoomOut);
|
||||||
|
connect(zoomOutAction, &QAction::triggered, m_map, &MapWidget::zoomOut);
|
||||||
|
|
||||||
|
auto *helpMenu = menuBar()->addMenu(QStringLiteral("Yardim"));
|
||||||
|
QAction *aboutAction = helpMenu->addAction(QStringLiteral("Location Plotter Hakkinda"));
|
||||||
|
connect(aboutAction, &QAction::triggered, this, [this] {
|
||||||
|
QMessageBox::about(this, QStringLiteral("Location Plotter"),
|
||||||
|
QStringLiteral("GNSS telemetri, konum analizi ve RINEX uydu gorunumu\n"
|
||||||
|
"macOS surumu 0.2.0"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::loadSettings()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
restoreGeometry(settings.value(QStringLiteral("window/geometry")).toByteArray());
|
||||||
|
if (auto *splitter = findChild<QSplitter *>(QStringLiteral("mainSplitter")))
|
||||||
|
splitter->restoreState(settings.value(QStringLiteral("window/mainSplitter")).toByteArray());
|
||||||
|
if (auto *splitter = findChild<QSplitter *>(QStringLiteral("sideSplitter")))
|
||||||
|
splitter->restoreState(settings.value(QStringLiteral("window/sideSplitter")).toByteArray());
|
||||||
|
|
||||||
|
const QString port = settings.value(QStringLiteral("serial/port")).toString();
|
||||||
|
if (!port.isEmpty()) {
|
||||||
|
if (m_portCombo->findText(port) < 0) m_portCombo->addItem(port);
|
||||||
|
m_portCombo->setCurrentText(port);
|
||||||
|
}
|
||||||
|
m_baudCombo->setCurrentText(settings.value(QStringLiteral("serial/baud"), 115200).toString());
|
||||||
|
m_maxPointsSpin->setValue(settings.value(QStringLiteral("telemetry/maxPoints"), 10).toInt());
|
||||||
|
m_autoFollowCheck->setChecked(settings.value(QStringLiteral("map/autoFollow"), true).toBool());
|
||||||
|
m_showTelemetryCheck->setChecked(settings.value(QStringLiteral("map/showTelemetry"), true).toBool());
|
||||||
|
m_showRecentPointsCheck->setChecked(settings.value(QStringLiteral("map/showRecent"), true).toBool());
|
||||||
|
m_showAverageCheck->setChecked(settings.value(QStringLiteral("map/showAverage"), true).toBool());
|
||||||
|
if (auto *elevation = findChild<QDoubleSpinBox *>(QStringLiteral("elevationMaskSpin")))
|
||||||
|
elevation->setValue(settings.value(QStringLiteral("globe/elevationMask"), 10.0).toDouble());
|
||||||
|
if (auto *tabs = findChild<QTabWidget *>(QStringLiteral("mainViewTabs")))
|
||||||
|
tabs->setCurrentIndex(settings.value(QStringLiteral("window/mainTab"), 0).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::saveSettings() const
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue(QStringLiteral("window/geometry"), saveGeometry());
|
||||||
|
if (auto *splitter = findChild<QSplitter *>(QStringLiteral("mainSplitter")))
|
||||||
|
settings.setValue(QStringLiteral("window/mainSplitter"), splitter->saveState());
|
||||||
|
if (auto *splitter = findChild<QSplitter *>(QStringLiteral("sideSplitter")))
|
||||||
|
settings.setValue(QStringLiteral("window/sideSplitter"), splitter->saveState());
|
||||||
|
settings.setValue(QStringLiteral("serial/port"), m_portCombo->currentText().trimmed());
|
||||||
|
settings.setValue(QStringLiteral("serial/baud"), selectedBaudRate());
|
||||||
|
settings.setValue(QStringLiteral("telemetry/maxPoints"), m_maxPointsSpin->value());
|
||||||
|
settings.setValue(QStringLiteral("map/autoFollow"), m_autoFollowCheck->isChecked());
|
||||||
|
settings.setValue(QStringLiteral("map/showTelemetry"), m_showTelemetryCheck->isChecked());
|
||||||
|
settings.setValue(QStringLiteral("map/showRecent"), m_showRecentPointsCheck->isChecked());
|
||||||
|
settings.setValue(QStringLiteral("map/showAverage"), m_showAverageCheck->isChecked());
|
||||||
|
if (auto *elevation = findChild<QDoubleSpinBox *>(QStringLiteral("elevationMaskSpin")))
|
||||||
|
settings.setValue(QStringLiteral("globe/elevationMask"), elevation->value());
|
||||||
|
if (auto *tabs = findChild<QTabWidget *>(QStringLiteral("mainViewTabs")))
|
||||||
|
settings.setValue(QStringLiteral("window/mainTab"), tabs->currentIndex());
|
||||||
|
settings.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::handleSerialLine(const QString &line)
|
void MainWindow::handleSerialLine(const QString &line)
|
||||||
@@ -1084,7 +1211,10 @@ void MainWindow::buildUi()
|
|||||||
connect(m_clearButton, &QPushButton::clicked, this, &MainWindow::clearPoints);
|
connect(m_clearButton, &QPushButton::clicked, this, &MainWindow::clearPoints);
|
||||||
connect(m_openLogButton, &QPushButton::clicked, this, &MainWindow::openTelemetryLog);
|
connect(m_openLogButton, &QPushButton::clicked, this, &MainWindow::openTelemetryLog);
|
||||||
connect(m_rinexUpdateButton, &QPushButton::clicked, m_rinex, &RinexNavigation::refresh);
|
connect(m_rinexUpdateButton, &QPushButton::clicked, m_rinex, &RinexNavigation::refresh);
|
||||||
connect(refreshPortsButton, &QToolButton::clicked, this, &MainWindow::refreshSerialPorts);
|
connect(refreshPortsButton, &QToolButton::clicked, this, [this] {
|
||||||
|
m_portsInitialized = false;
|
||||||
|
refreshSerialPorts();
|
||||||
|
});
|
||||||
connect(m_maxPointsSpin, qOverload<int>(&QSpinBox::valueChanged), this,
|
connect(m_maxPointsSpin, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||||
&MainWindow::trimToMaxPoints);
|
&MainWindow::trimToMaxPoints);
|
||||||
connect(m_autoFollowCheck, &QCheckBox::toggled, m_map, &MapWidget::setAutoFollow);
|
connect(m_autoFollowCheck, &QCheckBox::toggled, m_map, &MapWidget::setAutoFollow);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class QPushButton;
|
|||||||
class QSpinBox;
|
class QSpinBox;
|
||||||
class QTableView;
|
class QTableView;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
class QCloseEvent;
|
||||||
|
|
||||||
class MapWidget;
|
class MapWidget;
|
||||||
class GlobeWidget;
|
class GlobeWidget;
|
||||||
@@ -32,6 +33,9 @@ public:
|
|||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(QWidget *parent = nullptr);
|
||||||
bool loadTelemetryLog(const QString &filePath);
|
bool loadTelemetryLog(const QString &filePath);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void toggleSerial();
|
void toggleSerial();
|
||||||
void clearPoints();
|
void clearPoints();
|
||||||
@@ -47,6 +51,9 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void buildUi();
|
void buildUi();
|
||||||
|
void buildMenus();
|
||||||
|
void loadSettings();
|
||||||
|
void saveSettings() const;
|
||||||
void updateConnectionControls();
|
void updateConnectionControls();
|
||||||
void addTelemetryPoint(const ParsedTelemetry &telemetry);
|
void addTelemetryPoint(const ParsedTelemetry &telemetry);
|
||||||
void refreshTable();
|
void refreshTable();
|
||||||
@@ -98,6 +105,7 @@ private:
|
|||||||
QPlainTextEdit *m_log = nullptr;
|
QPlainTextEdit *m_log = nullptr;
|
||||||
QTimer *m_locationTimeoutTimer = nullptr;
|
QTimer *m_locationTimeoutTimer = nullptr;
|
||||||
QTimer *m_uiRefreshTimer = nullptr;
|
QTimer *m_uiRefreshTimer = nullptr;
|
||||||
|
QTimer *m_portRefreshTimer = nullptr;
|
||||||
|
|
||||||
QVector<TelemetryPoint> m_points;
|
QVector<TelemetryPoint> m_points;
|
||||||
QStringList m_pendingLogLines;
|
QStringList m_pendingLogLines;
|
||||||
@@ -108,4 +116,6 @@ private:
|
|||||||
DopValues m_lastDop;
|
DopValues m_lastDop;
|
||||||
ParsedTelemetry m_pendingTelemetry;
|
ParsedTelemetry m_pendingTelemetry;
|
||||||
bool m_telemetryRefreshPending = false;
|
bool m_telemetryRefreshPending = false;
|
||||||
|
bool m_portsInitialized = false;
|
||||||
|
QStringList m_lastDetectedPorts;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../src/MapWidget.h"
|
#include "../src/MapWidget.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QAction>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDoubleSpinBox>
|
#include <QDoubleSpinBox>
|
||||||
@@ -56,6 +57,9 @@ int main(int argc, char **argv)
|
|||||||
auto *mainViewTabs = window.findChild<QTabWidget *>(QStringLiteral("mainViewTabs"));
|
auto *mainViewTabs = window.findChild<QTabWidget *>(QStringLiteral("mainViewTabs"));
|
||||||
auto *sideSplitter = window.findChild<QSplitter *>(QStringLiteral("sideSplitter"));
|
auto *sideSplitter = window.findChild<QSplitter *>(QStringLiteral("sideSplitter"));
|
||||||
auto *rinexUpdateButton = window.findChild<QPushButton *>(QStringLiteral("rinexUpdateButton"));
|
auto *rinexUpdateButton = window.findChild<QPushButton *>(QStringLiteral("rinexUpdateButton"));
|
||||||
|
auto *openLogAction = window.findChild<QAction *>(QStringLiteral("openLogAction"));
|
||||||
|
auto *toggleSerialAction = window.findChild<QAction *>(QStringLiteral("toggleSerialAction"));
|
||||||
|
auto *rinexUpdateAction = window.findChild<QAction *>(QStringLiteral("rinexUpdateAction"));
|
||||||
assert(table != nullptr);
|
assert(table != nullptr);
|
||||||
assert(referenceLatInput != nullptr);
|
assert(referenceLatInput != nullptr);
|
||||||
assert(referenceLonInput != nullptr);
|
assert(referenceLonInput != nullptr);
|
||||||
@@ -85,6 +89,9 @@ int main(int argc, char **argv)
|
|||||||
assert(sideSplitter->orientation() == Qt::Vertical);
|
assert(sideSplitter->orientation() == Qt::Vertical);
|
||||||
assert(rinexUpdateButton != nullptr);
|
assert(rinexUpdateButton != nullptr);
|
||||||
assert(!rinexUpdateButton->toolTip().isEmpty());
|
assert(!rinexUpdateButton->toolTip().isEmpty());
|
||||||
|
assert(openLogAction != nullptr && !openLogAction->shortcut().isEmpty());
|
||||||
|
assert(toggleSerialAction != nullptr && !toggleSerialAction->shortcut().isEmpty());
|
||||||
|
assert(rinexUpdateAction != nullptr && !rinexUpdateAction->shortcut().isEmpty());
|
||||||
|
|
||||||
assert(QMetaObject::invokeMethod(map, "referenceLocationSelected", Qt::DirectConnection,
|
assert(QMetaObject::invokeMethod(map, "referenceLocationSelected", Qt::DirectConnection,
|
||||||
Q_ARG(double, 40.25), Q_ARG(double, 29.5)));
|
Q_ARG(double, 40.25), Q_ARG(double, 29.5)));
|
||||||
|
|||||||
Reference in New Issue
Block a user