mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-19 22:02:57 +02:00
The cluster and QSO monitor split its two tables at half the height, fixed: the Compose port had left the JavaFX SplitPane out until a splitter existed. SplitterPane exists now, so the monitor uses it and reads and writes the same profile value the SplitPane did (GUIpnl_directedMSGWin_dividerpositionDefault, default 0.8), so a profile gets its old position back. Each heading now sits in the pane of its table, and the "DX cluster" heading is translated (monitor.dxCluster). Fixes #144. Claude-Session: https://claude.ai/code/session_01AaorTymXfkXPm8yb4Btva7 Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
d38b385166
commit
4642d658a3
5 files changed
+76
-28
No files matched your search
@@ -426,6 +426,7 @@ column.lastQrg=Letzte QRG
|
|||||||
column.airplane=AP [Minuten / Pot%]
|
column.airplane=AP [Minuten / Pot%]
|
||||||
column.score=Wertung
|
column.score=Wertung
|
||||||
monitor.title=Cluster & Fremd-QSOs
|
monitor.title=Cluster & Fremd-QSOs
|
||||||
|
monitor.dxCluster=DX-Cluster
|
||||||
profiles.createFailed=Das Profil konnte nicht angelegt werden. Die Profilliste konnte nicht geschrieben werden.
|
profiles.createFailed=Das Profil konnte nicht angelegt werden. Die Profilliste konnte nicht geschrieben werden.
|
||||||
profiles.createdWithoutCredentials=Das Profil „{0}" wurde ohne Rufzeichen und Passwort angelegt. Nach dem Wechsel zu diesem Profil beides im Reiter „Station" eintragen.
|
profiles.createdWithoutCredentials=Das Profil „{0}" wurde ohne Rufzeichen und Passwort angelegt. Nach dem Wechsel zu diesem Profil beides im Reiter „Station" eintragen.
|
||||||
profiles.duplicateFailed=Das Profil konnte nicht dupliziert werden.
|
profiles.duplicateFailed=Das Profil konnte nicht dupliziert werden.
|
||||||
|
|||||||
@@ -435,6 +435,7 @@ column.lastQrg=Last QRG
|
|||||||
column.airplane=AP [minutes / pot%]
|
column.airplane=AP [minutes / pot%]
|
||||||
column.score=Score
|
column.score=Score
|
||||||
monitor.title=Cluster & QSO of the other
|
monitor.title=Cluster & QSO of the other
|
||||||
|
monitor.dxCluster=DX cluster
|
||||||
profiles.createFailed=The profile could not be created. The profile registry could not be written.
|
profiles.createFailed=The profile could not be created. The profile registry could not be written.
|
||||||
profiles.createdWithoutCredentials=The profile "{0}" was created without callsign and password. Enter them on the Station tab after switching to it.
|
profiles.createdWithoutCredentials=The profile "{0}" was created without callsign and password. Enter them on the Station tab after switching to it.
|
||||||
profiles.duplicateFailed=The profile could not be duplicated.
|
profiles.duplicateFailed=The profile could not be duplicated.
|
||||||
|
|||||||
@@ -3706,9 +3706,25 @@ public class Kst4ContestApplication implements StatusUpdateListener, SettingsHos
|
|||||||
clusterTable.replaceRows(chatcontroller.getLst_clusterMemberList().snapshot());
|
clusterTable.replaceRows(chatcontroller.getLst_clusterMemberList().snapshot());
|
||||||
qsoTable.replaceRows(chatcontroller.toOtherMessages());
|
qsoTable.replaceRows(chatcontroller.toOtherMessages());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The same preference the JavaFX SplitPane wrote, so a profile gets back the
|
||||||
|
* position its operator dragged before the Compose port (#144).
|
||||||
|
*/
|
||||||
|
SplitterState monitorSplitter = new SplitterState(
|
||||||
|
2,
|
||||||
|
chatcontroller.getChatPreferences().getGUIpnl_directedMSGWin_dividerpositionDefault(),
|
||||||
|
SplitterState.DEFAULT_MIN_PANE_PX,
|
||||||
|
positions -> {
|
||||||
|
chatcontroller.getChatPreferences()
|
||||||
|
.setGUIpnl_directedMSGWin_dividerpositionDefault(positions);
|
||||||
|
requestLayoutSave();
|
||||||
|
return kotlin.Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
|
||||||
MonitorWindow.show(
|
MonitorWindow.show(
|
||||||
clusterTable,
|
clusterTable,
|
||||||
qsoTable,
|
qsoTable,
|
||||||
|
monitorSplitter,
|
||||||
chatcontroller.getChatPreferences().isGUI_darkModeActive(),
|
chatcontroller.getChatPreferences().isGUI_darkModeActive(),
|
||||||
SETTINGS_WINDOW_FONT_SIZE_SP,
|
SETTINGS_WINDOW_FONT_SIZE_SP,
|
||||||
paletteStore,
|
paletteStore,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -17,10 +16,11 @@ import kst4contest.model.ClusterMessage
|
|||||||
/**
|
/**
|
||||||
* The cluster and QSO monitor.
|
* The cluster and QSO monitor.
|
||||||
*
|
*
|
||||||
* Two live tables, one above the other. This is the first window in which the table
|
* Two live tables, one above the other, with a divider the operator can drag. This
|
||||||
* carries data that changes while the operator watches, which is why the row identity
|
* is the first window in which the table carries data that changes while the operator
|
||||||
* rules in DataTableState matter here and not in the settings window: in Etappe 2 the
|
* watches, which is why the row identity rules in DataTableState matter here and not in
|
||||||
* visible defects all came from that coupling, not from the table itself.
|
* the settings window: in Etappe 2 the visible defects all came from that coupling, not
|
||||||
|
* from the table itself.
|
||||||
*/
|
*/
|
||||||
object MonitorWindow {
|
object MonitorWindow {
|
||||||
|
|
||||||
@@ -44,6 +44,8 @@ object MonitorWindow {
|
|||||||
fun show(
|
fun show(
|
||||||
clusterTable: DataTableState<ClusterMessage>,
|
clusterTable: DataTableState<ClusterMessage>,
|
||||||
qsoTable: DataTableState<ChatMessage>,
|
qsoTable: DataTableState<ChatMessage>,
|
||||||
|
/** The divider between the two tables, remembered in the profile (#144). */
|
||||||
|
splitter: SplitterState,
|
||||||
darkMode: Boolean,
|
darkMode: Boolean,
|
||||||
baseFontSizeSp: Float,
|
baseFontSizeSp: Float,
|
||||||
/**
|
/**
|
||||||
@@ -70,7 +72,7 @@ object MonitorWindow {
|
|||||||
heightDp = heightDp,
|
heightDp = heightDp,
|
||||||
onResized = onResized,
|
onResized = onResized,
|
||||||
) { _ ->
|
) { _ ->
|
||||||
MonitorContent(clusterTable, qsoTable)
|
MonitorContent(clusterTable, qsoTable, splitter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,29 +82,31 @@ object MonitorWindow {
|
|||||||
internal fun MonitorContent(
|
internal fun MonitorContent(
|
||||||
clusterTable: DataTableState<ClusterMessage>,
|
clusterTable: DataTableState<ClusterMessage>,
|
||||||
qsoTable: DataTableState<ChatMessage>,
|
qsoTable: DataTableState<ChatMessage>,
|
||||||
|
splitter: SplitterState,
|
||||||
) {
|
) {
|
||||||
|
val strings = kst4contest.view.i18n.LocalStrings.current
|
||||||
|
|
||||||
Surface(modifier = Modifier.fillMaxSize()) {
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(4.dp)) {
|
/*
|
||||||
Text("DX cluster", style = MaterialTheme.typography.titleSmall)
|
* Each heading sits in the pane of its table, so dragging the divider moves the
|
||||||
|
* heading with the table it names rather than leaving it stranded above the other.
|
||||||
|
*/
|
||||||
|
SplitterPane(
|
||||||
|
state = splitter,
|
||||||
|
orientation = SplitterOrientation.VERTICAL,
|
||||||
|
modifier = Modifier.fillMaxSize().padding(4.dp),
|
||||||
|
panes = listOf(
|
||||||
|
{ MonitorPane(strings.monitorDxCluster, clusterTable) },
|
||||||
|
{ MonitorPane(strings.messagesQsoOfTheOther, qsoTable) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
@Composable
|
||||||
* Half each, fixed. The JavaFX window used a SplitPane whose divider the
|
private fun <T> MonitorPane(heading: String, table: DataTableState<T>) {
|
||||||
* operator could drag, and that position was stored; reproducing the
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
* gesture belongs with the station list of Etappe 5, where a split pane is
|
Text(heading, style = MaterialTheme.typography.titleSmall, modifier = Modifier.padding(top = 4.dp))
|
||||||
* unavoidable. Until then an even split is honest rather than a broken
|
DataTableView(table, modifier = Modifier.weight(1f).fillMaxWidth())
|
||||||
* handle.
|
|
||||||
*/
|
|
||||||
DataTableView(clusterTable, modifier = Modifier.weight(1f).fillMaxWidth())
|
|
||||||
|
|
||||||
HorizontalDivider(thickness = Density.HAIRLINE)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
kst4contest.view.i18n.LocalStrings.current.messagesQsoOfTheOther,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier.padding(top = 4.dp),
|
|
||||||
)
|
|
||||||
|
|
||||||
DataTableView(qsoTable, modifier = Modifier.weight(1f).fillMaxWidth())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
package kst4contest.view.compose
|
package kst4contest.view.compose
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.test.ComposeUiTest
|
import androidx.compose.ui.test.ComposeUiTest
|
||||||
|
import androidx.compose.ui.test.getBoundsInRoot
|
||||||
|
import androidx.compose.ui.test.onRoot
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.test.ExperimentalTestApi
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onAllNodesWithText
|
import androidx.compose.ui.test.onAllNodesWithText
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
@@ -9,6 +15,7 @@ import kst4contest.model.ChatMember
|
|||||||
import kst4contest.model.ChatMessage
|
import kst4contest.model.ChatMessage
|
||||||
import kst4contest.model.ClusterMessage
|
import kst4contest.model.ClusterMessage
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,10 +63,13 @@ class MonitorWindowScreenTest {
|
|||||||
private fun ComposeUiTest.show(
|
private fun ComposeUiTest.show(
|
||||||
cluster: DataTableState<ClusterMessage>,
|
cluster: DataTableState<ClusterMessage>,
|
||||||
qsos: DataTableState<ChatMessage>,
|
qsos: DataTableState<ChatMessage>,
|
||||||
|
splitter: SplitterState = SplitterState(paneCount = 2, stored = doubleArrayOf(0.5)) {},
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
Kst4ContestTheme(darkMode = false, baseFontSizeSp = 12f) {
|
Kst4ContestTheme(darkMode = false, baseFontSizeSp = 12f) {
|
||||||
MonitorContent(cluster, qsos)
|
Box(Modifier.size(600.dp, 400.dp)) {
|
||||||
|
MonitorContent(cluster, qsos, splitter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
waitForIdle()
|
waitForIdle()
|
||||||
@@ -75,6 +85,22 @@ class MonitorWindowScreenTest {
|
|||||||
onNodeWithText("Call TX").assertExists()
|
onNodeWithText("Call TX").assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The two tables used to get half the height each, fixed, and the stored position the
|
||||||
|
* JavaFX SplitPane had written was read by nobody (#144). The QSO heading is the top of
|
||||||
|
* the lower pane, so where it sits says where the divider is.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun theStoredDividerPositionSplitsTheWindow() = runComposeUiTest {
|
||||||
|
show(clusterTable(), qsoTable(), SplitterState(paneCount = 2, stored = doubleArrayOf(0.8)) {})
|
||||||
|
|
||||||
|
val window = onRoot().getBoundsInRoot()
|
||||||
|
val qsoHeadingTop = onNodeWithText("QSO of the other").getBoundsInRoot().top
|
||||||
|
val share = (qsoHeadingTop - window.top) / (window.bottom - window.top)
|
||||||
|
|
||||||
|
assertTrue(share in 0.75f..0.85f, "QSO pane starts at $share of the height, expected about 0.8")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun messagesArrivingWhileTheWindowIsOpenReachBothTables() = runComposeUiTest {
|
fun messagesArrivingWhileTheWindowIsOpenReachBothTables() = runComposeUiTest {
|
||||||
val cluster = clusterTable()
|
val cluster = clusterTable()
|
||||||
|
|||||||
Reference in new issue
Block a user