Skip to content

Commit 3c59ba0

Browse files
authored
Allow Text as graphic node for numbered lists, including tests (#419)
* Allow Text as graphic node for numbered lists, including tests * avoid casting
1 parent b543cfe commit 3c59ba0

File tree

2 files changed

+109
-27
lines changed

2 files changed

+109
-27
lines changed

rta/src/main/java/com/gluonhq/richtextarea/ParagraphTile.java

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -219,35 +219,26 @@ private void updateGraphicBox(Layer layer, BiFunction<Integer, ParagraphDecorati
219219
graphicBox.getChildren().add(graphicNode);
220220

221221
double nodePrefWidth = 0d, nodePrefHeight = 0d;
222-
if (graphicNode instanceof Label) {
223-
Label numberedListLabel = (Label) graphicNode;
224-
String text = numberedListLabel.getText();
222+
Font font = layer.getFont();
223+
if (graphicNode instanceof Label label) {
224+
String text = label.getText();
225225
if (text != null) {
226-
if (text.contains("#")) {
227-
// numbered list
228-
AtomicInteger ordinal = new AtomicInteger();
229-
viewModel.getParagraphList().stream()
230-
.peek(p -> {
231-
if (p.getDecoration().getGraphicType() != ParagraphDecoration.GraphicType.NUMBERED_LIST ||
232-
p.getDecoration().getIndentationLevel() != indentationLevel) {
233-
// restart ordinal if previous paragraph has different indentation,
234-
// or if it is not a numbered list
235-
ordinal.set(0);
236-
} else {
237-
ordinal.incrementAndGet();
238-
}
239-
})
240-
.filter(p -> paragraph.equals(p))
241-
.findFirst()
242-
.ifPresent(p ->
243-
numberedListLabel.setText(text.replace("#", "" + ordinal.get())));
244-
}
245-
246-
Font font = layer.getFont();
247-
numberedListLabel.setFont(font);
248-
double w = Tools.computeStringWidth(font, numberedListLabel.getText());
226+
text = formatNumber(text, indentationLevel);
227+
label.setText(text);
228+
label.setFont(font);
229+
double w = Tools.computeStringWidth(font, text);
230+
nodePrefWidth = Math.max(w + 1, INDENT_PADDING);
231+
nodePrefHeight = Tools.computeStringHeight(font, text);
232+
}
233+
} else if (graphicNode instanceof Text textNode) {
234+
String text = textNode.getText();
235+
if (text != null) {
236+
text = formatNumber(text, indentationLevel);
237+
textNode.setText(text);
238+
textNode.setFont(font);
239+
double w = Tools.computeStringWidth(font, text);
249240
nodePrefWidth = Math.max(w + 1, INDENT_PADDING);
250-
nodePrefHeight = Tools.computeStringHeight(font, numberedListLabel.getText());
241+
nodePrefHeight = Tools.computeStringHeight(font, text);
251242
}
252243
} else {
253244
nodePrefWidth = Math.max(graphicNode.prefWidth(-1), INDENT_PADDING);
@@ -261,6 +252,29 @@ private void updateGraphicBox(Layer layer, BiFunction<Integer, ParagraphDecorati
261252
layer.updatePrefWidth(richTextAreaSkin.textFlowPrefWidthProperty.get() - boxPrefWidth);
262253
}
263254

255+
private String formatNumber(String text, int indentationLevel) {
256+
if (text.contains("#")) {
257+
// numbered list
258+
AtomicInteger ordinal = new AtomicInteger();
259+
return viewModel.getParagraphList().stream()
260+
.peek(p -> {
261+
if (p.getDecoration().getGraphicType() != ParagraphDecoration.GraphicType.NUMBERED_LIST ||
262+
p.getDecoration().getIndentationLevel() != indentationLevel) {
263+
// restart ordinal if previous paragraph has different indentation,
264+
// or if it is not a numbered list
265+
ordinal.set(0);
266+
} else {
267+
ordinal.incrementAndGet();
268+
}
269+
})
270+
.filter(p -> paragraph.equals(p))
271+
.findFirst()
272+
.map(p -> text.replace("#", "" + ordinal.get()))
273+
.orElse(text);
274+
}
275+
return text;
276+
}
277+
264278
void mousePressedListener(MouseEvent e) {
265279
if (control.isDisabled()) {
266280
return;

rta/src/test/java/com/gluonhq/richtextarea/ui/RTATest.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import javafx.geometry.Orientation;
4545
import javafx.scene.Node;
4646
import javafx.scene.Scene;
47+
import javafx.scene.control.Label;
4748
import javafx.scene.control.ScrollBar;
4849
import javafx.scene.control.ToggleButton;
4950
import javafx.scene.image.ImageView;
@@ -65,6 +66,7 @@
6566
import org.testfx.matcher.base.NodeMatchers;
6667

6768
import java.nio.CharBuffer;
69+
import java.util.Comparator;
6870
import java.util.List;
6971
import java.util.Locale;
7072
import java.util.concurrent.CountDownLatch;
@@ -1148,6 +1150,66 @@ public void findMarkdownTest(FxRobot robot) {
11481150
assertEquals(2, rta.getDocument().getDecorations().size());
11491151
}
11501152

1153+
@Test
1154+
public void defaultNumberedListTest(FxRobot robot) {
1155+
run(() -> {
1156+
String text = "Hello\nRTA";
1157+
TextDecoration textDecoration = TextDecoration.builder().presets().fontFamily("Arial").build();
1158+
ParagraphDecoration paragraphDecoration = ParagraphDecoration.builder().presets()
1159+
.graphicType(ParagraphDecoration.GraphicType.NUMBERED_LIST)
1160+
.indentationLevel(1).build();
1161+
Document document = new Document(text,
1162+
List.of(new DecorationModel(0, text.length(), textDecoration, paragraphDecoration)), text.length());
1163+
richTextArea.getActionFactory().open(document).execute(new ActionEvent());
1164+
});
1165+
waitForFxEvents();
1166+
1167+
verifyThat(".rich-text-area", node -> node instanceof RichTextArea);
1168+
List<Node> nodes = getSortedNodes(robot, ".numbered-list-label");
1169+
assertEquals(2, nodes.size());
1170+
for (int i = 0; i < 2; i++) {
1171+
assertInstanceOf(Label.class, nodes.get(i));
1172+
Label label = (Label) nodes.get(i);
1173+
assertNotNull(label);
1174+
assertNotNull(label.getText());
1175+
assertEquals(i + 1 + ".", label.getText());
1176+
}
1177+
}
1178+
1179+
@Test
1180+
public void customNumberedListTest(FxRobot robot) {
1181+
run(() -> {
1182+
String text = "Hello\nRTA";
1183+
TextDecoration textDecoration = TextDecoration.builder().presets().fontFamily("Arial").build();
1184+
ParagraphDecoration paragraphDecoration = ParagraphDecoration.builder().presets()
1185+
.graphicType(ParagraphDecoration.GraphicType.NUMBERED_LIST)
1186+
.indentationLevel(1).build();
1187+
Document document = new Document(text,
1188+
List.of(new DecorationModel(0, text.length(), textDecoration, paragraphDecoration)), text.length());
1189+
richTextArea.setParagraphGraphicFactory((i, t) -> {
1190+
if (i < 1) {
1191+
return null;
1192+
}
1193+
Text textNode = new Text("#");
1194+
textNode.getStyleClass().add("numbered-list-text");
1195+
return textNode;
1196+
});
1197+
richTextArea.getActionFactory().open(document).execute(new ActionEvent());
1198+
});
1199+
waitForFxEvents();
1200+
1201+
verifyThat(".rich-text-area", node -> node instanceof RichTextArea);
1202+
List<Node> nodes = getSortedNodes(robot, ".numbered-list-text");
1203+
assertEquals(2, nodes.size());
1204+
for (int i = 0; i < 2; i++) {
1205+
assertInstanceOf(Text.class, nodes.get(i));
1206+
Text textNode = (Text) nodes.get(i);
1207+
assertNotNull(textNode);
1208+
assertNotNull(textNode.getText());
1209+
assertEquals(String.valueOf(i + 1), textNode.getText());
1210+
}
1211+
}
1212+
11511213
private static void findEmoji(String text, BiConsumer<Emoji, Integer> onCodeNameFound) {
11521214
if (text.endsWith(" ")) {
11531215
return;
@@ -1208,6 +1270,12 @@ private String getInternalText(Document document, int end) {
12081270
return internalSb.toString();
12091271
}
12101272

1273+
private static List<Node> getSortedNodes(FxRobot robot, String query) {
1274+
return robot.lookup(query).queryAllAs(Node.class).stream()
1275+
.sorted(Comparator.comparingDouble(s -> s.localToScene(s.getLayoutBounds()).getMinY()))
1276+
.collect(Collectors.toList());
1277+
}
1278+
12111279
private void run(Runnable runnable) {
12121280
CountDownLatch countDownLatch = new CountDownLatch(1);
12131281
Platform.runLater(() -> {

0 commit comments

Comments
 (0)