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