Skip to content

Commit fb385a6

Browse files
Merge pull request #656 from Jugen/#653-phase2
RFE implementation for #653 / #655
2 parents a98c179 + d1ce45f commit fb385a6

7 files changed

Lines changed: 239 additions & 60 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.fxmisc.richtext.api;
2+
3+
import javafx.fxml.FXML;
4+
import javafx.scene.input.MouseEvent;
5+
6+
public class FxmlTest
7+
{
8+
@FXML void testWithMouseEvent( MouseEvent ME ) {
9+
// We're just checking that a property can be set in FXML
10+
}
11+
12+
@FXML void testWithOutMouseEvent() {
13+
// We're just checking that a property can be set in FXML
14+
}
15+
}

richtextfx/src/integrationTest/java/org/fxmisc/richtext/api/FxmlTester.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import javafx.fxml.FXMLLoader;
66
import javafx.fxml.LoadException;
77
import org.fxmisc.richtext.RichTextFXTestBase;
8+
import org.fxmisc.richtext.StyleClassedTextArea;
89
import org.junit.Test;
910

1011
public class FxmlTester extends RichTextFXTestBase {
@@ -15,14 +16,21 @@ public void start(Stage stage) throws Exception {
1516
}
1617

1718
@Test
18-
public void test_fxml_construction_of_area() throws IOException, LoadException
19-
{
20-
// FxmlTest.fxml is located in resources folder:
21-
// src/integrationTest/resources/org/fxmisc/richtext/api/
22-
Object obj = FXMLLoader.load( getClass().getResource( "FxmlTest.fxml" ) );
23-
// FXMLLoader will throw a LoadException if any properties failed to be set,
24-
// so if obj is not null then all properties are guaranteed to have been set.
25-
org.junit.Assert.assertNotNull( obj );
19+
public void test_fxml_construction_of_area() throws IOException, LoadException
20+
{
21+
// FxmlTest.fxml is located in resources folder: src/integrationTest/resources/org/fxmisc/richtext/api/
22+
FXMLLoader fxml = new FXMLLoader( getClass().getResource( "FxmlTest.fxml" ) );
23+
StyleClassedTextArea area = (StyleClassedTextArea) fxml.load();
24+
// fxml.load() will throw a LoadException if any properties failed to be set,
25+
// so if 'area' is not null then all properties are guaranteed to have been set.
26+
org.junit.Assert.assertNotNull( area );
27+
28+
FxmlTest ctrl = fxml.getController();
29+
// Check that the controller was loaded and that it has the relevant
30+
// test methods which are referenced in the loaded fxml file.
31+
org.junit.Assert.assertNotNull( ctrl );
32+
ctrl.testWithMouseEvent( null );
33+
ctrl.testWithOutMouseEvent();
2634
}
2735

2836
}

richtextfx/src/integrationTest/java/org/fxmisc/richtext/mouse/ClickAndDragTests.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.fxmisc.richtext.mouse;
22

33
import com.nitorcreations.junit.runners.NestedRunner;
4+
import javafx.beans.property.SimpleIntegerProperty;
45
import javafx.geometry.Bounds;
56
import javafx.stage.Stage;
67
import org.fxmisc.richtext.InlineCssTextAreaAppTest;
@@ -70,11 +71,15 @@ public void dragging_the_mouse_does_not_select_text() {
7071
public void releasing_the_mouse_after_drag_does_nothing() {
7172
assertEquals(0, area.getCaretPosition());
7273

74+
SimpleIntegerProperty i = new SimpleIntegerProperty(0);
75+
area.setOnNewSelectionDragFinished(e -> i.set(1));
76+
7377
moveTo(firstLineOfArea())
7478
.press(PRIMARY)
7579
.dropBy(20, 0);
7680

7781
assertEquals(0, area.getCaretPosition());
82+
assertEquals(0, i.get());
7883
}
7984

8085
}
@@ -128,6 +133,19 @@ public void pressing_mouse_over_text_and_dragging_mouse_selects_text() {
128133
assertFalse(area.getSelectedText().isEmpty());
129134
}
130135

136+
@Test
137+
public void pressing_mouse_over_text_and_dragging_and_releasing_mouse_triggers_new_selection_finished() {
138+
SimpleIntegerProperty i = new SimpleIntegerProperty(0);
139+
area.setOnNewSelectionDragFinished(e -> i.set(1));
140+
moveTo(firstLineOfArea())
141+
.press(PRIMARY)
142+
.moveBy(20, 0)
143+
.release(PRIMARY);
144+
145+
assertFalse(area.getSelectedText().isEmpty());
146+
assertEquals(1, i.get());
147+
}
148+
131149
}
132150

