4 Commits

Author SHA1 Message Date
5fc0e7d02d Win-Test Fix QRG Sked 2026-04-04 21:49:58 +02:00
e4648b596f Document Changes 2026-04-04 12:12:41 +02:00
b3b7bc6a43 Rename Building Artifacts 2026-04-04 12:12:28 +02:00
104cc07317 Improve Win-Test Integration in UI 2026-04-04 12:12:00 +02:00
11 changed files with 439 additions and 129 deletions

View File

@@ -4,7 +4,6 @@ on:
push: push:
branches: branches:
- main - main
- test-mac
schedule: schedule:
- cron: "20 2 * * *" - cron: "20 2 * * *"
workflow_dispatch: workflow_dispatch:
@@ -86,7 +85,7 @@ jobs:
SHORT_SHA="${GITHUB_SHA::7}" SHORT_SHA="${GITHUB_SHA::7}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "SHORT_SHA=$SHORT_SHA" >> "$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 - name: Set up Java 17
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v4.1.0
@@ -107,7 +106,7 @@ jobs:
mkdir -p dist mkdir -p dist
jpackage \ jpackage \
--type app-image \ --type app-image \
--name praktiKST \ --name KST4Contest \
--input target/dist-libs \ --input target/dist-libs \
--main-jar app.jar \ --main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \ --main-class kst4contest.view.Kst4ContestApplication \
@@ -117,41 +116,41 @@ jobs:
- name: Create AppDir metadata - name: Create AppDir metadata
run: | run: |
rm -rf target/praktiKST.AppDir rm -rf target/KST4Contest.AppDir
cp -a dist/praktiKST target/praktiKST.AppDir cp -a dist/KST4Contest target/KST4Contest.AppDir
cat > target/praktiKST.AppDir/AppRun << 'EOF' cat > target/KST4Contest.AppDir/AppRun << 'EOF'
#!/bin/sh #!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")" HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/bin/praktiKST" "$@" exec "$HERE/bin/KST4Contest" "$@"
EOF 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] [Desktop Entry]
Type=Application Type=Application
Name=praktiKST Name=KST4Contest
Exec=praktiKST Exec=KST4Contest
Icon=praktiKST Icon=KST4Contest
Categories=Network;HamRadio; Categories=Network;HamRadio;
Terminal=false Terminal=false
EOF EOF
if [ -f target/praktiKST.AppDir/lib/praktiKST.png ]; then if [ -f target/KST4Contest.AppDir/lib/KST4Contest.png ]; then
cp target/praktiKST.AppDir/lib/praktiKST.png target/praktiKST.AppDir/praktiKST.png cp target/KST4Contest.AppDir/lib/KST4Contest.png target/KST4Contest.AppDir/KST4Contest.png
fi fi
- name: Build AppImage - name: Build AppImage
run: | run: |
wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x target/appimagetool.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 - name: Upload Linux artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v4.3.4
with: with:
name: linux-appimage name: linux-appimage
path: dist/praktiKST-*-linux-x86_64.AppImage path: dist/KST4Contest-*-linux-x86_64.AppImage
retention-days: 14 retention-days: 14
build-macos-dmg: build-macos-dmg:
@@ -172,7 +171,7 @@ jobs:
ARCH=$(uname -m) ARCH=$(uname -m)
echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "SHORT_SHA=$SHORT_SHA" >> "$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" echo "ARCH=$ARCH" >> "$GITHUB_ENV"
- name: Set up Java 17 - name: Set up Java 17
@@ -194,7 +193,7 @@ jobs:
mkdir -p dist mkdir -p dist
jpackage \ jpackage \
--type dmg \ --type dmg \
--name praktiKST \ --name KST4Contest \
--input target/dist-libs \ --input target/dist-libs \
--main-jar app.jar \ --main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \ --main-class kst4contest.view.Kst4ContestApplication \
@@ -217,5 +216,5 @@ jobs:
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v4.3.4
with: with:
name: macos-dmg-${{ matrix.os }} name: macos-dmg-${{ matrix.os }}
path: dist/praktiKST-*-macos-*.dmg path: dist/KST4Contest-*-macos-*.dmg
retention-days: 14 retention-days: 14

View File

