Compare commits

...
12 Commits
Author SHA1 Message Date
Rsclub2_2andClaude Fable 5.1 4a90ae7d30 docs: describe worked-state check for automatic DX cluster spots (#91)
Add the new spot condition to the DX Cluster Server manual pages and the
forwarding section of the Features page in German and English, record
the fix in the v1.50 changelog, and note the rule in PROJECT_CONTEXT.

Fixes #91

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 01:12:11 +02:00
Rsclub2_2andClaude Fable 5.1 78de8b0a9c fix: suppress automatic DX cluster spots for worked stations (#91)
Automatic spots from directional opportunities were generated without
looking at the worked state, so stations already in the log kept being
spotted to the connected loggers.

The spot block in MessageBusManagementThread now asks
isAlreadyWorkedForDxClusterSpot(sender) before broadcasting. The rule:

- the band is taken from the sender's known frequency via
  FrequencyTextParser; an unknown band keeps the previous behaviour;
- the per-band worked flag of that band suppresses the spot
  (ChatMember.isWorkedOnBand maps B_1296/B_2320/B_5760 to the
  historically named worked1240/worked2300/worked5600 flags);
- the global worked flag suppresses the spot only when no per-band
  flag exists at all (Simplelogfile source without band information);
- the global flag together with per-band flags for other bands does
  not suppress, because the per-band information is authoritative.

Suppressed spots are logged at FINE. The direction check, the
inAngleAndRange marker, the tick sound and manual spots from the map
window and the settings test spot are unchanged.

Fixes #91

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 01:12:02 +02:00
Rsclub2_2andClaude Fable 5.1 ab28216280 fix: sort the AP column numerically instead of by its text (#87)
The "AP [minutes / pot%]" column of the station list and of the directed
messages had no comparator, so DataTableState fell back to the rendered
text and "13 (75%)" sorted above "3 (50%)".

AirplaneCell now owns a shared comparator, AirplaneCell.order, that both
column definitions use. The sort key is the first aeroplane of the cell,
which is the one the operator reads and the one the colour marks:
ascending by its arriving minutes, then by the better potential first for
equal minutes. A null info (nothing computed) and an empty list ("nil")
carry Int.MAX_VALUE, so they sort after every real aeroplane in ascending
order, consistent with the qrb and qtf sentinels. The second aeroplane of
a cell does not influence the order. Cell text, colours, column ids,
titles and weights are unchanged, as are DataTableState and the sort
cycle.

Fixes #87

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 01:09:49 +02:00
dependabot[bot] 456957808d build(deps): bump the minor-and-patch group across 1 directory with 5 updates
Bumps the minor-and-patch group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [gradle-wrapper](https://github.com/gradle/gradle) | `9.7.1` | `9.8.1` |
| [org.xerial:sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) | `3.43.2.2` | `3.53.4.0` |
| [org.mockito:mockito-core](https://github.com/mockito/mockito) | `5.7.0` | `5.24.0` |
| [org.mockito:mockito-core](https://github.com/mockito/mockito) | `5.7.0` | `5.24.0` |
| [org.mockito:mockito-junit-jupiter](https://github.com/mockito/mockito) | `5.7.0` | `5.24.0` |
| com.github.spotbugs | `6.0.26` | `6.5.12` |

Updates `gradle-wrapper` from 9.7.1 to 9.8.1
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.7.1...v9.8.1)

Updates `org.xerial:sqlite-jdbc` from 3.43.2.2 to 3.53.4.0
- [Release notes](https://github.com/xerial/sqlite-jdbc/releases)
- [Changelog](https://github.com/xerial/sqlite-jdbc/blob/master/CHANGELOG)
- [Commits](https://github.com/xerial/sqlite-jdbc/compare/3.43.2.2...3.53.4.0)

Updates `org.mockito:mockito-core` from 5.7.0 to 5.24.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.7.0...v5.24.0)

Updates `org.mockito:mockito-core` from 5.7.0 to 5.24.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.7.0...v5.24.0)

Updates `org.mockito:mockito-junit-jupiter` from 5.7.0 to 5.24.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.7.0...v5.24.0)

Updates `com.github.spotbugs` from 6.0.26 to 6.5.12

---
updated-dependencies:
- dependency-name: com.github.spotbugs
  dependency-version: 6.5.12
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: gradle-wrapper
  dependency-version: 9.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: org.mockito:mockito-core
  dependency-version: 5.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: org.mockito:mockito-core
  dependency-version: 5.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: org.mockito:mockito-junit-jupiter
  dependency-version: 5.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: org.xerial:sqlite-jdbc
  dependency-version: 3.53.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:09 +02:00
dependabot[bot] e67e2db2aa build(deps): bump junit from 5.10.1 to 6.1.3
Bumps `junit` from 5.10.1 to 6.1.3.

Updates `org.junit.jupiter:junit-jupiter` from 5.10.1 to 6.1.3
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.10.1...r6.1.3)

Updates `org.junit.jupiter:junit-jupiter-api` from 5.10.1 to 6.1.3
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.10.1...r6.1.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-version: 6.1.3
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 6.1.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:09 +02:00
dependabot[bot] 93c1423dcd build(deps): bump org.jetbrains:annotations from 24.0.1 to 26.1.0
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 24.0.1 to 26.1.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/24.0.1...26.1.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-version: 26.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:09 +02:00
dependabot[bot] eddcbc82e8 build(deps): bump actions/setup-java from 4.1.0 to 6.0.1
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.1.0 to 6.0.1.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4.1.0...v6.0.1)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:09 +02:00
dependabot[bot] ab42c6ef35 build(deps): bump actions/upload-artifact from 4 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:09 +02:00
dependabot[bot] 0039ec98e3 build(deps): bump actions/download-artifact from 4.1.3 to 8.0.1
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.3 to 8.0.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.3...v8.0.1)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:08 +02:00
dependabot[bot] 294a978c3a build(deps): bump actions/checkout from 4 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:08 +02:00
dependabot[bot] 19b9bf0fca build(deps): bump Andrew-Chen-Wang/github-wiki-action from 4 to 5
Bumps [Andrew-Chen-Wang/github-wiki-action](https://github.com/andrew-chen-wang/github-wiki-action) from 4 to 5.
- [Release notes](https://github.com/andrew-chen-wang/github-wiki-action/releases)
- [Commits](https://github.com/andrew-chen-wang/github-wiki-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: Andrew-Chen-Wang/github-wiki-action
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 00:37:08 +02:00
Rsclub2_2andClaude Sonnet 5.5 5b9fe0d3c1 chore: add Dependabot configuration for Gradle and GitHub Actions
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 00:37:08 +02:00
30 changed files with 722 additions and 95 deletions

No files matched your search

+33
View File
@@ -0,0 +1,33 @@
version: 2
updates:
# Gradle dependencies, including the version catalog in gradle/libs.versions.toml
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
target-branch: "main"
ignore:
# Kotlin and Compose Multiplatform are coupled; bump them manually together
- dependency-name: "org.jetbrains.kotlin*"
- dependency-name: "org.jetbrains.compose*"
groups:
minor-and-patch:
update-types:
- "minor"
- "patch"
# Actions used in .github/workflows
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
target-branch: "main"
groups:
minor-and-patch:
update-types:
- "minor"
- "patch"
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
useradd -m builder useradd -m builder
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
ssh-key: ${{ secrets.WEBSITE_DEPLOY_KEY }} ssh-key: ${{ secrets.WEBSITE_DEPLOY_KEY }}
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve version string - name: Resolve version string
run: | run: |
@@ -102,7 +102,7 @@ jobs:
github_docs/de-Changelog.md github_docs/de-Changelog.md
- name: Upload PDF artifacts - name: Upload PDF artifacts
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: docs-pdf name: docs-pdf
path: dist/KST4Contest-*-manual-*.pdf path: dist/KST4Contest-*-manual-*.pdf
+2 -2
View File
@@ -14,8 +14,8 @@ jobs:
publish-wiki: publish-wiki:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- uses: Andrew-Chen-Wang/github-wiki-action@v4 - uses: Andrew-Chen-Wang/github-wiki-action@v5
with: with:
path: github_docs path: github_docs
disable-empty-commits: true disable-empty-commits: true
+24 -24
View File
@@ -31,7 +31,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve nightly version info - name: Resolve nightly version info
shell: pwsh shell: pwsh
@@ -43,7 +43,7 @@ jobs:
Add-Content -Path $env:GITHUB_ENV -Value "ASSET_BASENAME=praktiKST-$version-$shortSha" Add-Content -Path $env:GITHUB_ENV -Value "ASSET_BASENAME=praktiKST-$version-$shortSha"
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -71,7 +71,7 @@ jobs:
Compress-Archive -Path dist/KST4Contest -DestinationPath "dist/$env:ASSET_BASENAME-windows-x64.zip" -Force Compress-Archive -Path dist/KST4Contest -DestinationPath "dist/$env:ASSET_BASENAME-windows-x64.zip" -Force
- name: Upload Windows artifact - name: Upload Windows artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: windows-zip name: windows-zip
path: dist/praktiKST-*-windows-x64.zip path: dist/praktiKST-*-windows-x64.zip
@@ -83,7 +83,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve nightly version info - name: Resolve nightly version info
run: | run: |
@@ -92,7 +92,7 @@ jobs:
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -144,7 +144,7 @@ jobs:
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/KST4Contest.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@v6
with: with:
name: linux-appimage name: linux-appimage
path: dist/KST4Contest-*-linux-x86_64.AppImage path: dist/KST4Contest-*-linux-x86_64.AppImage
@@ -156,7 +156,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve nightly version info - name: Resolve nightly version info
run: | run: |
@@ -165,7 +165,7 @@ jobs:
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -189,7 +189,7 @@ jobs:
mv "$DEB" "dist/${ASSET_BASENAME}-debian-amd64.deb" mv "$DEB" "dist/${ASSET_BASENAME}-debian-amd64.deb"
- name: Upload Debian artifact - name: Upload Debian artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: linux-debian name: linux-debian
path: dist/KST4Contest-*-debian-amd64.deb path: dist/KST4Contest-*-debian-amd64.deb
@@ -201,7 +201,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve nightly version info - name: Resolve nightly version info
run: | run: |
@@ -210,7 +210,7 @@ jobs:
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -234,7 +234,7 @@ jobs:
mv "$RPM" "dist/${ASSET_BASENAME}-fedora-x86_64.rpm" mv "$RPM" "dist/${ASSET_BASENAME}-fedora-x86_64.rpm"
- name: Upload Fedora artifact - name: Upload Fedora artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: linux-fedora name: linux-fedora
path: dist/KST4Contest-*-fedora-x86_64.rpm path: dist/KST4Contest-*-fedora-x86_64.rpm
@@ -246,7 +246,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve nightly version info - name: Resolve nightly version info
run: | run: |
@@ -257,7 +257,7 @@ jobs:
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -345,7 +345,7 @@ jobs:
tar --zstd --transform 's|^\./||' -cf "dist/${ASSET_BASENAME}-archlinux-${ARCH}.pkg.tar.zst" -C "$PKGROOT" . tar --zstd --transform 's|^\./||' -cf "dist/${ASSET_BASENAME}-archlinux-${ARCH}.pkg.tar.zst" -C "$PKGROOT" .
- name: Upload Arch Linux artifact - name: Upload Arch Linux artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: linux-arch name: linux-arch
path: dist/KST4Contest-*-archlinux-*.pkg.tar.zst path: dist/KST4Contest-*-archlinux-*.pkg.tar.zst
@@ -357,7 +357,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve nightly version info - name: Resolve nightly version info
run: | run: |
@@ -368,7 +368,7 @@ jobs:
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -463,13 +463,13 @@ jobs:
EOF EOF
- name: Upload flatpakref - name: Upload flatpakref
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: flatpakref name: flatpakref
path: dist/de.x08.KST4Contest.nightly.flatpakref path: dist/de.x08.KST4Contest.nightly.flatpakref
- name: Upload Flatpak OSTree repo - name: Upload Flatpak OSTree repo
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: flatpak-ostree-repo name: flatpak-ostree-repo
path: target/flatpak-repo/ path: target/flatpak-repo/
@@ -494,13 +494,13 @@ jobs:
echo "FLATPAK_GPG_KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr/{print $10; exit}')" >> "$GITHUB_ENV" echo "FLATPAK_GPG_KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr/{print $10; exit}')" >> "$GITHUB_ENV"
- name: Download OSTree repo artifact - name: Download OSTree repo artifact
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: flatpak-ostree-repo name: flatpak-ostree-repo
path: flatpak-ostree-repo/ path: flatpak-ostree-repo/
- name: Checkout existing flatpak-repo branch - name: Checkout existing flatpak-repo branch
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
with: with:
ref: flatpak-repo ref: flatpak-repo
path: existing-flatpak-repo path: existing-flatpak-repo
@@ -545,7 +545,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Resolve nightly version info - name: Resolve nightly version info
run: | run: |
@@ -558,7 +558,7 @@ jobs:
echo "ARCH=$ARCH" >> "$GITHUB_ENV" echo "ARCH=$ARCH" >> "$GITHUB_ENV"
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -602,7 +602,7 @@ jobs:
mv "$DMG" "dist/${ASSET_BASENAME}-macos-${ARCH}.dmg" mv "$DMG" "dist/${ASSET_BASENAME}-macos-${ARCH}.dmg"
- name: Upload macOS artifact - name: Upload macOS artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: macos-dmg-${{ matrix.os }} name: macos-dmg-${{ matrix.os }}
path: dist/KST4Contest-*-macos-*.dmg path: dist/KST4Contest-*-macos-*.dmg
+5 -5
View File
@@ -16,10 +16,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -100,10 +100,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -175,7 +175,7 @@ jobs:
- name: Keep the test reports on a failure - name: Keep the test reports on a failure
if: failure() if: failure()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: test-results name: test-results
path: | path: |
+36 -36
View File
@@ -22,10 +22,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -53,7 +53,7 @@ jobs:
Compress-Archive -Path dist/KST4Contest -DestinationPath dist/praktiKST-${{ github.ref_name }}-windows-x64.zip -Force Compress-Archive -Path dist/KST4Contest -DestinationPath dist/praktiKST-${{ github.ref_name }}-windows-x64.zip -Force
- name: Upload Windows artifact - name: Upload Windows artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: windows-zip name: windows-zip
path: dist/praktiKST-${{ github.ref_name }}-windows-x64.zip path: dist/praktiKST-${{ github.ref_name }}-windows-x64.zip
@@ -64,10 +64,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -119,7 +119,7 @@ jobs:
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/KST4Contest.AppDir dist/KST4Contest-${{ 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@v6
with: with:
name: linux-appimage name: linux-appimage
path: dist/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage path: dist/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage
@@ -130,10 +130,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -157,7 +157,7 @@ jobs:
mv "$DEB" "dist/KST4Contest-${{ github.ref_name }}-debian-amd64.deb" mv "$DEB" "dist/KST4Contest-${{ github.ref_name }}-debian-amd64.deb"
- name: Upload Debian artifact - name: Upload Debian artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: linux-debian name: linux-debian
path: dist/KST4Contest-${{ github.ref_name }}-debian-amd64.deb path: dist/KST4Contest-${{ github.ref_name }}-debian-amd64.deb
@@ -168,10 +168,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -195,7 +195,7 @@ jobs:
mv "$RPM" "dist/KST4Contest-${{ github.ref_name }}-fedora-x86_64.rpm" mv "$RPM" "dist/KST4Contest-${{ github.ref_name }}-fedora-x86_64.rpm"
- name: Upload Fedora artifact - name: Upload Fedora artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: linux-fedora name: linux-fedora
path: dist/KST4Contest-${{ github.ref_name }}-fedora-x86_64.rpm path: dist/KST4Contest-${{ github.ref_name }}-fedora-x86_64.rpm
@@ -206,10 +206,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -297,7 +297,7 @@ jobs:
tar --zstd --transform 's|^\./||' -cf "dist/KST4Contest-${{ github.ref_name }}-archlinux-${ARCH}.pkg.tar.zst" -C "$PKGROOT" . tar --zstd --transform 's|^\./||' -cf "dist/KST4Contest-${{ github.ref_name }}-archlinux-${ARCH}.pkg.tar.zst" -C "$PKGROOT" .
- name: Upload Arch Linux artifact - name: Upload Arch Linux artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: linux-arch name: linux-arch
path: dist/KST4Contest-${{ github.ref_name }}-archlinux-*.pkg.tar.zst path: dist/KST4Contest-${{ github.ref_name }}-archlinux-*.pkg.tar.zst
@@ -308,10 +308,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -416,13 +416,13 @@ jobs:
EOF EOF
- name: Upload flatpakref - name: Upload flatpakref
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: flatpakref name: flatpakref
path: dist/*.flatpakref path: dist/*.flatpakref
- name: Upload Flatpak OSTree repo - name: Upload Flatpak OSTree repo
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: flatpak-ostree-repo name: flatpak-ostree-repo
path: target/flatpak-repo/ path: target/flatpak-repo/
@@ -437,10 +437,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Set up Java 21 - name: Set up Java 21
uses: actions/setup-java@v4.1.0 uses: actions/setup-java@v6.0.1
with: with:
distribution: temurin distribution: temurin
java-version: "21" java-version: "21"
@@ -485,7 +485,7 @@ jobs:
mv "$DMG" "dist/KST4Contest-${{ 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@v6
with: with:
name: macos-dmg-${{ matrix.os }} name: macos-dmg-${{ matrix.os }}
path: dist/KST4Contest-${{ github.ref_name }}-macos-*.dmg path: dist/KST4Contest-${{ github.ref_name }}-macos-*.dmg
@@ -496,7 +496,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Install pandoc and LaTeX toolchain - name: Install pandoc and LaTeX toolchain
run: | run: |
@@ -562,7 +562,7 @@ jobs:
github_docs/de-Changelog.md github_docs/de-Changelog.md
- name: Upload PDF artifacts - name: Upload PDF artifacts
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: docs-pdf name: docs-pdf
path: dist/KST4Contest-${{ github.ref_name }}-manual-*.pdf path: dist/KST4Contest-${{ github.ref_name }}-manual-*.pdf
@@ -587,13 +587,13 @@ jobs:
echo "FLATPAK_GPG_KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr/{print $10; exit}')" >> "$GITHUB_ENV" echo "FLATPAK_GPG_KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr/{print $10; exit}')" >> "$GITHUB_ENV"
- name: Download OSTree repo artifact - name: Download OSTree repo artifact
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: flatpak-ostree-repo name: flatpak-ostree-repo
path: flatpak-ostree-repo/ path: flatpak-ostree-repo/
- name: Checkout existing flatpak-repo branch - name: Checkout existing flatpak-repo branch
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
with: with:
ref: flatpak-repo ref: flatpak-repo
path: existing-flatpak-repo path: existing-flatpak-repo
@@ -642,7 +642,7 @@ jobs:
steps: steps:
- name: Checkout release source - name: Checkout release source
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
with: with:
# Full history and tags, needed to list the commits since the previous release. # Full history and tags, needed to list the commits since the previous release.
fetch-depth: 0 fetch-depth: 0
@@ -729,50 +729,50 @@ jobs:
cat release-notes.md cat release-notes.md
- name: Download Windows artifact - name: Download Windows artifact
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: windows-zip name: windows-zip
path: release-assets/windows path: release-assets/windows
- name: Download Linux artifact - name: Download Linux artifact
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: linux-appimage name: linux-appimage
path: release-assets/linux path: release-assets/linux
- name: Download macOS artifacts - name: Download macOS artifacts
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
pattern: macos-dmg-* pattern: macos-dmg-*
merge-multiple: true merge-multiple: true
path: release-assets/macos path: release-assets/macos
- name: Download Debian artifact - name: Download Debian artifact
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: linux-debian name: linux-debian
path: release-assets/debian path: release-assets/debian
- name: Download Fedora artifact - name: Download Fedora artifact
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: linux-fedora name: linux-fedora
path: release-assets/fedora path: release-assets/fedora
- name: Download Arch Linux artifact - name: Download Arch Linux artifact
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: linux-arch name: linux-arch
path: release-assets/archlinux path: release-assets/archlinux
- name: Download flatpakref - name: Download flatpakref
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: flatpakref name: flatpakref
path: release-assets/flatpakref path: release-assets/flatpakref
- name: Download PDF manuals - name: Download PDF manuals
uses: actions/download-artifact@v4.1.3 uses: actions/download-artifact@v8.0.1
with: with:
name: docs-pdf name: docs-pdf
path: release-assets/docs path: release-assets/docs
@@ -835,7 +835,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload verified website artifact - name: Upload verified website artifact
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v6
with: with:
name: kst4contest-website-${{ github.ref_name }} name: kst4contest-website-${{ github.ref_name }}
path: website/_site/ path: website/_site/
@@ -10,7 +10,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v7
- name: Generate kst4ContestVersionInfo.xml from GitHub releases + history - name: Generate kst4ContestVersionInfo.xml from GitHub releases + history
env: env:
@@ -1,6 +1,7 @@
package kst4contest.view.compose package kst4contest.view.compose
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import kst4contest.model.AirPlane
import kst4contest.model.AirPlaneReflectionInfo import kst4contest.model.AirPlaneReflectionInfo
/** /**
@@ -34,6 +35,27 @@ object AirplaneCell {
*/ */
private const val NOTHING_COMING = "nil" private const val NOTHING_COMING = "nil"
// ---- the order ----------------------------------------------------------
/** Unknown or no aeroplane sorts after every real one. */
private const val NO_PLANE = Int.MAX_VALUE
/** The aeroplane the cell shows first; the parser already put the best one there. */
private fun first(info: AirPlaneReflectionInfo?): AirPlane? = info?.risingAirplanes?.firstOrNull()
/**
* Ascending: the minutes the operator reads first, then the better potential first.
*
* Issue #87: without a comparator the table fell back to the rendered text, which put
* "13 (75%)" above "3 (50%)". Only the first aeroplane counts, as it is the one the
* operator reads and the one the colour marks; the second is in the cell for context.
* Empty and "nil" cells carry [NO_PLANE]: like qrb and qtf, an absent value gets a
* sentinel instead of a null check, here the maximum so the cell sorts last ascending.
*/
val order: Comparator<AirPlaneReflectionInfo?> =
compareBy<AirPlaneReflectionInfo?> { first(it)?.arrivingDurationMinutes ?: NO_PLANE }
.thenByDescending { first(it)?.potential ?: 0 }
/** `.table-cell-100PercentAP`, identical in both stylesheets: a certain path. */ /** `.table-cell-100PercentAP`, identical in both stylesheets: a certain path. */
val PURPLE_100 = Color(0xFFF98AFF) val PURPLE_100 = Color(0xFFF98AFF)
@@ -57,6 +57,7 @@ object DirectedMessageColumns {
title = "AP [minutes / pot%]", title = "AP [minutes / pot%]",
value = { AirplaneCell.format(it.sender?.airPlaneReflectInfo) }, value = { AirplaneCell.format(it.sender?.airPlaneReflectInfo) },
accent = { _, rendered -> AirplaneCell.accentFor(rendered) }, accent = { _, rendered -> AirplaneCell.accentFor(rendered) },
comparator = compareBy(AirplaneCell.order) { it.sender?.airPlaneReflectInfo },
weight = 1.4f, weight = 1.4f,
), ),
DataColumn( DataColumn(
@@ -55,6 +55,7 @@ object StationColumns {
title = "AP [minutes / pot%]", title = "AP [minutes / pot%]",
value = { AirplaneCell.format(it.airPlaneReflectInfo) }, value = { AirplaneCell.format(it.airPlaneReflectInfo) },
accent = { _, rendered -> AirplaneCell.accentFor(rendered) }, accent = { _, rendered -> AirplaneCell.accentFor(rendered) },
comparator = compareBy(AirplaneCell.order) { it.airPlaneReflectInfo },
weight = 1.4f, weight = 1.4f,
)) ))
add(DataColumn(id = "tropo", title = "Tropo", value = tropoOf, weight = 0.7f)) add(DataColumn(id = "tropo", title = "Tropo", value = tropoOf, weight = 0.7f))
@@ -4,6 +4,7 @@ import kst4contest.model.AirPlane
import kst4contest.model.AirPlaneReflectionInfo import kst4contest.model.AirPlaneReflectionInfo
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertSame
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
/** /**
@@ -97,6 +98,70 @@ class AirplaneCellTest {
assertEquals(AirplaneCell.RED_75, AirplaneCell.accentFor("7 (50%) / 12 (75%)")?.foreground) assertEquals(AirplaneCell.RED_75, AirplaneCell.accentFor("7 (50%) / 12 (75%)")?.foreground)
} }
// ---- the order ----------------------------------------------------------
/**
* Issue #87: the column was sorted by its rendered text, so "13 (75%)" came before
* "3 (50%)". These are the thirteen cells of the screenshot in the issue, in the order
* they were shown; sorted, the minutes have to read as numbers.
*/
@Test
fun `the minutes sort as numbers, not as text`() {
val sorted = screenshotCells().sortedWith(AirplaneCell.order)
assertEquals(
listOf(1, 1, 3, 3, 6, 6, 8, 8, 9, 9, 13, 14, 19),
sorted.map { it.risingAirplanes.first().arrivingDurationMinutes },
)
}
/** Same minutes: the better potential first, which is also the one the colour marks. */
@Test
fun `equal minutes put the better potential first`() {
val sorted = screenshotCells().sortedWith(AirplaneCell.order)
assertEquals(
listOf("3 (75%)", "3 (50%)"),
sorted.map { AirplaneCell.format(it) }.filter { it.startsWith("3 ") },
"the screenshot had 3 (50%) above 3 (75%); the order is not just stable",
)
}
/**
* An empty cell (nothing computed) and "nil" (no aeroplane coming) both carry the
* maximum sentinel, so ascending puts them after the last real aeroplane.
*/
@Test
fun `no aeroplane sorts after the last real one`() {
val nothingComputed: AirPlaneReflectionInfo? = null
val nothingComing = reflectionInfo()
val cells = listOf(nothingComputed, nothingComing) + screenshotCells()
val sorted = cells.sortedWith(AirplaneCell.order)
assertEquals(19, sorted[sorted.size - 3]?.risingAirplanes?.first()?.arrivingDurationMinutes)
assertSame(nothingComputed, sorted[sorted.size - 2])
assertSame(nothingComing, sorted[sorted.size - 1])
}
/** Only the first aeroplane, the one the operator reads first, decides. */
@Test
fun `the second aeroplane of a cell does not influence the order`() {
val nineThenOne = reflectionInfo(plane(9, 75), plane(1, 100))
val threeThenTwenty = reflectionInfo(plane(3, 50), plane(20, 50))
val sorted = listOf(nineThenOne, threeThenTwenty).sortedWith(AirplaneCell.order)
assertSame(threeThenTwenty, sorted[0])
assertSame(nineThenOne, sorted[1])
}
/** The AP column of the issue's screenshot, top to bottom: (minutes, potential). */
private fun screenshotCells(): List<AirPlaneReflectionInfo> = listOf(
1 to 75, 1 to 75, 13 to 75, 14 to 50, 19 to 50, 3 to 50, 3 to 75,
6 to 75, 6 to 75, 8 to 75, 8 to 75, 9 to 75, 9 to 75,
).map { (minutes, potential) -> reflectionInfo(plane(minutes, potential)) }
private fun reflectionInfo(vararg planes: AirPlane): AirPlaneReflectionInfo = private fun reflectionInfo(vararg planes: AirPlane): AirPlaneReflectionInfo =
AirPlaneReflectionInfo().apply { risingAirplanes = planes.toMutableList() } AirPlaneReflectionInfo().apply { risingAirplanes = planes.toMutableList() }
@@ -6,6 +6,7 @@ import kst4contest.model.ChatCategory
import kst4contest.model.ChatMember import kst4contest.model.ChatMember
import kst4contest.model.ChatMessage import kst4contest.model.ChatMessage
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -98,6 +99,33 @@ class DirectedMessageColumnsTest {
assertEquals("7 (85%)", cell("airscout", message(sender = sender))) assertEquals("7 (85%)", cell("airscout", message(sender = sender)))
} }
/**
* Issue #87: the AP column had no comparator and fell back to its text, so "13 (75%)"
* sorted before "9 (75%)". Same shape as DataTableStateTest's "a column with a
* comparator sorts by it, not by its text", with the real columns.
*/
@Test
fun `airscout sorts numerically`() {
assertNotNull(columns.single { it.id == "airscout" }.comparator, "the AP column sorts by its own key")
val table = DataTableState(columns = columns, rowKey = RowKeys.byReference<ChatMessage>())
table.replaceRows(listOf(
message(sender = memberWithAeroplane("DL13ABC", minutes = 13)),
message(sender = memberWithAeroplane("DL9ABC", minutes = 9)),
))
table.toggleSort("airscout")
assertEquals(listOf("DL9ABC", "DL13ABC"), table.visibleRows.map { it.sender?.callSign },
"9 before 13 numerically; by text it would be the other way round")
}
private fun memberWithAeroplane(call: String, minutes: Int): ChatMember = member(call).apply {
airPlaneReflectInfo = AirPlaneReflectionInfo().apply {
risingAirplanes = mutableListOf(AirPlane().apply { arrivingDurationMinutes = minutes; potential = 75 })
}
}
@Test @Test
fun `the category names the chat the message arrived in`() { fun `the category names the chat the message arrived in`() {
val message = message(sender = member("DL0ABC")).apply { chatCategory = ChatCategory(2) } val message = message(sender = member("DL0ABC")).apply { chatCategory = ChatCategory(2) }
@@ -1,9 +1,12 @@
package kst4contest.view.compose package kst4contest.view.compose
import kst4contest.model.AirPlane
import kst4contest.model.AirPlaneReflectionInfo
import kst4contest.model.ChatMember import kst4contest.model.ChatMember
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.Assertions.assertDoesNotThrow
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
@@ -120,6 +123,32 @@ class StationColumnsTest {
assertEquals("", columns().first { it.id == "not-qrv" }.value(ChatMember())) assertEquals("", columns().first { it.id == "not-qrv" }.value(ChatMember()))
} }
/**
* Issue #87: the AP column had no comparator and fell back to its text, so "13 (75%)"
* sorted before "9 (75%)". Same shape as DataTableStateTest's "a column with a
* comparator sorts by it, not by its text", with the real columns.
*/
@Test
fun `airscout sorts numerically`() {
val columns = columns()
assertNotNull(columns.first { it.id == "airscout" }.comparator, "the AP column sorts by its own key")
val table = DataTableState(columns = columns, rowKey = RowKeys.byValue<ChatMember> { it.callSign.orEmpty() })
table.replaceRows(listOf(stationWithAeroplane("DL13ABC", minutes = 13), stationWithAeroplane("DL9ABC", minutes = 9)))
table.toggleSort("airscout")
assertEquals(listOf("DL9ABC", "DL13ABC"), table.visibleRows.map { it.callSign },
"9 before 13 numerically; by text it would be the other way round")
}
private fun stationWithAeroplane(call: String, minutes: Int): ChatMember = ChatMember().apply {
setCallSign(call)
airPlaneReflectInfo = AirPlaneReflectionInfo().apply {
risingAirplanes = mutableListOf(AirPlane().apply { arrivingDurationMinutes = minutes; potential = 75 })
}
}
@Test @Test
fun `tropo and score come from outside the station`() { fun `tropo and score come from outside the station`() {
val columns = StationColumns.all( val columns = StationColumns.all(
@@ -1310,7 +1310,8 @@ public class MessageBusManagementThread extends Thread {
? null ? null
: sender.getFrequency().get(); : sender.getFrequency().get();
if (detectedFrequency != null && !detectedFrequency.isBlank()) { if (detectedFrequency != null && !detectedFrequency.isBlank()
&& !isAlreadyWorkedForDxClusterSpot(sender)) {
/* /*
* The DX Cluster spot must not depend on AirScout data. A known * The DX Cluster spot must not depend on AirScout data. A known
* frequency and a valid directional opportunity are sufficient. * frequency and a valid directional opportunity are sufficient.
@@ -1329,6 +1330,10 @@ public class MessageBusManagementThread extends Thread {
onlyForSpottingObject onlyForSpottingObject
); );
} else if (detectedFrequency != null && !detectedFrequency.isBlank()) {
// Only reached when the worked check was the reason (issue #91).
LOGGER.fine("DX Cluster spot suppressed, station already worked on this band: "
+ sender.getCallSign());
} }
} catch (Exception exception) { } catch (Exception exception) {
LOGGER.log(Level.WARNING, LOGGER.log(Level.WARNING,
@@ -1852,6 +1857,51 @@ public class MessageBusManagementThread extends Thread {
} }
/**
* Decides whether an automatic DX Cluster spot must be suppressed because
* the sender has already been worked on the band of its known frequency.
*
* <p>Rules (issue #91):</p>
* <ul>
* <li>No sender, no frequency or a frequency outside the supported
* bands: the existing behaviour is kept and the spot is allowed.</li>
* <li>The per-band worked flag of the detected band is set: suppress.</li>
* <li>Only the global worked flag is set and no per-band flag exists
* (Simplelogfile source without band information): suppress, because
* the band cannot be told apart.</li>
* <li>The global flag is set together with per-band flags for other
* bands: allow, because the per-band information is authoritative.</li>
* </ul>
*
* <p>Manual spots from the map window and the settings test spot do not
* use this check.</p>
*
* @param sender station that triggered the directional opportunity
* @return {@code true} when the automatic spot has to be suppressed
*/
static boolean isAlreadyWorkedForDxClusterSpot(ChatMember sender) {
if (sender == null || sender.getFrequency() == null) {
return false;
}
FrequencyTextParser.DetectedFrequency detectedFrequency =
FrequencyTextParser.parseExplicitFrequency(
sender.getFrequency().get()
);
if (detectedFrequency == null) {
// Unknown band: keep the existing behaviour and allow the spot.
return false;
}
if (sender.isWorkedOnBand(detectedFrequency.getBand())) {
return true;
}
// Worked source without band information (Simplelogfile): global flag only.
return sender.isWorked() && !sender.hasAnyBandWorkedFlag();
}
/** /**
* Builds the comment transmitted with a local DX Cluster spot. * Builds the comment transmitted with a local DX Cluster spot.
* *
@@ -529,6 +529,76 @@ public class ChatMember {
return worked; return worked;
} }
/**
* Returns the per-band worked flag that belongs to the given band.
*
* <p>The flag names historically differ from the {@link Band} names:
* {@code B_1296} maps to {@link #isWorked1240()}, {@code B_2320} to
* {@link #isWorked2300()} and {@code B_5760} to {@link #isWorked5600()}.
* This is the same mapping that the worked gross-field cache and the
* external-log band mapping use. The global {@link #isWorked()} flag is
* deliberately not consulted here.</p>
*
* @param band band to check, may be {@code null}
* @return {@code true} when the station is marked worked on that band;
* {@code false} for {@code null} or a band without a flag
*/
public boolean isWorkedOnBand(Band band) {
if (band == null) {
return false;
}
switch (band) {
case B_50:
return isWorked50();
case B_70:
return isWorked70();
case B_144:
return isWorked144();
case B_432:
return isWorked432();
case B_1296:
return isWorked1240();
case B_2320:
return isWorked2300();
case B_3400:
return isWorked3400();
case B_5760:
return isWorked5600();
case B_10G:
return isWorked10G();
case B_24G:
return isWorked24G();
default:
return false;
}
}
/**
* Tells whether any per-band worked flag is set.
*
* <p>Sources without band information (for example the Simplelogfile
* interpreter) only set the global {@link #isWorked()} flag. A caller can
* use this method to distinguish such a station from one whose per-band
* state is known. The global flag itself is not part of the result.</p>
*
* @return {@code true} when at least one per-band flag is set
*/
public boolean hasAnyBandWorkedFlag() {
return isWorked50()
|| isWorked70()
|| isWorked144()
|| isWorked432()
|| isWorked1240()
|| isWorked2300()
|| isWorked3400()
|| isWorked5600()
|| isWorked10G()
|| isWorked24G()
|| isWorked47G()
|| isWorked76G();
}
public boolean isWorked50() { public boolean isWorked50() {
return Worked50; return Worked50;
} }
@@ -0,0 +1,115 @@
package kst4contest.controller;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import kst4contest.model.ChatMember;
import kst4contest.observe.SimpleValue;
import org.junit.jupiter.api.Test;
/**
* Covers the worked-state check that suppresses automatic DX Cluster spots
* (issue #91). Manual spots do not use this predicate.
*/
class MessageBusManagementThreadDxClusterWorkedTest {
@Test
void suppressesSpotWhenWorkedOnDetectedBand() {
ChatMember sender = senderOn("144.3");
sender.setWorked144(true);
assertTrue(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void allowsSpotWhenWorkedOnlyOnAnotherBand() {
ChatMember sender = senderOn("432.2");
sender.setWorked144(true);
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void mapsBand1296ToWorked1240Flag() {
ChatMember sender = senderOn("1296.2");
sender.setWorked1240(true);
assertTrue(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void allowsSpotWhenNothingIsWorked() {
ChatMember sender = senderOn("144.3");
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void globalWorkedFlagWithoutBandInformationSuppressesSpot() {
// Simplelogfile source: only the global flag is known, no per-band flags.
ChatMember sender = senderOn("144.3");
sender.setWorked(true);
assertTrue(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void globalWorkedFlagWithOtherBandInformationAllowsSpot() {
// Per-band information exists and says 432 only, so 144 is still open.
ChatMember sender = senderOn("144.3");
sender.setWorked(true);
sender.setWorked432(true);
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void allowsSpotWhenFrequencyWrapperIsNull() {
ChatMember sender = new ChatMember();
sender.setFrequency(null);
sender.setWorked(true);
sender.setWorked144(true);
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void allowsSpotWhenFrequencyValueIsNull() {
ChatMember sender = new ChatMember();
sender.setFrequency(new SimpleValue<>());
sender.setWorked(true);
sender.setWorked144(true);
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void allowsSpotWhenFrequencyIsEmpty() {
ChatMember sender = senderOn("");
sender.setWorked(true);
sender.setWorked144(true);
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void allowsSpotWhenFrequencyIsOutsideKnownBands() {
ChatMember sender = senderOn("999.9");
sender.setWorked(true);
sender.setWorked144(true);
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(sender));
}
@Test
void allowsSpotWhenSenderIsNull() {
assertFalse(MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot(null));
}
private static ChatMember senderOn(String frequencyMHz) {
ChatMember sender = new ChatMember();
sender.setCallSign("DL1ABC");
sender.setFrequency(new SimpleValue<>(frequencyMHz));
return sender;
}
}
@@ -0,0 +1,174 @@
package kst4contest.model;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class ChatMemberWorkedOnBandTest {
@ParameterizedTest
@EnumSource(Band.class)
void noFlagSetMeansNotWorkedOnAnyBand(Band band) {
ChatMember member = new ChatMember();
assertFalse(member.isWorkedOnBand(band));
}
@Test
void nullBandIsNeverWorked() {
ChatMember member = new ChatMember();
member.setWorked144(true);
assertFalse(member.isWorkedOnBand(null));
}
@Test
void globalWorkedFlagDoesNotCountAsBandFlag() {
ChatMember member = new ChatMember();
member.setWorked(true);
for (Band band : Band.values()) {
assertFalse(member.isWorkedOnBand(band), band.name());
}
}
@Test
void worked50MapsToBand50() {
ChatMember member = new ChatMember();
member.setWorked50(true);
assertOnlyBandWorked(member, Band.B_50);
}
@Test
void worked70MapsToBand70() {
ChatMember member = new ChatMember();
member.setWorked70(true);
assertOnlyBandWorked(member, Band.B_70);
}
@Test
void worked144MapsToBand144() {
ChatMember member = new ChatMember();
member.setWorked144(true);
assertOnlyBandWorked(member, Band.B_144);
}
@Test
void worked432MapsToBand432() {
ChatMember member = new ChatMember();
member.setWorked432(true);
assertOnlyBandWorked(member, Band.B_432);
}
@Test
void worked1240MapsToBand1296() {
ChatMember member = new ChatMember();
member.setWorked1240(true);
assertOnlyBandWorked(member, Band.B_1296);
}
@Test
void worked2300MapsToBand2320() {
ChatMember member = new ChatMember();
member.setWorked2300(true);
assertOnlyBandWorked(member, Band.B_2320);
}
@Test
void worked3400MapsToBand3400() {
ChatMember member = new ChatMember();
member.setWorked3400(true);
assertOnlyBandWorked(member, Band.B_3400);
}
@Test
void worked5600MapsToBand5760() {
ChatMember member = new ChatMember();
member.setWorked5600(true);
assertOnlyBandWorked(member, Band.B_5760);
}
@Test
void worked10GMapsToBand10G() {
ChatMember member = new ChatMember();
member.setWorked10G(true);
assertOnlyBandWorked(member, Band.B_10G);
}
@Test
void worked24GMapsToBand24G() {
ChatMember member = new ChatMember();
member.setWorked24G(true);
assertOnlyBandWorked(member, Band.B_24G);
}
@Test
void hasAnyBandWorkedFlagIsFalseWithoutFlags() {
ChatMember member = new ChatMember();
assertFalse(member.hasAnyBandWorkedFlag());
}
@Test
void hasAnyBandWorkedFlagIgnoresGlobalWorkedFlag() {
ChatMember member = new ChatMember();
member.setWorked(true);
assertFalse(member.hasAnyBandWorkedFlag());
}
@Test
void hasAnyBandWorkedFlagDetectsEachBandFlag() {
assertTrue(memberWith(m -> m.setWorked50(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked70(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked144(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked432(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked1240(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked2300(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked3400(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked5600(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked10G(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked24G(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked47G(true)).hasAnyBandWorkedFlag());
assertTrue(memberWith(m -> m.setWorked76G(true)).hasAnyBandWorkedFlag());
}
@Test
void hasAnyBandWorkedFlagIsFalseAfterReset() {
ChatMember member = new ChatMember();
member.setWorked144(true);
member.setWorked76G(true);
member.resetWorkedInformationAtAllBands();
assertFalse(member.hasAnyBandWorkedFlag());
}
private static void assertOnlyBandWorked(ChatMember member, Band expected) {
for (Band band : Band.values()) {
if (band == expected) {
assertTrue(member.isWorkedOnBand(band), band.name());
} else {
assertFalse(member.isWorkedOnBand(band), band.name());
}
}
}
private static ChatMember memberWith(java.util.function.Consumer<ChatMember> setup) {
ChatMember member = new ChatMember();
setup.accept(member);
return member;
}
}
+1
View File
@@ -113,6 +113,7 @@ CR/LF framing, XML framing, ports/transports, callsign normalization and frequen
- Comments are padded or truncated to exactly 30 characters. Automatic AirScout comments retain the locator first and use the compact form `JO51HK AP 1m/100%;4m/75%`. - Comments are padded or truncated to exactly 30 characters. Automatic AirScout comments retain the locator first and use the compact form `JO51HK AP 1m/100%;4m/75%`.
- A DX callsign longer than 12 characters is rejected and logged rather than truncated. - A DX callsign longer than 12 characters is rejected and logged rather than truncated.
- Trigger conditions, QRG recognition and normalisation, login, keepalive, multi-client delivery and the local-only trust boundary remain separate from line formatting. - Trigger conditions, QRG recognition and normalisation, login, keepalive, multi-client delivery and the local-only trust boundary remain separate from line formatting.
- Automatic spots are suppressed for stations already worked on the band of the sender's known frequency (`MessageBusManagementThread.isAlreadyWorkedForDxClusterSpot`); when only the global Worked flag exists without any per-band flag (Simplelogfile source), the global flag counts, an unknown band keeps the previous behaviour, and manual spots from the map window and the settings test spot are not affected (#91).
### Logging and Worked-state persistence ### Logging and Worked-state persistence
+3
View File
@@ -40,6 +40,9 @@ Mehrere Operateure an einem Rechner können jetzt eigene Rufzeichen, Locators un
- **Freigegebene Hintergrundressourcen:** Der ON4KST-Überwachungsthread, der Sked-Erinnerungs-Scheduler, der Reachability-Executor, der PSTRotator-Wiederholungs-Scheduler und der Kachel-Proxy der Karte werden beim Schließen des Chatcontrollers freigegeben. Bisher liefen sie bis zum Programmende weiter. - **Freigegebene Hintergrundressourcen:** Der ON4KST-Überwachungsthread, der Sked-Erinnerungs-Scheduler, der Reachability-Executor, der PSTRotator-Wiederholungs-Scheduler und der Kachel-Proxy der Karte werden beim Schließen des Chatcontrollers freigegeben. Bisher liefen sie bis zum Programmende weiter.
- **AP-Spalte sortiert numerisch:** Die Spalte **AP [minutes / pot%]** in der Benutzerliste und bei den gerichteten Nachrichten wurde nach ihrem Text sortiert, sodass `13 (75%)` vor `3 (50%)` stand. Jetzt sortiert sie nach den Minuten des ersten Flugzeugs und bei gleichen Minuten nach dem besseren Potenzial; leere und `nil`-Zellen stehen aufsteigend am Ende. Damit ist [Issue #87](https://github.com/praktimarc/kst4contest/issues/87) behoben.
- **Keine automatischen Cluster-Spots für gearbeitete Stationen:** Der lokale DX-Cluster-Server erzeugte Spots aus Richtungsgelegenheiten, ohne den Worked-Status zu prüfen. Jetzt wird kein Spot mehr erzeugt, wenn die Station auf dem Band der erkannten Frequenz bereits gearbeitet ist; ohne bandbezogene Information zählt der allgemeine Worked-Status. Manuelle Spots aus der Karte und der Test-Spot in den Einstellungen bleiben unverändert. Damit ist [Issue #91](https://github.com/praktimarc/kst4contest/issues/91) behoben.
### Hinweise zur Aktualisierung ### Hinweise zur Aktualisierung
- Bestehende Installationen werden **nicht** verändert: `preferences.xml` und `praktiKST.db` bleiben genau dort liegen, wo sie sind, und werden zum Profil **Default**. Es wird keine Datei verschoben oder kopiert. - Bestehende Installationen werden **nicht** verändert: `preferences.xml` und `praktiKST.db` bleiben genau dort liegen, wo sie sind, und werden zum Profil **Default**. Es wird keine Datei verschoben oder kopiert.
+2 -1
View File
@@ -43,7 +43,8 @@ Ein DX-Cluster-Spot wird nur erzeugt, wenn alle folgenden Bedingungen erfüllt s
3. Der Absender liegt innerhalb des konfigurierten maximalen QRB zur eigenen Station. 3. Der Absender liegt innerhalb des konfigurierten maximalen QRB zur eigenen Station.
4. Die eigene Station liegt aus Sicht des Absenders innerhalb des angenommenen Antennenkorridors. 4. Die eigene Station liegt aus Sicht des Absenders innerhalb des angenommenen Antennenkorridors.
5. Für den Absender ist eine verwertbare Frequenz bekannt oder in der aktuellen Nachricht erkannt worden. 5. Für den Absender ist eine verwertbare Frequenz bekannt oder in der aktuellen Nachricht erkannt worden.
6. Der lokale DX-Cluster-Server ist aktiviert. 6. Der Absender ist auf dem Band der erkannten Frequenz noch nicht gearbeitet. Liegt kein bandbezogener Worked-Status vor, etwa aus dem Simplelogfile, zählt der allgemeine Worked-Status.
7. Der lokale DX-Cluster-Server ist aktiviert.
Treffen die Bedingungen zu, wird der Spot unmittelbar beim Verarbeiten der Nachricht erzeugt. Die parallel angezeigte grüne Richtungsmarkierung bleibt dagegen fünf Minuten sichtbar und kann durch spätere Nachrichten verlängert oder vorzeitig entfernt werden. Treffen die Bedingungen zu, wird der Spot unmittelbar beim Verarbeiten der Nachricht erzeugt. Die parallel angezeigte grüne Richtungsmarkierung bleibt dagegen fünf Minuten sichtbar und kann durch spätere Nachrichten verlängert oder vorzeitig entfernt werden.
+1 -1
View File
@@ -64,7 +64,7 @@ Konfiguration:
## Weitergabe als DX-Cluster-Spot ## Weitergabe als DX-Cluster-Spot
Seit Version 1.23 kann KST4Contest eine erkannte Richtungsgelegenheit an den DX-Cluster-Client eines Logprogramms weitergeben. Dafür muss der lokale DX-Cluster-Server aktiviert und für den Absender eine verwertbare Frequenz bekannt sein. Seit Version 1.23 kann KST4Contest eine erkannte Richtungsgelegenheit an den DX-Cluster-Client eines Logprogramms weitergeben. Dafür muss der lokale DX-Cluster-Server aktiviert und für den Absender eine verwertbare Frequenz bekannt sein. Stationen, die auf dem Band dieser Frequenz bereits gearbeitet sind, werden nicht automatisch gespottet.
Die Frequenz kann bereits aus einer früheren Nachricht stammen oder erstmals in der aktuell auslösenden Nachricht stehen. In beiden Fällen steht sie der Spot-Prüfung zur Verfügung. KST4Contest überträgt damit nicht jede im Chat gefundene QRG, sondern nur Frequenzen, die mit einer geometrisch passenden gerichteten Nachricht zusammenfallen. Die Frequenz kann bereits aus einer früheren Nachricht stammen oder erstmals in der aktuell auslösenden Nachricht stehen. In beiden Fällen steht sie der Spot-Prüfung zur Verfügung. KST4Contest überträgt damit nicht jede im Chat gefundene QRG, sondern nur Frequenzen, die mit einer geometrisch passenden gerichteten Nachricht zusammenfallen.
+3
View File
@@ -40,6 +40,9 @@ Several operators sharing one computer can now use their own callsigns, locators
- **Background resources are released:** the ON4KST supervisor thread, the sked reminder scheduler, the reachability executor, the PSTRotator retry scheduler and the map tile proxy are released when the chat controller is closed. They used to keep running until the program ended. - **Background resources are released:** the ON4KST supervisor thread, the sked reminder scheduler, the reachability executor, the PSTRotator retry scheduler and the map tile proxy are released when the chat controller is closed. They used to keep running until the program ended.
- **AP column sorts numerically:** the **AP [minutes / pot%]** column in the user list and in the directed messages was sorted by its text, which put `13 (75%)` above `3 (50%)`. It now sorts by the minutes of the first aeroplane and, for equal minutes, by the better potential first; empty and `nil` cells go to the bottom in ascending order. This fixes [Issue #87](https://github.com/praktimarc/kst4contest/issues/87).
- **No automatic cluster spots for worked stations:** the local DX Cluster server generated spots from directional opportunities without checking the worked state. A spot is no longer created when the station has already been worked on the band of the detected frequency; without per-band information the global worked state counts. Manual spots from the map and the test spot in the settings are unchanged. This fixes [Issue #91](https://github.com/praktimarc/kst4contest/issues/91).
### Upgrade notes ### Upgrade notes
- Existing installations are **not** modified: `preferences.xml` and `praktiKST.db` stay exactly where they are and become the **Default** profile. No file is moved or copied. - Existing installations are **not** modified: `preferences.xml` and `praktiKST.db` stay exactly where they are and become the **Default** profile. No file is moved or copied.
+2 -1
View File
@@ -43,7 +43,8 @@ An automatic DX Cluster spot is created only when all of the following condition
3. The sender is within the configured **Default Maximum QRB** from the local station. 3. The sender is within the configured **Default Maximum QRB** from the local station.
4. The local station lies inside the assumed antenna corridor as seen from the sender. 4. The local station lies inside the assumed antenna corridor as seen from the sender.
5. A usable frequency is known for the sender or detected in the current message. 5. A usable frequency is known for the sender or detected in the current message.
6. The local DX Cluster server is enabled. 6. The sender has not yet been worked on the band of the detected frequency. When no per-band worked state exists, for example from the Simplelogfile, the global worked state counts.
7. The local DX Cluster server is enabled.
When these conditions are met, the spot is created while the message is processed. The green directional highlight shown in parallel remains visible for five minutes and may be extended or removed by later messages. When these conditions are met, the spot is created while the message is processed. The green directional highlight shown in parallel remains visible for five minutes and may be extended or removed by later messages.
+1 -1
View File
@@ -64,7 +64,7 @@ Configuration:
## Forwarding as a DX Cluster Spot ## Forwarding as a DX Cluster Spot
Since version 1.23, KST4Contest can forward a detected directional opportunity to the DX Cluster client of a logging programme. The local DX Cluster server must be enabled and a usable frequency must be known for the sender. Since version 1.23, KST4Contest can forward a detected directional opportunity to the DX Cluster client of a logging programme. The local DX Cluster server must be enabled and a usable frequency must be known for the sender. Stations already worked on the band of that frequency are not spotted automatically.
The frequency may come from an earlier message or may be detected for the first time in the message which triggers the opportunity. In both cases it is available when KST4Contest checks whether it can create a spot. The programme therefore does not forward every QRG found in the chat. Automatic spots require a geometrically matching directed message. The frequency may come from an earlier message or may be detected for the first time in the message which triggers the opportunity. In both cases it is available when KST4Contest checks whether it can create a spot. The programme therefore does not forward every QRG found in the chat. Automatic spots require a geometrically matching directed message.
+6 -6
View File
@@ -1,13 +1,13 @@
[versions] [versions]
sqliteJdbc = "3.43.2.2" sqliteJdbc = "3.53.4.0"
jlayer = "1.0.1" jlayer = "1.0.1"
junit = "5.10.1" junit = "6.1.3"
mockito = "5.7.0" mockito = "5.24.0"
mockitoCompile = "5.17.0" mockitoCompile = "5.24.0"
jetbrainsAnnotations = "24.0.1" jetbrainsAnnotations = "26.1.0"
kotlin = "2.2.0" kotlin = "2.2.0"
composeMultiplatform = "1.8.2" composeMultiplatform = "1.8.2"
spotbugsPlugin = "6.0.26" spotbugsPlugin = "6.5.12"
spotbugs = "4.9.8" spotbugs = "4.9.8"
pmd = "7.17.0" pmd = "7.17.0"
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0 retries=0
retryBackOffMs=500 retryBackOffMs=500
Vendored
+1 -1
View File
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/<unknown>/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+41 -11
View File
@@ -26,6 +26,33 @@
@rem Set local scope for the variables, and ensure extensions are enabled @rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions setlocal EnableExtensions
@rem Catch executions from older scripts and ensure they exit cleanly.
@rem This can be removed once we can be reasonably confident that few people
@rem will be migrating directly to this new wrapper.
goto afterSafetyNet
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
goto exitWithErrorLevel
:afterSafetyNet
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused @rem This is normally unused
@@ -45,13 +72,14 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2 1>&2 echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. 1>&2 1>&2 echo.
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 1>&2 echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. 1>&2 1>&2 echo location of your Java installation.
"%COMSPEC%" /c exit 1 "%COMSPEC%" /c exit 1
goto exitWithErrorLevel
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -59,13 +87,14 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. 1>&2 1>&2 echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. 1>&2 1>&2 echo.
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 1>&2 echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. 1>&2 1>&2 echo location of your Java installation.
"%COMSPEC%" /c exit 1 "%COMSPEC%" /c exit 1
goto exitWithErrorLevel
:execute :execute
@rem Setup the command line @rem Setup the command line
@@ -75,8 +104,9 @@ echo location of your Java installation. 1>&2
@rem Execute gradlew @rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command @rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
:exitWithErrorLevel :exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL% "%COMSPEC%" /c exit %ERRORLEVEL%