133151
public class And_Text_Is_Selected extends InlineCssTextAreaAppTest {
@@ -178,6 +196,25 @@ public void triple_clicking_within_selected_text_selects_paragraph() {
178196
assertEquals(firstParagraph, area.getSelectedText());
179197
}
180198

199+
@Test
200+
public void single_clicking_within_selected_text_does_not_trigger_new_selection_finished() {
201+
// setup
202+
interact(() -> {
203+
area.replaceText(firstParagraph);
204+
area.selectAll();
205+
});
206+
207+
SimpleIntegerProperty i = new SimpleIntegerProperty(0);
208+
area.setOnNewSelectionDragFinished(e -> i.set(1));
209+
210+
Bounds bounds = area.getCharacterBoundsOnScreen(
211+
firstWord.length(), firstWord.length() + 1).get();
212+
213+
moveTo(bounds).clickOn(PRIMARY);
214+
215+
assertEquals(0, i.get());
216+
}
217+
181218
@Test
182219
public void single_clicking_outside_of_selected_text_moves_caret_to_that_position() {
183220
// setup
@@ -220,6 +257,25 @@ public void triple_clicking_outside_of_selected_text_selects_paragraph() {
220257
assertEquals(firstParagraph, area.getSelectedText());
221258
}
222259

260+
@Test
261+
public void single_clicking_outside_of_selected_text_does_not_trigger_new_selection_finished() {
262+
// setup
263+
interact(() -> {
264+
area.replaceText(firstParagraph + "\n" + "this is the selected text");
265+
area.selectRange(1, 0, 2, -1);
266+
});
267+
268+
SimpleIntegerProperty i = new SimpleIntegerProperty(0);
269+
area.setOnNewSelectionDragFinished(e -> i.set(1));
270+
271+
Bounds bounds = area.getCharacterBoundsOnScreen(
272+
firstWord.length(), firstWord.length() + 1).get();
273+
274+
moveTo(bounds).clickOn(PRIMARY);
275+
276+
assertEquals(0, i.get());
277+
}
278+
223279
@Test
224280
public void pressing_mouse_on_unselected_text_and_dragging_makes_new_selection() {
225281
// setup
@@ -292,6 +348,34 @@ public void pressing_mouse_on_selection_and_dragging_and_releasing_moves_selecte
292348
assertEquals(expectedText, area.getText());
293349
}
294350

351+
@Test
352+
public void pressing_mouse_on_selection_and_dragging_and_releasing_does_not_trigger_new_selection_finished() {
353+
// Linux passes; Mac/Windows uncertain
354+
// TODO: update test to see if it works on Mac & Windows
355+
run_only_on_linux();
356+
357+
// setup
358+
String twoSpaces = " ";
359+
interact(() -> {
360+
area.replaceText(firstParagraph + "\n" + twoSpaces + extraText);
361+
area.selectRange(0, firstWord.length());
362+
});
363+
364+
SimpleIntegerProperty i = new SimpleIntegerProperty(0);
365+
area.setOnNewSelectionDragFinished(e -> i.set(1));
366+
367+
Bounds letterInFirstWord = area.getCharacterBoundsOnScreen(1, 2).get();
368+
369+
int insertionPosition = firstParagraph.length() + 2;
370+
Bounds insertionBounds = area.getCharacterBoundsOnScreen(insertionPosition, insertionPosition + 1).get();
371+
372+
moveTo(letterInFirstWord)
373+
.press(PRIMARY)
374+
.dropTo(insertionBounds);
375+
376+
assertEquals(0, i.get());
377+
}
378+
295379
}
296380

297381
}
Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

