Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFE implementation for #653 / #655 #656

Merged
merged 3 commits into from
Feb 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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