From e35dee55d15d3e5b61e7d40593abe2c8386997c1 Mon Sep 17 00:00:00 2001 From: Philipp Wagner Date: Sun, 19 Apr 2026 13:00:26 +0200 Subject: [PATCH] Wintest improvements sked and options (#32) * Improve Win-Test Integration in UI * Rename Building Artifacts * Document Changes * Win-Test Fix QRG Sked --- .github/workflows/nightly-artifacts.yml | 37 ++-- .github/workflows/tagged-release.yml | 38 ++-- github_docs/de-Installation.md | 31 ++- github_docs/de-Log-Synchronisation.md | 25 ++- github_docs/en-Installation.md | 31 ++- github_docs/en-Log-Sync.md | 25 ++- .../controller/ChatController.java | 108 ++++++++-- .../MessageBusManagementThread.java | 7 + .../controller/ReadUDPByWintestThread.java | 44 ++++- .../kst4contest/model/ChatPreferences.java | 36 ++++ .../view/Kst4ContestApplication.java | 186 ++++++++++++------ 11 files changed, 439 insertions(+), 129 deletions(-) diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index ecff87d..313aad9 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - test-mac schedule: - cron: "20 2 * * *" workflow_dispatch: @@ -86,7 +85,7 @@ jobs: SHORT_SHA="${GITHUB_SHA::7}" echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV" - echo "ASSET_BASENAME=praktiKST-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" + echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" - name: Set up Java 17 uses: actions/setup-java@v4.1.0 @@ -107,7 +106,7 @@ jobs: mkdir -p dist jpackage \ --type app-image \ - --name praktiKST \ + --name KST4Contest \ --input target/dist-libs \ --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ @@ -117,41 +116,41 @@ jobs: - name: Create AppDir metadata run: | - rm -rf target/praktiKST.AppDir - cp -a dist/praktiKST target/praktiKST.AppDir + rm -rf target/KST4Contest.AppDir + cp -a dist/KST4Contest target/KST4Contest.AppDir - cat > target/praktiKST.AppDir/AppRun << 'EOF' + cat > target/KST4Contest.AppDir/AppRun << 'EOF' #!/bin/sh HERE="$(dirname "$(readlink -f "$0")")" - exec "$HERE/bin/praktiKST" "$@" + exec "$HERE/bin/KST4Contest" "$@" EOF - chmod +x target/praktiKST.AppDir/AppRun + chmod +x target/KST4Contest.AppDir/AppRun - cat > target/praktiKST.AppDir/praktiKST.desktop << 'EOF' + cat > target/KST4Contest.AppDir/KST4Contest.desktop << 'EOF' [Desktop Entry] Type=Application - Name=praktiKST - Exec=praktiKST - Icon=praktiKST + Name=KST4Contest + Exec=KST4Contest + Icon=KST4Contest Categories=Network;HamRadio; Terminal=false EOF - if [ -f target/praktiKST.AppDir/lib/praktiKST.png ]; then - cp target/praktiKST.AppDir/lib/praktiKST.png target/praktiKST.AppDir/praktiKST.png + if [ -f target/KST4Contest.AppDir/lib/KST4Contest.png ]; then + cp target/KST4Contest.AppDir/lib/KST4Contest.png target/KST4Contest.AppDir/KST4Contest.png fi - name: Build AppImage run: | wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x target/appimagetool.AppImage - APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/praktiKST.AppDir "dist/${ASSET_BASENAME}-linux-x86_64.AppImage" + APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/KST4Contest.AppDir "dist/${ASSET_BASENAME}-linux-x86_64.AppImage" - name: Upload Linux artifact uses: actions/upload-artifact@v4.3.4 with: name: linux-appimage - path: dist/praktiKST-*-linux-x86_64.AppImage + path: dist/KST4Contest-*-linux-x86_64.AppImage retention-days: 14 build-macos-dmg: @@ -172,7 +171,7 @@ jobs: ARCH=$(uname -m) echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV" - echo "ASSET_BASENAME=praktiKST-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" + echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" echo "ARCH=$ARCH" >> "$GITHUB_ENV" - name: Set up Java 17 @@ -194,7 +193,7 @@ jobs: mkdir -p dist jpackage \ --type dmg \ - --name praktiKST \ + --name KST4Contest \ --input target/dist-libs \ --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ @@ -217,5 +216,5 @@ jobs: uses: actions/upload-artifact@v4.3.4 with: name: macos-dmg-${{ matrix.os }} - path: dist/praktiKST-*-macos-*.dmg + path: dist/KST4Contest-*-macos-*.dmg retention-days: 14 diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml index 1203258..2f3f520 100644 --- a/.github/workflows/tagged-release.yml +++ b/.github/workflows/tagged-release.yml @@ -88,7 +88,7 @@ jobs: mkdir -p dist jpackage \ --type app-image \ - --name praktiKST \ + --name KST4Contest \ --input target/dist-libs \ --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ @@ -98,41 +98,41 @@ jobs: - name: Create AppDir metadata run: | - rm -rf target/praktiKST.AppDir - cp -a dist/praktiKST target/praktiKST.AppDir + rm -rf target/KST4Contest.AppDir + cp -a dist/KST4Contest target/KST4Contest.AppDir - cat > target/praktiKST.AppDir/AppRun << 'EOF' + cat > target/KST4Contest.AppDir/AppRun << 'EOF' #!/bin/sh HERE="$(dirname "$(readlink -f "$0")")" - exec "$HERE/bin/praktiKST" "$@" + exec "$HERE/bin/KST4Contest" "$@" EOF - chmod +x target/praktiKST.AppDir/AppRun + chmod +x target/KST4Contest.AppDir/AppRun - cat > target/praktiKST.AppDir/praktiKST.desktop << 'EOF' + cat > target/KST4Contest.AppDir/KST4Contest.desktop << 'EOF' [Desktop Entry] Type=Application - Name=praktiKST - Exec=praktiKST - Icon=praktiKST + Name=KST4Contest + Exec=KST4Contest + Icon=KST4Contest Categories=Network;HamRadio; Terminal=false EOF - if [ -f target/praktiKST.AppDir/lib/praktiKST.png ]; then - cp target/praktiKST.AppDir/lib/praktiKST.png target/praktiKST.AppDir/praktiKST.png + if [ -f target/KST4Contest.AppDir/lib/KST4Contest.png ]; then + cp target/KST4Contest.AppDir/lib/KST4Contest.png target/KST4Contest.AppDir/KST4Contest.png fi - name: Build AppImage run: | wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x target/appimagetool.AppImage - APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/praktiKST.AppDir dist/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage + APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/KST4Contest.AppDir dist/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage - name: Upload Linux artifact uses: actions/upload-artifact@v4.3.4 with: name: linux-appimage - path: dist/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage + path: dist/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage build-macos-dmg: name: Build macOS DMG (${{ matrix.os }}) @@ -164,7 +164,7 @@ jobs: mkdir -p dist jpackage \ --type dmg \ - --name praktiKST \ + --name KST4Contest \ --input target/dist-libs \ --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ @@ -182,13 +182,13 @@ jobs: if [ -z "$DMG" ]; then echo "No DMG produced by jpackage" && exit 1 fi - mv "$DMG" "dist/praktiKST-${{ github.ref_name }}-macos-${ARCH}.dmg" + mv "$DMG" "dist/KST4Contest-${{ github.ref_name }}-macos-${ARCH}.dmg" - name: Upload macOS artifact uses: actions/upload-artifact@v4.3.4 with: name: macos-dmg-${{ matrix.os }} - path: dist/praktiKST-${{ github.ref_name }}-macos-*.dmg + path: dist/KST4Contest-${{ github.ref_name }}-macos-*.dmg build-docs-pdf: name: Build Documentation PDF @@ -315,7 +315,7 @@ jobs: generateReleaseNotes: true artifacts: >- release-assets/windows/praktiKST-${{ github.ref_name }}-windows-x64.zip, - release-assets/linux/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage, - release-assets/macos/praktiKST-${{ github.ref_name }}-macos-*.dmg, + release-assets/linux/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage, + release-assets/macos/KST4Contest-${{ github.ref_name }}-macos-*.dmg, release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-en.pdf, release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-de.pdf diff --git a/github_docs/de-Installation.md b/github_docs/de-Installation.md index 81186b2..2570f0c 100644 --- a/github_docs/de-Installation.md +++ b/github_docs/de-Installation.md @@ -46,7 +46,17 @@ Die aktuelle Version kann als AppImage heruntergeladen werden: **https://github.com/praktimarc/kst4contest/releases/latest** -Der Dateiname hat das Format `praktiKST-v-linux-x86_64.AppImage`. +Der Dateiname hat das Format `KST4Contest-v-linux-x86_64.AppImage`. + +### macOS + +> ⚠️ **Best-Effort-Support:** macOS-Builds werden als zusätzliche Option bereitgestellt, sind aber **nicht umfassend getestet**. Wir bauen und veröffentlichen macOS-Binaries mit jedem Release, können allerdings nicht alle Szenarien unter macOS testen. Bei Problemen freuen wir uns über eine Rückmeldung – wir versuchen unser Bestes, können aber nicht den gleichen Support-Umfang wie für Windows und Linux garantieren. + +Die aktuelle Version kann als DMG-Disk-Image heruntergeladen werden (für Apple-Silicon- und Intel-Macs verfügbar): + +**https://github.com/praktimarc/kst4contest/releases/latest** + +Der Dateiname hat das Format `KST4Contest-v-macos-.dmg`, wobei `` entweder `arm64` (Apple Silicon) oder `x86_64` (Intel) ist. --- @@ -64,11 +74,22 @@ Die Einstellungen werden unter `%USERPROFILE%\.praktikst\preferences.xml` gespei ### Linux 1. AppImage herunterladen. 2. AppImage in gewünschten Ordner entpacken. -3. AppImage ausführbar machen (geht im Terminal mit `chmod +x praktiKST-v-linux-x86_64.AppImage`) +3. AppImage ausführbar machen (geht im Terminal mit `chmod +x KST4Contest-v-linux-x86_64.AppImage`) 4. AppImage ausführen. Die Einstellungen werden unter `~/.praktikst/preferences.xml` gespeichert. +### macOS +1. DMG-Datei für die eigene Architektur herunterladen (Apple Silicon oder Intel). +2. DMG-Datei öffnen. +3. `KST4Contest.app` in den **Programme**-Ordner ziehen. +4. Beim ersten Start zeigt macOS ggf. eine Warnung, da die App nicht notarisiert ist. Zum Öffnen: + - Rechtsklick (oder Ctrl-Klick) auf `KST4Contest.app` im Finder → **Öffnen** wählen. + - Alternativ: **Systemeinstellungen → Datenschutz & Sicherheit** → **Trotzdem öffnen** klicken. +5. KST4Contest aus dem Programme-Ordner oder dem Launchpad starten. + +Die Einstellungen werden unter `~/.praktikst/preferences.xml` gespeichert. + --- ## Update @@ -98,6 +119,12 @@ Derzeit folgendermaßen: 2. neues AppImage ausführbar makieren 3. (optional) altes AppImage löschen. +#### macOS + +1. Neue DMG-Datei herunterladen. +2. DMG öffnen. +3. Die neue `KST4Contest.app` in den **Programme**-Ordner ziehen und die alte Version ersetzen. + --- diff --git a/github_docs/de-Log-Synchronisation.md b/github_docs/de-Log-Synchronisation.md index 8bc78ff..0fb98f4 100644 --- a/github_docs/de-Log-Synchronisation.md +++ b/github_docs/de-Log-Synchronisation.md @@ -83,20 +83,26 @@ Für den integrierten DX-Cluster-Server: N1MM+ als DX-Cluster-Client konfigurier ### Win-Test -Win-Test wird mit einem dedizierten UDP-Netzwerk-Listener unterstützt, der das native Win-Test Netzwerkprotokoll versteht. +Win-Test wird mit einem dedizierten UDP-Netzwerk-Listener unterstützt, der das native Win-Test Netzwerkprotokoll versteht. **Vorteile der Win-Test Integration:** - Automatische QSO-Synchronisation zur Markierung gearbeiteter Stationen. -- **Sked-Übergabe (ADDSKED):** Über den Button "Create sked" im Stationsinfo-Panel wird nicht nur in KST4Contest ein Sked angelegt, sondern dieses auch *direkt per UDP an das Win-Test Netzwerk als ADDSKED-Paket gesendet*. +- **Sked-Übergabe (ADDSKED):** Über den Button "Create sked" im Stationsinfo-Panel wird nicht nur in KST4Contest ein Sked angelegt, sondern dieser auch *direkt per UDP an das Win-Test Netzwerk als ADDSKED-Paket gesendet* – automatisch, sobald der Listener aktiv ist. - Es kann zwischen den Sked-Modi "AUTO", "SSB" oder "CW" gewählt werden. +- **Automatische QRG-Auflösung für SKEDs:** KST4Contest wählt die Sked-Frequenz intelligent: + 1. Hat die Gegenstation in einer Chat-Nachricht ihre QRG genannt, wird diese verwendet. + 2. Sonst wird die eigene aktuelle QRG verwendet (aus Win-Test STATUS oder manueller Eingabe). -**Notwendige Einstellungen in KST4Contest:** -- `UDP-Port for Win-Test listener` (Standard: 9871). +**Einstellungen im Reiter „Log-Synchronisation":** - `Receive Win-Test network based UDP log messages` aktivieren. -- `Win-Test sked transmission (push via ADDSKED to Win-Test network)` aktivieren. +- `UDP-Port for Win-Test listener` (Standard: 9871). - `KST station name in Win-Test network (src of SKED packets)`: Legt fest, unter welchem Stationsnamen KST4Contest im WT-Netzwerk auftritt (z.B. "KST"). -- `Win-Test station name filter`: Wenn hier ein Name eingetragen wird (z.B. "STN1"), werden nur QSOs von dieser bestimmten Win-Test Instanz verarbeitet. Leer lassen, um alle zu akzeptieren. -- `Win-Test network broadcast address`: Wird idR automatisch erkannt und ist erforderlich, um die Sked-Pakete ins Netzwerk zu senden. +- `Win-Test network broadcast address`: Wird i.d.R. automatisch erkannt; erforderlich für das Senden von Sked-Paketen. + +**Einstellungen im Reiter „TRX-Synchronisation":** +- `Win-Test STATUS QRG Sync`: Wenn aktiviert, übernimmt KST4Contest die aktuelle Transceiverfrequenz aus dem Win-Test STATUS-Paket als eigene QRG (MYQRG). +- `Use pass frequency from Win-Test STATUS`: Statt der eigenen TRX-QRG wird die im STATUS-Paket enthaltene Pass-Frequenz als MYQRG verwendet (für Multi-Op-Setups, bei denen mit einer Pass-QRG gearbeitet wird). +- `Win-Test station name filter`: Wird hier ein Name eingetragen (z.B. "STN1"), verarbeitet KST4Contest nur Pakete dieser Win-Test-Instanz. Leer lassen, um alle zu akzeptieren. **Einstellungen in Win-Test:** - Das Netzwerk in Win-Test muss aktiv sein. @@ -112,6 +118,11 @@ Neben der QSO-Synchronisation übertragen UCXLog und andere Programme auch die * **Ergebnis**: Die eigene QRG muss im Chat nie mehr manuell eingegeben werden – ein Klick auf den MYQRG-Button oder die Verwendung der Variable im Beacon genügt. +**Quellen für die eigene QRG (MYQRG):** +- UCXLog, N1MM+, DXLog.net, QARTest via UDP-Port 12060 +- Win-Test STATUS-Paket (optional, konfigurierbar im Reiter „TRX-Synchronisation" unter „Win-Test STATUS QRG Sync") +- Manuelle Eingabe im QRG-Feld + > **Hinweis für Multi-Setup**: Bei zwei Logprogrammen an zwei Computern sollte nur **eines** die Frequenzpakete senden. KST4Contest kann nicht zwischen den Quellen unterscheiden und verarbeitet alle eingehenden Pakete. --- diff --git a/github_docs/en-Installation.md b/github_docs/en-Installation.md index 7fa8fb1..a1115a2 100644 --- a/github_docs/en-Installation.md +++ b/github_docs/en-Installation.md @@ -46,7 +46,17 @@ The latest version can be downloaded as an AppImage: **https://github.com/praktimarc/kst4contest/releases/latest** -The filename has the format `praktiKST-v-linux-x86_64.AppImage`. +The filename has the format `KST4Contest-v-linux-x86_64.AppImage`. + +### macOS + +> ⚠️ **Best-Effort Support:** macOS builds are provided as a convenience but are **not fully tested**. We build and release macOS binaries with every release, but we cannot test every scenario on macOS. If you encounter issues, please report them – we will do our best to address them, but cannot guarantee the same level of support as for Windows and Linux. + +The latest version can be downloaded as a DMG disk image (available for both Apple Silicon and Intel Macs): + +**https://github.com/praktimarc/kst4contest/releases/latest** + +The filename has the format `KST4Contest-v-macos-.dmg`, where `` is `arm64` (Apple Silicon) or `x86_64` (Intel). --- @@ -64,11 +74,22 @@ Settings are stored at `%USERPROFILE%\.praktikst\preferences.xml`. ### Linux 1. Download the AppImage. 2. Unzip the AppImage into a folder of your choice. -3. Make the AppImage executable (in the terminal with `chmod +x praktiKST-v-linux-x86_64.AppImage`) +3. Make the AppImage executable (in the terminal with `chmod +x KST4Contest-v-linux-x86_64.AppImage`) 4. Run the AppImage. Settings are stored at `~/.praktikst/preferences.xml`. +### macOS +1. Download the DMG file for your architecture (Apple Silicon or Intel). +2. Open the DMG file. +3. Drag `KST4Contest.app` into your **Applications** folder. +4. On first launch, macOS may show a warning because the app is not notarised. To open it: + - Right-click (or Control-click) on `KST4Contest.app` in Finder and choose **Open**. + - Alternatively, go to **System Settings → Privacy & Security** and click **Open Anyway**. +5. Run KST4Contest from your Applications folder or Launchpad. + +Settings are stored at `~/.praktikst/preferences.xml`. + --- ## Updating @@ -98,6 +119,12 @@ Currently as follows: 2. Mark the new AppImage as executable 3. (optional) Delete the old AppImage. +#### macOS + +1. Download the new DMG file. +2. Open the DMG. +3. Drag the new `KST4Contest.app` into your **Applications** folder, replacing the old version. + --- diff --git a/github_docs/en-Log-Sync.md b/github_docs/en-Log-Sync.md index 7e432e7..85ea0ed 100644 --- a/github_docs/en-Log-Sync.md +++ b/github_docs/en-Log-Sync.md @@ -87,16 +87,22 @@ Win-Test is supported with a dedicated UDP network listener that understands the **Advantages of Win-Test Integration:** - Automatic QSO synchronization to mark worked stations. -- **Sked Handover (ADDSKED):** Using the "Create sked" button in the station info panel not only creates a sked in KST4Contest but also *sends it directly via UDP to the Win-Test network as an ADDSKED packet*. +- **Sked Handover (ADDSKED):** Using the "Create sked" button in the station info panel not only creates a sked in KST4Contest but also *sends it directly via UDP to the Win-Test network as an ADDSKED packet* – automatically, as soon as the listener is active. No separate toggle is needed. - You can choose between "AUTO", "SSB", or "CW" sked modes. +- **Automatic QRG resolution for SKEDs:** KST4Contest selects the sked frequency intelligently: + 1. If the other station mentioned their QRG in a recent chat message, that frequency is used. + 2. Otherwise, your own current QRG is used (from Win-Test STATUS or manual entry). -**Required Settings in KST4Contest:** -- `UDP-Port for Win-Test listener` (Default: 9871). +**Settings in the "Log Synchronisation" tab:** - Enable `Receive Win-Test network based UDP log messages`. -- Enable `Win-Test sked transmission (push via ADDSKED to Win-Test network)`. -- `KST station name in Win-Test network (src of SKED packets)`: Defines the station name KST4Contest uses in the WT network (e.g., "KST"). -- `Win-Test station name filter`: If a name is entered here (e.g., "STN1"), only QSOs from that specific Win-Test instance will be processed. Leave empty to accept all. -- `Win-Test network broadcast address`: Is usually detected automatically and is required to send sked packets to the network. +- `UDP-Port for Win-Test listener` (default: 9871). +- `KST station name in Win-Test network (src of SKED packets)`: Defines the station name KST4Contest uses in the WT network (e.g. "KST"). +- `Win-Test network broadcast address`: Usually detected automatically; required to send sked packets to the network. + +**Settings in the "TRX Synchronisation" tab:** +- `Win-Test STATUS QRG Sync`: When enabled, KST4Contest takes the current transceiver frequency from the Win-Test STATUS packet and uses it as your own QRG (MYQRG). +- `Use pass frequency from Win-Test STATUS`: Instead of the main TRX frequency, the pass frequency contained in the STATUS packet is used as MYQRG (useful for multi-op setups that operate with a dedicated pass QRG). +- `Win-Test station name filter`: If a name is entered here (e.g. "STN1"), KST4Contest only processes packets from that specific Win-Test instance. Leave empty to accept all. **Settings in Win-Test:** - The network in Win-Test must be active. @@ -112,6 +118,11 @@ In addition to QSO synchronisation, UCXLog and other programs also transmit the **Result**: Your own QRG never needs to be typed manually in the chat – clicking the MYQRG button or using the variable in the beacon is sufficient. +**Sources for your own QRG (MYQRG):** +- UCXLog, N1MM+, DXLog.net, QARTest via UDP port 12060 +- Win-Test STATUS packet (optional, configurable in the "TRX Synchronisation" tab under "Win-Test STATUS QRG Sync") +- Manual entry in the QRG field + > **Note for multi-setup**: With two logging programs on two computers, only **one** should send frequency packets. KST4Contest cannot distinguish between sources and processes all incoming packets. --- diff --git a/src/main/java/kst4contest/controller/ChatController.java b/src/main/java/kst4contest/controller/ChatController.java index 68a65d3..ca72731 100644 --- a/src/main/java/kst4contest/controller/ChatController.java +++ b/src/main/java/kst4contest/controller/ChatController.java @@ -810,6 +810,24 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList private final Map lastInboundCategoryByCallSignRaw = new java.util.concurrent.ConcurrentHashMap<>(); + /** Tracks the last time WE sent a message containing a QRG to a specific callsign (UPPERCASE). + * Compared against knownActiveBands.timestampEpoch to decide whose QRG to use in a SKED. */ + private final Map lastSentQRGToCallsign = + new java.util.concurrent.ConcurrentHashMap<>(); + + /** Call this whenever we send a PM to {@code receiverCallsign} that contains our QRG. */ + public void recordOutboundQRG(String receiverCallsign) { + if (receiverCallsign == null) return; + lastSentQRGToCallsign.put(receiverCallsign.trim().toUpperCase(), System.currentTimeMillis()); + System.out.println("[ChatController] Recorded outbound QRG to: " + receiverCallsign); + } + + /** Returns epoch-ms of when we last sent our QRG to this callsign, or 0 if never. */ + public long getLastSentQRGTimestamp(String callsign) { + if (callsign == null) return 0L; + return lastSentQRGToCallsign.getOrDefault(callsign.trim().toUpperCase(), 0L); + } + private final ScoreService scoreService = new ScoreService(this, new PriorityCalculator(), 15); private ScheduledExecutorService scoreScheduler; private final StationMetricsService stationMetricsService = new StationMetricsService(); @@ -827,8 +845,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList }); // Push sked to Win-Test via UDP if enabled - if (chatPreferences.isLogsynch_wintestNetworkSkedPushEnabled() - && chatPreferences.isLogsynch_wintestNetworkListenerEnabled()) { + if (chatPreferences.isLogsynch_wintestNetworkListenerEnabled()) { pushSkedToWinTest(sked); } } @@ -847,16 +864,69 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList WinTestSkedSender sender = new WinTestSkedSender(stationName, broadcastAddr, port, this); - // Get current frequency from QRG property (set by Win-Test STATUS or user) - double freqKHz = 144300.0; // fallback default - try { - String qrgStr = chatPreferences.getMYQRGFirstCat().get(); - if (qrgStr != null && !qrgStr.isBlank()) { - // QRG is in display format like "144.300.00" – strip dots → "14430000" → / 100 → 144300.0 kHz - String cleaned = qrgStr.trim().replace(".", ""); - freqKHz = Double.parseDouble(cleaned) / 100.0; + // Frequency resolution: + // Compare WHO sent a QRG most recently in the PM conversation: + // - OM sent their QRG last → use OM's Last Known QRG (ChatMember.frequency) + // - WE sent our QRG last → use our own Win-Test QRG (MYQRG) + // Fallback chain if no timestamps exist: OM's Last Known QRG → hardcoded default + double freqKHz = -1.0; + final long SKED_FREQ_MAX_AGE_MS = 60 * 60 * 1000L; // 60 minutes + + ChatMember targetMember = resolveSkedTargetMember(sked.getTargetCallsign()); + + // Collect timestamps: when did the OM last mention their QRG? When did WE last send ours? + long omLastQRGTimestamp = 0L; + double omLastQRGMhz = 0.0; + if (targetMember != null && sked.getBand() != null) { + ChatMember.ActiveFrequencyInfo fi = targetMember.getKnownActiveBands().get(sked.getBand()); + if (fi != null && fi.frequency > 0 + && (System.currentTimeMillis() - fi.timestampEpoch) <= SKED_FREQ_MAX_AGE_MS) { + omLastQRGTimestamp = fi.timestampEpoch; + omLastQRGMhz = fi.frequency; } - } catch (NumberFormatException ignored) { } + } + long ourLastQRGTimestamp = getLastSentQRGTimestamp(sked.getTargetCallsign()); + + // Decision: who was more recent? + if (omLastQRGTimestamp > 0 && omLastQRGTimestamp >= ourLastQRGTimestamp) { + // OM mentioned their QRG MORE RECENTLY (or at same time) → use their QRG + freqKHz = omLastQRGMhz * 1000.0; + System.out.println("[ChatController] SKED freq: OM sent last → " + + omLastQRGMhz + " MHz → " + freqKHz + " kHz"); + + } else if (ourLastQRGTimestamp > 0) { + // WE sent our QRG more recently → use our Win-Test QRG + try { + String qrgStr = chatPreferences.getMYQRGFirstCat().get(); + if (qrgStr != null && !qrgStr.isBlank()) { + String cleaned = qrgStr.trim().replace(".", ""); + double parsed = Double.parseDouble(cleaned) / 100.0; + if (parsed > 50000) { + freqKHz = parsed; + System.out.println("[ChatController] SKED freq: WE sent last → " + + freqKHz + " kHz (raw: " + qrgStr + ")"); + } + } + } catch (NumberFormatException ignored) { } + } + + // Fallback A: OM's Last Known QRG from KST field (if no PM QRG exchange found at all) + if (freqKHz < 0 && targetMember != null) { + try { + String memberQrg = targetMember.getFrequency().get(); + if (memberQrg != null && !memberQrg.isBlank()) { + double mhz = Double.parseDouble(memberQrg.trim()); + freqKHz = mhz * 1000.0; + System.out.println("[ChatController] SKED freq: fallback Last Known QRG → " + + mhz + " MHz → " + freqKHz + " kHz"); + } + } catch (NumberFormatException ignored) { } + } + + // Fallback B: hardcoded default + if (freqKHz < 0) { + freqKHz = 144300.0; + } // Build notes string with target locator/azimuth info like reference: [JO02OB - 279°] String targetLocator = resolveSkedTargetLocator(sked.getTargetCallsign()); @@ -883,6 +953,22 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList }, "WinTestSkedPush").start(); } + private ChatMember resolveSkedTargetMember(String targetCallsignRaw) { + if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) { + return null; + } + String normalizedTargetCall = normalizeCallRaw(targetCallsignRaw); + synchronized (getLst_chatMemberList()) { + for (ChatMember member : getLst_chatMemberList()) { + if (member == null || member.getCallSignRaw() == null) continue; + if (normalizeCallRaw(member.getCallSignRaw()).equals(normalizedTargetCall)) { + return member; + } + } + } + return null; + } + private String resolveSkedTargetLocator(String targetCallsignRaw) { if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) { return null; diff --git a/src/main/java/kst4contest/controller/MessageBusManagementThread.java b/src/main/java/kst4contest/controller/MessageBusManagementThread.java index 87356b6..8d8cb83 100644 --- a/src/main/java/kst4contest/controller/MessageBusManagementThread.java +++ b/src/main/java/kst4contest/controller/MessageBusManagementThread.java @@ -962,6 +962,13 @@ public class MessageBusManagementThread extends Thread { .setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage); this.client.getLst_globalChatMessageList().add(0,newMessageArrived); + // If our message contained a frequency (e.g. "QRG is: 144.375"), record that + // WE sent our QRG to this OM – used by SKED frequency resolution. + if (originalMessage != null && newMessageArrived.getReceiver() != null + && originalMessage.matches(".*\\b\\d{3,5}[.,]\\d{1,3}.*")) { + this.client.recordOutboundQRG(newMessageArrived.getReceiver().getCallSign()); + } + // if you sent the message to another station, it will be sorted in to // the "to me message list" with modified messagetext, added rxers callsign diff --git a/src/main/java/kst4contest/controller/ReadUDPByWintestThread.java b/src/main/java/kst4contest/controller/ReadUDPByWintestThread.java index 5c5044d..f53e1ff 100644 --- a/src/main/java/kst4contest/controller/ReadUDPByWintestThread.java +++ b/src/main/java/kst4contest/controller/ReadUDPByWintestThread.java @@ -1,5 +1,6 @@ package kst4contest.controller; +import javafx.application.Platform; import kst4contest.ApplicationConstants; import kst4contest.model.ChatMember; import kst4contest.model.ThreadStateMessage; @@ -75,9 +76,10 @@ public class ReadUDPByWintestThread extends Thread { socket = new DatagramSocket(null); //first init with null, then make ready for reuse socket.setReuseAddress(true); // socket = new DatagramSocket(PORT); - socket.bind(new InetSocketAddress(client.getChatPreferences().getLogsynch_wintestNetworkPort())); + int boundPort = client.getChatPreferences().getLogsynch_wintestNetworkPort(); + socket.bind(new InetSocketAddress(boundPort)); socket.setSoTimeout(3000); - System.out.println("[WinTest UDP listener] started at port: " + PORT); + System.out.println("[WinTest UDP listener] started at port: " + boundPort); } catch (SocketException e) { e.printStackTrace(); return; @@ -224,9 +226,43 @@ public class ReadUDPByWintestThread extends Thread { } else { formattedQRG = String.format(Locale.US, "%.1f", freqFloat); // fallback } - this.client.getChatPreferences().getMYQRGFirstCat().set(formattedQRG); + // Parse pass frequency from parts[11] if available (WT STATUS format) + String formattedPassQRG = null; + if (parts.size() > 11) { + try { + String passFreqRaw = parts.get(11); + double passFreqFloat = Integer.parseInt(passFreqRaw) / 10.0; + if (passFreqFloat > 100) { // Must be a valid radio frequency (> 100 kHz), protects against parsing boolean flag tokens + long passFreqHzTimes100 = Math.round(passFreqFloat * 100.0); + String passHzStr = String.valueOf(passFreqHzTimes100); + if (passHzStr.length() == 8) { + formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 3), passHzStr.substring(3, 6), passHzStr.substring(6, 8)); + } else if (passHzStr.length() == 9) { + formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 4), passHzStr.substring(4, 7), passHzStr.substring(7, 9)); + } else if (passHzStr.length() == 7) { + formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 2), passHzStr.substring(2, 5), passHzStr.substring(5, 7)); + } else if (passHzStr.length() == 6) { + formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 1), passHzStr.substring(1, 4), passHzStr.substring(4, 6)); + } else { + formattedPassQRG = String.format(Locale.US, "%.1f", passFreqFloat); + } + } + } catch (Exception ignored) { + // parts[11] not a valid frequency, leave formattedPassQRG as null + } + } - System.out.println("[WinTest STATUS] stn=" + stn + ", mode=" + mode + ", qrg=" + formattedQRG); + if (this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()) { + final String qrgToSet = (this.client.getChatPreferences().isLogsynch_wintestUsePassQrg() && formattedPassQRG != null) + ? formattedPassQRG + : formattedQRG; + // JavaFX StringProperty must be updated on the FX Application Thread + Platform.runLater(() -> this.client.getChatPreferences().getMYQRGFirstCat().set(qrgToSet)); + } + + System.out.println("[WinTest STATUS] stn=" + stn + ", mode=" + mode + ", qrg=" + formattedQRG + + (formattedPassQRG != null ? ", passQrg=" + formattedPassQRG : "") + + ", syncActive=" + this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()); } catch (Exception e) { System.out.println("[WinTest] STATUS parsing error: " + e.getMessage()); } diff --git a/src/main/java/kst4contest/model/ChatPreferences.java b/src/main/java/kst4contest/model/ChatPreferences.java index d6b9b35..02cbacf 100644 --- a/src/main/java/kst4contest/model/ChatPreferences.java +++ b/src/main/java/kst4contest/model/ChatPreferences.java @@ -204,6 +204,8 @@ public class ChatPreferences { String logsynch_wintestNetworkBroadcastAddress = "255.255.255.255"; // UDP broadcast address for sending to Win-Test boolean logsynch_wintestNetworkSkedPushEnabled = false; // push SKEDs to Win-Test via UDP String logsynch_wintestSkedMode = "SSB"; // CW, SSB or AUTO + boolean logsynch_wintestQrgSyncEnabled = true; // sync QRG from Win-Test STATUS packet + boolean logsynch_wintestUsePassQrg = false; // use pass frequency instead of main QRG from STATUS packet @@ -481,6 +483,22 @@ public class ChatPreferences { this.logsynch_wintestSkedMode = logsynch_wintestSkedMode; } + public boolean isLogsynch_wintestQrgSyncEnabled() { + return logsynch_wintestQrgSyncEnabled; + } + + public void setLogsynch_wintestQrgSyncEnabled(boolean logsynch_wintestQrgSyncEnabled) { + this.logsynch_wintestQrgSyncEnabled = logsynch_wintestQrgSyncEnabled; + } + + public boolean isLogsynch_wintestUsePassQrg() { + return logsynch_wintestUsePassQrg; + } + + public void setLogsynch_wintestUsePassQrg(boolean logsynch_wintestUsePassQrg) { + this.logsynch_wintestUsePassQrg = logsynch_wintestUsePassQrg; + } + public String getStn_loginLocatorSecondCat() { return stn_loginLocatorSecondCat; } @@ -1338,6 +1356,14 @@ public class ChatPreferences { logsynch_wintestSkedMode.setTextContent(this.logsynch_wintestSkedMode); logsynch.appendChild(logsynch_wintestSkedMode); + Element logsynch_wintestQrgSyncEnabled = doc.createElement("logsynch_wintestQrgSyncEnabled"); + logsynch_wintestQrgSyncEnabled.setTextContent(this.logsynch_wintestQrgSyncEnabled + ""); + logsynch.appendChild(logsynch_wintestQrgSyncEnabled); + + Element logsynch_wintestUsePassQrg = doc.createElement("logsynch_wintestUsePassQrg"); + logsynch_wintestUsePassQrg.setTextContent(this.logsynch_wintestUsePassQrg + ""); + logsynch.appendChild(logsynch_wintestUsePassQrg); + /** * trxSynchUCX @@ -1912,6 +1938,16 @@ public class ChatPreferences { logsynch_wintestSkedMode, "logsynch_wintestSkedMode"); + logsynch_wintestQrgSyncEnabled = getBoolean( + logsynchEl, + logsynch_wintestQrgSyncEnabled, + "logsynch_wintestQrgSyncEnabled"); + + logsynch_wintestUsePassQrg = getBoolean( + logsynchEl, + logsynch_wintestUsePassQrg, + "logsynch_wintestUsePassQrg"); + System.out.println( "[ChatPreferences, info]: file based worked-call interpreter: " + logsynch_fileBasedWkdCallInterpreterEnabled); System.out.println( diff --git a/src/main/java/kst4contest/view/Kst4ContestApplication.java b/src/main/java/kst4contest/view/Kst4ContestApplication.java index 758becd..ca518f5 100644 --- a/src/main/java/kst4contest/view/Kst4ContestApplication.java +++ b/src/main/java/kst4contest/view/Kst4ContestApplication.java @@ -3582,7 +3582,57 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL Menu fileMenu = new Menu("File"); - // create menuitems + // build "Connect to " label from saved preferences + ChatCategory mainCat = chatcontroller.getChatPreferences().getLoginChatCategoryMain(); + String connectLabel = "Connect to " + mainCat.getChatCategoryName(mainCat.getCategoryNumber()); + if (chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()) { + ChatCategory secCat = chatcontroller.getChatPreferences().getLoginChatCategorySecond(); + if (secCat != null) { + connectLabel += " & " + secCat.getChatCategoryName(secCat.getCategoryNumber()); + } + } + menuItemFileConnect = new MenuItem(connectLabel); + menuItemFileConnect.setDisable(false); + + if (chatcontroller.isConnectedAndLoggedIn() || chatcontroller.isConnectedAndNOTLoggedIn()) { + menuItemFileConnect.setDisable(true); + } + + menuItemFileConnect.setOnAction(event -> { + System.out.println("[Info] File menu: Connect clicked, using saved preferences"); + + String call = chatcontroller.getChatPreferences().getStn_loginCallSign(); + String pass = chatcontroller.getChatPreferences().getStn_loginPassword(); + + if (call == null || call.isBlank() || pass == null || pass.isBlank()) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle("Cannot connect"); + alert.setHeaderText("Login credentials missing"); + alert.setContentText("Please configure your callsign and password in Settings first."); + alert.show(); + return; + } + + try { + chatcontroller.execute(); + + menuItemFileConnect.setDisable(true); + menuItemFileDisconnect.setDisable(false); + menuItemOptionsAwayBack.setDisable(false); + menuItemOptionsSetFrequencyAsName.setDisable(false); + + chatcontroller.setConnectedAndLoggedIn(true); + chatcontroller.setDisconnected(false); + + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Connection failed"); + alert.setContentText("Could not connect: " + e.getMessage()); + alert.show(); + } + }); + menuItemFileDisconnect = new MenuItem("Disconnect"); menuItemFileDisconnect.setDisable(true); @@ -3595,6 +3645,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL public void handle(ActionEvent event) { chatcontroller.disconnect(ApplicationConstants.DISCSTRING_DISCONNECTONLY); menuItemFileDisconnect.setDisable(true); + menuItemFileConnect.setDisable(false); } }); @@ -3607,6 +3658,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL }); // add menu items to menu + fileMenu.getItems().add(menuItemFileConnect); fileMenu.getItems().add(menuItemFileDisconnect); fileMenu.getItems().add(m10); @@ -4010,6 +4062,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL Scene clusterAndQSOMonScene; Scene settingsScene; + MenuItem menuItemFileConnect; MenuItem menuItemFileDisconnect; MenuItem menuItemOptionsAwayBack; @@ -4170,10 +4223,15 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL timer_updatePrivatemessageTable.purge(); timer_updatePrivatemessageTable.cancel(); - chatcontroller.disconnect("CLOSEALL"); + + try { + chatcontroller.disconnect("CLOSEALL"); + } catch (Exception e) { + System.out.println("[Main.java, Warning:] Exception during disconnect: " + e.getMessage()); + } // Platform.exit(); - + System.exit(0); } private Queue musicList = new LinkedList(); @@ -6273,11 +6331,14 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL } }); + boolean isSecondChatEnabled = this.chatcontroller.getChatPreferences().isLoginToSecondChatEnabled(); Label lblNameSecondCat = new Label("Name in Chat 2:"); - lblNameSecondCat.setVisible(false); + lblNameSecondCat.setVisible(isSecondChatEnabled); + lblNameSecondCat.setDisable(!isSecondChatEnabled); TextField txtFldNameInChatSecondCat = new TextField(this.chatcontroller.getChatPreferences().getStn_loginNameSecondCat()); txtFldNameInChatSecondCat.setFocusTraversable(false); - txtFldNameInChatSecondCat.setVisible(false); + txtFldNameInChatSecondCat.setVisible(isSecondChatEnabled); + txtFldNameInChatSecondCat.setDisable(!isSecondChatEnabled); txtFldNameInChatSecondCat.textProperty().addListener(new ChangeListener() { @@ -6397,11 +6458,12 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL CheckBox station_chkBxEnableSecondChat = new CheckBox("2nd Chat: "); - station_chkBxEnableSecondChat.setSelected(chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()); + boolean isSecondChatEnabledForCheckbox = chatcontroller.getChatPreferences().isLoginToSecondChatEnabled(); + station_chkBxEnableSecondChat.setSelected(isSecondChatEnabledForCheckbox); - stn_choiceBxChatChategorySecond.setDisable(true); + stn_choiceBxChatChategorySecond.setDisable(!isSecondChatEnabledForCheckbox); station_chkBxEnableSecondChat.selectedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { @@ -6431,12 +6493,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL } }); - if (chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()) { - stn_choiceBxChatChategorySecond.setVisible(chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()); - stn_choiceBxChatChategorySecond.setDisable(!chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()); - txtFldNameInChatSecondCat.setVisible(chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()); - } TextField txtFldstn_antennaBeamWidthDeg = new TextField(this.chatcontroller.getChatPreferences().getStn_antennaBeamWidthDeg() + ""); txtFldstn_antennaBeamWidthDeg.setFocusTraversable(false); @@ -6667,7 +6724,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL grdPnlStation_bands.add(settings_chkbx_QRV3400, 1, 2); grdPnlStation_bands.add(settings_chkbx_QRV5600, 2, 2); grdPnlStation_bands.add(settings_chkbx_QRV10G, 0, 3); - grdPnlStation_bands.setMaxWidth(555.0); grdPnlStation_bands.setStyle(" -fx-border-color: lightgray;\n" + " -fx-vgap: 5;\n" + @@ -6882,15 +6938,32 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL grdPnlLog.add(lblUDPByWintest, 0, 8); grdPnlLog.add(txtFldUDPPortforWintest, 1, 8); - // --- Win-Test SKED push settings --- - Label lblEnableSkedPush = new Label("Push SKEDs to Win-Test via UDP (ADDSKED)"); - CheckBox chkBxEnableSkedPush = new CheckBox(); - chkBxEnableSkedPush.setSelected( - this.chatcontroller.getChatPreferences().isLogsynch_wintestNetworkSkedPushEnabled() + // --- QRG sync from Win-Test STATUS --- + Label lblWtQrgSync = new Label("Win-Test STATUS QRG Sync (updates own QRG from Win-Test transceiver frequency)"); + CheckBox chkBxWtQrgSync = new CheckBox(); + chkBxWtQrgSync.setSelected( + this.chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled() ); - chkBxEnableSkedPush.selectedProperty().addListener((obs, oldVal, newVal) -> { - chatcontroller.getChatPreferences().setLogsynch_wintestNetworkSkedPushEnabled(newVal); - System.out.println("[Main.java, Info]: Win-Test SKED push enabled: " + newVal); + chkBxWtQrgSync.selectedProperty().addListener((obs, oldVal, newVal) -> { + chatcontroller.getChatPreferences().setLogsynch_wintestQrgSyncEnabled(newVal); + System.out.println("[Main.java, Info]: Win-Test QRG sync enabled: " + newVal); + boolean anyActive = chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled() || newVal; + if (!anyActive) { + txt_ownqrgMainCategory.textProperty().unbind(); + txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by hand (watch prefs!)")); + } else { + txt_ownqrgMainCategory.textProperty().bind(chatcontroller.getChatPreferences().getMYQRGFirstCat()); + txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)")); + } + }); + Label lblWtUsePassQrg = new Label("Use pass frequency from Win-Test STATUS (instead of own QRG)"); + CheckBox chkBxWtUsePassQrg = new CheckBox(); + chkBxWtUsePassQrg.setSelected( + this.chatcontroller.getChatPreferences().isLogsynch_wintestUsePassQrg() + ); + chkBxWtUsePassQrg.selectedProperty().addListener((obs, oldVal, newVal) -> { + chatcontroller.getChatPreferences().setLogsynch_wintestUsePassQrg(newVal); + System.out.println("[Main.java, Info]: Win-Test use pass QRG: " + newVal); }); Label lblWtStationName = new Label("KST station name in Win-Test network (src of SKED packets)"); @@ -6935,13 +7008,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL } }); - grdPnlLog.add(lblEnableSkedPush, 0, 9); - grdPnlLog.add(chkBxEnableSkedPush, 1, 9); - - grdPnlLog.add(lblWtStationName, 0, 11); - grdPnlLog.add(txtFldWtStationName, 1, 11); - grdPnlLog.add(lblWtStationFilter, 0, 12); - grdPnlLog.add(txtFldWtStationFilter, 1, 12); + grdPnlLog.add(lblWtStationName, 0, 9); + grdPnlLog.add(txtFldWtStationName, 1, 9); // Auto-detect subnet broadcast if preference is still the default String currentBroadcast = this.chatcontroller.getChatPreferences().getLogsynch_wintestNetworkBroadcastAddress(); @@ -6959,8 +7027,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL // Re-read (may have been auto-detected) txtFldWtBroadcastAddr.setText(this.chatcontroller.getChatPreferences().getLogsynch_wintestNetworkBroadcastAddress()); - grdPnlLog.add(lblWtBroadcastAddr, 0, 13); - grdPnlLog.add(txtFldWtBroadcastAddr, 1, 13); + grdPnlLog.add(lblWtBroadcastAddr, 0, 10); + grdPnlLog.add(txtFldWtBroadcastAddr, 1, 10); VBox vbxLog = new VBox(); vbxLog.setPadding(new Insets(10, 10, 10, 10)); @@ -6995,51 +7063,45 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL chkBxEnableTRXMsgbyUCX.selectedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { -// chk2.setSelected(!newValue); - if (!newValue) { - chatcontroller.getChatPreferences() - .setTrxSynch_ucxLogUDPListenerEnabled(chkBxEnableTRXMsgbyUCX.isSelected()); + chatcontroller.getChatPreferences().setTrxSynch_ucxLogUDPListenerEnabled(newValue); + boolean anyActive = newValue || chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled(); + if (!anyActive) { txt_ownqrgMainCategory.textProperty().unbind(); txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by hand (watch prefs!)")); System.out.println("[Main.java, Info]: MYQRG will be changed only by User input"); - System.out.println("[Main.java, Info]: setted the trx-frequency updated by ucxlog to: " - + chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled()); - } else { - chatcontroller.getChatPreferences() - .setTrxSynch_ucxLogUDPListenerEnabled(chkBxEnableTRXMsgbyUCX.isSelected()); txt_ownqrgMainCategory.textProperty().bind(chatcontroller.getChatPreferences().getMYQRGFirstCat()); txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)")); - System.out.println("[Main.java, Info]: setted the trx-frequency updated by ucxlog to: " - + chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled()); } } }); - // Thats the default behaviour of the myqrg textfield - if (this.chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled()) { + // Unconditionally add listener to manually sync the textfield input to the button + // (this listener also fires correctly when the value is updated by the binding) + txt_ownqrgMainCategory.textProperty().addListener((observable, oldValue, newValue) -> { + MYQRGButton.textProperty().set(newValue); + }); + + // That's the default behaviour of the myqrg textfield + if (this.chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled() || this.chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()) { txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)")); - txt_ownqrgMainCategory.textProperty().bind(this.chatcontroller.getChatPreferences().getMYQRGFirstCat());// TODO: Bind darf nur - // gemacht werden, wenn - // ucxlog-Frequenznachrichten - // ausgewerttet werden! -// System.out.println("[Main.java, Info]: MYQRG will be changed only by UCXListener"); + txt_ownqrgMainCategory.textProperty().bind(this.chatcontroller.getChatPreferences().getMYQRGFirstCat()); } else { txt_ownqrgMainCategory.setTooltip(new Tooltip("enter your cq qrg here")); -// System.out.println("[Main.java, Info]: MYQRG will be changed only by User input"); - txt_ownqrgMainCategory.textProperty().addListener((observable, oldValue, newValue) -> { - - System.out.println( - "[Main.java, Info]: MYQRG Text changed from " + oldValue + " to " + newValue + " by hand"); - MYQRGButton.textProperty().set(newValue); - }); - } grdPnltrx.add(generateLabeledSeparator(100, "Receive UCXLog TRX info"), 0, 0, 2, 1); grdPnltrx.add(lblEnableTRXMsgbyUCX, 0, 1); grdPnltrx.add(chkBxEnableTRXMsgbyUCX, 1, 1); + grdPnltrx.add(generateLabeledSeparator(100, "Win-Test TRX sync"), 0, 2, 2, 1); + grdPnltrx.add(lblWtQrgSync, 0, 3); + grdPnltrx.add(chkBxWtQrgSync, 1, 3); + grdPnltrx.add(lblWtUsePassQrg, 0, 4); + grdPnltrx.add(chkBxWtUsePassQrg, 1, 4); + grdPnltrx.add(lblWtStationFilter, 0, 5); + grdPnltrx.add(txtFldWtStationFilter, 1, 5); + VBox vbxTRXSynch = new VBox(); vbxTRXSynch.setPadding(new Insets(10, 10, 10, 10)); vbxTRXSynch.getChildren().addAll(grdPnltrx); @@ -8124,6 +8186,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL else if (chatcontroller.isConnectedAndLoggedIn()) { btnOptionspnlDisconnectOnly.setDisable(false); menuItemFileDisconnect.setDisable(false); + menuItemFileConnect.setDisable(true); menuItemOptionsAwayBack.setDisable(false); } @@ -8147,13 +8210,19 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL txtFldstn_maxQRBDefault.setDisable(false); menuItemOptionsSetFrequencyAsName.setDisable(true); menuItemOptionsAwayBack.setDisable(true); + menuItemFileConnect.setDisable(false); station_chkBxEnableSecondChat.setDisable(false); stn_choiceBxChatChategorySecond.setDisable(false); } }); - btnOptionspnlConnect = new Button("Connect to " + chatcontroller.getChatPreferences().getLoginChatCategoryMain() - .getChatCategoryName(choiceBxChatChategory.getSelectionModel().getSelectedItem().getCategoryNumber())); + String btnText = "Connect to " + chatcontroller.getChatPreferences().getLoginChatCategoryMain() + .getChatCategoryName(choiceBxChatChategory.getSelectionModel().getSelectedItem().getCategoryNumber()); + ChatCategory secCat = chatcontroller.getChatPreferences().getLoginChatCategorySecond(); + if (chatcontroller.getChatPreferences().isLoginToSecondChatEnabled() && secCat != null) { + btnText += " & " + secCat.getChatCategoryName(secCat.getCategoryNumber()); + } + btnOptionspnlConnect = new Button(btnText); btnOptionspnlConnect.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { @@ -8185,6 +8254,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL btnOptionspnlDisconnectOnly.setDisable(false); menuItemFileDisconnect.setDisable(false); + menuItemFileConnect.setDisable(true); menuItemOptionsAwayBack.setDisable(false); menuItemOptionsSetFrequencyAsName.setDisable(false);