From 768cb640822d0d38ddce480bd58c966a5222dffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereda?= Date: Mon, 6 May 2024 12:25:18 +0200 Subject: [PATCH] Make Document and Decoration serializables (#312) --- .../gluonhq/richtextarea/RichListCell.java | 23 ++++++++++---- .../richtextarea/model/Decoration.java | 6 ++-- .../richtextarea/model/DecorationModel.java | 5 ++-- .../gluonhq/richtextarea/model/Document.java | 5 ++-- .../richtextarea/model/TextDecoration.java | 30 ++++++++++--------- .../model/PieceTableEmojiTests.java | 7 ++--- .../richtextarea/model/PieceTableTests.java | 14 ++++----- .../samples/BasicDocumentDemo.java | 5 ++-- .../samples/FullFeaturedDemo.java | 15 +++++++--- .../richtextarea/samples/HighlightDemo.java | 5 ++-- 10 files changed, 69 insertions(+), 46 deletions(-) diff --git a/rta/src/main/java/com/gluonhq/richtextarea/RichListCell.java b/rta/src/main/java/com/gluonhq/richtextarea/RichListCell.java index bf1a544f..774a0d91 100644 --- a/rta/src/main/java/com/gluonhq/richtextarea/RichListCell.java +++ b/rta/src/main/java/com/gluonhq/richtextarea/RichListCell.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Gluon + * Copyright (c) 2022, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,6 +56,8 @@ import javafx.scene.text.Text; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -70,6 +72,8 @@ class RichListCell extends ListCell { private static final Font MIN_LF_FONT = Font.font(10); + private final static Map COLOR_MAP = new HashMap<>(); + private final RichTextAreaSkin richTextAreaSkin; private final ParagraphTile paragraphTile; @@ -194,10 +198,11 @@ protected void updateItem(Paragraph item, boolean empty) { } else { final Node node = buildNode(unit, (TextDecoration) decoration); fragments.add(node); - Color background = ((TextDecoration) decoration).getBackground(); - if (background != Color.TRANSPARENT) { + String background = ((TextDecoration) decoration).getBackground(); + Color backgroundColor = COLOR_MAP.computeIfAbsent(background, s -> parseColorOrDefault(background, Color.TRANSPARENT)); + if (!Color.TRANSPARENT.equals(backgroundColor)) { backgroundIndexRanges.add(new IndexRangeColor( - length.get(), length.get() + unit.length(), background)); + length.get(), length.get() + unit.length(), backgroundColor)); } } length.addAndGet(unit.length()); @@ -249,7 +254,8 @@ private Text buildText(String content, TextDecoration decoration) { } Objects.requireNonNull(decoration); Text text = new Text(Objects.requireNonNull(content)); - text.setFill(decoration.getForeground()); + String foreground = decoration.getForeground(); + text.setFill(COLOR_MAP.computeIfAbsent(foreground, s -> parseColorOrDefault(foreground, Color.BLACK))); text.setStrikethrough(decoration.isStrikethrough()); text.setUnderline(decoration.isUnderline()); @@ -353,4 +359,11 @@ private Optional getParagraphTile() { return Optional.empty(); } + private static Color parseColorOrDefault(String color, Color defaultColor) { + try { + return Color.web(color); + } catch (Exception e) { + return defaultColor; + } + } } diff --git a/rta/src/main/java/com/gluonhq/richtextarea/model/Decoration.java b/rta/src/main/java/com/gluonhq/richtextarea/model/Decoration.java index 861b8df0..5515f0bd 100644 --- a/rta/src/main/java/com/gluonhq/richtextarea/model/Decoration.java +++ b/rta/src/main/java/com/gluonhq/richtextarea/model/Decoration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Gluon + * Copyright (c) 2022, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,12 +27,14 @@ */ package com.gluonhq.richtextarea.model; +import java.io.Serializable; + /** * Interface used to define the decoration applied to a fragment of RichTextArea * * Known implementations: {@link TextDecoration} and {@link ImageDecoration} for * text decoration, and {@link ParagraphDecoration} for paragraph decoration */ -public interface Decoration { +public interface Decoration extends Serializable { } diff --git a/rta/src/main/java/com/gluonhq/richtextarea/model/DecorationModel.java b/rta/src/main/java/com/gluonhq/richtextarea/model/DecorationModel.java index f43a9fc2..667b26e4 100644 --- a/rta/src/main/java/com/gluonhq/richtextarea/model/DecorationModel.java +++ b/rta/src/main/java/com/gluonhq/richtextarea/model/DecorationModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Gluon + * Copyright (c) 2022, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,13 +27,14 @@ */ package com.gluonhq.richtextarea.model; +import java.io.Serializable; import java.util.Objects; /** * A DecorationModel contains the text and paragraph decorations for a fragment of text, * defined by a start position and a length. */ -public class DecorationModel { +public class DecorationModel implements Serializable { private final int start; private final int length; private final Decoration decoration; // TextDecoration or ImageDecoration diff --git a/rta/src/main/java/com/gluonhq/richtextarea/model/Document.java b/rta/src/main/java/com/gluonhq/richtextarea/model/Document.java index 7cf71699..758c7b2b 100644 --- a/rta/src/main/java/com/gluonhq/richtextarea/model/Document.java +++ b/rta/src/main/java/com/gluonhq/richtextarea/model/Document.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Gluon + * Copyright (c) 2022, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +29,7 @@ import com.gluonhq.richtextarea.Tools; +import java.io.Serializable; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -46,7 +47,7 @@ * decorations or caret position) should refer to their position within the full raw text. * */ -public class Document { +public class Document implements Serializable { private final String text; private final List decorationList; diff --git a/rta/src/main/java/com/gluonhq/richtextarea/model/TextDecoration.java b/rta/src/main/java/com/gluonhq/richtextarea/model/TextDecoration.java index 5e446dde..9147b96a 100644 --- a/rta/src/main/java/com/gluonhq/richtextarea/model/TextDecoration.java +++ b/rta/src/main/java/com/gluonhq/richtextarea/model/TextDecoration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Gluon + * Copyright (c) 2022, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,8 +41,8 @@ */ public class TextDecoration implements Decoration { - private Color foreground; - private Color background; + private String foreground; + private String background; private String fontFamily; private double fontSize; private FontPosture fontPosture; @@ -55,30 +55,32 @@ private TextDecoration() {} /** * Gets the foreground color of the text. + * Any string value that can be parsed with {@link Color#web(String)} * - * @defaultValue {@link Color#BLACK} + * @defaultValue #000000, black * * @return the foreground color of the text */ - public Color getForeground() { + public String getForeground() { return foreground; } /** * Gets the background color of the text. + * Any string value that can be parsed with {@link Color#web(String)} * - * @defaultValue {@link Color#TRANSPARENT} + * @defaultValue #00000000, transparent * * @return the background color of the text */ - public Color getBackground() { + public String getBackground() { return background; } /** * Gets the font size of the text. * - * @defaultValue 12 + * @defaultValue 14 * * @return the font size of the text */ @@ -210,8 +212,8 @@ public int hashCode() { public static class Builder { - private Color foreground; - private Color background; + private String foreground; + private String background; private String fontFamily; private double fontSize; private FontPosture fontPosture; @@ -237,8 +239,8 @@ public TextDecoration build() { } public Builder presets() { - foreground = Color.BLACK; - background = Color.TRANSPARENT; + foreground = "black"; + background = "transparent"; fontFamily = "System"; fontSize = 14.0; fontPosture = FontPosture.REGULAR; @@ -262,12 +264,12 @@ public Builder fromDecoration(TextDecoration decoration) { return this; } - public Builder foreground(Color color) { + public Builder foreground(String color) { this.foreground = Objects.requireNonNull(color); return this; } - public Builder background(Color color) { + public Builder background(String color) { this.background = Objects.requireNonNull(color); return this; } diff --git a/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableEmojiTests.java b/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableEmojiTests.java index 6f920c11..22752301 100644 --- a/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableEmojiTests.java +++ b/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableEmojiTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Gluon + * Copyright (c) 2023, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,7 +27,6 @@ */ package com.gluonhq.richtextarea.model; -import javafx.scene.paint.Color; import javafx.scene.text.FontPosture; import javafx.scene.text.FontWeight; import org.junit.jupiter.api.Assertions; @@ -232,11 +231,11 @@ public void emojiSameBlockDecorateWeight() { public void sameBlockDecorateForegroundColor() { String text = "Original Bigger Text"; PieceTable pt = new PieceTable(new Document(text)); - pt.decorate(1, 2, TextDecoration.builder().foreground(Color.AQUA).build()); + pt.decorate(1, 2, TextDecoration.builder().foreground("aqua").build()); Assertions.assertEquals(text, pt.getText()); Assertions.assertTrue(pt.pieces.stream() .filter(piece -> piece.getInternalText().equals("r")) - .anyMatch(piece -> ((TextDecoration) piece.getDecoration()).getForeground() == Color.AQUA) + .anyMatch(piece -> ((TextDecoration) piece.getDecoration()).getForeground().equals("aqua")) ); } diff --git a/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableTests.java b/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableTests.java index 8cb58b07..88141903 100644 --- a/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableTests.java +++ b/rta/src/test/java/com/gluonhq/richtextarea/model/PieceTableTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Gluon + * Copyright (c) 2022, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -251,11 +251,11 @@ public void sameBlockDecoratePosture() { public void sameBlockDecorateForegroundColor() { String text = "Original Bigger Text"; PieceTable pt = new PieceTable(new Document(text)); - pt.decorate(1, 2, TextDecoration.builder().foreground(Color.AQUA).build()); + pt.decorate(1, 2, TextDecoration.builder().foreground("aqua").build()); Assertions.assertEquals(text, pt.getText()); Assertions.assertTrue(pt.pieces.stream() .filter(piece -> piece.getInternalText().equals("r")) - .anyMatch(piece -> ((TextDecoration) piece.getDecoration()).getForeground() == Color.AQUA) + .anyMatch(piece -> ((TextDecoration) piece.getDecoration()).getForeground().equals("aqua")) ); } @@ -264,11 +264,11 @@ public void sameBlockDecorateForegroundColor() { public void sameBlockDecorateBackgroundColor() { String text = "Original Bigger Text"; PieceTable pt = new PieceTable(new Document(text)); - pt.decorate(1, 2, TextDecoration.builder().background(Color.AQUA).build()); + pt.decorate(1, 2, TextDecoration.builder().background("aqua").build()); Assertions.assertEquals(text, pt.getText()); Assertions.assertTrue(pt.pieces.stream() .filter(piece -> piece.getInternalText().equals("r")) - .anyMatch(piece -> ((TextDecoration) piece.getDecoration()).getBackground() == Color.AQUA) + .anyMatch(piece -> ((TextDecoration) piece.getDecoration()).getBackground().equals("aqua")) ); } @@ -464,12 +464,12 @@ public void multiBlockDecorateSizeAndColor() { pt.insert(insert, 9); // 'Original Bigger Text' pt.insert(insert, 9); // 'Original Bigger Bigger Text' pt.decorate(9, 15, TextDecoration.builder().fontSize(20).build()); - pt.decorate(9, 15, TextDecoration.builder().foreground(Color.AQUA).build()); + pt.decorate(9, 15, TextDecoration.builder().foreground("aqua").build()); Assertions.assertEquals("Original Bigger Bigger Text", pt.getText()); Assertions.assertTrue(pt.pieces.stream() .filter(piece -> piece.getInternalText().equals("Bigger")) .anyMatch(piece -> ((TextDecoration) piece.getDecoration()).getFontSize() == 20 && - ((TextDecoration) piece.getDecoration()).getForeground() == Color.AQUA) + ((TextDecoration) piece.getDecoration()).getForeground().equals("aqua")) ); } diff --git a/samples/src/main/java/com/gluonhq/richtextarea/samples/BasicDocumentDemo.java b/samples/src/main/java/com/gluonhq/richtextarea/samples/BasicDocumentDemo.java index c63db380..b6befc42 100644 --- a/samples/src/main/java/com/gluonhq/richtextarea/samples/BasicDocumentDemo.java +++ b/samples/src/main/java/com/gluonhq/richtextarea/samples/BasicDocumentDemo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Gluon + * Copyright (c) 2023, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,7 +35,6 @@ import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; import javafx.stage.Stage; import java.util.List; @@ -82,7 +81,7 @@ public void start(Stage stage) { TextDecoration textDecoration = TextDecoration.builder().presets() .fontFamily("Arial") .fontSize(20) - .foreground(Color.RED) + .foreground("red") .build(); ParagraphDecoration paragraphDecoration = ParagraphDecoration.builder().presets().build(); DecorationModel decorationModel = new DecorationModel(0, text.length(), textDecoration, paragraphDecoration); diff --git a/samples/src/main/java/com/gluonhq/richtextarea/samples/FullFeaturedDemo.java b/samples/src/main/java/com/gluonhq/richtextarea/samples/FullFeaturedDemo.java index 179f0386..a699815a 100644 --- a/samples/src/main/java/com/gluonhq/richtextarea/samples/FullFeaturedDemo.java +++ b/samples/src/main/java/com/gluonhq/richtextarea/samples/FullFeaturedDemo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Gluon + * Copyright (c) 2022, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -140,7 +140,7 @@ public class FullFeaturedDemo extends Application { private final List decorations; { - TextDecoration bold14 = TextDecoration.builder().presets().fontWeight(BOLD).fontSize(14).build(); + TextDecoration bold14 = TextDecoration.builder().presets().fontWeight(BOLD).fontSize(14).foreground("darkblue").build(); TextDecoration preset = TextDecoration.builder().presets().build(); ParagraphDecoration center63 = ParagraphDecoration.builder().presets().alignment(TextAlignment.CENTER).topInset(6).bottomInset(3).build(); ParagraphDecoration justify22 = ParagraphDecoration.builder().presets().alignment(TextAlignment.JUSTIFY).topInset(2).bottomInset(2).build(); @@ -243,12 +243,12 @@ public Double fromString(String s) { final ColorPicker textForeground = new ColorPicker(); textForeground.getStyleClass().add("foreground"); - new TextDecorateAction<>(editor, textForeground.valueProperty(), TextDecoration::getForeground, (builder, a) -> builder.foreground(a).build()); + new TextDecorateAction<>(editor, textForeground.valueProperty(), (TextDecoration textDecoration1) -> Color.web(textDecoration1.getForeground()), (builder, a) -> builder.foreground(toHexString(a)).build()); textForeground.setValue(Color.BLACK); final ColorPicker textBackground = new ColorPicker(); textBackground.getStyleClass().add("background"); - new TextDecorateAction<>(editor, textBackground.valueProperty(), TextDecoration::getBackground, (builder, a) -> builder.background(a).build()); + new TextDecorateAction<>(editor, textBackground.valueProperty(), (TextDecoration textDecoration) -> Color.web(textDecoration.getBackground()), (builder, a) -> builder.background(toHexString(a)).build()); textBackground.setValue(Color.TRANSPARENT); CheckBox editableProp = new CheckBox("Editable"); @@ -598,6 +598,13 @@ private TextDecoration getStyleFromMarker(String marker) { return builder.build(); } + private String toHexString(Color value) { + return String.format("#%02X%02X%02X%02X", (int) Math.round(value.getRed() * 255), + (int) Math.round(value.getGreen() * 255), + (int) Math.round(value.getBlue() * 255), + (int) Math.round(value.getOpacity() * 255)); + } + public static void main(String[] args) { launch(args); } diff --git a/samples/src/main/java/com/gluonhq/richtextarea/samples/HighlightDemo.java b/samples/src/main/java/com/gluonhq/richtextarea/samples/HighlightDemo.java index 9d84eda8..1bdabc97 100644 --- a/samples/src/main/java/com/gluonhq/richtextarea/samples/HighlightDemo.java +++ b/samples/src/main/java/com/gluonhq/richtextarea/samples/HighlightDemo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Gluon + * Copyright (c) 2023, 2024, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,7 +35,6 @@ import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; -import javafx.scene.paint.Color; import javafx.stage.Stage; import java.util.ArrayList; @@ -71,7 +70,7 @@ public class HighlightDemo extends Application { TextDecoration.builder().presets().fontFamily("Arial").fontWeight(BOLD).fontSize(16).build(); private static final TextDecoration mono = TextDecoration.builder().presets().fontFamily("Monospaced").fontWeight(BOLD) - .fontPosture(ITALIC).background(Color.CORNFLOWERBLUE).build(); + .fontPosture(ITALIC).background("#6495ED").build(); private static final ParagraphDecoration parPreset = ParagraphDecoration.builder().presets().build();