3-
<?import javafx.scene.layout.VBox?>
43
<?import org.fxmisc.richtext.StyleClassedTextArea?>
54
<?import javafx.scene.control.ContextMenu?>
65
<?import javafx.scene.control.MenuItem?>
76

8-
<VBox xmlns:fx="http://javafx.com/fxml/1">
9-
<StyleClassedTextArea editable="true" wrapText="true" autoScrollOnDragDesired="true"
10-
contextMenuXOffset="12.0" contextMenuYOffset="34.0" >
11-
<contextMenu>
12-
<ContextMenu>
13-
<items><MenuItem text="Test Item" /></items>
14-
</ContextMenu>
15-
</contextMenu>
16-
</StyleClassedTextArea>
17-
</VBox>
18-
7+
<StyleClassedTextArea xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.fxmisc.richtext.api.FxmlTest"
8+
editable="true" wrapText="true" autoScrollOnDragDesired="true"
9+
onInsideSelectionMousePressReleased="#testWithMouseEvent" onOutsideSelectionMousePressed="#testWithOutMouseEvent"
10+
onNewSelectionDragFinished="#testWithMouseEvent" onSelectionDropped="#testWithOutMouseEvent"
11+
contextMenuXOffset="12.0" contextMenuYOffset="34.0" >
12+
<contextMenu>
13+
<ContextMenu>
14+
<items><MenuItem text="Test Item" /></items>
15+
</ContextMenu>
16+
</contextMenu>
17+
</StyleClassedTextArea>

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

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import javafx.css.Styleable;
3636
import javafx.css.StyleableObjectProperty;
3737
import javafx.event.Event;
38+
import javafx.event.EventHandler;
3839
import javafx.geometry.BoundingBox;
3940
import javafx.geometry.Bounds;
4041
import javafx.geometry.Insets;
@@ -182,8 +183,8 @@
182183
* <li>
183184
* <em>First (1 click count) Primary Button Mouse Pressed Events:</em>
184185
* (<code>EventPattern.mousePressed(MouseButton.PRIMARY).onlyIf(e -&gt; e.getClickCount() == 1)</code>).
185-
* Do not override. Instead, use {@link #onOutsideSelectionMousePress},
186-
* {@link #onInsideSelectionMousePressRelease}, or see next item.
186+
* Do not override. Instead, use {@link #onOutsideSelectionMousePressed},
187+
* {@link #onInsideSelectionMousePressReleased}, or see next item.
187188
* </li>
188189
* <li>(
189190
* <em>All Other Mouse Pressed Events (e.g., Primary with 2+ click count):</em>
@@ -223,7 +224,7 @@
223224
* <li>
224225
* <em>Primary-Button-only Mouse Released Events:</em>
225226
* (<code>EventPattern.mouseReleased().onlyIf(e -&gt; e.getButton() == MouseButton.PRIMARY &amp;&amp; !e.isMiddleButtonDown() &amp;&amp; !e.isSecondaryButtonDown())</code>).
226-
* Do not override. Instead, use {@link #onNewSelectionDragEnd}, {@link #onSelectionDrop}, or see next item.
227+
* Do not override. Instead, use {@link #onNewSelectionDragFinished}, {@link #onSelectionDropped}, or see next item.
227228
* </li>
228229
* <li>
229230
* <em>All other Mouse Released Events:</em>
@@ -348,7 +349,7 @@ protected void invalidated() {
348349
// Don't remove as FXMLLoader doesn't recognise default methods !
349350
@Override public void setWrapText(boolean value) { wrapText.set(value); }
350351
@Override public boolean isWrapText() { return wrapText.get(); }
351-
352+
352353
// undo manager
353354
private UndoManager undoManager;
354355
@Override public UndoManager getUndoManager() { return undoManager; }
@@ -406,41 +407,46 @@ protected void invalidated() {
406407
* *
407408
* ********************************************************************** */
408409

409-
private final ObjectProperty<Consumer<MouseEvent>> onOutsideSelectionMousePress = new SimpleObjectProperty<>(e -> {
410-
CharacterHit hit = hit(e.getX(), e.getY());
411-
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
410+
@Override public final EventHandler<MouseEvent> getOnOutsideSelectionMousePressed() { return onOutsideSelectionMousePressed.get(); }
411+
@Override public final void setOnOutsideSelectionMousePressed(EventHandler<MouseEvent> handler) { onOutsideSelectionMousePressed.set( handler ); }
412+
@Override public final ObjectProperty<EventHandler<MouseEvent>> onOutsideSelectionMousePressedProperty() { return onOutsideSelectionMousePressed; }
413+
private final ObjectProperty<EventHandler<MouseEvent>> onOutsideSelectionMousePressed = new SimpleObjectProperty<>( e -> {
414+
onOutsideSelectionMousePressProperty().get().accept(e);
412415
});
413-
@Override public final ObjectProperty<Consumer<MouseEvent>> onOutsideSelectionMousePressProperty() { return onOutsideSelectionMousePress; }
414416

415-
private final ObjectProperty<Consumer<MouseEvent>> onInsideSelectionMousePressRelease = new SimpleObjectProperty<>(e -> {
416-
CharacterHit hit = hit(e.getX(), e.getY());
417-
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
417+
@Override public final EventHandler<MouseEvent> getOnInsideSelectionMousePressReleased() { return onInsideSelectionMousePressReleased.get(); }
418+
@Override public final void setOnInsideSelectionMousePressReleased(EventHandler<MouseEvent> handler) { onInsideSelectionMousePressReleased.set( handler ); }
419+
@Override public final ObjectProperty<EventHandler<MouseEvent>> onInsideSelectionMousePressReleasedProperty() { return onInsideSelectionMousePressReleased; }
420+
private final ObjectProperty<EventHandler<MouseEvent>> onInsideSelectionMousePressReleased = new SimpleObjectProperty<>( e -> {
421+
onInsideSelectionMousePressReleaseProperty().get().accept(e);
418422
});
419-
@Override public final ObjectProperty<Consumer<MouseEvent>> onInsideSelectionMousePressReleaseProperty() { return onInsideSelectionMousePressRelease; }
420423

421424
private final ObjectProperty<Consumer<Point2D>> onNewSelectionDrag = new SimpleObjectProperty<>(p -> {
422425
CharacterHit hit = hit(p.getX(), p.getY());
423426
moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST);
424427
});
425428
@Override public final ObjectProperty<Consumer<Point2D>> onNewSelectionDragProperty() { return onNewSelectionDrag; }
426429

427-
private final ObjectProperty<Consumer<MouseEvent>> onNewSelectionDragEnd = new SimpleObjectProperty<>(e -> {
430+
@Override public final EventHandler<MouseEvent> getOnNewSelectionDragFinished() { return onNewSelectionDragFinished.get(); }
431+
@Override public final void setOnNewSelectionDragFinished(EventHandler<MouseEvent> handler) { onNewSelectionDragFinished.set( handler ); }
432+
@Override public final ObjectProperty<EventHandler<MouseEvent>> onNewSelectionDragFinishedProperty() { return onNewSelectionDragFinished; }
433+
private final ObjectProperty<EventHandler<MouseEvent>> onNewSelectionDragFinished = new SimpleObjectProperty<>( e -> {
428434
CharacterHit hit = hit(e.getX(), e.getY());
429435
moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST);
430436
});
431-
@Override public final ObjectProperty<Consumer<MouseEvent>> onNewSelectionDragEndProperty() { return onNewSelectionDragEnd; }
432437

433438
private final ObjectProperty<Consumer<Point2D>> onSelectionDrag = new SimpleObjectProperty<>(p -> {
434439
CharacterHit hit = hit(p.getX(), p.getY());
435440
displaceCaret(hit.getInsertionIndex());
436441
});
437442
@Override public final ObjectProperty<Consumer<Point2D>> onSelectionDragProperty() { return onSelectionDrag; }
438443

439-
private final ObjectProperty<Consumer<MouseEvent>> onSelectionDrop = new SimpleObjectProperty<>(e -> {
440-
CharacterHit hit = hit(e.getX(), e.getY());
441-
moveSelectedText(hit.getInsertionIndex());
444+
@Override public final EventHandler<MouseEvent> getOnSelectionDropped() { return onSelectionDropped.get(); }
445+
@Override public final void setOnSelectionDropped(EventHandler<MouseEvent> handler) { onSelectionDropped.set( handler ); }
446+
@Override public final ObjectProperty<EventHandler<MouseEvent>> onSelectionDroppedProperty() { return onSelectionDropped; }
447+
private final ObjectProperty<EventHandler<MouseEvent>> onSelectionDropped = new SimpleObjectProperty<>( e -> {
448+
onSelectionDropProperty().get().accept(e);
442449
});
443-
@Override public final ObjectProperty<Consumer<MouseEvent>> onSelectionDropProperty() { return onSelectionDrop; }
444450

445451
// not a hook, but still plays a part in the default mouse behavior
446452
private final BooleanProperty autoScrollOnDragDesired = new SimpleBooleanProperty(true);
@@ -1483,4 +1489,37 @@ private void suspendVisibleParsWhile(Runnable runnable) {
14831489
return CSS_META_DATA_LIST;
14841490
}
14851491

1492+
1493+
// Note: this code should be moved to `onOutsideSelectionMousePressed` property
1494+
// in the next major release before removing this deprecated field
1495+
@Deprecated private final ObjectProperty<Consumer<MouseEvent>> onOutsideSelectionMousePress = new SimpleObjectProperty<>( e -> {
1496+
CharacterHit hit = hit(e.getX(), e.getY());
1497+
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
1498+
});
1499+
@Deprecated
1500+
@Override public final ObjectProperty<Consumer<MouseEvent>> onOutsideSelectionMousePressProperty() {
1501+
return onOutsideSelectionMousePress;
1502+
}
1503+
1504+
// Note: this code should be moved to `onInsideSelectionMouseReleased` property
1505+
// in the next major release before removing this deprecated field
1506+
@Deprecated private final ObjectProperty<Consumer<MouseEvent>> onInsideSelectionMousePressRelease = new SimpleObjectProperty<>( e -> {
1507+
CharacterHit hit = hit(e.getX(), e.getY());
1508+
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
1509+
});
1510+
@Deprecated
1511+
@Override public final ObjectProperty<Consumer<MouseEvent>> onInsideSelectionMousePressReleaseProperty() {
1512+
return onInsideSelectionMousePressRelease;
1513+
}
1514+
1515+
// Note: this code should be moved to `onSelectionDropped` property
1516+
// in the next major release before removing this deprecated field
1517+
@Deprecated private final ObjectProperty<Consumer<MouseEvent>> onSelectionDrop = new SimpleObjectProperty<>( e -> {
1518+
CharacterHit hit = hit(e.getX(), e.getY());
1519+
moveSelectedText(hit.getInsertionIndex());
1520+
});
1521+
@Deprecated
1522+
@Override public final ObjectProperty<Consumer<MouseEvent>> onSelectionDropProperty() {
1523+
return onSelectionDrop;
1524+
}
14861525
}

0 commit comments

Comments
 (0)