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