mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-19 22:02:57 +02:00
Restore the turn-antenna button and send the PSTRotator azimuth with one decimal (#126)
* Send the PSTRotator azimuth with one decimal instead of truncating it
setAzimuth cast the azimuth to int, so 145.7 degrees turned the antenna
to 145. It is now rounded to one decimal and formatted with Locale.ROOT,
so a German default locale cannot turn the dot into a comma; a value that
rounds up to 360.0 is sent as 0.0. The loopback test pinned the old
truncation and now expects the decimal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Bring back the turn-antenna button lost in the Compose migration
The JavaFX station panel had "Turn ant1 to <call>", the only caller of
ChatController.rotateTo; removing JavaFX (fe4215bc) dropped it, so the
antenna could no longer be turned from KST4Contest at all. The Compose
panel gets the button again, enabled when a rotator callback is wired and
the selected station has a finite heading.
Review follow-ups for the decimal azimuth: formatAzimuth refuses NaN and
infinite values and wraps any finite value into [0, 360). The "reached"
check before the SPID retry uses a 1.0 degree tolerance, because the
target now carries a decimal while many controllers report whole degrees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep rotateTo's Javadoc on the method and word the German label as a bearing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e1e6fe7297
commit
2898c530d9
12 files changed
+150
-5
No files matched your search
@@ -253,6 +253,7 @@ selected.lookUpAndShow=Nachschlagen und zeigen
|
||||
selected.lookupQrz=Auf qrz.com nachschlagen
|
||||
selected.lookupQrzcq=Auf qrzcq.com nachschlagen
|
||||
selected.showPathInAs=Strecke in AS zeigen
|
||||
selected.turnAntenna=Antenne 1 Richtung {0} drehen
|
||||
selected.lastActivity=Letzte Aktivität: {0} (vor {1} Min.)
|
||||
candidates.title=Stationen mit höchster Priorität
|
||||
candidates.none=Noch keine Kandidaten.
|
||||
|
||||
@@ -262,6 +262,7 @@ selected.lookUpAndShow=Look up and show
|
||||
selected.lookupQrz=Lookup on qrz.com
|
||||
selected.lookupQrzcq=Lookup on qrzcq.com
|
||||
selected.showPathInAs=Show path in AS
|
||||
selected.turnAntenna=Turn ant1 to {0}
|
||||
selected.lastActivity=Last activity: {0} ({1} min ago)
|
||||
candidates.title=Top priority candidates
|
||||
candidates.none=No candidates yet.
|
||||
|
||||
@@ -3135,6 +3135,10 @@ public class Kst4ContestApplication implements StatusUpdateListener, SettingsHos
|
||||
if (member == null) return null;
|
||||
java.util.EnumSet<Band> enabledBands = kst4contest.logic.BandOpportunityResolver.getEnabledStationBands(chatcontroller.getChatPreferences());
|
||||
return resolveDefaultSkedBand(member, enabledBands);
|
||||
},
|
||||
azimuth -> {
|
||||
chatcontroller.rotateTo(azimuth);
|
||||
return kotlin.Unit.INSTANCE;
|
||||
});
|
||||
|
||||
if (chatcontroller.getChatPreferences().isGuiOptions_defaultFilterPmToMe()) {
|
||||
|
||||
@@ -184,6 +184,10 @@ fun SelectedStationPanel(
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(Density.BUTTON_GAP)) {
|
||||
Form.button(strings.selectedShowPathInAs, onClick = onShowPathInAirScout)
|
||||
Form.button("🗺 Show on map", onClick = onShowOnMap)
|
||||
Form.button(
|
||||
strings.selectedTurnAntenna(state.selected?.callSignRaw.orEmpty()),
|
||||
enabled = state.canTurnAntenna,
|
||||
) { state.turnAntenna() }
|
||||
Form.button(strings.selectedLookupQrz, enabled = state.canLookUp) {
|
||||
openInBrowser.accept(state.qrzComUrl())
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ enum class SelectedMessageFilter(val label: String) {
|
||||
* `propagateNotQrvStateToActiveMembers` does
|
||||
* @param createSked takes the station, the minutes, the band and the mode; returns
|
||||
* whether it was accepted
|
||||
* @param turnAntennaTo turns the antenna to an azimuth in degrees, which is what
|
||||
* `ChatController.rotateTo` does; null where no rotator can be driven
|
||||
*/
|
||||
class SelectedStationState(
|
||||
private val propagateNotQrv: (ChatMember) -> Unit,
|
||||
@@ -63,6 +65,7 @@ class SelectedStationState(
|
||||
private val onMessageFilterChange: (SelectedMessageFilter?) -> Unit = { },
|
||||
private val detectBands: (ChatMember) -> String = { "" },
|
||||
private val resolveDefaultSkedBand: (ChatMember) -> Band? = { null },
|
||||
private val turnAntennaTo: ((Double) -> Unit)? = null,
|
||||
) {
|
||||
|
||||
var selected: ChatMember? by mutableStateOf(null)
|
||||
@@ -103,6 +106,16 @@ class SelectedStationState(
|
||||
val canLookUp: Boolean
|
||||
get() = !selected?.callSign.isNullOrBlank()
|
||||
|
||||
/** Needs a rotator to drive and a finite heading to the selected station. */
|
||||
val canTurnAntenna: Boolean
|
||||
get() = turnAntennaTo != null && selected?.getQTFdirection()?.isFinite() == true
|
||||
|
||||
/** Turns the antenna to the selected station's heading; does nothing when it cannot. */
|
||||
fun turnAntenna() {
|
||||
val heading = selected?.getQTFdirection() ?: return
|
||||
if (heading.isFinite()) turnAntennaTo?.invoke(heading)
|
||||
}
|
||||
|
||||
fun select(member: ChatMember?) {
|
||||
if (selected?.callSignRaw != member?.callSignRaw) {
|
||||
skedBand = member?.let { resolveDefaultSkedBand(it) }
|
||||
|
||||
@@ -39,6 +39,42 @@ class SelectedStationStateTest {
|
||||
|
||||
private fun member(call: String) = ChatMember().apply { callSign = call }
|
||||
|
||||
@Test
|
||||
fun turnAntennaSendsTheSelectedStationsHeading() {
|
||||
val headings = mutableListOf<Double>()
|
||||
val state = SelectedStationState(
|
||||
propagateNotQrv = { },
|
||||
createSked = { _, _, _, _ -> true },
|
||||
turnAntennaTo = { headings.add(it) },
|
||||
)
|
||||
val station = member("DL1ABC")
|
||||
station.setQTFdirection(145.7)
|
||||
state.select(station)
|
||||
|
||||
assertTrue(state.canTurnAntenna)
|
||||
state.turnAntenna()
|
||||
|
||||
assertEquals(listOf(145.7), headings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun turnAntennaIsUnavailableWithoutARotatorOrAHeading() {
|
||||
val withoutRotator = SelectedStationState(propagateNotQrv = { }, createSked = { _, _, _, _ -> true })
|
||||
withoutRotator.select(member("DL1ABC").apply { setQTFdirection(145.7) })
|
||||
assertFalse(withoutRotator.canTurnAntenna)
|
||||
|
||||
val headings = mutableListOf<Double>()
|
||||
val withoutHeading = SelectedStationState(
|
||||
propagateNotQrv = { },
|
||||
createSked = { _, _, _, _ -> true },
|
||||
turnAntennaTo = { headings.add(it) },
|
||||
)
|
||||
withoutHeading.select(member("DL1ABC"))
|
||||
assertFalse(withoutHeading.canTurnAntenna)
|
||||
withoutHeading.turnAntenna()
|
||||
assertTrue(headings.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `without a selection there is nothing to act on`() {
|
||||
val state = Session().state()
|
||||
|
||||
Reference in new issue
Block a user