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
15 changed files with 18069 additions and 313 deletions

View File

@@ -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

View File

@@ -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

2
SimpleLogFile.txt Normal file
View File

@@ -0,0 +1,2 @@
dr2x
oe3cin

15832
bugsept24.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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<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
1. AppImage herunterladen.
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.
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.
---

View File

@@ -87,16 +87,22 @@ Win-Test wird mit einem dedizierten UDP-Netzwerk-Listener unterstützt, der das
**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.
---

View File

@@ -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<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
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<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.
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.
---

View File

@@ -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.
---

View File

@@ -23,7 +23,6 @@ import kst4contest.locatorUtils.DirectionUtils;
import kst4contest.logic.PriorityCalculator;
import kst4contest.model.*;
import kst4contest.test.MockKstServer;
import kst4contest.utils.BoundedDequeObservableList;
import kst4contest.utils.PlayAudioUtils;
import kst4contest.view.Kst4ContestApplication;
@@ -811,6 +810,24 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
private final Map<String, ChatCategory> 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<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 ScheduledExecutorService scoreScheduler;
private final StationMetricsService stationMetricsService = new StationMetricsService();
@@ -828,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);
}
}
@@ -848,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
// 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;
}
}
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()) {
// 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;
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());
@@ -884,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;
@@ -1008,8 +1093,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// ******All abstract types below here are used by the messageprocessor!
// ***************
private static final int MAX_CHAT_MESSAGES = 10000;
private final BoundedDequeObservableList<ChatMessage> lst_globalChatMessageList = new BoundedDequeObservableList<>(MAX_CHAT_MESSAGES); //All chatmessages will be put in there, later create filtered message lists
private ObservableList<ChatMessage> lst_globalChatMessageList = FXCollections.observableArrayList(); //All chatmessages will be put in there, later create filtered message lists
// private ObservableList<ChatMessage> lst_toAllMessageList = FXCollections.observableArrayList(); // directed to all
// (beacon)
private FilteredList<ChatMessage> lst_toAllMessageList = new FilteredList<>(lst_globalChatMessageList); // directed to all
@@ -1154,14 +1238,13 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
this.lst_selectedCallSignInfofilteredMessageList = lst_selectedCallSignInfofilteredMessageList;
}
public void addChatMessage(ChatMessage message) {
lst_globalChatMessageList.addFirst(message);
}
public ObservableList<ChatMessage> getLst_globalChatMessageList() {
return lst_globalChatMessageList;
}
public void setLst_globalChatMessageList(ObservableList<ChatMessage> lst_globalChatMessageList) {
this.lst_globalChatMessageList = lst_globalChatMessageList;
}
public String getHostname() {
return hostname;

View File

@@ -772,7 +772,7 @@ public class MessageBusManagementThread extends Thread {
dummy.setCallSign("ALL");
newMessageArrived.setReceiver(dummy);
this.client.addChatMessage(newMessageArrived); // sdtout to all message-List
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); // sdtout to all message-List
} else {
//message is directed to another chatmember, process as such!
@@ -817,7 +817,7 @@ public class MessageBusManagementThread extends Thread {
if (newMessageArrived.getReceiver().getCallSign()
.equals(this.client.getChatPreferences().getStn_loginCallSign())) {
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
if (this.client.getChatPreferences().isNotify_playSimpleSounds()) {
this.client.getPlayAudioUtils().playNoiseLauncher('P');
@@ -960,7 +960,14 @@ public class MessageBusManagementThread extends Thread {
String originalMessage = newMessageArrived.getMessageText();
newMessageArrived
.setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage);
this.client.addChatMessage(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
// the "to me message list" with modified messagetext, added rxers callsign
@@ -1024,7 +1031,7 @@ public class MessageBusManagementThread extends Thread {
newMessageArrived.getSender().setInAngleAndRange(false);
}
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
// System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign());
}
} catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) {
@@ -1364,7 +1371,7 @@ public class MessageBusManagementThread extends Thread {
dummy.setCallSign("ALL");
newMessageArrived.setReceiver(dummy);
this.client.addChatMessage(newMessageArrived); // sdtout to all message-List
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); // sdtout to all message-List
} else {
//message is directed to another chatmember, process as such!
@@ -1408,7 +1415,7 @@ public class MessageBusManagementThread extends Thread {
if (newMessageArrived.getReceiver().getCallSign()
.equals(this.client.getChatPreferences().getStn_loginCallSign())) {
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
System.out.println("Historic message directed to me: " + newMessageArrived.getReceiver().getCallSign() + ".");
@@ -1421,7 +1428,7 @@ public class MessageBusManagementThread extends Thread {
String originalMessage = newMessageArrived.getMessageText();
newMessageArrived
.setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage);
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0,newMessageArrived);
// 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
@@ -1441,7 +1448,7 @@ public class MessageBusManagementThread extends Thread {
newMessageArrived.getSender().setInAngleAndRange(false);
}
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
// System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign());
}
} catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) {
@@ -1514,7 +1521,7 @@ public class MessageBusManagementThread extends Thread {
for (int i = 0; i < 10; i++) {
client.addChatMessage(pwErrorMsg);
client.getLst_globalChatMessageList().add(pwErrorMsg);
// client.getLst_toMeMessageList().add(pwErrorMsg);
// client.getLst_toAllMessageList().add(pwErrorMsg);
}

View File

@@ -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());
}

View File

@@ -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(

View File

@@ -1,168 +0,0 @@
package kst4contest.utils;
import javafx.collections.ObservableListBase;
import java.util.Arrays;
/**
* A bounded ObservableList backed by a circular buffer (ring buffer).
* <p>
* Provides O(1) {@link #addFirst} and {@link #addLast} as well as O(1)
* random access via {@link #get}. When the list reaches {@code maxCapacity},
* adding a new element at the front automatically evicts the oldest element
* at the back — and vice versa.
* <p>
* This is a drop-in replacement for {@code FXCollections.observableArrayList()}
* wherever elements are prepended frequently, e.g. chat message lists.
*/
public class BoundedDequeObservableList<E> extends ObservableListBase<E> {
private final int maxCapacity;
private final Object[] elements;
private int head = 0;
private int size = 0;
public BoundedDequeObservableList(int maxCapacity) {
if (maxCapacity <= 0) throw new IllegalArgumentException("maxCapacity must be > 0");
this.maxCapacity = maxCapacity;
this.elements = new Object[maxCapacity];
}
// ── read access ──────────────────────────────────────────────────────────
@Override
public int size() {
return size;
}
@Override
@SuppressWarnings("unchecked")
public E get(int index) {
checkIndex(index);
return (E) elements[physicalIndex(index)];
}
// ── O(1) deque operations ─────────────────────────────────────────────────
/**
* Inserts {@code element} at index 0 (newest-first order).
* If the list is already at capacity the oldest element (last index) is
* removed first — both changes are reported as a single compound change.
*/
public void addFirst(E element) {
beginChange();
if (size == maxCapacity) {
// evict last element
int lastPhysical = physicalIndex(size - 1);
@SuppressWarnings("unchecked")
E evicted = (E) elements[lastPhysical];
elements[lastPhysical] = null;
size--;
nextRemove(size, evicted); // index after decrement == old last index
}
head = (head - 1 + maxCapacity) % maxCapacity;
elements[head] = element;
size++;
nextAdd(0, 1);
endChange();
}
/**
* Appends {@code element} at the last index (oldest-first order).
* If the list is already at capacity the newest element (index 0) is
* removed first.
*/
public void addLast(E element) {
beginChange();
if (size == maxCapacity) {
// evict first element
@SuppressWarnings("unchecked")
E evicted = (E) elements[head];
elements[head] = null;
head = (head + 1) % maxCapacity;
size--;
nextRemove(0, evicted);
}
elements[physicalIndex(size)] = element;
size++;
nextAdd(size - 1, size);
endChange();
}
// ── standard List mutation (O(n) — use addFirst/addLast for hot path) ─────
@Override
public void add(int index, E element) {
if (index == 0) {
addFirst(element);
return;
}
if (index == size) {
addLast(element);
return;
}
checkIndexForAdd(index);
beginChange();
if (size == maxCapacity) {
int lastPhysical = physicalIndex(size - 1);
@SuppressWarnings("unchecked")
E evicted = (E) elements[lastPhysical];
elements[lastPhysical] = null;
size--;
nextRemove(size, evicted);
}
// shift elements [index .. size-1] one position towards the end
for (int i = size; i > index; i--) {
elements[physicalIndex(i)] = elements[physicalIndex(i - 1)];
}
elements[physicalIndex(index)] = element;
size++;
nextAdd(index, index + 1);
endChange();
}
@Override
public E remove(int index) {
checkIndex(index);
beginChange();
@SuppressWarnings("unchecked")
E removed = (E) elements[physicalIndex(index)];
// shift elements [index+1 .. size-1] one position towards the front
for (int i = index; i < size - 1; i++) {
elements[physicalIndex(i)] = elements[physicalIndex(i + 1)];
}
elements[physicalIndex(size - 1)] = null;
size--;
nextRemove(index, removed);
endChange();
return removed;
}
@Override
public E set(int index, E element) {
checkIndex(index);
beginChange();
@SuppressWarnings("unchecked")
E old = (E) elements[physicalIndex(index)];
elements[physicalIndex(index)] = element;
nextSet(index, old);
endChange();
return old;
}
// ── helpers ───────────────────────────────────────────────────────────────
private int physicalIndex(int virtualIndex) {
return (head + virtualIndex) % maxCapacity;
}
private void checkIndex(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
private void checkIndexForAdd(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}

View File

@@ -3582,7 +3582,57 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
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.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();
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<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:");
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<String>() {
@@ -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<Boolean>() {
@Override
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() + "");
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<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> 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()) {
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");
} 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");
// 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) -> {
System.out.println(
"[Main.java, Info]: MYQRG Text changed from " + oldValue + " to " + newValue + " by hand");
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());
} else {
txt_ownqrgMainCategory.setTooltip(new Tooltip("enter your cq qrg here"));
}
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<ActionEvent>() {
@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);

1783
udpReaderBackup.txt Normal file

File diff suppressed because it is too large Load Diff