1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 01:18:38 -05:00

Merge pull request #2276 from srcejon/pager_notifications

Pager notifications
This commit is contained in:
Edouard Griffiths 2024-10-12 08:32:21 +02:00 committed by GitHub
commit f169cb5159
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1274 additions and 141 deletions

View File

@ -80,6 +80,15 @@
"cacheVariables": {
"ENABLE_QT6": "ON"
}
},
{
"name": "default-qt6-windows",
"inherits": "default-windows",
"binaryDir": "${sourceDir}/build-qt6",
"cacheVariables": {
"ENABLE_QT6": "ON",
"CMAKE_PREFIX_PATH": "C:/Qt/6.7.3/msvc2022_64;C:/Applications/boost_1_81_0"
}
}
],
"buildPresets": [
@ -94,6 +103,10 @@
{
"name": "default-qt6",
"configurePreset": "default-qt6"
},
{
"name": "default-qt6-windows",
"configurePreset": "default-qt6-windows"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -29,16 +29,25 @@ if(NOT SERVER_MODE)
pagerdemodgui.ui
pagerdemodcharsetdialog.cpp
pagerdemodcharsetdialog.ui
pagerdemodnotificationdialog.cpp
pagerdemodnotificationdialog.ui
pagerdemodfilterdialog.cpp
pagerdemodfilterdialog.ui
pagerdemodicons.qrc
)
set(demodpager_HEADERS
${demodpager_HEADERS}
pagerdemodgui.h
pagerdemodcharsetdialog.h
pagerdemodnotificationdialog.h
)
set(TARGET_NAME ${PLUGINS_PREFIX}demodpager)
set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui")
if(Qt${QT_DEFAULT_MAJOR_VERSION}TextToSpeech_FOUND)
list(APPEND TARGET_LIB_GUI Qt::TextToSpeech)
endif()
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME ${PLUGINSSRV_PREFIX}demodpagersrv)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -31,6 +31,7 @@
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "util/db.h"
#include "util/csv.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(PagerDemod::MsgConfigurePagerDemod, Message)
@ -141,7 +142,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
{
if (MsgConfigurePagerDemod::match(cmd))
{
MsgConfigurePagerDemod& cfg = (MsgConfigurePagerDemod&) cmd;
const MsgConfigurePagerDemod& cfg = (const MsgConfigurePagerDemod&) cmd;
qDebug() << "PagerDemod::handleMessage: MsgConfigurePagerDemod";
applySettings(cfg.getSettings(), cfg.getForce());
@ -149,7 +150,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
const DSPSignalNotification& notif = (const DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
// Forward to the sink
@ -166,7 +167,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
else if (MsgPagerMessage::match(cmd))
{
// Forward to GUI
MsgPagerMessage& report = (MsgPagerMessage&)cmd;
const MsgPagerMessage& report = (const MsgPagerMessage&)cmd;
if (getMessageQueueToGUI())
{
MsgPagerMessage *msg = new MsgPagerMessage(report);
@ -200,7 +201,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
<< report.getDateTime().time().toString() << ","
<< QString("%1").arg(report.getAddress(), 7, 10, QChar('0')) << ","
<< QString::number(report.getFunctionBits()) << ","
<< "\"" << report.getAlphaMessage() << "\","
<< CSV::escape(report.getAlphaMessage()) << ","
<< report.getNumericMessage() << ","
<< QString::number(report.getEvenParityErrors()) << ","
<< QString::number(report.getBCHParityErrors()) << "\n";

View File

@ -131,7 +131,7 @@ bool PagerDemodBaseband::handleMessage(const Message& cmd)
if (MsgConfigurePagerDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigurePagerDemodBaseband& cfg = (MsgConfigurePagerDemodBaseband&) cmd;
const MsgConfigurePagerDemodBaseband& cfg = (const MsgConfigurePagerDemodBaseband&) cmd;
qDebug() << "PagerDemodBaseband::handleMessage: MsgConfigurePagerDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
@ -141,7 +141,7 @@ bool PagerDemodBaseband::handleMessage(const Message& cmd)
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
const DSPSignalNotification& notif = (const DSPSignalNotification&) cmd;
qDebug() << "PagerDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));

View File

@ -0,0 +1,46 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QCheckBox>
#include "pagerdemodfilterdialog.h"
PagerDemodFilterDialog::PagerDemodFilterDialog(PagerDemodSettings *settings,
QWidget* parent) :
QDialog(parent),
ui(new Ui::PagerDemodFilterDialog),
m_settings(settings)
{
ui->setupUi(this);
ui->matchLastOnly->setChecked(m_settings->m_duplicateMatchLastOnly);
ui->matchMessageOnly->setChecked(m_settings->m_duplicateMatchMessageOnly);
}
PagerDemodFilterDialog::~PagerDemodFilterDialog()
{
delete ui;
}
void PagerDemodFilterDialog::accept()
{
m_settings->m_duplicateMatchLastOnly = ui->matchLastOnly->isChecked();
m_settings->m_duplicateMatchMessageOnly = ui->matchMessageOnly->isChecked();
QDialog::accept();
}

View File

@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODFILTERDIALOG_H
#define INCLUDE_PAGERDEMODFILTERDIALOG_H
#include "ui_pagerdemodfilterdialog.h"
#include "pagerdemodsettings.h"
class PagerDemodFilterDialog : public QDialog {
Q_OBJECT
public:
explicit PagerDemodFilterDialog(PagerDemodSettings* settings, QWidget* parent = nullptr);
~PagerDemodFilterDialog();
private slots:
void accept() override;
private:
Ui::PagerDemodFilterDialog* ui;
PagerDemodSettings *m_settings;
};
#endif // INCLUDE_PAGERDEMODFILTERDIALOG_H

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PagerDemodFilterDialog</class>
<widget class="QDialog" name="PagerDemodFilterDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>167</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::PreventContextMenu</enum>
</property>
<property name="windowTitle">
<string>Duplicate Filtering</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Duplicate Filtering</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="matchMessageOnlyLabel">
<property name="text">
<string>Match message only</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="matchMessageOnly">
<property name="toolTip">
<string>Whether both the address and message must match or only the message to be considered a duplicate</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="matchLastOnlyLabel">
<property name="text">
<string>Match last message only</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="matchLastOnly">
<property name="toolTip">
<string>Whether to match with only the last message or any message in the table</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PagerDemodFilterDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PagerDemodFilterDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -16,8 +16,6 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <limits>
#include <ctype.h>
#include <QDockWidget>
#include <QDebug>
#include <QAction>
@ -26,6 +24,7 @@
#include <QFileDialog>
#include <QMessageBox>
#include <QScrollBar>
#include <QProcess>
#include "pagerdemodgui.h"
@ -36,6 +35,8 @@
#include "plugin/pluginapi.h"
#include "util/db.h"
#include "util/csv.h"
#include "util/units.h"
#include "gui/crightclickenabler.h"
#include "gui/basicchannelsettingsdialog.h"
#include "dsp/dspengine.h"
#include "gui/dialogpositioner.h"
@ -43,6 +44,10 @@
#include "pagerdemod.h"
#include "pagerdemodcharsetdialog.h"
#include "pagerdemodnotificationdialog.h"
#include "pagerdemodfilterdialog.h"
#include "SWGMapItem.h"
void PagerDemodGUI::resizeTable()
{
@ -50,15 +55,15 @@ void PagerDemodGUI::resizeTable()
// Trailing spaces are for sort arrow
int row = ui->messages->rowCount();
ui->messages->setRowCount(row + 1);
ui->messages->setItem(row, MESSAGE_COL_DATE, new QTableWidgetItem("Fri Apr 15 2016-"));
ui->messages->setItem(row, MESSAGE_COL_TIME, new QTableWidgetItem("10:17:00"));
ui->messages->setItem(row, MESSAGE_COL_ADDRESS, new QTableWidgetItem("1000000"));
ui->messages->setItem(row, MESSAGE_COL_MESSAGE, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->messages->setItem(row, MESSAGE_COL_FUNCTION, new QTableWidgetItem("0"));
ui->messages->setItem(row, MESSAGE_COL_ALPHA, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->messages->setItem(row, MESSAGE_COL_NUMERIC, new QTableWidgetItem("123456789123456789123456789123456789123456789123456789"));
ui->messages->setItem(row, MESSAGE_COL_EVEN_PE, new QTableWidgetItem("0"));
ui->messages->setItem(row, MESSAGE_COL_BCH_PE, new QTableWidgetItem("0"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_DATE, new QTableWidgetItem("Fri Apr 15 2016--"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_TIME, new QTableWidgetItem("10:17:00"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ADDRESS, new QTableWidgetItem("1000000"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_MESSAGE, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_FUNCTION, new QTableWidgetItem("0"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ALPHA, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_NUMERIC, new QTableWidgetItem("123456789123456789123456789123456789123456789123456789"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_EVEN_PE, new QTableWidgetItem("0"));
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_BCH_PE, new QTableWidgetItem("0"));
ui->messages->resizeColumnsToContents();
ui->messages->removeRow(row);
}
@ -144,11 +149,101 @@ bool PagerDemodGUI::deserialize(const QByteArray& data)
}
}
QString PagerDemodGUI::selectMessage(int functionBits, const QString &numericMessage, const QString &alphaMessage) const
{
QString message;
// Standard way of choosing numeric or alpha decode isn't followed widely
if (m_settings.m_decode == PagerDemodSettings::Standard)
{
// Encoding is based on function bits
if (functionBits == 0) {
message = numericMessage;
} else {
message = alphaMessage;
}
}
else if (m_settings.m_decode == PagerDemodSettings::Inverted)
{
// Encoding is based on function bits, but inverted from standard
if (functionBits == 3) {
message = numericMessage;
} else {
message = alphaMessage;
}
}
else if (m_settings.m_decode == PagerDemodSettings::Numeric)
{
// Always display as numeric
message = numericMessage;
}
else if (m_settings.m_decode == PagerDemodSettings::Alphanumeric)
{
// Always display as alphanumeric
message = alphaMessage;
}
else
{
// Guess at what the encoding is
QString numeric = numericMessage;
QString alpha = alphaMessage;
bool done = false;
if (!done)
{
// If alpha contains control characters, possibly numeric
for (int i = 0; i < alpha.size(); i++)
{
if (iscntrl(alpha[i].toLatin1()) && !isspace(alpha[i].toLatin1()))
{
message = numeric;
done = true;
break;
}
}
}
if (!done) {
// Possibly not likely to get only longer than 15 digits
if (numeric.size() > 15)
{
done = true;
message = alpha;
}
}
if (!done) {
// Default to alpha
message = alpha;
}
}
return message;
}
// Add row to table
void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int functionBits,
const QString &numericMessage, const QString &alphaMessage,
int evenParityErrors, int bchParityErrors)
{
QString message = selectMessage(functionBits, numericMessage, alphaMessage);
QString addressString = QString("%1").arg(address, 7, 10, QChar('0'));
// Should we ignore the message if it is a duplicate?
if (m_settings.m_filterDuplicates && (ui->messages->rowCount() > 0))
{
int startRow = m_settings.m_duplicateMatchLastOnly ? ui->messages->rowCount() - 1 : 0;
for (int row = startRow; row < ui->messages->rowCount(); row++)
{
QString prevAddress = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_ADDRESS)->text();
QString prevMessage = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_MESSAGE)->text();
if ((message == prevMessage) && (m_settings.m_duplicateMatchMessageOnly || (addressString == prevAddress)))
{
// Ignore this message
return;
}
}
}
// Is scroll bar at bottom
QScrollBar *sb = ui->messages->verticalScrollBar();
bool scrollToBottom = sb->value() == sb->maximum();
@ -167,79 +262,19 @@ void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int f
QTableWidgetItem *numericItem = new QTableWidgetItem();
QTableWidgetItem *evenPEItem = new QTableWidgetItem();
QTableWidgetItem *bchPEItem = new QTableWidgetItem();
ui->messages->setItem(row, MESSAGE_COL_DATE, dateItem);
ui->messages->setItem(row, MESSAGE_COL_TIME, timeItem);
ui->messages->setItem(row, MESSAGE_COL_ADDRESS, addressItem);
ui->messages->setItem(row, MESSAGE_COL_MESSAGE, messageItem);
ui->messages->setItem(row, MESSAGE_COL_FUNCTION, functionItem);
ui->messages->setItem(row, MESSAGE_COL_ALPHA, alphaItem);
ui->messages->setItem(row, MESSAGE_COL_NUMERIC, numericItem);
ui->messages->setItem(row, MESSAGE_COL_EVEN_PE, evenPEItem);
ui->messages->setItem(row, MESSAGE_COL_BCH_PE, bchPEItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_DATE, dateItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_TIME, timeItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ADDRESS, addressItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_MESSAGE, messageItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_FUNCTION, functionItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ALPHA, alphaItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_NUMERIC, numericItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_EVEN_PE, evenPEItem);
ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_BCH_PE, bchPEItem);
dateItem->setText(dateTime.date().toString());
timeItem->setText(dateTime.time().toString());
addressItem->setText(QString("%1").arg(address, 7, 10, QChar('0')));
// Standard way of choosing numeric or alpha decode isn't followed widely
if (m_settings.m_decode == PagerDemodSettings::Standard)
{
// Encoding is based on function bits
if (functionBits == 0) {
messageItem->setText(numericMessage);
} else {
messageItem->setText(alphaMessage);
}
}
else if (m_settings.m_decode == PagerDemodSettings::Inverted)
{
// Encoding is based on function bits, but inverted from standard
if (functionBits == 3) {
messageItem->setText(numericMessage);
} else {
messageItem->setText(alphaMessage);
}
}
else if (m_settings.m_decode == PagerDemodSettings::Numeric)
{
// Always display as numeric
messageItem->setText(numericMessage);
}
else if (m_settings.m_decode == PagerDemodSettings::Alphanumeric)
{
// Always display as alphanumeric
messageItem->setText(alphaMessage);
}
else
{
// Guess at what the encoding is
QString numeric = numericMessage;
QString alpha = alphaMessage;
bool done = false;
if (!done)
{
// If alpha contains control characters, possibly numeric
for (int i = 0; i < alpha.size(); i++)
{
if (iscntrl(alpha[i].toLatin1()) && !isspace(alpha[i].toLatin1()))
{
messageItem->setText(numeric);
done = true;
break;
}
}
}
if (!done) {
// Possibly not likely to get only longer than 15 digits
if (numeric.size() > 15)
{
done = true;
messageItem->setText(alpha);
}
}
if (!done) {
// Default to alpha
messageItem->setText(alpha);
}
}
addressItem->setText(addressString);
messageItem->setText(message);
functionItem->setText(QString("%1").arg(functionBits));
alphaItem->setText(alphaMessage);
numericItem->setText(numericMessage);
@ -250,6 +285,7 @@ void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int f
if (scrollToBottom) {
ui->messages->scrollToBottom();
}
checkNotification(row);
}
bool PagerDemodGUI::handleMessage(const Message& message)
@ -257,7 +293,7 @@ bool PagerDemodGUI::handleMessage(const Message& message)
if (PagerDemod::MsgConfigurePagerDemod::match(message))
{
qDebug("PagerDemodGUI::handleMessage: PagerDemod::MsgConfigurePagerDemod");
const PagerDemod::MsgConfigurePagerDemod& cfg = (PagerDemod::MsgConfigurePagerDemod&) message;
const PagerDemod::MsgConfigurePagerDemod& cfg = (const PagerDemod::MsgConfigurePagerDemod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
ui->scopeGUI->updateSettings();
@ -268,7 +304,7 @@ bool PagerDemodGUI::handleMessage(const Message& message)
}
else if (PagerDemod::MsgPagerMessage::match(message))
{
PagerDemod::MsgPagerMessage& report = (PagerDemod::MsgPagerMessage&) message;
const PagerDemod::MsgPagerMessage& report = (const PagerDemod::MsgPagerMessage&) message;
messageReceived(report.getDateTime(), report.getAddress(), report.getFunctionBits(),
report.getNumericMessage(), report.getAlphaMessage(),
report.getEvenParityErrors(), report.getBCHParityErrors());
@ -276,7 +312,7 @@ bool PagerDemodGUI::handleMessage(const Message& message)
}
else if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
const DSPSignalNotification& notif = (const DSPSignalNotification&) message;
m_deviceCenterFrequency = notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate();
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
@ -398,7 +434,7 @@ void PagerDemodGUI::filterRow(int row)
if (m_settings.m_filterAddress != "")
{
QRegExp re(m_settings.m_filterAddress);
QTableWidgetItem *fromItem = ui->messages->item(row, MESSAGE_COL_ADDRESS);
QTableWidgetItem *fromItem = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_ADDRESS);
if (!re.exactMatch(fromItem->text())) {
hidden = true;
}
@ -479,7 +515,8 @@ PagerDemodGUI::PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas
m_deviceCenterFrequency(0),
m_basebandSampleRate(1),
m_doApplySettings(true),
m_tickCount(0)
m_tickCount(0),
m_speech(nullptr)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodpager/readme.md";
@ -526,6 +563,9 @@ PagerDemodGUI::PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
CRightClickEnabler *filterDuplicatesRightClickEnabler = new CRightClickEnabler(ui->filterDuplicates);
connect(filterDuplicatesRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(on_filterDuplicates_rightClicked(const QPoint &)));
// Resize the table using dummy data
resizeTable();
// Allow user to reorder columns
@ -575,6 +615,7 @@ void PagerDemodGUI::customContextMenuRequested(QPoint pos)
PagerDemodGUI::~PagerDemodGUI()
{
clearFromMap();
delete ui;
}
@ -638,6 +679,8 @@ void PagerDemodGUI::displaySettings()
ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
ui->logEnable->setChecked(m_settings.m_logEnabled);
ui->filterDuplicates->setChecked(m_settings.m_filterDuplicates);
// Order and size columns
QHeaderView *header = ui->messages->horizontalHeader();
@ -656,6 +699,7 @@ void PagerDemodGUI::displaySettings()
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
blockApplySettings(false);
enableSpeechIfNeeded();
}
void PagerDemodGUI::leaveEvent(QEvent* event)
@ -693,12 +737,39 @@ void PagerDemodGUI::tick()
void PagerDemodGUI::on_charset_clicked()
{
PagerDemodCharsetDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted)
{
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted) {
applySettings();
}
}
void PagerDemodGUI::on_notifications_clicked()
{
PagerDemodNotificationDialog dialog(&m_settings);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted)
{
enableSpeechIfNeeded();
applySettings();
}
}
void PagerDemodGUI::on_filterDuplicates_clicked(bool checked)
{
m_settings.m_filterDuplicates = checked;
applySettings();
}
void PagerDemodGUI::on_filterDuplicates_rightClicked(const QPoint &p)
{
(void) p;
PagerDemodFilterDialog dialog(&m_settings);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted) {
applySettings();
}
}
void PagerDemodGUI::on_logEnable_clicked(bool checked)
{
@ -813,6 +884,8 @@ void PagerDemodGUI::makeUIConnections()
QObject::connect(ui->udpEnabled, &QCheckBox::clicked, this, &PagerDemodGUI::on_udpEnabled_clicked);
QObject::connect(ui->udpAddress, &QLineEdit::editingFinished, this, &PagerDemodGUI::on_udpAddress_editingFinished);
QObject::connect(ui->udpPort, &QLineEdit::editingFinished, this, &PagerDemodGUI::on_udpPort_editingFinished);
QObject::connect(ui->notifications, &QToolButton::clicked, this, &PagerDemodGUI::on_notifications_clicked);
QObject::connect(ui->filterDuplicates, &ButtonSwitch::clicked, this, &PagerDemodGUI::on_filterDuplicates_clicked);
QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &PagerDemodGUI::on_logEnable_clicked);
QObject::connect(ui->logFilename, &QToolButton::clicked, this, &PagerDemodGUI::on_logFilename_clicked);
QObject::connect(ui->logOpen, &QToolButton::clicked, this, &PagerDemodGUI::on_logOpen_clicked);
@ -824,3 +897,178 @@ void PagerDemodGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}
// Initialise text to speech engine
// This takes 10 seconds on some versions of Linux, so only do it, if user actually
// has speech notifications configured
void PagerDemodGUI::enableSpeechIfNeeded()
{
#ifdef QT_TEXTTOSPEECH_FOUND
if (m_speech) {
return;
}
for (const auto& notification : m_settings.m_notificationSettings)
{
if (!notification->m_speech.isEmpty())
{
qDebug() << "PagerDemodGUI: Enabling text to speech";
m_speech = new QTextToSpeech(this);
return;
}
}
#endif
}
void PagerDemodGUI::checkNotification(int row)
{
QString address = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_ADDRESS)->text();
QString message = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_MESSAGE)->text();
for (int i = 0; i < m_settings.m_notificationSettings.size(); i++)
{
QString match;
switch (m_settings.m_notificationSettings[i]->m_matchColumn)
{
case PagerDemodSettings::MESSAGE_COL_ADDRESS:
match = address;
break;
case PagerDemodSettings::MESSAGE_COL_MESSAGE:
match = message;
break;
}
if (!match.isEmpty())
{
if (m_settings.m_notificationSettings[i]->m_regularExpression.isValid())
{
QRegularExpressionMatch matchResult = m_settings.m_notificationSettings[i]->m_regularExpression.match(match);
if (matchResult.hasMatch())
{
if (m_settings.m_notificationSettings[i]->m_highlight) {
ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_MESSAGE)->setForeground(QBrush(m_settings.m_notificationSettings[i]->m_highlightColor));
}
if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty())
{
QString speech = subStrings(address, message, matchResult, m_settings.m_notificationSettings[i]->m_speech);
speechNotification(speech);
}
if (!m_settings.m_notificationSettings[i]->m_command.isEmpty())
{
QString command = subStrings(address, message, matchResult, m_settings.m_notificationSettings[i]->m_command);
commandNotification(command);
}
if (m_settings.m_notificationSettings[i]->m_plotOnMap)
{
float latitude;
float longitude;
if (Units::stringToLatitudeAndLongitude(message, latitude, longitude, false))
{
QDateTime dateTime;
dateTime.setDate(QDate::fromString(ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_DATE)->text()));
dateTime.setTime(QTime::fromString(ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_TIME)->text()));
sendToMap(address, message, latitude, longitude, dateTime);
}
}
}
}
}
}
}
QString PagerDemodGUI::subStrings(const QString& address, const QString& message, const QRegularExpressionMatch& match, const QString &string) const
{
QString s = string;
s = s.replace("${address}", address);
s = s.replace("${message}", message);
for (int i = 0; i < match.capturedTexts().size(); i++)
{
QString escape = QString("${%1}").arg(i);
s = s.replace(escape, match.capturedTexts()[i]);
}
return s;
}
void PagerDemodGUI::speechNotification(const QString &speech)
{
#ifdef QT_TEXTTOSPEECH_FOUND
if (m_speech) {
m_speech->say(speech);
} else {
qWarning() << "PagerDemodGUI::speechNotification: Unable to say " << speech;
}
#else
qWarning() << "PagerDemodGUI::speechNotification: TextToSpeech not supported. Unable to say " << speech;
#endif
}
void PagerDemodGUI::commandNotification(const QString &command)
{
#if QT_CONFIG(process)
QStringList allArgs = QProcess::splitCommand(command);
if (allArgs.size() > 0)
{
QString program = allArgs[0];
allArgs.pop_front();
QProcess::startDetached(program, allArgs);
}
#else
qWarning() << "PagerDemodGUI::commandNotification: QProcess not supported. Can't run: " << command;
#endif
}
void PagerDemodGUI::sendToMap(const QString& address, const QString& message, float latitude, float longitude, QDateTime dateTime)
{
QList<ObjectPipe*> mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_pagerDemod, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(address));
swgMapItem->setLatitude(latitude);
swgMapItem->setLongitude(longitude);
swgMapItem->setAltitude(0);
swgMapItem->setAltitudeReference(1); // CLAMP_TO_GROUND
swgMapItem->setFixedPosition(false);
swgMapItem->setPositionDateTime(new QString(dateTime.toString(Qt::ISODateWithMs)));
swgMapItem->setImageRotation(0);
swgMapItem->setText(new QString(message));
swgMapItem->setImage(new QString(QString("pager.png")));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_pagerDemod, swgMapItem);
messageQueue->push(msg);
}
m_mapItems.insert(address);
}
// Clear all items from map
void PagerDemodGUI::clearFromMap()
{
for (const auto& address : m_mapItems)
{
QList<ObjectPipe*> mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_pagerDemod, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(address));
swgMapItem->setImage(new QString(QString("")));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_pagerDemod, swgMapItem);
messageQueue->push(msg);
}
}
m_mapItems.clear();
}