@@ -88,7 +88,7 @@ jobs:
mkdir -p dist mkdir -p dist
jpackage \ jpackage \
--type app-image \ --type app-image \
--name praktiKST \ --name KST4Contest \
--input target/dist-libs \ --input target/dist-libs \
--main-jar app.jar \ --main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \ --main-class kst4contest.view.Kst4ContestApplication \
@@ -98,41 +98,41 @@ jobs:
- name: Create AppDir metadata - name: Create AppDir metadata
run: | run: |
rm -rf target/praktiKST.AppDir rm -rf target/KST4Contest.AppDir
cp -a dist/praktiKST target/praktiKST.AppDir cp -a dist/KST4Contest target/KST4Contest.AppDir
cat > target/praktiKST.AppDir/AppRun << 'EOF' cat > target/KST4Contest.AppDir/AppRun << 'EOF'
#!/bin/sh #!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")" HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/bin/praktiKST" "$@" exec "$HERE/bin/KST4Contest" "$@"
EOF 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] [Desktop Entry]
Type=Application Type=Application
Name=praktiKST Name=KST4Contest
Exec=praktiKST Exec=KST4Contest
Icon=praktiKST Icon=KST4Contest
Categories=Network;HamRadio; Categories=Network;HamRadio;
Terminal=false Terminal=false
EOF EOF
if [ -f target/praktiKST.AppDir/lib/praktiKST.png ]; then if [ -f target/KST4Contest.AppDir/lib/KST4Contest.png ]; then
cp target/praktiKST.AppDir/lib/praktiKST.png target/praktiKST.AppDir/praktiKST.png cp target/KST4Contest.AppDir/lib/KST4Contest.png target/KST4Contest.AppDir/KST4Contest.png
fi fi
- name: Build AppImage - name: Build AppImage
run: | run: |
wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x target/appimagetool.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 - name: Upload Linux artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v4.3.4
with: with:
name: linux-appimage 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: build-macos-dmg:
name: Build macOS DMG (${{ matrix.os }}) name: Build macOS DMG (${{ matrix.os }})
@@ -164,7 +164,7 @@ jobs:
mkdir -p dist mkdir -p dist
jpackage \ jpackage \
--type dmg \ --type dmg \
--name praktiKST \ --name KST4Contest \
--input target/dist-libs \ --input target/dist-libs \
--main-jar app.jar \ --main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \ --main-class kst4contest.view.Kst4ContestApplication \
@@ -182,13 +182,13 @@ jobs:
if [ -z "$DMG" ]; then if [ -z "$DMG" ]; then
echo "No DMG produced by jpackage" && exit 1 echo "No DMG produced by jpackage" && exit 1
fi 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 - name: Upload macOS artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v4.3.4
with: with:
name: macos-dmg-${{ matrix.os }} name: macos-dmg-${{ matrix.os }}
path: dist/praktiKST-${{ github.ref_name }}-macos-*.dmg path: dist/KST4Contest-${{ github.ref_name }}-macos-*.dmg
build-docs-pdf: build-docs-pdf:
name: Build Documentation PDF name: Build Documentation PDF
@@ -315,7 +315,7 @@ jobs:
generateReleaseNotes: true generateReleaseNotes: true
artifacts: >- artifacts: >-
release-assets/windows/praktiKST-${{ github.ref_name }}-windows-x64.zip, release-assets/windows/praktiKST-${{ github.ref_name }}-windows-x64.zip,
release-assets/linux/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage, release-assets/linux/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage,
release-assets/macos/praktiKST-${{ github.ref_name }}-macos-*.dmg, 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-en.pdf,
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-de.pdf release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-de.pdf

View File

