1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.sun.javafx.scene.control.behavior; 26 27 import com.sun.javafx.PlatformUtil; 28 import com.sun.javafx.application.PlatformImpl; 29 import com.sun.javafx.scene.control.Properties; 30 import com.sun.javafx.scene.control.skin.FXVK; 31 32 import javafx.event.ActionEvent; 33 import javafx.event.Event; 34 import javafx.event.EventHandler; 35 import javafx.scene.control.skin.TextInputControlSkin; 36 import javafx.application.ConditionalFeature; 37 import javafx.beans.InvalidationListener; 38 import javafx.collections.ObservableList; 39 import javafx.geometry.NodeOrientation; 40 import javafx.scene.control.ContextMenu; 41 import javafx.scene.control.IndexRange; 42 import javafx.scene.control.MenuItem; 43 import javafx.scene.control.PasswordField; 44 import javafx.scene.control.SeparatorMenuItem; 45 import javafx.scene.control.TextInputControl; 46 import javafx.scene.input.ContextMenuEvent; 47 import javafx.scene.input.Clipboard; 48 import com.sun.javafx.scene.control.inputmap.InputMap; 49 import com.sun.javafx.scene.control.inputmap.KeyBinding; 50 import javafx.scene.input.KeyCode; 51 import javafx.scene.input.KeyEvent; 52 import javafx.scene.input.MouseEvent; 53 54 import java.text.Bidi; 55 import java.util.function.Predicate; 56 57 import static com.sun.javafx.PlatformUtil.isLinux; 58 import static com.sun.javafx.PlatformUtil.isMac; 59 import static com.sun.javafx.PlatformUtil.isWindows; 60 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString; 61 import static javafx.scene.control.skin.TextInputControlSkin.TextUnit; 62 import static javafx.scene.control.skin.TextInputControlSkin.Direction; 63 import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; 64 import static com.sun.javafx.scene.control.inputmap.InputMap.MouseMapping; 65 import static javafx.scene.input.KeyCode.*; 66 import static javafx.scene.input.KeyEvent.*; 67 68 /** 69 * All of the "button" types (CheckBox, RadioButton, ToggleButton, and Button) 70 * and also maybe some other types like hyperlinks operate on the "armed" 71 * selection strategy, just like JButton. This behavior class encapsulates that 72 * logic in a way that can be reused and extended by each of the individual 73 * class behaviors. 74 * 75 */ 76 public abstract class TextInputControlBehavior<T extends TextInputControl> extends BehaviorBase<T> { 77 78 /** 79 * Specifies whether we ought to show handles. We should do it on touch platforms, but not 80 * iOS (and maybe not Android either?) 81 */ 82 static final boolean SHOW_HANDLES = Properties.IS_TOUCH_SUPPORTED && !PlatformUtil.isIOS(); 83 84 /************************************************************************** 85 * Fields * 86 *************************************************************************/ 87 88 final T textInputControl; 89 90 /** 91 * Used to keep track of the most recent key event. This is used when 92 * handling InputCharacter actions. 93 */ 94 private KeyEvent lastEvent; 95 96 private InvalidationListener textListener = observable -> { 97 invalidateBidi(); 98 }; 99 100 private final InputMap<T> inputMap; 101 102 103 104 105 /*************************************************************************** 106 * * 107 * Constructors * 108 * * 109 **************************************************************************/ 110 111 public TextInputControlBehavior(T c) { 112 super(c); 113 114 this.textInputControl = c; 115 116 textInputControl.textProperty().addListener(textListener); 117 118 // create a map for text input-specific mappings (this reuses the default 119 // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings) 120 inputMap = createInputMap(); 121 122 // some of the mappings are only valid when the control is editable, or 123 // only on certain platforms, so we create the following predicates that filters out the mapping when the 124 // control is not in the correct state / on the correct platform 125 final Predicate<KeyEvent> validWhenEditable = e -> !c.isEditable(); 126 final Predicate<KeyEvent> validOnMac = e -> !PlatformUtil.isMac(); 127 final Predicate<KeyEvent> validOnWindows = e -> !PlatformUtil.isWindows(); 128 final Predicate<KeyEvent> validOnLinux = e -> !PlatformUtil.isLinux(); 129 130 // create a child input map for mappings which are applicable on all 131 // platforms, and regardless of editing state 132 addDefaultMapping(inputMap, 133 // caret movement 134 keyMapping(RIGHT, e -> nextCharacterVisually(true)), 135 keyMapping(LEFT, e -> nextCharacterVisually(false)), 136 keyMapping(UP, e -> c.home()), 137 keyMapping(HOME, e -> c.home()), 138 keyMapping(DOWN, e -> c.end()), 139 keyMapping(END, e -> c.end()), 140 keyMapping(ENTER, this::fire), 141 142 keyMapping(new KeyBinding(HOME).shortcut(), e -> c.home()), 143 keyMapping(new KeyBinding(END).shortcut(), e -> c.end()), 144 145 // deletion (only applies when control is editable) 146 keyMapping(new KeyBinding(BACK_SPACE), e -> deletePreviousChar(), validWhenEditable), 147 keyMapping(new KeyBinding(BACK_SPACE).shift(), e -> deletePreviousChar(), validWhenEditable), 148 keyMapping(new KeyBinding(DELETE), e -> deleteNextChar(), validWhenEditable), 149 keyMapping(new KeyBinding(DELETE).shift(), e -> deleteNextChar(), validWhenEditable), 150 151 // cut (only applies when control is editable) 152 keyMapping(new KeyBinding(X).shortcut(), e -> c.cut(), validWhenEditable), 153 keyMapping(new KeyBinding(CUT), e -> cut(), validWhenEditable), 154 keyMapping(new KeyBinding(DELETE).shift(), e -> cut(), validWhenEditable), 155 156 // copy 157 keyMapping(new KeyBinding(C).shortcut(), e -> c.copy()), 158 keyMapping(new KeyBinding(INSERT).shortcut(), e -> c.copy()), 159 keyMapping(COPY, e -> c.copy()), 160 161 // paste (only applies when control is editable) 162 keyMapping(new KeyBinding(V).shortcut(), e -> c.paste(), validWhenEditable), 163 keyMapping(new KeyBinding(PASTE), e -> paste(), validWhenEditable), 164 keyMapping(new KeyBinding(INSERT).shift(), e -> paste(), validWhenEditable), 165 166 // selection 167 keyMapping(new KeyBinding(RIGHT).shift(), e -> selectRight()), 168 keyMapping(new KeyBinding(LEFT).shift(), e -> selectLeft()), 169 keyMapping(new KeyBinding(UP).shift(), e -> selectHome()), 170 keyMapping(new KeyBinding(DOWN).shift(), e -> selectEnd()), 171 keyMapping(new KeyBinding(HOME).shortcut().shift(), e -> selectHome()), 172 keyMapping(new KeyBinding(END).shortcut().shift(), e -> selectEnd()), 173 keyMapping(new KeyBinding(LEFT).shortcut().shift(), e -> selectHomeExtend()), 174 keyMapping(new KeyBinding(RIGHT).shortcut().shift(), e -> selectEndExtend()), 175 keyMapping(new KeyBinding(A).shortcut(), e -> c.selectAll()), 176 177 // Traversal Bindings 178 new KeyMapping(new KeyBinding(TAB), FocusTraversalInputMap::traverseNext), 179 new KeyMapping(new KeyBinding(TAB).shift(), FocusTraversalInputMap::traversePrevious), 180 new KeyMapping(new KeyBinding(TAB).ctrl(), FocusTraversalInputMap::traverseNext), 181 new KeyMapping(new KeyBinding(TAB).ctrl().shift(), FocusTraversalInputMap::traversePrevious), 182 183 // The following keys are forwarded to the parent container 184 new KeyMapping(ESCAPE, this::cancelEdit), 185 new KeyMapping(F10, this::forwardToParent), 186 187 // Linux specific mappings 188 keyMapping(new KeyBinding(Z).ctrl(), e -> undo(), validOnLinux), 189 keyMapping(new KeyBinding(Z).ctrl().shift(), e -> redo(), validOnLinux), 190 191 // character input. 192 // Any other key press first goes to normal text input 193 // Note this is KEY_TYPED because otherwise the character is not available in the event. 194 keyMapping(new KeyBinding(null, KEY_TYPED), this::defaultKeyTyped), 195 196 // However, we want to consume other key press / release events too, for 197 // things that would have been handled by the InputCharacter normally 198 keyMapping(new KeyBinding(null, KEY_PRESSED), e -> e.consume()), 199 200 // VK 201 new KeyMapping(new KeyBinding(DIGIT9).ctrl().shift(), e -> { 202 FXVK.toggleUseVK(textInputControl); 203 }, p -> !PlatformImpl.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)), 204 205 // mouse and context menu mappings 206 new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed), 207 new MouseMapping(MouseEvent.MOUSE_DRAGGED, this::mouseDragged), 208 new MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased), 209 new InputMap.Mapping<ContextMenuEvent>(ContextMenuEvent.CONTEXT_MENU_REQUESTED, this::contextMenuRequested) { 210 @Override public int getSpecificity(Event event) { 211 return 1; 212 } 213 } 214 ); 215 216 // mac os specific mappings 217 InputMap<T> macOsInputMap = new InputMap<>(c); 218 macOsInputMap.setInterceptor(e -> !PlatformUtil.isMac()); 219 macOsInputMap.getMappings().addAll( 220 // Mac OS specific mappings 221 keyMapping(new KeyBinding(HOME).shift(), e -> selectHomeExtend()), 222 keyMapping(new KeyBinding(END).shift(), e -> selectEndExtend()), 223 keyMapping(new KeyBinding(LEFT).shortcut(), e -> c.home()), 224 keyMapping(new KeyBinding(RIGHT).shortcut(), e -> c.end()), 225 keyMapping(new KeyBinding(LEFT).alt(), e -> leftWord()), 226 keyMapping(new KeyBinding(RIGHT).alt(), e -> rightWord()), 227 keyMapping(new KeyBinding(DELETE).alt(), e -> deleteNextWord()), 228 keyMapping(new KeyBinding(BACK_SPACE).alt(), e -> deletePreviousWord()), 229 keyMapping(new KeyBinding(BACK_SPACE).shortcut(), e -> deleteFromLineStart()), 230 keyMapping(new KeyBinding(Z).shortcut(), e -> undo()), 231 keyMapping(new KeyBinding(Z).shortcut().shift(), e -> redo()), 232 233 // Mac OS specific selection mappings 234 keyMapping(new KeyBinding(LEFT).shift().alt(), e -> selectLeftWord()), 235 keyMapping(new KeyBinding(RIGHT).shift().alt(), e -> selectRightWord()) 236 ); 237 addDefaultChildMap(inputMap, macOsInputMap); 238 239 // windows / linux specific mappings 240 InputMap<T> nonMacOsInputMap = new InputMap<>(c); 241 nonMacOsInputMap.setInterceptor(e -> PlatformUtil.isMac()); 242 nonMacOsInputMap.getMappings().addAll( 243 keyMapping(new KeyBinding(HOME).shift(), e -> selectHome()), 244 keyMapping(new KeyBinding(END).shift(), e -> selectEnd()), 245 keyMapping(new KeyBinding(LEFT).ctrl(), e -> leftWord()), 246 keyMapping(new KeyBinding(RIGHT).ctrl(), e -> rightWord()), 247 keyMapping(new KeyBinding(H).ctrl(), e -> deletePreviousChar()), 248 keyMapping(new KeyBinding(DELETE).ctrl(), e -> deleteNextWord()), 249 keyMapping(new KeyBinding(BACK_SPACE).ctrl(), e -> deletePreviousWord()), 250 keyMapping(new KeyBinding(BACK_SLASH).ctrl(), e -> c.deselect()), 251 keyMapping(new KeyBinding(Z).ctrl(), e -> undo()), 252 keyMapping(new KeyBinding(Y).ctrl(), e -> redo()) 253 ); 254 addDefaultChildMap(inputMap, nonMacOsInputMap); 255 256 addKeyPadMappings(inputMap); 257 258 textInputControl.textProperty().addListener(textListener); 259 } 260 261 @Override public InputMap<T> getInputMap() { 262 return inputMap; 263 } 264 265 /** 266 * Bind keypad arrow keys to the same as the regular arrow keys. 267 */ 268 protected void addKeyPadMappings(InputMap<T> map) { 269 // First create a temporary map for the keypad mappings 270 InputMap<T> tmpMap = new InputMap<>(getNode()); 271 for (Object o : map.getMappings()) { 272 if (o instanceof KeyMapping) { 273 KeyMapping mapping = (KeyMapping)o; 274 KeyBinding kb = (KeyBinding)mapping.getMappingKey(); 275 if (kb.getCode() != null) { 276 KeyCode newCode = null; 277 switch (kb.getCode()) { 278 case LEFT: newCode = KP_LEFT; break; 279 case RIGHT: newCode = KP_RIGHT; break; 280 case UP: newCode = KP_UP; break; 281 case DOWN: newCode = KP_DOWN; break; 282 } 283 if (newCode != null) { 284 KeyBinding newkb = new KeyBinding(newCode).shift(kb.getShift()) 285 .ctrl(kb.getCtrl()) 286 .alt(kb.getAlt()) 287 .meta(kb.getMeta()); 288 tmpMap.getMappings().add(new KeyMapping(newkb, mapping.getEventHandler())); 289 } 290 } 291 } 292 } 293 // Install mappings 294 for (Object o : tmpMap.getMappings()) { 295 map.getMappings().add((KeyMapping)o); 296 } 297 298 // Recursive call for child maps 299 for (Object o : map.getChildInputMaps()) { 300 addKeyPadMappings((InputMap<T>)o); 301 } 302 } 303 304 305 /** 306 * Wraps the event handler to pause caret blinking when 307 * processing the key event. 308 */ 309 protected KeyMapping keyMapping(final KeyCode keyCode, final EventHandler<KeyEvent> eventHandler) { 310 return keyMapping(new KeyBinding(keyCode), eventHandler); 311 } 312 313 protected KeyMapping keyMapping(KeyBinding keyBinding, final EventHandler<KeyEvent> eventHandler) { 314 return keyMapping(keyBinding, eventHandler, null); 315 } 316 317 protected KeyMapping keyMapping(KeyBinding keyBinding, final EventHandler<KeyEvent> eventHandler, 318 Predicate<KeyEvent> interceptor) { 319 return new KeyMapping(keyBinding, 320 e -> { 321 setCaretAnimating(false); 322 eventHandler.handle(e); 323 setCaretAnimating(true); 324 }, 325 interceptor); 326 } 327 328 329 330 331 332 /************************************************************************** 333 * Disposal methods * 334 *************************************************************************/ 335 336 @Override public void dispose() { 337 textInputControl.textProperty().removeListener(textListener); 338 super.dispose(); 339 } 340 341 /************************************************************************** 342 * Abstract methods * 343 *************************************************************************/ 344 345 protected abstract void deleteChar(boolean previous); 346 protected abstract void replaceText(int start, int end, String txt); 347 protected abstract void setCaretAnimating(boolean play); 348 protected abstract void deleteFromLineStart(); 349 350 protected abstract void mousePressed(MouseEvent e); 351 protected abstract void mouseDragged(MouseEvent e); 352 protected abstract void mouseReleased(MouseEvent e); 353 protected abstract void contextMenuRequested(ContextMenuEvent e); 354 355 /************************************************************************** 356 * Key handling implementation * 357 *************************************************************************/ 358 359 /** 360 * The default handler for a key typed event, which is called when none of 361 * the other key bindings match. This is the method which handles basic 362 * text entry. 363 * @param event not null 364 */ 365 private void defaultKeyTyped(KeyEvent event) { 366 final TextInputControl textInput = getNode(); 367 // I'm not sure this case can actually ever happen, maybe this 368 // should be an assert instead? 369 if (!textInput.isEditable() || textInput.isDisabled()) return; 370 371 // Sometimes we get events with no key character, in which case 372 // we need to bail. 373 String character = event.getCharacter(); 374 if (character.length() == 0) return; 375 376 // Filter out control keys except control+Alt on PC or Alt on Mac 377 if (event.isControlDown() || event.isAltDown() || (isMac() && event.isMetaDown())) { 378 if (!((event.isControlDown() || isMac()) && event.isAltDown())) return; 379 } 380 381 setEditing(true); 382 383 // Ignore characters in the control range and the ASCII delete 384 // character as well as meta key presses 385 if (character.charAt(0) > 0x1F 386 && character.charAt(0) != 0x7F 387 && !event.isMetaDown()) { // Not sure about this one 388 final IndexRange selection = textInput.getSelection(); 389 final int start = selection.getStart(); 390 final int end = selection.getEnd(); 391 392 replaceText(start, end, character); 393 } 394 395 setEditing(false); 396 } 397 398 private Bidi bidi = null; 399 private Boolean mixed = null; 400 private Boolean rtlText = null; 401 402 private void invalidateBidi() { 403 bidi = null; 404 mixed = null; 405 rtlText = null; 406 } 407 408 private Bidi getBidi() { 409 if (bidi == null) { 410 bidi = new Bidi(textInputControl.textProperty().getValueSafe(), 411 (textInputControl.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) 412 ? Bidi.DIRECTION_RIGHT_TO_LEFT 413 : Bidi.DIRECTION_LEFT_TO_RIGHT); 414 } 415 return bidi; 416 } 417 418 protected boolean isMixed() { 419 if (mixed == null) { 420 mixed = getBidi().isMixed(); 421 } 422 return mixed; 423 } 424 425 protected boolean isRTLText() { 426 if (rtlText == null) { 427 Bidi bidi = getBidi(); 428 rtlText = 429 (bidi.isRightToLeft() || 430 (isMixed() && 431 textInputControl.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT)); 432 } 433 return rtlText; 434 } 435 436 private void nextCharacterVisually(boolean moveRight) { 437 if (isMixed()) { 438 TextInputControlSkin<?> skin = (TextInputControlSkin<?>)textInputControl.getSkin(); 439 skin.moveCaret(TextUnit.CHARACTER, moveRight ? Direction.RIGHT : Direction.LEFT, false); 440 } else if (moveRight != isRTLText()) { 441 textInputControl.forward(); 442 } else { 443 textInputControl.backward(); 444 } 445 } 446 447 private void selectLeft() { 448 if (isRTLText()) { 449 textInputControl.selectForward(); 450 } else { 451 textInputControl.selectBackward(); 452 } 453 } 454 455 private void selectRight() { 456 if (isRTLText()) { 457 textInputControl.selectBackward(); 458 } else { 459 textInputControl.selectForward(); 460 } 461 } 462 463 private void deletePreviousChar() { 464 setEditing(true); 465 deleteChar(true); 466 setEditing(false); 467 } 468 469 private void deleteNextChar() { 470 setEditing(true); 471 deleteChar(false); 472 setEditing(false); 473 } 474 475 protected void deletePreviousWord() { 476 setEditing(true); 477 TextInputControl textInputControl = getNode(); 478 int end = textInputControl.getCaretPosition(); 479 480 if (end > 0) { 481 textInputControl.previousWord(); 482 int start = textInputControl.getCaretPosition(); 483 replaceText(start, end, ""); 484 } 485 setEditing(false); 486 } 487 488 protected void deleteNextWord() { 489 setEditing(true); 490 TextInputControl textInputControl = getNode(); 491 int start = textInputControl.getCaretPosition(); 492 493 if (start < textInputControl.getLength()) { 494 nextWord(); 495 int end = textInputControl.getCaretPosition(); 496 replaceText(start, end, ""); 497 } 498 setEditing(false); 499 } 500 501 public void deleteSelection() { 502 setEditing(true); 503 TextInputControl textInputControl = getNode(); 504 IndexRange selection = textInputControl.getSelection(); 505 506 if (selection.getLength() > 0) { 507 deleteChar(false); 508 } 509 setEditing(false); 510 } 511 512 public void cut() { 513 setEditing(true); 514 getNode().cut(); 515 setEditing(false); 516 } 517 518 public void paste() { 519 setEditing(true); 520 getNode().paste(); 521 setEditing(false); 522 } 523 524 public void undo() { 525 setEditing(true); 526 getNode().undo(); 527 setEditing(false); 528 } 529 530 public void redo() { 531 setEditing(true); 532 getNode().redo(); 533 setEditing(false); 534 } 535 536 protected void selectPreviousWord() { 537 getNode().selectPreviousWord(); 538 } 539 540 public void selectNextWord() { 541 TextInputControl textInputControl = getNode(); 542 if (isMac() || isLinux()) { 543 textInputControl.selectEndOfNextWord(); 544 } else { 545 textInputControl.selectNextWord(); 546 } 547 } 548 549 private void selectLeftWord() { 550 if (isRTLText()) { 551 selectNextWord(); 552 } else { 553 selectPreviousWord(); 554 } 555 } 556 557 private void selectRightWord() { 558 if (isRTLText()) { 559 selectPreviousWord(); 560 } else { 561 selectNextWord(); 562 } 563 } 564 565 protected void selectWord() { 566 final TextInputControl textInputControl = getNode(); 567 textInputControl.previousWord(); 568 if (isWindows()) { 569 textInputControl.selectNextWord(); 570 } else { 571 textInputControl.selectEndOfNextWord(); 572 } 573 } 574 575 protected void previousWord() { 576 getNode().previousWord(); 577 } 578 579 protected void nextWord() { 580 TextInputControl textInputControl = getNode(); 581 if (isMac() || isLinux()) { 582 textInputControl.endOfNextWord(); 583 } else { 584 textInputControl.nextWord(); 585 } 586 } 587 588 private void leftWord() { 589 if (isRTLText()) { 590 nextWord(); 591 } else { 592 previousWord(); 593 } 594 } 595 596 private void rightWord() { 597 if (isRTLText()) { 598 previousWord(); 599 } else { 600 nextWord(); 601 } 602 } 603 604 protected void fire(KeyEvent event) { } // TODO move to TextFieldBehavior 605 protected void cancelEdit(KeyEvent event) { forwardToParent(event);} 606 607 protected void forwardToParent(KeyEvent event) { 608 if (getNode().getParent() != null) { 609 getNode().getParent().fireEvent(event); 610 } 611 } 612 613 protected void selectHome() { 614 getNode().selectHome(); 615 } 616 617 protected void selectEnd() { 618 getNode().selectEnd(); 619 } 620 621 protected void selectHomeExtend() { 622 getNode().extendSelection(0); 623 } 624 625 protected void selectEndExtend() { 626 TextInputControl textInputControl = getNode(); 627 textInputControl.extendSelection(textInputControl.getLength()); 628 } 629 630 private boolean editing = false; 631 protected void setEditing(boolean b) { 632 editing = b; 633 } 634 public boolean isEditing() { 635 return editing; 636 } 637 638 protected void populateContextMenu(ContextMenu contextMenu) { 639 TextInputControl textInputControl = getNode(); 640 boolean editable = textInputControl.isEditable(); 641 boolean hasText = (textInputControl.getLength() > 0); 642 boolean hasSelection = (textInputControl.getSelection().getLength() > 0); 643 boolean maskText = (textInputControl instanceof PasswordField); // (maskText("A") != "A"); 644 ObservableList<MenuItem> items = contextMenu.getItems(); 645 646 if (SHOW_HANDLES) { 647 items.clear(); 648 if (!maskText && hasSelection) { 649 if (editable) { 650 items.add(cutMI); 651 } 652 items.add(copyMI); 653 } 654 if (editable && Clipboard.getSystemClipboard().hasString()) { 655 items.add(pasteMI); 656 } 657 if (hasText) { 658 if (!hasSelection) { 659 items.add(selectWordMI); 660 } 661 items.add(selectAllMI); 662 } 663 selectWordMI.getProperties().put("refreshMenu", Boolean.TRUE); 664 selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE); 665 } else { 666 if (editable) { 667 items.setAll(undoMI, redoMI, cutMI, copyMI, pasteMI, deleteMI, 668 separatorMI, selectAllMI); 669 } else { 670 items.setAll(copyMI, separatorMI, selectAllMI); 671 } 672 undoMI.setDisable(!getNode().isUndoable()); 673 redoMI.setDisable(!getNode().isRedoable()); 674 cutMI.setDisable(maskText || !hasSelection); 675 copyMI.setDisable(maskText || !hasSelection); 676 pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()); 677 deleteMI.setDisable(!hasSelection); 678 } 679 } 680 681 private static class ContextMenuItem extends MenuItem { 682 ContextMenuItem(final String action, EventHandler<ActionEvent> onAction) { 683 super(getString("TextInputControl.menu." + action)); 684 setOnAction(onAction); 685 } 686 } 687 688 private final MenuItem undoMI = new ContextMenuItem("Undo", e -> undo()); 689 private final MenuItem redoMI = new ContextMenuItem("Redo", e -> redo()); 690 private final MenuItem cutMI = new ContextMenuItem("Cut", e -> cut()); 691 private final MenuItem copyMI = new ContextMenuItem("Copy", e -> getNode().copy()); 692 private final MenuItem pasteMI = new ContextMenuItem("Paste", e -> paste()); 693 private final MenuItem deleteMI = new ContextMenuItem("DeleteSelection", e -> deleteSelection()); 694 private final MenuItem selectWordMI = new ContextMenuItem("SelectWord", e -> selectNextWord()); 695 private final MenuItem selectAllMI = new ContextMenuItem("SelectAll", e -> getNode().selectAll()); 696 private final MenuItem separatorMI = new SeparatorMenuItem(); 697 698 }