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