Skip to content

Commit 9832e81

Browse files
Merge pull request #564 from JordanMartinez/selection
Enhancement: Selecting a newline char selects the rest of the line
2 parents ba9b23d + 33ae95a commit 9832e81

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,8 @@ public StyledDocument<PS, SEG, S> subDocument(int paragraphIndex) {
878878
}
879879

880880
/**
881-
* Returns the selection range in the given paragraph.
881+
* Returns the selection range in the given paragraph. Note: this method will return
882+
* {@code IndexRange(start, paragraph.length() + 1)} when the selection includes a newline character.
882883
*/
883884
public IndexRange getParagraphSelection(int paragraph) {
884885
return getParagraphSelection(caretSelectionBind, paragraph);
@@ -893,7 +894,7 @@ public IndexRange getParagraphSelection(Selection selection, int paragraph) {
893894
}
894895

895896
int start = paragraph == startPar ? selection.getStartColumnPosition() : 0;
896-
int end = paragraph == endPar ? selection.getEndColumnPosition() : getParagraphLength(paragraph);
897+
int end = paragraph == endPar ? selection.getEndColumnPosition() : getParagraphLength(paragraph) + 1;
897898

898899
// force rangeProperty() to be valid
899900
selection.getRange();

richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import javafx.scene.control.IndexRange;
2222
import javafx.scene.paint.Color;
2323
import javafx.scene.paint.Paint;
24+
import javafx.scene.shape.LineTo;
25+
import javafx.scene.shape.MoveTo;
2426
import javafx.scene.shape.Path;
2527
import javafx.scene.shape.PathElement;
2628
import javafx.scene.shape.StrokeLineCap;
@@ -201,7 +203,7 @@ public Bounds getCaretBoundsOnScreen() {
201203

202204
public Bounds getRangeBoundsOnScreen(int from, int to) {
203205
layout(); // ensure layout, is a no-op if not dirty
204-
PathElement[] rangeShape = getRangeShape(from, to);
206+
PathElement[] rangeShape = getRangeShapeSafely(from, to);
205207

206208
// switch out shapes to calculate the bounds on screen
207209
// Must take a copy of the list contents, not just a reference:
@@ -248,8 +250,60 @@ private void updateCaretShape() {
248250
private void updateSelectionShape() {
249251
int start = selection.get().getStart();
250252
int end = selection.get().getEnd();
251-
PathElement[] shape = getRangeShape(start, end);
252-
selectionShape.getElements().setAll(shape);
253+
selectionShape.getElements().setAll(getRangeShapeSafely(start, end));
254+
}
255+
256+
/**
257+
* Gets the range shape for the given positions within the text, including the newline character, if range
258+
* defined by the start/end arguments include it.
259+
*
260+
* @param start the start position of the range shape
261+
* @param end the end position of the range shape. If {@code end == paragraph.length() + 1}, the newline character
262+
* will be included in the selection by selecting the rest of the line
263+
*/
264+
private PathElement[] getRangeShapeSafely(int start, int end) {
265+
PathElement[] shape;
266+
if (end <= paragraph.length()) {
267+
// selection w/o newline char
268+
shape = getRangeShape(start, end);
269+
} else {
270+
// Selection includes a newline character.
271+
if (paragraph.length() == 0) {
272+
// empty paragraph
273+
shape = createRectangle(getLayoutX(), getLayoutY(), getWidth(), getHeight());
274+
} else if (start == paragraph.length()) {
275+
// selecting only the newline char
276+
277+
// calculate the bounds of the last character
278+
shape = getRangeShape(start - 1, start);
279+
LineTo lineToTopRight = (LineTo) shape[shape.length - 4];
280+
shape = createRectangle(lineToTopRight.getX(), lineToTopRight.getY(), getWidth(), getHeight());
281+
} else {
282+
shape = getRangeShape(start, paragraph.length());
283+
// Since this might be a wrapped multi-line paragraph,
284+
// there may be multiple groups of (1 MoveTo, 3 LineTo objects) for each line:
285+
// MoveTo(topLeft), LineTo(topRight), LineTo(bottomRight), LineTo(bottomLeft)
286+
287+
// We only need to adjust the top right and bottom right corners to extend to the
288+
// width/height of the line, simulating a full line selection.
289+
int length = shape.length;
290+
int bottomRightIndex = length - 3;
291+
int topRightIndex = bottomRightIndex - 1;
292+
LineTo lineToTopRight = (LineTo) shape[topRightIndex];
293+
shape[topRightIndex] = new LineTo(getWidth(), lineToTopRight.getY());
294+
shape[bottomRightIndex] = new LineTo(getWidth(), getHeight());
295+
}
296+
}
297+
return shape;
298+
}
299+
300+
private PathElement[] createRectangle(double topLeftX, double topLeftY, double bottomRightX, double bottomRightY) {
301+
return new PathElement[] {
302+
new MoveTo(topLeftX, topLeftY),
303+
new LineTo(bottomRightX, topLeftY),
304+
new LineTo(bottomRightX, bottomRightY),
305+
new LineTo(topLeftX, bottomRightY)
306+
};
253307
}
254308

255309
private void updateBackgroundShapes() {

0 commit comments

Comments
 (0)