mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-19 05:42:58 +02:00
Etappen 3 to 6a of the migration (129 commits). Every window the operator sees is Compose; the JavaFX ones still exist beside them so the two could be compared against a live session, which is the only way the differences came to light. The toolchain first, then the settings window with its eleven tabs and 72 real preferences; then the multi-column table as a component, and with it the rules it needs -- stable row identity, a selection that survives a refresh, column order the operator can drag, and a repaint when the rows change rather than when the list instance does. Then the update and monitor windows, and the main window: 22 station columns, the filter bar, the selected-station panel, the message tabs, the priority bar, the sked controls, the status indicators and the timeline, assembled behind a splitter because Compose has none. The map stops being a WebView. A Compose canvas projects WebMercator tiles itself, with the clustering in core and an asynchronous fetcher in place of the local tile proxy; derive() becomes a pure Kotlin reimplementation instead of a reflection call into com.sun.javafx. Most of the volume here is parity the operator would notice rather than new behaviour: desktop density instead of Material's phone minimums, colours taken from the JavaFX stylesheets instead of Material's palette, row dividers, band columns, grid-square labels and their colours, the home marker and antenna beam, pinch-to-zoom under Wayland, and layouts that survive a window narrower than the chart they stack. Two lessons are worth more than the diff. A control bound to a value Compose cannot observe writes through and never redraws -- that one shipped repeatedly with a green build, because no test drew anything. And a layout that clips instead of wrapping is worse than one that is merely too narrow: half a word is the worst available outcome.
154 lines
8.1 KiB
Markdown
154 lines
8.1 KiB
Markdown
# Umsetzungsplan Etappe 5c, Task 13 — das Hauptfenster zusammensetzen
|
||
|
||
Task 13 stand im Etappenplan als **ein** Schritt: „das Hauptfenster zusammengesetzt, das
|
||
JavaFX-Fenster ersetzt und entfernt". Nach dem Vermessen der Vorlage ist das nicht ein
|
||
Schritt, sondern fünf. Dieser Plan sagt, welche, und warum die Reihenfolge so ist.
|
||
|
||
## Der Baum des JavaFX-Fensters
|
||
|
||
`start()` reicht von Zeile 7236 bis 9400 — **2164 Zeilen für ein Fenster**. Was darin
|
||
steht:
|
||
|
||
```
|
||
Stage (primaryStage) Größe aus GUIscn_ChatwindowMainSceneSizeHW,
|
||
└─ Scene auf den Schirm begrenzt
|
||
└─ BorderPane bPaneChatWindow
|
||
├─ top: FlowPane flwpne_StatusBar ✅ Task 10 + 11
|
||
└─ center: SplitPane links (waagerecht, 1 Trenner)
|
||
├─ SplitPane Nachrichtenteil (senkrecht, 4 Trenner)
|
||
│ ├─ privateMessageTable ❌ fehlt
|
||
│ ├─ FlowPane flwPane_textSnippets ❌ fehlt
|
||
│ ├─ VBox pnl_inputAndSendButtons → TimelineView ✅ Task 12
|
||
│ ├─ HBox textInputFlowPane ❌ fehlt
|
||
│ └─ TabPane bottomGlobalMessageTabPane ✅ Etappe 5b
|
||
└─ SplitPane rechts (senkrecht, 2 Trenner)
|
||
├─ BorderPane chatMemberTableBorderPane ✅ Etappe 5a
|
||
├─ topPriorityListPane ✅ Etappe 5b
|
||
└─ selectedCallSignFurtherInfoPane ✅ Etappe 5b
|
||
```
|
||
|
||
Sechs von zehn Bausteinen stehen. Die vier fehlenden sind nicht die kleinen.
|
||
|
||
## Was fehlt, mit Zeilen
|
||
|
||
| Fehlt | Vorlage | Bemerkung |
|
||
|---|---|---|
|
||
| Tabelle der gerichteten Nachrichten | `initChatprivateMSGTable` ab 3730 | eigener Bereich im Splitter, nicht in `MessageTabs` |
|
||
| Knopfreihe der Textschnipsel | `flwPane_textSnippets` 7890, `buttonFactory` 5233, `refreshShortcutButtons` 4541 | Der **Bestand** liegt schon in `ShortcutsTabState`; nur die Knöpfe fehlen |
|
||
| Sendezeile | `textInputFlowPane` 7886 | Eingabefeld, `TX`, `Clear`, Trenner, eigene QRG Haupt-/Zweitkategorie, eigener QTF |
|
||
| Kontextmenüs | `initChatMemberTableContextMenu` 3132, `insertTextSnippet` 7473 | Stationsliste **und** Nachrichtentabellen; Strg+1…Strg+0 für Schnipsel |
|
||
| Fensterrahmen | `getScreenAwareMainSceneSizeHW` 7178, `ensureStageFitsPrimaryScreen` 9396, `buildOperatorProfileTitleSuffix` 6595 | Größe merken und begrenzen, Titel mit Profilzusatz |
|
||
| **Splitter mit gemerkten Trennern** | 3 `SplitPane`, 7 Trenner | **Compose hat das nicht** — siehe unten |
|
||
|
||
## Das eine Stück, das Compose nicht mitbringt
|
||
|
||
`SplitPane` gibt es in Compose Multiplatform nicht. Sieben Trennerstellungen werden
|
||
heute in den Einstellungen gemerkt:
|
||
|
||
```java
|
||
GUImainWindowLeftSplitPane_dividerposition = {0.51}
|
||
GUImainWindowRightSplitPane_dividerposition = {0.53, 0.78}
|
||
GUImessageSectionSplitpane_dividerposition = {0.62, 0.7, 0.75, 0.9}
|
||
```
|
||
|
||
Das ist kein Beiwerk: ein Operator richtet sein Fenster einmal für seinen Schirm ein und
|
||
erwartet es beim nächsten Contest genauso. Wir brauchen also eine eigene, gezogene
|
||
Trennleiste mit gemerkter Stellung — dasselbe Muster wie die Spaltengriffe aus Etappe 4
|
||
(`Density.COLUMN_HANDLE_WIDTH`, Haarlinie in 5 dp Greiffläche), nur senkrecht wie
|
||
waagerecht und mit Anteilen statt Pixeln.
|
||
|
||
`ChatPreferences` liefert die Felder als `double[]`; die Compose-Fassung braucht sie als
|
||
Zustand, sonst zeichnet das Ziehen nicht mit. Das ist derselbe Fehler wie in Etappe 3b
|
||
und wird gleich als solcher behandelt.
|
||
|
||
## Aufteilung
|
||
|
||
### 13a — `SplitterState` und `SplitterPane`
|
||
|
||
Ein `n`-teiliger Splitter, waagerecht oder senkrecht, mit `n−1` Trennern als
|
||
Compose-Zustand, Mindestgrößen pro Bereich und Schreiben nach `ChatPreferences` beim
|
||
Loslassen (nicht bei jeder Mausbewegung — die JavaFX-Fassung hat bei jedem Pixel
|
||
`requestLayoutSave()` gerufen und dabei auf die Platte geschrieben).
|
||
|
||
**Tests:** Anteile summieren sich zu 1; ein Trenner schiebt keinen anderen vorbei; eine
|
||
Mindestgröße wird eingehalten; ein abgelehntes Ziehen schreibt nichts (das ist der
|
||
aufgenommene Kleinmangel aus Etappe 4 bei `resizeColumn`); `Snapshot.observe` auf die
|
||
Stellungen; ein Bestand mit zu wenigen gemerkten Werten fällt auf die Vorgaben zurück,
|
||
statt zu stürzen.
|
||
|
||
**Abnahme:** Prüfstand mit fünf Bereichen, Ziehen in beiden Richtungen.
|
||
|
||
### 13b — Sendezeile und Schnipsel-Knöpfe
|
||
|
||
Die Zeile, in der der Operator tatsächlich tippt. `ChatInputState` mit dem Text, dem
|
||
Ziel (gewählte Station oder Hauptkategorie), den beiden eigenen QRG-Feldern und dem QTF.
|
||
Darüber die Knopfreihe aus dem Schnipsel-Bestand, die sich neu baut, wenn die
|
||
Einstellungen sie ändern — `ShortcutsTabState` ruft dafür schon
|
||
`refreshShortcutButtons`, das heute noch ins Leere zeigt.
|
||
|
||
**Tests:** Eingabetaste sendet und leert; `Clear` leert ohne zu senden; die Zielauflösung
|
||
in der Reihenfolge der Vorlage (gewählte Station → Tabellenauswahl → `ScoreService` →
|
||
Hauptkategorie); ein Schnipsel wird an der Einfügemarke eingesetzt, nicht angehängt;
|
||
`Snapshot.observe` auf Text und Ziel.
|
||
|
||
**Risiko:** Das Feld hat in JavaFX den Fokus zurückgeholt (`requestFocus`,
|
||
`selectEnd`) — in Compose ist das ein `FocusRequester` und muss nach jedem Senden und
|
||
nach jedem Schnipsel wieder greifen. Genau hier hat Etappe 3b Fehler gehabt.
|
||
|
||
### 13c — Gerichtete Nachrichten und die Kontextmenüs
|
||
|
||
Die vierte Tabelle, mit `DataTableState` wie die anderen, und die Kontextmenüs, die es
|
||
in Compose noch gar nicht gibt: Rechtsklick auf eine Station (Einträge aus dem
|
||
gemerkten Bestand), auf eine Nachricht, und Strg+1…Strg+0 für die Schnipsel.
|
||
|
||
**Tests:** die Spalten; welche Einträge bei welcher Auswahl erscheinen; dass ein
|
||
Menüeintrag ohne Auswahl nichts sendet.
|
||
|
||
### 13d — Der Fensterrahmen
|
||
|
||
Größe aus den Einstellungen, auf die Arbeitsfläche des Schirms begrenzt (die Vorlage tut
|
||
das, weil ein Fenster von einem größeren Monitor sonst unbedienbar aufgeht), Speichern
|
||
bei Änderung, Titel aus Chatzustand und Profilzusatz, Schließen über `ComposeWindowHost`.
|
||
|
||
**Tests:** die Begrenzungsrechnung mit erfundenen Schirmgrößen (das ist reine Rechnung
|
||
und gehört nach `core`-Art getestet); der Titel bei gesetztem und fehlendem Profil.
|
||
|
||
### 13e — Zusammensetzen, ersetzen, entfernen
|
||
|
||
Erst hier entsteht `MainWindow.kt`, und erst hier fällt der JavaFX-Teil. Reihenfolge:
|
||
|
||
1. `MainWindow` zusammensetzen, **neben** dem JavaFX-Fenster startbar (beide offen,
|
||
Vergleich am Bildschirm — so sind in Etappe 4 die Unterschiede aufgefallen).
|
||
2. Abnahme durch den Operator, mit laufender Verbindung.
|
||
3. Erst danach `start()` ausräumen und die Zeilenzahl festhalten.
|
||
|
||
**Abnahme:** ein Contest-Ablauf von Hand — anmelden, Station wählen, tippen, senden,
|
||
Sked setzen, Profil wechseln — ohne dass ein Zuhörer doppelt hängt.
|
||
|
||
## Reihenfolge und Begründung
|
||
|
||
13a zuerst, weil ohne Splitter kein Fenster zusammengeht. 13b vor 13c, weil die
|
||
Sendezeile das ist, was der Operator die meiste Zeit benutzt; ein Fehler dort wiegt mehr
|
||
als eine fehlende Tabelle. 13d ist klein und unabhängig. 13e zuletzt und mit beiden
|
||
Fenstern offen.
|
||
|
||
## Was aus Etappe 4 und 5 mitkommt
|
||
|
||
- Nach **jedem** sichtbaren Teil starten und hinsehen. Elf Fehler dieser Umstellung sind
|
||
ausschließlich durch Hinsehen aufgefallen, keiner durch einen Test.
|
||
- `Snapshot.observe` für jede neue Ansicht.
|
||
- Kein Verhalten weglassen, um ein Darstellungsproblem zu umgehen.
|
||
- Vor dem Schreiben die Vorlage lesen, nicht nach dem Schreiben. Die falschen
|
||
Zuschreibungen dieser Etappe kamen alle daher, dass ich nach dem Namen geraten habe.
|
||
|
||
## Offene Entscheidungen
|
||
|
||
1. **Die vier Nachrichtenfilter** und **„Always on top"** sind aus Etappe 5b nach 5c
|
||
verschoben. Gehören sie in 13b (bei der Sendezeile) oder in 13c (bei den Tabellen)?
|
||
Vorschlag: 13c, weil sie Tabellenfilter sind.
|
||
2. Der Kleinmangel `GUIpnl_directedMSGWin_dividerposition` ist verwaist — die Vorlage
|
||
hat nur den `...Default`-Wert. In 13a wegwerfen oder stehen lassen?
|
||
Vorschlag: wegwerfen, mit einem Satz in der Einbuchung.
|
||
3. **Der dritte Sortierklick** (Sortierung löschen) fehlt seit Etappe 4. In 13c
|
||
mitnehmen oder eigene Kleinigkeit?
|