From b8cdeaca59eaa97d7973c524e5ec6d2f5eb8836c Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 13:16:25 +0200 Subject: [PATCH 01/47] enable actions --- .github/workflows/github-actions.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/github-actions.yml diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..e007988 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,18 @@ +name: GitHub Actions Demo +run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 +on: [push] +jobs: + Explore-GitHub-Actions: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v3 + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ github.workspace }} + - run: echo "🍏 This job's status is ${{ job.status }}." From fcc006d8db7bbe0b74ab6865c32f5aec4f94f08d Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 13:29:54 +0200 Subject: [PATCH 02/47] testing docker builds --- .github/workflows/build.yml | 17 +++++++++++++++++ docker/ubuntu20.04/Dockerfile | 3 +++ 2 files changed, 20 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 docker/ubuntu20.04/Dockerfile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..895f50a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: Build packages +on: [push] + +env: + TARGET: package + +jobs: + build_focal + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build + + - name: Run Container + run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax diff --git a/docker/ubuntu20.04/Dockerfile b/docker/ubuntu20.04/Dockerfile new file mode 100644 index 0000000..390a423 --- /dev/null +++ b/docker/ubuntu20.04/Dockerfile @@ -0,0 +1,3 @@ +FROM ubuntu:20.4 + +RUN "Ubuntu 20.04 build" From fed84004c9f72955323ec0725b5c9139cefe1728 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 13:32:31 +0200 Subject: [PATCH 03/47] fix --- .github/workflows/build.yml | 18 +++++++++--------- .github/workflows/github-actions.yml | 18 ------------------ 2 files changed, 9 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/github-actions.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 895f50a..610f4ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,16 +2,16 @@ name: Build packages on: [push] env: - TARGET: package + TARGET: package jobs: - build_focal - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + build_focal: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 - - name: Create Docker Image - run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build - - name: Run Container - run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax + - name: Run Container + run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml deleted file mode 100644 index e007988..0000000 --- a/.github/workflows/github-actions.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: GitHub Actions Demo -run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 -on: [push] -jobs: - Explore-GitHub-Actions: - runs-on: ubuntu-latest - steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - - name: Check out repository code - uses: actions/checkout@v3 - - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - name: List files in the repository - run: | - ls ${{ github.workspace }} - - run: echo "🍏 This job's status is ${{ job.status }}." From 8adaf8ea4c846e4ed1606b2d8f68e8fa6fe9870c Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 13:33:29 +0200 Subject: [PATCH 04/47] fix 2 --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 610f4ea..9ab5dd5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,11 @@ env: jobs: build_focal: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + steps: + - uses: actions/checkout@v2 - - name: Create Docker Image - run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build - - name: Run Container - run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax + - name: Run Container + run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax From abd68c838e18cdc61f93c9531e80d03ab79a5ad6 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 13:46:42 +0200 Subject: [PATCH 05/47] fix 3 --- docker/ubuntu20.04/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/ubuntu20.04/Dockerfile b/docker/ubuntu20.04/Dockerfile index 390a423..4b08d90 100644 --- a/docker/ubuntu20.04/Dockerfile +++ b/docker/ubuntu20.04/Dockerfile @@ -1,3 +1,3 @@ -FROM ubuntu:20.4 +FROM ubuntu:20.04 -RUN "Ubuntu 20.04 build" +RUN echo "Ubuntu 20.04 build" From 52fc957c6444290347c770d5d7ebf713b6ef71a6 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 13:59:50 +0200 Subject: [PATCH 06/47] addedd new steps --- .github/workflows/build.yml | 16 ++++++++++++++++ docker/ubuntu20.04/Dockerfile | 1 + 2 files changed, 17 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ab5dd5..cda944a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,11 +5,27 @@ env: TARGET: package jobs: + download_wsjtx: + runs-on: ubuntu-latest + steps: + - name: Downlaod source code + run: wget https://physics.princeton.edu/pulsar/k1jt/wsjtx-2.5.4.tgz + + - name: Save WSJTX for next jobs + uses: actions/upload-artifact@v2 + with: + name: wsjtx-source-code + path: ${{runner.workspace}}/wsjtx-2.5.4.tgz + build_focal: + needs: ['download_wsjtx'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - name: Download artifacts + uses: actions/download-artifact@v2 + - name: Create Docker Image run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build diff --git a/docker/ubuntu20.04/Dockerfile b/docker/ubuntu20.04/Dockerfile index 4b08d90..cab98fe 100644 --- a/docker/ubuntu20.04/Dockerfile +++ b/docker/ubuntu20.04/Dockerfile @@ -1,3 +1,4 @@ FROM ubuntu:20.04 RUN echo "Ubuntu 20.04 build" + From 7432e846b81b3f0a78c4ae2ccf21dd43d204a570 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:01:54 +0200 Subject: [PATCH 07/47] addedd fixing download --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cda944a..985a107 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,9 @@ jobs: - name: Downlaod source code run: wget https://physics.princeton.edu/pulsar/k1jt/wsjtx-2.5.4.tgz + - name: where I am + run: pwd && ls -l + - name: Save WSJTX for next jobs uses: actions/upload-artifact@v2 with: From 386d8a4b8948ce179d55715e9433b5522fbcb9ab Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:05:43 +0200 Subject: [PATCH 08/47] addedd fixing download 2 --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 985a107..aded698 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,14 +11,11 @@ jobs: - name: Downlaod source code run: wget https://physics.princeton.edu/pulsar/k1jt/wsjtx-2.5.4.tgz - - name: where I am - run: pwd && ls -l - - name: Save WSJTX for next jobs uses: actions/upload-artifact@v2 with: name: wsjtx-source-code - path: ${{runner.workspace}}/wsjtx-2.5.4.tgz + path: ${{runner.workspace}}/wsjtx-regex/filter/wsjtx-2.5.4.tgz build_focal: needs: ['download_wsjtx'] From bcba48ea63c1c343ced58132f9c80255e63b5b08 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:06:26 +0200 Subject: [PATCH 09/47] addedd fixing download 3 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aded698..73b62f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: wsjtx-source-code - path: ${{runner.workspace}}/wsjtx-regex/filter/wsjtx-2.5.4.tgz + path: ${{runner.workspace}}/wsjtx-regex-filter/wsjtx-2.5.4.tgz build_focal: needs: ['download_wsjtx'] From 57551ded3d1bddcf6c3274c3fe551e8bb69bc401 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:12:39 +0200 Subject: [PATCH 10/47] testing variables and paths --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73b62f7..753f50b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,9 @@ jobs: - name: Downlaod source code run: wget https://physics.princeton.edu/pulsar/k1jt/wsjtx-2.5.4.tgz + - name: display variables + run: echo $GITHUB_WORKSPACE && echo ${{runner.workspace}} + - name: Save WSJTX for next jobs uses: actions/upload-artifact@v2 with: From 6728aee0cff3ba26360b3a851645cc0050347765 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:27:00 +0200 Subject: [PATCH 11/47] added patching step --- .github/workflows/build.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 753f50b..9203dab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,16 +11,13 @@ jobs: - name: Downlaod source code run: wget https://physics.princeton.edu/pulsar/k1jt/wsjtx-2.5.4.tgz - - name: display variables - run: echo $GITHUB_WORKSPACE && echo ${{runner.workspace}} - - name: Save WSJTX for next jobs uses: actions/upload-artifact@v2 with: name: wsjtx-source-code - path: ${{runner.workspace}}/wsjtx-regex-filter/wsjtx-2.5.4.tgz + path: $GITHUB_WORKSPACE/wsjtx-2.5.4.tgz - build_focal: + patch_source: needs: ['download_wsjtx'] runs-on: ubuntu-latest steps: @@ -28,6 +25,24 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v2 + with: + name: wsjtx-source-code + + - name: Unpack the source + run: mkdir $GITHUB_WORKSPACE/unpack && tar -xzvf $GITHUB_WORKSPACE/wsjtx-source-code/wsjtx-v2.5.4.tgz $GITHUB_WORSKPACE/unpack/ + + - name: Copy pattch + run: pwd && ls -l && $GITHUB_WORSKPACE/unpack/ + + + build_focal: + needs: ['patch_source'] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + name: wsjtx-patched-code - name: Create Docker Image run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build From 493988253f290a772eed901b9a189eec6dc326ff Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:30:51 +0200 Subject: [PATCH 12/47] test 5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9203dab..4e3b91c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: wsjtx-source-code - path: $GITHUB_WORKSPACE/wsjtx-2.5.4.tgz + path: wsjtx-2.5.4.tgz patch_source: needs: ['download_wsjtx'] From e7fffae0ca1288717a72a68119cbed3c3c14ff09 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:32:19 +0200 Subject: [PATCH 13/47] test 6 --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4e3b91c..886856d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,11 +28,11 @@ jobs: with: name: wsjtx-source-code - - name: Unpack the source - run: mkdir $GITHUB_WORKSPACE/unpack && tar -xzvf $GITHUB_WORKSPACE/wsjtx-source-code/wsjtx-v2.5.4.tgz $GITHUB_WORSKPACE/unpack/ +# - name: Unpack the source +# run: mkdir $GITHUB_WORKSPACE/unpack && tar -xzvf $GITHUB_WORKSPACE/wsjtx-source-code/wsjtx-v2.5.4.tgz $GITHUB_WORSKPACE/unpack/ - name: Copy pattch - run: pwd && ls -l && $GITHUB_WORSKPACE/unpack/ + run: mkdir $GITHUB_WORKSPACE/unpack && pwd && ls -l && $GITHUB_WORSKPACE/unpack/ build_focal: From 6aa856ce8f5f8e3ad09946162a4ecc98a7d01dca Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:34:46 +0200 Subject: [PATCH 14/47] test 7 --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 886856d..e690434 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,11 +28,11 @@ jobs: with: name: wsjtx-source-code -# - name: Unpack the source -# run: mkdir $GITHUB_WORKSPACE/unpack && tar -xzvf $GITHUB_WORKSPACE/wsjtx-source-code/wsjtx-v2.5.4.tgz $GITHUB_WORSKPACE/unpack/ + - name: Unpack the source + run: mkdir unpack && tar -xzvf wsjtx-v2.5.4.tgz unpack/ - name: Copy pattch - run: mkdir $GITHUB_WORKSPACE/unpack && pwd && ls -l && $GITHUB_WORSKPACE/unpack/ + run: pwd && ls -l && ls -l unpack/ build_focal: From 2cc52d476a532c2dd74d7cf205778886b2aa3fc6 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:34:57 +0200 Subject: [PATCH 15/47] test 8 --- patch/Configuration.cpp | 3211 ++++++++++++ patch/Configuration.hpp | 319 ++ patch/Configuration.ui | 3279 ++++++++++++ patch/widgets/mainwindow.cpp | 9469 ++++++++++++++++++++++++++++++++++ patch/widgets/mainwindow.h | 792 +++ patch/widgets/mainwindow.ui | 3650 +++++++++++++ 6 files changed, 20720 insertions(+) create mode 100755 patch/Configuration.cpp create mode 100755 patch/Configuration.hpp create mode 100755 patch/Configuration.ui create mode 100755 patch/widgets/mainwindow.cpp create mode 100755 patch/widgets/mainwindow.h create mode 100755 patch/widgets/mainwindow.ui diff --git a/patch/Configuration.cpp b/patch/Configuration.cpp new file mode 100755 index 0000000..3f30e65 --- /dev/null +++ b/patch/Configuration.cpp @@ -0,0 +1,3211 @@ +#include "Configuration.hpp" + +// +// Read me! +// +// This file defines a configuration dialog with the user. The general +// strategy is to expose agreed configuration parameters via a custom +// interface (See Configuration.hpp). The state exposed through this +// public interface reflects stored or derived data in the +// Configuration::impl object. The Configuration::impl structure is +// an implementation of the PIMPL (a.k.a. Cheshire Cat or compilation +// firewall) implementation hiding idiom that allows internal state to +// be completely removed from the public interface. +// +// There is a secondary level of parameter storage which reflects +// current settings UI state, these parameters are not copied to the +// state store that the public interface exposes until the +// Configuration:impl::accept() operation is successful. The accept() +// operation is tied to the settings OK button. The normal and most +// convenient place to store this intermediate settings UI state is in +// the data models of the UI controls, if that is not convenient then +// separate member variables must be used to store that state. It is +// important for the user experience that no publicly visible settings +// are changed while the settings UI are changed i.e. all settings +// changes must be deferred until the "OK" button is +// clicked. Conversely, all changes must be discarded if the settings +// UI "Cancel" button is clicked. +// +// There is a complication related to the radio interface since the +// this module offers the facility to test the radio interface. This +// test means that the public visibility to the radio being tested +// must be changed. To maintain the illusion of deferring changes +// until they are accepted, the original radio related settings are +// stored upon showing the UI and restored if the UI is dismissed by +// canceling. +// +// It should be noted that the settings UI lives as long as the +// application client that uses it does. It is simply shown and hidden +// as it is needed rather than creating it on demand. This strategy +// saves a lot of expensive UI drawing at the expense of a little +// storage and gives a convenient place to deliver settings values +// from. +// +// Here is an overview of the high level flow of this module: +// +// 1) On construction the initial environment is initialized and +// initial values for settings are read from the QSettings +// database. At this point default values for any new settings are +// established by providing a default value to the QSettings value +// queries. This should be the only place where a hard coded value for +// a settings item is defined. Any remaining one-time UI +// initialization is also done. At the end of the constructor a method +// initialize_models() is called to load the UI with the current +// settings values. +// +// 2) When the settings UI is displayed by a client calling the exec() +// operation, only temporary state need be stored as the UI state will +// already mirror the publicly visible settings state. +// +// 3) As the user makes changes to the settings UI only validation +// need be carried out since the UI control data models are used as +// the temporary store of unconfirmed settings. As some settings will +// depend on each other a validate() operation is available, this +// operation implements a check of any complex multi-field values. +// +// 4) If the user discards the settings changes by dismissing the UI +// with the "Cancel" button; the reject() operation is called. The +// reject() operation calls initialize_models() which will revert all +// the UI visible state to the values as at the initial exec() +// operation. No changes are moved into the data fields in +// Configuration::impl that reflect the settings state published by +// the public interface (Configuration.hpp). +// +// 5) If the user accepts the settings changes by dismissing the UI +// with the "OK" button; the accept() operation is called which calls +// the validate() operation again and, if it passes, the fields that +// are used to deliver the settings state are updated from the UI +// control models or other temporary state variables. At the end of +// the accept() operation, just before hiding the UI and returning +// control to the caller; the new settings values are stored into the +// settings database by a call to the write_settings() operation, thus +// ensuring that settings changes are saved even if the application +// crashes or is subsequently killed. +// +// 6) On destruction, which only happens when the application +// terminates, the settings are saved to the settings database by +// calling the write_settings() operation. This is largely redundant +// but is still done to save the default values of any new settings on +// an initial run. +// +// To add a new setting: +// +// 1) Update the UI with the new widget to view and change the value. +// +// 2) Add a member to Configuration::impl to store the accepted +// setting state. If the setting state is dynamic; add a new signal to +// broadcast the setting value. +// +// 3) Add a query method to the public interface (Configuration.hpp) +// to access the new setting value. If the settings is dynamic; this +// step is optional since value changes will be broadcast via a +// signal. +// +// 4) Add a forwarding operation to implement the new query (3) above. +// +// 5) Add a settings read call to read_settings() with a sensible +// default value. If the setting value is dynamic, add a signal emit +// call to broadcast the setting value change. +// +// 6) Add code to initialize_models() to load the widget control's +// data model with the current value. +// +// 7) If there is no convenient data model field, add a data member to +// store the proposed new value. Ensure this member has a valid value +// on exit from read_settings(). +// +// 8) Add any required inter-field validation to the validate() +// operation. +// +// 9) Add code to the accept() operation to extract the setting value +// from the widget control data model and load it into the +// Configuration::impl member that reflects the publicly visible +// setting state. If the setting value is dynamic; add a signal emit +// call to broadcast any changed state of the setting. +// +// 10) Add a settings write call to save the setting value to the +// settings database. +// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pimpl_impl.hpp" +#include "Logger.hpp" +#include "qt_helpers.hpp" +#include "MetaDataRegistry.hpp" +#include "SettingsGroup.hpp" +#include "widgets/FrequencyLineEdit.hpp" +#include "widgets/FrequencyDeltaLineEdit.hpp" +#include "item_delegates/CandidateKeyFilter.hpp" +#include "item_delegates/ForeignKeyDelegate.hpp" +#include "item_delegates/FrequencyDelegate.hpp" +#include "item_delegates/FrequencyDeltaDelegate.hpp" +#include "Transceiver/TransceiverFactory.hpp" +#include "Transceiver/Transceiver.hpp" +#include "models/Bands.hpp" +#include "models/IARURegions.hpp" +#include "models/Modes.hpp" +#include "models/FrequencyList.hpp" +#include "models/StationList.hpp" +#include "Network/NetworkServerLookup.hpp" +#include "widgets/MessageBox.hpp" +#include "validators/MaidenheadLocatorValidator.hpp" +#include "validators/CallsignValidator.hpp" +#include "Network/LotWUsers.hpp" +#include "models/DecodeHighlightingModel.hpp" +#include "logbook/logbook.h" +#include "widgets/LazyFillComboBox.hpp" + +#include "ui_Configuration.h" +#include "moc_Configuration.cpp" + +namespace +{ + // these undocumented flag values when stored in (Qt::UserRole - 1) + // of a ComboBox item model index allow the item to be enabled or + // disabled + int const combo_box_item_enabled (32 | 1); + int const combo_box_item_disabled (0); + +// QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"}; + QRegularExpression message_alphabet {"[- @A-Za-z0-9+./?#<>;$]*"}; + QRegularExpression RTTY_roundup_exchange_re { + R"( + ( + AL|AZ|AR|CA|CO|CT|DE|FL|GA # 48 contiguous states + |ID|IL|IN|IA|KS|KY|LA|ME|MD + |MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ + |NM|NY|NC|ND|OH|OK|OR|PA|RI|SC + |SD|TN|TX|UT|VT|VA|WA|WV|WI|WY + |NB|NS|QC|ON|MB|SK|AB|BC|NWT|NF # VE provinces + |LB|NU|YT|PEI + |DC # District of Columbia + |DX # anyone else + |SCC # Slovenia Contest Club contest + ) + )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption}; + + QRegularExpression field_day_exchange_re { + R"( + ( + [1-9] # # transmitters (1 to 32 inc.) + |[0-2]\d + |3[0-2] + ) + [A-F]\ * # class and optional space + ( + AB|AK|AL|AR|AZ|BC|CO|CT|DE|EB # ARRL/RAC section + |EMA|ENY|EPA|EWA|GA|GTA|IA|ID + |IL|IN|KS|KY|LA|LAX|MAR|MB|MDC + |ME|MI|MN|MO|MS|MT|NC|ND|NE|NFL + |NH|NL|NLI|NM|NNJ|NNY|NT|NTX|NV + |OH|OK|ONE|ONN|ONS|OR|ORG|PAC|PE + |PR|QC|RI|SB|SC|SCV|SD|SDG|SF + |SFL|SJV|SK|SNJ|STX|SV|TN|UT|VA + |VI|VT|WCF|WI|WMA|WNY|WPA|WTX + |WV|WWA|WY + |DX # anyone else + ) + )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption}; + + // Magic numbers for file validation + constexpr quint32 qrg_magic {0xadbccbdb}; + constexpr quint32 qrg_version {100}; // M.mm +} + + +// +// Dialog to get a new Frequency item +// +class FrequencyDialog final + : public QDialog +{ + Q_OBJECT + +public: + using Item = FrequencyList_v2::Item; + + explicit FrequencyDialog (IARURegions * regions_model, Modes * modes_model, QWidget * parent = nullptr) + : QDialog {parent} + { + setWindowTitle (QApplication::applicationName () + " - " + + tr ("Add Frequency")); + region_combo_box_.setModel (regions_model); + mode_combo_box_.setModel (modes_model); + + auto form_layout = new QFormLayout (); + form_layout->addRow (tr ("IARU &Region:"), ®ion_combo_box_); + form_layout->addRow (tr ("&Mode:"), &mode_combo_box_); + form_layout->addRow (tr ("&Frequency (MHz):"), &frequency_line_edit_); + + auto main_layout = new QVBoxLayout (this); + main_layout->addLayout (form_layout); + + auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; + main_layout->addWidget (button_box); + + connect (button_box, &QDialogButtonBox::accepted, this, &FrequencyDialog::accept); + connect (button_box, &QDialogButtonBox::rejected, this, &FrequencyDialog::reject); + } + + Item item () const + { + return {frequency_line_edit_.frequency () + , Modes::value (mode_combo_box_.currentText ()) + , IARURegions::value (region_combo_box_.currentText ())}; + } + +private: + QComboBox region_combo_box_; + QComboBox mode_combo_box_; + FrequencyLineEdit frequency_line_edit_; +}; + + +// +// Dialog to get a new Station item +// +class StationDialog final + : public QDialog +{ + Q_OBJECT + +public: + explicit StationDialog (StationList const * stations, Bands * bands, QWidget * parent = nullptr) + : QDialog {parent} + , filtered_bands_ {new CandidateKeyFilter {bands, stations, 0, 0}} + { + setWindowTitle (QApplication::applicationName () + " - " + tr ("Add Station")); + + band_.setModel (filtered_bands_.data ()); + + auto form_layout = new QFormLayout (); + form_layout->addRow (tr ("&Band:"), &band_); + form_layout->addRow (tr ("&Offset (MHz):"), &delta_); + form_layout->addRow (tr ("&Antenna:"), &description_); + + auto main_layout = new QVBoxLayout (this); + main_layout->addLayout (form_layout); + + auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; + main_layout->addWidget (button_box); + + connect (button_box, &QDialogButtonBox::accepted, this, &StationDialog::accept); + connect (button_box, &QDialogButtonBox::rejected, this, &StationDialog::reject); + + if (delta_.text ().isEmpty ()) + { + delta_.setText ("0"); + } + } + + StationList::Station station () const + { + return {band_.currentText (), delta_.frequency_delta (), description_.text ()}; + } + + int exec () override + { + filtered_bands_->set_active_key (); + return QDialog::exec (); + } + +private: + QScopedPointer filtered_bands_; + + QComboBox band_; + FrequencyDeltaLineEdit delta_; + QLineEdit description_; +}; + +class RearrangableMacrosModel + : public QStringListModel +{ +public: + Qt::ItemFlags flags (QModelIndex const& index) const override + { + auto flags = QStringListModel::flags (index); + if (index.isValid ()) + { + // disallow drop onto existing items + flags &= ~Qt::ItemIsDropEnabled; + } + return flags; + } +}; + + +// +// Class MessageItemDelegate +// +// Item delegate for message entry such as free text message macros. +// +class MessageItemDelegate final + : public QStyledItemDelegate +{ +public: + explicit MessageItemDelegate (QObject * parent = nullptr) + : QStyledItemDelegate {parent} + { + } + + QWidget * createEditor (QWidget * parent + , QStyleOptionViewItem const& /* option*/ + , QModelIndex const& /* index */ + ) const override + { + auto editor = new QLineEdit {parent}; + editor->setFrame (false); + editor->setValidator (new QRegularExpressionValidator {message_alphabet, editor}); + return editor; + } +}; + +// Internal implementation of the Configuration class. +class Configuration::impl final + : public QDialog +{ + Q_OBJECT; + +public: + using FrequencyDelta = Radio::FrequencyDelta; + using port_type = Configuration::port_type; + using audio_info_type = QPair >; + + explicit impl (Configuration * self + , QNetworkAccessManager * network_manager + , QDir const& temp_directory + , QSettings * settings + , LogBook * logbook + , QWidget * parent); + ~impl (); + + bool have_rig (); + + void transceiver_frequency (Frequency); + void transceiver_tx_frequency (Frequency); + void transceiver_mode (MODE); + void transceiver_ptt (bool); + void sync_transceiver (bool force_signal); + + Q_SLOT int exec () override; + Q_SLOT void accept () override; + Q_SLOT void reject () override; + Q_SLOT void done (int) override; + +private: + typedef QList AudioDevices; + + void read_settings (); + void write_settings (); + + void find_audio_devices (); + QAudioDeviceInfo find_audio_device (QAudio::Mode, QComboBox *, QString const& device_name); + void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); + void update_audio_channels (QComboBox const *, int, QComboBox *, bool); + + void load_network_interfaces (CheckableItemComboBox *, QStringList current); + Q_SLOT void validate_network_interfaces (QString const&); + QStringList get_selected_network_interfaces (CheckableItemComboBox *); + Q_SLOT void host_info_results (QHostInfo); + void check_multicast (QHostAddress const&); + + void find_tab (QWidget *); + + void initialize_models (); + bool split_mode () const + { + return + (WSJT_RIG_NONE_CAN_SPLIT || !rig_is_dummy_) && + (rig_params_.split_mode != TransceiverFactory::split_mode_none); + } + void set_cached_mode (); + bool open_rig (bool force = false); + //bool set_mode (); + void close_rig (); + TransceiverFactory::ParameterPack gather_rig_data (); + void enumerate_rigs (); + void set_rig_invariants (); + bool validate (); + void fill_port_combo_box (QComboBox *); + Frequency apply_calibration (Frequency) const; + Frequency remove_calibration (Frequency) const; + + void delete_frequencies (); + void load_frequencies (); + void merge_frequencies (); + void save_frequencies (); + void reset_frequencies (); + void insert_frequency (); + FrequencyList_v2::FrequencyItems read_frequencies_file (QString const&); + + void delete_stations (); + void insert_station (); + + Q_SLOT void on_font_push_button_clicked (); + Q_SLOT void on_decoded_text_font_push_button_clicked (); + Q_SLOT void on_PTT_port_combo_box_activated (int); + Q_SLOT void on_CAT_port_combo_box_activated (int); + Q_SLOT void on_CAT_serial_baud_combo_box_currentIndexChanged (int); + Q_SLOT void on_CAT_data_bits_button_group_buttonClicked (int); + Q_SLOT void on_CAT_stop_bits_button_group_buttonClicked (int); + Q_SLOT void on_CAT_handshake_button_group_buttonClicked (int); + Q_SLOT void on_CAT_poll_interval_spin_box_valueChanged (int); + Q_SLOT void on_split_mode_button_group_buttonClicked (int); + Q_SLOT void on_test_CAT_push_button_clicked (); + Q_SLOT void on_test_PTT_push_button_clicked (bool checked); + Q_SLOT void on_force_DTR_combo_box_currentIndexChanged (int); + Q_SLOT void on_force_RTS_combo_box_currentIndexChanged (int); + Q_SLOT void on_rig_combo_box_currentIndexChanged (int); + Q_SLOT void on_add_macro_push_button_clicked (bool = false); + Q_SLOT void on_delete_macro_push_button_clicked (bool = false); + Q_SLOT void on_PTT_method_button_group_buttonClicked (int); + Q_SLOT void on_add_macro_line_edit_editingFinished (); + Q_SLOT void delete_macro (); + void delete_selected_macros (QModelIndexList); + Q_SLOT void on_udp_server_line_edit_textChanged (QString const&); + Q_SLOT void on_udp_server_line_edit_editingFinished (); + Q_SLOT void on_save_path_select_push_button_clicked (bool); + Q_SLOT void on_azel_path_select_push_button_clicked (bool); + Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double); + Q_SLOT void on_calibration_slope_ppm_spin_box_valueChanged (double); + Q_SLOT void handle_transceiver_update (TransceiverState const&, unsigned sequence_number); + Q_SLOT void handle_transceiver_failure (QString const& reason); + Q_SLOT void on_reset_highlighting_to_defaults_push_button_clicked (bool); + Q_SLOT void on_rescan_log_push_button_clicked (bool); + Q_SLOT void on_LotW_CSV_fetch_push_button_clicked (bool); + Q_SLOT void on_cbx2ToneSpacing_clicked(bool); + Q_SLOT void on_cbx4ToneSpacing_clicked(bool); + Q_SLOT void on_prompt_to_log_check_box_clicked(bool); + Q_SLOT void on_cbAutoLog_clicked(bool); + Q_SLOT void on_Field_Day_Exchange_textEdited (QString const&); + Q_SLOT void on_RTTY_Exchange_textEdited (QString const&); + + // typenames used as arguments must match registered type names :( + Q_SIGNAL void start_transceiver (unsigned seqeunce_number) const; + Q_SIGNAL void set_transceiver (Transceiver::TransceiverState const&, + unsigned sequence_number) const; + Q_SIGNAL void stop_transceiver () const; + + Configuration * const self_; // back pointer to public interface + + QThread * transceiver_thread_; + TransceiverFactory transceiver_factory_; + QList rig_connections_; + + QScopedPointer ui_; + + QNetworkAccessManager * network_manager_; + QSettings * settings_; + LogBook * logbook_; + + QDir doc_dir_; + QDir data_dir_; + QDir temp_dir_; + QDir writeable_data_dir_; + QDir default_save_directory_; + QDir save_directory_; + QDir default_azel_directory_; + QDir azel_directory_; + + QFont font_; + QFont next_font_; + + QFont decoded_text_font_; + QFont next_decoded_text_font_; + + LotWUsers lotw_users_; + + bool restart_sound_input_device_; + bool restart_sound_output_device_; + + Type2MsgGen type_2_msg_gen_; + + QStringListModel macros_; + RearrangableMacrosModel next_macros_; + QAction * macro_delete_action_; + + Bands bands_; + IARURegions regions_; + IARURegions::Region region_; + Modes modes_; + FrequencyList_v2 frequencies_; + FrequencyList_v2 next_frequencies_; + StationList stations_; + StationList next_stations_; + FrequencyDelta current_offset_; + FrequencyDelta current_tx_offset_; + + QAction * frequency_delete_action_; + QAction * frequency_insert_action_; + QAction * load_frequencies_action_; + QAction * save_frequencies_action_; + QAction * merge_frequencies_action_; + QAction * reset_frequencies_action_; + FrequencyDialog * frequency_dialog_; + + QAction station_delete_action_; + QAction station_insert_action_; + StationDialog * station_dialog_; + + DecodeHighlightingModel decode_highlighing_model_; + DecodeHighlightingModel next_decode_highlighing_model_; + bool highlight_by_mode_; + bool highlight_only_fields_; + bool include_WAE_entities_; + int LotW_days_since_upload_; + + TransceiverFactory::ParameterPack rig_params_; + TransceiverFactory::ParameterPack saved_rig_params_; + TransceiverFactory::Capabilities::PortType last_port_type_; + bool rig_is_dummy_; + bool rig_active_; + bool have_rig_; + bool rig_changed_; + TransceiverState cached_rig_state_; + int rig_resolution_; // see Transceiver::resolution signal + CalibrationParams calibration_; + bool frequency_calibration_disabled_; // not persistent + unsigned transceiver_command_number_; + QString dynamic_grid_; + + // configuration fields that we publish + QString my_callsign_; + //SP6XD + QString regex_filter_; + QString my_grid_; + QString FD_exchange_; + QString RTTY_exchange_; + + qint32 id_interval_; + qint32 ntrials_; + qint32 aggressive_; + qint32 RxBandwidth_; + double degrade_; + double txDelay_; + bool id_after_73_; + bool tx_QSY_allowed_; + bool spot_to_psk_reporter_; + bool psk_reporter_tcpip_; + bool monitor_off_at_startup_; + bool monitor_last_used_; + bool log_as_RTTY_; + bool report_in_comments_; + bool prompt_to_log_; + bool autoLog_; + bool decodes_from_top_; + bool insert_blank_; + bool DXCC_; + bool ppfx_; + bool clear_DX_; + bool miles_; + bool quick_call_; + bool disable_TX_on_73_; + bool force_call_1st_; + bool alternate_bindings_; + int watchdog_; + bool TX_messages_; + bool enable_VHF_features_; + bool decode_at_52s_; + bool single_decode_; + bool twoPass_; + bool bSpecialOp_; + int SelectedActivity_; + bool x2ToneSpacing_; + bool x4ToneSpacing_; + bool use_dynamic_grid_; + QString opCall_; + QString udp_server_name_; + bool udp_server_name_edited_; + int dns_lookup_id_; + port_type udp_server_port_; + QStringList udp_interface_names_; + QString loopback_interface_name_; + int udp_TTL_; + QString n1mm_server_name_; + port_type n1mm_server_port_; + bool broadcast_to_n1mm_; + bool accept_udp_requests_; + bool udpWindowToFront_; + bool udpWindowRestore_; + DataMode data_mode_; + bool bLowSidelobes_; + bool pwrBandTxMemory_; + bool pwrBandTuneMemory_; + + QAudioDeviceInfo audio_input_device_; + QAudioDeviceInfo next_audio_input_device_; + AudioDevice::Channel audio_input_channel_; + AudioDevice::Channel next_audio_input_channel_; + QAudioDeviceInfo audio_output_device_; + QAudioDeviceInfo next_audio_output_device_; + AudioDevice::Channel audio_output_channel_; + AudioDevice::Channel next_audio_output_channel_; + + friend class Configuration; +}; + +#include "Configuration.moc" + + +// delegate to implementation class +Configuration::Configuration (QNetworkAccessManager * network_manager, QDir const& temp_directory, + QSettings * settings, LogBook * logbook, QWidget * parent) + : m_ {this, network_manager, temp_directory, settings, logbook, parent} +{ +} + +Configuration::~Configuration () +{ +} + +QDir Configuration::doc_dir () const {return m_->doc_dir_;} +QDir Configuration::data_dir () const {return m_->data_dir_;} +QDir Configuration::writeable_data_dir () const {return m_->writeable_data_dir_;} +QDir Configuration::temp_dir () const {return m_->temp_dir_;} + +void Configuration::select_tab (int index) {m_->ui_->configuration_tabs->setCurrentIndex (index);} +int Configuration::exec () {return m_->exec ();} +bool Configuration::is_active () const {return m_->isVisible ();} + +QAudioDeviceInfo const& Configuration::audio_input_device () const {return m_->audio_input_device_;} +AudioDevice::Channel Configuration::audio_input_channel () const {return m_->audio_input_channel_;} +QAudioDeviceInfo const& Configuration::audio_output_device () const {return m_->audio_output_device_;} +AudioDevice::Channel Configuration::audio_output_channel () const {return m_->audio_output_channel_;} +bool Configuration::restart_audio_input () const {return m_->restart_sound_input_device_;} +bool Configuration::restart_audio_output () const {return m_->restart_sound_output_device_;} +auto Configuration::type_2_msg_gen () const -> Type2MsgGen {return m_->type_2_msg_gen_;} +QString Configuration::my_callsign () const {return m_->my_callsign_;} +//SP6XD +QString Configuration::regex_filter () const {return m_->regex_filter_;} +QFont Configuration::text_font () const {return m_->font_;} +QFont Configuration::decoded_text_font () const {return m_->decoded_text_font_;} +qint32 Configuration::id_interval () const {return m_->id_interval_;} +qint32 Configuration::ntrials() const {return m_->ntrials_;} +qint32 Configuration::aggressive() const {return m_->aggressive_;} +double Configuration::degrade() const {return m_->degrade_;} +double Configuration::txDelay() const {return m_->txDelay_;} +qint32 Configuration::RxBandwidth() const {return m_->RxBandwidth_;} +bool Configuration::id_after_73 () const {return m_->id_after_73_;} +bool Configuration::tx_QSY_allowed () const {return m_->tx_QSY_allowed_;} +bool Configuration::spot_to_psk_reporter () const +{ + // rig must be open and working to spot externally + return is_transceiver_online () && m_->spot_to_psk_reporter_; +} +bool Configuration::psk_reporter_tcpip () const {return m_->psk_reporter_tcpip_;} +bool Configuration::monitor_off_at_startup () const {return m_->monitor_off_at_startup_;} +bool Configuration::monitor_last_used () const {return m_->rig_is_dummy_ || m_->monitor_last_used_;} +bool Configuration::log_as_RTTY () const {return m_->log_as_RTTY_;} +bool Configuration::report_in_comments () const {return m_->report_in_comments_;} +bool Configuration::prompt_to_log () const {return m_->prompt_to_log_;} +bool Configuration::autoLog() const {return m_->autoLog_;} +bool Configuration::decodes_from_top () const {return m_->decodes_from_top_;} +bool Configuration::insert_blank () const {return m_->insert_blank_;} +bool Configuration::DXCC () const {return m_->DXCC_;} +bool Configuration::ppfx() const {return m_->ppfx_;} +bool Configuration::clear_DX () const {return m_->clear_DX_;} +bool Configuration::miles () const {return m_->miles_;} +bool Configuration::quick_call () const {return m_->quick_call_;} +bool Configuration::disable_TX_on_73 () const {return m_->disable_TX_on_73_;} +bool Configuration::force_call_1st() const {return m_->force_call_1st_;} +bool Configuration::alternate_bindings() const {return m_->alternate_bindings_;} +int Configuration::watchdog () const {return m_->watchdog_;} +bool Configuration::TX_messages () const {return m_->TX_messages_;} +bool Configuration::enable_VHF_features () const {return m_->enable_VHF_features_;} +bool Configuration::decode_at_52s () const {return m_->decode_at_52s_;} +bool Configuration::single_decode () const {return m_->single_decode_;} +bool Configuration::twoPass() const {return m_->twoPass_;} +bool Configuration::x2ToneSpacing() const {return m_->x2ToneSpacing_;} +bool Configuration::x4ToneSpacing() const {return m_->x4ToneSpacing_;} +bool Configuration::split_mode () const {return m_->split_mode ();} +QString Configuration::opCall() const {return m_->opCall_;} +void Configuration::opCall (QString const& call) {m_->opCall_ = call;} +QString Configuration::udp_server_name () const {return m_->udp_server_name_;} +auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;} +QStringList Configuration::udp_interface_names () const {return m_->udp_interface_names_;} +int Configuration::udp_TTL () const {return m_->udp_TTL_;} +bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;} +QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;} +auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;} +bool Configuration::broadcast_to_n1mm () const {return m_->broadcast_to_n1mm_;} +bool Configuration::lowSidelobes() const {return m_->bLowSidelobes_;} +bool Configuration::udpWindowToFront () const {return m_->udpWindowToFront_;} +bool Configuration::udpWindowRestore () const {return m_->udpWindowRestore_;} +Bands * Configuration::bands () {return &m_->bands_;} +Bands const * Configuration::bands () const {return &m_->bands_;} +StationList * Configuration::stations () {return &m_->stations_;} +StationList const * Configuration::stations () const {return &m_->stations_;} +IARURegions::Region Configuration::region () const {return m_->region_;} +FrequencyList_v2 * Configuration::frequencies () {return &m_->frequencies_;} +FrequencyList_v2 const * Configuration::frequencies () const {return &m_->frequencies_;} +QStringListModel * Configuration::macros () {return &m_->macros_;} +QStringListModel const * Configuration::macros () const {return &m_->macros_;} +QDir Configuration::save_directory () const {return m_->save_directory_;} +QDir Configuration::azel_directory () const {return m_->azel_directory_;} +QString Configuration::rig_name () const {return m_->rig_params_.rig_name;} +bool Configuration::pwrBandTxMemory () const {return m_->pwrBandTxMemory_;} +bool Configuration::pwrBandTuneMemory () const {return m_->pwrBandTuneMemory_;} +LotWUsers const& Configuration::lotw_users () const {return m_->lotw_users_;} +DecodeHighlightingModel const& Configuration::decode_highlighting () const {return m_->decode_highlighing_model_;} +bool Configuration::highlight_by_mode () const {return m_->highlight_by_mode_;} +bool Configuration::highlight_only_fields () const {return m_->highlight_only_fields_;} +bool Configuration::include_WAE_entities () const {return m_->include_WAE_entities_;} + +void Configuration::set_calibration (CalibrationParams params) +{ + m_->calibration_ = params; +} + +void Configuration::enable_calibration (bool on) +{ + auto target_frequency = m_->remove_calibration (m_->cached_rig_state_.frequency ()) - m_->current_offset_; + m_->frequency_calibration_disabled_ = !on; + transceiver_frequency (target_frequency); +} + +bool Configuration::is_transceiver_online () const +{ + return m_->rig_active_; +} + +bool Configuration::is_dummy_rig () const +{ + return m_->rig_is_dummy_; +} + +bool Configuration::transceiver_online () +{ + LOG_TRACE (m_->cached_rig_state_); + return m_->have_rig (); +} + +int Configuration::transceiver_resolution () const +{ + return m_->rig_resolution_; +} + +void Configuration::transceiver_offline () +{ + LOG_TRACE (m_->cached_rig_state_); + m_->close_rig (); +} + +void Configuration::transceiver_frequency (Frequency f) +{ + LOG_TRACE (f << ' ' << m_->cached_rig_state_); + m_->transceiver_frequency (f); +} + +void Configuration::transceiver_tx_frequency (Frequency f) +{ + LOG_TRACE (f << ' ' << m_->cached_rig_state_); + m_->transceiver_tx_frequency (f); +} + +void Configuration::transceiver_mode (MODE mode) +{ + LOG_TRACE (mode << ' ' << m_->cached_rig_state_); + m_->transceiver_mode (mode); +} + +void Configuration::transceiver_ptt (bool on) +{ + LOG_TRACE (on << ' ' << m_->cached_rig_state_); + m_->transceiver_ptt (on); +} + +void Configuration::sync_transceiver (bool force_signal, bool enforce_mode_and_split) +{ + LOG_TRACE ("force signal: " << force_signal << " enforce_mode_and_split: " << enforce_mode_and_split << ' ' << m_->cached_rig_state_); + m_->sync_transceiver (force_signal); + if (!enforce_mode_and_split) + { + m_->transceiver_tx_frequency (0); + } +} + +void Configuration::invalidate_audio_input_device (QString /* error */) +{ + m_->audio_input_device_ = QAudioDeviceInfo {}; +} + +void Configuration::invalidate_audio_output_device (QString /* error */) +{ + m_->audio_output_device_ = QAudioDeviceInfo {}; +} + +bool Configuration::valid_n1mm_info () const +{ + // do very rudimentary checking on the n1mm server name and port number. + // + auto server_name = m_->n1mm_server_name_; + auto port_number = m_->n1mm_server_port_; + return(!(server_name.trimmed().isEmpty() || port_number == 0)); +} + +QString Configuration::my_grid() const +{ + auto the_grid = m_->my_grid_; + if (m_->use_dynamic_grid_ && m_->dynamic_grid_.size () >= 4) { + the_grid = m_->dynamic_grid_; + } + return the_grid; +} + +QString Configuration::Field_Day_Exchange() const +{ + return m_->FD_exchange_; +} + +void Configuration::setEU_VHF_Contest() +{ + m_->bSpecialOp_=true; + m_->ui_->gbSpecialOpActivity->setChecked(m_->bSpecialOp_); + m_->ui_->rbEU_VHF_Contest->setChecked(true); + m_->SelectedActivity_ = static_cast (SpecialOperatingActivity::EU_VHF); + m_->write_settings(); +} + +QString Configuration::RTTY_Exchange() const +{ + return m_->RTTY_exchange_; +} + +auto Configuration::special_op_id () const -> SpecialOperatingActivity +{ + return m_->bSpecialOp_ ? static_cast (m_->SelectedActivity_) : SpecialOperatingActivity::NONE; +} + +void Configuration::set_location (QString const& grid_descriptor) +{ + // change the dynamic grid + // qDebug () << "Configuration::set_location - location:" << grid_descriptor; + m_->dynamic_grid_ = grid_descriptor.trimmed (); +} + +namespace +{ +#if defined (Q_OS_MAC) + char const * app_root = "/../../../"; +#else + char const * app_root = "/../"; +#endif + QString doc_path () + { +#if CMAKE_BUILD + if (QDir::isRelativePath (CMAKE_INSTALL_DOCDIR)) + { + return QApplication::applicationDirPath () + app_root + CMAKE_INSTALL_DOCDIR; + } + return CMAKE_INSTALL_DOCDIR; +#else + return QApplication::applicationDirPath (); +#endif + } + + QString data_path () + { +#if CMAKE_BUILD + if (QDir::isRelativePath (CMAKE_INSTALL_DATADIR)) + { + return QApplication::applicationDirPath () + app_root + CMAKE_INSTALL_DATADIR + QChar {'/'} + CMAKE_PROJECT_NAME; + } + return CMAKE_INSTALL_DATADIR; +#else + return QApplication::applicationDirPath (); +#endif + } +} + +Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network_manager + , QDir const& temp_directory, QSettings * settings, LogBook * logbook + , QWidget * parent) + : QDialog {parent} + , self_ {self} + , transceiver_thread_ {nullptr} + , ui_ {new Ui::configuration_dialog} + , network_manager_ {network_manager} + , settings_ {settings} + , logbook_ {logbook} + , doc_dir_ {doc_path ()} + , data_dir_ {data_path ()} + , temp_dir_ {temp_directory} + , writeable_data_dir_ {QStandardPaths::writableLocation (QStandardPaths::DataLocation)} + , lotw_users_ {network_manager_} + , restart_sound_input_device_ {false} + , restart_sound_output_device_ {false} + , frequencies_ {&bands_} + , next_frequencies_ {&bands_} + , stations_ {&bands_} + , next_stations_ {&bands_} + , current_offset_ {0} + , current_tx_offset_ {0} + , frequency_dialog_ {new FrequencyDialog {®ions_, &modes_, this}} + , station_delete_action_ {tr ("&Delete"), nullptr} + , station_insert_action_ {tr ("&Insert ..."), nullptr} + , station_dialog_ {new StationDialog {&next_stations_, &bands_, this}} + , highlight_by_mode_ {false} + , highlight_only_fields_ {false} + , include_WAE_entities_ {false} + , LotW_days_since_upload_ {0} + , last_port_type_ {TransceiverFactory::Capabilities::none} + , rig_is_dummy_ {false} + , rig_active_ {false} + , have_rig_ {false} + , rig_changed_ {false} + , rig_resolution_ {0} + , frequency_calibration_disabled_ {false} + , transceiver_command_number_ {0} + , degrade_ {0.} // initialize to zero each run, not + // saved in settings + , udp_server_name_edited_ {false} + , dns_lookup_id_ {-1} +{ + ui_->setupUi (this); + + { + // Make sure the default save directory exists + QString save_dir {"save"}; + default_save_directory_ = writeable_data_dir_; + default_azel_directory_ = writeable_data_dir_; + if (!default_save_directory_.mkpath (save_dir) || !default_save_directory_.cd (save_dir)) + { + MessageBox::critical_message (this, tr ("Failed to create save directory"), + tr ("path: \"%1\%") + .arg (default_save_directory_.absoluteFilePath (save_dir))); + throw std::runtime_error {"Failed to create save directory"}; + } + + // we now have a deafult save path that exists + + // make sure samples directory exists + QString samples_dir {"samples"}; + if (!default_save_directory_.mkpath (samples_dir)) + { + MessageBox::critical_message (this, tr ("Failed to create samples directory"), + tr ("path: \"%1\"") + .arg (default_save_directory_.absoluteFilePath (samples_dir))); + throw std::runtime_error {"Failed to create samples directory"}; + } + + // copy in any new sample files to the sample directory + QDir dest_dir {default_save_directory_}; + dest_dir.cd (samples_dir); + + QDir source_dir {":/" + samples_dir}; + source_dir.cd (save_dir); + source_dir.cd (samples_dir); + auto list = source_dir.entryInfoList (QStringList {{"*.wav"}}, QDir::Files | QDir::Readable); + Q_FOREACH (auto const& item, list) + { + if (!dest_dir.exists (item.fileName ())) + { + QFile file {item.absoluteFilePath ()}; + file.copy (dest_dir.absoluteFilePath (item.fileName ())); + } + } + } + + // this must be done after the default paths above are set + read_settings (); + + // set up dynamic loading of audio devices + connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { + QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); + load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_); + update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false); + ui_->sound_input_channel_combo_box->setCurrentIndex (next_audio_input_channel_); + QGuiApplication::restoreOverrideCursor (); + }); + connect (ui_->sound_output_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { + QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); + load_audio_devices (QAudio::AudioOutput, ui_->sound_output_combo_box, &next_audio_output_device_); + update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); + ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_); + QGuiApplication::restoreOverrideCursor (); + }); + + // set up dynamic loading of network interfaces + connect (ui_->udp_interfaces_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { + QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); + load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); + QGuiApplication::restoreOverrideCursor (); + }); + connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, this, &Configuration::impl::validate_network_interfaces); + + // set up LoTW users CSV file fetching + connect (&lotw_users_, &LotWUsers::load_finished, [this] () { + ui_->LotW_CSV_fetch_push_button->setEnabled (true); + }); + lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv")); + + // + // validation + // + ui_->callsign_line_edit->setValidator (new CallsignValidator {this}); + ui_->grid_line_edit->setValidator (new MaidenheadLocatorValidator {this}); + ui_->add_macro_line_edit->setValidator (new QRegularExpressionValidator {message_alphabet, this}); + ui_->Field_Day_Exchange->setValidator (new QRegularExpressionValidator {field_day_exchange_re, this}); + ui_->RTTY_Exchange->setValidator (new QRegularExpressionValidator {RTTY_roundup_exchange_re, this}); + + // + // assign ids to radio buttons + // + ui_->CAT_data_bits_button_group->setId (ui_->CAT_default_bit_radio_button, TransceiverFactory::default_data_bits); + ui_->CAT_data_bits_button_group->setId (ui_->CAT_7_bit_radio_button, TransceiverFactory::seven_data_bits); + ui_->CAT_data_bits_button_group->setId (ui_->CAT_8_bit_radio_button, TransceiverFactory::eight_data_bits); + + ui_->CAT_stop_bits_button_group->setId (ui_->CAT_default_stop_bit_radio_button, TransceiverFactory::default_stop_bits); + ui_->CAT_stop_bits_button_group->setId (ui_->CAT_one_stop_bit_radio_button, TransceiverFactory::one_stop_bit); + ui_->CAT_stop_bits_button_group->setId (ui_->CAT_two_stop_bit_radio_button, TransceiverFactory::two_stop_bits); + + ui_->CAT_handshake_button_group->setId (ui_->CAT_handshake_default_radio_button, TransceiverFactory::handshake_default); + ui_->CAT_handshake_button_group->setId (ui_->CAT_handshake_none_radio_button, TransceiverFactory::handshake_none); + ui_->CAT_handshake_button_group->setId (ui_->CAT_handshake_xon_radio_button, TransceiverFactory::handshake_XonXoff); + ui_->CAT_handshake_button_group->setId (ui_->CAT_handshake_hardware_radio_button, TransceiverFactory::handshake_hardware); + + ui_->PTT_method_button_group->setId (ui_->PTT_VOX_radio_button, TransceiverFactory::PTT_method_VOX); + ui_->PTT_method_button_group->setId (ui_->PTT_CAT_radio_button, TransceiverFactory::PTT_method_CAT); + ui_->PTT_method_button_group->setId (ui_->PTT_DTR_radio_button, TransceiverFactory::PTT_method_DTR); + ui_->PTT_method_button_group->setId (ui_->PTT_RTS_radio_button, TransceiverFactory::PTT_method_RTS); + + ui_->TX_audio_source_button_group->setId (ui_->TX_source_mic_radio_button, TransceiverFactory::TX_audio_source_front); + ui_->TX_audio_source_button_group->setId (ui_->TX_source_data_radio_button, TransceiverFactory::TX_audio_source_rear); + + ui_->TX_mode_button_group->setId (ui_->mode_none_radio_button, data_mode_none); + ui_->TX_mode_button_group->setId (ui_->mode_USB_radio_button, data_mode_USB); + ui_->TX_mode_button_group->setId (ui_->mode_data_radio_button, data_mode_data); + + ui_->split_mode_button_group->setId (ui_->split_none_radio_button, TransceiverFactory::split_mode_none); + ui_->split_mode_button_group->setId (ui_->split_rig_radio_button, TransceiverFactory::split_mode_rig); + ui_->split_mode_button_group->setId (ui_->split_emulate_radio_button, TransceiverFactory::split_mode_emulate); + + ui_->special_op_activity_button_group->setId (ui_->rbNA_VHF_Contest, static_cast (SpecialOperatingActivity::NA_VHF)); + ui_->special_op_activity_button_group->setId (ui_->rbEU_VHF_Contest, static_cast (SpecialOperatingActivity::EU_VHF)); + ui_->special_op_activity_button_group->setId (ui_->rbField_Day, static_cast (SpecialOperatingActivity::FIELD_DAY)); + ui_->special_op_activity_button_group->setId (ui_->rbRTTY_Roundup, static_cast (SpecialOperatingActivity::RTTY)); + ui_->special_op_activity_button_group->setId (ui_->rbWW_DIGI, static_cast (SpecialOperatingActivity::WW_DIGI)); + ui_->special_op_activity_button_group->setId (ui_->rbFox, static_cast (SpecialOperatingActivity::FOX)); + ui_->special_op_activity_button_group->setId (ui_->rbHound, static_cast (SpecialOperatingActivity::HOUND)); + + // + // setup PTT port combo box drop down content + // + fill_port_combo_box (ui_->PTT_port_combo_box); + ui_->PTT_port_combo_box->addItem ("CAT"); + ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->count () - 1, "Delegate to proxy CAT service", Qt::ToolTipRole); + + // + // setup hooks to keep audio channels aligned with devices + // + { + using namespace std; + using namespace std::placeholders; + + function cb (bind (&Configuration::impl::update_audio_channels, this, ui_->sound_input_combo_box, _1, ui_->sound_input_channel_combo_box, false)); + connect (ui_->sound_input_combo_box, static_cast (&QComboBox::currentIndexChanged), cb); + cb = bind (&Configuration::impl::update_audio_channels, this, ui_->sound_output_combo_box, _1, ui_->sound_output_channel_combo_box, true); + connect (ui_->sound_output_combo_box, static_cast (&QComboBox::currentIndexChanged), cb); + } + + // + // setup macros list view + // + ui_->macros_list_view->setModel (&next_macros_); + ui_->macros_list_view->setItemDelegate (new MessageItemDelegate {this}); + + macro_delete_action_ = new QAction {tr ("&Delete"), ui_->macros_list_view}; + ui_->macros_list_view->insertAction (nullptr, macro_delete_action_); + connect (macro_delete_action_, &QAction::triggered, this, &Configuration::impl::delete_macro); + + // setup IARU region combo box model + ui_->region_combo_box->setModel (®ions_); + + // + // setup working frequencies table model & view + // + frequencies_.sort (FrequencyList_v2::frequency_column); + + ui_->frequencies_table_view->setModel (&next_frequencies_); + ui_->frequencies_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->frequencies_table_view->horizontalHeader ()->setResizeContentsPrecision (0); + ui_->frequencies_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->frequencies_table_view->verticalHeader ()->setResizeContentsPrecision (0); + ui_->frequencies_table_view->sortByColumn (FrequencyList_v2::frequency_column, Qt::AscendingOrder); + ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true); + + // delegates + ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::frequency_column, new FrequencyDelegate {this}); + ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::region_column, new ForeignKeyDelegate {®ions_, 0, this}); + ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::mode_column, new ForeignKeyDelegate {&modes_, 0, this}); + + // actions + frequency_delete_action_ = new QAction {tr ("&Delete"), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, frequency_delete_action_); + connect (frequency_delete_action_, &QAction::triggered, this, &Configuration::impl::delete_frequencies); + + frequency_insert_action_ = new QAction {tr ("&Insert ..."), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, frequency_insert_action_); + connect (frequency_insert_action_, &QAction::triggered, this, &Configuration::impl::insert_frequency); + + load_frequencies_action_ = new QAction {tr ("&Load ..."), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, load_frequencies_action_); + connect (load_frequencies_action_, &QAction::triggered, this, &Configuration::impl::load_frequencies); + + save_frequencies_action_ = new QAction {tr ("&Save as ..."), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, save_frequencies_action_); + connect (save_frequencies_action_, &QAction::triggered, this, &Configuration::impl::save_frequencies); + + merge_frequencies_action_ = new QAction {tr ("&Merge ..."), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, merge_frequencies_action_); + connect (merge_frequencies_action_, &QAction::triggered, this, &Configuration::impl::merge_frequencies); + + reset_frequencies_action_ = new QAction {tr ("&Reset"), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, reset_frequencies_action_); + connect (reset_frequencies_action_, &QAction::triggered, this, &Configuration::impl::reset_frequencies); + + // + // setup stations table model & view + // + stations_.sort (StationList::band_column); + ui_->stations_table_view->setModel (&next_stations_); + ui_->stations_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->stations_table_view->horizontalHeader ()->setResizeContentsPrecision (0); + ui_->stations_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->stations_table_view->verticalHeader ()->setResizeContentsPrecision (0); + ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder); + + // stations delegates + ui_->stations_table_view->setItemDelegateForColumn (StationList::offset_column, new FrequencyDeltaDelegate {this}); + ui_->stations_table_view->setItemDelegateForColumn (StationList::band_column, new ForeignKeyDelegate {&bands_, &next_stations_, 0, StationList::band_column, this}); + + // stations actions + ui_->stations_table_view->addAction (&station_delete_action_); + connect (&station_delete_action_, &QAction::triggered, this, &Configuration::impl::delete_stations); + + ui_->stations_table_view->addAction (&station_insert_action_); + connect (&station_insert_action_, &QAction::triggered, this, &Configuration::impl::insert_station); + + // + // colours and highlighting setup + // + ui_->highlighting_list_view->setModel (&next_decode_highlighing_model_); + + enumerate_rigs (); + initialize_models (); + + audio_input_device_ = next_audio_input_device_; + audio_input_channel_ = next_audio_input_channel_; + audio_output_device_ = next_audio_output_device_; + audio_output_channel_ = next_audio_output_channel_; + + bool fetch_if_needed {false}; + for (auto const& item : decode_highlighing_model_.items ()) + { + if (DecodeHighlightingModel::Highlight::LotW == item.type_) + { + fetch_if_needed = item.enabled_; + break; + } + } + // load the LoTW users dictionary if it exists, fetch and load if it + // doesn't and we need it + lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), fetch_if_needed); + + transceiver_thread_ = new QThread {this}; + transceiver_thread_->start (); +} + +Configuration::impl::~impl () +{ + transceiver_thread_->quit (); + transceiver_thread_->wait (); + write_settings (); +} + +void Configuration::impl::initialize_models () +{ + next_audio_input_device_ = audio_input_device_; + next_audio_input_channel_ = audio_input_channel_; + next_audio_output_device_ = audio_output_device_; + next_audio_output_channel_ = audio_output_channel_; + restart_sound_input_device_ = false; + restart_sound_output_device_ = false; + { + SettingsGroup g {settings_, "Configuration"}; + find_audio_devices (); + } + auto pal = ui_->callsign_line_edit->palette (); + if (my_callsign_.isEmpty ()) + { + pal.setColor (QPalette::Base, "#ffccff"); + } + else + { + pal.setColor (QPalette::Base, Qt::white); + } + //SP6XD + auto pal_reg = ui_->regex_line_edit->palette (); + if (regex_filter_.isEmpty ()) + { + pal_reg.setColor (QPalette::Base, "#ffccff"); + } + else + { + pal_reg.setColor (QPalette::Base, Qt::white); + } + + ui_->callsign_line_edit->setPalette (pal); + ui_->grid_line_edit->setPalette (pal); + ui_->callsign_line_edit->setText (my_callsign_); + //SP6XD + ui_->regex_line_edit->setPalette (pal_reg); + ui_->regex_line_edit->setText (regex_filter_); + ui_->grid_line_edit->setText (my_grid_); + ui_->use_dynamic_grid->setChecked(use_dynamic_grid_); + ui_->CW_id_interval_spin_box->setValue (id_interval_); + ui_->sbNtrials->setValue (ntrials_); + ui_->sbTxDelay->setValue (txDelay_); + ui_->sbAggressive->setValue (aggressive_); + ui_->sbDegrade->setValue (degrade_); + ui_->sbBandwidth->setValue (RxBandwidth_); + ui_->PTT_method_button_group->button (rig_params_.ptt_type)->setChecked (true); + + ui_->save_path_display_label->setText (save_directory_.absolutePath ()); + ui_->azel_path_display_label->setText (azel_directory_.absolutePath ()); + ui_->CW_id_after_73_check_box->setChecked (id_after_73_); + ui_->tx_QSY_check_box->setChecked (tx_QSY_allowed_); + ui_->psk_reporter_check_box->setChecked (spot_to_psk_reporter_); + ui_->psk_reporter_tcpip_check_box->setChecked (psk_reporter_tcpip_); + ui_->monitor_off_check_box->setChecked (monitor_off_at_startup_); + ui_->monitor_last_used_check_box->setChecked (monitor_last_used_); + ui_->log_as_RTTY_check_box->setChecked (log_as_RTTY_); + ui_->report_in_comments_check_box->setChecked (report_in_comments_); + ui_->prompt_to_log_check_box->setChecked (prompt_to_log_); + ui_->cbAutoLog->setChecked(autoLog_); + ui_->decodes_from_top_check_box->setChecked (decodes_from_top_); + ui_->insert_blank_check_box->setChecked (insert_blank_); + ui_->DXCC_check_box->setChecked (DXCC_); + ui_->ppfx_check_box->setChecked (ppfx_); + ui_->clear_DX_check_box->setChecked (clear_DX_); + ui_->miles_check_box->setChecked (miles_); + ui_->quick_call_check_box->setChecked (quick_call_); + ui_->disable_TX_on_73_check_box->setChecked (disable_TX_on_73_); + ui_->force_call_1st_check_box->setChecked (force_call_1st_); + ui_->alternate_bindings_check_box->setChecked (alternate_bindings_); + ui_->tx_watchdog_spin_box->setValue (watchdog_); + ui_->TX_messages_check_box->setChecked (TX_messages_); + ui_->enable_VHF_features_check_box->setChecked(enable_VHF_features_); + ui_->decode_at_52s_check_box->setChecked(decode_at_52s_); + ui_->single_decode_check_box->setChecked(single_decode_); + ui_->cbTwoPass->setChecked(twoPass_); + ui_->gbSpecialOpActivity->setChecked(bSpecialOp_); + ui_->special_op_activity_button_group->button (SelectedActivity_)->setChecked (true); + ui_->cbx2ToneSpacing->setChecked(x2ToneSpacing_); + ui_->cbx4ToneSpacing->setChecked(x4ToneSpacing_); + ui_->type_2_msg_gen_combo_box->setCurrentIndex (type_2_msg_gen_); + ui_->rig_combo_box->setCurrentText (rig_params_.rig_name); + ui_->TX_mode_button_group->button (data_mode_)->setChecked (true); + ui_->split_mode_button_group->button (rig_params_.split_mode)->setChecked (true); + ui_->CAT_serial_baud_combo_box->setCurrentText (QString::number (rig_params_.baud)); + ui_->CAT_data_bits_button_group->button (rig_params_.data_bits)->setChecked (true); + ui_->CAT_stop_bits_button_group->button (rig_params_.stop_bits)->setChecked (true); + ui_->CAT_handshake_button_group->button (rig_params_.handshake)->setChecked (true); + ui_->checkBoxPwrBandTxMemory->setChecked(pwrBandTxMemory_); + ui_->checkBoxPwrBandTuneMemory->setChecked(pwrBandTuneMemory_); + if (rig_params_.force_dtr) + { + ui_->force_DTR_combo_box->setCurrentIndex (rig_params_.dtr_high ? 1 : 2); + } + else + { + ui_->force_DTR_combo_box->setCurrentIndex (0); + } + if (rig_params_.force_rts) + { + ui_->force_RTS_combo_box->setCurrentIndex (rig_params_.rts_high ? 1 : 2); + } + else + { + ui_->force_RTS_combo_box->setCurrentIndex (0); + } + ui_->TX_audio_source_button_group->button (rig_params_.audio_source)->setChecked (true); + ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval); + ui_->opCallEntry->setText (opCall_); + ui_->udp_server_line_edit->setText (udp_server_name_); + on_udp_server_line_edit_editingFinished (); + ui_->udp_server_port_spin_box->setValue (udp_server_port_); + load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); + if (!udp_interface_names_.size ()) + { + udp_interface_names_ = get_selected_network_interfaces (ui_->udp_interfaces_combo_box); + } + ui_->udp_TTL_spin_box->setValue (udp_TTL_); + ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_); + ui_->n1mm_server_name_line_edit->setText (n1mm_server_name_); + ui_->n1mm_server_port_spin_box->setValue (n1mm_server_port_); + ui_->enable_n1mm_broadcast_check_box->setChecked (broadcast_to_n1mm_); + ui_->udpWindowToFront->setChecked(udpWindowToFront_); + ui_->udpWindowRestore->setChecked(udpWindowRestore_); + ui_->calibration_intercept_spin_box->setValue (calibration_.intercept); + ui_->calibration_slope_ppm_spin_box->setValue (calibration_.slope_ppm); + ui_->rbLowSidelobes->setChecked(bLowSidelobes_); + if(!bLowSidelobes_) ui_->rbMaxSensitivity->setChecked(true); + + if (rig_params_.ptt_port.isEmpty ()) + { + if (ui_->PTT_port_combo_box->count ()) + { + ui_->PTT_port_combo_box->setCurrentText (ui_->PTT_port_combo_box->itemText (0)); + } + } + else + { + ui_->PTT_port_combo_box->setCurrentText (rig_params_.ptt_port); + } + + ui_->region_combo_box->setCurrentIndex (region_); + + next_macros_.setStringList (macros_.stringList ()); + next_frequencies_.frequency_list (frequencies_.frequency_list ()); + next_stations_.station_list (stations_.station_list ()); + + next_decode_highlighing_model_.items (decode_highlighing_model_.items ()); + ui_->highlight_by_mode_check_box->setChecked (highlight_by_mode_); + ui_->only_fields_check_box->setChecked (highlight_only_fields_); + ui_->include_WAE_check_box->setChecked (include_WAE_entities_); + ui_->LotW_days_since_upload_spin_box->setValue (LotW_days_since_upload_); + + set_rig_invariants (); +} + +void Configuration::impl::done (int r) +{ + // do this here since window is still on screen at this point + SettingsGroup g {settings_, "Configuration"}; + settings_->setValue ("window/geometry", saveGeometry ()); + + QDialog::done (r); +} + +void Configuration::impl::read_settings () +{ + SettingsGroup g {settings_, "Configuration"}; + restoreGeometry (settings_->value ("window/geometry").toByteArray ()); + + my_callsign_ = settings_->value ("MyCall", QString {}).toString (); + //SP6XD + regex_filter_ = settings_->value ("RegexFilter", QString {}).toString (); + my_grid_ = settings_->value ("MyGrid", QString {}).toString (); + FD_exchange_ = settings_->value ("Field_Day_Exchange",QString {}).toString (); + RTTY_exchange_ = settings_->value ("RTTY_Exchange",QString {}).toString (); + ui_->Field_Day_Exchange->setText(FD_exchange_); + ui_->RTTY_Exchange->setText(RTTY_exchange_); + if (next_font_.fromString (settings_->value ("Font", QGuiApplication::font ().toString ()).toString ()) + && next_font_ != font_) + { + font_ = next_font_; + Q_EMIT self_->text_font_changed (font_); + } + else + { + next_font_ = font_; + } + if (next_decoded_text_font_.fromString (settings_->value ("DecodedTextFont", "Courier, 10").toString ()) + && next_decoded_text_font_ != decoded_text_font_) + { + decoded_text_font_ = next_decoded_text_font_; + next_decode_highlighing_model_.set_font (decoded_text_font_); + ui_->highlighting_list_view->reset (); + Q_EMIT self_->decoded_text_font_changed (decoded_text_font_); + } + else + { + next_decoded_text_font_ = decoded_text_font_; + } + + id_interval_ = settings_->value ("IDint", 0).toInt (); + ntrials_ = settings_->value ("nTrials", 6).toInt (); + txDelay_ = settings_->value ("TxDelay",0.2).toDouble(); + aggressive_ = settings_->value ("Aggressive", 0).toInt (); + RxBandwidth_ = settings_->value ("RxBandwidth", 2500).toInt (); + save_directory_.setPath (settings_->value ("SaveDir", default_save_directory_.absolutePath ()).toString ()); + azel_directory_.setPath (settings_->value ("AzElDir", default_azel_directory_.absolutePath ()).toString ()); + + type_2_msg_gen_ = settings_->value ("Type2MsgGen", QVariant::fromValue (Configuration::type_2_msg_3_full)).value (); + + monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool (); + monitor_last_used_ = settings_->value ("MonitorLastUsed", false).toBool (); + spot_to_psk_reporter_ = settings_->value ("PSKReporter", false).toBool (); + psk_reporter_tcpip_ = settings_->value ("PSKReporterTCPIP", false).toBool (); + id_after_73_ = settings_->value ("After73", false).toBool (); + tx_QSY_allowed_ = settings_->value ("TxQSYAllowed", false).toBool (); + use_dynamic_grid_ = settings_->value ("AutoGrid", false).toBool (); + + macros_.setStringList (settings_->value ("Macros", QStringList {"TNX 73 GL"}).toStringList ()); + + region_ = settings_->value ("Region", QVariant::fromValue (IARURegions::ALL)).value (); + + if (settings_->contains ("FrequenciesForRegionModes")) + { + auto const& v = settings_->value ("FrequenciesForRegionModes"); + if (v.isValid ()) + { + frequencies_.frequency_list (v.value ()); + } + else + { + frequencies_.reset_to_defaults (); + } + } + else + { + frequencies_.reset_to_defaults (); + } + + stations_.station_list (settings_->value ("stations").value ()); + + auto highlight_items = settings_->value ("DecodeHighlighting", QVariant::fromValue (DecodeHighlightingModel::default_items ())).value (); + if (!highlight_items.size ()) highlight_items = DecodeHighlightingModel::default_items (); + decode_highlighing_model_.items (highlight_items); + highlight_by_mode_ = settings_->value("HighlightByMode", false).toBool (); + highlight_only_fields_ = settings_->value("OnlyFieldsSought", false).toBool (); + include_WAE_entities_ = settings_->value("IncludeWAEEntities", false).toBool (); + LotW_days_since_upload_ = settings_->value ("LotWDaysSinceLastUpload", 365).toInt (); + lotw_users_.set_age_constraint (LotW_days_since_upload_); + + log_as_RTTY_ = settings_->value ("toRTTY", false).toBool (); + report_in_comments_ = settings_->value("dBtoComments", false).toBool (); + rig_params_.rig_name = settings_->value ("Rig", TransceiverFactory::basic_transceiver_name_).toString (); + rig_is_dummy_ = TransceiverFactory::basic_transceiver_name_ == rig_params_.rig_name; + rig_params_.network_port = settings_->value ("CATNetworkPort").toString (); + rig_params_.usb_port = settings_->value ("CATUSBPort").toString (); + rig_params_.serial_port = settings_->value ("CATSerialPort").toString (); + rig_params_.baud = settings_->value ("CATSerialRate", 4800).toInt (); + rig_params_.data_bits = settings_->value ("CATDataBits", QVariant::fromValue (TransceiverFactory::default_data_bits)).value (); + rig_params_.stop_bits = settings_->value ("CATStopBits", QVariant::fromValue (TransceiverFactory::default_stop_bits)).value (); + rig_params_.handshake = settings_->value ("CATHandshake", QVariant::fromValue (TransceiverFactory::handshake_default)).value (); + rig_params_.force_dtr = settings_->value ("CATForceDTR", false).toBool (); + rig_params_.dtr_high = settings_->value ("DTR", false).toBool (); + rig_params_.force_rts = settings_->value ("CATForceRTS", false).toBool (); + rig_params_.rts_high = settings_->value ("RTS", false).toBool (); + rig_params_.ptt_type = settings_->value ("PTTMethod", QVariant::fromValue (TransceiverFactory::PTT_method_VOX)).value (); + rig_params_.audio_source = settings_->value ("TXAudioSource", QVariant::fromValue (TransceiverFactory::TX_audio_source_front)).value (); + rig_params_.ptt_port = settings_->value ("PTTport").toString (); + data_mode_ = settings_->value ("DataMode", QVariant::fromValue (data_mode_none)).value (); + bLowSidelobes_ = settings_->value("LowSidelobes",true).toBool(); + prompt_to_log_ = settings_->value ("PromptToLog", false).toBool (); + autoLog_ = settings_->value ("AutoLog", false).toBool (); + decodes_from_top_ = settings_->value ("DecodesFromTop", false).toBool (); + insert_blank_ = settings_->value ("InsertBlank", false).toBool (); + DXCC_ = settings_->value ("DXCCEntity", false).toBool (); + ppfx_ = settings_->value ("PrincipalPrefix", false).toBool (); + clear_DX_ = settings_->value ("ClearCallGrid", false).toBool (); + miles_ = settings_->value ("Miles", false).toBool (); + quick_call_ = settings_->value ("QuickCall", false).toBool (); + disable_TX_on_73_ = settings_->value ("73TxDisable", false).toBool (); + force_call_1st_ = settings_->value ("ForceCallFirst", false).toBool (); + alternate_bindings_ = settings_->value ("AlternateBindings", false).toBool (); + watchdog_ = settings_->value ("TxWatchdog", 6).toInt (); + TX_messages_ = settings_->value ("Tx2QSO", true).toBool (); + enable_VHF_features_ = settings_->value("VHFUHF",false).toBool (); + decode_at_52s_ = settings_->value("Decode52",false).toBool (); + single_decode_ = settings_->value("SingleDecode",false).toBool (); + twoPass_ = settings_->value("TwoPass",true).toBool (); + bSpecialOp_ = settings_->value("SpecialOpActivity",false).toBool (); + SelectedActivity_ = settings_->value("SelectedActivity",1).toInt (); + x2ToneSpacing_ = settings_->value("x2ToneSpacing",false).toBool (); + x4ToneSpacing_ = settings_->value("x4ToneSpacing",false).toBool (); + rig_params_.poll_interval = settings_->value ("Polling", 0).toInt (); + rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value (); + opCall_ = settings_->value ("OpCall", "").toString (); + udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString (); + udp_interface_names_ = settings_->value ("UDPInterface").toStringList (); + udp_TTL_ = settings_->value ("UDPTTL", 1).toInt (); + udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt (); + n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString (); + n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt (); + broadcast_to_n1mm_ = settings_->value ("BroadcastToN1MM", false).toBool (); + accept_udp_requests_ = settings_->value ("AcceptUDPRequests", false).toBool (); + udpWindowToFront_ = settings_->value ("udpWindowToFront",false).toBool (); + udpWindowRestore_ = settings_->value ("udpWindowRestore",false).toBool (); + calibration_.intercept = settings_->value ("CalibrationIntercept", 0.).toDouble (); + calibration_.slope_ppm = settings_->value ("CalibrationSlopePPM", 0.).toDouble (); + pwrBandTxMemory_ = settings_->value("pwrBandTxMemory",false).toBool (); + pwrBandTuneMemory_ = settings_->value("pwrBandTuneMemory",false).toBool (); +} + +void Configuration::impl::find_audio_devices () +{ + // + // retrieve audio input device + // + auto saved_name = settings_->value ("SoundInName").toString (); + if (next_audio_input_device_.deviceName () != saved_name || next_audio_input_device_.isNull ()) + { + next_audio_input_device_ = find_audio_device (QAudio::AudioInput, ui_->sound_input_combo_box, saved_name); + next_audio_input_channel_ = AudioDevice::fromString (settings_->value ("AudioInputChannel", "Mono").toString ()); + update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false); + ui_->sound_input_channel_combo_box->setCurrentIndex (next_audio_input_channel_); + } + + // + // retrieve audio output device + // + saved_name = settings_->value("SoundOutName").toString(); + if (next_audio_output_device_.deviceName () != saved_name || next_audio_output_device_.isNull ()) + { + next_audio_output_device_ = find_audio_device (QAudio::AudioOutput, ui_->sound_output_combo_box, saved_name); + next_audio_output_channel_ = AudioDevice::fromString (settings_->value ("AudioOutputChannel", "Mono").toString ()); + update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); + ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_); + } +} + +void Configuration::impl::write_settings () +{ + SettingsGroup g {settings_, "Configuration"}; + + settings_->setValue ("MyCall", my_callsign_); + //SP6XD + settings_->setValue ("RegexFilter", regex_filter_); + settings_->setValue ("MyGrid", my_grid_); + settings_->setValue ("Field_Day_Exchange", FD_exchange_); + settings_->setValue ("RTTY_Exchange", RTTY_exchange_); + settings_->setValue ("Font", font_.toString ()); + settings_->setValue ("DecodedTextFont", decoded_text_font_.toString ()); + settings_->setValue ("IDint", id_interval_); + settings_->setValue ("nTrials", ntrials_); + settings_->setValue ("TxDelay", txDelay_); + settings_->setValue ("Aggressive", aggressive_); + settings_->setValue ("RxBandwidth", RxBandwidth_); + settings_->setValue ("PTTMethod", QVariant::fromValue (rig_params_.ptt_type)); + settings_->setValue ("PTTport", rig_params_.ptt_port); + settings_->setValue ("SaveDir", save_directory_.absolutePath ()); + settings_->setValue ("AzElDir", azel_directory_.absolutePath ()); + if (!audio_input_device_.isNull ()) + { + settings_->setValue ("SoundInName", audio_input_device_.deviceName ()); + settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_)); + } + if (!audio_output_device_.isNull ()) + { + settings_->setValue ("SoundOutName", audio_output_device_.deviceName ()); + settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_)); + } + settings_->setValue ("Type2MsgGen", QVariant::fromValue (type_2_msg_gen_)); + settings_->setValue ("MonitorOFF", monitor_off_at_startup_); + settings_->setValue ("MonitorLastUsed", monitor_last_used_); + settings_->setValue ("PSKReporter", spot_to_psk_reporter_); + settings_->setValue ("PSKReporterTCPIP", psk_reporter_tcpip_); + settings_->setValue ("After73", id_after_73_); + settings_->setValue ("TxQSYAllowed", tx_QSY_allowed_); + settings_->setValue ("Macros", macros_.stringList ()); + settings_->setValue ("FrequenciesForRegionModes", QVariant::fromValue (frequencies_.frequency_list ())); + settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ())); + settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ())); + settings_->setValue ("HighlightByMode", highlight_by_mode_); + settings_->setValue ("OnlyFieldsSought", highlight_only_fields_); + settings_->setValue ("IncludeWAEEntities", include_WAE_entities_); + settings_->setValue ("LotWDaysSinceLastUpload", LotW_days_since_upload_); + settings_->setValue ("toRTTY", log_as_RTTY_); + settings_->setValue ("dBtoComments", report_in_comments_); + settings_->setValue ("Rig", rig_params_.rig_name); + settings_->setValue ("CATNetworkPort", rig_params_.network_port); + settings_->setValue ("CATUSBPort", rig_params_.usb_port); + settings_->setValue ("CATSerialPort", rig_params_.serial_port); + settings_->setValue ("CATSerialRate", rig_params_.baud); + settings_->setValue ("CATDataBits", QVariant::fromValue (rig_params_.data_bits)); + settings_->setValue ("CATStopBits", QVariant::fromValue (rig_params_.stop_bits)); + settings_->setValue ("CATHandshake", QVariant::fromValue (rig_params_.handshake)); + settings_->setValue ("DataMode", QVariant::fromValue (data_mode_)); + settings_->setValue ("LowSidelobes",bLowSidelobes_); + settings_->setValue ("PromptToLog", prompt_to_log_); + settings_->setValue ("AutoLog", autoLog_); + settings_->setValue ("DecodesFromTop", decodes_from_top_); + settings_->setValue ("InsertBlank", insert_blank_); + settings_->setValue ("DXCCEntity", DXCC_); + settings_->setValue ("PrincipalPrefix", ppfx_); + settings_->setValue ("ClearCallGrid", clear_DX_); + settings_->setValue ("Miles", miles_); + settings_->setValue ("QuickCall", quick_call_); + settings_->setValue ("73TxDisable", disable_TX_on_73_); + settings_->setValue ("ForceCallFirst", force_call_1st_); + settings_->setValue ("AlternateBindings", alternate_bindings_); + settings_->setValue ("TxWatchdog", watchdog_); + settings_->setValue ("Tx2QSO", TX_messages_); + settings_->setValue ("CATForceDTR", rig_params_.force_dtr); + settings_->setValue ("DTR", rig_params_.dtr_high); + settings_->setValue ("CATForceRTS", rig_params_.force_rts); + settings_->setValue ("RTS", rig_params_.rts_high); + settings_->setValue ("TXAudioSource", QVariant::fromValue (rig_params_.audio_source)); + settings_->setValue ("Polling", rig_params_.poll_interval); + settings_->setValue ("SplitMode", QVariant::fromValue (rig_params_.split_mode)); + settings_->setValue ("VHFUHF", enable_VHF_features_); + settings_->setValue ("Decode52", decode_at_52s_); + settings_->setValue ("SingleDecode", single_decode_); + settings_->setValue ("TwoPass", twoPass_); + settings_->setValue ("SelectedActivity", SelectedActivity_); + settings_->setValue ("SpecialOpActivity", bSpecialOp_); + settings_->setValue ("x2ToneSpacing", x2ToneSpacing_); + settings_->setValue ("x4ToneSpacing", x4ToneSpacing_); + settings_->setValue ("OpCall", opCall_); + settings_->setValue ("UDPServer", udp_server_name_); + settings_->setValue ("UDPServerPort", udp_server_port_); + settings_->setValue ("UDPInterface", QVariant::fromValue (udp_interface_names_)); + settings_->setValue ("UDPTTL", udp_TTL_); + settings_->setValue ("N1MMServer", n1mm_server_name_); + settings_->setValue ("N1MMServerPort", n1mm_server_port_); + settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_); + settings_->setValue ("AcceptUDPRequests", accept_udp_requests_); + settings_->setValue ("udpWindowToFront", udpWindowToFront_); + settings_->setValue ("udpWindowRestore", udpWindowRestore_); + settings_->setValue ("CalibrationIntercept", calibration_.intercept); + settings_->setValue ("CalibrationSlopePPM", calibration_.slope_ppm); + settings_->setValue ("pwrBandTxMemory", pwrBandTxMemory_); + settings_->setValue ("pwrBandTuneMemory", pwrBandTuneMemory_); + settings_->setValue ("Region", QVariant::fromValue (region_)); + settings_->setValue ("AutoGrid", use_dynamic_grid_); + settings_->sync (); +} + +void Configuration::impl::set_rig_invariants () +{ + auto const& rig = ui_->rig_combo_box->currentText (); + auto const& ptt_port = ui_->PTT_port_combo_box->currentText (); + auto ptt_method = static_cast (ui_->PTT_method_button_group->checkedId ()); + + auto CAT_PTT_enabled = transceiver_factory_.has_CAT_PTT (rig); + auto CAT_indirect_serial_PTT = transceiver_factory_.has_CAT_indirect_serial_PTT (rig); + auto asynchronous_CAT = transceiver_factory_.has_asynchronous_CAT (rig); + auto is_hw_handshake = ui_->CAT_handshake_group_box->isEnabled () + && TransceiverFactory::handshake_hardware == static_cast (ui_->CAT_handshake_button_group->checkedId ()); + + ui_->test_CAT_push_button->setStyleSheet ({}); + + ui_->CAT_poll_interval_label->setEnabled (!asynchronous_CAT); + ui_->CAT_poll_interval_spin_box->setEnabled (!asynchronous_CAT); + + auto port_type = transceiver_factory_.CAT_port_type (rig); + + bool is_serial_CAT (TransceiverFactory::Capabilities::serial == port_type); + auto const& cat_port = ui_->CAT_port_combo_box->currentText (); + + // only enable CAT option if transceiver has CAT PTT + ui_->PTT_CAT_radio_button->setEnabled (CAT_PTT_enabled); + + auto enable_ptt_port = TransceiverFactory::PTT_method_CAT != ptt_method && TransceiverFactory::PTT_method_VOX != ptt_method; + ui_->PTT_port_combo_box->setEnabled (enable_ptt_port); + ui_->PTT_port_label->setEnabled (enable_ptt_port); + + if (CAT_indirect_serial_PTT) + { + ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->findText ("CAT") + , combo_box_item_enabled, Qt::UserRole - 1); + } + else + { + ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->findText ("CAT") + , combo_box_item_disabled, Qt::UserRole - 1); + if ("CAT" == ui_->PTT_port_combo_box->currentText () && ui_->PTT_port_combo_box->currentIndex () > 0) + { + ui_->PTT_port_combo_box->setCurrentIndex (ui_->PTT_port_combo_box->currentIndex () - 1); + } + } + ui_->PTT_RTS_radio_button->setEnabled (!(is_serial_CAT && ptt_port == cat_port && is_hw_handshake)); + + if (TransceiverFactory::basic_transceiver_name_ == rig) + { + // makes no sense with rig as "None" + ui_->monitor_last_used_check_box->setEnabled (false); + + ui_->CAT_control_group_box->setEnabled (false); + ui_->test_CAT_push_button->setEnabled (false); + ui_->test_PTT_push_button->setEnabled (TransceiverFactory::PTT_method_DTR == ptt_method + || TransceiverFactory::PTT_method_RTS == ptt_method); + ui_->TX_audio_source_group_box->setEnabled (false); + } + else + { + ui_->monitor_last_used_check_box->setEnabled (true); + ui_->CAT_control_group_box->setEnabled (true); + ui_->test_CAT_push_button->setEnabled (true); + ui_->test_PTT_push_button->setEnabled (false); + ui_->TX_audio_source_group_box->setEnabled (transceiver_factory_.has_CAT_PTT_mic_data (rig) && TransceiverFactory::PTT_method_CAT == ptt_method); + if (port_type != last_port_type_) + { + last_port_type_ = port_type; + switch (port_type) + { + case TransceiverFactory::Capabilities::serial: + fill_port_combo_box (ui_->CAT_port_combo_box); + ui_->CAT_port_combo_box->setCurrentText (rig_params_.serial_port); + if (ui_->CAT_port_combo_box->currentText ().isEmpty () && ui_->CAT_port_combo_box->count ()) + { + ui_->CAT_port_combo_box->setCurrentText (ui_->CAT_port_combo_box->itemText (0)); + } + ui_->CAT_port_label->setText (tr ("Serial Port:")); + ui_->CAT_port_combo_box->setToolTip (tr ("Serial port used for CAT control")); + ui_->CAT_port_combo_box->setEnabled (true); + break; + + case TransceiverFactory::Capabilities::network: + ui_->CAT_port_combo_box->clear (); + ui_->CAT_port_combo_box->setCurrentText (rig_params_.network_port); + ui_->CAT_port_label->setText (tr ("Network Server:")); + ui_->CAT_port_combo_box->setToolTip (tr ("Optional hostname and port of network service.\n" + "Leave blank for a sensible default on this machine.\n" + "Formats:\n" + "\thostname:port\n" + "\tIPv4-address:port\n" + "\t[IPv6-address]:port")); + ui_->CAT_port_combo_box->setEnabled (true); + break; + + case TransceiverFactory::Capabilities::usb: + ui_->CAT_port_combo_box->clear (); + ui_->CAT_port_combo_box->setCurrentText (rig_params_.usb_port); + ui_->CAT_port_label->setText (tr ("USB Device:")); + ui_->CAT_port_combo_box->setToolTip (tr ("Optional device identification.\n" + "Leave blank for a sensible default for the rig.\n" + "Format:\n" + "\t[VID[:PID[:VENDOR[:PRODUCT]]]]")); + ui_->CAT_port_combo_box->setEnabled (true); + break; + + default: + ui_->CAT_port_combo_box->clear (); + ui_->CAT_port_combo_box->setEnabled (false); + break; + } + } + ui_->CAT_serial_port_parameters_group_box->setEnabled (is_serial_CAT); + ui_->force_DTR_combo_box->setEnabled (is_serial_CAT + && (cat_port != ptt_port + || !ui_->PTT_DTR_radio_button->isEnabled () + || !ui_->PTT_DTR_radio_button->isChecked ())); + ui_->force_RTS_combo_box->setEnabled (is_serial_CAT + && !is_hw_handshake + && (cat_port != ptt_port + || !ui_->PTT_RTS_radio_button->isEnabled () + || !ui_->PTT_RTS_radio_button->isChecked ())); + } + ui_->mode_group_box->setEnabled (WSJT_RIG_NONE_CAN_SPLIT + || TransceiverFactory::basic_transceiver_name_ != rig); + ui_->split_operation_group_box->setEnabled (WSJT_RIG_NONE_CAN_SPLIT + || TransceiverFactory::basic_transceiver_name_ != rig); +} + +bool Configuration::impl::validate () +{ + if (ui_->sound_input_combo_box->currentIndex () < 0 + && next_audio_input_device_.isNull ()) + { + find_tab (ui_->sound_input_combo_box); + MessageBox::critical_message (this, tr ("Invalid audio input device")); + return false; + } + + if (ui_->sound_input_channel_combo_box->currentIndex () < 0 + && next_audio_input_device_.isNull ()) + { + find_tab (ui_->sound_input_combo_box); + MessageBox::critical_message (this, tr ("Invalid audio input device")); + return false; + } + + if (ui_->sound_output_combo_box->currentIndex () < 0 + && next_audio_output_device_.isNull ()) + { + find_tab (ui_->sound_output_combo_box); + MessageBox::information_message (this, tr ("Invalid audio output device")); + // don't reject as we can work without an audio output + } + + if (!ui_->PTT_method_button_group->checkedButton ()->isEnabled ()) + { + MessageBox::critical_message (this, tr ("Invalid PTT method")); + return false; + } + + auto ptt_method = static_cast (ui_->PTT_method_button_group->checkedId ()); + auto ptt_port = ui_->PTT_port_combo_box->currentText (); + if ((TransceiverFactory::PTT_method_DTR == ptt_method || TransceiverFactory::PTT_method_RTS == ptt_method) + && (ptt_port.isEmpty () + || combo_box_item_disabled == ui_->PTT_port_combo_box->itemData (ui_->PTT_port_combo_box->findText (ptt_port), Qt::UserRole - 1))) + { + MessageBox::critical_message (this, tr ("Invalid PTT port")); + return false; + } + + if (ui_->rbField_Day->isEnabled () && ui_->rbField_Day->isChecked () && + !ui_->Field_Day_Exchange->hasAcceptableInput ()) + { + find_tab (ui_->Field_Day_Exchange); + MessageBox::critical_message (this, tr ("Invalid Contest Exchange") + , tr ("You must input a valid ARRL Field Day exchange")); + return false; + } + + if (ui_->rbRTTY_Roundup->isEnabled () && ui_->rbRTTY_Roundup->isChecked () && + !ui_->RTTY_Exchange->hasAcceptableInput ()) + { + find_tab (ui_->RTTY_Exchange); + MessageBox::critical_message (this, tr ("Invalid Contest Exchange") + , tr ("You must input a valid ARRL RTTY Roundup exchange")); + return false; + } + + if (dns_lookup_id_ > -1) + { + MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later")); + return false; + } + + return true; +} + +int Configuration::impl::exec () +{ + // macros can be modified in the main window + next_macros_.setStringList (macros_.stringList ()); + + have_rig_ = rig_active_; // record that we started with a rig open + saved_rig_params_ = rig_params_; // used to detect changes that + // require the Transceiver to be + // re-opened + rig_changed_ = false; + + initialize_models (); + + return QDialog::exec(); +} + +TransceiverFactory::ParameterPack Configuration::impl::gather_rig_data () +{ + TransceiverFactory::ParameterPack result; + result.rig_name = ui_->rig_combo_box->currentText (); + + switch (transceiver_factory_.CAT_port_type (result.rig_name)) + { + case TransceiverFactory::Capabilities::network: + result.network_port = ui_->CAT_port_combo_box->currentText (); + result.usb_port = rig_params_.usb_port; + result.serial_port = rig_params_.serial_port; + break; + + case TransceiverFactory::Capabilities::usb: + result.usb_port = ui_->CAT_port_combo_box->currentText (); + result.network_port = rig_params_.network_port; + result.serial_port = rig_params_.serial_port; + break; + + default: + result.serial_port = ui_->CAT_port_combo_box->currentText (); + result.network_port = rig_params_.network_port; + result.usb_port = rig_params_.usb_port; + break; + } + + result.baud = ui_->CAT_serial_baud_combo_box->currentText ().toInt (); + result.data_bits = static_cast (ui_->CAT_data_bits_button_group->checkedId ()); + result.stop_bits = static_cast (ui_->CAT_stop_bits_button_group->checkedId ()); + result.handshake = static_cast (ui_->CAT_handshake_button_group->checkedId ()); + result.force_dtr = ui_->force_DTR_combo_box->isEnabled () && ui_->force_DTR_combo_box->currentIndex () > 0; + result.dtr_high = ui_->force_DTR_combo_box->isEnabled () && 1 == ui_->force_DTR_combo_box->currentIndex (); + result.force_rts = ui_->force_RTS_combo_box->isEnabled () && ui_->force_RTS_combo_box->currentIndex () > 0; + result.rts_high = ui_->force_RTS_combo_box->isEnabled () && 1 == ui_->force_RTS_combo_box->currentIndex (); + result.poll_interval = ui_->CAT_poll_interval_spin_box->value (); + result.ptt_type = static_cast (ui_->PTT_method_button_group->checkedId ()); + result.ptt_port = ui_->PTT_port_combo_box->currentText (); + result.audio_source = static_cast (ui_->TX_audio_source_button_group->checkedId ()); + result.split_mode = static_cast (ui_->split_mode_button_group->checkedId ()); + return result; +} + +void Configuration::impl::accept () +{ + // Called when OK button is clicked. + + if (!validate ()) + { + return; // not accepting + } + + // extract all rig related configuration parameters into temporary + // structure for checking if the rig needs re-opening without + // actually updating our live state + auto temp_rig_params = gather_rig_data (); + + // open_rig() uses values from models so we use it to validate the + // Transceiver settings before agreeing to accept the configuration + if (temp_rig_params != rig_params_ && !open_rig ()) + { + return; // not accepting + } + + QDialog::accept(); // do this before accessing custom + // models so that any changes in + // delegates in views get flushed to + // the underlying models before we + // access them + + sync_transceiver (true); // force an update + + // + // from here on we are bound to accept the new configuration + // parameters so extract values from models and make them live + // + + if (next_font_ != font_) + { + font_ = next_font_; + Q_EMIT self_->text_font_changed (font_); + } + + if (next_decoded_text_font_ != decoded_text_font_) + { + decoded_text_font_ = next_decoded_text_font_; + next_decode_highlighing_model_.set_font (decoded_text_font_); + ui_->highlighting_list_view->reset (); + Q_EMIT self_->decoded_text_font_changed (decoded_text_font_); + } + + rig_params_ = temp_rig_params; // now we can go live with the rig + // related configuration parameters + rig_is_dummy_ = TransceiverFactory::basic_transceiver_name_ == rig_params_.rig_name; + + { + auto const& selected_device = ui_->sound_input_combo_box->currentData ().value ().first; + if (selected_device != next_audio_input_device_) + { + next_audio_input_device_ = selected_device; + } + } + + { + auto const& selected_device = ui_->sound_output_combo_box->currentData ().value ().first; + if (selected_device != next_audio_output_device_) + { + next_audio_output_device_ = selected_device; + } + } + + if (next_audio_input_channel_ != static_cast (ui_->sound_input_channel_combo_box->currentIndex ())) + { + next_audio_input_channel_ = static_cast (ui_->sound_input_channel_combo_box->currentIndex ()); + } + Q_ASSERT (next_audio_input_channel_ <= AudioDevice::Right); + + if (next_audio_output_channel_ != static_cast (ui_->sound_output_channel_combo_box->currentIndex ())) + { + next_audio_output_channel_ = static_cast (ui_->sound_output_channel_combo_box->currentIndex ()); + } + Q_ASSERT (next_audio_output_channel_ <= AudioDevice::Both); + + if (audio_input_device_ != next_audio_input_device_ || next_audio_input_device_.isNull ()) + { + audio_input_device_ = next_audio_input_device_; + restart_sound_input_device_ = true; + } + if (audio_input_channel_ != next_audio_input_channel_) + { + audio_input_channel_ = next_audio_input_channel_; + restart_sound_input_device_ = true; + } + if (audio_output_device_ != next_audio_output_device_ || next_audio_output_device_.isNull ()) + { + audio_output_device_ = next_audio_output_device_; + restart_sound_output_device_ = true; + } + if (audio_output_channel_ != next_audio_output_channel_) + { + audio_output_channel_ = next_audio_output_channel_; + restart_sound_output_device_ = true; + } + // qDebug () << "Configure::accept: audio i/p:" << audio_input_device_.deviceName () + // << "chan:" << audio_input_channel_ + // << "o/p:" << audio_output_device_.deviceName () + // << "chan:" << audio_output_channel_ + // << "reset i/p:" << restart_sound_input_device_ + // << "reset o/p:" << restart_sound_output_device_; + + my_callsign_ = ui_->callsign_line_edit->text (); + //SP6XD + regex_filter_ = ui_->regex_line_edit->text (); + my_grid_ = ui_->grid_line_edit->text (); + FD_exchange_= ui_->Field_Day_Exchange->text ().toUpper (); + RTTY_exchange_= ui_->RTTY_Exchange->text ().toUpper (); + spot_to_psk_reporter_ = ui_->psk_reporter_check_box->isChecked (); + psk_reporter_tcpip_ = ui_->psk_reporter_tcpip_check_box->isChecked (); + id_interval_ = ui_->CW_id_interval_spin_box->value (); + ntrials_ = ui_->sbNtrials->value (); + txDelay_ = ui_->sbTxDelay->value (); + aggressive_ = ui_->sbAggressive->value (); + degrade_ = ui_->sbDegrade->value (); + RxBandwidth_ = ui_->sbBandwidth->value (); + id_after_73_ = ui_->CW_id_after_73_check_box->isChecked (); + tx_QSY_allowed_ = ui_->tx_QSY_check_box->isChecked (); + monitor_off_at_startup_ = ui_->monitor_off_check_box->isChecked (); + monitor_last_used_ = ui_->monitor_last_used_check_box->isChecked (); + type_2_msg_gen_ = static_cast (ui_->type_2_msg_gen_combo_box->currentIndex ()); + log_as_RTTY_ = ui_->log_as_RTTY_check_box->isChecked (); + report_in_comments_ = ui_->report_in_comments_check_box->isChecked (); + prompt_to_log_ = ui_->prompt_to_log_check_box->isChecked (); + autoLog_ = ui_->cbAutoLog->isChecked(); + decodes_from_top_ = ui_->decodes_from_top_check_box->isChecked (); + insert_blank_ = ui_->insert_blank_check_box->isChecked (); + DXCC_ = ui_->DXCC_check_box->isChecked (); + ppfx_ = ui_->ppfx_check_box->isChecked (); + clear_DX_ = ui_->clear_DX_check_box->isChecked (); + miles_ = ui_->miles_check_box->isChecked (); + quick_call_ = ui_->quick_call_check_box->isChecked (); + disable_TX_on_73_ = ui_->disable_TX_on_73_check_box->isChecked (); + force_call_1st_ = ui_->force_call_1st_check_box->isChecked (); + alternate_bindings_ = ui_->alternate_bindings_check_box->isChecked (); + watchdog_ = ui_->tx_watchdog_spin_box->value (); + TX_messages_ = ui_->TX_messages_check_box->isChecked (); + data_mode_ = static_cast (ui_->TX_mode_button_group->checkedId ()); + bLowSidelobes_ = ui_->rbLowSidelobes->isChecked(); + save_directory_.setPath (ui_->save_path_display_label->text ()); + azel_directory_.setPath (ui_->azel_path_display_label->text ()); + enable_VHF_features_ = ui_->enable_VHF_features_check_box->isChecked (); + decode_at_52s_ = ui_->decode_at_52s_check_box->isChecked (); + single_decode_ = ui_->single_decode_check_box->isChecked (); + twoPass_ = ui_->cbTwoPass->isChecked (); + bSpecialOp_ = ui_->gbSpecialOpActivity->isChecked (); + SelectedActivity_ = ui_->special_op_activity_button_group->checkedId(); + x2ToneSpacing_ = ui_->cbx2ToneSpacing->isChecked (); + x4ToneSpacing_ = ui_->cbx4ToneSpacing->isChecked (); + calibration_.intercept = ui_->calibration_intercept_spin_box->value (); + calibration_.slope_ppm = ui_->calibration_slope_ppm_spin_box->value (); + pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked (); + pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked (); + opCall_=ui_->opCallEntry->text(); + + auto new_server = ui_->udp_server_line_edit->text ().trimmed (); + auto new_interfaces = get_selected_network_interfaces (ui_->udp_interfaces_combo_box); + if (new_server != udp_server_name_ || new_interfaces != udp_interface_names_) + { + udp_server_name_ = new_server; + udp_interface_names_ = new_interfaces; + Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_names_); + } + + auto new_port = ui_->udp_server_port_spin_box->value (); + if (new_port != udp_server_port_) + { + udp_server_port_ = new_port; + Q_EMIT self_->udp_server_port_changed (udp_server_port_); + } + + auto new_TTL = ui_->udp_TTL_spin_box->value (); + if (new_TTL != udp_TTL_) + { + udp_TTL_ = new_TTL; + Q_EMIT self_->udp_TTL_changed (udp_TTL_); + } + + if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_) + { + accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked (); + Q_EMIT self_->accept_udp_requests_changed (accept_udp_requests_); + } + + n1mm_server_name_ = ui_->n1mm_server_name_line_edit->text (); + n1mm_server_port_ = ui_->n1mm_server_port_spin_box->value (); + broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked (); + + udpWindowToFront_ = ui_->udpWindowToFront->isChecked (); + udpWindowRestore_ = ui_->udpWindowRestore->isChecked (); + + if (macros_.stringList () != next_macros_.stringList ()) + { + macros_.setStringList (next_macros_.stringList ()); + } + + region_ = IARURegions::value (ui_->region_combo_box->currentText ()); + + if (frequencies_.frequency_list () != next_frequencies_.frequency_list ()) + { + frequencies_.frequency_list (next_frequencies_.frequency_list ()); + frequencies_.sort (FrequencyList_v2::frequency_column); + } + + if (stations_.station_list () != next_stations_.station_list ()) + { + stations_.station_list (next_stations_.station_list ()); + stations_.sort (StationList::band_column); + } + + if (decode_highlighing_model_.items () != next_decode_highlighing_model_.items ()) + { + decode_highlighing_model_.items (next_decode_highlighing_model_.items ()); + Q_EMIT self_->decode_highlighting_changed (decode_highlighing_model_); + } + highlight_by_mode_ = ui_->highlight_by_mode_check_box->isChecked (); + highlight_only_fields_ = ui_->only_fields_check_box->isChecked (); + include_WAE_entities_ = ui_->include_WAE_check_box->isChecked (); + LotW_days_since_upload_ = ui_->LotW_days_since_upload_spin_box->value (); + lotw_users_.set_age_constraint (LotW_days_since_upload_); + + if (ui_->use_dynamic_grid->isChecked() && !use_dynamic_grid_ ) + { + // turning on so clear it so only the next location update gets used + dynamic_grid_.clear (); + } + use_dynamic_grid_ = ui_->use_dynamic_grid->isChecked(); + + write_settings (); // make visible to all +} + +void Configuration::impl::reject () +{ + if (dns_lookup_id_ > -1) + { + QHostInfo::abortHostLookup (dns_lookup_id_); + dns_lookup_id_ = -1; + } + + initialize_models (); // reverts to settings as at exec () + + // check if the Transceiver instance changed, in which case we need + // to re open any prior Transceiver type + if (rig_changed_) + { + if (have_rig_) + { + // we have to do this since the rig has been opened since we + // were exec'ed even though it might fail + open_rig (); + } + else + { + close_rig (); + } + } + + // qDebug () << "Configure::reject: audio i/p:" << audio_input_device_.deviceName () + // << "chan:" << audio_input_channel_ + // << "o/p:" << audio_output_device_.deviceName () + // << "chan:" << audio_output_channel_ + // << "reset i/p:" << restart_sound_input_device_ + // << "reset o/p:" << restart_sound_output_device_; + + QDialog::reject (); +} + +void Configuration::impl::on_font_push_button_clicked () +{ + next_font_ = QFontDialog::getFont (0, next_font_, this); +} + +void Configuration::impl::on_reset_highlighting_to_defaults_push_button_clicked (bool /*checked*/) +{ + if (MessageBox::Yes == MessageBox::query_message (this + , tr ("Reset Decode Highlighting") + , tr ("Reset all decode highlighting and priorities to default values"))) + { + next_decode_highlighing_model_.items (DecodeHighlightingModel::default_items ()); + } +} + +void Configuration::impl::on_rescan_log_push_button_clicked (bool /*clicked*/) +{ + if (logbook_) logbook_->rescan (); +} + +void Configuration::impl::on_LotW_CSV_fetch_push_button_clicked (bool /*checked*/) +{ + lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), true, true); + ui_->LotW_CSV_fetch_push_button->setEnabled (false); +} + +void Configuration::impl::on_decoded_text_font_push_button_clicked () +{ + next_decoded_text_font_ = QFontDialog::getFont (0, decoded_text_font_ , this + , tr ("WSJT-X Decoded Text Font Chooser") + , QFontDialog::MonospacedFonts + ); +} + +void Configuration::impl::on_PTT_port_combo_box_activated (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_port_combo_box_activated (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_serial_baud_combo_box_currentIndexChanged (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_handshake_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_rig_combo_box_currentIndexChanged (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_data_bits_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_stop_bits_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_poll_interval_spin_box_valueChanged (int /* value */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_split_mode_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_test_CAT_push_button_clicked () +{ + if (!validate ()) + { + return; + } + + ui_->test_CAT_push_button->setStyleSheet ({}); + if (open_rig (true)) + { + //Q_EMIT sync (true); + } + + set_rig_invariants (); +} + +void Configuration::impl::on_test_PTT_push_button_clicked (bool checked) +{ + ui_->test_PTT_push_button->setChecked (!checked); // let status + // update check us + if (!validate ()) + { + return; + } + + if (open_rig ()) + { + Q_EMIT self_->transceiver_ptt (checked); + } +} + +void Configuration::impl::on_force_DTR_combo_box_currentIndexChanged (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_force_RTS_combo_box_currentIndexChanged (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_PTT_method_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_add_macro_line_edit_editingFinished () +{ + ui_->add_macro_line_edit->setText (ui_->add_macro_line_edit->text ().toUpper ()); +} + +void Configuration::impl::on_delete_macro_push_button_clicked (bool /* checked */) +{ + auto selection_model = ui_->macros_list_view->selectionModel (); + if (selection_model->hasSelection ()) + { + // delete all selected items + delete_selected_macros (selection_model->selectedRows ()); + } +} + +void Configuration::impl::delete_macro () +{ + auto selection_model = ui_->macros_list_view->selectionModel (); + if (!selection_model->hasSelection ()) + { + // delete item under cursor if any + auto index = selection_model->currentIndex (); + if (index.isValid ()) + { + next_macros_.removeRow (index.row ()); + } + } + else + { + // delete the whole selection + delete_selected_macros (selection_model->selectedRows ()); + } +} + +void Configuration::impl::delete_selected_macros (QModelIndexList selected_rows) +{ + // sort in reverse row order so that we can delete without changing + // indices underneath us + std::sort (selected_rows.begin (), selected_rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) + { + return rhs.row () < lhs.row (); // reverse row ordering + }); + + // now delete them + Q_FOREACH (auto index, selected_rows) + { + next_macros_.removeRow (index.row ()); + } +} + +void Configuration::impl::on_add_macro_push_button_clicked (bool /* checked */) +{ + if (next_macros_.insertRow (next_macros_.rowCount ())) + { + auto index = next_macros_.index (next_macros_.rowCount () - 1); + ui_->macros_list_view->setCurrentIndex (index); + next_macros_.setData (index, ui_->add_macro_line_edit->text ()); + ui_->add_macro_line_edit->clear (); + } +} + +void Configuration::impl::on_udp_server_line_edit_textChanged (QString const&) +{ + udp_server_name_edited_ = true; +} + +void Configuration::impl::on_udp_server_line_edit_editingFinished () +{ + if (udp_server_name_edited_) + { + auto const& server = ui_->udp_server_line_edit->text ().trimmed (); + QHostAddress ha {server}; + if (server.size () && ha.isNull ()) + { + // queue a host address lookup + // qDebug () << "server host DNS lookup:" << server; +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results); +#else + dns_lookup_id_ = QHostInfo::lookupHost (server, this, SLOT (host_info_results (QHostInfo))); +#endif + } + else + { + check_multicast (ha); + } + } +} + +void Configuration::impl::host_info_results (QHostInfo host_info) +{ + if (host_info.lookupId () != dns_lookup_id_) return; + dns_lookup_id_ = -1; + if (QHostInfo::NoError != host_info.error ()) + { + MessageBox::critical_message (this, tr ("UDP server DNS lookup failed"), host_info.errorString ()); + } + else + { + auto const& server_addresses = host_info.addresses (); + // qDebug () << "message server addresses:" << server_addresses; + if (server_addresses.size ()) + { + check_multicast (server_addresses[0]); + } + } +} + +void Configuration::impl::check_multicast (QHostAddress const& ha) +{ + auto is_multicast = is_multicast_address (ha); + ui_->udp_interfaces_label->setVisible (is_multicast); + ui_->udp_interfaces_combo_box->setVisible (is_multicast); + ui_->udp_TTL_label->setVisible (is_multicast); + ui_->udp_TTL_spin_box->setVisible (is_multicast); + if (isVisible ()) + { + if (is_MAC_ambiguous_multicast_address (ha)) + { + MessageBox::warning_message (this, tr ("MAC-ambiguous multicast groups addresses not supported")); + find_tab (ui_->udp_server_line_edit); + ui_->udp_server_line_edit->clear (); + } + } + udp_server_name_edited_ = false; +} + +void Configuration::impl::delete_frequencies () +{ + auto selection_model = ui_->frequencies_table_view->selectionModel (); + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + next_frequencies_.removeDisjointRows (selection_model->selectedRows ()); + ui_->frequencies_table_view->resizeColumnToContents (FrequencyList_v2::mode_column); +} + +void Configuration::impl::load_frequencies () +{ + auto file_name = QFileDialog::getOpenFileName (this, tr ("Load Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg);;All files (*.*)")); + if (!file_name.isNull ()) + { + auto const list = read_frequencies_file (file_name); + if (list.size () + && (!next_frequencies_.frequency_list ().size () + || MessageBox::Yes == MessageBox::query_message (this + , tr ("Replace Working Frequencies") + , tr ("Are you sure you want to discard your current " + "working frequencies and replace them with the " + "loaded ones?")))) + { + next_frequencies_.frequency_list (list); // update the model + } + } +} + +void Configuration::impl::merge_frequencies () +{ + auto file_name = QFileDialog::getOpenFileName (this, tr ("Merge Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg);;All files (*.*)")); + if (!file_name.isNull ()) + { + next_frequencies_.frequency_list_merge (read_frequencies_file (file_name)); // update the model + } +} + +FrequencyList_v2::FrequencyItems Configuration::impl::read_frequencies_file (QString const& file_name) +{ + QFile frequencies_file {file_name}; + frequencies_file.open (QFile::ReadOnly); + QDataStream ids {&frequencies_file}; + FrequencyList_v2::FrequencyItems list; + quint32 magic; + ids >> magic; + if (qrg_magic != magic) + { + MessageBox::warning_message (this, tr ("Not a valid frequencies file"), tr ("Incorrect file magic")); + return list; + } + quint32 version; + ids >> version; + // handle version checks and QDataStream version here if + // necessary + if (version > qrg_version) + { + MessageBox::warning_message (this, tr ("Not a valid frequencies file"), tr ("Version is too new")); + return list; + } + + // de-serialize the data using version if necessary to + // handle old schemata + ids >> list; + + if (ids.status () != QDataStream::Ok || !ids.atEnd ()) + { + MessageBox::warning_message (this, tr ("Not a valid frequencies file"), tr ("Contents corrupt")); + list.clear (); + return list; + } + + return list; +} + +void Configuration::impl::save_frequencies () +{ + auto file_name = QFileDialog::getSaveFileName (this, tr ("Save Working Frequencies"), writeable_data_dir_.absolutePath (), tr ("Frequency files (*.qrg);;All files (*.*)")); + if (!file_name.isNull ()) + { + QFile frequencies_file {file_name}; + frequencies_file.open (QFile::WriteOnly); + QDataStream ods {&frequencies_file}; + auto selection_model = ui_->frequencies_table_view->selectionModel (); + if (selection_model->hasSelection () + && MessageBox::Yes == MessageBox::query_message (this + , tr ("Only Save Selected Working Frequencies") + , tr ("Are you sure you want to save only the " + "working frequencies that are currently selected? " + "Click No to save all."))) + { + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + ods << qrg_magic << qrg_version << next_frequencies_.frequency_list (selection_model->selectedRows ()); + } + else + { + ods << qrg_magic << qrg_version << next_frequencies_.frequency_list (); + } + } +} + +void Configuration::impl::reset_frequencies () +{ + if (MessageBox::Yes == MessageBox::query_message (this, tr ("Reset Working Frequencies") + , tr ("Are you sure you want to discard your current " + "working frequencies and replace them with default " + "ones?"))) + { + next_frequencies_.reset_to_defaults (); + } +} + +void Configuration::impl::insert_frequency () +{ + if (QDialog::Accepted == frequency_dialog_->exec ()) + { + ui_->frequencies_table_view->setCurrentIndex (next_frequencies_.add (frequency_dialog_->item ())); + ui_->frequencies_table_view->resizeColumnToContents (FrequencyList_v2::mode_column); + } +} + +void Configuration::impl::delete_stations () +{ + auto selection_model = ui_->stations_table_view->selectionModel (); + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + next_stations_.removeDisjointRows (selection_model->selectedRows ()); + ui_->stations_table_view->resizeColumnToContents (StationList::band_column); + ui_->stations_table_view->resizeColumnToContents (StationList::offset_column); +} + +void Configuration::impl::insert_station () +{ + if (QDialog::Accepted == station_dialog_->exec ()) + { + ui_->stations_table_view->setCurrentIndex (next_stations_.add (station_dialog_->station ())); + ui_->stations_table_view->resizeColumnToContents (StationList::band_column); + ui_->stations_table_view->resizeColumnToContents (StationList::offset_column); + } +} + +void Configuration::impl::on_save_path_select_push_button_clicked (bool /* checked */) +{ + QFileDialog fd {this, tr ("Save Directory"), ui_->save_path_display_label->text ()}; + fd.setFileMode (QFileDialog::Directory); + fd.setOption (QFileDialog::ShowDirsOnly); + if (fd.exec ()) + { + if (fd.selectedFiles ().size ()) + { + ui_->save_path_display_label->setText (fd.selectedFiles ().at (0)); + } + } +} + +void Configuration::impl::on_azel_path_select_push_button_clicked (bool /* checked */) +{ + QFileDialog fd {this, tr ("AzEl Directory"), ui_->azel_path_display_label->text ()}; + fd.setFileMode (QFileDialog::Directory); + fd.setOption (QFileDialog::ShowDirsOnly); + if (fd.exec ()) { + if (fd.selectedFiles ().size ()) { + ui_->azel_path_display_label->setText(fd.selectedFiles().at(0)); + } + } +} + +void Configuration::impl::on_calibration_intercept_spin_box_valueChanged (double) +{ + rig_active_ = false; // force reset +} + +void Configuration::impl::on_calibration_slope_ppm_spin_box_valueChanged (double) +{ + rig_active_ = false; // force reset +} + +void Configuration::impl::on_prompt_to_log_check_box_clicked(bool checked) +{ + if(checked) ui_->cbAutoLog->setChecked(false); +} + +void Configuration::impl::on_cbAutoLog_clicked(bool checked) +{ + if(checked) ui_->prompt_to_log_check_box->setChecked(false); +} + +void Configuration::impl::on_cbx2ToneSpacing_clicked(bool b) +{ + if(b) ui_->cbx4ToneSpacing->setChecked(false); +} + +void Configuration::impl::on_cbx4ToneSpacing_clicked(bool b) +{ + if(b) ui_->cbx2ToneSpacing->setChecked(false); +} + +void Configuration::impl::on_Field_Day_Exchange_textEdited (QString const& exchange) +{ + auto text = exchange.simplified ().toUpper (); + auto class_pos = text.indexOf (QRegularExpression {R"([A-H])"}); + if (class_pos >= 0 && text.size () >= class_pos + 2 && text.at (class_pos + 1) != QChar {' '}) + { + text.insert (class_pos + 1, QChar {' '}); + } + ui_->Field_Day_Exchange->setText (text); +} + +void Configuration::impl::on_RTTY_Exchange_textEdited (QString const& exchange) +{ + ui_->RTTY_Exchange->setText (exchange.toUpper ()); +} + +bool Configuration::impl::have_rig () +{ + if (!open_rig ()) + { + MessageBox::critical_message (this, tr ("Rig control error") + , tr ("Failed to open connection to rig")); + } + return rig_active_; +} + +bool Configuration::impl::open_rig (bool force) +{ + auto result = false; + + auto const rig_data = gather_rig_data (); + if (force || !rig_active_ || rig_data != saved_rig_params_) + { + try + { + close_rig (); + + // create a new Transceiver object + auto rig = transceiver_factory_.create (rig_data, transceiver_thread_); + cached_rig_state_ = Transceiver::TransceiverState {}; + + // hook up Configuration transceiver control signals to Transceiver slots + // + // these connections cross the thread boundary + rig_connections_ << connect (this, &Configuration::impl::set_transceiver, + rig.get (), &Transceiver::set); + + // hook up Transceiver signals to Configuration signals + // + // these connections cross the thread boundary + rig_connections_ << connect (rig.get (), &Transceiver::resolution, this, [=] (int resolution) { + rig_resolution_ = resolution; + }); + rig_connections_ << connect (rig.get (), &Transceiver::update, this, &Configuration::impl::handle_transceiver_update); + rig_connections_ << connect (rig.get (), &Transceiver::failure, this, &Configuration::impl::handle_transceiver_failure); + + // setup thread safe startup and close down semantics + rig_connections_ << connect (this, &Configuration::impl::start_transceiver, rig.get (), &Transceiver::start); + rig_connections_ << connect (this, &Configuration::impl::stop_transceiver, rig.get (), &Transceiver::stop); + + auto p = rig.release (); // take ownership + + // schedule destruction on thread quit + connect (transceiver_thread_, &QThread::finished, p, &QObject::deleteLater); + + // schedule eventual destruction for non-closing situations + // + // must be queued connection to avoid premature + // self-immolation since finished signal is going to be + // emitted from the object that will get destroyed in its + // own stop slot i.e. a same thread signal to slot + // connection which by default will be reduced to a method + // function call. + connect (p, &Transceiver::finished, p, &Transceiver::deleteLater, Qt::QueuedConnection); + + ui_->test_CAT_push_button->setStyleSheet ({}); + rig_active_ = true; + LOG_TRACE ("emitting startup_transceiver"); + Q_EMIT start_transceiver (++transceiver_command_number_); // start rig on its thread + result = true; + } + catch (std::exception const& e) + { + handle_transceiver_failure (e.what ()); + } + + saved_rig_params_ = rig_data; + rig_changed_ = true; + } + else + { + result = true; + } + return result; +} + +void Configuration::impl::set_cached_mode () +{ + MODE mode {Transceiver::UNK}; + // override cache mode with what we want to enforce which includes + // UNK (unknown) where we want to leave the rig mode untouched + switch (data_mode_) + { + case data_mode_USB: mode = Transceiver::USB; break; + case data_mode_data: mode = Transceiver::DIG_U; break; + default: break; + } + + cached_rig_state_.mode (mode); +} + +void Configuration::impl::transceiver_frequency (Frequency f) +{ + cached_rig_state_.online (true); // we want the rig online + set_cached_mode (); + + // apply any offset & calibration + // we store the offset here for use in feedback from the rig, we + // cannot absolutely determine if the offset should apply but by + // simply picking an offset when the Rx frequency is set and + // sticking to it we get sane behaviour + current_offset_ = stations_.offset (f); + cached_rig_state_.frequency (apply_calibration (f + current_offset_)); + + // qDebug () << "Configuration::impl::transceiver_frequency: n:" << transceiver_command_number_ + 1 << "f:" << f; + LOG_TRACE ("emitting set_transceiver: requested state:" << cached_rig_state_); + Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_); +} + +void Configuration::impl::transceiver_tx_frequency (Frequency f) +{ + Q_ASSERT (!f || split_mode ()); + if (split_mode ()) + { + cached_rig_state_.online (true); // we want the rig online + set_cached_mode (); + cached_rig_state_.split (f); + cached_rig_state_.tx_frequency (f); + + // lookup offset for tx and apply calibration + if (f) + { + // apply and offset and calibration + // we store the offset here for use in feedback from the + // rig, we cannot absolutely determine if the offset should + // apply but by simply picking an offset when the Rx + // frequency is set and sticking to it we get sane behaviour + current_tx_offset_ = stations_.offset (f); + cached_rig_state_.tx_frequency (apply_calibration (f + current_tx_offset_)); + } + + // qDebug () << "Configuration::impl::transceiver_tx_frequency: n:" << transceiver_command_number_ + 1 << "f:" << f; + LOG_TRACE ("emitting set_transceiver: requested state:" << cached_rig_state_); + Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_); + } +} + +void Configuration::impl::transceiver_mode (MODE m) +{ + cached_rig_state_.online (true); // we want the rig online + cached_rig_state_.mode (m); + // qDebug () << "Configuration::impl::transceiver_mode: n:" << transceiver_command_number_ + 1 << "m:" << m; + LOG_TRACE ("emitting set_transceiver: requested state:" << cached_rig_state_); + Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_); +} + +void Configuration::impl::transceiver_ptt (bool on) +{ + cached_rig_state_.online (true); // we want the rig online + set_cached_mode (); + cached_rig_state_.ptt (on); + // qDebug () << "Configuration::impl::transceiver_ptt: n:" << transceiver_command_number_ + 1 << "on:" << on; + LOG_TRACE ("emitting set_transceiver: requested state:" << cached_rig_state_); + Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_); +} + +void Configuration::impl::sync_transceiver (bool /*force_signal*/) +{ + // pass this on as cache must be ignored + // Q_EMIT sync (force_signal); +} + +void Configuration::impl::handle_transceiver_update (TransceiverState const& state, + unsigned sequence_number) +{ + LOG_TRACE ("#: " << sequence_number << ' ' << state); + + // only follow rig on some information, ignore other stuff + cached_rig_state_.online (state.online ()); + cached_rig_state_.frequency (state.frequency ()); + cached_rig_state_.mode (state.mode ()); + cached_rig_state_.split (state.split ()); + + if (state.online ()) + { + ui_->test_PTT_push_button->setChecked (state.ptt ()); + + if (isVisible ()) + { + ui_->test_CAT_push_button->setStyleSheet ("QPushButton {background-color: green;}"); + + auto const& rig = ui_->rig_combo_box->currentText (); + auto ptt_method = static_cast (ui_->PTT_method_button_group->checkedId ()); + auto CAT_PTT_enabled = transceiver_factory_.has_CAT_PTT (rig); + ui_->test_PTT_push_button->setEnabled ((TransceiverFactory::PTT_method_CAT == ptt_method && CAT_PTT_enabled) + || TransceiverFactory::PTT_method_DTR == ptt_method + || TransceiverFactory::PTT_method_RTS == ptt_method); + } + } + else + { + close_rig (); + } + + // pass on to clients if current command is processed + if (sequence_number == transceiver_command_number_) + { + TransceiverState reported_state {state}; + // take off calibration & offset + reported_state.frequency (remove_calibration (reported_state.frequency ()) - current_offset_); + + if (reported_state.tx_frequency ()) + { + // take off calibration & offset + reported_state.tx_frequency (remove_calibration (reported_state.tx_frequency ()) - current_tx_offset_); + } + + Q_EMIT self_->transceiver_update (reported_state); + } +} + +void Configuration::impl::handle_transceiver_failure (QString const& reason) +{ + LOG_ERROR ("handle_transceiver_failure: reason: " << reason); + close_rig (); + ui_->test_PTT_push_button->setChecked (false); + + if (isVisible ()) + { + MessageBox::critical_message (this, tr ("Rig failure"), reason); + } + else + { + // pass on if our dialog isn't active + Q_EMIT self_->transceiver_failure (reason); + } +} + +void Configuration::impl::close_rig () +{ + ui_->test_PTT_push_button->setEnabled (false); + + // revert to no rig configured + if (rig_active_) + { + ui_->test_CAT_push_button->setStyleSheet ("QPushButton {background-color: red;}"); + LOG_TRACE ("emitting stop_transceiver"); + Q_EMIT stop_transceiver (); + for (auto const& connection: rig_connections_) + { + disconnect (connection); + } + rig_connections_.clear (); + rig_active_ = false; + } +} + +// find the audio device that matches the specified name, also +// populate into the selection combo box with any devices we find in +// the search +QAudioDeviceInfo Configuration::impl::find_audio_device (QAudio::Mode mode, QComboBox * combo_box + , QString const& device_name) +{ + using std::copy; + using std::back_inserter; + + if (device_name.size ()) + { + Q_EMIT self_->enumerating_audio_devices (); + auto const& devices = QAudioDeviceInfo::availableDevices (mode); + Q_FOREACH (auto const& p, devices) + { + // qDebug () << "Configuration::impl::find_audio_device: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes (); + if (p.deviceName () == device_name) + { + // convert supported channel counts into something we can store in the item model + QList channel_counts; + auto scc = p.supportedChannelCounts (); + copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); + combo_box->insertItem (0, device_name, QVariant::fromValue (audio_info_type {p, channel_counts})); + combo_box->setCurrentIndex (0); + return p; + } + } + // insert a place holder for the not found device + combo_box->insertItem (0, device_name + " (" + tr ("Not found", "audio device missing") + ")", QVariant::fromValue (audio_info_type {})); + combo_box->setCurrentIndex (0); + } + return {}; +} + +// load the available audio devices into the selection combo box +void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * combo_box + , QAudioDeviceInfo * device) +{ + using std::copy; + using std::back_inserter; + + combo_box->clear (); + + Q_EMIT self_->enumerating_audio_devices (); + int current_index = -1; + auto const& devices = QAudioDeviceInfo::availableDevices (mode); + Q_FOREACH (auto const& p, devices) + { + // qDebug () << "Configuration::impl::load_audio_devices: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes (); + + // convert supported channel counts into something we can store in the item model + QList channel_counts; + auto scc = p.supportedChannelCounts (); + copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); + + combo_box->addItem (p.deviceName (), QVariant::fromValue (audio_info_type {p, channel_counts})); + if (p == *device) + { + current_index = combo_box->count () - 1; + } + } + combo_box->setCurrentIndex (current_index); +} + +// load the available network interfaces into the selection combo box +void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo_box, QStringList current) +{ + combo_box->clear (); + for (auto const& net_if : QNetworkInterface::allInterfaces ()) + { + auto flags = QNetworkInterface::IsUp | QNetworkInterface::CanMulticast; + if ((net_if.flags () & flags) == flags) + { + bool check_it = current.contains (net_if.name ()); + if (net_if.flags () & QNetworkInterface::IsLoopBack) + { + loopback_interface_name_ = net_if.name (); + if (!current.size ()) + { + check_it = true; + } + } + auto item = combo_box->addCheckItem (net_if.humanReadableName () + , net_if.name () + , check_it ? Qt::Checked : Qt::Unchecked); + auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ()) + .arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down"); + auto hw_addr = net_if.hardwareAddress (); + if (hw_addr.size ()) + { + tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ()); + } + auto aes = net_if.addressEntries (); + if (aes.size ()) + { + tip += "\naddresses:"; + for (auto const& ae : aes) + { + tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ()); + } + } + item->setToolTip (tip); + } + } +} + +// get the select network interfaces from the selection combo box +void Configuration::impl::validate_network_interfaces (QString const& /*text*/) +{ + auto model = static_cast (ui_->udp_interfaces_combo_box->model ()); + bool has_checked {false}; + int loopback_row {-1}; + for (int row = 0; row < model->rowCount (); ++row) + { + if (model->item (row)->data ().toString () == loopback_interface_name_) + { + loopback_row = row; + } + else if (Qt::Checked == model->item (row)->checkState ()) + { + has_checked = true; + } + } + if (loopback_row >= 0) + { + if (!has_checked) + { + model->item (loopback_row)->setCheckState (Qt::Checked); + } + model->item (loopback_row)->setEnabled (has_checked); + } +} + +// get the select network interfaces from the selection combo box +QStringList Configuration::impl::get_selected_network_interfaces (CheckableItemComboBox * combo_box) +{ + QStringList interfaces; + auto model = static_cast (combo_box->model ()); + for (int row = 0; row < model->rowCount (); ++row) + { + if (Qt::Checked == model->item (row)->checkState ()) + { + interfaces << model->item (row)->data ().toString (); + } + } + return interfaces; +} + +// enable only the channels that are supported by the selected audio device +void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both) +{ + // disable all items + for (int i (0); i < combo_box->count (); ++i) + { + combo_box->setItemData (i, combo_box_item_disabled, Qt::UserRole - 1); + } + + Q_FOREACH (QVariant const& v + , (source_combo_box->itemData (index).value ().second)) + { + // enable valid options + int n {v.toInt ()}; + if (2 == n) + { + combo_box->setItemData (AudioDevice::Left, combo_box_item_enabled, Qt::UserRole - 1); + combo_box->setItemData (AudioDevice::Right, combo_box_item_enabled, Qt::UserRole - 1); + if (allow_both) + { + combo_box->setItemData (AudioDevice::Both, combo_box_item_enabled, Qt::UserRole - 1); + } + } + else if (1 == n) + { + combo_box->setItemData (AudioDevice::Mono, combo_box_item_enabled, Qt::UserRole - 1); + } + } +} + +void Configuration::impl::find_tab (QWidget * target) +{ + for (auto * parent = target->parentWidget (); parent; parent = parent->parentWidget ()) + { + auto index = ui_->configuration_tabs->indexOf (parent); + if (index != -1) + { + ui_->configuration_tabs->setCurrentIndex (index); + break; + } + } + target->setFocus (); +} + +// load all the supported rig names into the selection combo box +void Configuration::impl::enumerate_rigs () +{ + ui_->rig_combo_box->clear (); + + auto rigs = transceiver_factory_.supported_transceivers (); + + for (auto r = rigs.cbegin (); r != rigs.cend (); ++r) + { + if ("None" == r.key ()) + { + // put None first + ui_->rig_combo_box->insertItem (0, r.key (), r.value ().model_number_); + } + else + { + int i; + for(i=1;irig_combo_box->count() && (r.key().toLower() > ui_->rig_combo_box->itemText(i).toLower());++i); + if (i < ui_->rig_combo_box->count()) ui_->rig_combo_box->insertItem (i, r.key (), r.value ().model_number_); + else ui_->rig_combo_box->addItem (r.key (), r.value ().model_number_); + } + } + + ui_->rig_combo_box->setCurrentText (rig_params_.rig_name); +} + +void Configuration::impl::fill_port_combo_box (QComboBox * cb) +{ + auto current_text = cb->currentText (); + cb->clear (); + Q_FOREACH (auto const& p, QSerialPortInfo::availablePorts ()) + { + if (!p.portName ().contains ( "NULL" )) // virtual serial port pairs + { + // remove possibly confusing Windows device path (OK because + // it gets added back by Hamlib) + cb->addItem (p.systemLocation ().remove (QRegularExpression {R"(^\\\\\.\\)"})); + auto tip = QString {"%1 %2 %3"}.arg (p.manufacturer ()).arg (p.serialNumber ()).arg (p.description ()).trimmed (); + if (tip.size ()) + { + cb->setItemData (cb->count () - 1, tip, Qt::ToolTipRole); + } + } + } + cb->addItem ("USB"); + cb->setItemData (cb->count () - 1, "Custom USB device", Qt::ToolTipRole); + cb->setEditText (current_text); +} + +auto Configuration::impl::apply_calibration (Frequency f) const -> Frequency +{ + if (frequency_calibration_disabled_) return f; + return std::llround (calibration_.intercept + + (1. + calibration_.slope_ppm / 1.e6) * f); +} + +auto Configuration::impl::remove_calibration (Frequency f) const -> Frequency +{ + if (frequency_calibration_disabled_) return f; + return std::llround ((f - calibration_.intercept) + / (1. + calibration_.slope_ppm / 1.e6)); +} + +ENUM_QDATASTREAM_OPS_IMPL (Configuration, DataMode); +ENUM_QDATASTREAM_OPS_IMPL (Configuration, Type2MsgGen); + +ENUM_CONVERSION_OPS_IMPL (Configuration, DataMode); +ENUM_CONVERSION_OPS_IMPL (Configuration, Type2MsgGen); diff --git a/patch/Configuration.hpp b/patch/Configuration.hpp new file mode 100755 index 0000000..dfc35e1 --- /dev/null +++ b/patch/Configuration.hpp @@ -0,0 +1,319 @@ +#ifndef CONFIGURATION_HPP_ +#define CONFIGURATION_HPP_ + +#include +#include +#include + +#include "Radio.hpp" +#include "models/IARURegions.hpp" +#include "Audio/AudioDevice.hpp" +#include "Transceiver/Transceiver.hpp" + +#include "pimpl_h.hpp" + +class QSettings; +class QWidget; +class QAudioDeviceInfo; +class QDir; +class QNetworkAccessManager; +class Bands; +class FrequencyList_v2; +class StationList; +class QStringListModel; +class LotWUsers; +class DecodeHighlightingModel; +class LogBook; + +// +// Class Configuration +// +// Encapsulates the control, access and, persistence of user defined +// settings for the wsjtx GUI. Setting values are accessed through a +// QDialog window containing concept orientated tab windows. +// +// Responsibilities +// +// Provides management of the CAT and PTT rig interfaces, providing +// control access via a minimal generic set of Qt slots and status +// updates via Qt signals. Internally the rig control capability is +// farmed out to a separate thread since many of the rig control +// functions are blocking. +// +// All user settings required by the wsjtx GUI are exposed through +// query methods. Settings only become visible once they have been +// accepted by the user which is done by clicking the "OK" button on +// the settings dialog. +// +// The QSettings instance passed to the constructor is used to read +// and write user settings. +// +// Pointers to three QAbstractItemModel objects are provided to give +// access to amateur band information, user working frequencies and, +// user operating band information. These porovide consistent data +// models that can be used in GUI lists or tables or simply queried +// for user defined bands, default operating frequencies and, station +// descriptions. +// +class Configuration final + : public QObject +{ + Q_OBJECT + +public: + using MODE = Transceiver::MODE; + using TransceiverState = Transceiver::TransceiverState; + using Frequency = Radio::Frequency; + using port_type = quint16; + + enum DataMode {data_mode_none, data_mode_USB, data_mode_data}; + Q_ENUM (DataMode) + enum Type2MsgGen {type_2_msg_1_full, type_2_msg_3_full, type_2_msg_5_only}; + Q_ENUM (Type2MsgGen) + + explicit Configuration (QNetworkAccessManager *, QDir const& temp_directory, QSettings * settings, + LogBook * logbook, QWidget * parent = nullptr); + ~Configuration (); + + void select_tab (int); + int exec (); + bool is_active () const; + + QDir temp_dir () const; + QDir doc_dir () const; + QDir data_dir () const; + QDir writeable_data_dir () const; + + QAudioDeviceInfo const& audio_input_device () const; + AudioDevice::Channel audio_input_channel () const; + QAudioDeviceInfo const& audio_output_device () const; + AudioDevice::Channel audio_output_channel () const; + + // These query methods should be used after a call to exec() to + // determine if either the audio input or audio output stream + // parameters have changed. The respective streams should be + // re-opened if they return true. + bool restart_audio_input () const; + bool restart_audio_output () const; + + QString my_callsign () const; + //SP6XD + QString regex_filter () const; + QString my_grid () const; + QString Field_Day_Exchange() const; + QString RTTY_Exchange() const; + void setEU_VHF_Contest(); + QFont text_font () const; + QFont decoded_text_font () const; + qint32 id_interval () const; + qint32 ntrials() const; + qint32 aggressive() const; + qint32 RxBandwidth() const; + double degrade() const; + double txDelay() const; + bool id_after_73 () const; + bool tx_QSY_allowed () const; + bool spot_to_psk_reporter () const; + bool psk_reporter_tcpip () const; + bool monitor_off_at_startup () const; + bool monitor_last_used () const; + bool log_as_RTTY () const; + bool report_in_comments () const; + bool prompt_to_log () const; + bool autoLog() const; + bool decodes_from_top () const; + bool insert_blank () const; + bool DXCC () const; + bool ppfx() const; + bool clear_DX () const; + bool miles () const; + bool quick_call () const; + bool disable_TX_on_73 () const; + bool force_call_1st() const; + bool alternate_bindings() const; + int watchdog () const; + bool TX_messages () const; + bool split_mode () const; + bool enable_VHF_features () const; + bool decode_at_52s () const; + bool single_decode () const; + bool twoPass() const; + bool bFox() const; + bool bHound() const; + bool bLowSidelobes() const; + bool x2ToneSpacing() const; + bool x4ToneSpacing() const; + bool MyDx() const; + bool CQMyN() const; + bool NDxG() const; + bool NN() const; + bool EMEonly() const; + bool post_decodes () const; + QString opCall() const; + void opCall (QString const&); + QString udp_server_name () const; + port_type udp_server_port () const; + QStringList udp_interface_names () const; + int udp_TTL () const; + QString n1mm_server_name () const; + port_type n1mm_server_port () const; + bool valid_n1mm_info () const; + bool broadcast_to_n1mm() const; + bool lowSidelobes() const; + bool accept_udp_requests () const; + bool udpWindowToFront () const; + bool udpWindowRestore () const; + Bands * bands (); + Bands const * bands () const; + IARURegions::Region region () const; + FrequencyList_v2 * frequencies (); + FrequencyList_v2 const * frequencies () const; + StationList * stations (); + StationList const * stations () const; + QStringListModel * macros (); + QStringListModel const * macros () const; + QDir save_directory () const; + QDir azel_directory () const; + QString rig_name () const; + Type2MsgGen type_2_msg_gen () const; + bool pwrBandTxMemory () const; + bool pwrBandTuneMemory () const; + LotWUsers const& lotw_users () const; + DecodeHighlightingModel const& decode_highlighting () const; + bool highlight_by_mode () const; + bool highlight_only_fields () const; + bool include_WAE_entities () const; + + enum class SpecialOperatingActivity {NONE, NA_VHF, EU_VHF, FIELD_DAY, RTTY, WW_DIGI, FOX, HOUND}; + SpecialOperatingActivity special_op_id () const; + + struct CalibrationParams + { + CalibrationParams () + : intercept {0.} + , slope_ppm {0.} + { + } + + CalibrationParams (double the_intercept, double the_slope_ppm) + : intercept {the_intercept} + , slope_ppm {the_slope_ppm} + { + } + + double intercept; // Hertz + double slope_ppm; // Hertz + }; + + // Temporarily enable or disable calibration adjustments. + void enable_calibration (bool = true); + + // Set the calibration parameters and enable calibration corrections. + void set_calibration (CalibrationParams); + + // Set the dynamic grid which is only used if configuration setting is enabled. + void set_location (QString const&); + + // This method queries if a CAT and PTT connection is operational. + bool is_transceiver_online () const; + + // Start the rig connection, safe and normal to call when rig is + // already open. + bool transceiver_online (); + + // check if a real rig is configured + bool is_dummy_rig () const; + + // Frequency resolution of the rig + // + // 0 - 1Hz + // 1 - 10Hz rounded + // -1 - 10Hz truncated + // 2 - 100Hz rounded + // -2 - 100Hz truncated + int transceiver_resolution () const; + + // Close down connection to rig. + void transceiver_offline (); + + // Set transceiver frequency in Hertz. + Q_SLOT void transceiver_frequency (Frequency); + + // Setting a non zero TX frequency means split operation + // rationalise_mode means ensure TX uses same mode as RX. + Q_SLOT void transceiver_tx_frequency (Frequency = 0u); + + // Set transceiver mode. + Q_SLOT void transceiver_mode (MODE); + + // Set/unset PTT. + // + // Note that this must be called even if VOX PTT is selected since + // the "Emulate Split" mode requires PTT information to coordinate + // frequency changes. + Q_SLOT void transceiver_ptt (bool = true); + + // Attempt to (re-)synchronise transceiver state. + // + // Force signal guarantees either a transceiver_update or a + // transceiver_failure signal. + // + // The enforce_mode_and_split parameter ensures that future + // transceiver updates have the correct mode and split setting + // i.e. the transceiver is ready for use. + Q_SLOT void sync_transceiver (bool force_signal = false, bool enforce_mode_and_split = false); + + Q_SLOT void invalidate_audio_input_device (QString error); + Q_SLOT void invalidate_audio_output_device (QString error); + + // + // These signals indicate a font has been selected and accepted for + // the application text and decoded text respectively. + // + Q_SIGNAL void text_font_changed (QFont) const; + Q_SIGNAL void decoded_text_font_changed (QFont) const; + + // + // This signal is emitted when the UDP server changes + // + Q_SIGNAL void udp_server_changed (QString& udp_server_name, QStringList const& network_interfaces) const; + Q_SIGNAL void udp_server_port_changed (port_type server_port) const; + Q_SIGNAL void udp_TTL_changed (int TTL) const; + Q_SIGNAL void accept_udp_requests_changed (bool checked) const; + + // signal updates to decode highlighting + Q_SIGNAL void decode_highlighting_changed (DecodeHighlightingModel const&) const; + + // + // These signals are emitted and reflect transceiver state changes + // + + // signals a change in one of the TransceiverState members + Q_SIGNAL void transceiver_update (Transceiver::TransceiverState const&) const; + + // Signals a failure of a control rig CAT or PTT connection. + // + // A failed rig CAT or PTT connection is fatal and the underlying + // connections are closed automatically. The connections can be + // re-established with a call to transceiver_online(true) assuming + // the fault condition has been rectified or is transient. + Q_SIGNAL void transceiver_failure (QString const& reason) const; + + // signal announces audio devices are being enumerated + // + // As this can take some time, particularly on Linux, consumers + // might like to notify the user. + Q_SIGNAL void enumerating_audio_devices (); + +private: + class impl; + pimpl m_; +}; + +ENUM_QDATASTREAM_OPS_DECL (Configuration, DataMode); +ENUM_QDATASTREAM_OPS_DECL (Configuration, Type2MsgGen); + +ENUM_CONVERSION_OPS_DECL (Configuration, DataMode); +ENUM_CONVERSION_OPS_DECL (Configuration, Type2MsgGen); + +#endif diff --git a/patch/Configuration.ui b/patch/Configuration.ui new file mode 100755 index 0000000..8835c69 --- /dev/null +++ b/patch/Configuration.ui @@ -0,0 +1,3279 @@ + + + configuration_dialog + + + + 0 + 0 + 554 + 560 + + + + Settings + + + + + + 0 + + + + Genera&l + + + General station details and settings. + + + + + + Station Details + + + + + + + + + + My C&all: + + + callsign_line_edit + + + + + + + Station callsign. + + + + + + + + + + + M&y Grid: + + + grid_line_edit + + + + + + + <html><head/><body><p>Maidenhead locator, preferably 6 characters.</p></body></html> + + + + + + + + + Check to allow grid changes from external programs + + + AutoGrid + + + + + + + + + IARU Region: + + + region_combo_box + + + + + + + <html><head/><body><p>Select your IARU region.</p></body></html> + + + + + + + + + + + + + Message generation for type 2 compound callsign holders: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + type_2_msg_gen_combo_box + + + + + + + true + + + <html><head/><body><p>Type 2 compound callsigns are those with prefixes or suffixes not included in the allowed shortlist (See Help-&gt;Add-on prefixes and suffixes).</p><p>This option determines which generated messages should contain your full type 2 compound call sign rather than your base callsign. It only applies if you have a type 2 compound callsign.</p><p>This option controls the way the messages that are used to answer CQ calls are generated. Generated messages 6 (CQ) and 5 (73) will always contain your full callsign. The JT65 and JT9 protocols allow for some standard messages with your full call at the expense of another piece of information such as the DX call or your locator.</p><p>Choosing message 1 omits the DX callsign which may be an issue when replying to CQ calls. Choosing message 3 also omits the DX callsign and many versions of this and other software will not extract the report. Choosing neither means that your full callsign only goes in your message 5 (73) so your QSO partner may log the wrong callsign.</p><p>None of these options are perfect, message 3 is usually best but be aware your QSO partner may not log the report you send them.</p></body></html> + + + 1 + + + + Full call in Tx1 + + + + + Full call in Tx3 + + + + + Full call in Tx5 only + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Display + + + + + + Show outgoing transmitted messages in the Rx frequency window. + + + &Tx messages to Rx frequency window + + + + + + + Show if decoded stations are new DXCC entities or worked before. + + + Show &DXCC, grid, and worked-before status + + + false + + + + + + + <html><head/><body><p>Check to have decodes for a new period start at the top of the Band Activity window and not scroll off the top when the window is full.</p><p>This is to aid selecting decodes to double-click while decoding is still in progress. Use the Band Activity vertical scroll bar to reveal decodes past the bottom of the window.</p></body></html> + + + Start new period decodes at top + + + + + + + Show principal prefix instead of country name + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Set the font characteristics for the application. + + + Font... + + + + + + + Set the font characteristics for the Band Activity and Rx Frequency areas. + + + Decoded Text Font... + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + Include a separator line between periods in the band activity window. + + + &Blank line between decoding periods + + + + + + + Show distance to DX station in miles rather than kilometers. + + + Display dista&nce in miles + + + + + + + + + + Qt::Horizontal + + + + + + + Behavior + + + + + + Decode after EME delay + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Tx watchdog: + + + tx_watchdog_spin_box + + + + + + + <html><head/><body><p>Number of minutes before unattended transmissions are aborted</p></body></html> + + + Disabled + + + minutes + + + + + + 6 + + + + + + + + + Enable VHF and submode features + + + + + + + Single decode + + + + + + + <html><head/><body><p>Some rigs are not able to process CAT commands while transmitting. This means that if you are operating in split mode you may have to uncheck this option.</p></body></html> + + + Allow Tx frequency changes while transmitting + + + + + + + Don't start decoding until the monitor button is clicked. + + + Mon&itor off at startup + + + false + + + + + + + <html><head/><body><p>Check this if you wish to automatically return to the last monitored frequency when monitor is enabled, leave it unchecked if you wish to have the current rig frequency maintained.</p></body></html> + + + Monitor returns to last used frequency + + + + + + + Alternate F1-F6 bindings + + + + + + + Turns off automatic transmissions after sending a 73 or any other free +text message. + + + Di&sable Tx after sending 73 + + + + + + + + + Send a CW ID after every 73 or free text message. + + + CW ID a&fter 73 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Periodic CW ID Inter&val: + + + CW_id_interval_spin_box + + + + + + + Send a CW ID periodically every few minutes. +This might be required under your countries licence regulations. +It will not interfere with other users as it is always sent in the +quiet period when decoding is done. + + + + + + + + + Automatic transmission mode. + + + Doubl&e-click on call sets Tx enable + + + + + + + Calling CQ forces Call 1st + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + &Radio + + + Radio interface configuration settings. + + + + + + + 0 + 0 + + + + Settings that control your CAT interface. + + + CAT Control + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Port: + + + CAT_port_combo_box + + + + + + + + 1 + 0 + + + + Serial port used for CAT control. + + + true + + + + + + QComboBox::NoInsert + + + + + + + + + + 0 + 0 + + + + Serial Port Parameters + + + Serial Port Parameters + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Baud Rate: + + + CAT_serial_baud_combo_box + + + + + + + + 0 + 0 + + + + Serial port data rate which must match the setting of your radio. + + + 0 + + + + 1200 + + + + + 2400 + + + + + 4800 + + + + + 9600 + + + + + 19200 + + + + + 38400 + + + + + 57600 + + + + + 115200 + + + + + + + + + + <html><head/><body><p>Number of data bits used to communicate with your radio's CAT interface (usually eight).</p></body></html> + + + Data bits + + + Data Bits + + + + + + D&efault + + + true + + + CAT_data_bits_button_group + + + + + + + Se&ven + + + true + + + CAT_data_bits_button_group + + + + + + + E&ight + + + false + + + CAT_data_bits_button_group + + + + + + + + + + <html><head/><body><p>Number of stop bits used when communicating with your radio's CAT interface</p><p>(consult you radio's manual for details).</p></body></html> + + + Stop bits + + + Stop Bits + + + + + + Default + + + true + + + CAT_stop_bits_button_group + + + + + + + On&e + + + CAT_stop_bits_button_group + + + + + + + T&wo + + + false + + + CAT_stop_bits_button_group + + + + + + + + + + <html><head/><body><p>Flow control protocol used between this computer and your radio's CAT interface (usually &quot;None&quot; but some require &quot;Hardware&quot;).</p></body></html> + + + Handshake + + + Handshake + + + + + + Default + + + true + + + CAT_handshake_button_group + + + + + + + &None + + + false + + + CAT_handshake_button_group + + + + + + + Software flow control (very rare on CAT interfaces). + + + XON/XOFF + + + CAT_handshake_button_group + + + + + + + Flow control using the RTS and CTS RS-232 control lines +not often used but some radios have it as an option and +a few, particularly some Kenwood rigs, require it). + + + &Hardware + + + CAT_handshake_button_group + + + + + + + + + + Special control of CAT port control lines. + + + Force Control Lines + + + Force Control Lines + + + false + + + + + + + + + + + + + + High + + + + + Low + + + + + + + + DTR: + + + force_DTR_combo_box + + + + + + + + + + + RTS: + + + force_RTS_combo_box + + + + + + + + + + + + + High + + + + + Low + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + How this program activates the PTT on your radio? + + + PTT Method + + + + + + <html><head/><body><p>No PTT activation, instead the radio's automatic VOX is used to key the transmitter.</p><p>Use this if you have no radio interface hardware.</p></body></html> + + + VO&X + + + PTT_method_button_group + + + + + + + <html><head/><body><p>Use the RS-232 DTR control line to toggle your radio's PTT, requires hardware to interface the line.</p><p>Some commercial interface units also use this method.</p><p>The DTR control line of the CAT serial port may be used for this or a DTR control line on a different serial port may be used.</p></body></html> + + + &DTR + + + true + + + PTT_method_button_group + + + + + + + Some radios support PTT via CAT commands, +use this option if your radio supports it and you have no +other hardware interface for PTT. + + + C&AT + + + PTT_method_button_group + + + + + + + <html><head/><body><p>Use the RS-232 RTS control line to toggle your radio's PTT, requires hardware to interface the line.</p><p>Some commercial interface units also use this method.</p><p>The RTS control line of the CAT serial port may be used for this or a RTS control line on a different serial port may be used. Note that this option is not available on the CAT serial port when hardware flow control is used.</p></body></html> + + + R&TS + + + PTT_method_button_group + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Port: + + + PTT_port_combo_box + + + + + + + + 1 + 0 + + + + <html><head/><body><p>Select the RS-232 serial port utilised for PTT control, this option is available when DTR or RTS is selected above as a transmit method.</p><p>This port can be the same one as the one used for CAT control.</p><p>For some interface types the special value CAT may be chosen, this is used for non-serial CAT interfaces that can control serial port control lines remotely (OmniRig for example).</p></body></html> + + + true + + + + + + -1 + + + QComboBox::NoInsert + + + + + + + + + + + + Modulation mode selected on radio. + + + Mode + + + + + + <html><head/><body><p>USB is usually the correct modulation mode,</p><p>unless the radio has a special data or packet mode setting</p><p>for AFSK operation.</p></body></html> + + + US&B + + + true + + + TX_mode_button_group + + + + + + + Don't allow the program to set the radio mode +(not recommended but use if the wrong mode +or bandwidth is selected). + + + None + + + TX_mode_button_group + + + + + + + If this is available then it is usually the correct mode for this program. + + + Data/P&kt + + + TX_mode_button_group + + + + + + + + + + Some radios can select the audio input using a CAT command, +this setting allows you to select which audio input will be used +(if it is available then generally the Rear/Data option is best). + + + Transmit Audio Source + + + + + + Rear&/Data + + + TX_audio_source_button_group + + + + + + + &Front/Mic + + + true + + + TX_audio_source_button_group + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + Rig: + + + rig_combo_box + + + + + + + + 1 + 0 + + + + + + + + Poll Interval: + + + CAT_poll_interval_spin_box + + + + + + + <html><head/><body><p>Interval to poll rig for status. Longer intervals will mean that changes to the rig will take longer to be detected.</p></body></html> + + + s + + + 1 + + + + + + + + + Qt::Horizontal + + + + + + + + + <html><head/><body><p>Attempt to connect to the radio with these settings.</p><p>The button will turn green if the connection is successful or red if there is a problem.</p></body></html> + + + Test CAT + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 40 + 20 + + + + + + + + Attempt to activate the transmitter. +Click again to deactivate. Normally no power should be +output since there is no audio being generated at this time. +Check that any Tx indication on your radio and/or your +radio interface behave as expected. + + + QPushButton:checked { + background-color: red; + border-style : outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + Test PTT + + + true + + + + + + + + + Split Operation + + + + + + Fake It + + + split_mode_button_group + + + + + + + Rig + + + split_mode_button_group + + + + + + + None + + + true + + + split_mode_button_group + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + A&udio + + + Audio interface settings + + + + + + Souncard + + + Soundcard + + + + + + + 1 + 0 + + + + Select the audio CODEC to use for transmitting. +If this is your default device for system sounds then +ensure that all system sounds are disabled otherwise +you will broadcast any systems sounds generated during +transmitting periods. + + + + + + + Select the channel to use for receiving. + + + + Mono + + + + + Left + + + + + Right + + + + + Both + + + + + + + + + 1 + 0 + + + + Select the audio CODEC to use for receiving. + + + + + + + Ou&tput: + + + sound_output_combo_box + + + + + + + Select the audio channel used for transmission. +Unless you have multiple radios connected on different +channels; then you will usually want to select mono or +both here. + + + + Mono + + + + + Left + + + + + Right + + + + + Both + + + + + + + + &Input: + + + sound_input_combo_box + + + + + + + + + + Save Directory + + + Save Directory + + + + + + Loc&ation: + + + save_path_select_push_button + + + + + + + + 1 + 0 + + + + Path to which .WAV files are saved. + + + false + + + background-color: rgb(255, 255, 255); +color: rgb(0, 0, 0); + + + TextLabel + + + + + + + Click to select a different save directory for .WAV files. + + + S&elect + + + + + + + + + + AzEl Directory + + + AzEl Directory + + + + + + Location: + + + azel_path_select_push_button + + + + + + + + 1 + 0 + + + + background-color: rgb(255, 255, 255); +color: rgb(0, 0, 0); + + + TextLabel + + + + + + + Select + + + + + + + + + + Power Memory By Band + + + Remember power settings by band + + + + + + Enable power memory during transmit + + + Transmit + + + + + + + Enable power memory during tuning + + + Tune + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + Tx/RX &Macros + + + Regex ignore filter and canned free text messages setup + + + + + + + RX regex ignore filter: + + + + + + + Regex ignore filter. Auto responses and manual QSO are ignored for matching callsigns. + + + + + + + + Qt::Horizontal + + + + + + + + TX macros: + + + + + + + + + &Add + + + + + + + + + + &Delete + + + + + + + Qt::ActionsContextMenu + + + Drag and drop items to rearrange order +Right click for item specific actions +Click, SHIFT+Click and, CRTL+Click to select items + + + QListView { + show-decoration-selected: 1; /* make the selection span the entire width of the view */ +} + +QListView::item:alternate { + background: #EEEEEE; +} + +QListView::item:selected { + border: 1px solid #6a6ea9; +} + +QListView::item:selected:!active { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #ABAFE5, stop: 1 #8588B2); +} + +QListView::item:selected:active { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #6a6ea9, stop: 1 #888dd9); +} + +QListView::item:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FAFBFE, stop: 1 #DCDEF1); +} + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + true + + + + + + + + Reportin&g + + + Reporting and logging settings + + + + + + Logging + + + + + + The program will pop up a partially completed Log QSO dialog when you send a 73 or free text message. + + + Promp&t me to log QSO + + + + + + + Qt::Horizontal + + + + 136 + 20 + + + + + + + + Op Call: + + + opCallEntry + + + + + + + Some logging programs will not accept the type of reports +saved by this program. +Check this option to save the sent and received reports in the +comments field. + + + d&B reports to comments + + + + + + + Check this option to force the clearing of the DX Call +and DX Grid fields when a 73 or free text message is sent. + + + Clear &DX call and grid after logging + + + + + + + <html><head/><body><p>Some logging programs will not accept WSJT-X mode names.</p></body></html> + + + Con&vert mode to RTTY + + + + + + + <html><head/><body><p>The callsign of the operator, if different from the station callsign.</p></body></html> + + + + + + + <html><head/><body><p>Check to have QSOs logged automatically, when complete.</p></body></html> + + + Log automatically (contesting only) + + + + + + + + + + Qt::Horizontal + + + + + + + Network Services + + + + + + <html><head/><body><p>The program can send your station details and all decoded signals with grid squares as spots to the http://pskreporter.info web site.</p><p>This is used for reverse beacon analysis which is very useful for assessing propagation and system performance.</p></body></html> + + + Enable &PSK Reporter Spotting + + + + + + + <html><head/><body><p>Check this option if a reliable connection is needed</p><p>Most users do not need this, the default uses UDP which is more efficient. Only check this if you have evidence that UDP traffic from you to PSK Reporter is being lost.</p></body></html> + + + Use TCP/IP connection + + + + + + + + + + UDP Server + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + UDP Server: + + + udp_server_line_edit + + + + + + + <html><head/><body><p>Optional hostname of network service to receive decodes.</p><p>Formats:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 multicast group address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 multicast group address</li></ul><p>Clearing this field will disable the broadcasting of UDP status updates.</p></body></html> + + + Qt::ImhDigitsOnly + + + + + + + UDP Server port number: + + + udp_server_port_spin_box + + + + + + + <html><head/><body><p>Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be sent.</p></body></html> + + + 65534 + + + + + + + Outgoing interfaces: + + + udp_interfaces_combo_box + + + + + + + <html><head/><body><p>When sending updates to a multicast group address it is necessary to specify which network interface(s) to send them to. If the loop-back interface is multicast capable then at least that one will be selected.</p><p>For most users the loop-back interface is all that is needed, that will allow multiple other applications on the same machine to interoperate with WSJT-X. If applications running on other hosts are to receive status updates then a suitable network interface should be used.</p><p>On some Linux systems it may be necessary to enable multicast on the loop-back network interface.</p></body></html> + + + + + + + <html><head/><body><p>Sets the number or router hops that multicast datagrams are allowed to make. Almost everyone should set this to 1 to keep outgoing multicast traffic withn the local subnet.</p></body></html> + + + 255 + + + 1 + + + + + + + Multicast TTL: + + + udp_TTL_spin_box + + + + + + + + + + + <html><head/><body><p>With this enabled WSJT-X will accept certain requests back from a UDP server that receives decode messages.</p></body></html> + + + Accept UDP requests + + + + + + + <html><head/><body><p>Indicate acceptance of an incoming UDP request. The effect of this option varies depending on the operating system and window manager, its intent is to notify the acceptance of an incoming UDP request even if this application is minimized or hidden.</p></body></html> + + + Notify on accepted UDP request + + + + + + + <html><head/><body><p>Restore the window from minimized if an UDP request is accepted.</p></body></html> + + + Accepted UDP request restores window + + + + + + + + + + + + Secondary UDP Server (deprecated) + + + + + + <html><head/><body><p>When checked, WSJT-X will broadcast a logged contact in ADIF format to the configured hostname and port. </p></body></html> + + + Enable logged contact ADIF broadcast + + + + + + + Server name or IP address: + + + n1mm_server_name_line_edit + + + + + + + <html><head/><body><p>Optional host name of N1MM Logger+ program to receive ADIF UDP broadcasts. This is usually 'localhost' or ip address 127.0.0.1</p><p>Formats:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 multicast group address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 multicast group address</li></ul><p>Clearing this field will disable broadcasting of ADIF information via UDP.</p></body></html> + + + + + + + Server port number: + + + n1mm_server_port_spin_box + + + + + + + <html><head/><body><p>Enter the port number that WSJT-X should use for UDP broadcasts of ADIF log information. For N1MM Logger+, this value should be 2333. If this is zero, no updates will be broadcast.</p></body></html> + + + 65534 + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + Frequencies + + + Default frequencies and band specific station details setup + + + + + + <html><head/><body><p>See &quot;Frequency Calibration&quot; in the WSJT-X User Guide for details of how to determine these parameters for your radio.</p></body></html> + + + Frequency Calibration + + + + + + + + Slope: + + + calibration_slope_ppm_spin_box + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + ppm + + + 4 + + + -999.999900000000025 + + + 999.999900000000025 + + + 0.100000000000000 + + + 0.000000000000000 + + + + + + + + + + + Intercept: + + + calibration_intercept_spin_box + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Hz + + + 2 + + + -99999.990000000005239 + + + 99999.990000000005239 + + + 0.100000000000000 + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + Working Frequencies + + + + + + + 1 + 0 + + + + Qt::ActionsContextMenu + + + <html><head/><body><p>Right click to maintain the working frequencies list.</p></body></html> + + + QAbstractItemView::DragOnly + + + true + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + + + + + + + + Station Information + + + + + + Qt::ActionsContextMenu + + + true + + + Items may be edited. +Right click for insert and delete options. + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + true + + + true + + + false + + + + + + + + + + + Colors + + + + + + Decode Highlightling + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Click to scan the wsjtx_log.adi ADIF file again for worked before information</p></body></html> + + + Rescan ADIF Log + + + + + + + + + <html><head/><body><p>Push to reset all highlight items above to default values and priorities.</p></body></html> + + + Reset Highlighting + + + + + + + + 0 + 0 + + + + Qt::ActionsContextMenu + + + true + + + <html><head/><body><p>Enable or disable using the check boxes and right-click an item to change or unset the foreground color, background color, or reset the item to default values. Drag and drop the items to change their priority, higher in the list is higher in priority.</p><p>Note that each foreground or background color may be either set or unset, unset means that it is not allocated for that item's type and lower priority items may apply.</p></body></html> + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + QAbstractItemView::SingleSelection + + + QListView::Adjust + + + + + + + <html><head/><body><p>Check to indicate new DXCC entities, grid squares, and callsigns per mode.</p></body></html> + + + Highlight by Mode + + + + + + + Include extra WAE entities + + + + + + + Check to for grid highlighting to only apply to unworked grid fields + + + Only grid Fields sought + + + + + + + + + + <html><head/><body><p>Controls for Logbook of the World user lookup.</p></body></html> + + + Logbook of the World User Validation + + + + + + Users CSV file URL: + + + LotW_CSV_URL_line_edit + + + + + + + + + <html><head/><body><p>URL of the ARRL LotW user's last upload dates and times data file which is used to highlight decodes from stations that are known to upload their log file to LotW.</p></body></html> + + + URL + + + https://lotw.arrl.org/lotw-user-activity.csv + + + + + + + <html><head/><body><p>Push this button to fetch the latest LotW user's upload date and time data file.</p></body></html> + + + Fetch Now + + + + + + + + + Age of last upload less than: + + + LotW_days_since_upload_spin_box + + + + + + + <html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html> + + + Days since last upload + + + days + + + 0 + + + 9999 + + + 365 + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Advanced + + + + + + <html><head/><body><p>User-selectable parameters for JT65 VHF/UHF/Microwave decoding.</p></body></html> + + + JT65 VHF/UHF/Microwave decoding parameters + + + + + + Random erasure patterns: + + + sbNtrials + + + + + + + <html><head/><body><p>Maximum number of erasure patterns for stochastic soft-decision Reed Solomon decoder is 10^(n/2).</p></body></html> + + + 0 + + + 12 + + + 6 + + + + + + + Aggressive decoding level: + + + sbAggressive + + + + + + + <html><head/><body><p>Higher levels will increase the probability of decoding, but will also increase probability of a false decode.</p></body></html> + + + 10 + + + + + + + Two-pass decoding + + + true + + + + + + + + + + Special operating activity: Generation of FT4, FT8, and MSK144 messages + + + true + + + false + + + + + + <html><head/><body><p>FT8 DXpedition mode: Hound operator calling the DX.</p></body></html> + + + Hound + + + Hound + + + true + + + special_op_activity_button_group + + + + + + + + 0 + 0 + + + + <html><head/><body><p>North American VHF/UHF/Microwave contests and others in which a 4-character grid locator is the required exchange.</p></body></html> + + + NA VHF Contest + + + NA VHF Contest + + + special_op_activity_button_group + + + + + + + <html><head/><body><p>FT8 DXpedition mode: Fox (DXpedition) operator.</p></body></html> + + + Fox + + + Fox + + + false + + + special_op_activity_button_group + + + + + + + + 0 + 0 + + + + <html><head/><body><p>European VHF+ contests requiring a signal report, serial number, and 6-character locator.</p></body></html> + + + EU VHF Contest + + + EU VHF Contest + + + special_op_activity_button_group + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <html><head/><body><p>ARRL RTTY Roundup and similar contests. Exchange is US state, Canadian province, or &quot;DX&quot;.</p></body></html> + + + R T T Y Roundup + + + RTTY Roundup messages + + + special_op_activity_button_group + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + RTTY Roundup exchange + + + RTTY RU Exch: + + + RTTY_Exchange + + + + + + + + 70 + 0 + + + + <html><head/><body><p>ARRL RTTY Roundup and similar contests. Exchange is US state, Canadian province, or &quot;DX&quot;.</p></body></html> + + + NJ + + + Qt::AlignCenter + + + + + + + + + + + + + <html><head/><body><p>ARRL Field Day exchange: number of transmitters, Class, and ARRL/RAC section or &quot;DX&quot;.</p></body></html> + + + A R R L Field Day + + + ARRL Field Day + + + special_op_activity_button_group + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Field Day exchange + + + FD Exch: + + + Field_Day_Exchange + + + + + + + + 70 + 0 + + + + <html><head/><body><p>ARRL Field Day exchange: number of transmitters, Class, and ARRL/RAC section or &quot;DX&quot;.</p></body></html> + + + 6A SNJ + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 18 + + + + <html><head/><body><p>World-Wide Digi-mode contest</p><p><br/></p></body></html> + + + WW Digital Contest + + + WW Digi Contest + + + special_op_activity_button_group + + + + + + + + + + Miscellaneous + + + + + + Degrade S/N of .wav file: + + + sbDegrade + + + + + + + For offline sensitivity tests + + + dB + + + 1 + + + 1.000000000000000 + + + + + + + Receiver bandwidth: + + + sbBandwidth + + + + + + + For offline sensitivity tests + + + Hz + + + 6000 + + + 100 + + + 2500 + + + + + + + Tx delay: + + + sbTxDelay + + + + + + + Minimum delay between assertion of PTT and start of Tx audio. + + + s + + + 1 + + + 0.000000000000000 + + + 0.500000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 50 + + + + Tone spacing + + + Tone spacing + + + + + + <html><head/><body><p>Generate Tx audio with twice the normal tone spacing. Intended for special LF/MF transmitters that use a divide-by-2 before generating RF.</p></body></html> + + + x 2 + + + + + + + true + + + <html><head/><body><p>Generate Tx audio with four times the normal tone spacing. Intended for special LF/MF transmitters that use a divide-by-4 before generating RF.</p></body></html> + + + x 4 + + + + + + + + + + + 0 + 50 + + + + Waterfall spectra + + + Waterfall spectra + + + + + + Low sidelobes + + + true + + + + + + + Most sensitive + + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + <html><head/><body><p>Discard (Cancel) or apply (OK) configuration changes including</p><p>resetting the radio interface and applying any soundcard changes</p></body></html> + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + DecodeHighlightingListView + QListView +
widgets/DecodeHighlightingListView.hpp
+
+ + LazyFillComboBox + QComboBox +
widgets/LazyFillComboBox.hpp
+
+ + CheckableItemComboBox + QComboBox +
widgets/CheckableItemComboBox.hpp
+
+
+ + configuration_tabs + callsign_line_edit + regex_line_edit + grid_line_edit + use_dynamic_grid + region_combo_box + type_2_msg_gen_combo_box + decodes_from_top_check_box + insert_blank_check_box + miles_check_box + TX_messages_check_box + DXCC_check_box + ppfx_check_box + font_push_button + decoded_text_font_push_button + monitor_off_check_box + monitor_last_used_check_box + quick_call_check_box + disable_TX_on_73_check_box + force_call_1st_check_box + alternate_bindings_check_box + CW_id_after_73_check_box + enable_VHF_features_check_box + tx_QSY_check_box + single_decode_check_box + decode_at_52s_check_box + tx_watchdog_spin_box + CW_id_interval_spin_box + rig_combo_box + CAT_poll_interval_spin_box + CAT_port_combo_box + CAT_serial_baud_combo_box + CAT_default_bit_radio_button + CAT_7_bit_radio_button + CAT_8_bit_radio_button + CAT_default_stop_bit_radio_button + CAT_one_stop_bit_radio_button + CAT_two_stop_bit_radio_button + CAT_handshake_default_radio_button + CAT_handshake_xon_radio_button + CAT_handshake_none_radio_button + CAT_handshake_hardware_radio_button + force_DTR_combo_box + force_RTS_combo_box + PTT_VOX_radio_button + PTT_CAT_radio_button + PTT_DTR_radio_button + PTT_RTS_radio_button + PTT_port_combo_box + TX_source_data_radio_button + TX_source_mic_radio_button + mode_none_radio_button + mode_USB_radio_button + mode_data_radio_button + split_none_radio_button + split_rig_radio_button + split_emulate_radio_button + test_CAT_push_button + test_PTT_push_button + sound_input_combo_box + sound_input_channel_combo_box + sound_output_combo_box + sound_output_channel_combo_box + save_path_select_push_button + azel_path_select_push_button + checkBoxPwrBandTxMemory + checkBoxPwrBandTuneMemory + add_macro_line_edit + add_macro_push_button + delete_macro_push_button + macros_list_view + prompt_to_log_check_box + cbAutoLog + log_as_RTTY_check_box + report_in_comments_check_box + clear_DX_check_box + opCallEntry + psk_reporter_check_box + psk_reporter_tcpip_check_box + udp_server_line_edit + udp_server_port_spin_box + udp_interfaces_combo_box + udp_TTL_spin_box + accept_udp_requests_check_box + udpWindowToFront + udpWindowRestore + enable_n1mm_broadcast_check_box + n1mm_server_name_line_edit + n1mm_server_port_spin_box + calibration_slope_ppm_spin_box + calibration_intercept_spin_box + frequencies_table_view + stations_table_view + highlighting_list_view + reset_highlighting_to_defaults_push_button + highlight_by_mode_check_box + only_fields_check_box + include_WAE_check_box + rescan_log_push_button + LotW_CSV_URL_line_edit + LotW_CSV_fetch_push_button + LotW_days_since_upload_spin_box + sbNtrials + sbAggressive + cbTwoPass + sbDegrade + sbBandwidth + sbTxDelay + cbx2ToneSpacing + cbx4ToneSpacing + rbLowSidelobes + rbMaxSensitivity + gbSpecialOpActivity + rbFox + rbHound + rbNA_VHF_Contest + rbField_Day + rbEU_VHF_Contest + rbRTTY_Roundup + rbWW_DIGI + Field_Day_Exchange + RTTY_Exchange + + + + + configuration_dialog_button_box + accepted() + configuration_dialog + accept() + + + 281 + 488 + + + 157 + 274 + + + + + configuration_dialog_button_box + rejected() + configuration_dialog + reject() + + + 349 + 488 + + + 286 + 274 + + + + + add_macro_push_button + clicked() + add_macro_line_edit + setFocus() + + + 72 + 42 + + + 43 + 42 + + + + + add_macro_line_edit + returnPressed() + add_macro_push_button + setFocus() + + + 43 + 42 + + + 72 + 42 + + + + + + + + + + + + + + +
diff --git a/patch/widgets/mainwindow.cpp b/patch/widgets/mainwindow.cpp new file mode 100755 index 0000000..163b8e6 --- /dev/null +++ b/patch/widgets/mainwindow.cpp @@ -0,0 +1,9469 @@ +//---------------------------------------------------------- MainWindow +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) +#include +#endif + +#include "helper_functions.h" +#include "revision_utils.hpp" +#include "qt_helpers.hpp" +#include "Network/NetworkAccessManager.hpp" +#include "Audio/soundout.h" +#include "Audio/soundin.h" +#include "Modulator/Modulator.hpp" +#include "Detector/Detector.hpp" +#include "plotter.h" +#include "echoplot.h" +#include "echograph.h" +#include "fastplot.h" +#include "fastgraph.h" +#include "about.h" +#include "messageaveraging.h" +#include "colorhighlighting.h" +#include "widegraph.h" +#include "sleep.h" +#include "logqso.h" +#include "Decoder/decodedtext.h" +#include "Radio.hpp" +#include "models/Bands.hpp" +#include "Transceiver/TransceiverFactory.hpp" +#include "models/StationList.hpp" +#include "validators/LiveFrequencyValidator.hpp" +#include "Network/MessageClient.hpp" +#include "Network/wsprnet.h" +#include "signalmeter.h" +#include "HelpTextWindow.hpp" +#include "SampleDownloader.hpp" +#include "Audio/BWFFile.hpp" +#include "MultiSettings.hpp" +#include "validators/MaidenheadLocatorValidator.hpp" +#include "validators/CallsignValidator.hpp" +#include "EqualizationToolsDialog.hpp" +#include "Network/LotWUsers.hpp" +#include "logbook/AD1CCty.hpp" +#include "models/FoxLog.hpp" +#include "models/CabrilloLog.hpp" +#include "FoxLogWindow.hpp" +#include "CabrilloLogWindow.hpp" +#include "ExportCabrillo.h" +#include "ui_mainwindow.h" +#include "moc_mainwindow.cpp" + +extern "C" { + //----------------------------------------------------- C and Fortran routines + void symspec_(struct dec_data *, int* k, double* trperiod, int* nsps, int* ingain, + bool* bLowSidelobes, int* minw, float* px, float s[], float* df3, + int* nhsym, int* npts8, float *m_pxmax, int* npct); + + void hspec_(short int d2[], int* k, int* nutc0, int* ntrperiod, int* nrxfreq, int* ntol, + bool* bmsk144, bool* btrain, double const pcoeffs[], int* ingain, + char const * mycall, char const * hiscall, bool* bshmsg, bool* bswl, + char const * ddir, float green[], + float s[], int* jh, float *pxmax, float *rmsNoGain, char line[], + fortran_charlen_t, fortran_charlen_t, fortran_charlen_t, fortran_charlen_t); + + void genft8_(char* msg, int* i3, int* n3, char* msgsent, char ft8msgbits[], + int itone[], fortran_charlen_t, fortran_charlen_t); + + void genft4_(char* msg, int* ichk, char* msgsent, char ft4msgbits[], int itone[], + fortran_charlen_t, fortran_charlen_t); + + void genfst4_(char* msg, int* ichk, char* msgsent, char fst4msgbits[], + int itone[], int* iwspr, fortran_charlen_t, fortran_charlen_t); + + void gen_ft8wave_(int itone[], int* nsym, int* nsps, float* bt, float* fsample, float* f0, + float xjunk[], float wave[], int* icmplx, int* nwave); + + void gen_ft4wave_(int itone[], int* nsym, int* nsps, float* fsample, float* f0, + float xjunk[], float wave[], int* icmplx, int* nwave); + + void gen_fst4wave_(int itone[], int* nsym, int* nsps, int* nwave, float* fsample, + int* hmod, float* f0, int* icmplx, float xjunk[], float wave[]); + + void genwave_(int itone[], int* nsym, int* nsps, int* nwave, float* fsample, + int* hmod, float* f0, int* icmplx, float xjunk[], float wave[]); + + void gen4_(char* msg, int* ichk, char* msgsent, int itone[], + int* itext, fortran_charlen_t, fortran_charlen_t); + + void gen9_(char* msg, int* ichk, char* msgsent, int itone[], + int* itext, fortran_charlen_t, fortran_charlen_t); + + void genmsk_128_90_(char* msg, int* ichk, char* msgsent, int itone[], int* itype, + fortran_charlen_t, fortran_charlen_t); + + void gen65_(char* msg, int* ichk, char* msgsent, int itone[], + int* itext, fortran_charlen_t, fortran_charlen_t); + + void genq65_(char* msg, int* ichk, char* msgsent, int itone[], + int* i3, int* n3, fortran_charlen_t, fortran_charlen_t); + + void genwspr_(char* msg, char* msgsent, int itone[], fortran_charlen_t, fortran_charlen_t); + + void azdist_(char* MyGrid, char* HisGrid, double* utch, int* nAz, int* nEl, + int* nDmiles, int* nDkm, int* nHotAz, int* nHotABetter, + fortran_charlen_t, fortran_charlen_t); + + void morse_(char* msg, int* icw, int* ncw, fortran_charlen_t); + + int ptt_(int nport, int ntx, int* iptt, int* nopen); + + void wspr_downsample_(short int d2[], int* k); + + int savec2_(char const * fname, int* TR_seconds, double* dial_freq, fortran_charlen_t); + + void avecho_( short id2[], int* dop, int* nfrit, int* nqual, float* f1, + float* level, float* sigdb, float* snr, float* dfreq, + float* width); + + void fast_decode_(short id2[], int narg[], double * trperiod, + char msg[], char mycall[], char hiscall[], + fortran_charlen_t, fortran_charlen_t, fortran_charlen_t); + void degrade_snr_(short d2[], int* n, float* db, float* bandwidth); + + void wav12_(short d2[], short d1[], int* nbytes, short* nbitsam2); + + void refspectrum_(short int d2[], bool* bclearrefspec, + bool* brefspec, bool* buseref, const char* c_fname, fortran_charlen_t); + + void freqcal_(short d2[], int* k, int* nkhz,int* noffset, int* ntol, + char line[], fortran_charlen_t); + + void fix_contest_msg_(char* MyGrid, char* msg, fortran_charlen_t, fortran_charlen_t); + + void calibrate_(char const * data_dir, int* iz, double* a, double* b, double* rms, + double* sigmaa, double* sigmab, int* irc, fortran_charlen_t); + + void foxgen_(); + + void plotsave_(float swide[], int* m_w , int* m_h1, int* irow); + + void chkcall_(char* w, char* basc_call, bool cok, int len1, int len2); + + void get_ft4msg_(int* idecode, char* line, int len); + + void chk_samples_(int* m_ihsym,int* k, int* m_hsymStop); + + void save_dxbase_(char* dxbase, int len); +} + +int volatile itone[MAX_NUM_SYMBOLS]; //Audio tones for all Tx symbols +int volatile itone0[MAX_NUM_SYMBOLS]; //Dummy array, data not actually used +int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID +dec_data_t dec_data; // for sharing with Fortran + +int outBufSize; +int rc; +qint32 g_iptt {0}; +wchar_t buffer[256]; +float fast_green[703]; +float fast_green2[703]; +float fast_s[44992]; //44992=64*703 +float fast_s2[44992]; +int fast_jh {0}; +int fast_jhpeak {0}; +int fast_jh2 {0}; +int narg[15]; +QVector g_ColorTbl; + +using SpecOp = Configuration::SpecialOperatingActivity; + +namespace +{ + Radio::Frequency constexpr default_frequency {14076000}; + QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>;$]*"}; + // grid exact match excluding RR73 + QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"}; + auto quint32_max = std::numeric_limits::max (); + constexpr int N_WIDGETS {38}; + constexpr int default_rx_audio_buffer_frames {-1}; // lets Qt decide + constexpr int default_tx_audio_buffer_frames {-1}; // lets Qt decide + + bool message_is_73 (int type, QStringList const& msg_parts) + { + return type >= 0 + && (((type < 6 || 7 == type) + && (msg_parts.contains ("73") || msg_parts.contains ("RR73"))) + || (type == 6 && !msg_parts.filter ("73").isEmpty ())); + } + + int ms_minute_error () + { + auto const& now = QDateTime::currentDateTimeUtc (); + auto const& time = now.time (); + auto second = time.second (); + return now.msecsTo (now.addSecs (second > 30 ? 60 - second : -second)) - time.msec (); + } +} + +//--------------------------------------------------- MainWindow constructor +MainWindow::MainWindow(QDir const& temp_directory, bool multiple, + MultiSettings * multi_settings, QSharedMemory *shdmem, + unsigned downSampleFactor, + QSplashScreen * splash, QProcessEnvironment const& env, QWidget *parent) : + MultiGeometryWidget {parent}, + m_env {env}, + m_network_manager {this}, + m_valid {true}, + m_splash {splash}, + m_revision {revision ()}, + m_multiple {multiple}, + m_multi_settings {multi_settings}, + m_configurations_button {0}, + m_settings {multi_settings->settings ()}, + ui(new Ui::MainWindow), + m_config {&m_network_manager, temp_directory, m_settings, &m_logBook, this}, + m_logBook {&m_config}, + m_WSPR_band_hopping {m_settings, &m_config, this}, + m_WSPR_tx_next {false}, + m_rigErrorMessageBox {MessageBox::Critical, tr ("Rig Control Error") + , MessageBox::Cancel | MessageBox::Ok | MessageBox::Retry}, + m_wideGraph (new WideGraph(m_settings)), + m_echoGraph (new EchoGraph(m_settings)), + m_fastGraph (new FastGraph(m_settings)), + // no parent so that it has a taskbar icon + m_logDlg (new LogQSO (program_title (), m_settings, &m_config, &m_logBook, nullptr)), + m_lastDialFreq {0}, + m_dialFreqRxWSPR {0}, + m_detector {new Detector {RX_SAMPLE_RATE, double(NTMAX), downSampleFactor}}, + m_FFTSize {6192 / 2}, // conservative value to avoid buffer overruns + m_soundInput {new SoundInput}, + m_modulator {new Modulator {TX_SAMPLE_RATE, NTMAX}}, + m_soundOutput {new SoundOutput}, + m_rx_audio_buffer_frames {0}, + m_tx_audio_buffer_frames {0}, + m_msErase {0}, + m_secBandChanged {0}, + m_freqNominal {0}, + m_freqTxNominal {0}, + m_reverse_Doppler {"1" == env.value ("WSJT_REVERSE_DOPPLER", "0")}, + m_tRemaining {0.}, + m_TRperiod {60.0}, + m_DTtol {3.0}, + m_waterfallAvg {1}, + m_ntx {1}, + m_gen_message_is_cq {false}, + m_send_RR73 {false}, + m_XIT {0}, + m_sec0 {-1}, + m_RxLog {1}, //Write Date and Time to RxLog + m_nutc0 {999999}, + m_ntr {0}, + m_tx {0}, + m_inGain {0}, + m_secID {0}, + m_idleMinutes {0}, + m_nSubMode {0}, + m_nclearave {1}, + m_nWSPRdecodes {0}, + m_k0 {9999999}, + m_nPick {0}, + m_frequency_list_fcal_iter {m_config.frequencies ()->begin ()}, + m_nTx73 {0}, + m_btxok {false}, + m_diskData {false}, + m_loopall {false}, + m_txFirst {false}, + m_auto {false}, + m_restart {false}, + m_startAnother {false}, + m_saveDecoded {false}, + m_saveAll {false}, + m_widebandDecode {false}, + m_dataAvailable {false}, + m_decodedText2 {false}, + m_freeText {false}, + m_sentFirst73 {false}, + m_currentMessageType {-1}, + m_lastMessageType {-1}, + m_bShMsgs {false}, + m_bSWL {false}, + m_uploading {false}, + m_grid6 {false}, + m_tuneup {false}, + m_bTxTime {false}, + m_rxDone {true}, + m_bSimplex {false}, + m_bEchoTxOK {false}, + m_bTransmittedEcho {false}, + m_bEchoTxed {false}, + m_bFastDecodeCalled {false}, + m_bDoubleClickAfterCQnnn {false}, + m_bRefSpec {false}, + m_bClearRefSpec {false}, + m_bTrain {false}, + m_bAutoReply {false}, + m_QSOProgress {CALLING}, + m_ihsym {0}, + m_nzap {0}, + m_px {0.0}, + m_iptt0 {0}, + m_btxok0 {false}, + m_nsendingsh {0}, + m_onAirFreq0 {0.0}, + m_first_error {true}, + tx_status_label {tr ("Receiving")}, + wsprNet {new WSPRNet {&m_network_manager, this}}, + m_baseCall {Radio::base_callsign (m_config.my_callsign ())}, + m_appDir {QApplication::applicationDirPath ()}, + m_cqStr {""}, + m_palette {"Linrad"}, + m_mode {"JT9"}, + m_rpt {"-15"}, + m_pfx { + "1A", "1S", + "3A", "3B6", "3B8", "3B9", "3C", "3C0", "3D2", "3D2C", + "3D2R", "3DA", "3V", "3W", "3X", "3Y", "3YB", "3YP", + "4J", "4L", "4S", "4U1I", "4U1U", "4W", "4X", + "5A", "5B", "5H", "5N", "5R", "5T", "5U", "5V", "5W", "5X", "5Z", + "6W", "6Y", + "7O", "7P", "7Q", "7X", + "8P", "8Q", "8R", + "9A", "9G", "9H", "9J", "9K", "9L", "9M2", "9M6", "9N", + "9Q", "9U", "9V", "9X", "9Y", + "A2", "A3", "A4", "A5", "A6", "A7", "A9", "AP", + "BS7", "BV", "BV9", "BY", + "C2", "C3", "C5", "C6", "C9", "CE", "CE0X", "CE0Y", + "CE0Z", "CE9", "CM", "CN", "CP", "CT", "CT3", "CU", + "CX", "CY0", "CY9", + "D2", "D4", "D6", "DL", "DU", + "E3", "E4", "E5", "EA", "EA6", "EA8", "EA9", "EI", "EK", + "EL", "EP", "ER", "ES", "ET", "EU", "EX", "EY", "EZ", + "F", "FG", "FH", "FJ", "FK", "FKC", "FM", "FO", "FOA", + "FOC", "FOM", "FP", "FR", "FRG", "FRJ", "FRT", "FT5W", + "FT5X", "FT5Z", "FW", "FY", + "M", "MD", "MI", "MJ", "MM", "MU", "MW", + "H4", "H40", "HA", "HB", "HB0", "HC", "HC8", "HH", + "HI", "HK", "HK0", "HK0M", "HL", "HM", "HP", "HR", + "HS", "HV", "HZ", + "I", "IS", "IS0", + "J2", "J3", "J5", "J6", "J7", "J8", "JA", "JDM", + "JDO", "JT", "JW", "JX", "JY", + "K", "KC4", "KG4", "KH0", "KH1", "KH2", "KH3", "KH4", "KH5", + "KH5K", "KH6", "KH7", "KH8", "KH9", "KL", "KP1", "KP2", + "KP4", "KP5", + "LA", "LU", "LX", "LY", "LZ", + "OA", "OD", "OE", "OH", "OH0", "OJ0", "OK", "OM", "ON", + "OX", "OY", "OZ", + "P2", "P4", "PA", "PJ2", "PJ7", "PY", "PY0F", "PT0S", "PY0T", "PZ", + "R1F", "R1M", + "S0", "S2", "S5", "S7", "S9", "SM", "SP", "ST", "SU", + "SV", "SVA", "SV5", "SV9", + "T2", "T30", "T31", "T32", "T33", "T5", "T7", "T8", "T9", "TA", + "TF", "TG", "TI", "TI9", "TJ", "TK", "TL", "TN", "TR", "TT", + "TU", "TY", "TZ", + "UA", "UA2", "UA9", "UK", "UN", "UR", + "V2", "V3", "V4", "V5", "V6", "V7", "V8", "VE", "VK", "VK0H", + "VK0M", "VK9C", "VK9L", "VK9M", "VK9N", "VK9W", "VK9X", "VP2E", + "VP2M", "VP2V", "VP5", "VP6", "VP6D", "VP8", "VP8G", "VP8H", + "VP8O", "VP8S", "VP9", "VQ9", "VR", "VU", "VU4", "VU7", + "XE", "XF4", "XT", "XU", "XW", "XX9", "XZ", + "YA", "YB", "YI", "YJ", "YK", "YL", "YN", "YO", "YS", "YU", "YV", "YV0", + "Z2", "Z3", "ZA", "ZB", "ZC4", "ZD7", "ZD8", "ZD9", "ZF", "ZK1N", + "ZK1S", "ZK2", "ZK3", "ZL", "ZL7", "ZL8", "ZL9", "ZP", "ZS", "ZS8" + }, + m_sfx {"P", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A"}, + mem_jt9 {shdmem}, + m_downSampleFactor (downSampleFactor), + m_audioThreadPriority (QThread::HighPriority), + m_bandEdited {false}, + m_splitMode {false}, + m_monitoring {false}, + m_tx_when_ready {false}, + m_transmitting {false}, + m_tune {false}, + m_tx_watchdog {false}, + m_block_pwr_tooltip {false}, + m_PwrBandSetOK {true}, + m_lastMonitoredFrequency {default_frequency}, + m_toneSpacing {0.}, + m_firstDecode {0}, + m_optimizingProgress {"Optimizing decoder FFTs for your CPU.\n" + "Please be patient,\n" + "this may take a few minutes", QString {}, 0, 1, this}, + m_messageClient {new MessageClient {QApplication::applicationName (), + version (), revision (), + m_config.udp_server_name (), m_config.udp_server_port (), + m_config.udp_interface_names (), m_config.udp_TTL (), + this}}, + m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()}, + m_manual {&m_network_manager}, + m_block_udp_status_updates {false} +{ + ui->setupUi(this); + setUnifiedTitleAndToolBarOnMac (true); + createStatusBar(); + add_child_to_event_filter (this); + ui->dxGridEntry->setValidator (new MaidenheadLocatorValidator {this}); + ui->dxCallEntry->setValidator (new CallsignValidator {this}); + ui->sbTR->values ({5, 10, 15, 30, 60, 120, 300, 900, 1800}); + ui->sbTR_FST4W->values ({120, 300, 900, 1800}); + ui->decodedTextBrowser->set_configuration (&m_config, true); + ui->decodedTextBrowser2->set_configuration (&m_config); + + m_optimizingProgress.setWindowModality (Qt::WindowModal); + m_optimizingProgress.setAutoReset (false); + m_optimizingProgress.setMinimumDuration (15000); // only show after 15s delay + + // Closedown. + connect (ui->actionExit, &QAction::triggered, this, &QMainWindow::close); + + // parts of the rig error message box that are fixed + m_rigErrorMessageBox.setInformativeText (tr ("Do you want to reconfigure the radio interface?")); + m_rigErrorMessageBox.setDefaultButton (MessageBox::Ok); + + // start audio thread and hook up slots & signals for shutdown management + // these objects need to be in the audio thread so that invoking + // their slots is done in a thread safe way + m_soundOutput->moveToThread (&m_audioThread); + m_modulator->moveToThread (&m_audioThread); + m_soundInput->moveToThread (&m_audioThread); + m_detector->moveToThread (&m_audioThread); + bool ok; + auto buffer_size = env.value ("WSJT_RX_AUDIO_BUFFER_FRAMES", "0").toInt (&ok); + m_rx_audio_buffer_frames = ok && buffer_size ? buffer_size : default_rx_audio_buffer_frames; + buffer_size = env.value ("WSJT_TX_AUDIO_BUFFER_FRAMES", "0").toInt (&ok); + m_tx_audio_buffer_frames = ok && buffer_size ? buffer_size : default_tx_audio_buffer_frames; + + // hook up sound output stream slots & signals and disposal + connect (this, &MainWindow::initializeAudioOutputStream, m_soundOutput, &SoundOutput::setFormat); + connect (m_soundOutput, &SoundOutput::error, this, &MainWindow::showSoundOutError); + connect (m_soundOutput, &SoundOutput::error, &m_config, &Configuration::invalidate_audio_output_device); + // connect (m_soundOutput, &SoundOutput::status, this, &MainWindow::showStatusMessage); + connect (this, &MainWindow::outAttenuationChanged, m_soundOutput, &SoundOutput::setAttenuation); + connect (&m_audioThread, &QThread::finished, m_soundOutput, &QObject::deleteLater); + + // hook up Modulator slots and disposal + connect (this, &MainWindow::transmitFrequency, m_modulator, &Modulator::setFrequency); + connect (this, &MainWindow::endTransmitMessage, m_modulator, &Modulator::stop); + connect (this, &MainWindow::tune, m_modulator, &Modulator::tune); + connect (this, &MainWindow::sendMessage, m_modulator, &Modulator::start); + connect (&m_audioThread, &QThread::finished, m_modulator, &QObject::deleteLater); + + // hook up the audio input stream signals, slots and disposal + connect (this, &MainWindow::startAudioInputStream, m_soundInput, &SoundInput::start); + connect (this, &MainWindow::suspendAudioInputStream, m_soundInput, &SoundInput::suspend); + connect (this, &MainWindow::resumeAudioInputStream, m_soundInput, &SoundInput::resume); + connect (this, &MainWindow::reset_audio_input_stream, m_soundInput, &SoundInput::reset); + connect (this, &MainWindow::finished, m_soundInput, &SoundInput::stop); + connect(m_soundInput, &SoundInput::error, this, &MainWindow::showSoundInError); + connect(m_soundInput, &SoundInput::error, &m_config, &Configuration::invalidate_audio_input_device); + // connect(m_soundInput, &SoundInput::status, this, &MainWindow::showStatusMessage); + connect (&m_audioThread, &QThread::finished, m_soundInput, &QObject::deleteLater); + + connect (this, &MainWindow::finished, this, &MainWindow::close); + + // hook up the detector signals, slots and disposal + connect (this, &MainWindow::FFTSize, m_detector, &Detector::setBlockSize); + connect(m_detector, &Detector::framesWritten, this, &MainWindow::dataSink); + connect (&m_audioThread, &QThread::finished, m_detector, &QObject::deleteLater); + + // setup the waterfall + connect(m_wideGraph.data (), SIGNAL(freezeDecode2(int)),this,SLOT(freezeDecode(int))); + connect(m_wideGraph.data (), SIGNAL(f11f12(int)),this,SLOT(bumpFqso(int))); + connect(m_wideGraph.data (), SIGNAL(setXIT2(int)),this,SLOT(setXIT(int))); + + connect (m_fastGraph.data (), &FastGraph::fastPick, this, &MainWindow::fastPick); + + connect (this, &MainWindow::finished, m_wideGraph.data (), &WideGraph::close); + connect (this, &MainWindow::finished, m_echoGraph.data (), &EchoGraph::close); + connect (this, &MainWindow::finished, m_fastGraph.data (), &FastGraph::close); + + // setup the log QSO dialog + connect (m_logDlg.data (), &LogQSO::acceptQSO, this, &MainWindow::acceptQSO); + connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close); + + // hook up the log book + connect (&m_logBook, &LogBook::finished_loading, [this] (int record_count, QString const& error) { + if (error.size ()) + { + MessageBox::warning_message (this, tr ("Error Scanning ADIF Log"), error); + } + else + { + showStatusMessage (tr ("Scanned ADIF log, %1 worked before records created").arg (record_count)); + } + }); + + // Network message handlers + m_messageClient->enable (m_config.accept_udp_requests ()); + connect (m_messageClient, &MessageClient::clear_decodes, [this] (quint8 window) { + ++window; + if (window & 1) + { + ui->decodedTextBrowser->erase (); + } + if (window & 2) + { + ui->decodedTextBrowser2->erase (); + } + }); + connect (m_messageClient, &MessageClient::reply, this, &MainWindow::replyToCQ); + connect (m_messageClient, &MessageClient::close, this, &MainWindow::close); + connect (m_messageClient, &MessageClient::replay, this, &MainWindow::replayDecodes); + connect (m_messageClient, &MessageClient::location, this, &MainWindow::locationChange); + connect (m_messageClient, &MessageClient::halt_tx, [this] (bool auto_only) { + if (auto_only) { + if (ui->autoButton->isChecked ()) { + ui->autoButton->click(); + } + } else { + ui->stopTxButton->click(); + } + }); + connect (m_messageClient, &MessageClient::error, this, &MainWindow::networkError); + connect (m_messageClient, &MessageClient::free_text, [this] (QString const& text, bool send) { + tx_watchdog (false); + // send + non-empty text means set and send the free text + // message, !send + non-empty text means set the current free + // text message, send + empty text means send the current free + // text message without change, !send + empty text means clear + // the current free text message + if (0 == ui->tabWidget->currentIndex ()) { + if (!text.isEmpty ()) { + ui->tx5->setCurrentText (text); + } + if (send) { + ui->txb5->click (); + } else if (text.isEmpty ()) { + ui->tx5->setCurrentText (text); + } + } + QApplication::alert (this); + }); + + connect (m_messageClient, &MessageClient::highlight_callsign, ui->decodedTextBrowser, &DisplayText::highlight_callsign); + connect (m_messageClient, &MessageClient::switch_configuration, m_multi_settings, &MultiSettings::select_configuration); + connect (m_messageClient, &MessageClient::configure, this, &MainWindow::remote_configure); + + // Hook up WSPR band hopping + connect (ui->band_hopping_schedule_push_button, &QPushButton::clicked + , &m_WSPR_band_hopping, &WSPRBandHopping::show_dialog); + connect (ui->sbTxPercent, static_cast (&QSpinBox::valueChanged) + , &m_WSPR_band_hopping, &WSPRBandHopping::set_tx_percent); + + on_EraseButton_clicked (); + + QActionGroup* modeGroup = new QActionGroup(this); + ui->actionFST4->setActionGroup(modeGroup); + ui->actionFST4W->setActionGroup(modeGroup); + ui->actionFT4->setActionGroup(modeGroup); + ui->actionFT8->setActionGroup(modeGroup); + ui->actionJT9->setActionGroup(modeGroup); + ui->actionJT65->setActionGroup(modeGroup); + ui->actionJT4->setActionGroup(modeGroup); + ui->actionWSPR->setActionGroup(modeGroup); + ui->actionEcho->setActionGroup(modeGroup); + ui->actionMSK144->setActionGroup(modeGroup); + ui->actionQ65->setActionGroup(modeGroup); + ui->actionFreqCal->setActionGroup(modeGroup); + + QActionGroup* saveGroup = new QActionGroup(this); + ui->actionNone->setActionGroup(saveGroup); + ui->actionSave_decoded->setActionGroup(saveGroup); + ui->actionSave_all->setActionGroup(saveGroup); + + QActionGroup* DepthGroup = new QActionGroup(this); + ui->actionQuickDecode->setActionGroup(DepthGroup); + ui->actionMediumDecode->setActionGroup(DepthGroup); + ui->actionDeepestDecode->setActionGroup(DepthGroup); + + connect (ui->download_samples_action, &QAction::triggered, [this] () { + if (!m_sampleDownloader) + { + m_sampleDownloader.reset (new SampleDownloader {m_settings, &m_config, &m_network_manager, this}); + } + m_sampleDownloader->show (); + }); + + connect (ui->view_phase_response_action, &QAction::triggered, [this] () { + if (!m_equalizationToolsDialog) + { + m_equalizationToolsDialog.reset (new EqualizationToolsDialog {m_settings, m_config.writeable_data_dir (), m_phaseEqCoefficients, this}); + connect (m_equalizationToolsDialog.data (), &EqualizationToolsDialog::phase_equalization_changed, + [this] (QVector const& coeffs) { + m_phaseEqCoefficients = coeffs; + }); + } + m_equalizationToolsDialog->show (); + }); + + connect (&m_config.lotw_users (), &LotWUsers::LotW_users_error, this, [this] (QString const& reason) { + MessageBox::warning_message (this, tr ("Error Loading LotW Users Data"), reason); + }, Qt::QueuedConnection); + + QButtonGroup* txMsgButtonGroup = new QButtonGroup {this}; + txMsgButtonGroup->addButton(ui->txrb1,1); + txMsgButtonGroup->addButton(ui->txrb2,2); + txMsgButtonGroup->addButton(ui->txrb3,3); + txMsgButtonGroup->addButton(ui->txrb4,4); + txMsgButtonGroup->addButton(ui->txrb5,5); + txMsgButtonGroup->addButton(ui->txrb6,6); + set_dateTimeQSO(-1); + connect(txMsgButtonGroup,SIGNAL(buttonClicked(int)),SLOT(set_ntx(int))); + connect (ui->decodedTextBrowser, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnCall2); + connect (ui->decodedTextBrowser2, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnCall); + connect (ui->textBrowser4, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnFoxQueue); + connect (ui->decodedTextBrowser, &DisplayText::erased, this, &MainWindow::band_activity_cleared); + connect (ui->decodedTextBrowser2, &DisplayText::erased, this, &MainWindow::rx_frequency_activity_cleared); + + // initialize decoded text font and hook up font change signals + // defer initialization until after construction otherwise menu + // fonts do not get set + QTimer::singleShot (0, this, SLOT (initialize_fonts ())); + connect (&m_config, &Configuration::text_font_changed, [this] (QFont const& font) { + set_application_font (font); + }); + connect (&m_config, &Configuration::decoded_text_font_changed, [this] (QFont const& font) { + setDecodedTextFont (font); + }); + + setWindowTitle (program_title ()); + + connect(&proc_jt9, &QProcess::readyReadStandardOutput, this, &MainWindow::readFromStdout); +#if QT_VERSION < QT_VERSION_CHECK (5, 6, 0) + connect(&proc_jt9, static_cast (&QProcess::error), + [this] (QProcess::ProcessError error) { + subProcessError (&proc_jt9, error); + }); +#else + connect(&proc_jt9, &QProcess::errorOccurred, [this] (QProcess::ProcessError error) { + subProcessError (&proc_jt9, error); + }); +#endif + connect(&proc_jt9, static_cast (&QProcess::finished), + [this] (int exitCode, QProcess::ExitStatus status) { + if (subProcessFailed (&proc_jt9, exitCode, status)) + { + m_valid = false; // ensures exit if still + // constructing + QTimer::singleShot (0, this, SLOT (close ())); + } + }); + connect(&p1, &QProcess::started, [this] () { + showStatusMessage (QString {"Started: %1 \"%2\""}.arg (p1.program ()).arg (p1.arguments ().join ("\" \""))); + }); + connect(&p1, &QProcess::readyReadStandardOutput, this, &MainWindow::p1ReadFromStdout); +#if QT_VERSION < QT_VERSION_CHECK (5, 6, 0) + connect(&p1, static_cast (&QProcess::error), + [this] (QProcess::ProcessError error) { + subProcessError (&p1, error); + }); +#else + connect(&p1, &QProcess::errorOccurred, [this] (QProcess::ProcessError error) { + subProcessError (&p1, error); + }); +#endif + connect(&p1, static_cast (&QProcess::finished), + [this] (int exitCode, QProcess::ExitStatus status) { + if (subProcessFailed (&p1, exitCode, status)) + { + m_valid = false; // ensures exit if still + // constructing + QTimer::singleShot (0, this, SLOT (close ())); + } + }); + +#if QT_VERSION < QT_VERSION_CHECK (5, 6, 0) + connect(&p3, static_cast (&QProcess::error), + [this] (QProcess::ProcessError error) { +#else + connect(&p3, &QProcess::errorOccurred, [this] (QProcess::ProcessError error) { +#endif +#if !defined(Q_OS_WIN) + if (QProcess::FailedToStart != error) +#else + if (QProcess::Crashed != error) +#endif + { + subProcessError (&p3, error); + } + }); + connect(&p3, &QProcess::started, [this] () { + showStatusMessage (QString {"Started: %1 \"%2\""}.arg (p3.program ()).arg (p3.arguments ().join ("\" \""))); + }); + connect(&p3, static_cast (&QProcess::finished), + [this] (int exitCode, QProcess::ExitStatus status) { +#if defined(Q_OS_WIN) + // We forgo detecting user_hardware failures with exit + // code 1 on Windows. This is because we use CMD.EXE to + // run the executable. CMD.EXE returns exit code 1 when it + // can't find the target executable. + if (exitCode != 1) // CMD.EXE couldn't find file to execute +#else + // We forgo detecting user_hardware failures with exit + // code 127 non-Windows. This is because we use /bin/sh to + // run the executable. /bin/sh returns exit code 127 when it + // can't find the target executable. + if (exitCode != 127) // /bin/sh couldn't find file to execute +#endif + { + subProcessFailed (&p3, exitCode, status); + } + }); + + // hook up save WAV file exit handling + connect (&m_saveWAVWatcher, &QFutureWatcher::finished, [this] { + // extract the promise from the future + auto const& result = m_saveWAVWatcher.future ().result (); + if (!result.isEmpty ()) // error + { + MessageBox::critical_message (this, tr("Error Writing WAV File"), result); + } + }); + + // Hook up working frequencies. + ui->bandComboBox->setModel (m_config.frequencies ()); + ui->bandComboBox->setModelColumn (FrequencyList_v2::frequency_mhz_column); + + // Enable live band combo box entry validation and action. + auto band_validator = new LiveFrequencyValidator {ui->bandComboBox + , m_config.bands () + , m_config.frequencies () + , &m_freqNominal + , this}; + ui->bandComboBox->setValidator (band_validator); + + // Hook up signals. + connect (band_validator, &LiveFrequencyValidator::valid, this, &MainWindow::band_changed); + connect (ui->bandComboBox->lineEdit (), &QLineEdit::textEdited, [this] (QString const&) {m_bandEdited = true;}); + + // hook up configuration signals + connect (&m_config, &Configuration::transceiver_update, this, &MainWindow::handle_transceiver_update); + connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure); + connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server); + connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port); + connect (&m_config, &Configuration::udp_TTL_changed, m_messageClient, &MessageClient::set_TTL); + connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable); + connect (&m_config, &Configuration::enumerating_audio_devices, [this] () { + showStatusMessage (tr ("Enumerating audio devices")); + }); + + // set up configurations menu + connect (m_multi_settings, &MultiSettings::configurationNameChanged, [this] (QString const& name) { + if ("Default" != name) { + config_label.setText (name); + config_label.show (); + } + else { + config_label.hide (); + } + statusUpdate (); + }); + m_multi_settings->create_menu_actions (this, ui->menuConfig); + m_configurations_button = m_rigErrorMessageBox.addButton (tr ("Configurations...") + , QMessageBox::ActionRole); + + // set up message text validators + ui->tx1->setValidator (new QRegExpValidator {message_alphabet, this}); + ui->tx2->setValidator (new QRegExpValidator {message_alphabet, this}); + ui->tx3->setValidator (new QRegExpValidator {message_alphabet, this}); + ui->tx4->setValidator (new QRegExpValidator {message_alphabet, this}); + ui->tx5->setValidator (new QRegExpValidator {message_alphabet, this}); + ui->tx6->setValidator (new QRegExpValidator {message_alphabet, this}); + + // Free text macros model to widget hook up. + ui->tx5->setModel (m_config.macros ()); + connect (ui->tx5->lineEdit(), &QLineEdit::editingFinished, + [this] () {on_tx5_currentTextChanged (ui->tx5->lineEdit()->text());}); + connect(&m_guiTimer, &QTimer::timeout, this, &MainWindow::guiUpdate); + m_guiTimer.start(100); //### Don't change the 100 ms! ### + + ptt0Timer.setSingleShot(true); + connect(&ptt0Timer, &QTimer::timeout, this, &MainWindow::stopTx2); + + ptt1Timer.setSingleShot(true); + connect(&ptt1Timer, &QTimer::timeout, this, &MainWindow::startTx2); + + p1Timer.setSingleShot(true); + connect(&p1Timer, &QTimer::timeout, this, &MainWindow::startP1); + + logQSOTimer.setSingleShot(true); + connect(&logQSOTimer, &QTimer::timeout, this, &MainWindow::on_logQSOButton_clicked); + + tuneButtonTimer.setSingleShot(true); + connect(&tuneButtonTimer, &QTimer::timeout, this, &MainWindow::end_tuning); + + tuneATU_Timer.setSingleShot(true); + connect(&tuneATU_Timer, &QTimer::timeout, this, &MainWindow::stopTuneATU); + + killFileTimer.setSingleShot(true); + connect(&killFileTimer, &QTimer::timeout, this, &MainWindow::killFile); + + uploadTimer.setSingleShot(true); + connect(&uploadTimer, &QTimer::timeout, [this] () {uploadWSPRSpots ();}); + + TxAgainTimer.setSingleShot(true); + connect(&TxAgainTimer, SIGNAL(timeout()), this, SLOT(TxAgain())); + + connect(m_wideGraph.data (), SIGNAL(setFreq3(int,int)),this, + SLOT(setFreq4(int,int))); + + decodeBusy(false); + + m_msg[0][0]=0; + ui->labDXped->setVisible(false); + ui->labDXped->setStyleSheet("QLabel {background-color: red; color: white;}"); + + char const * const power[] = {"1 mW","2 mW","5 mW","10 mW","20 mW","50 mW","100 mW","200 mW","500 mW", + "1 W","2 W","5 W","10 W","20 W","50 W","100 W","200 W","500 W","1 kW"}; + for(auto i = 0u; i < sizeof power / sizeof power[0]; ++i) { //Initialize dBm values + auto dBm = int ((10. * i / 3.) + .5); + ui->TxPowerComboBox->addItem (QString {"%1 dBm %2"}.arg (dBm).arg (power[i]), dBm); + } + + m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc(); + m_dateTimeSentTx3=QDateTime::currentDateTimeUtc(); + + ui->labAz->setStyleSheet("border: 0px;"); + ui->labAz->setText(""); + auto t = "UTC dB DT Freq " + tr ("Message"); + ui->lh_decodes_headings_label->setText(t); + ui->rh_decodes_headings_label->setText(t); + readSettings(); //Restore user's setup parameters + m_audioThread.start (m_audioThreadPriority); + +#ifdef WIN32 + if (!m_multiple) + { + while(true) + { + int iret=killbyname("jt9.exe"); + if(iret == 603) break; + if(iret != 0) + MessageBox::warning_message (this, tr ("Error Killing jt9.exe Process") + , tr ("KillByName return code: %1") + .arg (iret)); + } + } +#endif + + { + //delete any .quit file that might have been left lying around + //since its presence will cause jt9 to exit a soon as we start it + //and decodes will hang + QFile quitFile {m_config.temp_dir ().absoluteFilePath (".quit")}; + while (quitFile.exists ()) + { + if (!quitFile.remove ()) + { + MessageBox::query_message (this, tr ("Error removing \"%1\"").arg (quitFile.fileName ()) + , tr ("Click OK to retry")); + } + } + } + + to_jt9(0,0,0); //initialize IPC variables + + QStringList jt9_args { + "-s", QApplication::applicationName () // shared memory key, + // includes rig +#ifdef NDEBUG + , "-w", "1" //FFTW patience - release +#else + , "-w", "1" //FFTW patience - debug builds for speed +#endif + // The number of threads for FFTW specified here is chosen as + // three because that gives the best throughput of the large + // FFTs used in jt9. The count is the minimum of (the number + // available CPU threads less one) and three. This ensures that + // there is always at least one free CPU thread to run the other + // mode decoder in parallel. + , "-m", QString::number (qMin (qMax (QThread::idealThreadCount () - 1, 1), 3)) //FFTW threads + + , "-e", QDir::toNativeSeparators (m_appDir) + , "-a", QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath ()) + , "-t", QDir::toNativeSeparators (m_config.temp_dir ().absolutePath ()) + }; + QProcessEnvironment new_env {m_env}; + new_env.insert ("OMP_STACKSIZE", "4M"); + proc_jt9.setProcessEnvironment (new_env); + proc_jt9.start(QDir::toNativeSeparators (m_appDir) + QDir::separator () + + "jt9", jt9_args, QIODevice::ReadWrite | QIODevice::Unbuffered); + + auto fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("wsjtx_wisdom.dat"))}; + fftwf_import_wisdom_from_filename (fname.toLocal8Bit ()); + + m_ntx = 6; + ui->txrb6->setChecked(true); + + connect (&m_wav_future_watcher, &QFutureWatcher::finished, this, &MainWindow::diskDat); + + connect(&watcher3, SIGNAL(finished()),this,SLOT(fast_decode_done())); + if (!m_config.audio_input_device ().isNull ()) + { + Q_EMIT startAudioInputStream (m_config.audio_input_device () + , m_rx_audio_buffer_frames + , m_detector, m_downSampleFactor, m_config.audio_input_channel ()); + } + if (!m_config.audio_output_device ().isNull ()) + { + Q_EMIT initializeAudioOutputStream (m_config.audio_output_device () + , AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2 + , m_tx_audio_buffer_frames); + } + Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT); + + enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook + + // this must be done before initializing the mode as some modes need + // to turn off split on the rig e.g. WSPR + m_config.transceiver_online (); + bool vhf {m_config.enable_VHF_features ()}; + + ui->txFirstCheckBox->setChecked(m_txFirst); + morse_(const_cast (m_config.my_callsign ().toLatin1().constData()), + const_cast (icw), &m_ncw, m_config.my_callsign ().length()); + on_actionWide_Waterfall_triggered(); + ui->cbShMsgs->setChecked(m_bShMsgs); + ui->cbSWL->setChecked(m_bSWL); + if(m_bFast9) m_bFastMode=true; + ui->cbFast9->setChecked(m_bFast9 or m_bFastMode); + + set_mode (m_mode); + if(m_mode=="Echo") monitor(false); //Don't auto-start Monitor in Echo mode. + ui->sbSubmode->setValue (vhf ? m_nSubMode : 0); //Submodes require VHF features + if(m_mode=="MSK144") { + Q_EMIT transmitFrequency (1000.0); + } else { + Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value() - m_XIT); + } + m_saveDecoded=ui->actionSave_decoded->isChecked(); + m_saveAll=ui->actionSave_all->isChecked(); + ui->TxPowerComboBox->setCurrentIndex(int(.3 * m_dBm + .2)); + ui->cbUploadWSPR_Spots->setChecked(m_uploadWSPRSpots); + if((m_ndepth&7)==1) ui->actionQuickDecode->setChecked(true); + if((m_ndepth&7)==2) ui->actionMediumDecode->setChecked(true); + if((m_ndepth&7)==3) ui->actionDeepestDecode->setChecked(true); + ui->actionInclude_averaging->setChecked(m_ndepth&16); + ui->actionInclude_correlation->setChecked(m_ndepth&32); + ui->actionEnable_AP_DXcall->setChecked(m_ndepth&64); + ui->actionAuto_Clear_Avg->setChecked(m_ndepth&128); + + m_UTCdisk=-1; + m_fCPUmskrtd=0.0; + m_bFastDone=false; + m_bAltV=false; + m_bNoMoreFiles=false; + m_bDoubleClicked=false; + m_bCallingCQ=false; + m_bCheckedContest=false; + m_bDisplayedOnce=false; + m_wait=0; + m_isort=-3; + m_max_dB=70; + m_CQtype="CQ"; + fixStop(); + VHF_features_enabled(m_config.enable_VHF_features()); + m_wideGraph->setVHF(m_config.enable_VHF_features()); + + connect( wsprNet, SIGNAL(uploadStatus(QString)), this, SLOT(uploadResponse(QString))); + + statusChanged(); + + m_fastGraph->setMode(m_mode); + m_wideGraph->setMode(m_mode); + + connect (&minuteTimer, &QTimer::timeout, this, &MainWindow::on_the_minute); + minuteTimer.setSingleShot (true); + minuteTimer.start (ms_minute_error () + 60 * 1000); + + connect (&splashTimer, &QTimer::timeout, this, &MainWindow::splash_done); + splashTimer.setSingleShot (true); + splashTimer.start (20 * 1000); + + if(QCoreApplication::applicationVersion().contains("-devel") or + QCoreApplication::applicationVersion().contains("-rc")) { + QTimer::singleShot (0, this, SLOT (not_GA_warning_message ())); + } + + ui->pbBestSP->setVisible(m_mode=="FT4"); + +// this must be the last statement of constructor + if (!m_valid) throw std::runtime_error {"Fatal initialization exception"}; +} + +void MainWindow::not_GA_warning_message () +{ + // MessageBox::critical_message (this, + // "This is a pre-release version of WSJT-X " + version (false) + " made\n" + // "available for testing purposes. By design it will\n" + // "be nonfunctional after Nov 30, 2021."); + // auto now = QDateTime::currentDateTimeUtc (); + // if (now >= QDateTime {{2021, 11, 30}, {23, 59, 59, 999}, Qt::UTC}) { + // Q_EMIT finished (); + // } +} + +void MainWindow::initialize_fonts () +{ + set_application_font (m_config.text_font ()); + setDecodedTextFont (m_config.decoded_text_font ()); +} + +void MainWindow::splash_done () +{ + m_splash && m_splash->close (); +} + +void MainWindow::on_the_minute () +{ + if (minuteTimer.isSingleShot ()) + { + minuteTimer.setSingleShot (false); + minuteTimer.start (60 * 1000); // run free + } + else + { + auto const& ms_error = ms_minute_error (); + if (qAbs (ms_error) > 1000) // keep drift within +-1s + { + minuteTimer.setSingleShot (true); + minuteTimer.start (ms_error + 60 * 1000); + } + } + + if (m_config.watchdog () && m_mode!="WSPR" && m_mode!="FST4W") { + if (m_idleMinutes < m_config.watchdog ()) ++m_idleMinutes; + update_watchdog_label (); + } else { + tx_watchdog (false); + } +} + +//--------------------------------------------------- MainWindow destructor +MainWindow::~MainWindow() +{ + m_astroWidget.reset (); + auto fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("wsjtx_wisdom.dat"))}; + fftwf_export_wisdom_to_filename (fname.toLocal8Bit ()); + m_audioThread.quit (); + m_audioThread.wait (); + remove_child_from_event_filter (this); +} + +//-------------------------------------------------------- writeSettings() +void MainWindow::writeSettings() +{ + m_settings->beginGroup("MainWindow"); + if (ui->actionSWL_Mode->isChecked ()) + { + m_settings->setValue ("SWLView", true); + m_settings->setValue ("ShowMenus", ui->cbMenus->isChecked ()); + m_settings->setValue ("geometry", geometries ()[0]); + m_settings->setValue ("SWLModeGeometry", saveGeometry ()); + m_settings->setValue ("geometryNoControls", geometries ()[2]); + } + else + { + if (ui->cbMenus->isChecked()) + { + m_settings->setValue ("SWLView", ui->actionSWL_Mode->isChecked ()); + m_settings->setValue ("ShowMenus", true); + m_settings->setValue ("geometry", saveGeometry ()); + m_settings->setValue ("SWLModeGeometry", geometries ()[1]); + m_settings->setValue ("geometryNoControls", geometries ()[2]); + } + else + { + m_settings->setValue ("SWLView", ui->actionSWL_Mode->isChecked ()); + m_settings->setValue ("ShowMenus", false); + m_settings->setValue ("geometry", geometries ()[0]); + m_settings->setValue ("SWLModeGeometry", geometries ()[1]); + m_settings->setValue ("geometryNoControls", saveGeometry ()); + } + } + m_settings->setValue ("state", saveState ()); + m_settings->setValue("MRUdir", m_path); + m_settings->setValue("TxFirst",m_txFirst); + m_settings->setValue("DXcall",ui->dxCallEntry->text()); + m_settings->setValue("DXgrid",ui->dxGridEntry->text()); + m_settings->setValue ("AstroDisplayed", m_astroWidget && m_astroWidget->isVisible()); + m_settings->setValue ("MsgAvgDisplayed", m_msgAvgWidget && m_msgAvgWidget->isVisible ()); + m_settings->setValue ("FoxLogDisplayed", m_foxLogWindow && m_foxLogWindow->isVisible ()); + m_settings->setValue ("ContestLogDisplayed", m_contestLogWindow && m_contestLogWindow->isVisible ()); + m_settings->setValue("CallFirst",ui->cbFirst->isChecked()); +//SP6XD: + m_settings->setValue("IgnoreRegex",ui->ignoreRegex->isChecked()); + m_settings->setValue("HoundSort",ui->comboBoxHoundSort->currentIndex()); + m_settings->setValue("FoxNlist",ui->sbNlist->value()); + m_settings->setValue("FoxNslots",ui->sbNslots->value()); + m_settings->setValue("FoxMaxDB_v2",ui->sbMax_dB->value()); // original key abandoned + m_settings->setValue ("SerialNumber",ui->sbSerialNumber->value ()); + m_settings->endGroup(); + + m_settings->beginGroup("Common"); + m_settings->setValue("Mode",m_mode); + m_settings->setValue("SaveNone",ui->actionNone->isChecked()); + m_settings->setValue("SaveDecoded",ui->actionSave_decoded->isChecked()); + m_settings->setValue("SaveAll",ui->actionSave_all->isChecked()); + m_settings->setValue("NDepth",m_ndepth); + m_settings->setValue("RxFreq",ui->RxFreqSpinBox->value()); + m_settings->setValue("TxFreq",ui->TxFreqSpinBox->value()); + m_settings->setValue("WSPRfreq",ui->WSPRfreqSpinBox->value()); + m_settings->setValue("FST4W_RxFreq",ui->sbFST4W_RxFreq->value()); + m_settings->setValue("FST4W_FTol",ui->sbFST4W_FTol->value()); + m_settings->setValue("FST4_FLow",ui->sbF_Low->value()); + m_settings->setValue("FST4_FHigh",ui->sbF_High->value()); + m_settings->setValue("SubMode",ui->sbSubmode->value()); + m_settings->setValue("DTtol",m_DTtol); + m_settings->setValue("Ftol", ui->sbFtol->value ()); + m_settings->setValue("MinSync",m_minSync); + m_settings->setValue ("AutoSeq", ui->cbAutoSeq->isChecked ()); + m_settings->setValue ("RxAll", ui->cbRxAll->isChecked ()); + m_settings->setValue("ShMsgs",m_bShMsgs); + m_settings->setValue("SWL",ui->cbSWL->isChecked()); + m_settings->setValue ("DialFreq", QVariant::fromValue(m_lastMonitoredFrequency)); + m_settings->setValue("OutAttenuation", ui->outAttenuation->value ()); + m_settings->setValue("NoSuffix",m_noSuffix); + m_settings->setValue("GUItab",ui->tabWidget->currentIndex()); + m_settings->setValue("OutBufSize",outBufSize); + m_settings->setValue ("HoldTxFreq", ui->cbHoldTxFreq->isChecked ()); + m_settings->setValue("PctTx", ui->sbTxPercent->value ()); + m_settings->setValue("RoundRobin",ui->RoundRobin->currentText()); + m_settings->setValue("dBm",m_dBm); + m_settings->setValue("RR73",m_send_RR73); + m_settings->setValue ("WSPRPreferType1", ui->WSPR_prefer_type_1_check_box->isChecked ()); + m_settings->setValue("UploadSpots",m_uploadWSPRSpots); + m_settings->setValue("NoOwnCall",ui->cbNoOwnCall->isChecked()); + m_settings->setValue ("BandHopping", ui->band_hopping_group_box->isChecked ()); + m_settings->setValue ("TRPeriod", ui->sbTR->value ()); + m_settings->setValue ("MaxDrift", ui->sbMaxDrift->value()); + m_settings->setValue ("TRPeriod_FST4W", ui->sbTR_FST4W->value ()); + m_settings->setValue("FastMode",m_bFastMode); + m_settings->setValue("Fast9",m_bFast9); + m_settings->setValue ("CQTxfreq", ui->sbCQTxFreq->value ()); + m_settings->setValue("pwrBandTxMemory",m_pwrBandTxMemory); + m_settings->setValue("pwrBandTuneMemory",m_pwrBandTuneMemory); + m_settings->setValue ("FT8AP", ui->actionEnable_AP_FT8->isChecked ()); + m_settings->setValue ("JT65AP", ui->actionEnable_AP_JT65->isChecked ()); + m_settings->setValue ("AutoClearAvg", ui->actionAuto_Clear_Avg->isChecked ()); + m_settings->setValue("SplitterState",ui->decodes_splitter->saveState()); + m_settings->setValue("Blanker",ui->sbNB->value()); + + { + QList coeffs; // suitable for QSettings + for (auto const& coeff : m_phaseEqCoefficients) + { + coeffs << coeff; + } + m_settings->setValue ("PhaseEqualizationCoefficients", QVariant {coeffs}); + } + m_settings->endGroup(); +} + +//---------------------------------------------------------- readSettings() +void MainWindow::readSettings() +{ + ui->cbAutoSeq->setVisible(false); + ui->cbFirst->setVisible(false); + m_settings->beginGroup("MainWindow"); + std::array the_geometries; + the_geometries[0] = m_settings->value ("geometry", saveGeometry ()).toByteArray (); + the_geometries[1] = m_settings->value ("SWLModeGeometry", saveGeometry ()).toByteArray (); + the_geometries[2] = m_settings->value ("geometryNoControls", saveGeometry ()).toByteArray (); + auto SWL_mode = m_settings->value ("SWLView", false).toBool (); + auto show_menus = m_settings->value ("ShowMenus", true).toBool (); + ui->actionSWL_Mode->setChecked (SWL_mode); + ui->cbMenus->setChecked (show_menus); + auto current_view_mode = SWL_mode ? 1 : show_menus ? 0 : 2; + change_layout (current_view_mode); + geometries (current_view_mode, the_geometries); + restoreState (m_settings->value ("state", saveState ()).toByteArray ()); + ui->dxCallEntry->setText (m_settings->value ("DXcall", QString {}).toString ()); + ui->dxGridEntry->setText (m_settings->value ("DXgrid", QString {}).toString ()); + m_path = m_settings->value("MRUdir", m_config.save_directory ().absolutePath ()).toString (); + m_txFirst = m_settings->value("TxFirst",false).toBool(); + auto displayAstro = m_settings->value ("AstroDisplayed", false).toBool (); + auto displayMsgAvg = m_settings->value ("MsgAvgDisplayed", false).toBool (); + auto displayFoxLog = m_settings->value ("FoxLogDisplayed", false).toBool (); + auto displayContestLog = m_settings->value ("ContestLogDisplayed", false).toBool (); + ui->cbFirst->setChecked(m_settings->value("CallFirst",true).toBool()); +//SP6XD: + ui->ignoreRegex->setChecked(m_settings->value("IgnoreRegex",true).toBool()); + ui->comboBoxHoundSort->setCurrentIndex(m_settings->value("HoundSort",3).toInt()); + ui->sbNlist->setValue(m_settings->value("FoxNlist",12).toInt()); + m_Nslots=m_settings->value("FoxNslots",5).toInt(); + ui->sbNslots->setValue(m_Nslots); + ui->sbMax_dB->setValue(m_settings->value("FoxMaxDB_v2",70).toInt()); + ui->sbSerialNumber->setValue (m_settings->value ("SerialNumber", 1).toInt ()); + m_settings->endGroup(); + + // do this outside of settings group because it uses groups internally + ui->actionAstronomical_data->setChecked (displayAstro); + + m_settings->beginGroup("Common"); + m_mode=m_settings->value("Mode","JT9").toString(); + ui->actionNone->setChecked(m_settings->value("SaveNone",true).toBool()); + ui->actionSave_decoded->setChecked(m_settings->value("SaveDecoded",false).toBool()); + ui->actionSave_all->setChecked(m_settings->value("SaveAll",false).toBool()); + ui->RxFreqSpinBox->setValue(0); // ensure a change is signaled + ui->RxFreqSpinBox->setValue(m_settings->value("RxFreq",1500).toInt()); + ui->sbFST4W_RxFreq->setValue(0); + ui->sbFST4W_RxFreq->setValue(m_settings->value("FST4W_RxFreq",1500).toInt()); + ui->sbF_Low->setValue(m_settings->value("FST4_FLow",600).toInt()); + ui->sbF_High->setValue(m_settings->value("FST4_FHigh",1400).toInt()); + m_nSubMode=m_settings->value("SubMode",0).toInt(); + ui->sbSubmode->setValue(m_nSubMode); + ui->sbFtol->setValue (m_settings->value("Ftol", 50).toInt()); + ui->sbFST4W_FTol->setValue(m_settings->value("FST4W_FTol",100).toInt()); + m_minSync=m_settings->value("MinSync",0).toInt(); + ui->syncSpinBox->setValue(m_minSync); + ui->cbAutoSeq->setChecked (m_settings->value ("AutoSeq", false).toBool()); + ui->cbRxAll->setChecked (m_settings->value ("RxAll", false).toBool()); + m_bShMsgs=m_settings->value("ShMsgs",false).toBool(); + m_bSWL=m_settings->value("SWL",false).toBool(); + m_bFast9=m_settings->value("Fast9",false).toBool(); + m_bFastMode=m_settings->value("FastMode",false).toBool(); + ui->sbTR->setValue (m_settings->value ("TRPeriod", 15).toInt()); + ui->sbMaxDrift->setValue (m_settings->value ("MaxDrift",0).toInt()); + ui->sbTR_FST4W->setValue (m_settings->value ("TRPeriod_FST4W", 15).toInt()); + m_lastMonitoredFrequency = m_settings->value ("DialFreq", + QVariant::fromValue (default_frequency)).value (); + ui->WSPRfreqSpinBox->setValue(0); // ensure a change is signaled + ui->WSPRfreqSpinBox->setValue(m_settings->value("WSPRfreq",1500).toInt()); + ui->TxFreqSpinBox->setValue(0); // ensure a change is signaled + ui->TxFreqSpinBox->setValue(m_settings->value("TxFreq",1500).toInt()); + m_ndepth=m_settings->value("NDepth",3).toInt(); + ui->sbTxPercent->setValue (m_settings->value ("PctTx", 20).toInt ()); + on_sbTxPercent_valueChanged (ui->sbTxPercent->value ()); + ui->RoundRobin->setCurrentText(m_settings->value("RoundRobin",tr("Random")).toString()); + m_dBm=m_settings->value("dBm",37).toInt(); + m_send_RR73=m_settings->value("RR73",false).toBool(); + if(m_send_RR73) { + m_send_RR73=false; + on_txrb4_doubleClicked(); + } + ui->WSPR_prefer_type_1_check_box->setChecked (m_settings->value ("WSPRPreferType1", true).toBool ()); + m_uploadWSPRSpots=m_settings->value("UploadSpots",false).toBool(); + ui->cbNoOwnCall->setChecked(m_settings->value("NoOwnCall",false).toBool()); + ui->band_hopping_group_box->setChecked (m_settings->value ("BandHopping", false).toBool()); + // setup initial value of tx attenuator + m_block_pwr_tooltip = true; + ui->outAttenuation->setValue (m_settings->value ("OutAttenuation", 0).toInt ()); + m_block_pwr_tooltip = false; + ui->sbCQTxFreq->setValue (m_settings->value ("CQTxFreq", 260).toInt()); + m_noSuffix=m_settings->value("NoSuffix",false).toBool(); + int n=m_settings->value("GUItab",0).toInt(); + ui->tabWidget->setCurrentIndex(n); + outBufSize=m_settings->value("OutBufSize",4096).toInt(); + ui->cbHoldTxFreq->setChecked (m_settings->value ("HoldTxFreq", false).toBool ()); + m_pwrBandTxMemory=m_settings->value("pwrBandTxMemory").toHash(); + m_pwrBandTuneMemory=m_settings->value("pwrBandTuneMemory").toHash(); + ui->actionEnable_AP_FT8->setChecked (m_settings->value ("FT8AP", false).toBool()); + ui->actionEnable_AP_JT65->setChecked (m_settings->value ("JT65AP", false).toBool()); + ui->actionAuto_Clear_Avg->setChecked (m_settings->value ("AutoClearAvg", false).toBool()); + ui->decodes_splitter->restoreState(m_settings->value("SplitterState").toByteArray()); + ui->sbNB->setValue(m_settings->value("Blanker",0).toInt()); + { + auto const& coeffs = m_settings->value ("PhaseEqualizationCoefficients" + , QList {0., 0., 0., 0., 0.}).toList (); + m_phaseEqCoefficients.clear (); + for (auto const& coeff : coeffs) + { + m_phaseEqCoefficients.append (coeff.value ()); + } + } + m_settings->endGroup(); + + // use these initialisation settings to tune the audio o/p buffer + // size and audio thread priority + m_settings->beginGroup ("Tune"); + m_audioThreadPriority = static_cast (m_settings->value ("Audio/ThreadPriority", QThread::HighPriority).toInt () % 8); + m_settings->endGroup (); + + checkMSK144ContestType(); + if(displayMsgAvg) on_actionMessage_averaging_triggered(); + if (displayFoxLog) on_fox_log_action_triggered (); + if (displayContestLog) on_contest_log_action_triggered (); +} + +void MainWindow::checkMSK144ContestType() +{ + if(SpecOp::NONE != m_config.special_op_id()) + { + if(m_mode=="MSK144" && SpecOp::EU_VHF < m_config.special_op_id()) + { + MessageBox::warning_message (this, tr ("Improper mode"), + "Mode will be changed to FT8. MSK144 not available if Field Day, WW Digi, RTTY or Fox/Hound is selected."); + on_actionFT8_triggered(); + } + } +} + +void MainWindow::set_application_font (QFont const& font) +{ + qApp->setFont (font); + // set font in the application style sheet as well in case it has + // been modified in the style sheet which has priority + QString ss; + if (qApp->styleSheet ().size ()) + { + auto sheet = qApp->styleSheet (); + sheet.remove ("file:///"); + QFile sf {sheet}; + if (sf.open (QFile::ReadOnly | QFile::Text)) + { + ss = sf.readAll () + ss; + } + } + qApp->setStyleSheet (ss + "* {" + font_as_stylesheet (font) + '}'); + for (auto& widget : qApp->topLevelWidgets ()) + { + widget->updateGeometry (); + } +} + +void MainWindow::setDecodedTextFont (QFont const& font) +{ + ui->decodedTextBrowser->setContentFont (font); + ui->decodedTextBrowser2->setContentFont (font); + ui->textBrowser4->setContentFont(font); + ui->textBrowser4->displayFoxToBeCalled(" "); + ui->textBrowser4->setText(""); + auto style_sheet = "QLabel {" + font_as_stylesheet (font) + '}'; + ui->lh_decodes_headings_label->setStyleSheet (ui->lh_decodes_headings_label->styleSheet () + style_sheet); + ui->rh_decodes_headings_label->setStyleSheet (ui->rh_decodes_headings_label->styleSheet () + style_sheet); + if (m_msgAvgWidget) { + m_msgAvgWidget->changeFont (font); + } + if (m_foxLogWindow) { + m_foxLogWindow->set_log_view_font (font); + } + if (m_contestLogWindow) { + m_contestLogWindow->set_log_view_font (font); + } + updateGeometry (); +} + +void MainWindow::fixStop() +{ + m_hsymStop=179; + if(m_mode=="WSPR") { + m_hsymStop=396; + } else if(m_mode=="Echo") { + m_hsymStop=9; + } else if (m_mode=="JT4"){ + m_hsymStop=176; + if(m_config.decode_at_52s()) m_hsymStop=179; + } else if (m_mode=="JT9"){ + m_hsymStop=173; + if(m_config.decode_at_52s()) m_hsymStop=179; + } else if (m_mode=="JT65"){ + m_hsymStop=174; + if(m_config.decode_at_52s()) m_hsymStop=179; + } else if (m_mode=="Q65"){ + m_hsymStop=48; // 13.8 s + if(m_TRperiod==30) { + m_hsymStop=96; // 27.6 s + if(m_config.decode_at_52s()) m_hsymStop=100; // 28.8 s + } + if(m_TRperiod==60) m_hsymStop=196; // 56.4 s + if(m_TRperiod==120) m_hsymStop=408; // 117.5 s + if(m_TRperiod==300) m_hsymStop=1030; // 296.6 s + } else if (m_mode=="FreqCal"){ + m_hsymStop=((int(m_TRperiod/0.288))/8)*8; + } else if (m_mode=="FT8") { + m_hsymStop=50; + } else if (m_mode=="FT4") { + m_hsymStop=21; + } else if(m_mode=="FST4" or m_mode=="FST4W") { + int stop[] = {39,85,187,387,1003,3107,6232}; + int stop_EME[] = {48,95,197,396,1012,3107,6232}; + int i=0; + if(m_TRperiod==30) i=1; + if(m_TRperiod==60) i=2; + if(m_TRperiod==120) i=3; + if(m_TRperiod==300) i=4; + if(m_TRperiod==900) i=5; + if(m_TRperiod==1800) i=6; + if(m_config.decode_at_52s()) { + m_hsymStop=stop_EME[i]; + } else { + m_hsymStop=stop[i]; + } + } +} + +//-------------------------------------------------------------- dataSink() +void MainWindow::dataSink(qint64 frames) +{ + static float s[NSMAX]; + char line[80]; + int k(frames); + auto fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("refspec.dat")).toLocal8Bit ()}; + + if(m_diskData) { + dec_data.params.ndiskdat=1; + } else { + dec_data.params.ndiskdat=0; + m_wideGraph->setDiskUTC(-1); + } + + m_bUseRef=m_wideGraph->useRef(); + if(!m_diskData) { + refspectrum_(&dec_data.d2[k-m_nsps/2],&m_bClearRefSpec,&m_bRefSpec, + &m_bUseRef, fname.constData (), fname.size ()); + } + m_bClearRefSpec=false; + + if(m_mode=="MSK144" or m_bFast9) { + fastSink(frames); + if(m_bFastMode) return; + } + +// Get power, spectrum, and ihsym + dec_data.params.nfa=m_wideGraph->nStartFreq(); + dec_data.params.nfb=m_wideGraph->Fmax(); + if(m_mode=="FST4") { + dec_data.params.nfa=ui->sbF_Low->value(); + dec_data.params.nfb=ui->sbF_High->value(); + } + int nsps=m_nsps; + if(m_bFastMode) nsps=6912; + int nsmo=m_wideGraph->smoothYellow()-1; + bool bLowSidelobes=m_config.lowSidelobes(); + int npct=0; + if(m_mode.startsWith("FST4")) npct=ui->sbNB->value(); + symspec_(&dec_data,&k,&m_TRperiod,&nsps,&m_inGain,&bLowSidelobes,&nsmo,&m_px,s, + &m_df3,&m_ihsym,&m_npts8,&m_pxmax,&npct); + if(m_mode=="WSPR" or m_mode=="FST4W") wspr_downsample_(dec_data.d2,&k); + if(m_ihsym <=0) return; + if(ui) ui->signal_meter_widget->setValue(m_px,m_pxmax); // Update thermometer + if(m_monitoring || m_diskData) { + m_wideGraph->dataSink2(s,m_df3,m_ihsym,m_diskData); + } + if(m_mode=="MSK144") return; + + fixStop(); + if (m_mode == "FreqCal" + // only calculate after 1st chunk, also skip chunk where rig + // changed frequency + && !(m_ihsym % 8) && m_ihsym > 8 && m_ihsym <= m_hsymStop) { + int RxFreq=ui->RxFreqSpinBox->value (); + int nkhz=(m_freqNominal+RxFreq)/1000; + int ftol = ui->sbFtol->value (); + freqcal_(&dec_data.d2[0],&k,&nkhz,&RxFreq,&ftol,&line[0],80); + QString t=QString::fromLatin1(line); + DecodedText decodedtext {t}; + ui->decodedTextBrowser->displayDecodedText (decodedtext, m_config.my_callsign (), m_mode, m_config.DXCC (), + m_logBook, m_currentBand, m_config.ppfx ()); + if (ui->measure_check_box->isChecked ()) { + // Append results text to file "fmt.all". + QFile f {m_config.writeable_data_dir ().absoluteFilePath ("fmt.all")}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QTextStream out(&f); + out << t +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; + f.close(); + } else { + MessageBox::warning_message (this, tr ("File Open Error") + , tr ("Cannot open \"%1\" for append: %2") + .arg (f.fileName ()).arg (f.errorString ())); + } + } + if(m_ihsym==m_hsymStop && ui->actionFrequency_calibration->isChecked()) { + freqCalStep(); + } + } + + if(m_ihsym==3*m_hsymStop/4) { + m_dialFreqRxWSPR=m_freqNominal; + } + + if(m_mode=="FT8") { + to_jt9(m_ihsym,-1,-1); //Allow jt9 to bail out early, if necessary + if(m_ihsym==40 and m_decoderBusy) { + qDebug() << "Clearing hung decoder status"; + decodeDone(); //Clear a hung decoder status + } + } + + bool bCallDecoder=false; + if(m_ihsym==m_hsymStop) bCallDecoder=true; + if(m_mode=="FT8" and !m_diskData) { + if(m_ihsym==m_earlyDecode) bCallDecoder=true; + if(m_ihsym==m_earlyDecode2) bCallDecoder=true; + } + if(bCallDecoder) { + if(m_mode=="Echo") { + float snr=0; + int nfrit=0; + int nqual=0; + float f1=1500.0; + float xlevel=0.0; + float sigdb=0.0; + float dfreq=0.0; + float width=0.0; + echocom_.nclearave=m_nclearave; + int nDop=0; + avecho_(dec_data.d2,&nDop,&nfrit,&nqual,&f1,&xlevel,&sigdb, + &snr,&dfreq,&width); + QString t; + t = t.asprintf("%3d %7.1f %7.1f %7.1f %7.1f %3d",echocom_.nsum,xlevel,sigdb, + dfreq,width,nqual); + t=QDateTime::currentDateTimeUtc().toString("hh:mm:ss ") + t; + if (ui) ui->decodedTextBrowser->appendText(t); + if(m_echoGraph->isVisible()) m_echoGraph->plotSpec(); + m_nclearave=0; +//Don't restart Monitor after an Echo transmission + if(m_bEchoTxed and !m_auto) { + monitor(false); + m_bEchoTxed=false; + } + return; + } + if(m_mode=="FreqCal") { + return; + } + if( m_dialFreqRxWSPR==0) m_dialFreqRxWSPR=m_freqNominal; + m_dataAvailable=true; + dec_data.params.npts8=(m_ihsym*m_nsps)/16; + dec_data.params.newdat=1; + dec_data.params.nagain=0; + dec_data.params.nzhsym=m_hsymStop; + if(m_mode=="FT8" and m_ihsym==m_earlyDecode and !m_diskData) dec_data.params.nzhsym=m_earlyDecode; + if(m_mode=="FT8" and m_ihsym==m_earlyDecode2 and !m_diskData) dec_data.params.nzhsym=m_earlyDecode2; + QDateTime now {QDateTime::currentDateTimeUtc ()}; + m_dateTime = now.toString ("yyyy-MMM-dd hh:mm"); + if(m_mode!="WSPR") decode(); //Start decoder + + if(m_mode=="FT8" and !m_diskData and (m_ihsym==m_earlyDecode or m_ihsym==m_earlyDecode2)) return; + if (!m_diskData) + { + Q_EMIT reset_audio_input_stream (true); // reports dropped samples + } + if(!m_diskData and (m_saveAll or m_saveDecoded or m_mode=="WSPR")) { + //Always save unless "Save None"; may delete later + if(m_TRperiod < 60) { + int n=fmod(double(now.time().second()),m_TRperiod); + if(n<(m_TRperiod/2)) n=n+m_TRperiod; + auto const& period_start=now.addSecs(-n); + m_fnameWE=m_config.save_directory().absoluteFilePath (period_start.toString("yyMMdd_hhmmss")); + } else { + auto const& period_start = now.addSecs (-(now.time ().minute () % (int(m_TRperiod) / 60)) * 60); + m_fnameWE=m_config.save_directory ().absoluteFilePath (period_start.toString ("yyMMdd_hhmm")); + } + int samples=m_TRperiod*12000; + if(m_mode=="FT4") samples=21*3456; + + // the following is potential a threading hazard - not a good + // idea to pass pointer to be processed in another thread + m_saveWAVWatcher.setFuture (QtConcurrent::run (std::bind (&MainWindow::save_wave_file, + this, m_fnameWE, &dec_data.d2[0], samples, m_config.my_callsign(), + m_config.my_grid(), m_mode, m_nSubMode, m_freqNominal, m_hisCall, m_hisGrid))); + if (m_mode=="WSPR") { + auto c2name {(m_fnameWE + ".c2").toLocal8Bit ()}; + int nsec=120; + int nbfo=1500; + double f0m1500=m_freqNominal/1000000.0 + nbfo - 1500; + int err = savec2_(c2name.constData (),&nsec,&f0m1500, c2name.size ()); + if (err!=0) MessageBox::warning_message (this, tr ("Error saving c2 file"), c2name); + } + } + if(m_mode=="WSPR") { + QStringList t2; + QStringList depth_args; + t2 << "-f" << QString {"%1"}.arg (m_dialFreqRxWSPR / 1e6, 0, 'f', 6); + if((m_ndepth&7)==1) depth_args << "-qB"; //2 pass w subtract, no Block detection, no shift jittering + if((m_ndepth&7)==2) depth_args << "-C" << "500" << "-o" << "4"; //3 pass, subtract, Block detection, OSD + if((m_ndepth&7)==3) depth_args << "-C" << "500" << "-o" << "4" << "-d"; //3 pass, subtract, Block detect, OSD, more candidates + QStringList degrade; + degrade << "-d" << QString {"%1"}.arg (m_config.degrade(), 4, 'f', 1); + m_cmndP1.clear (); + if(m_diskData) { + m_cmndP1 << depth_args << "-a" + << QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath()) << m_path; + } else { + m_cmndP1 << depth_args << "-a" + << QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath()) + << t2 << m_fnameWE + ".wav"; + } + if (ui) ui->DecodeButton->setChecked (true); + p1Timer.start(1000); + m_decoderBusy = true; + statusUpdate (); + } + m_rxDone=true; + } +} + +void MainWindow::startP1() +{ + p1.start (QDir::toNativeSeparators (QDir {QApplication::applicationDirPath ()}.absoluteFilePath ("wsprd")), m_cmndP1); +} + +QString MainWindow::save_wave_file (QString const& name, short const * data, int samples, + QString const& my_callsign, QString const& my_grid, QString const& mode, qint32 sub_mode, + Frequency frequency, QString const& his_call, QString const& his_grid) const +{ + // + // This member function runs in a thread and should not access + // members that may be changed in the GUI thread or any other thread + // without suitable synchronization. + // + QAudioFormat format; + format.setCodec ("audio/pcm"); + format.setSampleRate (12000); + format.setChannelCount (1); + format.setSampleSize (16); + format.setSampleType (QAudioFormat::SignedInt); + auto source = QString {"%1; %2"}.arg (my_callsign).arg (my_grid); + auto comment = QString {"Mode=%1%2; Freq=%3%4"} + .arg (mode) + .arg (QString {(mode.contains ('J') && !mode.contains ('+')) + || mode.startsWith ("FST4") || mode.startsWith ('Q') + ? QString {"; Sub Mode="} + QString::number (int (samples / 12000)) + QChar {'A' + sub_mode} + : QString {}}) + .arg (Radio::frequency_MHz_string (frequency)) + .arg (QString {mode!="WSPR" ? QString {"; DXCall=%1; DXGrid=%2"} + .arg (his_call) + .arg (his_grid).toLocal8Bit () : ""}); + BWFFile::InfoDictionary list_info { + {{{'I','S','R','C'}}, source.toLocal8Bit ()}, + {{{'I','S','F','T'}}, program_title (revision ()).simplified ().toLocal8Bit ()}, + {{{'I','C','R','D'}}, QDateTime::currentDateTimeUtc () + .toString ("yyyy-MM-ddTHH:mm:ss.zzzZ").toLocal8Bit ()}, + {{{'I','C','M','T'}}, comment.toLocal8Bit ()}, + }; + auto file_name = name + ".wav"; + BWFFile wav {format, file_name, list_info}; + if (!wav.open (BWFFile::WriteOnly) + || 0 > wav.write (reinterpret_cast (data) + , sizeof (short) * samples)) + { + return file_name + ": " + wav.errorString (); + } + return QString {}; +} + +//-------------------------------------------------------------- fastSink() +void MainWindow::fastSink(qint64 frames) +{ + int k (frames); + bool decodeNow=false; + if(k < m_k0) { //New sequence ? + memcpy(fast_green2,fast_green,4*703); //Copy fast_green[] to fast_green2[] + memcpy(fast_s2,fast_s,4*703*64); //Copy fast_s[] into fast_s2[] + fast_jh2=fast_jh; + if(!m_diskData) memset(dec_data.d2,0,2*30*12000); //Zero the d2[] array + m_bFastDecodeCalled=false; + m_bDecoded=false; + } + + QDateTime tnow=QDateTime::currentDateTimeUtc(); + int ihr=tnow.toString("hh").toInt(); + int imin=tnow.toString("mm").toInt(); + int isec=tnow.toString("ss").toInt(); + isec=isec - fmod(double(isec),m_TRperiod); + int nutc0=10000*ihr + 100*imin + isec; + if(m_diskData) nutc0=m_UTCdisk; + char line[80]; + bool bmsk144=((m_mode=="MSK144") and (m_monitoring or m_diskData)); + line[0]=0; + + int RxFreq=ui->RxFreqSpinBox->value (); + int nTRpDepth=m_TRperiod + 1000*(m_ndepth & 3); + qint64 ms0 = QDateTime::currentMSecsSinceEpoch(); +// ::memcpy(dec_data.params.mycall, (m_baseCall+" ").toLatin1(),sizeof dec_data.params.mycall); + ::memcpy(dec_data.params.mycall,(m_config.my_callsign () + " ").toLatin1(),sizeof dec_data.params.mycall); + QString hisCall {ui->dxCallEntry->text ()}; + bool bshmsg=ui->cbShMsgs->isChecked(); + bool bswl=ui->cbSWL->isChecked(); +// ::memcpy(dec_data.params.hiscall,(Radio::base_callsign (hisCall) + " ").toLatin1 ().constData (), sizeof dec_data.params.hiscall); + ::memcpy(dec_data.params.hiscall,(hisCall + " ").toLatin1 ().constData (), sizeof dec_data.params.hiscall); + ::memcpy(dec_data.params.mygrid, (m_config.my_grid()+" ").toLatin1(), sizeof dec_data.params.mygrid); + auto data_dir {m_config.writeable_data_dir ().absolutePath ().toLocal8Bit ()}; + float pxmax = 0; + float rmsNoGain = 0; + int ftol = ui->sbFtol->value (); + hspec_(dec_data.d2,&k,&nutc0,&nTRpDepth,&RxFreq,&ftol,&bmsk144, + &m_bTrain,m_phaseEqCoefficients.constData(),&m_inGain,&dec_data.params.mycall[0], + &dec_data.params.hiscall[0],&bshmsg,&bswl, + data_dir.constData (),fast_green,fast_s,&fast_jh,&pxmax,&rmsNoGain,&line[0],12,12,data_dir.size (),80); + float px = fast_green[fast_jh]; + QString t; + t = t.asprintf(" Rx noise: %5.1f ",px); + ui->signal_meter_widget->setValue(rmsNoGain,pxmax); // Update thermometer + m_fastGraph->plotSpec(m_diskData,m_UTCdisk); + + + + if(bmsk144 and (line[0]!=0)) { + QString message {QString::fromLatin1 (line)}; + DecodedText decodedtext {message.replace (QChar::LineFeed, "")}; + ui->decodedTextBrowser->displayDecodedText (decodedtext, m_config.my_callsign (), m_mode, m_config.DXCC(), + m_logBook, m_currentBand, m_config.ppfx ()); + m_bDecoded=true; + auto_sequence (decodedtext, ui->sbFtol->value (), std::numeric_limits::max ()); + postDecode (true, decodedtext.string ()); +// writeAllTxt(message); + write_all("Rx",message); + bool stdMsg = decodedtext.report(m_baseCall, + Radio::base_callsign(ui->dxCallEntry->text()),m_rptRcvd); + if (stdMsg) pskPost (decodedtext); + } + + float fracTR=float(k)/(12000.0*m_TRperiod); + decodeNow=false; + if(fracTR>0.92) { + m_dataAvailable=true; + fast_decode_done(); + m_bFastDone=true; + } + + m_k0=k; + if(m_diskData and m_k0 >= dec_data.params.kin - 7 * 512) decodeNow=true; + if(!m_diskData and m_tRemaining<0.35 and !m_bFastDecodeCalled) decodeNow=true; + if(m_mode=="MSK144") decodeNow=false; + + if(decodeNow) { + m_dataAvailable=true; + m_t0=0.0; + m_t1=k/12000.0; + m_kdone=k; + dec_data.params.newdat=1; + if(!m_decoderBusy) { + m_bFastDecodeCalled=true; + decode(); + } + } + + if(decodeNow or m_bFastDone) { + if(!m_diskData and (m_saveAll or m_saveDecoded)) { + QDateTime now {QDateTime::currentDateTimeUtc()}; + int n=fmod(double(now.time().second()),m_TRperiod); + if(n<(m_TRperiod/2)) n=n+m_TRperiod; + auto const& period_start = now.addSecs (-n); + m_fnameWE = m_config.save_directory ().absoluteFilePath (period_start.toString ("yyMMdd_hhmmss")); + if(m_saveAll or m_bAltV or (m_bDecoded and m_saveDecoded) or (m_mode!="MSK144")) { + m_bAltV=false; + // the following is potential a threading hazard - not a good + // idea to pass pointer to be processed in another thread + m_saveWAVWatcher.setFuture (QtConcurrent::run (std::bind (&MainWindow::save_wave_file, + this, m_fnameWE, &dec_data.d2[0], int(m_TRperiod*12000.0), m_config.my_callsign(), + m_config.my_grid(), m_mode, m_nSubMode, m_freqNominal, m_hisCall, m_hisGrid))); + } + if(m_mode!="MSK144") { + killFileTimer.start (int(750.0*m_TRperiod)); //Kill 3/4 period from now + } + } + m_bFastDone=false; + } + float tsec=0.001*(QDateTime::currentMSecsSinceEpoch() - ms0); + m_fCPUmskrtd=0.9*m_fCPUmskrtd + 0.1*tsec; +} + +void MainWindow::showSoundInError(const QString& errorMsg) +{ + if (m_splash && m_splash->isVisible ()) m_splash->hide (); + MessageBox::critical_message (this, tr ("Error in Sound Input"), errorMsg); +} + +void MainWindow::showSoundOutError(const QString& errorMsg) +{ + if (m_splash && m_splash->isVisible ()) m_splash->hide (); + MessageBox::critical_message (this, tr ("Error in Sound Output"), errorMsg); +} + +void MainWindow::showStatusMessage(const QString& statusMsg) +{ + statusBar()->showMessage(statusMsg, 5000); +} + +void MainWindow::on_actionSettings_triggered() //Setup Dialog +{ + // things that might change that we need know about + auto callsign = m_config.my_callsign (); + auto my_grid = m_config.my_grid (); + SpecOp nContest0=m_config.special_op_id(); + auto psk_on = m_config.spot_to_psk_reporter (); + if (QDialog::Accepted == m_config.exec ()) { + checkMSK144ContestType(); + if (m_config.my_callsign () != callsign) { + m_baseCall = Radio::base_callsign (m_config.my_callsign ()); + ui->tx1->setEnabled (elide_tx1_not_allowed () || ui->tx1->isEnabled ()); + morse_(const_cast (m_config.my_callsign ().toLatin1().constData()), + const_cast (icw), &m_ncw, m_config.my_callsign ().length()); + } + if (m_config.my_callsign () != callsign || m_config.my_grid () != my_grid) { + statusUpdate (); + } + on_dxGridEntry_textChanged (m_hisGrid); // recalculate distances in case of units change + enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook + + pskSetLocal (); + // this will close the connection to PSKReporter if it has been + // disabled + if (psk_on && !m_config.spot_to_psk_reporter ()) + { + m_psk_Reporter.sendReport (true); + } + + if(m_config.restart_audio_input () && !m_config.audio_input_device ().isNull ()) { + Q_EMIT startAudioInputStream (m_config.audio_input_device () + , m_rx_audio_buffer_frames + , m_detector, m_downSampleFactor + , m_config.audio_input_channel ()); + } + + if(m_config.restart_audio_output () && !m_config.audio_output_device ().isNull ()) { + Q_EMIT initializeAudioOutputStream (m_config.audio_output_device () + , AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2 + , m_tx_audio_buffer_frames); + } + + displayDialFrequency (); + bool vhf {m_config.enable_VHF_features()}; + m_wideGraph->setVHF(vhf); + if (!vhf) ui->sbSubmode->setValue (0); + + setup_status_bar (vhf); + bool b = vhf && (m_mode=="JT4" or m_mode=="JT65" or + m_mode=="JT9" or m_mode=="MSK144" or m_mode=="Q65"); + if(b) VHF_features_enabled(b); + set_mode (m_mode); + if(b) VHF_features_enabled(b); + + m_config.transceiver_online (); + if(!m_bFastMode) setXIT (ui->TxFreqSpinBox->value ()); + if ((m_config.single_decode () && !m_mode.startsWith ("FST4")) || m_mode=="JT4") { + ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes")); + ui->rh_decodes_title_label->setText(tr ("Average Decodes")); + } + + update_watchdog_label (); + if(!m_splitMode) ui->cbCQTx->setChecked(false); + if(!m_config.enable_VHF_features()) { + ui->actionInclude_averaging->setVisible(false); + ui->actionInclude_correlation->setVisible (false); + ui->actionInclude_averaging->setChecked(false); + ui->actionInclude_correlation->setChecked(false); + ui->actionEnable_AP_JT65->setVisible(false); + ui->actionAuto_Clear_Avg->setVisible(false); + } + if(m_config.special_op_id()!=nContest0) { + ui->tx1->setEnabled(true); + ui->txb1->setEnabled(true); + } + chkFT4(); + if(SpecOp::EU_VHF==m_config.special_op_id() and m_config.my_grid().size()<6) { + MessageBox::information_message (this, + "EU VHF Contest messages require a 6-character locator."); + } + if((m_config.special_op_id()==SpecOp::FOX or m_config.special_op_id()==SpecOp::HOUND) and + m_mode!="FT8") { + MessageBox::information_message (this, + "Fox-and-Hound operation is available only in FT8 mode.\nGo back and change your selection."); + } + } +} + +void MainWindow::on_monitorButton_clicked (bool checked) +{ + if (!m_transmitting) { + auto prior = m_monitoring; + monitor (checked); + if (checked && !prior) { + if (m_config.monitor_last_used ()) { + // put rig back where it was when last in control + setRig (m_lastMonitoredFrequency); + setXIT (ui->TxFreqSpinBox->value ()); + } + // ensure FreqCal triggers + if(m_mode=="FST4W") { + on_sbFST4W_RxFreq_valueChanged(ui->sbFST4W_RxFreq->value()); + } else { + on_RxFreqSpinBox_valueChanged (ui->RxFreqSpinBox->value ()); + } + } + //Get Configuration in/out of strict split and mode checking + m_config.sync_transceiver (true, checked); + } else { + ui->monitorButton->setChecked (false); // disallow + } +} + +void MainWindow::monitor (bool state) +{ + ui->monitorButton->setChecked (state); + if (state) { + m_diskData = false; // no longer reading WAV files + if (!m_monitoring) Q_EMIT resumeAudioInputStream (); + } else { + Q_EMIT suspendAudioInputStream (); + } + m_monitoring = state; +} + +void MainWindow::on_actionAbout_triggered() //Display "About" +{ + CAboutDlg {this}.exec (); +} + +void MainWindow::on_autoButton_clicked (bool checked) +{ + m_auto = checked; + if (checked + && ui->cbFirst->isVisible () && ui->cbFirst->isChecked() + && CALLING == m_QSOProgress) { + m_bAutoReply = false; // ready for next + m_bCallingCQ = true; // allows tail-enders to be picked up + ui->cbFirst->setStyleSheet ("QCheckBox{color:red}"); + } else { + ui->cbFirst->setStyleSheet(""); + } + if (!checked) m_bCallingCQ = false; + statusUpdate (); + m_bEchoTxOK=false; + if(m_auto and (m_mode=="Echo")) { + m_nclearave=1; + echocom_.nsum=0; + } + m_tAutoOn=QDateTime::currentMSecsSinceEpoch()/1000; +} + +void MainWindow::on_sbTxPercent_valueChanged (int n) +{ + update_dynamic_property (ui->sbTxPercent, "notx", !n); +} + +void MainWindow::auto_tx_mode (bool state) +{ + ui->autoButton->setChecked (state); + on_autoButton_clicked (state); +} + +void MainWindow::keyPressEvent (QKeyEvent * e) +{ + + if(SpecOp::FOX == m_config.special_op_id()) { + switch (e->key()) { + case Qt::Key_Return: + doubleClickOnCall2(Qt::KeyboardModifier(Qt::ShiftModifier + Qt::ControlModifier + Qt::AltModifier)); + return; + case Qt::Key_Enter: + doubleClickOnCall2(Qt::KeyboardModifier(Qt::ShiftModifier + Qt::ControlModifier + Qt::AltModifier)); + return; + case Qt::Key_Backspace: + qDebug() << "Key Backspace"; + return; + } + QMainWindow::keyPressEvent (e); + } + + if(SpecOp::HOUND == m_config.special_op_id()) { + switch (e->key()) { + case Qt::Key_Return: + auto_tx_mode(true); + return; + case Qt::Key_Enter: + auto_tx_mode(true); + return; + } + QMainWindow::keyPressEvent (e); + } + + int n; + bool bAltF1F6=m_config.alternate_bindings(); + switch(e->key()) + { + case Qt::Key_B: + if(m_mode=="FT4" && e->modifiers() & Qt::AltModifier) { + on_pbBestSP_clicked(); + } + return; + case Qt::Key_C: + if(m_mode=="FT4" && e->modifiers() & Qt::AltModifier) { + bool b=ui->cbFirst->isChecked(); + ui->cbFirst->setChecked(!b); + } + return; + case Qt::Key_D: + if(m_mode != "WSPR" && e->modifiers() & Qt::ShiftModifier) { + if(!m_decoderBusy) { + dec_data.params.newdat=0; + dec_data.params.nagain=0; + decode(); + return; + } + } + break; + case Qt::Key_F1: + if(bAltF1F6) { + auto_tx_mode(true); + on_txb6_clicked(); + return; + } else { + on_actionOnline_User_Guide_triggered(); + return; + } + case Qt::Key_F2: + if(bAltF1F6) { + auto_tx_mode(true); + on_txb2_clicked(); + return; + } else { + on_actionSettings_triggered(); + return; + } + case Qt::Key_F3: + if(bAltF1F6) { + auto_tx_mode(true); + on_txb3_clicked(); + return; + } else { + on_actionKeyboard_shortcuts_triggered(); + return; + } + case Qt::Key_F4: + if(bAltF1F6) { + auto_tx_mode(true); + on_txb4_clicked(); + return; + } else { + clearDX (); + ui->dxCallEntry->setFocus(); + return; + } + case Qt::Key_F5: + if(bAltF1F6) { + auto_tx_mode(true); + on_txb5_clicked(); + return; + } else { + on_actionSpecial_mouse_commands_triggered(); + return; + } + case Qt::Key_F6: + if(bAltF1F6) { + bool b=ui->cbFirst->isChecked(); + ui->cbFirst->setChecked(!b); + } else { + if(e->modifiers() & Qt::ShiftModifier) { + on_actionDecode_remaining_files_in_directory_triggered(); + } else { + on_actionOpen_next_in_directory_triggered(); + } + } + return; + case Qt::Key_F11: + if((e->modifiers() & Qt::ControlModifier) and (e->modifiers() & Qt::ShiftModifier)) { + m_bandEdited = true; + band_changed(m_freqNominal-2000); + } else { + n=11; + if(e->modifiers() & Qt::ControlModifier) n+=100; + if(e->modifiers() & Qt::ShiftModifier) { + int offset=60; + if(m_mode=="FT4") offset=90; + ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()-offset); + } else{ + bumpFqso(n); + } + } + return; + case Qt::Key_F12: + if((e->modifiers() & Qt::ControlModifier) and (e->modifiers() & Qt::ShiftModifier)) { + m_bandEdited = true; + band_changed(m_freqNominal+2000); + } else { + n=12; + if(e->modifiers() & Qt::ControlModifier) n+=100; + if(e->modifiers() & Qt::ShiftModifier) { + int offset=60; + if(m_mode=="FT4") offset=90; + ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()+offset); + } else { + bumpFqso(n); + } + } + return; + case Qt::Key_Escape: + m_nextCall=""; + on_stopTxButton_clicked(); + abortQSO(); + return; + case Qt::Key_E: + if((e->modifiers() & Qt::ShiftModifier) and SpecOp::FOX > m_config.special_op_id()) { + ui->txFirstCheckBox->setChecked(false); + return; + } + else if((e->modifiers() & Qt::ControlModifier) and SpecOp::FOX > m_config.special_op_id()) { + ui->txFirstCheckBox->setChecked(true); + return; + } + break; + case Qt::Key_F: + if(e->modifiers() & Qt::ControlModifier) { + if(ui->tabWidget->currentIndex()==0) { + ui->tx5->clearEditText(); + ui->tx5->setFocus(); + } + return; + } + break; + case Qt::Key_G: + if(e->modifiers() & Qt::AltModifier) { + genStdMsgs (m_rpt, true); + return; + } + break; + case Qt::Key_H: + if(e->modifiers() & Qt::AltModifier) { + on_stopTxButton_clicked(); + return; + } + break; + case Qt::Key_L: + if(e->modifiers() & Qt::ControlModifier) { + lookup(); + genStdMsgs(m_rpt); + return; + } + break; + case Qt::Key_O: + if(e->modifiers() & Qt::ControlModifier) { + on_actionOpen_triggered(); + return; + } + else if(e->modifiers() & Qt::AltModifier) { + bool ok; + auto call = QInputDialog::getText (this, tr ("Change Operator"), tr ("New operator:"), + QLineEdit::Normal, m_config.opCall (), &ok); + if (ok) { + m_config.opCall (call); + } + return; + } + break; + case Qt::Key_R: + if(e->modifiers() & Qt::AltModifier) { + if(!m_send_RR73) on_txrb4_doubleClicked(); + return; + } + if(e->modifiers() & Qt::ControlModifier) { + if(m_send_RR73) on_txrb4_doubleClicked(); + return; + } + break; + case Qt::Key_X: + if(e->modifiers() & Qt::AltModifier) { +// qDebug() << "Alt-X" << m_mode << m_TRperiod << m_nsps << m_bFast9 +// << tx_duration(m_mode,m_TRperiod,m_nsps,m_bFast9); + return; + } + } + + QMainWindow::keyPressEvent (e); +} + +void MainWindow::bumpFqso(int n) //bumpFqso() +{ + int i; + bool ctrl = (n>=100); + n=n%100; + i=ui->RxFreqSpinBox->value(); + bool bTrackTx=ui->TxFreqSpinBox->value() == i; + if(n==11) i--; + if(n==12) i++; + if (ui->RxFreqSpinBox->isEnabled ()) { + ui->RxFreqSpinBox->setValue (i); + } + if(ctrl and m_mode=="WSPR") { + ui->WSPRfreqSpinBox->setValue(i); + } else { + if(ctrl and bTrackTx) { + ui->TxFreqSpinBox->setValue (i); + } + } +} + +void MainWindow::displayDialFrequency () +{ + Frequency dial_frequency {m_rigState.ptt () && m_rigState.split () ? + m_rigState.tx_frequency () : m_rigState.frequency ()}; + + // lookup band + auto const& band_name = m_config.bands ()->find (dial_frequency); + if (m_lastBand != band_name) + { + // only change this when necessary as we get called a lot and it + // would trash any user input to the band combo box line edit + ui->bandComboBox->setCurrentText (band_name.size () ? band_name : m_config.bands ()->oob ()); + m_wideGraph->setRxBand (band_name); + m_lastBand = band_name; + band_changed(dial_frequency); + } + + // search working frequencies for one we are within 10kHz of (1 Mhz + // of on VHF and up) + bool valid {false}; + quint64 min_offset {99999999}; + for (auto const& item : *m_config.frequencies ()) + { + // we need to do specific checks for above and below here to + // ensure that we can use unsigned Radio::Frequency since we + // potentially use the full 64-bit unsigned range. + auto const& working_frequency = item.frequency_; + auto const& offset = dial_frequency > working_frequency ? + dial_frequency - working_frequency : + working_frequency - dial_frequency; + if (offset < min_offset) { + min_offset = offset; + } + } + if (min_offset < 10000u || (m_config.enable_VHF_features() && min_offset < 1000000u)) { + valid = true; + } + + update_dynamic_property (ui->labDialFreq, "oob", !valid); + ui->labDialFreq->setText (Radio::pretty_frequency_MHz_string (dial_frequency)); +} + +void MainWindow::statusChanged() +{ + statusUpdate (); + QFile f {m_config.temp_dir ().absoluteFilePath ("wsjtx_status.txt")}; + if(f.open(QFile::WriteOnly | QIODevice::Text)) { + QTextStream out(&f); + QString tmpGrid = m_hisGrid; + if (!tmpGrid.size ()) tmpGrid="n/a"; // Not Available + out << qSetRealNumberPrecision (12) << (m_freqNominal / 1.e6) + << ";" << m_mode << ";" << m_hisCall << ";" + << ui->rptSpinBox->value() << ";" << m_mode << ";" << tmpGrid +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; + f.close(); + } else { + if (m_splash && m_splash->isVisible ()) m_splash->hide (); + MessageBox::warning_message (this, tr ("Status File Error") + , tr ("Cannot open \"%1\" for writing: %2") + .arg (f.fileName ()).arg (f.errorString ())); + } + on_dxGridEntry_textChanged(m_hisGrid); +} + +bool MainWindow::eventFilter (QObject * object, QEvent * event) +{ + switch (event->type()) + { + case QEvent::KeyPress: + // fall through + case QEvent::MouseButtonPress: + // reset the Tx watchdog + tx_watchdog (false); + break; + + case QEvent::ChildAdded: + // ensure our child widgets get added to our event filter + add_child_to_event_filter (static_cast (event)->child ()); + break; + + case QEvent::ChildRemoved: + // ensure our child widgets get d=removed from our event filter + remove_child_from_event_filter (static_cast (event)->child ()); + break; + + default: break; + } + return QObject::eventFilter(object, event); +} + +void MainWindow::createStatusBar() //createStatusBar +{ + tx_status_label.setAlignment (Qt::AlignHCenter); + tx_status_label.setMinimumSize (QSize {100, 18}); + tx_status_label.setStyleSheet ("QLabel{color: #000000; background-color: #00ff00}"); + tx_status_label.setFrameStyle (QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget (&tx_status_label); + + config_label.setAlignment (Qt::AlignHCenter); + config_label.setMinimumSize (QSize {80, 18}); + config_label.setFrameStyle (QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget (&config_label); + config_label.hide (); // only shown for non-default configuration + + mode_label.setAlignment (Qt::AlignHCenter); + mode_label.setMinimumSize (QSize {80, 18}); + mode_label.setFrameStyle (QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget (&mode_label); + + last_tx_label.setAlignment (Qt::AlignHCenter); + last_tx_label.setMinimumSize (QSize {150, 18}); + last_tx_label.setFrameStyle (QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget (&last_tx_label); + + ndecodes_label.setAlignment (Qt::AlignHCenter); + ndecodes_label.setMinimumSize (QSize {30, 18}); + ndecodes_label.setFrameStyle (QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget (&ndecodes_label); + + band_hopping_label.setAlignment (Qt::AlignHCenter); + band_hopping_label.setMinimumSize (QSize {90, 18}); + band_hopping_label.setFrameStyle (QFrame::Panel | QFrame::Sunken); + + statusBar()->addPermanentWidget(&progressBar); + progressBar.setMinimumSize (QSize {150, 18}); + + statusBar ()->addPermanentWidget (&watchdog_label); + update_watchdog_label (); +} + +void MainWindow::setup_status_bar (bool vhf) +{ + auto submode = current_submode (); + if (vhf && submode != QChar::Null) { + QString t{m_mode + " " + submode}; + if(m_mode=="Q65") t=m_mode + "-" + QString::number(m_TRperiod) + submode; + mode_label.setText (t); + } else { + mode_label.setText (m_mode); + } + if ("JT9" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff6ec7}"); + } else if ("JT4" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #cc99ff}"); + } else if ("Echo" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #66ffff}"); + } else if ("JT65" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #66ff66}"); + } else if ("Q65" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #99ff33}"); + } else if ("MSK144" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff6666}"); + } else if ("FT4" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff0099}"); + } else if ("FT8" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff6699}"); + } else if ("FST4" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #99ff66}"); + } else if ("FST4W" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #6699ff}"); + } else if ("FreqCal" == m_mode) { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff9933}"); + } + last_tx_label.setText (QString {}); + if (m_mode.contains (QRegularExpression {R"(^(Echo))"})) { + if (band_hopping_label.isVisible ()) statusBar ()->removeWidget (&band_hopping_label); + } else if (m_mode=="WSPR") { + mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff66ff}"); + if (!band_hopping_label.isVisible ()) { + statusBar ()->addWidget (&band_hopping_label); + band_hopping_label.show (); + } + } else { + if (band_hopping_label.isVisible ()) statusBar ()->removeWidget (&band_hopping_label); + } +} + +bool MainWindow::subProcessFailed (QProcess * process, int exit_code, QProcess::ExitStatus status) +{ + if (m_valid && (exit_code || QProcess::NormalExit != status)) + { + QStringList arguments; + for (auto argument: process->arguments ()) + { + if (argument.contains (' ')) argument = '"' + argument + '"'; + arguments << argument; + } + if (m_splash && m_splash->isVisible ()) m_splash->hide (); + MessageBox::critical_message (this, tr ("Subprocess Error") + , tr ("Subprocess failed with exit code %1") + .arg (exit_code) + , tr ("Running: %1\n%2") + .arg (process->program () + ' ' + arguments.join (' ')) + .arg (QString {process->readAllStandardError()})); + return true; + } + return false; +} + +void MainWindow::subProcessError (QProcess * process, QProcess::ProcessError) +{ + if (m_valid) + { + QStringList arguments; + for (auto argument: process->arguments ()) + { + if (argument.contains (' ')) argument = '"' + argument + '"'; + arguments << argument; + } + if (m_splash && m_splash->isVisible ()) m_splash->hide (); + MessageBox::critical_message (this, tr ("Subprocess error") + , tr ("Running: %1\n%2") + .arg (process->program () + ' ' + arguments.join (' ')) + .arg (process->errorString ())); + m_valid = false; // ensures exit if still constructing + QTimer::singleShot (0, this, SLOT (close ())); + } +} + +void MainWindow::closeEvent(QCloseEvent * e) +{ + m_valid = false; // suppresses subprocess errors + m_config.transceiver_offline (); + writeSettings (); + m_astroWidget.reset (); + m_guiTimer.stop (); + m_prefixes.reset (); + m_shortcuts.reset (); + m_mouseCmnds.reset (); + m_colorHighlighting.reset (); + if(m_mode!="MSK144" and m_mode!="FT8") killFile(); + float sw=0.0; + int nw=400; + int nh=100; + int irow=-99; + plotsave_(&sw,&nw,&nh,&irow); + to_jt9(m_ihsym,999,-1); //Tell jt9 to terminate + if (!proc_jt9.waitForFinished(1000)) proc_jt9.close(); + mem_jt9->detach(); + Q_EMIT finished (); + QMainWindow::closeEvent (e); +} + +void MainWindow::on_stopButton_clicked() //stopButton +{ + monitor (false); + m_loopall=false; + if(m_bRefSpec) { + MessageBox::information_message (this, tr ("Reference spectrum saved")); + m_bRefSpec=false; + } +} + +void MainWindow::on_actionRelease_Notes_triggered () +{ + QDesktopServices::openUrl (QUrl {"http://physics.princeton.edu/pulsar/k1jt/Release_Notes.txt"}); +} + +void MainWindow::on_actionFT8_DXpedition_Mode_User_Guide_triggered() +{ + QDesktopServices::openUrl (QUrl {"http://physics.princeton.edu/pulsar/k1jt/FT8_DXpedition_Mode.pdf"}); +} + +void MainWindow::on_actionQSG_FST4_triggered() +{ + QDesktopServices::openUrl (QUrl {"https://physics.princeton.edu/pulsar/k1jt/FST4_Quick_Start.pdf"}); +} + +void MainWindow::on_actionQSG_Q65_triggered() +{ + QDesktopServices::openUrl (QUrl {"https://physics.princeton.edu/pulsar/k1jt/Q65_Quick_Start.pdf"}); +} + +void MainWindow::on_actionQSG_X250_M3_triggered() +{ + QDesktopServices::openUrl (QUrl {"https://physics.princeton.edu/pulsar/k1jt/WSJTX_2.5.0_MAP65_3.0_Quick_Start.pdf"}); +} + +void MainWindow::on_actionOnline_User_Guide_triggered() //Display manual +{ +#if defined (CMAKE_BUILD) + m_manual.display_html_url (QUrl {PROJECT_MANUAL_DIRECTORY_URL}, PROJECT_MANUAL); +#endif +} + +//Display local copy of manual +void MainWindow::on_actionLocal_User_Guide_triggered() +{ +#if defined (CMAKE_BUILD) + m_manual.display_html_file (m_config.doc_dir (), PROJECT_MANUAL); +#endif +} + +void MainWindow::on_actionWide_Waterfall_triggered() //Display Waterfalls +{ + m_wideGraph->showNormal(); +} + +void MainWindow::on_actionEcho_Graph_triggered() +{ + m_echoGraph->showNormal(); +} + +void MainWindow::on_actionFast_Graph_triggered() +{ + m_fastGraph->showNormal(); +} + +void MainWindow::on_actionSolve_FreqCal_triggered() +{ + auto data_dir {QDir::toNativeSeparators(m_config.writeable_data_dir().absolutePath()).toLocal8Bit ()}; + int iz,irc; + double a,b,rms,sigmaa,sigmab; + calibrate_(data_dir.constData (),&iz,&a,&b,&rms,&sigmaa,&sigmab,&irc,data_dir.size ()); + QString t2; + if(irc==-1) t2="Cannot open " + data_dir + "/fmt.all"; + if(irc==-2) t2="Cannot open " + data_dir + "/fcal2.out"; + if(irc==-3) t2="Insufficient data in fmt.all"; + if(irc==-4) t2 = tr ("Invalid data in fmt.all at line %1").arg (iz); + if(irc>0 or rms>1.0) t2="Check fmt.all for possible bad data."; + if (irc < 0 || irc > 0 || rms > 1.) { + MessageBox::warning_message (this, "Calibration Error", t2); + } + else if (MessageBox::Apply == MessageBox::query_message (this + , tr ("Good Calibration Solution") + , tr ("
"
+                                                                 "%1%L2 ±%L3 ppm\n"
+                                                                 "%4%L5 ±%L6 Hz\n\n"
+                                                                 "%7%L8\n"
+                                                                 "%9%L10 Hz"
+                                                                 "
") + .arg ("Slope: ", 12).arg (b, 0, 'f', 3).arg (sigmab, 0, 'f', 3) + .arg ("Intercept: ", 12).arg (a, 0, 'f', 2).arg (sigmaa, 0, 'f', 2) + .arg ("N: ", 12).arg (iz) + .arg ("StdDev: ", 12).arg (rms, 0, 'f', 2) + , QString {} + , MessageBox::Cancel | MessageBox::Apply)) { + m_config.set_calibration (Configuration::CalibrationParams {a, b}); + if (MessageBox::Yes == MessageBox::query_message (this + , tr ("Delete Calibration Measurements") + , tr ("The \"fmt.all\" file will be renamed as \"fmt.bak\""))) { + // rename fmt.all as we have consumed the resulting calibration + // solution + auto const& backup_file_name = m_config.writeable_data_dir ().absoluteFilePath ("fmt.bak"); + QFile::remove (backup_file_name); + QFile::rename (m_config.writeable_data_dir ().absoluteFilePath ("fmt.all"), backup_file_name); + } + } +} + +void MainWindow::on_actionCopyright_Notice_triggered() +{ + auto const& message = tr("If you make fair use of any part of WSJT-X under terms of the GNU " + "General Public License, you must display the following copyright " + "notice prominently in your derivative work:\n\n" + "\"The algorithms, source code, look-and-feel of WSJT-X and related " + "programs, and protocol specifications for the modes FSK441, FST4, FT8, " + "JT4, JT6M, JT9, JT65, JTMS, QRA64, Q65, MSK144 are Copyright (C) " + "2001-2021 by one or more of the following authors: Joseph Taylor, " + "K1JT; Bill Somerville, G4WJS; Steven Franke, K9AN; Nico Palermo, " + "IV3NWV; Greg Beam, KI7MT; Michael Black, W9MDB; Edson Pereira, PY2SDR; " + "Philip Karn, KA9Q; and other members of the WSJT Development Group.\""); + MessageBox::warning_message(this, message); +} + +// Implement the MultiGeometryWidget::change_layout() operation. +void MainWindow::change_layout (std::size_t n) +{ + switch (n) + { + case 1: // SWL view + ui->menuBar->show (); + ui->lower_panel_widget->hide (); + trim_view (false); // ensure we can switch back + break; + + case 2: // hide menus view + ui->menuBar->hide (); + ui->lower_panel_widget->show (); + trim_view (true); + break; + + default: // normal view + ui->menuBar->setVisible (ui->cbMenus->isChecked ()); + ui->lower_panel_widget->show (); + trim_view (!ui->cbMenus->isChecked ()); + break; + } +} + +void MainWindow::on_actionSWL_Mode_triggered (bool checked) +{ + select_geometry (checked ? 1 : ui->cbMenus->isChecked () ? 0 : 2); +} + +// This allows the window to shrink by removing certain things +// and reducing space used by controls +void MainWindow::trim_view (bool checked) +{ + int spacing = checked ? 1 : 6; + if (checked) { + statusBar ()->removeWidget (&auto_tx_label); + } else { + statusBar ()->addWidget(&auto_tx_label); + } + if (m_mode != "FreqCal" && m_mode != "WSPR" && m_mode != "FST4W") { + ui->lh_decodes_title_label->setVisible(!checked); + ui->rh_decodes_title_label->setVisible(!checked); + } + ui->lh_decodes_headings_label->setVisible(!checked); + ui->rh_decodes_headings_label->setVisible(!checked); + ui->gridLayout_5->layout()->setSpacing(spacing); + ui->horizontalLayout_2->layout()->setSpacing(spacing); + ui->horizontalLayout_5->layout()->setSpacing(spacing); + ui->horizontalLayout_6->layout()->setSpacing(spacing); + ui->horizontalLayout_7->layout()->setSpacing(spacing); + ui->horizontalLayout_8->layout()->setSpacing(spacing); + ui->horizontalLayout_9->layout()->setSpacing(spacing); + ui->horizontalLayout_10->layout()->setSpacing(spacing); + ui->horizontalLayout_11->layout()->setSpacing(spacing); + ui->horizontalLayout_12->layout()->setSpacing(spacing); + ui->horizontalLayout_13->layout()->setSpacing(spacing); + ui->horizontalLayout_14->layout()->setSpacing(spacing); + ui->rh_decodes_widget->layout()->setSpacing(spacing); + ui->verticalLayout_2->layout()->setSpacing(spacing); + ui->verticalLayout_3->layout()->setSpacing(spacing); + ui->verticalLayout_5->layout()->setSpacing(spacing); + ui->verticalLayout_7->layout()->setSpacing(spacing); + ui->verticalLayout_8->layout()->setSpacing(spacing); + ui->tab->layout()->setSpacing(spacing); +} + +void MainWindow::on_actionAstronomical_data_toggled (bool checked) +{ + if (checked) + { + m_astroWidget.reset (new Astro {m_settings, &m_config}); + + // hook up termination signal + connect (this, &MainWindow::finished, m_astroWidget.data (), &Astro::close); + connect (m_astroWidget.data (), &Astro::tracking_update, [this] { + m_astroCorrection = {}; + setRig (); + setXIT (ui->TxFreqSpinBox->value ()); + displayDialFrequency (); + }); + m_astroWidget->showNormal(); + m_astroWidget->raise (); + m_astroWidget->activateWindow (); + m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal); + } + else + { + m_astroWidget.reset (); + } +} + +void MainWindow::on_fox_log_action_triggered() +{ + if (!m_foxLogWindow) + { + m_foxLogWindow.reset (new FoxLogWindow {m_settings, &m_config, m_logBook.fox_log ()}); + + // Connect signals from fox log window + connect (this, &MainWindow::finished, m_foxLogWindow.data (), &FoxLogWindow::close); + connect (m_foxLogWindow.data (), &FoxLogWindow::reset_log_model, [this] () { + m_logBook.fox_log ()->reset (); + }); + } + m_foxLogWindow->showNormal (); + m_foxLogWindow->raise (); + m_foxLogWindow->activateWindow (); +} + +void MainWindow::on_contest_log_action_triggered() +{ + if (!m_contestLogWindow) + { + m_contestLogWindow.reset (new CabrilloLogWindow {m_settings, &m_config, m_logBook.contest_log ()->model ()}); + + // Connect signals from contest log window + connect (this, &MainWindow::finished, m_contestLogWindow.data (), &CabrilloLogWindow::close); + } + m_contestLogWindow->showNormal (); + m_contestLogWindow->raise (); + m_contestLogWindow->activateWindow (); +} + +void MainWindow::on_actionColors_triggered() +{ + if (!m_colorHighlighting) + { + m_colorHighlighting.reset (new ColorHighlighting {m_settings, m_config.decode_highlighting ()}); + connect (&m_config, &Configuration::decode_highlighting_changed, m_colorHighlighting.data (), &ColorHighlighting::set_items); + } + m_colorHighlighting->showNormal (); + m_colorHighlighting->raise (); + m_colorHighlighting->activateWindow (); +} + +void MainWindow::on_actionMessage_averaging_triggered() +{ + if(m_msgAvgWidget == NULL) { + m_msgAvgWidget.reset (new MessageAveraging {m_settings, m_config.decoded_text_font ()}); + + // Connect signals from Message Averaging window + connect (this, &MainWindow::finished, m_msgAvgWidget.data (), &MessageAveraging::close); + } + m_msgAvgWidget->showNormal(); + m_msgAvgWidget->raise(); + m_msgAvgWidget->activateWindow(); +} + +void MainWindow::on_actionOpen_triggered() //Open File +{ + monitor (false); + + QString fname; + fname=QFileDialog::getOpenFileName(this, "Open File", m_path, + "WSJT Files (*.wav)"); + if(!fname.isEmpty ()) { + m_path=fname; + int i1=fname.lastIndexOf("/"); + QString baseName=fname.mid(i1+1); + tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #99ffff}"); + tx_status_label.setText(" " + baseName + " "); + on_stopButton_clicked(); + m_diskData=true; + read_wav_file (fname); + } +} + +void MainWindow::read_wav_file (QString const& fname) +{ + // call diskDat() when done + int i0=fname.lastIndexOf("_"); + int i1=fname.indexOf(".wav"); + m_nutc0=m_UTCdisk; + m_UTCdisk=fname.mid(i0+1,i1-i0-1).toInt(); + m_wav_future_watcher.setFuture (QtConcurrent::run ([this, fname] { + auto basename = fname.mid (fname.lastIndexOf ('/') + 1); + auto pos = fname.indexOf (".wav", 0, Qt::CaseInsensitive); + // global variables and threads do not mix well, this needs changing + dec_data.params.nutc = 0; + if (pos > 0) { + if (pos == fname.indexOf ('_', -11) + 7) { + dec_data.params.nutc = fname.mid (pos - 6, 6).toInt (); + m_fileDateTime=fname.mid(pos-13,13); + } else { + dec_data.params.nutc = 100 * fname.mid (pos - 4, 4).toInt (); + m_fileDateTime=fname.mid(pos-11,11); + } + } + + BWFFile file {QAudioFormat {}, fname}; + bool ok=file.open (BWFFile::ReadOnly); + if(ok) { + auto bytes_per_frame = file.format ().bytesPerFrame (); + int nsamples=m_TRperiod * RX_SAMPLE_RATE; + qint64 max_bytes = std::min (std::size_t (nsamples), + sizeof (dec_data.d2) / sizeof (dec_data.d2[0]))* bytes_per_frame; + auto n = file.read (reinterpret_cast (dec_data.d2), + std::min (max_bytes, file.size ())); + int frames_read = n / bytes_per_frame; + // zero unfilled remaining sample space + std::memset(&dec_data.d2[frames_read],0,max_bytes - n); + if (11025 == file.format ().sampleRate ()) { + short sample_size = file.format ().sampleSize (); + wav12_ (dec_data.d2, dec_data.d2, &frames_read, &sample_size); + } + dec_data.params.kin = frames_read; + dec_data.params.newdat = 1; + } else { + dec_data.params.kin = 0; + dec_data.params.newdat = 0; + } + + if(basename.mid(0,10)=="000000_000" && m_mode == "FT8") { + int isec=15*basename.mid(10,3).toInt(); + int ih=isec/3600; + int im=(isec-3600*ih)/60; + isec=isec%60; + dec_data.params.nutc=3600*ih+60*im+isec; + } + + })); +} + +void MainWindow::on_actionOpen_next_in_directory_triggered() //Open Next +{ + if(m_decoderBusy) return; + monitor (false); + int i,len; + QFileInfo fi(m_path); + QStringList list; + list= fi.dir().entryList().filter(".wav",Qt::CaseInsensitive); + for (i = 0; i < list.size()-1; ++i) { + len=list.at(i).length(); + if(list.at(i)==m_path.right(len)) { + int n=m_path.length(); + QString fname=m_path.replace(n-len,len,list.at(i+1)); + m_path=fname; + int i1=fname.lastIndexOf("/"); + QString baseName=fname.mid(i1+1); + tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #99ffff}"); + tx_status_label.setText(" " + baseName + " "); + m_diskData=true; + read_wav_file (fname); + if(m_loopall and (i==list.size()-2)) { + m_loopall=false; + m_bNoMoreFiles=true; + } + return; + } + } +} +//Open all remaining files +void MainWindow::on_actionDecode_remaining_files_in_directory_triggered() +{ + if(m_decoderBusy) return; + m_loopall=true; + on_actionOpen_next_in_directory_triggered(); +} + +void MainWindow::diskDat() //diskDat() +{ + m_wideGraph->setDiskUTC(dec_data.params.nutc); + if(dec_data.params.kin>0) { + int k; + int kstep=m_FFTSize; + m_diskData=true; + float db=m_config.degrade(); + float bw=m_config.RxBandwidth(); + if(db > 0.0) degrade_snr_(dec_data.d2,&dec_data.params.kin,&db,&bw); + for(int n=1; n<=m_hsymStop; n++) { // Do the waterfall spectra +// k=(n+1)*kstep; //### Why was this (n+1) ??? ### + k=n*kstep; + if(k > dec_data.params.kin) break; + dec_data.params.npts8=k/8; + dataSink(k); + qApp->processEvents(); //Update the waterfall + } + } else { + MessageBox::information_message(this, tr("No data read from disk. Wrong file format?")); + } +} + +//Delete ../save/*.wav +void MainWindow::on_actionDelete_all_wav_files_in_SaveDir_triggered() +{ + auto button = MessageBox::query_message (this, tr ("Confirm Delete"), + tr ("Are you sure you want to delete all *.wav and *.c2 files in \"%1\"?") + .arg (QDir::toNativeSeparators (m_config.save_directory ().absolutePath ()))); + if (MessageBox::Yes == button) { + Q_FOREACH (auto const& file + , m_config.save_directory ().entryList ({"*.wav", "*.c2"}, QDir::Files | QDir::Writable)) { + m_config.save_directory ().remove (file); + } + } +} + +void MainWindow::on_actionNone_triggered() //Save None +{ + m_saveDecoded=false; + m_saveAll=false; + ui->actionNone->setChecked(true); +} + +void MainWindow::on_actionSave_decoded_triggered() +{ + m_saveDecoded=true; + m_saveAll=false; + ui->actionSave_decoded->setChecked(true); +} + +void MainWindow::on_actionSave_all_triggered() //Save All +{ + m_saveDecoded=false; + m_saveAll=true; + ui->actionSave_all->setChecked(true); +} + +void MainWindow::on_actionKeyboard_shortcuts_triggered() +{ + if (!m_shortcuts) + { + QFont font; + font.setPointSize (10); + m_shortcuts.reset (new HelpTextWindow {tr ("Keyboard Shortcuts"), + //: Keyboard shortcuts help window contents + tr (R"( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Esc Stop Tx, abort QSO, clear next-call queue
F1 Online User's Guide (Alt: transmit Tx6)
Shift+F1 Copyright Notice
Ctrl+F1 About WSJT-X
F2 Open settings window (Alt: transmit Tx2)
F3 Display keyboard shortcuts (Alt: transmit Tx3)
F4 Clear DX Call, DX Grid, Tx messages 1-4 (Alt: transmit Tx4)
Alt+F4 Exit program
F5 Display special mouse commands (Alt: transmit Tx5)
F6 Open next file in directory (Alt: toggle "Call 1st")
Shift+F6 Decode all remaining files in directory
F7 Display Message Averaging window
F11 Move Rx frequency down 1 Hz
Ctrl+F11 Move identical Rx and Tx frequencies down 1 Hz
Shift+F11 Move Tx frequency down 60 Hz (FT8) or 90 Hz (FT4)
Ctrl+Shift+F11 Move dial frequency down 2000 Hz
F12 Move Rx frequency up 1 Hz
Ctrl+F12 Move identical Rx and Tx frequencies up 1 Hz
Shift+F12 Move Tx frequency up 60 Hz (FT8) or 90 Hz (FT4)
Ctrl+Shift+F12 Move dial frequency up 2000 Hz
Alt+1-6 Set now transmission to this number on Tab 1
Ctl+1-6 Set next transmission to this number on Tab 1
Alt+B Toggle "Best S+P" status
Alt+C Toggle "Call 1st" checkbox
Alt+D Decode again at QSO frequency
Shift+D Full decode (both windows)
Ctrl+E Turn on TX even/1st
Shift+E Turn off TX even/1st
Alt+E Erase
Ctrl+F Edit the free text message box
Alt+G Generate standard messages
Alt+H Halt Tx
Ctrl+L Lookup callsign in database, generate standard messages
Alt+M Monitor
Alt+N Enable Tx
Ctrl+O Open a .wav file
Alt+O Change operator
Alt+Q Log QSO
Ctrl+R Set Tx4 message to RRR (not in FT4)
Alt+R Set Tx4 message to RR73
Alt+S Stop monitoring
Alt+T Toggle Tune status
Alt+Z Clear hung decoder status
)"), font}); + } + m_shortcuts->showNormal (); + m_shortcuts->raise (); +} + +void MainWindow::on_actionSpecial_mouse_commands_triggered() +{ + if (!m_mouseCmnds) + { + QFont font; + font.setPointSize (10); + m_mouseCmnds.reset (new HelpTextWindow {tr ("Special Mouse Commands"), + //: Mouse commands help window contents + tr (R"( + + + + + + + + + + + + + + + + +
Click onAction
Waterfall:Click to set Rx frequency.
+ Shift-click to set Tx frequency.
+ Ctrl-click or Right-click to set Rx and Tx frequencies.
+ Double-click to also decode at Rx frequency.
+
Decoded text:Double-click to copy second callsign to Dx Call,
+ locator to Dx Grid, change Rx and Tx frequency to
+ decoded signal's frequency, and generate standard
+ messages.
+ If Hold Tx Freq is checked or first callsign in message
+ is your own call, Tx frequency is not changed unless
+ Ctrl is held down.
+
Erase button:Click to erase QSO window.
+ Double-click to erase QSO and Band Activity windows. +
)"), font}); + } + m_mouseCmnds->showNormal (); + m_mouseCmnds->raise (); +} + +void MainWindow::on_DecodeButton_clicked (bool /* checked */) //Decode request +{ + if(m_mode=="MSK144") { + ui->DecodeButton->setChecked(false); + } else { + if(m_mode!="WSPR" && !m_decoderBusy) { + dec_data.params.newdat=0; + dec_data.params.nagain=1; + decode(); + } + } +} + +void MainWindow::freezeDecode(int n) //freezeDecode() +{ + if((n%100)==2) { + if(m_mode=="FST4" and m_config.single_decode() and ui->sbFtol->value()>10) ui->sbFtol->setValue(10); + on_DecodeButton_clicked (true); + } +} + +void MainWindow::on_ClrAvgButton_clicked() +{ + m_nclearave=1; + if(m_msgAvgWidget != NULL) { + if(m_msgAvgWidget->isVisible()) m_msgAvgWidget->displayAvg(""); + } + if(m_mode=="Q65") ndecodes_label.setText("0 0"); +} + +void MainWindow::msgAvgDecode2() +{ + on_DecodeButton_clicked (true); +} + +void MainWindow::decode() //decode() +{ + if(m_decoderBusy) return; //Don't start decoder if it's already busy. + QDateTime now = QDateTime::currentDateTimeUtc (); + if( m_dateTimeLastTX.isValid () ) { + qint64 isecs_since_tx = m_dateTimeLastTX.secsTo(now); + dec_data.params.lapcqonly= (isecs_since_tx > 300); + } else { + m_dateTimeLastTX = now.addSecs(-900); + dec_data.params.lapcqonly=true; + } + if( m_diskData ) { + dec_data.params.lapcqonly=false; + } + if(!m_dataAvailable or m_TRperiod==0.0) return; + ui->DecodeButton->setChecked (true); + if(!dec_data.params.nagain && m_diskData && m_TRperiod >= 60.) { + dec_data.params.nutc=dec_data.params.nutc/100; + } + if(dec_data.params.nagain==0 && dec_data.params.newdat==1 && (!m_diskData)) { + m_dateTimeSeqStart = qt_truncate_date_time_to (QDateTime::currentDateTimeUtc (), m_TRperiod * 1.e3); + auto t = m_dateTimeSeqStart.time (); + dec_data.params.nutc = t.hour () * 100 + t.minute (); + if (m_TRperiod < 60.) + { + dec_data.params.nutc = dec_data.params.nutc * 100 + t.second (); + } + } + + if(m_nPick==1 and !m_diskData) { + QDateTime t=QDateTime::currentDateTimeUtc(); + int ihr=t.toString("hh").toInt(); + int imin=t.toString("mm").toInt(); + int isec=t.toString("ss").toInt(); + isec=isec - fmod(double(isec),m_TRperiod); + dec_data.params.nutc=10000*ihr + 100*imin + isec; + } + if(m_nPick==2) dec_data.params.nutc=m_nutc0; + dec_data.params.nQSOProgress = m_QSOProgress; + dec_data.params.nfqso=m_wideGraph->rxFreq(); + dec_data.params.nftx = ui->TxFreqSpinBox->value (); + qint32 depth {m_ndepth}; + if (!ui->actionInclude_averaging->isVisible ()) depth &= ~16; + if (!ui->actionInclude_correlation->isVisible ()) depth &= ~32; + if (!ui->actionEnable_AP_DXcall->isVisible ()) depth &= ~64; + if (!ui->actionAuto_Clear_Avg->isVisible()) depth &= ~128; + dec_data.params.ndepth=depth; + dec_data.params.n2pass=1; + if(m_config.twoPass()) dec_data.params.n2pass=2; + dec_data.params.nranera=m_config.ntrials(); + dec_data.params.naggressive=m_config.aggressive(); + dec_data.params.nrobust=0; + dec_data.params.ndiskdat=0; + if(m_diskData) dec_data.params.ndiskdat=1; + dec_data.params.nfa=m_wideGraph->nStartFreq(); + dec_data.params.nfSplit=m_wideGraph->Fmin(); + dec_data.params.nfb=m_wideGraph->Fmax(); + if(m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id() and !ui->cbRxAll->isChecked()) dec_data.params.nfb=1000; + if(m_mode=="FT8" and SpecOp::FOX == m_config.special_op_id() ) dec_data.params.nfqso=200; + dec_data.params.ntol=ui->sbFtol->value (); + if(!m_config.enable_VHF_features()) { + dec_data.params.ntol=20; + dec_data.params.naggressive=0; + } + if(m_mode=="FST4") { + dec_data.params.ntol=ui->sbFtol->value(); + if(m_config.single_decode()) { + dec_data.params.nfa=m_wideGraph->rxFreq() - ui->sbFtol->value(); + dec_data.params.nfb=m_wideGraph->rxFreq() + ui->sbFtol->value(); + } else { + dec_data.params.nfa=ui->sbF_Low->value(); + dec_data.params.nfb=ui->sbF_High->value(); + } + } + if(m_mode=="FST4W") dec_data.params.ntol=ui->sbFST4W_FTol->value(); + if(dec_data.params.nutc < m_nutc0) m_RxLog = 1; //Date and Time to file "ALL.TXT". + if(dec_data.params.newdat==1 and !m_diskData) m_nutc0=dec_data.params.nutc; + dec_data.params.ntxmode=9; + dec_data.params.nmode=9; + if(m_mode=="JT65") dec_data.params.nmode=65; + if(m_mode=="JT65") dec_data.params.ljt65apon = ui->actionEnable_AP_JT65->isVisible () && + ui->actionEnable_AP_JT65->isChecked (); + if(m_mode=="Q65") dec_data.params.nmode=66; + if(m_mode=="Q65") dec_data.params.ntxmode=66; + if(m_mode=="JT4") { + dec_data.params.nmode=4; + dec_data.params.ntxmode=4; + } + if(m_mode=="FT8") dec_data.params.nmode=8; + if(m_mode=="FT8") dec_data.params.lft8apon = ui->actionEnable_AP_FT8->isVisible () && + ui->actionEnable_AP_FT8->isChecked (); + if(m_mode=="FT8") dec_data.params.napwid=50; + if(m_mode=="FT4") { + dec_data.params.nmode=5; + m_BestCQpriority=""; + } + if(m_mode=="FST4") dec_data.params.nmode=240; + if(m_mode=="FST4W") dec_data.params.nmode=241; + dec_data.params.ntxmode=dec_data.params.nmode; // Is this used any more? + dec_data.params.ntrperiod=m_TRperiod; + dec_data.params.nsubmode=m_nSubMode; + dec_data.params.minw=0; + dec_data.params.nclearave=m_nclearave; + if(m_nclearave!=0) { + QFile f(m_config.temp_dir ().absoluteFilePath ("avemsg.txt")); + f.remove(); + } + dec_data.params.dttol=m_DTtol; + dec_data.params.emedelay=0.0; + if(m_config.decode_at_52s()) dec_data.params.emedelay=2.5; + dec_data.params.minSync=ui->syncSpinBox->isVisible () ? m_minSync : 0; + dec_data.params.nexp_decode = static_cast (m_config.special_op_id()); + if(m_config.single_decode()) dec_data.params.nexp_decode += 32; + if(m_config.enable_VHF_features()) dec_data.params.nexp_decode += 64; + if(m_mode.startsWith("FST4")) dec_data.params.nexp_decode += 256*(ui->sbNB->value()+3); + dec_data.params.max_drift=ui->sbMaxDrift->value(); + + ::memcpy(dec_data.params.datetime, m_dateTime.toLatin1()+" ", sizeof dec_data.params.datetime); + ::memcpy(dec_data.params.mycall, (m_config.my_callsign()+" ").toLatin1(), sizeof dec_data.params.mycall); + ::memcpy(dec_data.params.mygrid, (m_config.my_grid()+" ").toLatin1(), sizeof dec_data.params.mygrid); + QString hisCall {ui->dxCallEntry->text ()}; + QString hisGrid {ui->dxGridEntry->text ()}; + memcpy(dec_data.params.hiscall,(hisCall + " ").toLatin1 ().constData (), sizeof dec_data.params.hiscall); + memcpy(dec_data.params.hisgrid,(hisGrid + " ").toLatin1 ().constData (), sizeof dec_data.params.hisgrid); + + //newdat=1 ==> this is new data, must do the big FFT + //nagain=1 ==> decode only at fQSO +/- Tol + + if (auto * to = reinterpret_cast (mem_jt9->data())) + { + char *from = (char*) dec_data.ipc; + int size=sizeof(struct dec_data); + if(dec_data.params.newdat==0) { + int noffset {offsetof (struct dec_data, params.nutc)}; + to += noffset; + from += noffset; + size -= noffset; + } + if(m_mode=="MSK144" or m_bFast9) { + float t0=m_t0; + float t1=m_t1; + qApp->processEvents(); //Update the waterfall + if(m_nPick > 0) { + t0=m_t0Pick; + t1=m_t1Pick; + } + static short int d2b[360000]; + narg[0]=dec_data.params.nutc; + if(m_kdone>int(12000.0*m_TRperiod)) { + m_kdone=int(12000.0*m_TRperiod); + } + narg[1]=m_kdone; + narg[2]=m_nSubMode; + narg[3]=dec_data.params.newdat; + narg[4]=dec_data.params.minSync; + narg[5]=m_nPick; + narg[6]=1000.0*t0; + narg[7]=1000.0*t1; + narg[8]=2; //Max decode lines per decode attempt + if(dec_data.params.minSync<0) narg[8]=50; + if(m_mode=="JT9") narg[9]=102; //Fast JT9 + if(m_mode=="MSK144") narg[9]=104; //MSK144 + narg[10]=ui->RxFreqSpinBox->value(); + narg[11]=ui->sbFtol->value (); + narg[12]=0; + narg[13]=-1; + narg[14]=m_config.aggressive(); + memcpy(d2b,dec_data.d2,2*360000); + watcher3.setFuture (QtConcurrent::run (std::bind (fast_decode_,&d2b[0], + &narg[0],&m_TRperiod,&m_msg[0][0], + dec_data.params.mycall,dec_data.params.hiscall,8000,12,12))); + } else { + mem_jt9->lock (); + memcpy(to, from, qMin(mem_jt9->size(), size)); + mem_jt9->unlock (); + to_jt9(m_ihsym,1,-1); //Send m_ihsym to jt9[.exe] and start decoding + decodeBusy(true); + } + } +} + +void::MainWindow::fast_decode_done() +{ + float t,tmax=-99.0; + dec_data.params.nagain=false; + dec_data.params.ndiskdat=false; +// if(m_msg[0][0]==0) m_bDecoded=false; + for(int i=0; m_msg[i][0] && i<100; i++) { + QString message=QString::fromLatin1(m_msg[i]); + m_msg[i][0]=0; + if(message.length()>80) message=message.left (80); + if(narg[13]/8==narg[12]) message=message.trimmed().replace("<...>",m_calls); + +//Left (Band activity) window + DecodedText decodedtext {message.replace (QChar::LineFeed, "")}; + if(!m_bFastDone) { + ui->decodedTextBrowser->displayDecodedText (decodedtext, m_config.my_callsign (), m_mode, m_config.DXCC (), + m_logBook, m_currentBand, m_config.ppfx ()); + } + + t=message.mid(10,5).toFloat(); + if(t>tmax) { + tmax=t; + m_bDecoded=true; + } + postDecode (true, decodedtext.string ()); + write_all("Rx",message); + + if(m_mode=="JT9" or m_mode=="MSK144") { +// find and extract any report for myCall + bool stdMsg = decodedtext.report(m_baseCall, + Radio::base_callsign(ui->dxCallEntry->text()), m_rptRcvd); + +// extract details and send to PSKreporter + if (stdMsg) pskPost (decodedtext); + } + if (tmax >= 0.0) auto_sequence (decodedtext, ui->sbFtol->value (), ui->sbFtol->value ()); + } + m_startAnother=m_loopall; + m_nPick=0; + ui->DecodeButton->setChecked (false); + m_bFastDone=false; +} + +void MainWindow::to_jt9(qint32 n, qint32 istart, qint32 idone) +{ + if (auto * dd = reinterpret_cast (mem_jt9->data())) + { + mem_jt9->lock (); + dd->ipc[0]=n; + if(istart>=0) dd->ipc[1]=istart; + if(idone>=0) dd->ipc[2]=idone; + mem_jt9->unlock (); + } +} + +void MainWindow::decodeDone () +{ + if(m_mode=="Q65") m_wideGraph->drawRed(0,0); + if ("FST4W" == m_mode) + { + if (m_uploadWSPRSpots + && m_config.is_transceiver_online ()) { // need working rig control +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + uploadTimer.start(QRandomGenerator::global ()->bounded (0, 20000)); // Upload delay +#else + uploadTimer.start(20000 * qrand()/((double)RAND_MAX + 1.0)); // Upload delay +#endif + } + } + auto tnow = QDateTime::currentDateTimeUtc (); + double tdone = fmod(double(tnow.time().second()),m_TRperiod); + int mswait; + if( tdone < 0.5*m_TRperiod ) { + mswait = 1000.0 * ( 0.6 * m_TRperiod - tdone ); + } else { + mswait = 1000.0 * ( 1.6 * m_TRperiod - tdone ); + } + m_bDecoded=m_nDecodes>0; +// qDebug() << "aa 3316" << m_saveDecoded << m_saveAll << m_bDecoded << m_nDecodes +// << m_TRperiod << tdone << mswait; + if(!m_diskData and !m_saveAll) { + if(m_saveDecoded and (m_nDecodes==0)) { +// qDebug() << "bb 3319" << mswait; + killFileTimer.start(mswait); //Kill at 3/4 period + } + } + if(m_mode!="FT8" or dec_data.params.nzhsym==50) m_nDecodes=0; + + dec_data.params.nagain=0; + dec_data.params.ndiskdat=0; + m_nclearave=0; +// pause_jt9 (); + ui->DecodeButton->setChecked (false); + decodeBusy(false); + m_RxLog=0; + if(SpecOp::FOX == m_config.special_op_id()) houndCallers(); + to_jt9(m_ihsym,-1,1); //Tell jt9 we know it has finished + + m_startAnother=m_loopall; + if(m_bNoMoreFiles) { + MessageBox::information_message(this, tr("No more files to open.")); + m_bNoMoreFiles=false; + } +} + +void MainWindow::readFromStdout() //readFromStdout +{ + while(proc_jt9.canReadLine()) { + auto line_read = proc_jt9.readLine (); + if (auto p = std::strpbrk (line_read.constData (), "\n\r")) { + // truncate before line ending chars + line_read = line_read.left (p - line_read.constData ()); + } + bool haveFSpread {false}; + float fSpread {0.}; + if (m_mode.startsWith ("FST4")) + { + auto text = line_read.mid (64, 6).trimmed (); + if (text.size ()) + { + fSpread = text.toFloat (&haveFSpread); + line_read = line_read.left (64); + } + auto const& cs = m_config.my_callsign ().toLocal8Bit (); + if ("FST4W" == m_mode && ui->cbNoOwnCall->isChecked () + && (line_read.contains (" " + cs + " ") + || line_read.contains ("<" + cs + ">"))) { + continue; + } + } + if (m_mode!="FT8" and m_mode!="FT4" and !m_mode.startsWith ("FST4") and m_mode!="Q65") { + //Pad 22-char msg to at least 37 chars + line_read = line_read.left(44) + " " + line_read.mid(44); + } + bool bAvgMsg=false; + int navg=0; + + if(line_read.indexOf("") >= 0) { + m_bDecoded = line_read.mid(20).trimmed().toInt() > 0; + int n=line_read.trimmed().size(); + int n2=line_read.trimmed().mid(n-7).toInt(); + int n0=n2/1000; + int n1=n2%1000; + if(m_mode=="Q65") { + ndecodes_label.setText(QString {"%1 %2"}.arg (n0).arg (n1)); + } else { + if(m_nDecodes==0) ndecodes_label.setText("0"); + } + decodeDone (); + return; + } else { + m_nDecodes+=1; + if(m_mode!="Q65") ndecodes_label.setText(QString::number(m_nDecodes)); + if(m_mode=="JT4" or m_mode=="JT65" or m_mode=="Q65") { + //### Do something about Q65 here ? ### + int nf=line_read.indexOf("f"); + if(nf>0) { + navg=line_read.mid(nf+1,1).toInt(); + if(line_read.indexOf("f*")>0) navg=10; + } + int nd=-1; + if(nf<0) nd=line_read.indexOf("d"); + if(nd>0) { + navg=line_read.mid(nd+2,1).toInt(); + if(line_read.mid(nd+2,1)=="*") navg=10; + } + int na=-1; + if(nf<0 and nd<0) na=line_read.indexOf("a"); + if(na>0) { + navg=line_read.mid(na+2,1).toInt(); + if(line_read.mid(na+2,1)=="*") navg=10; + } + int nq=-1; + if(nf<0 and nd<0 and na<0) nq=line_read.indexOf("q"); + if(nq>0) { + navg=line_read.mid(nq+2,1).toInt(); + if(line_read.mid(nq+2,1)=="*") navg=10; + } + if(navg>=2) bAvgMsg=true; + } + write_all("Rx",line_read.trimmed()); + int ntime=6; + if(m_TRperiod>=60) ntime=4; + if (line_read.left(ntime) != m_tBlankLine) { + ui->decodedTextBrowser->new_period (); + if (m_config.insert_blank () + && SpecOp::FOX != m_config.special_op_id()) { + QString band; + if((QDateTime::currentMSecsSinceEpoch() / 1000 - m_secBandChanged) > 4*int(m_TRperiod)/4) { + band = ' ' + m_config.bands ()->find (m_freqNominal); + } + ui->decodedTextBrowser->insertLineSpacer (band.rightJustified (40, '-')); + } + m_tBlankLine = line_read.left(ntime); + } + if ("FST4W" == m_mode) + { + uploadWSPRSpots (true, line_read); + } + DecodedText decodedtext0 {QString::fromUtf8(line_read.constData())}; + DecodedText decodedtext {QString::fromUtf8(line_read.constData()).remove("TU; ")}; + + if(m_mode=="FT8" and SpecOp::FOX == m_config.special_op_id() and + (decodedtext.string().contains("R+") or decodedtext.string().contains("R-"))) { + auto for_us = decodedtext.string().contains(" " + m_config.my_callsign() + " ") or + decodedtext.string().contains(" "+m_baseCall) or + decodedtext.string().contains(m_baseCall+" ") or + decodedtext.string().contains(" <" + m_config.my_callsign() + "> "); + if(decodedtext.string().contains(" DE ")) for_us=true; //Hound with compound callsign + if(for_us) { + QString houndCall,houndGrid; + decodedtext.deCallAndGrid(/*out*/houndCall,houndGrid); + foxRxSequencer(decodedtext.string(),houndCall,houndGrid); + } + } + +//Left (Band activity) window + if(!bAvgMsg) { + if(m_mode=="FT8" and SpecOp::FOX == m_config.special_op_id()) { + if(!m_bDisplayedOnce) { + // This hack sets the font. Surely there's a better way! + DecodedText dt{"."}; + ui->decodedTextBrowser->displayDecodedText (dt, m_config.my_callsign (), m_mode, m_config.DXCC (), + m_logBook, m_currentBand, m_config.ppfx ()); + m_bDisplayedOnce=true; + } + } else { + DecodedText decodedtext1=decodedtext0; + ui->decodedTextBrowser->displayDecodedText (decodedtext1, m_config.my_callsign (), m_mode, m_config.DXCC (), + m_logBook, m_currentBand, m_config.ppfx (), + ui->cbCQonly->isVisible() && ui->cbCQonly->isChecked(), + haveFSpread, fSpread); + + if(m_bBestSPArmed && m_mode=="FT4" && CALLING == m_QSOProgress) { + QString messagePriority=ui->decodedTextBrowser->CQPriority(); + if(messagePriority!="") { + if(messagePriority=="New Call on Band" + and m_BestCQpriority!="New Call on Band" + and m_BestCQpriority!="New Multiplier") { + m_BestCQpriority="New Call on Band"; + m_bDoubleClicked = true; + processMessage(decodedtext0); + } + if(messagePriority=="New DXCC" + and m_BestCQpriority!="New DXCC" + and m_BestCQpriority!="New Multiplier") { + m_BestCQpriority="New DXCC"; + m_bDoubleClicked = true; + processMessage(decodedtext0); + } + } + } + + } + } + +//Right (Rx Frequency) window + bool bDisplayRight=bAvgMsg; + int audioFreq=decodedtext.frequencyOffset(); + if(m_mode=="FT8" or m_mode=="FT4" or m_mode=="FST4" or m_mode=="Q65") { + int ftol=10; + if(m_mode=="Q65") ftol=ui->sbFtol->value(); + auto const& parts = decodedtext.string().remove("<").remove(">") + .split (' ', SkipEmptyParts); + if (parts.size() > 6) { + auto for_us = parts[5].contains (m_baseCall) + || ("DE" == parts[5] && qAbs (ui->RxFreqSpinBox->value () - audioFreq) <= ftol); + if(m_baseCall == m_config.my_callsign()) + { + if (m_baseCall != parts[5]) + { + for_us=false; + } + } + else + { + if (m_config.my_callsign () != parts[5]) + { + for_us = false; // same base call as ours but + // different prefix or suffix, rare + // but can happen with multi station + // special events + } + } + if(m_bCallingCQ && !m_bAutoReply && for_us && ui->cbFirst->isChecked() and + SpecOp::FOX > m_config.special_op_id()) { + m_bDoubleClicked=true; + m_bAutoReply = true; + // SP6XD: detect auto reply here + // The easiest way (for now) is just to skip but we should + // move on to another reply (if any). + if(SpecOp::FOX != m_config.special_op_id()) processMessage (decodedtext); + ui->cbFirst->setStyleSheet(""); + } + + if(SpecOp::FOX==m_config.special_op_id() and decodedtext.string().contains(" DE ")) for_us=true; //Hound with compound callsign + if(SpecOp::FOX==m_config.special_op_id() and for_us and (audioFreq<1000)) bDisplayRight=true; + if(SpecOp::FOX!=m_config.special_op_id() and (for_us or (abs(audioFreq - m_wideGraph->rxFreq()) <= 10))) bDisplayRight=true; + } + } else { + if((abs(audioFreq - m_wideGraph->rxFreq()) <= 10) and + !m_config.enable_VHF_features()) bDisplayRight=true; + } + + if (bDisplayRight) { + // This msg is within 10 hertz of our tuned frequency, or a JT4 or JT65 avg, + // or contains MyCall + if(!m_bBestSPArmed or m_mode!="FT4") { + ui->decodedTextBrowser2->displayDecodedText (decodedtext0, m_config.my_callsign (), m_mode, m_config.DXCC (), + m_logBook, m_currentBand, m_config.ppfx ()); + } + m_QSOText = decodedtext.string ().trimmed (); + } + + postDecode (true, decodedtext.string ()); + + if(m_mode=="FT8" and SpecOp::HOUND==m_config.special_op_id()) { + if(decodedtext.string().contains(";")) { + QStringList w=decodedtext.string().mid(24).split(" ",SkipEmptyParts); + QString foxCall=w.at(3); + foxCall=foxCall.remove("<").remove(">"); + if(w.at(0)==m_config.my_callsign() or w.at(0)==Radio::base_callsign(m_config.my_callsign())) { + //### Check for ui->dxCallEntry->text()==foxCall before logging! ### + ui->stopTxButton->click (); + logQSOTimer.start(0); + } + if((w.at(2)==m_config.my_callsign() or w.at(2)==Radio::base_callsign(m_config.my_callsign())) + and ui->tx3->text().length()>0) { + m_rptRcvd=w.at(4); + m_rptSent=decodedtext.string().mid(7,3); + m_nFoxFreq=decodedtext.string().mid(16,4).toInt(); + hound_reply (); + } + } else { + QStringList w=decodedtext.string().mid(24).split(" ",SkipEmptyParts); + if(decodedtext.string().contains("/")) w.append(" +00"); //Add a dummy report + if(w.size()>=3) { + QString foxCall=w.at(1); + if((w.at(0)==m_config.my_callsign() or w.at(0)==Radio::base_callsign(m_config.my_callsign())) and + ui->tx3->text().length()>0) { + if(w.at(2)=="RR73") { + ui->stopTxButton->click (); + logQSOTimer.start(0); + } else { + if(w.at(1)==Radio::base_callsign(ui->dxCallEntry->text()) and + (w.at(2).mid(0,1)=="+" or w.at(2).mid(0,1)=="-")) { + m_rptRcvd=w.at(2); + m_rptSent=decodedtext.string().mid(7,3); + m_nFoxFreq=decodedtext.string().mid(16,4).toInt(); + hound_reply (); + } + } + } + } + } + } + +//### I think this is where we are preventing Hounds from spotting Fox ### + if(m_mode!="FT8" or (SpecOp::HOUND != m_config.special_op_id())) { + if(m_mode=="FT8" or m_mode=="FT4" or m_mode=="Q65" + or m_mode=="JT4" or m_mode=="JT65" or m_mode=="JT9" or m_mode=="FST4") { + auto_sequence (decodedtext, 25, 50); + } + +// find and extract any report for myCall, but save in m_rptRcvd only if it's from DXcall + QString rpt; + bool stdMsg = decodedtext.report(m_baseCall, + Radio::base_callsign(ui->dxCallEntry->text()), rpt); + QString deCall; + QString grid; + decodedtext.deCallAndGrid(/*out*/deCall,grid); + { + auto t = Radio::base_callsign (ui->dxCallEntry->text ()); + auto const& dx_call = decodedtext.call (); + if (rpt.size () // report in message + && (m_baseCall == Radio::base_callsign (dx_call) // for us + || "DE" == dx_call) // probably for us + && (t == deCall // DX station base call is QSO partner + || ui->dxCallEntry->text () == deCall // DX station full call is QSO partner + || !t.size ())) // not in QSO + { + m_rptRcvd = rpt; + } + } +// extract details and send to PSKreporter + int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged; + bool okToPost=(nsec > int(4*m_TRperiod)/5); + if(m_mode=="FST4W" and okToPost) { + line_read=line_read.left(22) + " CQ " + line_read.trimmed().mid(22); + auto p = line_read.lastIndexOf (' '); + DecodedText FST4W_post {QString::fromUtf8 (line_read.left (p).constData ())}; + pskPost(FST4W_post); + } else { + if (stdMsg && okToPost) pskPost(decodedtext); + } + if((m_mode=="JT4" or m_mode=="JT65" or m_mode=="Q65") and + m_msgAvgWidget!=NULL) { + if(m_msgAvgWidget->isVisible()) { + QFile f(m_config.temp_dir ().absoluteFilePath ("avemsg.txt")); + if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream s(&f); + QString t=s.readAll(); + m_msgAvgWidget->displayAvg(t); + } + } + } + } + } + } +} + +// +// start_tolerance - only respond to "DE ..." and free text 73 +// messages within +/- this value +// +// stop_tolerance - kill Tx if running station is seen to reply to +// another caller and we are going to transmit within +// +/- this value of the reply to another caller +// +void MainWindow::auto_sequence (DecodedText const& message, unsigned start_tolerance, unsigned stop_tolerance) +{ + auto const& message_words = message.messageWords (); + auto is_73 = message_words.filter (QRegularExpression {"^(73|RR73)$"}).size(); + auto msg_no_hash = message.clean_string(); + msg_no_hash = msg_no_hash.mid(22).remove("<").remove(">"); + bool is_OK=false; + if(m_mode=="MSK144" && msg_no_hash.indexOf(ui->dxCallEntry->text()+" R ")>0) is_OK=true; + if (message_words.size () > 3 && (message.isStandardMessage() || (is_73 or is_OK))) { + auto df = message.frequencyOffset (); + auto within_tolerance = (qAbs (ui->RxFreqSpinBox->value () - df) <= int (start_tolerance) + || qAbs (ui->TxFreqSpinBox->value () - df) <= int (start_tolerance)); + bool acceptable_73 = is_73 + && m_QSOProgress >= ROGER_REPORT + && ((message.isStandardMessage () + && (message_words.contains (m_baseCall) + || message_words.contains (m_config.my_callsign ()) + || message_words.contains (ui->dxCallEntry->text ()) + || message_words.contains (Radio::base_callsign (ui->dxCallEntry->text ())) + || message_words.contains ("DE"))) + || !message.isStandardMessage ()); // free text 73/RR73 + + auto const& w = msg_no_hash.split(" ",SkipEmptyParts); + QString w2; + int nrpt=0; + if (w.size () > 2) + { + w2=w.at(2); + if(w.size()>3) { + nrpt=w2.toInt(); + if(w2=="R") nrpt=w.at(3).toInt(); + } + } + bool bEU_VHF=(nrpt>=520001 and nrpt<=594000); + if(bEU_VHF and message.clean_string ().contains("<"+m_config.my_callsign() + "> ")) { + m_xRcvd=message.clean_string ().trimmed().right(13); + } + if (m_auto + && (m_QSOProgress==REPLYING or (!ui->tx1->isEnabled () and m_QSOProgress==REPORT)) + && qAbs (ui->TxFreqSpinBox->value () - df) <= int (stop_tolerance) + && message_words.at (2) != "DE" + && !message_words.at (2).contains (QRegularExpression {"(^(CQ|QRZ))|" + m_baseCall}) + && message_words.at (3).contains (Radio::base_callsign (ui->dxCallEntry->text ()))) { + // auto stop to avoid accidental QRM + ui->stopTxButton->click (); // halt any transmission + } else if (m_auto // transmit allowed + && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked () // auto-sequencing allowed + && ((!m_bCallingCQ // not calling CQ/QRZ + && !m_sentFirst73 // not finished QSO + && ((message_words.at (2).contains (m_baseCall) + // being called and not already in a QSO + && (message_words.at(3).contains(Radio::base_callsign(ui->dxCallEntry->text())) + or bEU_VHF)) + || message_words.at(1) == m_baseCall // RR73; ... + // type 2 compound replies + || (within_tolerance && + (acceptable_73 || + ("DE" == message_words.at (2) && + w2.contains(Radio::base_callsign (m_hisCall))))))) + || (m_bCallingCQ && m_bAutoReply + // look for type 2 compound call replies on our Tx and Rx offsets + && ((within_tolerance && "DE" == message_words.at (2)) + || message_words.at (2).contains (m_baseCall))))) { + if(SpecOp::FOX != m_config.special_op_id()) processMessage (message); + } + } +} + +void MainWindow::pskPost (DecodedText const& decodedtext) +{ + if (m_diskData || !m_config.spot_to_psk_reporter() || decodedtext.isLowConfidence ()) return; + + QString msgmode=m_mode; + QString deCall; + QString grid; + decodedtext.deCallAndGrid(/*out*/deCall,grid); + int audioFrequency = decodedtext.frequencyOffset(); + if(m_mode=="FT8" or m_mode=="MSK144" or m_mode=="FT4") { + audioFrequency=decodedtext.string().mid(16,4).toInt(); + } + int snr = decodedtext.snr(); + Frequency frequency = m_freqNominal + audioFrequency; + if(grid.contains (grid_regexp)) { +// qDebug() << "To PSKreporter:" << deCall << grid << frequency << msgmode << snr; + if (!m_psk_Reporter.addRemoteStation (deCall, grid, frequency, msgmode, snr)) + { + showStatusMessage (tr ("Spotting to PSK Reporter unavailable")); + } + } +} + +void MainWindow::killFile () +{ +// qDebug() << "cc 3725" << m_saveDecoded << m_saveAll << m_bDecoded << m_nDecodes << m_fnameWE; + if (m_fnameWE.size () && !(m_saveAll || (m_saveDecoded && m_bDecoded))) { + QFile f1 {m_fnameWE + ".wav"}; + if(f1.exists()) f1.remove(); + if(m_mode=="WSPR" or m_mode=="FST4W") { + QFile f2 {m_fnameWE + ".c2"}; + if(f2.exists()) f2.remove(); + } + } +} + +void MainWindow::on_EraseButton_clicked () +{ + qint64 ms=QDateTime::currentMSecsSinceEpoch(); + ui->decodedTextBrowser2->erase (); + if(m_mode=="WSPR" or m_mode=="Echo" or m_mode=="FST4W") { + ui->decodedTextBrowser->erase (); + } else { + if((ms-m_msErase)<500) { + ui->decodedTextBrowser->erase (); + } + } + m_msErase=ms; +} + +void MainWindow::band_activity_cleared () +{ + m_messageClient->decodes_cleared (); + QFile f(m_config.temp_dir ().absoluteFilePath ("decoded.txt")); + if(f.exists()) f.remove(); +} + +void MainWindow::rx_frequency_activity_cleared () +{ + m_QSOText.clear(); + set_dateTimeQSO(-1); // G4WJS: why do we do this? +} + +void MainWindow::decodeBusy(bool b) //decodeBusy() +{ + if (!b) { + m_optimizingProgress.reset (); + } + m_decoderBusy=b; + ui->DecodeButton->setEnabled(!b); + ui->actionOpen->setEnabled(!b); + ui->actionOpen_next_in_directory->setEnabled(!b); + ui->actionDecode_remaining_files_in_directory->setEnabled(!b); + + statusUpdate (); +} + +//------------------------------------------------------------- //guiUpdate() +void MainWindow::guiUpdate() +{ + static char message[38]; + static char msgsent[38]; + double txDuration; + + if(m_TRperiod==0) m_TRperiod=60.0; + txDuration=tx_duration(m_mode,m_TRperiod,m_nsps,m_bFast9); + double tx1=0.0; + double tx2=txDuration; + if(m_mode=="FT8" or m_mode=="FT4") icw[0]=0; //No CW ID in FT4 or FT8 mode + if((icw[0]>0) and (!m_bFast9)) tx2 += icw[0]*2560.0/48000.0; //Full length including CW ID + if(tx2>m_TRperiod) tx2=m_TRperiod; + if(!m_txFirst and m_mode!="WSPR" and m_mode!="FST4W") { + tx1 += m_TRperiod; + tx2 += m_TRperiod; + } + + qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; + int nsec=ms/1000; + double tsec=0.001*ms; + double t2p=fmod(tsec,2*m_TRperiod); + double s6=fmod(tsec,6.0); + int nseq = fmod(double(nsec),m_TRperiod); + m_tRemaining=m_TRperiod - fmod(tsec,m_TRperiod); + + if(m_mode=="Echo") { + tx1=0.0; + tx2=txDuration; + if(m_auto and s6>4.0) m_bEchoTxOK=true; + if(m_transmitting) m_bEchoTxed=true; + } + + if(m_mode=="WSPR" or m_mode=="FST4W") { + if(nseq==0 and m_ntr==0) { //Decide whether to Tx or Rx + m_tuneup=false; //This is not an ATU tuneup + bool btx = m_auto && m_WSPR_tx_next; // To Tx, we need m_auto and + // scheduled transmit + m_WSPR_tx_next = false; + if(btx) { + m_ntr=-1; //This says we will have transmitted + ui->pbTxNext->setChecked (false); + m_bTxTime=true; //Start a WSPR or FST4W Tx sequence + } else { + // This will be a WSPR or FST4W Rx sequence. + m_ntr=1; //This says we will have received + m_bTxTime=false; //Start a WSPR or FST4W Rx sequence + } + } + + } else { + // For all modes other than WSPR and FST4W + m_bTxTime = (t2p >= tx1) and (t2p < tx2); + if(m_mode=="Echo") m_bTxTime = m_bTxTime and m_bEchoTxOK; + if(m_mode=="FT8" and ui->tx5->currentText().contains("/B ")) { + //FT8 beacon transmissiion from Tx5 only at top of a UTC minute + double t4p=fmod(tsec,4*m_TRperiod); + if(t4p >= 30.0) m_bTxTime=false; + } + } + if(m_tune) m_bTxTime=true; //"Tune" takes precedence + + if(m_transmitting or m_auto or m_tune) { + m_dateTimeLastTX = QDateTime::currentDateTimeUtc (); + +// Check for "txboth" (FT4 testing purposes only) + QFile f(m_appDir + "/txboth"); + if(f.exists() and fmod(tsec,m_TRperiod) < (0.5 + 105.0*576.0/12000.0)) m_bTxTime=true; + +// Don't transmit another mode in the 30 m WSPR sub-band + Frequency onAirFreq = m_freqNominal + ui->TxFreqSpinBox->value(); + if ((onAirFreq > 10139900 and onAirFreq < 10140320) and m_mode!="WSPR") { + m_bTxTime=false; + if (m_auto) auto_tx_mode (false); + if(onAirFreq!=m_onAirFreq0) { + m_onAirFreq0=onAirFreq; + auto const& message = tr ("Please choose another Tx frequency." + " WSJT-X will not knowingly transmit another" + " mode in the WSPR sub-band on 30m."); + QTimer::singleShot (0, [=] { // don't block guiUpdate + MessageBox::warning_message (this, tr ("WSPR Guard Band"), message); + }); + } + } + + if(m_mode=="FT8" and SpecOp::FOX==m_config.special_op_id()) { +// Don't allow Fox mode in any of the default FT8 sub-bands. + qint32 ft8Freq[]={1840,3573,7074,10136,14074,18100,21074,24915,28074,50313,70100}; + for(int i=0; i<11; i++) { + int kHzdiff=m_freqNominal/1000 - ft8Freq[i]; + if(qAbs(kHzdiff) < 4) { + m_bTxTime=false; + if (m_auto) auto_tx_mode (false); + auto const& message = tr ("Please choose another dial frequency." + " WSJT-X will not operate in Fox mode" + " in the standard FT8 sub-bands."); + QTimer::singleShot (0, [=] { // don't block guiUpdate + MessageBox::warning_message (this, tr ("Fox Mode warning"), message); + }); + break; + } + } + } + + if (m_config.watchdog() && m_mode!="WSPR" && m_mode!="FST4W" + && m_idleMinutes >= m_config.watchdog ()) { + tx_watchdog (true); // disable transmit + } + + double fTR=float((ms%int(1000.0*m_TRperiod)))/int(1000.0*m_TRperiod); + + QString txMsg; + if(m_ntx == 1) txMsg=ui->tx1->text(); + if(m_ntx == 2) txMsg=ui->tx2->text(); + if(m_ntx == 3) txMsg=ui->tx3->text(); + if(m_ntx == 4) txMsg=ui->tx4->text(); + if(m_ntx == 5) txMsg=ui->tx5->currentText(); + if(m_ntx == 6) txMsg=ui->tx6->text(); + int msgLength=txMsg.trimmed().length(); + if(msgLength==0 and !m_tune) on_stopTxButton_clicked(); + + if(g_iptt==0 and ((m_bTxTime and (fTR < 0.75) and (msgLength>0)) or m_tune)) { + //### Allow late starts + icw[0]=m_ncw; + g_iptt = 1; + setRig (); + if(m_mode=="FT8") { + if (SpecOp::FOX == m_config.special_op_id()) { + if (ui->TxFreqSpinBox->value() > 900) { + ui->TxFreqSpinBox->setValue(300); + } + } + else if (SpecOp::HOUND == m_config.special_op_id()) { + if(m_auto && !m_tune) { + if (ui->TxFreqSpinBox->value() < 999 && m_ntx != 3) { + // Hound randomized range: 1000-3000 Hz +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + ui->TxFreqSpinBox->setValue (QRandomGenerator::global ()->bounded (1000, 2999)); +#else + ui->TxFreqSpinBox->setValue ((qrand () % 2000) + 1000); +#endif + } + } + if (m_nSentFoxRrpt==2 and m_ntx==3) { + // move off the original Fox frequency on subsequent tries of Tx3 + int nfreq=m_nFoxFreq + 300; + if(m_nFoxFreq>600) nfreq=m_nFoxFreq - 300; //keep nfreq below 900 Hz + ui->TxFreqSpinBox->setValue(nfreq); + } + if (m_nSentFoxRrpt == 1) { + ++m_nSentFoxRrpt; + } + } + } + + +// If HoldTxFreq is not checked, randomize Fox's Tx Freq +// NB: Maybe this should be done no more than once every 5 minutes or so ? + if(m_mode=="FT8" and SpecOp::FOX==m_config.special_op_id() and !ui->cbHoldTxFreq->isChecked()) { +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + ui->TxFreqSpinBox->setValue (QRandomGenerator::global ()->bounded (300, 599)); +#else + ui->TxFreqSpinBox->setValue(300.0 + 300.0*double(qrand())/RAND_MAX); +#endif + } + + setXIT (ui->TxFreqSpinBox->value ()); + m_config.transceiver_ptt (true); //Assert the PTT + m_tx_when_ready = true; + } +// if(!m_bTxTime and !m_tune and m_mode!="FT4") m_btxok=false; //Time to stop transmitting + if(!m_bTxTime and !m_tune) m_btxok=false; //Time to stop transmitting + } + + if((m_mode=="WSPR" or m_mode=="FST4W") and + ((m_ntr==1 and m_rxDone) or (m_ntr==-1 and nseq>tx2))) { + if(m_monitoring) { + m_rxDone=false; + } + if(m_transmitting) { + WSPR_history(m_freqNominal,-1); + m_bTxTime=false; //Time to stop a WSPR or FST4W transmission + m_btxok=false; + } + else if (m_ntr != -1) { + WSPR_scheduling (); + m_ntr=0; //This WSPR or FST4W Rx sequence is complete + } + } + + + // Calculate Tx tones when needed + if((g_iptt==1 && m_iptt0==0) || m_restart) { +//---------------------------------------------------------------------- + QByteArray ba; + QByteArray ba0; + + if(m_mode=="WSPR") { + ba=WSPR_message().toLatin1(); + } else { + if(SpecOp::HOUND == m_config.special_op_id() and m_ntx!=3) { //Hound transmits only Tx1 or Tx3 + m_ntx=1; + ui->txrb1->setChecked(true); + } + + if(m_mode=="FT4" and m_bBestSPArmed) { + m_BestCQpriority=""; + m_bBestSPArmed=false; + ui->pbBestSP->setStyleSheet (""); + } + + if(m_ntx == 1) ba=ui->tx1->text().toLocal8Bit(); + if(m_ntx == 2) ba=ui->tx2->text().toLocal8Bit(); + if(m_ntx == 3) ba=ui->tx3->text().toLocal8Bit(); + if(m_ntx == 4) ba=ui->tx4->text().toLocal8Bit(); + if(m_ntx == 5) ba=ui->tx5->currentText().toLocal8Bit(); + if(m_ntx == 6) ba=ui->tx6->text().toLocal8Bit(); + } + + ba2msg(ba,message); + int ichk=0; + if (m_lastMessageSent != m_currentMessage + || m_lastMessageType != m_currentMessageType) + { + m_lastMessageSent = m_currentMessage; + m_lastMessageType = m_currentMessageType; + } + m_currentMessageType = 0; + if(m_tune or m_mode=="Echo") { + itone[0]=0; + } else { + if(m_QSOProgress==REPORT || m_QSOProgress==ROGER_REPORT) m_bSentReport=true; + if(m_bSentReport and (m_QSOProgressROGER_REPORT)) m_bSentReport=false; + if(m_mode=="JT4") gen4_(message, &ichk , msgsent, const_cast (itone), + &m_currentMessageType, 22, 22); + if(m_mode=="JT9") gen9_(message, &ichk, msgsent, const_cast (itone), + &m_currentMessageType, 22, 22); + if(m_mode=="JT65") gen65_(message, &ichk, msgsent, const_cast (itone), + &m_currentMessageType, 22, 22); + if(m_mode=="WSPR") genwspr_(message, msgsent, const_cast (itone), + 22, 22); + if(m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4" + or m_mode=="FST4" or m_mode=="FST4W" || "Q65" == m_mode) { + if(m_mode=="MSK144") { + genmsk_128_90_(message, &ichk, msgsent, const_cast (itone), + &m_currentMessageType, 37, 37); + if(m_restart) { + int nsym=144; + if(itone[40]==-40) nsym=40; + m_modulator->set_nsym(nsym); + } + } + + if(m_mode=="FT8") { + if(SpecOp::FOX==m_config.special_op_id() and ui->tabWidget->currentIndex()==1) { + foxTxSequencer(); + } else { + int i3=0; + int n3=0; + char ft8msgbits[77]; + genft8_(message, &i3, &n3, msgsent, const_cast (ft8msgbits), + const_cast (itone), 37, 37); + int nsym=79; + int nsps=4*1920; + float fsample=48000.0; + float bt=2.0; + float f0=ui->TxFreqSpinBox->value() - m_XIT; + int icmplx=0; + int nwave=nsym*nsps; + gen_ft8wave_(const_cast(itone),&nsym,&nsps,&bt,&fsample,&f0,foxcom_.wave, + foxcom_.wave,&icmplx,&nwave); + if(SpecOp::FOX == m_config.special_op_id()) { + //Fox must generate the full Tx waveform, not just an itone[] array. + QString fm = QString::fromStdString(message).trimmed(); + foxGenWaveform(0,fm); + foxcom_.nslots=1; + foxcom_.nfreq=ui->TxFreqSpinBox->value(); + if(m_config.split_mode()) foxcom_.nfreq = foxcom_.nfreq - m_XIT; //Fox Tx freq + QString foxCall=m_config.my_callsign() + " "; + ::memcpy(foxcom_.mycall, foxCall.toLatin1(), sizeof foxcom_.mycall); //Copy Fox callsign into foxcom_ + foxgen_(); + } + } + } + if(m_mode=="FT4") { + int ichk=0; + char ft4msgbits[77]; + genft4_(message, &ichk, msgsent, const_cast (ft4msgbits), + const_cast(itone), 37, 37); + int nsym=103; + int nsps=4*576; + float fsample=48000.0; + float f0=ui->TxFreqSpinBox->value() - m_XIT; + int nwave=(nsym+2)*nsps; + int icmplx=0; + gen_ft4wave_(const_cast(itone),&nsym,&nsps,&fsample,&f0,foxcom_.wave, + foxcom_.wave,&icmplx,&nwave); + } + if(m_mode=="FST4" or m_mode=="FST4W") { + int ichk=0; + int iwspr=0; + char fst4msgbits[101]; + QString wmsg; + if(m_mode=="FST4W") { + iwspr = 1; + wmsg=WSPR_message(); + ba=wmsg.toLatin1(); + ba2msg(ba,message); + } + genfst4_(message,&ichk,msgsent,const_cast (fst4msgbits), + const_cast(itone), &iwspr, 37, 37); + int hmod=1; + if(m_config.x2ToneSpacing()) hmod=2; + if(m_config.x4ToneSpacing()) hmod=4; + int nsps=720; + if(m_TRperiod==30) nsps=1680; + if(m_TRperiod==60) nsps=3888; + if(m_TRperiod==120) nsps=8200; + if(m_TRperiod==300) nsps=21504; + if(m_TRperiod==900) nsps=66560; + if(m_TRperiod==1800) nsps=134400; + nsps=4*nsps; //48000 Hz sampling + int nsym=160; + float fsample=48000.0; + float dfreq=hmod*fsample/nsps; + float f0=ui->TxFreqSpinBox->value() - m_XIT + 1.5*dfreq; + if(m_mode=="FST4W") f0=ui->WSPRfreqSpinBox->value() - m_XIT + 1.5*dfreq; + int nwave=(nsym+2)*nsps; + int icmplx=0; + gen_fst4wave_(const_cast(itone),&nsym,&nsps,&nwave, + &fsample,&hmod,&f0,&icmplx,foxcom_.wave,foxcom_.wave); + + QString t = QString::fromStdString(message).trimmed(); + } + if(m_mode=="Q65") { + int i3=-1; + int n3=-1; + genq65_(message,&ichk,msgsent,const_cast(itone),&i3,&n3,37,37); + int nsps=1800; + if(m_TRperiod==30) nsps=3600; + if(m_TRperiod==60) nsps=7200; + if(m_TRperiod==120) nsps=16000; + if(m_TRperiod==300) nsps=41472; + int nsps4=4*nsps; //48000 Hz sampling + int nsym=85; + float fsample=48000.0; + int nwave=(nsym+2)*nsps4; + int icmplx=0; + int hmod=1; + float f0=ui->TxFreqSpinBox->value()-m_XIT; + genwave_(const_cast(itone),&nsym,&nsps4,&nwave, + &fsample,&hmod,&f0,&icmplx,foxcom_.wave,foxcom_.wave); + } + + if(SpecOp::EU_VHF==m_config.special_op_id()) { + if(m_ntx==2) m_xSent=ui->tx2->text().right(13); + if(m_ntx==3) m_xSent=ui->tx3->text().right(13); + } + + if(SpecOp::FIELD_DAY==m_config.special_op_id() or SpecOp::RTTY==m_config.special_op_id()) { + if(m_ntx==2 or m_ntx==3) { + QStringList t=ui->tx2->text().split(' ', SkipEmptyParts); + int n=t.size(); + m_xSent=t.at(n-2) + " " + t.at(n-1); + } + } + } + msgsent[37]=0; + } + + { + auto temp = m_currentMessage; + m_currentMessage = QString::fromLatin1(msgsent); + if (m_currentMessage != temp) // check if tx message changed + { + statusUpdate (); + } + } + m_bCallingCQ = 6 == m_ntx + || m_currentMessage.contains (QRegularExpression {"^(CQ|QRZ) "}); + if(m_mode=="FT8" or m_mode=="FT4") { + if(m_bCallingCQ && ui->cbFirst->isVisible () && ui->cbFirst->isChecked ()) { + ui->cbFirst->setStyleSheet("QCheckBox{color:red}"); + } else { + ui->cbFirst->setStyleSheet(""); + } + } + + if (m_tune) { + m_currentMessage = "TUNE"; + m_currentMessageType = -1; + } + if(m_restart) { + write_all("Tx",m_currentMessage); + if (m_config.TX_messages ()) { + ui->decodedTextBrowser2->displayTransmittedText(m_currentMessage.trimmed(),m_mode, + ui->TxFreqSpinBox->value(),m_bFastMode,m_TRperiod); + } + } + + auto t2 = QDateTime::currentDateTimeUtc ().toString ("hhmm"); + icw[0] = 0; + auto msg_parts = m_currentMessage.split (' ', SkipEmptyParts); + if (msg_parts.size () > 2) { + // clean up short code forms + msg_parts[0].remove (QChar {'<'}); + msg_parts[0].remove (QChar {'>'}); + msg_parts[1].remove (QChar {'<'}); + msg_parts[1].remove (QChar {'>'}); + } + auto is_73 = message_is_73 (m_currentMessageType, msg_parts); + m_sentFirst73 = is_73 + && !message_is_73 (m_lastMessageType, m_lastMessageSent.split (' ', SkipEmptyParts)); + if (m_sentFirst73 || (is_73 && CALLING == m_QSOProgress)) { + m_qsoStop=t2; + if(m_config.id_after_73 ()) { + icw[0] = m_ncw; + } + if((m_config.prompt_to_log() or m_config.autoLog()) && !m_tune && CALLING != m_QSOProgress) + { + logQSOTimer.start(0); + } + else + { + cease_auto_Tx_after_QSO (); + } + } + + bool b=("FT8"==m_mode or "FT4"==m_mode or "Q65"==m_mode) and ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked (); + if(is_73 and (m_config.disable_TX_on_73() or b)) { + m_nextCall=""; //### Temporary: disable use of "TU;" messages; + if(m_nextCall!="") { + useNextCall(); + } else { + auto_tx_mode (false); + if(b) { + m_ntx=6; + ui->txrb6->setChecked(true); + m_QSOProgress = CALLING; + } + } + } + + if(m_config.id_interval () >0) { + int nmin=(m_sec0-m_secID)/60; + if(m_sec0= m_config.id_interval()) { + icw[0]=m_ncw; + m_secID=m_sec0; + } + } + + if ((m_currentMessageType < 6 || 7 == m_currentMessageType) + && msg_parts.length() >= 3 + && (msg_parts[1] == m_config.my_callsign () || + msg_parts[1] == m_baseCall)) + { + int i1; + bool ok; + i1 = msg_parts[2].toInt(&ok); + if(ok and i1>=-50 and i1<50) + { + m_rptSent = msg_parts[2]; + m_qsoStart = t2; + } else { + if (msg_parts[2].mid (0, 1) == "R") + { + i1 = msg_parts[2].mid (1).toInt (&ok); + if (ok and i1 >= -50 and i1 < 50) + { + m_rptSent = msg_parts[2].mid (1); + m_qsoStart = t2; + } + } + } + } + m_restart=false; +//---------------------------------------------------------------------- + } else { + if (!m_auto && m_sentFirst73) { + m_sentFirst73 = false; + } + } + + if (g_iptt == 1 && m_iptt0 == 0) { + auto const& current_message = QString::fromLatin1 (msgsent); + if(m_config.watchdog () && m_mode!="WSPR" && m_mode!="FST4W" + && current_message != m_msgSent0) { + tx_watchdog (false); // in case we are auto sequencing + m_msgSent0 = current_message; + } + + if (m_mode != "FST4W" && m_mode != "WSPR") + { + if(!m_tune) write_all("Tx",m_currentMessage); + if (m_config.TX_messages () && !m_tune && SpecOp::FOX!=m_config.special_op_id()) + { + ui->decodedTextBrowser2->displayTransmittedText(current_message.trimmed(), + m_mode,ui->TxFreqSpinBox->value(),m_bFastMode,m_TRperiod); + } + } + + switch (m_ntx) + { + case 1: m_QSOProgress = REPLYING; break; + case 2: m_QSOProgress = REPORT; break; + case 3: m_QSOProgress = ROGER_REPORT; break; + case 4: m_QSOProgress = ROGERS; break; + case 5: m_QSOProgress = SIGNOFF; break; + case 6: m_QSOProgress = CALLING; break; + default: break; // determined elsewhere + } + m_transmitting = true; + transmitDisplay (true); + statusUpdate (); + } + + if(!m_btxok && m_btxok0 && g_iptt==1) { + stopTx(); + if ("1" == m_env.value ("WSJT_TX_BOTH", "0")) { + m_txFirst = !m_txFirst; + ui->txFirstCheckBox->setChecked (m_txFirst); + } + } + + if(m_startAnother) { + if(m_mode=="MSK144") { + m_wait++; + } + if(m_mode!="MSK144" or m_wait>=4) { + m_wait=0; + m_startAnother=false; + on_actionOpen_next_in_directory_triggered(); + } + } + + if(m_mode=="FT8" or m_mode=="MSK144" or m_mode=="FT4" || "Q65" == m_mode) { + if(ui->txrb1->isEnabled() and + (SpecOp::NA_VHF==m_config.special_op_id() or + SpecOp::FIELD_DAY==m_config.special_op_id() or + SpecOp::RTTY==m_config.special_op_id() or + SpecOp::WW_DIGI==m_config.special_op_id()) ) { + //We're in a contest-like mode other than EU_VHF: start QSO with Tx2. + ui->tx1->setEnabled(false); + ui->txb1->setEnabled(false); + } + if(!ui->tx1->isEnabled() and SpecOp::EU_VHF==m_config.special_op_id()) { + //We're in EU_VHF mode: start QSO with Tx1. + ui->tx1->setEnabled(true); + ui->txb1->setEnabled(true); + } + } + +//Once per second (onesec) + if(nsec != m_sec0) { +// qDebug() << "AAA" << nsec; + + if(m_mode=="FST4") chk_FST4_freq_range(); + m_currentBand=m_config.bands()->find(m_freqNominal); + if( SpecOp::HOUND == m_config.special_op_id() ) { + qint32 tHound=QDateTime::currentMSecsSinceEpoch()/1000 - m_tAutoOn; + //To keep calling Fox, Hound must reactivate Enable Tx at least once every 2 minutes + if(tHound >= 120 and m_ntx==1) auto_tx_mode(false); + } + + progressBar.setVisible(true); + progressBar.setFormat ("%v/%m"); + if(m_auto and m_mode=="Echo" and m_bEchoTxOK) { + progressBar.setMaximum(3); + progressBar.setValue(int(s6)); + } + if(m_mode!="Echo") { + if(m_monitoring or m_transmitting) { + progressBar.setMaximum(m_TRperiod); + int isec=int(fmod(tsec,m_TRperiod)); + if(m_TRperiod-int(m_TRperiod)>0.0) { + QString progBarLabel; + progBarLabel = progBarLabel.asprintf("%d/%3.1f",isec,m_TRperiod); + progressBar.setFormat (progBarLabel); + } + progressBar.setValue(isec); + } else { + progressBar.setValue(0); + } + } + + astroUpdate (); + + if(m_transmitting) { + char s[42]; + if(SpecOp::FOX==m_config.special_op_id() and ui->tabWidget->currentIndex()==1) { + sprintf(s,"Tx: %d Slots",foxcom_.nslots); + } else { + sprintf(s,"Tx: %s",msgsent); + } + m_nsendingsh=0; + if(s[4]==64) m_nsendingsh=1; + if(m_nsendingsh==1 or m_currentMessageType==7) { + tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #66ffff}"); + } else if(m_nsendingsh==-1 or m_currentMessageType==6) { + tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #ffccff}"); + } else { + tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #ffff33}"); + } + if(m_tune) { + tx_status_label.setText("Tx: TUNE"); + } else { + if(m_mode=="Echo") { + tx_status_label.setText("Tx: ECHO"); + } else { + s[40]=0; + QString t{QString::fromLatin1(s)}; + if(SpecOp::FOX==m_config.special_op_id() and ui->tabWidget->currentIndex()==1 and foxcom_.nslots==1) { + t=m_fm1.trimmed(); + } + if(m_mode=="FT4") t="Tx: "+ m_currentMessage; + tx_status_label.setText(t.trimmed()); + } + } + } else if(m_monitoring) { + if (!m_tx_watchdog) { + tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #00ff00}"); + auto t = tr ("Receiving"); + if(m_mode=="MSK144") { + int npct=int(100.0*m_fCPUmskrtd/0.298667); + if(npct>90) tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #ff0000}"); + t += QString {" %1%"}.arg (npct, 2); + } + tx_status_label.setText (t); + } + transmitDisplay(false); + } else if (!m_diskData && !m_tx_watchdog) { + tx_status_label.setStyleSheet(""); + tx_status_label.setText(""); + } + + QDateTime t = QDateTime::currentDateTimeUtc(); + QString utc = t.date().toString("yyyy MMM dd") + "\n " + + t.time().toString() + " "; + ui->labUTC->setText(utc); + if(m_bBestSPArmed and (m_dateTimeBestSP.secsTo(t) >= 120)) on_pbBestSP_clicked(); //BestSP timeout + if(!m_monitoring and !m_diskData) ui->signal_meter_widget->setValue(0,0); + m_sec0=nsec; + displayDialFrequency (); + } + m_iptt0=g_iptt; + m_btxok0=m_btxok; +} //End of guiUpdate + +void MainWindow::useNextCall() +{ + ui->dxCallEntry->setText(m_nextCall); + m_nextCall=""; + if(m_nextGrid.contains(grid_regexp)) { + ui->dxGridEntry->setText(m_nextGrid); + m_ntx=2; + ui->txrb2->setChecked(true); + } else { + m_ntx=3; + ui->txrb3->setChecked(true); + } + genStdMsgs(m_nextRpt); +} + +void MainWindow::startTx2() +{ + if (!m_modulator->isActive ()) { // TODO - not thread safe + double fSpread=0.0; + double snr=99.0; + QString t=ui->tx5->currentText(); + if(t.mid(0,1)=="#") fSpread=t.mid(1,5).toDouble(); + m_modulator->setSpread(fSpread); // TODO - not thread safe + t=ui->tx6->text(); + if(t.mid(0,1)=="#") snr=t.mid(1,5).toDouble(); + if(snr>0.0 or snr < -50.0) snr=99.0; + if((m_ntx==6 or m_ntx==7) and m_config.force_call_1st()) { + ui->cbAutoSeq->setChecked(true); + ui->cbFirst->setChecked(true); + } + transmit (snr); + ui->signal_meter_widget->setValue(0,0); + if(m_mode=="Echo" and !m_tune) m_bTransmittedEcho=true; + + if((m_mode=="WSPR" or m_mode=="FST4W") and !m_tune) { + if (m_config.TX_messages ()) { + t = " Transmitting " + m_mode + " ----------------------- " + + m_config.bands ()->find (m_freqNominal); + t=beacon_start_time (m_TRperiod / 2) + ' ' + t.rightJustified (66, '-'); + ui->decodedTextBrowser->appendText(t); + } + write_all("Tx",m_currentMessage); + } + } +} + +void MainWindow::stopTx() +{ + Q_EMIT endTransmitMessage (); + m_btxok = false; + m_transmitting = false; + g_iptt=0; + if (!m_tx_watchdog) { + tx_status_label.setStyleSheet(""); + tx_status_label.setText(""); + } + ptt0Timer.start(200); //end-of-transmission sequencer delay + monitor (true); + statusUpdate (); +} + +void MainWindow::stopTx2() +{ + m_config.transceiver_ptt (false); //Lower PTT + if (m_mode == "JT9" && m_bFast9 + && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked () + && m_ntx == 5 && m_nTx73 >= 5) { + on_stopTxButton_clicked (); + m_nTx73 = 0; + } + if(((m_mode=="WSPR" or m_mode=="FST4W") and m_ntr==-1) and !m_tuneup) { + m_wideGraph->setWSPRtransmitted(); + WSPR_scheduling (); + m_ntr=0; + } + last_tx_label.setText(tr ("Last Tx: %1").arg (m_currentMessage.trimmed())); +} + +void MainWindow::ba2msg(QByteArray ba, char message[]) //ba2msg() +{ + int iz=ba.length(); + for(int i=0; i<37; i++) { + if(i=97 and int(ba[i])<=122) ba[i]=int(ba[i])-32; + message[i]=ba[i]; + } else { + message[i]=32; + } + } + message[37]=0; +} + +void MainWindow::on_txFirstCheckBox_stateChanged(int nstate) //TxFirst +{ + m_txFirst = (nstate==2); +} + +void MainWindow::set_dateTimeQSO(int m_ntx) +{ + // m_ntx = -1 resets to default time + // Our QSO start time can be fairly well determined from Tx 2 and Tx 3 -- the grid reports + // If we CQ'd and sending sigrpt then 2 minutes ago n=2 + // If we're on msg 3 then 3 minutes ago n=3 -- might have sat on msg1 for a while + // If we've already set our time on just return. + // This should mean that Tx2 or Tx3 has been repeated so don't update the start time + // We reset it in several places + if (m_ntx == -1) { // we use a default date to detect change + m_dateTimeQSOOn = QDateTime {}; + } + else if (m_dateTimeQSOOn.isValid ()) { + return; + } + else { // we also take of m_TRperiod/2 to allow for late clicks + auto now = QDateTime::currentDateTimeUtc(); + m_dateTimeQSOOn = now.addSecs (-(m_ntx - 2) * int(m_TRperiod) - + int(fmod(double(now.time().second()),m_TRperiod))); + } +} + +void MainWindow::set_ntx(int n) //set_ntx() +{ + m_ntx=n; +} + +void MainWindow::on_txrb1_toggled (bool status) +{ + if (status) { + if (ui->tx1->isEnabled ()) { + m_ntx = 1; + set_dateTimeQSO (-1); // we reset here as tx2/tx3 is used for start times + } + else { + QTimer::singleShot (0, ui->txrb2, SLOT (click ())); + } + } +} + +bool MainWindow::elide_tx1_not_allowed () const +{ + auto const& my_callsign = m_config.my_callsign (); + return + (m_mode=="FT8" && SpecOp::HOUND == m_config.special_op_id()) + || ((m_mode.startsWith ("FT") || "MSK144" == m_mode || "Q65" == m_mode || "FST4" == m_mode) + && Radio::is_77bit_nonstandard_callsign (my_callsign)) + || (my_callsign != m_baseCall && !shortList (my_callsign)); +} + +void MainWindow::on_txrb1_doubleClicked () +{ + ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ()); + if (!ui->tx1->isEnabled ()) { + // leave time for clicks to complete before setting txrb2 + QTimer::singleShot (500, ui->txrb2, SLOT (click ())); + } +} + +void MainWindow::on_txrb2_toggled (bool status) +{ + // Tx 2 means we already have CQ'd so good reference + if (status) { + m_ntx=2; + set_dateTimeQSO (m_ntx); + } +} + +void MainWindow::on_txrb3_toggled(bool status) +{ + // Tx 3 means we should have already have done Tx 1 so good reference + if (status) { + m_ntx=3; + set_dateTimeQSO(m_ntx); + } +} + +void MainWindow::on_txrb4_toggled (bool status) +{ + if (status) { + m_ntx=4; + } +} + +void MainWindow::on_txrb4_doubleClicked () +{ + // RR73 only allowed if not a type 2 compound callsign + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73); + if(m_mode=="FT4") m_send_RR73=true; + genStdMsgs (m_rpt); +} + +void MainWindow::on_txrb5_toggled (bool status) +{ + if (status) { + m_ntx = 5; + } +} + +void MainWindow::on_txrb5_doubleClicked () +{ + genStdMsgs (m_rpt, true); +} + +void MainWindow::on_txrb6_toggled(bool status) +{ + if (status) { + m_ntx=6; + if (ui->txrb6->text().contains (QRegularExpression {"^(CQ|QRZ) "})) set_dateTimeQSO(-1); + } +} + +void MainWindow::on_txb1_clicked() +{ + if (ui->tx1->isEnabled ()) { + m_ntx=1; + m_QSOProgress = REPLYING; + ui->txrb1->setChecked(true); + if(m_transmitting) m_restart=true; + } + else { + on_txb2_clicked (); + } +} + +void MainWindow::on_txb1_doubleClicked() +{ + ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ()); +} + +void MainWindow::on_txb2_clicked() +{ + m_ntx=2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked(true); + if(m_transmitting) m_restart=true; +} + +void MainWindow::on_txb3_clicked() +{ + m_ntx=3; + m_QSOProgress = ROGER_REPORT; + ui->txrb3->setChecked(true); + if(m_transmitting) m_restart=true; +} + +void MainWindow::on_txb4_clicked() +{ + m_ntx=4; + m_QSOProgress = ROGERS; + ui->txrb4->setChecked(true); + if(m_transmitting) m_restart=true; +} + +void MainWindow::on_txb4_doubleClicked() +{ + // RR73 only allowed if not a type 2 compound callsign + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73); + if(m_mode=="FT4") m_send_RR73=true; + genStdMsgs (m_rpt); +} + +void MainWindow::on_txb5_clicked() +{ + m_ntx=5; + m_QSOProgress = SIGNOFF; + ui->txrb5->setChecked(true); + if(m_transmitting) m_restart=true; +} + +void MainWindow::on_txb5_doubleClicked() +{ + genStdMsgs (m_rpt, true); +} + +void MainWindow::on_txb6_clicked() +{ + m_ntx=6; + m_QSOProgress = CALLING; + set_dateTimeQSO(-1); + ui->txrb6->setChecked(true); + if(m_transmitting) m_restart=true; +} + +void MainWindow::doubleClickOnCall2(Qt::KeyboardModifiers modifiers) +{ +//Confusing: come here after double-click on left text window, not right window. + set_dateTimeQSO(-1); // reset our QSO start time + m_decodedText2=true; + doubleClickOnCall(modifiers); + m_decodedText2=false; +} + +void MainWindow::doubleClickOnCall(Qt::KeyboardModifiers modifiers) +{ + QTextCursor cursor; + if(m_mode=="FST4W") { + MessageBox::information_message (this, + "Double-click not available for FST4W mode"); + return; + } + if(m_decodedText2) { + cursor=ui->decodedTextBrowser->textCursor(); + } else { + cursor=ui->decodedTextBrowser2->textCursor(); + } + + if(modifiers==(Qt::ShiftModifier + Qt::ControlModifier + Qt::AltModifier)) { + //### What was the purpose of this ??? ### + cursor.setPosition(0); + } else { + cursor.setPosition(cursor.selectionStart()); + } + + if(SpecOp::FOX==m_config.special_op_id() and m_decodedText2) { + if(m_houndQueue.count()<10 and m_nSortedHounds>0) { + QString t=cursor.block().text(); + selectHound(t); + } + return; + } + DecodedText message {cursor.block().text().trimmed().left(61).remove("TU; ")}; + m_bDoubleClicked = true; + processMessage (message, modifiers); +} + +void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifiers modifiers) +{ + // decode keyboard modifiers we are interested in + auto shift = modifiers.testFlag (Qt::ShiftModifier); + auto ctrl = modifiers.testFlag (Qt::ControlModifier); + // auto alt = modifiers.testFlag (Qt::AltModifier); + auto auto_seq = ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked (); + // basic mode sanity checks + auto const& parts = message.clean_string ().split (' ', SkipEmptyParts); + if (parts.size () < 5) return; + auto const& mode = parts.at (4).left (1); + if (("JT65" == m_mode && mode != "#") + || ("JT9" == m_mode && mode != "@") + || ("MSK144" == m_mode && !("&" == mode || "^" == mode)) + || ("Q65" == m_mode && mode.left (1) != ":")) { + return; //Currently we do auto-sequencing only in FT4, FT8, MSK144, FST4, and Q65 + } + + //Skip the rest if no decoded text extracted + int frequency = message.frequencyOffset(); + if (message.isTX()) { + if (!m_config.enable_VHF_features()) { + if(!shift) ui->RxFreqSpinBox->setValue(frequency); //Set Rx freq + if((ctrl or shift) and !ui->cbHoldTxFreq->isChecked ()) { + ui->TxFreqSpinBox->setValue(frequency); //Set Tx freq + } + } + return; + } + + // check for CQ with listening frequency + if (parts.size () >= 7 + && (m_bFastMode || m_mode=="FT8") + && "CQ" == parts[5] + && m_config.is_transceiver_online ()) { + bool ok; + auto kHz = parts[6].toUInt (&ok); + if (ok && kHz >= 10 && 3 == parts[6].size ()) { + // QSY Freq for answering CQ nnn + setRig (m_freqNominal / 1000000 * 1000000 + 1000 * kHz); + ui->decodedTextBrowser2->displayQSY (QString {"QSY %1"}.arg (m_freqNominal / 1e6, 7, 'f', 3)); + } + } + + int nmod = fmod(double(message.timeInSeconds()),2.0*m_TRperiod); + m_txFirst=(nmod!=0); + if( SpecOp::HOUND == m_config.special_op_id() ) m_txFirst=false; //Hound must not transmit first + if( SpecOp::FOX == m_config.special_op_id() ) m_txFirst=true; //Fox must always transmit first + ui->txFirstCheckBox->setChecked(m_txFirst); + + auto const& message_words = message.messageWords (); + if (message_words.size () < 3) return; + + QString hiscall; + QString hisgrid; + message.deCallAndGrid(/*out*/hiscall,hisgrid); + + + if(message.clean_string ().contains(hiscall+"/R")) { + hiscall+="/R"; + ui->dxCallEntry->setText(hiscall); + } + if(message.clean_string ().contains(hiscall+"/P")) { + hiscall+="/P"; + ui->dxCallEntry->setText(hiscall); + } + + //SP6XD I found this place as the best for extracting remote callsign + //and decide wheter to answer or not + if ( ui->ignoreRegex->isChecked() && !m_config.regex_filter().isEmpty() && hiscall.contains(QRegularExpression(m_config.regex_filter())) ) { + ui->lastIgnored->setStyleSheet("QLabel {background-color: red; color: white;}"); + ui->lastIgnored->setText(hiscall); + qDebug () << "Ignoring station:" << hiscall << " grid:" << hisgrid; + //printf("Ignoring %s\n ", hiscall.toStdString().c_str()); + return; + } else { + qDebug () << "Processing station:" << hiscall << " grid:" << hisgrid; + //printf("Processing %s\n ", hiscall.toStdString().c_str()); + } + + QStringList w=message.clean_string ().mid(22).remove("<").remove(">").split(" ",SkipEmptyParts); + int nw=w.size(); + if(nw>=4) { + if(message_words.size()<4) return; + int n=w.at(nw-2).toInt(); + if(n>=520001 and n<=592047) { + hiscall=w.at(1); + hisgrid=w.at(nw-1); + } + } + + bool is_73 = message_words.filter (QRegularExpression {"^(73|RR73)$"}).size (); + if (!is_73 and !message.isStandardMessage() and !message.clean_string ().contains("<")) { + qDebug () << "Not processing message - hiscall:" << hiscall << "hisgrid:" << hisgrid + << message.clean_string () << message.isStandardMessage(); + return; + } + + if ((message.isJT9 () and m_mode != "JT9" and m_mode != "JT4") or + (message.isJT65 () and m_mode != "JT65" and m_mode != "JT4")) { + // We are not allowing mode change, so don't process decode + return; + } + + // ignore calls by other hounds + if (SpecOp::HOUND == m_config.special_op_id() + && message.messageWords ().indexOf (QRegularExpression {R"(R\+-[0-9]+)"}) >= 1) + { + return; + } + + QString firstcall = message.call(); + if(firstcall.length()==5 and firstcall.mid(0,3)=="CQ ") firstcall="CQ"; + if(!m_bFastMode and (!m_config.enable_VHF_features() or m_mode=="FT8")) { + // Don't change Tx freq if in a fast mode, or VHF features enabled; also not if a + // station is calling me, unless CTRL or SHIFT is held down. + if ((Radio::is_callsign (firstcall) + && firstcall != m_config.my_callsign () && firstcall != m_baseCall + && firstcall != "DE") + || "CQ" == firstcall || "QRZ" == firstcall || ctrl || shift) { + if (((SpecOp::HOUND != m_config.special_op_id()) || m_mode != "FT8") + && (!ui->cbHoldTxFreq->isChecked () || shift || ctrl)) { + ui->TxFreqSpinBox->setValue(frequency); + } + if(m_mode != "JT4" && m_mode != "JT65" && !m_mode.startsWith ("JT9") && + m_mode != "Q65" && m_mode!="FT8" && m_mode!="FT4" && m_mode!="FST4") { + return; + } + } + } + + // prior DX call (possible QSO partner) + auto qso_partner_base_call = Radio::base_callsign (ui->dxCallEntry->text ()); + auto base_call = Radio::base_callsign (hiscall); + +// Determine appropriate response to received message + auto dtext = " " + message.clean_string () + " "; + dtext=dtext.remove("<").remove(">"); + if(dtext.contains (" " + m_baseCall + " ") + || dtext.contains ("<" + m_baseCall + "> ") +//###??? || dtext.contains ("<" + m_baseCall + " " + hiscall + "> ") + || dtext.contains ("/" + m_baseCall + " ") + || dtext.contains (" " + m_baseCall + "/") + || (firstcall == "DE")) { + + QString w2; + int nw=w.size(); + if(nw>=3) w2=w.at(2); + int nrpt=w2.toInt(); + QString w34; + if(nw>=4) { +// w34=w.at(nw-2); + nrpt=w.at(nw-2).toInt(); + w34=w.at(nw-1); + } + bool bRTTY = (nrpt>=529 and nrpt<=599); + bool bEU_VHF_w2=(nrpt>=520001 and nrpt<=594000); + if(bEU_VHF_w2 and SpecOp::EU_VHF!=m_config.special_op_id()) { + auto const& msg = tr("Should you switch to EU VHF Contest mode?\n\n" + "To do so, check 'Special operating activity' and\n" + "'EU VHF Contest' on the Settings | Advanced tab."); + MessageBox::information_message (this, msg); + } + + QStringList t=message.clean_string ().split(' ', SkipEmptyParts); + int n=t.size(); + QString t0=t.at(n-2); + QString t1=t0.right(1); + bool bFieldDay_msg = (t1>="A" and t1<="F" and t0.size()<=3 and n>=9); + int m=t0.remove(t1).toInt(); + if(m < 1) bFieldDay_msg=false; + if(bFieldDay_msg) { + m_xRcvd=t.at(n-2) + " " + t.at(n-1); + t0=t.at(n-3); + } + if(bFieldDay_msg and SpecOp::FIELD_DAY!=m_config.special_op_id()) { + // ### Should be in ARRL Field Day mode ??? ### + MessageBox::information_message (this, tr ("Should you switch to ARRL Field Day mode?")); + } + + if(bRTTY and SpecOp::RTTY != m_config.special_op_id()) { + // ### Should be in RTTY contest mode ??? ### + MessageBox::information_message (this, tr ("Should you switch to RTTY contest mode?")); + } + + if(SpecOp::EU_VHF==m_config.special_op_id() and message_words.at(2).contains(m_baseCall) and + (!message_words.at(3).contains(qso_partner_base_call)) and (!m_bDoubleClicked)) { + return; + } + + bool bContestOK=(m_mode=="FT4" or m_mode=="FT8" or m_mode=="Q65" or m_mode=="MSK144"); + if(message_words.size () > 4 // enough fields for a normal message + && (message_words.at(2).contains(m_baseCall) || "DE" == message_words.at(2)) + && (message_words.at(3).contains(qso_partner_base_call) or m_bDoubleClicked + or bEU_VHF_w2 or (m_QSOProgress==CALLING))) { + if(message_words.at(4).contains(grid_regexp) and SpecOp::EU_VHF!=m_config.special_op_id()) { + if((SpecOp::NA_VHF==m_config.special_op_id() or SpecOp::WW_DIGI==m_config.special_op_id()) and bContestOK){ + setTxMsg(3); + m_QSOProgress=ROGER_REPORT; + } else { + if(m_mode=="JT65" and message_words.size()>5 and message_words.at(5)=="OOO") { + setTxMsg(3); + m_QSOProgress=ROGER_REPORT; + } else { + setTxMsg(2); + m_QSOProgress=REPORT; + } + } + } else if(w34.contains(grid_regexp) and SpecOp::EU_VHF==m_config.special_op_id()) { + + if(nrpt==0) { + setTxMsg(2); + m_QSOProgress=REPORT; + } else { + if(w2=="R") { + setTxMsg(4); + m_QSOProgress=ROGERS; + } else { + setTxMsg(3); + m_QSOProgress=ROGER_REPORT; + } + } + } else if(SpecOp::RTTY == m_config.special_op_id() and bRTTY) { + if(w2=="R") { + setTxMsg(4); + m_QSOProgress=ROGERS; + } else { + setTxMsg(3); + m_QSOProgress=ROGER_REPORT; + } + m_xRcvd=t[n-2] + " " + t[n-1]; + } else if(SpecOp::FIELD_DAY==m_config.special_op_id() and bFieldDay_msg) { + if(t0=="R") { + setTxMsg(4); + m_QSOProgress=ROGERS; + } else { + setTxMsg(3); + m_QSOProgress=ROGER_REPORT; + } + } else { // no grid on end of msg + auto const& word_3 = message_words.at (4); + auto word_3_as_number = word_3.toInt (); + if (("RRR" == word_3 + || (word_3_as_number == 73 && ROGERS == m_QSOProgress) + || "RR73" == word_3 + || ("R" == word_3 && m_QSOProgress != REPORT))) { + if(m_mode=="FT4" and "RR73" == word_3) m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc(); + m_bTUmsg=false; + m_nextCall=""; //### Temporary: disable use of "TU;" message + if(SpecOp::RTTY == m_config.special_op_id() and m_nextCall!="") { + // We're in RTTY contest and have "nextCall" queued up: send a "TU; ..." message + if (m_config.prompt_to_log() || m_config.autoLog()) { + logQSOTimer.start(0); + } + else { + cease_auto_Tx_after_QSO (); + } + ui->tx3->setText(ui->tx3->text().remove("TU; ")); + useNextCall(); + QString t="TU; " + ui->tx3->text(); + ui->tx3->setText(t); + m_bTUmsg=true; + } else { + if (m_QSOProgress > CALLING && m_QSOProgress < SIGNOFF + && SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id () + && ("RR73" == word_3 || 73 == word_3_as_number)) + { + if (m_config.prompt_to_log() || m_config.autoLog()) { + logQSOTimer.start(0); + } + else { + cease_auto_Tx_after_QSO (); + } + m_ntx=6; + ui->txrb6->setChecked(true); + } + else if (word_3.contains (QRegularExpression {"^R(?!R73|RR)"}) + && m_QSOProgress != ROGER_REPORT) + { + m_ntx=4; + ui->txrb4->setChecked(true); + } + else if ((m_QSOProgress > CALLING && m_QSOProgress < ROGERS) + || word_3.contains (QRegularExpression {"^RR(?:R|73)$"})) + { + m_ntx=5; + ui->txrb5->setChecked(true); + } + else if (ROGERS == m_QSOProgress) + { + if (m_config.prompt_to_log() || m_config.autoLog()) { + logQSOTimer.start(0); + } + else { + cease_auto_Tx_after_QSO (); + } + m_ntx=6; + ui->txrb6->setChecked(true); + } + else + { + // just work them (again) + if (ui->tx1->isEnabled ()) { + m_ntx = 1; + m_QSOProgress = REPLYING; + ui->txrb1->setChecked (true); + } else { + m_ntx=2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked (true); + } + } + } + if (m_QSOProgress >= ROGER_REPORT) + { + m_QSOProgress = SIGNOFF; + } + } else if((m_QSOProgress >= REPORT + || (m_QSOProgress >= REPLYING && + (m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4" || "Q65" == m_mode))) + && word_3.startsWith ('R')) { + m_ntx=4; + m_QSOProgress = ROGERS; + if(SpecOp::RTTY == m_config.special_op_id()) { + int n=t.size(); + int nRpt=t[n-2].toInt(); + if(nRpt>=529 and nRpt<=599) m_xRcvd=t[n-2] + " " + t[n-1]; + } + ui->txrb4->setChecked(true); + } else if (m_QSOProgress >= CALLING) + { + if ((word_3_as_number >= -50 && word_3_as_number <= 49) + || (word_3_as_number >= 529 && word_3_as_number <= 599)) + { + if(SpecOp::EU_VHF==m_config.special_op_id() or + SpecOp::FIELD_DAY==m_config.special_op_id() or + SpecOp::RTTY==m_config.special_op_id()) + { + setTxMsg(2); + m_QSOProgress=REPORT; + } + else + { + if (word_3.startsWith ("R-") || word_3.startsWith ("R+")) + { + setTxMsg(4); + m_QSOProgress=ROGERS; + } + else + { + setTxMsg (3); + m_QSOProgress = ROGER_REPORT; + } + } + } + } + else + { // nothing for us + return; + } + } + } + else if (5 == message_words.size () + && m_baseCall == message_words.at (1)) { + // dual Fox style message, possibly from MSHV + if (m_config.prompt_to_log() || m_config.autoLog()) { + logQSOTimer.start(0); + } + else { + cease_auto_Tx_after_QSO (); + } + m_ntx=6; + ui->txrb6->setChecked(true); + } + else if (m_QSOProgress >= ROGERS + && message_words.size () > 3 && message_words.at (2).contains (m_baseCall) + && message_words.at (3) == "73") { + // 73 back to compound call holder + m_ntx=5; + ui->txrb5->setChecked(true); + m_QSOProgress = SIGNOFF; + } + else if (!(m_bAutoReply && (m_QSOProgress > CALLING))) { + if ((message_words.size () > 5 && message_words.at (2).contains (m_baseCall) + && message_words.at (5) == "OOO")) { + // EME short code report or MSK144/FT8 contest mode reply, send back Tx3 + m_ntx=3; + m_QSOProgress = ROGER_REPORT; + ui->txrb3->setChecked (true); + } else if (!is_73) { // don't respond to sign off messages + m_ntx=2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked(true); + if (m_bDoubleClickAfterCQnnn and m_transmitting) { + on_stopTxButton_clicked(); + TxAgainTimer.start(1500); + } + m_bDoubleClickAfterCQnnn=false; + } + else { + return; // nothing we need to respond to + } + } + else { // nothing for us + return; + } + } + else if (firstcall == "DE" && message_words.size () > 4 && message_words.at (4) == "73") { + if (m_QSOProgress >= ROGERS && base_call == qso_partner_base_call && m_currentMessageType) { + // 73 back to compound call holder + m_ntx=5; + ui->txrb5->setChecked(true); + m_QSOProgress = SIGNOFF; + } else { + // treat like a CQ/QRZ + if (ui->tx1->isEnabled ()) { + m_ntx = 1; + m_QSOProgress = REPLYING; + ui->txrb1->setChecked (true); + } else { + m_ntx=2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked (true); + } + } + } + else if (is_73 && !message.isStandardMessage ()) { + m_ntx=5; + ui->txrb5->setChecked(true); + m_QSOProgress = SIGNOFF; + } else { + // just work them + if (ui->tx1->isEnabled ()) { + m_ntx = 1; + m_QSOProgress = REPLYING; + ui->txrb1->setChecked (true); + } else { + m_ntx=2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked (true); + } + } + // if we get here then we are reacting to the message + if (m_bAutoReply) m_bCallingCQ = CALLING == m_QSOProgress; + if (ui->RxFreqSpinBox->isEnabled () and m_mode != "MSK144" and !shift) { + ui->RxFreqSpinBox->setValue (frequency); //Set Rx freq + } + + QString s1 = m_QSOText.trimmed (); + QString s2 = message.clean_string ().trimmed(); + if (s1!=s2 and !message.isTX()) { + if (!s2.contains(m_baseCall) or m_mode=="MSK144") { // Taken care of elsewhere if for_us and slow mode + ui->decodedTextBrowser2->displayDecodedText (message, m_config.my_callsign (), m_mode, m_config.DXCC (), + m_logBook, m_currentBand, m_config.ppfx ()); + } + m_QSOText = s2; + } + + if (Radio::is_callsign (hiscall) + && (base_call != qso_partner_base_call || base_call != hiscall)) { + if (qso_partner_base_call != base_call) { + // clear the DX grid if the base call of his call is different + // from the current DX call + ui->dxGridEntry->clear (); + } + // his base call different or his call more qualified + // i.e. compound version of same base call + ui->dxCallEntry->setText (hiscall); + } + if (hisgrid.contains (grid_regexp)) { + if(ui->dxGridEntry->text().mid(0,4) != hisgrid) ui->dxGridEntry->setText(hisgrid); + } + lookup(); + m_hisGrid = ui->dxGridEntry->text(); + + if (m_bDoubleClicked) + { + // extract our report if present + message.report (m_baseCall, Radio::base_callsign(ui->dxCallEntry->text()), m_rptRcvd); + } + + if (!m_bSentReport || base_call != qso_partner_base_call) // Don't change report within a QSO + { + auto n = message.report ().toInt (); + if(m_mode=="MSK144" and m_bShMsgs) { + if(n<=-2) n=-3; + if(n>=-1 and n<=1) n=0; + if(n>=2 and n<=4) n=3; + if(n>=5 and n<=7) n=6; + if(n>=8 and n<=11) n=10; + if(n>=12 and n<=14) n=13; + if(n>=15) n=16; + } + ui->rptSpinBox->setValue (n); + } +// Don't genStdMsgs if we're already sending 73, or a "TU; " msg is queued. + m_bTUmsg=false; //### Temporary: disable use of "TU;" messages + if (!m_nTx73 and !m_bTUmsg) { + genStdMsgs (QString::number (ui->rptSpinBox->value ())); + } + if(m_transmitting) m_restart=true; + if (auto_seq && !m_bDoubleClicked && m_mode!="FT4") { + return; + } + if(m_config.quick_call() && m_bDoubleClicked) auto_tx_mode(true); + m_bDoubleClicked=false; +} + +void MainWindow::setTxMsg(int n) +{ + m_ntx=n; + if(n==1) ui->txrb1->setChecked(true); + if(n==2) ui->txrb2->setChecked(true); + if(n==3) ui->txrb3->setChecked(true); + if(n==4) ui->txrb4->setChecked(true); + if(n==5) ui->txrb5->setChecked(true); + if(n==6) ui->txrb6->setChecked(true); +} + +void MainWindow::genCQMsg () +{ + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + auto is_type_two = !is77BitMode () && is_compound && stdCall (m_baseCall) && !shortList (my_callsign); + if(my_callsign.size () && m_config.my_grid().size ()) { + auto const& grid = m_config.my_grid (); + if (ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () && ui->cbCQTx->isChecked ()) { + if(stdCall (my_callsign) + || is_type_two) { + msgtype (QString {"CQ %1 %2 %3"} + .arg (m_freqNominal / 1000 - m_freqNominal / 1000000 * 1000, 3, 10, QChar {'0'}) + .arg (my_callsign) + .arg (grid.left (4)), + ui->tx6); + } else { + msgtype (QString {"CQ %1 %2"} + .arg (m_freqNominal / 1000 - m_freqNominal / 1000000 * 1000, 3, 10, QChar {'0'}) + .arg (my_callsign), + ui->tx6); + } + } else { + if (stdCall (my_callsign) + || is_type_two) { + msgtype (QString {"%1 %2 %3"}.arg(m_CQtype).arg(my_callsign) + .arg(grid.left(4)),ui->tx6); + } else { + msgtype (QString {"%1 %2"}.arg(m_CQtype).arg(my_callsign),ui->tx6); + } + } + if ((m_mode=="JT4" or m_mode=="Q65") and ui->cbShMsgs->isChecked()) { + if (ui->cbTx6->isChecked ()) { + msgtype ("@1250 (SEND MSGS)", ui->tx6); + } else { + msgtype ("@1000 (TUNE)", ui->tx6); + } + } + + QString t=ui->tx6->text(); + QStringList tlist=t.split(" "); + if((m_mode=="FT4" or m_mode=="FT8" or m_mode=="MSK144" || "Q65" == m_mode) and + SpecOp::NONE != m_config.special_op_id() and + ( tlist.at(1)==my_callsign or + tlist.at(2)==my_callsign ) and + stdCall(my_callsign)) { + if(SpecOp::NA_VHF == m_config.special_op_id()) m_cqStr="TEST"; + if(SpecOp::EU_VHF == m_config.special_op_id()) m_cqStr="TEST"; + if(SpecOp::FIELD_DAY == m_config.special_op_id()) m_cqStr="FD"; + if(SpecOp::RTTY == m_config.special_op_id()) m_cqStr="RU"; + if(SpecOp::WW_DIGI == m_config.special_op_id()) m_cqStr="WW"; + if( tlist.at(1)==my_callsign ) { + t="CQ " + m_cqStr + " " + tlist.at(1) + " " + tlist.at(2); + } else { + t="CQ " + m_cqStr + " " + tlist.at(2) + " " + tlist.at(3); + } + ui->tx6->setText(t); + } + } else { + ui->tx6->clear (); + } +} + +void MainWindow::abortQSO() +{ + bool b=m_auto; + clearDX(); + if(b) auto_tx_mode(false); + ui->txrb6->setChecked(true); +} + +bool MainWindow::stdCall(QString const& w) +{ + static QRegularExpression standard_call_re { + R"( + ^\s* # optional leading spaces + ( [A-Z]{0,2} | [A-Z][0-9] | [0-9][A-Z] ) # part 1 + ( [0-9][A-Z]{0,3} ) # part 2 + (/R | /P)? # optional suffix + \s*$ # optional trailing spaces + )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption}; + return standard_call_re.match (w).hasMatch (); +} + +bool MainWindow::is77BitMode () const +{ + return "FT8" == m_mode || "FT4" == m_mode || "MSK144" == m_mode + || "FST4" == m_mode || "Q65" == m_mode; +} + +void MainWindow::genStdMsgs(QString rpt, bool unconditional) +{ + genCQMsg (); + auto const& hisCall=ui->dxCallEntry->text(); + if(!hisCall.size ()) { + ui->labAz->clear (); + ui->tx1->clear (); + ui->tx2->clear (); + ui->tx3->clear (); + ui->tx4->clear (); + if(unconditional) ui->tx5->lineEdit ()->clear (); //Test if it needs sending again + m_gen_message_is_cq = false; + return; + } + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + auto is_type_one = !is77BitMode () && is_compound && shortList (my_callsign); + auto const& my_grid = m_config.my_grid ().left (4); + auto const& hisBase = Radio::base_callsign (hisCall); + save_dxbase_(const_cast ((hisBase + " ").left (6).toLatin1().constData()),6); + auto eme_short_codes = m_config.enable_VHF_features () && ui->cbShMsgs->isChecked () + && m_mode == "JT65"; + + bool bMyCall=stdCall(my_callsign); + bool bHisCall=stdCall(hisCall); + + QString t0=hisBase + " " + m_baseCall + " "; + QString t0s=hisCall + " " + my_callsign + " "; + QString t0a,t0b; + + if (is77BitMode () && bHisCall && bMyCall) t0=hisCall + " " + my_callsign + " "; + t0a="<"+hisCall + "> " + my_callsign + " "; + t0b=hisCall + " <" + my_callsign + "> "; + + QString t00=t0; + QString t {t0 + my_grid}; + if(!bMyCall) t=t0a; + msgtype(t, ui->tx1); + if (eme_short_codes) { + t=t+" OOO"; + if(!bHisCall) t=hisCall + " " + m_baseCall + " OOO"; + if(!bMyCall) t=hisBase + " " + my_callsign + " OOO"; + msgtype(t, ui->tx2); + msgtype("RO", ui->tx3); + msgtype("RRR", ui->tx4); + msgtype("73", ui->tx5->lineEdit()); + } else { + int n=rpt.toInt(); + rpt = rpt.asprintf("%+2.2d",n); + + if (is77BitMode ()) { + QString t2,t3; + QString sent=rpt; + QString rs,rst; + int nn=(n+36)/6; + if(nn<2) nn=2; + if(nn>9) nn=9; + rst = rst.asprintf("5%1d9 ",nn); + rs=rst.mid(0,2); + t=t0; + if(!bMyCall) { + t=t0b; + msgtype(t0a, ui->tx1); + } + if(!bHisCall) { + t=t0a; + msgtype(t0a + my_grid, ui->tx1); + } + if(SpecOp::NA_VHF==m_config.special_op_id()) sent=my_grid; + if(SpecOp::WW_DIGI==m_config.special_op_id()) sent=my_grid; + if(SpecOp::FIELD_DAY==m_config.special_op_id()) sent=m_config.Field_Day_Exchange(); + if(SpecOp::RTTY==m_config.special_op_id()) { + sent=rst + m_config.RTTY_Exchange(); + QString t1=m_config.RTTY_Exchange(); + if(t1=="DX" or t1=="#") { + t1 = t1.asprintf("%4.4d",ui->sbSerialNumber->value()); + sent=rst + t1; + } + } + if(SpecOp::EU_VHF==m_config.special_op_id()) { + QString a; + t="<" + t0s.split(" ").at(0) + "> <" + t0s.split(" ").at(1) + "> "; + a = a.asprintf("%4.4d ",ui->sbSerialNumber->value()); + sent=rs + a + m_config.my_grid(); + } + msgtype(t + sent, ui->tx2); + if(sent==rpt) msgtype(t + "R" + sent, ui->tx3); + if(sent!=rpt) msgtype(t + "R " + sent, ui->tx3); + if(m_mode=="FT4" and SpecOp::RTTY==m_config.special_op_id()) { + QDateTime now=QDateTime::currentDateTimeUtc(); + int sinceTx3 = m_dateTimeSentTx3.secsTo(now); + int sinceRR73 = m_dateTimeRcvdRR73.secsTo(now); + if(m_bDoubleClicked and (sinceTx3 < 15) and (sinceRR73 < 3)) { + t="TU; " + ui->tx3->text(); + ui->tx3->setText(t); + } + } + } + + if(m_mode=="MSK144" and m_bShMsgs) { + int i=t0s.length()-1; + t0="<" + t0s.mid(0,i) + "> "; + if(SpecOp::NA_VHF != m_config.special_op_id()) { + if(n<=-2) n=-3; + if(n>=-1 and n<=1) n=0; + if(n>=2 and n<=4) n=3; + if(n>=5 and n<=7) n=6; + if(n>=8 and n<=11) n=10; + if(n>=12 and n<=14) n=13; + if(n>=15) n=16; + rpt = rpt.asprintf("%+2.2d",n); + } + } + + if (!is77BitMode ()) { + t=(is_type_one ? t0 : t00) + rpt; + msgtype(t, ui->tx2); + t=t0 + "R" + rpt; + msgtype(t, ui->tx3); + } + + if(m_mode=="MSK144" and m_bShMsgs) { + if(m_config.special_op_id()==SpecOp::NONE) { + t=t0 + "R" + rpt; + msgtype(t, ui->tx3); + } + m_send_RR73=false; + } + + t=t0 + (m_send_RR73 ? "RR73" : "RRR"); + if((m_mode=="MSK144" and !m_bShMsgs) or m_mode=="FT8" or m_mode=="FT4" || m_mode == "FST4" || m_mode == "Q65") { + if(!bHisCall and bMyCall) t=hisCall + " <" + my_callsign + "> " + (m_send_RR73 ? "RR73" : "RRR"); + if(bHisCall and !bMyCall) t="<" + hisCall + "> " + my_callsign + " " + (m_send_RR73 ? "RR73" : "RRR"); + } + if ((m_mode=="JT4" || m_mode=="Q65") && m_bShMsgs) t="@1500 (RRR)"; + msgtype(t, ui->tx4); + + t=t0 + "73"; + if((m_mode=="MSK144" and !m_bShMsgs) or m_mode=="FT8" or m_mode=="FT4" || m_mode == "FST4" || m_mode == "Q65") { + if(!bHisCall and bMyCall) t=hisCall + " <" + my_callsign + "> 73"; + if(bHisCall and !bMyCall) t="<" + hisCall + "> " + my_callsign + " 73"; + } + if (m_mode=="JT4" || m_mode=="Q65") { + if (m_bShMsgs) t="@1750 (73)"; + msgtype(t, ui->tx5->lineEdit()); + } else if ("MSK144" == m_mode && m_bShMsgs) { + msgtype(t, ui->tx5->lineEdit()); + } else if(unconditional || hisBase != m_lastCallsign || !m_lastCallsign.size ()) { + // only update tx5 when forced or callsign changes + msgtype(t, ui->tx5->lineEdit()); + m_lastCallsign = hisBase; + } + } + + if (is77BitMode ()) return; + + if (is_compound) { + if (is_type_one) { + t=hisBase + " " + my_callsign; + msgtype(t, ui->tx1); + } else { + t = "DE " + my_callsign + " "; + switch (m_config.type_2_msg_gen ()) + { + case Configuration::type_2_msg_1_full: + msgtype(t + my_grid, ui->tx1); + if (!eme_short_codes) { + if(is77BitMode () && SpecOp::NA_VHF == m_config.special_op_id()) { + msgtype(t + "R " + my_grid, ui->tx3); // #### Unreachable code + } else { + msgtype(t + "R" + rpt, ui->tx3); + } + if ((m_mode != "JT4" && m_mode != "Q65") || !m_bShMsgs) { + msgtype(t + "73", ui->tx5->lineEdit ()); + } + } + break; + + case Configuration::type_2_msg_3_full: + if (is77BitMode () && SpecOp::NA_VHF == m_config.special_op_id()) { + msgtype(t + "R " + my_grid, ui->tx3); + msgtype(t + "RRR", ui->tx4); + } else { + msgtype(t00 + my_grid, ui->tx1); + msgtype(t + "R" + rpt, ui->tx3); + } + if (!eme_short_codes && ((m_mode != "JT4" && m_mode != "Q65") || !m_bShMsgs)) { + msgtype(t + "73", ui->tx5->lineEdit ()); + } + break; + + case Configuration::type_2_msg_5_only: + msgtype(t00 + my_grid, ui->tx1); + if (!eme_short_codes) { + if (is77BitMode () && SpecOp::NA_VHF == m_config.special_op_id()) { + msgtype(t + "R " + my_grid, ui->tx3); // #### Unreachable code + msgtype(t + "RRR", ui->tx4); + } else { + msgtype(t0 + "R" + rpt, ui->tx3); + } + } + // don't use short codes here as in a sked with a type 2 + // prefix we would never send out prefix/suffix + msgtype(t + "73", ui->tx5->lineEdit ()); + break; + } + } + if (hisCall != hisBase + && m_config.type_2_msg_gen () != Configuration::type_2_msg_5_only + && !eme_short_codes) { + // cfm we have his full call copied as we could not do this earlier + t = hisCall + " 73"; + msgtype(t, ui->tx5->lineEdit ()); + } + } else { + if (hisCall != hisBase and SpecOp::HOUND != m_config.special_op_id()) { + if (shortList(hisCall)) { + // cfm we know his full call with a type 1 tx1 message + t = hisCall + " " + my_callsign; + msgtype(t, ui->tx1); + } + else if (!eme_short_codes + && ("MSK144" != m_mode || !m_bShMsgs)) { + t=hisCall + " 73"; + msgtype(t, ui->tx5->lineEdit ()); + } + } + } + m_rpt=rpt; + if(SpecOp::HOUND == m_config.special_op_id() and is_compound) ui->tx1->setText("DE " + my_callsign); +} + +void MainWindow::TxAgain() +{ + auto_tx_mode(true); +} + +void MainWindow::clearDX () +{ + set_dateTimeQSO (-1); + if (m_QSOProgress != CALLING) { + auto_tx_mode (false); + } + ui->dxCallEntry->clear (); + ui->dxGridEntry->clear (); + m_lastCallsign.clear (); + m_rptSent.clear (); + m_rptRcvd.clear (); + m_qsoStart.clear (); + m_qsoStop.clear (); + m_inQSOwith.clear(); + genStdMsgs (QString {}); + if (m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) { + m_ntx=1; + ui->txrb1->setChecked(true); + } else { + m_ntx=6; + ui->txrb6->setChecked(true); + } + m_QSOProgress = CALLING; +} + +void MainWindow::lookup() +{ + QString hisCall {ui->dxCallEntry->text()}; + QString hisgrid0 {ui->dxGridEntry->text()}; + if (!hisCall.size ()) return; + QFile f {m_config.writeable_data_dir ().absoluteFilePath ("CALL3.TXT")}; + if (f.open (QIODevice::ReadOnly | QIODevice::Text)) + { + char c[132]; + qint64 n=0; + for(int i=0; i<999999; i++) { + n=f.readLine(c,sizeof(c)); + if(n <= 0) { + if(!hisgrid0.contains(grid_regexp)) { + ui->dxGridEntry->clear(); + } + break; + } + QString t=QString(c); + int i1=t.indexOf(","); + if(t.left(i1)==hisCall) { + QString hisgrid=t.mid(i1+1,6); + i1=hisgrid.indexOf(","); + if(i1>0) { + hisgrid=hisgrid.mid(0,4); + } else { + hisgrid=hisgrid.mid(0,6).toUpper(); + } + if(hisgrid.left(4)==hisgrid0.left(4) or (hisgrid0.size()==0)) { + ui->dxGridEntry->setText(hisgrid); + } + break; + } + } + f.close(); + } +} + +void MainWindow::on_lookupButton_clicked() //Lookup button +{ + lookup(); +} + +void MainWindow::on_addButton_clicked() //Add button +{ + if(!ui->dxGridEntry->text ().size ()) { + MessageBox::warning_message (this, tr ("Add to CALL3.TXT") + , tr ("Please enter a valid grid locator")); + return; + } + m_call3Modified=false; + QString hisCall=ui->dxCallEntry->text(); + QString hisgrid=ui->dxGridEntry->text(); + QString newEntry=hisCall + "," + hisgrid; + + // int ret = MessageBox::query_message(this, tr ("Add to CALL3.TXT"), + // tr ("Is %1 known to be active on EME?").arg (newEntry)); + // if(ret==MessageBox::Yes) { + // newEntry += ",EME,,"; + // } else { + newEntry += ",,,"; + // } + + QFile f1 {m_config.writeable_data_dir ().absoluteFilePath ("CALL3.TXT")}; + if(!f1.open(QIODevice::ReadWrite | QIODevice::Text)) { + MessageBox::warning_message (this, tr ("Add to CALL3.TXT") + , tr ("Cannot open \"%1\" for read/write: %2") + .arg (f1.fileName ()).arg (f1.errorString ())); + return; + } + if(f1.size()==0) { + QTextStream out(&f1); + out << "ZZZZZZ" +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; + f1.seek (0); + } + QFile f2 {m_config.writeable_data_dir ().absoluteFilePath ("CALL3.TMP")}; + if(!f2.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { + MessageBox::warning_message (this, tr ("Add to CALL3.TXT") + , tr ("Cannot open \"%1\" for writing: %2") + .arg (f2.fileName ()).arg (f2.errorString ())); + return; + } + { + QTextStream in(&f1); //Read from CALL3.TXT + QTextStream out(&f2); //Copy into CALL3.TMP + QString hc=hisCall; + QString hc1=""; + QString hc2="000000"; + QString s; + do { + s=in.readLine(); + hc1=hc2; + if(s.mid(0,2)=="//") { + out << s + QChar::LineFeed; //Copy all comment lines + } else { + int i1=s.indexOf(","); + hc2=s.mid(0,i1); + if(hc>hc1 && hchc1 && !m_call3Modified) out << newEntry + QChar::LineFeed; + } + + if(m_call3Modified) { + auto const& old_path = m_config.writeable_data_dir ().absoluteFilePath ("CALL3.OLD"); + QFile f0 {old_path}; + if (f0.exists ()) f0.remove (); + f1.copy (old_path); // copying as we want to + // preserve symlinks + f1.open (QFile::WriteOnly | QFile::Text); // truncates + f2.seek (0); + f1.write (f2.readAll ()); // copy contents + f2.remove (); + } +} + +void MainWindow::msgtype(QString t, QLineEdit* tx) //msgtype() +{ +// Set background colors of the Tx message boxes, depending on message type + char message[38]; + char msgsent[38]; + QByteArray s=t.toUpper().toLocal8Bit(); + ba2msg(s,message); + int ichk=1,itype=0; + gen65_(message,&ichk,msgsent,const_cast(itone0),&itype,22,22); + msgsent[22]=0; + bool text=false; + bool shortMsg=false; + if(itype==6) text=true; + +//### Check this stuff ### + if(itype==7 and m_config.enable_VHF_features() and m_mode=="JT65") shortMsg=true; + if(m_mode=="MSK144" and t.mid(0,1)=="<") text=false; + if((m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4" || "Q65" == m_mode) and + SpecOp::NA_VHF==m_config.special_op_id()) { + int i0=t.trimmed().length()-7; + if(t.mid(i0,3)==" R ") text=false; + } + text=false; +//### ... to here ... + + + QPalette p(tx->palette()); + if(text) { + p.setColor(QPalette::Base,"#ffccff"); //pink + } else { + if(shortMsg) { + p.setColor(QPalette::Base,"#66ffff"); //light blue + } else { + p.setColor(QPalette::Base,Qt::transparent); + if ("MSK144" == m_mode && t.count ('<') == 1) { + p.setColor(QPalette::Base,"#00ffff"); //another light blue + } + } + } + tx->setPalette(p); + + auto pos = tx->cursorPosition (); + tx->setText(t.toUpper()); + tx->setCursorPosition (pos); +} + +void MainWindow::on_tx1_editingFinished() //tx1 edited +{ + QString t=ui->tx1->text(); + msgtype(t, ui->tx1); +} + +void MainWindow::on_tx2_editingFinished() //tx2 edited +{ + QString t=ui->tx2->text(); + msgtype(t, ui->tx2); +} + +void MainWindow::on_tx3_editingFinished() //tx3 edited +{ + QString t=ui->tx3->text(); + msgtype(t, ui->tx3); +} + +void MainWindow::on_tx4_editingFinished() //tx4 edited +{ + QString t=ui->tx4->text(); + msgtype(t, ui->tx4); +} + +void MainWindow::on_tx5_currentTextChanged (QString const& text) //tx5 edited +{ + msgtype(text, ui->tx5->lineEdit ()); +} + +void MainWindow::on_tx6_editingFinished() //tx6 edited +{ + QString t=ui->tx6->text().toUpper(); + if(t.indexOf(" ")>0) { + QString t1=t.split(" ").at(1); + QRegExp AZ4("^[A-Z]{1,4}$"); + QRegExp NN3("^[0-9]{1,3}$"); + m_CQtype="CQ"; + if(t1.size()<=4 and t1.contains(AZ4)) m_CQtype="CQ " + t1; + if(t1.size()<=3 and t1.contains(NN3)) m_CQtype="CQ " + t1; + } + msgtype(t, ui->tx6); +} + +void MainWindow::on_RoundRobin_currentTextChanged(QString text) +{ + ui->sbTxPercent->setEnabled (text == tr ("Random")); +} + + +void MainWindow::on_dxCallEntry_textChanged (QString const& call) +{ + m_hisCall = call; + ui->dxGridEntry->clear(); + statusChanged(); + statusUpdate (); +} + +void MainWindow::on_dxCallEntry_editingFinished() +{ + auto const& dxBase = Radio::base_callsign (m_hisCall); + save_dxbase_(const_cast ((dxBase + " ").left (6).toLatin1().constData()),6); +} + + +void MainWindow::on_dxCallEntry_returnPressed () +{ + on_lookupButton_clicked(); +} + +void MainWindow::on_dxGridEntry_textChanged (QString const& grid) +{ + if (ui->dxGridEntry->hasAcceptableInput ()) { + if (grid != m_hisGrid) { + m_hisGrid = grid; + statusUpdate (); + } + qint64 nsec = (QDateTime::currentMSecsSinceEpoch()/1000) % 86400; + double utch=nsec/3600.0; + int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter; + azdist_(const_cast ((m_config.my_grid () + " ").left (6).toLatin1().constData()), + const_cast ((m_hisGrid + " ").left (6).toLatin1().constData()),&utch, + &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6); + QString t; + int nd=nDkm; + if(m_config.miles()) nd=nDmiles; + if(m_mode=="MSK144") { + if(nHotABetter==0)t = t.asprintf("Az: %d B: %d El: %d %d",nAz,nHotAz,nEl,nd); + if(nHotABetter!=0)t = t.asprintf("Az: %d A: %d El: %d %d",nAz,nHotAz,nEl,nd); + } else { + t = t.asprintf("Az: %d %d",nAz,nd); + } + if(m_config.miles()) t += " mi"; + if(!m_config.miles()) t += " km"; + ui->labAz->setText (t); + } else { + if (m_hisGrid.size ()) { + m_hisGrid.clear (); + ui->labAz->clear (); + statusUpdate (); + } + } +} + +void MainWindow::on_genStdMsgsPushButton_clicked() //genStdMsgs button +{ + genStdMsgs(m_rpt); +} + +void MainWindow::cease_auto_Tx_after_QSO () +{ + if (SpecOp::FOX != m_config.special_op_id () + && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked ()) + { + // ensure that auto Tx is disabled even if disable Tx + // on 73 is not checked, unless in Fox mode where it is allowed + // to be a robot. + auto_tx_mode (false); + } +} + +void MainWindow::on_logQSOButton_clicked() //Log QSO button +{ + cease_auto_Tx_after_QSO (); + + if (!m_hisCall.size ()) { + MessageBox::warning_message (this, tr ("Warning: DX Call field is empty.")); + } + // m_dateTimeQSOOn should really already be set but we'll ensure it gets set to something just in case + if (!m_dateTimeQSOOn.isValid ()) { + m_dateTimeQSOOn = QDateTime::currentDateTimeUtc(); + } + auto dateTimeQSOOff = QDateTime::currentDateTimeUtc(); + if (dateTimeQSOOff < m_dateTimeQSOOn) dateTimeQSOOff = m_dateTimeQSOOn; + QString grid=m_hisGrid; + if(grid=="....") grid=""; + + switch( m_config.special_op_id() ) + { + case SpecOp::NA_VHF: + m_xSent=m_config.my_grid().left(4); + m_xRcvd=m_hisGrid; + break; + case SpecOp::EU_VHF: + m_rptSent=m_xSent.split(" ").at(0).left(2); + m_rptRcvd=m_xRcvd.split(" ").at(0).left(2); + if(m_xRcvd.split(" ").size()>=2) m_hisGrid=m_xRcvd.split(" ").at(1); + grid=m_hisGrid; + ui->dxGridEntry->setText(grid); + break; + case SpecOp::FIELD_DAY: + m_rptSent=m_xSent.split(" ").at(0); + m_rptRcvd=m_xRcvd.split(" ").at(0); + break; + case SpecOp::RTTY: + m_rptSent=m_xSent.split(" ").at(0); + m_rptRcvd=m_xRcvd.split(" ").at(0); + break; + case SpecOp::WW_DIGI: + m_xSent=m_config.my_grid().left(4); + m_xRcvd=m_hisGrid; + break; + default: break; + } + + m_logDlg->initLogQSO (m_hisCall, grid, m_mode, m_rptSent, m_rptRcvd, + m_dateTimeQSOOn, dateTimeQSOOff, m_freqNominal + + ui->TxFreqSpinBox->value(), m_noSuffix, m_xSent, m_xRcvd); + m_inQSOwith=""; +} + +void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, QString const& grid + , Frequency dial_freq, QString const& mode + , QString const& rpt_sent, QString const& rpt_received + , QString const& tx_power, QString const& comments + , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call + , QString const& my_call, QString const& my_grid + , QString const& exchange_sent, QString const& exchange_rcvd + , QString const& propmode, QByteArray const& ADIF) +{ + QString date = QSO_date_on.toString("yyyyMMdd"); + if (!m_logBook.add (call, grid, m_config.bands()->find(dial_freq), mode, ADIF)) + { + MessageBox::warning_message (this, tr ("Log file error"), + tr ("Cannot open \"%1\"").arg (m_logBook.path ())); + } + + m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received + , tx_power, comments, name, QSO_date_on, operator_call, my_call, my_grid + , exchange_sent, exchange_rcvd, propmode); + m_messageClient->logged_ADIF (ADIF); + + // Log to N1MM Logger + if (m_config.broadcast_to_n1mm () && m_config.valid_n1mm_info ()) + { + QUdpSocket sock; + if (-1 == sock.writeDatagram (ADIF + " " + , QHostAddress {m_config.n1mm_server_name ()} + , m_config.n1mm_server_port ())) + { + MessageBox::warning_message (this, tr ("Error sending log to N1MM"), + tr ("Write returned \"%1\"").arg (sock.errorString ())); + } + } + + if(m_config.clear_DX () and SpecOp::HOUND != m_config.special_op_id()) clearDX (); + m_dateTimeQSOOn = QDateTime {}; + auto special_op = m_config.special_op_id (); + if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) { + ui->sbSerialNumber->setValue(ui->sbSerialNumber->value() + 1); + } + + m_xSent.clear (); + m_xRcvd.clear (); +} + +qint64 MainWindow::nWidgets(QString t) +{ + Q_ASSERT(t.length()==N_WIDGETS); + qint64 n=0; + for(int i=0; itxFirstCheckBox->setVisible(b); + if(i==1) ui->TxFreqSpinBox->setVisible(b); + if(i==2) ui->RxFreqSpinBox->setVisible(b); + if(i==3) ui->sbFtol->setVisible(b); + if(i==4) ui->rptSpinBox->setVisible(b); + if(i==5) ui->sbTR->setVisible(b); + if(i==6) { + ui->sbCQTxFreq->setVisible (b); + ui->cbCQTx->setVisible (b); + auto is_compound = m_config.my_callsign () != m_baseCall; + ui->cbCQTx->setEnabled (b && (!is_compound || shortList (m_config.my_callsign ()))); + } + if(i==7) ui->cbShMsgs->setVisible(b); + if(i==8) ui->cbFast9->setVisible(b); + if(i==9) ui->cbAutoSeq->setVisible(b); + if(i==10) ui->cbTx6->setVisible(b); + // if(i==11) ui->pbTxMode->setVisible(b); + if(i==12) ui->pbR2T->setVisible(b); + if(i==13) ui->pbT2R->setVisible(b); + if(i==14) ui->cbHoldTxFreq->setVisible(b); + if(i==15) ui->sbSubmode->setVisible(b); + if(i==16) ui->syncSpinBox->setVisible(b); + if(i==17) ui->WSPR_controls_widget->setVisible(b); + if(i==18) ui->ClrAvgButton->setVisible(b); + if(i==19) ui->actionQuickDecode->setEnabled(b); + if(i==19) ui->actionMediumDecode->setEnabled(b); + if(i==19) ui->actionDeepestDecode->setEnabled(b); + if(i==20) ui->actionInclude_averaging->setVisible (b); + if(i==21) ui->actionInclude_correlation->setVisible (b); + if(i==22) { + if(!b && m_echoGraph->isVisible()) m_echoGraph->hide(); + } + if(i==23) ui->cbSWL->setVisible(b); + if(i==24) ui->actionEnable_AP_FT8->setVisible (b); + if(i==25) ui->actionEnable_AP_JT65->setVisible (b); + if(i==26) ui->actionEnable_AP_DXcall->setVisible (b); + if(i==27) ui->cbFirst->setVisible(b); + // if(i==28) ui->labNextCall->setVisible(b); + if(i==29) ui->measure_check_box->setVisible(b); + if(i==30) ui->labDXped->setVisible(b); + if(i==31) ui->cbRxAll->setVisible(b); + if(i==32) ui->cbCQonly->setVisible(b); + if(i==33) ui->sbTR_FST4W->setVisible(b); + if (34 == i) // adjust the stacked widget + { + ui->opt_controls_stack->setCurrentIndex (b ? 1 : 0); + ui->sbF_Low->setVisible(b); + } + if(i==35) ui->sbF_High->setVisible(b); + if(i==36) ui->actionAuto_Clear_Avg->setVisible (b); + if(i==37) ui->sbMaxDrift->setVisible(b); + j=j>>1; + } + ui->pbBestSP->setVisible(m_mode=="FT4"); + b=false; + if(m_mode=="FT4" or m_mode=="FT8" || "Q65" == m_mode) { + b=SpecOp::EU_VHF==m_config.special_op_id() or + ( SpecOp::RTTY==m_config.special_op_id() and + (m_config.RTTY_Exchange()=="DX" or m_config.RTTY_Exchange()=="#") ); + } + if(m_mode=="MSK144") b=SpecOp::EU_VHF==m_config.special_op_id(); + ui->sbSerialNumber->setVisible(b); + m_lastCallsign.clear (); // ensures Tx5 is updated for new modes + b=m_mode.startsWith("FST4"); + ui->sbNB->setVisible(b); + genStdMsgs (m_rpt, true); +} + +void MainWindow::on_actionFST4_triggered() +{ + m_mode="FST4"; + m_mode="FST4"; + ui->actionFST4->setChecked(true); + m_bFast9=false; + m_bFastMode=false; + m_fastGraph->hide(); + m_wideGraph->show(); + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize(m_FFTSize); + ui->lh_decodes_title_label->setText(tr ("Band Activity")); + ui->rh_decodes_title_label->setText(tr ("Rx Frequency")); + WSPR_config(false); + if(m_config.single_decode()) { +// 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11111100010011100001000000010000000000")); + m_wideGraph->setSingleDecode(true); + } else { + displayWidgets(nWidgets("11101100010011100001000000010000001100")); + m_wideGraph->setSingleDecode(false); + ui->sbFtol->setValue(20); + } + setup_status_bar(false); + ui->cbAutoSeq->setChecked(true); + m_wideGraph->setMode(m_mode); + m_wideGraph->setPeriod(m_TRperiod,6912); + m_wideGraph->setRxFreq(ui->RxFreqSpinBox->value()); + m_wideGraph->setTol(ui->sbFtol->value()); + m_wideGraph->setTxFreq(ui->TxFreqSpinBox->value()); + m_wideGraph->setFST4_FreqRange(ui->sbF_Low->value(),ui->sbF_High->value()); + chk_FST4_freq_range(); + switch_mode (Modes::FST4); + m_wideGraph->setMode(m_mode); + ui->sbTR->values ({15, 30, 60, 120, 300, 900, 1800}); + on_sbTR_valueChanged (ui->sbTR->value()); + statusChanged(); + m_bOK_to_chk=true; + chk_FST4_freq_range(); +} + +void MainWindow::on_actionFST4W_triggered() +{ + m_mode="FST4W"; + ui->actionFST4W->setChecked(true); + m_bFast9=false; + m_bFastMode=false; + m_fastGraph->hide(); + m_wideGraph->show(); + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize(m_FFTSize); + WSPR_config(true); +// 01234567890123456789012345678901234567 + displayWidgets(nWidgets("00000000000000000101000000000000010000")); + setup_status_bar(false); + ui->band_hopping_group_box->setChecked(false); + ui->band_hopping_group_box->setVisible(false); + on_sbTR_FST4W_valueChanged (ui->sbTR_FST4W->value ()); + ui->WSPRfreqSpinBox->setMinimum(100); + ui->WSPRfreqSpinBox->setMaximum(5000); + m_wideGraph->setMode(m_mode); + m_wideGraph->setPeriod(m_TRperiod,6912); + m_wideGraph->setTxFreq(ui->WSPRfreqSpinBox->value()); + m_wideGraph->setRxFreq(ui->sbFST4W_RxFreq->value()); + m_wideGraph->setTol(ui->sbFST4W_FTol->value()); + ui->sbFtol->setValue(100); + switch_mode (Modes::FST4W); + statusChanged(); +} + +void MainWindow::on_actionFT4_triggered() +{ + m_mode="FT4"; + m_TRperiod=7.5; + bool bVHF=m_config.enable_VHF_features(); + m_bFast9=false; + m_bFastMode=false; + WSPR_config(false); + switch_mode (Modes::FT4); + m_nsps=6912; + m_FFTSize = m_nsps/2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=21; + setup_status_bar (bVHF); + m_toneSpacing=12000.0/576.0; + ui->actionFT4->setChecked(true); + m_wideGraph->setMode(m_mode); + m_send_RR73=true; + VHF_features_enabled(bVHF); + m_fastGraph->hide(); + m_wideGraph->show(); + ui->rh_decodes_headings_label->setText(" UTC dB DT Freq " + tr ("Message")); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + ui->rh_decodes_title_label->setText(tr ("Rx Frequency")); + ui->lh_decodes_title_label->setText(tr ("Band Activity")); + ui->lh_decodes_headings_label->setText( " UTC dB DT Freq " + tr ("Message")); +// 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11101000010011100001000000011000100000")); + ui->txrb2->setEnabled(true); + ui->txrb4->setEnabled(true); + ui->txrb5->setEnabled(true); + ui->txrb6->setEnabled(true); + ui->txb2->setEnabled(true); + ui->txb4->setEnabled(true); + ui->txb5->setEnabled(true); + ui->txb6->setEnabled(true); + ui->txFirstCheckBox->setEnabled(true); + chkFT4(); + statusChanged(); +} + +void MainWindow::on_actionFT8_triggered() +{ + m_mode="FT8"; + bool bVHF=m_config.enable_VHF_features(); + m_bFast9=false; + m_bFastMode=false; + WSPR_config(false); + switch_mode (Modes::FT8); + m_nsps=6912; + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=50; + setup_status_bar (bVHF); + m_toneSpacing=0.0; //??? + ui->actionFT8->setChecked(true); //??? + m_wideGraph->setMode(m_mode); + VHF_features_enabled(bVHF); + ui->cbAutoSeq->setChecked(true); + m_TRperiod=15.0; + m_fastGraph->hide(); + m_wideGraph->show(); + ui->rh_decodes_headings_label->setText(" UTC dB DT Freq " + tr ("Message")); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + ui->rh_decodes_title_label->setText(tr ("Rx Frequency")); + if(SpecOp::FOX==m_config.special_op_id()) { + ui->lh_decodes_title_label->setText(tr ("Stations calling DXpedition %1").arg (m_config.my_callsign())); + ui->lh_decodes_headings_label->setText( "Call Grid dB Freq Dist Age Continent"); + } else { + ui->lh_decodes_title_label->setText(tr ("Band Activity")); + ui->lh_decodes_headings_label->setText( " UTC dB DT Freq " + tr ("Message")); + } +// 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11101000010011100001000010011000100000")); + ui->txrb2->setEnabled(true); + ui->txrb4->setEnabled(true); + ui->txrb5->setEnabled(true); + ui->txrb6->setEnabled(true); + ui->txb2->setEnabled(true); + ui->txb4->setEnabled(true); + ui->txb5->setEnabled(true); + ui->txb6->setEnabled(true); + ui->txFirstCheckBox->setEnabled(true); + ui->cbAutoSeq->setEnabled(true); + if(SpecOp::FOX==m_config.special_op_id()) { + ui->txFirstCheckBox->setChecked(true); + ui->txFirstCheckBox->setEnabled(false); + ui->cbHoldTxFreq->setChecked(true); + ui->cbAutoSeq->setEnabled(false); + ui->tabWidget->setCurrentIndex(1); + ui->TxFreqSpinBox->setValue(300); + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11101000010011100001000000000010000000")); + ui->labDXped->setText(tr ("Fox")); + on_fox_log_action_triggered(); + } + if(SpecOp::HOUND == m_config.special_op_id()) { + ui->txFirstCheckBox->setChecked(false); + ui->txFirstCheckBox->setEnabled(false); + ui->cbAutoSeq->setEnabled(false); + ui->tabWidget->setCurrentIndex(0); + ui->cbHoldTxFreq->setChecked(true); + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11101000010011000001000000000011000000")); + ui->labDXped->setText(tr ("Hound")); + ui->txrb1->setChecked(true); + ui->txrb2->setEnabled(false); + ui->txrb4->setEnabled(false); + ui->txrb5->setEnabled(false); + ui->txrb6->setEnabled(false); + ui->txb2->setEnabled(false); + ui->txb4->setEnabled(false); + ui->txb5->setEnabled(false); + ui->txb6->setEnabled(false); + } + + if (SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()) { + QString t0=""; + if(SpecOp::NA_VHF==m_config.special_op_id()) t0+="NA VHF"; + if(SpecOp::EU_VHF==m_config.special_op_id()) t0+="EU VHF"; + if(SpecOp::FIELD_DAY==m_config.special_op_id()) t0+="Field Day"; + if(SpecOp::RTTY==m_config.special_op_id()) t0+="RTTY"; + if(SpecOp::WW_DIGI==m_config.special_op_id()) t0+="WW_DIGI"; + if(t0=="") { + ui->labDXped->setVisible(false); + } else { + ui->labDXped->setVisible(true); + ui->labDXped->setText(t0); + } + on_contest_log_action_triggered(); + } + + if((SpecOp::FOX==m_config.special_op_id() or SpecOp::HOUND==m_config.special_op_id()) and !m_config.split_mode() and !m_bWarnedSplit) { + QString errorMsg; + MessageBox::critical_message (this, + "Operation in FT8 DXpedition mode normally requires\n" + " *Split* rig control (either *Rig* or *Fake It* on\n" + "the *Settings | Radio* tab.)", errorMsg); + m_bWarnedSplit=true; + } + statusChanged(); +} + +void MainWindow::on_actionJT4_triggered() +{ + m_mode="JT4"; + bool bVHF=m_config.enable_VHF_features(); + WSPR_config(false); + switch_mode (Modes::JT4); + m_TRperiod=60.0; + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=176; + if(m_config.decode_at_52s()) m_hsymStop=184; + m_toneSpacing=0.0; + ui->actionJT4->setChecked(true); + VHF_features_enabled(true); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_wideGraph->setMode(m_mode); + m_bFastMode=false; + m_bFast9=false; + setup_status_bar (bVHF); + ui->sbSubmode->setMaximum(6); + ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes")); + ui->rh_decodes_title_label->setText(tr ("Average Decodes")); + ui->lh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + ui->rh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + if(bVHF) { + ui->sbSubmode->setValue(m_nSubMode); + } else { + ui->sbSubmode->setValue(0); + } + if(bVHF) { + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11111001001011011011110000000000000000")); + } else { + displayWidgets(nWidgets("11101000000011000011000000000000000000")); + } + fast_config(false); + statusChanged(); +} + +void MainWindow::on_actionJT9_triggered() +{ + m_mode="JT9"; + bool bVHF=m_config.enable_VHF_features(); + m_bFast9=ui->cbFast9->isChecked(); + m_bFastMode=m_bFast9; + WSPR_config(false); + switch_mode (Modes::JT9); + m_nsps=6912; + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=173; + if(m_config.decode_at_52s()) m_hsymStop=179; + setup_status_bar (bVHF); + m_toneSpacing=0.0; + ui->actionJT9->setChecked(true); + m_wideGraph->setMode(m_mode); + VHF_features_enabled(bVHF); + if(m_nSubMode>=4 and bVHF) { + ui->cbFast9->setEnabled(true); + } else { + ui->cbFast9->setEnabled(false); + ui->cbFast9->setChecked(false); + } + ui->sbSubmode->setMaximum(7); + if(m_bFast9) { + ui->sbTR->values ({5, 10, 15, 30}); + on_sbTR_valueChanged (ui->sbTR->value()); + m_wideGraph->hide(); + m_fastGraph->showNormal(); + ui->TxFreqSpinBox->setValue(700); + ui->RxFreqSpinBox->setValue(700); + ui->lh_decodes_headings_label->setText(" UTC dB T Freq " + tr ("Message")); + ui->rh_decodes_headings_label->setText(" UTC dB T Freq " + tr ("Message")); + } else { + ui->cbAutoSeq->setChecked(false); + if (m_mode != "FST4") + { + m_TRperiod=60.0; + ui->lh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + ui->rh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + } + } + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + ui->lh_decodes_title_label->setText(tr ("Band Activity")); + ui->rh_decodes_title_label->setText(tr ("Rx Frequency")); + if(bVHF) { + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11111010100011111001000000000000000000")); + } else { + displayWidgets(nWidgets("11101000000011100001000000000000100000")); + } + fast_config(m_bFastMode); + ui->cbAutoSeq->setVisible(m_bFast9); + statusChanged(); +} + +void MainWindow::on_actionJT65_triggered() +{ + on_actionJT9_triggered(); + m_mode="JT65"; + bool bVHF=m_config.enable_VHF_features(); + WSPR_config(false); + switch_mode (Modes::JT65); + m_TRperiod=60.0; + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=174; + if(m_config.decode_at_52s()) m_hsymStop=183; + m_toneSpacing=0.0; + ui->actionJT65->setChecked(true); + VHF_features_enabled(bVHF); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_wideGraph->setMode(m_mode); + m_wideGraph->setRxFreq(ui->RxFreqSpinBox->value()); + m_wideGraph->setTol(ui->sbFtol->value()); + m_wideGraph->setTxFreq(ui->TxFreqSpinBox->value()); + setup_status_bar (bVHF); + m_bFastMode=false; + m_bFast9=false; + ui->sbSubmode->setMaximum(2); + if(bVHF) { + ui->sbSubmode->setValue(m_nSubMode); + ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes")); + ui->rh_decodes_title_label->setText(tr ("Average Decodes")); + } else { + ui->sbSubmode->setValue(0); + ui->lh_decodes_title_label->setText(tr ("Band Activity")); + ui->rh_decodes_title_label->setText(tr ("Rx Frequency")); + } + if(bVHF) { + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11111001000011011010110001000000000000")); + } else { + displayWidgets(nWidgets("11101000000011100001000000000000100000")); + } + fast_config(false); + if(ui->cbShMsgs->isChecked()) { + ui->cbAutoSeq->setChecked(false); + ui->cbAutoSeq->setVisible(false); + } + statusChanged(); +} + +void MainWindow::on_actionQ65_triggered() +{ + m_mode="Q65"; + ui->actionQ65->setChecked(true); + switch_mode(Modes::Q65); + fast_config(false); + WSPR_config(false); + setup_status_bar(true); +// ui->actionQuickDecode->setChecked(true); + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize(m_FFTSize); + m_hsymStop=49; + ui->sbTR->values ({15, 30, 60, 120, 300}); + on_sbTR_valueChanged (ui->sbTR->value()); + ui->sbSubmode->setValue(m_nSubMode); + QString fname {QDir::toNativeSeparators(m_config.temp_dir().absoluteFilePath ("red.dat"))}; + m_wideGraph->setRedFile(fname); + m_wideGraph->setMode(m_mode); + m_wideGraph->setPeriod(m_TRperiod,6912); + m_wideGraph->setTol(ui->sbFtol->value()); + m_wideGraph->setRxFreq(ui->RxFreqSpinBox->value()); + m_wideGraph->setTxFreq(ui->TxFreqSpinBox->value()); + switch_mode (Modes::Q65); +// 01234567890123456789012345678901234567 + displayWidgets(nWidgets("11111101011011010011100000010000000011")); + ui->labDXped->setText(""); + ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes")); + ui->rh_decodes_title_label->setText(tr ("Average Decodes")); + ui->lh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + ui->rh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + statusChanged(); + + if (SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()) { + QString t0=""; + if(SpecOp::NA_VHF==m_config.special_op_id()) t0="NA VHF"; + if(SpecOp::EU_VHF==m_config.special_op_id()) t0="EU VHF"; + if(SpecOp::FIELD_DAY==m_config.special_op_id()) t0="Field Day"; + if(SpecOp::RTTY==m_config.special_op_id()) t0+="RTTY"; + if(SpecOp::WW_DIGI==m_config.special_op_id()) t0+="WW_DIGI"; + if(t0=="") { + ui->labDXped->setVisible(false); + } else { + ui->labDXped->setVisible(true); + ui->labDXped->setText(t0); + } + on_contest_log_action_triggered(); + } + +} + +void MainWindow::on_actionMSK144_triggered() +{ + if(SpecOp::EU_VHF < m_config.special_op_id()) { +// We are rejecting the requested mode change, so re-check the old mode + if("FT8"==m_mode) ui->actionFT8->setChecked(true); + if("JT4"==m_mode) ui->actionJT4->setChecked(true); + if("JT9"==m_mode) ui->actionJT9->setChecked(true); + if("JT65"==m_mode) ui->actionJT65->setChecked(true); + if("Q65"==m_mode) ui->actionQ65->setChecked(true); + if("WSPR"==m_mode) ui->actionWSPR->setChecked(true); + if("Echo"==m_mode) ui->actionEcho->setChecked(true); + if("FreqCal"==m_mode) ui->actionFreqCal->setChecked(true); + if("FST4"==m_mode) ui->actionFST4->setChecked(true); + if("FST4W"==m_mode) ui->actionFST4W->setChecked(true); +// Make sure that MSK144 is not checked. + ui->actionMSK144->setChecked(false); + MessageBox::warning_message (this, tr ("Improper mode"), + "MSK144 not available if Fox, Hound, Field Day, RTTY, or WW Digi contest is selected."); + return; + } + m_mode="MSK144"; + ui->actionMSK144->setChecked(true); + switch_mode (Modes::MSK144); + m_nsps=6; + m_FFTSize = 7 * 512; + Q_EMIT FFTSize (m_FFTSize); + setup_status_bar (true); + m_toneSpacing=0.0; + WSPR_config(false); + VHF_features_enabled(true); + m_bFastMode=true; + m_bFast9=false; + ui->sbTR->values ({5, 10, 15, 30}); + on_sbTR_valueChanged (ui->sbTR->value()); + m_wideGraph->hide(); + m_fastGraph->showNormal(); + ui->TxFreqSpinBox->setValue(1500); + ui->RxFreqSpinBox->setValue(1500); + ui->RxFreqSpinBox->setMinimum(1400); + ui->RxFreqSpinBox->setMaximum(1600); + ui->RxFreqSpinBox->setSingleStep(10); + ui->lh_decodes_headings_label->setText(" UTC dB T Freq " + tr ("Message")); + ui->rh_decodes_headings_label->setText(" UTC dB T Freq " + tr ("Message")); + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_fastGraph->setTRPeriod(m_TRperiod); + ui->lh_decodes_title_label->setText(tr ("Band Activity")); + ui->rh_decodes_title_label->setText(tr ("Tx Messages")); + ui->actionMSK144->setChecked(true); + ui->rptSpinBox->setMinimum(-8); + ui->rptSpinBox->setMaximum(24); + ui->rptSpinBox->setValue(0); + ui->rptSpinBox->setSingleStep(1); + ui->sbFtol->values ({20, 50, 100, 200}); + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("10111111010000000001000100001000000000")); + fast_config(m_bFastMode); + statusChanged(); + + QString t0=""; + if(SpecOp::NA_VHF==m_config.special_op_id()) t0+="NA VHF"; + if(SpecOp::EU_VHF==m_config.special_op_id()) t0+="EU VHF"; + if(t0=="") { + ui->labDXped->setVisible(false); + } else { + ui->labDXped->setVisible(true); + ui->labDXped->setText(t0); + on_contest_log_action_triggered(); + } +} + +void MainWindow::on_actionWSPR_triggered() +{ + m_mode="WSPR"; + WSPR_config(true); + switch_mode (Modes::WSPR); + m_TRperiod=120.0; + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=396; + m_toneSpacing=12000.0/8192.0; + setup_status_bar (false); + ui->actionWSPR->setChecked(true); + VHF_features_enabled(false); + ui->WSPRfreqSpinBox->setMinimum(1400); + ui->WSPRfreqSpinBox->setMaximum(1600); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_wideGraph->setMode(m_mode); + m_bFastMode=false; + m_bFast9=false; + ui->TxFreqSpinBox->setValue(ui->WSPRfreqSpinBox->value()); + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("00000000000000000101000000000000000000")); + fast_config(false); + statusChanged(); +} + +void MainWindow::on_actionEcho_triggered() +{ + on_actionJT4_triggered(); + m_mode="Echo"; + ui->actionEcho->setChecked(true); + m_TRperiod=3.0; + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=9; + m_toneSpacing=1.0; + switch_mode(Modes::Echo); + setup_status_bar (true); + m_wideGraph->setMode(m_mode); + ui->TxFreqSpinBox->setValue(1500); + ui->TxFreqSpinBox->setEnabled (false); + if(!m_echoGraph->isVisible()) m_echoGraph->show(); + if (!ui->actionAstronomical_data->isChecked ()) { + ui->actionAstronomical_data->setChecked (true); + } + m_bFastMode=false; + m_bFast9=false; + WSPR_config(true); + ui->lh_decodes_headings_label->setText(" UTC N Level Sig DF Width Q"); + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("00000000000000000000001000000000000000")); + fast_config(false); + statusChanged(); +} + +void MainWindow::on_actionFreqCal_triggered() +{ + on_actionJT9_triggered(); + m_mode="FreqCal"; + ui->actionFreqCal->setChecked(true); + switch_mode(Modes::FreqCal); + m_wideGraph->setMode(m_mode); + ui->sbTR->values ({5, 10, 15, 30}); + on_sbTR_valueChanged (ui->sbTR->value()); + m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_detector->setTRPeriod(m_TRperiod); // TODO - not thread safe + m_nsps=6912; //For symspec only + m_FFTSize = m_nsps / 2; + Q_EMIT FFTSize (m_FFTSize); + m_hsymStop=((int(m_TRperiod/0.288))/8)*8; + m_frequency_list_fcal_iter = m_config.frequencies ()->begin (); + ui->RxFreqSpinBox->setValue(1500); + setup_status_bar (true); +// 18:15:47 0 1 1500 1550.349 0.100 3.5 10.2 + ui->lh_decodes_headings_label->setText(" UTC Freq CAL Offset fMeas DF Level S/N"); + ui->measure_check_box->setChecked (false); + // 01234567890123456789012345678901234567 + displayWidgets(nWidgets("00110100000000000000000000000100000000")); + statusChanged(); +} + +void MainWindow::switch_mode (Mode mode) +{ + m_fastGraph->setMode(m_mode); + m_config.frequencies ()->filter (m_config.region (), mode); + auto const& row = m_config.frequencies ()->best_working_frequency (m_freqNominal); + ui->bandComboBox->setCurrentIndex (row); + if (row >= 0) { + on_bandComboBox_activated (row); + } + ui->rptSpinBox->setSingleStep(1); + ui->rptSpinBox->setMinimum(-50); + ui->rptSpinBox->setMaximum(49); + ui->sbFtol->values ({1, 2, 5, 10, 20, 50, 100, 200, 300, 400, 500, 1000}); + ui->sbFST4W_FTol->values({1, 2, 5, 10, 20, 50, 100}); + if(m_mode=="MSK144") { + ui->RxFreqSpinBox->setMinimum(1400); + ui->RxFreqSpinBox->setMaximum(1600); + ui->RxFreqSpinBox->setSingleStep(25); + } else { + ui->RxFreqSpinBox->setMinimum(200); + ui->RxFreqSpinBox->setMaximum(5000); + ui->RxFreqSpinBox->setSingleStep(1); + } + bool b=m_mode=="FreqCal"; + ui->tabWidget->setVisible(!b); + if(b) { + ui->DX_controls_widget->setVisible(false); + ui->rh_decodes_widget->setVisible (false); + ui->lh_decodes_title_label->setVisible(false); + } +} + +void MainWindow::WSPR_config(bool b) +{ + ui->rh_decodes_widget->setVisible(!b); + ui->controls_stack_widget->setCurrentIndex (b && m_mode != "Echo" ? 1 : 0); + ui->QSO_controls_widget->setVisible (!b); + ui->DX_controls_widget->setVisible (!b); + ui->WSPR_controls_widget->setVisible (b); + ui->lh_decodes_title_label->setVisible(!b and ui->cbMenus->isChecked()); + ui->logQSOButton->setVisible(!b); + ui->DecodeButton->setEnabled(!b); + bool bFST4W=(m_mode=="FST4W"); + ui->sbTxPercent->setEnabled(!bFST4W or (tr("Random") == ui->RoundRobin->currentText())); + ui->band_hopping_group_box->setVisible(true); + ui->RoundRobin->setVisible(bFST4W); + ui->sbFST4W_RxFreq->setVisible(bFST4W); + ui->sbFST4W_FTol->setVisible(bFST4W); + ui->RoundRobin->lineEdit()->setAlignment(Qt::AlignCenter); + if(b and m_mode!="Echo" and m_mode!="FST4W") { + QString t="UTC dB DT Freq Drift Call Grid dBm "; + if(m_config.miles()) t += " mi"; + if(!m_config.miles()) t += " km"; + ui->lh_decodes_headings_label->setText(t); + if (m_config.is_transceiver_online ()) { + m_config.transceiver_tx_frequency (0); // turn off split + } + m_bSimplex = true; + } else + { + m_bSimplex = false; + } + enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook +} + +void MainWindow::fast_config(bool b) +{ + m_bFastMode=b; + ui->TxFreqSpinBox->setEnabled(!b); + ui->sbTR->setVisible(b); + if(b and (m_bFast9 or m_mode=="MSK144")) { + m_wideGraph->hide(); + m_fastGraph->showNormal(); + } else { + m_wideGraph->showNormal(); + m_fastGraph->hide(); + } +} + +void MainWindow::on_TxFreqSpinBox_valueChanged(int n) +{ + m_wideGraph->setTxFreq(n); +// if (ui->cbHoldTxFreq->isChecked ()) ui->RxFreqSpinBox->setValue(n); + if(m_mode!="MSK144") { + Q_EMIT transmitFrequency (n - m_XIT); + } + + if(m_mode=="Q65") { + if(((m_nSubMode==4 && m_TRperiod==60.0) || (m_nSubMode==3 && m_TRperiod==30.0) || + (m_nSubMode==2 && m_TRperiod==15.0)) && ui->TxFreqSpinBox->value()!=700) { + ui->TxFreqSpinBox->setStyleSheet("QSpinBox{background-color:red}"); + } else { + ui->TxFreqSpinBox->setStyleSheet(""); + } + } + + statusUpdate (); +} + +void MainWindow::on_RxFreqSpinBox_valueChanged(int n) +{ + m_wideGraph->setRxFreq(n); + if (m_mode == "FreqCal") { + setRig (); + } + statusUpdate (); +} + +void MainWindow::on_sbF_Low_valueChanged(int n) +{ + m_wideGraph->setFST4_FreqRange(n,ui->sbF_High->value()); + chk_FST4_freq_range(); +} + +void MainWindow::on_sbF_High_valueChanged(int n) +{ + m_wideGraph->setFST4_FreqRange(ui->sbF_Low->value(),n); + chk_FST4_freq_range(); +} + +void MainWindow::chk_FST4_freq_range() +{ + if(!m_bOK_to_chk) return; + if(ui->sbF_Low->value() < m_wideGraph->nStartFreq()) ui->sbF_Low->setValue(m_wideGraph->nStartFreq()); + if(ui->sbF_High->value() > m_wideGraph->Fmax()) { + int n=m_wideGraph->Fmax()/100; + ui->sbF_High->setValue(100*n); + } + int maxDiff=2000; + if(m_TRperiod==120) maxDiff=1000; + if(m_TRperiod==300) maxDiff=400; + if(m_TRperiod>=900) maxDiff=200; + int diff=ui->sbF_High->value() - ui->sbF_Low->value(); + + if(diff<100 or diff>maxDiff) { + ui->sbF_Low->setStyleSheet("QSpinBox { color: white; background-color: red; }"); + ui->sbF_High->setStyleSheet("QSpinBox { color: white; background-color: red; }"); + } else { + ui->sbF_Low->setStyleSheet(""); + ui->sbF_High->setStyleSheet(""); + } +} + +void MainWindow::on_actionQuickDecode_toggled (bool checked) +{ + m_ndepth ^= (-checked ^ m_ndepth) & 0x00000001; +} + +void MainWindow::on_actionMediumDecode_toggled (bool checked) +{ + m_ndepth ^= (-checked ^ m_ndepth) & 0x00000002; +} + +void MainWindow::on_actionDeepestDecode_toggled (bool checked) +{ + m_ndepth ^= (-checked ^ m_ndepth) & 0x00000003; +} + +void MainWindow::on_actionInclude_averaging_toggled (bool checked) +{ + m_ndepth ^= (-checked ^ m_ndepth) & 0x00000010; +} + +void MainWindow::on_actionInclude_correlation_toggled (bool checked) +{ + m_ndepth ^= (-checked ^ m_ndepth) & 0x00000020; +} + +void MainWindow::on_actionEnable_AP_DXcall_toggled (bool checked) +{ + m_ndepth ^= (-checked ^ m_ndepth) & 0x00000040; +} + +void MainWindow::on_actionAuto_Clear_Avg_toggled (bool checked) +{ + m_ndepth ^= (-checked ^ m_ndepth) & 0x00000080; +} + +void MainWindow::on_actionErase_ALL_TXT_triggered() //Erase ALL.TXT +{ + int ret = MessageBox::query_message (this, tr ("Confirm Erase"), + tr ("Are you sure you want to erase file ALL.TXT?")); + if(ret==MessageBox::Yes) { + QFile f {m_config.writeable_data_dir ().absoluteFilePath ("ALL.TXT")}; + f.remove(); + m_RxLog=1; + } +} + +void MainWindow::on_reset_cabrillo_log_action_triggered () +{ + if (MessageBox::Yes == MessageBox::query_message (this, tr ("Confirm Reset"), + tr ("Are you sure you want to erase your contest log?"), + tr ("Doing this will remove all QSO records for the current contest. " + "They will be kept in the ADIF log file but will not be available " + "for export in your Cabrillo log."))) + { + if(m_config.RTTY_Exchange()!="SCC") ui->sbSerialNumber->setValue(1); + m_logBook.contest_log ()->reset (); + } +} + +void MainWindow::on_actionExport_Cabrillo_log_triggered() +{ + if (QDialog::Accepted == ExportCabrillo {m_settings, &m_config, m_logBook.contest_log ()}.exec()) + { + MessageBox::information_message (this, tr ("Cabrillo Log saved")); + } +} + + +void MainWindow::on_actionErase_wsjtx_log_adi_triggered() +{ + int ret = MessageBox::query_message (this, tr ("Confirm Erase"), + tr ("Are you sure you want to erase file wsjtx_log.adi?")); + if(ret==MessageBox::Yes) { + QFile f {m_config.writeable_data_dir ().absoluteFilePath ("wsjtx_log.adi")}; + f.remove(); + } +} + +void MainWindow::on_actionErase_WSPR_hashtable_triggered() +{ + int ret = MessageBox::query_message(this, tr ("Confirm Erase"), + tr ("Are you sure you want to erase the WSPR hashtable?")); + if(ret==MessageBox::Yes) { + QFile f {m_config.writeable_data_dir().absoluteFilePath("hashtable.txt")}; + f.remove(); + } +} + + +void MainWindow::on_actionOpen_log_directory_triggered () +{ + QDesktopServices::openUrl (QUrl::fromLocalFile (m_config.writeable_data_dir ().absolutePath ())); +} + +void MainWindow::on_bandComboBox_currentIndexChanged (int index) +{ + auto const& frequencies = m_config.frequencies (); + auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2::frequency_column)); + Frequency frequency {m_freqNominal}; + if (source_index.isValid ()) + { + frequency = frequencies->frequency_list ()[source_index.row ()].frequency_; + } + + // Lookup band + auto const& band = m_config.bands ()->find (frequency); + ui->bandComboBox->setCurrentText (band.size () ? band : m_config.bands ()->oob ()); + displayDialFrequency (); +} + +void MainWindow::on_bandComboBox_editTextChanged (QString const& text) +{ + if (text.size () && m_config.bands ()->oob () != text) + { + ui->bandComboBox->lineEdit ()->setStyleSheet ({}); + } + else + { + ui->bandComboBox->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}"); + } +} + +void MainWindow::on_bandComboBox_activated (int index) +{ + auto const& frequencies = m_config.frequencies (); + auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2::frequency_column)); + Frequency frequency {m_freqNominal}; + if (source_index.isValid ()) + { + frequency = frequencies->frequency_list ()[source_index.row ()].frequency_; + } + m_bandEdited = true; + band_changed (frequency); + m_wideGraph->setRxBand (m_config.bands ()->find (frequency)); +} + +void MainWindow::band_changed (Frequency f) +{ + // Set the attenuation value if options are checked + if (m_config.pwrBandTxMemory() && !m_tune) { + auto const&curBand = ui->bandComboBox->currentText(); + if (m_pwrBandTxMemory.contains(curBand)) { + ui->outAttenuation->setValue(m_pwrBandTxMemory[curBand].toInt()); + } + else { + m_pwrBandTxMemory[curBand] = ui->outAttenuation->value(); + } + } + + if (m_bandEdited) { + if (m_mode!="WSPR") { // band hopping preserves auto Tx + if (f + m_wideGraph->nStartFreq () > m_freqNominal + ui->TxFreqSpinBox->value () + || f + m_wideGraph->nStartFreq () + m_wideGraph->fSpan () <= + m_freqNominal + ui->TxFreqSpinBox->value ()) { +// qDebug () << "start f:" << m_wideGraph->nStartFreq () << "span:" << m_wideGraph->fSpan () << "DF:" << ui->TxFreqSpinBox->value (); + // disable auto Tx if "blind" QSY outside of waterfall + ui->stopTxButton->click (); // halt any transmission + auto_tx_mode (false); // disable auto Tx +// m_send_RR73 = false; // force user to reassess on new band + } + } + m_lastBand.clear (); + m_bandEdited = false; + if (m_config.spot_to_psk_reporter ()) + { + // Upload any queued spots before changing band + m_psk_Reporter.sendReport(); + } + if (!m_transmitting) monitor (true); + if ("FreqCal" == m_mode) + { + m_frequency_list_fcal_iter = m_config.frequencies ()->find (f); + } + setRig (f); + setXIT (ui->TxFreqSpinBox->value ()); + } +} + +void MainWindow::enable_DXCC_entity (bool on) +{ + if (on and m_mode!="WSPR" and m_mode!="FST4W" and m_mode!="Echo") { + //m_logBook.init(); // re-read the log and cty.dat files +// ui->gridLayout->setColumnStretch(0,55); // adjust proportions of text displays +// ui->gridLayout->setColumnStretch(1,45); + } else { +// ui->gridLayout->setColumnStretch(0,0); +// ui->gridLayout->setColumnStretch(1,0); + } + updateGeometry (); +} + +void MainWindow::on_rptSpinBox_valueChanged(int n) +{ + int step=ui->rptSpinBox->singleStep(); + if(n%step !=0) { + n++; + ui->rptSpinBox->setValue(n); + } + m_rpt=QString::number(n); + int ntx0=m_ntx; + genStdMsgs(m_rpt); + m_ntx=ntx0; + if(m_ntx==1) ui->txrb1->setChecked(true); + if(m_ntx==2) ui->txrb2->setChecked(true); + if(m_ntx==3) ui->txrb3->setChecked(true); + if(m_ntx==4) ui->txrb4->setChecked(true); + if(m_ntx==5) ui->txrb5->setChecked(true); + if(m_ntx==6) ui->txrb6->setChecked(true); + statusChanged(); +} + +void MainWindow::on_tuneButton_clicked (bool checked) +{ + static bool lastChecked = false; + if (lastChecked == checked) return; + lastChecked = checked; + if (checked && m_tune==false) { // we're starting tuning so remember Tx and change pwr to Tune value + if (m_config.pwrBandTuneMemory ()) { + auto const& curBand = ui->bandComboBox->currentText(); + m_pwrBandTxMemory[curBand] = ui->outAttenuation->value(); // remember our Tx pwr + m_PwrBandSetOK = false; + if (m_pwrBandTuneMemory.contains(curBand)) { + ui->outAttenuation->setValue(m_pwrBandTuneMemory[curBand].toInt()); // set to Tune pwr + } + m_PwrBandSetOK = true; + } + } + if (m_tune) { + tuneButtonTimer.start(250); + } else { + m_sentFirst73=false; + itone[0]=0; + on_monitorButton_clicked (true); + m_tune=true; + } + Q_EMIT tune (checked); +} + +void MainWindow::end_tuning () +{ + on_stopTxButton_clicked (); + // we're turning off so remember our Tune pwr setting and reset to Tx pwr + if (m_config.pwrBandTuneMemory() || m_config.pwrBandTxMemory()) { + auto const& curBand = ui->bandComboBox->currentText(); + m_pwrBandTuneMemory[curBand] = ui->outAttenuation->value(); // remember our Tune pwr + m_PwrBandSetOK = false; + ui->outAttenuation->setValue(m_pwrBandTxMemory[curBand].toInt()); // set to Tx pwr + m_PwrBandSetOK = true; + } +} + +void MainWindow::stop_tuning () +{ + on_tuneButton_clicked(false); + ui->tuneButton->setChecked (false); + m_bTxTime=false; + m_tune=false; +} + +void MainWindow::stopTuneATU() +{ + on_tuneButton_clicked(false); + m_bTxTime=false; +} + +void MainWindow::on_stopTxButton_clicked() //Stop Tx +{ + if (m_tune) stop_tuning (); + if (m_auto and !m_tuneup) auto_tx_mode (false); + m_btxok=false; + m_bCallingCQ = false; + m_bAutoReply = false; // ready for next + ui->cbFirst->setStyleSheet (""); +} + +void MainWindow::rigOpen () +{ + update_dynamic_property (ui->readFreq, "state", "warning"); + ui->readFreq->setText (""); + ui->readFreq->setEnabled (true); + m_config.transceiver_online (); + m_config.sync_transceiver (true, true); +} + +void MainWindow::on_pbR2T_clicked() +{ + ui->TxFreqSpinBox->setValue(ui->RxFreqSpinBox->value ()); +} + +void MainWindow::on_pbT2R_clicked() +{ + if (ui->RxFreqSpinBox->isEnabled ()) + { + ui->RxFreqSpinBox->setValue (ui->TxFreqSpinBox->value ()); + } +} + + +void MainWindow::on_readFreq_clicked() +{ + if (m_transmitting) return; + + if (m_config.transceiver_online ()) + { + m_config.sync_transceiver (true, true); + } +} + +void MainWindow::setXIT(int n, Frequency base) +{ + if (m_transmitting && !m_config.tx_QSY_allowed ()) return; + // If "CQ nnn ..." feature is active, set the proper Tx frequency + if(m_config.split_mode () && ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () && + ui->cbCQTx->isChecked()) + { + if (6 == m_ntx || (7 == m_ntx && m_gen_message_is_cq)) + { + // All conditions are met, use calling frequency + base = m_freqNominal / 1000000 * 1000000 + 1000 * ui->sbCQTxFreq->value () + m_XIT; + } + } + if (!base) base = m_freqNominal; + m_XIT = 0; + if (!m_bSimplex) { + // m_bSimplex is false, so we can use split mode if requested + if (m_config.split_mode () && (!m_config.enable_VHF_features () || + m_mode=="FT4" || m_mode == "FT8" || m_mode=="FST4")) { + // Don't use XIT for VHF & up + m_XIT=(n/500)*500 - 1500; + } + + if ((m_monitoring || m_transmitting) + && m_config.is_transceiver_online () + && m_config.split_mode ()) + { + // All conditions are met, reset the transceiver Tx dial + // frequency + m_freqTxNominal = base + m_XIT; + if (m_astroWidget) m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal); + m_config.transceiver_tx_frequency (m_freqTxNominal + m_astroCorrection.tx); + } + } + //Now set the audio Tx freq + Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT); +} + +void MainWindow::setFreq4(int rxFreq, int txFreq) +{ + if (ui->RxFreqSpinBox->isEnabled ()) ui->RxFreqSpinBox->setValue(rxFreq); + if(m_mode=="WSPR" or m_mode=="FST4W") { + ui->WSPRfreqSpinBox->setValue(txFreq); + } else { + if (ui->TxFreqSpinBox->isEnabled ()) { + ui->TxFreqSpinBox->setValue(txFreq); + if ("FT8" == m_mode || "FT4" == m_mode || m_mode=="FST4") + { + // we need to regenerate the current transmit waveform for + // GFSK modulated modes + if (m_transmitting) m_restart = true; + } + } + else if (m_config.enable_VHF_features () + && (Qt::ControlModifier & QApplication::keyboardModifiers ())) { + // for VHF & up we adjust Tx dial frequency to equalize Tx to Rx + // when user CTRL+clicks on waterfall + auto temp = ui->TxFreqSpinBox->value (); + ui->RxFreqSpinBox->setValue (temp); + setRig (m_freqNominal + txFreq - temp); + setXIT (ui->TxFreqSpinBox->value ()); + } + } +} + +void MainWindow::handle_transceiver_update (Transceiver::TransceiverState const& s) +{ + Transceiver::TransceiverState old_state {m_rigState}; + //transmitDisplay (s.ptt ()); + if (s.ptt () // && !m_rigState.ptt () + ) { // safe to start audio + // (caveat - DX Lab Suite Commander) + if (m_tx_when_ready && g_iptt) { // waiting to Tx and still needed + int ms_delay=1000*m_config.txDelay(); + if(m_mode=="FT4") ms_delay=20; + ptt1Timer.start(ms_delay); //Start-of-transmission sequencer delay + m_tx_when_ready = false; + } + } + m_rigState = s; + auto old_freqNominal = m_freqNominal; + if (!old_freqNominal) + { + // always take initial rig frequency to avoid start up problems + // with bogus Tx frequencies + m_freqNominal = s.frequency (); + } + if (old_state.online () == false && s.online () == true) + { + // initializing + on_monitorButton_clicked (!m_config.monitor_off_at_startup ()); + } + if (s.frequency () != old_state.frequency () || s.split () != m_splitMode) + { + m_splitMode = s.split (); + if (!s.ptt ()) + { + m_freqNominal = s.frequency () - m_astroCorrection.rx; + if (old_freqNominal != m_freqNominal) + { + m_freqTxNominal = m_freqNominal; + genCQMsg (); + } + + if (m_monitoring) + { + m_lastMonitoredFrequency = m_freqNominal; + } + if (m_lastDialFreq != m_freqNominal && + (m_mode != "MSK144" + || !(ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () && ui->cbCQTx->isChecked()))) { + m_lastDialFreq = m_freqNominal; + m_secBandChanged=QDateTime::currentMSecsSinceEpoch()/1000; + pskSetLocal (); + statusChanged(); + m_wideGraph->setDialFreq(m_freqNominal / 1.e6); + } + } else { + m_freqTxNominal = s.split () ? s.tx_frequency () - m_astroCorrection.tx : s.frequency (); + } + if (m_astroWidget) m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal); + } + // ensure frequency display is correct + if (m_astroWidget && old_state.ptt () != s.ptt ()) setRig (); + + displayDialFrequency (); + update_dynamic_property (ui->readFreq, "state", "ok"); + ui->readFreq->setEnabled (false); + ui->readFreq->setText (s.split () ? "S" : ""); +} + +void MainWindow::handle_transceiver_failure (QString const& reason) +{ + update_dynamic_property (ui->readFreq, "state", "error"); + ui->readFreq->setEnabled (true); + on_stopTxButton_clicked (); + rigFailure (reason); +} + +void MainWindow::rigFailure (QString const& reason) +{ + if (m_first_error) + { + // one automatic retry + QTimer::singleShot (0, this, SLOT (rigOpen ())); + m_first_error = false; + } + else + { + if (m_splash && m_splash->isVisible ()) m_splash->hide (); + m_rigErrorMessageBox.setDetailedText (reason + "\n\nTimestamp: " +#if QT_VERSION >= QT_VERSION_CHECK (5, 8, 0) + + QDateTime::currentDateTimeUtc ().toString (Qt::ISODateWithMs) +#else + + QDateTime::currentDateTimeUtc ().toString ("yyyy-MM-ddTHH:mm:ss.zzzZ") +#endif + ); + + // don't call slot functions directly to avoid recursion + m_rigErrorMessageBox.exec (); + auto const clicked_button = m_rigErrorMessageBox.clickedButton (); + if (clicked_button == m_configurations_button) + { + ui->menuConfig->exec (QCursor::pos ()); + } + else + { + switch (m_rigErrorMessageBox.standardButton (clicked_button)) + { + case MessageBox::Ok: + m_config.select_tab (1); + QTimer::singleShot (0, this, SLOT (on_actionSettings_triggered ())); + break; + + case MessageBox::Retry: + QTimer::singleShot (0, this, SLOT (rigOpen ())); + break; + + case MessageBox::Cancel: + QTimer::singleShot (0, this, SLOT (close ())); + break; + + default: break; // squashing compile warnings + } + } + m_first_error = true; // reset + } +} + +void MainWindow::transmit (double snr) +{ + double toneSpacing=0.0; + if (m_mode == "JT65") { + if(m_nSubMode==0) toneSpacing=11025.0/4096.0; + if(m_nSubMode==1) toneSpacing=2*11025.0/4096.0; + if(m_nSubMode==2) toneSpacing=4*11025.0/4096.0; + Q_EMIT sendMessage (m_mode, NUM_JT65_SYMBOLS, + 4096.0*12000.0/11025.0, ui->TxFreqSpinBox->value () - m_XIT, + toneSpacing, m_soundOutput, m_config.audio_output_channel (), + true, false, snr, m_TRperiod); + } + + if (m_mode == "FT8") { +// toneSpacing=12000.0/1920.0; + toneSpacing=-3; + if(m_config.x2ToneSpacing()) toneSpacing=2*12000.0/1920.0; + if(m_config.x4ToneSpacing()) toneSpacing=4*12000.0/1920.0; + if(SpecOp::FOX==m_config.special_op_id() and !m_tune) toneSpacing=-1; + Q_EMIT sendMessage (m_mode, NUM_FT8_SYMBOLS, + 1920.0, ui->TxFreqSpinBox->value () - m_XIT, + toneSpacing, m_soundOutput, m_config.audio_output_channel (), + true, false, snr, m_TRperiod); + } + + if (m_mode == "FT4") { + m_dateTimeSentTx3=QDateTime::currentDateTimeUtc(); + toneSpacing=-2.0; //Transmit a pre-computed, filtered waveform. + Q_EMIT sendMessage (m_mode, NUM_FT4_SYMBOLS, + 576.0, ui->TxFreqSpinBox->value() - m_XIT, + toneSpacing, m_soundOutput, m_config.audio_output_channel(), + true, false, snr, m_TRperiod); + } + + if (m_mode == "FST4" or m_mode == "FST4W") { + m_dateTimeSentTx3=QDateTime::currentDateTimeUtc(); + toneSpacing=-2.0; //Transmit a pre-computed, filtered waveform. + int nsps=720; + if(m_TRperiod==30) nsps=1680; + if(m_TRperiod==60) nsps=3888; + if(m_TRperiod==120) nsps=8200; + if(m_TRperiod==300) nsps=21504; + if(m_TRperiod==900) nsps=66560; + if(m_TRperiod==1800) nsps=134400; + int hmod=1; + if(m_config.x2ToneSpacing()) hmod=2; + if(m_config.x4ToneSpacing()) hmod=4; + double dfreq=hmod*12000.0/nsps; + double f0=ui->WSPRfreqSpinBox->value() - m_XIT; + if(m_mode=="FST4") f0=ui->TxFreqSpinBox->value() - m_XIT; + if(!m_tune) f0 += 1.5*dfreq; + Q_EMIT sendMessage (m_mode, NUM_FST4_SYMBOLS,double(nsps),f0,toneSpacing, + m_soundOutput,m_config.audio_output_channel(), + true, false, snr, m_TRperiod); + } + + if (m_mode == "Q65") { + int nsps=1800; + if(m_TRperiod==30) nsps=3600; + if(m_TRperiod==60) nsps=7200; + if(m_TRperiod==120) nsps=16000; + if(m_TRperiod==300) nsps=41472; + int mode65=pow(2.0,double(m_nSubMode)); + toneSpacing=mode65*12000.0/nsps; +// toneSpacing=-4.0; + Q_EMIT sendMessage (m_mode, NUM_Q65_SYMBOLS, + double(nsps), ui->TxFreqSpinBox->value () - m_XIT, + toneSpacing, m_soundOutput, m_config.audio_output_channel (), + true, false, snr, m_TRperiod); + } + + if (m_mode == "JT9") { + int nsub=pow(2,m_nSubMode); + int nsps[]={480,240,120,60}; + double sps=m_nsps; + m_toneSpacing=nsub*12000.0/6912.0; + if(m_config.x2ToneSpacing()) m_toneSpacing=2.0*m_toneSpacing; + if(m_config.x4ToneSpacing()) m_toneSpacing=4.0*m_toneSpacing; + bool fastmode=false; + if(m_bFast9 and (m_nSubMode>=4)) { + fastmode=true; + sps=nsps[m_nSubMode-4]; + m_toneSpacing=12000.0/sps; + } + Q_EMIT sendMessage (m_mode, NUM_JT9_SYMBOLS, sps, + ui->TxFreqSpinBox->value() - m_XIT, m_toneSpacing, + m_soundOutput, m_config.audio_output_channel (), + true, fastmode, snr, m_TRperiod); + } + + if (m_mode == "MSK144") { + m_nsps=6; + double f0=1000.0; + if(!m_bFastMode) { + m_nsps=192; + f0=ui->TxFreqSpinBox->value () - m_XIT - 0.5*m_toneSpacing; + } + m_toneSpacing=6000.0/m_nsps; + m_FFTSize = 7 * 512; + Q_EMIT FFTSize (m_FFTSize); + int nsym; + nsym=NUM_MSK144_SYMBOLS; + if(itone[40] < 0) nsym=40; + Q_EMIT sendMessage (m_mode, nsym, double(m_nsps), f0, m_toneSpacing, + m_soundOutput, m_config.audio_output_channel (), + true, true, snr, m_TRperiod); + } + + if (m_mode == "JT4") { + if(m_nSubMode==0) toneSpacing=4.375; + if(m_nSubMode==1) toneSpacing=2*4.375; + if(m_nSubMode==2) toneSpacing=4*4.375; + if(m_nSubMode==3) toneSpacing=9*4.375; + if(m_nSubMode==4) toneSpacing=18*4.375; + if(m_nSubMode==5) toneSpacing=36*4.375; + if(m_nSubMode==6) toneSpacing=72*4.375; + Q_EMIT sendMessage (m_mode, NUM_JT4_SYMBOLS, + 2520.0*12000.0/11025.0, ui->TxFreqSpinBox->value () - m_XIT, + toneSpacing, m_soundOutput, m_config.audio_output_channel (), + true, false, snr, m_TRperiod); + } + if (m_mode=="WSPR") { + int nToneSpacing=1; + if(m_config.x2ToneSpacing()) nToneSpacing=2; + if(m_config.x4ToneSpacing()) nToneSpacing=4; + Q_EMIT sendMessage (m_mode, NUM_WSPR_SYMBOLS, 8192.0, + ui->TxFreqSpinBox->value() - 1.5 * 12000 / 8192, + m_toneSpacing*nToneSpacing, m_soundOutput, + m_config.audio_output_channel(),true, false, snr, + m_TRperiod); + } + + if(m_mode=="Echo") { + //??? should use "fastMode = true" here ??? + Q_EMIT sendMessage (m_mode, 27, 1024.0, 1500.0, 0.0, m_soundOutput, + m_config.audio_output_channel(), + false, false, snr, m_TRperiod); + } + +// In auto-sequencing mode, stop after 5 transmissions of "73" message. + if (m_bFastMode || m_bFast9) { + if (ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked ()) { + if(m_ntx==5) { + m_nTx73 += 1; + } else { + m_nTx73=0; + } + } + } +} + +void MainWindow::on_outAttenuation_valueChanged (int a) +{ + QString tt_str; + qreal dBAttn {a / 10.}; // slider interpreted as dB / 100 + if (m_tune && m_config.pwrBandTuneMemory()) { + tt_str = tr ("Tune digital gain "); + } else { + tt_str = tr ("Transmit digital gain "); + } + tt_str += (a ? QString::number (-dBAttn, 'f', 1) : "0") + "dB"; + if (!m_block_pwr_tooltip) { + QToolTip::showText (QCursor::pos (), tt_str, ui->outAttenuation); + } + QString curBand = ui->bandComboBox->currentText(); + if (m_PwrBandSetOK && !m_tune && m_config.pwrBandTxMemory ()) { + m_pwrBandTxMemory[curBand] = a; // remember our Tx pwr + } + if (m_PwrBandSetOK && m_tune && m_config.pwrBandTuneMemory()) { + m_pwrBandTuneMemory[curBand] = a; // remember our Tune pwr + } + Q_EMIT outAttenuationChanged (dBAttn); +} + +void MainWindow::on_actionShort_list_of_add_on_prefixes_and_suffixes_triggered() +{ + if (!m_prefixes) { + m_prefixes.reset (new HelpTextWindow {tr ("Prefixes") + , R"(Type 1 Prefixes: + + 1A 1S 3A 3B6 3B8 3B9 3C 3C0 3D2 3D2C 3D2R 3DA 3V 3W 3X + 3Y 3YB 3YP 4J 4L 4S 4U1I 4U1U 4W 4X 5A 5B 5H 5N 5R + 5T 5U 5V 5W 5X 5Z 6W 6Y 7O 7P 7Q 7X 8P 8Q 8R + 9A 9G 9H 9J 9K 9L 9M2 9M6 9N 9Q 9U 9V 9X 9Y A2 + A3 A4 A5 A6 A7 A9 AP BS7 BV BV9 BY C2 C3 C5 C6 + C9 CE CE0X CE0Y CE0Z CE9 CM CN CP CT CT3 CU CX CY0 CY9 + D2 D4 D6 DL DU E3 E4 EA EA6 EA8 EA9 EI EK EL EP + ER ES ET EU EX EY EZ F FG FH FJ FK FKC FM FO + FOA FOC FOM FP FR FRG FRJ FRT FT5W FT5X FT5Z FW FY M MD + MI MJ MM MU MW H4 H40 HA HB HB0 HC HC8 HH HI HK + HK0A HK0M HL HM HP HR HS HV HZ I IS IS0 J2 J3 J5 + J6 J7 J8 JA JDM JDO JT JW JX JY K KG4 KH0 KH1 KH2 + KH3 KH4 KH5 KH5K KH6 KH7 KH8 KH9 KL KP1 KP2 KP4 KP5 LA LU + LX LY LZ OA OD OE OH OH0 OJ0 OK OM ON OX OY OZ + P2 P4 PA PJ2 PJ7 PY PY0F PT0S PY0T PZ R1F R1M S0 S2 S5 + S7 S9 SM SP ST SU SV SVA SV5 SV9 T2 T30 T31 T32 T33 + T5 T7 T8 T9 TA TF TG TI TI9 TJ TK TL TN TR TT + TU TY TZ UA UA2 UA9 UK UN UR V2 V3 V4 V5 V6 V7 + V8 VE VK VK0H VK0M VK9C VK9L VK9M VK9N VK9W VK9X VP2E VP2M VP2V VP5 + VP6 VP6D VP8 VP8G VP8H VP8O VP8S VP9 VQ9 VR VU VU4 VU7 XE XF4 + XT XU XW XX9 XZ YA YB YI YJ YK YL YN YO YS YU + YV YV0 Z2 Z3 ZA ZB ZC4 ZD7 ZD8 ZD9 ZF ZK1N ZK1S ZK2 ZK3 + ZL ZL7 ZL8 ZL9 ZP ZS ZS8 KC4 E5 + +Type 1 Suffixes: /0 /1 /2 /3 /4 /5 /6 /7 /8 /9 /A /P)", {"Courier", 10}}); + } + m_prefixes->showNormal(); + m_prefixes->raise (); +} + +bool MainWindow::shortList(QString callsign) const +{ + int n=callsign.length(); + int i1=callsign.indexOf("/"); + Q_ASSERT(i1>0 and i1match (stations->index (0, StationList::band_column) + , Qt::DisplayRole + , ui->bandComboBox->currentText () + , 1 + , Qt::MatchExactly); + QString antenna_description; + if (!matches.isEmpty ()) { + antenna_description = stations->index (matches.first ().row () + , StationList::description_column).data ().toString (); + } + // qDebug() << "To PSKreporter: local station details"; + m_psk_Reporter.setLocalStation(m_config.my_callsign (), m_config.my_grid (), antenna_description); +} + +void MainWindow::transmitDisplay (bool transmitting) +{ + if (transmitting == m_transmitting) { + if (transmitting) { + ui->signal_meter_widget->setValue(0,0); + if (m_monitoring) monitor (false); + m_btxok=true; + } + + auto QSY_allowed = !transmitting or m_config.tx_QSY_allowed () or + !m_config.split_mode (); + if (ui->cbHoldTxFreq->isChecked ()) { + ui->TxFreqSpinBox->setEnabled (QSY_allowed); + ui->pbT2R->setEnabled (QSY_allowed); + } + + if (m_mode!="WSPR" and m_mode!="FST4W") { + if(m_config.enable_VHF_features ()) { + ui->TxFreqSpinBox->setEnabled (true); + } else { + ui->TxFreqSpinBox->setEnabled (QSY_allowed and !m_bFastMode); + ui->pbR2T->setEnabled (QSY_allowed); + ui->cbHoldTxFreq->setEnabled (QSY_allowed); + } + } + + // the following are always disallowed in transmit + ui->menuMode->setEnabled (!transmitting); + } +} + +void MainWindow::on_sbFtol_valueChanged(int value) +{ + m_wideGraph->setTol (value); + statusUpdate (); +} + +void::MainWindow::VHF_features_enabled(bool b) +{ + if(m_mode!="JT4" and m_mode!="JT65" and m_mode!="Q65") b=false; + if(b and m_mode!="Q65" and (ui->actionInclude_averaging->isChecked() or + ui->actionInclude_correlation->isChecked())) { + ui->actionDeepestDecode->setChecked (true); + } + ui->actionInclude_averaging->setVisible (b); + ui->actionInclude_correlation->setVisible (b && m_mode!="Q65"); + ui->actionMessage_averaging->setEnabled(b && (m_mode=="JT4" or m_mode=="JT65")); + ui->actionEnable_AP_JT65->setVisible (b && m_mode=="JT65"); + + if(!b && m_msgAvgWidget and (SpecOp::FOX != m_config.special_op_id()) and !m_config.autoLog()) { + if(m_msgAvgWidget->isVisible() and m_mode!="JT4" and m_mode!="JT9" and m_mode!="JT65") { + m_msgAvgWidget->close(); + } + } +} + +void MainWindow::on_sbTR_valueChanged(int value) +{ + // if(!m_bFastMode and n>m_nSubMode) m_MinW=m_nSubMode; + if(m_bFastMode or m_mode=="FreqCal" or m_mode=="FST4" or m_mode=="FST4W" or m_mode=="Q65") { + m_TRperiod = value; + if (m_mode == "FST4" || m_mode == "FST4W" || m_mode=="Q65") + { + if (m_TRperiod < 60) + { + ui->lh_decodes_headings_label->setText(" UTC dB DT Freq " + tr ("Message")); + if (m_mode != "FST4W") + { + ui->rh_decodes_headings_label->setText(" UTC dB DT Freq " + tr ("Message")); + } + } + else + { + ui->lh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + if (m_mode != "FST4W") + { + ui->rh_decodes_headings_label->setText("UTC dB DT Freq " + tr ("Message")); + } + } + + if ("Q65" == m_mode) + { + switch (value) + { + case 15: ui->sbSubmode->setMaximum (2); break; + case 30: ui->sbSubmode->setMaximum (3); break; + default: ui->sbSubmode->setMaximum (4); break; + } + } + } + m_fastGraph->setTRPeriod (value); + m_modulator->setTRPeriod (value); // TODO - not thread safe + m_detector->setTRPeriod (value); // TODO - not thread safe + m_wideGraph->setPeriod (value, m_nsps); + progressBar.setMaximum (value); + } + if(m_mode=="FST4") chk_FST4_freq_range(); +// if(m_transmitting) on_stopTxButton_clicked(); //### Is this needed or desirable? ### + on_sbSubmode_valueChanged(ui->sbSubmode->value()); + statusUpdate (); +} + +void MainWindow::on_sbTR_FST4W_valueChanged(int value) +{ + on_sbTR_valueChanged(value); +} + +QChar MainWindow::current_submode () const +{ + QChar submode {0}; + if (m_mode.contains (QRegularExpression {R"(^(JT65|JT9|JT4|Q65)$)"}) + && (m_config.enable_VHF_features () || "JT4" == m_mode)) + { + submode = m_nSubMode + 65; + } + return submode; +} + +void MainWindow::on_sbSubmode_valueChanged(int n) +{ + m_nSubMode=n; + m_wideGraph->setSubMode(m_nSubMode); + auto submode = current_submode (); + if (submode != QChar::Null) { + QString t{m_mode + " " + submode}; + if(m_mode=="Q65") t=m_mode + "-" + QString::number(m_TRperiod) + submode; + mode_label.setText (t); + } else { + mode_label.setText (m_mode); + } + if(m_mode=="Q65") { + if(((m_nSubMode==4 && m_TRperiod==60.0) || (m_nSubMode==3 && m_TRperiod==30.0) || + (m_nSubMode==2 && m_TRperiod==15.0)) && ui->TxFreqSpinBox->value()!=700) { + ui->TxFreqSpinBox->setStyleSheet("QSpinBox{background-color:red}"); + } else { + ui->TxFreqSpinBox->setStyleSheet(""); + } + } + if(m_mode=="JT9") { + if(m_nSubMode<4) { + ui->cbFast9->setChecked(false); + on_cbFast9_clicked(false); + ui->cbFast9->setEnabled(false); + ui->sbTR->setVisible(false); + m_TRperiod=60.0; + } else { + ui->cbFast9->setEnabled(true); + } + ui->sbTR->setVisible(m_bFast9); + if(m_bFast9) ui->TxFreqSpinBox->setValue(700); + } + if(m_transmitting and m_bFast9 and m_nSubMode>=4) transmit (99.0); + statusUpdate (); +} + +void MainWindow::on_cbFast9_clicked(bool b) +{ + if(m_mode=="JT9") { + m_bFast9=b; +// ui->cbAutoSeq->setVisible(b); + on_actionJT9_triggered(); + } + + if(b) { + m_TRperiod = ui->sbTR->value (); + } else { + m_TRperiod=60.0; + } + progressBar.setMaximum(int(m_TRperiod)); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + fast_config(b); + statusChanged (); +} + + +void MainWindow::on_cbShMsgs_toggled(bool b) +{ + ui->cbTx6->setEnabled(b); + m_bShMsgs=b; + if(b) ui->cbSWL->setChecked(false); + if(m_bShMsgs and (m_mode=="MSK144")) ui->rptSpinBox->setValue(1); + int it0=itone[0]; + int ntx=m_ntx; + m_lastCallsign.clear (); // ensure Tx5 gets updated + genStdMsgs(m_rpt); + itone[0]=it0; + if(ntx==1) ui->txrb1->setChecked(true); + if(ntx==2) ui->txrb2->setChecked(true); + if(ntx==3) ui->txrb3->setChecked(true); + if(ntx==4) ui->txrb4->setChecked(true); + if(ntx==5) ui->txrb5->setChecked(true); + if(ntx==6) ui->txrb6->setChecked(true); +} + +void MainWindow::on_cbSWL_toggled(bool b) +{ + if(b) ui->cbShMsgs->setChecked(false); +} + +void MainWindow::on_cbTx6_toggled(bool) +{ + genCQMsg (); +} + +// Takes a decoded CQ line and sets it up for reply +void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 delta_frequency + , QString const& mode, QString const& message_text + , bool /*low_confidence*/, quint8 modifiers) +{ + QString format_string {"%1 %2 %3 %4 %5 %6"}; + auto const& time_string = time.toString ("~" == mode || "&" == mode || "+" == mode + || (m_TRperiod < 60. && ("`" == mode || ":" == mode)) + ? "hhmmss" : "hhmm"); + auto text = message_text; + auto ap_pos = text.lastIndexOf (QRegularExpression {R"((?:\?\s)?(?:a[0-9]|q[0-9][0-9]?)$)"}); + if (ap_pos >= 0) + { + // beware of decodes ending on shorter version of wanted call so + // add a space + text = text.left (ap_pos).trimmed () + ' '; + } + auto message_line = format_string + .arg (time_string) + .arg (snr, 3) + .arg (delta_time, 4, 'f', 1) + .arg (delta_frequency, 4) + .arg (mode, -2) + .arg (text); + QTextCursor start {ui->decodedTextBrowser->document ()}; + start.movePosition (QTextCursor::End); + auto cursor = ui->decodedTextBrowser->document ()->find (message_line, start, QTextDocument::FindBackward); + if (cursor.isNull ()) + { + // try again with with -0.0 delta time + cursor = ui->decodedTextBrowser->document ()->find (format_string + .arg (time_string) + .arg (snr, 3) + .arg ('-' + QString::number (delta_time, 'f', 1), 4) + .arg (delta_frequency, 4) + .arg (mode, -2) + .arg (text), start, QTextDocument::FindBackward); + } + if (!cursor.isNull ()) + { + if (m_config.udpWindowToFront ()) + { + show (); + raise (); + activateWindow (); + } + if (m_config.udpWindowRestore () && isMinimized ()) + { + showNormal (); + raise (); + } + if (text.contains (QRegularExpression {R"(^(CQ |CQDX |QRZ ))"})) { + // a message we are willing to accept and auto reply to + m_bDoubleClicked = true; + } + DecodedText message {message_line}; + Qt::KeyboardModifiers kbmod {modifiers << 24}; + processMessage (message, kbmod); + tx_watchdog (false); + QApplication::alert (this); + } + else + { + qDebug () << "process reply message ignored, decode not found:" << message_line; + } +} + +void MainWindow::locationChange (QString const& location) +{ + QString grid {location.trimmed ()}; + int len; + + // string 6 chars or fewer, interpret as a grid, or use with a 'GRID:' prefix + if (grid.size () > 6) { + if (grid.toUpper ().startsWith ("GRID:")) { + grid = grid.mid (5).trimmed (); + } + else { + // TODO - support any other formats, e.g. latlong? Or have that conversion done external to wsjtx + return; + } + } + if (MaidenheadLocatorValidator::Acceptable == MaidenheadLocatorValidator ().validate (grid, len)) { + qDebug() << "locationChange: Grid supplied is " << grid; + if (m_config.my_grid () != grid) { + m_config.set_location (grid); + genStdMsgs (m_rpt, false); + statusUpdate (); + } + } else { + qDebug() << "locationChange: Invalid grid " << grid; + } +} + +void MainWindow::replayDecodes () +{ + // we accept this request even if the setting to accept UDP requests + // is not checked + + // attempt to parse the decoded text + for (QTextBlock block = ui->decodedTextBrowser->document ()->firstBlock (); block.isValid (); block = block.next ()) + { + auto message = block.text (); + message = message.left (message.indexOf (QChar::Nbsp)); // discard + // any + // appended info + if (message.size() >= 4 && message.left (4) != "----") + { + auto const& parts = message.split (' ', SkipEmptyParts); + if (parts.size () >= 5 && parts[3].contains ('.')) { + postWSPRDecode (false, parts); + } else { + postDecode (false, message); + } + } + } + statusChanged (); +} + +void MainWindow::postDecode (bool is_new, QString const& message) +{ + auto const& decode = message.trimmed (); + auto const& parts = decode.left (22).split (' ', SkipEmptyParts); + if (parts.size () >= 5) + { + auto has_seconds = parts[0].size () > 4; + m_messageClient->decode (is_new + , QTime::fromString (parts[0], has_seconds ? "hhmmss" : "hhmm") + , parts[1].toInt () + , parts[2].toFloat (), parts[3].toUInt (), parts[4] + , decode.mid (has_seconds ? 24 : 22) + , QChar {'?'} == decode.mid (has_seconds ? 24 + 36 : 22 + 36, 1) + , m_diskData); + } +} + +void MainWindow::postWSPRDecode (bool is_new, QStringList parts) +{ + if (parts.size () < 8) + { + parts.insert (6, ""); + } + m_messageClient->WSPR_decode (is_new, QTime::fromString (parts[0], "hhmm"), parts[1].toInt () + , parts[2].toFloat (), Radio::frequency (parts[3].toFloat (), 6) + , parts[4].toInt (), parts[5], parts[6], parts[7].toInt () + , m_diskData); +} + +void MainWindow::networkError (QString const& e) +{ + if (m_splash && m_splash->isVisible ()) m_splash->hide (); + if (MessageBox::Retry == MessageBox::warning_message (this, tr ("Network Error") + , tr ("Error: %1\nUDP server %2:%3") + .arg (e) + .arg (m_config.udp_server_name ()) + .arg (m_config.udp_server_port ()) + , QString {} + , MessageBox::Cancel | MessageBox::Retry + , MessageBox::Cancel)) + { + // retry server lookup + m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_names ()); + } +} + +void MainWindow::on_syncSpinBox_valueChanged(int n) +{ + m_minSync=n; +} + +void MainWindow::p1ReadFromStdout() //p1readFromStdout +{ + QString t1; + while(p1.canReadLine()) { + QString t(p1.readLine()); + if(ui->cbNoOwnCall->isChecked()) { + if(t.contains(" " + m_config.my_callsign() + " ")) continue; + if(t.contains(" <" + m_config.my_callsign() + "> ")) continue; + } + if(t.indexOf("") >= 0) { + m_bDecoded = m_nWSPRdecodes > 0; + if(!m_diskData) { + WSPR_history(m_dialFreqRxWSPR, m_nWSPRdecodes); + if(m_nWSPRdecodes==0 and ui->band_hopping_group_box->isChecked()) { + t = " " + tr ("Receiving") + " " + m_mode + " ----------------------- " + + m_config.bands ()->find (m_dialFreqRxWSPR); + t=beacon_start_time (-m_TRperiod / 2) + ' ' + t.rightJustified (66, '-'); + ui->decodedTextBrowser->appendText(t); + } + killFileTimer.start (45*1000); //Kill in 45s (for slow modes) + } + m_nWSPRdecodes=0; + ui->DecodeButton->setChecked (false); + if (m_uploadWSPRSpots + && m_config.is_transceiver_online ()) { // need working rig control +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + uploadTimer.start(QRandomGenerator::global ()->bounded (0, 20000)); // Upload delay +#else + uploadTimer.start(20000 * qrand()/((double)RAND_MAX + 1.0)); // Upload delay +#endif + } else { + QFile f {QDir::toNativeSeparators (m_config.writeable_data_dir ().absoluteFilePath ("wspr_spots.txt"))}; + if (f.exists ()) f.remove (); + } + m_RxLog=0; + m_startAnother=m_loopall; + m_decoderBusy = false; + statusUpdate (); + } else { + + int n=t.length(); + t=t.mid(0,n-2) + " "; + t.remove(QRegExp("\\s+$")); + QStringList rxFields = t.split(QRegExp("\\s+")); + QString rxLine; + QString grid=""; + if ( rxFields.count() == 8 ) { + rxLine = QString("%1 %2 %3 %4 %5 %6 %7 %8") + .arg(rxFields.at(0), 4) + .arg(rxFields.at(1), 4) + .arg(rxFields.at(2), 5) + .arg(rxFields.at(3), 11) + .arg(rxFields.at(4), 4) + .arg(rxFields.at(5).leftJustified (12)) + .arg(rxFields.at(6), -6) + .arg(rxFields.at(7), 3); + postWSPRDecode (true, rxFields); + grid = rxFields.at(6); + } else if ( rxFields.count() == 7 ) { // Type 2 message + rxLine = QString("%1 %2 %3 %4 %5 %6 %7 %8") + .arg(rxFields.at(0), 4) + .arg(rxFields.at(1), 4) + .arg(rxFields.at(2), 5) + .arg(rxFields.at(3), 11) + .arg(rxFields.at(4), 4) + .arg(rxFields.at(5).leftJustified (12)) + .arg("", -6) + .arg(rxFields.at(6), 3); + postWSPRDecode (true, rxFields); + } else { + rxLine = t; + } + if(grid!="") { + double utch=0.0; + int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter; + azdist_(const_cast ((m_config.my_grid () + " ").left (6).toLatin1 ().constData ()), + const_cast ((grid + " ").left (6).toLatin1 ().constData ()),&utch, + &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6); + QString t1; + if(m_config.miles()) { + t1 = t1.asprintf("%7d",nDmiles); + } else { + t1 = t1.asprintf("%7d",nDkm); + } + rxLine += t1; + } + + if (rxLine.left (4) != m_tBlankLine) { + ui->decodedTextBrowser->new_period (); + if (m_config.insert_blank ()) { + QString band; + Frequency f=1000000.0*rxFields.at(3).toDouble()+0.5; + band = ' ' + m_config.bands ()->find (f); + ui->decodedTextBrowser->appendText(band.rightJustified (71, '-')); + } + m_tBlankLine = rxLine.left(4); + } + m_nWSPRdecodes += 1; + ui->decodedTextBrowser->appendText(rxLine); + } + } +} + +QString MainWindow::beacon_start_time (int n) +{ + auto bt = qt_truncate_date_time_to (QDateTime::currentDateTimeUtc ().addSecs (n), m_TRperiod * 1.e3); + if (m_TRperiod < 60.) + { + return bt.toString ("HHmmss"); + } + else + { + return bt.toString ("HHmm"); + } +} + +void MainWindow::WSPR_history(Frequency dialFreq, int ndecodes) +{ + QDateTime t=QDateTime::currentDateTimeUtc().addSecs(-60); + QString t1=t.toString("yyMMdd"); + QString t2=beacon_start_time (-m_TRperiod / 2); + QString t3; + t3 = t3.asprintf("%13.6f",0.000001*dialFreq); + if(ndecodes<0) { + t1=t1 + " " + t2 + t3 + " T"; + } else { + QString t4; + t4 = t4.asprintf("%4d",ndecodes); + t1=t1 + " " + t2 + t3 + " R" + t4; + } + QFile f {m_config.writeable_data_dir ().absoluteFilePath ("WSPR_history.txt")}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QTextStream out(&f); + out << t1 +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; + f.close(); + } else { + MessageBox::warning_message (this, tr ("File Error") + , tr ("Cannot open \"%1\" for append: %2") + .arg (f.fileName ()).arg (f.errorString ())); + } +} + +void MainWindow::uploadWSPRSpots (bool direct_post, QString const& decode_text) +{ + // do not spot if disabled, replays, or if rig control not working + if(!m_uploadWSPRSpots || m_diskData || !m_config.is_transceiver_online ()) return; + if(m_uploading && !decode_text.size ()) { + qDebug() << "Previous upload has not completed, spots were lost"; + wsprNet->abortOutstandingRequests (); + m_uploading = false; + } + QString rfreq = QString("%1").arg((m_dialFreqRxWSPR + m_wideGraph->rxFreq ()) / 1e6, 0, 'f', 6); + QString tfreq = QString("%1").arg((m_dialFreqRxWSPR + + ui->TxFreqSpinBox->value()) / 1e6, 0, 'f', 6); + auto pct = QString::number (ui->autoButton->isChecked () ? ui->sbTxPercent->value () : 0); + if (direct_post) + { + // queues one FST4W spot + wsprNet->post (m_config.my_callsign (), m_config.my_grid (), rfreq, tfreq, + m_mode, m_TRperiod, pct, + QString::number (m_dBm), version (), decode_text); + } + else + { + // queues spots for each decode in wspr_spots.txt + wsprNet->upload (m_config.my_callsign (), m_config.my_grid (), rfreq, tfreq, + m_mode, m_TRperiod, pct, + QString::number (m_dBm), version (), + m_config.writeable_data_dir ().absoluteFilePath ("wspr_spots.txt")); + } + // trigger upload of any queued spots + if (!decode_text.size ()) + { + m_uploading = true; + } +} + +void MainWindow::uploadResponse(QString const& response) +{ + if (response == "done") { + m_uploading=false; + } else { + if (response.startsWith ("Upload Failed")) { + m_uploading=false; + } + qDebug () << "WSPRnet.org status:" << response; + } +} + +void MainWindow::on_TxPowerComboBox_currentIndexChanged(int index) +{ + m_dBm = ui->TxPowerComboBox->itemData (index).toInt (); +} + +void MainWindow::on_cbUploadWSPR_Spots_toggled(bool b) +{ + m_uploadWSPRSpots=b; +} + +void MainWindow::on_WSPRfreqSpinBox_valueChanged(int n) +{ + ui->TxFreqSpinBox->setValue(n); +} + +void MainWindow::on_sbFST4W_RxFreq_valueChanged(int n) +{ + m_wideGraph->setRxFreq(n); + statusUpdate (); +} + +void MainWindow::on_sbFST4W_FTol_valueChanged(int n) +{ + ui->sbFST4W_RxFreq->setSingleStep(n); + m_wideGraph->setTol(n); + statusUpdate (); +} + +void MainWindow::on_pbTxNext_clicked(bool b) +{ + if (b && !ui->autoButton->isChecked ()) + { + ui->autoButton->click (); // make sure Tx is possible + } +} + +void MainWindow::WSPR_scheduling () +{ + if (ui->pbTxNext->isEnabled () && ui->pbTxNext->isChecked ()) + { + // Tx Next button overrides all scheduling + m_WSPR_tx_next = true; + return; + } + QString t=ui->RoundRobin->currentText(); + if(m_mode=="FST4W" and t != tr ("Random")) { + bool ok; + int i=t.left (1).toInt (&ok) - 1; + if (!ok) return; + int n=t.right (1).toInt (&ok); + if (!ok || 0 == n) return; + + qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; + int nsec=ms/1000; + int ntr=m_TRperiod; + int j=((nsec+ntr-1) % (n*ntr))/ntr; + m_WSPR_tx_next = i == j; + return; + } + m_WSPR_tx_next = false; + if (!ui->sbTxPercent->isEnabled ()) + { + return; // don't schedule if %age disabled + } + if (m_config.is_transceiver_online () // need working rig control for hopping + && !m_config.is_dummy_rig () + && ui->band_hopping_group_box->isChecked ()) { + auto hop_data = m_WSPR_band_hopping.next_hop (m_auto); + qDebug () << "hop data: period:" << hop_data.period_name_ + << "frequencies index:" << hop_data.frequencies_index_ + << "tune:" << hop_data.tune_required_ + << "tx:" << hop_data.tx_next_; + m_WSPR_tx_next = hop_data.tx_next_; + if (hop_data.frequencies_index_ >= 0) { // new band + ui->bandComboBox->setCurrentIndex (hop_data.frequencies_index_); + on_bandComboBox_activated (hop_data.frequencies_index_); + // Execute user's hardware controller + auto band = m_config.bands ()->find (m_freqNominal).remove ('m'); +#if defined(Q_OS_WIN) + // On windows we use CMD.EXE to find and execute the + // user_hardware executable. This means that the first matching + // file extension on the PATHEXT environment variable found on + // the PATH environment variable path list. This give maximum + // flexibility for users to write user_hardware in their + // language of choice, and place the file anywhere on the PATH + // environment variable. Equivalent to typing user_hardware + // without any path or extension at the CMD.EXE prompt. + p3.start("CMD", QStringList {"/C", "user_hardware", band}); +#else + // On non-Windows systems we expect the user_hardware executable + // to be anywhere in the paths specified in the PATH environment + // variable path list, and executable. Equivalent to typing + // user_hardware without any path at the shell prompt. + p3.start("/bin/sh", QStringList {"-c", "user_hardware " + band}); +#endif + + // Produce a short tuneup signal + m_tuneup = false; + if (hop_data.tune_required_) { + m_tuneup = true; + on_tuneButton_clicked (true); + tuneATU_Timer.start (2500); + } + } + + // Display grayline status + band_hopping_label.setText (hop_data.period_name_); + } + else { + m_WSPR_tx_next = m_WSPR_band_hopping.next_is_tx(m_mode=="FST4W"); + } +} + +void MainWindow::astroUpdate () +{ + if (m_astroWidget) + { + // no Doppler correction while CTRL pressed allows manual tuning + if (Qt::ControlModifier & QApplication::queryKeyboardModifiers ()) return; + + auto correction = m_astroWidget->astroUpdate(QDateTime::currentDateTimeUtc (), + m_config.my_grid(), m_hisGrid, + m_freqNominal, + "Echo" == m_mode, m_transmitting, + !m_config.tx_QSY_allowed (), m_TRperiod); + // no Doppler correction in Tx if rig can't do it + if (m_transmitting && !m_config.tx_QSY_allowed ()) return; + if (!m_astroWidget->doppler_tracking ()) return; + if ((m_monitoring || m_transmitting) + // no Doppler correction below 6m + && m_freqNominal >= 50000000 + && m_config.split_mode ()) + { + // adjust for rig resolution + if (m_config.transceiver_resolution () > 2) + { + correction.rx = (correction.rx + 50) / 100 * 100; + correction.tx = (correction.tx + 50) / 100 * 100; + } + else if (m_config.transceiver_resolution () > 1) + { + correction.rx = (correction.rx + 10) / 20 * 20; + correction.tx = (correction.tx + 10) / 20 * 20; + } + else if (m_config.transceiver_resolution () > 0) + { + correction.rx = (correction.rx + 5) / 10 * 10; + correction.tx = (correction.tx + 5) / 10 * 10; + } + else if (m_config.transceiver_resolution () < -2) + { + correction.rx = correction.rx / 100 * 100; + correction.tx = correction.tx / 100 * 100; + } + else if (m_config.transceiver_resolution () < -1) + { + correction.rx = correction.rx / 20 * 20; + correction.tx = correction.tx / 20 * 20; + } + else if (m_config.transceiver_resolution () < 0) + { + correction.rx = correction.rx / 10 * 10; + correction.tx = correction.tx / 10 * 10; + } + m_astroCorrection = correction; + if (m_reverse_Doppler) + { + m_astroCorrection.reverse (); + } + } + else + { + m_astroCorrection = {}; + } + + setRig (); + } +} + +void MainWindow::setRig (Frequency f) +{ + if (f) + { + m_freqNominal = f; + genCQMsg (); + m_freqTxNominal = m_freqNominal; + if (m_astroWidget) m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal); + } + if (m_mode == "FreqCal" + && m_frequency_list_fcal_iter != m_config.frequencies ()->end ()) { + m_freqNominal = m_frequency_list_fcal_iter->frequency_ - ui->RxFreqSpinBox->value (); + } + if(m_transmitting && !m_config.tx_QSY_allowed ()) return; + if ((m_monitoring || m_transmitting) && m_config.transceiver_online ()) + { + if (m_transmitting && m_config.split_mode ()) + { + m_config.transceiver_tx_frequency (m_freqTxNominal + m_astroCorrection.tx); + } + else + { + m_config.transceiver_frequency (m_freqNominal + m_astroCorrection.rx); + } + } +} + +void MainWindow::fastPick(int x0, int x1, int y) +{ + float pixPerSecond=12000.0/512.0; + if(m_TRperiod<30.0) pixPerSecond=12000.0/256.0; + if(m_mode!="MSK144") return; + if(!m_decoderBusy) { + dec_data.params.newdat=0; + dec_data.params.nagain=1; + m_nPick=1; + if(y > 120) m_nPick=2; + m_t0Pick=x0/pixPerSecond; + m_t1Pick=x1/pixPerSecond; + m_dataAvailable=true; + decode(); + } +} + +void MainWindow::on_actionMeasure_reference_spectrum_triggered() +{ + if(!m_monitoring) on_monitorButton_clicked (true); + m_bRefSpec=true; +} + +void MainWindow::on_actionMeasure_phase_response_triggered() +{ + if(m_bTrain) { + m_bTrain=false; + MessageBox::information_message (this, tr ("Phase Training Disabled")); + } else { + m_bTrain=true; + MessageBox::information_message (this, tr ("Phase Training Enabled")); + } +} + +void MainWindow::on_actionErase_reference_spectrum_triggered() +{ + m_bClearRefSpec=true; +} + +void MainWindow::freqCalStep() +{ + if (m_frequency_list_fcal_iter == m_config.frequencies ()->end () + || ++m_frequency_list_fcal_iter == m_config.frequencies ()->end ()) { + m_frequency_list_fcal_iter = m_config.frequencies ()->begin (); + } + + // allow for empty list + if (m_frequency_list_fcal_iter != m_config.frequencies ()->end ()) { + setRig (m_frequency_list_fcal_iter->frequency_ - ui->RxFreqSpinBox->value ()); + } +} + +void MainWindow::on_sbCQTxFreq_valueChanged(int) +{ + setXIT (ui->TxFreqSpinBox->value ()); +} + +void MainWindow::on_cbCQTx_toggled(bool b) +{ + ui->sbCQTxFreq->setEnabled(b); + genCQMsg(); + if(b) { + ui->txrb6->setChecked(true); + m_ntx=6; + m_QSOProgress = CALLING; + } + setRig (); + setXIT (ui->TxFreqSpinBox->value ()); +} + +void MainWindow::statusUpdate () const +{ + if (!ui || m_block_udp_status_updates) return; + auto submode = current_submode (); + auto ftol = ui->sbFtol->value (); + if ("FST4W" == m_mode) + { + ftol = ui->sbFST4W_FTol->value (); + } + else if (!(ui->sbFtol->isVisible () && ui->sbFtol->isEnabled ())) + { + ftol = quint32_max; + } + auto tr_period = ui->sbTR->value (); + auto rx_frequency = ui->RxFreqSpinBox->value (); + if ("FST4W" == m_mode) + { + tr_period = ui->sbTR_FST4W->value (); + rx_frequency = ui->sbFST4W_RxFreq->value (); + } + else if (!(ui->sbTR->isVisible () && ui->sbTR->isEnabled ())) + { + tr_period = quint32_max; + } + m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall, + QString::number (ui->rptSpinBox->value ()), + m_mode, ui->autoButton->isChecked (), + m_transmitting, m_decoderBusy, + rx_frequency, ui->TxFreqSpinBox->value (), + m_config.my_callsign (), m_config.my_grid (), + m_hisGrid, m_tx_watchdog, + submode != QChar::Null ? QString {submode} : QString {}, m_bFastMode, + static_cast (m_config.special_op_id ()), + ftol, tr_period, m_multi_settings->configuration_name (), + m_currentMessage); +} + +void MainWindow::childEvent (QChildEvent * e) +{ + if (e->child ()->isWidgetType ()) + { + switch (e->type ()) + { + case QEvent::ChildAdded: add_child_to_event_filter (e->child ()); break; + case QEvent::ChildRemoved: remove_child_from_event_filter (e->child ()); break; + default: break; + } + } + QMainWindow::childEvent (e); +} + +// add widget and any child widgets to our event filter so that we can +// take action on key press ad mouse press events anywhere in the main window +void MainWindow::add_child_to_event_filter (QObject * target) +{ + if (target && target->isWidgetType ()) + { + target->installEventFilter (this); + } + auto const& children = target->children (); + for (auto iter = children.begin (); iter != children.end (); ++iter) + { + add_child_to_event_filter (*iter); + } +} + +// recursively remove widget and any child widgets from our event filter +void MainWindow::remove_child_from_event_filter (QObject * target) +{ + auto const& children = target->children (); + for (auto iter = children.begin (); iter != children.end (); ++iter) + { + remove_child_from_event_filter (*iter); + } + if (target && target->isWidgetType ()) + { + target->removeEventFilter (this); + } +} + +void MainWindow::tx_watchdog (bool triggered) +{ + auto prior = m_tx_watchdog; + m_tx_watchdog = triggered; + if (triggered) + { + m_bTxTime=false; + if (m_tune) stop_tuning (); + if (m_auto) auto_tx_mode (false); + tx_status_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff0000}"); + tx_status_label.setText (tr ("Runaway Tx watchdog")); + QApplication::alert (this); + } + else + { + m_idleMinutes = 0; + update_watchdog_label (); + } + if (prior != triggered) statusUpdate (); +} + +void MainWindow::update_watchdog_label () +{ + if (m_config.watchdog () && m_mode!="WSPR" && m_mode!="FST4W") + { + watchdog_label.setText (tr ("WD:%1m").arg (m_config.watchdog () - m_idleMinutes)); + watchdog_label.setVisible (true); + } + else + { + watchdog_label.setText (QString {}); + watchdog_label.setVisible (false); + } +} + +void MainWindow::on_cbMenus_toggled(bool b) +{ + select_geometry (!b ? 2 : ui->actionSWL_Mode->isChecked () ? 1 : 0); +} + +void MainWindow::on_cbCQonly_toggled(bool) +{ //Fix this -- no decode here? + to_jt9(m_ihsym,1,-1); //Send m_ihsym to jt9[.exe] and start decoding + decodeBusy(true); +} + +void MainWindow::on_cbFirst_toggled(bool b) +{ + if (b) { + if (m_auto && CALLING == m_QSOProgress) { + ui->cbFirst->setStyleSheet ("QCheckBox{color:red}"); + } + } else { + ui->cbFirst->setStyleSheet (""); + } +} + +void MainWindow::on_cbAutoSeq_toggled(bool b) +{ + if(!b) ui->cbFirst->setChecked(false); + ui->cbFirst->setVisible((m_mode=="FT8" or m_mode=="FT4" or m_mode=="FST4" + or m_mode=="Q65") and b); +} + +void MainWindow::on_measure_check_box_stateChanged (int state) +{ + m_config.enable_calibration (Qt::Checked != state); +} + +void MainWindow::write_transmit_entry (QString const& file_name) +{ + QFile f {m_config.writeable_data_dir ().absoluteFilePath (file_name)}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) + { + QTextStream out(&f); + auto time = QDateTime::currentDateTimeUtc (); + time = time.addSecs (-fmod(double(time.time().second()),m_TRperiod)); + out << time.toString("yyMMdd_hhmmss") + << " Transmitting " << qSetRealNumberPrecision (12) << (m_freqNominal / 1.e6) + << " MHz " << m_mode + << ": " << m_currentMessage +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; + f.close(); + } + else + { + auto const& message = tr ("Cannot open \"%1\" for append: %2") + .arg (f.fileName ()).arg (f.errorString ()); + QTimer::singleShot (0, [=] { // don't block guiUpdate + MessageBox::warning_message (this, tr ("Log File Error"), message); + }); + } +} + +// -------------------------- Code for FT8 DXpedition Mode --------------------------- + +void MainWindow::hound_reply () +{ + if (!m_tune) { + //Select TX3, set TxFreq to FoxFreq, and Force Auto ON. + ui->txrb3->setChecked (true); + m_nSentFoxRrpt = 1; + ui->rptSpinBox->setValue(m_rptSent.toInt()); + if (!m_auto) auto_tx_mode(true); + ui->TxFreqSpinBox->setValue (m_nFoxFreq); + } +} + +void MainWindow::on_sbNlist_valueChanged(int n) +{ + m_Nlist=n; +} + +void MainWindow::on_sbNslots_valueChanged(int n) +{ + m_Nslots=n; + if(m_config.special_op_id()!=SpecOp::FOX) return; + QString t; + t = t.asprintf(" NSlots %d",m_Nslots); + writeFoxQSO(t); +} + +void MainWindow::on_sbMax_dB_valueChanged(int n) +{ + m_max_dB=n; + if(m_config.special_op_id()!=SpecOp::FOX) return; + QString t; + t = t.asprintf(" Max_dB %d",m_max_dB); + writeFoxQSO(t); +} + +void MainWindow::on_pbFoxReset_clicked() +{ + if(m_config.special_op_id()!=SpecOp::FOX) return; + auto button = MessageBox::query_message (this, tr ("Confirm Reset"), + tr ("Are you sure you want to clear the QSO queues?")); + if(button == MessageBox::Yes) { + QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt")); + f.remove(); + ui->decodedTextBrowser->setText(""); + ui->textBrowser4->setText(""); + m_houndQueue.clear(); + m_foxQSO.clear(); + m_foxQSOinProgress.clear(); + writeFoxQSO(" Reset"); + } +} + +void MainWindow::on_comboBoxHoundSort_activated(int index) +{ + if(index!=-99) houndCallers(); //Silence compiler warning +} + +//------------------------------------------------------------------------------ +QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB) +{ +/* Called from "houndCallers()" to sort the list of calling stations by + * specified criteria. + * + * QString "t" contains a list of Hound callers read from file "houndcallers.txt". + * isort=0: random (shuffled order) + * 1: Call + * 2: Grid + * 3: SNR (reverse order) + * 4: Distance (reverse order) +*/ + + QMap map; + QStringList lines,lines2; + QString msg,houndCall,t1; + QString ABC{"ABCDEFGHIJKLMNOPQRSTUVWXYZ _"}; + QList list; + int i,j,k,n,nlines; + bool bReverse=(isort >= 3); + + isort=qAbs(isort); +// Save only the most recent transmission from each caller. + lines = t.split("\n"); + nlines=lines.length()-1; + for(i=0; i1) { + if(bReverse) { + std::sort (list.begin (), list.end (), std::greater ()); + } else { + std::sort (list.begin (), list.end ()); + } + } + + if(isort>1) { + for(i=0; i-1; i--) { +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + j = (i + 1) * QRandomGenerator::global ()->generateDouble (); +#else + j=(i+1)*double(qrand())/RAND_MAX; +#endif + std::swap (a[j], a[i]); + t += lines2.at(a[i]) + "\n"; + } + } + + int i0=t.indexOf("\n") + 1; + m_nSortedHounds=0; + if(i0 > 0) { + m_nSortedHounds=qMin(t.length(),m_Nlist*i0)/i0; // Number of sorted & displayed Hounds + } + m_houndCallers=t.mid(0,m_Nlist*i0); + + return m_houndCallers; +} + +//------------------------------------------------------------------------------ +void MainWindow::selectHound(QString line) +{ +/* Called from doubleClickOnCall() in DXpedition Fox mode. + * QString "line" is a user-selected line from left text window. + * The line may be selected by double-clicking; alternatively, hitting + * is equivalent to double-clicking on the top-most line. +*/ + + if(line.length()==0) return; + QString houndCall=line.split(" ",SkipEmptyParts).at(0); + +// Don't add a call already enqueued or in QSO + if(ui->textBrowser4->toPlainText().indexOf(houndCall) >= 0) return; + + QString houndGrid=line.split(" ",SkipEmptyParts).at(1); // Hound caller's grid + QString rpt=line.split(" ",SkipEmptyParts).at(2); // Hound SNR + + m_houndCallers=m_houndCallers.remove(line+"\n"); // Remove t from sorted Hound list + m_nSortedHounds--; + ui->decodedTextBrowser->setText(m_houndCallers); // Populate left window with Hound callers + QString t1=houndCall + " "; + QString t2=rpt; + if(rpt.mid(0,1) != "-" and rpt.mid(0,1) != "+") t2="+" + rpt; + if(t2.length()==2) t2=t2.mid(0,1) + "0" + t2.mid(1,1); + t1=t1.mid(0,12) + t2; + ui->textBrowser4->displayFoxToBeCalled(t1); // Add hound call and rpt to tb4 + t1=t1 + " " + houndGrid; // Append the grid + m_houndQueue.enqueue(t1); // Put this hound into the queue + writeFoxQSO(" Sel: " + t1); + QTextCursor cursor = ui->textBrowser4->textCursor(); + cursor.setPosition(0); // Scroll to top of list + ui->textBrowser4->setTextCursor(cursor); +} + +//------------------------------------------------------------------------------ +void MainWindow::houndCallers() +{ +/* Called from decodeDone(), in DXpedition Fox mode. Reads decodes from file + * "houndcallers.txt", ignoring any that are not addressed to MyCall, are already + * in the stack, or with whom a QSO has been started. Others are considered to + * be Hounds eager for a QSO. We add caller information (Call, Grid, SNR, Freq, + * Distance, Age, and Continent) to a list, sort the list by specified criteria, + * and display the top N_Hounds entries in the left text window. +*/ + QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt")); + if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream s(&f); + QString t=""; + QString line,houndCall,paddedHoundCall; + m_nHoundsCalling=0; + int nTotal=0; //Total number of decoded Hounds calling Fox in 4 most recent Rx sequences + +// Read and process the file of Hound callers. + while(!s.atEnd()) { + line=s.readLine(); + nTotal++; + int i0=line.indexOf(" "); + houndCall=line.mid(0,i0); + paddedHoundCall=houndCall + " "; + //Don't list a hound already in the queue + if(!ui->textBrowser4->toPlainText().contains(paddedHoundCall)) { + if(m_loggedByFox[houndCall].contains(m_lastBand)) continue; //already logged on this band + if(m_foxQSO.contains(houndCall)) continue; //still in the QSO map + auto const& entity = m_logBook.countries ()->lookup (houndCall); + auto const& continent = AD1CCty::continent (entity.continent); + +//If we are using a directed CQ, ignore Hound calls that do not comply. + QString CQtext=ui->comboBoxCQ->currentText(); + if(CQtext.length()==5 and (continent!=CQtext.mid(3,2))) continue; + int nCallArea=-1; + if(CQtext.length()==4) { + for(int i=houndCall.length()-1; i>0; i--) { + if(houndCall.mid(i,1).toInt() > 0) nCallArea=houndCall.mid(i,1).toInt(); + if(houndCall.mid(i,1)=="0") nCallArea=0; + if(nCallArea>=0) break; + } + if(nCallArea!=CQtext.mid(3,1).toInt()) continue; + } +//This houndCall passes all tests, add it to the list. + t = t + line + " " + continent + "\n"; + m_nHoundsCalling++; // Number of accepted Hounds to be sorted + } + } + if(m_foxLogWindow) m_foxLogWindow->callers (nTotal); + +// Sort and display accumulated list of Hound callers + if(t.length()>30) { + m_isort=ui->comboBoxHoundSort->currentIndex(); + QString t1=sortHoundCalls(t,m_isort,m_max_dB); + ui->decodedTextBrowser->setText(t1); + } + f.close(); + } +} + +void MainWindow::foxRxSequencer(QString msg, QString houndCall, QString rptRcvd) +{ +/* Called from "readFromStdOut()" to process decoded messages of the form + * "myCall houndCall R+rpt". + * + * If houndCall matches a callsign in one of our active QSO slots, we + * prepare to send "houndCall RR73" to that caller. +*/ + if(m_foxQSO.contains(houndCall)) { + m_foxQSO[houndCall].rcvd=rptRcvd.mid(1); //Save report Rcvd, for the log + m_foxQSO[houndCall].tFoxRrpt=m_tFoxTx; //Save time R+rpt was received + writeFoxQSO(" Rx: " + msg.trimmed()); + } else { + for(QString hc: m_foxQSO.keys()) { //Check for a matching compound call + if(hc.contains("/"+houndCall) or hc.contains(houndCall+"/")) { + m_foxQSO[hc].rcvd=rptRcvd.mid(1); //Save report Rcvd, for the log + m_foxQSO[hc].tFoxRrpt=m_tFoxTx; //Save time R+rpt was received + writeFoxQSO(" Rx: " + msg.trimmed()); + } + } + } +} + +void MainWindow::foxTxSequencer() +{ +/* Called from guiUpdate at the point where an FT8 Fox-mode transmission + * is to be started. + * + * Determine what the Tx message(s) will be for each active slot, call + * foxgen() to generate and accumulate the corresponding waveform. +*/ + + qint64 now=QDateTime::currentMSecsSinceEpoch()/1000; + QStringList list1; //Up to NSlots Hound calls to be sent RR73 + QStringList list2; //Up to NSlots Hound calls to be sent a report + QString fm; //Fox message to be transmitted + QString hc,hc1,hc2; //Hound calls + QString t,rpt; + qint32 islot=0; + qint32 n1,n2,n3; + + m_tFoxTx++; //Increment Fox Tx cycle counter + + //Is it time for a stand-alone CQ? + if(m_tFoxTxSinceCQ >= m_foxCQtime and ui->cbMoreCQs->isChecked()) { + fm=ui->comboBoxCQ->currentText() + " " + m_config.my_callsign(); + if(!fm.contains("/")) { + //If Fox is not a compound callsign, add grid to the CQ message. + fm += " " + m_config.my_grid().mid(0,4); + m_fullFoxCallTime=now; + } + m_tFoxTx0=m_tFoxTx; //Remember when we sent a CQ + islot++; + foxGenWaveform(islot-1,fm); + goto Transmit; + } +//Compile list1: up to NSLots Hound calls to be sent RR73 + for(QString hc: m_foxQSO.keys()) { //Check all Hound calls: First priority + if(m_foxQSO[hc].tFoxRrpt<0) continue; + if(m_foxQSO[hc].tFoxRrpt - m_foxQSO[hc].tFoxTxRR73 > 3) { + //Has been a long time since we sent RR73 + list1 << hc; //Add to list1 + m_foxQSO[hc].tFoxTxRR73 = m_tFoxTx; //Time RR73 is sent + m_foxQSO[hc].nRR73++; //Increment RR73 counter + if(list1.size()==m_Nslots) goto list1Done; + } + } + + for(QString hc: m_foxQSO.keys()) { //Check all Hound calls: Second priority + if(m_foxQSO[hc].tFoxRrpt<0) continue; + if(m_foxQSO[hc].tFoxTxRR73 < 0) { + //Have not yet sent RR73 + list1 << hc; //Add to list1 + m_foxQSO[hc].tFoxTxRR73 = m_tFoxTx; //Time RR73 is sent + m_foxQSO[hc].nRR73++; //Increment RR73 counter + if(list1.size()==m_Nslots) goto list1Done; + } + } + + for(QString hc: m_foxQSO.keys()) { //Check all Hound calls: Third priority + if(m_foxQSO[hc].tFoxRrpt<0) continue; + if(m_foxQSO[hc].tFoxTxRR73 <= m_foxQSO[hc].tFoxRrpt) { + //We received R+rpt more recently than we sent RR73 + list1 << hc; //Add to list1 + m_foxQSO[hc].tFoxTxRR73 = m_tFoxTx; //Time RR73 is sent + m_foxQSO[hc].nRR73++; //Increment RR73 counter + if(list1.size()==m_Nslots) goto list1Done; + } + } + +list1Done: +//Compile list2: Up to Nslots Hound calls to be sent a report. + for(int i=0; i=2*m_Nslots) goto list2Done; + } + +list2Done: + n1=list1.size(); + n2=list2.size(); + n3=qMax(n1,n2); + if(n3>m_Nslots) n3=m_Nslots; + for(int i=0; i " + m_foxQSO[hc2].sent; + } + if(i=n2) { + hc1=list1.at(i); + fm = Radio::base_callsign(hc1) + " " + m_baseCall + " RR73"; //Standard FT8 message + } + + if(hc1!="") { + // Log this QSO! + auto QSO_time = QDateTime::currentDateTimeUtc (); + m_hisCall=hc1; + m_hisGrid=m_foxQSO[hc1].grid; + m_rptSent=m_foxQSO[hc1].sent; + m_rptRcvd=m_foxQSO[hc1].rcvd; + if (!m_foxLogWindow) on_fox_log_action_triggered (); + if (m_logBook.fox_log ()->add_QSO (QSO_time, m_hisCall, m_hisGrid, m_rptSent, m_rptRcvd, m_lastBand)) + { + writeFoxQSO (QString {" Log: %1 %2 %3 %4 %5"}.arg (m_hisCall).arg (m_hisGrid) + .arg (m_rptSent).arg (m_rptRcvd).arg (m_lastBand)); + on_logQSOButton_clicked (); + m_foxRateQueue.enqueue (now); //Add present time in seconds + //to Rate queue. + } + m_loggedByFox[hc1] += (m_lastBand + " "); + } + + if(i=4) and ui->cbMoreCQs->isChecked())) { + //Roughly every 4th Tx sequence, put a CQ message in an otherwise empty slot + fm=ui->comboBoxCQ->currentText() + " " + m_config.my_callsign(); + if(!fm.contains("/")) { + fm += " " + m_config.my_grid().mid(0,4); + m_tFoxTx0=m_tFoxTx; //Remember when we send a CQ + m_fullFoxCallTime=now; + } + islot++; + foxGenWaveform(islot-1,fm); + } + } + +Transmit: + foxcom_.nslots=islot; + foxcom_.nfreq=ui->TxFreqSpinBox->value(); + if(m_config.split_mode()) foxcom_.nfreq = foxcom_.nfreq - m_XIT; //Fox Tx freq + QString foxCall=m_config.my_callsign() + " "; + ::memcpy(foxcom_.mycall, foxCall.toLatin1(),sizeof foxcom_.mycall); //Copy Fox callsign into foxcom_ + foxgen_(); + m_tFoxTxSinceCQ++; + + for(QString hc: m_foxQSO.keys()) { //Check for strikeout or timeout + if(m_foxQSO[hc].ncall>=m_maxStrikes) m_foxQSO[hc].ncall++; + bool b1=((m_tFoxTx - m_foxQSO[hc].tFoxRrpt) > 2*m_maxFoxWait) and + (m_foxQSO[hc].tFoxRrpt > 0); + bool b2=((m_tFoxTx - m_foxQSO[hc].tFoxTxRR73) > m_maxFoxWait) and + (m_foxQSO[hc].tFoxTxRR73>0); + bool b3=(m_foxQSO[hc].ncall >= m_maxStrikes+m_maxFoxWait); + bool b4=(m_foxQSO[hc].nRR73 >= m_maxStrikes); + if(b1 or b2 or b3 or b4) { + m_foxQSO.remove(hc); + m_foxQSOinProgress.removeOne(hc); + } + } + + while(!m_foxRateQueue.isEmpty()) { + qint64 age = now - m_foxRateQueue.head(); + if(age < 3600) break; + m_foxRateQueue.dequeue(); + } + if (m_foxLogWindow) + { + m_foxLogWindow->rate (m_foxRateQueue.size ()); + m_foxLogWindow->queued (m_foxQSOinProgress.count ()); + } +} + +void MainWindow::rm_tb4(QString houndCall) +{ + if(houndCall=="") return; + QString t=""; + QString tb4=ui->textBrowser4->toPlainText(); + QStringList list=tb4.split("\n"); + int n=list.size(); + int j=0; + for (int i=0; i0) t += "\n"; + QString line=list.at(i); + if(!line.contains(houndCall + " ")) { + j++; + t += line; + } + } + t.replace("\n\n","\n"); + ui->textBrowser4->setText(t); +} + +void MainWindow::doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers) +{ + if(modifiers==9999) return; //Silence compiler warning + QTextCursor cursor=ui->textBrowser4->textCursor(); + cursor.setPosition(cursor.selectionStart()); + QString houndCall=cursor.block().text().mid(0,12).trimmed(); + rm_tb4(houndCall); + writeFoxQSO(" Del: " + houndCall); + QQueue tmpQueue; + while(!m_houndQueue.isEmpty()) { + QString t=m_houndQueue.dequeue(); + QString hc=t.mid(0,12).trimmed(); + if(hc != houndCall) tmpQueue.enqueue(t); + } + m_houndQueue.swap(tmpQueue); +} + +void MainWindow::foxGenWaveform(int i,QString fm) +{ +//Generate and accumulate the Tx waveform + fm += " "; + fm=fm.mid(0,40); + if(fm.mid(0,3)=="CQ ") m_tFoxTxSinceCQ=-1; + + QString txModeArg; + txModeArg = txModeArg.asprintf("FT8fox %d",i+1); + ui->decodedTextBrowser2->displayTransmittedText(fm.trimmed(), txModeArg, + ui->TxFreqSpinBox->value()+60*i,m_bFastMode,m_TRperiod); + foxcom_.i3bit[i]=0; + if(fm.indexOf("<")>0) foxcom_.i3bit[i]=1; + strncpy(&foxcom_.cmsg[i][0],fm.toLatin1(),40); //Copy this message into cmsg[i] + if(i==0) m_fm1=fm; + QString t; + t = t.asprintf(" Tx%d: ",i+1); + writeFoxQSO(t + fm.trimmed()); +} + +void MainWindow::writeFoxQSO(QString const& msg) +{ + QString t; + t = t.asprintf("%3d%3d%3d",m_houndQueue.count(),m_foxQSOinProgress.count(),m_foxQSO.count()); + QFile f {m_config.writeable_data_dir ().absoluteFilePath ("FoxQSO.txt")}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QTextStream out(&f); + out << QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd hh:mm:ss") << " " +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::fixed +#else + << fixed +#endif + << qSetRealNumberPrecision (3) << (m_freqNominal/1.e6) + << t << msg +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; + f.close(); + } else { + MessageBox::warning_message (this, tr("File Open Error"), + tr("Cannot open \"%1\" for append: %2").arg(f.fileName()).arg(f.errorString())); + } +} + +/*################################################################################### */ +void MainWindow::foxTest() +{ + QFile f("steps.txt"); + if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) return; + + QFile fdiag("diag.txt"); + if(!fdiag.open(QIODevice::WriteOnly | QIODevice::Text)) return; + + QTextStream s(&f); + QTextStream sdiag(&fdiag); + QString line; + QString t; + QString msg; + QString hc1; + QString rptRcvd; + qint32 n=0; + + while(!s.atEnd()) { + line=s.readLine(); + if(line.length()==0) continue; + if(line.mid(0,4).toInt()==0) line=" " + line; + if(line.contains("NSlots")) { + n=line.mid(44,1).toInt(); + ui->sbNslots->setValue(n); + } + if(line.contains("Sel:")) { + t=line.mid(43,6) + " " + line.mid(54,4) + " " + line.mid(50,3); + selectHound(t); + } + + if(line.contains("Del:")) { + int i0=line.indexOf("Del:"); + hc1=line.mid(i0+6); + int i1=hc1.indexOf(" "); + hc1=hc1.mid(0,i1); + rm_tb4(hc1); + writeFoxQSO(" Del: " + hc1); + QQueue tmpQueue; + while(!m_houndQueue.isEmpty()) { + t=m_houndQueue.dequeue(); + QString hc=t.mid(0,6).trimmed(); + if(hc != hc1) tmpQueue.enqueue(t); + } + m_houndQueue.swap(tmpQueue); + } + if(line.contains("Rx:")) { + msg=line.mid(43); + t=msg.mid(24); + int i0=t.indexOf(" "); + hc1=t.mid(i0+1); + int i1=hc1.indexOf(" "); + hc1=hc1.mid(0,i1); + int i2=qMax(msg.indexOf("R+"),msg.indexOf("R-")); + if(i2>20) { + rptRcvd=msg.mid(i2,4); + foxRxSequencer(msg,hc1,rptRcvd); + } + } + if(line.contains("Tx1:")) { + foxTxSequencer(); + } else { + t = t.asprintf("%3d %3d %3d %3d %5d ",m_houndQueue.count(), + m_foxQSOinProgress.count(),m_foxQSO.count(), + m_loggedByFox.count(),m_tFoxTx); + sdiag << t << line.mid(37).trimmed() << "\n"; + } + } +} + +void MainWindow::write_all(QString txRx, QString message) +{ + QString line; + QString t; + QString msg; + QString mode_string; + + if (message.size () > 5 && message[4]==' ') { + msg=message.mid(4,-1); + } else { + msg=message.mid(6,-1); + } + + if (message.size () > 19 && message[19]=='#') { + mode_string="JT65 "; + } else if (message.size () > 19 && message[19]=='@') { + mode_string="JT9 "; + } else if(m_mode=="Q65") { + mode_string=mode_label.text(); + } else { + mode_string=m_mode.leftJustified(6,' '); + } + + msg=msg.mid(0,15) + msg.mid(18,-1); + + t = t.asprintf("%5d",ui->TxFreqSpinBox->value()); + if (txRx=="Tx") msg=" 0 0.0" + t + " " + message; + auto time = QDateTime::currentDateTimeUtc (); + if( txRx=="Rx" && !m_bFastMode ) time=m_dateTimeSeqStart; + + t = t.asprintf("%10.3f ",m_freqNominal/1.e6); + if (m_diskData) { + if (m_fileDateTime.size()==11) { + line=m_fileDateTime + " " + t + txRx + " " + mode_string + msg; + } else { + line=m_fileDateTime + t + txRx + " " + mode_string + msg; + } + } else { + line=time.toString("yyMMdd_hhmmss") + t + txRx + " " + mode_string + msg; + } + + QString file_name="ALL.TXT"; + if (m_mode=="WSPR") file_name="ALL_WSPR.TXT"; + QFile f{m_config.writeable_data_dir().absoluteFilePath(file_name)}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QTextStream out(&f); + out << line.trimmed() +#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) + << Qt::endl +#else + << endl +#endif + ; + f.close(); + } else { + auto const& message2 = tr ("Cannot open \"%1\" for append: %2") + .arg (f.fileName ()).arg (f.errorString ()); + QTimer::singleShot (0, [=] { // don't block guiUpdate + MessageBox::warning_message(this, tr ("Log File Error"), message2); }); + } +} + +void MainWindow::chkFT4() +{ + if(m_mode!="FT4") return; + ui->cbAutoSeq->setEnabled(true); + ui->cbFirst->setVisible(true); + ui->cbFirst->setEnabled(true); + ui->labDXped->setVisible(m_config.special_op_id()!=SpecOp::NONE); + ui->cbFirst->setVisible(ui->cbAutoSeq->isChecked()); + + if (SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()) { + QString t0=""; + if(SpecOp::NA_VHF==m_config.special_op_id()) t0+="NA VHF"; + if(SpecOp::EU_VHF==m_config.special_op_id()) t0+="EU VHF"; + if(SpecOp::FIELD_DAY==m_config.special_op_id()) t0+="Field Day"; + if(SpecOp::RTTY==m_config.special_op_id()) t0+="RTTY"; + if(SpecOp::WW_DIGI==m_config.special_op_id()) t0+="WW_DIGI"; + if(t0=="") { + ui->labDXped->setVisible(false); + } else { + ui->labDXped->setVisible(true); + ui->labDXped->setText(t0); + } + on_contest_log_action_triggered(); + } + if (SpecOp::HOUND == m_config.special_op_id() or SpecOp::FOX == m_config.special_op_id()) { + ui->labDXped->setVisible(false); + } + +} + +void MainWindow::on_pbBestSP_clicked() +{ + m_bBestSPArmed = !m_bBestSPArmed; + if(m_bBestSPArmed and !m_transmitting) ui->pbBestSP->setStyleSheet ("QPushButton{color:red}"); + if(!m_bBestSPArmed) ui->pbBestSP->setStyleSheet (""); + if(m_bBestSPArmed) m_dateTimeBestSP=QDateTime::currentDateTimeUtc(); +} + +void MainWindow::set_mode (QString const& mode) +{ + if ("FT4" == mode) on_actionFT4_triggered (); + else if ("FST4" == mode) on_actionFST4_triggered (); + else if ("FST4W" == mode) on_actionFST4W_triggered (); + else if ("FT8" == mode) on_actionFT8_triggered (); + else if ("JT4" == mode) on_actionJT4_triggered (); + else if ("JT9" == mode) on_actionJT9_triggered (); + else if ("JT65" == mode) on_actionJT65_triggered (); + else if ("Q65" == mode) on_actionQ65_triggered (); + else if ("FreqCal" == mode) on_actionFreqCal_triggered (); + else if ("MSK144" == mode) on_actionMSK144_triggered (); + else if ("WSPR" == mode) on_actionWSPR_triggered (); + else if ("Echo" == mode) on_actionEcho_triggered (); +} + +void MainWindow::remote_configure (QString const& mode, quint32 frequency_tolerance + , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df + , QString const& dx_call, QString const& dx_grid, bool generate_messages) +{ + if (mode.size ()) + { + set_mode (mode); + } + auto is_FST4W = "FST4W" == m_mode; + if (frequency_tolerance != quint32_max && (ui->sbFtol->isVisible () || is_FST4W)) + { + m_block_udp_status_updates = true; + if (is_FST4W) + { + ui->sbFST4W_FTol->setValue (frequency_tolerance); + } + else + { + ui->sbFtol->setValue (frequency_tolerance); + } + m_block_udp_status_updates = false; + } + if (submode.size () && ui->sbSubmode->isVisible ()) + { + ui->sbSubmode->setValue (submode.toUpper ().at (0).toLatin1 () - 'A'); + } + if (ui->cbFast9->isVisible () && ui->cbFast9->isChecked () != fast_mode) + { + ui->cbFast9->click (); + } + if (tr_period != quint32_max && ui->sbTR->isVisible ()) + { + if (is_FST4W) + { + ui->sbTR_FST4W->setValue (tr_period); + ui->sbTR_FST4W->interpretText (); + } + else + { + ui->sbTR->setValue (tr_period); + ui->sbTR->interpretText (); + } + } + if (rx_df != quint32_max && ui->RxFreqSpinBox->isVisible ()) + { + m_block_udp_status_updates = true; + if (is_FST4W) + { + ui->sbFST4W_RxFreq->setValue (rx_df); + ui->sbFST4W_RxFreq->interpretText (); + } + else + { + ui->RxFreqSpinBox->setValue (rx_df); + ui->RxFreqSpinBox->interpretText (); + } + m_block_udp_status_updates = false; + } + if (dx_call.size () && ui->dxCallEntry->isVisible ()) + { + ui->dxCallEntry->setText (dx_call); + } + if (dx_grid.size () && ui->dxGridEntry->isVisible ()) + { + ui->dxGridEntry->setText (dx_grid); + } + if (generate_messages && ui->genStdMsgsPushButton->isVisible ()) + { + ui->genStdMsgsPushButton->click (); + } + if (m_config.udpWindowToFront ()) + { + show (); + raise (); + activateWindow (); + } + if (m_config.udpWindowRestore () && isMinimized ()) + { + showNormal (); + raise (); + } + tx_watchdog (false); + QApplication::alert (this); +} + +QString MainWindow::WSPR_message() +{ + QString sdBm,msg0,msg1,msg2; + sdBm = sdBm.asprintf(" %d",m_dBm); + m_tx=1-m_tx; + int i2=m_config.my_callsign().indexOf("/"); + if(i2>0 + || (6 == m_config.my_grid ().size () + && !ui->WSPR_prefer_type_1_check_box->isChecked ())) { + if(i2<0) { // "Type 2" WSPR message + msg1=m_config.my_callsign() + " " + m_config.my_grid().mid(0,4) + sdBm; + } else { + msg1=m_config.my_callsign() + sdBm; + } + msg0="<" + m_config.my_callsign() + "> " + m_config.my_grid(); + if(m_mode=="WSPR") msg0 += sdBm; + if(m_tx==0) msg2=msg0; + if(m_tx==1) msg2=msg1; + } else { + msg2=m_config.my_callsign() + " " + m_config.my_grid().mid(0,4) + sdBm; // Normal WSPR message + } + return msg2; +} diff --git a/patch/widgets/mainwindow.h b/patch/widgets/mainwindow.h new file mode 100755 index 0000000..139c30c --- /dev/null +++ b/patch/widgets/mainwindow.h @@ -0,0 +1,792 @@ +// -*- Mode: C++ -*- +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MultiGeometryWidget.hpp" +#include "NonInheritingProcess.hpp" +#include "Audio/AudioDevice.hpp" +#include "commons.h" +#include "Radio.hpp" +#include "models/Modes.hpp" +#include "models/FrequencyList.hpp" +#include "Configuration.hpp" +#include "WSPR/WSPRBandHopping.hpp" +#include "Transceiver/Transceiver.hpp" +#include "DisplayManual.hpp" +#include "Network/PSKReporter.hpp" +#include "logbook/logbook.h" +#include "astro.h" +#include "MessageBox.hpp" +#include "Network/NetworkAccessManager.hpp" + +#define NUM_JT4_SYMBOLS 206 //(72+31)*2, embedded sync +#define NUM_JT65_SYMBOLS 126 //63 data + 63 sync +#define NUM_JT9_SYMBOLS 85 //69 data + 16 sync +#define NUM_WSPR_SYMBOLS 162 //(50+31)*2, embedded sync +#define NUM_MSK144_SYMBOLS 144 //s8 + d48 + s8 + d80 +#define NUM_Q65_SYMBOLS 85 //63 data + 22 sync +#define NUM_FT8_SYMBOLS 79 +#define NUM_FT4_SYMBOLS 105 +#define NUM_FST4_SYMBOLS 160 //240/2 data + 5*8 sync +#define NUM_CW_SYMBOLS 250 +#define MAX_NUM_SYMBOLS 250 +#define TX_SAMPLE_RATE 48000 +#define NRING 3456000 + +extern int volatile itone[MAX_NUM_SYMBOLS]; //Audio tones for all Tx symbols +extern int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID + +//--------------------------------------------------------------- MainWindow +namespace Ui { + class MainWindow; +} + +class QProcessEnvironment; +class QSharedMemory; +class QSplashScreen; +class QSettings; +class QLineEdit; +class QFont; +class QHostInfo; +class EchoGraph; +class FastGraph; +class WideGraph; +class LogQSO; +class Transceiver; +class MessageAveraging; +class FoxLogWindow; +class CabrilloLogWindow; +class ColorHighlighting; +class MessageClient; +class QTime; +class WSPRBandHopping; +class HelpTextWindow; +class WSPRNet; +class SoundOutput; +class Modulator; +class SoundInput; +class Detector; +class SampleDownloader; +class MultiSettings; +class EqualizationToolsDialog; +class DecodedText; + +class MainWindow + : public MultiGeometryWidget<3, QMainWindow> +{ + Q_OBJECT; + +public: + using Frequency = Radio::Frequency; + using FrequencyDelta = Radio::FrequencyDelta; + using Mode = Modes::Mode; + + explicit MainWindow(QDir const& temp_directory, bool multiple, MultiSettings *, + QSharedMemory *shdmem, unsigned downSampleFactor, + QSplashScreen *, QProcessEnvironment const&, + QWidget *parent = nullptr); + ~MainWindow(); + + int decoderBusy () const {return m_decoderBusy;} + +public slots: + void showSoundInError(const QString& errorMsg); + void showSoundOutError(const QString& errorMsg); + void showStatusMessage(const QString& statusMsg); + void dataSink(qint64 frames); + void fastSink(qint64 frames); + void diskDat(); + void freezeDecode(int n); + void guiUpdate(); + void doubleClickOnCall (Qt::KeyboardModifiers); + void doubleClickOnCall2(Qt::KeyboardModifiers); + void doubleClickOnFoxQueue(Qt::KeyboardModifiers); + void readFromStdout(); + void p1ReadFromStdout(); + void setXIT(int n, Frequency base = 0u); + void setFreq4(int rxFreq, int txFreq); + void msgAvgDecode2(); + void fastPick(int x0, int x1, int y); + +private: + void change_layout (std::size_t) override; + void keyPressEvent (QKeyEvent *) override; + void closeEvent(QCloseEvent *) override; + void childEvent(QChildEvent *) override; + bool eventFilter(QObject *, QEvent *) override; + +private slots: + void initialize_fonts (); + void on_tx1_editingFinished(); + void on_tx2_editingFinished(); + void on_tx3_editingFinished(); + void on_tx4_editingFinished(); + void on_tx5_currentTextChanged (QString const&); + void on_tx6_editingFinished(); + void on_actionSettings_triggered(); + void on_monitorButton_clicked (bool); + void on_actionAbout_triggered(); + void on_autoButton_clicked (bool); + void on_stopTxButton_clicked(); + void on_stopButton_clicked(); + void on_actionRelease_Notes_triggered (); + void on_actionFT8_DXpedition_Mode_User_Guide_triggered(); + void on_actionQSG_FST4_triggered(); + void on_actionQSG_Q65_triggered(); + void on_actionQSG_X250_M3_triggered(); + void on_actionOnline_User_Guide_triggered(); + void on_actionLocal_User_Guide_triggered(); + void on_actionWide_Waterfall_triggered(); + void on_actionOpen_triggered(); + void on_actionOpen_next_in_directory_triggered(); + void on_actionDecode_remaining_files_in_directory_triggered(); + void on_actionDelete_all_wav_files_in_SaveDir_triggered(); + void on_actionOpen_log_directory_triggered (); + void on_actionNone_triggered(); + void on_actionSave_all_triggered(); + void on_actionKeyboard_shortcuts_triggered(); + void on_actionSpecial_mouse_commands_triggered(); + void on_actionSolve_FreqCal_triggered(); + void on_actionCopyright_Notice_triggered(); + void on_actionSWL_Mode_triggered (bool checked); + void on_DecodeButton_clicked (bool); + void decode(); + void decodeBusy(bool b); + void on_EraseButton_clicked(); + void band_activity_cleared (); + void rx_frequency_activity_cleared (); + void on_txFirstCheckBox_stateChanged(int arg1); + void set_dateTimeQSO(int m_ntx); + void set_ntx(int n); + void on_txrb1_toggled(bool status); + void on_txrb1_doubleClicked (); + void on_txrb2_toggled(bool status); + void on_txrb3_toggled(bool status); + void on_txrb4_toggled(bool status); + void on_txrb4_doubleClicked (); + void on_txrb5_toggled(bool status); + void on_txrb5_doubleClicked (); + void on_txrb6_toggled(bool status); + void on_txb1_clicked(); + void on_txb1_doubleClicked (); + void on_txb2_clicked(); + void on_txb3_clicked(); + void on_txb4_clicked(); + void on_txb4_doubleClicked (); + void on_txb5_clicked(); + void on_txb5_doubleClicked (); + void on_txb6_clicked(); + void on_lookupButton_clicked(); + void on_addButton_clicked(); + void on_dxCallEntry_textChanged (QString const&); + void on_dxGridEntry_textChanged (QString const&); + void on_dxCallEntry_editingFinished(); + void on_dxCallEntry_returnPressed (); + void on_genStdMsgsPushButton_clicked(); + void on_logQSOButton_clicked(); + void on_actionJT9_triggered(); + void on_actionJT65_triggered(); + void on_actionJT4_triggered(); + void on_actionFT4_triggered(); + void on_actionFT8_triggered(); + void on_actionFST4_triggered(); + void on_actionFST4W_triggered(); + void on_TxFreqSpinBox_valueChanged(int arg1); + void on_actionSave_decoded_triggered(); + void on_actionQuickDecode_toggled (bool); + void on_actionMediumDecode_toggled (bool); + void on_actionDeepestDecode_toggled (bool); + void bumpFqso(int n); + void on_actionErase_ALL_TXT_triggered(); + void on_reset_cabrillo_log_action_triggered (); + void on_actionErase_wsjtx_log_adi_triggered(); + void on_actionErase_WSPR_hashtable_triggered(); + void on_actionExport_Cabrillo_log_triggered(); + void startTx2(); + void startP1(); + void stopTx(); + void stopTx2(); + void on_rptSpinBox_valueChanged(int n); + void killFile(); + void on_tuneButton_clicked (bool); + void on_pbR2T_clicked(); + void on_pbT2R_clicked(); + void acceptQSO (QDateTime const&, QString const& call, QString const& grid + , Frequency dial_freq, QString const& mode + , QString const& rpt_sent, QString const& rpt_received + , QString const& tx_power, QString const& comments + , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call + , QString const& my_call, QString const& my_grid + , QString const& exchange_sent, QString const& exchange_rcvd + , QString const& propmode, QByteArray const& ADIF); + void on_bandComboBox_currentIndexChanged (int index); + void on_bandComboBox_editTextChanged (QString const& text); + void on_bandComboBox_activated (int index); + void on_readFreq_clicked(); + void on_RxFreqSpinBox_valueChanged(int n); + void on_outAttenuation_valueChanged (int); + void rigOpen (); + void handle_transceiver_update (Transceiver::TransceiverState const&); + void handle_transceiver_failure (QString const& reason); + void on_actionAstronomical_data_toggled (bool); + void on_actionShort_list_of_add_on_prefixes_and_suffixes_triggered(); + void band_changed (Frequency); + void monitor (bool); + void end_tuning (); + void stop_tuning (); + void stopTuneATU(); + void auto_tx_mode(bool); + void on_actionMessage_averaging_triggered(); + void on_contest_log_action_triggered (); + void on_fox_log_action_triggered (); + void on_actionColors_triggered(); + void on_actionInclude_averaging_toggled (bool); + void on_actionInclude_correlation_toggled (bool); + void on_actionEnable_AP_DXcall_toggled (bool); + void on_actionAuto_Clear_Avg_toggled (bool); + void VHF_features_enabled(bool b); + void on_sbSubmode_valueChanged(int n); + void on_cbShMsgs_toggled(bool b); + void on_cbSWL_toggled(bool b); + void on_cbTx6_toggled(bool b); + void on_cbMenus_toggled(bool b); + void on_cbCQonly_toggled(bool b); + void on_cbFirst_toggled(bool b); + void on_cbAutoSeq_toggled(bool b); + void networkError (QString const&); + void on_ClrAvgButton_clicked(); + void on_actionWSPR_triggered(); + void on_syncSpinBox_valueChanged(int n); + void on_TxPowerComboBox_currentIndexChanged(int); + void on_sbTxPercent_valueChanged(int n); + void on_cbUploadWSPR_Spots_toggled(bool b); + void WSPR_config(bool b); + void uploadWSPRSpots (bool direct_post = false, QString const& decode_text = QString {}); + void TxAgain(); + void uploadResponse(QString const& response); + void on_WSPRfreqSpinBox_valueChanged(int n); + void on_sbFST4W_RxFreq_valueChanged(int n); + void on_sbFST4W_FTol_valueChanged(int n); + void on_pbTxNext_clicked(bool b); + void on_actionEcho_Graph_triggered(); + void on_actionEcho_triggered(); + void on_actionFast_Graph_triggered(); + void fast_decode_done(); + void on_actionMeasure_reference_spectrum_triggered(); + void on_actionErase_reference_spectrum_triggered(); + void on_actionMeasure_phase_response_triggered(); + void on_sbTR_valueChanged (int); + void on_sbTR_FST4W_valueChanged (int); + void on_sbFtol_valueChanged (int); + void on_cbFast9_clicked(bool b); + void on_sbCQTxFreq_valueChanged(int n); + void on_cbCQTx_toggled(bool b); + void on_actionMSK144_triggered(); + void on_actionQ65_triggered(); + void on_actionFreqCal_triggered(); + void splash_done (); + void on_measure_check_box_stateChanged (int); + void on_sbNlist_valueChanged(int n); + void on_sbNslots_valueChanged(int n); + void on_sbMax_dB_valueChanged(int n); + void on_sbF_Low_valueChanged(int n); + void on_sbF_High_valueChanged(int n); + void chk_FST4_freq_range(); + void on_pbFoxReset_clicked(); + void on_comboBoxHoundSort_activated (int index); + void not_GA_warning_message (); + void checkMSK144ContestType(); + void on_pbBestSP_clicked(); + void on_RoundRobin_currentTextChanged(QString text); + void setTxMsg(int n); + bool stdCall(QString const& w); + void remote_configure (QString const& mode, quint32 frequency_tolerance, QString const& submode + , bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call + , QString const& dx_grid, bool generate_messages); + +private: + Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo, + unsigned channels, unsigned msBuffered) const; + Q_SIGNAL void stopAudioOutputStream () const; + Q_SIGNAL void startAudioInputStream (QAudioDeviceInfo const&, + int framesPerBuffer, AudioDevice * sink, + unsigned downSampleFactor, AudioDevice::Channel) const; + Q_SIGNAL void suspendAudioInputStream () const; + Q_SIGNAL void resumeAudioInputStream () const; + Q_SIGNAL void startDetector (AudioDevice::Channel) const; + Q_SIGNAL void FFTSize (unsigned) const; + Q_SIGNAL void detectorClose () const; + Q_SIGNAL void finished () const; + Q_SIGNAL void transmitFrequency (double) const; + Q_SIGNAL void endTransmitMessage (bool quick = false) const; + Q_SIGNAL void tune (bool = true) const; + Q_SIGNAL void sendMessage (QString mode, unsigned symbolsLength, + double framesPerSymbol, double frequency, double toneSpacing, + SoundOutput *, AudioDevice::Channel = AudioDevice::Mono, + bool synchronize = true, bool fastMode = false, double dBSNR = 99., + int TRperiod=60) const; + Q_SIGNAL void outAttenuationChanged (qreal) const; + Q_SIGNAL void toggleShorthand () const; + Q_SIGNAL void reset_audio_input_stream (bool report_dropped_frames) const; + +private: + void set_mode (QString const& mode); + void astroUpdate (); + void writeAllTxt(QString message); + void auto_sequence (DecodedText const& message, unsigned start_tolerance, unsigned stop_tolerance); + void trim_view (bool b); + void foxTest(); + void setColorHighlighting(); + void chkFT4(); + bool elide_tx1_not_allowed () const; + + QProcessEnvironment const& m_env; + NetworkAccessManager m_network_manager; + bool m_valid; + QSplashScreen * m_splash; + QString m_revision; + bool m_multiple; + MultiSettings * m_multi_settings; + QPushButton * m_configurations_button; + QSettings * m_settings; + QScopedPointer ui; + + Configuration m_config; + LogBook m_logBook; // must be after Configuration construction + WSPRBandHopping m_WSPR_band_hopping; + bool m_WSPR_tx_next; + MessageBox m_rigErrorMessageBox; + QScopedPointer m_sampleDownloader; + QScopedPointer m_equalizationToolsDialog; + + QScopedPointer m_wideGraph; + QScopedPointer m_echoGraph; + QScopedPointer m_fastGraph; + QScopedPointer m_logDlg; + QScopedPointer m_astroWidget; + QScopedPointer m_shortcuts; + QScopedPointer m_prefixes; + QScopedPointer m_mouseCmnds; + QScopedPointer m_msgAvgWidget; + QScopedPointer m_foxLogWindow; + QScopedPointer m_contestLogWindow; + QScopedPointer m_colorHighlighting; + Transceiver::TransceiverState m_rigState; + Frequency m_lastDialFreq; + QString m_lastBand; + QString m_lastCallsign; + Frequency m_dialFreqRxWSPR; // best guess at WSPR QRG + + Detector * m_detector; + unsigned m_FFTSize; + SoundInput * m_soundInput; + Modulator * m_modulator; + SoundOutput * m_soundOutput; + int m_rx_audio_buffer_frames; + int m_tx_audio_buffer_frames; + QThread m_audioThread; + + qint64 m_msErase; + qint64 m_secBandChanged; + qint64 m_freqMoon; + qint64 m_fullFoxCallTime; + + Frequency m_freqNominal; + Frequency m_freqTxNominal; + Astro::Correction m_astroCorrection; + bool m_reverse_Doppler; + + double m_tRemaining; + double m_TRperiod; + + float m_DTtol; + float m_t0; + float m_t1; + float m_t0Pick; + float m_t1Pick; + float m_fCPUmskrtd; + + qint32 m_waterfallAvg; + qint32 m_ntx; + bool m_gen_message_is_cq; + bool m_send_RR73; + qint32 m_timeout; + qint32 m_XIT; + qint32 m_setftx; + qint32 m_ndepth; + qint32 m_sec0; + qint32 m_RxLog; + qint32 m_nutc0; + qint32 m_ntr; + qint32 m_tx; + qint32 m_hsym; + qint32 m_nsps; + qint32 m_hsymStop; + qint32 m_inGain; + qint32 m_ncw; + qint32 m_secID; + qint32 m_idleMinutes; + qint32 m_nSubMode; + qint32 m_nclearave; + qint32 m_minSync; + qint32 m_dBm; + qint32 m_nWSPRdecodes; + qint32 m_k0; + qint32 m_kdone; + qint32 m_nPick; + FrequencyList_v2::const_iterator m_frequency_list_fcal_iter; + qint32 m_nTx73; + qint32 m_UTCdisk; + qint32 m_wait; + qint32 m_isort; + qint32 m_max_dB; + qint32 m_nDXped=0; + qint32 m_nSortedHounds=0; + qint32 m_nHoundsCalling=0; + qint32 m_Nlist=12; + qint32 m_Nslots=5; + qint32 m_nFoxMsgTimes[5]={0,0,0,0,0}; + qint32 m_tAutoOn; + qint32 m_tFoxTx=0; + qint32 m_tFoxTx0=0; + qint32 m_maxStrikes=3; //Max # of repeats: 3 strikes and you're out + qint32 m_maxFoxWait=3; //Max wait time for expected Hound replies + qint32 m_foxCQtime=10; //CQs at least every 5 minutes + qint32 m_tFoxTxSinceCQ=999; //Fox Tx cycles since most recent CQ + qint32 m_nFoxFreq; //Audio freq at which Hound received a call from Fox + qint32 m_nSentFoxRrpt=0; //Serial number for next R+rpt Hound will send to Fox + qint32 m_kin0=0; + qint32 m_earlyDecode=41; + qint32 m_earlyDecode2=47; + qint32 m_nDecodes=0; + + bool m_btxok; //True if OK to transmit + bool m_diskData; + bool m_loopall; + bool m_decoderBusy; + bool m_txFirst; + bool m_auto; + bool m_restart; + bool m_startAnother; + bool m_saveDecoded; + bool m_saveAll; + bool m_widebandDecode; + bool m_call3Modified; + bool m_dataAvailable; + bool m_bDecoded; + bool m_noSuffix; + bool m_decodedText2; + bool m_freeText; + bool m_sentFirst73; + int m_currentMessageType; + QString m_currentMessage; + int m_lastMessageType; + QString m_lastMessageSent; + QString m_tBlankLine; + bool m_bShMsgs; + bool m_bSWL; + bool m_uploadWSPRSpots; + bool m_uploading; + bool m_grid6; + bool m_tuneup; + bool m_bTxTime; + bool m_rxDone; + bool m_bSimplex; // not using split even if it is available + bool m_bEchoTxOK; + bool m_bTransmittedEcho; + bool m_bEchoTxed; + bool m_bFastMode; + bool m_bFast9; + bool m_bFastDecodeCalled; + bool m_bDoubleClickAfterCQnnn; + bool m_bRefSpec; + bool m_bClearRefSpec; + bool m_bTrain; + bool m_bUseRef; + bool m_bFastDone; + bool m_bAltV; + bool m_bNoMoreFiles; + bool m_bDoubleClicked; + bool m_bCallingCQ; + bool m_bAutoReply; + bool m_bCheckedContest; + bool m_bWarnedSplit=false; + bool m_bTUmsg; + bool m_bBestSPArmed=false; + bool m_bOK_to_chk=false; + bool m_bSentReport=false; + + enum + { + CALLING, + REPLYING, + REPORT, + ROGER_REPORT, + ROGERS, + SIGNOFF + } + m_QSOProgress; //State machine counter + + enum {CALL, GRID, DXCC, MULT}; + + int m_ihsym; + int m_nzap; + int m_npts8; + float m_px; + float m_pxmax; + float m_df3; + int m_iptt0; + bool m_btxok0; + int m_nsendingsh; + double m_onAirFreq0; + bool m_first_error; + + char m_msg[100][80]; + + // labels in status bar + QLabel tx_status_label; + QLabel config_label; + QLabel mode_label; + QLabel last_tx_label; + QLabel auto_tx_label; + QLabel band_hopping_label; + QLabel ndecodes_label; + QProgressBar progressBar; + QLabel watchdog_label; + + QFuture m_wav_future; + QFutureWatcher m_wav_future_watcher; + QFutureWatcher watcher3; + QFutureWatcher m_saveWAVWatcher; + + NonInheritingProcess proc_jt9; + NonInheritingProcess p1; + NonInheritingProcess p3; + + WSPRNet *wsprNet; + + QTimer m_guiTimer; + QTimer ptt1Timer; //StartTx delay + QTimer ptt0Timer; //StopTx delay + QTimer logQSOTimer; + QTimer killFileTimer; + QTimer tuneButtonTimer; + QTimer uploadTimer; + QTimer tuneATU_Timer; + QTimer TxAgainTimer; + QTimer minuteTimer; + QTimer splashTimer; + QTimer p1Timer; + + QString m_path; + QString m_baseCall; + QString m_hisCall; + QString m_hisGrid; + QString m_appDir; + QString m_cqStr; + QString m_palette; + QString m_dateTime; + QString m_mode; + QString m_fnameWE; // save path without extension + QString m_rpt; + QString m_nextRpt; + QString m_rptSent; + QString m_rptRcvd; + QString m_qsoStart; + QString m_qsoStop; + QStringList m_cmndP1; + QString m_msgSent0; + QString m_calls; + QString m_CQtype; + QString m_opCall; + QString m_houndCallers; //Sorted list of Hound callers + QString m_fm0; + QString m_fm1; + QString m_xSent; //Contest exchange sent + QString m_xRcvd; //Contest exchange received + QString m_currentBand; + QString m_nextCall; + QString m_nextGrid; + QString m_fileDateTime; + QString m_inQSOwith; + QString m_BestCQpriority; + + QSet m_pfx; + QSet m_sfx; + + struct FoxQSO //Everything we need to know about QSOs in progress (or recently logged). + { + QString grid; //Hound's declared locator + QString sent; //Report sent to Hound + QString rcvd; //Report received from Hound + qint32 ncall; //Number of times report sent to Hound + qint32 nRR73; //Number of times RR73 sent to Hound + qint32 tFoxRrpt; //m_tFoxTx (Fox Tx cycle counter) when R+rpt was received from Hound + qint32 tFoxTxRR73; //m_tFoxTx when RR73 was sent to Hound + }; + + QMap m_foxQSO; //Key = HoundCall, value = parameters for QSO in progress + QMap m_loggedByFox; //Key = HoundCall, value = logged band + + struct FixupQSO //Info for fixing Fox's log from file "FoxQSO.txt" + { + QString grid; //Hound's declared locator + QString sent; //Report sent to Hound + QString rcvd; //Report received from Hound + QDateTime QSO_time; + }; + QMap m_fixupQSO; //Key = HoundCall, value = info for QSO in progress + + QQueue m_houndQueue; //Selected Hounds available for starting a QSO + QQueue m_foxQSOinProgress; //QSOs in progress: Fox has sent a report + QQueue m_foxRateQueue; + + QDateTime m_dateTimeQSOOn; + QDateTime m_dateTimeLastTX; + QDateTime m_dateTimeSentTx3; + QDateTime m_dateTimeRcvdRR73; + QDateTime m_dateTimeBestSP; + QDateTime m_dateTimeSeqStart; //Nominal start time of Rx sequence about to be decoded + + QSharedMemory *mem_jt9; + QString m_QSOText; + unsigned m_downSampleFactor; + QThread::Priority m_audioThreadPriority; + bool m_bandEdited; + bool m_splitMode; + bool m_monitoring; + bool m_tx_when_ready; + bool m_transmitting; + bool m_tune; + bool m_tx_watchdog; // true when watchdog triggered + bool m_block_pwr_tooltip; + bool m_PwrBandSetOK; + bool m_bDisplayedOnce; + Frequency m_lastMonitoredFrequency; + double m_toneSpacing; + int m_firstDecode; + QProgressDialog m_optimizingProgress; + QTimer m_heartbeat; + MessageClient * m_messageClient; + PSKReporter m_psk_Reporter; + DisplayManual m_manual; + QHash m_pwrBandTxMemory; // Remembers power level by band + QHash m_pwrBandTuneMemory; // Remembers power level by band for tuning + QByteArray m_geometryNoControls; + QVector m_phaseEqCoefficients; + bool m_block_udp_status_updates; + + //---------------------------------------------------- private functions + void readSettings(); + void set_application_font (QFont const&); + void setDecodedTextFont (QFont const&); + void writeSettings(); + void createStatusBar(); + void updateStatusBar(); + void genStdMsgs(QString rpt, bool unconditional = false); + void genCQMsg(); + void clearDX (); + void lookup(); + void ba2msg(QByteArray ba, char* message); + void msgtype(QString t, QLineEdit* tx); + void stub(); + void statusChanged(); + void fixStop(); + bool shortList(QString callsign) const; + void transmit (double snr = 99.); + void rigFailure (QString const& reason); + void pskSetLocal (); + void pskPost(DecodedText const& decodedtext); + void displayDialFrequency (); + void transmitDisplay (bool); + void processMessage(DecodedText const& message, Qt::KeyboardModifiers = Qt::NoModifier); + void replyToCQ (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text, bool low_confidence, quint8 modifiers); + void locationChange(QString const& location); + void replayDecodes (); + void postDecode (bool is_new, QString const& message); + void postWSPRDecode (bool is_new, QStringList message_parts); + void enable_DXCC_entity (bool on); + void switch_mode (Mode); + void WSPR_scheduling (); + void freqCalStep(); + void setRig (Frequency = 0); // zero frequency means no change + void WSPR_history(Frequency dialFreq, int ndecodes); + QString beacon_start_time (int n = 0); + QString WSPR_message(); + void fast_config(bool b); + void CQTxFreq(); + void useNextCall(); + void abortQSO(); + void write_all(QString txRx, QString message); + bool isWorked(int itype, QString key, float fMHz=0, QString=""); + + QString save_wave_file (QString const& name + , short const * data + , int samples + , QString const& my_callsign + , QString const& my_grid + , QString const& mode + , qint32 sub_mode + , Frequency frequency + , QString const& his_call + , QString const& his_grid) const; + void hound_reply (); + QString sortHoundCalls(QString t, int isort, int max_dB); + void rm_tb4(QString houndCall); + void read_wav_file (QString const& fname); + void decodeDone (); + bool subProcessFailed (QProcess *, int exit_code, QProcess::ExitStatus); + void subProcessError (QProcess *, QProcess::ProcessError); + void statusUpdate () const; + void update_watchdog_label (); + void on_the_minute (); + void add_child_to_event_filter (QObject *); + void remove_child_from_event_filter (QObject *); + void setup_status_bar (bool vhf); + void tx_watchdog (bool triggered); + qint64 nWidgets(QString t); + void displayWidgets(qint64 n); + QChar current_submode () const; // returns QChar {0} if submode is not appropriate + void write_transmit_entry (QString const& file_name); + void selectHound(QString t); + void houndCallers(); + void foxRxSequencer(QString msg, QString houndCall, QString rptRcvd); + void foxTxSequencer(); + void foxGenWaveform(int i,QString fm); + void writeFoxQSO (QString const& msg); + void to_jt9(qint32 n, qint32 istart, qint32 idone); + bool is77BitMode () const; + void cease_auto_Tx_after_QSO (); +}; + +extern int killbyname(const char* progName); +extern void getDev(int* numDevices,char hostAPI_DeviceName[][50], + int minChan[], int maxChan[], + int minSpeed[], int maxSpeed[]); +extern int next_tx_state(int pctx); + +#endif // MAINWINDOW_H diff --git a/patch/widgets/mainwindow.ui b/patch/widgets/mainwindow.ui new file mode 100755 index 0000000..a12c4c6 --- /dev/null +++ b/patch/widgets/mainwindow.ui @@ -0,0 +1,3650 @@ + + + MainWindow + + + WSJT-X by K1JT + + + + + + + + + + Qt::Horizontal + + + + + + + + 500 + 16777215 + + + + + 10 + 50 + false + + + + Band Activity + + + Qt::AlignCenter + + + + + + + + 300 + 20 + + + + + 16777215 + 20 + + + + + + + + + 252 + 252 + 252 + + + + + + + 170 + 170 + 170 + + + + + + + + + 252 + 252 + 252 + + + + + + + 170 + 170 + 170 + + + + + + + + + 170 + 170 + 170 + + + + + + + 170 + 170 + 170 + + + + + + + + true + + + UTC dB DT Freq Dr + + + Qt::PlainText + + + 5 + + + + + + + QFrame::StyledPanel + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAsNeeded + + + 0 + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + 50 + false + + + + Rx Frequency + + + Qt::AlignCenter + + + + + + + + 300 + 20 + + + + + 16777215 + 20 + + + + + + + + + 252 + 252 + 252 + + + + + + + 170 + 170 + 170 + + + + + + + + + 252 + 252 + 252 + + + + + + + 170 + 170 + 170 + + + + + + + + + 170 + 170 + 170 + + + + + + + 170 + 170 + 170 + + + + + + + + true + + + UTC dB DT Freq Dr + + + Qt::PlainText + + + 5 + + + + + + + true + + + Qt::ScrollBarAlwaysOn + + + 0 + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + CQ only + + + + + + + + 50 + 0 + + + + Enter this QSO in log + + + Log &QSO + + + + + + + + 50 + 0 + + + + Stop monitoring + + + &Stop + + + + + + + + 50 + 0 + + + + Toggle monitoring On/Off + + + QPushButton:checked { + color: #000000; + background-color: #00ff00; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + &Monitor + + + true + + + false + + + + + + + + 50 + 0 + + + + <html><head/><body><p>Erase right window. Double-click to erase both windows.</p></body></html> + + + Erase right window. Double-click to erase both windows. + + + &Erase + + + + + + + true + + + <html><head/><body><p>Clear the accumulating message average.</p></body></html> + + + Clear the accumulating message average. + + + Clear Avg + + + + + + + + 50 + 0 + + + + <html><head/><body><p>Decode most recent Rx period at QSO Frequency</p></body></html> + + + Decode most recent Rx period at QSO Frequency + + + QPushButton:checked { + color: rgb(0, 0, 0); + background-color: cyan; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + &Decode + + + true + + + + + + + + 50 + 0 + + + + <html><head/><body><p>Toggle Auto-Tx On/Off</p></body></html> + + + Toggle Auto-Tx On/Off + + + QPushButton:checked { + color: rgb(0, 0, 0); + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + E&nable Tx + + + true + + + + + + + + 50 + 0 + + + + Stop transmitting immediately + + + &Halt Tx + + + + + + + <html><head/><body><p>Toggle a pure Tx tone On/Off</p></body></html> + + + Toggle a pure Tx tone On/Off + + + QPushButton:checked { + color: rgb(0, 0, 0); + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + &Tune + + + true + + + + + + + Menus + + + true + + + + + + + + + + + false + + + <html><head/><body><p>If orange or red there has been a rig control failure, click to reset and read the dial frequency. S implies split mode.</p></body></html> + + + If orange or red there has been a rig control failure, click to reset and read the dial frequency. S implies split mode. + + + QPushButton { + font-family: helvetica; + font-size: 9pt; + font-weight: bold; + background-color: white; + color: black; + border-style: solid; + border-width:1px; + border-radius:10px; + border-color: gray; + max-width:20px; + max-height:20px; + min-width:20px; + min-height:20px; +} +QPushButton[state="error"] { + background-color: red; +} +QPushButton[state="warning"] { + background-color: orange; +} +QPushButton[state="ok"] { + background-color: #00ff00; +} + + + ? + + + + + + + Pwr + + + + + + + Adjust Tx audio level + + + 450 + + + 0 + + + Qt::Vertical + + + true + + + true + + + QSlider::TicksBelow + + + 50 + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 159 + 175 + 213 + + + + + + + 159 + 175 + 213 + + + + + + + + true + + + DX Call + + + Qt::AlignCenter + + + 5 + + + 2 + + + dxCallEntry + + + + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 159 + 175 + 213 + + + + + + + 159 + 175 + 213 + + + + + + + + true + + + DX Grid + + + Qt::AlignCenter + + + 5 + + + 2 + + + dxGridEntry + + + + + + + + 0 + 0 + + + + Callsign of station to be worked + + + 11 + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Locator of station to be worked + + + ` + + + 6 + + + Qt::AlignCenter + + + + + + + + + Search for callsign in database + + + &Lookup + + + + + + + Add callsign and locator to database + + + Add + + + + + + + true + + + Az: 251 16553 km + + + Qt::AlignCenter + + + 4 + + + + + + + + + + + + + 0 + 0 + + + + QLabel { + font-family: MS Shell Dlg 2; + font-size: 16pt; + background-color : black; + color : yellow; +} + + + QFrame::StyledPanel + + + QFrame::Sunken + + + 2 + + + 0 + + + <html><head/><body><p align="center"> 2015 Jun 17 </p><p align="center"> 01:23:45 </p></body></html> + + + Qt::AlignCenter + + + 5 + + + + + + + + 0 + 0 + + + + USB dial frequency + + + QLabel { + font-family: MS Shell Dlg 2; + font-size: 16pt; + color : yellow; + background-color : black; +} +QLabel[oob="true"] { + background-color: red; +} + + + 14.078 000 + + + Qt::AlignCenter + + + 5 + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + false + + + <html><head/><body><p>Frequency to call CQ on in kHz above the current MHz</p></body></html> + + + Frequency to call CQ on in kHz above the current MHz + + + Tx CQ + + + 1 + + + 999 + + + 260 + + + + + + + false + + + <html><head/><body><p>Check this to call CQ on the &quot;Tx CQ&quot; frequency. Rx will be on the current frequency and the CQ message wiill include the current Rx frequency so callers know which frequency to reply on.</p><p>Not available to nonstandard callsign holders.</p></body></html> + + + Check this to call CQ on the "Tx CQ" frequency. Rx will be on the current frequency and the CQ message wiill include the current Rx frequency so callers know which frequency to reply on. +Not available to nonstandard callsign holders. + + + + + + + + + + Decode other Hounds calling above 1000 Hz audio offset + + + Rx All Freqs + + + + + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + Fox + + + Qt::AlignCenter + + + + + + + <html><head/><body><p>Check to monitor Sh messages.</p></body></html> + + + Check to monitor Sh messages. + + + SWL + + + + + + + Enable auto response to the first decode from a new DXCC or new call on the current band. + + + QPushButton:checked { + color: rgb(0, 0, 0); + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + Best S+P + + + true + + + + + + + <html><head/><body><p>Check this to start recording calibration data.<br/>While measuring calibration correction is disabled.<br/>When not checked you can view the calibration results.</p></body></html> + + + Check this to start recording calibration data. +While measuring calibration correction is disabled. +When not checked you can view the calibration results. + + + Measure + + + + + + + + + + + <html><head/><body><p>Check to use short-format messages.</p></body></html> + + + Check to use short-format messages. + + + Sh + + + + + + + <html><head/><body><p>Check to enable JT9 fast modes</p></body></html> + + + Check to enable JT9 fast modes + + + Fast + + + + + + + <html><head/><body><p>Check to enable automatic sequencing of Tx messages based on received messages.</p></body></html> + + + Check to enable automatic sequencing of Tx messages based on received messages. + + + Auto Seq + + + + + + + <html><head/><body><p>Check to call the first decoded responder to my CQ.</p></body></html> + + + Check to call the first decoded responder to my CQ. + + + Call 1st + + + + + + + + + false + + + Check to generate "@1250 (SEND MSGS)" in Tx6. + + + Tx6 + + + + + + + + + + + + <html><head/><body><p>Ignore calls from callsigns matching regex.</p></body></html> + + + Check to ignore calls from callsigns matching regex. + + + Regex ignore filter + + + + + + + + + + + + Last ignored + + + Callsign of last ignored station + + + + + + + + 0 + 0 + + + + Callsign of last ignored station + + + Qt::AlignCenter + + + + + + + + + + + <html><head/><body><p>Check to Tx in even-numbered minutes or sequences, starting at 0; uncheck for odd sequences.</p></body></html> + + + Check to Tx in even-numbered minutes or sequences, starting at 0; uncheck for odd sequences. + + + Tx even/1st + + + + + + + Audio Tx frequency + + + Qt::AlignCenter + + + Hz + + + Tx + + + 200 + + + 5000 + + + 1500 + + + + + + + + + + 0 + 0 + + + + + 35 + 0 + + + + Set Tx frequency to Rx Frequency + + + Set Tx frequency to Rx Frequency + + + + + + + + + + Frequency tolerance (Hz) + + + Qt::AlignCenter + + + F Tol + + + 1 + + + 1000 + + + 10 + + + + + + + + 0 + 0 + + + + + 35 + 0 + + + + Set Rx frequency to Tx Frequency + + + Set Rx frequency to Tx Frequency + + + + + + + + + + + + Audio Rx frequency + + + Qt::AlignCenter + + + Hz + + + Rx + + + 200 + + + 5000 + + + 1500 + + + + + + + <html><head/><body><p>Signal report: Signal-to-noise ratio in 2500 Hz reference bandwidth (dB).</p></body></html> + + + Signal report: Signal-to-noise ratio in 2500 Hz reference bandwidth (dB). + + + Qt::AlignCenter + + + Report + + + -50 + + + 49 + + + -15 + + + + + + + <html><head/><body><p>Tx/Rx or Frequency calibration sequence length</p></body></html> + + + Tx/Rx or Frequency calibration sequence length + + + Qt::AlignCenter + + + s + + + T/R + + + 5 + + + 1800 + + + 30 + + + + + + + + + + + <html><head/><body><p>Check to keep Tx frequency fixed when double-clicking on decoded text.</p></body></html> + + + Check to keep Tx frequency fixed when double-clicking on decoded text. + + + Hold Tx Freq + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Tx# + + + 1 + + + 4095 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + F Low + + + 100 + + + 5000 + + + 50 + + + 600 + + + + + + + Qt::AlignCenter + + + + + + F High + + + 100 + + + 5000 + + + 50 + + + 1400 + + + + + + + + + + + <html><head/><body><p>Submode determines tone spacing; A is narrowest.</p></body></html> + + + Submode determines tone spacing; A is narrowest. + + + Qt::AlignCenter + + + Submode + + + 0 + + + 7 + + + + + + + <html><head/><body><p>Synchronizing threshold. Lower numbers accept weaker sync signals.</p></body></html> + + + Synchronizing threshold. Lower numbers accept weaker sync signals. + + + Qt::AlignCenter + + + Sync + + + -1 + + + 10 + + + 1 + + + + + + + Maximum drift rate in units of symbol rate per transmission. + + + Max Drift + + + 50 + + + 5 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + QTabWidget::West + + + QTabWidget::Triangular + + + 0 + + + + 1 + + + + + + + + Send this message in next Tx interval + + + margin-left: 10%; margin-right: 0% + + + + + + Ctrl+6 + + + true + + + + + + + Queue up the next Tx message + + + Next + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + + + + <html><head/><body><p>Send this message in next Tx interval</p><p>Double-click to toggle between RRR and RR73 messages in Tx4 (not allowed for type 2 compound call holders)</p><p>RR73 messages should only be used when you are reasonably confident that no message repetitions will be required</p></body></html> + + + Send this message in next Tx interval +Double-click to toggle between RRR and RR73 messages in Tx4 (not allowed for type 2 compound call holders) +RR73 messages should only be used when you are reasonably confident that no message repetitions will be required + + + margin-left: 10%; margin-right: 0% + + + + + + Ctrl+4 + + + + + + + <html><head/><body><p>Send this message in next Tx interval</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station (not allowed for type 1 compound call holders)</p></body></html> + + + Send this message in next Tx interval +Double click to toggle the use of the Tx1 message to start a QSO with a station (not allowed for type 1 compound call holders) + + + margin-left: 10%; margin-right: 0% + + + + + + Ctrl+1 + + + + + + + + 0 + 0 + + + + + + + + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double-click to reset to the standard 73 message</p></body></html> + + + Switch to this Tx message NOW +Double-click to reset to the standard 73 message + + + padding-left: 15%; padding-right: 15%; padding-top: 3%; padding-bottom: 3% + + + Tx &5 + + + Alt+5 + + + + + + + Switch to this Tx message NOW + + + padding-left: 15%; padding-right: 15%; padding-top: 3%; padding-bottom: 3% + + + Tx &2 + + + Alt+2 + + + + + + + + 0 + 0 + + + + + + + + Switch to this Tx message NOW + + + Now + + + Qt::AlignCenter + + + + + + + Send this message in next Tx interval + + + margin-left: 10%; margin-right: 0% + + + + + + Ctrl+2 + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Generate standard messages for minimal QSO + + + Generate Std Msgs + + + + + + + Switch to this Tx message NOW + + + padding-left: 15%; padding-right: 15%; padding-top: 3%; padding-bottom: 3% + + + Tx &3 + + + Alt+3 + + + + + + + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double-click to toggle between RRR and RR73 messages in Tx4 (not allowed for type2 compound call holders)</p><p>RR73 messages should only be used when you are reasonably confident that no message repetitions will be required</p></body></html> + + + Switch to this Tx message NOW +Double-click to toggle between RRR and RR73 messages in Tx4 (not allowed for type2 compound call holders) +RR73 messages should only be used when you are reasonably confident that no message repetitions will be required + + + padding-left: 15%; padding-right: 15%; padding-top: 3%; padding-bottom: 3% + + + Tx &4 + + + Alt+4 + + + + + + + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station (not allowed for type 1 compound call holders)</p></body></html> + + + Switch to this Tx message NOW +Double click to toggle the use of the Tx1 message to start a QSO with a station (not allowed for type 1 compound call holders) + + + Qt::LeftToRight + + + padding-left: 15%; padding-right: 15%; padding-top: 3%; padding-bottom: 3% + + + Tx &1 + + + Alt+1 + + + + + + + Switch to this Tx message NOW + + + padding-left: 15%; padding-right: 15%; padding-top: 3%; padding-bottom: 3% + + + Tx &6 + + + Alt+6 + + + + + + + Send this message in next Tx interval + + + margin-left: 10%; margin-right: 0% + + + + + + Ctrl+3 + + + + + + + Enter a free text message (maximum 13 characters) +or select a predefined macro from the dropdown list. +Press ENTER to add the current text to the predefined +list. The list can be maintained in Settings (F2). + + + true + + + QComboBox::InsertAtBottom + + + QComboBox::AdjustToMinimumContentsLength + + + + + + + <html><head/><body><p>Send this message in next Tx interval</p><p>Double-click to reset to the standard 73 message</p></body></html> + + + Send this message in next Tx interval +Double-click to reset to the standard 73 message + + + margin-left: 10%; margin-right: 0% + + + + + + Ctrl+5 + + + + + + + + + + 2 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Max dB + + + -15 + + + 70 + + + 30 + + + + + + + CQ + + + + CQ + + + + + CQ AF + + + + + CQ AN + + + + + CQ AS + + + + + CQ EU + + + + + CQ NA + + + + + CQ OC + + + + + CQ SA + + + + + CQ 0 + + + + + CQ 1 + + + + + CQ 2 + + + + + CQ 3 + + + + + CQ 4 + + + + + CQ 5 + + + + + CQ 6 + + + + + CQ 7 + + + + + CQ 8 + + + + + CQ 9 + + + + + + + + Reset + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + N List + + + 5 + + + 100 + + + 12 + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + N Slots + + + 1 + + + 5 + + + 1 + + + 10 + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Random + + + 5 + + + + Random + + + + + Call + + + + + Grid + + + + + S/N (dB) + + + + + Distance + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + More CQs + + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::AlignCenter + + + Hz + + + Tx + + + 1400 + + + 1600 + + + 1500 + + + + + + + Qt::AlignCenter + + + Hz + + + Rx + + + 100 + + + 4900 + + + 100 + + + 1500 + + + + + + + Qt::AlignCenter + + + Hz + + + F Tol + + + 100 + + + 500 + + + 100 + + + + + + + true + + + 0 + + + + Random + + + + + 1/2 + + + + + 2/2 + + + + + 1/3 + + + + + 2/3 + + + + + 3/3 + + + + + 1/4 + + + + + 2/4 + + + + + 3/4 + + + + + 4/4 + + + + + 1/5 + + + + + 2/5 + + + + + 3/5 + + + + + 4/5 + + + + + 5/5 + + + + + 1/6 + + + + + 2/6 + + + + + 3/6 + + + + + 4/6 + + + + + 5/6 + + + + + 6/6 + + + + + + + + Percentage of minute sequences devoted to transmitting. + + + QSpinBox:enabled[notx="true"] { + color: rgb(0, 0, 0); + background-color: rgb(255, 255, 0); +} + + + Qt::AlignCenter + + + % + + + Tx Pct + + + 100 + + + + + + + Qt::AlignCenter + + + s + + + T/R + + + 15 + + + 1800 + + + + + + + Band Hopping + + + true + + + + + + Choose bands and times of day for band-hopping. + + + Schedule ... + + + + + + + + + + + + + + + + + + Upload decoded messages to WSPRnet.org. + + + QCheckBox:unchecked { + color: rgb(0, 0, 0); + background-color: rgb(255, 255, 0); +} + + + Upload spots + + + + + + + + + <html><head/><body><p>6 digit locators cause 2 different messages to be sent, the second contains the full locator but only a hashed callsign, other stations must have decoded the first once before they can decode your call in the second. Check this option to only send 4 digit locators if it will avoid the two message protocol.</p></body></html> + + + 6 digit locators cause 2 different messages to be sent, the second contains the full locator but only a hashed callsign, other stations must have decoded the first once before they can decode your call in the second. Check this option to only send 4 digit locators if it will avoid the two message protocol. + + + Prefer Type 1 messages + + + true + + + + + + + No own call decodes + + + + + + + + + <html><head/><body><p>Transmit during the next sequence.</p></body></html> + + + QPushButton:checked { + color: rgb(0, 0, 0); + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + Tx Next + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Set Tx power in dBm (dB above 1 mW) as part of your WSPR message. + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + Qt::AlignCenter + + + % + + + NB + + + -2 + + + 25 + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + <html><head/><body><p>30dB recommended when only noise present<br/>Green when good<br/>Red when clipping may occur<br/>Yellow when too low</p></body></html> + + + Rx Signal + + + 30dB recommended when only noise present +Green when good +Red when clipping may occur +Yellow when too low + + + QFrame::Panel + + + QFrame::Sunken + + + + + + + + + <html><head/><body><p>Select operating band or enter frequency in MHz or enter kHz increment followed by k.</p></body></html> + + + Frequency entry + + + Select operating band or enter frequency in MHz or enter kHz increment followed by k. + + + true + + + QComboBox::NoInsert + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + + + + + + + + + + + + 0 + 0 + 842 + 21 + + + + + File + + + + + + + + + + + + + + + + + + + + + View + + + + + + + + + + + + + + Decode + + + + + + + + + + + + + + + Save + + + + + + + + Help + + + + + + + + + + + + + + + + + + + + + Mode + + + + + + + + + + + + + + + + + + + + + Configurations + + + + + Tools + + + + + + + + + + + + + + + + + + + + + + + Exit + + + QAction::QuitRole + + + + + About WSJT-X + + + QAction::AboutRole + + + + + Waterfall + + + + + Open + + + Ctrl+O + + + + + Open next in directory + + + + + Decode remaining files in directory + + + Shift+F6 + + + + + Delete all *.wav && *.c2 files in SaveDir + + + + + true + + + false + + + Fast + + + + + true + + + true + + + None + + + + + true + + + Save all + + + + + Online User Guide + + + + + Keyboard shortcuts + + + + + Special mouse commands + + + + + true + + + true + + + JT9 + + + + + true + + + true + + + Save decoded + + + + + true + + + false + + + Normal + + + + + true + + + true + + + Deep + + + + + Erase ALL.TXT + + + + + Erase wsjtx_log.adi + + + + + true + + + JT65 + + + + + true + + + Astronomical data + + + + + List of Type 1 prefixes and suffixes + + + + + Settings... + + + QAction::PreferencesRole + + + + + Local User Guide + + + + + Open log directory + + + + + true + + + JT4 + + + + + Message averaging + + + F7 + + + + + true + + + Enable averaging + + + + + true + + + Enable deep search + + + + + true + + + WSPR + + + + + Echo Graph + + + F8 + + + + + true + + + Echo + + + EME Echo mode + + + + + true + + + ISCAT + + + + + Fast Graph + + + F9 + + + + + &Download Samples ... + + + <html><head/><body><p>Download sample audio files demonstrating the various modes.</p></body></html> + + + + + true + + + MSK144 + + + + + true + + + QRA64 + + + + + Release Notes + + + + + true + + + Enable AP for DX Call + + + + + true + + + FreqCal + + + + + Measure reference spectrum + + + + + Measure phase response + + + + + Erase reference spectrum + + + + + true + + + Execute frequency calibration cycle + + + + + Equalization tools ... + + + + + true + + + true + + + FT8 + + + + + true + + + Enable AP + + + + + true + + + Enable AP + + + + + Solve for calibration parameters + + + + + Copyright notice + + + Shift+F1 + + + + + false + + + Fox log + + + + + FT8 DXpedition Mode User Guide + + + + + Reset Cabrillo log ... + + + + + Color highlighting scheme + + + + + Export Cabrillo log ... + + + + + Quick-Start Guide to FST4 and FST4W + + + + + Contest log + + + + + Erase WSPR hashtable + + + + + true + + + FT4 + + + + + true + + + FST4 + + + + + true + + + FST4W + + + + + true + + + Q65 + + + + + true + + + SWL Mode + + + Hide lower panel controls to maximize deocde windows + + + + + Quick-Start Guide to Q65 + + + + + true + + + Auto Clear Avg after decode + + + + + Quick-Start Guide to WSJT-X 2.5.0 and MAP65 3.0 + + + + + + + DisplayText + QTextBrowser +
widgets/displaytext.h
+
+ + LettersSpinBox + QSpinBox +
widgets/LettersSpinBox.hpp
+
+ + SignalMeter + QFrame +
widgets/signalmeter.h
+ 1 +
+ + HintedSpinBox + QSpinBox +
widgets/HintedSpinBox.hpp
+
+ + RestrictedSpinBox + QSpinBox +
widgets/RestrictedSpinBox.hpp
+
+ + DoubleClickableRadioButton + QRadioButton +
widgets/DoubleClickableRadioButton.hpp
+
+ + DoubleClickablePushButton + QPushButton +
widgets/DoubleClickablePushButton.hpp
+
+ + BandComboBox + QComboBox +
widgets/BandComboBox.hpp
+
+
+ + decodedTextBrowser + decodedTextBrowser2 + cbCQonly + logQSOButton + stopButton + monitorButton + EraseButton + ClrAvgButton + DecodeButton + autoButton + stopTxButton + tuneButton + cbMenus + bandComboBox + readFreq + sbNB + dxCallEntry + dxGridEntry + txFirstCheckBox + TxFreqSpinBox + pbR2T + sbFtol + pbT2R + RxFreqSpinBox + rptSpinBox + sbTR + cbHoldTxFreq + sbF_Low + sbF_High + sbSubmode + syncSpinBox + sbCQTxFreq + cbCQTx + cbRxAll + cbShMsgs + cbFast9 + cbAutoSeq + cbFirst + ignoreRegex + cbTx6 + cbSWL + pbBestSP + measure_check_box + tabWidget + genStdMsgsPushButton + tx1 + txrb1 + txb1 + tx2 + txrb2 + txb2 + tx3 + txrb3 + txb3 + tx4 + txrb4 + txb4 + tx5 + txrb5 + txb5 + tx6 + txrb6 + txb6 + textBrowser4 + comboBoxHoundSort + sbNlist + sbMax_dB + sbNslots + comboBoxCQ + cbMoreCQs + pbFoxReset + WSPRfreqSpinBox + sbFST4W_RxFreq + sbFST4W_FTol + RoundRobin + sbTxPercent + sbTR_FST4W + band_hopping_group_box + band_hopping_schedule_push_button + cbUploadWSPR_Spots + WSPR_prefer_type_1_check_box + cbNoOwnCall + pbTxNext + TxPowerComboBox + outAttenuation + sbSerialNumber + + + +
From 92402c6d80072b27a43081a79c1d049019151fa3 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:36:23 +0200 Subject: [PATCH 16/47] test 8 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e690434..15abedd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: name: wsjtx-source-code - name: Unpack the source - run: mkdir unpack && tar -xzvf wsjtx-v2.5.4.tgz unpack/ + run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-v2.5.4.tgz unpack/ - name: Copy pattch run: pwd && ls -l && ls -l unpack/ From 152f2b902f8e410e45e79ea80a6836cd9169e5a2 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:37:24 +0200 Subject: [PATCH 17/47] test 9 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15abedd..91da7d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: name: wsjtx-source-code - name: Unpack the source - run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-v2.5.4.tgz unpack/ + run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz unpack/ - name: Copy pattch run: pwd && ls -l && ls -l unpack/ From 1c15efa58028886702b5d1eed38b5cb84dc96f4a Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 14:39:03 +0200 Subject: [PATCH 18/47] test 10 --- .github/workflows/build.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91da7d4..0291f12 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,23 +29,23 @@ jobs: name: wsjtx-source-code - name: Unpack the source - run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz unpack/ + run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ - name: Copy pattch run: pwd && ls -l && ls -l unpack/ - build_focal: - needs: ['patch_source'] - runs-on: ubuntu-latest - steps: - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: wsjtx-patched-code - - - name: Create Docker Image - run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build - - - name: Run Container - run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax +# build_focal: +# needs: ['patch_source'] +# runs-on: ubuntu-latest +# steps: +# - name: Download artifacts +# uses: actions/download-artifact@v2 +# with: +# name: wsjtx-patched-code +# +# - name: Create Docker Image +# run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build +# +# - name: Run Container +# run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax From 4a9d840fe23e881e583af889975e6e8dd492f86a Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 15:02:00 +0200 Subject: [PATCH 19/47] patch flow --- .github/workflows/build.yml | 50 +++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0291f12..db33179 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,19 +31,43 @@ jobs: - name: Unpack the source run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ - - name: Copy pattch - run: pwd && ls -l && ls -l unpack/ - - -# build_focal: -# needs: ['patch_source'] -# runs-on: ubuntu-latest -# steps: -# - name: Download artifacts -# uses: actions/download-artifact@v2 -# with: -# name: wsjtx-patched-code -# + - name: Prepare source + run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ + + - name: Patch WSJTX + run: cp -R patch/* newsrc/wsjtx/ + + - name: Repack source + run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. + + - name: Copy package + run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ + + - name: Generate md5sum + run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum + + - name: Create new archive + run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * + + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-patched-code + path: upload/wsjtx-patched-2.5.4.tgz + + + build_focal: + needs: ['patch_source'] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + name: wsjtx-patched-code + + - name: where am I + run: pwd && ls -l + # - name: Create Docker Image # run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build # From d2ff0386e7a7ef079abe619615fbb325646753f4 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 15:04:10 +0200 Subject: [PATCH 20/47] typo fix --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db33179..9375c50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: wsjtx-patched-code - path: upload/wsjtx-patched-2.5.4.tgz + path: unpack/wsjtx-patched-2.5.4.tgz build_focal: @@ -66,7 +66,7 @@ jobs: name: wsjtx-patched-code - name: where am I - run: pwd && ls -l + run: pwd && ls -l # - name: Create Docker Image # run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build From a5be2d2c2ff139842790393abe46c6384c9314f8 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 15:06:51 +0200 Subject: [PATCH 21/47] updates checkouts --- .github/workflows/build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9375c50..c745efe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,8 @@ jobs: needs: ['download_wsjtx'] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Get source code + uses: actions/checkout@v2 - name: Download artifacts uses: actions/download-artifact@v2 @@ -60,6 +61,10 @@ jobs: needs: ['patch_source'] runs-on: ubuntu-latest steps: + + - name: Get source code + uses: actions/checkout@v2 + - name: Download artifacts uses: actions/download-artifact@v2 with: From 72e5860eb56cd33e94a049a0b824db39a4a706d6 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 16:54:18 +0200 Subject: [PATCH 22/47] enbaled build in docker --- .github/workflows/build.yml | 11 ++++++----- docker/ubuntu20.04/Dockerfile | 4 ---- docker/ubuntu_focal/Dockerfile | 8 ++++++++ docker/ubuntu_focal/build.sh | 9 +++++++++ 4 files changed, 23 insertions(+), 9 deletions(-) delete mode 100644 docker/ubuntu20.04/Dockerfile create mode 100644 docker/ubuntu_focal/Dockerfile create mode 100755 docker/ubuntu_focal/build.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c745efe..fbfe616 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,8 +73,9 @@ jobs: - name: where am I run: pwd && ls -l -# - name: Create Docker Image -# run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build -# -# - name: Run Container -# run: docker run --name build -v $GITHUB_WORKSPACE:/tmp ubuntu_focal_build ps uax + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build + + - name: Run Container + run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh + diff --git a/docker/ubuntu20.04/Dockerfile b/docker/ubuntu20.04/Dockerfile deleted file mode 100644 index cab98fe..0000000 --- a/docker/ubuntu20.04/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM ubuntu:20.04 - -RUN echo "Ubuntu 20.04 build" - diff --git a/docker/ubuntu_focal/Dockerfile b/docker/ubuntu_focal/Dockerfile new file mode 100644 index 0000000..c67c581 --- /dev/null +++ b/docker/ubuntu_focal/Dockerfile @@ -0,0 +1,8 @@ +FROM ubuntu:20.04 + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -y gcc\ + g++ clang gfortran cmake git asciidoc texinfo qtmultimedia5-dev\ + libqt5serialport5-dev qttools5-dev qttools5-dev-tools libboost-all-dev\ + libfftw3-dev libreadline-dev libusb-1.0-0-dev libudev-dev portaudio19-dev\ + build-essential binutils lintian debhelper dh-make devscripts + diff --git a/docker/ubuntu_focal/build.sh b/docker/ubuntu_focal/build.sh new file mode 100755 index 0000000..5c1e246 --- /dev/null +++ b/docker/ubuntu_focal/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +tar -xzvf wsjtx-patched-2.5.4.tgz +mkdir build +cd build +cmake -DWSJT_SKIP_MANPAGES=ON -DWSJT_GENERATE_DOCS=OFF ../wsjtx-patched-2.5.4 +cmake --build . --target package + +cp /build/build/wsjtx-prefix/src/wsjtx-build/wsjtx_2.5.4_amd64.deb /build/wsjtx_2.5.4_focal_amd64.deb \ No newline at end of file From 2e22e8a45c780430bc0f95263ff7aed33743a52c Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 16:56:07 +0200 Subject: [PATCH 23/47] fixed path for ubuntu --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbfe616..da1ff31 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,11 +70,8 @@ jobs: with: name: wsjtx-patched-code - - name: where am I - run: pwd && ls -l - - name: Create Docker Image - run: cd $GITHUB_WORKSPACE/docker/ubuntu20.04 && docker build . --tag ubuntu_focal_build + run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build - name: Run Container run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh From 173d565bea195b24db9f03fa67d49da75aebaaf3 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 17:02:53 +0200 Subject: [PATCH 24/47] path fix --- docker/ubuntu_focal/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/ubuntu_focal/build.sh b/docker/ubuntu_focal/build.sh index 5c1e246..efd3055 100755 --- a/docker/ubuntu_focal/build.sh +++ b/docker/ubuntu_focal/build.sh @@ -3,7 +3,7 @@ tar -xzvf wsjtx-patched-2.5.4.tgz mkdir build cd build -cmake -DWSJT_SKIP_MANPAGES=ON -DWSJT_GENERATE_DOCS=OFF ../wsjtx-patched-2.5.4 +cmake -DWSJT_SKIP_MANPAGES=ON -DWSJT_GENERATE_DOCS=OFF ../wsjtx-2.5.4 cmake --build . --target package cp /build/build/wsjtx-prefix/src/wsjtx-build/wsjtx_2.5.4_amd64.deb /build/wsjtx_2.5.4_focal_amd64.deb \ No newline at end of file From 8a07b70581f920a5cf90eab6a19f8a93e8788ab6 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 17:28:32 +0200 Subject: [PATCH 25/47] upload package and clean old artifacts --- .github/workflows/build.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da1ff31..5e55d1c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,7 +61,6 @@ jobs: needs: ['patch_source'] runs-on: ubuntu-latest steps: - - name: Get source code uses: actions/checkout@v2 @@ -76,3 +75,19 @@ jobs: - name: Run Container run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-focal-package + path: wsjtx_2.5.4_focal_amd64.deb + + clean_artifacts: + needs: ['build_focal'] + runs-on: ubuntu-focal + steps: + - name: Delete Artifact + uses: GeekyEggo/delete-artifact@v1.0.0 + with: + name: | + wsjtx-patched-code + wsjtx-source-code From 761210bfceb6cde380147329fc9c63e2225af0e7 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:04:12 +0200 Subject: [PATCH 26/47] release test --- .github/workflows/build.yml | 158 +++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 65 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e55d1c..b975866 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,77 +17,105 @@ jobs: name: wsjtx-source-code path: wsjtx-2.5.4.tgz - patch_source: - needs: ['download_wsjtx'] + release: runs-on: ubuntu-latest steps: - - name: Get source code - uses: actions/checkout@v2 - - name: Download artifacts uses: actions/download-artifact@v2 with: name: wsjtx-source-code - - name: Unpack the source - run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ - - - name: Prepare source - run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ - - - name: Patch WSJTX - run: cp -R patch/* newsrc/wsjtx/ - - - name: Repack source - run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. - - - name: Copy package - run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ - - - name: Generate md5sum - run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum - - - name: Create new archive - run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * - - - name: Upload new patched archive - uses: actions/upload-artifact@v2 - with: - name: wsjtx-patched-code - path: unpack/wsjtx-patched-2.5.4.tgz - - - build_focal: - needs: ['patch_source'] - runs-on: ubuntu-latest - steps: - - name: Get source code - uses: actions/checkout@v2 - - - name: Download artifacts - uses: actions/download-artifact@v2 - with: - name: wsjtx-patched-code - - - name: Create Docker Image - run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build - - - name: Run Container - run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh - - - name: Upload new patched archive - uses: actions/upload-artifact@v2 + - name: release + uses: actions/create-release@v1 + id: create_release with: - name: wsjtx-focal-package - path: wsjtx_2.5.4_focal_amd64.deb - - clean_artifacts: - needs: ['build_focal'] - runs-on: ubuntu-focal - steps: - - name: Delete Artifact - uses: GeekyEggo/delete-artifact@v1.0.0 + draft: true + prerelease: true + release_name: ${{ steps.version.outputs.version }} + tag_name: ${{ github.ref }} + env: + GITHUB_TOKEN: ${{ github.token }} + - name: upload darwin artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} with: - name: | - wsjtx-patched-code - wsjtx-source-code + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: wsjtx-2.5.4.tgz + asset_name: wsjtx-2.5.4.tgz + asset_content_type: application/gzip + +# patch_source: +# needs: ['download_wsjtx'] +# runs-on: ubuntu-latest +# steps: +# - name: Get source code +# uses: actions/checkout@v2 +# +# - name: Download artifacts +# uses: actions/download-artifact@v2 +# with: +# name: wsjtx-source-code +# +# - name: Unpack the source +# run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ +# +# - name: Prepare source +# run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ +# +# - name: Patch WSJTX +# run: cp -R patch/* newsrc/wsjtx/ +# +# - name: Repack source +# run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. +# +# - name: Copy package +# run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ +# +# - name: Generate md5sum +# run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum +# +# - name: Create new archive +# run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * +# +# - name: Upload new patched archive +# uses: actions/upload-artifact@v2 +# with: +# name: wsjtx-patched-code +# path: unpack/wsjtx-patched-2.5.4.tgz +# + +# build_focal: +# needs: ['patch_source'] +# runs-on: ubuntu-latest +# steps: +# - name: Get source code +# uses: actions/checkout@v2 +# +# - name: Download artifacts +# uses: actions/download-artifact@v2 +# with: +# name: wsjtx-patched-code +# +# - name: Create Docker Image +# run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build +# +# - name: Run Container +# run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh +# +# - name: Upload new patched archive +# uses: actions/upload-artifact@v2 +# with: +# name: wsjtx-focal-package +# path: wsjtx_2.5.4_focal_amd64.deb + +# clean_artifacts: +# needs: ['build_focal'] +# runs-on: ubuntu-latest +# steps: +# - name: Delete Artifact +# uses: GeekyEggo/delete-artifact@v1.0.0 +# with: +# name: | +# wsjtx-patched-code +# wsjtx-source-code From a1e79f497c845c9fa0700e1a52958162bcce2185 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:07:06 +0200 Subject: [PATCH 27/47] release fix --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b975866..1e15549 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,7 @@ jobs: path: wsjtx-2.5.4.tgz release: + needs: ['download_wsjtx'] runs-on: ubuntu-latest steps: - name: Download artifacts From 6b982d355857b3b9fde1bea21b39031cbc804f89 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:11:39 +0200 Subject: [PATCH 28/47] release test --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e15549..0ee9210 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: with: draft: true prerelease: true - release_name: ${{ steps.version.outputs.version }} + release_name: Release ${{ github.ref }} tag_name: ${{ github.ref }} env: GITHUB_TOKEN: ${{ github.token }} From 95e368312235949c784d188257b313750d0dd212 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:16:14 +0200 Subject: [PATCH 29/47] release test 2 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ee9210..ada04d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: with: draft: true prerelease: true - release_name: Release ${{ github.ref }} + release_name: Release ${{ github.ref_name }} ${{ github.sha }} tag_name: ${{ github.ref }} env: GITHUB_TOKEN: ${{ github.token }} From 5e98958e4a7efee8230958fd4e90d34b34f50dcc Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:18:30 +0200 Subject: [PATCH 30/47] release test 3 --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ada04d1..f59c92b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,13 +26,17 @@ jobs: with: name: wsjtx-source-code + - name: Set current date as env variable + run: echo "NOW::$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV + + - name: release uses: actions/create-release@v1 id: create_release with: draft: true prerelease: true - release_name: Release ${{ github.ref_name }} ${{ github.sha }} + release_name: Release ${NOW} tag_name: ${{ github.ref }} env: GITHUB_TOKEN: ${{ github.token }} From 206884c6385f942af8684577e06efb60481cf6a5 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:20:27 +0200 Subject: [PATCH 31/47] release test 4 --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f59c92b..173bb86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,9 @@ jobs: with: name: wsjtx-source-code + - name: display + run: echo $GITHUB_ENV + - name: Set current date as env variable run: echo "NOW::$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV From 00743d72353fdbc1328963aed5e8cb1370e893f8 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:21:29 +0200 Subject: [PATCH 32/47] release test 5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 173bb86..75620ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: name: wsjtx-source-code - name: display - run: echo $GITHUB_ENV + run: cat $GITHUB_ENV - name: Set current date as env variable run: echo "NOW::$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV From 9c14590a641ebb894f0e3427d0a5e89b136f0338 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:25:40 +0200 Subject: [PATCH 33/47] release test 6 --- .github/workflows/build.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75620ea..ea50b84 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,17 +29,13 @@ jobs: - name: display run: cat $GITHUB_ENV - - name: Set current date as env variable - run: echo "NOW::$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV - - - name: release uses: actions/create-release@v1 id: create_release with: draft: true prerelease: true - release_name: Release ${NOW} + release_name: Release ${{ github.run_number }} tag_name: ${{ github.ref }} env: GITHUB_TOKEN: ${{ github.token }} From eef808aa1859a455985f510ed15af0d2db15ceeb Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:26:45 +0200 Subject: [PATCH 34/47] trigger From 06e657aa0ebbc3e97380803ce54d35c649c3b7b6 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:46:49 +0200 Subject: [PATCH 35/47] test full release draft --- .github/workflows/build.yml | 170 ++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 87 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea50b84..c7f0261 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,6 @@ name: Build packages on: [push] -env: - TARGET: package - jobs: download_wsjtx: runs-on: ubuntu-latest @@ -17,109 +14,108 @@ jobs: name: wsjtx-source-code path: wsjtx-2.5.4.tgz - release: + + patch_source: needs: ['download_wsjtx'] runs-on: ubuntu-latest steps: + - name: Get source code + uses: actions/checkout@v2 + - name: Download artifacts uses: actions/download-artifact@v2 with: name: wsjtx-source-code - - name: display - run: cat $GITHUB_ENV + - name: Unpack the source + run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ + + - name: Prepare source + run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ + + - name: Patch WSJTX + run: cp -R patch/* newsrc/wsjtx/ + + - name: Repack source + run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. + + - name: Copy package + run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ + + - name: Generate md5sum + run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum - - name: release + - name: Create new archive + run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * + + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-patched-code + path: unpack/wsjtx-patched-2.5.4.tgz + + + build_focal: + needs: ['patch_source'] + runs-on: ubuntu-latest + steps: + - name: Get source code + uses: actions/checkout@v2 + + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + name: wsjtx-patched-code + + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build + + - name: Run Container + run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh + + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-focal-package + path: wsjtx_2.5.4_focal_amd64.deb + + clean_artifacts: + needs: ['build_focal'] + runs-on: ubuntu-latest + steps: + - name: Delete Artifact + uses: GeekyEggo/delete-artifact@v1.0.0 + with: + name: | + wsjtx-patched-code + wsjtx-source-code + + release: + needs: ['clean_artifacts'] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + + - name: Release draft uses: actions/create-release@v1 - id: create_release + id: create_release_draft with: draft: true prerelease: true release_name: Release ${{ github.run_number }} tag_name: ${{ github.ref }} + body: | + Automatic build on push. This is not a release. env: GITHUB_TOKEN: ${{ github.token }} - - name: upload darwin artifact + + - name: upload focal artifact uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ github.token }} with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: wsjtx-2.5.4.tgz - asset_name: wsjtx-2.5.4.tgz + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx_2.5.4_focal_amd64.deb + asset_name: wsjtx_2.5.4_focal_amd64.deb asset_content_type: application/gzip - -# patch_source: -# needs: ['download_wsjtx'] -# runs-on: ubuntu-latest -# steps: -# - name: Get source code -# uses: actions/checkout@v2 -# -# - name: Download artifacts -# uses: actions/download-artifact@v2 -# with: -# name: wsjtx-source-code -# -# - name: Unpack the source -# run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ -# -# - name: Prepare source -# run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ -# -# - name: Patch WSJTX -# run: cp -R patch/* newsrc/wsjtx/ -# -# - name: Repack source -# run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. -# -# - name: Copy package -# run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ -# -# - name: Generate md5sum -# run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum -# -# - name: Create new archive -# run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * -# -# - name: Upload new patched archive -# uses: actions/upload-artifact@v2 -# with: -# name: wsjtx-patched-code -# path: unpack/wsjtx-patched-2.5.4.tgz -# - -# build_focal: -# needs: ['patch_source'] -# runs-on: ubuntu-latest -# steps: -# - name: Get source code -# uses: actions/checkout@v2 -# -# - name: Download artifacts -# uses: actions/download-artifact@v2 -# with: -# name: wsjtx-patched-code -# -# - name: Create Docker Image -# run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build -# -# - name: Run Container -# run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh -# -# - name: Upload new patched archive -# uses: actions/upload-artifact@v2 -# with: -# name: wsjtx-focal-package -# path: wsjtx_2.5.4_focal_amd64.deb - -# clean_artifacts: -# needs: ['build_focal'] -# runs-on: ubuntu-latest -# steps: -# - name: Delete Artifact -# uses: GeekyEggo/delete-artifact@v1.0.0 -# with: -# name: | -# wsjtx-patched-code -# wsjtx-source-code From bc5d424dbc700088b5df3e186081fa431bea861d Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 18:48:14 +0200 Subject: [PATCH 36/47] test full release draft 2 --- .github/workflows/build.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7f0261..e9bcf8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,16 +79,16 @@ jobs: name: wsjtx-focal-package path: wsjtx_2.5.4_focal_amd64.deb - clean_artifacts: - needs: ['build_focal'] - runs-on: ubuntu-latest - steps: - - name: Delete Artifact - uses: GeekyEggo/delete-artifact@v1.0.0 - with: - name: | - wsjtx-patched-code - wsjtx-source-code + clean_artifacts: + needs: ['build_focal'] + runs-on: ubuntu-latest + steps: + - name: Delete Artifact + uses: GeekyEggo/delete-artifact@v1.0.0 + with: + name: | + wsjtx-patched-code + wsjtx-source-code release: needs: ['clean_artifacts'] From 240af4265c96fa1a371ea85aad0be709ee3fccd1 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 19:07:48 +0200 Subject: [PATCH 37/47] looking for the issue --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9bcf8f..a7fbf82 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,6 +97,9 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v2 + - name: what inside + run: ls -l && pwd + - name: Release draft uses: actions/create-release@v1 id: create_release_draft @@ -110,6 +113,9 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: what inside + run: ls -l && pwd + - name: upload focal artifact uses: actions/upload-release-asset@v1 env: From 754fed70bfcca999dbb42d0086f6c6e4d6ab30d7 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 19:27:37 +0200 Subject: [PATCH 38/47] another test --- .github/workflows/build.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7fbf82..73ce1ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,16 +90,13 @@ jobs: wsjtx-patched-code wsjtx-source-code - release: + release_draft: needs: ['clean_artifacts'] runs-on: ubuntu-latest steps: - name: Download artifacts uses: actions/download-artifact@v2 - - name: what inside - run: ls -l && pwd - - name: Release draft uses: actions/create-release@v1 id: create_release_draft @@ -113,15 +110,12 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} - - name: what inside - run: ls -l && pwd - - name: upload focal artifact uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ github.token }} with: upload_url: ${{ steps.create_release_draft.outputs.upload_url }} - asset_path: wsjtx_2.5.4_focal_amd64.deb + asset_path: wsjtx-focal-package/wsjtx_2.5.4_focal_amd64.deb asset_name: wsjtx_2.5.4_focal_amd64.deb asset_content_type: application/gzip From 19468b4bda6abb7bcad853a5d00e268c825a56b7 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 20:06:46 +0200 Subject: [PATCH 39/47] added jammy --- .github/workflows/build.yml | 36 +++++++++++++++++++++++++++++++++- README.md | 35 ++++++++++++++++++++++++++++++++- docker/ubuntu_jammy/Dockerfile | 8 ++++++++ docker/ubuntu_jammy/build.sh | 9 +++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 docker/ubuntu_jammy/Dockerfile create mode 100755 docker/ubuntu_jammy/build.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73ce1ae..f13ca10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,8 +79,32 @@ jobs: name: wsjtx-focal-package path: wsjtx_2.5.4_focal_amd64.deb + build_jammy: + needs: ['patch_source'] + runs-on: ubuntu-latest + steps: + - name: Get source code + uses: actions/checkout@v2 + + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + name: wsjtx-patched-code + + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu_jammy && docker build . --tag ubuntu_jammy_build + + - name: Run Container + run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_jammy_build /build/docker/ubuntu_jammy/build.sh + + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-jammy-package + path: wsjtx_2.5.4_jammy_amd64.deb + clean_artifacts: - needs: ['build_focal'] + needs: ['build_focal' , 'build_jammy'] runs-on: ubuntu-latest steps: - name: Delete Artifact @@ -119,3 +143,13 @@ jobs: asset_path: wsjtx-focal-package/wsjtx_2.5.4_focal_amd64.deb asset_name: wsjtx_2.5.4_focal_amd64.deb asset_content_type: application/gzip + + - name: upload jammy artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx-jammy-package/wsjtx_2.5.4_jammy_amd64.deb + asset_name: wsjtx_2.5.4_jammy_amd64.deb + asset_content_type: application/gzip diff --git a/README.md b/README.md index 1c4ecf3..a55a76a 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -# wsjtx-regex-filter +# WSJT-X 2.5.4 - Regex ignore filter + +In order to apply this change just overwrite contents of root and `widgets/` +folders. To find out what has changed execute a diff or search for `SP6XD` +comments in the source code. + +## Features +- Added option: Setup -> RX/TX Macros -> RX regex ignore filter +![Options](images/options.png) +- CQ answers from matched callsigns are ignored +- reply to CQ from matched callsigns are ignored +- Manual clicks (calls) to matched stations are ignored + +![Main window](images/main.png) + +Just like that. Tested during WW DIGI contest. + +## Note + +I have to confess that I'm not a software developer and this patch is just +a dirty hack to make it possible to make auto QSO with all except Russians. +To filter out RU stations use this filter: **^(R[A-Z1-9]|U[A-I])+** + +I know that this is not part of "ham spirit" to make such a software but... +let's face it. One does not simply attack its neighbour and expects others +to stay quiet. As a Pole I know my country has long and cloudy history with +both Russians and Ukrainians,but what happened in the past should stay in the past. +One may remember, one may forget, one may cry for revenge or one my simply forgive. +From my perspective, we live in 2022, Europe, in the times of wide spread Internet. +Suddenly war explodes at the borders of my country. The war that makes no sense, +pure aggression and disgusting act of terrorism. As long as regular Russians do not +resist to their goverment I would act and make software like this, no matter what. + +Bartek SP6XD diff --git a/docker/ubuntu_jammy/Dockerfile b/docker/ubuntu_jammy/Dockerfile new file mode 100644 index 0000000..7d13ff0 --- /dev/null +++ b/docker/ubuntu_jammy/Dockerfile @@ -0,0 +1,8 @@ +FROM ubuntu:22.04 + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -y gcc\ + g++ clang gfortran cmake git asciidoc texinfo qtmultimedia5-dev\ + libqt5serialport5-dev qttools5-dev qttools5-dev-tools libboost-all-dev\ + libfftw3-dev libreadline-dev libusb-1.0-0-dev libudev-dev portaudio19-dev\ + build-essential binutils lintian debhelper dh-make devscripts + diff --git a/docker/ubuntu_jammy/build.sh b/docker/ubuntu_jammy/build.sh new file mode 100755 index 0000000..8016a1c --- /dev/null +++ b/docker/ubuntu_jammy/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +tar -xzvf wsjtx-patched-2.5.4.tgz +mkdir build +cd build +cmake -DWSJT_SKIP_MANPAGES=ON -DWSJT_GENERATE_DOCS=OFF ../wsjtx-2.5.4 +cmake --build . --target package -- -j12 + +cp /build/build/wsjtx-prefix/src/wsjtx-build/wsjtx_2.5.4_amd64.deb /build/wsjtx_2.5.4_jammy_amd64.deb From 11d52b28b0056a0118a0002a075d057ca3a67dec Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 20:08:24 +0200 Subject: [PATCH 40/47] reorder pipeline --- .github/workflows/build.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f13ca10..a89e0aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,19 +103,9 @@ jobs: name: wsjtx-jammy-package path: wsjtx_2.5.4_jammy_amd64.deb - clean_artifacts: - needs: ['build_focal' , 'build_jammy'] - runs-on: ubuntu-latest - steps: - - name: Delete Artifact - uses: GeekyEggo/delete-artifact@v1.0.0 - with: - name: | - wsjtx-patched-code - wsjtx-source-code release_draft: - needs: ['clean_artifacts'] + needs: ['build_focal' , 'build_jammy'] runs-on: ubuntu-latest steps: - name: Download artifacts @@ -153,3 +143,16 @@ jobs: asset_path: wsjtx-jammy-package/wsjtx_2.5.4_jammy_amd64.deb asset_name: wsjtx_2.5.4_jammy_amd64.deb asset_content_type: application/gzip + + clean_artifacts: + needs: ['release_draft'] + runs-on: ubuntu-latest + steps: + - name: Delete Artifact + uses: GeekyEggo/delete-artifact@v1.0.0 + with: + name: | + wsjtx-patched-code + wsjtx-source-code + wsjtx-focal-package + wsjtx-jammy-package From 4efe742e4cb32141f9043cf27893b1508870b735 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 21:08:07 +0200 Subject: [PATCH 41/47] still working on actions --- .github/workflows/build.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a89e0aa..618f775 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,10 @@ -name: Build packages -on: [push] +name: Build dev packages +on: + push: + tags-ignore: + - '**' + branches: + - 'dev/*' jobs: download_wsjtx: From 05d3dd2a8b2f996ab392a50ce6a14988164676dd Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 21:43:24 +0200 Subject: [PATCH 42/47] enable release tags --- .github/workflows/release.yml | 196 ++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..791b99c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,196 @@ +name: Build release +on: + push: + tags: + - '**' + branches-ignore: + - '**' + +jobs: + download_wsjtx: + runs-on: ubuntu-latest + steps: + - name: Downlaod source code + run: wget https://physics.princeton.edu/pulsar/k1jt/wsjtx-2.5.4.tgz + + - name: Save WSJTX for next jobs + uses: actions/upload-artifact@v2 + with: + name: wsjtx-source-code + path: wsjtx-2.5.4.tgz + + + release: + needs: ['download_wsjtx'] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + - wsjtx-source-code + + - name: Release draft + uses: actions/create-release@v1 + id: create_release_draft + with: + draft: false + prerelease: false + release_name: Release ${{ github.ref_name }} + tag_name: ${{ github.ref }} + body: | + Release build. + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: upload focal artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx-2.5.4.tgz + asset_name: wsjtx-2.5.4.tgz + asset_content_type: application/gzip + + +# patch_source: +# needs: ['download_wsjtx'] +# runs-on: ubuntu-latest +# steps: +# - name: Get source code +# uses: actions/checkout@v2 +# +# - name: Download artifacts +# uses: actions/download-artifact@v2 +# with: +# name: wsjtx-source-code +# +# - name: Unpack the source +# run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ +# +# - name: Prepare source +# run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ +# +# - name: Patch WSJTX +# run: cp -R patch/* newsrc/wsjtx/ +# +# - name: Repack source +# run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. +# +# - name: Copy package +# run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ +# +# - name: Generate md5sum +# run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum +# +# - name: Create new archive +# run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * +# +# - name: Upload new patched archive +# uses: actions/upload-artifact@v2 +# with: +# name: wsjtx-patched-code +# path: unpack/wsjtx-patched-2.5.4.tgz +# +# +# build_focal: +# needs: ['patch_source'] +# runs-on: ubuntu-latest +# steps: +# - name: Get source code +# uses: actions/checkout@v2 +# +# - name: Download artifacts +# uses: actions/download-artifact@v2 +# with: +# name: wsjtx-patched-code +# +# - name: Create Docker Image +# run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build +# +# - name: Run Container +# run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh +# +# - name: Upload new patched archive +# uses: actions/upload-artifact@v2 +# with: +# name: wsjtx-focal-package +# path: wsjtx_2.5.4_focal_amd64.deb +# +# build_jammy: +# needs: ['patch_source'] +# runs-on: ubuntu-latest +# steps: +# - name: Get source code +# uses: actions/checkout@v2 +# +# - name: Download artifacts +# uses: actions/download-artifact@v2 +# with: +# name: wsjtx-patched-code +# +# - name: Create Docker Image +# run: cd $GITHUB_WORKSPACE/docker/ubuntu_jammy && docker build . --tag ubuntu_jammy_build +# +# - name: Run Container +# run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_jammy_build /build/docker/ubuntu_jammy/build.sh +# +# - name: Upload new patched archive +# uses: actions/upload-artifact@v2 +# with: +# name: wsjtx-jammy-package +# path: wsjtx_2.5.4_jammy_amd64.deb +# + + release_draft: + needs: ['build_focal' , 'build_jammy'] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + + - name: Release draft + uses: actions/create-release@v1 + id: create_release_draft + with: + draft: true + prerelease: true + release_name: Release ${{ github.run_number }} + tag_name: ${{ github.ref }} + body: | + Automatic build on push. This is not a release. + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: upload focal artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx-focal-package/wsjtx_2.5.4_focal_amd64.deb + asset_name: wsjtx_2.5.4_focal_amd64.deb + asset_content_type: application/gzip + + - name: upload jammy artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx-jammy-package/wsjtx_2.5.4_jammy_amd64.deb + asset_name: wsjtx_2.5.4_jammy_amd64.deb + asset_content_type: application/gzip + + clean_artifacts: + needs: ['release_draft'] + runs-on: ubuntu-latest + steps: + - name: Delete Artifact + uses: GeekyEggo/delete-artifact@v1.0.0 + with: + name: | + wsjtx-patched-code + wsjtx-source-code + wsjtx-focal-package + wsjtx-jammy-package From 50fe9d77d22cadb9e2523f34f48d387ed2c01860 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 21:49:19 +0200 Subject: [PATCH 43/47] fixing pipeline --- .github/workflows/release.yml | 110 +++++++++++++++++----------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 791b99c..5b5efe4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,6 @@ jobs: name: wsjtx-source-code path: wsjtx-2.5.4.tgz - release: needs: ['download_wsjtx'] runs-on: ubuntu-latest @@ -27,7 +26,7 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v2 with: - - wsjtx-source-code + name: wsjtx-source-code - name: Release draft uses: actions/create-release@v1 @@ -141,56 +140,57 @@ jobs: # name: wsjtx-jammy-package # path: wsjtx_2.5.4_jammy_amd64.deb # - - release_draft: - needs: ['build_focal' , 'build_jammy'] - runs-on: ubuntu-latest - steps: - - name: Download artifacts - uses: actions/download-artifact@v2 - - - name: Release draft - uses: actions/create-release@v1 - id: create_release_draft - with: - draft: true - prerelease: true - release_name: Release ${{ github.run_number }} - tag_name: ${{ github.ref }} - body: | - Automatic build on push. This is not a release. - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: upload focal artifact - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release_draft.outputs.upload_url }} - asset_path: wsjtx-focal-package/wsjtx_2.5.4_focal_amd64.deb - asset_name: wsjtx_2.5.4_focal_amd64.deb - asset_content_type: application/gzip - - - name: upload jammy artifact - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release_draft.outputs.upload_url }} - asset_path: wsjtx-jammy-package/wsjtx_2.5.4_jammy_amd64.deb - asset_name: wsjtx_2.5.4_jammy_amd64.deb - asset_content_type: application/gzip - - clean_artifacts: - needs: ['release_draft'] - runs-on: ubuntu-latest - steps: - - name: Delete Artifact - uses: GeekyEggo/delete-artifact@v1.0.0 - with: - name: | - wsjtx-patched-code - wsjtx-source-code - wsjtx-focal-package - wsjtx-jammy-package +# +# release_draft: +# needs: ['build_focal' , 'build_jammy'] +# runs-on: ubuntu-latest +# steps: +# - name: Download artifacts +# uses: actions/download-artifact@v2 +# +# - name: Release draft +# uses: actions/create-release@v1 +# id: create_release_draft +# with: +# draft: true +# prerelease: true +# release_name: Release ${{ github.run_number }} +# tag_name: ${{ github.ref }} +# body: | +# Automatic build on push. This is not a release. +# env: +# GITHUB_TOKEN: ${{ github.token }} +# +# - name: upload focal artifact +# uses: actions/upload-release-asset@v1 +# env: +# GITHUB_TOKEN: ${{ github.token }} +# with: +# upload_url: ${{ steps.create_release_draft.outputs.upload_url }} +# asset_path: wsjtx-focal-package/wsjtx_2.5.4_focal_amd64.deb +# asset_name: wsjtx_2.5.4_focal_amd64.deb +# asset_content_type: application/gzip +# +# - name: upload jammy artifact +# uses: actions/upload-release-asset@v1 +# env: +# GITHUB_TOKEN: ${{ github.token }} +# with: +# upload_url: ${{ steps.create_release_draft.outputs.upload_url }} +# asset_path: wsjtx-jammy-package/wsjtx_2.5.4_jammy_amd64.deb +# asset_name: wsjtx_2.5.4_jammy_amd64.deb +# asset_content_type: application/gzip +# +# clean_artifacts: +# needs: ['release_draft'] +# runs-on: ubuntu-latest +# steps: +# - name: Delete Artifact +# uses: GeekyEggo/delete-artifact@v1.0.0 +# with: +# name: | +# wsjtx-patched-code +# wsjtx-source-code +# wsjtx-focal-package +# wsjtx-jammy-package +# \ No newline at end of file From 4036ca758bd524c13f2f4e87a0493ad0f9371b20 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 22:09:56 +0200 Subject: [PATCH 44/47] added images and release test --- .github/workflows/release.yml | 272 ++++++++++++++++------------------ README.md | 12 +- images/main.png | Bin 0 -> 39970 bytes images/options.png | Bin 0 -> 30091 bytes 4 files changed, 135 insertions(+), 149 deletions(-) create mode 100644 images/main.png create mode 100644 images/options.png diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b5efe4..7b1d4f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Build release +name: Build release packages on: push: tags: @@ -19,22 +19,110 @@ jobs: name: wsjtx-source-code path: wsjtx-2.5.4.tgz - release: + + patch_source: needs: ['download_wsjtx'] runs-on: ubuntu-latest steps: + - name: Get source code + uses: actions/checkout@v2 + - name: Download artifacts uses: actions/download-artifact@v2 with: name: wsjtx-source-code + - name: Unpack the source + run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ + + - name: Prepare source + run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ + + - name: Patch WSJTX + run: cp -R patch/* newsrc/wsjtx/ + + - name: Repack source + run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. + + - name: Copy package + run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ + + - name: Generate md5sum + run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum + + - name: Create new archive + run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * + + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-patched-code + path: unpack/wsjtx-patched-2.5.4.tgz + + + build_focal: + needs: ['patch_source'] + runs-on: ubuntu-latest + steps: + - name: Get source code + uses: actions/checkout@v2 + + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + name: wsjtx-patched-code + + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build + + - name: Run Container + run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh + + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-focal-package + path: wsjtx_2.5.4_focal_amd64.deb + + build_jammy: + needs: ['patch_source'] + runs-on: ubuntu-latest + steps: + - name: Get source code + uses: actions/checkout@v2 + + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + name: wsjtx-patched-code + + - name: Create Docker Image + run: cd $GITHUB_WORKSPACE/docker/ubuntu_jammy && docker build . --tag ubuntu_jammy_build + + - name: Run Container + run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_jammy_build /build/docker/ubuntu_jammy/build.sh + + - name: Upload new patched archive + uses: actions/upload-artifact@v2 + with: + name: wsjtx-jammy-package + path: wsjtx_2.5.4_jammy_amd64.deb + + + release_draft: + needs: ['build_focal' , 'build_jammy'] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + - name: Release draft uses: actions/create-release@v1 id: create_release_draft with: draft: false prerelease: false - release_name: Release ${{ github.ref_name }} + release_name: Release ${{ github.rev_name }} tag_name: ${{ github.ref }} body: | Release build. @@ -47,150 +135,40 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: upload_url: ${{ steps.create_release_draft.outputs.upload_url }} - asset_path: wsjtx-2.5.4.tgz - asset_name: wsjtx-2.5.4.tgz + asset_path: wsjtx-focal-package/wsjtx_2.5.4_focal_amd64.deb + asset_name: wsjtx_2.5.4_focal_amd64.deb + asset_content_type: application/gzip + + - name: upload jammy artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx-jammy-package/wsjtx_2.5.4_jammy_amd64.deb + asset_name: wsjtx_2.5.4_jammy_amd64.deb + asset_content_type: application/gzip + + - name: upload patched source code + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx-patched-2.5.4.tgz + asset_name: wsjtx-patched-2.5.4.tgz asset_content_type: application/gzip -# patch_source: -# needs: ['download_wsjtx'] -# runs-on: ubuntu-latest -# steps: -# - name: Get source code -# uses: actions/checkout@v2 -# -# - name: Download artifacts -# uses: actions/download-artifact@v2 -# with: -# name: wsjtx-source-code -# -# - name: Unpack the source -# run: mkdir unpack && pwd && ls -l && tar -xzvf wsjtx-2.5.4.tgz -C unpack/ -# -# - name: Prepare source -# run: mkdir newsrc && tar -xzvf unpack/wsjtx-2.5.4/src/wsjtx.tgz -C newsrc/ -# -# - name: Patch WSJTX -# run: cp -R patch/* newsrc/wsjtx/ -# -# - name: Repack source -# run: cd newsrc/ && tar -czvf wsjtx.tgz * && cd .. -# -# - name: Copy package -# run: cp newsrc/wsjtx.tgz unpack/wsjtx-2.5.4/src/ -# -# - name: Generate md5sum -# run: cd unpack/wsjtx-2.5.4/src/ && md5sum wsjtx.tgz > wsjtx.tgz.md5sum -# -# - name: Create new archive -# run: cd unpack/ && tar -czvf wsjtx-patched-2.5.4.tgz * -# -# - name: Upload new patched archive -# uses: actions/upload-artifact@v2 -# with: -# name: wsjtx-patched-code -# path: unpack/wsjtx-patched-2.5.4.tgz -# -# -# build_focal: -# needs: ['patch_source'] -# runs-on: ubuntu-latest -# steps: -# - name: Get source code -# uses: actions/checkout@v2 -# -# - name: Download artifacts -# uses: actions/download-artifact@v2 -# with: -# name: wsjtx-patched-code -# -# - name: Create Docker Image -# run: cd $GITHUB_WORKSPACE/docker/ubuntu_focal && docker build . --tag ubuntu_focal_build -# -# - name: Run Container -# run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_focal_build /build/docker/ubuntu_focal/build.sh -# -# - name: Upload new patched archive -# uses: actions/upload-artifact@v2 -# with: -# name: wsjtx-focal-package -# path: wsjtx_2.5.4_focal_amd64.deb -# -# build_jammy: -# needs: ['patch_source'] -# runs-on: ubuntu-latest -# steps: -# - name: Get source code -# uses: actions/checkout@v2 -# -# - name: Download artifacts -# uses: actions/download-artifact@v2 -# with: -# name: wsjtx-patched-code -# -# - name: Create Docker Image -# run: cd $GITHUB_WORKSPACE/docker/ubuntu_jammy && docker build . --tag ubuntu_jammy_build -# -# - name: Run Container -# run: docker run -t -v $GITHUB_WORKSPACE:/build -w /build ubuntu_jammy_build /build/docker/ubuntu_jammy/build.sh -# -# - name: Upload new patched archive -# uses: actions/upload-artifact@v2 -# with: -# name: wsjtx-jammy-package -# path: wsjtx_2.5.4_jammy_amd64.deb -# -# -# release_draft: -# needs: ['build_focal' , 'build_jammy'] -# runs-on: ubuntu-latest -# steps: -# - name: Download artifacts -# uses: actions/download-artifact@v2 -# -# - name: Release draft -# uses: actions/create-release@v1 -# id: create_release_draft -# with: -# draft: true -# prerelease: true -# release_name: Release ${{ github.run_number }} -# tag_name: ${{ github.ref }} -# body: | -# Automatic build on push. This is not a release. -# env: -# GITHUB_TOKEN: ${{ github.token }} -# -# - name: upload focal artifact -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ github.token }} -# with: -# upload_url: ${{ steps.create_release_draft.outputs.upload_url }} -# asset_path: wsjtx-focal-package/wsjtx_2.5.4_focal_amd64.deb -# asset_name: wsjtx_2.5.4_focal_amd64.deb -# asset_content_type: application/gzip -# -# - name: upload jammy artifact -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ github.token }} -# with: -# upload_url: ${{ steps.create_release_draft.outputs.upload_url }} -# asset_path: wsjtx-jammy-package/wsjtx_2.5.4_jammy_amd64.deb -# asset_name: wsjtx_2.5.4_jammy_amd64.deb -# asset_content_type: application/gzip -# -# clean_artifacts: -# needs: ['release_draft'] -# runs-on: ubuntu-latest -# steps: -# - name: Delete Artifact -# uses: GeekyEggo/delete-artifact@v1.0.0 -# with: -# name: | -# wsjtx-patched-code -# wsjtx-source-code -# wsjtx-focal-package -# wsjtx-jammy-package -# \ No newline at end of file + clean_artifacts: + needs: ['release_draft'] + runs-on: ubuntu-latest + steps: + - name: Delete Artifact + uses: GeekyEggo/delete-artifact@v1.0.0 + with: + name: | + wsjtx-patched-code + wsjtx-source-code + wsjtx-focal-package + wsjtx-jammy-package diff --git a/README.md b/README.md index a55a76a..29912f1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ -# WSJT-X 2.5.4 - Regex ignore filter +# WSJT-X 2.5.4 - Regex ignore filter + +## Build and binaries In order to apply this change just overwrite contents of root and `widgets/` folders. To find out what has changed execute a diff or search for `SP6XD` comments in the source code. +Navigate to [GitHub Actions](https://github.com/d3cker/wsjtx-regex-filter/actions) for build process details. +[Releases](https://github.com/d3cker/wsjtx-regex-filter/releases): +- Ubuntu Focal 20.04 +- Ubuntu Jammy 22.04 +- Patched source code tgz archive (follow original INSTALL instructions) + ## Features - Added option: Setup -> RX/TX Macros -> RX regex ignore filter ![Options](images/options.png) @@ -13,7 +21,7 @@ comments in the source code. ![Main window](images/main.png) -Just like that. Tested during WW DIGI contest. +Just like that. Tested during WW DIGI'22 contest. ## Note diff --git a/images/main.png b/images/main.png new file mode 100644 index 0000000000000000000000000000000000000000..e9b91faa690f6a41f6a1b5dc02e2a8ee41b54be1 GIT binary patch literal 39970 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 Y9}A!hbyyQ{b^rhX07*qoM6N<$g2QktdH?_b literal 0 HcmV?d00001 diff --git a/images/options.png b/images/options.png new file mode 100644 index 0000000000000000000000000000000000000000..ef276a15982a016fb0fb3c1489c29a835734b0d4 GIT binary patch literal 30091 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 literal 0 HcmV?d00001 From aaa576e09c6198f58aaa7cce7f970d03ac815553 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 22:32:15 +0200 Subject: [PATCH 45/47] fixed path for source code --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b1d4f4..44075d5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -155,7 +155,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: upload_url: ${{ steps.create_release_draft.outputs.upload_url }} - asset_path: wsjtx-patched-2.5.4.tgz + asset_path: wsjtx-patched-code/wsjtx-patched-2.5.4.tgz asset_name: wsjtx-patched-2.5.4.tgz asset_content_type: application/gzip From e6134087b684303fafa41549ab2cf967f37650f2 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 22:35:44 +0200 Subject: [PATCH 46/47] naming fixed --- .github/workflows/build.yml | 13 ++++++++++++- .github/workflows/release.yml | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 618f775..8f72a11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,7 +122,7 @@ jobs: with: draft: true prerelease: true - release_name: Release ${{ github.run_number }} + release_name: Development build ${{ github.run_number }} tag_name: ${{ github.ref }} body: | Automatic build on push. This is not a release. @@ -149,6 +149,17 @@ jobs: asset_name: wsjtx_2.5.4_jammy_amd64.deb asset_content_type: application/gzip + - name: upload patched source code + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release_draft.outputs.upload_url }} + asset_path: wsjtx-patched-code/wsjtx-patched-2.5.4.tgz + asset_name: wsjtx-patched-2.5.4.tgz + asset_content_type: application/gzip + + clean_artifacts: needs: ['release_draft'] runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44075d5..7fcc220 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -122,7 +122,7 @@ jobs: with: draft: false prerelease: false - release_name: Release ${{ github.rev_name }} + release_name: Release ${{ github.ref_name }} tag_name: ${{ github.ref }} body: | Release build. From ce4bc04e5f7553b6ab155f994d5fb59d69a6aee0 Mon Sep 17 00:00:00 2001 From: d3cker Date: Sun, 2 Oct 2022 23:01:05 +0200 Subject: [PATCH 47/47] updated readme --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 29912f1..6259388 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,29 @@ ## Build and binaries -In order to apply this change just overwrite contents of root and `widgets/` -folders. To find out what has changed execute a diff or search for `SP6XD` -comments in the source code. - Navigate to [GitHub Actions](https://github.com/d3cker/wsjtx-regex-filter/actions) for build process details. -[Releases](https://github.com/d3cker/wsjtx-regex-filter/releases): + +[Available releases](https://github.com/d3cker/wsjtx-regex-filter/releases): - Ubuntu Focal 20.04 - Ubuntu Jammy 22.04 -- Patched source code tgz archive (follow original INSTALL instructions) +- Patched source code tgz archive + +If you want to compile this patch by your own, just grab patched source code from +[Releases](https://github.com/d3cker/wsjtx-regex-filter/releases) page and follow +original INSTALL instructions. ## Features - Added option: Setup -> RX/TX Macros -> RX regex ignore filter ![Options](images/options.png) - CQ answers from matched callsigns are ignored - reply to CQ from matched callsigns are ignored -- Manual clicks (calls) to matched stations are ignored +- Manual clicks (calls) on matched stations are ignored ![Main window](images/main.png) Just like that. Tested during WW DIGI'22 contest. -## Note +## Note on purpose I have to confess that I'm not a software developer and this patch is just a dirty hack to make it possible to make auto QSO with all except Russians.