modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization

*** 1,7 **** /* ! * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this --- 1,7 ---- /* ! * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this
*** 20,71 **** * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ - package com.sun.javafx.scene.control.behavior; import javafx.application.ConditionalFeature; import javafx.beans.InvalidationListener; import javafx.geometry.NodeOrientation; import javafx.scene.control.IndexRange; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyEvent; import java.text.Bidi; ! import java.text.BreakIterator; ! import java.util.ArrayList; ! import java.util.List; ! ! import com.sun.javafx.application.PlatformImpl; ! import com.sun.javafx.scene.control.skin.TextInputControlSkin; ! ! import static javafx.scene.input.KeyEvent.KEY_PRESSED; ! import static com.sun.javafx.PlatformUtil.*; /** ! * Abstract base class for text input behaviors. */ public abstract class TextInputControlBehavior<T extends TextInputControl> extends BehaviorBase<T> { ! /************************************************************************** ! * Setup KeyBindings * ! *************************************************************************/ ! protected static final List<KeyBinding> TEXT_INPUT_BINDINGS = new ArrayList<KeyBinding>(); ! static { ! TEXT_INPUT_BINDINGS.addAll(TextInputControlBindings.BINDINGS); ! // However, we want to consume other key press / release events too, for ! // things that would have been handled by the InputCharacter normally ! TEXT_INPUT_BINDINGS.add(new KeyBinding(null, KEY_PRESSED, "Consume")); ! } /************************************************************************** * Fields * *************************************************************************/ ! T textInputControl; /** * Used to keep track of the most recent key event. This is used when * handling InputCharacter actions. */ --- 20,93 ---- * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javafx.scene.control.behavior; + import com.sun.javafx.PlatformUtil; + import com.sun.javafx.application.PlatformImpl; + import com.sun.javafx.scene.control.Properties; + import com.sun.javafx.scene.control.skin.FXVK; + + import javafx.event.ActionEvent; + import javafx.event.Event; + import javafx.event.EventHandler; + import javafx.scene.control.skin.TextInputControlSkin; import javafx.application.ConditionalFeature; import javafx.beans.InvalidationListener; + import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; + import javafx.scene.control.ContextMenu; import javafx.scene.control.IndexRange; + import javafx.scene.control.MenuItem; + import javafx.scene.control.PasswordField; + import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.TextInputControl; + import javafx.scene.input.ContextMenuEvent; + import javafx.scene.input.Clipboard; + import com.sun.javafx.scene.control.inputmap.InputMap; + import com.sun.javafx.scene.control.inputmap.KeyBinding; + import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; + import javafx.scene.input.MouseEvent; import java.text.Bidi; ! import java.util.function.Predicate; ! import static com.sun.javafx.PlatformUtil.isLinux; ! import static com.sun.javafx.PlatformUtil.isMac; ! import static com.sun.javafx.PlatformUtil.isWindows; ! import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString; ! import static javafx.scene.control.skin.TextInputControlSkin.TextUnit; ! import static javafx.scene.control.skin.TextInputControlSkin.Direction; ! import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; ! import static com.sun.javafx.scene.control.inputmap.InputMap.MouseMapping; ! import static javafx.scene.input.KeyCode.*; ! import static javafx.scene.input.KeyEvent.*; /** ! * All of the "button" types (CheckBox, RadioButton, ToggleButton, and Button) ! * and also maybe some other types like hyperlinks operate on the "armed" ! * selection strategy, just like JButton. This behavior class encapsulates that ! * logic in a way that can be reused and extended by each of the individual ! * class behaviors. ! * */ public abstract class TextInputControlBehavior<T extends TextInputControl> extends BehaviorBase<T> { ! ! /** ! * Specifies whether we ought to show handles. We should do it on touch platforms, but not ! * iOS (and maybe not Android either?) ! */ ! static final boolean SHOW_HANDLES = Properties.IS_TOUCH_SUPPORTED && !PlatformUtil.isIOS(); /************************************************************************** * Fields * *************************************************************************/ ! final T textInputControl; /** * Used to keep track of the most recent key event. This is used when * handling InputCharacter actions. */
*** 73,98 **** private InvalidationListener textListener = observable -> { invalidateBidi(); }; ! /************************************************************************** * Constructors * ! *************************************************************************/ /** ! * Create a new TextInputControlBehavior. ! * @param textInputControl cannot be null */ ! public TextInputControlBehavior(T textInputControl, List<KeyBinding> bindings) { ! super(textInputControl, bindings); - this.textInputControl = textInputControl; ! textInputControl.textProperty().addListener(textListener); } /************************************************************************** * Disposal methods * *************************************************************************/ @Override public void dispose() { --- 95,336 ---- private InvalidationListener textListener = observable -> { invalidateBidi(); }; ! private final InputMap<T> inputMap; ! ! ! ! ! /*************************************************************************** ! * * * Constructors * ! * * ! **************************************************************************/ ! ! public TextInputControlBehavior(T c) { ! super(c); ! ! this.textInputControl = c; ! ! textInputControl.textProperty().addListener(textListener); ! ! // create a map for text input-specific mappings (this reuses the default ! // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings) ! inputMap = createInputMap(); ! ! // some of the mappings are only valid when the control is editable, or ! // only on certain platforms, so we create the following predicates that filters out the mapping when the ! // control is not in the correct state / on the correct platform ! final Predicate<KeyEvent> validWhenEditable = e -> !c.isEditable(); ! final Predicate<KeyEvent> validOnMac = e -> !PlatformUtil.isMac(); ! final Predicate<KeyEvent> validOnWindows = e -> !PlatformUtil.isWindows(); ! final Predicate<KeyEvent> validOnLinux = e -> !PlatformUtil.isLinux(); ! ! // create a child input map for mappings which are applicable on all ! // platforms, and regardless of editing state ! addDefaultMapping(inputMap, ! // caret movement ! keyMapping(RIGHT, e -> nextCharacterVisually(true)), ! keyMapping(LEFT, e -> nextCharacterVisually(false)), ! keyMapping(UP, e -> c.home()), ! keyMapping(HOME, e -> c.home()), ! keyMapping(DOWN, e -> c.end()), ! keyMapping(END, e -> c.end()), ! keyMapping(ENTER, this::fire), ! ! keyMapping(new KeyBinding(HOME).shortcut(), e -> c.home()), ! keyMapping(new KeyBinding(END).shortcut(), e -> c.end()), ! ! // deletion (only applies when control is editable) ! keyMapping(new KeyBinding(BACK_SPACE), e -> deletePreviousChar(), validWhenEditable), ! keyMapping(new KeyBinding(BACK_SPACE).shift(), e -> deletePreviousChar(), validWhenEditable), ! keyMapping(new KeyBinding(DELETE), e -> deleteNextChar(), validWhenEditable), ! keyMapping(new KeyBinding(DELETE).shift(), e -> deleteNextChar(), validWhenEditable), ! ! // cut (only applies when control is editable) ! keyMapping(new KeyBinding(X).shortcut(), e -> c.cut(), validWhenEditable), ! keyMapping(new KeyBinding(CUT), e -> cut(), validWhenEditable), ! keyMapping(new KeyBinding(DELETE).shift(), e -> cut(), validWhenEditable), ! ! // copy ! keyMapping(new KeyBinding(C).shortcut(), e -> c.copy()), ! keyMapping(new KeyBinding(INSERT).shortcut(), e -> c.copy()), ! keyMapping(COPY, e -> c.copy()), ! ! // paste (only applies when control is editable) ! keyMapping(new KeyBinding(V).shortcut(), e -> c.paste(), validWhenEditable), ! keyMapping(new KeyBinding(PASTE), e -> paste(), validWhenEditable), ! keyMapping(new KeyBinding(INSERT).shift(), e -> paste(), validWhenEditable), ! ! // selection ! keyMapping(new KeyBinding(RIGHT).shift(), e -> selectRight()), ! keyMapping(new KeyBinding(LEFT).shift(), e -> selectLeft()), ! keyMapping(new KeyBinding(UP).shift(), e -> selectHome()), ! keyMapping(new KeyBinding(DOWN).shift(), e -> selectEnd()), ! keyMapping(new KeyBinding(HOME).shortcut().shift(), e -> selectHome()), ! keyMapping(new KeyBinding(END).shortcut().shift(), e -> selectEnd()), ! keyMapping(new KeyBinding(LEFT).shortcut().shift(), e -> selectHomeExtend()), ! keyMapping(new KeyBinding(RIGHT).shortcut().shift(), e -> selectEndExtend()), ! keyMapping(new KeyBinding(A).shortcut(), e -> c.selectAll()), ! ! // Traversal Bindings ! new KeyMapping(new KeyBinding(TAB), FocusTraversalInputMap::traverseNext), ! new KeyMapping(new KeyBinding(TAB).shift(), FocusTraversalInputMap::traversePrevious), ! new KeyMapping(new KeyBinding(TAB).ctrl(), FocusTraversalInputMap::traverseNext), ! new KeyMapping(new KeyBinding(TAB).ctrl().shift(), FocusTraversalInputMap::traversePrevious), ! ! // The following keys are forwarded to the parent container ! new KeyMapping(ESCAPE, this::cancelEdit), ! new KeyMapping(F10, this::forwardToParent), ! ! // Linux specific mappings ! keyMapping(new KeyBinding(Z).ctrl(), e -> undo(), validOnLinux), ! keyMapping(new KeyBinding(Z).ctrl().shift(), e -> redo(), validOnLinux), ! ! // character input. ! // Any other key press first goes to normal text input ! // Note this is KEY_TYPED because otherwise the character is not available in the event. ! keyMapping(new KeyBinding(null, KEY_TYPED), this::defaultKeyTyped), ! ! // However, we want to consume other key press / release events too, for ! // things that would have been handled by the InputCharacter normally ! keyMapping(new KeyBinding(null, KEY_PRESSED), e -> e.consume()), ! ! // VK ! new KeyMapping(new KeyBinding(DIGIT9).ctrl().shift(), e -> { ! FXVK.toggleUseVK(textInputControl); ! }, p -> !PlatformImpl.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)), ! ! // mouse and context menu mappings ! new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed), ! new MouseMapping(MouseEvent.MOUSE_DRAGGED, this::mouseDragged), ! new MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased), ! new InputMap.Mapping<ContextMenuEvent>(ContextMenuEvent.CONTEXT_MENU_REQUESTED, this::contextMenuRequested) { ! @Override public int getSpecificity(Event event) { ! return 1; ! } ! } ! ); ! ! // mac os specific mappings ! InputMap<T> macOsInputMap = new InputMap<>(c); ! macOsInputMap.setInterceptor(e -> !PlatformUtil.isMac()); ! macOsInputMap.getMappings().addAll( ! // Mac OS specific mappings ! keyMapping(new KeyBinding(HOME).shift(), e -> selectHomeExtend()), ! keyMapping(new KeyBinding(END).shift(), e -> selectEndExtend()), ! keyMapping(new KeyBinding(LEFT).shortcut(), e -> c.home()), ! keyMapping(new KeyBinding(RIGHT).shortcut(), e -> c.end()), ! keyMapping(new KeyBinding(LEFT).alt(), e -> leftWord()), ! keyMapping(new KeyBinding(RIGHT).alt(), e -> rightWord()), ! keyMapping(new KeyBinding(DELETE).alt(), e -> deleteNextWord()), ! keyMapping(new KeyBinding(BACK_SPACE).alt(), e -> deletePreviousWord()), ! keyMapping(new KeyBinding(BACK_SPACE).shortcut(), e -> deleteFromLineStart()), ! keyMapping(new KeyBinding(Z).shortcut(), e -> undo()), ! keyMapping(new KeyBinding(Z).shortcut().shift(), e -> redo()), ! ! // Mac OS specific selection mappings ! keyMapping(new KeyBinding(LEFT).shift().alt(), e -> selectLeftWord()), ! keyMapping(new KeyBinding(RIGHT).shift().alt(), e -> selectRightWord()) ! ); ! addDefaultChildMap(inputMap, macOsInputMap); ! ! // windows / linux specific mappings ! InputMap<T> nonMacOsInputMap = new InputMap<>(c); ! nonMacOsInputMap.setInterceptor(e -> PlatformUtil.isMac()); ! nonMacOsInputMap.getMappings().addAll( ! keyMapping(new KeyBinding(HOME).shift(), e -> selectHome()), ! keyMapping(new KeyBinding(END).shift(), e -> selectEnd()), ! keyMapping(new KeyBinding(LEFT).ctrl(), e -> leftWord()), ! keyMapping(new KeyBinding(RIGHT).ctrl(), e -> rightWord()), ! keyMapping(new KeyBinding(H).ctrl(), e -> deletePreviousChar()), ! keyMapping(new KeyBinding(DELETE).ctrl(), e -> deleteNextWord()), ! keyMapping(new KeyBinding(BACK_SPACE).ctrl(), e -> deletePreviousWord()), ! keyMapping(new KeyBinding(BACK_SLASH).ctrl(), e -> c.deselect()), ! keyMapping(new KeyBinding(Z).ctrl(), e -> undo()), ! keyMapping(new KeyBinding(Y).ctrl(), e -> redo()) ! ); ! addDefaultChildMap(inputMap, nonMacOsInputMap); ! ! addKeyPadMappings(inputMap); ! ! textInputControl.textProperty().addListener(textListener); ! } ! ! @Override public InputMap<T> getInputMap() { ! return inputMap; ! } /** ! * Bind keypad arrow keys to the same as the regular arrow keys. */ ! protected void addKeyPadMappings(InputMap<T> map) { ! // First create a temporary map for the keypad mappings ! InputMap<T> tmpMap = new InputMap<>(getNode()); ! for (Object o : map.getMappings()) { ! if (o instanceof KeyMapping) { ! KeyMapping mapping = (KeyMapping)o; ! KeyBinding kb = (KeyBinding)mapping.getMappingKey(); ! if (kb.getCode() != null) { ! KeyCode newCode = null; ! switch (kb.getCode()) { ! case LEFT: newCode = KP_LEFT; break; ! case RIGHT: newCode = KP_RIGHT; break; ! case UP: newCode = KP_UP; break; ! case DOWN: newCode = KP_DOWN; break; ! } ! if (newCode != null) { ! KeyBinding newkb = new KeyBinding(newCode).shift(kb.getShift()) ! .ctrl(kb.getCtrl()) ! .alt(kb.getAlt()) ! .meta(kb.getMeta()); ! tmpMap.getMappings().add(new KeyMapping(newkb, mapping.getEventHandler())); ! } ! } ! } ! } ! // Install mappings ! for (Object o : tmpMap.getMappings()) { ! map.getMappings().add((KeyMapping)o); ! } ! ! // Recursive call for child maps ! for (Object o : map.getChildInputMaps()) { ! addKeyPadMappings((InputMap<T>)o); ! } ! } ! /** ! * Wraps the event handler to pause caret blinking when ! * processing the key event. ! */ ! protected KeyMapping keyMapping(final KeyCode keyCode, final EventHandler<KeyEvent> eventHandler) { ! return keyMapping(new KeyBinding(keyCode), eventHandler); } + protected KeyMapping keyMapping(KeyBinding keyBinding, final EventHandler<KeyEvent> eventHandler) { + return keyMapping(keyBinding, eventHandler, null); + } + + protected KeyMapping keyMapping(KeyBinding keyBinding, final EventHandler<KeyEvent> eventHandler, + Predicate<KeyEvent> interceptor) { + return new KeyMapping(keyBinding, + e -> { + setCaretAnimating(false); + eventHandler.handle(e); + setCaretAnimating(true); + }, + interceptor); + } + + + + + /************************************************************************** * Disposal methods * *************************************************************************/ @Override public void dispose() {
*** 107,215 **** protected abstract void deleteChar(boolean previous); protected abstract void replaceText(int start, int end, String txt); protected abstract void setCaretAnimating(boolean play); protected abstract void deleteFromLineStart(); ! protected void scrollCharacterToVisible(int index) { ! // TODO this method should be removed when TextAreaSkin ! // TODO is refactored to no longer need it. ! } /************************************************************************** * Key handling implementation * *************************************************************************/ /** - * Records the last KeyEvent we saw. - * @param e - */ - @Override protected void callActionForEvent(KeyEvent e) { - lastEvent = e; - super.callActionForEvent(e); - } - - @Override public void callAction(String name) { - TextInputControl textInputControl = getControl(); - boolean done = false; - - setCaretAnimating(false); - - if (textInputControl.isEditable()) { - setEditing(true); - done = true; - if ("InputCharacter".equals(name)) defaultKeyTyped(lastEvent); - else if ("Cut".equals(name)) cut(); - else if ("Paste".equals(name)) paste(); - else if ("DeleteFromLineStart".equals(name)) deleteFromLineStart(); - else if ("DeletePreviousChar".equals(name)) deletePreviousChar(); - else if ("DeleteNextChar".equals(name)) deleteNextChar(); - else if ("DeletePreviousWord".equals(name)) deletePreviousWord(); - else if ("DeleteNextWord".equals(name)) deleteNextWord(); - else if ("DeleteSelection".equals(name)) deleteSelection(); - else if ("Undo".equals(name)) textInputControl.undo(); - else if ("Redo".equals(name)) textInputControl.redo(); - else { - done = false; - } - setEditing(false); - } - if (!done) { - done = true; - if ("Copy".equals(name)) textInputControl.copy(); - else if ("SelectBackward".equals(name)) textInputControl.selectBackward(); - else if ("SelectForward".equals(name)) textInputControl.selectForward(); - else if ("SelectLeft".equals(name)) selectLeft(); - else if ("SelectRight".equals(name)) selectRight(); - else if ("PreviousWord".equals(name)) previousWord(); - else if ("NextWord".equals(name)) nextWord(); - else if ("LeftWord".equals(name)) leftWord(); - else if ("RightWord".equals(name)) rightWord(); - else if ("SelectPreviousWord".equals(name)) selectPreviousWord(); - else if ("SelectNextWord".equals(name)) selectNextWord(); - else if ("SelectLeftWord".equals(name)) selectLeftWord(); - else if ("SelectRightWord".equals(name)) selectRightWord(); - else if ("SelectWord".equals(name)) selectWord(); - else if ("SelectAll".equals(name)) textInputControl.selectAll(); - else if ("Home".equals(name)) textInputControl.home(); - else if ("End".equals(name)) textInputControl.end(); - else if ("Forward".equals(name)) textInputControl.forward(); - else if ("Backward".equals(name)) textInputControl.backward(); - else if ("Right".equals(name)) nextCharacterVisually(true); - else if ("Left".equals(name)) nextCharacterVisually(false); - else if ("Fire".equals(name)) fire(lastEvent); - else if ("Cancel".equals(name)) cancelEdit(lastEvent); - else if ("Unselect".equals(name)) textInputControl.deselect(); - else if ("SelectHome".equals(name)) selectHome(); - else if ("SelectEnd".equals(name)) selectEnd(); - else if ("SelectHomeExtend".equals(name)) selectHomeExtend(); - else if ("SelectEndExtend".equals(name)) selectEndExtend(); - else if ("ToParent".equals(name)) forwardToParent(lastEvent); - /*DEBUG*/else if ("UseVK".equals(name) && PlatformImpl.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { - ((TextInputControlSkin<?,?>)textInputControl.getSkin()).toggleUseVK(); - } else { - done = false; - } - } - setCaretAnimating(true); - - if (!done) { - if ("TraverseNext".equals(name)) traverseNext(); - else if ("TraversePrevious".equals(name)) traversePrevious(); - else super.callAction(name); - - } - // Note, I don't have to worry about "Consume" here. - } - - /** * The default handler for a key typed event, which is called when none of * the other key bindings match. This is the method which handles basic * text entry. * @param event not null */ private void defaultKeyTyped(KeyEvent event) { ! final TextInputControl textInput = getControl(); // I'm not sure this case can actually ever happen, maybe this // should be an assert instead? if (!textInput.isEditable() || textInput.isDisabled()) return; // Sometimes we get events with no key character, in which case --- 345,371 ---- protected abstract void deleteChar(boolean previous); protected abstract void replaceText(int start, int end, String txt); protected abstract void setCaretAnimating(boolean play); protected abstract void deleteFromLineStart(); ! protected abstract void mousePressed(MouseEvent e); ! protected abstract void mouseDragged(MouseEvent e); ! protected abstract void mouseReleased(MouseEvent e); ! protected abstract void contextMenuRequested(ContextMenuEvent e); /************************************************************************** * Key handling implementation * *************************************************************************/ /** * The default handler for a key typed event, which is called when none of * the other key bindings match. This is the method which handles basic * text entry. * @param event not null */ private void defaultKeyTyped(KeyEvent event) { ! final TextInputControl textInput = getNode(); // I'm not sure this case can actually ever happen, maybe this // should be an assert instead? if (!textInput.isEditable() || textInput.isDisabled()) return; // Sometimes we get events with no key character, in which case
*** 220,247 **** // Filter out control keys except control+Alt on PC or Alt on Mac if (event.isControlDown() || event.isAltDown() || (isMac() && event.isMetaDown())) { if (!((event.isControlDown() || isMac()) && event.isAltDown())) return; } // Ignore characters in the control range and the ASCII delete // character as well as meta key presses if (character.charAt(0) > 0x1F && character.charAt(0) != 0x7F && !event.isMetaDown()) { // Not sure about this one final IndexRange selection = textInput.getSelection(); final int start = selection.getStart(); final int end = selection.getEnd(); - // if (textInput.getLength() - selection.getLength() - // + character.length() > textInput.getMaximumLength()) { - // // TODO Beep? - // } else { replaceText(start, end, character); - // } - - scrollCharacterToVisible(start); } } private Bidi bidi = null; private Boolean mixed = null; private Boolean rtlText = null; --- 376,400 ---- // Filter out control keys except control+Alt on PC or Alt on Mac if (event.isControlDown() || event.isAltDown() || (isMac() && event.isMetaDown())) { if (!((event.isControlDown() || isMac()) && event.isAltDown())) return; } + setEditing(true); + // Ignore characters in the control range and the ASCII delete // character as well as meta key presses if (character.charAt(0) > 0x1F && character.charAt(0) != 0x7F && !event.isMetaDown()) { // Not sure about this one final IndexRange selection = textInput.getSelection(); final int start = selection.getStart(); final int end = selection.getEnd(); replaceText(start, end, character); } + + setEditing(false); } private Bidi bidi = null; private Boolean mixed = null; private Boolean rtlText = null;
*** 280,291 **** return rtlText; } private void nextCharacterVisually(boolean moveRight) { if (isMixed()) { ! TextInputControlSkin<?,?> skin = (TextInputControlSkin<?,?>)textInputControl.getSkin(); ! skin.nextCharacterVisually(moveRight); } else if (moveRight != isRTLText()) { textInputControl.forward(); } else { textInputControl.backward(); } --- 433,444 ---- return rtlText; } private void nextCharacterVisually(boolean moveRight) { if (isMixed()) { ! TextInputControlSkin<?> skin = (TextInputControlSkin<?>)textInputControl.getSkin(); ! skin.moveCaret(TextUnit.CHARACTER, moveRight ? Direction.RIGHT : Direction.LEFT, false); } else if (moveRight != isRTLText()) { textInputControl.forward(); } else { textInputControl.backward(); }
*** 306,369 **** textInputControl.selectForward(); } } private void deletePreviousChar() { deleteChar(true); } private void deleteNextChar() { deleteChar(false); } protected void deletePreviousWord() { ! TextInputControl textInputControl = getControl(); int end = textInputControl.getCaretPosition(); if (end > 0) { textInputControl.previousWord(); int start = textInputControl.getCaretPosition(); replaceText(start, end, ""); } } protected void deleteNextWord() { ! TextInputControl textInputControl = getControl(); int start = textInputControl.getCaretPosition(); if (start < textInputControl.getLength()) { nextWord(); int end = textInputControl.getCaretPosition(); replaceText(start, end, ""); } } ! private void deleteSelection() { ! TextInputControl textInputControl = getControl(); IndexRange selection = textInputControl.getSelection(); if (selection.getLength() > 0) { deleteChar(false); } } ! private void cut() { ! TextInputControl textInputControl = getControl(); ! textInputControl.cut(); } ! private void paste() { ! TextInputControl textInputControl = getControl(); ! textInputControl.paste(); } protected void selectPreviousWord() { ! getControl().selectPreviousWord(); } ! protected void selectNextWord() { ! TextInputControl textInputControl = getControl(); if (isMac() || isLinux()) { textInputControl.selectEndOfNextWord(); } else { textInputControl.selectNextWord(); } --- 459,546 ---- textInputControl.selectForward(); } } private void deletePreviousChar() { + setEditing(true); deleteChar(true); + setEditing(false); } private void deleteNextChar() { + setEditing(true); deleteChar(false); + setEditing(false); } protected void deletePreviousWord() { ! setEditing(true); ! TextInputControl textInputControl = getNode(); int end = textInputControl.getCaretPosition(); if (end > 0) { textInputControl.previousWord(); int start = textInputControl.getCaretPosition(); replaceText(start, end, ""); } + setEditing(false); } protected void deleteNextWord() { ! setEditing(true); ! TextInputControl textInputControl = getNode(); int start = textInputControl.getCaretPosition(); if (start < textInputControl.getLength()) { nextWord(); int end = textInputControl.getCaretPosition(); replaceText(start, end, ""); } + setEditing(false); } ! public void deleteSelection() { ! setEditing(true); ! TextInputControl textInputControl = getNode(); IndexRange selection = textInputControl.getSelection(); if (selection.getLength() > 0) { deleteChar(false); } + setEditing(false); } ! public void cut() { ! setEditing(true); ! getNode().cut(); ! setEditing(false); } ! public void paste() { ! setEditing(true); ! getNode().paste(); ! setEditing(false); ! } ! ! public void undo() { ! setEditing(true); ! getNode().undo(); ! setEditing(false); ! } ! ! public void redo() { ! setEditing(true); ! getNode().redo(); ! setEditing(false); } protected void selectPreviousWord() { ! getNode().selectPreviousWord(); } ! public void selectNextWord() { ! TextInputControl textInputControl = getNode(); if (isMac() || isLinux()) { textInputControl.selectEndOfNextWord(); } else { textInputControl.selectNextWord(); }
*** 384,408 **** selectNextWord(); } } protected void selectWord() { ! final TextInputControl textInputControl = getControl(); textInputControl.previousWord(); if (isWindows()) { textInputControl.selectNextWord(); } else { textInputControl.selectEndOfNextWord(); } } protected void previousWord() { ! getControl().previousWord(); } protected void nextWord() { ! TextInputControl textInputControl = getControl(); if (isMac() || isLinux()) { textInputControl.endOfNextWord(); } else { textInputControl.nextWord(); } --- 561,585 ---- selectNextWord(); } } protected void selectWord() { ! final TextInputControl textInputControl = getNode(); textInputControl.previousWord(); if (isWindows()) { textInputControl.selectNextWord(); } else { textInputControl.selectEndOfNextWord(); } } protected void previousWord() { ! getNode().previousWord(); } protected void nextWord() { ! TextInputControl textInputControl = getNode(); if (isMac() || isLinux()) { textInputControl.endOfNextWord(); } else { textInputControl.nextWord(); }
*** 426,460 **** protected void fire(KeyEvent event) { } // TODO move to TextFieldBehavior protected void cancelEdit(KeyEvent event) { forwardToParent(event);} protected void forwardToParent(KeyEvent event) { ! if (getControl().getParent() != null) { ! getControl().getParent().fireEvent(event); } } ! private void selectHome() { ! getControl().selectHome(); } ! private void selectEnd() { ! getControl().selectEnd(); } ! private void selectHomeExtend() { ! getControl().extendSelection(0); } ! private void selectEndExtend() { ! TextInputControl textInputControl = getControl(); textInputControl.extendSelection(textInputControl.getLength()); } private boolean editing = false; protected void setEditing(boolean b) { editing = b; } public boolean isEditing() { return editing; } } --- 603,698 ---- protected void fire(KeyEvent event) { } // TODO move to TextFieldBehavior protected void cancelEdit(KeyEvent event) { forwardToParent(event);} protected void forwardToParent(KeyEvent event) { ! if (getNode().getParent() != null) { ! getNode().getParent().fireEvent(event); } } ! protected void selectHome() { ! getNode().selectHome(); } ! protected void selectEnd() { ! getNode().selectEnd(); } ! protected void selectHomeExtend() { ! getNode().extendSelection(0); } ! protected void selectEndExtend() { ! TextInputControl textInputControl = getNode(); textInputControl.extendSelection(textInputControl.getLength()); } private boolean editing = false; protected void setEditing(boolean b) { editing = b; } public boolean isEditing() { return editing; } + + protected void populateContextMenu(ContextMenu contextMenu) { + TextInputControl textInputControl = getNode(); + boolean editable = textInputControl.isEditable(); + boolean hasText = (textInputControl.getLength() > 0); + boolean hasSelection = (textInputControl.getSelection().getLength() > 0); + boolean maskText = (textInputControl instanceof PasswordField); // (maskText("A") != "A"); + ObservableList<MenuItem> items = contextMenu.getItems(); + + if (SHOW_HANDLES) { + items.clear(); + if (!maskText && hasSelection) { + if (editable) { + items.add(cutMI); + } + items.add(copyMI); + } + if (editable && Clipboard.getSystemClipboard().hasString()) { + items.add(pasteMI); + } + if (hasText) { + if (!hasSelection) { + items.add(selectWordMI); + } + items.add(selectAllMI); + } + selectWordMI.getProperties().put("refreshMenu", Boolean.TRUE); + selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE); + } else { + if (editable) { + items.setAll(undoMI, redoMI, cutMI, copyMI, pasteMI, deleteMI, + separatorMI, selectAllMI); + } else { + items.setAll(copyMI, separatorMI, selectAllMI); + } + undoMI.setDisable(!getNode().isUndoable()); + redoMI.setDisable(!getNode().isRedoable()); + cutMI.setDisable(maskText || !hasSelection); + copyMI.setDisable(maskText || !hasSelection); + pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()); + deleteMI.setDisable(!hasSelection); + } + } + + private static class ContextMenuItem extends MenuItem { + ContextMenuItem(final String action, EventHandler<ActionEvent> onAction) { + super(getString("TextInputControl.menu." + action)); + setOnAction(onAction); + } + } + + private final MenuItem undoMI = new ContextMenuItem("Undo", e -> undo()); + private final MenuItem redoMI = new ContextMenuItem("Redo", e -> redo()); + private final MenuItem cutMI = new ContextMenuItem("Cut", e -> cut()); + private final MenuItem copyMI = new ContextMenuItem("Copy", e -> getNode().copy()); + private final MenuItem pasteMI = new ContextMenuItem("Paste", e -> paste()); + private final MenuItem deleteMI = new ContextMenuItem("DeleteSelection", e -> deleteSelection()); + private final MenuItem selectWordMI = new ContextMenuItem("SelectWord", e -> selectNextWord()); + private final MenuItem selectAllMI = new ContextMenuItem("SelectAll", e -> getNode().selectAll()); + private final MenuItem separatorMI = new SeparatorMenuItem(); + }