Skip to content

Commit

Permalink
Merge pull request #656 from Jugen/#653-phase2
Browse files Browse the repository at this point in the history
RFE implementation for #653 / #655
  • Loading branch information
JordanMartinez authored Feb 9, 2018
2 parents a98c179 + d1ce45f commit fb385a6
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.fxmisc.richtext.api;

import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;

public class FxmlTest
{
@FXML void testWithMouseEvent( MouseEvent ME ) {
// We're just checking that a property can be set in FXML
}

@FXML void testWithOutMouseEvent() {
// We're just checking that a property can be set in FXML
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import javafx.fxml.FXMLLoader;
import javafx.fxml.LoadException;
import org.fxmisc.richtext.RichTextFXTestBase;
import org.fxmisc.richtext.StyleClassedTextArea;
import org.junit.Test;

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

@Test
public void test_fxml_construction_of_area() throws IOException, LoadException
{
// FxmlTest.fxml is located in resources folder:
// src/integrationTest/resources/org/fxmisc/richtext/api/
Object obj = FXMLLoader.load( getClass().getResource( "FxmlTest.fxml" ) );
// FXMLLoader will throw a LoadException if any properties failed to be set,
// so if obj is not null then all properties are guaranteed to have been set.
org.junit.Assert.assertNotNull( obj );
public void test_fxml_construction_of_area() throws IOException, LoadException
{
// FxmlTest.fxml is located in resources folder: src/integrationTest/resources/org/fxmisc/richtext/api/
FXMLLoader fxml = new FXMLLoader( getClass().getResource( "FxmlTest.fxml" ) );
StyleClassedTextArea area = (StyleClassedTextArea) fxml.load();
// fxml.load() will throw a LoadException if any properties failed to be set,
// so if 'area' is not null then all properties are guaranteed to have been set.
org.junit.Assert.assertNotNull( area );

FxmlTest ctrl = fxml.getController();
// Check that the controller was loaded and that it has the relevant
// test methods which are referenced in the loaded fxml file.
org.junit.Assert.assertNotNull( ctrl );
ctrl.testWithMouseEvent( null );
ctrl.testWithOutMouseEvent();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.fxmisc.richtext.mouse;

import com.nitorcreations.junit.runners.NestedRunner;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Bounds;
import javafx.stage.Stage;
import org.fxmisc.richtext.InlineCssTextAreaAppTest;
Expand Down Expand Up @@ -70,11 +71,15 @@ public void dragging_the_mouse_does_not_select_text() {
public void releasing_the_mouse_after_drag_does_nothing() {
assertEquals(0, area.getCaretPosition());

SimpleIntegerProperty i = new SimpleIntegerProperty(0);
area.setOnNewSelectionDragFinished(e -> i.set(1));

moveTo(firstLineOfArea())
.press(PRIMARY)
.dropBy(20, 0);

assertEquals(0, area.getCaretPosition());
assertEquals(0, i.get());
}

}
Expand Down Expand Up @@ -128,6 +133,19 @@ public void pressing_mouse_over_text_and_dragging_mouse_selects_text() {
assertFalse(area.getSelectedText().isEmpty());
}

@Test
public void pressing_mouse_over_text_and_dragging_and_releasing_mouse_triggers_new_selection_finished() {
SimpleIntegerProperty i = new SimpleIntegerProperty(0);
area.setOnNewSelectionDragFinished(e -> i.set(1));
moveTo(firstLineOfArea())
.press(PRIMARY)
.moveBy(20, 0)
.release(PRIMARY);

assertFalse(area.getSelectedText().isEmpty());
assertEquals(1, i.get());
}

}

public class And_Text_Is_Selected extends InlineCssTextAreaAppTest {
Expand Down Expand Up @@ -178,6 +196,25 @@ public void triple_clicking_within_selected_text_selects_paragraph() {
assertEquals(firstParagraph, area.getSelectedText());
}

@Test
public void single_clicking_within_selected_text_does_not_trigger_new_selection_finished() {
// setup
interact(() -> {
area.replaceText(firstParagraph);
area.selectAll();
});

SimpleIntegerProperty i = new SimpleIntegerProperty(0);
area.setOnNewSelectionDragFinished(e -> i.set(1));

Bounds bounds = area.getCharacterBoundsOnScreen(
firstWord.length(), firstWord.length() + 1).get();

moveTo(bounds).clickOn(PRIMARY);

assertEquals(0, i.get());
}

@Test
public void single_clicking_outside_of_selected_text_moves_caret_to_that_position() {
// setup
Expand Down Expand Up @@ -220,6 +257,25 @@ public void triple_clicking_outside_of_selected_text_selects_paragraph() {
assertEquals(firstParagraph, area.getSelectedText());
}

@Test
public void single_clicking_outside_of_selected_text_does_not_trigger_new_selection_finished() {
// setup
interact(() -> {
area.replaceText(firstParagraph + "\n" + "this is the selected text");
area.selectRange(1, 0, 2, -1);
});

SimpleIntegerProperty i = new SimpleIntegerProperty(0);
area.setOnNewSelectionDragFinished(e -> i.set(1));

Bounds bounds = area.getCharacterBoundsOnScreen(
firstWord.length(), firstWord.length() + 1).get();

moveTo(bounds).clickOn(PRIMARY);

assertEquals(0, i.get());
}

@Test
public void pressing_mouse_on_unselected_text_and_dragging_makes_new_selection() {
// setup
Expand Down Expand Up @@ -292,6 +348,34 @@ public void pressing_mouse_on_selection_and_dragging_and_releasing_moves_selecte
assertEquals(expectedText, area.getText());
}

@Test
public void pressing_mouse_on_selection_and_dragging_and_releasing_does_not_trigger_new_selection_finished() {
// Linux passes; Mac/Windows uncertain
// TODO: update test to see if it works on Mac & Windows
run_only_on_linux();

// setup
String twoSpaces = " ";
interact(() -> {
area.replaceText(firstParagraph + "\n" + twoSpaces + extraText);
area.selectRange(0, firstWord.length());
});

SimpleIntegerProperty i = new SimpleIntegerProperty(0);
area.setOnNewSelectionDragFinished(e -> i.set(1));

Bounds letterInFirstWord = area.getCharacterBoundsOnScreen(1, 2).get();

int insertionPosition = firstParagraph.length() + 2;
Bounds insertionBounds = area.getCharacterBoundsOnScreen(insertionPosition, insertionPosition + 1).get();

moveTo(letterInFirstWord)
.press(PRIMARY)
.dropTo(insertionBounds);

assertEquals(0, i.get());
}

}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import org.fxmisc.richtext.StyleClassedTextArea?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.MenuItem?>

<VBox xmlns:fx="http://javafx.com/fxml/1">
<StyleClassedTextArea editable="true" wrapText="true" autoScrollOnDragDesired="true"
contextMenuXOffset="12.0" contextMenuYOffset="34.0" >
<contextMenu>
<ContextMenu>
<items><MenuItem text="Test Item" /></items>
</ContextMenu>
</contextMenu>
</StyleClassedTextArea>
</VBox>

<StyleClassedTextArea xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.fxmisc.richtext.api.FxmlTest"
editable="true" wrapText="true" autoScrollOnDragDesired="true"
onInsideSelectionMousePressReleased="#testWithMouseEvent" onOutsideSelectionMousePressed="#testWithOutMouseEvent"
onNewSelectionDragFinished="#testWithMouseEvent" onSelectionDropped="#testWithOutMouseEvent"
contextMenuXOffset="12.0" contextMenuYOffset="34.0" >
<contextMenu>
<ContextMenu>
<items><MenuItem text="Test Item" /></items>
</ContextMenu>
</contextMenu>
</StyleClassedTextArea>
75 changes: 57 additions & 18 deletions richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
Expand Down Expand Up @@ -182,8 +183,8 @@
* <li>
* <em>First (1 click count) Primary Button Mouse Pressed Events:</em>
* (<code>EventPattern.mousePressed(MouseButton.PRIMARY).onlyIf(e -&gt; e.getClickCount() == 1)</code>).
* Do not override. Instead, use {@link #onOutsideSelectionMousePress},
* {@link #onInsideSelectionMousePressRelease}, or see next item.
* Do not override. Instead, use {@link #onOutsideSelectionMousePressed},
* {@link #onInsideSelectionMousePressReleased}, or see next item.
* </li>
* <li>(
* <em>All Other Mouse Pressed Events (e.g., Primary with 2+ click count):</em>
Expand Down Expand Up @@ -223,7 +224,7 @@
* <li>
* <em>Primary-Button-only Mouse Released Events:</em>
* (<code>EventPattern.mouseReleased().onlyIf(e -&gt; e.getButton() == MouseButton.PRIMARY &amp;&amp; !e.isMiddleButtonDown() &amp;&amp; !e.isSecondaryButtonDown())</code>).
* Do not override. Instead, use {@link #onNewSelectionDragEnd}, {@link #onSelectionDrop}, or see next item.
* Do not override. Instead, use {@link #onNewSelectionDragFinished}, {@link #onSelectionDropped}, or see next item.
* </li>
* <li>
* <em>All other Mouse Released Events:</em>
Expand Down Expand Up @@ -348,7 +349,7 @@ protected void invalidated() {
// Don't remove as FXMLLoader doesn't recognise default methods !
@Override public void setWrapText(boolean value) { wrapText.set(value); }
@Override public boolean isWrapText() { return wrapText.get(); }

// undo manager
private UndoManager undoManager;
@Override public UndoManager getUndoManager() { return undoManager; }
Expand Down Expand Up @@ -406,41 +407,46 @@ protected void invalidated() {
* *
* ********************************************************************** */

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

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

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

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

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

private final ObjectProperty<Consumer<MouseEvent>> onSelectionDrop = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveSelectedText(hit.getInsertionIndex());
@Override public final EventHandler<MouseEvent> getOnSelectionDropped() { return onSelectionDropped.get(); }
@Override public final void setOnSelectionDropped(EventHandler<MouseEvent> handler) { onSelectionDropped.set( handler ); }
@Override public final ObjectProperty<EventHandler<MouseEvent>> onSelectionDroppedProperty() { return onSelectionDropped; }
private final ObjectProperty<EventHandler<MouseEvent>> onSelectionDropped = new SimpleObjectProperty<>( e -> {
onSelectionDropProperty().get().accept(e);
});
@Override public final ObjectProperty<Consumer<MouseEvent>> onSelectionDropProperty() { return onSelectionDrop; }

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


// Note: this code should be moved to `onOutsideSelectionMousePressed` property
// in the next major release before removing this deprecated field
@Deprecated private final ObjectProperty<Consumer<MouseEvent>> onOutsideSelectionMousePress = new SimpleObjectProperty<>( e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
});
@Deprecated
@Override public final ObjectProperty<Consumer<MouseEvent>> onOutsideSelectionMousePressProperty() {
return onOutsideSelectionMousePress;
}

// Note: this code should be moved to `onInsideSelectionMouseReleased` property
// in the next major release before removing this deprecated field
@Deprecated private final ObjectProperty<Consumer<MouseEvent>> onInsideSelectionMousePressRelease = new SimpleObjectProperty<>( e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
});
@Deprecated
@Override public final ObjectProperty<Consumer<MouseEvent>> onInsideSelectionMousePressReleaseProperty() {
return onInsideSelectionMousePressRelease;
}

// Note: this code should be moved to `onSelectionDropped` property
// in the next major release before removing this deprecated field
@Deprecated private final ObjectProperty<Consumer<MouseEvent>> onSelectionDrop = new SimpleObjectProperty<>( e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveSelectedText(hit.getInsertionIndex());
});
@Deprecated
@Override public final ObjectProperty<Consumer<MouseEvent>> onSelectionDropProperty() {
return onSelectionDrop;
}
}
Loading

0 comments on commit fb385a6

Please sign in to comment.