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