View File

@ -20,6 +20,7 @@
#define INCLUDE_PAGERDEMODGUI_H
#include <QMenu>
#include <QTextToSpeech>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
@ -45,6 +46,7 @@ class PagerDemodGUI : public ChannelGUI {
Q_OBJECT
public:
static PagerDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy();
@ -86,12 +88,18 @@ private:
QMenu *messagesMenu; // Column select context menu
#ifdef QT_TEXTTOSPEECH_FOUND
QTextToSpeech *m_speech;
#endif
QSet<QString> m_mapItems;
explicit PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~PagerDemodGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
QString selectMessage(int functionBits, const QString &numericMessage, const QString &alphaMessage) const;
void messageReceived(const QDateTime dateTime, int address, int functionBits,
const QString &numericMessage, const QString &alphaMessage,
int evenParityErrors, int bchParityErrors);
@ -105,17 +113,13 @@ private:
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot);
enum MessageCol {
MESSAGE_COL_DATE,
MESSAGE_COL_TIME,
MESSAGE_COL_ADDRESS,
MESSAGE_COL_MESSAGE,
MESSAGE_COL_FUNCTION,
MESSAGE_COL_ALPHA,
MESSAGE_COL_NUMERIC,
MESSAGE_COL_EVEN_PE,
MESSAGE_COL_BCH_PE
};
void enableSpeechIfNeeded();
void checkNotification(int row);
void speechNotification(const QString &speech);
void commandNotification(const QString &command);
QString subStrings(const QString& address, const QString& message, const QRegularExpressionMatch& match, const QString &string) const;
void sendToMap(const QString& address, const QString& message, float latitide, float longitude, QDateTime dateTime);
void clearFromMap();
private slots:
void on_deltaFrequency_changed(qint64 value);
@ -131,6 +135,9 @@ private slots:
void on_udpPort_editingFinished();
void on_channel1_currentIndexChanged(int index);
void on_channel2_currentIndexChanged(int index);
void on_notifications_clicked();
void on_filterDuplicates_clicked(bool checked=false);
void on_filterDuplicates_rightClicked(const QPoint &);
void on_logEnable_clicked(bool checked=false);
void on_logFilename_clicked();
void on_logOpen_clicked();