@@ -46,7 +46,17 @@ Die aktuelle Version kann als AppImage heruntergeladen werden:
**https://github.com/praktimarc/kst4contest/releases/latest** **https://github.com/praktimarc/kst4contest/releases/latest**
Der Dateiname hat das Format `praktiKST-v<Versionsnummer>-linux-x86_64.AppImage`. Der Dateiname hat das Format `KST4Contest-v<Versionsnummer>-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<Versionsnummer>-macos-<Architektur>.dmg`, wobei `<Architektur>` entweder `arm64` (Apple Silicon) oder `x86_64` (Intel) ist.
--- ---
@@ -64,11 +74,22 @@ Die Einstellungen werden unter `%USERPROFILE%\.praktikst\preferences.xml` gespei
### Linux ### Linux
1. AppImage herunterladen. 1. AppImage herunterladen.
2. AppImage in gewünschten Ordner entpacken. 2. AppImage in gewünschten Ordner entpacken.
3. AppImage ausführbar machen (geht im Terminal mit `chmod +x praktiKST-v<Versionsnummer>-linux-x86_64.AppImage`) 3. AppImage ausführbar machen (geht im Terminal mit `chmod +x KST4Contest-v<Versionsnummer>-linux-x86_64.AppImage`)
4. AppImage ausführen. 4. AppImage ausführen.
Die Einstellungen werden unter `~/.praktikst/preferences.xml` gespeichert. 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 ## Update
@@ -98,6 +119,12 @@ Derzeit folgendermaßen:
2. neues AppImage ausführbar makieren 2. neues AppImage ausführbar makieren
3. (optional) altes AppImage löschen. 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.
--- ---

View File

@@ -83,20 +83,26 @@ Für den integrierten DX-Cluster-Server: N1MM+ als DX-Cluster-Client konfigurier
### Win-Test ### 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:** **Vorteile der Win-Test Integration:**
- Automatische QSO-Synchronisation zur Markierung gearbeiteter Stationen. - 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. - 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:** **Einstellungen im Reiter „Log-Synchronisation":**
- `UDP-Port for Win-Test listener` (Standard: 9871).
- `Receive Win-Test network based UDP log messages` aktivieren. - `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"). - `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 i.d.R. automatisch erkannt; erforderlich für das Senden von Sked-Paketen.
- `Win-Test network broadcast address`: Wird idR automatisch erkannt und ist erforderlich, um die Sked-Pakete ins Netzwerk zu senden.
**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:** **Einstellungen in Win-Test:**
- Das Netzwerk in Win-Test muss aktiv sein. - 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. **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. > **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.
--- ---

View File

@@ -46,7 +46,17 @@ The latest version can be downloaded as an AppImage:
**https://github.com/praktimarc/kst4contest/releases/latest** **https://github.com/praktimarc/kst4contest/releases/latest**
The filename has the format `praktiKST-v<version_number>-linux-x86_64.AppImage`. The filename has the format `KST4Contest-v<version_number>-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<version_number>-macos-<arch>.dmg`, where `<arch>` is `arm64` (Apple Silicon) or `x86_64` (Intel).
--- ---
@@ -64,11 +74,22 @@ Settings are stored at `%USERPROFILE%\.praktikst\preferences.xml`.
### Linux ### Linux
1. Download the AppImage. 1. Download the AppImage.
2. Unzip the AppImage into a folder of your choice. 2. Unzip the AppImage into a folder of your choice.
3. Make the AppImage executable (in the terminal with `chmod +x praktiKST-v<version_number>-linux-x86_64.AppImage`) 3. Make the AppImage executable (in the terminal with `chmod +x KST4Contest-v<version_number>-linux-x86_64.AppImage`)
4. Run the AppImage. 4. Run the AppImage.
Settings are stored at `~/.praktikst/preferences.xml`. 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 ## Updating
@@ -98,6 +119,12 @@ Currently as follows:
2. Mark the new AppImage as executable 2. Mark the new AppImage as executable
3. (optional) Delete the old AppImage. 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.
--- ---

View File

