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