1 /* 2 * Copyright (c) 2010, 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 javafx.scene; 27 28 import com.sun.glass.ui.Application; 29 import com.sun.glass.ui.Accessible; 30 import com.sun.javafx.util.Logging; 31 import com.sun.javafx.util.Utils; 32 import com.sun.javafx.application.PlatformImpl; 33 import com.sun.javafx.collections.TrackableObservableList; 34 import com.sun.javafx.css.StyleManager; 35 import com.sun.javafx.cursor.CursorFrame; 36 import com.sun.javafx.event.EventQueue; 37 import com.sun.javafx.geom.PickRay; 38 import com.sun.javafx.geom.Vec3d; 39 import com.sun.javafx.geom.transform.BaseTransform; 40 import com.sun.javafx.perf.PerformanceTracker; 41 import com.sun.javafx.robot.impl.FXRobotHelper; 42 import com.sun.javafx.runtime.SystemProperties; 43 import com.sun.javafx.scene.CssFlags; 44 import com.sun.javafx.scene.LayoutFlags; 45 import com.sun.javafx.scene.SceneEventDispatcher; 46 import com.sun.javafx.scene.SceneHelper; 47 import com.sun.javafx.scene.input.DragboardHelper; 48 import com.sun.javafx.scene.input.ExtendedInputMethodRequests; 49 import com.sun.javafx.scene.input.InputEventUtils; 50 import com.sun.javafx.scene.input.PickResultChooser; 51 import com.sun.javafx.scene.traversal.Direction; 52 import com.sun.javafx.scene.traversal.SceneTraversalEngine; 53 import com.sun.javafx.scene.traversal.TopMostTraversalEngine; 54 import com.sun.javafx.sg.prism.NGCamera; 55 import com.sun.javafx.sg.prism.NGLightBase; 56 import com.sun.javafx.tk.*; 57 import com.sun.prism.impl.PrismSettings; 58 59 import javafx.animation.KeyFrame; 60 import javafx.animation.Timeline; 61 import javafx.application.ConditionalFeature; 62 import javafx.application.Platform; 63 import javafx.beans.DefaultProperty; 64 import javafx.beans.InvalidationListener; 65 import javafx.beans.NamedArg; 66 import javafx.beans.property.*; 67 import javafx.collections.FXCollections; 68 import javafx.collections.ListChangeListener.Change; 69 import javafx.collections.ObservableList; 70 import javafx.collections.ObservableMap; 71 import javafx.css.CssMetaData; 72 import javafx.css.StyleableObjectProperty; 73 import javafx.event.*; 74 import javafx.geometry.*; 75 import javafx.scene.image.WritableImage; 76 import javafx.scene.input.*; 77 import javafx.scene.paint.Color; 78 import javafx.scene.paint.Paint; 79 import javafx.stage.PopupWindow; 80 import javafx.stage.Stage; 81 import javafx.stage.StageStyle; 82 import javafx.stage.Window; 83 import javafx.util.Callback; 84 import javafx.util.Duration; 85 import sun.util.logging.PlatformLogger; 86 import sun.util.logging.PlatformLogger.Level; 87 88 import java.security.AccessControlContext; 89 import java.security.AccessController; 90 import java.security.PrivilegedAction; 91 import java.util.*; 92 93 import com.sun.javafx.logging.PulseLogger; 94 95 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED; 96 97 /** 98 * The JavaFX {@code Scene} class is the container for all content in a scene graph. 99 * The background of the scene is filled as specified by the {@code fill} property. 100 * <p> 101 * The application must specify the root {@code Node} for the scene graph by setting 102 * the {@code root} property. If a {@code Group} is used as the root, the 103 * contents of the scene graph will be clipped by the scene's width and height and 104 * changes to the scene's size (if user resizes the stage) will not alter the 105 * layout of the scene graph. If a resizable node (layout {@code Region} or 106 * {@code Control} is set as the root, then the root's size will track the 107 * scene's size, causing the contents to be relayed out as necessary. 108 * <p> 109 * The scene's size may be initialized by the application during construction. 110 * If no size is specified, the scene will automatically compute its initial 111 * size based on the preferred size of its content. If only one dimension is specified, 112 * the other dimension is computed using the specified dimension, respecting content bias 113 * of a root. 114 * <p> 115 * An application may request depth buffer support or scene anti-aliasing 116 * support at the creation of a {@code Scene}. A scene with only 2D shapes and 117 * without any 3D transforms does not need a depth buffer nor scene 118 * anti-aliasing support. A scene containing 3D shapes or 2D shapes with 3D 119 * transforms may use depth buffer support for proper depth sorted rendering; to 120 * avoid depth fighting (also known as Z fighting), disable depth testing on 2D 121 * shapes that have no 3D transforms. See 122 * {@link Node#depthTestProperty depthTest} for more information. A scene with 123 * 3D shapes may enable scene anti-aliasing to improve its rendering quality. 124 * <p> 125 * The depthBuffer and antiAliasing flags are conditional features. With the 126 * respective default values of: false and {@code SceneAntialiasing.DISABLED}. 127 * See {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 128 * for more information. 129 * <p> 130 * A default headlight will be added to a scene that contains one or more 131 * {@code Shape3D} nodes, but no light nodes. This light source is a 132 * {@code Color.WHITE} {@code PointLight} placed at the camera position. 133 * 134 * <p> 135 * Scene objects must be constructed and modified on the 136 * JavaFX Application Thread. 137 * </p> 138 * 139 * <p>Example:</p> 140 * 141 * <p> 142 * <pre> 143 import javafx.scene.*; 144 import javafx.scene.paint.*; 145 import javafx.scene.shape.*; 146 147 Group root = new Group(); 148 Scene s = new Scene(root, 300, 300, Color.BLACK); 149 150 Rectangle r = new Rectangle(25,25,250,250); 151 r.setFill(Color.BLUE); 152 153 root.getChildren().add(r); 154 * </pre> 155 * </p> 156 * @since JavaFX 2.0 157 */ 158 @DefaultProperty("root") 159 public class Scene implements EventTarget { 160 161 private double widthSetByUser = -1.0; 162 private double heightSetByUser = -1.0; 163 private boolean sizeInitialized = false; 164 private final boolean depthBuffer; 165 private final SceneAntialiasing antiAliasing; 166 167 private int dirtyBits; 168 169 final AccessControlContext acc = AccessController.getContext(); 170 171 private Camera defaultCamera; 172 173 /** 174 * A node that is temporarily responsible for the FOCUS_NODE 175 * accessibility attribute. E.g. a currently active MenuBar. 176 */ 177 private Node transientFocusContainer; 178 179 //Neither width nor height are initialized and will be calculated according to content when this Scene 180 //is shown for the first time. 181 // public Scene() { 182 // //this(-1, -1, (Parent) new Group()); 183 // this(-1, -1, (Parent)null); 184 // } 185 186 /** 187 * Creates a Scene for a specific root Node. 188 * 189 * @param root The root node of the scene graph 190 * 191 * @throws NullPointerException if root is null 192 */ 193 public Scene(@NamedArg("root") Parent root) { 194 this(root, -1, -1, Color.WHITE, false, SceneAntialiasing.DISABLED); 195 } 196 197 //Public constructor initializing public-init properties 198 //When width < 0, and or height < 0 is passed, then width and/or height are understood as unitialized 199 //Unitialized dimension is calculated when Scene is shown for the first time. 200 // public Scene( 201 // @Default("-1") double width, 202 // @Default("-1") double height) { 203 // //this(width, height, (Parent)new Group()); 204 // this(width, height, (Parent)null); 205 // } 206 // 207 // public Scene(double width, double height, Paint fill) { 208 // //this(width, height, (Parent) new Group()); 209 // this(width, height, (Parent)null); 210 // setFill(fill); 211 // } 212 213 /** 214 * Creates a Scene for a specific root Node with a specific size. 215 * 216 * @param root The root node of the scene graph 217 * @param width The width of the scene 218 * @param height The height of the scene 219 * 220 * @throws NullPointerException if root is null 221 */ 222 public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height) { 223 this(root, width, height, Color.WHITE, false, SceneAntialiasing.DISABLED); 224 } 225 226 /** 227 * Creates a Scene for a specific root Node with a fill. 228 * 229 * @param root The parent 230 * @param fill The fill 231 * 232 * @throws NullPointerException if root is null 233 */ 234 public Scene(@NamedArg("root") Parent root, @NamedArg(value="fill", defaultValue="WHITE") Paint fill) { 235 this(root, -1, -1, fill, false, SceneAntialiasing.DISABLED); 236 } 237 238 /** 239 * Creates a Scene for a specific root Node with a specific size and fill. 240 * 241 * @param root The root node of the scene graph 242 * @param width The width of the scene 243 * @param height The height of the scene 244 * @param fill The fill 245 * 246 * @throws NullPointerException if root is null 247 */ 248 public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height, 249 @NamedArg(value="fill", defaultValue="WHITE") Paint fill) { 250 this(root, width, height, fill, false, SceneAntialiasing.DISABLED); 251 } 252 253 /** 254 * Constructs a scene consisting of a root, with a dimension of width and 255 * height, and specifies whether a depth buffer is created for this scene. 256 * <p> 257 * A scene with only 2D shapes and without any 3D transforms does not need a 258 * depth buffer. A scene containing 3D shapes or 2D shapes with 3D 259 * transforms may use depth buffer support for proper depth sorted 260 * rendering; to avoid depth fighting (also known as Z fighting), disable 261 * depth testing on 2D shapes that have no 3D transforms. See 262 * {@link Node#depthTestProperty depthTest} for more information. 263 * 264 * @param root The root node of the scene graph 265 * @param width The width of the scene 266 * @param height The height of the scene 267 * @param depthBuffer The depth buffer flag 268 * <p> 269 * The depthBuffer flag is a conditional feature and its default value is 270 * false. See 271 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 272 * for more information. 273 * 274 * @throws NullPointerException if root is null 275 * 276 * @see javafx.scene.Node#setDepthTest(DepthTest) 277 */ 278 public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height, @NamedArg("depthBuffer") boolean depthBuffer) { 279 this(root, width, height, Color.WHITE, depthBuffer, SceneAntialiasing.DISABLED); 280 } 281 282 /** 283 * Constructs a scene consisting of a root, with a dimension of width and 284 * height, specifies whether a depth buffer is created for this scene and 285 * specifies whether scene anti-aliasing is requested. 286 * <p> 287 * A scene with only 2D shapes and without any 3D transforms does not need a 288 * depth buffer nor scene anti-aliasing support. A scene containing 3D 289 * shapes or 2D shapes with 3D transforms may use depth buffer support for 290 * proper depth sorted rendering; to avoid depth fighting (also known as Z 291 * fighting), disable depth testing on 2D shapes that have no 3D transforms. 292 * See {@link Node#depthTestProperty depthTest} for more information. A 293 * scene with 3D shapes may enable scene anti-aliasing to improve its 294 * rendering quality. 295 * <p> 296 * A Scene can be created and modified on any thread until it is attached to a {@code Window} that is showing 297 * ({@link javafx.stage.Window#isShowing()}. This does not mean the Scene is thread-safe, 298 * so manipulation from multiple threads at the same time is illegal, may lead to unexpected results and must be avoided. 299 * 300 * @param root The root node of the scene graph 301 * @param width The width of the scene 302 * @param height The height of the scene 303 * @param depthBuffer The depth buffer flag 304 * @param antiAliasing The scene anti-aliasing attribute. A value of 305 * {@code null} is treated as DISABLED. 306 * <p> 307 * The depthBuffer and antiAliasing are conditional features. With the 308 * respective default values of: false and {@code SceneAntialiasing.DISABLED}. See 309 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 310 * for more information. 311 * 312 * @throws NullPointerException if root is null 313 * 314 * @see javafx.scene.Node#setDepthTest(DepthTest) 315 * @since JavaFX 8.0 316 */ 317 public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height, 318 @NamedArg("depthBuffer") boolean depthBuffer, 319 @NamedArg(value="antiAliasing", defaultValue="DISABLED") SceneAntialiasing antiAliasing) { 320 this(root, width, height, Color.WHITE, depthBuffer, antiAliasing); 321 322 if (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED && 323 !Toolkit.getToolkit().isMSAASupported()) 324 { 325 String logname = Scene.class.getName(); 326 PlatformLogger.getLogger(logname).warning("System can't support " 327 + "antiAliasing"); 328 } 329 } 330 331 private Scene(Parent root, double width, double height, Paint fill, 332 boolean depthBuffer, SceneAntialiasing antiAliasing) { 333 this.depthBuffer = depthBuffer; 334 this.antiAliasing = antiAliasing; 335 if (root == null) { 336 throw new NullPointerException("Root cannot be null"); 337 } 338 339 if ((depthBuffer || (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED)) 340 && !Platform.isSupported(ConditionalFeature.SCENE3D)) { 341 String logname = Scene.class.getName(); 342 PlatformLogger.getLogger(logname).warning("System can't support " 343 + "ConditionalFeature.SCENE3D"); 344 } 345 346 init(); 347 setRoot(root); 348 init(width, height); 349 setFill(fill); 350 } 351 352 static { 353 PerformanceTracker.setSceneAccessor(new PerformanceTracker.SceneAccessor() { 354 public void setPerfTracker(Scene scene, PerformanceTracker tracker) { 355 synchronized (trackerMonitor) { 356 scene.tracker = tracker; 357 } 358 } 359 public PerformanceTracker getPerfTracker(Scene scene) { 360 synchronized (trackerMonitor) { 361 return scene.tracker; 362 } 363 } 364 }); 365 FXRobotHelper.setSceneAccessor(new FXRobotHelper.FXRobotSceneAccessor() { 366 public void processKeyEvent(Scene scene, KeyEvent keyEvent) { 367 scene.impl_processKeyEvent(keyEvent); 368 } 369 public void processMouseEvent(Scene scene, MouseEvent mouseEvent) { 370 scene.impl_processMouseEvent(mouseEvent); 371 } 372 public void processScrollEvent(Scene scene, ScrollEvent scrollEvent) { 373 scene.processGestureEvent(scrollEvent, scene.scrollGesture); 374 } 375 public ObservableList<Node> getChildren(Parent parent) { 376 return parent.getChildren(); //was impl_getChildren 377 } 378 public Object renderToImage(Scene scene, Object platformImage) { 379 return scene.snapshot(null).impl_getPlatformImage(); 380 } 381 }); 382 SceneHelper.setSceneAccessor( 383 new SceneHelper.SceneAccessor() { 384 @Override 385 public void setPaused(boolean paused) { 386 Scene.paused = paused; 387 } 388 389 @Override 390 public void parentEffectiveOrientationInvalidated( 391 final Scene scene) { 392 scene.parentEffectiveOrientationInvalidated(); 393 } 394 395 @Override 396 public Camera getEffectiveCamera(Scene scene) { 397 return scene.getEffectiveCamera(); 398 } 399 400 @Override 401 public Scene createPopupScene(Parent root) { 402 return new Scene(root) { 403 @Override 404 void doLayoutPass() { 405 resizeRootToPreferredSize(getRoot()); 406 super.doLayoutPass(); 407 } 408 409 @Override 410 void resizeRootOnSceneSizeChange( 411 double newWidth, 412 double newHeight) { 413 // don't resize 414 } 415 }; 416 } 417 418 @Override 419 public void setTransientFocusContainer(Scene scene, Node node) { 420 if (scene != null) { 421 scene.transientFocusContainer = node; 422 } 423 } 424 425 @Override 426 public Accessible getAccessible(Scene scene) { 427 return scene.getAccessible(); 428 } 429 }); 430 } 431 432 // Reserve space for 30 nodes in the dirtyNodes set. 433 private static final int MIN_DIRTY_CAPACITY = 30; 434 435 // For debugging 436 private static boolean inSynchronizer = false; 437 private static boolean inMousePick = false; 438 private static boolean allowPGAccess = false; 439 private static int pgAccessCount = 0; 440 441 // Flag set by the Toolkit when we are paused for JMX debugging 442 private static boolean paused = false; 443 444 /** 445 * Used for debugging purposes. Returns true if we are in either the 446 * mouse event code (picking) or the synchronizer, or if the scene is 447 * not yet initialized, 448 * 449 */ 450 static boolean isPGAccessAllowed() { 451 return inSynchronizer || inMousePick || allowPGAccess; 452 } 453 454 /** 455 * @treatAsPrivate implementation detail 456 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 457 */ 458 @Deprecated 459 public static void impl_setAllowPGAccess(boolean flag) { 460 if (Utils.assertionEnabled()) { 461 if (flag) { 462 pgAccessCount++; 463 allowPGAccess = true; 464 } 465 else { 466 if (pgAccessCount <= 0) { 467 throw new java.lang.AssertionError("*** pgAccessCount underflow"); 468 } 469 if (--pgAccessCount == 0) { 470 allowPGAccess = false; 471 } 472 } 473 } 474 } 475 476 /** 477 * If true, use the platform's drag gesture detection 478 * else use Scene-level detection as per DnDGesture.process(MouseEvent, List) 479 */ 480 private static final boolean PLATFORM_DRAG_GESTURE_INITIATION = false; 481 482 /** 483 * Set of dirty nodes; processed once per frame by the synchronizer. 484 * When a node's state changes such that it becomes "dirty" with respect 485 * to the graphics stack and requires synchronization, then that node 486 * is added to this list. Note that if state on the Node changes, but it 487 * was already dirty, then the Node doesn't add itself again. 488 * <p> 489 * Because at initialization time every node in the scene graph is dirty, 490 * we have a special state and special code path during initialization 491 * that does not involve adding each node to the dirtyNodes list. When 492 * dirtyNodes is null, that means this Scene has not yet been synchronized. 493 * A good default size is then created for the dirtyNodes list. 494 * <p> 495 * We double-buffer the set so that we can add new nodes to the 496 * set while processing the existing set. This avoids our having to 497 * take a snapshot of the set (e.g., with toArray()) and reduces garbage. 498 */ 499 private Node[] dirtyNodes; 500 private int dirtyNodesSize; 501 502 /** 503 * Add the specified node to this scene's dirty list. Called by the 504 * markDirty method in Node or when the Node's scene changes. 505 */ 506 void addToDirtyList(Node n) { 507 if (dirtyNodes == null || dirtyNodesSize == 0) { 508 if (impl_peer != null) { 509 Toolkit.getToolkit().requestNextPulse(); 510 } 511 } 512 513 if (dirtyNodes != null) { 514 if (dirtyNodesSize == dirtyNodes.length) { 515 Node[] tmp = new Node[dirtyNodesSize + (dirtyNodesSize >> 1)]; 516 System.arraycopy(dirtyNodes, 0, tmp, 0, dirtyNodesSize); 517 dirtyNodes = tmp; 518 } 519 dirtyNodes[dirtyNodesSize++] = n; 520 } 521 } 522 523 private void doCSSPass() { 524 final Parent sceneRoot = getRoot(); 525 // 526 // RT-17547: when the tree is synchronized, the dirty bits are 527 // are cleared but the cssFlag might still be something other than 528 // clean. 529 // 530 // Before RT-17547, the code checked the dirty bit. But this is 531 // superfluous since the dirty bit will be set if the flag is not clean, 532 // but the flag will never be anything other than clean if the dirty 533 // bit is not set. The dirty bit is still needed, however, since setting 534 // it ensures a pulse if no other dirty bits have been set. 535 // 536 // For the purpose of showing the change, the dirty bit 537 // check code was commented out and not removed. 538 // 539 // if (sceneRoot.impl_isDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS)) { 540 if (sceneRoot.cssFlag != CssFlags.CLEAN) { 541 // The dirty bit isn't checked but we must ensure it is cleared. 542 // The cssFlag is set to clean in either Node.processCSS or 543 // Node.impl_processCSS(boolean) 544 sceneRoot.impl_clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS); 545 sceneRoot.processCSS(); 546 } 547 } 548 549 void doLayoutPass() { 550 final Parent r = getRoot(); 551 if (r != null) { 552 r.layout(); 553 } 554 } 555 556 /** 557 * The peer of this scene 558 * 559 * @treatAsPrivate implementation detail 560 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 561 */ 562 @Deprecated 563 private TKScene impl_peer; 564 565 /** 566 * Get Scene's peer 567 * 568 * @treatAsPrivate implementation detail 569 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 570 */ 571 @Deprecated 572 public TKScene impl_getPeer() { 573 return impl_peer; 574 } 575 576 /** 577 * The scene pulse listener that gets called on toolkit pulses 578 */ 579 ScenePulseListener scenePulseListener = new ScenePulseListener(); 580 581 /** 582 * @treatAsPrivate implementation detail 583 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 584 */ 585 @Deprecated 586 public TKPulseListener impl_getScenePulseListener() { 587 if (SystemProperties.isDebug()) { 588 return scenePulseListener; 589 } 590 return null; 591 } 592 593 /** 594 * Return the defined {@code SceneAntialiasing} for this {@code Scene}. 595 * <p> 596 * Note: this is a conditional feature. See 597 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 598 * and {@link javafx.scene.SceneAntialiasing SceneAntialiasing} 599 * for more information. 600 * @since JavaFX 8.0 601 */ 602 public final SceneAntialiasing getAntiAliasing() { 603 return antiAliasing; 604 } 605 606 private boolean getAntiAliasingInternal() { 607 return (antiAliasing != null && 608 Toolkit.getToolkit().isMSAASupported() && 609 Platform.isSupported(ConditionalFeature.SCENE3D)) ? 610 antiAliasing != SceneAntialiasing.DISABLED : false; 611 } 612 613 /** 614 * The {@code Window} for this {@code Scene} 615 */ 616 private ReadOnlyObjectWrapper<Window> window; 617 618 private void setWindow(Window value) { 619 windowPropertyImpl().set(value); 620 } 621 622 public final Window getWindow() { 623 return window == null ? null : window.get(); 624 } 625 626 public final ReadOnlyObjectProperty<Window> windowProperty() { 627 return windowPropertyImpl().getReadOnlyProperty(); 628 } 629 630 private ReadOnlyObjectWrapper<Window> windowPropertyImpl() { 631 if (window == null) { 632 window = new ReadOnlyObjectWrapper<Window>() { 633 private Window oldWindow; 634 635 @Override protected void invalidated() { 636 final Window newWindow = get(); 637 getKeyHandler().windowForSceneChanged(oldWindow, newWindow); 638 if (oldWindow != null) { 639 impl_disposePeer(); 640 } 641 if (newWindow != null) { 642 impl_initPeer(); 643 } 644 parentEffectiveOrientationInvalidated(); 645 646 oldWindow = newWindow; 647 } 648 649 @Override 650 public Object getBean() { 651 return Scene.this; 652 } 653 654 @Override 655 public String getName() { 656 return "window"; 657 } 658 }; 659 } 660 return window; 661 } 662 663 /** 664 * @treatAsPrivate implementation detail 665 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 666 */ 667 @Deprecated 668 public void impl_setWindow(Window value) { 669 setWindow(value); 670 } 671 672 /** 673 * @treatAsPrivate implementation detail 674 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 675 */ 676 @Deprecated 677 public void impl_initPeer() { 678 assert impl_peer == null; 679 680 Window window = getWindow(); 681 // impl_initPeer() is only called from Window, either when the window 682 // is being shown, or the window scene is being changed. In any case 683 // this scene's window cannot be null. 684 assert window != null; 685 686 TKStage windowPeer = window.impl_getPeer(); 687 if (windowPeer == null) { 688 // This is fine, the window is not visible. impl_initPeer() will 689 // be called again later, when the window is being shown. 690 return; 691 } 692 693 final boolean isTransparentWindowsSupported = Platform.isSupported(ConditionalFeature.TRANSPARENT_WINDOW); 694 if (!isTransparentWindowsSupported) { 695 PlatformImpl.addNoTransparencyStylesheetToScene(this); 696 } 697 698 PerformanceTracker.logEvent("Scene.initPeer started"); 699 700 impl_setAllowPGAccess(true); 701 702 Toolkit tk = Toolkit.getToolkit(); 703 impl_peer = windowPeer.createTKScene(isDepthBufferInternal(), getAntiAliasingInternal(), acc); 704 PerformanceTracker.logEvent("Scene.initPeer TKScene created"); 705 impl_peer.setTKSceneListener(new ScenePeerListener()); 706 impl_peer.setTKScenePaintListener(new ScenePeerPaintListener()); 707 PerformanceTracker.logEvent("Scene.initPeer TKScene set"); 708 impl_peer.setRoot(getRoot().impl_getPeer()); 709 impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill())); 710 getEffectiveCamera().impl_updatePeer(); 711 impl_peer.setCamera((NGCamera) getEffectiveCamera().impl_getPeer()); 712 impl_peer.markDirty(); 713 PerformanceTracker.logEvent("Scene.initPeer TKScene initialized"); 714 715 impl_setAllowPGAccess(false); 716 717 tk.addSceneTkPulseListener(scenePulseListener); 718 // listen to dnd gestures coming from the platform 719 if (PLATFORM_DRAG_GESTURE_INITIATION) { 720 if (dragGestureListener == null) { 721 dragGestureListener = new DragGestureListener(); 722 } 723 tk.registerDragGestureListener(impl_peer, EnumSet.allOf(TransferMode.class), dragGestureListener); 724 } 725 tk.enableDrop(impl_peer, new DropTargetListener()); 726 tk.installInputMethodRequests(impl_peer, new InputMethodRequestsDelegate()); 727 728 PerformanceTracker.logEvent("Scene.initPeer finished"); 729 } 730 731 /** 732 * @treatAsPrivate implementation detail 733 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 734 */ 735 @Deprecated 736 public void impl_disposePeer() { 737 if (impl_peer == null) { 738 // This is fine, the window is either not shown yet and there is no 739 // need in disposing scene peer, or is hidden and impl_disposePeer() 740 // has already been called. 741 return; 742 } 743 744 PerformanceTracker.logEvent("Scene.disposePeer started"); 745 746 Toolkit tk = Toolkit.getToolkit(); 747 tk.removeSceneTkPulseListener(scenePulseListener); 748 if (accessible != null) { 749 disposeAccessibles(); 750 Node root = getRoot(); 751 if (root != null) root.releaseAccessible(); 752 accessible.dispose(); 753 accessible = null; 754 } 755 impl_peer.dispose(); 756 impl_peer = null; 757 758 PerformanceTracker.logEvent("Scene.disposePeer finished"); 759 } 760 761 DnDGesture dndGesture = null; 762 DragGestureListener dragGestureListener; 763 /** 764 * The horizontal location of this {@code Scene} on the {@code Window}. 765 */ 766 private ReadOnlyDoubleWrapper x; 767 768 private final void setX(double value) { 769 xPropertyImpl().set(value); 770 } 771 772 public final double getX() { 773 return x == null ? 0.0 : x.get(); 774 } 775 776 public final ReadOnlyDoubleProperty xProperty() { 777 return xPropertyImpl().getReadOnlyProperty(); 778 } 779 780 private ReadOnlyDoubleWrapper xPropertyImpl() { 781 if (x == null) { 782 x = new ReadOnlyDoubleWrapper(this, "x"); 783 } 784 return x; 785 } 786 787 /** 788 * The vertical location of this {@code Scene} on the {@code Window}. 789 */ 790 private ReadOnlyDoubleWrapper y; 791 792 private final void setY(double value) { 793 yPropertyImpl().set(value); 794 } 795 796 public final double getY() { 797 return y == null ? 0.0 : y.get(); 798 } 799 800 public final ReadOnlyDoubleProperty yProperty() { 801 return yPropertyImpl().getReadOnlyProperty(); 802 } 803 804 private ReadOnlyDoubleWrapper yPropertyImpl() { 805 if (y == null) { 806 y = new ReadOnlyDoubleWrapper(this, "y"); 807 } 808 return y; 809 } 810 811 /** 812 * The width of this {@code Scene} 813 */ 814 private ReadOnlyDoubleWrapper width; 815 816 private final void setWidth(double value) { 817 widthPropertyImpl().set(value); 818 } 819 820 public final double getWidth() { 821 return width == null ? 0.0 : width.get(); 822 } 823 824 public final ReadOnlyDoubleProperty widthProperty() { 825 return widthPropertyImpl().getReadOnlyProperty(); 826 } 827 828 private ReadOnlyDoubleWrapper widthPropertyImpl() { 829 if (width == null) { 830 width = new ReadOnlyDoubleWrapper() { 831 832 @Override 833 protected void invalidated() { 834 final Parent _root = getRoot(); 835 //TODO - use a better method to update mirroring 836 if (_root.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { 837 _root.impl_transformsChanged(); 838 } 839 if (_root.isResizable()) { 840 resizeRootOnSceneSizeChange(get() - _root.getLayoutX() - _root.getTranslateX(), _root.getLayoutBounds().getHeight()); 841 } 842 843 getEffectiveCamera().setViewWidth(get()); 844 } 845 846 @Override 847 public Object getBean() { 848 return Scene.this; 849 } 850 851 @Override 852 public String getName() { 853 return "width"; 854 } 855 }; 856 } 857 return width; 858 } 859 860 /** 861 * The height of this {@code Scene} 862 */ 863 private ReadOnlyDoubleWrapper height; 864 865 private final void setHeight(double value) { 866 heightPropertyImpl().set(value); 867 } 868 869 public final double getHeight() { 870 return height == null ? 0.0 : height.get(); 871 } 872 873 public final ReadOnlyDoubleProperty heightProperty() { 874 return heightPropertyImpl().getReadOnlyProperty(); 875 } 876 877 private ReadOnlyDoubleWrapper heightPropertyImpl() { 878 if (height == null) { 879 height = new ReadOnlyDoubleWrapper() { 880 881 @Override 882 protected void invalidated() { 883 final Parent _root = getRoot(); 884 if (_root.isResizable()) { 885 resizeRootOnSceneSizeChange(_root.getLayoutBounds().getWidth(), get() - _root.getLayoutY() - _root.getTranslateY()); 886 } 887 888 getEffectiveCamera().setViewHeight(get()); 889 } 890 891 @Override 892 public Object getBean() { 893 return Scene.this; 894 } 895 896 @Override 897 public String getName() { 898 return "height"; 899 } 900 }; 901 } 902 return height; 903 } 904 905 void resizeRootOnSceneSizeChange(double newWidth, double newHeight) { 906 getRoot().resize(newWidth, newHeight); 907 } 908 909 // Reusable target wrapper (to avoid creating new one for each picking) 910 private TargetWrapper tmpTargetWrapper = new TargetWrapper(); 911 912 /** 913 * Specifies the type of camera use for rendering this {@code Scene}. 914 * If {@code camera} is null, a parallel camera is used for rendering. 915 * It is illegal to set a camera that belongs to other {@code Scene} 916 * or {@code SubScene}. 917 * <p> 918 * Note: this is a conditional feature. See 919 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 920 * for more information. 921 * 922 * @defaultValue null 923 */ 924 private ObjectProperty<Camera> camera; 925 926 public final void setCamera(Camera value) { 927 cameraProperty().set(value); 928 } 929 930 public final Camera getCamera() { 931 return camera == null ? null : camera.get(); 932 } 933 934 public final ObjectProperty<Camera> cameraProperty() { 935 if (camera == null) { 936 camera = new ObjectPropertyBase<Camera>() { 937 Camera oldCamera = null; 938 939 @Override 940 protected void invalidated() { 941 Camera _value = get(); 942 if (_value != null) { 943 if (_value instanceof PerspectiveCamera 944 && !Platform.isSupported(ConditionalFeature.SCENE3D)) { 945 String logname = Scene.class.getName(); 946 PlatformLogger.getLogger(logname).warning("System can't support " 947 + "ConditionalFeature.SCENE3D"); 948 } 949 // Illegal value if it belongs to other scene or any subscene 950 if ((_value.getScene() != null && _value.getScene() != Scene.this) 951 || _value.getSubScene() != null) { 952 throw new IllegalArgumentException(_value 953 + "is already part of other scene or subscene"); 954 } 955 // throws exception if the camera already has a different owner 956 _value.setOwnerScene(Scene.this); 957 _value.setViewWidth(getWidth()); 958 _value.setViewHeight(getHeight()); 959 } 960 if (oldCamera != null && oldCamera != _value) { 961 oldCamera.setOwnerScene(null); 962 } 963 oldCamera = _value; 964 } 965 966 @Override 967 public Object getBean() { 968 return Scene.this; 969 } 970 971 @Override 972 public String getName() { 973 return "camera"; 974 } 975 }; 976 } 977 return camera; 978 } 979 980 Camera getEffectiveCamera() { 981 final Camera cam = getCamera(); 982 if (cam == null 983 || (cam instanceof PerspectiveCamera 984 && !Platform.isSupported(ConditionalFeature.SCENE3D))) { 985 if (defaultCamera == null) { 986 defaultCamera = new ParallelCamera(); 987 defaultCamera.setOwnerScene(this); 988 defaultCamera.setViewWidth(getWidth()); 989 defaultCamera.setViewHeight(getHeight()); 990 } 991 return defaultCamera; 992 } 993 994 return cam; 995 } 996 997 // Used by the camera 998 void markCameraDirty() { 999 markDirty(DirtyBits.CAMERA_DIRTY); 1000 setNeedsRepaint(); 1001 } 1002 1003 void markCursorDirty() { 1004 markDirty(DirtyBits.CURSOR_DIRTY); 1005 } 1006 1007 /** 1008 * Defines the background fill of this {@code Scene}. Both a {@code null} 1009 * value meaning 'paint no background' and a {@link javafx.scene.paint.Paint} 1010 * with transparency are supported. The default fill of the Scene is 1011 * {@link Color#WHITE}, but it is more commonly the case that the initial 1012 * color shown to users is the background fill of the 1013 * {@link #rootProperty() root node} of the {@code Scene}, as it typically is 1014 * stretched to take up all available space in the {@code Scene}. The 1015 * root node of the {@code Scene} is given the CSS style class 'root', and 1016 * the default user agent stylesheets that ship with JavaFX (presently 1017 * Caspian and Modena) apply styling on to this root style class. In the 1018 * case of Caspian this does not impact the background fill color of the 1019 * root node, but in the case of Modena the default fill is set to be a 1020 * light gray color. 1021 * 1022 * @defaultValue WHITE 1023 */ 1024 private ObjectProperty<Paint> fill; 1025 1026 public final void setFill(Paint value) { 1027 fillProperty().set(value); 1028 } 1029 1030 public final Paint getFill() { 1031 return fill == null ? Color.WHITE : fill.get(); 1032 } 1033 1034 public final ObjectProperty<Paint> fillProperty() { 1035 if (fill == null) { 1036 fill = new ObjectPropertyBase<Paint>(Color.WHITE) { 1037 1038 @Override 1039 protected void invalidated() { 1040 markDirty(DirtyBits.FILL_DIRTY); 1041 } 1042 1043 @Override 1044 public Object getBean() { 1045 return Scene.this; 1046 } 1047 1048 @Override 1049 public String getName() { 1050 return "fill"; 1051 } 1052 }; 1053 } 1054 return fill; 1055 } 1056 1057 /** 1058 * Defines the root {@code Node} of the scene graph. 1059 * If a {@code Group} is used as the root, the 1060 * contents of the scene graph will be clipped by the scene's width and height and 1061 * changes to the scene's size (if user resizes the stage) will not alter the 1062 * layout of the scene graph. If a resizable node (layout {@code Region} or 1063 * {@code Control}) is set as the root, then the root's size will track the 1064 * scene's size, causing the contents to be relayed out as necessary. 1065 * 1066 * Scene doesn't accept null root. 1067 * 1068 */ 1069 private ObjectProperty<Parent> root; 1070 1071 public final void setRoot(Parent value) { 1072 rootProperty().set(value); 1073 } 1074 1075 public final Parent getRoot() { 1076 return root == null ? null : root.get(); 1077 } 1078 1079 Parent oldRoot; 1080 public final ObjectProperty<Parent> rootProperty() { 1081 if (root == null) { 1082 root = new ObjectPropertyBase<Parent>() { 1083 1084 private void forceUnbind() { 1085 System.err.println("Unbinding illegal root."); 1086 unbind(); 1087 } 1088 1089 @Override 1090 protected void invalidated() { 1091 Parent _value = get(); 1092 1093 if (_value == null) { 1094 if (isBound()) forceUnbind(); 1095 throw new NullPointerException("Scene's root cannot be null"); 1096 } 1097 1098 if (_value.getParent() != null) { 1099 if (isBound()) forceUnbind(); 1100 throw new IllegalArgumentException(_value + 1101 "is already inside a scene-graph and cannot be set as root"); 1102 } 1103 if (_value.getClipParent() != null) { 1104 if (isBound()) forceUnbind(); 1105 throw new IllegalArgumentException(_value + 1106 "is set as a clip on another node, so cannot be set as root"); 1107 } 1108 if (_value.getScene() != null && _value.getScene().getRoot() == _value && _value.getScene() != Scene.this) { 1109 if (isBound()) forceUnbind(); 1110 throw new IllegalArgumentException(_value + 1111 "is already set as root of another scene"); 1112 } 1113 1114 if (oldRoot != null) { 1115 oldRoot.setScenes(null, null); 1116 } 1117 oldRoot = _value; 1118 _value.getStyleClass().add(0, "root"); 1119 _value.setScenes(Scene.this, null); 1120 markDirty(DirtyBits.ROOT_DIRTY); 1121 _value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable 1122 _value.requestLayout(); 1123 } 1124 1125 @Override 1126 public Object getBean() { 1127 return Scene.this; 1128 } 1129 1130 @Override 1131 public String getName() { 1132 return "root"; 1133 } 1134 }; 1135 } 1136 return root; 1137 } 1138 1139 void setNeedsRepaint() { 1140 if (this.impl_peer != null) { 1141 impl_peer.entireSceneNeedsRepaint(); 1142 } 1143 } 1144 1145 // Process CSS and layout and sync the scene prior to the snapshot 1146 // operation of the given node for this scene (currently the node 1147 // is unused but could possibly be used in the future to optimize this) 1148 void doCSSLayoutSyncForSnapshot(Node node) { 1149 if (!sizeInitialized) { 1150 preferredSize(); 1151 } else { 1152 doCSSPass(); 1153 } 1154 1155 // we do not need pulse in the snapshot code 1156 // because this scene can be stage-less 1157 doLayoutPass(); 1158 1159 if (!paused) { 1160 getRoot().updateBounds(); 1161 if (impl_peer != null) { 1162 impl_peer.waitForRenderingToComplete(); 1163 impl_peer.waitForSynchronization(); 1164 try { 1165 // Run the synchronizer while holding the render lock 1166 scenePulseListener.synchronizeSceneNodes(); 1167 } finally { 1168 impl_peer.releaseSynchronization(false); 1169 } 1170 } else { 1171 scenePulseListener.synchronizeSceneNodes(); 1172 } 1173 } 1174 1175 } 1176 1177 // Shared method for Scene.snapshot and Node.snapshot. It is static because 1178 // we might be doing a Node snapshot with a null scene 1179 static WritableImage doSnapshot(Scene scene, 1180 double x, double y, double w, double h, 1181 Node root, BaseTransform transform, boolean depthBuffer, 1182 Paint fill, Camera camera, WritableImage wimg) { 1183 1184 Toolkit tk = Toolkit.getToolkit(); 1185 Toolkit.ImageRenderingContext context = new Toolkit.ImageRenderingContext(); 1186 1187 int xMin = (int)Math.floor(x); 1188 int yMin = (int)Math.floor(y); 1189 int xMax = (int)Math.ceil(x + w); 1190 int yMax = (int)Math.ceil(y + h); 1191 int width = Math.max(xMax - xMin, 1); 1192 int height = Math.max(yMax - yMin, 1); 1193 if (wimg == null) { 1194 wimg = new WritableImage(width, height); 1195 } else { 1196 width = (int)wimg.getWidth(); 1197 height = (int)wimg.getHeight(); 1198 } 1199 1200 impl_setAllowPGAccess(true); 1201 context.x = xMin; 1202 context.y = yMin; 1203 context.width = width; 1204 context.height = height; 1205 context.transform = transform; 1206 context.depthBuffer = depthBuffer; 1207 context.root = root.impl_getPeer(); 1208 context.platformPaint = fill == null ? null : tk.getPaint(fill); 1209 double cameraViewWidth = 1.0; 1210 double cameraViewHeight = 1.0; 1211 if (camera != null) { 1212 // temporarily adjust camera viewport to the snapshot size 1213 cameraViewWidth = camera.getViewWidth(); 1214 cameraViewHeight = camera.getViewHeight(); 1215 camera.setViewWidth(width); 1216 camera.setViewHeight(height); 1217 camera.impl_updatePeer(); 1218 context.camera = camera.impl_getPeer(); 1219 } else { 1220 context.camera = null; 1221 } 1222 1223 // Grab the lights from the scene 1224 context.lights = null; 1225 if (scene != null && !scene.lights.isEmpty()) { 1226 context.lights = new NGLightBase[scene.lights.size()]; 1227 for (int i = 0; i < scene.lights.size(); i++) { 1228 context.lights[i] = scene.lights.get(i).impl_getPeer(); 1229 } 1230 } 1231 1232 Toolkit.WritableImageAccessor accessor = Toolkit.getWritableImageAccessor(); 1233 context.platformImage = accessor.getTkImageLoader(wimg); 1234 impl_setAllowPGAccess(false); 1235 Object tkImage = tk.renderToImage(context); 1236 accessor.loadTkImage(wimg, tkImage); 1237 1238 if (camera != null) { 1239 impl_setAllowPGAccess(true); 1240 camera.setViewWidth(cameraViewWidth); 1241 camera.setViewHeight(cameraViewHeight); 1242 camera.impl_updatePeer(); 1243 impl_setAllowPGAccess(false); 1244 } 1245 1246 // if this scene belongs to some stage 1247 // we need to mark the entire scene as dirty 1248 // because dirty logic is buggy 1249 if (scene != null && scene.impl_peer != null) { 1250 scene.setNeedsRepaint(); 1251 } 1252 1253 return wimg; 1254 } 1255 1256 /** 1257 * Implementation method for snapshot 1258 */ 1259 private WritableImage doSnapshot(WritableImage img) { 1260 // TODO: no need to do CSS, layout or sync in the deferred case, 1261 // if this scene is attached to a visible stage 1262 doCSSLayoutSyncForSnapshot(getRoot()); 1263 1264 double w = getWidth(); 1265 double h = getHeight(); 1266 BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM; 1267 1268 return doSnapshot(this, 0, 0, w, h, 1269 getRoot(), transform, isDepthBufferInternal(), 1270 getFill(), getEffectiveCamera(), img); 1271 } 1272 1273 // Pulse listener used to run all deferred (async) snapshot requests 1274 private static TKPulseListener snapshotPulseListener = null; 1275 1276 private static List<Runnable> snapshotRunnableListA; 1277 private static List<Runnable> snapshotRunnableListB; 1278 private static List<Runnable> snapshotRunnableList; 1279 1280 static void addSnapshotRunnable(final Runnable runnable) { 1281 Toolkit.getToolkit().checkFxUserThread(); 1282 1283 if (snapshotPulseListener == null) { 1284 snapshotRunnableListA = new ArrayList<Runnable>(); 1285 snapshotRunnableListB = new ArrayList<Runnable>(); 1286 snapshotRunnableList = snapshotRunnableListA; 1287 1288 snapshotPulseListener = () -> { 1289 if (snapshotRunnableList.size() > 0) { 1290 List<Runnable> runnables = snapshotRunnableList; 1291 if (snapshotRunnableList == snapshotRunnableListA) { 1292 snapshotRunnableList = snapshotRunnableListB; 1293 } else { 1294 snapshotRunnableList = snapshotRunnableListA; 1295 } 1296 for (Runnable r : runnables) { 1297 try { 1298 r.run(); 1299 } catch (Throwable th) { 1300 System.err.println("Exception in snapshot runnable"); 1301 th.printStackTrace(System.err); 1302 } 1303 } 1304 runnables.clear(); 1305 } 1306 }; 1307 1308 // Add listener that will be called after all of the scenes have 1309 // had layout and CSS processing, and have been synced 1310 Toolkit.getToolkit().addPostSceneTkPulseListener(snapshotPulseListener); 1311 } 1312 1313 final AccessControlContext acc = AccessController.getContext(); 1314 snapshotRunnableList.add(() -> { 1315 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 1316 runnable.run(); 1317 return null; 1318 }, acc); 1319 }); 1320 Toolkit.getToolkit().requestNextPulse(); 1321 } 1322 1323 /** 1324 * Takes a snapshot of this scene and returns the rendered image when 1325 * it is ready. 1326 * CSS and layout processing will be done for the scene prior to 1327 * rendering it. 1328 * The entire destination image is cleared using the fill {@code Paint} 1329 * of this scene. The nodes in the scene are then rendered to the image. 1330 * The point (0,0) in scene coordinates is mapped to (0,0) in the image. 1331 * If the image is smaller than the size of the scene, then the rendering 1332 * will be clipped by the image. 1333 * 1334 * <p> 1335 * When taking a snapshot of a scene that is being animated, either 1336 * explicitly by the application or implicitly (such as chart animation), 1337 * the snapshot will be rendered based on the state of the scene graph at 1338 * the moment the snapshot is taken and will not reflect any subsequent 1339 * animation changes. 1340 * </p> 1341 * 1342 * @param image the writable image that will be used to hold the rendered scene. 1343 * It may be null in which case a new WritableImage will be constructed. 1344 * If the image is non-null, the scene will be rendered into the 1345 * existing image. 1346 * In this case, the width and height of the image determine the area 1347 * that is rendered instead of the width and height of the scene. 1348 * 1349 * @throws IllegalStateException if this method is called on a thread 1350 * other than the JavaFX Application Thread. 1351 * 1352 * @return the rendered image 1353 * @since JavaFX 2.2 1354 */ 1355 public WritableImage snapshot(WritableImage image) { 1356 if (!paused) { 1357 Toolkit.getToolkit().checkFxUserThread(); 1358 } 1359 1360 return doSnapshot(image); 1361 } 1362 1363 /** 1364 * Takes a snapshot of this scene at the next frame and calls the 1365 * specified callback method when the image is ready. 1366 * CSS and layout processing will be done for the scene prior to 1367 * rendering it. 1368 * The entire destination image is cleared using the fill {@code Paint} 1369 * of this scene. The nodes in the scene are then rendered to the image. 1370 * The point (0,0) in scene coordinates is mapped to (0,0) in the image. 1371 * If the image is smaller than the size of the scene, then the rendering 1372 * will be clipped by the image. 1373 * 1374 * <p> 1375 * This is an asynchronous call, which means that other 1376 * events or animation might be processed before the scene is rendered. 1377 * If any such events modify a node in the scene that modification will 1378 * be reflected in the rendered image (as it will also be reflected in 1379 * the frame rendered to the Stage). 1380 * </p> 1381 * 1382 * <p> 1383 * When taking a snapshot of a scene that is being animated, either 1384 * explicitly by the application or implicitly (such as chart animation), 1385 * the snapshot will be rendered based on the state of the scene graph at 1386 * the moment the snapshot is taken and will not reflect any subsequent 1387 * animation changes. 1388 * </p> 1389 * 1390 * @param callback a class whose call method will be called when the image 1391 * is ready. The SnapshotResult that is passed into the call method of 1392 * the callback will contain the rendered image and the source scene 1393 * that was rendered. The callback parameter must not be null. 1394 * 1395 * @param image the writable image that will be used to hold the rendered scene. 1396 * It may be null in which case a new WritableImage will be constructed. 1397 * If the image is non-null, the scene will be rendered into the 1398 * existing image. 1399 * In this case, the width and height of the image determine the area 1400 * that is rendered instead of the width and height of the scene. 1401 * 1402 * @throws IllegalStateException if this method is called on a thread 1403 * other than the JavaFX Application Thread. 1404 * 1405 * @throws NullPointerException if the callback parameter is null. 1406 * @since JavaFX 2.2 1407 */ 1408 public void snapshot(Callback<SnapshotResult, Void> callback, WritableImage image) { 1409 Toolkit.getToolkit().checkFxUserThread(); 1410 if (callback == null) { 1411 throw new NullPointerException("The callback must not be null"); 1412 } 1413 1414 final Callback<SnapshotResult, Void> theCallback = callback; 1415 final WritableImage theImage = image; 1416 1417 // Create a deferred runnable that will be run from a pulse listener 1418 // that is called after all of the scenes have been synced but before 1419 // any of them have been rendered. 1420 final Runnable snapshotRunnable = () -> { 1421 WritableImage img = doSnapshot(theImage); 1422 // System.err.println("Calling snapshot callback"); 1423 SnapshotResult result = new SnapshotResult(img, Scene.this, null); 1424 try { 1425 Void v = theCallback.call(result); 1426 } catch (Throwable th) { 1427 System.err.println("Exception in snapshot callback"); 1428 th.printStackTrace(System.err); 1429 } 1430 }; 1431 // System.err.println("Schedule a snapshot in the future"); 1432 addSnapshotRunnable(snapshotRunnable); 1433 } 1434 1435 /** 1436 * Defines the mouse cursor for this {@code Scene}. 1437 */ 1438 private ObjectProperty<Cursor> cursor; 1439 1440 public final void setCursor(Cursor value) { 1441 cursorProperty().set(value); 1442 } 1443 1444 public final Cursor getCursor() { 1445 return cursor == null ? null : cursor.get(); 1446 } 1447 1448 public final ObjectProperty<Cursor> cursorProperty() { 1449 if (cursor == null) { 1450 cursor = new ObjectPropertyBase<Cursor>() { 1451 @Override 1452 protected void invalidated() { 1453 markCursorDirty(); 1454 } 1455 1456 @Override 1457 public Object getBean() { 1458 return Scene.this; 1459 } 1460 1461 @Override 1462 public String getName() { 1463 return "cursor"; 1464 } 1465 }; 1466 } 1467 return cursor; 1468 } 1469 1470 /** 1471 * Looks for any node within the scene graph based on the specified CSS selector. 1472 * If more than one node matches the specified selector, this function 1473 * returns the first of them. 1474 * If no nodes are found with this id, then null is returned. 1475 * 1476 * @param selector The css selector to look up 1477 * @return the {@code Node} in the scene which matches the CSS {@code selector}, 1478 * or {@code null} if none is found. 1479 */ 1480 public Node lookup(String selector) { 1481 return getRoot().lookup(selector); 1482 } 1483 /** 1484 * A ObservableList of string URLs linking to the stylesheets to use with this scene's 1485 * contents. For additional information about using CSS with the 1486 * scene graph, see the <a href="doc-files/cssref.html">CSS Reference 1487 * Guide</a>. 1488 */ 1489 private final ObservableList<String> stylesheets = new TrackableObservableList<String>() { 1490 @Override 1491 protected void onChanged(Change<String> c) { 1492 StyleManager.getInstance().stylesheetsChanged(Scene.this, c); 1493 // RT-9784 - if stylesheet is removed, reset styled properties to 1494 // their initial value. 1495 c.reset(); 1496 while(c.next()) { 1497 if (c.wasRemoved() == false) { 1498 continue; 1499 } 1500 break; // no point in resetting more than once... 1501 } 1502 getRoot().impl_reapplyCSS(); 1503 } 1504 }; 1505 1506 /** 1507 * Gets an observable list of string URLs linking to the stylesheets to use 1508 * with this scene's contents. 1509 * <p> 1510 * The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL 1511 * does not have a [scheme:] component, the URL is considered to be the [path] component only. 1512 * Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to 1513 * the root of the application's classpath. 1514 * </p> 1515 * <code><pre> 1516 * 1517 * package com.example.javafx.app; 1518 * 1519 * import javafx.application.Application; 1520 * import javafx.scene.Group; 1521 * import javafx.scene.Scene; 1522 * import javafx.stage.Stage; 1523 * 1524 * public class MyApp extends Application { 1525 * 1526 * {@literal @}Override public void start(Stage stage) { 1527 * Scene scene = new Scene(new Group()); 1528 * scene.getStylesheets().add("/com/example/javafx/app/mystyles.css"); 1529 * stage.setScene(scene); 1530 * stage.show(); 1531 * } 1532 * 1533 * public static void main(String[] args) { 1534 * launch(args); 1535 * } 1536 * } 1537 * </pre></code> 1538 * For additional information about using CSS with the scene graph, 1539 * see the <a href="doc-files/cssref.html">CSS Reference Guide</a>. 1540 * 1541 * @return the list of stylesheets to use with this scene 1542 */ 1543 public final ObservableList<String> getStylesheets() { return stylesheets; } 1544 1545 private ObjectProperty<String> userAgentStylesheet = null; 1546 1547 /** 1548 * @return the userAgentStylesheet property. 1549 * @see #getUserAgentStylesheet() 1550 * @see #setUserAgentStylesheet(String) 1551 * @since JavaFX 8u20 1552 */ 1553 public final ObjectProperty<String> userAgentStylesheetProperty() { 1554 if (userAgentStylesheet == null) { 1555 userAgentStylesheet = new SimpleObjectProperty<String>(Scene.this, "userAgentStylesheet", null) { 1556 @Override protected void invalidated() { 1557 StyleManager.getInstance().forget(Scene.this); 1558 getRoot().impl_reapplyCSS(); 1559 } 1560 }; 1561 } 1562 return userAgentStylesheet; 1563 } 1564 1565 /** 1566 * Get the URL of the user-agent stylesheet that will be used by this Scene. If the URL has not been set, 1567 * the platform-default user-agent stylesheet will be used. 1568 * <p> 1569 * For additional information about using CSS with the scene graph, 1570 * see the <a href="doc-files/cssref.html">CSS Reference Guide</a>. 1571 * </p> 1572 * @return The URL of the user-agent stylesheet that will be used by this Scene, 1573 * or null if has not been set. 1574 * @since JavaFX 8u20 1575 */ 1576 public final String getUserAgentStylesheet() { 1577 return userAgentStylesheet == null ? null : userAgentStylesheet.get(); 1578 } 1579 1580 /** 1581 * Set the URL of the user-agent stylesheet that will be used by this Scene in place of the 1582 * the platform-default user-agent stylesheet. If the URL does not resolve to a valid location, 1583 * the platform-default user-agent stylesheet will be used. 1584 * <p> 1585 * For additional information about using CSS with the scene graph, 1586 * see the <a href="doc-files/cssref.html">CSS Reference Guide</a>. 1587 * </p> 1588 * @param url The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL 1589 * does not have a [scheme:] component, the URL is considered to be the [path] component only. 1590 * Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to 1591 * the root of the application's classpath. 1592 * @since JavaFX 8u20 1593 */ 1594 public final void setUserAgentStylesheet(String url) { 1595 userAgentStylesheetProperty().set(url); 1596 } 1597 1598 /** 1599 * Retrieves the depth buffer attribute for this scene. 1600 * @return the depth buffer attribute. 1601 */ 1602 public final boolean isDepthBuffer() { 1603 return depthBuffer; 1604 } 1605 1606 boolean isDepthBufferInternal() { 1607 if (!Platform.isSupported(ConditionalFeature.SCENE3D)) { 1608 return false; 1609 } 1610 return depthBuffer; 1611 } 1612 1613 private void init(double width, double height) { 1614 if (width >= 0) { 1615 widthSetByUser = width; 1616 setWidth((float)width); 1617 } 1618 if (height >= 0) { 1619 heightSetByUser = height; 1620 setHeight((float)height); 1621 } 1622 sizeInitialized = (widthSetByUser >= 0 && heightSetByUser >= 0); 1623 } 1624 1625 private void init() { 1626 if (PerformanceTracker.isLoggingEnabled()) { 1627 PerformanceTracker.logEvent("Scene.init for [" + this + "]"); 1628 } 1629 mouseHandler = new MouseHandler(); 1630 clickGenerator = new ClickGenerator(); 1631 1632 if (PerformanceTracker.isLoggingEnabled()) { 1633 PerformanceTracker.logEvent("Scene.init for [" + this + "] - finished"); 1634 } 1635 } 1636 1637 private void preferredSize() { 1638 final Parent root = getRoot(); 1639 1640 // one or the other isn't initialized, need to perform layout in 1641 // order to ensure we can properly measure the preferred size of the 1642 // scene 1643 doCSSPass(); 1644 1645 resizeRootToPreferredSize(root); 1646 doLayoutPass(); 1647 1648 if (widthSetByUser < 0) { 1649 setWidth(root.isResizable()? root.getLayoutX() + root.getTranslateX() + root.getLayoutBounds().getWidth() : 1650 root.getBoundsInParent().getMaxX()); 1651 } else { 1652 setWidth(widthSetByUser); 1653 } 1654 1655 if (heightSetByUser < 0) { 1656 setHeight(root.isResizable()? root.getLayoutY() + root.getTranslateY() + root.getLayoutBounds().getHeight() : 1657 root.getBoundsInParent().getMaxY()); 1658 } else { 1659 setHeight(heightSetByUser); 1660 } 1661 1662 sizeInitialized = (getWidth() > 0) && (getHeight() > 0); 1663 1664 PerformanceTracker.logEvent("Scene preferred bounds computation complete"); 1665 } 1666 1667 final void resizeRootToPreferredSize(Parent root) { 1668 final double preferredWidth; 1669 final double preferredHeight; 1670 1671 final Orientation contentBias = root.getContentBias(); 1672 if (contentBias == null) { 1673 preferredWidth = getPreferredWidth(root, widthSetByUser, -1); 1674 preferredHeight = getPreferredHeight(root, heightSetByUser, -1); 1675 } else if (contentBias == Orientation.HORIZONTAL) { 1676 // height depends on width 1677 preferredWidth = getPreferredWidth(root, widthSetByUser, -1); 1678 preferredHeight = getPreferredHeight(root, heightSetByUser, 1679 preferredWidth); 1680 } else /* if (contentBias == Orientation.VERTICAL) */ { 1681 // width depends on height 1682 preferredHeight = getPreferredHeight(root, heightSetByUser, -1); 1683 preferredWidth = getPreferredWidth(root, widthSetByUser, 1684 preferredHeight); 1685 } 1686 1687 root.resize(preferredWidth, preferredHeight); 1688 } 1689 1690 private static double getPreferredWidth(Parent root, 1691 double forcedWidth, 1692 double height) { 1693 if (forcedWidth >= 0) { 1694 return forcedWidth; 1695 } 1696 final double normalizedHeight = (height >= 0) ? height : -1; 1697 return root.boundedSize(root.prefWidth(normalizedHeight), 1698 root.minWidth(normalizedHeight), 1699 root.maxWidth(normalizedHeight)); 1700 } 1701 1702 private static double getPreferredHeight(Parent root, 1703 double forcedHeight, 1704 double width) { 1705 if (forcedHeight >= 0) { 1706 return forcedHeight; 1707 } 1708 final double normalizedWidth = (width >= 0) ? width : -1; 1709 return root.boundedSize(root.prefHeight(normalizedWidth), 1710 root.minHeight(normalizedWidth), 1711 root.maxHeight(normalizedWidth)); 1712 } 1713 1714 /** 1715 * @treatAsPrivate implementation detail 1716 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1717 */ 1718 @Deprecated 1719 public void impl_preferredSize() { 1720 preferredSize(); 1721 } 1722 1723 private PerformanceTracker tracker; 1724 private static final Object trackerMonitor = new Object(); 1725 1726 // mouse events handling 1727 private MouseHandler mouseHandler; 1728 private ClickGenerator clickGenerator; 1729 1730 // gesture events handling 1731 private Point2D cursorScreenPos; 1732 private Point2D cursorScenePos; 1733 1734 private static class TouchGesture { 1735 EventTarget target; 1736 Point2D sceneCoords; 1737 Point2D screenCoords; 1738 boolean finished; 1739 } 1740 1741 private final TouchGesture scrollGesture = new TouchGesture(); 1742 private final TouchGesture zoomGesture = new TouchGesture(); 1743 private final TouchGesture rotateGesture = new TouchGesture(); 1744 private final TouchGesture swipeGesture = new TouchGesture(); 1745 1746 // touch events handling 1747 private TouchMap touchMap = new TouchMap(); 1748 private TouchEvent nextTouchEvent = null; 1749 private TouchPoint[] touchPoints = null; 1750 private int touchEventSetId = 0; 1751 private int touchPointIndex = 0; 1752 private Map<Integer, EventTarget> touchTargets = 1753 new HashMap<Integer, EventTarget>(); 1754 1755 /** 1756 * @treatAsPrivate implementation detail 1757 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1758 */ 1759 // SB-dependency: RT-22747 has been filed to track this 1760 @Deprecated 1761 public void impl_processMouseEvent(MouseEvent e) { 1762 mouseHandler.process(e, false); 1763 } 1764 1765 private void processMenuEvent(double x2, double y2, double xAbs, double yAbs, boolean isKeyboardTrigger) { 1766 EventTarget eventTarget = null; 1767 Scene.inMousePick = true; 1768 if (isKeyboardTrigger) { 1769 Node sceneFocusOwner = getFocusOwner(); 1770 1771 // for keyboard triggers set coordinates inside focus owner 1772 final double xOffset = xAbs - x2; 1773 final double yOffset = yAbs - y2; 1774 if (sceneFocusOwner != null) { 1775 final Bounds bounds = sceneFocusOwner.localToScene( 1776 sceneFocusOwner.getBoundsInLocal()); 1777 x2 = bounds.getMinX() + bounds.getWidth() / 4; 1778 y2 = bounds.getMinY() + bounds.getHeight() / 2; 1779 eventTarget = sceneFocusOwner; 1780 } else { 1781 x2 = Scene.this.getWidth() / 4; 1782 y2 = Scene.this.getWidth() / 2; 1783 eventTarget = Scene.this; 1784 } 1785 1786 xAbs = x2 + xOffset; 1787 yAbs = y2 + yOffset; 1788 } 1789 1790 final PickResult res = pick(x2, y2); 1791 1792 if (!isKeyboardTrigger) { 1793 eventTarget = res.getIntersectedNode(); 1794 if (eventTarget == null) { 1795 eventTarget = this; 1796 } 1797 } 1798 1799 if (eventTarget != null) { 1800 ContextMenuEvent context = new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED, 1801 x2, y2, xAbs, yAbs, isKeyboardTrigger, res); 1802 Event.fireEvent(eventTarget, context); 1803 } 1804 Scene.inMousePick = false; 1805 } 1806 1807 private void processGestureEvent(GestureEvent e, TouchGesture gesture) { 1808 EventTarget pickedTarget = null; 1809 1810 if (e.getEventType() == ZoomEvent.ZOOM_STARTED || 1811 e.getEventType() == RotateEvent.ROTATION_STARTED || 1812 e.getEventType() == ScrollEvent.SCROLL_STARTED) { 1813 gesture.target = null; 1814 gesture.finished = false; 1815 } 1816 1817 if (gesture.target != null && (!gesture.finished || e.isInertia())) { 1818 pickedTarget = gesture.target; 1819 } else { 1820 pickedTarget = e.getPickResult().getIntersectedNode(); 1821 if (pickedTarget == null) { 1822 pickedTarget = this; 1823 } 1824 } 1825 1826 if (e.getEventType() == ZoomEvent.ZOOM_STARTED || 1827 e.getEventType() == RotateEvent.ROTATION_STARTED || 1828 e.getEventType() == ScrollEvent.SCROLL_STARTED) { 1829 gesture.target = pickedTarget; 1830 } 1831 if (e.getEventType() != ZoomEvent.ZOOM_FINISHED && 1832 e.getEventType() != RotateEvent.ROTATION_FINISHED && 1833 e.getEventType() != ScrollEvent.SCROLL_FINISHED && 1834 !e.isInertia()) { 1835 gesture.sceneCoords = new Point2D(e.getSceneX(), e.getSceneY()); 1836 gesture.screenCoords = new Point2D(e.getScreenX(), e.getScreenY()); 1837 } 1838 1839 if (pickedTarget != null) { 1840 Event.fireEvent(pickedTarget, e); 1841 } 1842 1843 if (e.getEventType() == ZoomEvent.ZOOM_FINISHED || 1844 e.getEventType() == RotateEvent.ROTATION_FINISHED || 1845 e.getEventType() == ScrollEvent.SCROLL_FINISHED) { 1846 gesture.finished = true; 1847 } 1848 } 1849 1850 private void processTouchEvent(TouchEvent e, TouchPoint[] touchPoints) { 1851 inMousePick = true; 1852 touchEventSetId++; 1853 1854 List<TouchPoint> touchList = Arrays.asList(touchPoints); 1855 1856 // fire all the events 1857 for (TouchPoint tp : touchPoints) { 1858 if (tp.getTarget() != null) { 1859 EventType<TouchEvent> type = null; 1860 switch (tp.getState()) { 1861 case MOVED: 1862 type = TouchEvent.TOUCH_MOVED; 1863 break; 1864 case PRESSED: 1865 type = TouchEvent.TOUCH_PRESSED; 1866 break; 1867 case RELEASED: 1868 type = TouchEvent.TOUCH_RELEASED; 1869 break; 1870 case STATIONARY: 1871 type = TouchEvent.TOUCH_STATIONARY; 1872 break; 1873 } 1874 1875 for (TouchPoint t : touchPoints) { 1876 t.impl_reset(); 1877 } 1878 1879 TouchEvent te = new TouchEvent(type, tp, touchList, 1880 touchEventSetId, e.isShiftDown(), e.isControlDown(), 1881 e.isAltDown(), e.isMetaDown()); 1882 1883 Event.fireEvent(tp.getTarget(), te); 1884 } 1885 } 1886 1887 // process grabbing 1888 for (TouchPoint tp : touchPoints) { 1889 EventTarget grabbed = tp.getGrabbed(); 1890 if (grabbed != null) { 1891 touchTargets.put(tp.getId(), grabbed); 1892 }; 1893 1894 if (grabbed == null || tp.getState() == TouchPoint.State.RELEASED) { 1895 touchTargets.remove(tp.getId()); 1896 } 1897 } 1898 1899 inMousePick = false; 1900 } 1901 1902 /** 1903 * Note: The only user of this method is in unit test: PickAndContainTest. 1904 */ 1905 Node test_pick(double x, double y) { 1906 inMousePick = true; 1907 PickResult result = mouseHandler.pickNode(new PickRay(x, y, 1, 1908 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); 1909 inMousePick = false; 1910 if (result != null) { 1911 return result.getIntersectedNode(); 1912 } 1913 return null; 1914 } 1915 1916 private PickResult pick(final double x, final double y) { 1917 pick(tmpTargetWrapper, x, y); 1918 return tmpTargetWrapper.getResult(); 1919 } 1920 1921 private boolean isInScene(double x, double y) { 1922 if (x < 0 || y < 0 || x > getWidth() || y > getHeight()) { 1923 return false; 1924 } 1925 1926 Window w = getWindow(); 1927 if (w instanceof Stage 1928 && ((Stage) w).getStyle() == StageStyle.TRANSPARENT 1929 && getFill() == null) { 1930 return false; 1931 } 1932 1933 return true; 1934 } 1935 1936 private void pick(TargetWrapper target, final double x, final double y) { 1937 final PickRay pickRay = getEffectiveCamera().computePickRay( 1938 x, y, null); 1939 1940 final double mag = pickRay.getDirectionNoClone().length(); 1941 pickRay.getDirectionNoClone().normalize(); 1942 final PickResult res = mouseHandler.pickNode(pickRay); 1943 if (res != null) { 1944 target.setNodeResult(res); 1945 } else { 1946 //TODO: is this the intersection with projection plane? 1947 Vec3d o = pickRay.getOriginNoClone(); 1948 Vec3d d = pickRay.getDirectionNoClone(); 1949 target.setSceneResult(new PickResult( 1950 null, new Point3D( 1951 o.x + mag * d.x, 1952 o.y + mag * d.y, 1953 o.z + mag * d.z), 1954 mag), 1955 isInScene(x, y) ? this : null); 1956 } 1957 } 1958 1959 /*************************************************************************** 1960 * * 1961 * Key Events and Focus Traversal * 1962 * * 1963 **************************************************************************/ 1964 1965 /* 1966 * We cannot initialize keyHandler in init because some of the triggers 1967 * access it before the init block. 1968 * No clue why def keyHandler = bind lazy {KeyHandler{scene:this};} 1969 * does not compile. 1970 */ 1971 private KeyHandler keyHandler = null; 1972 private KeyHandler getKeyHandler() { 1973 if (keyHandler == null) { 1974 keyHandler = new KeyHandler(); 1975 } 1976 return keyHandler; 1977 } 1978 /** 1979 * Set to true if something has happened to the focused node that makes 1980 * it no longer eligible to have the focus. 1981 * 1982 */ 1983 private boolean focusDirty = true; 1984 1985 final void setFocusDirty(boolean value) { 1986 if (!focusDirty) { 1987 Toolkit.getToolkit().requestNextPulse(); 1988 } 1989 focusDirty = value; 1990 } 1991 1992 final boolean isFocusDirty() { 1993 return focusDirty; 1994 } 1995 1996 private TopMostTraversalEngine traversalEngine = new SceneTraversalEngine(this); 1997 1998 /** 1999 * Traverses focus from the given node in the given direction. 2000 */ 2001 boolean traverse(Node node, Direction dir) { 2002 if (node.getSubScene() != null) { 2003 return node.getSubScene().traverse(node, dir); 2004 } 2005 return traversalEngine.trav(node, dir) != null; 2006 } 2007 2008 /** 2009 * Moves the focus to a reasonable initial location. Called when a scene's 2010 * focus is dirty and there's no current owner, or if the owner has been 2011 * removed from the scene. 2012 */ 2013 private void focusInitial() { 2014 traversalEngine.traverseToFirst(); 2015 } 2016 2017 /** 2018 * Moves the focus to a reasonble location "near" the given node. 2019 * Called when the focused node is no longer eligible to have 2020 * the focus because it has become invisible or disabled. This 2021 * function assumes that it is still a member of the same scene. 2022 */ 2023 private void focusIneligible(Node node) { 2024 traverse(node, Direction.NEXT); 2025 } 2026 2027 /** 2028 * @treatAsPrivate implementation detail 2029 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2030 */ 2031 // SB-dependency: RT-24668 has been filed to track this 2032 @Deprecated 2033 public void impl_processKeyEvent(KeyEvent e) { 2034 if (dndGesture != null) { 2035 if (!dndGesture.processKey(e)) { 2036 dndGesture = null; 2037 } 2038 } 2039 2040 getKeyHandler().process(e); 2041 } 2042 2043 void requestFocus(Node node) { 2044 getKeyHandler().requestFocus(node); 2045 } 2046 2047 private Node oldFocusOwner; 2048 2049 /** 2050 * The scene's current focus owner node. This node's "focused" 2051 * variable might be false if this scene has no window, or if the 2052 * window is inactive (window.focused == false). 2053 * @since JavaFX 2.2 2054 */ 2055 private ReadOnlyObjectWrapper<Node> focusOwner = new ReadOnlyObjectWrapper<Node>(this, "focusOwner") { 2056 2057 @Override 2058 protected void invalidated() { 2059 if (oldFocusOwner != null) { 2060 ((Node.FocusedProperty) oldFocusOwner.focusedProperty()).store(false); 2061 } 2062 Node value = get(); 2063 if (value != null) { 2064 ((Node.FocusedProperty) value.focusedProperty()).store(keyHandler.windowFocused); 2065 if (value != oldFocusOwner) { 2066 value.getScene().impl_enableInputMethodEvents( 2067 value.getInputMethodRequests() != null 2068 && value.getOnInputMethodTextChanged() != null); 2069 } 2070 } 2071 // for the rest of the method we need to update the oldFocusOwner 2072 // and use a local copy of it because the user handlers can cause 2073 // recurrent calls of requestFocus 2074 Node localOldOwner = oldFocusOwner; 2075 oldFocusOwner = value; 2076 if (localOldOwner != null) { 2077 ((Node.FocusedProperty) localOldOwner.focusedProperty()).notifyListeners(); 2078 } 2079 if (value != null) { 2080 ((Node.FocusedProperty) value.focusedProperty()).notifyListeners(); 2081 } 2082 PlatformLogger logger = Logging.getFocusLogger(); 2083 if (logger.isLoggable(Level.FINE)) { 2084 if (value == get()) { 2085 logger.fine("Changed focus from " 2086 + localOldOwner + " to " + value); 2087 } else { 2088 logger.fine("Changing focus from " 2089 + localOldOwner + " to " + value 2090 + " canceled by nested requestFocus"); 2091 } 2092 } 2093 if (accessible != null) { 2094 accessible.sendNotification(AccessibleAttribute.FOCUS_NODE); 2095 } 2096 } 2097 }; 2098 2099 public final Node getFocusOwner() { 2100 return focusOwner.get(); 2101 } 2102 2103 public final ReadOnlyObjectProperty<Node> focusOwnerProperty() { 2104 return focusOwner.getReadOnlyProperty(); 2105 } 2106 2107 // For testing. 2108 void focusCleanup() { 2109 scenePulseListener.focusCleanup(); 2110 } 2111 2112 private void processInputMethodEvent(InputMethodEvent e) { 2113 Node node = getFocusOwner(); 2114 if (node != null) { 2115 node.fireEvent(e); 2116 } 2117 } 2118 2119 /** 2120 * @treatAsPrivate implementation detail 2121 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2122 */ 2123 @Deprecated 2124 public void impl_enableInputMethodEvents(boolean enable) { 2125 if (impl_peer != null) { 2126 impl_peer.enableInputMethodEvents(enable); 2127 } 2128 } 2129 2130 /** 2131 * Returns true if this scene is quiescent, i.e. it has no activity 2132 * pending on it such as CSS processing or layout requests. 2133 * 2134 * Intended to be used for tests only 2135 * 2136 * @return boolean indicating whether the scene is quiescent 2137 */ 2138 boolean isQuiescent() { 2139 final Parent r = getRoot(); 2140 return !isFocusDirty() 2141 && (r == null || (r.cssFlag == CssFlags.CLEAN && 2142 r.layoutFlag == LayoutFlags.CLEAN)); 2143 } 2144 2145 /** 2146 * A listener for pulses, used for testing. If non-null, this is called at 2147 * the very end of ScenePulseListener.pulse(). 2148 * 2149 * Intended to be used for tests only 2150 */ 2151 Runnable testPulseListener = null; 2152 2153 /** 2154 * Set the specified dirty bit and mark the peer as dirty 2155 */ 2156 private void markDirty(DirtyBits dirtyBit) { 2157 setDirty(dirtyBit); 2158 if (impl_peer != null) { 2159 Toolkit.getToolkit().requestNextPulse(); 2160 } 2161 } 2162 2163 /** 2164 * Set the specified dirty bit 2165 */ 2166 private void setDirty(DirtyBits dirtyBit) { 2167 dirtyBits |= dirtyBit.getMask(); 2168 } 2169 2170 /** 2171 * Test the specified dirty bit 2172 */ 2173 private boolean isDirty(DirtyBits dirtyBit) { 2174 return ((dirtyBits & dirtyBit.getMask()) != 0); 2175 } 2176 2177 /** 2178 * Test whether the dirty bits are empty 2179 */ 2180 private boolean isDirtyEmpty() { 2181 return dirtyBits == 0; 2182 } 2183 2184 /** 2185 * Clear all dirty bits 2186 */ 2187 private void clearDirty() { 2188 dirtyBits = 0; 2189 } 2190 2191 private enum DirtyBits { 2192 FILL_DIRTY, 2193 ROOT_DIRTY, 2194 CAMERA_DIRTY, 2195 LIGHTS_DIRTY, 2196 CURSOR_DIRTY; 2197 2198 private int mask; 2199 2200 private DirtyBits() { 2201 mask = 1 << ordinal(); 2202 } 2203 2204 public final int getMask() { return mask; } 2205 } 2206 2207 private List<LightBase> lights = new ArrayList<>(); 2208 2209 // @param light must not be null 2210 final void addLight(LightBase light) { 2211 if (!lights.contains(light)) { 2212 lights.add(light); 2213 markDirty(DirtyBits.LIGHTS_DIRTY); 2214 } 2215 } 2216 2217 final void removeLight(LightBase light) { 2218 if (lights.remove(light)) { 2219 markDirty(DirtyBits.LIGHTS_DIRTY); 2220 } 2221 } 2222 2223 /** 2224 * PG Light synchronizer. 2225 */ 2226 private void syncLights() { 2227 if (!isDirty(DirtyBits.LIGHTS_DIRTY)) { 2228 return; 2229 } 2230 inSynchronizer = true; 2231 NGLightBase peerLights[] = impl_peer.getLights(); 2232 if (!lights.isEmpty() || (peerLights != null)) { 2233 if (lights.isEmpty()) { 2234 impl_peer.setLights(null); 2235 } else { 2236 if (peerLights == null || peerLights.length < lights.size()) { 2237 peerLights = new NGLightBase[lights.size()]; 2238 } 2239 int i = 0; 2240 for (; i < lights.size(); i++) { 2241 peerLights[i] = lights.get(i).impl_getPeer(); 2242 } 2243 // Clear the rest of the list 2244 while (i < peerLights.length && peerLights[i] != null) { 2245 peerLights[i++] = null; 2246 } 2247 impl_peer.setLights(peerLights); 2248 } 2249 } 2250 inSynchronizer = false; 2251 } 2252 2253 //INNER CLASSES 2254 2255 /******************************************************************************* 2256 * * 2257 * Scene Pulse Listener * 2258 * * 2259 ******************************************************************************/ 2260 2261 class ScenePulseListener implements TKPulseListener { 2262 2263 private boolean firstPulse = true; 2264 2265 /** 2266 * PG synchronizer. Called once per frame from the pulse listener. 2267 * This function calls the synchronizePGNode method on each node in 2268 * the dirty list. 2269 */ 2270 private void synchronizeSceneNodes() { 2271 Toolkit.getToolkit().checkFxUserThread(); 2272 2273 Scene.inSynchronizer = true; 2274 2275 // if dirtyNodes is null then that means this Scene has not yet been 2276 // synchronized, and so we will simply synchronize every node in the 2277 // scene and then create the dirty nodes array list 2278 if (Scene.this.dirtyNodes == null) { 2279 // must do this recursively 2280 syncAll(getRoot()); 2281 dirtyNodes = new Node[MIN_DIRTY_CAPACITY]; 2282 2283 } else { 2284 // This is not the first time this scene has been synchronized, 2285 // so we will only synchronize those nodes that need it 2286 for (int i = 0 ; i < dirtyNodesSize; ++i) { 2287 Node node = dirtyNodes[i]; 2288 dirtyNodes[i] = null; 2289 if (node.getScene() == Scene.this) { 2290 node.impl_syncPeer(); 2291 } 2292 } 2293 dirtyNodesSize = 0; 2294 } 2295 2296 Scene.inSynchronizer = false; 2297 } 2298 2299 /** 2300 * Recursive function for synchronizing every node in the scenegraph. 2301 * The return value is the number of nodes in the graph. 2302 */ 2303 private int syncAll(Node node) { 2304 node.impl_syncPeer(); 2305 int size = 1; 2306 if (node instanceof Parent) { 2307 Parent p = (Parent) node; 2308 final int childrenCount = p.getChildren().size(); 2309 2310 for (int i = 0; i < childrenCount; i++) { 2311 Node n = p.getChildren().get(i); 2312 if (n != null) { 2313 size += syncAll(n); 2314 } 2315 } 2316 } else if (node instanceof SubScene) { 2317 SubScene subScene = (SubScene)node; 2318 size += syncAll(subScene.getRoot()); 2319 } 2320 if (node.getClip() != null) { 2321 size += syncAll(node.getClip()); 2322 } 2323 2324 return size; 2325 } 2326 2327 private void synchronizeSceneProperties() { 2328 inSynchronizer = true; 2329 if (isDirty(DirtyBits.ROOT_DIRTY)) { 2330 impl_peer.setRoot(getRoot().impl_getPeer()); 2331 } 2332 2333 if (isDirty(DirtyBits.FILL_DIRTY)) { 2334 Toolkit tk = Toolkit.getToolkit(); 2335 impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill())); 2336 } 2337 2338 // new camera was set on the scene or old camera changed 2339 final Camera cam = getEffectiveCamera(); 2340 if (isDirty(DirtyBits.CAMERA_DIRTY)) { 2341 cam.impl_updatePeer(); 2342 impl_peer.setCamera((NGCamera) cam.impl_getPeer()); 2343 } 2344 2345 if (isDirty(DirtyBits.CURSOR_DIRTY)) { 2346 mouseHandler.updateCursor(getCursor()); 2347 } 2348 2349 clearDirty(); 2350 inSynchronizer = false; 2351 } 2352 2353 /** 2354 * The focus is considered dirty if something happened to 2355 * the scene graph that may require the focus to be moved. 2356 * This must handle cases where (a) the focus owner may have 2357 * become ineligible to have the focus, and (b) where the focus 2358 * owner is null and a node may have become traversable and eligible. 2359 */ 2360 private void focusCleanup() { 2361 if (Scene.this.isFocusDirty()) { 2362 final Node oldOwner = Scene.this.getFocusOwner(); 2363 if (oldOwner == null) { 2364 Scene.this.focusInitial(); 2365 } else if (oldOwner.getScene() != Scene.this) { 2366 Scene.this.requestFocus(null); 2367 Scene.this.focusInitial(); 2368 } else if (!oldOwner.isCanReceiveFocus()) { 2369 Scene.this.requestFocus(null); 2370 Scene.this.focusIneligible(oldOwner); 2371 } 2372 Scene.this.setFocusDirty(false); 2373 } 2374 } 2375 2376 @Override 2377 public void pulse() { 2378 if (Scene.this.tracker != null) { 2379 Scene.this.tracker.pulse(); 2380 } 2381 if (firstPulse) { 2382 PerformanceTracker.logEvent("Scene - first repaint"); 2383 } 2384 2385 focusCleanup(); 2386 2387 disposeAccessibles(); 2388 2389 if (PULSE_LOGGING_ENABLED) { 2390 PulseLogger.newPhase("CSS Pass"); 2391 } 2392 Scene.this.doCSSPass(); 2393 2394 if (PULSE_LOGGING_ENABLED) { 2395 PulseLogger.newPhase("Layout Pass"); 2396 } 2397 Scene.this.doLayoutPass(); 2398 2399 boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty(); 2400 if (dirty) { 2401 if (PULSE_LOGGING_ENABLED) { 2402 PulseLogger.newPhase("Update bounds"); 2403 } 2404 getRoot().updateBounds(); 2405 if (impl_peer != null) { 2406 try { 2407 if (PULSE_LOGGING_ENABLED) { 2408 PulseLogger.newPhase("Waiting for previous rendering"); 2409 } 2410 impl_peer.waitForRenderingToComplete(); 2411 impl_peer.waitForSynchronization(); 2412 // synchronize scene properties 2413 if (PULSE_LOGGING_ENABLED) { 2414 PulseLogger.newPhase("Copy state to render graph"); 2415 } 2416 syncLights(); 2417 synchronizeSceneProperties(); 2418 // Run the synchronizer 2419 synchronizeSceneNodes(); 2420 Scene.this.mouseHandler.pulse(); 2421 // Tell the scene peer that it needs to repaint 2422 impl_peer.markDirty(); 2423 } finally { 2424 impl_peer.releaseSynchronization(true); 2425 } 2426 } else { 2427 if (PULSE_LOGGING_ENABLED) { 2428 PulseLogger.newPhase("Synchronize with null peer"); 2429 } 2430 synchronizeSceneProperties(); 2431 synchronizeSceneNodes(); 2432 Scene.this.mouseHandler.pulse(); 2433 } 2434 2435 if (Scene.this.getRoot().cssFlag != CssFlags.CLEAN) { 2436 Scene.this.getRoot().impl_markDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS); 2437 } 2438 } 2439 2440 // required for image cursor created from animated image 2441 Scene.this.mouseHandler.updateCursorFrame(); 2442 2443 if (firstPulse) { 2444 if (PerformanceTracker.isLoggingEnabled()) { 2445 PerformanceTracker.logEvent("Scene - first repaint - layout complete"); 2446 if (PrismSettings.perfLogFirstPaintFlush) { 2447 PerformanceTracker.outputLog(); 2448 } 2449 if (PrismSettings.perfLogFirstPaintExit) { 2450 System.exit(0); 2451 } 2452 } 2453 firstPulse = false; 2454 } 2455 2456 if (testPulseListener != null) { 2457 testPulseListener.run(); 2458 } 2459 } 2460 } 2461 2462 /******************************************************************************* 2463 * * 2464 * Scene Peer Listener * 2465 * * 2466 ******************************************************************************/ 2467 2468 class ScenePeerListener implements TKSceneListener { 2469 @Override 2470 public void changedLocation(float x, float y) { 2471 if (x != Scene.this.getX()) { 2472 Scene.this.setX(x); 2473 } 2474 if (y != Scene.this.getY()) { 2475 Scene.this.setY(y); 2476 } 2477 } 2478 2479 @Override 2480 public void changedSize(float w, float h) { 2481 if (w != Scene.this.getWidth()) Scene.this.setWidth(w); 2482 if (h != Scene.this.getHeight()) Scene.this.setHeight(h); 2483 } 2484 2485 @Override 2486 public void mouseEvent(EventType<MouseEvent> type, double x, double y, double screenX, double screenY, 2487 MouseButton button, boolean popupTrigger, boolean synthesized, 2488 boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown, 2489 boolean primaryDown, boolean middleDown, boolean secondaryDown) 2490 { 2491 MouseEvent mouseEvent = new MouseEvent(type, x, y, screenX, screenY, button, 2492 0, // click count will be adjusted by clickGenerator later anyway 2493 shiftDown, controlDown, altDown, metaDown, 2494 primaryDown, middleDown, secondaryDown, synthesized, popupTrigger, false, null); 2495 impl_processMouseEvent(mouseEvent); 2496 } 2497 2498 2499 @Override 2500 public void keyEvent(KeyEvent keyEvent) 2501 { 2502 impl_processKeyEvent(keyEvent); 2503 } 2504 2505 @Override 2506 public void inputMethodEvent(EventType<InputMethodEvent> type, 2507 ObservableList<InputMethodTextRun> composed, String committed, 2508 int caretPosition) 2509 { 2510 InputMethodEvent inputMethodEvent = new InputMethodEvent( 2511 type, composed, committed, caretPosition); 2512 processInputMethodEvent(inputMethodEvent); 2513 } 2514 2515 public void menuEvent(double x, double y, double xAbs, double yAbs, 2516 boolean isKeyboardTrigger) { 2517 Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger); 2518 } 2519 2520 @Override 2521 public void scrollEvent( 2522 EventType<ScrollEvent> eventType, 2523 double scrollX, double scrollY, 2524 double totalScrollX, double totalScrollY, 2525 double xMultiplier, double yMultiplier, 2526 int touchCount, 2527 int scrollTextX, int scrollTextY, 2528 int defaultTextX, int defaultTextY, 2529 double x, double y, double screenX, double screenY, 2530 boolean _shiftDown, boolean _controlDown, 2531 boolean _altDown, boolean _metaDown, 2532 boolean _direct, boolean _inertia) { 2533 2534 ScrollEvent.HorizontalTextScrollUnits xUnits = scrollTextX > 0 ? 2535 ScrollEvent.HorizontalTextScrollUnits.CHARACTERS : 2536 ScrollEvent.HorizontalTextScrollUnits.NONE; 2537 2538 double xText = scrollTextX < 0 ? 0 : scrollTextX * scrollX; 2539 2540 ScrollEvent.VerticalTextScrollUnits yUnits = scrollTextY > 0 ? 2541 ScrollEvent.VerticalTextScrollUnits.LINES : 2542 (scrollTextY < 0 ? 2543 ScrollEvent.VerticalTextScrollUnits.PAGES : 2544 ScrollEvent.VerticalTextScrollUnits.NONE); 2545 2546 double yText = scrollTextY < 0 ? scrollY : scrollTextY * scrollY; 2547 2548 xMultiplier = defaultTextX > 0 && scrollTextX >= 0 2549 ? Math.round(xMultiplier * scrollTextX / defaultTextX) 2550 : xMultiplier; 2551 2552 yMultiplier = defaultTextY > 0 && scrollTextY >= 0 2553 ? Math.round(yMultiplier * scrollTextY / defaultTextY) 2554 : yMultiplier; 2555 2556 if (eventType == ScrollEvent.SCROLL_FINISHED) { 2557 x = scrollGesture.sceneCoords.getX(); 2558 y = scrollGesture.sceneCoords.getY(); 2559 screenX = scrollGesture.screenCoords.getX(); 2560 screenY = scrollGesture.screenCoords.getY(); 2561 } else if (Double.isNaN(x) || Double.isNaN(y) || 2562 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2563 if (cursorScenePos == null || cursorScreenPos == null) { 2564 return; 2565 } 2566 x = cursorScenePos.getX(); 2567 y = cursorScenePos.getY(); 2568 screenX = cursorScreenPos.getX(); 2569 screenY = cursorScreenPos.getY(); 2570 } 2571 2572 inMousePick = true; 2573 Scene.this.processGestureEvent(new ScrollEvent( 2574 eventType, 2575 x, y, screenX, screenY, 2576 _shiftDown, _controlDown, _altDown, _metaDown, 2577 _direct, _inertia, 2578 scrollX * xMultiplier, scrollY * yMultiplier, 2579 totalScrollX * xMultiplier, totalScrollY * yMultiplier, 2580 xMultiplier, yMultiplier, 2581 xUnits, xText, yUnits, yText, touchCount, pick(x, y)), 2582 scrollGesture); 2583 inMousePick = false; 2584 } 2585 2586 @Override 2587 public void zoomEvent( 2588 EventType<ZoomEvent> eventType, 2589 double zoomFactor, double totalZoomFactor, 2590 double x, double y, double screenX, double screenY, 2591 boolean _shiftDown, boolean _controlDown, 2592 boolean _altDown, boolean _metaDown, 2593 boolean _direct, boolean _inertia) { 2594 2595 if (eventType == ZoomEvent.ZOOM_FINISHED) { 2596 x = zoomGesture.sceneCoords.getX(); 2597 y = zoomGesture.sceneCoords.getY(); 2598 screenX = zoomGesture.screenCoords.getX(); 2599 screenY = zoomGesture.screenCoords.getY(); 2600 } else if (Double.isNaN(x) || Double.isNaN(y) || 2601 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2602 if (cursorScenePos == null || cursorScreenPos == null) { 2603 return; 2604 } 2605 x = cursorScenePos.getX(); 2606 y = cursorScenePos.getY(); 2607 screenX = cursorScreenPos.getX(); 2608 screenY = cursorScreenPos.getY(); 2609 } 2610 2611 inMousePick = true; 2612 Scene.this.processGestureEvent(new ZoomEvent(eventType, 2613 x, y, screenX, screenY, 2614 _shiftDown, _controlDown, _altDown, _metaDown, 2615 _direct, _inertia, 2616 zoomFactor, totalZoomFactor, pick(x, y)), 2617 zoomGesture); 2618 inMousePick = false; 2619 } 2620 2621 @Override 2622 public void rotateEvent( 2623 EventType<RotateEvent> eventType, double angle, double totalAngle, 2624 double x, double y, double screenX, double screenY, 2625 boolean _shiftDown, boolean _controlDown, 2626 boolean _altDown, boolean _metaDown, 2627 boolean _direct, boolean _inertia) { 2628 2629 if (eventType == RotateEvent.ROTATION_FINISHED) { 2630 x = rotateGesture.sceneCoords.getX(); 2631 y = rotateGesture.sceneCoords.getY(); 2632 screenX = rotateGesture.screenCoords.getX(); 2633 screenY = rotateGesture.screenCoords.getY(); 2634 } else if (Double.isNaN(x) || Double.isNaN(y) || 2635 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2636 if (cursorScenePos == null || cursorScreenPos == null) { 2637 return; 2638 } 2639 x = cursorScenePos.getX(); 2640 y = cursorScenePos.getY(); 2641 screenX = cursorScreenPos.getX(); 2642 screenY = cursorScreenPos.getY(); 2643 } 2644 2645 inMousePick = true; 2646 Scene.this.processGestureEvent(new RotateEvent( 2647 eventType, x, y, screenX, screenY, 2648 _shiftDown, _controlDown, _altDown, _metaDown, 2649 _direct, _inertia, angle, totalAngle, pick(x, y)), 2650 rotateGesture); 2651 inMousePick = false; 2652 2653 } 2654 2655 @Override 2656 public void swipeEvent( 2657 EventType<SwipeEvent> eventType, int touchCount, 2658 double x, double y, double screenX, double screenY, 2659 boolean _shiftDown, boolean _controlDown, 2660 boolean _altDown, boolean _metaDown, boolean _direct) { 2661 2662 if (Double.isNaN(x) || Double.isNaN(y) || 2663 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2664 if (cursorScenePos == null || cursorScreenPos == null) { 2665 return; 2666 } 2667 x = cursorScenePos.getX(); 2668 y = cursorScenePos.getY(); 2669 screenX = cursorScreenPos.getX(); 2670 screenY = cursorScreenPos.getY(); 2671 } 2672 2673 inMousePick = true; 2674 Scene.this.processGestureEvent(new SwipeEvent( 2675 eventType, x, y, screenX, screenY, 2676 _shiftDown, _controlDown, _altDown, _metaDown, _direct, 2677 touchCount, pick(x, y)), 2678 swipeGesture); 2679 inMousePick = false; 2680 } 2681 2682 @Override 2683 public void touchEventBegin( 2684 long time, int touchCount, boolean isDirect, 2685 boolean _shiftDown, boolean _controlDown, 2686 boolean _altDown, boolean _metaDown) { 2687 2688 if (!isDirect) { 2689 nextTouchEvent = null; 2690 return; 2691 } 2692 nextTouchEvent = new TouchEvent( 2693 TouchEvent.ANY, null, null, 0, 2694 _shiftDown, _controlDown, _altDown, _metaDown); 2695 if (touchPoints == null || touchPoints.length != touchCount) { 2696 touchPoints = new TouchPoint[touchCount]; 2697 } 2698 touchPointIndex = 0; 2699 } 2700 2701 @Override 2702 public void touchEventNext( 2703 TouchPoint.State state, long touchId, 2704 int x, int y, int xAbs, int yAbs) { 2705 2706 inMousePick = true; 2707 if (nextTouchEvent == null) { 2708 // ignore indirect touch events 2709 return; 2710 } 2711 touchPointIndex++; 2712 int id = (state == TouchPoint.State.PRESSED 2713 ? touchMap.add(touchId) : touchMap.get(touchId)); 2714 if (state == TouchPoint.State.RELEASED) { 2715 touchMap.remove(touchId); 2716 } 2717 int order = touchMap.getOrder(id); 2718 2719 if (order >= touchPoints.length) { 2720 throw new RuntimeException("Too many touch points reported"); 2721 } 2722 2723 // pick target 2724 boolean isGrabbed = false; 2725 PickResult pickRes = pick(x, y); 2726 EventTarget pickedTarget = touchTargets.get(id); 2727 if (pickedTarget == null) { 2728 pickedTarget = pickRes.getIntersectedNode(); 2729 if (pickedTarget == null) { 2730 pickedTarget = Scene.this; 2731 } 2732 } else { 2733 isGrabbed = true; 2734 } 2735 2736 TouchPoint tp = new TouchPoint(id, state, 2737 x, y, xAbs, yAbs, pickedTarget, pickRes); 2738 2739 touchPoints[order] = tp; 2740 2741 if (isGrabbed) { 2742 tp.grab(pickedTarget); 2743 } 2744 if (tp.getState() == TouchPoint.State.PRESSED) { 2745 tp.grab(pickedTarget); 2746 touchTargets.put(tp.getId(), pickedTarget); 2747 } else if (tp.getState() == TouchPoint.State.RELEASED) { 2748 touchTargets.remove(tp.getId()); 2749 } 2750 inMousePick = false; 2751 } 2752 2753 @Override 2754 public void touchEventEnd() { 2755 if (nextTouchEvent == null) { 2756 // ignore indirect touch events 2757 return; 2758 } 2759 2760 if (touchPointIndex != touchPoints.length) { 2761 throw new RuntimeException("Wrong number of touch points reported"); 2762 } 2763 2764 Scene.this.processTouchEvent(nextTouchEvent, touchPoints); 2765 2766 if (touchMap.cleanup()) { 2767 // gesture finished 2768 touchEventSetId = 0; 2769 } 2770 } 2771 2772 @Override 2773 public Accessible getSceneAccessible() { 2774 return getAccessible(); 2775 } 2776 } 2777 2778 private class ScenePeerPaintListener implements TKScenePaintListener { 2779 @Override 2780 public void frameRendered() { 2781 // must use tracker with synchronization since this method is called on render thread 2782 synchronized (trackerMonitor) { 2783 if (Scene.this.tracker != null) { 2784 Scene.this.tracker.frameRendered(); 2785 } 2786 } 2787 } 2788 } 2789 2790 /******************************************************************************* 2791 * * 2792 * Drag and Drop * 2793 * * 2794 ******************************************************************************/ 2795 2796 class DropTargetListener implements TKDropTargetListener { 2797 2798 /* 2799 * This function is called when an drag operation enters a valid drop target. 2800 * This may be from either an internal or external dnd operation. 2801 */ 2802 @Override 2803 public TransferMode dragEnter(double x, double y, double screenX, double screenY, 2804 TransferMode transferMode, TKClipboard dragboard) 2805 { 2806 if (dndGesture == null) { 2807 dndGesture = new DnDGesture(); 2808 } 2809 Dragboard db = Dragboard.impl_createDragboard(dragboard); 2810 dndGesture.dragboard = db; 2811 DragEvent dragEvent = 2812 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2813 transferMode, null, null, pick(x, y)); 2814 return dndGesture.processTargetEnterOver(dragEvent); 2815 } 2816 2817 @Override 2818 public TransferMode dragOver(double x, double y, double screenX, double screenY, 2819 TransferMode transferMode) 2820 { 2821 if (Scene.this.dndGesture == null) { 2822 System.err.println("GOT A dragOver when dndGesture is null!"); 2823 return null; 2824 } else { 2825 if (dndGesture.dragboard == null) { 2826 throw new RuntimeException("dndGesture.dragboard is null in dragOver"); 2827 } 2828 DragEvent dragEvent = 2829 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2830 transferMode, null, null, pick(x, y)); 2831 return dndGesture.processTargetEnterOver(dragEvent); 2832 } 2833 } 2834 2835 @Override 2836 public void dragExit(double x, double y, double screenX, double screenY) { 2837 if (dndGesture == null) { 2838 System.err.println("GOT A dragExit when dndGesture is null!"); 2839 } else { 2840 if (dndGesture.dragboard == null) { 2841 throw new RuntimeException("dndGesture.dragboard is null in dragExit"); 2842 } 2843 DragEvent dragEvent = 2844 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2845 null, null, null, pick(x, y)); 2846 dndGesture.processTargetExit(dragEvent); 2847 if (dndGesture.source == null) { 2848 dndGesture.dragboard = null; 2849 dndGesture = null; 2850 } 2851 } 2852 } 2853 2854 2855 @Override 2856 public TransferMode drop(double x, double y, double screenX, double screenY, 2857 TransferMode transferMode) 2858 { 2859 if (dndGesture == null) { 2860 System.err.println("GOT A drop when dndGesture is null!"); 2861 return null; 2862 } else { 2863 if (dndGesture.dragboard == null) { 2864 throw new RuntimeException("dndGesture.dragboard is null in dragDrop"); 2865 } 2866 DragEvent dragEvent = 2867 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2868 transferMode, null, null, pick(x, y)); 2869 // Data dropped to the app can be accessed without restriction 2870 DragboardHelper.setDataAccessRestriction(dndGesture.dragboard, false); 2871 2872 TransferMode tm; 2873 try { 2874 tm = dndGesture.processTargetDrop(dragEvent); 2875 } finally { 2876 DragboardHelper.setDataAccessRestriction( 2877 dndGesture.dragboard, true); 2878 } 2879 2880 if (dndGesture.source == null) { 2881 dndGesture.dragboard = null; 2882 dndGesture = null; 2883 } 2884 return tm; 2885 } 2886 } 2887 } 2888 2889 class DragGestureListener implements TKDragGestureListener { 2890 2891 @Override 2892 public void dragGestureRecognized(double x, double y, double screenX, double screenY, 2893 int button, TKClipboard dragboard) 2894 { 2895 Dragboard db = Dragboard.impl_createDragboard(dragboard); 2896 dndGesture = new DnDGesture(); 2897 dndGesture.dragboard = db; 2898 // TODO: support mouse buttons in DragEvent 2899 DragEvent dragEvent = new DragEvent(DragEvent.ANY, db, x, y, screenX, screenY, 2900 null, null, null, pick(x, y)); 2901 dndGesture.processRecognized(dragEvent); 2902 dndGesture = null; 2903 } 2904 } 2905 2906 /** 2907 * A Drag and Drop gesture has a lifespan that lasts from mouse 2908 * PRESSED event to mouse RELEASED event. 2909 */ 2910 class DnDGesture { 2911 private final double hysteresisSizeX = 2912 Toolkit.getToolkit().getMultiClickMaxX(); 2913 private final double hysteresisSizeY = 2914 Toolkit.getToolkit().getMultiClickMaxY(); 2915 2916 private EventTarget source = null; 2917 private Set<TransferMode> sourceTransferModes = null; 2918 private TransferMode acceptedTransferMode = null; 2919 private Dragboard dragboard = null; 2920 private EventTarget potentialTarget = null; 2921 private EventTarget target = null; 2922 private DragDetectedState dragDetected = DragDetectedState.NOT_YET; 2923 private double pressedX; 2924 private double pressedY; 2925 private List<EventTarget> currentTargets = new ArrayList<EventTarget>(); 2926 private List<EventTarget> newTargets = new ArrayList<EventTarget>(); 2927 private EventTarget fullPDRSource = null; 2928 2929 /** 2930 * Fires event on a given target or on scene if the node is null 2931 */ 2932 private void fireEvent(EventTarget target, Event e) { 2933 if (target != null) { 2934 Event.fireEvent(target, e); 2935 } 2936 } 2937 2938 /** 2939 * Called when DRAG_DETECTED event is going to be processed by 2940 * application 2941 */ 2942 private void processingDragDetected() { 2943 dragDetected = DragDetectedState.PROCESSING; 2944 } 2945 2946 /** 2947 * Called after DRAG_DETECTED event has been processed by application 2948 */ 2949 private void dragDetectedProcessed() { 2950 dragDetected = DragDetectedState.DONE; 2951 final boolean hasContent = (dragboard != null) && (dragboard.impl_contentPut()); 2952 if (hasContent) { 2953 /* start DnD */ 2954 Toolkit.getToolkit().startDrag(Scene.this.impl_peer, 2955 sourceTransferModes, 2956 new DragSourceListener(), 2957 dragboard); 2958 } else if (fullPDRSource != null) { 2959 /* start PDR */ 2960 Scene.this.mouseHandler.enterFullPDR(fullPDRSource); 2961 } 2962 2963 fullPDRSource = null; 2964 } 2965 2966 /** 2967 * Sets the default dragDetect value 2968 */ 2969 private void processDragDetection(MouseEvent mouseEvent) { 2970 2971 if (dragDetected != DragDetectedState.NOT_YET) { 2972 mouseEvent.setDragDetect(false); 2973 return; 2974 } 2975 2976 if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) { 2977 pressedX = mouseEvent.getSceneX(); 2978 pressedY = mouseEvent.getSceneY(); 2979 2980 mouseEvent.setDragDetect(false); 2981 2982 } else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) { 2983 2984 double deltaX = Math.abs(mouseEvent.getSceneX() - pressedX); 2985 double deltaY = Math.abs(mouseEvent.getSceneY() - pressedY); 2986 mouseEvent.setDragDetect(deltaX > hysteresisSizeX || 2987 deltaY > hysteresisSizeY); 2988 2989 } 2990 } 2991 2992 /** 2993 * This function is useful for drag gesture recognition from 2994 * within this Scene (as opposed to in the TK implementation... by the platform) 2995 */ 2996 private boolean process(MouseEvent mouseEvent, EventTarget target) { 2997 boolean continueProcessing = true; 2998 if (!PLATFORM_DRAG_GESTURE_INITIATION) { 2999 3000 if (dragDetected != DragDetectedState.DONE && 3001 (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED || 3002 mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) && 3003 mouseEvent.isDragDetect()) { 3004 3005 processingDragDetected(); 3006 3007 if (target != null) { 3008 final MouseEvent detectedEvent = mouseEvent.copyFor( 3009 mouseEvent.getSource(), target, 3010 MouseEvent.DRAG_DETECTED); 3011 3012 try { 3013 fireEvent(target, detectedEvent); 3014 } finally { 3015 // Putting data to dragboard finished, restrict access to them 3016 if (dragboard != null) { 3017 DragboardHelper.setDataAccessRestriction( 3018 dragboard, true); 3019 } 3020 } 3021 } 3022 3023 dragDetectedProcessed(); 3024 } 3025 3026 if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED) { 3027 continueProcessing = false; 3028 } 3029 } 3030 return continueProcessing; 3031 } 3032 3033 /* 3034 * Called when a drag source is recognized. This occurs at the very start of 3035 * the publicly visible drag and drop API, as it is responsible for calling 3036 * the Node.onDragSourceRecognized function. 3037 */ 3038 private boolean processRecognized(DragEvent de) { 3039 MouseEvent me = new MouseEvent( 3040 MouseEvent.DRAG_DETECTED, de.getX(), de.getY(), 3041 de.getSceneX(), de.getScreenY(), MouseButton.PRIMARY, 1, 3042 false, false, false, false, false, true, false, false, false, 3043 false, de.getPickResult()); 3044 3045 processingDragDetected(); 3046 3047 final EventTarget target = de.getPickResult().getIntersectedNode(); 3048 try { 3049 fireEvent(target != null ? target : Scene.this, me); 3050 } finally { 3051 // Putting data to dragboard finished, restrict access to them 3052 if (dragboard != null) { 3053 DragboardHelper.setDataAccessRestriction( 3054 dragboard, true); 3055 } 3056 } 3057 3058 dragDetectedProcessed(); 3059 3060 final boolean hasContent = dragboard != null 3061 && !dragboard.getContentTypes().isEmpty(); 3062 return hasContent; 3063 } 3064 3065 private void processDropEnd(DragEvent de) { 3066 if (source == null) { 3067 System.out.println("Scene.DnDGesture.processDropEnd() - UNEXPECTD - source is NULL"); 3068 return; 3069 } 3070 3071 de = new DragEvent(de.getSource(), source, DragEvent.DRAG_DONE, 3072 de.getDragboard(), de.getSceneX(), de.getSceneY(), 3073 de.getScreenX(), de.getScreenY(), 3074 de.getTransferMode(), source, target, de.getPickResult()); 3075 3076 Event.fireEvent(source, de); 3077 3078 tmpTargetWrapper.clear(); 3079 handleExitEnter(de, tmpTargetWrapper); 3080 3081 // at this point the drag and drop operation is completely over, so we 3082 // can tell the toolkit that it can clean up if needs be. 3083 Toolkit.getToolkit().stopDrag(dragboard); 3084 } 3085 3086 private TransferMode processTargetEnterOver(DragEvent de) { 3087 pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY()); 3088 final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget(); 3089 3090 if (dragboard == null) { 3091 dragboard = createDragboard(de, false); 3092 } 3093 3094 de = new DragEvent(de.getSource(), pickedTarget, de.getEventType(), 3095 dragboard, de.getSceneX(), de.getSceneY(), 3096 de.getScreenX(), de.getScreenY(), 3097 de.getTransferMode(), source, potentialTarget, de.getPickResult()); 3098 3099 handleExitEnter(de, tmpTargetWrapper); 3100 3101 de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_OVER, 3102 de.getDragboard(), de.getSceneX(), de.getSceneY(), 3103 de.getScreenX(), de.getScreenY(), 3104 de.getTransferMode(), source, potentialTarget, de.getPickResult()); 3105 3106 fireEvent(pickedTarget, de); 3107 3108 Object acceptingObject = de.getAcceptingObject(); 3109 potentialTarget = acceptingObject instanceof EventTarget 3110 ? (EventTarget) acceptingObject : null; 3111 acceptedTransferMode = de.getAcceptedTransferMode(); 3112 return acceptedTransferMode; 3113 } 3114 3115 private void processTargetActionChanged(DragEvent de) { 3116 // Do we want DRAG_TRANSFER_MODE_CHANGED event? 3117 // final Node pickedNode = Scene.this.mouseHandler.pickNode(de.getX(), de.getY()); 3118 // if (pickedNode != null && pickedNode.impl_isTreeVisible()) { 3119 // de = DragEvent.impl_copy(de.getSource(), pickedNode, source, 3120 // pickedNode, de, DragEvent.DRAG_TRANSFER_MODE_CHANGED); 3121 // 3122 // if (dragboard == null) { 3123 // dragboard = createDragboard(de); 3124 // } 3125 // dragboard = de.impl_getPlatformDragboard(); 3126 // 3127 // fireEvent(pickedNode, de); 3128 // } 3129 } 3130 3131 private void processTargetExit(DragEvent de) { 3132 if (dragboard == null) { 3133 // dragboard should have been created in processTargetEnterOver() 3134 throw new NullPointerException("dragboard is null in processTargetExit()"); 3135 } 3136 3137 if (currentTargets.size() > 0) { 3138 potentialTarget = null; 3139 tmpTargetWrapper.clear(); 3140 handleExitEnter(de, tmpTargetWrapper); 3141 } 3142 } 3143 3144 private TransferMode processTargetDrop(DragEvent de) { 3145 pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY()); 3146 final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget(); 3147 3148 de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_DROPPED, 3149 de.getDragboard(), de.getSceneX(), de.getSceneY(), 3150 de.getScreenX(), de.getScreenY(), 3151 acceptedTransferMode, source, potentialTarget, de.getPickResult()); 3152 3153 if (dragboard == null) { 3154 // dragboard should have been created in processTargetEnterOver() 3155 throw new NullPointerException("dragboard is null in processTargetDrop()"); 3156 } 3157 3158 handleExitEnter(de, tmpTargetWrapper); 3159 3160 fireEvent(pickedTarget, de); 3161 3162 Object acceptingObject = de.getAcceptingObject(); 3163 potentialTarget = acceptingObject instanceof EventTarget 3164 ? (EventTarget) acceptingObject : null; 3165 target = potentialTarget; 3166 3167 TransferMode result = de.isDropCompleted() ? 3168 de.getAcceptedTransferMode() : null; 3169 3170 tmpTargetWrapper.clear(); 3171 handleExitEnter(de, tmpTargetWrapper); 3172 3173 return result; 3174 } 3175 3176 private void handleExitEnter(DragEvent e, TargetWrapper target) { 3177 EventTarget currentTarget = 3178 currentTargets.size() > 0 ? currentTargets.get(0) : null; 3179 3180 if (target.getEventTarget() != currentTarget) { 3181 3182 target.fillHierarchy(newTargets); 3183 3184 int i = currentTargets.size() - 1; 3185 int j = newTargets.size() - 1; 3186 3187 while (i >= 0 && j >= 0 && currentTargets.get(i) == newTargets.get(j)) { 3188 i--; 3189 j--; 3190 } 3191 3192 for (; i >= 0; i--) { 3193 EventTarget t = currentTargets.get(i); 3194 if (potentialTarget == t) { 3195 potentialTarget = null; 3196 } 3197 e = e.copyFor(e.getSource(), t, source, 3198 potentialTarget, DragEvent.DRAG_EXITED_TARGET); 3199 Event.fireEvent(t, e); 3200 } 3201 3202 potentialTarget = null; 3203 for (; j >= 0; j--) { 3204 EventTarget t = newTargets.get(j); 3205 e = e.copyFor(e.getSource(), t, source, 3206 potentialTarget, DragEvent.DRAG_ENTERED_TARGET); 3207 Object acceptingObject = e.getAcceptingObject(); 3208 if (acceptingObject instanceof EventTarget) { 3209 potentialTarget = (EventTarget) acceptingObject; 3210 } 3211 Event.fireEvent(t, e); 3212 } 3213 3214 currentTargets.clear(); 3215 currentTargets.addAll(newTargets); 3216 newTargets.clear(); 3217 } 3218 } 3219 3220 // function getIntendedTransferMode(e:MouseEvent):TransferMode { 3221 // return if (e.altDown) TransferMode.COPY else TransferMode.MOVE; 3222 // } 3223 3224 /* 3225 * Function that hooks into the key processing code in Scene to handle the 3226 * situation where a drag and drop event is taking place and the user presses 3227 * the escape key to cancel the drag and drop operation. 3228 */ 3229 private boolean processKey(KeyEvent e) { 3230 //note: this seems not to be called, the DnD cancelation is provided by platform 3231 if ((e.getEventType() == KeyEvent.KEY_PRESSED) && (e.getCode() == KeyCode.ESCAPE)) { 3232 3233 // cancel drag and drop 3234 DragEvent de = new DragEvent( 3235 source, source, DragEvent.DRAG_DONE, dragboard, 0, 0, 0, 0, 3236 null, source, null, null); 3237 if (source != null) { 3238 Event.fireEvent(source, de); 3239 } 3240 3241 tmpTargetWrapper.clear(); 3242 handleExitEnter(de, tmpTargetWrapper); 3243 3244 return false; 3245 } 3246 return true; 3247 } 3248 3249 /* 3250 * This starts the drag gesture running, creating the dragboard used for 3251 * the remainder of this drag and drop operation. 3252 */ 3253 private Dragboard startDrag(EventTarget source, Set<TransferMode> t) { 3254 if (dragDetected != DragDetectedState.PROCESSING) { 3255 throw new IllegalStateException("Cannot start drag and drop " 3256 + "outside of DRAG_DETECTED event handler"); 3257 } 3258 3259 if (t.isEmpty()) { 3260 dragboard = null; 3261 } else if (dragboard == null) { 3262 dragboard = createDragboard(null, true); 3263 } 3264 3265 // The app can see what it puts to dragboard without restriction 3266 DragboardHelper.setDataAccessRestriction(dragboard, false); 3267 3268 this.source = source; 3269 potentialTarget = source; 3270 sourceTransferModes = t; 3271 return dragboard; 3272 } 3273 3274 /* 3275 * This starts the full PDR gesture. 3276 */ 3277 private void startFullPDR(EventTarget source) { 3278 fullPDRSource = source; 3279 } 3280 3281 private Dragboard createDragboard(final DragEvent de, boolean isDragSource) { 3282 Dragboard dragboard = null; 3283 if (de != null) { 3284 dragboard = de.getDragboard(); 3285 if (dragboard != null) { 3286 return dragboard; 3287 } 3288 } 3289 TKClipboard dragboardPeer = impl_peer.createDragboard(isDragSource); 3290 return Dragboard.impl_createDragboard(dragboardPeer); 3291 } 3292 } 3293 3294 /** 3295 * State of a drag gesture with regards to DRAG_DETECTED event. 3296 */ 3297 private enum DragDetectedState { 3298 NOT_YET, 3299 PROCESSING, 3300 DONE 3301 } 3302 3303 class DragSourceListener implements TKDragSourceListener { 3304 3305 @Override 3306 public void dragDropEnd(double x, double y, double screenX, double screenY, 3307 TransferMode transferMode) 3308 { 3309 if (dndGesture != null) { 3310 if (dndGesture.dragboard == null) { 3311 throw new RuntimeException("dndGesture.dragboard is null in dragDropEnd"); 3312 } 3313 DragEvent dragEvent = 3314 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 3315 transferMode, null, null, null); 3316 3317 // DRAG_DONE event is delivered to gesture source, it can access 3318 // its own data without restriction 3319 DragboardHelper.setDataAccessRestriction(dndGesture.dragboard, false); 3320 try { 3321 dndGesture.processDropEnd(dragEvent); 3322 } finally { 3323 DragboardHelper.setDataAccessRestriction(dndGesture.dragboard, true); 3324 } 3325 dndGesture = null; 3326 } 3327 } 3328 } 3329 3330 /******************************************************************************* 3331 * * 3332 * Mouse Event Handling * 3333 * * 3334 ******************************************************************************/ 3335 3336 static class ClickCounter { 3337 Toolkit toolkit = Toolkit.getToolkit(); 3338 private int count; 3339 private boolean out; 3340 private boolean still; 3341 private Timeline timeout; 3342 private double pressedX, pressedY; 3343 3344 private void inc() { count++; } 3345 private int get() { return count; } 3346 private boolean isStill() { return still; } 3347 3348 private void clear() { 3349 count = 0; 3350 stopTimeout(); 3351 } 3352 3353 private void out() { 3354 out = true; 3355 stopTimeout(); 3356 } 3357 3358 private void applyOut() { 3359 if (out) clear(); 3360 out = false; 3361 } 3362 3363 private void moved(double x, double y) { 3364 if (Math.abs(x - pressedX) > toolkit.getMultiClickMaxX() || 3365 Math.abs(y - pressedY) > toolkit.getMultiClickMaxY()) { 3366 out(); 3367 still = false; 3368 } 3369 } 3370 3371 private void start(double x, double y) { 3372 pressedX = x; 3373 pressedY = y; 3374 out = false; 3375 3376 if (timeout != null) { 3377 timeout.stop(); 3378 } 3379 timeout = new Timeline(); 3380 timeout.getKeyFrames().add( 3381 new KeyFrame(new Duration(toolkit.getMultiClickTime()), 3382 event -> { 3383 out = true; 3384 timeout = null; 3385 } 3386 )); 3387 timeout.play(); 3388 still = true; 3389 } 3390 3391 private void stopTimeout() { 3392 if (timeout != null) { 3393 timeout.stop(); 3394 timeout = null; 3395 } 3396 } 3397 } 3398 3399 static class ClickGenerator { 3400 private ClickCounter lastPress = null; 3401 3402 private Map<MouseButton, ClickCounter> counters = 3403 new EnumMap<MouseButton, ClickCounter>(MouseButton.class); 3404 private List<EventTarget> pressedTargets = new ArrayList<EventTarget>(); 3405 private List<EventTarget> releasedTargets = new ArrayList<EventTarget>(); 3406 3407 public ClickGenerator() { 3408 for (MouseButton mb : MouseButton.values()) { 3409 if (mb != MouseButton.NONE) { 3410 counters.put(mb, new ClickCounter()); 3411 } 3412 } 3413 } 3414 3415 private MouseEvent preProcess(MouseEvent e) { 3416 for (ClickCounter cc : counters.values()) { 3417 cc.moved(e.getSceneX(), e.getSceneY()); 3418 } 3419 3420 ClickCounter cc = counters.get(e.getButton()); 3421 boolean still = lastPress != null ? lastPress.isStill() : false; 3422 3423 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { 3424 3425 if (! e.isPrimaryButtonDown()) { counters.get(MouseButton.PRIMARY).clear(); } 3426 if (! e.isSecondaryButtonDown()) { counters.get(MouseButton.SECONDARY).clear(); } 3427 if (! e.isMiddleButtonDown()) { counters.get(MouseButton.MIDDLE).clear(); } 3428 3429 cc.applyOut(); 3430 cc.inc(); 3431 cc.start(e.getSceneX(), e.getSceneY()); 3432 lastPress = cc; 3433 } 3434 3435 return new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(), 3436 e.getScreenX(), e.getScreenY(), e.getButton(), 3437 cc != null && e.getEventType() != MouseEvent.MOUSE_MOVED ? cc.get() : 0, 3438 e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(), 3439 e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(), 3440 e.isSynthesized(), e.isPopupTrigger(), still, e.getPickResult()); 3441 } 3442 3443 private void postProcess(MouseEvent e, TargetWrapper target, TargetWrapper pickedTarget) { 3444 3445 if (e.getEventType() == MouseEvent.MOUSE_RELEASED) { 3446 ClickCounter cc = counters.get(e.getButton()); 3447 3448 target.fillHierarchy(pressedTargets); 3449 pickedTarget.fillHierarchy(releasedTargets); 3450 int i = pressedTargets.size() - 1; 3451 int j = releasedTargets.size() - 1; 3452 3453 EventTarget clickedTarget = null; 3454 while (i >= 0 && j >= 0 && pressedTargets.get(i) == releasedTargets.get(j)) { 3455 clickedTarget = pressedTargets.get(i); 3456 i--; 3457 j--; 3458 } 3459 3460 pressedTargets.clear(); 3461 releasedTargets.clear(); 3462 3463 if (clickedTarget != null && lastPress != null) { 3464 MouseEvent click = new MouseEvent(null, clickedTarget, 3465 MouseEvent.MOUSE_CLICKED, e.getSceneX(), e.getSceneY(), 3466 e.getScreenX(), e.getScreenY(), e.getButton(), 3467 cc.get(), 3468 e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(), 3469 e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(), 3470 e.isSynthesized(), e.isPopupTrigger(), lastPress.isStill(), e.getPickResult()); 3471 Event.fireEvent(clickedTarget, click); 3472 } 3473 } 3474 } 3475 } 3476 3477 /** 3478 * Generates mouse exited event for a node which is going to be removed 3479 * and its children, where appropriate. 3480 * @param removing Node which is going to be removed 3481 */ 3482 void generateMouseExited(Node removing) { 3483 mouseHandler.handleNodeRemoval(removing); 3484 } 3485 3486 class MouseHandler { 3487 private TargetWrapper pdrEventTarget = new TargetWrapper(); // pdr - press-drag-release 3488 private boolean pdrInProgress = false; 3489 private boolean fullPDREntered = false; 3490 3491 private EventTarget currentEventTarget = null; 3492 private MouseEvent lastEvent; 3493 private boolean hover = false; 3494 3495 private boolean primaryButtonDown = false; 3496 private boolean secondaryButtonDown = false; 3497 private boolean middleButtonDown = false; 3498 3499 private EventTarget fullPDRSource = null; 3500 private TargetWrapper fullPDRTmpTargetWrapper = new TargetWrapper(); 3501 3502 /* lists needed for enter/exit events generation */ 3503 private final List<EventTarget> pdrEventTargets = new ArrayList<EventTarget>(); 3504 private final List<EventTarget> currentEventTargets = new ArrayList<EventTarget>(); 3505 private final List<EventTarget> newEventTargets = new ArrayList<EventTarget>(); 3506 3507 private final List<EventTarget> fullPDRCurrentEventTargets = new ArrayList<EventTarget>(); 3508 private final List<EventTarget> fullPDRNewEventTargets = new ArrayList<EventTarget>(); 3509 private EventTarget fullPDRCurrentTarget = null; 3510 3511 private Cursor currCursor; 3512 private CursorFrame currCursorFrame; 3513 private EventQueue queue = new EventQueue(); 3514 3515 private Runnable pickProcess = new Runnable() { 3516 3517 @Override 3518 public void run() { 3519 // Make sure this is run only if the peer is still alive 3520 // and there is an event to deliver 3521 if (Scene.this.impl_peer != null && lastEvent != null) { 3522 process(lastEvent, true); 3523 } 3524 } 3525 }; 3526 3527 private void pulse() { 3528 if (hover && lastEvent != null) { 3529 //Shouldn't run user code directly. User can call stage.showAndWait() and block the pulse. 3530 Platform.runLater(pickProcess); 3531 } 3532 } 3533 3534 private void clearPDREventTargets() { 3535 pdrInProgress = false; 3536 currentEventTarget = currentEventTargets.size() > 0 3537 ? currentEventTargets.get(0) : null; 3538 pdrEventTarget.clear(); 3539 } 3540 3541 public void enterFullPDR(EventTarget gestureSource) { 3542 fullPDREntered = true; 3543 fullPDRSource = gestureSource; 3544 fullPDRCurrentTarget = null; 3545 fullPDRCurrentEventTargets.clear(); 3546 } 3547 3548 public void exitFullPDR(MouseEvent e) { 3549 if (!fullPDREntered) { 3550 return; 3551 } 3552 fullPDREntered = false; 3553 for (int i = fullPDRCurrentEventTargets.size() - 1; i >= 0; i--) { 3554 EventTarget entered = fullPDRCurrentEventTargets.get(i); 3555 Event.fireEvent(entered, MouseEvent.copyForMouseDragEvent(e, 3556 entered, entered, 3557 MouseDragEvent.MOUSE_DRAG_EXITED_TARGET, 3558 fullPDRSource, e.getPickResult())); 3559 } 3560 fullPDRSource = null; 3561 fullPDRCurrentEventTargets.clear(); 3562 fullPDRCurrentTarget = null; 3563 } 3564 3565 private void handleNodeRemoval(Node removing) { 3566 if (lastEvent == null) { 3567 // this can happen only if everything has been exited anyway 3568 return; 3569 } 3570 3571 3572 if (currentEventTargets.contains(removing)) { 3573 int i = 0; 3574 EventTarget trg = null; 3575 while(trg != removing) { 3576 trg = currentEventTargets.get(i++); 3577 3578 queue.postEvent(lastEvent.copyFor(trg, trg, 3579 MouseEvent.MOUSE_EXITED_TARGET)); 3580 } 3581 currentEventTargets.subList(0, i).clear(); 3582 } 3583 3584 if (fullPDREntered && fullPDRCurrentEventTargets.contains(removing)) { 3585 int i = 0; 3586 EventTarget trg = null; 3587 while (trg != removing) { 3588 trg = fullPDRCurrentEventTargets.get(i++); 3589 3590 queue.postEvent( 3591 MouseEvent.copyForMouseDragEvent(lastEvent, trg, trg, 3592 MouseDragEvent.MOUSE_DRAG_EXITED_TARGET, 3593 fullPDRSource, lastEvent.getPickResult())); 3594 } 3595 3596 fullPDRCurrentEventTargets.subList(0, i).clear(); 3597 } 3598 3599 queue.fire(); 3600 3601 if (pdrInProgress && pdrEventTargets.contains(removing)) { 3602 int i = 0; 3603 EventTarget trg = null; 3604 while (trg != removing) { 3605 trg = pdrEventTargets.get(i++); 3606 3607 // trg.setHover(false) - already taken care of 3608 // by the code above which sent a mouse exited event 3609 ((Node) trg).setPressed(false); 3610 } 3611 pdrEventTargets.subList(0, i).clear(); 3612 3613 trg = pdrEventTargets.get(0); 3614 final PickResult res = pdrEventTarget.getResult(); 3615 if (trg instanceof Node) { 3616 pdrEventTarget.setNodeResult(new PickResult((Node) trg, 3617 res.getIntersectedPoint(), res.getIntersectedDistance())); 3618 } else { 3619 pdrEventTarget.setSceneResult(new PickResult(null, 3620 res.getIntersectedPoint(), res.getIntersectedDistance()), 3621 (Scene) trg); 3622 } 3623 } 3624 } 3625 3626 private void handleEnterExit(MouseEvent e, TargetWrapper pickedTarget) { 3627 if (pickedTarget.getEventTarget() != currentEventTarget || 3628 e.getEventType() == MouseEvent.MOUSE_EXITED) { 3629 3630 if (e.getEventType() == MouseEvent.MOUSE_EXITED) { 3631 newEventTargets.clear(); 3632 } else { 3633 pickedTarget.fillHierarchy(newEventTargets); 3634 } 3635 3636 int newTargetsSize = newEventTargets.size(); 3637 int i = currentEventTargets.size() - 1; 3638 int j = newTargetsSize - 1; 3639 int k = pdrEventTargets.size() - 1; 3640 3641 while (i >= 0 && j >= 0 && currentEventTargets.get(i) == newEventTargets.get(j)) { 3642 i--; 3643 j--; 3644 k--; 3645 } 3646 3647 final int memk = k; 3648 for (; i >= 0; i--, k--) { 3649 final EventTarget exitedEventTarget = currentEventTargets.get(i); 3650 if (pdrInProgress && 3651 (k < 0 || exitedEventTarget != pdrEventTargets.get(k))) { 3652 break; 3653 } 3654 queue.postEvent(e.copyFor( 3655 exitedEventTarget, exitedEventTarget, 3656 MouseEvent.MOUSE_EXITED_TARGET)); 3657 } 3658 3659 k = memk; 3660 for (; j >= 0; j--, k--) { 3661 final EventTarget enteredEventTarget = newEventTargets.get(j); 3662 if (pdrInProgress && 3663 (k < 0 || enteredEventTarget != pdrEventTargets.get(k))) { 3664 break; 3665 } 3666 queue.postEvent(e.copyFor( 3667 enteredEventTarget, enteredEventTarget, 3668 MouseEvent.MOUSE_ENTERED_TARGET)); 3669 } 3670 3671 currentEventTarget = pickedTarget.getEventTarget(); 3672 currentEventTargets.clear(); 3673 for (j++; j < newTargetsSize; j++) { 3674 currentEventTargets.add(newEventTargets.get(j)); 3675 } 3676 } 3677 queue.fire(); 3678 } 3679 3680 private void process(MouseEvent e, boolean onPulse) { 3681 Toolkit.getToolkit().checkFxUserThread(); 3682 Scene.inMousePick = true; 3683 3684 cursorScreenPos = new Point2D(e.getScreenX(), e.getScreenY()); 3685 cursorScenePos = new Point2D(e.getSceneX(), e.getSceneY()); 3686 3687 boolean gestureStarted = false; 3688 if (!onPulse) { 3689 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { 3690 if (!(primaryButtonDown || secondaryButtonDown || middleButtonDown)) { 3691 //old gesture ended and new one started 3692 gestureStarted = true; 3693 if (!PLATFORM_DRAG_GESTURE_INITIATION) { 3694 Scene.this.dndGesture = new DnDGesture(); 3695 } 3696 clearPDREventTargets(); 3697 } 3698 } else if (e.getEventType() == MouseEvent.MOUSE_MOVED) { 3699 // gesture ended 3700 clearPDREventTargets(); 3701 } else if (e.getEventType() == MouseEvent.MOUSE_ENTERED) { 3702 hover = true; 3703 } else if (e.getEventType() == MouseEvent.MOUSE_EXITED) { 3704 hover = false; 3705 } 3706 3707 primaryButtonDown = e.isPrimaryButtonDown(); 3708 secondaryButtonDown = e.isSecondaryButtonDown(); 3709 middleButtonDown = e.isMiddleButtonDown(); 3710 } 3711 3712 pick(tmpTargetWrapper, e.getSceneX(), e.getSceneY()); 3713 PickResult res = tmpTargetWrapper.getResult(); 3714 if (res != null) { 3715 e = new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(), 3716 e.getScreenX(), e.getScreenY(), e.getButton(), e.getClickCount(), 3717 e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(), 3718 e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(), 3719 e.isSynthesized(), e.isPopupTrigger(), e.isStillSincePress(), res); 3720 } 3721 3722 if (e.getEventType() == MouseEvent.MOUSE_EXITED) { 3723 tmpTargetWrapper.clear(); 3724 } 3725 3726 TargetWrapper target; 3727 if (pdrInProgress) { 3728 target = pdrEventTarget; 3729 } else { 3730 target = tmpTargetWrapper; 3731 } 3732 3733 if (gestureStarted) { 3734 pdrEventTarget.copy(target); 3735 pdrEventTarget.fillHierarchy(pdrEventTargets); 3736 } 3737 3738 if (!onPulse) { 3739 e = clickGenerator.preProcess(e); 3740 } 3741 3742 // enter/exit handling 3743 handleEnterExit(e, tmpTargetWrapper); 3744 3745 //deliver event to the target node 3746 if (Scene.this.dndGesture != null) { 3747 Scene.this.dndGesture.processDragDetection(e); 3748 } 3749 3750 if (fullPDREntered && e.getEventType() == MouseEvent.MOUSE_RELEASED) { 3751 processFullPDR(e, onPulse); 3752 } 3753 3754 if (target.getEventTarget() != null) { 3755 if (e.getEventType() != MouseEvent.MOUSE_ENTERED 3756 && e.getEventType() != MouseEvent.MOUSE_EXITED 3757 && !onPulse) { 3758 Event.fireEvent(target.getEventTarget(), e); 3759 } 3760 } 3761 3762 if (fullPDREntered && e.getEventType() != MouseEvent.MOUSE_RELEASED) { 3763 processFullPDR(e, onPulse); 3764 } 3765 3766 if (!onPulse) { 3767 clickGenerator.postProcess(e, target, tmpTargetWrapper); 3768 } 3769 3770 // handle drag and drop 3771 3772 if (!PLATFORM_DRAG_GESTURE_INITIATION && !onPulse) { 3773 if (Scene.this.dndGesture != null) { 3774 if (!Scene.this.dndGesture.process(e, target.getEventTarget())) { 3775 dndGesture = null; 3776 } 3777 } 3778 } 3779 3780 Cursor cursor = target.getCursor(); 3781 if (e.getEventType() != MouseEvent.MOUSE_EXITED) { 3782 if (cursor == null && hover) { 3783 cursor = Scene.this.getCursor(); 3784 } 3785 3786 updateCursor(cursor); 3787 updateCursorFrame(); 3788 } 3789 3790 if (gestureStarted) { 3791 pdrInProgress = true; 3792 } 3793 3794 if (pdrInProgress && 3795 !(primaryButtonDown || secondaryButtonDown || middleButtonDown)) { 3796 clearPDREventTargets(); 3797 exitFullPDR(e); 3798 // we need to do new picking in case the originally picked node 3799 // was moved or removed by the event handlers 3800 pick(tmpTargetWrapper, e.getSceneX(), e.getSceneY()); 3801 handleEnterExit(e, tmpTargetWrapper); 3802 } 3803 3804 lastEvent = e.getEventType() == MouseEvent.MOUSE_EXITED ? null : e; 3805 Scene.inMousePick = false; 3806 } 3807 3808 private void processFullPDR(MouseEvent e, boolean onPulse) { 3809 3810 pick(fullPDRTmpTargetWrapper, e.getSceneX(), e.getSceneY()); 3811 final PickResult result = fullPDRTmpTargetWrapper.getResult(); 3812 3813 final EventTarget eventTarget = fullPDRTmpTargetWrapper.getEventTarget(); 3814 3815 // enter/exit handling 3816 if (eventTarget != fullPDRCurrentTarget) { 3817 3818 fullPDRTmpTargetWrapper.fillHierarchy(fullPDRNewEventTargets); 3819 3820 int newTargetsSize = fullPDRNewEventTargets.size(); 3821 int i = fullPDRCurrentEventTargets.size() - 1; 3822 int j = newTargetsSize - 1; 3823 3824 while (i >= 0 && j >= 0 && 3825 fullPDRCurrentEventTargets.get(i) == fullPDRNewEventTargets.get(j)) { 3826 i--; 3827 j--; 3828 } 3829 3830 for (; i >= 0; i--) { 3831 final EventTarget exitedEventTarget = fullPDRCurrentEventTargets.get(i); 3832 Event.fireEvent(exitedEventTarget, MouseEvent.copyForMouseDragEvent(e, 3833 exitedEventTarget, exitedEventTarget, 3834 MouseDragEvent.MOUSE_DRAG_EXITED_TARGET, 3835 fullPDRSource, result)); 3836 } 3837 3838 for (; j >= 0; j--) { 3839 final EventTarget enteredEventTarget = fullPDRNewEventTargets.get(j); 3840 Event.fireEvent(enteredEventTarget, MouseEvent.copyForMouseDragEvent(e, 3841 enteredEventTarget, enteredEventTarget, 3842 MouseDragEvent.MOUSE_DRAG_ENTERED_TARGET, 3843 fullPDRSource, result)); 3844 } 3845 3846 fullPDRCurrentTarget = eventTarget; 3847 fullPDRCurrentEventTargets.clear(); 3848 fullPDRCurrentEventTargets.addAll(fullPDRNewEventTargets); 3849 fullPDRNewEventTargets.clear(); 3850 } 3851 // done enter/exit handling 3852 3853 // event delivery 3854 if (eventTarget != null && !onPulse) { 3855 if (e.getEventType() == MouseEvent.MOUSE_DRAGGED) { 3856 Event.fireEvent(eventTarget, MouseEvent.copyForMouseDragEvent(e, 3857 eventTarget, eventTarget, 3858 MouseDragEvent.MOUSE_DRAG_OVER, 3859 fullPDRSource, result)); 3860 } 3861 if (e.getEventType() == MouseEvent.MOUSE_RELEASED) { 3862 Event.fireEvent(eventTarget, MouseEvent.copyForMouseDragEvent(e, 3863 eventTarget, eventTarget, 3864 MouseDragEvent.MOUSE_DRAG_RELEASED, 3865 fullPDRSource, result)); 3866 } 3867 } 3868 } 3869 3870 private void updateCursor(Cursor newCursor) { 3871 if (currCursor != newCursor) { 3872 if (currCursor != null) { 3873 currCursor.deactivate(); 3874 } 3875 3876 if (newCursor != null) { 3877 newCursor.activate(); 3878 } 3879 3880 currCursor = newCursor; 3881 } 3882 } 3883 3884 public void updateCursorFrame() { 3885 final CursorFrame newCursorFrame = 3886 (currCursor != null) 3887 ? currCursor.getCurrentFrame() 3888 : Cursor.DEFAULT.getCurrentFrame(); 3889 if (currCursorFrame != newCursorFrame) { 3890 if (Scene.this.impl_peer != null) { 3891 Scene.this.impl_peer.setCursor(newCursorFrame); 3892 } 3893 3894 currCursorFrame = newCursorFrame; 3895 } 3896 } 3897 3898 private PickResult pickNode(PickRay pickRay) { 3899 PickResultChooser r = new PickResultChooser(); 3900 Scene.this.getRoot().impl_pickNode(pickRay, r); 3901 return r.toPickResult(); 3902 } 3903 } 3904 3905 /******************************************************************************* 3906 * * 3907 * Key Event Handling * 3908 * * 3909 ******************************************************************************/ 3910 3911 class KeyHandler { 3912 private void setFocusOwner(final Node value) { 3913 // Cancel IM composition if there is one in progress. 3914 // This needs to be done before the focus owner is switched as it 3915 // generates event that needs to be delivered to the old focus owner. 3916 if (oldFocusOwner != null) { 3917 final Scene s = oldFocusOwner.getScene(); 3918 if (s != null) { 3919 final TKScene peer = s.impl_getPeer(); 3920 if (peer != null) { 3921 peer.finishInputMethodComposition(); 3922 } 3923 } 3924 } 3925 focusOwner.set(value); 3926 } 3927 3928 private boolean windowFocused; 3929 protected boolean isWindowFocused() { return windowFocused; } 3930 protected void setWindowFocused(boolean value) { 3931 windowFocused = value; 3932 if (getFocusOwner() != null) { 3933 getFocusOwner().setFocused(windowFocused); 3934 } 3935 if (windowFocused) { 3936 if (accessible != null) { 3937 accessible.sendNotification(AccessibleAttribute.FOCUS_NODE); 3938 } 3939 } 3940 } 3941 3942 private void windowForSceneChanged(Window oldWindow, Window window) { 3943 if (oldWindow != null) { 3944 oldWindow.focusedProperty().removeListener(sceneWindowFocusedListener); 3945 } 3946 3947 if (window != null) { 3948 window.focusedProperty().addListener(sceneWindowFocusedListener); 3949 setWindowFocused(window.isFocused()); 3950 } else { 3951 setWindowFocused(false); 3952 } 3953 } 3954 3955 private final InvalidationListener sceneWindowFocusedListener = valueModel -> setWindowFocused(((ReadOnlyBooleanProperty)valueModel).get()); 3956 3957 private void process(KeyEvent e) { 3958 final Node sceneFocusOwner = getFocusOwner(); 3959 final EventTarget eventTarget = 3960 (sceneFocusOwner != null) ? sceneFocusOwner 3961 : Scene.this; 3962 3963 // send the key event to the current focus owner or to scene if 3964 // the focus owner is not set 3965 Event.fireEvent(eventTarget, e); 3966 } 3967 3968 private void requestFocus(Node node) { 3969 if (getFocusOwner() == node || (node != null && !node.isCanReceiveFocus())) { 3970 return; 3971 } 3972 setFocusOwner(node); 3973 } 3974 } 3975 /*************************************************************************** 3976 * * 3977 * Event Dispatch * 3978 * * 3979 **************************************************************************/ 3980 // PENDING_DOC_REVIEW 3981 /** 3982 * Specifies the event dispatcher for this scene. When replacing the value 3983 * with a new {@code EventDispatcher}, the new dispatcher should forward 3984 * events to the replaced dispatcher to keep the scene's default event 3985 * handling behavior. 3986 */ 3987 private ObjectProperty<EventDispatcher> eventDispatcher; 3988 3989 public final void setEventDispatcher(EventDispatcher value) { 3990 eventDispatcherProperty().set(value); 3991 } 3992 3993 public final EventDispatcher getEventDispatcher() { 3994 return eventDispatcherProperty().get(); 3995 } 3996 3997 public final ObjectProperty<EventDispatcher> 3998 eventDispatcherProperty() { 3999 initializeInternalEventDispatcher(); 4000 return eventDispatcher; 4001 } 4002 4003 private SceneEventDispatcher internalEventDispatcher; 4004 4005 // Delegates requests from platform input method to the focused 4006 // node's one, if any. 4007 class InputMethodRequestsDelegate implements ExtendedInputMethodRequests { 4008 @Override 4009 public Point2D getTextLocation(int offset) { 4010 InputMethodRequests requests = getClientRequests(); 4011 if (requests != null) { 4012 return requests.getTextLocation(offset); 4013 } else { 4014 return new Point2D(0, 0); 4015 } 4016 } 4017 4018 @Override 4019 public int getLocationOffset(int x, int y) { 4020 InputMethodRequests requests = getClientRequests(); 4021 if (requests != null) { 4022 return requests.getLocationOffset(x, y); 4023 } else { 4024 return 0; 4025 } 4026 } 4027 4028 @Override 4029 public void cancelLatestCommittedText() { 4030 InputMethodRequests requests = getClientRequests(); 4031 if (requests != null) { 4032 requests.cancelLatestCommittedText(); 4033 } 4034 } 4035 4036 @Override 4037 public String getSelectedText() { 4038 InputMethodRequests requests = getClientRequests(); 4039 if (requests != null) { 4040 return requests.getSelectedText(); 4041 } 4042 return null; 4043 } 4044 4045 @Override 4046 public int getInsertPositionOffset() { 4047 InputMethodRequests requests = getClientRequests(); 4048 if (requests != null && requests instanceof ExtendedInputMethodRequests) { 4049 return ((ExtendedInputMethodRequests)requests).getInsertPositionOffset(); 4050 } 4051 return 0; 4052 } 4053 4054 @Override 4055 public String getCommittedText(int begin, int end) { 4056 InputMethodRequests requests = getClientRequests(); 4057 if (requests != null && requests instanceof ExtendedInputMethodRequests) { 4058 return ((ExtendedInputMethodRequests)requests).getCommittedText(begin, end); 4059 } 4060 return null; 4061 } 4062 4063 @Override 4064 public int getCommittedTextLength() { 4065 InputMethodRequests requests = getClientRequests(); 4066 if (requests != null && requests instanceof ExtendedInputMethodRequests) { 4067 return ((ExtendedInputMethodRequests)requests).getCommittedTextLength(); 4068 } 4069 return 0; 4070 } 4071 4072 private InputMethodRequests getClientRequests() { 4073 Node focusOwner = getFocusOwner(); 4074 if (focusOwner != null) { 4075 return focusOwner.getInputMethodRequests(); 4076 } 4077 return null; 4078 } 4079 } 4080 4081 // PENDING_DOC_REVIEW 4082 /** 4083 * Registers an event handler to this scene. The handler is called when the 4084 * scene receives an {@code Event} of the specified type during the bubbling 4085 * phase of event delivery. 4086 * 4087 * @param <T> the specific event class of the handler 4088 * @param eventType the type of the events to receive by the handler 4089 * @param eventHandler the handler to register 4090 * @throws NullPointerException if the event type or handler is null 4091 */ 4092 public final <T extends Event> void addEventHandler( 4093 final EventType<T> eventType, 4094 final EventHandler<? super T> eventHandler) { 4095 getInternalEventDispatcher().getEventHandlerManager() 4096 .addEventHandler(eventType, eventHandler); 4097 } 4098 4099 // PENDING_DOC_REVIEW 4100 /** 4101 * Unregisters a previously registered event handler from this scene. One 4102 * handler might have been registered for different event types, so the 4103 * caller needs to specify the particular event type from which to 4104 * unregister the handler. 4105 * 4106 * @param <T> the specific event class of the handler 4107 * @param eventType the event type from which to unregister 4108 * @param eventHandler the handler to unregister 4109 * @throws NullPointerException if the event type or handler is null 4110 */ 4111 public final <T extends Event> void removeEventHandler( 4112 final EventType<T> eventType, 4113 final EventHandler<? super T> eventHandler) { 4114 getInternalEventDispatcher().getEventHandlerManager() 4115 .removeEventHandler(eventType, 4116 eventHandler); 4117 } 4118 4119 // PENDING_DOC_REVIEW 4120 /** 4121 * Registers an event filter to this scene. The filter is called when the 4122 * scene receives an {@code Event} of the specified type during the 4123 * capturing phase of event delivery. 4124 * 4125 * @param <T> the specific event class of the filter 4126 * @param eventType the type of the events to receive by the filter 4127 * @param eventFilter the filter to register 4128 * @throws NullPointerException if the event type or filter is null 4129 */ 4130 public final <T extends Event> void addEventFilter( 4131 final EventType<T> eventType, 4132 final EventHandler<? super T> eventFilter) { 4133 getInternalEventDispatcher().getEventHandlerManager() 4134 .addEventFilter(eventType, eventFilter); 4135 } 4136 4137 // PENDING_DOC_REVIEW 4138 /** 4139 * Unregisters a previously registered event filter from this scene. One 4140 * filter might have been registered for different event types, so the 4141 * caller needs to specify the particular event type from which to 4142 * unregister the filter. 4143 * 4144 * @param <T> the specific event class of the filter 4145 * @param eventType the event type from which to unregister 4146 * @param eventFilter the filter to unregister 4147 * @throws NullPointerException if the event type or filter is null 4148 */ 4149 public final <T extends Event> void removeEventFilter( 4150 final EventType<T> eventType, 4151 final EventHandler<? super T> eventFilter) { 4152 getInternalEventDispatcher().getEventHandlerManager() 4153 .removeEventFilter(eventType, eventFilter); 4154 } 4155 4156 /** 4157 * Sets the handler to use for this event type. There can only be one such 4158 * handler specified at a time. This handler is guaranteed to be called 4159 * first. This is used for registering the user-defined onFoo event 4160 * handlers. 4161 * 4162 * @param <T> the specific event class of the handler 4163 * @param eventType the event type to associate with the given eventHandler 4164 * @param eventHandler the handler to register, or null to unregister 4165 * @throws NullPointerException if the event type is null 4166 */ 4167 protected final <T extends Event> void setEventHandler( 4168 final EventType<T> eventType, 4169 final EventHandler<? super T> eventHandler) { 4170 getInternalEventDispatcher().getEventHandlerManager() 4171 .setEventHandler(eventType, eventHandler); 4172 } 4173 4174 private SceneEventDispatcher getInternalEventDispatcher() { 4175 initializeInternalEventDispatcher(); 4176 return internalEventDispatcher; 4177 } 4178 4179 final void initializeInternalEventDispatcher() { 4180 if (internalEventDispatcher == null) { 4181 internalEventDispatcher = createInternalEventDispatcher(); 4182 eventDispatcher = new SimpleObjectProperty<EventDispatcher>( 4183 this, 4184 "eventDispatcher", 4185 internalEventDispatcher); 4186 } 4187 } 4188 4189 private SceneEventDispatcher createInternalEventDispatcher() { 4190 return new SceneEventDispatcher(this); 4191 } 4192 4193 /** 4194 * Registers the specified mnemonic. 4195 * 4196 * @param m The mnemonic 4197 */ 4198 public void addMnemonic(Mnemonic m) { 4199 getInternalEventDispatcher().getKeyboardShortcutsHandler() 4200 .addMnemonic(m); 4201 } 4202 4203 4204 /** 4205 * Unregisters the specified mnemonic. 4206 * 4207 * @param m The mnemonic 4208 */ 4209 public void removeMnemonic(Mnemonic m) { 4210 getInternalEventDispatcher().getKeyboardShortcutsHandler() 4211 .removeMnemonic(m); 4212 } 4213 4214 final void clearNodeMnemonics(Node node) { 4215 getInternalEventDispatcher().getKeyboardShortcutsHandler() 4216 .clearNodeMnemonics(node); 4217 } 4218 4219 4220 /** 4221 * Gets the list of mnemonics for this {@code Scene}. 4222 * 4223 * @return the list of mnemonics 4224 */ 4225 public ObservableMap<KeyCombination, ObservableList<Mnemonic>> getMnemonics() { 4226 return getInternalEventDispatcher().getKeyboardShortcutsHandler() 4227 .getMnemonics(); 4228 } 4229 4230 /** 4231 * Gets the list of accelerators for this {@code Scene}. 4232 * 4233 * @return the list of accelerators 4234 */ 4235 public ObservableMap<KeyCombination, Runnable> getAccelerators() { 4236 return getInternalEventDispatcher().getKeyboardShortcutsHandler() 4237 .getAccelerators(); 4238 } 4239 4240 // PENDING_DOC_REVIEW 4241 /** 4242 * Construct an event dispatch chain for this scene. The event dispatch 4243 * chain contains all event dispatchers from the stage to this scene. 4244 * 4245 * @param tail the initial chain to build from 4246 * @return the resulting event dispatch chain for this scene 4247 */ 4248 @Override 4249 public EventDispatchChain buildEventDispatchChain( 4250 EventDispatchChain tail) { 4251 if (eventDispatcher != null) { 4252 final EventDispatcher eventDispatcherValue = eventDispatcher.get(); 4253 if (eventDispatcherValue != null) { 4254 tail = tail.prepend(eventDispatcherValue); 4255 } 4256 } 4257 4258 if (getWindow() != null) { 4259 tail = getWindow().buildEventDispatchChain(tail); 4260 } 4261 4262 return tail; 4263 } 4264 4265 /*************************************************************************** 4266 * * 4267 * Context Menus * 4268 * * 4269 **************************************************************************/ 4270 4271 /** 4272 * Defines a function to be called when a mouse button has been clicked 4273 * (pressed and released) on this {@code Scene}. 4274 * @since JavaFX 2.1 4275 */ 4276 4277 private ObjectProperty<EventHandler<? super ContextMenuEvent>> onContextMenuRequested; 4278 4279 public final void setOnContextMenuRequested(EventHandler<? super ContextMenuEvent> value) { 4280 onContextMenuRequestedProperty().set(value); 4281 } 4282 4283 public final EventHandler<? super ContextMenuEvent> getOnContextMenuRequested() { 4284 return onContextMenuRequested == null ? null : onContextMenuRequested.get(); 4285 } 4286 4287 public final ObjectProperty<EventHandler<? super ContextMenuEvent>> onContextMenuRequestedProperty() { 4288 if (onContextMenuRequested == null) { 4289 onContextMenuRequested = new ObjectPropertyBase<EventHandler<? super ContextMenuEvent>>() { 4290 4291 @Override 4292 protected void invalidated() { 4293 setEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, get()); 4294 } 4295 4296 @Override 4297 public Object getBean() { 4298 return Scene.this; 4299 } 4300 4301 @Override 4302 public String getName() { 4303 return "onContextMenuRequested"; 4304 } 4305 }; 4306 } 4307 return onContextMenuRequested; 4308 } 4309 4310 /*************************************************************************** 4311 * * 4312 * Mouse Handling * 4313 * * 4314 **************************************************************************/ 4315 4316 /** 4317 * Defines a function to be called when a mouse button has been clicked 4318 * (pressed and released) on this {@code Scene}. 4319 */ 4320 private ObjectProperty<EventHandler<? super MouseEvent>> onMouseClicked; 4321 4322 public final void setOnMouseClicked(EventHandler<? super MouseEvent> value) { 4323 onMouseClickedProperty().set(value); 4324 } 4325 4326 public final EventHandler<? super MouseEvent> getOnMouseClicked() { 4327 return onMouseClicked == null ? null : onMouseClicked.get(); 4328 } 4329 4330 public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseClickedProperty() { 4331 if (onMouseClicked == null) { 4332 onMouseClicked = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4333 4334 @Override 4335 protected void invalidated() { 4336 setEventHandler(MouseEvent.MOUSE_CLICKED, get()); 4337 } 4338 4339 @Override 4340 public Object getBean() { 4341 return Scene.this; 4342 } 4343 4344 @Override 4345 public String getName() { 4346 return "onMouseClicked"; 4347 } 4348 }; 4349 } 4350 return onMouseClicked; 4351 } 4352 4353 /** 4354 * Defines a function to be called when a mouse button is pressed 4355 * on this {@code Scene} and then dragged. 4356 */ 4357 private ObjectProperty<EventHandler<? super MouseEvent>> onMouseDragged; 4358 4359 public final void setOnMouseDragged(EventHandler<? super MouseEvent> value) { 4360 onMouseDraggedProperty().set(value); 4361 } 4362 4363 public final EventHandler<? super MouseEvent> getOnMouseDragged() { 4364 return onMouseDragged == null ? null : onMouseDragged.get(); 4365 } 4366 4367 public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseDraggedProperty() { 4368 if (onMouseDragged == null) { 4369 onMouseDragged = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4370 4371 @Override 4372 protected void invalidated() { 4373 setEventHandler(MouseEvent.MOUSE_DRAGGED, get()); 4374 } 4375 4376 @Override 4377 public Object getBean() { 4378 return Scene.this; 4379 } 4380 4381 @Override 4382 public String getName() { 4383 return "onMouseDragged"; 4384 } 4385 }; 4386 } 4387 return onMouseDragged; 4388 } 4389 4390 /** 4391 * Defines a function to be called when the mouse enters this {@code Scene}. 4392 */ 4393 private ObjectProperty<EventHandler<? super MouseEvent>> onMouseEntered; 4394 4395 public final void setOnMouseEntered(EventHandler<? super MouseEvent> value) { 4396 onMouseEnteredProperty().set(value); 4397 } 4398 4399 public final EventHandler<? super MouseEvent> getOnMouseEntered() { 4400 return onMouseEntered == null ? null : onMouseEntered.get(); 4401 } 4402 4403 public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseEnteredProperty() { 4404 if (onMouseEntered == null) { 4405 onMouseEntered = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4406 4407 @Override 4408 protected void invalidated() { 4409 setEventHandler(MouseEvent.MOUSE_ENTERED, get()); 4410 } 4411 4412 @Override 4413 public Object getBean() { 4414 return Scene.this; 4415 } 4416 4417 @Override 4418 public String getName() { 4419 return "onMouseEntered"; 4420 } 4421 }; 4422 } 4423 return onMouseEntered; 4424 } 4425 4426 /** 4427 * Defines a function to be called when the mouse exits this {@code Scene}. 4428 */ 4429 private ObjectProperty<EventHandler<? super MouseEvent>> onMouseExited; 4430 4431 public final void setOnMouseExited(EventHandler<? super MouseEvent> value) { 4432 onMouseExitedProperty().set(value); 4433 } 4434 4435 public final EventHandler<? super MouseEvent> getOnMouseExited() { 4436 return onMouseExited == null ? null : onMouseExited.get(); 4437 } 4438 4439 public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseExitedProperty() { 4440 if (onMouseExited == null) { 4441 onMouseExited = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4442 4443 @Override 4444 protected void invalidated() { 4445 setEventHandler(MouseEvent.MOUSE_EXITED, get()); 4446 } 4447 4448 @Override 4449 public Object getBean() { 4450 return Scene.this; 4451 } 4452 4453 @Override 4454 public String getName() { 4455 return "onMouseExited"; 4456 } 4457 }; 4458 } 4459 return onMouseExited; 4460 } 4461 4462 /** 4463 * Defines a function to be called when mouse cursor moves within 4464 * this {@code Scene} but no buttons have been pushed. 4465 */ 4466 private ObjectProperty<EventHandler<? super MouseEvent>> onMouseMoved; 4467 4468 public final void setOnMouseMoved(EventHandler<? super MouseEvent> value) { 4469 onMouseMovedProperty().set(value); 4470 } 4471 4472 public final EventHandler<? super MouseEvent> getOnMouseMoved() { 4473 return onMouseMoved == null ? null : onMouseMoved.get(); 4474 } 4475 4476 public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseMovedProperty() { 4477 if (onMouseMoved == null) { 4478 onMouseMoved = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4479 4480 @Override 4481 protected void invalidated() { 4482 setEventHandler(MouseEvent.MOUSE_MOVED, get()); 4483 } 4484 4485 @Override 4486 public Object getBean() { 4487 return Scene.this; 4488 } 4489 4490 @Override 4491 public String getName() { 4492 return "onMouseMoved"; 4493 } 4494 }; 4495 } 4496 return onMouseMoved; 4497 } 4498 4499 /** 4500 * Defines a function to be called when a mouse button 4501 * has been pressed on this {@code Scene}. 4502 */ 4503 private ObjectProperty<EventHandler<? super MouseEvent>> onMousePressed; 4504 4505 public final void setOnMousePressed(EventHandler<? super MouseEvent> value) { 4506 onMousePressedProperty().set(value); 4507 } 4508 4509 public final EventHandler<? super MouseEvent> getOnMousePressed() { 4510 return onMousePressed == null ? null : onMousePressed.get(); 4511 } 4512 4513 public final ObjectProperty<EventHandler<? super MouseEvent>> onMousePressedProperty() { 4514 if (onMousePressed == null) { 4515 onMousePressed = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4516 4517 @Override 4518 protected void invalidated() { 4519 setEventHandler(MouseEvent.MOUSE_PRESSED, get()); 4520 } 4521 4522 @Override 4523 public Object getBean() { 4524 return Scene.this; 4525 } 4526 4527 @Override 4528 public String getName() { 4529 return "onMousePressed"; 4530 } 4531 }; 4532 } 4533 return onMousePressed; 4534 } 4535 4536 /** 4537 * Defines a function to be called when a mouse button 4538 * has been released on this {@code Scene}. 4539 */ 4540 private ObjectProperty<EventHandler<? super MouseEvent>> onMouseReleased; 4541 4542 public final void setOnMouseReleased(EventHandler<? super MouseEvent> value) { 4543 onMouseReleasedProperty().set(value); 4544 } 4545 4546 public final EventHandler<? super MouseEvent> getOnMouseReleased() { 4547 return onMouseReleased == null ? null : onMouseReleased.get(); 4548 } 4549 4550 public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseReleasedProperty() { 4551 if (onMouseReleased == null) { 4552 onMouseReleased = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4553 4554 @Override 4555 protected void invalidated() { 4556 setEventHandler(MouseEvent.MOUSE_RELEASED, get()); 4557 } 4558 4559 @Override 4560 public Object getBean() { 4561 return Scene.this; 4562 } 4563 4564 @Override 4565 public String getName() { 4566 return "onMouseReleased"; 4567 } 4568 }; 4569 } 4570 return onMouseReleased; 4571 } 4572 4573 /** 4574 * Defines a function to be called when drag gesture has been 4575 * detected. This is the right place to start drag and drop operation. 4576 */ 4577 private ObjectProperty<EventHandler<? super MouseEvent>> onDragDetected; 4578 4579 public final void setOnDragDetected(EventHandler<? super MouseEvent> value) { 4580 onDragDetectedProperty().set(value); 4581 } 4582 4583 public final EventHandler<? super MouseEvent> getOnDragDetected() { 4584 return onDragDetected == null ? null : onDragDetected.get(); 4585 } 4586 4587 public final ObjectProperty<EventHandler<? super MouseEvent>> onDragDetectedProperty() { 4588 if (onDragDetected == null) { 4589 onDragDetected = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() { 4590 4591 @Override 4592 protected void invalidated() { 4593 setEventHandler(MouseEvent.DRAG_DETECTED, get()); 4594 } 4595 4596 @Override 4597 public Object getBean() { 4598 return Scene.this; 4599 } 4600 4601 @Override 4602 public String getName() { 4603 return "onDragDetected"; 4604 } 4605 }; 4606 } 4607 return onDragDetected; 4608 } 4609 4610 /** 4611 * Defines a function to be called when a full press-drag-release gesture 4612 * progresses within this {@code Scene}. 4613 * @since JavaFX 2.1 4614 */ 4615 private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragOver; 4616 4617 public final void setOnMouseDragOver(EventHandler<? super MouseDragEvent> value) { 4618 onMouseDragOverProperty().set(value); 4619 } 4620 4621 public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() { 4622 return onMouseDragOver == null ? null : onMouseDragOver.get(); 4623 } 4624 4625 public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragOverProperty() { 4626 if (onMouseDragOver == null) { 4627 onMouseDragOver = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() { 4628 4629 @Override 4630 protected void invalidated() { 4631 setEventHandler(MouseDragEvent.MOUSE_DRAG_OVER, get()); 4632 } 4633 4634 @Override 4635 public Object getBean() { 4636 return Scene.this; 4637 } 4638 4639 @Override 4640 public String getName() { 4641 return "onMouseDragOver"; 4642 } 4643 }; 4644 } 4645 return onMouseDragOver; 4646 } 4647 4648 /** 4649 * Defines a function to be called when a full press-drag-release gesture 4650 * ends within this {@code Scene}. 4651 * @since JavaFX 2.1 4652 */ 4653 private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragReleased; 4654 4655 public final void setOnMouseDragReleased(EventHandler<? super MouseDragEvent> value) { 4656 onMouseDragReleasedProperty().set(value); 4657 } 4658 4659 public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() { 4660 return onMouseDragReleased == null ? null : onMouseDragReleased.get(); 4661 } 4662 4663 public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragReleasedProperty() { 4664 if (onMouseDragReleased == null) { 4665 onMouseDragReleased = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() { 4666 4667 @Override 4668 protected void invalidated() { 4669 setEventHandler(MouseDragEvent.MOUSE_DRAG_RELEASED, get()); 4670 } 4671 4672 @Override 4673 public Object getBean() { 4674 return Scene.this; 4675 } 4676 4677 @Override 4678 public String getName() { 4679 return "onMouseDragReleased"; 4680 } 4681 }; 4682 } 4683 return onMouseDragReleased; 4684 } 4685 4686 /** 4687 * Defines a function to be called when a full press-drag-release gesture 4688 * enters this {@code Scene}. 4689 * @since JavaFX 2.1 4690 */ 4691 private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragEntered; 4692 4693 public final void setOnMouseDragEntered(EventHandler<? super MouseDragEvent> value) { 4694 onMouseDragEnteredProperty().set(value); 4695 } 4696 4697 public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() { 4698 return onMouseDragEntered == null ? null : onMouseDragEntered.get(); 4699 } 4700 4701 public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragEnteredProperty() { 4702 if (onMouseDragEntered == null) { 4703 onMouseDragEntered = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() { 4704 4705 @Override 4706 protected void invalidated() { 4707 setEventHandler(MouseDragEvent.MOUSE_DRAG_ENTERED, get()); 4708 } 4709 4710 @Override 4711 public Object getBean() { 4712 return Scene.this; 4713 } 4714 4715 @Override 4716 public String getName() { 4717 return "onMouseDragEntered"; 4718 } 4719 }; 4720 } 4721 return onMouseDragEntered; 4722 } 4723 4724 /** 4725 * Defines a function to be called when a full press-drag-release gesture 4726 * exits this {@code Scene}. 4727 * @since JavaFX 2.1 4728 */ 4729 private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragExited; 4730 4731 public final void setOnMouseDragExited(EventHandler<? super MouseDragEvent> value) { 4732 onMouseDragExitedProperty().set(value); 4733 } 4734 4735 public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() { 4736 return onMouseDragExited == null ? null : onMouseDragExited.get(); 4737 } 4738 4739 public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragExitedProperty() { 4740 if (onMouseDragExited == null) { 4741 onMouseDragExited = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() { 4742 4743 @Override 4744 protected void invalidated() { 4745 setEventHandler(MouseDragEvent.MOUSE_DRAG_EXITED, get()); 4746 } 4747 4748 @Override 4749 public Object getBean() { 4750 return Scene.this; 4751 } 4752 4753 @Override 4754 public String getName() { 4755 return "onMouseDragExited"; 4756 } 4757 }; 4758 } 4759 return onMouseDragExited; 4760 } 4761 4762 4763 /*************************************************************************** 4764 * * 4765 * Gestures Handling * 4766 * * 4767 **************************************************************************/ 4768 4769 /** 4770 * Defines a function to be called when a scrolling gesture is detected. 4771 * @since JavaFX 2.2 4772 */ 4773 private ObjectProperty<EventHandler<? super ScrollEvent>> onScrollStarted; 4774 4775 public final void setOnScrollStarted(EventHandler<? super ScrollEvent> value) { 4776 onScrollStartedProperty().set(value); 4777 } 4778 4779 public final EventHandler<? super ScrollEvent> getOnScrollStarted() { 4780 return onScrollStarted == null ? null : onScrollStarted.get(); 4781 } 4782 4783 public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollStartedProperty() { 4784 if (onScrollStarted == null) { 4785 onScrollStarted = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() { 4786 4787 @Override 4788 protected void invalidated() { 4789 setEventHandler(ScrollEvent.SCROLL_STARTED, get()); 4790 } 4791 4792 @Override 4793 public Object getBean() { 4794 return Scene.this; 4795 } 4796 4797 @Override 4798 public String getName() { 4799 return "onScrollStarted"; 4800 } 4801 }; 4802 } 4803 return onScrollStarted; 4804 } 4805 4806 /** 4807 * Defines a function to be called when user performs a scrolling action. 4808 */ 4809 private ObjectProperty<EventHandler<? super ScrollEvent>> onScroll; 4810 4811 public final void setOnScroll(EventHandler<? super ScrollEvent> value) { 4812 onScrollProperty().set(value); 4813 } 4814 4815 public final EventHandler<? super ScrollEvent> getOnScroll() { 4816 return onScroll == null ? null : onScroll.get(); 4817 } 4818 4819 public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollProperty() { 4820 if (onScroll == null) { 4821 onScroll = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() { 4822 4823 @Override 4824 protected void invalidated() { 4825 setEventHandler(ScrollEvent.SCROLL, get()); 4826 } 4827 4828 @Override 4829 public Object getBean() { 4830 return Scene.this; 4831 } 4832 4833 @Override 4834 public String getName() { 4835 return "onScroll"; 4836 } 4837 }; 4838 } 4839 return onScroll; 4840 } 4841 4842 /** 4843 * Defines a function to be called when a scrolling gesture ends. 4844 * @since JavaFX 2.2 4845 */ 4846 private ObjectProperty<EventHandler<? super ScrollEvent>> onScrollFinished; 4847 4848 public final void setOnScrollFinished(EventHandler<? super ScrollEvent> value) { 4849 onScrollFinishedProperty().set(value); 4850 } 4851 4852 public final EventHandler<? super ScrollEvent> getOnScrollFinished() { 4853 return onScrollFinished == null ? null : onScrollFinished.get(); 4854 } 4855 4856 public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollFinishedProperty() { 4857 if (onScrollFinished == null) { 4858 onScrollFinished = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() { 4859 4860 @Override 4861 protected void invalidated() { 4862 setEventHandler(ScrollEvent.SCROLL_FINISHED, get()); 4863 } 4864 4865 @Override 4866 public Object getBean() { 4867 return Scene.this; 4868 } 4869 4870 @Override 4871 public String getName() { 4872 return "onScrollFinished"; 4873 } 4874 }; 4875 } 4876 return onScrollFinished; 4877 } 4878 4879 /** 4880 * Defines a function to be called when a rotating gesture is detected. 4881 * @since JavaFX 2.2 4882 */ 4883 private ObjectProperty<EventHandler<? super RotateEvent>> onRotationStarted; 4884 4885 public final void setOnRotationStarted(EventHandler<? super RotateEvent> value) { 4886 onRotationStartedProperty().set(value); 4887 } 4888 4889 public final EventHandler<? super RotateEvent> getOnRotationStarted() { 4890 return onRotationStarted == null ? null : onRotationStarted.get(); 4891 } 4892 4893 public final ObjectProperty<EventHandler<? super RotateEvent>> onRotationStartedProperty() { 4894 if (onRotationStarted == null) { 4895 onRotationStarted = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() { 4896 4897 @Override 4898 protected void invalidated() { 4899 setEventHandler(RotateEvent.ROTATION_STARTED, get()); 4900 } 4901 4902 @Override 4903 public Object getBean() { 4904 return Scene.this; 4905 } 4906 4907 @Override 4908 public String getName() { 4909 return "onRotationStarted"; 4910 } 4911 }; 4912 } 4913 return onRotationStarted; 4914 } 4915 4916 /** 4917 * Defines a function to be called when user performs a rotating action. 4918 * @since JavaFX 2.2 4919 */ 4920 private ObjectProperty<EventHandler<? super RotateEvent>> onRotate; 4921 4922 public final void setOnRotate(EventHandler<? super RotateEvent> value) { 4923 onRotateProperty().set(value); 4924 } 4925 4926 public final EventHandler<? super RotateEvent> getOnRotate() { 4927 return onRotate == null ? null : onRotate.get(); 4928 } 4929 4930 public final ObjectProperty<EventHandler<? super RotateEvent>> onRotateProperty() { 4931 if (onRotate == null) { 4932 onRotate = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() { 4933 4934 @Override 4935 protected void invalidated() { 4936 setEventHandler(RotateEvent.ROTATE, get()); 4937 } 4938 4939 @Override 4940 public Object getBean() { 4941 return Scene.this; 4942 } 4943 4944 @Override 4945 public String getName() { 4946 return "onRotate"; 4947 } 4948 }; 4949 } 4950 return onRotate; 4951 } 4952 4953 /** 4954 * Defines a function to be called when a rotating gesture ends. 4955 * @since JavaFX 2.2 4956 */ 4957 private ObjectProperty<EventHandler<? super RotateEvent>> onRotationFinished; 4958 4959 public final void setOnRotationFinished(EventHandler<? super RotateEvent> value) { 4960 onRotationFinishedProperty().set(value); 4961 } 4962 4963 public final EventHandler<? super RotateEvent> getOnRotationFinished() { 4964 return onRotationFinished == null ? null : onRotationFinished.get(); 4965 } 4966 4967 public final ObjectProperty<EventHandler<? super RotateEvent>> onRotationFinishedProperty() { 4968 if (onRotationFinished == null) { 4969 onRotationFinished = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() { 4970 4971 @Override 4972 protected void invalidated() { 4973 setEventHandler(RotateEvent.ROTATION_FINISHED, get()); 4974 } 4975 4976 @Override 4977 public Object getBean() { 4978 return Scene.this; 4979 } 4980 4981 @Override 4982 public String getName() { 4983 return "onRotationFinished"; 4984 } 4985 }; 4986 } 4987 return onRotationFinished; 4988 } 4989 4990 /** 4991 * Defines a function to be called when a zooming gesture is detected. 4992 * @since JavaFX 2.2 4993 */ 4994 private ObjectProperty<EventHandler<? super ZoomEvent>> onZoomStarted; 4995 4996 public final void setOnZoomStarted(EventHandler<? super ZoomEvent> value) { 4997 onZoomStartedProperty().set(value); 4998 } 4999 5000 public final EventHandler<? super ZoomEvent> getOnZoomStarted() { 5001 return onZoomStarted == null ? null : onZoomStarted.get(); 5002 } 5003 5004 public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomStartedProperty() { 5005 if (onZoomStarted == null) { 5006 onZoomStarted = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() { 5007 5008 @Override 5009 protected void invalidated() { 5010 setEventHandler(ZoomEvent.ZOOM_STARTED, get()); 5011 } 5012 5013 @Override 5014 public Object getBean() { 5015 return Scene.this; 5016 } 5017 5018 @Override 5019 public String getName() { 5020 return "onZoomStarted"; 5021 } 5022 }; 5023 } 5024 return onZoomStarted; 5025 } 5026 5027 /** 5028 * Defines a function to be called when user performs a zooming action. 5029 * @since JavaFX 2.2 5030 */ 5031 private ObjectProperty<EventHandler<? super ZoomEvent>> onZoom; 5032 5033 public final void setOnZoom(EventHandler<? super ZoomEvent> value) { 5034 onZoomProperty().set(value); 5035 } 5036 5037 public final EventHandler<? super ZoomEvent> getOnZoom() { 5038 return onZoom == null ? null : onZoom.get(); 5039 } 5040 5041 public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomProperty() { 5042 if (onZoom == null) { 5043 onZoom = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() { 5044 5045 @Override 5046 protected void invalidated() { 5047 setEventHandler(ZoomEvent.ZOOM, get()); 5048 } 5049 5050 @Override 5051 public Object getBean() { 5052 return Scene.this; 5053 } 5054 5055 @Override 5056 public String getName() { 5057 return "onZoom"; 5058 } 5059 }; 5060 } 5061 return onZoom; 5062 } 5063 5064 /** 5065 * Defines a function to be called when a zooming gesture ends. 5066 * @since JavaFX 2.2 5067 */ 5068 private ObjectProperty<EventHandler<? super ZoomEvent>> onZoomFinished; 5069 5070 public final void setOnZoomFinished(EventHandler<? super ZoomEvent> value) { 5071 onZoomFinishedProperty().set(value); 5072 } 5073 5074 public final EventHandler<? super ZoomEvent> getOnZoomFinished() { 5075 return onZoomFinished == null ? null : onZoomFinished.get(); 5076 } 5077 5078 public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomFinishedProperty() { 5079 if (onZoomFinished == null) { 5080 onZoomFinished = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() { 5081 5082 @Override 5083 protected void invalidated() { 5084 setEventHandler(ZoomEvent.ZOOM_FINISHED, get()); 5085 } 5086 5087 @Override 5088 public Object getBean() { 5089 return Scene.this; 5090 } 5091 5092 @Override 5093 public String getName() { 5094 return "onZoomFinished"; 5095 } 5096 }; 5097 } 5098 return onZoomFinished; 5099 } 5100 5101 /** 5102 * Defines a function to be called when an upward swipe gesture 5103 * happens in this scene. 5104 * @since JavaFX 2.2 5105 */ 5106 private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeUp; 5107 5108 public final void setOnSwipeUp(EventHandler<? super SwipeEvent> value) { 5109 onSwipeUpProperty().set(value); 5110 } 5111 5112 public final EventHandler<? super SwipeEvent> getOnSwipeUp() { 5113 return onSwipeUp == null ? null : onSwipeUp.get(); 5114 } 5115 5116 public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeUpProperty() { 5117 if (onSwipeUp == null) { 5118 onSwipeUp = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() { 5119 5120 @Override 5121 protected void invalidated() { 5122 setEventHandler(SwipeEvent.SWIPE_UP, get()); 5123 } 5124 5125 @Override 5126 public Object getBean() { 5127 return Scene.this; 5128 } 5129 5130 @Override 5131 public String getName() { 5132 return "onSwipeUp"; 5133 } 5134 }; 5135 } 5136 return onSwipeUp; 5137 } 5138 5139 /** 5140 * Defines a function to be called when an downward swipe gesture 5141 * happens in this scene. 5142 * @since JavaFX 2.2 5143 */ 5144 private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeDown; 5145 5146 public final void setOnSwipeDown(EventHandler<? super SwipeEvent> value) { 5147 onSwipeDownProperty().set(value); 5148 } 5149 5150 public final EventHandler<? super SwipeEvent> getOnSwipeDown() { 5151 return onSwipeDown == null ? null : onSwipeDown.get(); 5152 } 5153 5154 public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeDownProperty() { 5155 if (onSwipeDown == null) { 5156 onSwipeDown = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() { 5157 5158 @Override 5159 protected void invalidated() { 5160 setEventHandler(SwipeEvent.SWIPE_DOWN, get()); 5161 } 5162 5163 @Override 5164 public Object getBean() { 5165 return Scene.this; 5166 } 5167 5168 @Override 5169 public String getName() { 5170 return "onSwipeDown"; 5171 } 5172 }; 5173 } 5174 return onSwipeDown; 5175 } 5176 5177 /** 5178 * Defines a function to be called when an leftward swipe gesture 5179 * happens in this scene. 5180 * @since JavaFX 2.2 5181 */ 5182 private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeLeft; 5183 5184 public final void setOnSwipeLeft(EventHandler<? super SwipeEvent> value) { 5185 onSwipeLeftProperty().set(value); 5186 } 5187 5188 public final EventHandler<? super SwipeEvent> getOnSwipeLeft() { 5189 return onSwipeLeft == null ? null : onSwipeLeft.get(); 5190 } 5191 5192 public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeLeftProperty() { 5193 if (onSwipeLeft == null) { 5194 onSwipeLeft = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() { 5195 5196 @Override 5197 protected void invalidated() { 5198 setEventHandler(SwipeEvent.SWIPE_LEFT, get()); 5199 } 5200 5201 @Override 5202 public Object getBean() { 5203 return Scene.this; 5204 } 5205 5206 @Override 5207 public String getName() { 5208 return "onSwipeLeft"; 5209 } 5210 }; 5211 } 5212 return onSwipeLeft; 5213 } 5214 5215 /** 5216 * Defines a function to be called when an rightward swipe gesture 5217 * happens in this scene. 5218 * @since JavaFX 2.2 5219 */ 5220 private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeRight; 5221 5222 public final void setOnSwipeRight(EventHandler<? super SwipeEvent> value) { 5223 onSwipeRightProperty().set(value); 5224 } 5225 5226 public final EventHandler<? super SwipeEvent> getOnSwipeRight() { 5227 return onSwipeRight == null ? null : onSwipeRight.get(); 5228 } 5229 5230 public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeRightProperty() { 5231 if (onSwipeRight == null) { 5232 onSwipeRight = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() { 5233 5234 @Override 5235 protected void invalidated() { 5236 setEventHandler(SwipeEvent.SWIPE_RIGHT, get()); 5237 } 5238 5239 @Override 5240 public Object getBean() { 5241 return Scene.this; 5242 } 5243 5244 @Override 5245 public String getName() { 5246 return "onSwipeRight"; 5247 } 5248 }; 5249 } 5250 return onSwipeRight; 5251 } 5252 5253 /*************************************************************************** 5254 * * 5255 * Touch Handling * 5256 * * 5257 **************************************************************************/ 5258 5259 /** 5260 * Defines a function to be called when a new touch point is pressed. 5261 * @since JavaFX 2.2 5262 */ 5263 private ObjectProperty<EventHandler<? super TouchEvent>> onTouchPressed; 5264 5265 public final void setOnTouchPressed(EventHandler<? super TouchEvent> value) { 5266 onTouchPressedProperty().set(value); 5267 } 5268 5269 public final EventHandler<? super TouchEvent> getOnTouchPressed() { 5270 return onTouchPressed == null ? null : onTouchPressed.get(); 5271 } 5272 5273 public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchPressedProperty() { 5274 if (onTouchPressed == null) { 5275 onTouchPressed = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() { 5276 5277 @Override 5278 protected void invalidated() { 5279 setEventHandler(TouchEvent.TOUCH_PRESSED, get()); 5280 } 5281 5282 @Override 5283 public Object getBean() { 5284 return Scene.this; 5285 } 5286 5287 @Override 5288 public String getName() { 5289 return "onTouchPressed"; 5290 } 5291 }; 5292 } 5293 return onTouchPressed; 5294 } 5295 5296 /** 5297 * Defines a function to be called when a touch point is moved. 5298 * @since JavaFX 2.2 5299 */ 5300 private ObjectProperty<EventHandler<? super TouchEvent>> onTouchMoved; 5301 5302 public final void setOnTouchMoved(EventHandler<? super TouchEvent> value) { 5303 onTouchMovedProperty().set(value); 5304 } 5305 5306 public final EventHandler<? super TouchEvent> getOnTouchMoved() { 5307 return onTouchMoved == null ? null : onTouchMoved.get(); 5308 } 5309 5310 public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchMovedProperty() { 5311 if (onTouchMoved == null) { 5312 onTouchMoved = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() { 5313 5314 @Override 5315 protected void invalidated() { 5316 setEventHandler(TouchEvent.TOUCH_MOVED, get()); 5317 } 5318 5319 @Override 5320 public Object getBean() { 5321 return Scene.this; 5322 } 5323 5324 @Override 5325 public String getName() { 5326 return "onTouchMoved"; 5327 } 5328 }; 5329 } 5330 return onTouchMoved; 5331 } 5332 5333 /** 5334 * Defines a function to be called when a new touch point is pressed. 5335 * @since JavaFX 2.2 5336 */ 5337 private ObjectProperty<EventHandler<? super TouchEvent>> onTouchReleased; 5338 5339 public final void setOnTouchReleased(EventHandler<? super TouchEvent> value) { 5340 onTouchReleasedProperty().set(value); 5341 } 5342 5343 public final EventHandler<? super TouchEvent> getOnTouchReleased() { 5344 return onTouchReleased == null ? null : onTouchReleased.get(); 5345 } 5346 5347 public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchReleasedProperty() { 5348 if (onTouchReleased == null) { 5349 onTouchReleased = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() { 5350 5351 @Override 5352 protected void invalidated() { 5353 setEventHandler(TouchEvent.TOUCH_RELEASED, get()); 5354 } 5355 5356 @Override 5357 public Object getBean() { 5358 return Scene.this; 5359 } 5360 5361 @Override 5362 public String getName() { 5363 return "onTouchReleased"; 5364 } 5365 }; 5366 } 5367 return onTouchReleased; 5368 } 5369 5370 /** 5371 * Defines a function to be called when a touch point stays pressed and 5372 * still. 5373 * @since JavaFX 2.2 5374 */ 5375 private ObjectProperty<EventHandler<? super TouchEvent>> onTouchStationary; 5376 5377 public final void setOnTouchStationary(EventHandler<? super TouchEvent> value) { 5378 onTouchStationaryProperty().set(value); 5379 } 5380 5381 public final EventHandler<? super TouchEvent> getOnTouchStationary() { 5382 return onTouchStationary == null ? null : onTouchStationary.get(); 5383 } 5384 5385 public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchStationaryProperty() { 5386 if (onTouchStationary == null) { 5387 onTouchStationary = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() { 5388 5389 @Override 5390 protected void invalidated() { 5391 setEventHandler(TouchEvent.TOUCH_STATIONARY, get()); 5392 } 5393 5394 @Override 5395 public Object getBean() { 5396 return Scene.this; 5397 } 5398 5399 @Override 5400 public String getName() { 5401 return "onTouchStationary"; 5402 } 5403 }; 5404 } 5405 return onTouchStationary; 5406 } 5407 5408 /* 5409 * This class provides reordering and ID mapping of particular touch points. 5410 * Platform may report arbitrary touch point IDs and they may be reused 5411 * during one gesture. This class keeps track of it and provides 5412 * sequentially sorted IDs, unique in scope of a gesture. 5413 * 5414 * Some platforms report always small numbers, these take fast paths through 5415 * the algorithm, directly indexing an array. Bigger numbers take a slow 5416 * path using a hash map. 5417 * 5418 * The algorithm performance was measured and it doesn't impose 5419 * any significant slowdown on the event delivery. 5420 */ 5421 private static class TouchMap { 5422 private static final int FAST_THRESHOLD = 10; 5423 int[] fastMap = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 5424 Map<Long, Integer> slowMap = new HashMap<Long, Integer>(); 5425 List<Integer> order = new LinkedList<Integer>(); 5426 List<Long> removed = new ArrayList<Long>(10); 5427 int counter = 0; 5428 int active = 0; 5429 5430 public int add(long id) { 5431 counter++; 5432 active++; 5433 if (id < FAST_THRESHOLD) { 5434 fastMap[(int) id] = counter; 5435 } else { 5436 slowMap.put(id, counter); 5437 } 5438 order.add(counter); 5439 return counter; 5440 } 5441 5442 public void remove(long id) { 5443 // book the removal - it needs to be done after all touch points 5444 // of an event are processed - see cleanup() 5445 removed.add(id); 5446 } 5447 5448 public int get(long id) { 5449 if (id < FAST_THRESHOLD) { 5450 int result = fastMap[(int) id]; 5451 if (result == 0) { 5452 throw new RuntimeException("Platform reported wrong " 5453 + "touch point ID"); 5454 } 5455 return result; 5456 } else { 5457 try { 5458 return slowMap.get(id); 5459 } catch (NullPointerException e) { 5460 throw new RuntimeException("Platform reported wrong " 5461 + "touch point ID"); 5462 } 5463 } 5464 } 5465 5466 public int getOrder(int id) { 5467 return order.indexOf(id); 5468 } 5469 5470 // returns true if gesture finished (no finger is touched) 5471 public boolean cleanup() { 5472 for (long id : removed) { 5473 active--; 5474 order.remove(Integer.valueOf(get(id))); 5475 if (id < FAST_THRESHOLD) { 5476 fastMap[(int) id] = 0; 5477 } else { 5478 slowMap.remove(id); 5479 } 5480 if (active == 0) { 5481 // gesture finished 5482 counter = 0; 5483 } 5484 } 5485 removed.clear(); 5486 return active == 0; 5487 } 5488 } 5489 5490 5491 /*************************************************************************** 5492 * * 5493 * Drag and Drop Handling * 5494 * * 5495 **************************************************************************/ 5496 5497 private ObjectProperty<EventHandler<? super DragEvent>> onDragEntered; 5498 5499 public final void setOnDragEntered(EventHandler<? super DragEvent> value) { 5500 onDragEnteredProperty().set(value); 5501 } 5502 5503 public final EventHandler<? super DragEvent> getOnDragEntered() { 5504 return onDragEntered == null ? null : onDragEntered.get(); 5505 } 5506 5507 /** 5508 * Defines a function to be called when drag gesture 5509 * enters this {@code Scene}. 5510 */ 5511 public final ObjectProperty<EventHandler<? super DragEvent>> onDragEnteredProperty() { 5512 if (onDragEntered == null) { 5513 onDragEntered = new ObjectPropertyBase<EventHandler<? super DragEvent>>() { 5514 5515 @Override 5516 protected void invalidated() { 5517 setEventHandler(DragEvent.DRAG_ENTERED, get()); 5518 } 5519 5520 @Override 5521 public Object getBean() { 5522 return Scene.this; 5523 } 5524 5525 @Override 5526 public String getName() { 5527 return "onDragEntered"; 5528 } 5529 }; 5530 } 5531 return onDragEntered; 5532 } 5533 5534 private ObjectProperty<EventHandler<? super DragEvent>> onDragExited; 5535 5536 public final void setOnDragExited(EventHandler<? super DragEvent> value) { 5537 onDragExitedProperty().set(value); 5538 } 5539 5540 public final EventHandler<? super DragEvent> getOnDragExited() { 5541 return onDragExited == null ? null : onDragExited.get(); 5542 } 5543 5544 /** 5545 * Defines a function to be called when drag gesture 5546 * exits this {@code Scene}. 5547 */ 5548 public final ObjectProperty<EventHandler<? super DragEvent>> onDragExitedProperty() { 5549 if (onDragExited == null) { 5550 onDragExited = new ObjectPropertyBase<EventHandler<? super DragEvent>>() { 5551 5552 @Override 5553 protected void invalidated() { 5554 setEventHandler(DragEvent.DRAG_EXITED, get()); 5555 } 5556 5557 @Override 5558 public Object getBean() { 5559 return Scene.this; 5560 } 5561 5562 @Override 5563 public String getName() { 5564 return "onDragExited"; 5565 } 5566 }; 5567 } 5568 return onDragExited; 5569 } 5570 5571 private ObjectProperty<EventHandler<? super DragEvent>> onDragOver; 5572 5573 public final void setOnDragOver(EventHandler<? super DragEvent> value) { 5574 onDragOverProperty().set(value); 5575 } 5576 5577 public final EventHandler<? super DragEvent> getOnDragOver() { 5578 return onDragOver == null ? null : onDragOver.get(); 5579 } 5580 5581 /** 5582 * Defines a function to be called when drag gesture progresses 5583 * within this {@code Scene}. 5584 */ 5585 public final ObjectProperty<EventHandler<? super DragEvent>> onDragOverProperty() { 5586 if (onDragOver == null) { 5587 onDragOver = new ObjectPropertyBase<EventHandler<? super DragEvent>>() { 5588 5589 @Override 5590 protected void invalidated() { 5591 setEventHandler(DragEvent.DRAG_OVER, get()); 5592 } 5593 5594 @Override 5595 public Object getBean() { 5596 return Scene.this; 5597 } 5598 5599 @Override 5600 public String getName() { 5601 return "onDragOver"; 5602 } 5603 }; 5604 } 5605 return onDragOver; 5606 } 5607 5608 // Do we want DRAG_TRANSFER_MODE_CHANGED event? 5609 // private ObjectProperty<EventHandler<? super DragEvent>> onDragTransferModeChanged; 5610 // 5611 // public final void setOnDragTransferModeChanged(EventHandler<? super DragEvent> value) { 5612 // onDragTransferModeChangedProperty().set(value); 5613 // } 5614 // 5615 // public final EventHandler<? super DragEvent> getOnDragTransferModeChanged() { 5616 // return onDragTransferModeChanged == null ? null : onDragTransferModeChanged.get(); 5617 // } 5618 // 5619 // /** 5620 // * Defines a function to be called this {@code Scene} if it is a potential 5621 // * drag-and-drop target when the user takes action to change the intended 5622 // * {@code TransferMode}. 5623 // * The user can change the intended {@link TransferMode} by holding down 5624 // * or releasing key modifiers. 5625 // */ 5626 // public ObjectProperty<EventHandler<? super DragEvent>> onDragTransferModeChangedProperty() { 5627 // if (onDragTransferModeChanged == null) { 5628 // onDragTransferModeChanged = new SimpleObjectProperty<EventHandler<? super DragEvent>>() { 5629 // 5630 // @Override 5631 // protected void invalidated() { 5632 // setEventHandler(DragEvent.DRAG_TRANSFER_MODE_CHANGED, get()); 5633 // } 5634 // }; 5635 // } 5636 // return onDragTransferModeChanged; 5637 // } 5638 5639 private ObjectProperty<EventHandler<? super DragEvent>> onDragDropped; 5640 5641 public final void setOnDragDropped(EventHandler<? super DragEvent> value) { 5642 onDragDroppedProperty().set(value); 5643 } 5644 5645 public final EventHandler<? super DragEvent> getOnDragDropped() { 5646 return onDragDropped == null ? null : onDragDropped.get(); 5647 } 5648 5649 /** 5650 * Defines a function to be called when the mouse button is released 5651 * on this {@code Scene} during drag and drop gesture. Transfer of data from 5652 * the {@link DragEvent}'s {@link DragEvent#dragboard dragboard} should 5653 * happen in this function. 5654 */ 5655 public final ObjectProperty<EventHandler<? super DragEvent>> onDragDroppedProperty() { 5656 if (onDragDropped == null) { 5657 onDragDropped = new ObjectPropertyBase<EventHandler<? super DragEvent>>() { 5658 5659 @Override 5660 protected void invalidated() { 5661 setEventHandler(DragEvent.DRAG_DROPPED, get()); 5662 } 5663 5664 @Override 5665 public Object getBean() { 5666 return Scene.this; 5667 } 5668 5669 @Override 5670 public String getName() { 5671 return "onDragDropped"; 5672 } 5673 }; 5674 } 5675 return onDragDropped; 5676 } 5677 5678 private ObjectProperty<EventHandler<? super DragEvent>> onDragDone; 5679 5680 public final void setOnDragDone(EventHandler<? super DragEvent> value) { 5681 onDragDoneProperty().set(value); 5682 } 5683 5684 public final EventHandler<? super DragEvent> getOnDragDone() { 5685 return onDragDone == null ? null : onDragDone.get(); 5686 } 5687 5688 /** 5689 * Defines a function to be called when this @{code Scene} is a 5690 * drag and drop gesture source after its data has 5691 * been dropped on a drop target. The {@code transferMode} of the 5692 * event shows what just happened at the drop target. 5693 * If {@code transferMode} has the value {@code MOVE}, then the source can 5694 * clear out its data. Clearing the source's data gives the appropriate 5695 * appearance to a user that the data has been moved by the drag and drop 5696 * gesture. A {@code transferMode} that has the value {@code NONE} 5697 * indicates that no data was transferred during the drag and drop gesture. 5698 * Positional data for the {@code DragEvent} is invalid. Valid positional 5699 * data for the {@code DragEvent} is presented in the {@link onDragDropped} 5700 * event handler. 5701 */ 5702 public final ObjectProperty<EventHandler<? super DragEvent>> onDragDoneProperty() { 5703 if (onDragDone == null) { 5704 onDragDone = new ObjectPropertyBase<EventHandler<? super DragEvent>>() { 5705 5706 @Override 5707 protected void invalidated() { 5708 setEventHandler(DragEvent.DRAG_DONE, get()); 5709 } 5710 5711 @Override 5712 public Object getBean() { 5713 return Scene.this; 5714 } 5715 5716 @Override 5717 public String getName() { 5718 return "onDragDone"; 5719 } 5720 }; 5721 } 5722 return onDragDone; 5723 } 5724 5725 /** 5726 * Confirms a potential drag and drop gesture that is recognized over this 5727 * {@code Scene}. 5728 * Can be called only from a DRAG_DETECTED event handler. The returned 5729 * {@link Dragboard} is used to transfer data during 5730 * the drag and drop gesture. Placing this {@code Scene}'s data on the 5731 * {@link Dragboard} also identifies this {@code Scene} as the source of 5732 * the drag and drop gesture. 5733 * More detail about drag and drop gestures is described in the overivew 5734 * of {@link DragEvent}. 5735 * 5736 * @see DragEvent 5737 * @param transferModes The supported {@code TransferMode}(s) of this {@code Node} 5738 * @return A {@code Dragboard} to place this {@code Scene}'s data on 5739 * @throws IllegalStateException if drag and drop cannot be started at this 5740 * moment (it's called outside of {@code DRAG_DETECTED} event handling). 5741 */ 5742 public Dragboard startDragAndDrop(TransferMode... transferModes) { 5743 return startDragAndDrop(this, transferModes); 5744 } 5745 5746 /** 5747 * Starts a full press-drag-release gesture with this scene as gesture 5748 * source. This method can be called only from a {@code DRAG_DETECTED} mouse 5749 * event handler. More detail about dragging gestures can be found 5750 * in the overview of {@link MouseEvent} and {@link MouseDragEvent}. 5751 * 5752 * @see MouseEvent 5753 * @see MouseDragEvent 5754 * @throws IllegalStateException if the full press-drag-release gesture 5755 * cannot be started at this moment (it's called outside of 5756 * {@code DRAG_DETECTED} event handling). 5757 * @since JavaFX 2.1 5758 */ 5759 public void startFullDrag() { 5760 startFullDrag(this); 5761 } 5762 5763 5764 Dragboard startDragAndDrop(EventTarget source, TransferMode... transferModes) { 5765 Toolkit.getToolkit().checkFxUserThread(); 5766 if (dndGesture == null || 5767 (dndGesture.dragDetected != DragDetectedState.PROCESSING)) 5768 { 5769 throw new IllegalStateException("Cannot start drag and drop " + 5770 "outside of DRAG_DETECTED event handler"); 5771 } 5772 5773 Set<TransferMode> set = EnumSet.noneOf(TransferMode.class); 5774 for (TransferMode tm : InputEventUtils.safeTransferModes(transferModes)) { 5775 set.add(tm); 5776 } 5777 return dndGesture.startDrag(source, set); 5778 } 5779 5780 void startFullDrag(EventTarget source) { 5781 Toolkit.getToolkit().checkFxUserThread(); 5782 if (dndGesture.dragDetected != DragDetectedState.PROCESSING) { 5783 throw new IllegalStateException("Cannot start full drag " + 5784 "outside of DRAG_DETECTED event handler"); 5785 } 5786 5787 if (dndGesture != null) { 5788 dndGesture.startFullPDR(source); 5789 return; 5790 } 5791 5792 throw new IllegalStateException("Cannot start full drag when " 5793 + "mouse button is not pressed"); 5794 } 5795 5796 /*************************************************************************** 5797 * * 5798 * Keyboard Handling * 5799 * * 5800 **************************************************************************/ 5801 5802 /** 5803 * Defines a function to be called when some {@code Node} of this 5804 * {@code Scene} has input focus and a key has been pressed. The function 5805 * is called only if the event hasn't been already consumed during its 5806 * capturing or bubbling phase. 5807 */ 5808 private ObjectProperty<EventHandler<? super KeyEvent>> onKeyPressed; 5809 5810 public final void setOnKeyPressed(EventHandler<? super KeyEvent> value) { 5811 onKeyPressedProperty().set(value); 5812 } 5813 5814 public final EventHandler<? super KeyEvent> getOnKeyPressed() { 5815 return onKeyPressed == null ? null : onKeyPressed.get(); 5816 } 5817 5818 public final ObjectProperty<EventHandler<? super KeyEvent>> onKeyPressedProperty() { 5819 if (onKeyPressed == null) { 5820 onKeyPressed = new ObjectPropertyBase<EventHandler<? super KeyEvent>>() { 5821 5822 @Override 5823 protected void invalidated() { 5824 setEventHandler(KeyEvent.KEY_PRESSED, get()); 5825 } 5826 5827 @Override 5828 public Object getBean() { 5829 return Scene.this; 5830 } 5831 5832 @Override 5833 public String getName() { 5834 return "onKeyPressed"; 5835 } 5836 }; 5837 } 5838 return onKeyPressed; 5839 } 5840 5841 /** 5842 * Defines a function to be called when some {@code Node} of this 5843 * {@code Scene} has input focus and a key has been released. The function 5844 * is called only if the event hasn't been already consumed during its 5845 * capturing or bubbling phase. 5846 */ 5847 private ObjectProperty<EventHandler<? super KeyEvent>> onKeyReleased; 5848 5849 public final void setOnKeyReleased(EventHandler<? super KeyEvent> value) { 5850 onKeyReleasedProperty().set(value); 5851 } 5852 5853 public final EventHandler<? super KeyEvent> getOnKeyReleased() { 5854 return onKeyReleased == null ? null : onKeyReleased.get(); 5855 } 5856 5857 public final ObjectProperty<EventHandler<? super KeyEvent>> onKeyReleasedProperty() { 5858 if (onKeyReleased == null) { 5859 onKeyReleased = new ObjectPropertyBase<EventHandler<? super KeyEvent>>() { 5860 5861 @Override 5862 protected void invalidated() { 5863 setEventHandler(KeyEvent.KEY_RELEASED, get()); 5864 } 5865 5866 @Override 5867 public Object getBean() { 5868 return Scene.this; 5869 } 5870 5871 @Override 5872 public String getName() { 5873 return "onKeyReleased"; 5874 } 5875 }; 5876 } 5877 return onKeyReleased; 5878 } 5879 5880 /** 5881 * Defines a function to be called when some {@code Node} of this 5882 * {@code Scene} has input focus and a key has been typed. The function 5883 * is called only if the event hasn't been already consumed during its 5884 * capturing or bubbling phase. 5885 */ 5886 private ObjectProperty<EventHandler<? super KeyEvent>> onKeyTyped; 5887 5888 public final void setOnKeyTyped( 5889 EventHandler<? super KeyEvent> value) { 5890 onKeyTypedProperty().set( value); 5891 5892 } 5893 5894 public final EventHandler<? super KeyEvent> getOnKeyTyped( 5895 ) { 5896 return onKeyTyped == null ? null : onKeyTyped.get(); 5897 } 5898 5899 public final ObjectProperty<EventHandler<? super KeyEvent>> onKeyTypedProperty( 5900 ) { 5901 if (onKeyTyped == null) { 5902 onKeyTyped = new ObjectPropertyBase<EventHandler<? super KeyEvent>>() { 5903 5904 @Override 5905 protected void invalidated() { 5906 setEventHandler(KeyEvent.KEY_TYPED, get()); 5907 } 5908 5909 @Override 5910 public Object getBean() { 5911 return Scene.this; 5912 } 5913 5914 @Override 5915 public String getName() { 5916 return "onKeyTyped"; 5917 } 5918 }; 5919 } 5920 return onKeyTyped; 5921 } 5922 5923 /*************************************************************************** 5924 * * 5925 * Input Method Handling * 5926 * * 5927 **************************************************************************/ 5928 5929 /** 5930 * Defines a function to be called when this {@code Node} 5931 * has input focus and the input method text has changed. If this 5932 * function is not defined in this {@code Node}, then it 5933 * receives the result string of the input method composition as a 5934 * series of {@code onKeyTyped} function calls. 5935 * </p> 5936 * When the {@code Node} loses the input focus, the JavaFX runtime 5937 * automatically commits the existing composed text if any. 5938 */ 5939 private ObjectProperty<EventHandler<? super InputMethodEvent>> onInputMethodTextChanged; 5940 5941 public final void setOnInputMethodTextChanged( 5942 EventHandler<? super InputMethodEvent> value) { 5943 onInputMethodTextChangedProperty().set( value); 5944 } 5945 5946 public final EventHandler<? super InputMethodEvent> getOnInputMethodTextChanged() { 5947 return onInputMethodTextChanged == null ? null : onInputMethodTextChanged.get(); 5948 } 5949 5950 public final ObjectProperty<EventHandler<? super InputMethodEvent>> onInputMethodTextChangedProperty() { 5951 if (onInputMethodTextChanged == null) { 5952 onInputMethodTextChanged = new ObjectPropertyBase<EventHandler<? super InputMethodEvent>>() { 5953 5954 @Override 5955 protected void invalidated() { 5956 setEventHandler(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, get()); 5957 } 5958 5959 @Override 5960 public Object getBean() { 5961 return Scene.this; 5962 } 5963 5964 @Override 5965 public String getName() { 5966 return "onInputMethodTextChanged"; 5967 } 5968 }; 5969 } 5970 return onInputMethodTextChanged; 5971 } 5972 5973 /* 5974 * This class represents a picked target - either node, or scne, or null. 5975 * It provides functionality needed for the targets and covers the fact 5976 * that they are different kinds of animals. 5977 */ 5978 private static class TargetWrapper { 5979 private Scene scene; 5980 private Node node; 5981 private PickResult result; 5982 5983 /** 5984 * Fills the list with the target and all its parents (including scene) 5985 */ 5986 public void fillHierarchy(final List<EventTarget> list) { 5987 list.clear(); 5988 Node n = node; 5989 while(n != null) { 5990 list.add(n); 5991 final Parent p = n.getParent(); 5992 n = p != null ? p : n.getSubScene(); 5993 } 5994 5995 if (scene != null) { 5996 list.add(scene); 5997 } 5998 } 5999 6000 public EventTarget getEventTarget() { 6001 return node != null ? node : scene; 6002 } 6003 6004 public Cursor getCursor() { 6005 Cursor cursor = null; 6006 if (node != null) { 6007 cursor = node.getCursor(); 6008 Node n = node.getParent(); 6009 while (cursor == null && n != null) { 6010 cursor = n.getCursor(); 6011 6012 final Parent p = n.getParent(); 6013 n = p != null ? p : n.getSubScene(); 6014 } 6015 } 6016 return cursor; 6017 } 6018 6019 public void clear() { 6020 set(null, null); 6021 result = null; 6022 } 6023 6024 public void setNodeResult(PickResult result) { 6025 if (result != null) { 6026 this.result = result; 6027 final Node n = result.getIntersectedNode(); 6028 set(n, n.getScene()); 6029 } 6030 } 6031 6032 // Pass null scene if the mouse is outside of the window content 6033 public void setSceneResult(PickResult result, Scene scene) { 6034 if (result != null) { 6035 this.result = result; 6036 set(null, scene); 6037 } 6038 } 6039 6040 public PickResult getResult() { 6041 return result; 6042 } 6043 6044 public void copy(TargetWrapper tw) { 6045 node = tw.node; 6046 scene = tw.scene; 6047 result = tw.result; 6048 } 6049 6050 private void set(Node n, Scene s) { 6051 node = n; 6052 scene = s; 6053 } 6054 } 6055 6056 /************************************************************************* 6057 * * 6058 * * 6059 * * 6060 *************************************************************************/ 6061 6062 private static final Object USER_DATA_KEY = new Object(); 6063 // A map containing a set of properties for this scene 6064 private ObservableMap<Object, Object> properties; 6065 6066 /** 6067 * Returns an observable map of properties on this node for use primarily 6068 * by application developers. 6069 * 6070 * @return an observable map of properties on this node for use primarily 6071 * by application developers 6072 * 6073 * @since JavaFX 8u40 6074 */ 6075 public final ObservableMap<Object, Object> getProperties() { 6076 if (properties == null) { 6077 properties = FXCollections.observableMap(new HashMap<Object, Object>()); 6078 } 6079 return properties; 6080 } 6081 6082 /** 6083 * Tests if Scene has properties. 6084 * @return true if node has properties. 6085 * 6086 * @since JavaFX 8u40 6087 */ 6088 public boolean hasProperties() { 6089 return properties != null && !properties.isEmpty(); 6090 } 6091 6092 /** 6093 * Convenience method for setting a single Object property that can be 6094 * retrieved at a later date. This is functionally equivalent to calling 6095 * the getProperties().put(Object key, Object value) method. This can later 6096 * be retrieved by calling {@link Scene#getUserData()}. 6097 * 6098 * @param value The value to be stored - this can later be retrieved by calling 6099 * {@link Scene#getUserData()}. 6100 * 6101 * @since JavaFX 8u40 6102 */ 6103 public void setUserData(Object value) { 6104 getProperties().put(USER_DATA_KEY, value); 6105 } 6106 6107 /** 6108 * Returns a previously set Object property, or null if no such property 6109 * has been set using the {@link Scene#setUserData(java.lang.Object)} method. 6110 * 6111 * @return The Object that was previously set, or null if no property 6112 * has been set or if null was set. 6113 * 6114 * @since JavaFX 8u40 6115 */ 6116 public Object getUserData() { 6117 return getProperties().get(USER_DATA_KEY); 6118 } 6119 6120 /*************************************************************************** 6121 * * 6122 * Component Orientation Properties * 6123 * * 6124 **************************************************************************/ 6125 6126 private static final NodeOrientation defaultNodeOrientation = 6127 AccessController.doPrivileged( 6128 (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("javafx.scene.nodeOrientation.RTL")) ? NodeOrientation.RIGHT_TO_LEFT : NodeOrientation.INHERIT; 6129 6130 6131 6132 private ObjectProperty<NodeOrientation> nodeOrientation; 6133 private EffectiveOrientationProperty effectiveNodeOrientationProperty; 6134 6135 private NodeOrientation effectiveNodeOrientation; 6136 6137 public final void setNodeOrientation(NodeOrientation orientation) { 6138 nodeOrientationProperty().set(orientation); 6139 } 6140 6141 public final NodeOrientation getNodeOrientation() { 6142 return nodeOrientation == null ? defaultNodeOrientation : nodeOrientation.get(); 6143 } 6144 6145 /** 6146 * Property holding NodeOrientation. 6147 * <p> 6148 * Node orientation describes the flow of visual data within a node. 6149 * In the English speaking world, visual data normally flows from 6150 * left-to-right. In an Arabic or Hebrew world, visual data flows 6151 * from right-to-left. This is consistent with the reading order 6152 * of text in both worlds. The default value is left-to-right. 6153 * </p> 6154 * 6155 * @return NodeOrientation 6156 * @since JavaFX 8.0 6157 */ 6158 public final ObjectProperty<NodeOrientation> nodeOrientationProperty() { 6159 if (nodeOrientation == null) { 6160 nodeOrientation = new StyleableObjectProperty<NodeOrientation>(defaultNodeOrientation) { 6161 @Override 6162 protected void invalidated() { 6163 sceneEffectiveOrientationInvalidated(); 6164 getRoot().applyCss(); 6165 } 6166 6167 @Override 6168 public Object getBean() { 6169 return Scene.this; 6170 } 6171 6172 @Override 6173 public String getName() { 6174 return "nodeOrientation"; 6175 } 6176 6177 @Override 6178 public CssMetaData getCssMetaData() { 6179 //TODO - not yet supported 6180 throw new UnsupportedOperationException("Not supported yet."); 6181 } 6182 }; 6183 } 6184 return nodeOrientation; 6185 } 6186 6187 public final NodeOrientation getEffectiveNodeOrientation() { 6188 if (effectiveNodeOrientation == null) { 6189 effectiveNodeOrientation = calcEffectiveNodeOrientation(); 6190 } 6191 6192 return effectiveNodeOrientation; 6193 } 6194 6195 /** 6196 * The effective node orientation of a scene resolves the inheritance of 6197 * node orientation, returning either left-to-right or right-to-left. 6198 * @since JavaFX 8.0 6199 */ 6200 public final ReadOnlyObjectProperty<NodeOrientation> 6201 effectiveNodeOrientationProperty() { 6202 if (effectiveNodeOrientationProperty == null) { 6203 effectiveNodeOrientationProperty = 6204 new EffectiveOrientationProperty(); 6205 } 6206 6207 return effectiveNodeOrientationProperty; 6208 } 6209 6210 private void parentEffectiveOrientationInvalidated() { 6211 if (getNodeOrientation() == NodeOrientation.INHERIT) { 6212 sceneEffectiveOrientationInvalidated(); 6213 } 6214 } 6215 6216 private void sceneEffectiveOrientationInvalidated() { 6217 effectiveNodeOrientation = null; 6218 6219 if (effectiveNodeOrientationProperty != null) { 6220 effectiveNodeOrientationProperty.invalidate(); 6221 } 6222 6223 getRoot().parentResolvedOrientationInvalidated(); 6224 } 6225 6226 private NodeOrientation calcEffectiveNodeOrientation() { 6227 NodeOrientation orientation = getNodeOrientation(); 6228 if (orientation == NodeOrientation.INHERIT) { 6229 Window window = getWindow(); 6230 if (window != null) { 6231 Window parent = null; 6232 if (window instanceof Stage) { 6233 parent = ((Stage)window).getOwner(); 6234 } else { 6235 if (window instanceof PopupWindow) { 6236 parent = ((PopupWindow)window).getOwnerWindow(); 6237 } 6238 } 6239 if (parent != null) { 6240 Scene scene = parent.getScene(); 6241 if (scene != null) return scene.getEffectiveNodeOrientation(); 6242 } 6243 } 6244 return NodeOrientation.LEFT_TO_RIGHT; 6245 } 6246 return orientation; 6247 } 6248 6249 private final class EffectiveOrientationProperty 6250 extends ReadOnlyObjectPropertyBase<NodeOrientation> { 6251 @Override 6252 public NodeOrientation get() { 6253 return getEffectiveNodeOrientation(); 6254 } 6255 6256 @Override 6257 public Object getBean() { 6258 return Scene.this; 6259 } 6260 6261 @Override 6262 public String getName() { 6263 return "effectiveNodeOrientation"; 6264 } 6265 6266 public void invalidate() { 6267 fireValueChangedEvent(); 6268 } 6269 } 6270 6271 private Map<Node, Accessible> accMap; 6272 Accessible removeAccessible(Node node) { 6273 if (accMap == null) return null; 6274 return accMap.remove(node); 6275 } 6276 6277 void addAccessible(Node node, Accessible acc) { 6278 if (accMap == null) { 6279 accMap = new HashMap<Node, Accessible>(); 6280 } 6281 accMap.put(node, acc); 6282 } 6283 6284 private void disposeAccessibles() { 6285 if (accMap != null) { 6286 for (Map.Entry<Node, Accessible> entry : accMap.entrySet()) { 6287 Node node = entry.getKey(); 6288 Accessible acc = entry.getValue(); 6289 if (node.accessible != null) { 6290 /* This node has already been initialized to another scene. 6291 * Note an accessible can be returned to the node before the 6292 * pulse if getAccessible() is called. In which case it must 6293 * already being removed from accMap. 6294 */ 6295 if (node.accessible == acc) { 6296 System.err.println("[A11y] 'node.accessible == acc' should never happen."); 6297 } 6298 if (node.getScene() == this) { 6299 System.err.println("[A11y] 'node.getScene() == this' should never happen."); 6300 } 6301 acc.dispose(); 6302 } else { 6303 if (node.getScene() == this) { 6304 node.accessible = acc; 6305 } else { 6306 acc.dispose(); 6307 } 6308 } 6309 } 6310 accMap.clear(); 6311 } 6312 } 6313 6314 private Accessible accessible; 6315 Accessible getAccessible() { 6316 /* 6317 * The accessible for the Scene should never be 6318 * requested when the peer is not set. 6319 * This can only happen in a error case where a 6320 * descender of this Scene was not disposed and 6321 * it still being used by the AT client and trying 6322 * to reach to the top level window. 6323 */ 6324 if (impl_peer == null) return null; 6325 if (accessible == null) { 6326 accessible = Application.GetApplication().createAccessible(); 6327 accessible.setEventHandler(new Accessible.EventHandler() { 6328 @Override public AccessControlContext getAccessControlContext() { 6329 return impl_getPeer().getAccessControlContext(); 6330 } 6331 6332 @Override public Object getAttribute(AccessibleAttribute attribute, 6333 Object... parameters) { 6334 switch (attribute) { 6335 case CHILDREN: { 6336 Parent root = getRoot(); 6337 if (root != null) { 6338 return FXCollections.observableArrayList(root); 6339 } 6340 break; 6341 } 6342 case TEXT: { 6343 Window w = getWindow(); 6344 if (w instanceof Stage) { 6345 return ((Stage)w).getTitle(); 6346 } 6347 break; 6348 } 6349 case NODE_AT_POINT: { 6350 Window window = getWindow(); 6351 /* is this screen to scene translation correct ? not considering camera ? */ 6352 Point2D pt = (Point2D)parameters[0]; 6353 PickResult res = pick(pt.getX() - getX() - window.getX(), pt.getY() - getY() - window.getY()); 6354 if (res != null) { 6355 Node node = res.getIntersectedNode(); 6356 if (node != null) return node; 6357 } 6358 return getRoot();//not sure 6359 } 6360 case ROLE: return AccessibleRole.PARENT; 6361 case SCENE: return Scene.this; 6362 case FOCUS_NODE: { 6363 if (transientFocusContainer != null) { 6364 return transientFocusContainer.queryAccessibleAttribute(AccessibleAttribute.FOCUS_NODE); 6365 } 6366 return getFocusOwner(); 6367 } 6368 default: 6369 } 6370 return super.getAttribute(attribute, parameters); 6371 } 6372 }); 6373 PlatformImpl.accessibilityActiveProperty().set(true); 6374 } 6375 return accessible; 6376 } 6377 }