@@ -87,16 +87,22 @@ Win-Test is supported with a dedicated UDP network listener that understands the
**Advantages of Win-Test Integration:** **Advantages of Win-Test Integration:**
- Automatic QSO synchronization to mark worked stations. - 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. - 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:** **Settings in the "Log Synchronisation" tab:**
- `UDP-Port for Win-Test listener` (Default: 9871).
- Enable `Receive Win-Test network based UDP log messages`. - Enable `Receive Win-Test network based UDP log messages`.
- Enable `Win-Test sked transmission (push via ADDSKED to Win-Test 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"). - `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`: Usually detected automatically; required to send sked packets to the network.
- `Win-Test network broadcast address`: Is usually detected automatically and is 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:** **Settings in Win-Test:**
- The network in Win-Test must be active. - 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. **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. > **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.
--- ---

View File

@@ -810,6 +810,24 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
private final Map<String, ChatCategory> lastInboundCategoryByCallSignRaw = private final Map<String, ChatCategory> lastInboundCategoryByCallSignRaw =
new java.util.concurrent.ConcurrentHashMap<>(); 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<String, Long> 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 final ScoreService scoreService = new ScoreService(this, new PriorityCalculator(), 15);
private ScheduledExecutorService scoreScheduler; private ScheduledExecutorService scoreScheduler;
private final StationMetricsService stationMetricsService = new StationMetricsService(); 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 // Push sked to Win-Test via UDP if enabled
if (chatPreferences.isLogsynch_wintestNetworkSkedPushEnabled() if (chatPreferences.isLogsynch_wintestNetworkListenerEnabled()) {
&& chatPreferences.isLogsynch_wintestNetworkListenerEnabled()) {
pushSkedToWinTest(sked); pushSkedToWinTest(sked);
} }
} }
@@ -847,16 +864,69 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
WinTestSkedSender sender = new WinTestSkedSender(stationName, broadcastAddr, port, this); WinTestSkedSender sender = new WinTestSkedSender(stationName, broadcastAddr, port, this);
// Get current frequency from QRG property (set by Win-Test STATUS or user) // Frequency resolution:
double freqKHz = 144300.0; // fallback default // Compare WHO sent a QRG most recently in the PM conversation:
try { // - OM sent their QRG last → use OM's Last Known QRG (ChatMember.frequency)
String qrgStr = chatPreferences.getMYQRGFirstCat().get(); // - WE sent our QRG last → use our own Win-Test QRG (MYQRG)
if (qrgStr != null && !qrgStr.isBlank()) { // Fallback chain if no timestamps exist: OM's Last Known QRG → hardcoded default
// QRG is in display format like "144.300.00" strip dots → "14430000" → / 100 → 144300.0 kHz double freqKHz = -1.0;
String cleaned = qrgStr.trim().replace(".", ""); final long SKED_FREQ_MAX_AGE_MS = 60 * 60 * 1000L; // 60 minutes
freqKHz = Double.parseDouble(cleaned) / 100.0;
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°] // Build notes string with target locator/azimuth info like reference: [JO02OB - 279°]
String targetLocator = resolveSkedTargetLocator(sked.getTargetCallsign()); String targetLocator = resolveSkedTargetLocator(sked.getTargetCallsign());
@@ -883,6 +953,22 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
}, "WinTestSkedPush").start(); }, "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) { private String resolveSkedTargetLocator(String targetCallsignRaw) {
if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) { if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) {
return null; return null;

View File

@@ -962,6 +962,13 @@ public class MessageBusManagementThread extends Thread {
.setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage); .setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage);
this.client.getLst_globalChatMessageList().add(0,newMessageArrived); 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 // 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 // the "to me message list" with modified messagetext, added rxers callsign

View File

@@ -1,5 +1,6 @@
package kst4contest.controller; package kst4contest.controller;
import javafx.application.Platform;
import kst4contest.ApplicationConstants; import kst4contest.ApplicationConstants;
import kst4contest.model.ChatMember; import kst4contest.model.ChatMember;
import kst4contest.model.ThreadStateMessage; 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 = new DatagramSocket(null); //first init with null, then make ready for reuse
socket.setReuseAddress(true); socket.setReuseAddress(true);
// socket = new DatagramSocket(PORT); // 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); 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) { } catch (SocketException e) {
e.printStackTrace(); e.printStackTrace();
return; return;
@@ -224,9 +226,43 @@ public class ReadUDPByWintestThread extends Thread {
} else { } else {
formattedQRG = String.format(Locale.US, "%.1f", freqFloat); // fallback 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) { } catch (Exception e) {
System.out.println("[WinTest] STATUS parsing error: " + e.getMessage()); System.out.println("[WinTest] STATUS parsing error: " + e.getMessage());
} }

View File

@@ -204,6 +204,8 @@ public class ChatPreferences {
String logsynch_wintestNetworkBroadcastAddress = "255.255.255.255"; // UDP broadcast address for sending to Win-Test 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 boolean logsynch_wintestNetworkSkedPushEnabled = false; // push SKEDs to Win-Test via UDP
String logsynch_wintestSkedMode = "SSB"; // CW, SSB or AUTO 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; 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() { public String getStn_loginLocatorSecondCat() {
return stn_loginLocatorSecondCat; return stn_loginLocatorSecondCat;
} }
@@ -1338,6 +1356,14 @@ public class ChatPreferences {
logsynch_wintestSkedMode.setTextContent(this.logsynch_wintestSkedMode); logsynch_wintestSkedMode.setTextContent(this.logsynch_wintestSkedMode);
logsynch.appendChild(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 * trxSynchUCX
@@ -1912,6 +1938,16 @@ public class ChatPreferences {
logsynch_wintestSkedMode, logsynch_wintestSkedMode,
"logsynch_wintestSkedMode"); "logsynch_wintestSkedMode");
logsynch_wintestQrgSyncEnabled = getBoolean(
logsynchEl,
logsynch_wintestQrgSyncEnabled,
"logsynch_wintestQrgSyncEnabled");
logsynch_wintestUsePassQrg = getBoolean(
logsynchEl,
logsynch_wintestUsePassQrg,
"logsynch_wintestUsePassQrg");
System.out.println( System.out.println(
"[ChatPreferences, info]: file based worked-call interpreter: " + logsynch_fileBasedWkdCallInterpreterEnabled); "[ChatPreferences, info]: file based worked-call interpreter: " + logsynch_fileBasedWkdCallInterpreterEnabled);
System.out.println( System.out.println(

View File

@@ -3582,7 +3582,57 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
Menu fileMenu = new Menu("File"); Menu fileMenu = new Menu("File");
// create menuitems // build "Connect to <configured chat>" 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 = new MenuItem("Disconnect");
menuItemFileDisconnect.setDisable(true); menuItemFileDisconnect.setDisable(true);
@@ -3595,6 +3645,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
public void handle(ActionEvent event) { public void handle(ActionEvent event) {
chatcontroller.disconnect(ApplicationConstants.DISCSTRING_DISCONNECTONLY); chatcontroller.disconnect(ApplicationConstants.DISCSTRING_DISCONNECTONLY);
menuItemFileDisconnect.setDisable(true); menuItemFileDisconnect.setDisable(true);
menuItemFileConnect.setDisable(false);
} }
}); });
@@ -3607,6 +3658,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
}); });
// add menu items to menu // add menu items to menu
fileMenu.getItems().add(menuItemFileConnect);
fileMenu.getItems().add(menuItemFileDisconnect); fileMenu.getItems().add(menuItemFileDisconnect);
fileMenu.getItems().add(m10); fileMenu.getItems().add(m10);
@@ -4010,6 +4062,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
Scene clusterAndQSOMonScene; Scene clusterAndQSOMonScene;
Scene settingsScene; Scene settingsScene;
MenuItem menuItemFileConnect;
MenuItem menuItemFileDisconnect; MenuItem menuItemFileDisconnect;
MenuItem menuItemOptionsAwayBack; MenuItem menuItemOptionsAwayBack;
@@ -4170,10 +4223,15 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
timer_updatePrivatemessageTable.purge(); timer_updatePrivatemessageTable.purge();
timer_updatePrivatemessageTable.cancel(); 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(); // Platform.exit();
System.exit(0);
} }
private Queue<Media> musicList = new LinkedList<Media>(); private Queue<Media> musicList = new LinkedList<Media>();
@@ -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:"); 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()); TextField txtFldNameInChatSecondCat = new TextField(this.chatcontroller.getChatPreferences().getStn_loginNameSecondCat());
txtFldNameInChatSecondCat.setFocusTraversable(false); txtFldNameInChatSecondCat.setFocusTraversable(false);
txtFldNameInChatSecondCat.setVisible(false); txtFldNameInChatSecondCat.setVisible(isSecondChatEnabled);
txtFldNameInChatSecondCat.setDisable(!isSecondChatEnabled);
txtFldNameInChatSecondCat.textProperty().addListener(new ChangeListener<String>() { txtFldNameInChatSecondCat.textProperty().addListener(new ChangeListener<String>() {
@@ -6397,11 +6458,12 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
CheckBox station_chkBxEnableSecondChat = new CheckBox("2nd Chat: "); 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<Boolean>() { station_chkBxEnableSecondChat.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override @Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { public void changed(ObservableValue<? extends Boolean> 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() + ""); TextField txtFldstn_antennaBeamWidthDeg = new TextField(this.chatcontroller.getChatPreferences().getStn_antennaBeamWidthDeg() + "");
txtFldstn_antennaBeamWidthDeg.setFocusTraversable(false); 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_QRV3400, 1, 2);
grdPnlStation_bands.add(settings_chkbx_QRV5600, 2, 2); grdPnlStation_bands.add(settings_chkbx_QRV5600, 2, 2);
grdPnlStation_bands.add(settings_chkbx_QRV10G, 0, 3); grdPnlStation_bands.add(settings_chkbx_QRV10G, 0, 3);
grdPnlStation_bands.setMaxWidth(555.0);
grdPnlStation_bands.setStyle(" -fx-border-color: lightgray;\n" + grdPnlStation_bands.setStyle(" -fx-border-color: lightgray;\n" +
" -fx-vgap: 5;\n" + " -fx-vgap: 5;\n" +
@@ -6882,15 +6938,32 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
grdPnlLog.add(lblUDPByWintest, 0, 8); grdPnlLog.add(lblUDPByWintest, 0, 8);
grdPnlLog.add(txtFldUDPPortforWintest, 1, 8); grdPnlLog.add(txtFldUDPPortforWintest, 1, 8);
// --- Win-Test SKED push settings --- // --- QRG sync from Win-Test STATUS ---
Label lblEnableSkedPush = new Label("Push SKEDs to Win-Test via UDP (ADDSKED)"); Label lblWtQrgSync = new Label("Win-Test STATUS QRG Sync (updates own QRG from Win-Test transceiver frequency)");
CheckBox chkBxEnableSkedPush = new CheckBox(); CheckBox chkBxWtQrgSync = new CheckBox();
chkBxEnableSkedPush.setSelected( chkBxWtQrgSync.setSelected(
this.chatcontroller.getChatPreferences().isLogsynch_wintestNetworkSkedPushEnabled() this.chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()
); );
chkBxEnableSkedPush.selectedProperty().addListener((obs, oldVal, newVal) -> { chkBxWtQrgSync.selectedProperty().addListener((obs, oldVal, newVal) -> {
chatcontroller.getChatPreferences().setLogsynch_wintestNetworkSkedPushEnabled(newVal); chatcontroller.getChatPreferences().setLogsynch_wintestQrgSyncEnabled(newVal);
System.out.println("[Main.java, Info]: Win-Test SKED push enabled: " + 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)"); 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(lblWtStationName, 0, 9);
grdPnlLog.add(chkBxEnableSkedPush, 1, 9); grdPnlLog.add(txtFldWtStationName, 1, 9);
grdPnlLog.add(lblWtStationName, 0, 11);
grdPnlLog.add(txtFldWtStationName, 1, 11);
grdPnlLog.add(lblWtStationFilter, 0, 12);
grdPnlLog.add(txtFldWtStationFilter, 1, 12);
// Auto-detect subnet broadcast if preference is still the default // Auto-detect subnet broadcast if preference is still the default
String currentBroadcast = this.chatcontroller.getChatPreferences().getLogsynch_wintestNetworkBroadcastAddress(); 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) // Re-read (may have been auto-detected)
txtFldWtBroadcastAddr.setText(this.chatcontroller.getChatPreferences().getLogsynch_wintestNetworkBroadcastAddress()); txtFldWtBroadcastAddr.setText(this.chatcontroller.getChatPreferences().getLogsynch_wintestNetworkBroadcastAddress());
grdPnlLog.add(lblWtBroadcastAddr, 0, 13); grdPnlLog.add(lblWtBroadcastAddr, 0, 10);
grdPnlLog.add(txtFldWtBroadcastAddr, 1, 13); grdPnlLog.add(txtFldWtBroadcastAddr, 1, 10);
VBox vbxLog = new VBox(); VBox vbxLog = new VBox();
vbxLog.setPadding(new Insets(10, 10, 10, 10)); vbxLog.setPadding(new Insets(10, 10, 10, 10));
@@ -6995,51 +7063,45 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
chkBxEnableTRXMsgbyUCX.selectedProperty().addListener(new ChangeListener<Boolean>() { chkBxEnableTRXMsgbyUCX.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override @Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// chk2.setSelected(!newValue); chatcontroller.getChatPreferences().setTrxSynch_ucxLogUDPListenerEnabled(newValue);
if (!newValue) { boolean anyActive = newValue || chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled();
chatcontroller.getChatPreferences() if (!anyActive) {
.setTrxSynch_ucxLogUDPListenerEnabled(chkBxEnableTRXMsgbyUCX.isSelected());
txt_ownqrgMainCategory.textProperty().unbind(); txt_ownqrgMainCategory.textProperty().unbind();
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by hand (watch prefs!)")); 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]: 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 { } else {
chatcontroller.getChatPreferences()
.setTrxSynch_ucxLogUDPListenerEnabled(chkBxEnableTRXMsgbyUCX.isSelected());
txt_ownqrgMainCategory.textProperty().bind(chatcontroller.getChatPreferences().getMYQRGFirstCat()); txt_ownqrgMainCategory.textProperty().bind(chatcontroller.getChatPreferences().getMYQRGFirstCat());
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)")); 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 // Unconditionally add listener to manually sync the textfield input to the button
if (this.chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled()) { // (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.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 txt_ownqrgMainCategory.textProperty().bind(this.chatcontroller.getChatPreferences().getMYQRGFirstCat());
// gemacht werden, wenn
// ucxlog-Frequenznachrichten
// ausgewerttet werden!
// System.out.println("[Main.java, Info]: MYQRG will be changed only by UCXListener");
} else { } else {
txt_ownqrgMainCategory.setTooltip(new Tooltip("enter your cq qrg here")); 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(generateLabeledSeparator(100, "Receive UCXLog TRX info"), 0, 0, 2, 1);
grdPnltrx.add(lblEnableTRXMsgbyUCX, 0, 1); grdPnltrx.add(lblEnableTRXMsgbyUCX, 0, 1);
grdPnltrx.add(chkBxEnableTRXMsgbyUCX, 1, 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(); VBox vbxTRXSynch = new VBox();
vbxTRXSynch.setPadding(new Insets(10, 10, 10, 10)); vbxTRXSynch.setPadding(new Insets(10, 10, 10, 10));
vbxTRXSynch.getChildren().addAll(grdPnltrx); vbxTRXSynch.getChildren().addAll(grdPnltrx);
@@ -8124,6 +8186,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
else if (chatcontroller.isConnectedAndLoggedIn()) { else if (chatcontroller.isConnectedAndLoggedIn()) {
btnOptionspnlDisconnectOnly.setDisable(false); btnOptionspnlDisconnectOnly.setDisable(false);
menuItemFileDisconnect.setDisable(false); menuItemFileDisconnect.setDisable(false);
menuItemFileConnect.setDisable(true);
menuItemOptionsAwayBack.setDisable(false); menuItemOptionsAwayBack.setDisable(false);
} }
@@ -8147,13 +8210,19 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
txtFldstn_maxQRBDefault.setDisable(false); txtFldstn_maxQRBDefault.setDisable(false);
menuItemOptionsSetFrequencyAsName.setDisable(true); menuItemOptionsSetFrequencyAsName.setDisable(true);
menuItemOptionsAwayBack.setDisable(true); menuItemOptionsAwayBack.setDisable(true);
menuItemFileConnect.setDisable(false);
station_chkBxEnableSecondChat.setDisable(false); station_chkBxEnableSecondChat.setDisable(false);
stn_choiceBxChatChategorySecond.setDisable(false); stn_choiceBxChatChategorySecond.setDisable(false);
} }
}); });
btnOptionspnlConnect = new Button("Connect to " + chatcontroller.getChatPreferences().getLoginChatCategoryMain() String btnText = "Connect to " + chatcontroller.getChatPreferences().getLoginChatCategoryMain()
.getChatCategoryName(choiceBxChatChategory.getSelectionModel().getSelectedItem().getCategoryNumber())); .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<ActionEvent>() { btnOptionspnlConnect.setOnAction(new EventHandler<ActionEvent>() {
@Override @Override
public void handle(ActionEvent event) { public void handle(ActionEvent event) {
@@ -8185,6 +8254,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
btnOptionspnlDisconnectOnly.setDisable(false); btnOptionspnlDisconnectOnly.setDisable(false);
menuItemFileDisconnect.setDisable(false); menuItemFileDisconnect.setDisable(false);
menuItemFileConnect.setDisable(true);
menuItemOptionsAwayBack.setDisable(false); menuItemOptionsAwayBack.setDisable(false);
menuItemOptionsSetFrequencyAsName.setDisable(false); menuItemOptionsSetFrequencyAsName.setDisable(false);