use of org.fxmisc.richtext.model.Paragraph in project RichTextFX by FXMisc.
the class RichText method start.
@Override
public void start(Stage primaryStage) {
mainStage = primaryStage;
Button loadBtn = createButton("loadfile", this::loadDocument, "Load document.\n\n" + "Note: the demo will load only previously-saved \"" + RTFX_FILE_EXTENSION + "\" files. " + "This file format is abitrary and may change across versions.");
Button saveBtn = createButton("savefile", this::saveDocument, "Save document.\n\n" + "Note: the demo will save the area's content to a \"" + RTFX_FILE_EXTENSION + "\" file. " + "This file format is abitrary and may change across versions.");
CheckBox wrapToggle = new CheckBox("Wrap");
wrapToggle.setSelected(true);
area.wrapTextProperty().bind(wrapToggle.selectedProperty());
Button undoBtn = createButton("undo", area::undo, "Undo");
Button redoBtn = createButton("redo", area::redo, "Redo");
Button cutBtn = createButton("cut", area::cut, "Cut");
Button copyBtn = createButton("copy", area::copy, "Copy");
Button pasteBtn = createButton("paste", area::paste, "Paste");
Button boldBtn = createButton("bold", this::toggleBold, "Bold");
Button italicBtn = createButton("italic", this::toggleItalic, "Italic");
Button underlineBtn = createButton("underline", this::toggleUnderline, "Underline");
Button strikeBtn = createButton("strikethrough", this::toggleStrikethrough, "Strike Trough");
Button insertImageBtn = createButton("insertimage", this::insertImage, "Insert Image");
ToggleGroup alignmentGrp = new ToggleGroup();
ToggleButton alignLeftBtn = createToggleButton(alignmentGrp, "align-left", this::alignLeft, "Align left");
ToggleButton alignCenterBtn = createToggleButton(alignmentGrp, "align-center", this::alignCenter, "Align center");
ToggleButton alignRightBtn = createToggleButton(alignmentGrp, "align-right", this::alignRight, "Align right");
ToggleButton alignJustifyBtn = createToggleButton(alignmentGrp, "align-justify", this::alignJustify, "Justify");
ColorPicker paragraphBackgroundPicker = new ColorPicker();
ComboBox<Integer> sizeCombo = new ComboBox<>(FXCollections.observableArrayList(5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28, 32, 36, 40, 48, 56, 64, 72));
sizeCombo.getSelectionModel().select(Integer.valueOf(12));
sizeCombo.setTooltip(new Tooltip("Font size"));
ComboBox<String> familyCombo = new ComboBox<>(FXCollections.observableList(Font.getFamilies()));
familyCombo.getSelectionModel().select("Serif");
familyCombo.setTooltip(new Tooltip("Font family"));
ColorPicker textColorPicker = new ColorPicker(Color.BLACK);
ColorPicker backgroundColorPicker = new ColorPicker();
paragraphBackgroundPicker.setTooltip(new Tooltip("Paragraph background"));
textColorPicker.setTooltip(new Tooltip("Text color"));
backgroundColorPicker.setTooltip(new Tooltip("Text background"));
paragraphBackgroundPicker.valueProperty().addListener((o, old, color) -> updateParagraphBackground(color));
sizeCombo.setOnAction(evt -> updateFontSize(sizeCombo.getValue()));
familyCombo.setOnAction(evt -> updateFontFamily(familyCombo.getValue()));
textColorPicker.valueProperty().addListener((o, old, color) -> updateTextColor(color));
backgroundColorPicker.valueProperty().addListener((o, old, color) -> updateBackgroundColor(color));
undoBtn.disableProperty().bind(area.undoAvailableProperty().map(x -> !x));
redoBtn.disableProperty().bind(area.redoAvailableProperty().map(x -> !x));
BooleanBinding selectionEmpty = new BooleanBinding() {
{
bind(area.selectionProperty());
}
@Override
protected boolean computeValue() {
return area.getSelection().getLength() == 0;
}
};
cutBtn.disableProperty().bind(selectionEmpty);
copyBtn.disableProperty().bind(selectionEmpty);
area.beingUpdatedProperty().addListener((o, old, beingUpdated) -> {
if (!beingUpdated) {
boolean bold, italic, underline, strike;
Integer fontSize;
String fontFamily;
Color textColor;
Color backgroundColor;
IndexRange selection = area.getSelection();
if (selection.getLength() != 0) {
StyleSpans<TextStyle> styles = area.getStyleSpans(selection);
bold = styles.styleStream().anyMatch(s -> s.bold.orElse(false));
italic = styles.styleStream().anyMatch(s -> s.italic.orElse(false));
underline = styles.styleStream().anyMatch(s -> s.underline.orElse(false));
strike = styles.styleStream().anyMatch(s -> s.strikethrough.orElse(false));
int[] sizes = styles.styleStream().mapToInt(s -> s.fontSize.orElse(-1)).distinct().toArray();
fontSize = sizes.length == 1 ? sizes[0] : -1;
String[] families = styles.styleStream().map(s -> s.fontFamily.orElse(null)).distinct().toArray(String[]::new);
fontFamily = families.length == 1 ? families[0] : null;
Color[] colors = styles.styleStream().map(s -> s.textColor.orElse(null)).distinct().toArray(Color[]::new);
textColor = colors.length == 1 ? colors[0] : null;
Color[] backgrounds = styles.styleStream().map(s -> s.backgroundColor.orElse(null)).distinct().toArray(i -> new Color[i]);
backgroundColor = backgrounds.length == 1 ? backgrounds[0] : null;
} else {
int p = area.getCurrentParagraph();
int col = area.getCaretColumn();
TextStyle style = area.getStyleAtPosition(p, col);
bold = style.bold.orElse(false);
italic = style.italic.orElse(false);
underline = style.underline.orElse(false);
strike = style.strikethrough.orElse(false);
fontSize = style.fontSize.orElse(-1);
fontFamily = style.fontFamily.orElse(null);
textColor = style.textColor.orElse(null);
backgroundColor = style.backgroundColor.orElse(null);
}
int startPar = area.offsetToPosition(selection.getStart(), Forward).getMajor();
int endPar = area.offsetToPosition(selection.getEnd(), Backward).getMajor();
List<Paragraph<ParStyle, Either<String, LinkedImage>, TextStyle>> pars = area.getParagraphs().subList(startPar, endPar + 1);
@SuppressWarnings("unchecked") Optional<TextAlignment>[] alignments = pars.stream().map(p -> p.getParagraphStyle().alignment).distinct().toArray(Optional[]::new);
Optional<TextAlignment> alignment = alignments.length == 1 ? alignments[0] : Optional.empty();
@SuppressWarnings("unchecked") Optional<Color>[] paragraphBackgrounds = pars.stream().map(p -> p.getParagraphStyle().backgroundColor).distinct().toArray(Optional[]::new);
Optional<Color> paragraphBackground = paragraphBackgrounds.length == 1 ? paragraphBackgrounds[0] : Optional.empty();
updatingToolbar.suspendWhile(() -> {
if (bold) {
if (!boldBtn.getStyleClass().contains("pressed")) {
boldBtn.getStyleClass().add("pressed");
}
} else {
boldBtn.getStyleClass().remove("pressed");
}
if (italic) {
if (!italicBtn.getStyleClass().contains("pressed")) {
italicBtn.getStyleClass().add("pressed");
}
} else {
italicBtn.getStyleClass().remove("pressed");
}
if (underline) {
if (!underlineBtn.getStyleClass().contains("pressed")) {
underlineBtn.getStyleClass().add("pressed");
}
} else {
underlineBtn.getStyleClass().remove("pressed");
}
if (strike) {
if (!strikeBtn.getStyleClass().contains("pressed")) {
strikeBtn.getStyleClass().add("pressed");
}
} else {
strikeBtn.getStyleClass().remove("pressed");
}
if (alignment.isPresent()) {
TextAlignment al = alignment.get();
switch(al) {
case LEFT:
alignmentGrp.selectToggle(alignLeftBtn);
break;
case CENTER:
alignmentGrp.selectToggle(alignCenterBtn);
break;
case RIGHT:
alignmentGrp.selectToggle(alignRightBtn);
break;
case JUSTIFY:
alignmentGrp.selectToggle(alignJustifyBtn);
break;
}
} else {
alignmentGrp.selectToggle(null);
}
paragraphBackgroundPicker.setValue(paragraphBackground.orElse(null));
if (fontSize != -1) {
sizeCombo.getSelectionModel().select(fontSize);
} else {
sizeCombo.getSelectionModel().clearSelection();
}
if (fontFamily != null) {
familyCombo.getSelectionModel().select(fontFamily);
} else {
familyCombo.getSelectionModel().clearSelection();
}
if (textColor != null) {
textColorPicker.setValue(textColor);
}
backgroundColorPicker.setValue(backgroundColor);
});
}
});
ToolBar toolBar1 = new ToolBar(loadBtn, saveBtn, new Separator(Orientation.VERTICAL), wrapToggle, new Separator(Orientation.VERTICAL), undoBtn, redoBtn, new Separator(Orientation.VERTICAL), cutBtn, copyBtn, pasteBtn, new Separator(Orientation.VERTICAL), boldBtn, italicBtn, underlineBtn, strikeBtn, new Separator(Orientation.VERTICAL), alignLeftBtn, alignCenterBtn, alignRightBtn, alignJustifyBtn, new Separator(Orientation.VERTICAL), insertImageBtn, new Separator(Orientation.VERTICAL), paragraphBackgroundPicker);
ToolBar toolBar2 = new ToolBar(sizeCombo, familyCombo, textColorPicker, backgroundColorPicker);
VirtualizedScrollPane<GenericStyledArea<ParStyle, Either<String, LinkedImage>, TextStyle>> vsPane = new VirtualizedScrollPane<>(area);
VBox vbox = new VBox();
VBox.setVgrow(vsPane, Priority.ALWAYS);
vbox.getChildren().addAll(toolBar1, toolBar2, vsPane);
Scene scene = new Scene(vbox, 600, 400);
scene.getStylesheets().add(RichText.class.getResource("rich-text.css").toExternalForm());
primaryStage.setScene(scene);
area.requestFocus();
primaryStage.setTitle("Rich Text Demo");
primaryStage.show();
}
use of org.fxmisc.richtext.model.Paragraph in project RichTextFX by FXMisc.
the class GenericStyledArea method getCharacterBoundsOnScreen.
@Override
public Optional<Bounds> getCharacterBoundsOnScreen(int from, int to) {
if (from < 0) {
throw new IllegalArgumentException("From is negative: " + from);
}
if (from > to) {
throw new IllegalArgumentException(String.format("From is greater than to. from=%s to=%s", from, to));
}
if (to > getLength()) {
throw new IllegalArgumentException(String.format("To is greater than area's length. length=%s, to=%s", getLength(), to));
}
// no bounds exist if range is just a newline character
if (getText(from, to).equals("\n")) {
return Optional.empty();
}
// if 'from' is the newline character at the end of a multi-line paragraph, it returns a Bounds that whose
// minX & minY are the minX and minY of the paragraph itself, not the newline character. So, ignore it.
int realFrom = getText(from, from + 1).equals("\n") ? from + 1 : from;
Position startPosition = offsetToPosition(realFrom, Bias.Forward);
int startRow = startPosition.getMajor();
Position endPosition = startPosition.offsetBy(to - realFrom, Bias.Forward);
int endRow = endPosition.getMajor();
if (startRow == endRow) {
return getRangeBoundsOnScreen(startRow, startPosition.getMinor(), endPosition.getMinor());
} else {
Optional<Bounds> rangeBounds = getRangeBoundsOnScreen(startRow, startPosition.getMinor(), getParagraph(startRow).length());
for (int i = startRow + 1; i <= endRow; i++) {
Optional<Bounds> nextLineBounds = getRangeBoundsOnScreen(i, 0, i == endRow ? endPosition.getMinor() : getParagraph(i).length());
if (nextLineBounds.isPresent()) {
if (rangeBounds.isPresent()) {
Bounds lineBounds = nextLineBounds.get();
rangeBounds = rangeBounds.map(b -> {
double minX = Math.min(b.getMinX(), lineBounds.getMinX());
double minY = Math.min(b.getMinY(), lineBounds.getMinY());
double maxX = Math.max(b.getMaxX(), lineBounds.getMaxX());
double maxY = Math.max(b.getMaxY(), lineBounds.getMaxY());
return new BoundingBox(minX, minY, maxX - minX, maxY - minY);
});
} else {
rangeBounds = nextLineBounds;
}
}
}
return rangeBounds;
}
}
use of org.fxmisc.richtext.model.Paragraph in project RichTextFX by FXMisc.
the class GenericStyledArea method showCaretAtBottom.
void showCaretAtBottom() {
int parIdx = getCurrentParagraph();
Cell<Paragraph<PS, SEG, S>, ParagraphBox<PS, SEG, S>> cell = virtualFlow.getCell(parIdx);
Bounds caretBounds = cell.getNode().getCaretBounds(caretSelectionBind.getUnderlyingCaret());
double y = caretBounds.getMaxY();
suspendVisibleParsWhile(() -> virtualFlow.showAtOffset(parIdx, getViewportHeight() - y));
}
use of org.fxmisc.richtext.model.Paragraph in project RichTextFX by FXMisc.
the class GenericStyledArea method followCaret.
/**
* Assumes this method is called within a {@link #suspendVisibleParsWhile(Runnable)} block
*/
private void followCaret() {
int parIdx = getCurrentParagraph();
Cell<Paragraph<PS, SEG, S>, ParagraphBox<PS, SEG, S>> cell = virtualFlow.getCell(parIdx);
Bounds caretBounds = cell.getNode().getCaretBounds(caretSelectionBind.getUnderlyingCaret());
double graphicWidth = cell.getNode().getGraphicPrefWidth();
Bounds region = extendLeft(caretBounds, graphicWidth);
virtualFlow.show(parIdx, region);
}
use of org.fxmisc.richtext.model.Paragraph in project RichTextFX by FXMisc.
the class GenericStyledArea method currentLine.
/* ********************************************************************** *
* *
* Package-Private methods *
* *
* ********************************************************************** */
/**
* Returns the current line as a two-level index.
* The major number is the paragraph index, the minor
* number is the line number within the paragraph.
*
* <p>This method has a side-effect of bringing the current
* paragraph to the viewport if it is not already visible.
*/
TwoDimensional.Position currentLine() {
int parIdx = getCurrentParagraph();
Cell<Paragraph<PS, SEG, S>, ParagraphBox<PS, SEG, S>> cell = virtualFlow.getCell(parIdx);
int lineIdx = cell.getNode().getCurrentLineIndex(caretSelectionBind.getUnderlyingCaret());
return paragraphLineNavigator.position(parIdx, lineIdx);
}
Aggregations