View File

@ -29,7 +29,7 @@
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>Pager Demodulator</string>
@ -110,7 +110,7 @@
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
@ -127,14 +127,14 @@
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -152,7 +152,7 @@
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
<enum>Qt::LayoutDirection::RightToLeft</enum>
</property>
<property name="text">
<string>0.0</string>
@ -209,7 +209,7 @@
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -252,7 +252,7 @@
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -268,14 +268,14 @@
<string>10.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
</widget>
</item>
@ -316,7 +316,7 @@
<number>24</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -332,7 +332,7 @@
<string>2.4k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
@ -368,7 +368,7 @@
<item>
<widget class="Line" name="line_10">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
</widget>
</item>
@ -413,7 +413,7 @@
<item>
<widget class="Line" name="line_11">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
</widget>
</item>
@ -479,7 +479,7 @@
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -494,22 +494,29 @@
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="udpLayout">
<item>
<widget class="QLabel" name="udpLabel">
<property name="text">
<string>UDP</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="udpEnabled">
<property name="toolTip">
<string>Forward messages via UDP</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
<enum>Qt::LayoutDirection::RightToLeft</enum>
</property>
<property name="text">
<string>UDP</string>
<string/>
</property>
</widget>
</item>
@ -522,7 +529,7 @@
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
<enum>Qt::FocusPolicy::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP address</string>
@ -541,7 +548,7 @@
<string>:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
@ -560,7 +567,7 @@
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
<enum>Qt::FocusPolicy::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP port</string>
@ -576,7 +583,7 @@
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -591,7 +598,7 @@
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -614,7 +621,7 @@
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -624,6 +631,43 @@
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="filterDuplicates">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Filter duplicate messages. Right click for options.</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="pagerdemodicons.qrc">
<normaloff>:/icons/filterduplicate.png</normaloff>:/icons/filterduplicate.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="notifications">
<property name="toolTip">
<string>Open notifications dialog</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/mono.png</normaloff>:/mono.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="logEnable">
<property name="maximumSize">
@ -736,7 +780,7 @@
<string>Received messages</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<column>
<property name="text">
@ -1056,6 +1100,7 @@
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="pagerdemodicons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>icons/filterduplicate.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,173 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QComboBox>
#include <QPushButton>
#include <QCheckBox>
#include <QHBoxLayout>
#include "gui/tablecolorchooser.h"
#include "pagerdemodnotificationdialog.h"
#include "pagerdemodgui.h"
// Map main table column numbers to combo box indices
std::vector<int> PagerDemodNotificationDialog::m_columnMap = {
PagerDemodSettings::MESSAGE_COL_ADDRESS, PagerDemodSettings::MESSAGE_COL_MESSAGE
};
PagerDemodNotificationDialog::PagerDemodNotificationDialog(PagerDemodSettings *settings,
QWidget* parent) :
QDialog(parent),
ui(new Ui::PagerDemodNotificationDialog),
m_settings(settings)
{
ui->setupUi(this);
resizeTable();
for (int i = 0; i < m_settings->m_notificationSettings.size(); i++) {
addRow(m_settings->m_notificationSettings[i]);
}
}
PagerDemodNotificationDialog::~PagerDemodNotificationDialog()
{
delete ui;
qDeleteAll(m_colorGUIs);
}
void PagerDemodNotificationDialog::accept()
{
qDeleteAll(m_settings->m_notificationSettings);
m_settings->m_notificationSettings.clear();
for (int i = 0; i < ui->table->rowCount(); i++)
{
PagerDemodSettings::NotificationSettings *notificationSettings = new PagerDemodSettings::NotificationSettings();
int idx = ((QComboBox *)ui->table->cellWidget(i, NOTIFICATION_COL_MATCH))->currentIndex();
notificationSettings->m_matchColumn = m_columnMap[idx];
notificationSettings->m_regExp = ui->table->item(i, NOTIFICATION_COL_REG_EXP)->data(Qt::DisplayRole).toString().trimmed();
notificationSettings->m_speech = ui->table->item(i, NOTIFICATION_COL_SPEECH)->data(Qt::DisplayRole).toString().trimmed();
notificationSettings->m_command = ui->table->item(i, NOTIFICATION_COL_COMMAND)->data(Qt::DisplayRole).toString().trimmed();
notificationSettings->m_highlight = !m_colorGUIs[i]->m_noColor;
notificationSettings->m_highlightColor = m_colorGUIs[i]->m_color;
notificationSettings->m_plotOnMap = ((QCheckBox *) ui->table->cellWidget(i, NOTIFICATION_COL_PLOT_ON_MAP))->isChecked();
notificationSettings->updateRegularExpression();
m_settings->m_notificationSettings.append(notificationSettings);
}
QDialog::accept();
}
void PagerDemodNotificationDialog::resizeTable()
{
PagerDemodSettings::NotificationSettings dummy;
dummy.m_matchColumn = PagerDemodSettings::MESSAGE_COL_ADDRESS;
dummy.m_regExp = "1234567";
dummy.m_speech = "${message}";
dummy.m_command = "cmail.exe -to:user@host.com \"-subject: Paging ${address}\" \"-body: ${message}\"";
dummy.m_highlight = true;
dummy.m_plotOnMap = true;
addRow(&dummy);
ui->table->resizeColumnsToContents();
ui->table->selectRow(0);
on_remove_clicked();
ui->table->selectRow(-1);
}
void PagerDemodNotificationDialog::on_add_clicked()
{
addRow();
}
// Remove selected row
void PagerDemodNotificationDialog::on_remove_clicked()
{
// Selection mode is single, so only a single row should be returned
QModelIndexList indexList = ui->table->selectionModel()->selectedRows();
if (!indexList.isEmpty())
{
int row = indexList.at(0).row();
ui->table->removeRow(row);
m_colorGUIs.removeAt(row);
}
}
void PagerDemodNotificationDialog::addRow(PagerDemodSettings::NotificationSettings *settings)
{
int row = ui->table->rowCount();
ui->table->setSortingEnabled(false);
ui->table->setRowCount(row + 1);
QComboBox *match = new QComboBox();
TableColorChooser *highlight;
if (settings) {
highlight = new TableColorChooser(ui->table, row, NOTIFICATION_COL_HIGHLIGHT, !settings->m_highlight, settings->m_highlightColor);
} else {
highlight = new TableColorChooser(ui->table, row, NOTIFICATION_COL_HIGHLIGHT, false, QColor(Qt::red).rgba());
}
m_colorGUIs.append(highlight);
QCheckBox *plotOnMap = new QCheckBox();
plotOnMap->setChecked(false);
QWidget *matchWidget = new QWidget();
QHBoxLayout *pLayout = new QHBoxLayout(matchWidget);
pLayout->addWidget(match);
pLayout->setAlignment(Qt::AlignCenter);
pLayout->setContentsMargins(0, 0, 0, 0);
matchWidget->setLayout(pLayout);
match->addItem("Address");
match->addItem("Message");
QTableWidgetItem *regExpItem = new QTableWidgetItem();
QTableWidgetItem *speechItem = new QTableWidgetItem();
QTableWidgetItem *commandItem = new QTableWidgetItem();
if (settings != nullptr)
{
for (unsigned int i = 0; i < m_columnMap.size(); i++)
{
if (m_columnMap[i] == settings->m_matchColumn)
{
match->setCurrentIndex(i);
break;
}
}
regExpItem->setData(Qt::DisplayRole, settings->m_regExp);
speechItem->setData(Qt::DisplayRole, settings->m_speech);
commandItem->setData(Qt::DisplayRole, settings->m_command);
plotOnMap->setChecked(settings->m_plotOnMap);
}
else
{
match->setCurrentIndex(0);
regExpItem->setData(Qt::DisplayRole, ".*");
speechItem->setData(Qt::DisplayRole, "${message}");
#ifdef _MSC_VER
commandItem->setData(Qt::DisplayRole, "cmail.exe -to:user@host.com \"-subject: Paging ${address}\" \"-body: ${message}\"");
#else
commandItem->setData(Qt::DisplayRole, "sendmail -s \"Paging ${address}: ${message}\" user@host.com");
#endif
}
ui->table->setCellWidget(row, NOTIFICATION_COL_MATCH, match);
ui->table->setItem(row, NOTIFICATION_COL_REG_EXP, regExpItem);
ui->table->setItem(row, NOTIFICATION_COL_SPEECH, speechItem);
ui->table->setItem(row, NOTIFICATION_COL_COMMAND, commandItem);
ui->table->setCellWidget(row, NOTIFICATION_COL_PLOT_ON_MAP, plotOnMap);
ui->table->setSortingEnabled(true);
}

