1 /* 2 * Copyright (c) 2011, 2016, 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 26 package com.sun.javafx.scene.control.behavior; 27 28 import com.sun.javafx.PlatformUtil; 29 import com.sun.javafx.geom.transform.Affine3D; 30 import com.sun.javafx.scene.control.Properties; 31 import javafx.scene.control.skin.TextAreaSkin; 32 import javafx.beans.value.ChangeListener; 33 import javafx.beans.value.ObservableValue; 34 import javafx.geometry.Bounds; 35 import javafx.geometry.Point2D; 36 import javafx.geometry.Rectangle2D; 37 import javafx.scene.Scene; 38 import javafx.scene.control.ContextMenu; 39 import javafx.scene.control.TextArea; 40 import com.sun.javafx.scene.control.skin.Utils; 41 import javafx.scene.input.ContextMenuEvent; 42 import com.sun.javafx.scene.control.inputmap.InputMap; 43 import com.sun.javafx.scene.control.inputmap.KeyBinding; 44 import javafx.scene.input.KeyEvent; 45 import javafx.scene.input.MouseButton; 46 import javafx.scene.input.MouseEvent; 47 import javafx.scene.text.HitInfo; 48 import javafx.stage.Screen; 49 import javafx.stage.Window; 50 51 import java.util.function.Predicate; 52 53 import static com.sun.javafx.PlatformUtil.isMac; 54 import static com.sun.javafx.PlatformUtil.isWindows; 55 import static javafx.scene.control.skin.TextInputControlSkin.TextUnit; 56 import static javafx.scene.control.skin.TextInputControlSkin.Direction; 57 import static javafx.scene.input.KeyCode.*; 58 59 60 /** 61 * Text area behavior. 62 */ 63 public class TextAreaBehavior extends TextInputControlBehavior<TextArea> { 64 // /************************************************************************** 65 // * Setup KeyBindings * 66 // *************************************************************************/ 67 // protected static final List<KeyBinding> TEXT_AREA_BINDINGS = new ArrayList<KeyBinding>(); 68 // static { 69 // TEXT_AREA_BINDINGS.add(new KeyBinding(HOME, KEY_PRESSED, "LineStart")); // changed 70 // TEXT_AREA_BINDINGS.add(new KeyBinding(END, KEY_PRESSED, "LineEnd")); // changed 71 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "PreviousLine")); // changed 72 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "PreviousLine")); // changed 73 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "NextLine")); // changed 74 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "NextLine")); // changed 75 // TEXT_AREA_BINDINGS.add(new KeyBinding(PAGE_UP, KEY_PRESSED, "PreviousPage")); // new 76 // TEXT_AREA_BINDINGS.add(new KeyBinding(PAGE_DOWN, KEY_PRESSED, "NextPage")); // new 77 // TEXT_AREA_BINDINGS.add(new KeyBinding(ENTER, KEY_PRESSED, "InsertNewLine")); // changed 78 // TEXT_AREA_BINDINGS.add(new KeyBinding(TAB, KEY_PRESSED, "TraverseOrInsertTab")); // changed 79 // 80 // TEXT_AREA_BINDINGS.add(new KeyBinding(HOME, KEY_PRESSED, "SelectLineStart").shift()); // changed 81 // TEXT_AREA_BINDINGS.add(new KeyBinding(END, KEY_PRESSED, "SelectLineEnd").shift()); // changed 82 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "SelectPreviousLine").shift()); // changed 83 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "SelectPreviousLine").shift()); // changed 84 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "SelectNextLine").shift()); // changed 85 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "SelectNextLine").shift()); // changed 86 // TEXT_AREA_BINDINGS.add(new KeyBinding(PAGE_UP, KEY_PRESSED, "SelectPreviousPage").shift()); // new 87 // TEXT_AREA_BINDINGS.add(new KeyBinding(PAGE_DOWN, KEY_PRESSED, "SelectNextPage").shift()); // new 88 // // Platform specific settings 89 // if (isMac()) { 90 // TEXT_AREA_BINDINGS.add(new KeyBinding(LEFT, KEY_PRESSED, "LineStart").shortcut()); // changed 91 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_LEFT, KEY_PRESSED, "LineStart").shortcut()); // changed 92 // TEXT_AREA_BINDINGS.add(new KeyBinding(RIGHT, KEY_PRESSED, "LineEnd").shortcut()); // changed 93 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_RIGHT, KEY_PRESSED, "LineEnd").shortcut()); // changed 94 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "Home").shortcut()); 95 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "Home").shortcut()); 96 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "End").shortcut()); 97 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "End").shortcut()); 98 // 99 // TEXT_AREA_BINDINGS.add(new KeyBinding(LEFT, KEY_PRESSED, "SelectLineStartExtend").shift().shortcut()); // changed 100 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_LEFT, KEY_PRESSED, "SelectLineStartExtend").shift().shortcut()); // changed 101 // TEXT_AREA_BINDINGS.add(new KeyBinding(RIGHT, KEY_PRESSED, "SelectLineEndExtend").shift().shortcut()); // changed 102 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_RIGHT, KEY_PRESSED, "SelectLineEndExtend").shift().shortcut()); // changed 103 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "SelectHomeExtend").shortcut().shift()); 104 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "SelectHomeExtend").shortcut().shift()); 105 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "SelectEndExtend").shortcut().shift()); 106 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "SelectEndExtend").shortcut().shift()); 107 // 108 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "ParagraphStart").alt()); 109 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "ParagraphStart").alt()); 110 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "ParagraphEnd").alt()); 111 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "ParagraphEnd").alt()); 112 // 113 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "SelectParagraphStart").alt().shift()); 114 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "SelectParagraphStart").alt().shift()); 115 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "SelectParagraphEnd").alt().shift()); 116 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "SelectParagraphEnd").alt().shift()); 117 // } else { 118 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "ParagraphStart").ctrl()); 119 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "ParagraphStart").ctrl()); 120 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "ParagraphEnd").ctrl()); 121 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "ParagraphEnd").ctrl()); 122 // TEXT_AREA_BINDINGS.add(new KeyBinding(UP, KEY_PRESSED, "SelectParagraphStart").ctrl().shift()); 123 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_UP, KEY_PRESSED, "SelectParagraphStart").ctrl().shift()); 124 // TEXT_AREA_BINDINGS.add(new KeyBinding(DOWN, KEY_PRESSED, "SelectParagraphEnd").ctrl().shift()); 125 // TEXT_AREA_BINDINGS.add(new KeyBinding(KP_DOWN, KEY_PRESSED, "SelectParagraphEnd").ctrl().shift()); 126 // } 127 // // Add the other standard key bindings in 128 // TEXT_AREA_BINDINGS.addAll(TextInputControlBindings.BINDINGS); 129 // // However, we want to consume other key press / release events too, for 130 // // things that would have been handled by the InputCharacter normally 131 // TEXT_AREA_BINDINGS.add(new KeyBinding(null, KEY_PRESSED, "Consume")); 132 // } 133 // 134 private TextAreaSkin skin; 135 private TwoLevelFocusBehavior tlFocus; 136 137 /************************************************************************** 138 * Constructors * 139 *************************************************************************/ 140 141 public TextAreaBehavior(final TextArea c) { 142 super(c); 143 144 if (Properties.IS_TOUCH_SUPPORTED) { 145 contextMenu.getStyleClass().add("text-input-context-menu"); 146 } 147 148 // some of the mappings are only valid when the control is editable, or 149 // only on certain platforms, so we create the following predicates that filters out the mapping when the 150 // control is not in the correct state / on the correct platform 151 final Predicate<KeyEvent> validWhenEditable = e -> !c.isEditable(); 152 final Predicate<KeyEvent> validOnMac = e -> !PlatformUtil.isMac(); 153 final Predicate<KeyEvent> validOnWindows = e -> !PlatformUtil.isWindows(); 154 final Predicate<KeyEvent> validOnLinux = e -> !PlatformUtil.isLinux(); 155 156 // Add these bindings as a child input map, so they take precedence 157 InputMap<TextArea> textAreaInputMap = new InputMap<>(c); 158 textAreaInputMap.getMappings().addAll( 159 keyMapping(HOME, e -> lineStart(false)), 160 keyMapping(END, e -> lineEnd(false)), 161 keyMapping(UP, e -> skin.moveCaret(TextUnit.LINE, Direction.UP, false)), 162 keyMapping(DOWN, e -> skin.moveCaret(TextUnit.LINE, Direction.DOWN, false)), 163 keyMapping(PAGE_UP, e -> skin.moveCaret(TextUnit.PAGE, Direction.UP, false)), 164 keyMapping(PAGE_DOWN, e -> skin.moveCaret(TextUnit.PAGE, Direction.DOWN, false)), 165 166 keyMapping(new KeyBinding(HOME).shift(), e -> lineStart(true)), 167 keyMapping(new KeyBinding(END).shift(), e -> lineEnd(true)), 168 keyMapping(new KeyBinding(UP).shift(), e -> skin.moveCaret(TextUnit.LINE, Direction.UP, true)), 169 keyMapping(new KeyBinding(DOWN).shift(), e -> skin.moveCaret(TextUnit.LINE, Direction.DOWN, true)), 170 keyMapping(new KeyBinding(PAGE_UP).shift(), e -> skin.moveCaret(TextUnit.PAGE, Direction.UP, true)), 171 keyMapping(new KeyBinding(PAGE_DOWN).shift(), e -> skin.moveCaret(TextUnit.PAGE, Direction.DOWN, true)), 172 173 // editing-only mappings 174 keyMapping(new KeyBinding(ENTER), e -> insertNewLine(), validWhenEditable), 175 keyMapping(new KeyBinding(TAB), e -> insertTab(), validWhenEditable) 176 ); 177 addDefaultChildMap(getInputMap(), textAreaInputMap); 178 179 // mac os specific mappings 180 InputMap<TextArea> macOsInputMap = new InputMap<>(c); 181 macOsInputMap.setInterceptor(e -> !PlatformUtil.isMac()); 182 macOsInputMap.getMappings().addAll( 183 // Mac OS specific mappings 184 keyMapping(new KeyBinding(LEFT).shortcut(), e -> lineStart(false)), 185 keyMapping(new KeyBinding(RIGHT).shortcut(), e -> lineEnd(false)), 186 keyMapping(new KeyBinding(UP).shortcut(), e -> c.home()), 187 keyMapping(new KeyBinding(DOWN).shortcut(), e -> c.end()), 188 189 keyMapping(new KeyBinding(LEFT).shortcut().shift(), e -> lineStart(true)), 190 keyMapping(new KeyBinding(RIGHT).shortcut().shift(), e -> lineEnd(true)), 191 keyMapping(new KeyBinding(UP).shortcut().shift(), e -> selectHomeExtend()), 192 keyMapping(new KeyBinding(DOWN).shortcut().shift(), e -> selectEndExtend()), 193 194 keyMapping(new KeyBinding(UP).alt(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.UP, false)), 195 keyMapping(new KeyBinding(DOWN).alt(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.DOWN, false)), 196 keyMapping(new KeyBinding(UP).alt().shift(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.UP, true)), 197 keyMapping(new KeyBinding(DOWN).alt().shift(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.DOWN, true)) 198 ); 199 addDefaultChildMap(textAreaInputMap, macOsInputMap); 200 201 // windows / linux specific mappings 202 InputMap<TextArea> nonMacOsInputMap = new InputMap<>(c); 203 nonMacOsInputMap.setInterceptor(e -> PlatformUtil.isMac()); 204 nonMacOsInputMap.getMappings().addAll( 205 keyMapping(new KeyBinding(UP).ctrl(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.UP, false)), 206 keyMapping(new KeyBinding(DOWN).ctrl(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.DOWN, false)), 207 keyMapping(new KeyBinding(UP).ctrl().shift(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.UP, true)), 208 keyMapping(new KeyBinding(DOWN).ctrl().shift(), e -> skin.moveCaret(TextUnit.PARAGRAPH, Direction.DOWN, true)) 209 ); 210 addDefaultChildMap(textAreaInputMap, nonMacOsInputMap); 211 212 addKeyPadMappings(textAreaInputMap); 213 214 // Register for change events 215 c.focusedProperty().addListener(new ChangeListener<Boolean>() { 216 @Override 217 public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { 218 // NOTE: The code in this method is *almost* and exact copy of what is in TextFieldBehavior. 219 // The only real difference is that TextFieldBehavior selects all the text when the control 220 // receives focus (when not gained by mouse click), whereas TextArea doesn't, and also the 221 // TextArea doesn't lose selection on focus lost, whereas the TextField does. 222 final TextArea textArea = getNode(); 223 if (textArea.isFocused()) { 224 if (PlatformUtil.isIOS()) { 225 // Special handling of focus on iOS is required to allow to 226 // control native keyboard, because native keyboard is popped-up only when native 227 // text component gets focus. When we have JFX keyboard we can remove this code 228 final Bounds bounds = textArea.getBoundsInParent(); 229 double w = bounds.getWidth(); 230 double h = bounds.getHeight(); 231 Affine3D trans = TextFieldBehavior.calculateNodeToSceneTransform(textArea); 232 String text = textArea.textProperty().getValueSafe(); 233 234 // we need to display native text input component on the place where JFX component is drawn 235 // all parameters needed to do that are passed to native impl. here 236 textArea.getScene().getWindow().impl_getPeer().requestInput(text, TextFieldBehavior.TextInputTypes.TEXT_AREA.ordinal(), w, h, 237 trans.getMxx(), trans.getMxy(), trans.getMxz(), trans.getMxt(), 238 trans.getMyx(), trans.getMyy(), trans.getMyz(), trans.getMyt(), 239 trans.getMzx(), trans.getMzy(), trans.getMzz(), trans.getMzt()); 240 } 241 if (!focusGainedByMouseClick) { 242 setCaretAnimating(true); 243 } 244 } else { 245 // skin.hideCaret(); 246 if (PlatformUtil.isIOS() && textArea.getScene() != null) { 247 // releasing the focus => we need to hide the native component and also native keyboard 248 textArea.getScene().getWindow().impl_getPeer().releaseInput(); 249 } 250 focusGainedByMouseClick = false; 251 setCaretAnimating(false); 252 } 253 } 254 }); 255 256 // Only add this if we're on an embedded platform that supports 5-button navigation 257 if (Utils.isTwoLevelFocus()) { 258 tlFocus = new TwoLevelFocusBehavior(c); // needs to be last. 259 } 260 } 261 262 @Override public void dispose() { 263 if (tlFocus != null) tlFocus.dispose(); 264 super.dispose(); 265 } 266 267 // An unholy back-reference! 268 public void setTextAreaSkin(TextAreaSkin skin) { 269 this.skin = skin; 270 } 271 272 private void insertNewLine() { 273 setEditing(true); 274 getNode().replaceSelection("\n"); 275 setEditing(false); 276 } 277 278 private void insertTab() { 279 setEditing(true); 280 getNode().replaceSelection("\t"); 281 setEditing(false); 282 } 283 284 @Override protected void deleteChar(boolean previous) { 285 if (previous) { 286 getNode().deletePreviousChar(); 287 } else { 288 getNode().deleteNextChar(); 289 } 290 } 291 292 @Override protected void deleteFromLineStart() { 293 TextArea textArea = getNode(); 294 int end = textArea.getCaretPosition(); 295 296 if (end > 0) { 297 lineStart(false); 298 int start = textArea.getCaretPosition(); 299 if (end > start) { 300 replaceText(start, end, ""); 301 } 302 } 303 } 304 305 private void lineStart(boolean select) { 306 skin.moveCaret(TextUnit.LINE, Direction.BEGINNING, select); 307 } 308 309 private void lineEnd(boolean select) { 310 skin.moveCaret(TextUnit.LINE, Direction.END, select); 311 } 312 313 @Override protected void replaceText(int start, int end, String txt) { 314 getNode().replaceText(start, end, txt); 315 } 316 317 /** 318 * If the focus is gained via response to a mouse click, then we don't 319 * want to select all the text even if selectOnFocus is true. 320 */ 321 private boolean focusGainedByMouseClick = false; // TODO!! 322 private boolean shiftDown = false; 323 private boolean deferClick = false; 324 325 @Override public void mousePressed(MouseEvent e) { 326 TextArea textArea = getNode(); 327 // We never respond to events if disabled 328 if (!textArea.isDisabled()) { 329 // If the text field doesn't have focus, then we'll attempt to set 330 // the focus and we'll indicate that we gained focus by a mouse 331 // click, TODO which will then NOT honor the selectOnFocus variable 332 // of the textInputControl 333 if (!textArea.isFocused()) { 334 focusGainedByMouseClick = true; 335 textArea.requestFocus(); 336 } 337 338 // stop the caret animation 339 setCaretAnimating(false); 340 // only if there is no selection should we see the caret 341 // setCaretOpacity(if (textInputControl.dot == textInputControl.mark) then 1.0 else 0.0); 342 343 // if the primary button was pressed 344 if (e.getButton() == MouseButton.PRIMARY && !(e.isMiddleButtonDown() || e.isSecondaryButtonDown())) { 345 HitInfo hit = skin.getIndex(e.getX(), e.getY()); 346 int i = hit.getInsertionIndex(); 347 final int anchor = textArea.getAnchor(); 348 final int caretPosition = textArea.getCaretPosition(); 349 if (e.getClickCount() < 2 && 350 (e.isSynthesized() || 351 (anchor != caretPosition && 352 ((i > anchor && i < caretPosition) || (i < anchor && i > caretPosition))))) { 353 // if there is a selection, then we will NOT handle the 354 // press now, but will defer until the release. If you 355 // select some text and then press down, we change the 356 // caret and wait to allow you to drag the text (TODO). 357 // When the drag concludes, then we handle the click 358 359 deferClick = true; 360 // TODO start a timer such that after some millis we 361 // switch into text dragging mode, change the cursor 362 // to indicate the text can be dragged, etc. 363 } else if (!(e.isControlDown() || e.isAltDown() || e.isShiftDown() || e.isMetaDown() || e.isShortcutDown())) { 364 switch (e.getClickCount()) { 365 case 1: skin.positionCaret(hit, false); break; 366 case 2: mouseDoubleClick(hit); break; 367 case 3: mouseTripleClick(hit); break; 368 default: // no-op 369 } 370 } else if (e.isShiftDown() && !(e.isControlDown() || e.isAltDown() || e.isMetaDown() || e.isShortcutDown()) && e.getClickCount() == 1) { 371 // didn't click inside the selection, so select 372 shiftDown = true; 373 // if we are on mac os, then we will accumulate the 374 // selection instead of just moving the dot. This happens 375 // by figuring out past which (dot/mark) are extending the 376 // selection, and set the mark to be the other side and 377 // the dot to be the new position. 378 // everywhere else we just move the dot. 379 if (isMac()) { 380 textArea.extendSelection(i); 381 } else { 382 skin.positionCaret(hit, true); 383 } 384 } 385 // skin.setForwardBias(hit.isLeading()); 386 // if (textInputControl.editable) 387 // displaySoftwareKeyboard(true); 388 } 389 if (contextMenu.isShowing()) { 390 contextMenu.hide(); 391 } 392 } 393 } 394 395 @Override public void mouseDragged(MouseEvent e) { 396 final TextArea textArea = getNode(); 397 // we never respond to events if disabled, but we do notify any onXXX 398 // event listeners on the control 399 if (!textArea.isDisabled() && !e.isSynthesized()) { 400 if (e.getButton() == MouseButton.PRIMARY && 401 !(e.isMiddleButtonDown() || e.isSecondaryButtonDown() || 402 e.isControlDown() || e.isAltDown() || e.isShiftDown() || e.isMetaDown())) { 403 skin.positionCaret(skin.getIndex(e.getX(), e.getY()), true); 404 } 405 } 406 deferClick = false; 407 } 408 409 @Override public void mouseReleased(final MouseEvent e) { 410 final TextArea textArea = getNode(); 411 // we never respond to events if disabled, but we do notify any onXXX 412 // event listeners on the control 413 if (!textArea.isDisabled()) { 414 setCaretAnimating(false); 415 if (deferClick) { 416 deferClick = false; 417 skin.positionCaret(skin.getIndex(e.getX(), e.getY()), shiftDown); 418 shiftDown = false; 419 } 420 setCaretAnimating(true); 421 } 422 } 423 424 @Override public void contextMenuRequested(ContextMenuEvent e) { 425 final TextArea textArea = getNode(); 426 427 if (contextMenu.isShowing()) { 428 contextMenu.hide(); 429 } else if (textArea.getContextMenu() == null && 430 textArea.getOnContextMenuRequested() == null) { 431 double screenX = e.getScreenX(); 432 double screenY = e.getScreenY(); 433 double sceneX = e.getSceneX(); 434 435 if (Properties.IS_TOUCH_SUPPORTED) { 436 Point2D menuPos; 437 if (textArea.getSelection().getLength() == 0) { 438 skin.positionCaret(skin.getIndex(e.getX(), e.getY()), false); 439 menuPos = skin.getMenuPosition(); 440 } else { 441 menuPos = skin.getMenuPosition(); 442 if (menuPos != null && (menuPos.getX() <= 0 || menuPos.getY() <= 0)) { 443 skin.positionCaret(skin.getIndex(e.getX(), e.getY()), false); 444 menuPos = skin.getMenuPosition(); 445 } 446 } 447 448 if (menuPos != null) { 449 Point2D p = getNode().localToScene(menuPos); 450 Scene scene = getNode().getScene(); 451 Window window = scene.getWindow(); 452 Point2D location = new Point2D(window.getX() + scene.getX() + p.getX(), 453 window.getY() + scene.getY() + p.getY()); 454 screenX = location.getX(); 455 sceneX = p.getX(); 456 screenY = location.getY(); 457 } 458 } 459 460 populateContextMenu(); 461 double menuWidth = contextMenu.prefWidth(-1); 462 double menuX = screenX - (Properties.IS_TOUCH_SUPPORTED ? (menuWidth / 2) : 0); 463 Screen currentScreen = com.sun.javafx.util.Utils.getScreenForPoint(screenX, 0); 464 Rectangle2D bounds = currentScreen.getBounds(); 465 466 if (menuX < bounds.getMinX()) { 467 getNode().getProperties().put("CONTEXT_MENU_SCREEN_X", screenX); 468 getNode().getProperties().put("CONTEXT_MENU_SCENE_X", sceneX); 469 contextMenu.show(getNode(), bounds.getMinX(), screenY); 470 } else if (screenX + menuWidth > bounds.getMaxX()) { 471 double leftOver = menuWidth - ( bounds.getMaxX() - screenX); 472 getNode().getProperties().put("CONTEXT_MENU_SCREEN_X", screenX); 473 getNode().getProperties().put("CONTEXT_MENU_SCENE_X", sceneX); 474 contextMenu.show(getNode(), screenX - leftOver, screenY); 475 } else { 476 getNode().getProperties().put("CONTEXT_MENU_SCREEN_X", 0); 477 getNode().getProperties().put("CONTEXT_MENU_SCENE_X", 0); 478 contextMenu.show(getNode(), menuX, screenY); 479 } 480 } 481 482 e.consume(); 483 } 484 485 @Override protected void setCaretAnimating(boolean play) { 486 skin.setCaretAnimating(play); 487 } 488 489 protected void mouseDoubleClick(HitInfo hit) { 490 final TextArea textArea = getNode(); 491 textArea.previousWord(); 492 if (isWindows()) { 493 textArea.selectNextWord(); 494 } else { 495 textArea.selectEndOfNextWord(); 496 } 497 } 498 499 protected void mouseTripleClick(HitInfo hit) { 500 // select the line 501 skin.moveCaret(TextUnit.PARAGRAPH, Direction.BEGINNING, false); 502 skin.moveCaret(TextUnit.PARAGRAPH, Direction.END, true); 503 } 504 }