diff --git a/CHANGELOG b/CHANGELOG index 2008924..1c53008 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,81 @@ # CHANGELOG +- 20220929 + + Fix for squished screen + + bump version to 1.51 + + +- 20220928 + + Only request passband when there is a scope available + + Change default passband colors. + + Fix passband colorswatch + +- 20220927 + + See if this works better for color pickers? + + Ignore second VFO scope data (for now) + + Silently ignore server audio issues if server is not enabled. + + Always use 8bit encoding for audio device names + + Make multimedia-plugins message only for Linux + +- 20220926 + + Add PSK modes to IC-7610 and to passband. + + Add passband for FM mode + + Added click-drag tuning. Needs refinement but it's a start. + + colorpicker: Move to 3 columns + + First look at a passband indicator + +- 20220925 + + Remove obsolete code + + Remove logging of audio device realm + +- 20220923 + + ready for v1.50 + +- 20220923 + + Delete quit confirmation checkbox + + Use date/time for log name if none specified + + Fix logfile/directory opening in Windows + + Fixed glitch when tabs are changed prior to rigcaps. + + Remove redundant CL args + + Additional resize fixes for non-spectrum rigs. + + Hide UI elements not needed for non-spectrum radios. + + updated to v1.48 + +- 20220921 + + Fix that was stopping users being created when none existed + - 20220920 Added dialog box to the toFixed button where an edge can be selected. + + Add quick fix for rigctld fmv issue updated to v1.47 diff --git a/WHATSNEW b/WHATSNEW index 6b0b567..f7f88e9 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -1,44 +1,25 @@ -The following highlights are in this 1.41-release: +The following highlights are in this 1.51-release ince v1.50: - New major change is the audio transport mechanism. Lower latencies. - About box updated to include Patreon site - added 500 Hz step for VFO - Added clock and UTC toggle. - Added forced manual RTS setting - Add RIT function and other rigctl fixes - Adjusted window size for radios without spectrum. Thanks K5TUX. - Allow dynamic restarting of server - New Settings tab - Enable High DPI Scaling - More multi-radio support (mostly working!) - Split LAN waterfall data for N1MM+ Spectrum Scope support: There is a combobox that allows you to select - split/combine (or default). Split only makes sense for LAN and Combine for USB. - added radio status display with meters for audio (speaker/mic) - selector for audio: QT, PortAudio, RealTime audio - version bumped to 1.4 -- rethinking of a new version schema that makes more sense - temporary squashed logs; may redo later - audio fixes at exit - introduction of peak decays at the scope - resizing of top and bottom scope/waterfall - various fixes on the spectrum display -+ 1.41 added color picker support for all kinds of vsual elements -+ 1.42 added three additional second meter choices. RxAudio, TxAdio, and TxRxAudio -+ 1.43 fixed resizing issues. -+ 1.44 added logging window. you can send the logs to termbin.com and share the link on the forum -+ 1.45 some more log enhancements - moved connect button and added cancel option - add enable/disable audioSystemServerCombo - Fixed bug where the frequency dial skipped extra when crossing zero. -+ 1.46 Added controls for custom scope edges, hide/show scope controls - depending upon the scope mode the radio reports. - Fixed clear peaks button to work with the plasma underlay. - both audio system button are disabled when connected to radio and enabled when not. - Remove password from log - Fix server user handling -+ 1.47 Added dialog box to the toFixed button where an edge can be selected. ++ 1.51 + + Fix for squished screen + Only request passband when there is a scope available + Change default passband colors. + Fix passband colorswatch + Ignore second VFO scope data (for now) + Silently ignore server audio issues if server is not enabled. + Always use 8bit encoding for audio device names + Make multimedia-plugins message only for Linux + Add PSK modes to IC-7610 and to passband. + Add passband for FM mode + Added click-drag tuning. Needs refinement but it's a start. + colorpicker: Move to 3 columns + passband indicator + Remove logging of audio device realm + Notes: - We know about high CPU usage on RPi. diff --git a/audiohandler.cpp b/audiohandler.cpp index 2244960..5628f4c 100644 --- a/audiohandler.cpp +++ b/audiohandler.cpp @@ -46,7 +46,11 @@ audioHandler::~audioHandler() qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "audio handler starting:" << setup.name; if (setup.port.isNull()) { - qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found. You probably need to install libqt5multimedia-plugins."; +#ifdef Q_OS_LINUX + qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found. You probably need to install libqt5multimedia-plugins."; +#else + qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "Audio device is NULL, please check device selection in settings."; +#endif return false; } diff --git a/cluster.cpp b/cluster.cpp new file mode 100644 index 0000000..eaec4a2 --- /dev/null +++ b/cluster.cpp @@ -0,0 +1,291 @@ +#include "cluster.h" +#include "logcategories.h" + + +dxClusterClient::dxClusterClient(QObject* parent): + QObject(parent) +{ + qInfo(logCluster()) << "starting dxClusterClient()"; +} + +dxClusterClient::~dxClusterClient() +{ + qInfo(logCluster()) << "closing dxClusterClient()"; + enableUdp(false); + enableTcp(false); +#ifdef USESQL + database db; + db.close(); +#else + QMap::iterator spot = allSpots.begin(); + while (spot != allSpots.end()) + { + delete spot.value(); // Stop memory leak? + spot = allSpots.erase(spot); + } +#endif +} + +void dxClusterClient::enableUdp(bool enable) +{ + udpEnable = enable; + if (enable) + { + if (udpSocket == Q_NULLPTR) + { + udpSocket = new QUdpSocket(this); + bool result = udpSocket->bind(QHostAddress::AnyIPv4, udpPort); + qInfo(logCluster()) << "Starting udpSocket() on:" << udpPort << "Result:" << result; + + connect(udpSocket, SIGNAL(readyRead()), this, SLOT(udpDataReceived()), Qt::QueuedConnection); + } + } + else { + if (udpSocket != Q_NULLPTR) + { + qInfo(logCluster()) << "Stopping udpSocket() on:" << udpPort; + + udpSocket->disconnect(); + delete udpSocket; + udpSocket = Q_NULLPTR; + } + } +} + +void dxClusterClient::enableTcp(bool enable) +{ + tcpEnable = enable; + if (enable) + { + tcpRegex = QRegularExpression("^DX de ([a-z|A-Z|0-9|/]+):\\s+([0-9|.]+)\\s+([a-z|A-Z|0-9|/]+)+\\s+(.*)\\s+(\\d{4}Z)"); + + if (tcpSocket == Q_NULLPTR) + { + tcpSocket = new QTcpSocket(this); + tcpSocket->connectToHost(tcpServerName, tcpPort); + qInfo(logCluster()) << "Starting tcpSocket() on:" << tcpPort; + + connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(tcpDataReceived()), Qt::QueuedConnection); + connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(tcpDisconnected())); + + tcpCleanupTimer = new QTimer(this); + tcpCleanupTimer->setInterval(1000 * 10); // Run once a minute + connect(tcpCleanupTimer, SIGNAL(timeout()), this, SLOT(tcpCleanup())); + tcpCleanupTimer->start(); + } + } + else { + if (tcpSocket != Q_NULLPTR) + { + qInfo(logCluster()) << "Disconnecting tcpSocket() on:" << tcpPort; + if (tcpCleanupTimer != Q_NULLPTR) + { + tcpCleanupTimer->stop(); + delete tcpCleanupTimer; + tcpCleanupTimer = Q_NULLPTR; + } + tcpSocket->disconnect(); + delete tcpSocket; + tcpSocket = Q_NULLPTR; + //emit deleteOldSpots(0); + } + } +} + +void dxClusterClient::udpDataReceived() +{ + QHostAddress sender; + quint16 port; + QByteArray datagram; + datagram.resize(udpSocket->pendingDatagramSize()); + udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &port); + + if (udpSpotReader.setContent(datagram)) + { + QDomElement spot = udpSpotReader.firstChildElement("spot"); + if (spot.nodeName() == "spot") + { + // This is a spot? + QString action = spot.firstChildElement("action").text(); + if (action == "add") { + spotData* data = new spotData(); + data->dxcall = spot.firstChildElement("dxcall").text(); + data->frequency = spot.firstChildElement("frequency").text().toDouble() / 1000.0; + data->spottercall = spot.firstChildElement("spottercall").text(); + data->timestamp = QDateTime::fromString(spot.firstChildElement("timestamp").text(),"yyyy-MM-dd hh:mm:ss"); + data->mode = spot.firstChildElement("mode").text(); + data->comment = spot.firstChildElement("comment").text(); + +#ifdef USESQL + database db = database(); + db.query(QString("DELETE from spots where dxcall='%1'").arg(data->dxcall)); + QString query = QString("INSERT INTO spots(type,spottercall,frequency,dxcall,mode,comment,timestamp) VALUES('%1','%2',%3,'%4','%5','%6','%7')\n") + .arg("UDP").arg(data->spottercall).arg(data->frequency).arg(data->dxcall).arg(data->mode).arg(data->comment).arg(data->timestamp.toString("yyyy-MM-dd hh:mm:ss")); + db.query(query); +#else + bool found = false; + QMap::iterator spot = allSpots.find(data->dxcall); + while (spot != allSpots.end() && spot.key() == data->dxcall && spot.value()->frequency == data->frequency) { + found = true; + ++spot; + } + if (found == false) { + allSpots.insert(data->dxcall, data); + } +#endif + emit sendOutput(QString("add%1%2%3%4\n") + .arg(data->dxcall).arg(data->spottercall).arg(data->frequency).arg(data->comment)); + + } + else if (action == "delete") + { + QString dxcall = spot.firstChildElement("dxcall").text(); + double frequency = spot.firstChildElement("frequency").text().toDouble() / 1000.0; + +#ifdef USESQL + database db = database(); + QString query=QString("DELETE from spots where dxcall='%1' AND frequency=%2").arg(dxcall).arg(frequency); + db.query(query); + qInfo(logCluster()) << query; +#else + QMap::iterator spot = allSpots.find(dxcall); + while (spot != allSpots.end() && spot.key() == dxcall && spot.value()->frequency == frequency) + { + delete spot.value(); // Stop memory leak? + spot = allSpots.erase(spot); + } +#endif + emit sendOutput(QString("delete%1%3\n") + .arg(dxcall).arg(frequency)); + } + updateSpots(); + } + } +} + + +void dxClusterClient::tcpDataReceived() +{ + QString data = QString(tcpSocket->readAll()); + + emit sendOutput(data); + if (data.contains("login:")) { + sendTcpData(QString("%1\n").arg(tcpUserName)); + return; + } + if (data.contains("password:")) { + sendTcpData(QString("%1\n").arg(tcpPassword)); + return; + } + + QRegularExpressionMatchIterator i = tcpRegex.globalMatch(data); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + if (match.hasMatch()) { + spotData* data = new spotData(); + data->spottercall = match.captured(1); + data->frequency = match.captured(2).toDouble() / 1000.0; + data->dxcall = match.captured(3); + data->comment = match.captured(4).trimmed(); + data->timestamp = QDateTime::currentDateTimeUtc(); + +#ifdef USESQL + database db = database(); + db.query(QString("DELETE from spots where dxcall='%1'").arg(data->dxcall)); + QString query = QString("INSERT INTO spots(type,spottercall,frequency,dxcall,comment,timestamp) VALUES('%1','%2',%3,'%4','%5','%6')\n") + .arg("TCP").arg(data->spottercall).arg(data->frequency).arg(data->dxcall).arg(data->comment).arg(data->timestamp.toString("yyyy-MM-dd hh:mm:ss")); + db.query(query); +#else + bool found = false; + QMap::iterator spot = allSpots.find(data->dxcall); + while (spot != allSpots.end() && spot.key() == data->dxcall && spot.value()->frequency == data->frequency) { + found = true; + ++spot; + } + if (found == false) { + allSpots.insert(data->dxcall, data); + } +#endif + } + } + updateSpots(); +} + + +void dxClusterClient::sendTcpData(QString data) +{ + qInfo(logCluster()) << "Sending:" << data; + if (tcpSocket != Q_NULLPTR && tcpSocket->isValid() && tcpSocket->isOpen()) + { + tcpSocket->write(data.toLatin1()); + } + else + { + qInfo(logCluster()) << "socket not open!"; + } +} + +void dxClusterClient::tcpCleanup() +{ +#ifdef USESQL + database db = database(); + db.query(QString("DELETE FROM spots where timestamp < datetime('now', '-%1 minutes')").arg(tcpTimeout)); +#else + QMap::iterator spot = allSpots.begin();; + while (spot != allSpots.end()) { + if (spot.value()->timestamp.addSecs(tcpTimeout * 60) < QDateTime::currentDateTimeUtc()) + { + delete spot.value(); // Stop memory leak? + spot = allSpots.erase(spot); + } + else + { + ++spot; + } + } +#endif +} + +void dxClusterClient::tcpDisconnected() { + qWarning(logCluster()) << "TCP Cluster server disconnected..."; + // Need to start a timer and attempt reconnect. +} + +void dxClusterClient::freqRange(double low, double high) +{ + lowFreq = low; + highFreq = high; + //qInfo(logCluster) << "New range" << low << "-" << high; + updateSpots(); +} + +void dxClusterClient::updateSpots() +{ + QList spots; +#ifdef USESQL + // Set the required frequency range. + QString queryText = QString("SELECT * FROM spots WHERE frequency > %1 AND frequency < %2").arg(lowFreq).arg(highFreq); + //QString queryText = QString("SELECT * FROM spots"); + database db; + auto query = db.query(queryText); + + while (query.next()) { + // Step through all current spots within range + spotData s = spotData(); + s.dxcall = query.value(query.record().indexOf("dxcall")).toString(); + s.frequency = query.value(query.record().indexOf("frequency")).toDouble(); + spots.append(s); + } +#else + QMap::iterator spot = allSpots.begin();; + while (spot != allSpots.end()) { + if (spot.value()->frequency > lowFreq && spot.value()->frequency < highFreq) + { + spots.append(**spot); + } + ++spot; + } + +#endif + emit sendSpots(spots); +} diff --git a/cluster.h b/cluster.h new file mode 100644 index 0000000..ba357ae --- /dev/null +++ b/cluster.h @@ -0,0 +1,104 @@ +#ifndef CLUSTER_H +#define CLUSTER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USESQL +#include +#include +#endif + +#include + +#ifdef USESQL +#include "database.h" +#endif + +struct spotData { + QString dxcall; + double frequency; + QString spottercall; + QDateTime timestamp; + QString mode; + QString comment; + QCPItemText* text = Q_NULLPTR; + bool current = false; +}; + +struct clusterSettings { + QString server; + int port=7300; + QString userName; + QString password; + int timeout=30; + bool isdefault; +}; + +class dxClusterClient : public QObject +{ + Q_OBJECT + +public: + explicit dxClusterClient(QObject* parent = nullptr); + virtual ~dxClusterClient(); + +signals: + void addSpot(spotData* spot); + void deleteSpot(QString dxcall); + void deleteOldSpots(int minutes); + void sendOutput(QString text); + void sendSpots(QList spots); + +public slots: + void udpDataReceived(); + void tcpDataReceived(); + void tcpDisconnected(); + void enableUdp(bool enable); + void enableTcp(bool enable); + void setUdpPort(int p) { udpPort = p; } + void setTcpServerName(QString s) { tcpServerName = s; } + void setTcpPort(int p) { tcpPort = p; } + void setTcpUserName(QString s) { tcpUserName = s; } + void setTcpPassword(QString s) { tcpPassword = s; } + void setTcpTimeout(int p) { tcpTimeout = p; } + void tcpCleanup(); + void freqRange(double low, double high); + +private: + void sendTcpData(QString data); + bool databaseOpen(); + void updateSpots(); + + bool udpEnable; + bool tcpEnable; + QUdpSocket* udpSocket=Q_NULLPTR; + QTcpSocket* tcpSocket=Q_NULLPTR; + int udpPort; + QString tcpServerName; + int tcpPort; + QString tcpUserName; + QString tcpPassword; + int tcpTimeout; + QDomDocument udpSpotReader; + QRegularExpression tcpRegex; + QMutex mutex; + bool authenticated=false; + QTimer* tcpCleanupTimer=Q_NULLPTR; +#ifdef USESQL + QSqlDatabase db; +#endif + double lowFreq; + double highFreq; + QMap allSpots; +}; + +#endif diff --git a/colorprefs.h b/colorprefs.h index 7db11fc..7e8ddd6 100644 --- a/colorprefs.h +++ b/colorprefs.h @@ -18,6 +18,7 @@ struct colorPrefsType{ QColor underlayFill; QColor plotBackground; QColor tuningLine; + QColor passband; // Waterfall: QColor wfBackground; @@ -32,6 +33,10 @@ struct colorPrefsType{ QColor meterPeakScale; QColor meterLowerLine; QColor meterLowText; + + // Assorted + QColor clusterSpots; + }; diff --git a/database.cpp b/database.cpp new file mode 100644 index 0000000..178399b --- /dev/null +++ b/database.cpp @@ -0,0 +1,107 @@ +#ifdef USESQL + +#include "database.h" +#include "logcategories.h" + +database::database() +{ + open(); +} + +database::~database() +{ +} + +bool database::open() +{ + auto name = "my_db_" + QString::number((quint64)QThread::currentThread(), 16); + if (QSqlDatabase::contains(name)) + { + db = QSqlDatabase::database(name); + qu = QSqlQuery(db); + return true; + } + else { + qInfo(logCluster()) << "Creating new connection" << name; + db = QSqlDatabase::addDatabase("QSQLITE", name); + qu = QSqlQuery(db); + } + + //QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + "wfview.db"; + QString path = ":memory:"; + qInfo(logCluster()) << "DB Filename" << path; + db.setDatabaseName(path); + if (db.isValid()) + { + db.open(); + if (check()) { + return true; + } + } + qWarning(logCluster()) << "Database is not valid!"; + return false; +} + +void database::close() +{ + auto name = "my_db_" + QString::number((quint64)QThread::currentThread(), 16); + qInfo(logCluster()) << "Closing database connection:" << name; + db.close(); +} + + + +QSqlQuery database::query(QString query) +{ + if (!db.isOpen()) + { + qWarning(logCluster()) << "Query Database is not open!"; + db.open(); + } + qu.exec(query); + return qu; +} + + + +bool database::check() +{ + if (db.isOpen()) { + for (const auto& table : db.tables()) + { + if (table == "spots") + { + qInfo(logCluster()) << "DB Contains spots table"; + return true; + } + } + qInfo(logCluster()) << "Creating spots table"; + // Spots table does not exist, need to create it. + /* + QString dxcall; + double frequency; + QString spottercall; + QDateTime timestamp; + QString mode; + QString comment; + QCPItemText* text = Q_NULLPTR;*/ + qu.exec("CREATE TABLE spots " + "(id INTEGER PRIMARY KEY, " + "type VARCHAR(3)," + "dxcall VARCHAR(30)," + "spottercall VARCHAR(30)," + "frequency DOUBLE," + "timestamp DATETIME," + "mode VARCHAR(30)," + "comment VARCHAR(255) )"); + qu.exec("CREATE INDEX spots_index ON spots(type,dxcall,frequency,timestamp)"); + return true; + } + else { + qWarning(logCluster()) << "Database is not open"; + + } + return false; +} + +#endif \ No newline at end of file diff --git a/database.h b/database.h new file mode 100644 index 0000000..7e80e9e --- /dev/null +++ b/database.h @@ -0,0 +1,36 @@ +#ifdef USESQL + +#ifndef DATABASE_H +#define DATABASE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class database +{ +public: + explicit database(); + virtual ~database(); + bool open(); + void close(); + QSqlQuery query(QString query); + +signals: + +public slots: +private: + bool check(); + QSqlDatabase db; + QSqlQuery qu; + +}; + +#endif + +#endif diff --git a/freqmemory.h b/freqmemory.h index 96d3c6b..0067158 100644 --- a/freqmemory.h +++ b/freqmemory.h @@ -17,6 +17,8 @@ enum mode_kind { modeFM=0x05, modeCW_R=0x07, modeRTTY_R=0x08, + modePSK = 0x12, + modePSK_R = 0x13, modeLSB_D=0x80, modeUSB_D=0x81, modeDV=0x17, @@ -29,9 +31,7 @@ enum mode_kind { modedPMR, modeNXDN_VN, modeNXDN_N, - modeDCR, - modePSK, - modePSK_R + modeDCR }; struct mode_info { diff --git a/logcategories.cpp b/logcategories.cpp index 19e183e..527f619 100644 --- a/logcategories.cpp +++ b/logcategories.cpp @@ -13,3 +13,4 @@ Q_LOGGING_CATEGORY(logRigCtlD, "rigctld") Q_LOGGING_CATEGORY(logTcpServer, "tcpserver") Q_LOGGING_CATEGORY(logUsbControl, "usbcontrol") Q_LOGGING_CATEGORY(logAudioConverter, "audioconverter") +Q_LOGGING_CATEGORY(logCluster, "cluster") diff --git a/logcategories.h b/logcategories.h index f5b0a12..38c6b56 100644 --- a/logcategories.h +++ b/logcategories.h @@ -16,6 +16,7 @@ Q_DECLARE_LOGGING_CATEGORY(logRigCtlD) Q_DECLARE_LOGGING_CATEGORY(logTcpServer) Q_DECLARE_LOGGING_CATEGORY(logUsbControl) Q_DECLARE_LOGGING_CATEGORY(logAudioConverter) +Q_DECLARE_LOGGING_CATEGORY(logCluster) #if defined(Q_OS_WIN) && !defined(__PRETTY_FUNCTION__) diff --git a/loggingwindow.cpp b/loggingwindow.cpp index c9ad5be..709db1b 100644 --- a/loggingwindow.cpp +++ b/loggingwindow.cpp @@ -1,9 +1,10 @@ #include "loggingwindow.h" #include "ui_loggingwindow.h" -loggingWindow::loggingWindow(QWidget *parent) : +loggingWindow::loggingWindow(QString logFilename, QWidget *parent) : QWidget(parent), - ui(new Ui::loggingWindow) + ui(new Ui::loggingWindow), + logFilename(logFilename) { ui->setupUi(this); this->setWindowTitle("Log"); @@ -12,20 +13,15 @@ loggingWindow::loggingWindow(QWidget *parent) : ui->annotateBtn->setDefault(true); ui->logTextDisplay->setFocusPolicy(Qt::NoFocus); ui->annotateBtn->setFocusPolicy(Qt::NoFocus); + + QDir d = QFileInfo(logFilename).absoluteDir(); + logDirectory = d.absolutePath(); QFont font("Monospace"); font.setStyleHint(QFont::TypeWriter); ui->logTextDisplay->setFont(font); ui->userAnnotationText->setFont(font); -#ifdef Q_OS_MAC - logFilename = QStandardPaths::standardLocations(QStandardPaths::DownloadLocation)[0] + "/wfview.log"; - logDirectory = QStandardPaths::standardLocations(QStandardPaths::DownloadLocation)[0]; - -#else - logFilename= QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0] + "/wfview.log"; - logDirectory = QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0]; -#endif clipboard = QApplication::clipboard(); socket = new QTcpSocket(this); connect(socket, SIGNAL(connected()), this, SLOT(connectedToHost())); @@ -142,37 +138,39 @@ void loggingWindow::on_clearDisplayBtn_clicked() void loggingWindow::on_openDirBtn_clicked() { QString cmd; - int rtnval = 0; -#ifdef Q_OS_MAC - cmd = "open " + logDirectory; -#endif + bool rtn = false; + QStringList arg; + const QFileInfo dir(logDirectory); + #ifdef Q_OS_LINUX - cmd = "xdg-open " + logDirectory; + cmd = "xdg-open"; +#elif defined(Q_OS_WIN) + cmd = QStandardPaths::findExecutable("explorer.exe"); + if (!dir.isDir()) + arg += QLatin1String("/select,"); +#else + cmd = "open"; #endif -#ifdef Q_OS_WIN - cmd = "start " + logDirectory; -#endif - rtnval = system(cmd.toLocal8Bit().data()); - if(rtnval) - qInfo(logLogger()) << "Error, open log directory command returned error code " << rtnval; + arg += QDir::toNativeSeparators(dir.canonicalFilePath());; + rtn = QProcess::startDetached(cmd, arg); + if(!rtn) + qInfo(logLogger()) << "Error, open log directory" << logDirectory << "command failed"; } void loggingWindow::on_openLogFileBtn_clicked() { QString cmd; - int rtnval = 0; -#ifdef Q_OS_MAC - cmd = "open " + logFilename; -#endif + bool rtn = false; #ifdef Q_OS_LINUX - cmd = "xdg-open " + logFilename; + cmd = "xdg-open"; +#elif defined(Q_OS_WIN) + cmd = QStandardPaths::findExecutable("notepad.exe"); +#else + cmd = "open"; #endif -#ifdef Q_OS_WIN - cmd = "notepad " + logFilename; -#endif - rtnval = system(cmd.toLocal8Bit().data()); - if(rtnval) - qInfo(logLogger()) << "Error, open log file command returned error code " << rtnval; + rtn = QProcess::startDetached(cmd, { logFilename }); + if(!rtn) + qInfo(logLogger()) << "Error, open log file command failed"; } void loggingWindow::on_sendToPasteBtn_clicked() diff --git a/loggingwindow.h b/loggingwindow.h index dda0efa..d453eb7 100644 --- a/loggingwindow.h +++ b/loggingwindow.h @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include "logcategories.h" @@ -22,7 +25,7 @@ class loggingWindow : public QWidget Q_OBJECT public: - explicit loggingWindow(QWidget *parent = nullptr); + explicit loggingWindow(QString logFilename, QWidget *parent = 0); ~loggingWindow(); void acceptLogText(QString text); @@ -57,14 +60,14 @@ signals: void setDebugMode(bool debugOn); private: - Ui::loggingWindow *ui; + Ui::loggingWindow* ui; + QString logFilename; + QString logDirectory; QClipboard *clipboard; QMessageBox URLmsgBox; QScrollBar *vertLogScroll; QScrollBar *horizLogScroll; QMutex textMutex; - QString logFilename; - QString logDirectory; QTcpSocket *socket; void sendToTermbin(); }; diff --git a/main.cpp b/main.cpp index a16b010..00dbacd 100644 --- a/main.cpp +++ b/main.cpp @@ -71,19 +71,15 @@ int main(int argc, char *argv[]) //debugMode = true; #endif - QString serialPortCL; - QString hostCL; - QString civCL; -#ifdef Q_OS_MAC - QString logFilename= QStandardPaths::standardLocations(QStandardPaths::DownloadLocation)[0] + "/wfview.log"; -#else - QString logFilename= QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0] + "/wfview.log"; -#endif + QDateTime date = QDateTime::currentDateTime(); + QString formattedTime = date.toString("dd.MM.yyyy hh:mm:ss"); + QString logFilename = (QString("%1/%2-%3.log").arg(QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0]).arg(a.applicationName()).arg(date.toString("yyyyMMddhhmmss"))); + QString settingsFile = NULL; QString currentArg; - const QString helpText = QString("\nUsage: -p --port /dev/port, -h --host remotehostname, -c --civ 0xAddr, -l --logfile filename.log, -s --settings filename.ini, -d --debug, -v --version\n"); // TODO... + const QString helpText = QString("\nUsage: -l --logfile filename.log, -s --settings filename.ini, -d --debug, -v --version\n"); // TODO... #ifdef BUILD_WFSERVER const QString version = QString("wfserver version: %1 (Git:%2 on %3 at %4 by %5@%6)\nOperating System: %7 (%8)\nBuild Qt Version %9. Current Qt Version: %10\n") .arg(QString(WFVIEW_VERSION)) @@ -102,34 +98,10 @@ int main(int argc, char *argv[]) //qInfo() << "Argc: " << c << " argument: " << argv[c]; currentArg = QString(argv[c]); - if ((currentArg == "-p") || (currentArg == "--port")) - { - if (argc > c) - { - serialPortCL = argv[c + 1]; - c += 1; - } - } - else if ((currentArg == "-d") || (currentArg == "--debug")) + if ((currentArg == "-d") || (currentArg == "--debug")) { debugMode = true; } - else if ((currentArg == "-h") || (currentArg == "--host")) - { - if(argc > c) - { - hostCL = argv[c+1]; - c+=1; - } - } - else if ((currentArg == "-c") || (currentArg == "--civ")) - { - if (argc > c) - { - civCL = argv[c + 1]; - c += 1; - } - } else if ((currentArg == "-l") || (currentArg == "--logfile")) { if (argc > c) @@ -173,9 +145,7 @@ int main(int argc, char *argv[]) qInstallMessageHandler(messageHandler); qInfo(logSystem()) << version; - qDebug(logSystem()) << QString("SerialPortCL as set by parser: %1").arg(serialPortCL); - qDebug(logSystem()) << QString("remote host as set by parser: %1").arg(hostCL); - qDebug(logSystem()) << QString("CIV as set by parser: %1").arg(civCL); + #endif #ifdef BUILD_WFSERVER #ifdef Q_OS_WIN @@ -185,10 +155,10 @@ int main(int argc, char *argv[]) signal(SIGTERM, cleanup); signal(SIGKILL, cleanup); #endif - w = new servermain(serialPortCL, hostCL, settingsFile); + w = new servermain(logFilename, settingsFile); #else a.setWheelScrollLines(1); // one line per wheel click - wfmain w(serialPortCL, hostCL, settingsFile, debugMode); + wfmain w(settingsFile, logFilename, debugMode); w.show(); #endif diff --git a/rigcommander.cpp b/rigcommander.cpp index d6ea80c..1d2a30e 100644 --- a/rigcommander.cpp +++ b/rigcommander.cpp @@ -872,6 +872,13 @@ void rigCommander::getDuplexMode() prepDataAndSend(payload); } +void rigCommander::getPassband() +{ + QByteArray payload; + payload.setRawData("\x1A\x03", 2); + prepDataAndSend(payload); +} + void rigCommander::getTransmitFrequency() { QByteArray payload; @@ -2511,6 +2518,9 @@ void rigCommander::parseRegisters1A() // band stacking register parseBandStackReg(); break; + case '\x03': + emit havePassband(bcdHexToUChar((quint8)payloadIn[2])); + break; case '\x04': state.set(AGC, (quint8)payloadIn[2], false); break; @@ -3239,8 +3249,8 @@ void rigCommander::determineRigCaps() rigCaps.bands.push_back(bandGen); rigCaps.bsr[bandGen] = 0x11; rigCaps.modes = commonModes; - rigCaps.modes.insert(rigCaps.modes.end(), {createMode(modePSK, 0x12, "PSK"), - createMode(modePSK_R, 0x13, "PSK-R")}); + rigCaps.modes.insert(rigCaps.modes.end(), { createMode(modePSK, 0x12, "PSK"), + createMode(modePSK_R, 0x13, "PSK-R") }); rigCaps.transceiveCommand = QByteArrayLiteral("\x1a\x05\x00\x97"); break; case model7610: @@ -3273,6 +3283,8 @@ void rigCommander::determineRigCaps() rigCaps.bands.push_back(band630m); rigCaps.bands.push_back(band2200m); rigCaps.modes = commonModes; + rigCaps.modes.insert(rigCaps.modes.end(), { createMode(modePSK, 0x12, "PSK"), + createMode(modePSK_R, 0x13, "PSK-R") }); rigCaps.hasRXAntenna = true; rigCaps.transceiveCommand = QByteArrayLiteral("\x1a\x05\x01\x12"); break; @@ -3771,9 +3783,16 @@ void rigCommander::parseSpectrum() freqt fStart; freqt fEnd; + unsigned char vfo = bcdHexToUChar(payloadIn[02]); unsigned char sequence = bcdHexToUChar(payloadIn[03]); + //unsigned char sequenceMax = bcdHexToDecimal(payloadIn[04]); + if (vfo == 1) + { + // This is for the second VFO! + return; + } // unsigned char waveInfo = payloadIn[06]; // really just one byte? //qInfo(logRig()) << "Spectrum Data received: " << sequence << "/" << sequenceMax << " mode: " << scopeMode << " waveInfo: " << waveInfo << " length: " << payloadIn.length(); diff --git a/rigcommander.h b/rigcommander.h index e26941e..3655e41 100644 --- a/rigcommander.h +++ b/rigcommander.h @@ -157,6 +157,8 @@ public slots: void setManualNotch(bool enabled); void getManualNotch(); + void getPassband(); + // Repeater: void setDuplexMode(duplexMode dm); void getDuplexMode(); @@ -321,6 +323,7 @@ signals: void haveBandStackReg(freqt f, char mode, char filter, bool dataOn); void haveRitEnabled(bool ritEnabled); void haveRitFrequency(int ritHz); + void havePassband(quint8 pass); // Repeater: void haveDuplexMode(duplexMode); diff --git a/rigidentities.h b/rigidentities.h index a668df5..f32b0af 100644 --- a/rigidentities.h +++ b/rigidentities.h @@ -107,7 +107,7 @@ struct rigCapabilities { QVector inputs; - bool hasSpectrum; + bool hasSpectrum=true; quint8 spectSeqMax; quint16 spectAmpMax; quint16 spectLenMax; diff --git a/servermain.cpp b/servermain.cpp index c2942c5..3257bfa 100644 --- a/servermain.cpp +++ b/servermain.cpp @@ -8,10 +8,8 @@ // This code is copyright 2017-2020 Elliott H. Liggett // All rights reserved -servermain::servermain(const QString serialPortCL, const QString hostCL, const QString settingsFile) +servermain::servermain(const QString settingsFile, const QString logFile) { - this->serialPortCL = serialPortCL; - this->hostCL = hostCL; qRegisterMetaType (); // Needs to be registered early. qRegisterMetaType (); diff --git a/servermain.h b/servermain.h index 1b7e3a8..b19d8ca 100644 --- a/servermain.h +++ b/servermain.h @@ -46,9 +46,7 @@ class servermain : public QObject Q_OBJECT public: - servermain(const QString serialPortCL, const QString hostCL, const QString settingsFile); - QString serialPortCL; - QString hostCL; + servermain(const QString logFile, const QString settingsFile); ~servermain(); signals: diff --git a/wfmain.cpp b/wfmain.cpp index ee5aab2..7224f50 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -20,9 +20,10 @@ bool debugModeLogging = true; bool debugModeLogging = false; #endif -wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString settingsFile, bool debugMode, QWidget *parent ) : +wfmain::wfmain(const QString settingsFile, const QString logFile, bool debugMode, QWidget *parent ) : QMainWindow(parent), - ui(new Ui::wfmain) + ui(new Ui::wfmain), + logFilename(logFile) { QGuiApplication::setApplicationDisplayName("wfview"); QGuiApplication::setApplicationName(QString("wfview")); @@ -38,14 +39,11 @@ wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString s ui->setupUi(this); setWindowTitle(QString("wfview")); - logWindow = new loggingWindow(); + logWindow = new loggingWindow(logFile); initLogging(); logWindow->setInitialDebugState(debugMode); qInfo(logSystem()) << version; - this->serialPortCL = serialPortCL; - this->hostCL = hostCL; - cal = new calibrationWindow(); rpt = new repeaterSetup(); sat = new satelliteSetup(); @@ -63,6 +61,7 @@ wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString s qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType (); qRegisterMetaType (); @@ -73,6 +72,8 @@ wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString s qRegisterMetaType*>(); qRegisterMetaType*>(); qRegisterMetaType(); + qRegisterMetaType>(); + qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType(); @@ -114,6 +115,46 @@ wfmain::wfmain(const QString serialPortCL, const QString hostCL, const QString s rigConnections(); + cluster = new dxClusterClient(); + + clusterThread = new QThread(this); + clusterThread->setObjectName("dxcluster()"); + + cluster->moveToThread(clusterThread); + + connect(this, SIGNAL(setClusterEnableUdp(bool)), cluster, SLOT(enableUdp(bool))); + connect(this, SIGNAL(setClusterEnableTcp(bool)), cluster, SLOT(enableTcp(bool))); + connect(this, SIGNAL(setClusterUdpPort(int)), cluster, SLOT(setUdpPort(int))); + connect(this, SIGNAL(setClusterServerName(QString)), cluster, SLOT(setTcpServerName(QString))); + connect(this, SIGNAL(setClusterTcpPort(int)), cluster, SLOT(setTcpPort(int))); + connect(this, SIGNAL(setClusterUserName(QString)), cluster, SLOT(setTcpUserName(QString))); + connect(this, SIGNAL(setClusterPassword(QString)), cluster, SLOT(setTcpPassword(QString))); + connect(this, SIGNAL(setClusterTimeout(int)), cluster, SLOT(setTcpTimeout(int))); + connect(this, SIGNAL(setFrequencyRange(double, double)), cluster, SLOT(freqRange(double, double))); + + connect(cluster, SIGNAL(sendSpots(QList)), this, SLOT(receiveSpots(QList))); + connect(cluster, SIGNAL(sendOutput(QString)), this, SLOT(receiveClusterOutput(QString))); + + connect(clusterThread, SIGNAL(finished()), cluster, SLOT(deleteLater())); + + clusterThread->start(); + + emit setClusterUdpPort(prefs.clusterUdpPort); + emit setClusterEnableUdp(prefs.clusterUdpEnable); + + for (int f = 0; f < clusters.size(); f++) + { + if (clusters[f].isdefault) + { + emit setClusterServerName(clusters[f].server); + emit setClusterTcpPort(clusters[f].port); + emit setClusterUserName(clusters[f].userName); + emit setClusterPassword(clusters[f].password); + emit setClusterTimeout(clusters[f].timeout); + } + } + emit setClusterEnableTcp(prefs.clusterTcpEnable); + setServerToPrefs(); amTransmitting = false; @@ -137,6 +178,10 @@ wfmain::~wfmain() serverThread->quit(); serverThread->wait(); } + if (clusterThread != Q_NULLPTR) { + clusterThread->quit(); + clusterThread->wait(); + } if (rigCtl != Q_NULLPTR) { delete rigCtl; } @@ -160,6 +205,7 @@ void wfmain::closeEvent(QCloseEvent *event) QApplication::exit(); } QCheckBox *cb = new QCheckBox("Don't ask me again"); + cb->setToolTip("Don't ask me to confirm exit again"); QMessageBox msgbox; msgbox.setText("Are you sure you wish to exit?\n"); msgbox.setIcon(QMessageBox::Icon::Question); @@ -187,6 +233,7 @@ void wfmain::closeEvent(QCloseEvent *event) } else { event->ignore(); } + delete cb; } void wfmain::openRig() @@ -211,20 +258,6 @@ void wfmain::openRig() ui->connectBtn->setText("Cancel connection"); // We are attempting to connect - // TODO: Use these if they are found - if(!serialPortCL.isEmpty()) - { - qDebug(logSystem()) << "Serial port specified by user: " << serialPortCL; - } else { - qDebug(logSystem()) << "Serial port not specified. "; - } - - if(!hostCL.isEmpty()) - { - qDebug(logSystem()) << "Remote host name specified by user: " << hostCL; - } - - makeRig(); if (prefs.enableLAN) @@ -236,16 +269,11 @@ void wfmain::openRig() emit sendCommSetup(prefs.radioCIVAddr, udpPrefs, rxSetup, txSetup, prefs.virtualSerialPort, prefs.tcpPort); } else { ui->serialEnableBtn->setChecked(true); - if( (prefs.serialPortRadio.toLower() == QString("auto")) && (serialPortCL.isEmpty())) + if( (prefs.serialPortRadio.toLower() == QString("auto"))) { findSerialPort(); } else { - if(serialPortCL.isEmpty()) - { - serialPortRig = prefs.serialPortRadio; - } else { - serialPortRig = serialPortCL; - } + serialPortRig = prefs.serialPortRadio; } usingLAN = false; emit sendCommSetup(prefs.radioCIVAddr, serialPortRig, prefs.serialPortBaud,prefs.virtualSerialPort, prefs.tcpPort,prefs.waterfallFormat); @@ -264,8 +292,9 @@ void wfmain::createSettingsListItems() ui->settingsList->addItem("Radio Settings"); // 2 ui->settingsList->addItem("Radio Server"); // 3 ui->settingsList->addItem("External Control"); // 4 - ui->settingsList->addItem("Experimental"); // 5 - //ui->settingsList->addItem("Audio Processing"); // 6 + ui->settingsList->addItem("DX Cluster"); // 5 + ui->settingsList->addItem("Experimental"); // 6 + //ui->settingsList->addItem("Audio Processing"); // 7 ui->settingsStack->setCurrentIndex(0); } @@ -313,6 +342,7 @@ void wfmain::rigConnections() connect(this, SIGNAL(scopeDisplayEnable()), rig, SLOT(enableSpectrumDisplay())); connect(rig, SIGNAL(haveMode(unsigned char, unsigned char)), this, SLOT(receiveMode(unsigned char, unsigned char))); connect(rig, SIGNAL(haveDataMode(bool)), this, SLOT(receiveDataModeStatus(bool))); + connect(rig, SIGNAL(havePassband(quint8)), this, SLOT(receivePassband(quint8))); connect(rpt, SIGNAL(getDuplexMode()), rig, SLOT(getDuplexMode())); connect(rpt, SIGNAL(setDuplexMode(duplexMode)), rig, SLOT(setDuplexMode(duplexMode))); @@ -332,6 +362,7 @@ void wfmain::rigConnections() connect(this, SIGNAL(getDuplexMode()), rig, SLOT(getDuplexMode())); + connect(this, SIGNAL(getPassband()), rig, SLOT(getPassband())); connect(this, SIGNAL(getTone()), rig, SLOT(getTone())); connect(this, SIGNAL(getTSQL()), rig, SLOT(getTSQL())); connect(this, SIGNAL(getRptAccessMode()), rig, SLOT(getRptAccessMode())); @@ -706,10 +737,24 @@ void wfmain::setupPlots() wf = ui->waterfall; + passbandIndicator = new QCPItemRect(plot); + passbandIndicator->setAntialiased(true); + passbandIndicator->setPen(QPen(Qt::red)); + passbandIndicator->setBrush(QBrush(Qt::red)); + freqIndicatorLine = new QCPItemLine(plot); freqIndicatorLine->setAntialiased(true); freqIndicatorLine->setPen(QPen(Qt::blue)); + /* + text = new QCPItemText(plot); + text->setAntialiased(true); + text->setColor(QColor(Qt::red)); + text->setText("TEST"); + text->position->setCoords(14.195, rigCaps.spectAmpMax); + text->setFont(QFont(font().family(), 12)); + */ + ui->plot->addGraph(); // primary ui->plot->addGraph(0, 0); // secondary, peaks, same axis as first. ui->plot->addLayer( "Top Layer", ui->plot->layer("main")); @@ -734,14 +779,19 @@ void wfmain::setupPlots() plot->graph(1)->setPen(QPen(color.lighter(200))); plot->graph(1)->setBrush(QBrush(color)); - freqIndicatorLine->start->setCoords(0.5,0); - freqIndicatorLine->end->setCoords(0.5,160); + freqIndicatorLine->start->setCoords(0.5, 0); + freqIndicatorLine->end->setCoords(0.5, 160); + + passbandIndicator->topLeft->setCoords(0.5, 0); + passbandIndicator->bottomRight->setCoords(0.5, 160); // Plot user interaction connect(plot, SIGNAL(mouseDoubleClick(QMouseEvent*)), this, SLOT(handlePlotDoubleClick(QMouseEvent*))); connect(wf, SIGNAL(mouseDoubleClick(QMouseEvent*)), this, SLOT(handleWFDoubleClick(QMouseEvent*))); connect(plot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(handlePlotClick(QMouseEvent*))); connect(wf, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(handleWFClick(QMouseEvent*))); + connect(plot, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(handlePlotMouseRelease(QMouseEvent*))); + connect(plot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(handlePlotMouseMove(QMouseEvent *))); connect(wf, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(handleWFScroll(QWheelEvent*))); connect(plot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(handlePlotScroll(QWheelEvent*))); spectrumDrawLock = false; @@ -984,41 +1034,35 @@ void wfmain::prepareSettingsWindow() void wfmain::updateSizes(int tabIndex) { - if(!haveRigCaps) - return; + // This function does nothing unless you are using a rig without spectrum. // This is a hack. It is not great, but it seems to work ok. if(!rigCaps.hasSpectrum) { // Set "ignore" size policy for non-selected tabs: - for(int i=0;itabWidget->count();i++) - if((i!=tabIndex) && tabIndex != 0) + for(int i=1;itabWidget->count();i++) + if((i!=tabIndex)) ui->tabWidget->widget(i)->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); // allows size to be any size that fits the tab bar - if(tabIndex==0 && !rigCaps.hasSpectrum) + if(tabIndex==0) { ui->tabWidget->widget(0)->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ui->tabWidget->widget(0)->setMaximumSize(ui->tabWidget->widget(0)->minimumSizeHint()); ui->tabWidget->widget(0)->adjustSize(); // tab this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - this->setMaximumSize(QSize(1024,350)); - this->setMinimumSize(QSize(1024,350)); + this->setMaximumSize(QSize(940,350)); + this->setMinimumSize(QSize(940,350)); resize(minimumSize()); adjustSize(); // main window - } else if(tabIndex==0 && rigCaps.hasSpectrum) { - // At main tab (0) and we have spectrum: - ui->tabWidget->widget(0)->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - - resize(minimumSizeHint()); - adjustSize(); // Without this call, the window retains the size of the previous tab. } else { // At some other tab, with or without spectrum: ui->tabWidget->widget(tabIndex)->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); this->setMinimumSize(QSize(1024, 600)); // not large enough for settings tab this->setMaximumSize(QSize(65535,65535)); + resize(minimumSize()); adjustSize(); } } else { @@ -1543,6 +1587,8 @@ void wfmain::loadSettings() prefs.confirmExit = settings->value("ConfirmExit", defPrefs.confirmExit).toBool(); prefs.confirmPowerOff = settings->value("ConfirmPowerOff", defPrefs.confirmPowerOff).toBool(); prefs.meter2Type = static_cast(settings->value("Meter2Type", defPrefs.meter2Type).toInt()); + prefs.clickDragTuningEnable = settings->value("ClickDragTuning", false).toBool(); + ui->clickDragTuningEnableChk->setChecked(prefs.clickDragTuningEnable); settings->endGroup(); // Load in the color presets. The default values are already loaded. @@ -1579,6 +1625,7 @@ void wfmain::loadSettings() p->underlayFill.setNamedColor(settings->value("underlayFill", p->underlayFill.name(QColor::HexArgb)).toString()); p->plotBackground.setNamedColor(settings->value("plotBackground", p->plotBackground.name(QColor::HexArgb)).toString()); p->tuningLine.setNamedColor(settings->value("tuningLine", p->tuningLine.name(QColor::HexArgb)).toString()); + p->passband.setNamedColor(settings->value("passband", p->passband.name(QColor::HexArgb)).toString()); p->wfBackground.setNamedColor(settings->value("wfBackground", p->wfBackground.name(QColor::HexArgb)).toString()); p->wfGrid.setNamedColor(settings->value("wfGrid", p->wfGrid.name(QColor::HexArgb)).toString()); p->wfAxis.setNamedColor(settings->value("wfAxis", p->wfAxis.name(QColor::HexArgb)).toString()); @@ -1589,6 +1636,7 @@ void wfmain::loadSettings() p->meterPeakScale.setNamedColor(settings->value("meterPeakScale", p->meterPeakScale.name(QColor::HexArgb)).toString()); p->meterLowerLine.setNamedColor(settings->value("meterLowerLine", p->meterLowerLine.name(QColor::HexArgb)).toString()); p->meterLowText.setNamedColor(settings->value("meterLowText", p->meterLowText.name(QColor::HexArgb)).toString()); + p->clusterSpots.setNamedColor(settings->value("clusterSpots", p->clusterSpots.name(QColor::HexArgb)).toString()); } } settings->endArray(); @@ -1765,10 +1813,10 @@ void wfmain::loadSettings() ui->audioTXCodecCombo->setCurrentIndex(f); ui->audioRXCodecCombo->blockSignals(false); - rxSetup.name = settings->value("AudioOutput", "").toString(); + rxSetup.name = settings->value("AudioOutput", "Default Output Device").toString(); qInfo(logGui()) << "Got Audio Output from Settings: " << rxSetup.name; - txSetup.name = settings->value("AudioInput", "").toString(); + txSetup.name = settings->value("AudioInput", "Default Input Device").toString(); qInfo(logGui()) << "Got Audio Input from Settings: " << txSetup.name; @@ -1845,8 +1893,8 @@ void wfmain::loadSettings() memcpy(rigTemp->guid, QUuid::fromString(guid).toRfc4122().constData(), GUIDLEN); #endif - rigTemp->rxAudioSetup.name = settings->value("ServerAudioInput", "").toString(); - rigTemp->txAudioSetup.name = settings->value("ServerAudioOutput", "").toString(); + rigTemp->rxAudioSetup.name = settings->value("ServerAudioInput", "Default Input Device").toString(); + rigTemp->txAudioSetup.name = settings->value("ServerAudioOutput", "Default Output Device").toString(); serverConfig.rigs.append(rigTemp); int row = 0; @@ -1869,6 +1917,12 @@ void wfmain::loadSettings() if (row == 0) { serverAddUserLine("", "", 0); + SERVERUSER user; + user.username = ""; + user.password = ""; + user.userType = 0; + serverConfig.users.append(user); + ui->serverAddUserBtn->setEnabled(false); } @@ -1908,6 +1962,71 @@ void wfmain::loadSettings() settings->endArray(); settings->endGroup(); + settings->beginGroup("Cluster"); + + prefs.clusterUdpEnable = settings->value("UdpEnabled", false).toBool(); + prefs.clusterTcpEnable = settings->value("TcpEnabled", false).toBool(); + prefs.clusterUdpPort = settings->value("UdpPort", 12060).toInt(); + ui->clusterUdpPortLineEdit->setText(QString::number(prefs.clusterUdpPort)); + ui->clusterUdpEnable->setChecked(prefs.clusterUdpEnable); + ui->clusterTcpEnable->setChecked(prefs.clusterTcpEnable); + + int numClusters = settings->beginReadArray("Servers"); + clusters.clear(); + if (numClusters > 0) { + { + for (int f = 0; f < numClusters; f++) + { + settings->setArrayIndex(f); + clusterSettings c; + c.server = settings->value("ServerName", "").toString(); + c.port = settings->value("Port", 7300).toInt(); + c.userName = settings->value("UserName", "").toString(); + c.password = settings->value("Password", "").toString(); + c.timeout = settings->value("Timeout", 0).toInt(); + c.isdefault = settings->value("Default", false).toBool(); + if (!c.server.isEmpty()) { + clusters.append(c); + } + } + int defaultCluster = 0; + ui->clusterServerNameCombo->blockSignals(true); + for (int f = 0; f < clusters.size(); f++) + { + ui->clusterServerNameCombo->addItem(clusters[f].server); + if (clusters[f].isdefault) { + defaultCluster = f; + } + } + ui->clusterServerNameCombo->blockSignals(false); + + if (clusters.size() > defaultCluster) + { + ui->clusterServerNameCombo->setCurrentIndex(defaultCluster); + ui->clusterTcpPortLineEdit->blockSignals(true); + ui->clusterUsernameLineEdit->blockSignals(true); + ui->clusterPasswordLineEdit->blockSignals(true); + ui->clusterTimeoutLineEdit->blockSignals(true); + ui->clusterTcpPortLineEdit->setText(QString::number(clusters[defaultCluster].port)); + ui->clusterUsernameLineEdit->setText(clusters[defaultCluster].userName); + ui->clusterPasswordLineEdit->setText(clusters[defaultCluster].password); + ui->clusterTimeoutLineEdit->setText(QString::number(clusters[defaultCluster].timeout)); + ui->clusterTcpPortLineEdit->blockSignals(false); + ui->clusterUsernameLineEdit->blockSignals(false); + ui->clusterPasswordLineEdit->blockSignals(false); + ui->clusterTimeoutLineEdit->blockSignals(false); + } + } + } + else { + ui->clusterTcpPortLineEdit->setEnabled(false); + ui->clusterUsernameLineEdit->setEnabled(false); + ui->clusterPasswordLineEdit->setEnabled(false); + ui->clusterTimeoutLineEdit->setEnabled(false); + } + settings->endArray(); + + settings->endGroup(); /* Load USB buttons*/ settings->beginGroup("USB"); int numCommands = settings->beginReadArray("Commands"); @@ -2060,9 +2179,7 @@ void wfmain::loadSettings() settings->endArray(); } - - settings->endGroup(); - + } void wfmain::serverAddUserLine(const QString& user, const QString& pass, const int& type) @@ -2263,6 +2380,8 @@ void wfmain::saveSettings() settings->setValue("ConfirmExit", prefs.confirmExit); settings->setValue("ConfirmPowerOff", prefs.confirmPowerOff); settings->setValue("Meter2Type", (int)prefs.meter2Type); + settings->setValue("ClickDragTuning", prefs.clickDragTuningEnable); + settings->endGroup(); // Radio and Comms: C-IV addr, port to use @@ -2348,6 +2467,7 @@ void wfmain::saveSettings() settings->setValue("underlayFill", p->underlayFill.name(QColor::HexArgb)); settings->setValue("plotBackground", p->plotBackground.name(QColor::HexArgb)); settings->setValue("tuningLine", p->tuningLine.name(QColor::HexArgb)); + settings->setValue("passband", p->passband.name(QColor::HexArgb)); settings->setValue("wfBackground", p->wfBackground.name(QColor::HexArgb)); settings->setValue("wfGrid", p->wfGrid.name(QColor::HexArgb)); settings->setValue("wfAxis", p->wfAxis.name(QColor::HexArgb)); @@ -2358,6 +2478,7 @@ void wfmain::saveSettings() settings->setValue("meterPeakLevel", p->meterPeakLevel.name(QColor::HexArgb)); settings->setValue("meterLowerLine", p->meterLowerLine.name(QColor::HexArgb)); settings->setValue("meterLowText", p->meterLowText.name(QColor::HexArgb)); + settings->setValue("clusterSpots", p->clusterSpots.name(QColor::HexArgb)); } settings->endArray(); settings->endGroup(); @@ -2394,15 +2515,33 @@ void wfmain::saveSettings() } settings->endArray(); - qInfo() << "Server config stored"; + settings->endGroup(); + + settings->beginGroup("Cluster"); + settings->setValue("UdpEnabled", prefs.clusterUdpEnable); + settings->setValue("TcpEnabled", prefs.clusterTcpEnable); + settings->setValue("UdpPort", prefs.clusterUdpPort); + + settings->beginWriteArray("Servers"); + + for (int f = 0; f < clusters.count(); f++) + { + settings->setArrayIndex(f); + settings->setValue("ServerName", clusters[f].server); + settings->setValue("UserName", clusters[f].userName); + settings->setValue("Port", clusters[f].port); + settings->setValue("Password", clusters[f].password); + settings->setValue("Timeout", clusters[f].timeout); + settings->setValue("Default", clusters[f].isdefault); + } + + settings->endArray(); settings->endGroup(); settings->beginGroup("USB"); - // Store USB Controller - settings->beginWriteArray("Buttons"); for (int nb = 0; nb < usbButtons.count(); nb++) { @@ -3053,6 +3192,7 @@ void wfmain::setDefaultColors(int presetNumber) p->underlayFill = QColor(20+200/4.0*1,70*(1.6-1/4.0), 150, 150); p->plotBackground = QColor(Qt::black); p->tuningLine = QColor(Qt::blue); + p->passband = QColor(Qt::blue); p->meterLevel = QColor("#148CD2").darker(); p->meterAverage = QColor("#3FB7CD"); @@ -3066,6 +3206,8 @@ void wfmain::setDefaultColors(int presetNumber) p->wfGrid = QColor(Qt::white); p->wfText = QColor(Qt::white); + p->clusterSpots = QColor(Qt::red); + //qInfo(logSystem()) << "default color preset [" << pn << "] set to pn.presetNum index [" << p->presetNum << "]" << ", with name " << *(p->presetName); switch (presetNumber) @@ -3084,6 +3226,7 @@ void wfmain::setDefaultColors(int presetNumber) p->underlayLine = QColor("#9633ff55"); p->underlayFill = QColor(20+200/4.0*1,70*(1.6-1/4.0), 150, 150); p->tuningLine = QColor("#ff55ffff"); + p->passband = QColor("#32ffffff"); p->meterLevel = QColor("#148CD2").darker(); p->meterAverage = QColor("#3FB7CD"); @@ -3096,6 +3239,7 @@ void wfmain::setDefaultColors(int presetNumber) p->wfAxis = QColor(Qt::white); p->wfGrid = QColor("transparent"); p->wfText = QColor(Qt::white); + p->clusterSpots = QColor(Qt::red); break; } case 1: @@ -3111,6 +3255,7 @@ void wfmain::setDefaultColors(int presetNumber) p->spectrumLine = QColor(Qt::black); p->underlayLine = QColor(Qt::blue); p->tuningLine = QColor(Qt::darkBlue); + p->passband = QColor("#64000080"); p->meterAverage = QColor("#3FB7CD"); p->meterPeakLevel = QColor("#3CA0DB"); @@ -3122,6 +3267,7 @@ void wfmain::setDefaultColors(int presetNumber) p->wfAxis = QColor(200,200,200,255); p->wfGrid = QColor("transparent"); p->wfText = QColor(Qt::black); + p->clusterSpots = QColor(Qt::red); break; } @@ -3342,6 +3488,9 @@ void wfmain::doCmd(cmds cmd) case cmdGetDuplexMode: emit getDuplexMode(); break; + case cmdGetPassband: + emit getPassband(); + break; case cmdGetTone: emit getTone(); break; @@ -3718,8 +3867,29 @@ void wfmain::receiveRigID(rigCapabilities rigCaps) this->spectWidth = rigCaps.spectLenMax; // used once haveRigCaps is true. //wfCeiling = rigCaps.spectAmpMax; //plotCeiling = rigCaps.spectAmpMax; - ui->topLevelSlider->setMaximum(rigCaps.spectAmpMax); + if(rigCaps.hasSpectrum) + { + ui->topLevelSlider->setVisible(true); + ui->labelTop->setVisible(true); + ui->botLevelSlider->setVisible(true); + ui->labelBot->setVisible(true); + ui->scopeRefLevelSlider->setVisible(true); + ui->refLabel->setVisible(true); + ui->wfLengthSlider->setVisible(true); + ui->lenLabel->setVisible(true); + ui->topLevelSlider->setMaximum(rigCaps.spectAmpMax); + ui->botLevelSlider->setMaximum(rigCaps.spectAmpMax); + } else { + ui->scopeRefLevelSlider->setVisible(false); + ui->refLabel->setVisible(false); + ui->wfLengthSlider->setVisible(false); + ui->lenLabel->setVisible(false); + ui->topLevelSlider->setVisible(false); + ui->labelTop->setVisible(false); + ui->botLevelSlider->setVisible(false); + ui->labelBot->setVisible(false); + } haveRigCaps = true; // Added so that server receives rig capabilities. @@ -3888,6 +4058,7 @@ void wfmain::receiveRigID(rigCapabilities rigCaps) } } } + updateSizes(ui->tabWidget->currentIndex()); } void wfmain::initPeriodicCommands() @@ -3914,6 +4085,11 @@ void wfmain::initPeriodicCommands() insertSlowPeriodicCommand(cmdGetAntenna, 128); } insertSlowPeriodicCommand(cmdGetDuplexMode, 128); + + if (rigCaps.hasSpectrum) { + // Get passband + insertPeriodicCommand(cmdGetPassband, 128); + } } void wfmain::insertPeriodicCommand(cmds cmd, unsigned char priority) @@ -4028,6 +4204,8 @@ void wfmain::receiveSpectrumData(QByteArray spectrum, double startFreq, double e // This will break if the button is ever moved or renamed. on_clearPeakBtn_clicked(); } + // Inform other threads (cluster) that the frequency range has changed. + emit setFrequencyRange(startFreq, endFreq); // TODO: Add clear-out for the buffer } @@ -4086,8 +4264,32 @@ void wfmain::receiveSpectrumData(QByteArray spectrum, double startFreq, double e plot->graph(0)->setData(x,y, true); if((freq.MHzDouble < endFreq) && (freq.MHzDouble > startFreq)) { - freqIndicatorLine->start->setCoords(freq.MHzDouble,0); - freqIndicatorLine->end->setCoords(freq.MHzDouble,rigCaps.spectAmpMax); + freqIndicatorLine->start->setCoords(freq.MHzDouble, 0); + freqIndicatorLine->end->setCoords(freq.MHzDouble, rigCaps.spectAmpMax); + + if (currentModeInfo.mk == modeLSB || currentModeInfo.mk == modePSK_R) { + passbandIndicator->topLeft->setCoords(freq.MHzDouble - passBand - 0.0001, 0); + passbandIndicator->bottomRight->setCoords(freq.MHzDouble - 0.0001, rigCaps.spectAmpMax); + } + else if (currentModeInfo.mk == modeUSB || currentModeInfo.mk == modePSK) { + passbandIndicator->topLeft->setCoords(freq.MHzDouble + 0.0001, 0); + passbandIndicator->bottomRight->setCoords(freq.MHzDouble + 0.0001 + passBand, rigCaps.spectAmpMax); + } + else + { + if (currentModeInfo.mk == modeFM) { + if (currentModeInfo.filter == 1) + passBand = 0.015; + else if (currentModeInfo.filter == 2) + passBand = 0.010; + else + passBand = 0.007; + } + passbandIndicator->topLeft->setCoords(freq.MHzDouble - (passBand / 2), 0); + passbandIndicator->bottomRight->setCoords(freq.MHzDouble + (passBand / 2), rigCaps.spectAmpMax); + } + + } if(underlayMode == underlayPeakHold) @@ -4266,10 +4468,84 @@ void wfmain::handleWFDoubleClick(QMouseEvent *me) } } -void wfmain::handlePlotClick(QMouseEvent *me) +void wfmain::handlePlotClick(QMouseEvent* me) { - double x = plot->xAxis->pixelToCoord(me->pos().x()); - showStatusBarText(QString("Selected %1 MHz").arg(x)); + QCPAbstractItem* item = plot->itemAt(me->pos(), true); + QCPItemText* textItem = dynamic_cast (item); + if (me->button() == Qt::RightButton && textItem != nullptr) { + QMap::iterator spot = clusterSpots.find(textItem->text()); + if (spot != clusterSpots.end() && spot.key() == textItem->text()) { + /* parent and children are destroyed on close */ + QDialog* spotDialog = new QDialog(); + QVBoxLayout* vlayout = new QVBoxLayout; + //spotDialog->setFixedSize(240, 100); + spotDialog->setBaseSize(1, 1); + spotDialog->setWindowTitle(spot.value()->dxcall); + QLabel* dxcall = new QLabel(QString("DX:%1").arg(spot.value()->dxcall)); + QLabel* spotter = new QLabel(QString("Spotter:%1").arg(spot.value()->spottercall)); + QLabel* frequency = new QLabel(QString("Frequency:%1 MHz").arg(spot.value()->frequency)); + QLabel* comment = new QLabel(QString("Comment:%1").arg(spot.value()->comment)); + QAbstractButton* bExit = new QPushButton("Close"); + vlayout->addWidget(dxcall); + vlayout->addWidget(spotter); + vlayout->addWidget(frequency); + vlayout->addWidget(comment); + vlayout->addWidget(bExit); + spotDialog->setLayout(vlayout); + spotDialog->show(); + spotDialog->connect(bExit, SIGNAL(clicked()), spotDialog, SLOT(close())); + } + } + else if (textItem != nullptr) + { + QMap::iterator spot = clusterSpots.find(textItem->text()); + if (spot != clusterSpots.end() && spot.key() == textItem->text()) + { + qInfo(logGui()) << "Clicked on spot:" << textItem->text(); + freqt freqGo; + freqGo.Hz = ( spot.value()->frequency)*1E6; + freqGo.MHzDouble = spot.value()->frequency; + issueCmdUniquePriority(cmdSetFreq, freqGo); + } + } + else if (prefs.clickDragTuningEnable) + { + double x = plot->xAxis->pixelToCoord(me->pos().x()); + showStatusBarText(QString("Selected %1 MHz").arg(x)); + this->mousePressFreq = x; + } +} + +void wfmain::handlePlotMouseRelease(QMouseEvent* me) +{ + QCPAbstractItem* item = plot->itemAt(me->pos(), true); + QCPItemText* textItem = dynamic_cast (item); + + if (textItem == nullptr && prefs.clickDragTuningEnable) { + this->mouseReleaseFreq = plot->xAxis->pixelToCoord(me->pos().x()); + double delta = mouseReleaseFreq - mousePressFreq; + qInfo(logGui()) << "Mouse release delta: " << delta; + + } +} + +void wfmain::handlePlotMouseMove(QMouseEvent *me) +{ + QCPAbstractItem* item = plot->itemAt(me->pos(), true); + QCPItemText* textItem = dynamic_cast (item); + if(me->buttons() == Qt::LeftButton && textItem==nullptr && prefs.clickDragTuningEnable) + { + double delta = plot->xAxis->pixelToCoord(me->pos().x()) - mousePressFreq; + qInfo(logGui()) << "Mouse moving delta: " << delta; + if( (( delta < -0.0001 ) || (delta > 0.0001)) && ((delta < 0.501) && (delta > -0.501)) ) + { + freqt freqGo; + freqGo.Hz = ( freq.MHzDouble + delta)*1E6; + //freqGo.Hz = roundFrequency(freqGo.Hz, tsWfScrollHz); + freqGo.MHzDouble = (float)freqGo.Hz / 1E6; + issueCmdUniquePriority(cmdSetFreq, freqGo); + } + } } void wfmain::handleWFClick(QMouseEvent *me) @@ -4356,6 +4632,8 @@ void wfmain::receiveMode(unsigned char mode, unsigned char filter) } } currentModeIndex = mode; + currentModeInfo.mk = (mode_kind)mode; + currentModeInfo.filter = filter; } else { qInfo(logSystem()) << __func__ << "Invalid mode " << mode << " received. "; } @@ -4373,7 +4651,6 @@ void wfmain::receiveMode(unsigned char mode, unsigned char filter) (void)filter; - // Note: we need to know if the DATA mode is active to reach mode-D // some kind of queued query: if (rigCaps.hasDataModes && rigCaps.hasTransmit) @@ -5603,6 +5880,22 @@ void wfmain::receiveLANGain(unsigned char level) processModLevel(inputLAN, level); } +void wfmain::receivePassband(quint8 pass) +{ + int calc; + if (currentModeInfo.mk == modeAM) { + calc = 200 + (pass * 200); + } + else if (pass <= 10) + { + calc = 50 + (pass * 50); + } + else { + calc = 600 + ((pass - 10) * 100); + } + passBand = (double)(calc / 1000000.0); +} + void wfmain::receiveMeter(meterKind inMeter, unsigned char level) { @@ -6411,42 +6704,54 @@ void wfmain::setAudioDevicesUI() ui->serverTXAudioOutputCombo->clear(); ui->serverRXAudioInputCombo->clear(); - qDebug(logSystem()) << "Finding audio devices, output=" << rxSetup.name << "input="<audioInputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); - ui->serverRXAudioInputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); - inCount++; + ui->audioInputCombo->addItem(deviceInfo.deviceName().toLocal8Bit(), QVariant::fromValue(deviceInfo)); + ui->serverRXAudioInputCombo->addItem(deviceInfo.deviceName().toLocal8Bit(), QVariant::fromValue(deviceInfo)); #ifdef Q_OS_WIN } #endif + inCount++; } - const auto audioOutputs = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - for (const QAudioDeviceInfo& deviceInfo : audioOutputs) { + int outCount = 0; + foreach(const QAudioDeviceInfo & deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) + { + if (outCount == 0) + defaultAudioOutputName = QString(deviceInfo.deviceName().toLocal8Bit()); #ifdef Q_OS_WIN if (deviceInfo.realm() == "wasapi") { #endif - ui->audioOutputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); - ui->serverTXAudioOutputCombo->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); - outCount++; + ui->audioOutputCombo->addItem(deviceInfo.deviceName().toLocal8Bit(), QVariant::fromValue(deviceInfo)); + ui->serverTXAudioOutputCombo->addItem(deviceInfo.deviceName().toLocal8Bit(), QVariant::fromValue(deviceInfo)); #ifdef Q_OS_WIN } #endif + outCount++; } break; } @@ -6472,23 +6777,21 @@ void wfmain::setAudioDevicesUI() { info = Pa_GetDeviceInfo(i); if (info->maxInputChannels > 0) { - qDebug(logAudio()) << (i == Pa_GetDefaultInputDevice() ? "*" : " ") << "(" << i << ") Input Device : " << info->name; + qDebug(logAudio()) << (i == Pa_GetDefaultInputDevice() ? "*" : " ") << "(" << i << ") Input Device : " << QString(info->name).toLocal8Bit(); - ui->audioInputCombo->addItem(info->name, i); - ui->serverRXAudioInputCombo->addItem(info->name, i); + ui->audioInputCombo->addItem(QString(info->name).toLocal8Bit(), i); + ui->serverRXAudioInputCombo->addItem(QString(info->name).toLocal8Bit(), i); if (i == Pa_GetDefaultInputDevice()) { - defaultAudioInputIndex = inCount; + defaultAudioInputName = info->name; } - inCount++; } if (info->maxOutputChannels > 0) { - qDebug(logAudio()) << (i == Pa_GetDefaultOutputDevice() ? "*" : " ") << "(" << i << ") Output Device : " << info->name; - ui->audioOutputCombo->addItem(info->name, i); - ui->serverTXAudioOutputCombo->addItem(info->name, i); + qDebug(logAudio()) << (i == Pa_GetDefaultOutputDevice() ? "*" : " ") << "(" << i << ") Output Device : " << QString(info->name).toLocal8Bit(); + ui->audioOutputCombo->addItem(QString(info->name).toLocal8Bit(), i); + ui->serverTXAudioOutputCombo->addItem(QString(info->name).toLocal8Bit(), i); if (i == Pa_GetDefaultOutputDevice()) { - defaultAudioOutputIndex = outCount; + defaultAudioOutputName = info->name; } - outCount++; } } break; @@ -6538,22 +6841,20 @@ void wfmain::setAudioDevicesUI() for (unsigned int i = 1; i < devices; i++) { info = audio->getDeviceInfo(i); if (info.inputChannels > 0) { - qInfo(logAudio()) << (info.isDefaultInput ? "*" : " ") << "(" << i << ") Input Device : " << QString::fromStdString(info.name); - ui->audioInputCombo->addItem(QString::fromStdString(info.name), i); - ui->serverRXAudioInputCombo->addItem(QString::fromStdString(info.name), i); + qInfo(logAudio()) << (info.isDefaultInput ? "*" : " ") << "(" << i << ") Input Device : " << QString::fromStdString(info.name).toLocal8Bit(); + ui->audioInputCombo->addItem(QString::fromStdString(info.name).toLocal8Bit(), i); + ui->serverRXAudioInputCombo->addItem(QString::fromStdString(info.name).toLocal8Bit(), i); if (info.isDefaultInput) { - defaultAudioInputIndex = inCount; + defaultAudioInputName = QString::fromStdString(info.name).toLocal8Bit(); } - inCount++; } if (info.outputChannels > 0) { - qInfo(logAudio()) << (info.isDefaultOutput ? "*" : " ") << "(" << i << ") Output Device : " << QString::fromStdString(info.name); - ui->audioOutputCombo->addItem(QString::fromStdString(info.name), i); - ui->serverTXAudioOutputCombo->addItem(QString::fromStdString(info.name), i); + qInfo(logAudio()) << (info.isDefaultOutput ? "*" : " ") << "(" << i << ") Output Device : " << QString::fromStdString(info.name).toLocal8Bit(); + ui->audioOutputCombo->addItem(QString::fromStdString(info.name).toLocal8Bit(), i); + ui->serverTXAudioOutputCombo->addItem(QString::fromStdString(info.name).toLocal8Bit(), i); if (info.isDefaultOutput) { - defaultAudioOutputIndex = outCount; + defaultAudioOutputName = QString::fromStdString(info.name).toLocal8Bit(); } - outCount++; } } @@ -6584,47 +6885,69 @@ void wfmain::setAudioDevicesUI() int audioInputIndex = ui->audioInputCombo->findText(txSetup.name); if (audioInputIndex != -1) { + qInfo(logGui()) << "Found Audio Input Device: " << txSetup.name; ui->audioInputCombo->setCurrentIndex(audioInputIndex); } else { - qDebug(logSystem()) << "Audio input not found"; - ui->audioInputCombo->setCurrentIndex(defaultAudioInputIndex); + qWarning(logGui()) << "Audio Input Device: " << txSetup.name << "Not Found, trying to select default"; + audioInputIndex = ui->audioInputCombo->findText(defaultAudioInputName); + if (audioInputIndex != -1) { + ui->audioInputCombo->setCurrentIndex(audioInputIndex); + } + else { + qWarning(logGui()) << "Unable to select default input device,"<< defaultAudioInputName << " help....."; + } + } - int audioOutputIndex = ui->audioOutputCombo->findText(rxSetup.name); + int audioOutputIndex = ui->audioOutputCombo->findText(rxSetup.name.toLocal8Bit()); if (audioOutputIndex != -1) { + qInfo(logGui()) << "Found Audio Output Device: " << rxSetup.name.toLocal8Bit(); ui->audioOutputCombo->setCurrentIndex(audioOutputIndex); } else { - qDebug(logSystem()) << "Audio output not found"; - ui->audioOutputCombo->setCurrentIndex(defaultAudioOutputIndex); + qWarning(logGui()) << "Audio output Device: " << rxSetup.name << "Not Found, trying to select default"; + audioOutputIndex = ui->audioOutputCombo->findText(defaultAudioOutputName.toLocal8Bit()); + if (audioOutputIndex != -1) { + ui->audioOutputCombo->setCurrentIndex(audioOutputIndex); + } + else { + qWarning(logGui()) << "Unable to select default output device," << defaultAudioOutputName.toLocal8Bit() << " help....."; + } } if (!serverConfig.rigs.isEmpty()) { - qInfo(logGui()) << "Got Server Audio Input: " << serverConfig.rigs.first()->rxAudioSetup.name; + if (serverConfig.enabled) + qInfo(logGui()) << "Got Server Audio Input: " << serverConfig.rigs.first()->rxAudioSetup.name.toLocal8Bit(); serverConfig.rigs.first()->rxAudioSetup.type = prefs.audioSystem; serverConfig.rigs.first()->txAudioSetup.type = prefs.audioSystem; - int serverAudioInputIndex = ui->serverRXAudioInputCombo->findText(serverConfig.rigs.first()->rxAudioSetup.name); + ui->serverRXAudioInputCombo->setCurrentIndex(defaultAudioInputIndex); + int serverAudioInputIndex = ui->serverRXAudioInputCombo->findText(serverConfig.rigs.first()->rxAudioSetup.name.toLocal8Bit()); if (serverAudioInputIndex != -1) { ui->serverRXAudioInputCombo->setCurrentIndex(serverAudioInputIndex); } else { - // Set to default - ui->serverRXAudioInputCombo->setCurrentIndex(defaultAudioInputIndex); + if (serverConfig.enabled) + qWarning(logGui()) << "Server audio input NOT FOUND " << serverConfig.rigs.first()->rxAudioSetup.name.toLocal8Bit() << "not selecting default"; } - qInfo(logGui()) << "Got Server Audio Output: " << serverConfig.rigs.first()->txAudioSetup.name; - int serverAudioOutputIndex = ui->serverTXAudioOutputCombo->findText(serverConfig.rigs.first()->txAudioSetup.name); + if (serverConfig.enabled) + qInfo(logGui()) << "Got Server Audio Output: " << serverConfig.rigs.first()->txAudioSetup.name.toLocal8Bit(); + + ui->serverTXAudioOutputCombo->setCurrentIndex(defaultAudioOutputIndex); + int serverAudioOutputIndex = ui->serverTXAudioOutputCombo->findText(serverConfig.rigs.first()->txAudioSetup.name.toLocal8Bit()); if (serverAudioOutputIndex != -1) { ui->serverTXAudioOutputCombo->setCurrentIndex(serverAudioOutputIndex); } else { - ui->serverTXAudioOutputCombo->setCurrentIndex(defaultAudioOutputIndex); + if (serverConfig.enabled) + qWarning(logGui()) << "Server audio output NOT FOUND " << serverConfig.rigs.first()->txAudioSetup.name.toLocal8Bit() << "not selecting default"; } + } qDebug(logSystem()) << "Audio devices done."; @@ -6906,6 +7229,8 @@ void wfmain::useColorPreset(colorPrefsType *cp) plot->yAxis->setTickPen(cp->axisColor); freqIndicatorLine->setPen(QPen(cp->tuningLine)); + passbandIndicator->setPen(QPen(cp->passband)); + passbandIndicator->setBrush(QBrush(cp->passband)); plot->graph(0)->setPen(QPen(cp->spectrumLine)); plot->graph(0)->setBrush(QBrush(cp->spectrumFill)); @@ -6928,6 +7253,8 @@ void wfmain::useColorPreset(colorPrefsType *cp) ui->meterSPoWidget->setColors(cp->meterLevel, cp->meterPeakScale, cp->meterPeakLevel, cp->meterAverage, cp->meterLowerLine, cp->meterLowText); ui->meter2Widget->setColors(cp->meterLevel, cp->meterPeakScale, cp->meterPeakLevel, cp->meterAverage, cp->meterLowerLine, cp->meterLowText); + + clusterColor = cp->clusterSpots; } void wfmain::setColorButtonOperations(QColor *colorStore, @@ -6967,16 +7294,18 @@ void wfmain::setColorLineEditOperations(QColor *colorStore, void wfmain::on_colorPopOutBtn_clicked() { - if(settingsTabisAttached) + if (settingsTabisAttached) { settingsTab = ui->tabWidget->currentWidget(); ui->tabWidget->removeTab(ui->tabWidget->indexOf(settingsTab)); settingsWidgetTab->addTab(settingsTab, "Settings"); settingsWidgetWindow->show(); ui->colorPopOutBtn->setText("Re-attach"); + ui->clusterPopOutBtn->setText("Re-attach"); ui->tabWidget->setCurrentIndex(0); settingsTabisAttached = false; - } else { + } + else { settingsTab = settingsWidgetTab->currentWidget(); settingsWidgetTab->removeTab(settingsWidgetTab->indexOf(settingsTab)); @@ -6984,6 +7313,7 @@ void wfmain::on_colorPopOutBtn_clicked() settingsWidgetWindow->close(); ui->colorPopOutBtn->setText("Pop-Out"); + ui->clusterPopOutBtn->setText("Pop-Out"); ui->tabWidget->setCurrentIndex(3); settingsTabisAttached = true; } @@ -7033,6 +7363,7 @@ void wfmain::loadColorPresetToUIandPlots(int presetNumber) setEditAndLedFromColor(p.underlayFill, ui->colorEditUnderlayFill, ui->colorSwatchUnderlayFill); setEditAndLedFromColor(p.plotBackground, ui->colorEditPlotBackground, ui->colorSwatchPlotBackground); setEditAndLedFromColor(p.tuningLine, ui->colorEditTuningLine, ui->colorSwatchTuningLine); + setEditAndLedFromColor(p.passband, ui->colorEditPassband, ui->colorSwatchPassband); setEditAndLedFromColor(p.meterLevel, ui->colorEditMeterLevel, ui->colorSwatchMeterLevel); setEditAndLedFromColor(p.meterAverage, ui->colorEditMeterAvg, ui->colorSwatchMeterAverage); @@ -7046,6 +7377,8 @@ void wfmain::loadColorPresetToUIandPlots(int presetNumber) setEditAndLedFromColor(p.wfAxis, ui->colorEditWfAxis, ui->colorSwatchWfAxis); setEditAndLedFromColor(p.wfText, ui->colorEditWfText, ui->colorSwatchWfText); + setEditAndLedFromColor(p.clusterSpots, ui->colorEditClusterSpots, ui->colorSwatchClusterSpots); + useColorPreset(&p); } @@ -7252,13 +7585,14 @@ void wfmain::on_colorEditWfAxis_editingFinished() void wfmain::on_colorSetBtnWfText_clicked() { int pos = ui->colorPresetCombo->currentIndex(); - QColor *c = &(colorPreset[pos].wfText); + QColor* c = &(colorPreset[pos].wfText); setColorButtonOperations(c, ui->colorEditWfText, ui->colorSwatchWfText); } + void wfmain::on_colorEditWfText_editingFinished() { int pos = ui->colorPresetCombo->currentIndex(); - QColor *c = &(colorPreset[pos].wfText); + QColor* c = &(colorPreset[pos].wfText); setColorLineEditOperations(c, ui->colorEditWfText, ui->colorSwatchWfText); } @@ -7266,16 +7600,30 @@ void wfmain::on_colorEditWfText_editingFinished() void wfmain::on_colorSetBtnTuningLine_clicked() { int pos = ui->colorPresetCombo->currentIndex(); - QColor *c = &(colorPreset[pos].tuningLine); + QColor* c = &(colorPreset[pos].tuningLine); setColorButtonOperations(c, ui->colorEditTuningLine, ui->colorSwatchTuningLine); } void wfmain::on_colorEditTuningLine_editingFinished() { int pos = ui->colorPresetCombo->currentIndex(); - QColor *c = &(colorPreset[pos].tuningLine); + QColor* c = &(colorPreset[pos].tuningLine); setColorLineEditOperations(c, ui->colorEditTuningLine, ui->colorSwatchTuningLine); } +// Passband: +void wfmain::on_colorSetBtnPassband_clicked() +{ + int pos = ui->colorPresetCombo->currentIndex(); + QColor* c = &(colorPreset[pos].passband); + setColorButtonOperations(c, ui->colorEditPassband, ui->colorSwatchPassband); +} +void wfmain::on_colorEditPassband_editingFinished() +{ + int pos = ui->colorPresetCombo->currentIndex(); + QColor* c = &(colorPreset[pos].passband); + setColorLineEditOperations(c, ui->colorEditPassband, ui->colorSwatchPassband); +} + // Meter Level: void wfmain::on_colorSetBtnMeterLevel_clicked() { @@ -7360,6 +7708,20 @@ void wfmain::on_colorEditMeterText_editingFinished() setColorLineEditOperations(c, ui->colorEditMeterText, ui->colorSwatchMeterText); } +// Cluster Spots: +void wfmain::on_colorSetBtnClusterSpots_clicked() +{ + int pos = ui->colorPresetCombo->currentIndex(); + QColor* c = &(colorPreset[pos].clusterSpots); + setColorButtonOperations(c, ui->colorEditClusterSpots, ui->colorSwatchClusterSpots); +} +void wfmain::on_colorEditClusterSpots_editingFinished() +{ + int pos = ui->colorPresetCombo->currentIndex(); + QColor* c = &(colorPreset[pos].clusterSpots); + setColorLineEditOperations(c, ui->colorEditClusterSpots, ui->colorSwatchClusterSpots); +} + // ---------- End color UI slots ----------// void wfmain::on_colorSavePresetBtn_clicked() @@ -7385,6 +7747,7 @@ void wfmain::on_colorSavePresetBtn_clicked() settings->setValue("underlayFill", p->underlayFill.name(QColor::HexArgb)); settings->setValue("plotBackground", p->plotBackground.name(QColor::HexArgb)); settings->setValue("tuningLine", p->tuningLine.name(QColor::HexArgb)); + settings->setValue("passband", p->passband.name(QColor::HexArgb)); settings->setValue("wfBackground", p->wfBackground.name(QColor::HexArgb)); settings->setValue("wfGrid", p->wfGrid.name(QColor::HexArgb)); settings->setValue("wfAxis", p->wfAxis.name(QColor::HexArgb)); @@ -7395,6 +7758,7 @@ void wfmain::on_colorSavePresetBtn_clicked() settings->setValue("meterPeakLevel", p->meterPeakLevel.name(QColor::HexArgb)); settings->setValue("meterLowerLine", p->meterLowerLine.name(QColor::HexArgb)); settings->setValue("meterLowText", p->meterLowText.name(QColor::HexArgb)); + settings->setValue("clusterSpots", p->clusterSpots.name(QColor::HexArgb)); settings->endArray(); settings->endGroup(); @@ -7416,11 +7780,6 @@ void wfmain::on_showLogBtn_clicked() void wfmain::initLogging() { -#ifdef Q_OS_MAC - logFilename= QStandardPaths::standardLocations(QStandardPaths::DownloadLocation)[0] + "/wfview.log"; -#else - logFilename= QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0] + "/wfview.log"; -#endif // Set the logging file before doing anything else. m_logFile.reset(new QFile(logFilename)); // Open the file logging @@ -7562,6 +7921,255 @@ errMsg: } + +void wfmain::receiveClusterOutput(QString text) { + ui->clusterOutputTextEdit->moveCursor(QTextCursor::End); + ui->clusterOutputTextEdit->insertPlainText(text); + ui->clusterOutputTextEdit->moveCursor(QTextCursor::End); +} + +void wfmain::on_clusterUdpEnable_clicked(bool enable) +{ + prefs.clusterUdpEnable = enable; + emit setClusterEnableUdp(enable); +} + +void wfmain::on_clusterTcpEnable_clicked(bool enable) +{ + prefs.clusterTcpEnable = enable; + emit setClusterEnableTcp(enable); +} + +void wfmain::on_clusterUdpPortLineEdit_editingFinished() +{ + prefs.clusterUdpPort = ui->clusterUdpPortLineEdit->text().toInt(); + emit setClusterUdpPort(prefs.clusterUdpPort); +} + +void wfmain::on_clusterServerNameCombo_currentIndexChanged(int index) +{ + if (index < 0) + return; + + QString text = ui->clusterServerNameCombo->currentText(); + + if (clusters.size() <= index) + { + qInfo(logGui) << "Adding Cluster server" << text; + clusterSettings c; + c.server = text; + clusters.append(c); + ui->clusterTcpPortLineEdit->setEnabled(true); + ui->clusterUsernameLineEdit->setEnabled(true); + ui->clusterPasswordLineEdit->setEnabled(true); + ui->clusterTimeoutLineEdit->setEnabled(true); + + } + else { + qInfo(logGui) << "Editing Cluster server" << text; + clusters[index].server = text; + } + ui->clusterUsernameLineEdit->blockSignals(true); + ui->clusterPasswordLineEdit->blockSignals(true); + ui->clusterTimeoutLineEdit->blockSignals(true); + ui->clusterTcpPortLineEdit->setText(QString::number(clusters[index].port)); + ui->clusterUsernameLineEdit->setText(clusters[index].userName); + ui->clusterPasswordLineEdit->setText(clusters[index].password); + ui->clusterTimeoutLineEdit->setText(QString::number(clusters[index].timeout)); + ui->clusterUsernameLineEdit->blockSignals(false); + ui->clusterPasswordLineEdit->blockSignals(false); + ui->clusterTimeoutLineEdit->blockSignals(false); + + + for (int i = 0; i < clusters.size(); i++) { + if (i == index) + clusters[index].isdefault = true; + else + clusters[index].isdefault = false; + } + + emit setClusterServerName(clusters[index].server); + emit setClusterTcpPort(clusters[index].port); + emit setClusterUserName(clusters[index].userName); + emit setClusterPassword(clusters[index].password); + emit setClusterTimeout(clusters[index].timeout); + +} + +void wfmain::on_clusterServerNameCombo_currentTextChanged(QString text) +{ + if (text.isEmpty()) { + int index = ui->clusterServerNameCombo->currentIndex(); + ui->clusterServerNameCombo->removeItem(index); + clusters.removeAt(index); + if (clusters.size() == 0) + { + ui->clusterTcpPortLineEdit->setEnabled(false); + ui->clusterUsernameLineEdit->setEnabled(false); + ui->clusterPasswordLineEdit->setEnabled(false); + ui->clusterTimeoutLineEdit->setEnabled(false); + } + } +} + +void wfmain::on_clusterTcpPortLineEdit_editingFinished() +{ + int index = ui->clusterServerNameCombo->currentIndex(); + if (index < clusters.size()) + { + clusters[index].port = ui->clusterTcpPortLineEdit->displayText().toInt(); + emit setClusterTcpPort(clusters[index].port); + } +} + +void wfmain::on_clusterUsernameLineEdit_editingFinished() +{ + int index = ui->clusterServerNameCombo->currentIndex(); + if (index < clusters.size()) + { + clusters[index].userName = ui->clusterUsernameLineEdit->text(); + emit setClusterUserName(clusters[index].userName); + } +} + +void wfmain::on_clusterPasswordLineEdit_editingFinished() +{ + int index = ui->clusterServerNameCombo->currentIndex(); + if (index < clusters.size()) + { + clusters[index].password = ui->clusterPasswordLineEdit->text(); + emit setClusterPassword(clusters[index].password); + } + +} + +void wfmain::on_clusterTimeoutLineEdit_editingFinished() +{ + int index = ui->clusterServerNameCombo->currentIndex(); + if (index < clusters.size()) + { + clusters[index].timeout = ui->clusterTimeoutLineEdit->displayText().toInt(); + emit setClusterTimeout(clusters[index].timeout); + } +} + + +void wfmain::receiveSpots(QList spots) +{ + QElapsedTimer timer; + timer.start(); + + bool current = false; + + if (clusterSpots.size() > 0) { + current=clusterSpots.begin().value()->current; + } + + foreach(spotData s, spots) + { + bool found = false; + QMap::iterator spot = clusterSpots.find(s.dxcall); + + while (spot != clusterSpots.end() && spot.key() == s.dxcall && spot.value()->frequency == s.frequency) { + spot.value()->current = !current; + found = true; + ++spot; + } + + if (!found) + { + spotData* sp = new spotData(s); + + //qDebug(logCluster()) << "ADD:" << sp->dxcall; + sp->current = !current; + bool conflict = true; + double left = sp->frequency; + QCPRange range=plot->yAxis->range(); + double top = range.upper-15.0; + sp->text = new QCPItemText(plot); + sp->text->setAntialiased(true); + sp->text->setColor(clusterColor); + sp->text->setText(sp->dxcall); + sp->text->setFont(QFont(font().family(), 10)); + sp->text->setPositionAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + sp->text->position->setType(QCPItemPosition::ptPlotCoords); + sp->text->setSelectable(true); + QMargins margin; + int width = (sp->text->right - sp->text->left) / 2; + margin.setLeft(width); + margin.setRight(width); + sp->text->setPadding(margin); + sp->text->position->setCoords(left, top); + sp->text->setVisible(false); + while (conflict) { + QCPAbstractItem* item = plot->itemAt(sp->text->position->pixelPosition(), true); + QCPItemText* textItem = dynamic_cast (item); + if (textItem != nullptr && sp->text != textItem) { + //qInfo(logGui()) << "CONFLICT:" << textItem->text() << "SAME POSITION AS" << sp->dxcall << sp->text->position->pixelPosition(); + top = top - 5.0; + } + else { + //qInfo(logGui()) << "OK:" << sp->dxcall << sp->text->position->pixelPosition(); + conflict = false; + } + sp->text->position->setCoords(left, top); + } + + sp->text->setVisible(true); + clusterSpots.insert(sp->dxcall, sp); + } + } + + QMap::iterator spot2 = clusterSpots.begin(); + while (spot2 != clusterSpots.end()) { + if (spot2.value()->current == current) { + plot->removeItem(spot2.value()->text); + //qDebug(logCluster()) << "REMOVE:" << spot2.value()->dxcall; + delete spot2.value(); // Stop memory leak? + spot2 = clusterSpots.erase(spot2); + } + else { + ++spot2; + } + + } + + qDebug(logCluster()) << "Processing took" << timer.nsecsElapsed() / 1000 << "us"; +} + +void wfmain::on_clusterPopOutBtn_clicked() +{ + + if (settingsTabisAttached) + { + settingsTab = ui->tabWidget->currentWidget(); + ui->tabWidget->removeTab(ui->tabWidget->indexOf(settingsTab)); + settingsWidgetTab->addTab(settingsTab, "Settings"); + settingsWidgetWindow->show(); + ui->clusterPopOutBtn->setText("Re-attach"); + ui->colorPopOutBtn->setText("Re-attach"); + ui->tabWidget->setCurrentIndex(0); + settingsTabisAttached = false; + } + else { + settingsTab = settingsWidgetTab->currentWidget(); + + settingsWidgetTab->removeTab(settingsWidgetTab->indexOf(settingsTab)); + ui->tabWidget->addTab(settingsTab, "Settings"); + settingsWidgetWindow->close(); + + ui->clusterPopOutBtn->setText("Pop-Out"); + ui->colorPopOutBtn->setText("Pop-Out"); + ui->tabWidget->setCurrentIndex(3); + settingsTabisAttached = true; + } +} + +void wfmain::on_clickDragTuningEnableChk_clicked(bool checked) +{ + prefs.clickDragTuningEnable = checked; +} + void wfmain::on_usbControllerBtn_clicked() { if (shut != Q_NULLPTR) { @@ -7573,11 +8181,3 @@ void wfmain::on_usbControllerBtn_clicked() } } } - - -void wfmain::updateUsbButtons() -{ - -} - - diff --git a/wfmain.h b/wfmain.h index f4f6816..7c787a7 100644 --- a/wfmain.h +++ b/wfmain.h @@ -40,6 +40,7 @@ #include "selectradio.h" #include "colorprefs.h" #include "loggingwindow.h" +#include "cluster.h" #include #include @@ -67,9 +68,7 @@ class wfmain : public QMainWindow Q_OBJECT public: - explicit wfmain(const QString serialPortCL, const QString hostCL, const QString settingsFile, bool debugMode, QWidget *parent = 0); - QString serialPortCL; - QString hostCL; + explicit wfmain(const QString settingsFile, const QString logFile, bool debugMode, QWidget *parent = 0); ~wfmain(); static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); void handleLogText(QString text); @@ -80,7 +79,7 @@ signals: void setRigID(unsigned char rigID); void setRTSforPTT(bool enabled); - // Power + void sendPowerOn(); void sendPowerOff(); @@ -103,6 +102,7 @@ signals: // Repeater: void getDuplexMode(); + void getPassband(); void getTone(); void getTSQL(); void getDTCS(); @@ -196,7 +196,16 @@ signals: void shuttleLed(bool, unsigned char); void sendUsbControllerCommands(QVector* cmds); void sendUsbControllerButtons(QVector