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