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