View File

@ -0,0 +1,61 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODNOTIFICATIONDIALOG_H
#define INCLUDE_PAGERDEMODNOTIFICATIONDIALOG_H
#include <QHash>
#include "ui_pagerdemodnotificationdialog.h"
#include "pagerdemodsettings.h"
class TableColorChooser;
class PagerDemodNotificationDialog : public QDialog {
Q_OBJECT
public:
explicit PagerDemodNotificationDialog(PagerDemodSettings* settings, QWidget* parent = 0);
~PagerDemodNotificationDialog();
private:
void resizeTable();
private slots:
void accept() override;
void on_add_clicked();
void on_remove_clicked();
void addRow(PagerDemodSettings::NotificationSettings *settings=nullptr);
private:
Ui::PagerDemodNotificationDialog* ui;
PagerDemodSettings *m_settings;
QList<TableColorChooser *> m_colorGUIs;
enum NotificationCol {
NOTIFICATION_COL_MATCH,
NOTIFICATION_COL_REG_EXP,
NOTIFICATION_COL_SPEECH,
NOTIFICATION_COL_COMMAND,
NOTIFICATION_COL_HIGHLIGHT,
NOTIFICATION_COL_PLOT_ON_MAP
};
static std::vector<int> m_columnMap;
};
#endif // INCLUDE_PagerDEMODNOTIFICATIONDIALOG_H

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PagerDemodNotificationDialog</class>
<widget class="QDialog" name="PagerDemodNotificationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1100</width>
<height>400</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::PreventContextMenu</enum>
</property>
<property name="windowTitle">
<string>Notifications</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<widget class="QTableWidget" name="table">
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Match</string>
</property>
<property name="toolTip">
<string>ADS-B data to match</string>
</property>
</column>
<column>
<property name="text">
<string>Reg Exp</string>
</property>
<property name="toolTip">
<string>Regular expression to match with</string>
</property>
</column>
<column>
<property name="text">
<string>Speech</string>
</property>
<property name="toolTip">
<string>Speech for the computer to read when a match is made</string>
</property>
</column>
<column>
<property name="text">
<string>Command</string>
</property>
<property name="toolTip">
<string>Command/script to execute when a match is made</string>
</property>
</column>
<column>
<property name="text">
<string>Highlight</string>
</property>
</column>
<column>
<property name="text">
<string>Plot on Map</string>
</property>
</column>
</widget>
</item>
<item row="5" column="0">
<layout class="QHBoxLayout" name="buttonsHorizontalLayout">
<item>
<widget class="QToolButton" name="add">
<property name="toolTip">
<string>Add device set control</string>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove">
<property name="toolTip">
<string>Remove device set control</string>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item>
<spacer name="buttonsHorizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>table</tabstop>
<tabstop>add</tabstop>
<tabstop>remove</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PagerDemodNotificationDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PagerDemodNotificationDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -20,6 +20,7 @@
#include <QColor>
#include <QDataStream>
#include <QDebug>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
@ -59,6 +60,9 @@ void PagerDemodSettings::resetToDefaults()
m_reverse = false;
m_workspaceIndex = 0;
m_hidden = false;
m_filterDuplicates = false;
m_duplicateMatchMessageOnly = false;
m_duplicateMatchLastOnly = false;
for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++)
{
@ -110,6 +114,11 @@ QByteArray PagerDemodSettings::serialize() const
s.writeBlob(29, m_geometryBytes);
s.writeBool(30, m_hidden);
s.writeList(31, m_notificationSettings);
s.writeBool(32, m_filterDuplicates);
s.writeBool(33, m_duplicateMatchMessageOnly);
s.writeBool(34, m_duplicateMatchLastOnly);
for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) {
s.writeS32(100 + i, m_messageColumnIndexes[i]);
}
@ -205,6 +214,12 @@ bool PagerDemodSettings::deserialize(const QByteArray& data)
d.readBlob(29, &m_geometryBytes);
d.readBool(30, &m_hidden, false);
d.readList(31, &m_notificationSettings);
d.readBool(32, &m_filterDuplicates);
d.readBool(33, &m_duplicateMatchMessageOnly);
d.readBool(34, &m_duplicateMatchLastOnly);
for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) {
d.readS32(100 + i, &m_messageColumnIndexes[i], i);
}
@ -237,3 +252,80 @@ void PagerDemodSettings::deserializeIntList(const QByteArray& data, QList<qint32
(*stream) >> ints;
delete stream;
}
PagerDemodSettings::NotificationSettings::NotificationSettings() :
m_matchColumn(PagerDemodSettings::MESSAGE_COL_ADDRESS),
m_highlight(false),
m_highlightColor(Qt::red),
m_plotOnMap(false)
{
}
void PagerDemodSettings::NotificationSettings::updateRegularExpression()
{
m_regularExpression.setPattern(m_regExp);
m_regularExpression.optimize();
if (!m_regularExpression.isValid()) {
qDebug() << "PagerDemodSettings::NotificationSettings: Regular expression is not valid: " << m_regExp;
}
}
QByteArray PagerDemodSettings::NotificationSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_matchColumn);
s.writeString(2, m_regExp);
s.writeString(3, m_speech);
s.writeString(4, m_command);
s.writeBool(5, m_highlight);
s.writeS32(6, m_highlightColor);
s.writeBool(7, m_plotOnMap);
return s.final();
}
bool PagerDemodSettings::NotificationSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid()) {
return false;
}
if (d.getVersion() == 1)
{
QByteArray blob;
d.readS32(1, &m_matchColumn);
d.readString(2, &m_regExp);
d.readString(3, &m_speech);
d.readString(4, &m_command);
d.readBool(5, &m_highlight, false);
d.readS32(6, &m_highlightColor, QColor(Qt::red).rgba());
d.readBool(7, &m_plotOnMap, false);
updateRegularExpression();
return true;
}
else
{
return false;
}
}
QDataStream& operator<<(QDataStream& out, const PagerDemodSettings::NotificationSettings *settings)
{
out << settings->serialize();
return out;
}
QDataStream& operator>>(QDataStream& in, PagerDemodSettings::NotificationSettings*& settings)
{
settings = new PagerDemodSettings::NotificationSettings();
QByteArray data;
in >> data;
settings->deserialize(data);
return in;
}

