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