Skip to content

Commit b17622a

Browse files
authored
Merge pull request #25 from karlkauc/feature/xmlspy-grid-view
feat: XMLSpy-style grid view for graphical XML editor
2 parents 1cdaf0e + 3f3a68c commit b17622a

7 files changed

Lines changed: 608 additions & 37 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Grid Cell Expand Arrows Fix — Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Fix expand/collapse arrows in RepeatingElementsTable grid cells — they are invisible because they use Unicode characters that don't render in Monospaced font on Canvas. Replace with Canvas-drawn polygon triangles.
6+
7+
**Architecture:** Replace `gc.fillText(arrow, ...)` calls for cell expand indicators with `gc.fillPolygon()` calls that draw triangles directly, matching the approach used for tree expand bars.
8+
9+
**Tech Stack:** Java 25, JavaFX 24.0.1, Canvas (GraphicsContext)
10+
11+
**Spec:** `docs/superpowers/specs/2026-03-26-grid-cell-expansion-design.md`
12+
13+
---
14+
15+
## File Structure
16+
17+
| File | Action | Responsibility |
18+
|------|--------|----------------|
19+
| `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java` | Modify | Replace Unicode arrows with Canvas-drawn polygons in `renderInlineTable()` |
20+
21+
---
22+
23+
### Task 1: Replace Unicode arrows with Canvas-drawn polygons in grid cells
24+
25+
**Files:**
26+
- Modify: `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java`
27+
28+
- [ ] **Step 1: Replace the expand arrow rendering in `renderInlineTable()`**
29+
30+
In `renderInlineTable()` (around line 1080-1088), replace the Unicode-based arrow drawing:
31+
32+
```java
33+
// CURRENT (broken — Unicode chars don't render in Monospaced on Canvas):
34+
if (isComplex) {
35+
gc.setFont(SMALL_FONT);
36+
gc.setFill(TEXT_SECONDARY);
37+
gc.setTextAlign(TextAlignment.LEFT);
38+
gc.setTextBaseline(VPos.CENTER);
39+
String arrow = row.isColumnExpanded(colName) ? "\u25BC" : "\u25B6";
40+
gc.fillText(arrow, cellX + 2, cellCenterY);
41+
}
42+
```
43+
44+
With Canvas-drawn polygon triangles (same approach as tree expand bars at line 768-778):
45+
46+
```java
47+
if (isComplex) {
48+
double arrowX = cellX + 8;
49+
double arrowY = cellCenterY;
50+
double arrowSize = 3;
51+
gc.setFill(TEXT_SECONDARY);
52+
53+
if (row.isColumnExpanded(colName)) {
54+
// Down-pointing triangle ▼ (expanded)
55+
gc.fillPolygon(
56+
new double[]{arrowX - arrowSize, arrowX, arrowX + arrowSize},
57+
new double[]{arrowY - arrowSize / 2, arrowY + arrowSize, arrowY - arrowSize / 2},
58+
3
59+
);
60+
} else {
61+
// Right-pointing triangle ► (collapsed)
62+
gc.fillPolygon(
63+
new double[]{arrowX - arrowSize / 2, arrowX + arrowSize, arrowX - arrowSize / 2},
64+
new double[]{arrowY - arrowSize, arrowY, arrowY + arrowSize},
65+
3
66+
);
67+
}
68+
}
69+
```
70+
71+
- [ ] **Step 2: Verify build compiles**
72+
73+
Run: `./gradlew compileJava`
74+
Expected: BUILD SUCCESSFUL
75+
76+
- [ ] **Step 3: Run all tests**
77+
78+
Run: `./gradlew test`
79+
Expected: All tests PASS
80+
81+
- [ ] **Step 4: Commit**
82+
83+
```bash
84+
git add src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java
85+
git commit -m "fix: replace Unicode arrows with Canvas-drawn polygons in grid cells"
86+
```
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# Grid Cell Expansion — Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Enable inline expand/collapse for complex table cells in the RepeatingElementsTable grid view, so users can see and edit child nodes directly within the table.
6+
7+
**Architecture:** Add cell-level expand state to `TableRow`, use `FlatRow.flattenElement()` to produce child rows on demand, render them inline within the cell with dynamic row heights, and handle click/edit interactions on the expanded content.
8+
9+
**Tech Stack:** Java 25, JavaFX 24.0.1, Canvas (GraphicsContext), JUnit 5
10+
11+
**Spec:** `docs/superpowers/specs/2026-03-26-grid-cell-expansion-design.md`
12+
13+
**Status:** Already implemented (commit `19b7eb4a`). This plan documents what was built.
14+
15+
---
16+
17+
## File Structure
18+
19+
| File | Action | Responsibility |
20+
|------|--------|----------------|
21+
| `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/FlatRow.java` | Modify | Add `flattenElement(XmlElement)` convenience method |
22+
| `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/RepeatingElementsTable.java` | Modify | Add cell expand state to TableRow, variable row heights |
23+
| `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java` | Modify | Render expanded cells, handle expand clicks, variable positioning |
24+
25+
---
26+
27+
### Task 1: Add FlatRow.flattenElement() convenience method
28+
29+
**Files:**
30+
- Modify: `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/FlatRow.java`
31+
32+
- [x] **Step 1: Add public static flattenElement(XmlElement) method**
33+
34+
```java
35+
public static List<FlatRow> flattenElement(XmlElement element) {
36+
List<FlatRow> rows = new ArrayList<>();
37+
// Add attributes of the element itself at depth 0
38+
int attrIdx = 0;
39+
for (Map.Entry<String, String> attr : element.getAttributes().entrySet()) {
40+
FlatRow attrRow = new FlatRow(RowType.ATTRIBUTE, 0, element, null,
41+
attr.getKey(), attr.getValue(), 0);
42+
attrRow.setAttributeIndex(attrIdx++);
43+
rows.add(attrRow);
44+
}
45+
// Flatten children recursively
46+
for (XmlNode child : element.getChildren()) {
47+
if (child instanceof XmlElement childEl) {
48+
flattenElement(childEl, 0, null, rows, true);
49+
} else if (child instanceof XmlText text) {
50+
if (text.getText() != null && !text.getText().isBlank()) {
51+
rows.add(new FlatRow(RowType.TEXT, 0, text, null,
52+
"#text", text.getText().strip(), 0));
53+
}
54+
} else if (child instanceof XmlComment comment) {
55+
rows.add(new FlatRow(RowType.COMMENT, 0, comment, null,
56+
null, comment.getText(), 0));
57+
} else if (child instanceof XmlCData cdata) {
58+
rows.add(new FlatRow(RowType.CDATA, 0, cdata, null,
59+
null, cdata.getText(), 0));
60+
}
61+
}
62+
return rows;
63+
}
64+
```
65+
66+
- [x] **Step 2: Verify build**
67+
68+
Run: `./gradlew compileJava`
69+
Expected: BUILD SUCCESSFUL
70+
71+
- [x] **Step 3: Commit**
72+
73+
```bash
74+
git add src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/FlatRow.java
75+
git commit -m "feat: add FlatRow.flattenElement() for cell expansion"
76+
```
77+
78+
---
79+
80+
### Task 2: Add cell expansion state to RepeatingElementsTable.TableRow
81+
82+
**Files:**
83+
- Modify: `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/RepeatingElementsTable.java`
84+
85+
- [x] **Step 1: Add expansion fields and methods to TableRow**
86+
87+
```java
88+
// In TableRow inner class:
89+
private final Set<String> expandedColumns = new HashSet<>();
90+
private final Map<String, List<FlatRow>> expandedCellRows = new LinkedHashMap<>();
91+
92+
public boolean isColumnExpanded(String columnName) {
93+
return expandedColumns.contains(columnName);
94+
}
95+
96+
public void toggleColumnExpanded(String columnName) {
97+
if (expandedColumns.contains(columnName)) {
98+
expandedColumns.remove(columnName);
99+
expandedCellRows.remove(columnName);
100+
} else {
101+
expandedColumns.add(columnName);
102+
XmlElement child = complexChildren.get(columnName);
103+
if (child != null) {
104+
expandedCellRows.put(columnName, FlatRow.flattenElement(child));
105+
}
106+
}
107+
}
108+
109+
public List<FlatRow> getExpandedCellRows(String columnName) {
110+
return expandedCellRows.getOrDefault(columnName, List.of());
111+
}
112+
113+
public Set<String> getExpandedColumns() {
114+
return expandedColumns;
115+
}
116+
```
117+
118+
- [x] **Step 2: Update calculateRowHeight() to return variable height**
119+
120+
```java
121+
public double calculateRowHeight(TableRow row) {
122+
double maxCellHeight = ROW_HEIGHT;
123+
for (String colName : row.getExpandedColumns()) {
124+
List<FlatRow> cellRows = row.getExpandedCellRows(colName);
125+
double cellHeight = ROW_HEIGHT + cellRows.size() * ROW_HEIGHT;
126+
maxCellHeight = Math.max(maxCellHeight, cellHeight);
127+
}
128+
return maxCellHeight;
129+
}
130+
```
131+
132+
- [x] **Step 3: Update getHeight() to use variable row heights**
133+
134+
The `calculateHeight()` method iterates rows and calls `calculateRowHeight(row)` for each, accumulating the total.
135+
136+
- [x] **Step 4: Update getRowIndexAtY() and getRowY() for cumulative heights**
137+
138+
Both methods iterate with `calculateRowHeight()` instead of dividing by fixed `ROW_HEIGHT`.
139+
140+
- [x] **Step 5: Verify build and tests**
141+
142+
Run: `./gradlew compileJava && ./gradlew test`
143+
Expected: BUILD SUCCESSFUL, all tests pass
144+
145+
- [x] **Step 6: Commit**
146+
147+
```bash
148+
git add src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/RepeatingElementsTable.java
149+
git commit -m "feat: add cell expansion state and variable row heights to RepeatingElementsTable"
150+
```
151+
152+
---
153+
154+
### Task 3: Render expanded cells and handle interaction in XmlCanvasView
155+
156+
**Files:**
157+
- Modify: `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java`
158+
159+
- [x] **Step 1: Update renderInlineTable() for cumulative row positioning**
160+
161+
Replace `dataY + r * ROW_HEIGHT` with cumulative `currentRowY` variable that uses `table.calculateRowHeight(row)`.
162+
163+
- [x] **Step 2: Draw expand arrows for complex cells**
164+
165+
Before drawing cell value, check `row.hasComplexChild(col.getName())`. Draw ▼ if expanded, ► if collapsed, using `gc.fillText()`.
166+
167+
- [x] **Step 3: Render expanded cell sub-rows**
168+
169+
After drawing cell summary, if `row.isColumnExpanded(colName)`:
170+
- Get `row.getExpandedCellRows(colName)`
171+
- Draw each FlatRow with: icon (`drawRowIcon()`), label (colored by type), value
172+
- Position below summary line at `subRowY` increments of `ROW_HEIGHT`
173+
- Apply depth-based indentation
174+
175+
- [x] **Step 4: Handle click on cell expand arrow**
176+
177+
In `handleTableClick()`, for single-clicks on data rows:
178+
- Check if click is within arrow zone (first 16px of complex cell)
179+
- If so, call `row.toggleColumnExpanded(col.getName())`
180+
- Trigger `recalculateVisibleRows()`, `updateScrollBars()`, `render()`
181+
182+
- [x] **Step 5: Update getTableRowIndexAtScreenY() for variable heights**
183+
184+
Iterate with cumulative `calculateRowHeight()` instead of fixed division.
185+
186+
- [x] **Step 6: Update startEditingTableCell() for variable positioning**
187+
188+
Calculate cell Y position with cumulative row heights for rows before the target.
189+
190+
- [x] **Step 7: Verify build and tests**
191+
192+
Run: `./gradlew compileJava && ./gradlew test`
193+
Expected: BUILD SUCCESSFUL, all tests pass
194+
195+
- [x] **Step 8: Commit**
196+
197+
```bash
198+
git add src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java
199+
git commit -m "feat: render expanded cells and handle expand interaction in grid view"
200+
```
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Inline Expansion for Complex Table Cells
2+
3+
**Date:** 2026-03-26
4+
**Status:** Approved
5+
6+
## Summary
7+
8+
In the RepeatingElementsTable (grid view), table cells containing complex XML elements (elements with children) currently show only a summary text. Users cannot see or interact with the child nodes. This spec adds inline expand/collapse to those cells, rendering children as flat rows within the cell.
9+
10+
## Current Behavior
11+
12+
A column like "Bond" that contains `<Bond><Issuer>ABC</Issuer><Coupon>5.0</Coupon></Bond>` shows only `"Issuer, Coupon"` (a summary of child element names). The children are invisible and not editable.
13+
14+
## New Behavior
15+
16+
### Expand Indicator
17+
18+
- Cells with complex elements show a small expand arrow (►) to the left of the summary text.
19+
- Clicking the arrow expands the cell. The arrow becomes ▼.
20+
- Clicking again collapses back to the summary.
21+
22+
### Expanded Cell Content
23+
24+
When expanded, the cell grows vertically and shows the child nodes as flat rows using the same XMLSpy-style rendering:
25+
- `<>` icon + name + value for child elements
26+
- `=` icon + name + value for attributes of the complex element
27+
- `T` icon for text nodes
28+
- Indentation for nested levels
29+
- Recursive expansion: child elements with their own children can also be expanded.
30+
31+
### Row Height
32+
33+
- The table row height becomes the maximum of all cell heights in that row.
34+
- Non-expanded cells in the same row are top-aligned.
35+
- The total table height is recalculated when any cell expands/collapses.
36+
37+
### Interaction
38+
39+
- **Single click on arrow:** Toggle expand/collapse for that cell.
40+
- **Double-click on a value** in the expanded content: Start inline editing (same as in the tree view — uses Commands for undo/redo).
41+
- **Context menu** on expanded content: Same as regular tree view context menu.
42+
43+
## Affected Files
44+
45+
### RepeatingElementsTable.java
46+
- Re-add expand state tracking per cell: `TableRow.expandedColumns` (Set<String>) and `TableRow.isColumnExpanded(columnName)`/`toggleColumnExpanded(columnName)`.
47+
- Store flattened child rows per expanded cell: `TableRow.expandedCellRows` (Map<String, List<FlatRow>>).
48+
- `calculateRowHeight(TableRow)` returns dynamic height based on expanded cells.
49+
- `getHeight()` accounts for variable row heights.
50+
51+
### XmlCanvasView.java
52+
- Render expanded cell content: iterate `FlatRow` list for the cell and draw each row with icon, indent, name, value.
53+
- Draw expand/collapse arrow in complex cells.
54+
- Hit-testing: detect clicks on cell expand arrows vs. cell content vs. expanded sub-rows.
55+
- Inline editing: `startEditingTableCell` must handle clicks on expanded sub-rows (delegate to `startEditingValue` for the sub-row's FlatRow).
56+
- Recalculate `rowYPositions` when cell expansion changes table height.
57+
58+
### FlatRow.java
59+
- No changes needed. The existing `flatten()` method can be used to flatten a single `XmlElement` into rows for cell content. Add a convenience method: `static List<FlatRow> flattenElement(XmlElement element)`.
60+
61+
## Performance
62+
63+
- Expansion is lazy: child rows are only created when the cell is first expanded.
64+
- Cell rows are cached in `TableRow.expandedCellRows` and cleared on model changes.

0 commit comments

Comments
 (0)