View File

@ -23,6 +23,7 @@
#include <QByteArray>
#include <QString>
#include <QRegularExpression>
#include "dsp/dsptypes.h"
@ -33,6 +34,35 @@ class Serializable;
struct PagerDemodSettings
{
enum MessageCol {
MESSAGE_COL_DATE,
MESSAGE_COL_TIME,
MESSAGE_COL_ADDRESS,
MESSAGE_COL_MESSAGE,
MESSAGE_COL_FUNCTION,
MESSAGE_COL_ALPHA,
MESSAGE_COL_NUMERIC,
MESSAGE_COL_EVEN_PE,
MESSAGE_COL_BCH_PE
};
struct NotificationSettings {
int m_matchColumn;
QString m_regExp;
QString m_speech;
QString m_command;
bool m_highlight;
qint32 m_highlightColor;
bool m_plotOnMap;
QRegularExpression m_regularExpression;
NotificationSettings();
void updateRegularExpression();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
qint32 m_baud; //!< 512, 1200 or 2400
qint32 m_inputFrequencyOffset;
Real m_rfBandwidth;
@ -73,6 +103,12 @@ struct PagerDemodSettings
QByteArray m_geometryBytes;
bool m_hidden;
QList<NotificationSettings *> m_notificationSettings;
bool m_filterDuplicates;
bool m_duplicateMatchMessageOnly;
bool m_duplicateMatchLastOnly;
int m_messageColumnIndexes[PAGERDEMOD_MESSAGE_COLUMNS];//!< How the columns are ordered in the table
int m_messageColumnSizes[PAGERDEMOD_MESSAGE_COLUMNS]; //!< Size of the columns in the table

View File

@ -1,4 +1,4 @@
<h1>Pager demodulator plugin</h1>
<h1>Pager demodulator plugin</h1>
<h2>Introduction</h2>
@ -38,7 +38,7 @@ Specifies the pager modulation. Currently only POCSAG is supported.
POCSAG uses FSK with 4.5kHz frequency shift, at 512, 1200 or 2400 baud.
High frequency is typically 0, with low 1, but occasionally this appears to be reversed, so the demodulator supports either.
Data is framed as specified in ITU-R M.584-2: https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf
Data is framed as specified in [ITU-R M.584-2](https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf)
<h3>7: Baud</h3>
@ -86,15 +86,45 @@ IP address of the host to forward received messages to via UDP.
UDP port number to forward received messages to.
<h3>15: Start/stop Logging Messages to .csv File</h3>
<h3>15: Filter Duplicates</h3>
Check to filter (discard) duplicate messages. Right click to show the Duplicate Filter options dialog:
- Match message only: When unchecked, compare address and message. When checked, compare only message, ignoring the address.
- Match last message only: When unchecked the message is compared against all messages in the table. When checked, the message is compared against the last received message only.
<h3>16: Open Notifications Dialog</h3>
When clicked, opens the Notifications Dialog, which allows speech notifications or programs/scripts to be run when messages matching user-defined rules are received.
By running a program such as [cmail](https://www.inveigle.net/cmail/download) on Windows or sendmail on Linux, e-mail notifications can be sent containing the received message.
Messages can be highlighted in a user-defined colour, selected in the Highlight column.
By checking Plot on Map, if a message contains a position specified as latitude and longitude, the message can be displayed on the [Map](../../feature/map/readme.md) feature.
The format of the coordinates should follow [ISO 6709](https://en.wikipedia.org/wiki/ISO_6709), E.g: 50°4046″N 95°4826″W or -23.342,5.234
Here are a few examples:
![Notifications Dialog](../../../doc/img/PagerDemod_plugin_notifications.png)
In the Speech and Command strings, variables can be used to substitute data from the received message:
* ${address},
* ${message},
* ${1}, ${2}... are replaced with the string from the corresponding capture group in the regular expression.
To experiment with regular expressions, try [https://regexr.com/](https://regexr.com/).
<h3>17: Start/stop Logging Messages to .csv File</h3>
When checked, writes all received messages to a .csv file.
<h3>16: .csv Log Filename</h3>
<h3>18: .csv Log Filename</h3>
Click to specify the name of the .csv file which received messages are logged to.
<h3>17: Read Data from .csv File</h3>
<h3>19: Read Data from .csv File</h3>
Click to specify a previously written .csv log file, which is read and used to update the table.

View File

@ -25,6 +25,7 @@
<file>map/airport_small.png</file>
<file>map/heliport.png</file>
<file>map/waypoint.png</file>
<file>map/pager.png</file>
<file>map/map3d.html</file>
<file>data/transmitters.csv</file>
</qresource>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -38,6 +38,7 @@ const QStringList MapSettings::m_pipeTypes = {
QStringLiteral("FT8Demod"),
QStringLiteral("HeatMap"),
QStringLiteral("ILSDemod"),
QStringLiteral("PagerDemod"),
QStringLiteral("Radiosonde"),
QStringLiteral("StarTracker"),
QStringLiteral("SatelliteTracker"),
@ -55,6 +56,7 @@ const QStringList MapSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.ft8demod"),
QStringLiteral("sdrangel.channel.heatmap"),
QStringLiteral("sdrangel.channel.ilsdemod"),
QStringLiteral("sdrangel.channel.pagerdemod"),
QStringLiteral("sdrangel.feature.radiosonde"),
QStringLiteral("sdrangel.feature.startracker"),
QStringLiteral("sdrangel.feature.satellitetracker"),
@ -96,6 +98,7 @@ MapSettings::MapSettings() :
m_itemSettings.insert("StarTracker", new MapItemSettings("StarTracker", true, QColor(230, 230, 230), true, true, 3));
m_itemSettings.insert("SatelliteTracker", new MapItemSettings("SatelliteTracker", true, QColor(0, 0, 255), true, false, 0, modelMinPixelSize));
m_itemSettings.insert("Beacons", new MapItemSettings("Beacons", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("PagerDemod", new MapItemSettings("PagerDemod", true, QColor(200, 191, 231), true, false, 11));
m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", true, QColor(102, 0, 102), true, false, 11, modelMinPixelSize));
m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("Radar", new MapItemSettings("Radar", true, QColor(255, 0, 0), false, true, 8));

View File

@ -16,7 +16,8 @@ On top of this, it can plot data from other plugins, such as:
* Radials and estimated position from the VOR localizer feature,
* ILS course line and glide path from the ILS Demodulator,
* DSC geographic call areas,
* SID paths.
* SID paths,
* Pager messages that contain coordinates.
As well as internet and built-in data sources:
@ -313,6 +314,7 @@ Mapbox: https://www.mapbox.com/ Cesium: https://www.cesium.com Bing: https://www
Ionosonde data and MUF/coF2 contours from [KC2G](https://prop.kc2g.com/) with source data from [GIRO](https://giro.uml.edu/) and [NOAA NCEI](https://www.ngdc.noaa.gov/stp/iono/ionohome.html).
Sea Marks are from OpenSeaMap: https://www.openseamap.org/
Railways are from OpenRailwayMap: https://www.openrailwaymap.org/
@ -324,6 +326,7 @@ World icons created by turkkub from Flaticon: https://www.flaticon.com
Layers and Boat icons created by Freepik from Flaticon: https://www.flaticon.com
Railway icons created by Prosymbols Premium from Flaticon: https://www.flaticon.com
Satellite icons created by SyafriStudio from Flaticon: https://www.flaticon.com
Pager icons created by xnimrodx from Flaticon: https://www.flaticon.com
3D models are by various artists under a variety of licenses. See: https://github.com/srcejon/sdrangel-3d-models

View File

@ -167,3 +167,11 @@ QHash<QString, int> CSV::readHeader(QTextStream &in, QStringList requiredColumns
return colNumbers;
}
QString CSV::escape(const QString& string)
{
QString s = string;
s.replace('"', "\"\"");
s = QString("\"%1\"").arg(s);
return s;
}

View File

@ -52,6 +52,8 @@ struct SDRBASE_API CSV {
static bool readRow(QTextStream &in, QStringList *row, char seperator=',');
static QHash<QString, int> readHeader(QTextStream &in, QStringList requiredColumns, QString &error, char seperator=',');
static QString escape(const QString& string);
};
#endif /* INCLUDE_CSV_H */

View File

@ -269,11 +269,15 @@ public:
// Try to convert a string to latitude and longitude. Returns false if not recognised format.
// https://en.wikipedia.org/wiki/ISO_6709 specifies a standard syntax
// We support both decimal and DMS formats
static bool stringToLatitudeAndLongitude(const QString& string, float& latitude, float& longitude)
static bool stringToLatitudeAndLongitude(const QString& string, float& latitude, float& longitude, bool exact=true)
{
QRegularExpressionMatch match;
QRegularExpression decimal(QRegularExpression::anchoredPattern("(-?[0-9]+(\\.[0-9]+)?) *,? *(-?[0-9]+(\\.[0-9]+)?)"));
QString decimalPattern = "(-?[0-9]+(\\.[0-9]+)?) *,? *(-?[0-9]+(\\.[0-9]+)?)";
if (exact) {
decimalPattern = QRegularExpression::anchoredPattern(decimalPattern);
}
QRegularExpression decimal(decimalPattern);
match = decimal.match(string);
if (match.hasMatch())
{
@ -282,7 +286,11 @@ public:
return true;
}
QRegularExpression dms(QRegularExpression::anchoredPattern(QString("([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([NS]) *,? *([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([EW])").arg(QChar(0xb0))));
QString dmsPattern = QString("([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([NS]) *,? *([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([EW])").arg(QChar(0xb0));
if (exact) {
dmsPattern = QRegularExpression::anchoredPattern(dmsPattern);
}
QRegularExpression dms(dmsPattern);
match = dms.match(string);
if (match.hasMatch())
{
@ -303,7 +311,11 @@ public:
return true;
}
QRegularExpression dms2(QRegularExpression::anchoredPattern(QString("([0-9]+)([NS])([0-9]{2})([0-9]{2}) *,?([0-9]+)([EW])([0-9]{2})([0-9]{2})")));
QString dms2Pattern = "([0-9]+)([NS])([0-9]{2})([0-9]{2}) *,?([0-9]+)([EW])([0-9]{2})([0-9]{2})";
if (exact) {
dms2Pattern = QRegularExpression::anchoredPattern(dms2Pattern);
}
QRegularExpression dms2(dms2Pattern);
match = dms2.match(string);
if (match.hasMatch())
{
@ -325,7 +337,11 @@ public:
}
// 512255.5900N 0024400.6105W as used on aviation charts
QRegularExpression dms3(QRegularExpression::anchoredPattern(QString("(\\d{2})(\\d{2})((\\d{2})(\\.\\d+)?)([NS]) *,?(\\d{3})(\\d{2})((\\d{2})(\\.\\d+)?)([EW])")));
QString dms3Pattern = "(\\d{2})(\\d{2})((\\d{2})(\\.\\d+)?)([NS]) *,?(\\d{3})(\\d{2})((\\d{2})(\\.\\d+)?)([EW])";
if (exact) {
dms3Pattern = QRegularExpression::anchoredPattern(dms3Pattern);
}
QRegularExpression dms3(dms3Pattern);
match = dms3.match(string);
if (match.hasMatch())
{