Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristoph Caks2016-01-19 11:09:13 +0000
committerChristoph Caks2016-01-19 11:09:13 +0000
commit52ff6dabf0d8d0095e381ec85b125b39a176b907 (patch)
tree566b7fac48b1ce6402ed0dc076e427e3307cdda0
parent316c969cfc1c772b16dea884e34f220c9a13b821 (diff)
parent265a4de7cf0be24caed1f8010fb4eadbd9dfd4d4 (diff)
downloadorg.eclipse.efxclipse-52ff6dabf0d8d0095e381ec85b125b39a176b907.tar.gz
org.eclipse.efxclipse-52ff6dabf0d8d0095e381ec85b125b39a176b907.tar.xz
org.eclipse.efxclipse-52ff6dabf0d8d0095e381ec85b125b39a176b907.zip
Merge remote-tracking branch 'origin/codeeditor'
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java51
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java216
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java1433
-rw-r--r--bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java83
4 files changed, 1481 insertions, 302 deletions
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java
index 81385f997..28b6d4d4e 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextArea.java
@@ -6,6 +6,7 @@
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
+ * Christoph Caks <ccaks@bestsolution.at> - improved editor behavior
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
* IBM Corporation - initial API and implementation
*******************************************************************************/
@@ -71,6 +72,12 @@ public class StyledTextArea extends Control {
@NonNull
final ObjectProperty<@NonNull StyledTextContent> contentProperty;
+// @NonNull
+// final SetProperty<@NonNull StyledTextAnnotation> annotationsProperty = new SimpleSetProperty<>(this, "annotations", FXCollections.observableSet()); //$NON-NLS-1$
+// public SetProperty<@NonNull StyledTextAnnotation> getAnnotations() {
+// return this.annotationsProperty;
+// }
+
TextChangeListener textChangeListener = new TextChangeListener() {
@Override
public void textChanging(TextChangingEvent event) {
@@ -229,7 +236,11 @@ public class StyledTextArea extends Control {
((StyledTextSkin) getSkin()).recalculateItems();
}
- updateSelection(this.lastTextChangeStart, this.lastTextChangeReplaceCharCount, this.lastTextChangeNewCharCount);
+ // TODO We need to re enable this in the future.
+ // For each change coming from outside (for example refactoring)
+ // we need to update the caret
+
+// updateSelection(this.lastTextChangeStart, this.lastTextChangeReplaceCharCount, this.lastTextChangeNewCharCount);
// lastCharCount += lastTextChangeNewCharCount;
// lastCharCount -= lastTextChangeReplaceCharCount;
@@ -241,7 +252,12 @@ public class StyledTextArea extends Control {
setSelection(new TextSelection(startOffset + newLength, 0)/*, true, false*/);
} else {
// move selection to keep same text selected
- setSelection(new TextSelection(getSelection().offset + newLength - replacedLength, getSelection().length)/*, true, false*/);
+
+ int computedOffset = getSelection().offset + newLength - replacedLength;
+ if (computedOffset >= 0 && computedOffset < getCharCount()) {
+ // we only set this if the offset is valid!!
+ setSelection(new TextSelection(computedOffset, getSelection().length)/*, true, false*/);
+ }
if( getSelection().length > 0 ) {
int delta = this.lastTextChangeNewCharCount - this.lastTextChangeReplaceCharCount;
this.caretOffsetProperty.set(Math.max(0,Math.min(getCharCount()-1,getCaretOffset() + delta)));
@@ -1421,6 +1437,34 @@ public class StyledTextArea extends Control {
return getContent().getTextRange(start, end - start + 1);
}
+ public boolean isSelectionEmpty() {
+ return getSelection().length == 0;
+ }
+
+ /**
+ * inserts text at the caret location or replaces a given selection
+ * @param text
+ */
+ public void insert(CharSequence text) {
+ System.err.println("insert('"+text+"')");
+ if (text == null) throw new NullPointerException();
+
+ int start, replaceLength;
+ if (!isSelectionEmpty()) {
+ // replace selection
+ start = this.getSelection().offset;
+ replaceLength = this.getSelection().length;
+ }
+ else {
+ // insert at caret
+ start = this.getCaretOffset();
+ replaceLength = 0;
+ }
+ this.getContent().replaceTextRange(start, replaceLength, text.toString());
+ this.setCaretOffset(start + text.length());
+ }
+
+
/**
* Paste the clipboard content
*/
@@ -1429,8 +1473,7 @@ public class StyledTextArea extends Control {
if (clipboard.hasString()) {
final String text = clipboard.getString();
if (text != null) {
- // TODO Once we have a real selection we need
- getContent().replaceTextRange(getCaretOffset(), 0, text);
+ insert(text);
}
}
}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java
index 76c7bb4c9..dd9c78be1 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/StyledTextLayoutContainer.java
@@ -7,6 +7,7 @@
*
* Contributors:
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
+ * Christoph Caks <ccaks@bestsolution.at> - improved editor behavior
*******************************************************************************/
package org.eclipse.fx.ui.controls.styledtext;
@@ -14,8 +15,7 @@ import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import javafx.animation.Animation;
-import javafx.animation.KeyFrame;
-import javafx.animation.Timeline;
+import javafx.animation.FadeTransition;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
@@ -26,9 +26,8 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
import javafx.geometry.Point2D;
+import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.shape.Line;
import javafx.scene.text.TextFlow;
@@ -45,8 +44,20 @@ public class StyledTextLayoutContainer extends Region {
private final ObservableList<@NonNull StyledTextNode> textNodes = FXCollections.observableArrayList();
@NonNull
+ private final IntegerProperty caretIndex = new SimpleIntegerProperty(this, "caretIndex"); //$NON-NLS-1$
+
+ @NonNull
private final IntegerProperty startOffset = new SimpleIntegerProperty(this, "startOffset"); //$NON-NLS-1$
+
+// @NonNull
+// final SetProperty<@NonNull StyledTextAnnotation> annotationsProperty = new SimpleSetProperty<>(this, "annotations", FXCollections.observableSet()); //$NON-NLS-1$
+// public SetProperty<@NonNull StyledTextAnnotation> getAnnotations() {
+// return this.annotationsProperty;
+// }
+//
+// private Map<StyledTextAnnotation, Rectangle> annotationMarkers = new HashMap<>();
+
/**
* The start offset if used in a bigger context like {@link StyledTextArea}
*
@@ -57,6 +68,37 @@ public class StyledTextLayoutContainer extends Region {
}
/**
+ * the the caret index property.
+ *
+ * <p>the index or <code>-1</code> if caret is to be hidden</p>
+ *
+ * @return the caret index property
+ */
+ public final IntegerProperty caretIndexProperty() {
+ return this.caretIndex;
+ }
+
+ /**
+ * Set the caret index
+ *
+ * @param index
+ * the index or <code>-1</code> if caret is to be hidden
+ */
+ public void setCaretIndex(int index) {
+ this.caretIndex.set(index);
+ }
+
+ /**
+ * Returns the caret index
+ *
+ * @return the index or <code>-1</code> if caret is to be hidden
+ */
+ public int getCaretIndex() {
+ return this.caretIndex.get();
+ }
+
+
+ /**
* The start offset if used in a bigger context like {@link StyledTextArea}
*
* @return the offset
@@ -114,8 +156,7 @@ public class StyledTextLayoutContainer extends Region {
private double selectionStartX;
private double selectionEndX;
- private Timeline flashTimeline;
- int caretIndex = -1;
+ private Animation caretAnimation;
private final ReadOnlyBooleanProperty ownerFocusedProperty;
@@ -127,6 +168,15 @@ public class StyledTextLayoutContainer extends Region {
this(new SimpleBooleanProperty(true));
}
+ private static Animation createCaretAnimation(Node caret) {
+ FadeTransition t = new FadeTransition(Duration.millis(400), caret);
+ t.setAutoReverse(true);
+ t.setFromValue(1);
+ t.setToValue(0);
+ t.setCycleCount(Animation.INDEFINITE);
+ return t;
+ }
+
/**
* Create a container to layout text and allows to show e.g. a selection
* range
@@ -148,31 +198,72 @@ public class StyledTextLayoutContainer extends Region {
this.caret.setManaged(false);
this.caret.getStyleClass().add("text-caret"); //$NON-NLS-1$
- this.flashTimeline = new Timeline();
- this.flashTimeline.setCycleCount(Animation.INDEFINITE);
-
- EventHandler<ActionEvent> startEvent = e -> {
- if( ! ownerFocusedProperty.get() ) {
- StyledTextLayoutContainer.this.caret.setVisible(false);
- } else {
- StyledTextLayoutContainer.this.caret.setVisible(StyledTextLayoutContainer.this.caretIndex != -1);
- }
- };
-
- EventHandler<ActionEvent> endEvent = e -> {
- StyledTextLayoutContainer.this.caret.setVisible(false);
- };
-
- this.flashTimeline.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, startEvent), new KeyFrame(Duration.millis(500), endEvent), new KeyFrame(Duration.millis(1000)));
+ this.caretAnimation = createCaretAnimation(this.caret);
Bindings.bindContent(this.textLayoutNode.getChildren(), this.textNodes);
getChildren().setAll(this.selectionMarker, this.textLayoutNode, this.caret);
selectionProperty().addListener(this::handleSelectionChange);
- ownerFocusedProperty.addListener( o -> {
- if( ! ownerFocusedProperty.get() ) {
- this.caret.setVisible(false);
- }
- });
+
+ this.ownerFocusedProperty.addListener(this::updateCaretVisibility);
+ this.caretIndex.addListener(this::updateCaretVisibility);
+
+// this.annotationsProperty.addListener(new SetChangeListener<StyledTextAnnotation>() {
+// @Override
+// public void onChanged(javafx.collections.SetChangeListener.Change<? extends StyledTextAnnotation> change) {
+// System.err.println("ON ANNOTATION MARKER CHANGE");
+// if (change.getElementAdded() != null) {
+// StyledTextAnnotation a = change.getElementAdded();
+//
+// Rectangle child = new Rectangle();
+//
+// if (a.getType().contains("ERROR")) {
+// child.setFill(Color.RED);
+// }
+// else if (a.getType().contains("WARN")) {
+// child.setFill(Color.YELLOW);
+// }
+// child.setOpacity(0.3);
+//
+// Tooltip tt = new Tooltip();
+// tt.setText(a.getText());
+// getChildren().add(child);
+// Tooltip.install(child, tt);
+//
+// annotationMarkers.put(a, child);
+// }
+// if (change.getElementRemoved() != null) {
+// StyledTextAnnotation a = change.getElementRemoved();
+//
+// Rectangle child = annotationMarkers.remove(a);
+// if (child != null) {
+// getChildren().remove(child);
+// }
+// }
+//
+// requestLayout();
+// }
+// });
+ }
+
+ private void updateCaretVisibility(Observable o) {
+ if (this.ownerFocusedProperty.get() && this.caretIndex.get() != -1) {
+ showCaret();
+ }
+ else {
+ hideCaret();
+ }
+ }
+
+ private void showCaret() {
+ this.caret.setVisible(true);
+ this.caretAnimation.play();
+ requestLayout();
+ }
+
+ private void hideCaret() {
+ this.caret.setVisible(false);
+ this.caretAnimation.stop();
+ requestLayout();
}
private int getEndOffset() {
@@ -241,25 +332,63 @@ public class StyledTextLayoutContainer extends Region {
return d;
}
+ private double findX(int localOffset) {
+
+ double len = 0;
+ for (StyledTextNode t : this.textNodes) {
+ if (t.getStartOffset() <= localOffset && t.getEndOffset() > localOffset || this.textNodes.get(this.textNodes.size() - 1) == t) {
+ return len + t.getCharLocation(localOffset - t.getStartOffset());
+ }
+ len += t.getWidth();
+ }
+ return -1;
+ }
+
@Override
protected void layoutChildren() {
super.layoutChildren();
this.textLayoutNode.relocate(getInsets().getLeft(), getInsets().getTop());
+// for (Entry<StyledTextAnnotation, Rectangle> e : annotationMarkers.entrySet()) {
+// System.err.println("LAYOUTING MARKER: " + e.getKey().getText());
+// final int globalBeginIndex = e.getKey().getStartOffset();
+// final int globalEndIndex = e.getKey().getStartOffset() + e.getKey().getLength();
+//
+// System.err.println("global: " + globalBeginIndex + " - " + globalEndIndex);
+//
+// final int localBeginIndex = Math.max(0, globalBeginIndex - getStartOffset());
+// final int localEndIndex = Math.min(getText().length(), globalEndIndex - getStartOffset());
+//
+// System.err.println("local: " + localBeginIndex + " - " + localEndIndex);
+//
+// double xBegin = findX(localBeginIndex);
+// double xEnd = findX(localEndIndex);
+//
+//// System.err.println(xBegin + ", " + getInsets().getTop() + ", " + (xEnd - xBegin)+ ", " + textLayoutNode.prefHeight(-1));
+//// e.getValue().resizeRelocate(xBegin, getInsets().getTop(), xEnd - xBegin, textLayoutNode.prefHeight(-1));
+// e.getValue().setX(xBegin);
+// e.getValue().setY(getInsets().getTop());
+// e.getValue().setWidth(xEnd - xBegin);
+// e.getValue().setHeight(textLayoutNode.prefHeight(-1));
+// e.getValue().toFront();
+// System.err.println(" -> " + e.getValue());
+// }
+
+
if (this.selectionStartNode != null && this.selectionEndNode != null) {
double x1 = this.textLayoutNode.localToParent(this.selectionStartNode.getBoundsInParent().getMinX(), 0).getX() + this.selectionStartX;
double x2 = this.textLayoutNode.localToParent(this.selectionEndNode.getBoundsInParent().getMinX(), 0).getX() + this.selectionEndX;
this.selectionMarker.resizeRelocate(x1, 0, x2 - x1, getHeight());
}
- if (this.caretIndex >= 0) {
+ if (this.getCaretIndex() >= 0) {
this.textLayoutNode.layout();
this.textLayoutNode.applyCss();
for (StyledTextNode t : this.textNodes) {
t.applyCss();
- if (t.getStartOffset() <= this.caretIndex && (t.getEndOffset() > this.caretIndex || this.textNodes.get(this.textNodes.size() - 1) == t)) {
- double caretX = t.getCharLocation(this.caretIndex - t.getStartOffset());
+ if (t.getStartOffset() <= this.getCaretIndex() && (t.getEndOffset() > this.getCaretIndex() || this.textNodes.get(this.textNodes.size() - 1) == t)) {
+ double caretX = t.getCharLocation(this.getCaretIndex() - t.getStartOffset());
double x = this.textLayoutNode.localToParent(t.getBoundsInParent().getMinX(), 0).getX() + caretX;
double h = t.prefHeight(-1);
@@ -319,30 +448,6 @@ public class StyledTextLayoutContainer extends Region {
}
/**
- * Set the caret to show at the given index
- *
- * @param index
- * the index or <code>-1</code> if caret is to be hidden
- */
- public void setCaretIndex(int index) {
- if (index >= 0) {
- this.caretIndex = index;
- if( this.ownerFocusedProperty.get() ) {
- this.caret.setVisible(true);
- } else {
- this.caret.setVisible(false);
- }
- this.flashTimeline.play();
- requestLayout();
- } else {
- this.caretIndex = -1;
- this.flashTimeline.stop();
- this.caret.setVisible(false);
- requestLayout();
- }
- }
-
- /**
* Find the position of a the caret at a given index
*
* @param index
@@ -363,7 +468,6 @@ public class StyledTextLayoutContainer extends Region {
* Releases resources immediately
*/
public void dispose() {
- this.flashTimeline.stop();
- this.flashTimeline.getKeyFrames().clear();
+ this.caretAnimation.stop();
}
}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java
index 17428e2cd..af96ea859 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/behavior/StyledTextBehavior.java
@@ -6,25 +6,67 @@
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
+ * Christoph Caks <ccaks@bestsolution.at> - improved editor behavior
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
*******************************************************************************/
package org.eclipse.fx.ui.controls.styledtext.behavior;
+import static javafx.scene.input.KeyCode.A;
+import static javafx.scene.input.KeyCode.BACK_SPACE;
+import static javafx.scene.input.KeyCode.C;
+import static javafx.scene.input.KeyCode.D;
+import static javafx.scene.input.KeyCode.DELETE;
+import static javafx.scene.input.KeyCode.DOWN;
+import static javafx.scene.input.KeyCode.E;
+import static javafx.scene.input.KeyCode.END;
+import static javafx.scene.input.KeyCode.ENTER;
+import static javafx.scene.input.KeyCode.HOME;
+import static javafx.scene.input.KeyCode.LEFT;
+import static javafx.scene.input.KeyCode.RIGHT;
+import static javafx.scene.input.KeyCode.TAB;
+import static javafx.scene.input.KeyCode.UP;
+import static javafx.scene.input.KeyCode.V;
+import static javafx.scene.input.KeyCode.X;
+import static org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.MetaKey.AltKey;
+import static org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.MetaKey.ControlKey;
+import static org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.MetaKey.MetaKey;
+import static org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.MetaKey.ShiftKey;
+
+import java.text.BreakIterator;
+import java.text.StringCharacterIterator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
import org.eclipse.fx.core.Util;
import org.eclipse.fx.ui.controls.styledtext.ActionEvent;
import org.eclipse.fx.ui.controls.styledtext.ActionEvent.ActionType;
import org.eclipse.fx.ui.controls.styledtext.StyledTextArea;
+import org.eclipse.fx.ui.controls.styledtext.StyledTextContent;
import org.eclipse.fx.ui.controls.styledtext.StyledTextLayoutContainer;
import org.eclipse.fx.ui.controls.styledtext.TextSelection;
import org.eclipse.fx.ui.controls.styledtext.VerifyEvent;
+import org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.InputAction;
+import org.eclipse.fx.ui.controls.styledtext.behavior.StyledTextBehavior.KeyMapping.KeyCombo;
+import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin;
import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin.LineCell;
+import org.eclipse.jdt.annotation.NonNull;
import javafx.event.Event;
import javafx.geometry.Bounds;
+import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
/**
@@ -33,6 +75,8 @@ import javafx.scene.input.MouseEvent;
public class StyledTextBehavior {
private final StyledTextArea styledText;
+ private KeyMapping keyMapping = new KeyMapping();
+
/**
* Create a new behavior
*
@@ -41,7 +85,259 @@ public class StyledTextBehavior {
*/
public StyledTextBehavior(StyledTextArea styledText) {
this.styledText = styledText;
- styledText.addEventHandler(KeyEvent.ANY, this::callActionForEvent);
+ styledText.addEventHandler(KeyEvent.KEY_PRESSED, this::onKeyPressed);
+ styledText.addEventHandler(KeyEvent.KEY_TYPED, this::onKeyTyped);
+
+ styledText.addEventHandler(MouseEvent.MOUSE_PRESSED, this::onMousePressed);
+
+ initKeymapping(keyMapping);
+ }
+
+ // called from skin
+ public void installContentListeners(final Node contentNode) {
+ contentNode.addEventHandler(MouseEvent.MOUSE_PRESSED, this::onContentMousePressed);
+ contentNode.addEventHandler(MouseEvent.DRAG_DETECTED, this::onContentDragDetected);
+ contentNode.addEventHandler(MouseEvent.MOUSE_DRAGGED, this::onContentMouseDragged);
+ contentNode.addEventHandler(MouseEvent.MOUSE_RELEASED, this::onContentMouseReleased);
+ contentNode.addEventHandler(MouseEvent.MOUSE_CLICKED, this::onContentMouseClicked);
+ }
+
+ // text manipulation utils
+
+ static int computeStart(StyledTextContent content, int firstLine) {
+ return content.getOffsetAtLine(firstLine);
+ }
+
+ static int computeEnd(StyledTextContent content, int lastLine) {
+ int endIndex;
+ if (content.getLineCount() > lastLine + 1) {
+ endIndex = content.getOffsetAtLine(lastLine + 1);
+ }
+ else {
+ endIndex = content.getOffsetAtLine(lastLine) + content.getLine(lastLine).length();
+ }
+ return endIndex;
+ }
+
+ static int computeLength(StyledTextContent content, int firstLine, int lastLine) {
+ return computeEnd(content, lastLine) - computeStart(content, firstLine);
+ }
+
+ private class LineRegion extends Region {
+ public final int firstLine;
+ public final int lastLine;
+
+ public LineRegion(int firstLine, int lastLine) {
+ super(computeStart(getControl().getContent(), firstLine), computeLength(getControl().getContent(), firstLine, lastLine));
+ this.firstLine = firstLine;
+ this.lastLine = lastLine;
+ }
+
+ public LineRegion(int singleLineIndex) {
+ this(singleLineIndex, singleLineIndex);
+ }
+
+ }
+
+ private @NonNull LineRegion getLineRegion(TextSelection selection) {
+ int firstLine = getControl().getLineAtOffset(selection.offset);
+ int lastLine = getControl().getLineAtOffset(selection.offset + selection.length);
+ int lastLineBegin = getControl().getOffsetAtLine(lastLine);
+ // dont count the last line if the caret is at index 0
+ if (lastLineBegin == selection.offset + selection.length) {
+ lastLine -= 1;
+ }
+ // limit
+ lastLine = Math.min(getControl().getContent().getLineCount()-1, lastLine);
+ lastLine = Math.max(firstLine, lastLine);
+
+ return new LineRegion(firstLine, lastLine);
+ }
+
+ private class Region {
+ public final int start;
+ public final int end;
+ public final int length;
+
+ Region(int startIndex, int length) {
+ this.start = startIndex;
+ this.end = startIndex + length;
+ this.length = length;
+ }
+
+ public String read() {
+ return getControl().getContent().getTextRange(start, length);
+ }
+
+ public void replace(String replacement) {
+ getControl().getContent().replaceTextRange(start, length, replacement);
+ }
+
+ public void select() {
+ System.err.println("selecting " + start + " l " + length);
+ moveCaretAbsolute(start);
+ getControl().setSelection(new TextSelection(start, length));
+ }
+
+
+ }
+
+ private volatile boolean dragMoveTextMode = false;
+ private volatile boolean dragSelectionMode = false;
+
+
+ // state for dnd stuff
+
+ private volatile int dragMoveTextOffset = -1;
+ private volatile int dragMoveTextLength = -1;
+
+ private int mousePressedOffset = -1;
+
+
+ private boolean isInRange(int offset, int rangeOffset, int rangeLength) {
+ return offset >= rangeOffset && offset < (rangeOffset + rangeLength);
+ }
+
+ private boolean isInSelection(int offset) {
+ int selOffset = getControl().getSelection().offset;
+ int selLength = getControl().getSelection().length;
+ boolean r = selLength > 0 && isInRange(offset, selOffset, selLength);
+ System.err.println("isInSelection(" + offset + ")" + "[" + selOffset + ", " + (selOffset + selLength) + ") -> " + r);
+ return r;
+ }
+
+
+
+ private void onKeyPressed(KeyEvent event) {
+ VerifyEvent evt = new VerifyEvent(getControl(), getControl(), event);
+ Event.fireEvent(getControl(), evt);
+
+ // Bug in JavaFX who enables the menu when ALT is pressed
+ if (Util.isMacOS()) {
+ if (event.getCode() == KeyCode.ALT || event.isAltDown()) {
+ event.consume();
+ }
+ }
+
+ if (evt.isConsumed()) {
+ event.consume();
+ return;
+ }
+
+ Optional<InputAction> keyAction = this.keyMapping.get(event);
+ keyAction.ifPresent(a->{
+ a.run();
+ event.consume();
+ });
+ }
+
+ private void onKeyTyped(KeyEvent event) {
+ if (getControl().getEditable()) {
+
+ String character = event.getCharacter();
+ if (character.length() == 0) {
+ return;
+ }
+
+ // check the modifiers
+ // - OS-X: ALT+L ==> @
+ // - win32/linux: ALTGR+Q ==> @
+ if (event.isControlDown() || event.isAltDown() || (Util.isMacOS() && event.isMetaDown())) {
+ if (!((event.isControlDown() || Util.isMacOS()) && event.isAltDown()))
+ return;
+ }
+
+ if (character.charAt(0) > 31 // No ascii control chars
+ && character.charAt(0) != 127 // no delete key
+ && !event.isMetaDown()) {
+
+ getControl().insert(character);
+ }
+
+ }
+ }
+
+
+ private void onContentMousePressed(MouseEvent event) {
+ this.mousePressedOffset = computeCursorOffset(event);
+ System.err.println("MOUSE_PRESSED @ " + mousePressedOffset);
+
+ if (isInSelection(mousePressedOffset)) {
+ System.err.println(" -> starting dragMoveTextMode");
+ event.setDragDetect(true);
+ this.dragMoveTextMode = true;
+ this.dragMoveTextOffset = getControl().getSelection().offset;
+ this.dragMoveTextLength = getControl().getSelection().length;
+ }
+ else {
+ System.err.println(" -> setting Caret");
+ moveCaretAbsolute(this.mousePressedOffset, event.isShiftDown());
+ }
+ }
+ private void onContentDragDetected(MouseEvent event) {
+ if (!this.dragMoveTextMode && ! this.dragSelectionMode) {
+ this.dragSelectionMode = true;
+ }
+ }
+ private void onContentMouseDragged(MouseEvent event) {
+ if (this.dragSelectionMode) {
+// System.err.println("MOUSE_DRAGGED [dragSelectionMode]");
+ int offset = computeCursorOffset(event);
+ moveCaretAbsolute(offset, true);
+ event.consume();
+ }
+ else if (this.dragMoveTextMode) {
+ //System.err.println("MOUSE_DRAGGED [dragMoveTextMode]");
+ // nothing to do
+ event.consume();
+ }
+ }
+ private void onContentMouseReleased(MouseEvent event) {
+ System.err.println("MOUSE_RELEASED");
+ if (this.dragSelectionMode) {
+ this.dragSelectionMode = false;
+ event.consume();
+ }
+ else if (this.dragMoveTextMode) {
+ int targetOffset = computeCursorOffset(event);
+
+ // replace
+ if (isInRange(targetOffset, dragMoveTextOffset, dragMoveTextLength)) {
+ targetOffset = dragMoveTextOffset;
+ }
+ // after
+ else if (targetOffset >= dragMoveTextOffset + dragMoveTextLength) {
+ targetOffset -= dragMoveTextLength;
+ }
+
+ // read text
+ @NonNull
+ String text = getControl().getContent().getTextRange(dragMoveTextOffset, dragMoveTextLength);
+
+ // remove
+ getControl().getContent().replaceTextRange(dragMoveTextOffset, dragMoveTextLength, "");
+
+ // insert
+ getControl().getContent().replaceTextRange(targetOffset, 0, text);
+
+ // move caret to end of insertion
+ moveCaretAbsolute(targetOffset + text.length());
+
+ this.dragMoveTextMode = false;
+ event.consume();
+ }
+ }
+
+ private void onContentMouseClicked(MouseEvent event) {
+ if (event.isStillSincePress()) {
+ System.err.println("MOUSE_CLICKED @ " + mousePressedOffset);
+ if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
+ System.err.println(" -> executing double click");
+ // double click
+ this.ACTION_SELECT_WORD.run();
+ event.consume();
+ }
+ }
}
/**
@@ -51,295 +347,978 @@ public class StyledTextBehavior {
return this.styledText;
}
+ private void onMousePressed(MouseEvent event) {
+ getControl().requestFocus();
+ }
+
+ private int computeCurrentLineNumber() {
+ final int offset = getControl().getCaretOffset();
+ return getControl().getLineAtOffset(offset);
+ }
+
+ private int computeCurrentLineStartOffset() {
+ final int lineNumber = computeCurrentLineNumber();
+ return getControl().getOffsetAtLine(lineNumber);
+ }
+
/**
- * Handle key event
- *
- * @param event
- * the event
+ * Action to go the start of the text
*/
- protected void callActionForEvent(KeyEvent event) {
- if (event.getEventType() == KeyEvent.KEY_PRESSED) {
- _keyPressed(event);
- } else if (event.getEventType() == KeyEvent.KEY_TYPED) {
- _keyTyped(event);
- }
+ protected final StyledTextInputAction ACTION_NAVIGATE_TEXT_START = new StyledTextInputAction(ActionType.TEXT_START, this::defaultNavigateTextStart);
+ /**
+ * default implementation for {@link #ACTION_NAVIGATE_TEXT_START}
+ */
+ protected void defaultNavigateTextStart() {
+ getControl().setCaretOffset(0);
}
/**
- * Handle the mouse event that happens on the content
- *
- * @param event
- * the event
- * @param visibleCells
- * the visible cell
+ * Action to go the start of the text
*/
- public void handleContentMouseEvent(MouseEvent event, List<LineCell> visibleCells) {
- if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
- updateCursor(event, visibleCells, event.isShiftDown());
+ protected final InputAction ACTION_NAVIGATE_TEXT_END = new StyledTextInputAction(ActionType.TEXT_END, this::defaultNavigateTextEnd);
+ /**
+ * default implementation for {@link #ACTION_NAVIGATE_TEXT_END}
+ */
+ protected void defaultNavigateTextEnd() {
+ getControl().setCaretOffset(getControl().getContent().getCharCount());
+ }
- // The consuming does not help because it looks like the
- // selection change happens earlier => should be push a new
- // ListViewBehavior?
- event.consume();
+ /**
+ * Action to go to the line start
+ */
+ protected final InputAction ACTION_NAVIGATE_LINE_START = new StyledTextInputAction(ActionType.LINE_START, this::defaultNavigateLineStart);
+ /**
+ * default implementation for {@link #ACTION_NAVIGATE_LINE_START}
+ */
+ protected void defaultNavigateLineStart() {
+ //TODO Should be position to the first none whitespace char??
+ moveCaretAbsolute(computeCurrentLineStartOffset());
+ }
+
+ /**
+ * Action to go to the line end
+ */
+ protected final InputAction ACTION_NAVIGATE_LINE_END = new StyledTextInputAction(ActionType.LINE_END, this::defaultNavigateLineEnd);
+ /**
+ * default implementation for {@link #ACTION_NAVIGATE_LINE_END}
+ */
+ protected void defaultNavigateLineEnd() {
+ final int caretLine = computeCurrentLineNumber();
+ moveCaretAbsolute(getControl().getContent().getOffsetAtLine(caretLine) + getControl().getContent().getLine(caretLine).length());
+ }
+
+ /**
+ * Action to go to the next word
+ */
+ protected final InputAction ACTION_NAVIGATE_WORD_NEXT = new StyledTextInputAction(ActionType.WORD_NEXT, this::defaultNavigateWordNext);
+ /**
+ * default implementation for {@link #ACTION_NAVIGATE_WORD_NEXT}
+ */
+ protected void defaultNavigateWordNext() {
+ BreakIterator wordInstance = BreakIterator.getWordInstance();
+ wordInstance.setText(new StringCharacterIterator(getControl().getContent().getTextRange(0, getControl().getContent().getCharCount())));
+ int following = wordInstance.following(getControl().getCaretOffset());
+ if( following != BreakIterator.DONE ) {
+ moveCaretAbsolute(following);
}
}
/**
- * handle the mouse pressed
- *
- * @param arg0
- * the mouse event
+ * Action to go to the previous word
*/
- public void mousePressed(MouseEvent arg0) {
- getControl().requestFocus();
+ protected final InputAction ACTION_NAVIGATE_WORD_PREVIOUS = new StyledTextInputAction(ActionType.WORD_PREVIOUS, this::defaultNavigateWordPrevious);
+ /**
+ * default implementation for {@link #ACTION_NAVIGATE_WORD_PREVIOUS}
+ */
+ protected void defaultNavigateWordPrevious() {
+ BreakIterator wordInstance = BreakIterator.getWordInstance();
+ wordInstance.setText(new StringCharacterIterator(getControl().getContent().getTextRange(0, getControl().getContent().getCharCount())));
+ int previous = wordInstance.preceding(getControl().getCaretOffset());
+ if( previous != BreakIterator.DONE ) {
+ moveCaretAbsolute(previous);
+ }
}
/**
- * Invoke an action
- *
- * @param action
- * the action
+ * Action to select to the start of the text
*/
- public void invokeAction(ActionType action) {
- ActionEvent evt = new ActionEvent(getControl(), getControl(), action);
- Event.fireEvent(getControl(), evt);
+ protected final InputAction ACTION_SELECT_TEXT_START = new StyledTextInputAction(ActionType.SELECT_TEXT_START, this::defaultSelectTextStart);
+ /**
+ * default implementation for {@link #ACTION_SELECT_TEXT_START}
+ */
+ protected void defaultSelectTextStart() {
+ moveCaretAbsolute(0, true);
}
- @SuppressWarnings("deprecation")
- private void _keyPressed(KeyEvent event) {
- VerifyEvent evt = new VerifyEvent(getControl(), getControl(), event);
- Event.fireEvent(getControl(), evt);
+ /**
+ * Action to select to the end of the text
+ */
+ protected final InputAction ACTION_SELECT_TEXT_END = new StyledTextInputAction(ActionType.SELECT_TEXT_END, this::defaultSelectTextEnd);
+ /**
+ * default implementation for {@link #ACTION_SELECT_TEXT_END}
+ */
+ protected void defaultSelectTextEnd() {
+ moveCaretAbsolute(getControl().getCharCount(),true);
+ }
- // Bug in JavaFX who enables the menu when ALT is pressed
- if (Util.isMacOS()) {
- if (event.getCode() == KeyCode.ALT || event.isAltDown()) {
- event.consume();
- }
+ /**
+ * Action to select until the start of the line
+ */
+ protected final InputAction ACTION_SELECT_LINE_START = new StyledTextInputAction(ActionType.SELECT_LINE_START, this::defaultSelectLineStart);
+ /**
+ * default implementation for {@link #ACTION_SELECT_LINE_START}
+ */
+ protected void defaultSelectLineStart() {
+ //TODO Should be position to the first none whitespace char??
+ moveCaretAbsolute(computeCurrentLineStartOffset(), true);
+ }
+
+ /**
+ * Action to select until the end of the line
+ */
+ protected final InputAction ACTION_SELECT_LINE_END = new StyledTextInputAction(ActionType.SELECT_LINE_END, this::defaultSelectLineEnd);
+ /**
+ * default implementation for {@link #ACTION_SELECT_LINE_END}
+ */
+ protected void defaultSelectLineEnd() {
+ int caretLine = getControl().getContent().getLineAtOffset(getControl().getCaretOffset());
+ int end = getControl().getContent().getOffsetAtLine(caretLine) + getControl().getContent().getLine(caretLine).length();
+ moveCaretAbsolute(end, true);
+ }
+
+ /**
+ * Action to select the next word
+ */
+ protected final InputAction ACTION_SELECT_WORD_NEXT = new StyledTextInputAction(ActionType.SELECT_WORD_NEXT, this::defaultSelectWordNext);
+ /**
+ * default implementation for {@link #ACTION_SELECT_WORD_NEXT}
+ */
+ protected void defaultSelectWordNext() {
+ BreakIterator wordInstance = BreakIterator.getWordInstance();
+ wordInstance.setText(new StringCharacterIterator(getControl().getContent().getTextRange(0, getControl().getContent().getCharCount())));
+ int following = wordInstance.following(getControl().getCaretOffset());
+ if( following != BreakIterator.DONE ) {
+ moveCaretAbsolute(following, true);
}
+ }
- if (evt.isConsumed()) {
- event.consume();
- return;
+ /**
+ * Action to select the previous word
+ */
+ protected final InputAction ACTION_SELECT_WORD_PREVIOUS = new StyledTextInputAction(ActionType.SELECT_WORD_PREVIOUS, this::defaultSelectWordPrevious);
+ /**
+ * default implementation for {@link #ACTION_SELECT_WORD_PREVIOUS}
+ */
+ protected void defaultSelectWordPrevious() {
+ BreakIterator wordInstance = BreakIterator.getWordInstance();
+ wordInstance.setText(new StringCharacterIterator(getControl().getContent().getTextRange(0, getControl().getContent().getCharCount())));
+ int previous = wordInstance.preceding(getControl().getCaretOffset());
+ if( previous != BreakIterator.DONE ) {
+ moveCaretAbsolute(previous, true);
}
+ }
- int currentRowIndex = getControl().getContent().getLineAtOffset(getControl().getCaretOffset());
+ /**
+ * Action to select the word at the current cursor
+ */
+ protected final InputAction ACTION_SELECT_WORD = new StyledTextInputAction(ActionType.SELECT_WORD, this::defaultSelectWord);
+ /**
+ * default implementation for {@link #ACTION_SELECT_WORD}
+ */
+ protected void defaultSelectWord() {
+ BreakIterator wordInstance = BreakIterator.getWordInstance();
+ wordInstance.setText(new StringCharacterIterator(getControl().getContent().getTextRange(0, getControl().getContent().getCharCount())));
+ int previous = wordInstance.preceding(getControl().getCaretOffset());
+ int next = wordInstance.following(getControl().getCaretOffset());
+ if( previous != BreakIterator.DONE && next != BreakIterator.DONE ) {
+ moveCaretAbsolute(previous);
+ moveCaretAbsolute(next, true);
+ }
+ }
- final int offset = getControl().getCaretOffset();
+ /**
+ * Action to delete current line
+ */
+ protected final InputAction ACTION_DELETE_LINE = new StyledTextInputAction(this::defaultDeleteLine);
+ /**
+ * default implementation for {@link #ACTION_DELETE_WORD_NEXT}
+ */
+ protected void defaultDeleteLine() {
+ LineRegion lineRegion = getLineRegion(getControl().getSelection());
+ lineRegion.replace(""); //$NON-NLS-1$
+ moveCaretAbsolute(lineRegion.start);
- switch (event.getCode()) {
- case SHIFT:
- case ALT:
- case CONTROL:
- break;
- case LEFT: {
- if (event.isAltDown()) {
- invokeAction(ActionType.WORD_PREVIOUS);
- } else {
- if (offset == 0) {
- event.consume();
- break;
- }
- int newOffset = offset - 1;
- @SuppressWarnings("unused")
- int currentLine = getControl().getContent().getLineAtOffset(offset);
- @SuppressWarnings("unused")
- int newLine = getControl().getContent().getLineAtOffset(newOffset);
- getControl().impl_setCaretOffset(newOffset, event.isShiftDown());
- event.consume();
+// @NonNull
+// LineSelection lineSelection = getLineSelection();
+//
+// System.err.println("delete " + lineSelection.firstLine + " to " + lineSelection.lastLine);
+// int beginIndex = getControl().getOffsetAtLine(lineSelection.firstLine);
+// int endIndex;
+// if (getControl().getContent().getLineCount() > lineSelection.lastLine + 1) {
+// endIndex = getControl().getOffsetAtLine(lineSelection.lastLine + 1);
+// }
+// else {
+// endIndex = getControl().getOffsetAtLine(lineSelection.lastLine) + getControl().getContent().getLine(lineSelection.lastLine).length();
+// }
+//
+// getControl().getContent().replaceTextRange(beginIndex, endIndex - beginIndex, ""); //$NON-NLS-1$
+// moveCaretAbsolute(beginIndex);
+ }
+
+ /**
+ * Action to delete next word
+ */
+ protected final InputAction ACTION_DELETE_WORD_NEXT = new StyledTextInputAction(ActionType.DELETE_WORD_NEXT, this::defaultDeleteWordNext);
+ /**
+ * default implementation for {@link #ACTION_DELETE_WORD_NEXT}
+ */
+ protected void defaultDeleteWordNext() {
+ int offset = getControl().getCaretOffset();
+ BreakIterator wordInstance = BreakIterator.getWordInstance();
+ wordInstance.setText(new StringCharacterIterator(getControl().getContent().getTextRange(0, getControl().getContent().getCharCount())));
+ int following = wordInstance.following(getControl().getCaretOffset());
+ if( following != BreakIterator.DONE ) {
+ getControl().getContent().replaceTextRange(getControl().getCaretOffset(), following - offset, ""); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Action to delete previous word
+ */
+ protected final InputAction ACTION_DELETE_WORD_PREVIOUS = new StyledTextInputAction(ActionType.DELETE_WORD_PREVIOUS, this::defaultDeleteWordPrevious);
+ /**
+ * default implementation for {@link #ACTION_DELETE_WORD_PREVIOUS}
+ */
+ protected void defaultDeleteWordPrevious() {
+ int offset = getControl().getCaretOffset();
+ BreakIterator wordInstance = BreakIterator.getWordInstance();
+ wordInstance.setText(new StringCharacterIterator(getControl().getContent().getTextRange(0, getControl().getContent().getCharCount())));
+ int previous = wordInstance.preceding(getControl().getCaretOffset());
+ if( previous != BreakIterator.DONE ) {
+ getControl().setCaretOffset(previous);
+ getControl().getContent().replaceTextRange(previous, offset - previous, ""); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Action to move selected lines up
+ */
+ protected final InputAction ACTION_MOVE_LINES_UP = new StyledTextInputAction(this::defaultMoveLinesUp);
+ /**
+ * default implementation for {@link #ACTION_MOVE_LINES_UP}
+ */
+ protected void defaultMoveLinesUp() {
+ LineRegion moveTarget = getLineRegion(getControl().getSelection());
+ if (moveTarget.firstLine > 0) {
+ LineRegion above = new LineRegion(moveTarget.firstLine -1);
+ LineRegion all = new LineRegion(above.firstLine, moveTarget.lastLine);
+
+ String aboveText = above.read();
+ String moveTargetText = moveTarget.read();
+ // we reach the last line
+ if (moveTarget.lastLine + 1 == getControl().getContent().getLineCount()) {
+ moveTargetText += getControl().getLineSeparator().getValue();
+ aboveText = aboveText.replaceFirst("\r?\n$", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
- break;
+
+ all.replace( moveTargetText + aboveText);
+ new LineRegion(moveTarget.firstLine -1, moveTarget.lastLine -1).select();
}
- case RIGHT: {
- if (event.isAltDown()) {
- invokeAction(ActionType.WORD_NEXT);
- } else if (event.isMetaDown()) {
- int currentLine = getControl().getContent().getLineAtOffset(offset);
- int lineOffset = getControl().getContent().getOffsetAtLine(currentLine);
- String lineContent = getControl().getContent().getLine(currentLine);
+ }
- getControl().impl_setCaretOffset(lineOffset + lineContent.length(), event.isShiftDown());
- event.consume();
- } else {
- if (offset + 1 > getControl().getContent().getCharCount()) {
- break;
- }
- int newOffset = offset + 1;
- // @SuppressWarnings("unused")
- // int currentLine =
- // getControl().getContent().getLineAtOffset(offset);
- // @SuppressWarnings("unused")
- // int newLine =
- // getControl().getContent().getLineAtOffset(newOffset);
- getControl().impl_setCaretOffset(newOffset, event.isShiftDown());
- event.consume();
+ /**
+ * Action to move selected lines down
+ */
+ protected final InputAction ACTION_MOVE_LINES_DOWN = new StyledTextInputAction(this::defaultMoveLinesDown);
+ /**
+ * default implementation for {@link #ACTION_MOVE_LINES_DOWN}
+ */
+ protected void defaultMoveLinesDown() {
+ LineRegion moveTarget = getLineRegion(getControl().getSelection());
+ if (moveTarget.lastLine + 1 < getControl().getContent().getLineCount()) {
+
+ LineRegion below = new LineRegion(moveTarget.lastLine + 1);
+ LineRegion all = new LineRegion(moveTarget.firstLine, below.lastLine);
+
+ String belowText = below.read();
+ String moveTargetText = moveTarget.read();
+ // we reach the last line
+ if (below.lastLine + 1 == getControl().getContent().getLineCount()) {
+ belowText += getControl().getLineSeparator().getValue();
+ moveTargetText = moveTargetText.replaceFirst("\r?\n$", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
- break;
+
+ all.replace(belowText + moveTargetText);
+ new LineRegion(moveTarget.firstLine +1, moveTarget.lastLine +1).select();
}
- case UP: {
- int rowIndex = currentRowIndex;
+ }
+
- if (rowIndex == 0) {
+ /**
+ * Action to create a new line
+ */
+ protected final InputAction ACTION_NEW_LINE = new StyledTextInputAction(ActionType.NEW_LINE, this::defaultNewLine);
+ /**
+ * default implementation for {@link #ACTION_NEW_LINE}
+ */
+ protected void defaultNewLine() {
+ int offset = getControl().getCaretOffset();
+ int line = getControl().getContent().getLineAtOffset(offset);
+ String lineContent = getControl().getContent().getLine(line);
+
+ // Should we make this configurable
+ char[] chars = lineContent.toCharArray();
+ String prefix = ""; //$NON-NLS-1$
+ for (int i = 0; i < chars.length; i++) {
+ if (chars[i] == ' ' || chars[i] == '\t') {
+ prefix += chars[i];
+ } else {
break;
}
+ }
+ getControl().getContent().replaceTextRange(getControl().getCaretOffset(), 0, getControl().getLineSeparator().getValue() + prefix);
+ getControl().setCaretOffset(offset + getControl().getLineSeparator().getValue().length() + prefix.length());
- int colIdx = offset - getControl().getContent().getOffsetAtLine(rowIndex);
- rowIndex -= 1;
+ }
- int lineOffset = getControl().getContent().getOffsetAtLine(rowIndex);
- int newCaretPosition = lineOffset + colIdx;
- int maxPosition = lineOffset + getControl().getContent().getLine(rowIndex).length();
+ /**
+ * Action to select all
+ */
+ protected final InputAction ACTION_SELECT_ALL = new StyledTextInputAction(this::defaultSelectAll);
+ /**
+ * default implementation for {@link #ACTION_SELECT_ALL}
+ */
+ protected void defaultSelectAll() {
+ int length = getControl().getContent().getCharCount();
+ getControl().setSelectionRange(0, length);
+ }
- getControl().impl_setCaretOffset(Math.min(newCaretPosition, maxPosition), event.isShiftDown());
- event.consume();
- break;
+ /**
+ * Action to copy
+ */
+ protected final InputAction ACTION_COPY = new StyledTextInputAction(this::defaultCopy);
+ /**
+ * default implementation for {@link #ACTION_COPY}
+ */
+ protected void defaultCopy() {
+ getControl().copy();
+ }
+
+ /**
+ * Action to paste
+ */
+ protected final InputAction ACTION_PASTE = new StyledTextInputAction(this::defaultPaste);
+ /**
+ * default implementation for {@link #ACTION_PASTE}
+ */
+ protected void defaultPaste() {
+ if (getControl().getEditable()) {
+ getControl().paste();
}
- case DOWN: {
- int rowIndex = currentRowIndex;
- if (rowIndex + 1 == getControl().getContent().getLineCount()) {
- break;
+ }
+
+ /**
+ * Action to cut
+ */
+ protected final InputAction ACTION_CUT = new StyledTextInputAction(this::defaultCut);
+ /**
+ * default implementation for {@link #ACTION_CUT}
+ */
+ protected void defaultCut() {
+ if (getControl().getEditable()) {
+ getControl().cut();
+ }
+ }
+
+ /**
+ * Action to delete
+ */
+ protected final InputAction ACTION_DELETE = new StyledTextInputAction(this::defaultDelete);
+ /**
+ * default implementation for {@link #ACTION_DELETE}
+ */
+ protected void defaultDelete() {
+ int offset = getControl().getCaretOffset();
+ TextSelection selection = getControl().getSelection();
+ if (selection.length > 0) {
+ getControl().getContent().replaceTextRange(selection.offset, selection.length, ""); //$NON-NLS-1$
+ getControl().setCaretOffset(selection.offset);
+ } else {
+ getControl().getContent().replaceTextRange(getControl().getCaretOffset(), 1, ""); //$NON-NLS-1$
+ getControl().setCaretOffset(offset);
+ }
+ }
+
+ /**
+ * Action to delete backwards
+ */
+ protected final InputAction ACTION_BACKSPACE = new StyledTextInputAction(this::defaultBackspace);
+ /**
+ * default implementation for {@link #ACTION_BACKSPACE}
+ */
+ protected void defaultBackspace() {
+ int offset = getControl().getCaretOffset();
+ TextSelection selection = getControl().getSelection();
+ if (selection.length > 0) {
+ getControl().getContent().replaceTextRange(selection.offset, selection.length, ""); //$NON-NLS-1$
+ getControl().setCaretOffset(selection.offset);
+ } else {
+ getControl().getContent().replaceTextRange(getControl().getCaretOffset() - 1, 1, ""); //$NON-NLS-1$
+ getControl().setCaretOffset(offset - 1);
+ }
+ }
+
+
+ private boolean isMultilineSelection() {
+ return getControl().getLineAtOffset(getControl().getSelection().offset) != getControl().getLineAtOffset(getControl().getSelection().offset + getControl().getSelection().length);
+ }
+
+
+ /**
+ * Action to indent
+ */
+ protected final InputAction ACTION_INDENT = new StyledTextInputAction(ActionType.INDENT, this::defaultIndent);
+ /**
+ * default implementation for {@link #ACTION_INDENT}
+ */
+ protected void defaultIndent() {
+ if (isMultilineSelection()) {
+
+ // TODO use LineRegion
+ // TODO only replace selected lines
+
+ String allContent = getControl().getContent().getTextRange(0, getControl().getCharCount());
+ StringBuffer dataBuffer = new StringBuffer(allContent);
+
+ final int caret = getControl().getCaretOffset();
+ final int selectionOffset = getControl().getSelection().offset;
+ final int selectionLength = getControl().getSelection().length;
+
+ final int firstLine = getControl().getLineAtOffset(selectionOffset);
+ int lastLine = getControl().getLineAtOffset(selectionOffset + selectionLength);
+
+ if (getControl().getOffsetAtLine(lastLine) < selectionOffset + selectionLength) {
+ // we need to indent this line too
+ lastLine += 1;
}
- int colIdx = offset - getControl().getContent().getOffsetAtLine(rowIndex);
- rowIndex += 1;
+ int added = 0;
- int lineOffset = getControl().getContent().getOffsetAtLine(rowIndex);
- int newCaretPosition = lineOffset + colIdx;
- int maxPosition = lineOffset + getControl().getContent().getLine(rowIndex).length();
+ int firstLineDelta = 0;
- getControl().impl_setCaretOffset(Math.min(newCaretPosition, maxPosition), event.isShiftDown());
- event.consume();
- break;
- }
- case ENTER:
- if (getControl().getEditable()) {
- int line = getControl().getContent().getLineAtOffset(getControl().getCaretOffset());
- String lineContent = getControl().getContent().getLine(line);
-
- // FIXME Temp hack
- char[] chars = lineContent.toCharArray();
- String prefix = ""; //$NON-NLS-1$
- for (int i = 0; i < chars.length; i++) {
- if (chars[i] == ' ') {
- prefix += " "; //$NON-NLS-1$
- } else {
- break;
+ for (int lineNumber = firstLine; lineNumber < lastLine; lineNumber++) {
+ int lineStart = getControl().getOffsetAtLine(lineNumber) + added;
+ dataBuffer.replace(lineStart, lineStart + 0, "\t"); //$NON-NLS-1$
+ added += 1;
+ if (lineNumber == firstLine) {
+ if (selectionOffset > lineStart) {
+ firstLineDelta = 1;
}
}
+ }
+
+ getControl().getContent().setText(dataBuffer.toString());
+ getControl().setCaretOffset(caret + added);
+ getControl().setSelectionRange(selectionOffset + firstLineDelta, selectionLength + added - firstLineDelta);
+ }
+ }
+
+ /**
+ * Action to outdent (opposite of indentation)
+ */
+ protected final StyledTextInputAction ACTION_OUTDENT = new StyledTextInputAction(ActionType.OUTDENT, this::defaultOutdent);
+ /**
+ * default implementation for {@link #ACTION_OUTDENT}
+ */
+ protected void defaultOutdent() {
+ // TODO use LineRegion
+ // TODO only replace selected lines
+
+ String allContent = getControl().getContent().getTextRange(0, getControl().getCharCount());
+ StringBuffer dataBuffer = new StringBuffer(allContent);
+
+ final int caret = getControl().getCaretOffset();
+ final int selectionOffset = getControl().getSelection().offset;
+ final int selectionLength = getControl().getSelection().length;
+
+ final int firstLine = getControl().getLineAtOffset(selectionOffset);
+ int lastLine = getControl().getLineAtOffset(selectionOffset + selectionLength);
+
+ if (getControl().getOffsetAtLine(lastLine) < selectionOffset + selectionLength) {
+ // we need to indent this line too
+ lastLine += 1;
+ }
- String newLine = System.getProperty("line.separator"); //$NON-NLS-1$
+ int firstLineDelta = 0;
- getControl().getContent().replaceTextRange(getControl().getCaretOffset(), 0, newLine + prefix);
- // listView.getSelectionModel().select(listView.getSelectionModel().getSelectedIndex()+1);
- getControl().setCaretOffset(offset + newLine.length() + prefix.length());
+ for (int lineNumber = firstLine; lineNumber < lastLine; lineNumber++) {
+ int lineStart = getControl().getOffsetAtLine(lineNumber);
+ if (dataBuffer.charAt(lineStart)!='\t') {
+ return;
}
- break;
- case DELETE:
- if (getControl().getEditable()) {
- if (event.isMetaDown()) {
- invokeAction(ActionType.DELETE_WORD_NEXT);
- } else {
- getControl().getContent().replaceTextRange(getControl().getCaretOffset(), 1, ""); //$NON-NLS-1$
- getControl().setCaretOffset(offset);
+ if (lineNumber == firstLine) {
+ if (selectionOffset > lineStart) {
+ firstLineDelta = 1;
}
- break;
}
- case BACK_SPACE:
- if (getControl().getEditable()) {
- if (event.isMetaDown()) {
- invokeAction(ActionType.DELETE_WORD_PREVIOUS);
- } else {
- TextSelection selection = getControl().getSelection();
- if (selection.length > 0) {
- getControl().getContent().replaceTextRange(selection.offset, selection.length, ""); //$NON-NLS-1$
- getControl().setCaretOffset(selection.offset);
- } else {
- getControl().getContent().replaceTextRange(getControl().getCaretOffset() - 1, 1, ""); //$NON-NLS-1$
- getControl().setCaretOffset(offset - 1);
- }
- }
- break;
+ }
+
+ int removed = 0;
+
+ for (int lineNumber = lastLine-1; lineNumber >= firstLine; lineNumber--) {
+ int lineStart = getControl().getOffsetAtLine(lineNumber);
+ dataBuffer.replace(lineStart, lineStart + 1, "");
+ removed += 1;
+ }
+
+ getControl().getContent().setText(dataBuffer.toString());
+ getControl().setCaretOffset(caret - removed);
+ getControl().setSelectionRange(selectionOffset - firstLineDelta, selectionLength - removed + firstLineDelta);
+ }
+
+ /**
+ * Action to move caret upwards
+ */
+ protected final StyledTextInputAction ACTION_MOVE_UP = new StyledTextInputAction(()->defaultUp(false));
+ /**
+ * Action to move caret upwards while selecting
+ */
+ protected final StyledTextInputAction ACTION_SELECT_UP = new StyledTextInputAction(()->defaultUp(true));
+
+ /**
+ * default implementation for {@link #ACTION_MOVE_UP} and {@link #ACTION_SELECT_UP}
+ * @param select whether to change the selection
+ */
+ protected void defaultUp(boolean select) {
+ int currentRowIndex = getControl().getContent().getLineAtOffset(getControl().getCaretOffset());
+
+ final int offset = getControl().getCaretOffset();
+
+ int rowIndex = currentRowIndex;
+
+ if (rowIndex == 0) {
+ return;
+ }
+
+ int colIdx = offset - getControl().getContent().getOffsetAtLine(rowIndex);
+ rowIndex -= 1;
+
+ int lineOffset = getControl().getContent().getOffsetAtLine(rowIndex);
+ int newCaretPosition = lineOffset + colIdx;
+ int maxPosition = lineOffset + getControl().getContent().getLine(rowIndex).length();
+
+ moveCaretAbsolute(Math.min(newCaretPosition, maxPosition), select);
+ }
+
+ /**
+ * Action to move caret down
+ */
+ protected final StyledTextInputAction ACTION_MOVE_DOWN = new StyledTextInputAction(()->defaultDown(false));
+ /**
+ * Action to move caret down while selecting
+ */
+ protected final StyledTextInputAction ACTION_SELECT_DOWN = new StyledTextInputAction(()->defaultDown(true));
+
+ /**
+ * default implementation for {@link #ACTION_MOVE_DOWN} and {@link #ACTION_SELECT_DOWN}
+ * @param select whether to change the selection
+ */
+ protected void defaultDown(boolean select) {
+ int currentRowIndex = getControl().getContent().getLineAtOffset(getControl().getCaretOffset());
+
+ final int offset = getControl().getCaretOffset();
+
+ int rowIndex = currentRowIndex;
+ if (rowIndex + 1 == getControl().getContent().getLineCount()) {
+ return;
+ }
+
+ int colIdx = offset - getControl().getContent().getOffsetAtLine(rowIndex);
+ rowIndex += 1;
+
+ int lineOffset = getControl().getContent().getOffsetAtLine(rowIndex);
+ int newCaretPosition = lineOffset + colIdx;
+ int maxPosition = lineOffset + getControl().getContent().getLine(rowIndex).length();
+
+ moveCaretAbsolute(Math.min(newCaretPosition, maxPosition), select);
+ }
+
+ /**
+ * Action to move caret left
+ */
+ protected final StyledTextInputAction ACTION_MOVE_LEFT = new StyledTextInputAction(()->defaultLeft(false));
+ /**
+ * Action to move caret left while selecting
+ */
+ protected final StyledTextInputAction ACTION_SELECT_LEFT = new StyledTextInputAction(()->defaultLeft(true));
+
+ /**
+ * default implementation for {@link #ACTION_MOVE_LEFT} and {@link #ACTION_SELECT_LEFT}
+ * @param select whether to change the selection
+ */
+ protected void defaultLeft(boolean select) {
+ moveCaretRelative(-1, select);
+ }
+
+ /**
+ * Action to move caret right
+ */
+ protected final StyledTextInputAction ACTION_MOVE_RIGHT = new StyledTextInputAction(()->defaultRight(false));
+ /**
+ * Action to move caret right while selecting
+ */
+ protected final StyledTextInputAction ACTION_SELECT_RIGHT = new StyledTextInputAction(()->defaultRight(true));
+
+ /**
+ * default implementation for {@link #ACTION_MOVE_RIGHT} and {@link #ACTION_SELECT_RIGHT}
+ * @param select whether to change the selection
+ */
+ protected void defaultRight(boolean select) {
+ moveCaretRelative(1, select);
+ }
+
+
+ /**
+ * Action to scroll one line up
+ */
+ protected final StyledTextInputAction ACTION_SCROLL_LINE_UP = new StyledTextInputAction(this::defaultScrollLineUp);
+ /**
+ * default implementation for {@link #ACTION_SCROLL_LINE_UP}
+ */
+ protected void defaultScrollLineUp() {
+ ((StyledTextSkin)getControl().getSkin()).scrollLineUp();
+ }
+
+ /**
+ * Action to scroll one line down
+ */
+ protected final StyledTextInputAction ACTION_SCROLL_LINE_DOWN = new StyledTextInputAction(this::defaultScrollLineDown);
+ /**
+ * default implementation for {@link #ACTION_SCROLL_LINE_DOWN}
+ */
+ protected void defaultScrollLineDown() {
+ ((StyledTextSkin)getControl().getSkin()).scrollLineDown();
+ }
+
+
+ private void moveCaretAbsolute(int absoluteOffset) {
+ int offset = Math.max(0, absoluteOffset);
+ offset = Math.min(getControl().getCharCount(), offset);
+ getControl().setCaretOffset(offset);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void moveCaretAbsolute(int absoluteOffset, boolean select) {
+ int offset = Math.max(0, absoluteOffset);
+ offset = Math.min(getControl().getCharCount(), offset);
+ getControl().impl_setCaretOffset(offset, select);
+ }
+
+ private void moveCaretRelative(int deltaOffset, boolean select) {
+ int offset = getControl().getCaretOffset() + deltaOffset;
+ moveCaretAbsolute(offset, select);
+ }
+
+ protected static class KeyMapping {
+ public static enum MetaKey {
+ AltKey,
+ ControlKey,
+ MetaKey,
+ ShiftKey
+ }
+
+ public static class KeyCombo {
+ public final KeyCode code;
+ public final Set<MetaKey> meta;
+
+ public KeyCombo(KeyCode code, MetaKey... meta) {
+ this.code = code;
+ this.meta = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(meta)));
}
- case TAB:
- if (getControl().getEditable()) {
- event.consume();
- if (event.isShiftDown()) {
- // TODO Remove first 4 white space chars???
- break;
- } else {
- getControl().getContent().replaceTextRange(getControl().getCaretOffset(), 0, "\t"); //$NON-NLS-1$
- getControl().setCaretOffset(offset + 1);
- break;
- }
+
+ @Override
+ public String toString() {
+ return code + " " + meta;
}
- case V:
- if (getControl().getEditable()) {
- if (event.isShortcutDown()) {
- getControl().paste();
- event.consume();
- break;
- }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((code == null) ? 0 : code.hashCode());
+ result = prime * result + ((meta == null) ? 0 : meta.hashCode());
+ return result;
}
- case X:
- if (getControl().getEditable()) {
- if (event.isShortcutDown()) {
- getControl().cut();
- event.consume();
- break;
- }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ KeyCombo other = (KeyCombo) obj;
+ if (code != other.code)
+ return false;
+ if (meta == null) {
+ if (other.meta != null)
+ return false;
+ } else if (!meta.equals(other.meta))
+ return false;
+ return true;
}
- case C: {
- if (event.isShortcutDown()) {
- getControl().copy();
- event.consume();
- break;
+
+ }
+
+ public static interface Mapping {
+ Supplier<Boolean> getCondition();
+ InputAction getAction();
+ }
+
+ public static class SimpleMapping implements Mapping {
+ private final Supplier<Boolean> condition;
+ private final InputAction action;
+
+ @Override
+ public Supplier<Boolean> getCondition() {
+ return condition;
+ }
+
+ @Override
+ public InputAction getAction() {
+ return action;
+ }
+
+ public SimpleMapping(Supplier<Boolean> condition, InputAction action) {
+ this.condition = condition;
+ this.action = action;
}
+
}
- default:
- break;
+
+ public interface InputAction extends Runnable {
}
- }
- private void _keyTyped(KeyEvent event) {
- if (getControl().getEditable()) {
+ private Map<KeyCombo, List<Mapping>> comboMapping = new HashMap<>();
- String character = event.getCharacter();
- if (character.length() == 0) {
- return;
+ /**
+ * maps a key combination to an action
+ * @param combo
+ * @param action
+ */
+ public void mapKey(KeyCombo combo, InputAction action) {
+ List<Mapping> list = this.comboMapping.get(combo);
+ if (list == null) {
+ list = new ArrayList<>();
+ this.comboMapping.put(combo, list);
}
+ list.add(new SimpleMapping(()->true, action));
+ }
- // check the modifiers
- // - OS-X: ALT+L ==> @
- // - win32/linux: ALTGR+Q ==> @
- if (event.isControlDown() || event.isAltDown() || (Util.isMacOS() && event.isMetaDown())) {
- if (!((event.isControlDown() || Util.isMacOS()) && event.isAltDown()))
- return;
+ /**
+ * maps a key combination to an action
+ * @param combo
+ * @param action
+ * @param condition
+ */
+ public void mapKey(KeyCombo combo, InputAction action, Supplier<Boolean> condition) {
+ List<Mapping> list = this.comboMapping.get(combo);
+ if (list == null) {
+ list = new ArrayList<>();
+ this.comboMapping.put(combo, list);
}
+ list.add(new SimpleMapping(condition, action));
+ }
- if (character.charAt(0) > 31 // No ascii control chars
- && character.charAt(0) != 127 // no delete key
- && !event.isMetaDown()) {
- final int offset = getControl().getCaretOffset();
- getControl().getContent().replaceTextRange(getControl().getCaretOffset(), 0, character);
- getControl().setCaretOffset(offset + 1);
+ /**
+ * looks up the action for the specified combo.
+ * @param combo
+ * @return the input action
+ */
+ public Optional<InputAction> get(KeyCombo combo) {
+ final List<Mapping> list = this.comboMapping.getOrDefault(combo, Collections.emptyList());
+ return list.stream().filter(m->m.getCondition().get()).findFirst().map(m->m.getAction());
+ }
+
+ /**
+ * looks up the action for the specified key event
+ * @param event
+ * @return the input action
+ */
+ public Optional<InputAction> get(KeyEvent event) {
+ return this.get(createFromEvent(event));
+ }
+
+ /**
+ * unmaps all keys
+ */
+ public void clearKeyMappings() {
+ this.comboMapping.clear();
+ }
+
+ /**
+ * unmaps a key
+ * @param combo
+ */
+ public void unmapKey(KeyCombo combo) {
+ this.comboMapping.remove(combo);
+ }
+
+ public static KeyCombo createFromEvent(KeyEvent event) {
+ Set<MetaKey> metaKeys = new HashSet<>();
+ if (event.isShiftDown()) metaKeys.add(ShiftKey);
+ if (event.isControlDown()) metaKeys.add(ControlKey);
+ if (event.isAltDown()) metaKeys.add(AltKey);
+ if (event.isMetaDown()) metaKeys.add(MetaKey);
+
+ return new KeyCombo(event.getCode(), metaKeys.toArray(new MetaKey[] {}));
+ }
+
+ }
+
+ private class StyledTextInputAction implements KeyMapping.InputAction {
+ private final ActionType event;
+ private final Runnable fallback;
+
+ private final Runnable action;
+
+ public StyledTextInputAction(ActionType event, Runnable fallback) {
+ this.event = event;
+ this.fallback = fallback;
+ this.action = null;
+ }
+
+ public StyledTextInputAction(Runnable action) {
+ this.event = null;
+ this.fallback = null;
+ this.action = action;
+ }
+
+
+ @Override
+ public void run() {
+ if (this.event != null) {
+ ActionEvent evt = new ActionEvent(getControl(), getControl(), this.event);
+ Event.fireEvent(getControl(), evt);
+
+ if ( !evt.isConsumed() ) {
+ this.fallback.run();
+ }
+ }
+ else {
+ this.action.run();
}
}
}
/**
- * Send a mouse pressed
+ * initializes the key mappings.
*
+ * @param keyMapping the mapping
+ */
+ protected void initKeymapping(KeyMapping keyMapping) {
+
+ if (Util.isMacOS()) {
+
+ keyMapping.mapKey(new KeyCombo(LEFT, ControlKey), this.ACTION_NAVIGATE_LINE_START);
+ keyMapping.mapKey(new KeyCombo(A, ControlKey), this.ACTION_NAVIGATE_LINE_START);
+ keyMapping.mapKey(new KeyCombo(RIGHT, ControlKey), this.ACTION_NAVIGATE_LINE_END);
+ keyMapping.mapKey(new KeyCombo(E, ControlKey), this.ACTION_NAVIGATE_LINE_END);
+ keyMapping.mapKey(new KeyCombo(UP, ControlKey), this.ACTION_NAVIGATE_TEXT_START);
+ keyMapping.mapKey(new KeyCombo(DOWN, ControlKey), this.ACTION_NAVIGATE_TEXT_END);
+ keyMapping.mapKey(new KeyCombo(RIGHT, AltKey), this.ACTION_NAVIGATE_WORD_NEXT);
+ keyMapping.mapKey(new KeyCombo(LEFT, AltKey), this.ACTION_NAVIGATE_WORD_PREVIOUS);
+
+ keyMapping.mapKey(new KeyCombo(LEFT, ControlKey, ShiftKey), this.ACTION_SELECT_LINE_START);
+ keyMapping.mapKey(new KeyCombo(A, ControlKey, ShiftKey), this.ACTION_SELECT_LINE_START);
+ keyMapping.mapKey(new KeyCombo(RIGHT, ControlKey, ShiftKey), this.ACTION_SELECT_LINE_END);
+ keyMapping.mapKey(new KeyCombo(E, ControlKey, ShiftKey), this.ACTION_SELECT_LINE_END);
+ keyMapping.mapKey(new KeyCombo(UP, ControlKey, ShiftKey), this.ACTION_SELECT_TEXT_START);
+ keyMapping.mapKey(new KeyCombo(DOWN, ControlKey, ShiftKey), this.ACTION_SELECT_TEXT_END);
+ keyMapping.mapKey(new KeyCombo(RIGHT, AltKey, ShiftKey), this.ACTION_SELECT_WORD_NEXT);
+ keyMapping.mapKey(new KeyCombo(LEFT, AltKey, ShiftKey), this.ACTION_SELECT_WORD_PREVIOUS);
+
+ keyMapping.mapKey(new KeyCombo(DELETE, AltKey), this.ACTION_DELETE_WORD_NEXT);
+ keyMapping.mapKey(new KeyCombo(BACK_SPACE, AltKey), this.ACTION_DELETE_WORD_PREVIOUS);
+ keyMapping.mapKey(new KeyCombo(D, MetaKey), this.ACTION_DELETE_LINE);
+
+ keyMapping.mapKey(new KeyCombo(C, MetaKey), this.ACTION_COPY);
+ keyMapping.mapKey(new KeyCombo(V, MetaKey), this.ACTION_PASTE);
+ keyMapping.mapKey(new KeyCombo(X, MetaKey), this.ACTION_CUT);
+
+ }
+ else {
+ keyMapping.mapKey(new KeyCombo(RIGHT, ControlKey), this.ACTION_NAVIGATE_WORD_NEXT);
+ keyMapping.mapKey(new KeyCombo(LEFT, ControlKey), this.ACTION_NAVIGATE_WORD_PREVIOUS);
+
+ keyMapping.mapKey(new KeyCombo(RIGHT, ShiftKey, ControlKey), this.ACTION_SELECT_WORD_NEXT);
+ keyMapping.mapKey(new KeyCombo(LEFT, ShiftKey, ControlKey), this.ACTION_SELECT_WORD_PREVIOUS);
+
+ keyMapping.mapKey(new KeyCombo(HOME), this.ACTION_NAVIGATE_LINE_START);
+ keyMapping.mapKey(new KeyCombo(HOME, ShiftKey), this.ACTION_SELECT_LINE_START);
+ keyMapping.mapKey(new KeyCombo(HOME, ControlKey), this.ACTION_NAVIGATE_TEXT_START);
+ keyMapping.mapKey(new KeyCombo(HOME, ControlKey, ShiftKey), this.ACTION_SELECT_TEXT_START);
+
+ keyMapping.mapKey(new KeyCombo(END), this.ACTION_NAVIGATE_LINE_END);
+ keyMapping.mapKey(new KeyCombo(END, ShiftKey), this.ACTION_SELECT_LINE_END);
+ keyMapping.mapKey(new KeyCombo(END, ControlKey), this.ACTION_NAVIGATE_TEXT_END);
+ keyMapping.mapKey(new KeyCombo(END, ControlKey, ShiftKey), this.ACTION_SELECT_TEXT_END);
+
+ keyMapping.mapKey(new KeyCombo(DELETE, ControlKey), this.ACTION_DELETE_WORD_NEXT);
+ keyMapping.mapKey(new KeyCombo(BACK_SPACE, ControlKey), this.ACTION_DELETE_WORD_PREVIOUS);
+
+ keyMapping.mapKey(new KeyCombo(C, ControlKey), this.ACTION_COPY);
+ keyMapping.mapKey(new KeyCombo(V, ControlKey), this.ACTION_PASTE);
+ keyMapping.mapKey(new KeyCombo(X, ControlKey), this.ACTION_CUT);
+
+ keyMapping.mapKey(new KeyCombo(A, ControlKey), this.ACTION_SELECT_ALL);
+
+ keyMapping.mapKey(new KeyCombo(UP, ControlKey), this.ACTION_SCROLL_LINE_UP);
+ keyMapping.mapKey(new KeyCombo(DOWN, ControlKey), this.ACTION_SCROLL_LINE_DOWN);
+
+ keyMapping.mapKey(new KeyCombo(D, ControlKey), this.ACTION_DELETE_LINE);
+
+ keyMapping.mapKey(new KeyCombo(UP, AltKey), this.ACTION_MOVE_LINES_UP);
+ keyMapping.mapKey(new KeyCombo(DOWN, AltKey), this.ACTION_MOVE_LINES_DOWN);
+
+ }
+
+ keyMapping.mapKey(new KeyCombo(TAB), this.ACTION_INDENT, this::isMultilineSelection);
+ keyMapping.mapKey(new KeyCombo(TAB, ShiftKey), this.ACTION_OUTDENT);
+
+ keyMapping.mapKey(new KeyCombo(DELETE), this.ACTION_DELETE);
+ keyMapping.mapKey(new KeyCombo(BACK_SPACE), this.ACTION_BACKSPACE);
+
+ keyMapping.mapKey(new KeyCombo(ENTER), this.ACTION_NEW_LINE);
+
+ keyMapping.mapKey(new KeyCombo(UP), this.ACTION_MOVE_UP);
+ keyMapping.mapKey(new KeyCombo(DOWN), this.ACTION_MOVE_DOWN);
+ keyMapping.mapKey(new KeyCombo(LEFT), this.ACTION_MOVE_LEFT);
+ keyMapping.mapKey(new KeyCombo(RIGHT), this.ACTION_MOVE_RIGHT);
+
+ keyMapping.mapKey(new KeyCombo(UP, ShiftKey), this.ACTION_SELECT_UP);
+ keyMapping.mapKey(new KeyCombo(DOWN, ShiftKey), this.ACTION_SELECT_DOWN);
+ keyMapping.mapKey(new KeyCombo(LEFT, ShiftKey), this.ACTION_SELECT_LEFT);
+ keyMapping.mapKey(new KeyCombo(RIGHT, ShiftKey), this.ACTION_SELECT_RIGHT);
+
+
+ // action for insert tab support
+ keyMapping.mapKey(new KeyCombo(TAB), ()->getControl().insert("\t")); //$NON-NLS-1$
+ }
+
+ /**
+ * computes the text offset under the mouse cursor.
* @param event
- * the event
- * @param visibleCells
- * the visible cells
- * @param selection
- * are we in selection mode
- * @return if the cursor update succeeded
+ * @return the offset
*/
- @SuppressWarnings("deprecation")
- public boolean updateCursor(MouseEvent event, List<LineCell> visibleCells, boolean selection) {
+ protected int computeCursorOffset(MouseEvent event) {
+ List<LineCell> visibleCells = ((StyledTextSkin) getControl().getSkin()).getCurrentVisibleCells();
+
LineCell lastCell = null;
+ int result = getControl().getContent().getCharCount();
+
for (LineCell tmp : visibleCells) {
Bounds boundsInParent = tmp.getBoundsInParent();
if (boundsInParent.getMinY() > event.getY()) {
@@ -352,25 +1331,21 @@ public class StyledTextBehavior {
if (n.localToScene(n.getBoundsInLocal()).contains(event.getSceneX(), event.getSceneY())) {
int index = n.getCaretIndexAtPoint(n.sceneToLocal(event.getSceneX(), event.getSceneY()));
if (index >= 0) {
- getControl().impl_setCaretOffset(n.getStartOffset() + index, selection);
- return true;
+ return n.getStartOffset() + index;
}
}
- int offset = lastCell.getDomainElement().getLineOffset() + lastCell.getDomainElement().getLineLength();
- getControl().impl_setCaretOffset(offset, selection);
+ final double minX = n.localToScene(n.getBoundsInLocal()).getMinX();
+ final double mouseX = event.getSceneX();
+ final boolean left = minX >= mouseX;
+ result = lastCell.getDomainElement().getLineOffset() + (left ? 0 : lastCell.getDomainElement().getLineLength());
}
break;
}
lastCell = tmp;
}
- getControl().requestFocus();
- Event.fireEvent(getControl(), event.copyFor(getControl(), getControl()));
- return false;
+ return result;
}
- // public void mouseDragged(MouseEvent event, List<LineCell> visibleCells) {
- //
- // }
}
diff --git a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java
index d881a91af..587e5b164 100644
--- a/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java
+++ b/bundles/runtime/org.eclipse.fx.ui.controls/src/org/eclipse/fx/ui/controls/styledtext/skin/StyledTextSkin.java
@@ -6,6 +6,7 @@
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
+ * Christoph Caks <ccaks@bestsolution.at> - improved editor behavior
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
*******************************************************************************/
package org.eclipse.fx.ui.controls.styledtext.skin;
@@ -36,7 +37,6 @@ import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Node;
@@ -45,7 +45,6 @@ import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
-import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
@@ -104,7 +103,6 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
return new MyListViewSkin(this);
}
};
- styledText.addEventHandler(MouseEvent.MOUSE_PRESSED, behavior::mousePressed);
initializeContentViewer(this.contentView);
@@ -117,6 +115,22 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
this.rootContainer.getChildren().addAll(this.contentView);
getChildren().addAll(this.rootContainer);
+// styledText.getAnnotations().addListener(new SetChangeListener<StyledTextAnnotation>() {
+// @Override
+// public void onChanged(javafx.collections.SetChangeListener.Change<? extends StyledTextAnnotation> change) {
+// if (change.getElementAdded() != null) {
+// StyledTextAnnotation a = change.getElementAdded();
+// addAnnotation(a);
+// System.err.println("removed " + a.getId() + " " + a.getType() + ": " + a.getText() + " @ " + a.getStartOffset() + ", " + a.getLength());
+// }
+// if (change.getElementRemoved() != null) {
+// StyledTextAnnotation a = change.getElementRemoved();
+// removeAnnotation(a);
+// System.err.println("added " + a.getId() + " " + a.getType() + ": " + a.getText() + " @ " + a.getStartOffset() + ", " + a.getLength());
+// }
+// }
+// });
+
styledText.caretOffsetProperty().addListener(new ChangeListener<Number>() {
@Override
@@ -179,6 +193,41 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
});
}
+// private void addAnnotation(StyledTextAnnotation annotation) {
+// int startOffset = annotation.getStartOffset();
+// int endOffset = annotation.getStartOffset() + annotation.getLength();
+//
+// int startLineIndex = getSkinnable().getContent().getLineAtOffset(startOffset);
+// int endLineIndex = getSkinnable().getContent().getLineAtOffset(endOffset);
+//
+// Set<Line> affectedLines = IntStream.range(startLineIndex, endLineIndex + 1).mapToObj(StyledTextSkin.this.lineList::get).collect(Collectors.toSet());
+//
+// for (LineCell c : getCurrentVisibleCells()) {
+// if (affectedLines.contains(c.domainElement)) {
+// StyledTextLayoutContainer p = (StyledTextLayoutContainer) c.getGraphic();
+// p.getAnnotations().add(annotation);
+// }
+// }
+// }
+//
+// private void removeAnnotation(StyledTextAnnotation annotation) {
+// int startOffset = annotation.getStartOffset();
+// int endOffset = annotation.getStartOffset() + annotation.getLength();
+//
+// int startLineIndex = getSkinnable().getContent().getLineAtOffset(startOffset);
+// int endLineIndex = getSkinnable().getContent().getLineAtOffset(endOffset);
+//
+// Set<Line> affectedLines = IntStream.range(startLineIndex, endLineIndex + 1).mapToObj(StyledTextSkin.this.lineList::get).collect(Collectors.toSet());
+//
+// for (LineCell c : getCurrentVisibleCells()) {
+// if (affectedLines.contains(c.domainElement)) {
+// StyledTextLayoutContainer p = (StyledTextLayoutContainer) c.getGraphic();
+// p.getAnnotations().remove(annotation);
+// }
+// }
+// }
+
+
StyledTextBehavior getBehavior() {
return this.behavior;
}
@@ -217,15 +266,7 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
// this.contentView.setFixedCellSize(value);
// this.contentView.setFixedCellSize(15);
- contentView.setOnMousePressed( e -> getBehavior().handleContentMouseEvent(e, getCurrentVisibleCells()));
- contentView.setOnMouseDragged(new EventHandler<MouseEvent>() {
-
- @Override
- public void handle(MouseEvent event) {
- getBehavior().updateCursor(event, getCurrentVisibleCells(), true);
- event.consume();
- }
- });
+ getBehavior().installContentListeners(contentView);
}
private StyledTextLayoutContainer currentActiveNode;
@@ -352,13 +393,29 @@ public class StyledTextSkin extends SkinBase<StyledTextArea> {
}
}
- List<LineCell> getCurrentVisibleCells() {
+ public List<LineCell> getCurrentVisibleCells() {
if (this.contentView == null || this.contentView.getSkin() == null) {
return Collections.emptyList();
}
return ((MyListViewSkin) this.contentView.getSkin()).getFlow().getCells();
}
+ public void scrollLineUp() {
+ MyListViewSkin s = ((MyListViewSkin) this.contentView.getSkin());
+
+ LineCell top = s.getFlow().getFirstVisibleCellWithinViewPort();
+
+ this.contentView.scrollTo(top.getIndex() - 1);
+ }
+
+ public void scrollLineDown() {
+ MyListViewSkin s = ((MyListViewSkin) this.contentView.getSkin());
+
+ LineCell top = s.getFlow().getFirstVisibleCellWithinViewPort();
+
+ this.contentView.scrollTo(top.getIndex() + 1);
+ }
+
/**
* A line cell
*/

Back to the top