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