| /******************************************************************************* |
| * Copyright (c) 2014 BestSolution.at and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * 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 java.text.BreakIterator; |
| import java.util.List; |
| import java.util.Optional; |
| |
| import org.eclipse.fx.core.IntTuple; |
| import org.eclipse.fx.core.Util; |
| import org.eclipse.fx.core.text.DefaultTextEditActions; |
| import org.eclipse.fx.core.text.TextEditAction; |
| import org.eclipse.fx.core.text.TextUtil; |
| import org.eclipse.fx.ui.controls.styledtext.StyledTextArea; |
| import org.eclipse.fx.ui.controls.styledtext.StyledTextArea.CustomQuickLink; |
| import org.eclipse.fx.ui.controls.styledtext.StyledTextArea.QuickLink; |
| import org.eclipse.fx.ui.controls.styledtext.StyledTextArea.QuickLinkable; |
| import org.eclipse.fx.ui.controls.styledtext.StyledTextArea.SimpleQuickLink; |
| import org.eclipse.fx.ui.controls.styledtext.StyledTextContent; |
| import org.eclipse.fx.ui.controls.styledtext.TextSelection; |
| import org.eclipse.fx.ui.controls.styledtext.TriggerActionMapping; |
| import org.eclipse.fx.ui.controls.styledtext.TriggerActionMapping.Context; |
| import org.eclipse.fx.ui.controls.styledtext.VerifyEvent; |
| import org.eclipse.fx.ui.controls.styledtext.events.TextPositionEvent; |
| import org.eclipse.fx.ui.controls.styledtext.events.UndoHintEvent; |
| import org.eclipse.fx.ui.controls.styledtext.internal.TextNode; |
| import org.eclipse.fx.ui.controls.styledtext.skin.StyledTextSkin; |
| import org.eclipse.jdt.annotation.NonNull; |
| |
| import javafx.css.PseudoClass; |
| import javafx.event.Event; |
| import javafx.geometry.Point2D; |
| import javafx.scene.Cursor; |
| import javafx.scene.input.KeyCode; |
| import javafx.scene.input.KeyCombination; |
| import javafx.scene.input.KeyEvent; |
| import javafx.scene.input.MouseButton; |
| import javafx.scene.input.MouseEvent; |
| |
| /** |
| * Behavior for styled text |
| */ |
| public class StyledTextBehavior { |
| |
| private TriggerActionMapping keyTriggerMapping = new TriggerActionMapping(); |
| |
| private TextPositionSupport textPositionSupport; |
| private HoverSupport hoverSupport; |
| |
| private final StyledTextArea styledText; |
| |
| /** |
| * Create a new behavior |
| * |
| * @param styledText |
| * the styled text control |
| */ |
| public StyledTextBehavior(StyledTextArea styledText) { |
| this.styledText = styledText; |
| styledText.addEventHandler(KeyEvent.KEY_PRESSED, this::onKeyPressed); |
| styledText.addEventHandler(KeyEvent.KEY_TYPED, this::onKeyTyped); |
| |
| styledText.addEventHandler(MouseEvent.MOUSE_PRESSED, this::onMousePressed); |
| styledText.addEventHandler(TextPositionEvent.TEXT_POSITION_MOVED, this::onTextPositionMoved); |
| |
| this.keyTriggerMapping.subscribe(this::defaultHandle); |
| initKeymapping(this.keyTriggerMapping); |
| |
| |
| styledText.addEventHandler(TextPositionEvent.TEXT_POSITION_PRESSED, this::onTextPositionPressed); |
| styledText.addEventHandler(TextPositionEvent.TEXT_POSITION_CLICKED, this::onTextPositionClicked); |
| styledText.addEventHandler(TextPositionEvent.TEXT_POSITION_RELEASED, this::onTextPositionReleased); |
| styledText.addEventHandler(TextPositionEvent.TEXT_POSITION_DRAGGED, this::onTextPositionDragged); |
| styledText.addEventHandler(TextPositionEvent.TEXT_POSITION_DRAG_DETECTED, this::onTextPositionDragDetected); |
| |
| this.keyTriggerMapping.overrideProperty().bind(styledText.overrideActionMappingProperty()); |
| } |
| |
| /** |
| * Install the content listener |
| * |
| * @param contentNode |
| * the content node |
| */ |
| public void installContentListeners(final javafx.scene.layout.Region contentNode) { |
| this.textPositionSupport = TextPositionSupport.install(contentNode, getControl()); |
| this.hoverSupport = HoverSupport.install(contentNode); |
| } |
| |
| // 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(this.start, this.length); |
| } |
| |
| public void replace(@NonNull String replacement) { |
| getControl().getContent().replaceTextRange(this.start, this.length, replacement); |
| } |
| |
| public void selectWithCaretAtStart() { |
| moveCaretAbsolute(this.start); |
| getControl().setSelection(new TextSelection(this.start, this.length)); |
| } |
| |
| public void selectWithCaretAtEnd() { |
| moveCaretAbsolute(this.end); |
| getControl().setSelection(new TextSelection(this.start, this.length)); |
| } |
| |
| } |
| |
| // state for dnd stuff |
| private volatile boolean pressedInSelection = false; |
| private volatile boolean dragMoveTextMode = false; |
| private volatile boolean dragSelectionMode = false; |
| private volatile int dragMoveTextOffset = -1; |
| private volatile int dragMoveTextLength = -1; |
| private int mousePressedOffset = -1; |
| |
| private static 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); |
| return r; |
| } |
| |
| private void onKeyPressed(KeyEvent event) { |
| |
| if( this.keyTriggerMapping.exists(event) ) { |
| getControl().fireEvent(UndoHintEvent.createBeginCompoundChangeEvent()); |
| try { |
| boolean handled = this.keyTriggerMapping.triggerAction(event, new Context(getControl())); |
| if (handled) { |
| event.consume(); |
| return; |
| } |
| } |
| finally { |
| getControl().fireEvent(UndoHintEvent.createEndCompoundChangeEvent()); |
| } |
| } |
| |
| if( this.dragMoveTextMode ) { |
| |
| if (event.getCode() == KeyCode.ESCAPE) { |
| // Bug 491693 - StyledTextArea - Drag And Drop not canceled on Esc |
| // cancel dnd operation |
| this.dragMoveTextMode = false; |
| this.pressedInSelection = false; // to prevent selection changes after cancel |
| |
| // update insertion marker |
| ((StyledTextSkin)getControl().getSkin()).updateInsertionMarkerIndex(-1); |
| |
| getControl().pseudoClassStateChanged(DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE, false); |
| getControl().pseudoClassStateChanged(DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE, false); |
| |
| event.consume(); |
| return; |
| } |
| |
| if( event.isShortcutDown() ) { |
| getControl().pseudoClassStateChanged(DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE, false); |
| getControl().pseudoClassStateChanged(DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE, true); |
| } else { |
| getControl().pseudoClassStateChanged(DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE, true); |
| getControl().pseudoClassStateChanged(DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE, false); |
| } |
| } |
| |
| 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(); |
| } |
| } else if( org.eclipse.fx.ui.controls.Util.MNEMONICS_FIX ) { |
| // Fix JavaFX bug with invalid mnemonics activation on ALTGR |
| if( event.isAltDown() && event.isControlDown() ) { |
| event.consume(); |
| } |
| } |
| |
| if (evt.isConsumed()) { |
| event.consume(); |
| return; |
| } |
| |
| // tab insertion if not otherwise handled |
| if (KeyCombination.keyCombination("Tab").match(event)) { //$NON-NLS-1$ |
| getControl().insert("\t"); //$NON-NLS-1$ |
| 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); |
| |
| // check for typed char action |
| |
| if( this.keyTriggerMapping.exists(event) ) { |
| getControl().fireEvent(UndoHintEvent.createBeginCompoundChangeEvent()); |
| try { |
| this.keyTriggerMapping.triggerAction(character.charAt(0), new Context(getControl())); |
| } |
| finally { |
| getControl().fireEvent(UndoHintEvent.createEndCompoundChangeEvent()); |
| } |
| } |
| } |
| } |
| } |
| |
| private void onTextPositionDragDetected(TextPositionEvent event) { |
| if (this.pressedInSelection) { |
| if( org.eclipse.fx.ui.controls.Util.isCopyEvent(event) ) { |
| getControl().pseudoClassStateChanged(DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE, true); |
| } else { |
| getControl().pseudoClassStateChanged(DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE, true); |
| } |
| this.dragMoveTextMode = true; |
| this.dragMoveTextOffset = getControl().getSelection().offset; |
| this.dragMoveTextLength = getControl().getSelection().length; |
| } else { |
| this.dragSelectionMode = true; |
| } |
| } |
| |
| private void onTextPositionDragged(TextPositionEvent event) { |
| if (this.dragSelectionMode) { |
| if( event.getOffset() >= 0 ) { |
| moveCaretAbsolute(event.getOffset(), true); |
| } |
| event.consume(); |
| } else if (this.dragMoveTextMode) { |
| // update insertion marker |
| ((StyledTextSkin)getControl().getSkin()).updateInsertionMarkerIndex(event.getOffset()); |
| |
| if( org.eclipse.fx.ui.controls.Util.isCopyEvent(event) ) { |
| getControl().pseudoClassStateChanged(DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE, false); |
| getControl().pseudoClassStateChanged(DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE, true); |
| } else { |
| getControl().pseudoClassStateChanged(DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE, true); |
| getControl().pseudoClassStateChanged(DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE, false); |
| } |
| event.consume(); |
| } |
| } |
| |
| private void onTextPositionReleased(TextPositionEvent event) { |
| if (this.dragSelectionMode) { |
| this.dragSelectionMode = false; |
| event.consume(); |
| } else if (this.dragMoveTextMode) { |
| // update insertion marker |
| ((StyledTextSkin)getControl().getSkin()).updateInsertionMarkerIndex(-1); |
| |
| getControl().pseudoClassStateChanged(DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE, false); |
| getControl().pseudoClassStateChanged(DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE, false); |
| |
| int targetOffset = event.getOffset(); |
| |
| if( getControl().getSelection().contains(targetOffset) ) { |
| this.dragMoveTextMode = false; |
| event.consume(); |
| return; |
| } |
| |
| if( targetOffset < 0 ) { |
| this.dragMoveTextMode = false; |
| event.consume(); |
| return; |
| } |
| |
| // read text |
| @NonNull |
| String text = getControl().getContent().getTextRange(this.dragMoveTextOffset, this.dragMoveTextLength); |
| |
| // notify the undo implementation that a compund change occurs |
| getControl().fireEvent(UndoHintEvent.createBeginCompoundChangeEvent()); |
| try { |
| if( ! org.eclipse.fx.ui.controls.Util.isCopyEvent(event) ) { |
| // replace |
| if (isInRange(targetOffset, this.dragMoveTextOffset, this.dragMoveTextLength)) { |
| targetOffset = this.dragMoveTextOffset; |
| } |
| // after |
| else if (targetOffset >= this.dragMoveTextOffset + this.dragMoveTextLength) { |
| targetOffset -= this.dragMoveTextLength; |
| } |
| |
| // remove |
| getControl().getContent().replaceTextRange(this.dragMoveTextOffset, this.dragMoveTextLength, ""); //$NON-NLS-1$ |
| } |
| |
| // insert |
| getControl().getContent().replaceTextRange(targetOffset, 0, text); |
| |
| // move caret to end of insertion and select the text |
| moveCaretAbsolute(targetOffset + text.length()); |
| getControl().setSelection(new TextSelection(targetOffset, text.length())); |
| } |
| finally { |
| getControl().fireEvent(UndoHintEvent.createEndCompoundChangeEvent()); |
| } |
| |
| |
| this.dragMoveTextMode = false; |
| event.consume(); |
| } else if (this.pressedInSelection) { |
| moveCaretAbsolute(this.mousePressedOffset, event.isShiftDown()); |
| event.consume(); |
| } |
| |
| // in any case reset pressedInSelection |
| this.pressedInSelection = false; |
| } |
| |
| private void onTextPositionPressed(TextPositionEvent event) { |
| if( event.getButton() != MouseButton.PRIMARY ) { |
| return; |
| } |
| |
| this.mousePressedOffset = event.getOffset(); |
| |
| if( this.mousePressedOffset < 0 ) { |
| this.mousePressedOffset = getControl().getContent().getCharCount(); |
| } |
| |
| if (isInSelection(this.mousePressedOffset)) { |
| this.pressedInSelection = true; |
| event.consume(); |
| } else { |
| moveCaretAbsolute(this.mousePressedOffset, event.isShiftDown()); |
| event.consume(); |
| } |
| } |
| |
| private void doLink(QuickLink link) { |
| if (link instanceof SimpleQuickLink) { |
| SimpleQuickLink simple = (SimpleQuickLink) link; |
| getControl().setCaretOffset(simple.getRegion().upperEndpoint()); |
| getControl().setSelection(new TextSelection(simple.getRegion().lowerEndpoint(), simple.getRegion().upperEndpoint() - simple.getRegion().lowerEndpoint())); |
| } |
| else if (link instanceof CustomQuickLink) { |
| CustomQuickLink custom = (CustomQuickLink) link; |
| custom.getAction().run(); |
| } |
| } |
| |
| private void onTextPositionClicked(TextPositionEvent event) { |
| if (event.isStillSincePress()) { |
| if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) { |
| this.keyTriggerMapping.triggerAction(DefaultTextEditActions.SELECT_WORD, new Context(getControl())); |
| event.consume(); |
| } |
| if (event.getClickCount() == 3 && event.getButton() == MouseButton.PRIMARY) { |
| this.keyTriggerMapping.triggerAction(DefaultTextEditActions.SELECT_LINE, new Context(getControl())); |
| event.consume(); |
| } |
| } |
| |
| if (event.isShortcutDown()) { |
| Optional<QuickLinkable> linkable = getControl().getQuickLinkCallback().apply(event.getOffset()); |
| linkable.ifPresent((l) -> { |
| if (l.getLinks().size() == 1) { |
| doLink(l.getLinks().get(0)); |
| } |
| else { |
| // TODO handle case of multiple links |
| } |
| }); |
| |
| } |
| |
| } |
| |
| /** |
| * @return the control |
| */ |
| protected StyledTextArea getControl() { |
| return this.styledText; |
| } |
| |
| private void onMousePressed(MouseEvent event) { |
| getControl().requestFocus(); |
| } |
| |
| private Optional<TextNode> currentQuickLinkNode = Optional.empty(); |
| |
| private static final PseudoClass QUICK_LINK_ACTIVE_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("quick-link-active"); //$NON-NLS-1$ |
| private static final PseudoClass DRAG_TEXT_MOVE_ACTIVE_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("drag-text-move-active"); //$NON-NLS-1$ |
| private static final PseudoClass DRAG_TEXT_COPY_ACTIVE_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("drag-text-copy-active"); //$NON-NLS-1$ |
| |
| private void setCurrentQuickLinkNode(Optional<TextNode> node) { |
| if (node.isPresent()) { |
| this.getControl().setCursor(Cursor.HAND); |
| } |
| else { |
| this.getControl().setCursor(null); |
| } |
| this.currentQuickLinkNode.ifPresent(n->n.getStyleClass().remove("quick_link")); //$NON-NLS-1$ |
| this.currentQuickLinkNode.ifPresent(n->n.setCursor(null)); |
| if( this.currentQuickLinkNode.isPresent() != node.isPresent() ) { |
| getControl().pseudoClassStateChanged(QUICK_LINK_ACTIVE_PSEUDOCLASS_STATE, ! this.currentQuickLinkNode.isPresent()); |
| } |
| |
| this.currentQuickLinkNode = node; |
| this.currentQuickLinkNode.ifPresent(n->n.getStyleClass().add("quick_link")); //$NON-NLS-1$ |
| this.currentQuickLinkNode.ifPresent(n->n.requestLayout()); |
| } |
| |
| private void onTextPositionMoved(TextPositionEvent event) { |
| if (event.isShortcutDown()) { |
| Optional<QuickLinkable> linkable = getControl().getQuickLinkCallback().apply(event.getOffset()); |
| if (linkable.isPresent()) { |
| Point2D screenLocation = new Point2D(event.getScreenX(), event.getScreenY()); |
| Optional<TextNode> node = ((StyledTextSkin)getControl().getSkin()).findTextNode(screenLocation); |
| |
| if (!this.currentQuickLinkNode.equals(node)) { |
| // changed |
| setCurrentQuickLinkNode(node); |
| } |
| } |
| else { |
| setCurrentQuickLinkNode(Optional.empty()); |
| } |
| |
| } |
| else { |
| setCurrentQuickLinkNode(Optional.empty()); |
| } |
| } |
| |
| private int computeCurrentLineNumber() { |
| final int offset = getControl().getCaretOffset(); |
| return getControl().getLineAtOffset(offset); |
| } |
| |
| private int computeCurrentLineStartOffset() { |
| final int lineNumber = computeCurrentLineNumber(); |
| return getControl().getOffsetAtLine(lineNumber); |
| } |
| |
| protected boolean defaultHandle(TextEditAction action, Context context) { |
| if (action == DefaultTextEditActions.TEXT_START) { |
| defaultNavigateTextStart(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.TEXT_END) { |
| defaultNavigateTextEnd(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.TEXT_PAGE_DOWN) { |
| defaultNavigateTextPageDown(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.TEXT_PAGE_UP) { |
| defaultNavigateTextPageUp(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.LINE_START) { |
| defaultNavigateLineStart(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.LINE_END) { |
| defaultNavigateLineEnd(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.WORD_NEXT) { |
| defaultNavigateWordNext(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.WORD_PREVIOUS) { |
| defaultNavigateWordPrevious(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_TEXT_START) { |
| defaultSelectTextStart(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_TEXT_END) { |
| defaultSelectTextEnd(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_TEXT_PAGE_DOWN) { |
| defaultSelectTextPageDown(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_TEXT_PAGE_UP) { |
| defaultSelectTextPageUp(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_LINE_START) { |
| defaultSelectLineStart(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_LINE_END) { |
| defaultSelectLineEnd(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_WORD_NEXT) { |
| defaultSelectWordNext(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_WORD_PREVIOUS) { |
| defaultSelectWordPrevious(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_WORD) { |
| defaultSelectWord(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_LINE) { |
| defaultSelectLine(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.DELETE_LINE) { |
| defaultDeleteLine(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.DELETE_WORD_NEXT) { |
| defaultDeleteWordNext(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.DELETE_WORD_PREVIOUS) { |
| defaultDeleteWordPrevious(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.MOVE_LINES_UP) { |
| defaultMoveLinesUp(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.MOVE_LINES_DOWN) { |
| defaultMoveLinesDown(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.INDENT) { |
| defaultIndent(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.OUTDENT) { |
| defaultOutdent(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.NEW_LINE) { |
| defaultNewLine(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_ALL) { |
| defaultSelectAll(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.CUT) { |
| defaultCut(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.COPY) { |
| defaultCopy(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.PASTE) { |
| defaultPaste(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.DELETE) { |
| defaultDelete(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.DELETE_PREVIOUS) { |
| defaultDeletePrevious(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.DELETE_LINE) { |
| defaultDeleteLine(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SCROLL_LINE_UP) { |
| defaultScrollLineUp(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SCROLL_LINE_DOWN) { |
| defaultScrollLineDown(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.NAVIGATE_TO_LINE) { |
| defaultNavigateLineEnd(); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.MOVE_UP) { |
| defaultUp(false); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.MOVE_LEFT) { |
| defaultLeft(false); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.MOVE_RIGHT) { |
| defaultRight(false); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.MOVE_DOWN) { |
| defaultDown(false); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_UP) { |
| defaultUp(true); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_LEFT) { |
| defaultLeft(true); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_RIGHT) { |
| defaultRight(true); |
| return true; |
| } |
| else if (action == DefaultTextEditActions.SELECT_DOWN) { |
| defaultDown(true); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#TEXT_START} |
| */ |
| protected void defaultNavigateTextStart() { |
| getControl().setCaretOffset(0); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#TEXT_END} |
| */ |
| protected void defaultNavigateTextEnd() { |
| getControl().setCaretOffset(getControl().getContent().getCharCount()); |
| } |
| |
| private int calculatePageDownOffset() { |
| final int linesPerPage = ((StyledTextSkin)getControl().getSkin()).getVisibleLineCount(); |
| final int globalOffset = getControl().getCaretOffset(); |
| final int lineIndex = getControl().getContent().getLineAtOffset(globalOffset); |
| final int lineLocalOffset = globalOffset - getControl().getContent().getOffsetAtLine(lineIndex); |
| |
| final int newLineIndex = Math.min(getControl().getContent().getLineCount()-1, lineIndex + linesPerPage); |
| final int newLineLocalOffset = Math.min(getControl().getContent().getLine(newLineIndex).length(), lineLocalOffset); |
| |
| return getControl().getOffsetAtLine(newLineIndex) + newLineLocalOffset; |
| } |
| |
| private int calculatePageUpOffset() { |
| final int linesPerPage = ((StyledTextSkin)getControl().getSkin()).getVisibleLineCount(); |
| final int globalOffset = getControl().getCaretOffset(); |
| final int lineIndex = getControl().getContent().getLineAtOffset(globalOffset); |
| final int lineLocalOffset = globalOffset - getControl().getContent().getOffsetAtLine(lineIndex); |
| |
| final int newLineIndex = Math.max(0, lineIndex - linesPerPage); |
| final int newLineLocalOffset = Math.min(getControl().getContent().getLine(newLineIndex).length(), lineLocalOffset); |
| |
| return getControl().getOffsetAtLine(newLineIndex) + newLineLocalOffset; |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#TEXT_PAGE_UP} |
| */ |
| protected void defaultSelectTextPageUp() { |
| moveCaretAbsolute(calculatePageUpOffset(), true); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#TEXT_PAGE_DOWN} |
| */ |
| protected void defaultSelectTextPageDown() { |
| moveCaretAbsolute(calculatePageDownOffset(), true); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#LINE_START} |
| */ |
| protected void defaultNavigateLineStart() { |
| // TODO Should be position to the first none whitespace char?? |
| moveCaretAbsolute(computeCurrentLineStartOffset()); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#LINE_END} |
| */ |
| protected void defaultNavigateLineEnd() { |
| final int caretLine = computeCurrentLineNumber(); |
| moveCaretAbsolute(getControl().getContent().getOffsetAtLine(caretLine) + getControl().getContent().getLine(caretLine).length()); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#WORD_NEXT} |
| */ |
| protected void defaultNavigateWordNext() { |
| int following = TextUtil.findWordEndOffset(getControl().getContent(), getControl().getCaretOffset(), true); |
| if (following != BreakIterator.DONE) { |
| moveCaretAbsolute(following); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#WORD_PREVIOUS} |
| */ |
| protected void defaultNavigateWordPrevious() { |
| int previous = TextUtil.findWordStartOffset(getControl().getContent(), getControl().getCaretOffset(), true); |
| if (previous != BreakIterator.DONE) { |
| moveCaretAbsolute(previous); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#TEXT_START} |
| */ |
| protected void defaultSelectTextStart() { |
| moveCaretAbsolute(0, true); |
| } |
| |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#TEXT_END} |
| */ |
| protected void defaultSelectTextEnd() { |
| moveCaretAbsolute(getControl().getCharCount(), true); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SELECT_TEXT_PAGE_UP} |
| */ |
| protected void defaultNavigateTextPageUp() { |
| moveCaretAbsolute(calculatePageUpOffset(), false); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SELECT_TEXT_PAGE_DOWN} |
| */ |
| protected void defaultNavigateTextPageDown() { |
| moveCaretAbsolute(calculatePageDownOffset(), false); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#LINE_START} |
| */ |
| protected void defaultSelectLineStart() { |
| // TODO Should be position to the first none whitespace char?? |
| moveCaretAbsolute(computeCurrentLineStartOffset(), true); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#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); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SELECT_WORD_NEXT} |
| */ |
| protected void defaultSelectWordNext() { |
| int following = TextUtil.findWordEndOffset( |
| getControl().getContent(), |
| getControl().getCaretOffset(), true); |
| if (following != BreakIterator.DONE) { |
| moveCaretAbsolute(following, true); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SELECT_WORD_PREVIOUS} |
| */ |
| protected void defaultSelectWordPrevious() { |
| int previous = TextUtil.findWordStartOffset( |
| getControl().getContent(), |
| getControl().getCaretOffset(), true); |
| if (previous != BreakIterator.DONE) { |
| moveCaretAbsolute(previous, true); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SELECT_WORD} |
| */ |
| protected void defaultSelectWord() { |
| IntTuple bounds = TextUtil.findWordBounds(getControl().getContent(), getControl().getCaretOffset(), true); |
| int previous = bounds.value1; |
| int next = bounds.value2; |
| if (previous != BreakIterator.DONE && next != BreakIterator.DONE) { |
| moveCaretAbsolute(previous); |
| moveCaretAbsolute(next, true); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SELECT_LINE} |
| */ |
| protected void defaultSelectLine() { |
| @NonNull |
| LineRegion lineRegion = getLineRegion(getControl().getSelection()); |
| lineRegion.selectWithCaretAtEnd(); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#DELETE_LINE} |
| */ |
| protected void defaultDeleteLine() { |
| LineRegion lineRegion = getLineRegion(getControl().getSelection()); |
| lineRegion.replace(""); //$NON-NLS-1$ |
| moveCaretAbsolute(lineRegion.start); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#DELETE_WORD_NEXT} |
| */ |
| protected void defaultDeleteWordNext() { |
| int offset = getControl().getCaretOffset(); |
| int following = TextUtil.findWordEndOffset( |
| getControl().getContent(), |
| offset, true); |
| if (following != BreakIterator.DONE) { |
| getControl().getContent().replaceTextRange(getControl().getCaretOffset(), following - offset, ""); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#DELETE_WORD_PREVIOUS} |
| */ |
| protected void defaultDeleteWordPrevious() { |
| int offset = getControl().getCaretOffset(); |
| int previous = TextUtil.findWordStartOffset(getControl().getContent(), offset, true); |
| if (previous != BreakIterator.DONE) { |
| getControl().setCaretOffset(previous); |
| getControl().getContent().replaceTextRange(previous, offset - previous, ""); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#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$ |
| } |
| |
| all.replace(moveTargetText + aboveText); |
| new LineRegion(moveTarget.firstLine - 1, moveTarget.lastLine - 1).selectWithCaretAtStart(); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#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$ |
| } |
| |
| all.replace(belowText + moveTargetText); |
| new LineRegion(moveTarget.firstLine + 1, moveTarget.lastLine + 1).selectWithCaretAtStart(); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#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; |
| } |
| } |
| |
| if (getControl().getSelection().length > 0) { |
| int caretPos = getControl().getSelection().offset + getControl().getLineSeparator().getValue().length() + prefix.length(); |
| getControl().getContent().replaceTextRange(getControl().getSelection().offset, getControl().getSelection().length, getControl().getLineSeparator().getValue() + prefix); |
| getControl().setCaretOffset( caretPos ); |
| } |
| else { |
| getControl().getContent().replaceTextRange(getControl().getCaretOffset(), 0, getControl().getLineSeparator().getValue() + prefix); |
| getControl().setCaretOffset(offset + getControl().getLineSeparator().getValue().length() + prefix.length()); |
| } |
| |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SELECT_ALL} |
| */ |
| protected void defaultSelectAll() { |
| int length = getControl().getContent().getCharCount(); |
| getControl().setSelectionRange(0, length); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#COPY} |
| */ |
| protected void defaultCopy() { |
| getControl().copy(); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#PASTE} |
| */ |
| protected void defaultPaste() { |
| if (getControl().getEditable()) { |
| getControl().paste(); |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#CUT} |
| */ |
| protected void defaultCut() { |
| if (getControl().getEditable()) { |
| getControl().cut(); |
| } |
| } |
| |
| |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#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 { |
| |
| int del = 1; |
| if (getControl().getCaretOffset() + 2 <= getControl().getContent().getCharCount()) { |
| if ("\r\n".equals(getControl().getContent().getTextRange(getControl().getCaretOffset(), 2))) { //$NON-NLS-1$ |
| del = 2; |
| } |
| } |
| |
| if (getControl().getCaretOffset() + del <= getControl().getContent().getCharCount()) { |
| getControl().getContent().replaceTextRange(getControl().getCaretOffset(), del, ""); //$NON-NLS-1$ |
| getControl().setCaretOffset(offset); |
| } |
| } |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#DELETE_PREVIOUS} |
| */ |
| protected void defaultDeletePrevious() { |
| 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 { |
| int start = getControl().getCaretOffset() - 1; |
| int del = 1; |
| |
| if (start - 1 >= 0) { |
| if ("\r\n".equals(getControl().getContent().getTextRange(getControl().getCaretOffset()-2, 2))) { //$NON-NLS-1$ |
| start = start - 1; |
| del = 2; |
| } |
| } |
| |
| if( start >= 0 ) { |
| getControl().getContent().replaceTextRange(start, del, ""); //$NON-NLS-1$ |
| getControl().setCaretOffset(offset - del); } |
| } |
| } |
| |
| private boolean isMultilineSelection() { |
| return getControl().getLineAtOffset(getControl().getSelection().offset) != getControl().getLineAtOffset(getControl().getSelection().offset + getControl().getSelection().length); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#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 added = 0; |
| |
| int firstLineDelta = 0; |
| int indentLength = getControl().isInsertSpacesForTab() ? getControl().getTabAdvance() : 1; |
| |
| String insertString = "\t"; //$NON-NLS-1$ |
| if( getControl().isInsertSpacesForTab() ) { |
| StringBuilder b = new StringBuilder(); |
| for( int i = 0; i < getControl().getTabAdvance(); i++ ) { |
| b.append(" "); //$NON-NLS-1$ |
| } |
| insertString = b.toString(); |
| } |
| |
| for (int lineNumber = firstLine; lineNumber < lastLine; lineNumber++) { |
| int lineStart = getControl().getOffsetAtLine(lineNumber) + added; |
| dataBuffer.replace(lineStart, lineStart + 0, insertString); |
| added += insertString.length(); |
| if (lineNumber == firstLine) { |
| if (selectionOffset > lineStart) { |
| firstLineDelta = selectionOffset - lineStart; |
| } |
| } |
| } |
| |
| int start = selectionOffset - firstLineDelta; |
| int length = selectionLength + added + firstLineDelta; |
| String replaced = dataBuffer.substring(start,start+length); |
| |
| getControl().getContent().replaceTextRange(start, length-added, replaced); |
| getControl().setCaretOffset(selectionOffset == caret ? caret + indentLength : caret + added); |
| getControl().setSelectionRange(selectionOffset + indentLength, selectionLength + added - indentLength); |
| } |
| } |
| |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#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; |
| } |
| |
| int firstLineDelta = 0; |
| int firstLineSelectionIndent = 0; |
| int[] removals = new int[lastLine-firstLine]; |
| |
| for (int lineNumber = firstLine; lineNumber < lastLine; lineNumber++) { |
| int lineStart = getControl().getOffsetAtLine(lineNumber); |
| if (dataBuffer.charAt(lineStart) != '\t') { |
| String begin = dataBuffer.substring(lineStart,Math.min(lineStart + getControl().getTabAdvance(), dataBuffer.length())); |
| if( begin.length() != getControl().getTabAdvance() ) { |
| return; |
| } else { |
| char[] cs = begin.toCharArray(); |
| for( int i = 0; i < cs.length; i++ ) { |
| if( cs[i] == '\t' ) { |
| removals[lineNumber-firstLine] = i+1; |
| break; |
| } else if( cs[i] != ' ' ) { |
| return; |
| } |
| } |
| if( removals[lineNumber-firstLine] == 0 ) { |
| removals[lineNumber-firstLine] = getControl().getTabAdvance(); |
| } |
| } |
| } else { |
| removals[lineNumber-firstLine] = 1; |
| } |
| if (lineNumber == firstLine) { |
| if (selectionOffset > lineStart) { |
| firstLineDelta = removals[lineNumber-firstLine]; |
| firstLineSelectionIndent = selectionOffset - lineStart; |
| } |
| } |
| } |
| |
| int start = selectionOffset - firstLineSelectionIndent; |
| int end = start + selectionLength + firstLineSelectionIndent; |
| String replacedText = dataBuffer.substring(start, end); |
| |
| int removed = 0; |
| |
| for (int lineNumber = lastLine - 1; lineNumber >= firstLine; lineNumber--) { |
| int lineStart = getControl().getOffsetAtLine(lineNumber); |
| dataBuffer.replace(lineStart, lineStart + removals[lineNumber-firstLine], ""); //$NON-NLS-1$ |
| removed += removals[lineNumber-firstLine]; |
| } |
| |
| String newText = dataBuffer.substring(start, end - removed); |
| |
| getControl().getContent().replaceTextRange(start, replacedText.length(), newText); |
| getControl().setCaretOffset(selectionOffset == caret ? caret - firstLineDelta : caret - removed); |
| getControl().setSelectionRange(selectionOffset - firstLineDelta, selectionLength - removed + firstLineDelta); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#MOVE_UP} and |
| * {@link DefaultTextEditActions#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); |
| } |
| |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#MOVE_DOWN} and |
| * {@link DefaultTextEditActions#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); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#MOVE_LEFT} and |
| * {@link DefaultTextEditActions#SELECT_LEFT} |
| * |
| * @param select |
| * whether to change the selection |
| */ |
| protected void defaultLeft(boolean select) { |
| moveCaretRelative(-1, select); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#NAVIGATE_TO_LINE} |
| */ |
| protected void defaultNavigateToLine() { |
| try { |
| Optional<Integer> num = ((StyledTextSkin)getControl().getSkin()).fastQuery("Goto Line", "Line Number", Integer::parseInt); |
| num.ifPresent((n)->defaultNavigateToLine(n-1)); |
| |
| } |
| catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| protected void defaultNavigateToLine(int lineIndex) { |
| getControl().navigateToLine(lineIndex); |
| } |
| |
| |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#MOVE_RIGHT} and |
| * {@link DefaultTextEditActions#SELECT_RIGHT} |
| * |
| * @param select |
| * whether to change the selection |
| */ |
| protected void defaultRight(boolean select) { |
| moveCaretRelative(1, select); |
| } |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SCROLL_LINE_UP} |
| */ |
| protected void defaultScrollLineUp() { |
| ((StyledTextSkin) getControl().getSkin()).scrollLineUp(); |
| } |
| |
| |
| /** |
| * default implementation for {@link DefaultTextEditActions#SCROLL_LINE_DOWN} |
| */ |
| protected void defaultScrollLineDown() { |
| ((StyledTextSkin) getControl().getSkin()).scrollLineDown(); |
| } |
| |
| 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); |
| |
| // we need to jump 2 chars if windows line endings are in effect |
| if( offset > 0 ) { |
| String textRange = getControl().getContent().getTextRange(offset-1, 1); |
| // check the navigation direction |
| if( getControl().getCaretOffset() > offset ) { |
| if( textRange.equals("\r") ) { //$NON-NLS-1$ |
| offset -= 1; |
| } |
| } else { |
| if( textRange.equals("\r") ) { //$NON-NLS-1$ |
| offset += 1; |
| } |
| } |
| } |
| |
| getControl().impl_setCaretOffset(Math.max(0,offset), select); |
| } |
| |
| private void moveCaretRelative(int deltaOffset, boolean select) { |
| int offset = getControl().getCaretOffset() + deltaOffset; |
| moveCaretAbsolute(offset, select); |
| } |
| |
| /** |
| * initializes the key mappings. |
| * |
| * @param keyMapping |
| * the mapping |
| */ |
| protected void initKeymapping(TriggerActionMapping m) { |
| |
| if (Util.isMacOS()) { |
| m.map("Meta+Left", DefaultTextEditActions.LINE_START); //$NON-NLS-1$ |
| m.map("Meta+Right", DefaultTextEditActions.LINE_END); //$NON-NLS-1$ |
| m.map("Meta+Up", DefaultTextEditActions.TEXT_START); //$NON-NLS-1$ |
| m.map("Meta+Down", DefaultTextEditActions.TEXT_END); //$NON-NLS-1$ |
| m.map("Alt+Right", DefaultTextEditActions.WORD_NEXT); //$NON-NLS-1$ |
| m.map("Alt+Left", DefaultTextEditActions.WORD_PREVIOUS); //$NON-NLS-1$ |
| m.map("Page Down", DefaultTextEditActions.TEXT_PAGE_DOWN); //$NON-NLS-1$ |
| m.map("Page Up", DefaultTextEditActions.TEXT_PAGE_UP); //$NON-NLS-1$ |
| |
| m.map("Meta+Shift+Left", DefaultTextEditActions.SELECT_LINE_START); //$NON-NLS-1$ |
| m.map("Meta+Shift+Right", DefaultTextEditActions.SELECT_LINE_END); //$NON-NLS-1$ |
| m.map("Meta+Shift+Up", DefaultTextEditActions.SELECT_TEXT_START); //$NON-NLS-1$ |
| m.map("Meta+Shift+Down", DefaultTextEditActions.SELECT_TEXT_END); //$NON-NLS-1$ |
| m.map("Alt+Shift+Right", DefaultTextEditActions.SELECT_WORD_NEXT); //$NON-NLS-1$ |
| m.map("Alt+Shift+Left", DefaultTextEditActions.SELECT_WORD_PREVIOUS); //$NON-NLS-1$ |
| m.map("Shift+Page Down", DefaultTextEditActions.SELECT_TEXT_PAGE_DOWN); //$NON-NLS-1$ |
| m.map("Shift+Page Up", DefaultTextEditActions.SELECT_TEXT_PAGE_UP); //$NON-NLS-1$ |
| |
| m.map("Alt+Delete", DefaultTextEditActions.DELETE_WORD_NEXT); //$NON-NLS-1$ |
| m.map("Alt+Backspace", DefaultTextEditActions.DELETE_WORD_PREVIOUS); //$NON-NLS-1$ |
| |
| m.map("Meta+D", DefaultTextEditActions.DELETE_LINE); //$NON-NLS-1$ |
| |
| m.map("Meta+C", DefaultTextEditActions.COPY); //$NON-NLS-1$ |
| m.map("Meta+V", DefaultTextEditActions.PASTE); //$NON-NLS-1$ |
| m.map("Meta+X", DefaultTextEditActions.CUT); //$NON-NLS-1$ |
| |
| m.map("Meta+A", DefaultTextEditActions.SELECT_ALL); //$NON-NLS-1$ |
| |
| m.map("Meta+Up", DefaultTextEditActions.SCROLL_LINE_UP); //$NON-NLS-1$ |
| m.map("Meta+Down", DefaultTextEditActions.SCROLL_LINE_DOWN); //$NON-NLS-1$ |
| |
| m.map("Alt+Up", DefaultTextEditActions.MOVE_LINES_UP); //$NON-NLS-1$ |
| m.map("Alt+Down", DefaultTextEditActions.MOVE_LINES_DOWN); //$NON-NLS-1$ |
| |
| m.map("Meta+L", DefaultTextEditActions.NAVIGATE_TO_LINE); //$NON-NLS-1$ |
| } else { |
| |
| m.map("Ctrl+Right", DefaultTextEditActions.WORD_NEXT); //$NON-NLS-1$ |
| m.map("Ctrl+Left", DefaultTextEditActions.WORD_PREVIOUS); //$NON-NLS-1$ |
| |
| m.map("Ctrl+Shift+Right", DefaultTextEditActions.SELECT_WORD_NEXT); //$NON-NLS-1$ |
| m.map("Ctrl+Shift+Left", DefaultTextEditActions.SELECT_WORD_PREVIOUS); //$NON-NLS-1$ |
| |
| m.map("Page Down", DefaultTextEditActions.TEXT_PAGE_DOWN); //$NON-NLS-1$ |
| m.map("Page Up", DefaultTextEditActions.TEXT_PAGE_UP); //$NON-NLS-1$ |
| m.map("Shift+Page Down", DefaultTextEditActions.SELECT_TEXT_PAGE_DOWN); //$NON-NLS-1$ |
| m.map("Shift+Page Up", DefaultTextEditActions.SELECT_TEXT_PAGE_UP); //$NON-NLS-1$ |
| |
| m.map("Home", DefaultTextEditActions.LINE_START); //$NON-NLS-1$ |
| m.map("Shift+Home", DefaultTextEditActions.SELECT_LINE_START); //$NON-NLS-1$ |
| |
| m.map("Ctrl+Home", DefaultTextEditActions.TEXT_START); //$NON-NLS-1$ |
| |
| m.map("Ctrl+Shift+Home", DefaultTextEditActions.SELECT_TEXT_START); //$NON-NLS-1$ |
| |
| m.map("End", DefaultTextEditActions.LINE_END); //$NON-NLS-1$ |
| m.map("Shift+End", DefaultTextEditActions.SELECT_LINE_END); //$NON-NLS-1$ |
| m.map("Ctrl+End", DefaultTextEditActions.TEXT_END); //$NON-NLS-1$ |
| m.map("Ctrl+Shift+End", DefaultTextEditActions.SELECT_TEXT_END); //$NON-NLS-1$ |
| |
| m.map("Ctrl+Delete", DefaultTextEditActions.DELETE_WORD_NEXT); //$NON-NLS-1$ |
| m.map("Ctrl+Backspace", DefaultTextEditActions.DELETE_WORD_PREVIOUS); //$NON-NLS-1$ |
| |
| m.map("Ctrl+C", DefaultTextEditActions.COPY); //$NON-NLS-1$ |
| m.map("Ctrl+V", DefaultTextEditActions.PASTE); //$NON-NLS-1$ |
| m.map("Ctrl+X", DefaultTextEditActions.CUT); //$NON-NLS-1$ |
| |
| m.map("Ctrl+A", DefaultTextEditActions.SELECT_ALL); //$NON-NLS-1$ |
| |
| m.map("Ctrl+Up", DefaultTextEditActions.SCROLL_LINE_UP); //$NON-NLS-1$ |
| m.map("Ctrl+Down", DefaultTextEditActions.SCROLL_LINE_DOWN); //$NON-NLS-1$ |
| |
| m.map("Ctrl+D", DefaultTextEditActions.DELETE_LINE); //$NON-NLS-1$ |
| |
| m.map("Alt+Up", DefaultTextEditActions.MOVE_LINES_UP); //$NON-NLS-1$ |
| m.map("Alt+Down", DefaultTextEditActions.MOVE_LINES_DOWN); //$NON-NLS-1$ |
| |
| m.map("Ctrl+L", DefaultTextEditActions.NAVIGATE_TO_LINE); //$NON-NLS-1$ |
| |
| } |
| |
| m.mapConditional("tab-on-multiline", this::isMultilineSelection, "Tab", DefaultTextEditActions.INDENT); //$NON-NLS-1$//$NON-NLS-2$ |
| m.map("Shift+Tab", DefaultTextEditActions.OUTDENT); //$NON-NLS-1$ |
| |
| m.map("Delete", DefaultTextEditActions.DELETE); //$NON-NLS-1$ |
| m.map("Backspace", DefaultTextEditActions.DELETE_PREVIOUS); //$NON-NLS-1$ |
| |
| m.map("Enter", DefaultTextEditActions.NEW_LINE); //$NON-NLS-1$ |
| |
| m.map("Up", DefaultTextEditActions.MOVE_UP); //$NON-NLS-1$ |
| m.map("Down", DefaultTextEditActions.MOVE_DOWN); //$NON-NLS-1$ |
| m.map("Left", DefaultTextEditActions.MOVE_LEFT); //$NON-NLS-1$ |
| m.map("Right", DefaultTextEditActions.MOVE_RIGHT); //$NON-NLS-1$ |
| |
| m.map("Shift+Up", DefaultTextEditActions.SELECT_UP); //$NON-NLS-1$ |
| m.map("Shift+Down", DefaultTextEditActions.SELECT_DOWN); //$NON-NLS-1$ |
| m.map("Shift+Left", DefaultTextEditActions.SELECT_LEFT); //$NON-NLS-1$ |
| m.map("Shift+Right", DefaultTextEditActions.SELECT_RIGHT); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Trigger the action |
| * @param action the action |
| */ |
| public void triggerAction(TextEditAction action) { |
| keyTriggerMapping.triggerAction(action, new Context(getControl())); |
| } |
| |
| } |