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.scene.input.ExtendedInputMethodRequests; 29 import com.sun.javafx.tk.TKClipboard; 30 import javafx.animation.KeyFrame; 31 import javafx.animation.Timeline; 32 import javafx.application.ConditionalFeature; 33 import javafx.application.Platform; 34 import javafx.beans.DefaultProperty; 35 import javafx.beans.InvalidationListener; 36 import javafx.beans.NamedArg; 37 import javafx.beans.Observable; 38 import javafx.beans.property.ObjectProperty; 39 import javafx.beans.property.ObjectPropertyBase; 40 import javafx.beans.property.ReadOnlyBooleanProperty; 41 import javafx.beans.property.ReadOnlyDoubleProperty; 42 import javafx.beans.property.ReadOnlyDoubleWrapper; 43 import javafx.beans.property.ReadOnlyObjectProperty; 44 import javafx.beans.property.ReadOnlyObjectPropertyBase; 45 import javafx.beans.property.ReadOnlyObjectWrapper; 46 import javafx.beans.property.SimpleObjectProperty; 47 import javafx.beans.value.WritableValue; 48 import javafx.collections.ListChangeListener.Change; 49 import javafx.collections.ObservableList; 50 import javafx.collections.ObservableMap; 51 import javafx.css.CssMetaData; 52 import javafx.css.StyleableObjectProperty; 53 import javafx.event.ActionEvent; 54 import javafx.event.Event; 55 import javafx.event.EventDispatchChain; 56 import javafx.event.EventDispatcher; 57 import javafx.event.EventHandler; 58 import javafx.event.EventTarget; 59 import javafx.event.EventType; 60 import javafx.geometry.Bounds; 61 import javafx.geometry.NodeOrientation; 62 import javafx.geometry.Orientation; 63 import javafx.geometry.Point2D; 64 import javafx.geometry.Point3D; 65 import javafx.scene.image.WritableImage; 66 import javafx.scene.input.ContextMenuEvent; 67 import javafx.scene.input.DragEvent; 68 import javafx.scene.input.Dragboard; 69 import javafx.scene.input.GestureEvent; 70 import javafx.scene.input.InputMethodEvent; 71 import javafx.scene.input.InputMethodRequests; 72 import javafx.scene.input.InputMethodTextRun; 73 import javafx.scene.input.KeyCode; 74 import javafx.scene.input.KeyCombination; 75 import javafx.scene.input.KeyEvent; 76 import javafx.scene.input.Mnemonic; 77 import javafx.scene.input.MouseButton; 78 import javafx.scene.input.MouseDragEvent; 79 import javafx.scene.input.MouseEvent; 80 import javafx.scene.input.PickResult; 81 import javafx.scene.input.RotateEvent; 82 import javafx.scene.input.ScrollEvent; 83 import javafx.scene.input.SwipeEvent; 84 import javafx.scene.input.TouchEvent; 85 import javafx.scene.input.TouchPoint; 86 import javafx.scene.input.TransferMode; 87 import javafx.scene.input.ZoomEvent; 88 import javafx.scene.paint.Color; 89 import javafx.scene.paint.Paint; 90 import javafx.stage.PopupWindow; 91 import javafx.stage.Stage; 92 import javafx.stage.StageStyle; 93 import javafx.stage.Window; 94 import javafx.util.Callback; 95 import javafx.util.Duration; 96 97 import java.security.AccessControlContext; 98 import java.security.AccessController; 99 import java.security.PrivilegedAction; 100 import java.util.ArrayList; 101 import java.util.Arrays; 102 import java.util.EnumMap; 103 import java.util.EnumSet; 104 import java.util.HashMap; 105 import java.util.LinkedHashSet; 106 import java.util.LinkedList; 107 import java.util.List; 108 import java.util.Map; 109 import java.util.Set; 110 111 import com.sun.javafx.Logging; 112 import com.sun.javafx.Utils; 113 import com.sun.javafx.collections.TrackableObservableList; 114 import com.sun.javafx.css.StyleManager; 115 import com.sun.javafx.cursor.CursorFrame; 116 import com.sun.javafx.event.EventQueue; 117 import com.sun.javafx.geom.PickRay; 118 import com.sun.javafx.geom.Vec3d; 119 import com.sun.javafx.geom.transform.BaseTransform; 120 import com.sun.javafx.perf.PerformanceTracker; 121 import com.sun.javafx.robot.impl.FXRobotHelper; 122 import com.sun.javafx.runtime.SystemProperties; 123 import com.sun.javafx.scene.CssFlags; 124 import com.sun.javafx.scene.SceneEventDispatcher; 125 import com.sun.javafx.scene.SceneHelper; 126 import com.sun.javafx.scene.input.InputEventUtils; 127 import com.sun.javafx.scene.input.KeyCodeMap; 128 import com.sun.javafx.scene.input.PickResultChooser; 129 import com.sun.javafx.scene.traversal.Direction; 130 import com.sun.javafx.scene.traversal.TraversalEngine; 131 import com.sun.javafx.sg.prism.NGCamera; 132 import com.sun.javafx.sg.prism.NGLightBase; 133 import com.sun.javafx.tk.TKDragGestureListener; 134 import com.sun.javafx.tk.TKDragSourceListener; 135 import com.sun.javafx.tk.TKDropTargetListener; 136 import com.sun.javafx.tk.TKPulseListener; 137 import com.sun.javafx.tk.TKScene; 138 import com.sun.javafx.tk.TKSceneListener; 139 import com.sun.javafx.tk.TKScenePaintListener; 140 import com.sun.javafx.tk.TKStage; 141 import com.sun.javafx.tk.Toolkit; 142 import com.sun.javafx.scene.LayoutFlags; 143 import com.sun.prism.impl.PrismSettings; 144 145 import sun.util.logging.PlatformLogger; 146 import sun.util.logging.PlatformLogger.Level; 147 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER; 148 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED; 149 150 /** 151 * The JavaFX {@code Scene} class is the container for all content in a scene graph. 152 * The background of the scene is filled as specified by the {@code fill} property. 153 * <p> 154 * The application must specify the root {@code Node} for the scene graph by setting 155 * the {@code root} property. If a {@code Group} is used as the root, the 156 * contents of the scene graph will be clipped by the scene's width and height and 157 * changes to the scene's size (if user resizes the stage) will not alter the 158 * layout of the scene graph. If a resizable node (layout {@code Region} or 159 * {@code Control} is set as the root, then the root's size will track the 160 * scene's size, causing the contents to be relayed out as necessary. 161 * <p> 162 * The scene's size may be initialized by the application during construction. 163 * If no size is specified, the scene will automatically compute its initial 164 * size based on the preferred size of its content. If only one dimension is specified, 165 * the other dimension is computed using the specified dimension, respecting content bias 166 * of a root. 167 * 168 * <p> 169 * A default headlight will be added to a scene that contains {@code Shape3D} 170 * but without light source. This light source is a {@code Color.WHITE} 171 * {@code PointLight} placed at the camera position. 172 * 173 * <p> 174 * Scene objects must be constructed and modified on the 175 * JavaFX Application Thread. 176 * </p> 177 * 178 * <p>Example:</p> 179 * 180 * <p> 181 * <pre> 182 import javafx.scene.*; 183 import javafx.scene.paint.*; 184 import javafx.scene.shape.*; 185 186 Group root = new Group(); 187 Scene s = new Scene(root, 300, 300, Color.BLACK); 188 189 Rectangle r = new Rectangle(25,25,250,250); 190 r.setFill(Color.BLUE); 191 192 root.getChildren().add(r); 193 * </pre> 194 * </p> 195 * @since JavaFX 2.0 196 */ 197 @DefaultProperty("root") 198 public class Scene implements EventTarget { 199 200 private double widthSetByUser = -1.0; 201 private double heightSetByUser = -1.0; 202 private boolean sizeInitialized = false; 203 private final boolean depthBuffer; 204 private final SceneAntialiasing antiAliasing; 205 206 private int dirtyBits; 207 208 private final AccessControlContext acc = AccessController.getContext(); 209 210 private Camera defaultCamera; 211 212 //Neither width nor height are initialized and will be calculated according to content when this Scene 213 //is shown for the first time. 214 // public Scene() { 215 // //this(-1, -1, (Parent) new Group()); 216 // this(-1, -1, (Parent)null); 217 // } 218 219 /** 220 * Creates a Scene for a specific root Node. 221 * 222 * @param root The root node of the scene graph 223 * 224 * @throws IllegalStateException if this constructor is called on a thread 225 * other than the JavaFX Application Thread. 226 * @throws NullPointerException if root is null 227 */ 228 public Scene(@NamedArg("root") Parent root) { 229 this(root, -1, -1, Color.WHITE, false, SceneAntialiasing.DISABLED); 230 } 231 232 //Public constructor initializing public-init properties 233 //When width < 0, and or height < 0 is passed, then width and/or height are understood as unitialized 234 //Unitialized dimension is calculated when Scene is shown for the first time. 235 // public Scene( 236 // @Default("-1") double width, 237 // @Default("-1") double height) { 238 // //this(width, height, (Parent)new Group()); 239 // this(width, height, (Parent)null); 240 // } 241 // 242 // public Scene(double width, double height, Paint fill) { 243 // //this(width, height, (Parent) new Group()); 244 // this(width, height, (Parent)null); 245 // setFill(fill); 246 // } 247 248 /** 249 * Creates a Scene for a specific root Node with a specific size. 250 * 251 * @param root The root node of the scene graph 252 * @param width The width of the scene 253 * @param height The height of the scene 254 * 255 * @throws IllegalStateException if this constructor is called on a thread 256 * other than the JavaFX Application Thread. 257 * @throws NullPointerException if root is null 258 */ 259 public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height) { 260 this(root, width, height, Color.WHITE, false, SceneAntialiasing.DISABLED); 261 } 262 263 /** 264 * Creates a Scene for a specific root Node with a fill. 265 * 266 * @param root The parent 267 * @param fill The fill 268 * 269 * @throws IllegalStateException if this constructor is called on a thread 270 * other than the JavaFX Application Thread. 271 * @throws NullPointerException if root is null 272 */ 273 public Scene(@NamedArg("root") Parent root, @NamedArg(value="fill", defaultValue="WHITE") Paint fill) { 274 this(root, -1, -1, fill, false, SceneAntialiasing.DISABLED); 275 } 276 277 /** 278 * Creates a Scene for a specific root Node with a specific size and fill. 279 * 280 * @param root The root node of the scene graph 281 * @param width The width of the scene 282 * @param height The height of the scene 283 * @param fill The fill 284 * 285 * @throws IllegalStateException if this constructor is called on a thread 286 * other than the JavaFX Application Thread. 287 * @throws NullPointerException if root is null 288 */ 289 public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height, 290 @NamedArg(value="fill", defaultValue="WHITE") Paint fill) { 291 this(root, width, height, fill, false, SceneAntialiasing.DISABLED); 292 } 293 294 /** 295 * Constructs a scene consisting of a root, with a dimension of width and 296 * height, and specifies whether a depth buffer is created for this scene. 297 * 298 * @param root The root node of the scene graph 299 * @param width The width of the scene 300 * @param height The height of the scene 301 * @param depthBuffer The depth buffer flag 302 * <p> 303 * The depthBuffer flag is a conditional feature and its default value is 304 * false. See 305 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 306 * for more information. 307 * 308 * @throws IllegalStateException if this constructor is called on a thread 309 * other than the JavaFX Application Thread. 310 * @throws NullPointerException if root is null 311 * 312 * @see javafx.scene.Node#setDepthTest(DepthTest) 313 */ 314 public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height, @NamedArg("depthBuffer") boolean depthBuffer) { 315 this(root, width, height, Color.WHITE, depthBuffer, SceneAntialiasing.DISABLED); 316 } 317 318 /** 319 * Constructs a scene consisting of a root, with a dimension of width and 320 * height, specifies whether a depth buffer is created for this scene and 321 * specifies whether scene anti-aliasing is requested. 322 * 323 * @param root The root node of the scene graph 324 * @param width The width of the scene 325 * @param height The height of the scene 326 * @param depthBuffer The depth buffer flag 327 * @param antiAliasing The scene anti-aliasing attribute. A value of 328 * {@code null} is treated as DISABLED. 329 * <p> 330 * The depthBuffer and antiAliasing are conditional features. With the 331 * respective default values of: false and {@code SceneAntialiasing.DISABLED}. See 332 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 333 * for more information. 334 * 335 * @throws IllegalStateException if this constructor is called on a thread 336 * other than the JavaFX Application Thread. 337 * @throws NullPointerException if root is null 338 * 339 * @see javafx.scene.Node#setDepthTest(DepthTest) 340 * @since JavaFX 8.0 341 */ 342 public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height, 343 @NamedArg("depthBuffer") boolean depthBuffer, 344 @NamedArg(value="antiAliasing", defaultValue="DISABLED") SceneAntialiasing antiAliasing) { 345 this(root, width, height, Color.WHITE, depthBuffer, antiAliasing); 346 347 if (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED && 348 !Toolkit.getToolkit().isAntiAliasingSupported()) 349 { 350 String logname = Scene.class.getName(); 351 PlatformLogger.getLogger(logname).warning("System can't support " 352 + "antiAliasing"); 353 } 354 } 355 356 private Scene(Parent root, double width, double height, Paint fill, 357 boolean depthBuffer, SceneAntialiasing antiAliasing) { 358 this.depthBuffer = depthBuffer; 359 this.antiAliasing = antiAliasing; 360 if (root == null) { 361 throw new NullPointerException("Root cannot be null"); 362 } 363 364 if ((depthBuffer || (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED)) 365 && !Platform.isSupported(ConditionalFeature.SCENE3D)) { 366 String logname = Scene.class.getName(); 367 PlatformLogger.getLogger(logname).warning("System can't support " 368 + "ConditionalFeature.SCENE3D"); 369 } 370 371 Toolkit.getToolkit().checkFxUserThread(); 372 init(); 373 setRoot(root); 374 init(width, height); 375 setFill(fill); 376 } 377 378 static { 379 PerformanceTracker.setSceneAccessor(new PerformanceTracker.SceneAccessor() { 380 public void setPerfTracker(Scene scene, PerformanceTracker tracker) { 381 synchronized (trackerMonitor) { 382 scene.tracker = tracker; 383 } 384 } 385 public PerformanceTracker getPerfTracker(Scene scene) { 386 synchronized (trackerMonitor) { 387 return scene.tracker; 388 } 389 } 390 }); 391 FXRobotHelper.setSceneAccessor(new FXRobotHelper.FXRobotSceneAccessor() { 392 public void processKeyEvent(Scene scene, KeyEvent keyEvent) { 393 scene.impl_processKeyEvent(keyEvent); 394 } 395 public void processMouseEvent(Scene scene, MouseEvent mouseEvent) { 396 scene.impl_processMouseEvent(mouseEvent); 397 } 398 public void processScrollEvent(Scene scene, ScrollEvent scrollEvent) { 399 scene.processGestureEvent(scrollEvent, scene.scrollGesture); 400 } 401 public ObservableList<Node> getChildren(Parent parent) { 402 return parent.getChildren(); //was impl_getChildren 403 } 404 public Object renderToImage(Scene scene, Object platformImage) { 405 return scene.snapshot(null).impl_getPlatformImage(); 406 } 407 }); 408 SceneHelper.setSceneAccessor( 409 new SceneHelper.SceneAccessor() { 410 @Override 411 public void setPaused(boolean paused) { 412 Scene.paused = paused; 413 } 414 415 @Override 416 public void parentEffectiveOrientationInvalidated( 417 final Scene scene) { 418 scene.parentEffectiveOrientationInvalidated(); 419 } 420 421 @Override 422 public Camera getEffectiveCamera(Scene scene) { 423 return scene.getEffectiveCamera(); 424 } 425 426 @Override 427 public Scene createPopupScene(Parent root) { 428 return new Scene(root) { 429 @Override 430 void doLayoutPass() { 431 resizeRootToPreferredSize(getRoot()); 432 super.doLayoutPass(); 433 } 434 435 @Override 436 void resizeRootOnSceneSizeChange( 437 double newWidth, 438 double newHeight) { 439 // don't resize 440 } 441 }; 442 } 443 }); 444 } 445 446 // Reserve space for 30 nodes in the dirtyNodes set. 447 private static final int MIN_DIRTY_CAPACITY = 30; 448 449 // For debugging 450 private static boolean inSynchronizer = false; 451 private static boolean inMousePick = false; 452 private static boolean allowPGAccess = false; 453 private static int pgAccessCount = 0; 454 455 // Flag set by the Toolkit when we are paused for JMX debugging 456 private static boolean paused = false; 457 458 /** 459 * Used for debugging purposes. Returns true if we are in either the 460 * mouse event code (picking) or the synchronizer, or if the scene is 461 * not yet initialized, 462 * 463 */ 464 static boolean isPGAccessAllowed() { 465 return inSynchronizer || inMousePick || allowPGAccess; 466 } 467 468 /** 469 * @treatAsPrivate implementation detail 470 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 471 */ 472 @Deprecated 473 public static void impl_setAllowPGAccess(boolean flag) { 474 if (Utils.assertionEnabled()) { 475 if (flag) { 476 pgAccessCount++; 477 allowPGAccess = true; 478 } 479 else { 480 if (pgAccessCount <= 0) { 481 throw new java.lang.AssertionError("*** pgAccessCount underflow"); 482 } 483 if (--pgAccessCount == 0) { 484 allowPGAccess = false; 485 } 486 } 487 } 488 } 489 490 /** 491 * If true, use the platform's drag gesture detection 492 * else use Scene-level detection as per DnDGesture.process(MouseEvent, List) 493 */ 494 private static final boolean PLATFORM_DRAG_GESTURE_INITIATION = false; 495 496 /** 497 * Set of dirty nodes; processed once per frame by the synchronizer. 498 * When a node's state changes such that it becomes "dirty" with respect 499 * to the graphics stack and requires synchronization, then that node 500 * is added to this list. Note that if state on the Node changes, but it 501 * was already dirty, then the Node doesn't add itself again. 502 * <p> 503 * Because at initialization time every node in the scene graph is dirty, 504 * we have a special state and special code path during initialization 505 * that does not involve adding each node to the dirtyNodes list. When 506 * dirtyNodes is null, that means this Scene has not yet been synchronized. 507 * A good default size is then created for the dirtyNodes list. 508 * <p> 509 * We double-buffer the set so that we can add new nodes to the 510 * set while processing the existing set. This avoids our having to 511 * take a snapshot of the set (e.g., with toArray()) and reduces garbage. 512 */ 513 private Node[] dirtyNodes; 514 private int dirtyNodesSize; 515 516 /** 517 * Add the specified node to this scene's dirty list. Called by the 518 * markDirty method in Node or when the Node's scene changes. 519 */ 520 void addToDirtyList(Node n) { 521 Toolkit.getToolkit().checkFxUserThread(); 522 523 if (dirtyNodes == null || dirtyNodesSize == 0) { 524 if (impl_peer != null) { 525 Toolkit.getToolkit().requestNextPulse(); 526 } 527 } 528 529 if (dirtyNodes != null) { 530 if (dirtyNodesSize == dirtyNodes.length) { 531 Node[] tmp = new Node[dirtyNodesSize + (dirtyNodesSize >> 1)]; 532 System.arraycopy(dirtyNodes, 0, tmp, 0, dirtyNodesSize); 533 dirtyNodes = tmp; 534 } 535 dirtyNodes[dirtyNodesSize++] = n; 536 } 537 } 538 539 private void doCSSPass() { 540 final Parent sceneRoot = getRoot(); 541 // 542 // RT-17547: when the tree is synchronized, the dirty bits are 543 // are cleared but the cssFlag might still be something other than 544 // clean. 545 // 546 // Before RT-17547, the code checked the dirty bit. But this is 547 // superfluous since the dirty bit will be set if the flag is not clean, 548 // but the flag will never be anything other than clean if the dirty 549 // bit is not set. The dirty bit is still needed, however, since setting 550 // it ensures a pulse if no other dirty bits have been set. 551 // 552 // For the purpose of showing the change, the dirty bit 553 // check code was commented out and not removed. 554 // 555 // if (sceneRoot.impl_isDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS)) { 556 if (sceneRoot.cssFlag != CssFlags.CLEAN) { 557 // The dirty bit isn't checked but we must ensure it is cleared. 558 // The cssFlag is set to clean in either Node.processCSS or 559 // Node.impl_processCSS(boolean) 560 sceneRoot.impl_clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS); 561 sceneRoot.processCSS(null); 562 } 563 } 564 565 void doLayoutPass() { 566 final Parent r = getRoot(); 567 if (r != null) { 568 r.layout(); 569 } 570 } 571 572 /** 573 * The peer of this scene 574 * 575 * @treatAsPrivate implementation detail 576 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 577 */ 578 @Deprecated 579 private TKScene impl_peer; 580 581 /** 582 * Get Scene's peer 583 * 584 * @treatAsPrivate implementation detail 585 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 586 */ 587 @Deprecated 588 public TKScene impl_getPeer() { 589 return impl_peer; 590 } 591 592 /** 593 * The scene pulse listener that gets called on toolkit pulses 594 */ 595 ScenePulseListener scenePulseListener = new ScenePulseListener(); 596 597 /** 598 * @treatAsPrivate implementation detail 599 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 600 */ 601 @Deprecated 602 public TKPulseListener impl_getScenePulseListener() { 603 if (SystemProperties.isDebug()) { 604 return scenePulseListener; 605 } 606 return null; 607 } 608 609 /** 610 * Return the defined {@code SceneAntialiasing} for this {@code Scene}. 611 * <p> 612 * Note: this is a conditional feature. See 613 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 614 * and {@link javafx.scene.SceneAntialiasing SceneAntialiasing} 615 * for more information. 616 * @since JavaFX 8.0 617 */ 618 public final SceneAntialiasing getAntiAliasing() { 619 return antiAliasing; 620 } 621 622 private boolean getAntiAliasingInternal() { 623 return (antiAliasing != null && 624 Toolkit.getToolkit().isAntiAliasingSupported() && 625 Platform.isSupported(ConditionalFeature.SCENE3D)) ? 626 antiAliasing != SceneAntialiasing.DISABLED : false; 627 } 628 629 /** 630 * The {@code Window} for this {@code Scene} 631 */ 632 private ReadOnlyObjectWrapper<Window> window; 633 634 private void setWindow(Window value) { 635 windowPropertyImpl().set(value); 636 } 637 638 public final Window getWindow() { 639 return window == null ? null : window.get(); 640 } 641 642 public final ReadOnlyObjectProperty<Window> windowProperty() { 643 return windowPropertyImpl().getReadOnlyProperty(); 644 } 645 646 private ReadOnlyObjectWrapper<Window> windowPropertyImpl() { 647 if (window == null) { 648 window = new ReadOnlyObjectWrapper<Window>() { 649 private Window oldWindow; 650 651 @Override protected void invalidated() { 652 final Window newWindow = get(); 653 getKeyHandler().windowForSceneChanged(oldWindow, newWindow); 654 if (oldWindow != null) { 655 impl_disposePeer(); 656 } 657 if (newWindow != null) { 658 impl_initPeer(); 659 } 660 parentEffectiveOrientationInvalidated(); 661 662 oldWindow = newWindow; 663 } 664 665 @Override 666 public Object getBean() { 667 return Scene.this; 668 } 669 670 @Override 671 public String getName() { 672 return "window"; 673 } 674 }; 675 } 676 return window; 677 } 678 679 /** 680 * @treatAsPrivate implementation detail 681 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 682 */ 683 @Deprecated 684 public void impl_setWindow(Window value) { 685 setWindow(value); 686 } 687 688 /** 689 * @treatAsPrivate implementation detail 690 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 691 */ 692 @Deprecated 693 public void impl_initPeer() { 694 assert impl_peer == null; 695 696 Window window = getWindow(); 697 // impl_initPeer() is only called from Window, either when the window 698 // is being shown, or the window scene is being changed. In any case 699 // this scene's window cannot be null. 700 assert window != null; 701 702 TKStage windowPeer = window.impl_getPeer(); 703 if (windowPeer == null) { 704 // This is fine, the window is not visible. impl_initPeer() will 705 // be called again later, when the window is being shown. 706 return; 707 } 708 709 PerformanceTracker.logEvent("Scene.initPeer started"); 710 711 impl_setAllowPGAccess(true); 712 713 Toolkit tk = Toolkit.getToolkit(); 714 impl_peer = windowPeer.createTKScene(isDepthBufferInternal(), getAntiAliasingInternal(), acc); 715 PerformanceTracker.logEvent("Scene.initPeer TKScene created"); 716 impl_peer.setTKSceneListener(new ScenePeerListener()); 717 impl_peer.setTKScenePaintListener(new ScenePeerPaintListener()); 718 PerformanceTracker.logEvent("Scene.initPeer TKScene set"); 719 impl_peer.setRoot(getRoot().impl_getPeer()); 720 impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill())); 721 getEffectiveCamera().impl_updatePeer(); 722 impl_peer.setCamera((NGCamera) getEffectiveCamera().impl_getPeer()); 723 impl_peer.markDirty(); 724 PerformanceTracker.logEvent("Scene.initPeer TKScene initialized"); 725 726 impl_setAllowPGAccess(false); 727 728 tk.addSceneTkPulseListener(scenePulseListener); 729 // listen to dnd gestures coming from the platform 730 if (PLATFORM_DRAG_GESTURE_INITIATION) { 731 if (dragGestureListener == null) { 732 dragGestureListener = new DragGestureListener(); 733 } 734 tk.registerDragGestureListener(impl_peer, EnumSet.allOf(TransferMode.class), dragGestureListener); 735 } 736 tk.enableDrop(impl_peer, new DropTargetListener()); 737 tk.installInputMethodRequests(impl_peer, new InputMethodRequestsDelegate()); 738 739 PerformanceTracker.logEvent("Scene.initPeer finished"); 740 } 741 742 /** 743 * @treatAsPrivate implementation detail 744 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 745 */ 746 @Deprecated 747 public void impl_disposePeer() { 748 if (impl_peer == null) { 749 // This is fine, the window is either not shown yet and there is no 750 // need in disposing scene peer, or is hidden and impl_disposePeer() 751 // has already been called. 752 return; 753 } 754 755 PerformanceTracker.logEvent("Scene.disposePeer started"); 756 757 Toolkit tk = Toolkit.getToolkit(); 758 tk.removeSceneTkPulseListener(scenePulseListener); 759 impl_peer.dispose(); 760 impl_peer = null; 761 762 PerformanceTracker.logEvent("Scene.disposePeer finished"); 763 } 764 765 DnDGesture dndGesture = null; 766 DragGestureListener dragGestureListener; 767 /** 768 * The horizontal location of this {@code Scene} on the {@code Window}. 769 */ 770 private ReadOnlyDoubleWrapper x; 771 772 private final void setX(double value) { 773 xPropertyImpl().set(value); 774 } 775 776 public final double getX() { 777 return x == null ? 0.0 : x.get(); 778 } 779 780 public final ReadOnlyDoubleProperty xProperty() { 781 return xPropertyImpl().getReadOnlyProperty(); 782 } 783 784 private ReadOnlyDoubleWrapper xPropertyImpl() { 785 if (x == null) { 786 x = new ReadOnlyDoubleWrapper(this, "x"); 787 } 788 return x; 789 } 790 791 /** 792 * The vertical location of this {@code Scene} on the {@code Window}. 793 */ 794 private ReadOnlyDoubleWrapper y; 795 796 private final void setY(double value) { 797 yPropertyImpl().set(value); 798 } 799 800 public final double getY() { 801 return y == null ? 0.0 : y.get(); 802 } 803 804 public final ReadOnlyDoubleProperty yProperty() { 805 return yPropertyImpl().getReadOnlyProperty(); 806 } 807 808 private ReadOnlyDoubleWrapper yPropertyImpl() { 809 if (y == null) { 810 y = new ReadOnlyDoubleWrapper(this, "y"); 811 } 812 return y; 813 } 814 815 /** 816 * The width of this {@code Scene} 817 */ 818 private ReadOnlyDoubleWrapper width; 819 820 private final void setWidth(double value) { 821 widthPropertyImpl().set(value); 822 } 823 824 public final double getWidth() { 825 return width == null ? 0.0 : width.get(); 826 } 827 828 public final ReadOnlyDoubleProperty widthProperty() { 829 return widthPropertyImpl().getReadOnlyProperty(); 830 } 831 832 private ReadOnlyDoubleWrapper widthPropertyImpl() { 833 if (width == null) { 834 width = new ReadOnlyDoubleWrapper() { 835 836 @Override 837 protected void invalidated() { 838 final Parent _root = getRoot(); 839 //TODO - use a better method to update mirroring 840 if (_root.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { 841 _root.impl_transformsChanged(); 842 } 843 if (_root.isResizable()) { 844 resizeRootOnSceneSizeChange(get() - _root.getLayoutX() - _root.getTranslateX(), _root.getLayoutBounds().getHeight()); 845 } 846 847 getEffectiveCamera().setViewWidth(get()); 848 } 849 850 @Override 851 public Object getBean() { 852 return Scene.this; 853 } 854 855 @Override 856 public String getName() { 857 return "width"; 858 } 859 }; 860 } 861 return width; 862 } 863 864 /** 865 * The height of this {@code Scene} 866 */ 867 private ReadOnlyDoubleWrapper height; 868 869 private final void setHeight(double value) { 870 heightPropertyImpl().set(value); 871 } 872 873 public final double getHeight() { 874 return height == null ? 0.0 : height.get(); 875 } 876 877 public final ReadOnlyDoubleProperty heightProperty() { 878 return heightPropertyImpl().getReadOnlyProperty(); 879 } 880 881 private ReadOnlyDoubleWrapper heightPropertyImpl() { 882 if (height == null) { 883 height = new ReadOnlyDoubleWrapper() { 884 885 @Override 886 protected void invalidated() { 887 final Parent _root = getRoot(); 888 if (_root.isResizable()) { 889 resizeRootOnSceneSizeChange(_root.getLayoutBounds().getWidth(), get() - _root.getLayoutY() - _root.getTranslateY()); 890 } 891 892 getEffectiveCamera().setViewHeight(get()); 893 } 894 895 @Override 896 public Object getBean() { 897 return Scene.this; 898 } 899 900 @Override 901 public String getName() { 902 return "height"; 903 } 904 }; 905 } 906 return height; 907 } 908 909 void resizeRootOnSceneSizeChange(double newWidth, double newHeight) { 910 getRoot().resize(newWidth, newHeight); 911 } 912 913 // Reusable target wrapper (to avoid creating new one for each picking) 914 private TargetWrapper tmpTargetWrapper = new TargetWrapper(); 915 916 /** 917 * Specifies the type of camera use for rendering this {@code Scene}. 918 * If {@code camera} is null, a parallel camera is used for rendering. 919 * It is illegal to set a camera that belongs to other {@code Scene} 920 * or {@code SubScene}. 921 * <p> 922 * Note: this is a conditional feature. See 923 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 924 * for more information. 925 * 926 * @defaultValue null 927 */ 928 private ObjectProperty<Camera> camera; 929 930 public final void setCamera(Camera value) { 931 cameraProperty().set(value); 932 } 933 934 public final Camera getCamera() { 935 return camera == null ? null : camera.get(); 936 } 937 938 public final ObjectProperty<Camera> cameraProperty() { 939 if (camera == null) { 940 camera = new ObjectPropertyBase<Camera>() { 941 Camera oldCamera = null; 942 943 @Override 944 protected void invalidated() { 945 Camera _value = get(); 946 if (_value != null) { 947 if (_value instanceof PerspectiveCamera 948 && !Platform.isSupported(ConditionalFeature.SCENE3D)) { 949 String logname = Scene.class.getName(); 950 PlatformLogger.getLogger(logname).warning("System can't support " 951 + "ConditionalFeature.SCENE3D"); 952 } 953 // Illegal value if it belongs to other scene or any subscene 954 if ((_value.getScene() != null && _value.getScene() != Scene.this) 955 || _value.getSubScene() != null) { 956 throw new IllegalArgumentException(_value 957 + "is already part of other scene or subscene"); 958 } 959 // throws exception if the camera already has a different owner 960 _value.setOwnerScene(Scene.this); 961 _value.setViewWidth(getWidth()); 962 _value.setViewHeight(getHeight()); 963 } 964 if (oldCamera != null && oldCamera != _value) { 965 oldCamera.setOwnerScene(null); 966 } 967 oldCamera = _value; 968 } 969 970 @Override 971 public Object getBean() { 972 return Scene.this; 973 } 974 975 @Override 976 public String getName() { 977 return "camera"; 978 } 979 }; 980 } 981 return camera; 982 } 983 984 Camera getEffectiveCamera() { 985 final Camera cam = getCamera(); 986 if (cam == null 987 || (cam instanceof PerspectiveCamera 988 && !Platform.isSupported(ConditionalFeature.SCENE3D))) { 989 if (defaultCamera == null) { 990 defaultCamera = new ParallelCamera(); 991 defaultCamera.setOwnerScene(this); 992 defaultCamera.setViewWidth(getWidth()); 993 defaultCamera.setViewHeight(getHeight()); 994 } 995 return defaultCamera; 996 } 997 998 return cam; 999 } 1000 1001 // Used by the camera 1002 void markCameraDirty() { 1003 markDirty(DirtyBits.CAMERA_DIRTY); 1004 setNeedsRepaint(); 1005 } 1006 1007 void markCursorDirty() { 1008 markDirty(DirtyBits.CURSOR_DIRTY); 1009 } 1010 1011 /** 1012 * Defines the background fill of this {@code Scene}. Both a {@code null} 1013 * value meaning paint no background and a {@link javafx.scene.paint.Paint} 1014 * with transparency are supported, but what is painted behind it will 1015 * depend on the platform. The default value is the color white. 1016 * 1017 * @defaultValue WHITE 1018 */ 1019 private ObjectProperty<Paint> fill; 1020 1021 public final void setFill(Paint value) { 1022 fillProperty().set(value); 1023 } 1024 1025 public final Paint getFill() { 1026 return fill == null ? Color.WHITE : fill.get(); 1027 } 1028 1029 public final ObjectProperty<Paint> fillProperty() { 1030 if (fill == null) { 1031 fill = new ObjectPropertyBase<Paint>(Color.WHITE) { 1032 1033 @Override 1034 protected void invalidated() { 1035 markDirty(DirtyBits.FILL_DIRTY); 1036 } 1037 1038 @Override 1039 public Object getBean() { 1040 return Scene.this; 1041 } 1042 1043 @Override 1044 public String getName() { 1045 return "fill"; 1046 } 1047 }; 1048 } 1049 return fill; 1050 } 1051 1052 /** 1053 * Defines the root {@code Node} of the scene graph. 1054 * If a {@code Group} is used as the root, the 1055 * contents of the scene graph will be clipped by the scene's width and height and 1056 * changes to the scene's size (if user resizes the stage) will not alter the 1057 * layout of the scene graph. If a resizable node (layout {@code Region} or 1058 * {@code Control}) is set as the root, then the root's size will track the 1059 * scene's size, causing the contents to be relayed out as necessary. 1060 * 1061 * Scene doesn't accept null root. 1062 * 1063 */ 1064 private ObjectProperty<Parent> root; 1065 1066 public final void setRoot(Parent value) { 1067 rootProperty().set(value); 1068 } 1069 1070 public final Parent getRoot() { 1071 return root == null ? null : root.get(); 1072 } 1073 1074 Parent oldRoot; 1075 public final ObjectProperty<Parent> rootProperty() { 1076 if (root == null) { 1077 root = new ObjectPropertyBase<Parent>() { 1078 1079 private void forceUnbind() { 1080 System.err.println("Unbinding illegal root."); 1081 unbind(); 1082 } 1083 1084 @Override 1085 protected void invalidated() { 1086 Parent _value = get(); 1087 1088 if (_value == null) { 1089 if (isBound()) forceUnbind(); 1090 throw new NullPointerException("Scene's root cannot be null"); 1091 } 1092 1093 if (_value.getParent() != null) { 1094 if (isBound()) forceUnbind(); 1095 throw new IllegalArgumentException(_value + 1096 "is already inside a scene-graph and cannot be set as root"); 1097 } 1098 if (_value.getClipParent() != null) { 1099 if (isBound()) forceUnbind(); 1100 throw new IllegalArgumentException(_value + 1101 "is set as a clip on another node, so cannot be set as root"); 1102 } 1103 if (_value.getScene() != null && _value.getScene().getRoot() == _value && _value.getScene() != Scene.this) { 1104 if (isBound()) forceUnbind(); 1105 throw new IllegalArgumentException(_value + 1106 "is already set as root of another scene"); 1107 } 1108 1109 if (oldRoot != null) { 1110 oldRoot.setScenes(null, null); 1111 oldRoot.setImpl_traversalEngine(null); 1112 } 1113 oldRoot = _value; 1114 if (_value.getImpl_traversalEngine() == null) { 1115 _value.setImpl_traversalEngine(new TraversalEngine(_value, true)); 1116 } 1117 _value.getStyleClass().add(0, "root"); 1118 _value.setScenes(Scene.this, null); 1119 markDirty(DirtyBits.ROOT_DIRTY); 1120 _value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable 1121 _value.requestLayout(); 1122 } 1123 1124 @Override 1125 public Object getBean() { 1126 return Scene.this; 1127 } 1128 1129 @Override 1130 public String getName() { 1131 return "root"; 1132 } 1133 }; 1134 } 1135 return root; 1136 } 1137 1138 void setNeedsRepaint() { 1139 if (this.impl_peer != null) { 1140 impl_peer.entireSceneNeedsRepaint(); 1141 } 1142 } 1143 1144 // Process CSS and layout and sync the scene prior to the snapshot 1145 // operation of the given node for this scene (currently the node 1146 // is unused but could possibly be used in the future to optimize this) 1147 void doCSSLayoutSyncForSnapshot(Node node) { 1148 if (!sizeInitialized) { 1149 preferredSize(); 1150 } else { 1151 doCSSPass(); 1152 } 1153 1154 // we do not need pulse in the snapshot code 1155 // because this scene can be stage-less 1156 doLayoutPass(); 1157 1158 if (!paused) { 1159 getRoot().updateBounds(); 1160 if (impl_peer != null) { 1161 impl_peer.waitForRenderingToComplete(); 1162 impl_peer.waitForSynchronization(); 1163 try { 1164 // Run the synchronizer while holding the render lock 1165 scenePulseListener.synchronizeSceneNodes(); 1166 } finally { 1167 impl_peer.releaseSynchronization(false); 1168 } 1169 } else { 1170 scenePulseListener.synchronizeSceneNodes(); 1171 } 1172 } 1173 1174 } 1175 1176 // Shared method for Scene.snapshot and Node.snapshot. It is static because 1177 // we might be doing a Node snapshot with a null scene 1178 static WritableImage doSnapshot(Scene scene, 1179 double x, double y, double w, double h, 1180 Node root, BaseTransform transform, boolean depthBuffer, 1181 Paint fill, Camera camera, WritableImage wimg) { 1182 1183 Toolkit tk = Toolkit.getToolkit(); 1184 Toolkit.ImageRenderingContext context = new Toolkit.ImageRenderingContext(); 1185 1186 int xMin = (int)Math.floor(x); 1187 int yMin = (int)Math.floor(y); 1188 int xMax = (int)Math.ceil(x + w); 1189 int yMax = (int)Math.ceil(y + h); 1190 int width = Math.max(xMax - xMin, 1); 1191 int height = Math.max(yMax - yMin, 1); 1192 if (wimg == null) { 1193 wimg = new WritableImage(width, height); 1194 } else { 1195 width = (int)wimg.getWidth(); 1196 height = (int)wimg.getHeight(); 1197 } 1198 1199 impl_setAllowPGAccess(true); 1200 context.x = xMin; 1201 context.y = yMin; 1202 context.width = width; 1203 context.height = height; 1204 context.transform = transform; 1205 context.depthBuffer = depthBuffer; 1206 context.root = root.impl_getPeer(); 1207 context.platformPaint = fill == null ? null : tk.getPaint(fill); 1208 double cameraViewWidth = 1.0; 1209 double cameraViewHeight = 1.0; 1210 if (camera != null) { 1211 // temporarily adjust camera viewport to the snapshot size 1212 cameraViewWidth = camera.getViewWidth(); 1213 cameraViewHeight = camera.getViewHeight(); 1214 camera.setViewWidth(width); 1215 camera.setViewHeight(height); 1216 camera.impl_updatePeer(); 1217 context.camera = camera.impl_getPeer(); 1218 } else { 1219 context.camera = null; 1220 } 1221 1222 // Grab the lights from the scene 1223 context.lights = null; 1224 if (scene != null && !scene.lights.isEmpty()) { 1225 context.lights = new NGLightBase[scene.lights.size()]; 1226 for (int i = 0; i < scene.lights.size(); i++) { 1227 context.lights[i] = scene.lights.get(i).impl_getPeer(); 1228 } 1229 } 1230 1231 Toolkit.WritableImageAccessor accessor = Toolkit.getWritableImageAccessor(); 1232 context.platformImage = accessor.getTkImageLoader(wimg); 1233 impl_setAllowPGAccess(false); 1234 Object tkImage = tk.renderToImage(context); 1235 accessor.loadTkImage(wimg, tkImage); 1236 1237 if (camera != null) { 1238 impl_setAllowPGAccess(true); 1239 camera.setViewWidth(cameraViewWidth); 1240 camera.setViewHeight(cameraViewHeight); 1241 camera.impl_updatePeer(); 1242 impl_setAllowPGAccess(false); 1243 } 1244 1245 // if this scene belongs to some stage 1246 // we need to mark the entire scene as dirty 1247 // because dirty logic is buggy 1248 if (scene != null && scene.impl_peer != null) { 1249 scene.setNeedsRepaint(); 1250 } 1251 1252 return wimg; 1253 } 1254 1255 /** 1256 * Implementation method for snapshot 1257 */ 1258 private WritableImage doSnapshot(WritableImage img) { 1259 // TODO: no need to do CSS, layout or sync in the deferred case, 1260 // if this scene is attached to a visible stage 1261 doCSSLayoutSyncForSnapshot(getRoot()); 1262 1263 double w = getWidth(); 1264 double h = getHeight(); 1265 BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM; 1266 1267 return doSnapshot(this, 0, 0, w, h, 1268 getRoot(), transform, isDepthBufferInternal(), 1269 getFill(), getEffectiveCamera(), img); 1270 } 1271 1272 // Pulse listener used to run all deferred (async) snapshot requests 1273 private static TKPulseListener snapshotPulseListener = null; 1274 1275 private static List<Runnable> snapshotRunnableListA; 1276 private static List<Runnable> snapshotRunnableListB; 1277 private static List<Runnable> snapshotRunnableList; 1278 1279 static void addSnapshotRunnable(Runnable r) { 1280 Toolkit.getToolkit().checkFxUserThread(); 1281 1282 if (snapshotPulseListener == null) { 1283 snapshotRunnableListA = new ArrayList<Runnable>(); 1284 snapshotRunnableListB = new ArrayList<Runnable>(); 1285 snapshotRunnableList = snapshotRunnableListA; 1286 1287 snapshotPulseListener = new TKPulseListener() { 1288 @Override public void pulse() { 1289 if (snapshotRunnableList.size() > 0) { 1290 List<Runnable> runnables = snapshotRunnableList; 1291 if (snapshotRunnableList == snapshotRunnableListA) { 1292 snapshotRunnableList = snapshotRunnableListB; 1293 } else { 1294 snapshotRunnableList = snapshotRunnableListA; 1295 } 1296 for (Runnable r : runnables) { 1297 try { 1298 r.run(); 1299 } catch (Throwable th) { 1300 System.err.println("Exception in snapshot runnable"); 1301 th.printStackTrace(System.err); 1302 } 1303 } 1304 runnables.clear(); 1305 } 1306 } 1307 }; 1308 1309 // Add listener that will be called after all of the scenes have 1310 // had layout and CSS processing, and have been synced 1311 Toolkit.getToolkit().addPostSceneTkPulseListener(snapshotPulseListener); 1312 } 1313 snapshotRunnableList.add(r); 1314 Toolkit.getToolkit().requestNextPulse(); 1315 } 1316 1317 /** 1318 * Takes a snapshot of this scene and returns the rendered image when 1319 * it is ready. 1320 * CSS and layout processing will be done for the scene prior to 1321 * rendering it. 1322 * The entire destination image is cleared using the fill {@code Paint} 1323 * of this scene. The nodes in the scene are then rendered to the image. 1324 * The point (0,0) in scene coordinates is mapped to (0,0) in the image. 1325 * If the image is smaller than the size of the scene, then the rendering 1326 * will be clipped by the image. 1327 * 1328 * <p> 1329 * When taking a snapshot of a scene that is being animated, either 1330 * explicitly by the application or implicitly (such as chart animation), 1331 * the snapshot will be rendered based on the state of the scene graph at 1332 * the moment the snapshot is taken and will not reflect any subsequent 1333 * animation changes. 1334 * </p> 1335 * 1336 * @param image the writable image that will be used to hold the rendered scene. 1337 * It may be null in which case a new WritableImage will be constructed. 1338 * If the image is non-null, the scene will be rendered into the 1339 * existing image. 1340 * In this case, the width and height of the image determine the area 1341 * that is rendered instead of the width and height of the scene. 1342 * 1343 * @throws IllegalStateException if this method is called on a thread 1344 * other than the JavaFX Application Thread. 1345 * 1346 * @return the rendered image 1347 * @since JavaFX 2.2 1348 */ 1349 public WritableImage snapshot(WritableImage image) { 1350 if (!paused) { 1351 Toolkit.getToolkit().checkFxUserThread(); 1352 } 1353 1354 return doSnapshot(image); 1355 } 1356 1357 /** 1358 * Takes a snapshot of this scene at the next frame and calls the 1359 * specified callback method when the image is ready. 1360 * CSS and layout processing will be done for the scene prior to 1361 * rendering it. 1362 * The entire destination image is cleared using the fill {@code Paint} 1363 * of this scene. The nodes in the scene are then rendered to the image. 1364 * The point (0,0) in scene coordinates is mapped to (0,0) in the image. 1365 * If the image is smaller than the size of the scene, then the rendering 1366 * will be clipped by the image. 1367 * 1368 * <p> 1369 * This is an asynchronous call, which means that other 1370 * events or animation might be processed before the scene is rendered. 1371 * If any such events modify a node in the scene that modification will 1372 * be reflected in the rendered image (as it will also be reflected in 1373 * the frame rendered to the Stage). 1374 * </p> 1375 * 1376 * <p> 1377 * When taking a snapshot of a scene that is being animated, either 1378 * explicitly by the application or implicitly (such as chart animation), 1379 * the snapshot will be rendered based on the state of the scene graph at 1380 * the moment the snapshot is taken and will not reflect any subsequent 1381 * animation changes. 1382 * </p> 1383 * 1384 * @param callback a class whose call method will be called when the image 1385 * is ready. The SnapshotResult that is passed into the call method of 1386 * the callback will contain the rendered image and the source scene 1387 * that was rendered. The callback parameter must not be null. 1388 * 1389 * @param image the writable image that will be used to hold the rendered scene. 1390 * It may be null in which case a new WritableImage will be constructed. 1391 * If the image is non-null, the scene will be rendered into the 1392 * existing image. 1393 * In this case, the width and height of the image determine the area 1394 * that is rendered instead of the width and height of the scene. 1395 * 1396 * @throws IllegalStateException if this method is called on a thread 1397 * other than the JavaFX Application Thread. 1398 * 1399 * @throws NullPointerException if the callback parameter is null. 1400 * @since JavaFX 2.2 1401 */ 1402 public void snapshot(Callback<SnapshotResult, Void> callback, WritableImage image) { 1403 Toolkit.getToolkit().checkFxUserThread(); 1404 if (callback == null) { 1405 throw new NullPointerException("The callback must not be null"); 1406 } 1407 1408 final Callback<SnapshotResult, Void> theCallback = callback; 1409 final WritableImage theImage = image; 1410 1411 // Create a deferred runnable that will be run from a pulse listener 1412 // that is called after all of the scenes have been synced but before 1413 // any of them have been rendered. 1414 final Runnable snapshotRunnable = new Runnable() { 1415 @Override public void run() { 1416 WritableImage img = doSnapshot(theImage); 1417 // System.err.println("Calling snapshot callback"); 1418 SnapshotResult result = new SnapshotResult(img, Scene.this, null); 1419 try { 1420 Void v = theCallback.call(result); 1421 } catch (Throwable th) { 1422 System.err.println("Exception in snapshot callback"); 1423 th.printStackTrace(System.err); 1424 } 1425 } 1426 }; 1427 // System.err.println("Schedule a snapshot in the future"); 1428 addSnapshotRunnable(snapshotRunnable); 1429 } 1430 1431 /** 1432 * Defines the mouse cursor for this {@code Scene}. 1433 */ 1434 private ObjectProperty<Cursor> cursor; 1435 1436 public final void setCursor(Cursor value) { 1437 cursorProperty().set(value); 1438 } 1439 1440 public final Cursor getCursor() { 1441 return cursor == null ? null : cursor.get(); 1442 } 1443 1444 public final ObjectProperty<Cursor> cursorProperty() { 1445 if (cursor == null) { 1446 cursor = new ObjectPropertyBase<Cursor>() { 1447 @Override 1448 protected void invalidated() { 1449 markCursorDirty(); 1450 } 1451 1452 @Override 1453 public Object getBean() { 1454 return Scene.this; 1455 } 1456 1457 @Override 1458 public String getName() { 1459 return "cursor"; 1460 } 1461 }; 1462 } 1463 return cursor; 1464 } 1465 1466 /** 1467 * Looks for any node within the scene graph based on the specified CSS selector. 1468 * If more than one node matches the specified selector, this function 1469 * returns the first of them. 1470 * If no nodes are found with this id, then null is returned. 1471 * 1472 * @param selector The css selector to look up 1473 * @return the {@code Node} in the scene which matches the CSS {@code selector}, 1474 * or {@code null} if none is found. 1475 */ 1476 public Node lookup(String selector) { 1477 return getRoot().lookup(selector); 1478 } 1479 /** 1480 * A ObservableList of string URLs linking to the stylesheets to use with this scene's 1481 * contents. For additional information about using CSS with the 1482 * scene graph, see the <a href="doc-files/cssref.html">CSS Reference 1483 * Guide</a>. 1484 */ 1485 private final ObservableList<String> stylesheets = new TrackableObservableList<String>() { 1486 @Override 1487 protected void onChanged(Change<String> c) { 1488 StyleManager.getInstance().stylesheetsChanged(Scene.this, c); 1489 // RT-9784 - if stylesheet is removed, reset styled properties to 1490 // their initial value. 1491 c.reset(); 1492 while(c.next()) { 1493 if (c.wasRemoved() == false) { 1494 continue; 1495 } 1496 break; // no point in resetting more than once... 1497 } 1498 getRoot().impl_reapplyCSS(); 1499 } 1500 }; 1501 1502 /** 1503 * Gets an observable list of string URLs linking to the stylesheets to use 1504 * with this scene's contents. For additional information about using CSS 1505 * with the scene graph, see the <a href="doc-files/cssref.html">CSS Reference 1506 * Guide</a>. 1507 * 1508 * @return the list of stylesheets to use with this scene 1509 */ 1510 public final ObservableList<String> getStylesheets() { return stylesheets; } 1511 1512 /** 1513 * Retrieves the depth buffer attribute for this scene. 1514 * @return the depth buffer attribute. 1515 */ 1516 public final boolean isDepthBuffer() { 1517 return depthBuffer; 1518 } 1519 1520 boolean isDepthBufferInternal() { 1521 if (!Platform.isSupported(ConditionalFeature.SCENE3D)) { 1522 return false; 1523 } 1524 return depthBuffer; 1525 } 1526 1527 private void init(double width, double height) { 1528 if (width >= 0) { 1529 widthSetByUser = width; 1530 setWidth((float)width); 1531 } 1532 if (height >= 0) { 1533 heightSetByUser = height; 1534 setHeight((float)height); 1535 } 1536 sizeInitialized = (widthSetByUser >= 0 && heightSetByUser >= 0); 1537 } 1538 1539 private void init() { 1540 if (PerformanceTracker.isLoggingEnabled()) { 1541 PerformanceTracker.logEvent("Scene.init for [" + this + "]"); 1542 } 1543 mouseHandler = new MouseHandler(); 1544 clickGenerator = new ClickGenerator(); 1545 1546 if (PerformanceTracker.isLoggingEnabled()) { 1547 PerformanceTracker.logEvent("Scene.init for [" + this + "] - finished"); 1548 } 1549 } 1550 1551 private void preferredSize() { 1552 final Parent root = getRoot(); 1553 1554 // one or the other isn't initialized, need to perform layout in 1555 // order to ensure we can properly measure the preferred size of the 1556 // scene 1557 doCSSPass(); 1558 1559 resizeRootToPreferredSize(root); 1560 doLayoutPass(); 1561 1562 if (widthSetByUser < 0) { 1563 setWidth(root.isResizable()? root.getLayoutX() + root.getTranslateX() + root.getLayoutBounds().getWidth() : 1564 root.getBoundsInParent().getMaxX()); 1565 } else { 1566 setWidth(widthSetByUser); 1567 } 1568 1569 if (heightSetByUser < 0) { 1570 setHeight(root.isResizable()? root.getLayoutY() + root.getTranslateY() + root.getLayoutBounds().getHeight() : 1571 root.getBoundsInParent().getMaxY()); 1572 } else { 1573 setHeight(heightSetByUser); 1574 } 1575 1576 sizeInitialized = (getWidth() > 0) && (getHeight() > 0); 1577 1578 PerformanceTracker.logEvent("Scene preferred bounds computation complete"); 1579 } 1580 1581 final void resizeRootToPreferredSize(Parent root) { 1582 final double preferredWidth; 1583 final double preferredHeight; 1584 1585 final Orientation contentBias = root.getContentBias(); 1586 if (contentBias == null) { 1587 preferredWidth = getPreferredWidth(root, widthSetByUser, -1); 1588 preferredHeight = getPreferredHeight(root, heightSetByUser, -1); 1589 } else if (contentBias == Orientation.HORIZONTAL) { 1590 // height depends on width 1591 preferredWidth = getPreferredWidth(root, widthSetByUser, -1); 1592 preferredHeight = getPreferredHeight(root, heightSetByUser, 1593 preferredWidth); 1594 } else /* if (contentBias == Orientation.VERTICAL) */ { 1595 // width depends on height 1596 preferredHeight = getPreferredHeight(root, heightSetByUser, -1); 1597 preferredWidth = getPreferredWidth(root, widthSetByUser, 1598 preferredHeight); 1599 } 1600 1601 root.resize(preferredWidth, preferredHeight); 1602 } 1603 1604 private static double getPreferredWidth(Parent root, 1605 double forcedWidth, 1606 double height) { 1607 if (forcedWidth >= 0) { 1608 return forcedWidth; 1609 } 1610 final double normalizedHeight = (height >= 0) ? height : -1; 1611 return root.boundedSize(root.prefWidth(normalizedHeight), 1612 root.minWidth(normalizedHeight), 1613 root.maxWidth(normalizedHeight)); 1614 } 1615 1616 private static double getPreferredHeight(Parent root, 1617 double forcedHeight, 1618 double width) { 1619 if (forcedHeight >= 0) { 1620 return forcedHeight; 1621 } 1622 final double normalizedWidth = (width >= 0) ? width : -1; 1623 return root.boundedSize(root.prefHeight(normalizedWidth), 1624 root.minHeight(normalizedWidth), 1625 root.maxHeight(normalizedWidth)); 1626 } 1627 1628 /** 1629 * @treatAsPrivate implementation detail 1630 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1631 */ 1632 @Deprecated 1633 public void impl_preferredSize() { 1634 preferredSize(); 1635 } 1636 1637 private PerformanceTracker tracker; 1638 private static final Object trackerMonitor = new Object(); 1639 1640 // mouse events handling 1641 private MouseHandler mouseHandler; 1642 private ClickGenerator clickGenerator; 1643 1644 // gesture events handling 1645 private Point2D cursorScreenPos; 1646 private Point2D cursorScenePos; 1647 1648 private static class TouchGesture { 1649 EventTarget target; 1650 Point2D sceneCoords; 1651 Point2D screenCoords; 1652 boolean finished; 1653 } 1654 1655 private final TouchGesture scrollGesture = new TouchGesture(); 1656 private final TouchGesture zoomGesture = new TouchGesture(); 1657 private final TouchGesture rotateGesture = new TouchGesture(); 1658 private final TouchGesture swipeGesture = new TouchGesture(); 1659 1660 // touch events handling 1661 private TouchMap touchMap = new TouchMap(); 1662 private TouchEvent nextTouchEvent = null; 1663 private TouchPoint[] touchPoints = null; 1664 private int touchEventSetId = 0; 1665 private int touchPointIndex = 0; 1666 private Map<Integer, EventTarget> touchTargets = 1667 new HashMap<Integer, EventTarget>(); 1668 1669 /** 1670 * @treatAsPrivate implementation detail 1671 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1672 */ 1673 // SB-dependency: RT-22747 has been filed to track this 1674 @Deprecated 1675 public void impl_processMouseEvent(MouseEvent e) { 1676 mouseHandler.process(e, false); 1677 } 1678 1679 private void processMenuEvent(double x2, double y2, double xAbs, double yAbs, boolean isKeyboardTrigger) { 1680 EventTarget eventTarget = null; 1681 Scene.inMousePick = true; 1682 if (isKeyboardTrigger) { 1683 Node sceneFocusOwner = getFocusOwner(); 1684 1685 // for keyboard triggers set coordinates inside focus owner 1686 final double xOffset = xAbs - x2; 1687 final double yOffset = yAbs - y2; 1688 if (sceneFocusOwner != null) { 1689 final Bounds bounds = sceneFocusOwner.localToScene( 1690 sceneFocusOwner.getBoundsInLocal()); 1691 x2 = bounds.getMinX() + bounds.getWidth() / 4; 1692 y2 = bounds.getMinY() + bounds.getHeight() / 2; 1693 eventTarget = sceneFocusOwner; 1694 } else { 1695 x2 = Scene.this.getWidth() / 4; 1696 y2 = Scene.this.getWidth() / 2; 1697 eventTarget = Scene.this; 1698 } 1699 1700 xAbs = x2 + xOffset; 1701 yAbs = y2 + yOffset; 1702 } 1703 1704 final PickResult res = pick(x2, y2); 1705 1706 if (!isKeyboardTrigger) { 1707 eventTarget = res.getIntersectedNode(); 1708 if (eventTarget == null) { 1709 eventTarget = this; 1710 } 1711 } 1712 1713 if (eventTarget != null) { 1714 ContextMenuEvent context = new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED, 1715 x2, y2, xAbs, yAbs, isKeyboardTrigger, res); 1716 Event.fireEvent(eventTarget, context); 1717 } 1718 Scene.inMousePick = false; 1719 } 1720 1721 private void processGestureEvent(GestureEvent e, TouchGesture gesture) { 1722 EventTarget pickedTarget = null; 1723 1724 if (e.getEventType() == ZoomEvent.ZOOM_STARTED || 1725 e.getEventType() == RotateEvent.ROTATION_STARTED || 1726 e.getEventType() == ScrollEvent.SCROLL_STARTED) { 1727 gesture.target = null; 1728 gesture.finished = false; 1729 } 1730 1731 if (gesture.target != null && (!gesture.finished || e.isInertia())) { 1732 pickedTarget = gesture.target; 1733 } else { 1734 pickedTarget = e.getPickResult().getIntersectedNode(); 1735 if (pickedTarget == null) { 1736 pickedTarget = this; 1737 } 1738 } 1739 1740 if (e.getEventType() == ZoomEvent.ZOOM_STARTED || 1741 e.getEventType() == RotateEvent.ROTATION_STARTED || 1742 e.getEventType() == ScrollEvent.SCROLL_STARTED) { 1743 gesture.target = pickedTarget; 1744 } 1745 if (e.getEventType() != ZoomEvent.ZOOM_FINISHED && 1746 e.getEventType() != RotateEvent.ROTATION_FINISHED && 1747 e.getEventType() != ScrollEvent.SCROLL_FINISHED && 1748 !e.isInertia()) { 1749 gesture.sceneCoords = new Point2D(e.getSceneX(), e.getSceneY()); 1750 gesture.screenCoords = new Point2D(e.getScreenX(), e.getScreenY()); 1751 } 1752 1753 if (pickedTarget != null) { 1754 Event.fireEvent(pickedTarget, e); 1755 } 1756 1757 if (e.getEventType() == ZoomEvent.ZOOM_FINISHED || 1758 e.getEventType() == RotateEvent.ROTATION_FINISHED || 1759 e.getEventType() == ScrollEvent.SCROLL_FINISHED) { 1760 gesture.finished = true; 1761 } 1762 } 1763 1764 private void processTouchEvent(TouchEvent e, TouchPoint[] touchPoints) { 1765 inMousePick = true; 1766 touchEventSetId++; 1767 1768 List<TouchPoint> touchList = Arrays.asList(touchPoints); 1769 1770 // fire all the events 1771 for (TouchPoint tp : touchPoints) { 1772 if (tp.getTarget() != null) { 1773 EventType<TouchEvent> type = null; 1774 switch (tp.getState()) { 1775 case MOVED: 1776 type = TouchEvent.TOUCH_MOVED; 1777 break; 1778 case PRESSED: 1779 type = TouchEvent.TOUCH_PRESSED; 1780 break; 1781 case RELEASED: 1782 type = TouchEvent.TOUCH_RELEASED; 1783 break; 1784 case STATIONARY: 1785 type = TouchEvent.TOUCH_STATIONARY; 1786 break; 1787 } 1788 1789 for (TouchPoint t : touchPoints) { 1790 t.impl_reset(); 1791 } 1792 1793 TouchEvent te = new TouchEvent(type, tp, touchList, 1794 touchEventSetId, e.isShiftDown(), e.isControlDown(), 1795 e.isAltDown(), e.isMetaDown()); 1796 1797 Event.fireEvent(tp.getTarget(), te); 1798 } 1799 } 1800 1801 // process grabbing 1802 for (TouchPoint tp : touchPoints) { 1803 EventTarget grabbed = tp.getGrabbed(); 1804 if (grabbed != null) { 1805 touchTargets.put(tp.getId(), grabbed); 1806 }; 1807 1808 if (grabbed == null || tp.getState() == TouchPoint.State.RELEASED) { 1809 touchTargets.remove(tp.getId()); 1810 } 1811 } 1812 1813 inMousePick = false; 1814 } 1815 1816 /** 1817 * Note: The only user of this method is in unit test: PickAndContainTest. 1818 */ 1819 Node test_pick(double x, double y) { 1820 inMousePick = true; 1821 PickResult result = mouseHandler.pickNode(new PickRay(x, y, 1, 1822 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); 1823 inMousePick = false; 1824 if (result != null) { 1825 return result.getIntersectedNode(); 1826 } 1827 return null; 1828 } 1829 1830 private PickResult pick(final double x, final double y) { 1831 pick(tmpTargetWrapper, x, y); 1832 return tmpTargetWrapper.getResult(); 1833 } 1834 1835 private boolean isInScene(double x, double y) { 1836 if (x < 0 || y < 0 || x > getWidth() || y > getHeight()) { 1837 return false; 1838 } 1839 1840 Window w = getWindow(); 1841 if (w instanceof Stage 1842 && ((Stage) w).getStyle() == StageStyle.TRANSPARENT 1843 && getFill() == null) { 1844 return false; 1845 } 1846 1847 return true; 1848 } 1849 1850 private void pick(TargetWrapper target, final double x, final double y) { 1851 final PickRay pickRay = getEffectiveCamera().computePickRay( 1852 x, y, null); 1853 1854 final double mag = pickRay.getDirectionNoClone().length(); 1855 pickRay.getDirectionNoClone().normalize(); 1856 final PickResult res = mouseHandler.pickNode(pickRay); 1857 if (res != null) { 1858 target.setNodeResult(res); 1859 } else { 1860 //TODO: is this the intersection with projection plane? 1861 Vec3d o = pickRay.getOriginNoClone(); 1862 Vec3d d = pickRay.getDirectionNoClone(); 1863 target.setSceneResult(new PickResult( 1864 null, new Point3D( 1865 o.x + mag * d.x, 1866 o.y + mag * d.y, 1867 o.z + mag * d.z), 1868 mag), 1869 isInScene(x, y) ? this : null); 1870 } 1871 } 1872 1873 /*************************************************************************** 1874 * * 1875 * Key Events and Focus Traversal * 1876 * * 1877 **************************************************************************/ 1878 1879 /* 1880 * We cannot initialize keyHandler in init because some of the triggers 1881 * access it before the init block. 1882 * No clue why def keyHandler = bind lazy {KeyHandler{scene:this};} 1883 * does not compile. 1884 */ 1885 private KeyHandler keyHandler = null; 1886 private KeyHandler getKeyHandler() { 1887 if (keyHandler == null) { 1888 keyHandler = new KeyHandler(); 1889 } 1890 return keyHandler; 1891 } 1892 /** 1893 * Set to true if something has happened to the focused node that makes 1894 * it no longer eligible to have the focus. 1895 * 1896 */ 1897 private boolean focusDirty = true; 1898 1899 final void setFocusDirty(boolean value) { 1900 if (!focusDirty) { 1901 Toolkit.getToolkit().requestNextPulse(); 1902 } 1903 focusDirty = value; 1904 } 1905 1906 final boolean isFocusDirty() { 1907 return focusDirty; 1908 } 1909 1910 /** 1911 * This is a map from focusTraversable nodes within this scene 1912 * to instances of a traversal engine. The traversal engine is 1913 * either the instance for the scene itself, or for a Parent 1914 * nested somewhere within this scene. 1915 * 1916 * This has package access for testing purposes. 1917 */ 1918 Map traversalRegistry; // Map<Node,TraversalEngine> 1919 1920 /** 1921 * Searches up the scene graph for a Parent with a traversal engine. 1922 */ 1923 private TraversalEngine lookupTraversalEngine(Node node) { 1924 Parent p = node.getParent(); 1925 1926 while (p != null) { 1927 if (p.getImpl_traversalEngine() != null) { 1928 return p.getImpl_traversalEngine(); 1929 } 1930 p = p.getParent(); 1931 } 1932 1933 // This shouldn't ever occur, since walking up the tree 1934 // should always find the Scene's root, which always has 1935 // a traversal engine. But if for some reason we get here, 1936 // just return the root's traversal engine. 1937 1938 return getRoot().getImpl_traversalEngine(); 1939 } 1940 1941 /** 1942 * Registers a traversable node with a traversal engine 1943 * on this scene. 1944 */ 1945 void registerTraversable(Node n) { 1946 initializeInternalEventDispatcher(); 1947 1948 final TraversalEngine te = lookupTraversalEngine(n); 1949 if (te != null) { 1950 if (traversalRegistry == null) { 1951 traversalRegistry = new HashMap(); 1952 } 1953 traversalRegistry.put(n, te); 1954 te.reg(n); 1955 } 1956 } 1957 1958 /** 1959 * Unregisters a traversable node from this scene. 1960 */ 1961 void unregisterTraversable(Node n) { 1962 final TraversalEngine te = (TraversalEngine) traversalRegistry.remove(n); 1963 if (te != null) { 1964 te.unreg(n); 1965 } 1966 } 1967 1968 /** 1969 * Traverses focus from the given node in the given direction. 1970 */ 1971 void traverse(Node node, Direction dir) { 1972 /* 1973 ** if the registry is null then there are no 1974 ** registered traversable nodes in this scene 1975 */ 1976 if (traversalRegistry != null) { 1977 TraversalEngine te = (TraversalEngine) traversalRegistry.get(node); 1978 if (te == null) { 1979 te = lookupTraversalEngine(node); 1980 } 1981 te.trav(node, dir); 1982 } 1983 } 1984 1985 /** 1986 * Moves the focus to a reasonable initial location. Called when a scene's 1987 * focus is dirty and there's no current owner, or if the owner has been 1988 * removed from the scene. 1989 */ 1990 private void focusInitial() { 1991 getRoot().getImpl_traversalEngine().getTopLeftFocusableNode(); 1992 } 1993 1994 /** 1995 * Moves the focus to a reasonble location "near" the given node. 1996 * Called when the focused node is no longer eligible to have 1997 * the focus because it has become invisible or disabled. This 1998 * function assumes that it is still a member of the same scene. 1999 */ 2000 private void focusIneligible(Node node) { 2001 traverse(node, Direction.NEXT); 2002 } 2003 2004 /** 2005 * @treatAsPrivate implementation detail 2006 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2007 */ 2008 // SB-dependency: RT-24668 has been filed to track this 2009 @Deprecated 2010 public void impl_processKeyEvent(KeyEvent e) { 2011 if (dndGesture != null) { 2012 if (!dndGesture.processKey(e)) { 2013 dndGesture = null; 2014 } 2015 } 2016 2017 getKeyHandler().process(e); 2018 } 2019 2020 void requestFocus(Node node) { 2021 getKeyHandler().requestFocus(node); 2022 } 2023 2024 private Node oldFocusOwner; 2025 2026 /** 2027 * The scene's current focus owner node. This node's "focused" 2028 * variable might be false if this scene has no window, or if the 2029 * window is inactive (window.focused == false). 2030 * @since JavaFX 2.2 2031 */ 2032 private ReadOnlyObjectWrapper<Node> focusOwner = new ReadOnlyObjectWrapper<Node>(this, "focusOwner") { 2033 2034 @Override 2035 protected void invalidated() { 2036 if (oldFocusOwner != null) { 2037 ((Node.FocusedProperty) oldFocusOwner.focusedProperty()).store(false); 2038 } 2039 Node value = get(); 2040 if (value != null) { 2041 ((Node.FocusedProperty) value.focusedProperty()).store(keyHandler.windowFocused); 2042 if (value != oldFocusOwner) { 2043 value.getScene().impl_enableInputMethodEvents( 2044 value.getInputMethodRequests() != null 2045 && value.getOnInputMethodTextChanged() != null); 2046 } 2047 } 2048 // for the rest of the method we need to update the oldFocusOwner 2049 // and use a local copy of it because the user handlers can cause 2050 // recurrent calls of requestFocus 2051 Node localOldOwner = oldFocusOwner; 2052 oldFocusOwner = value; 2053 if (localOldOwner != null) { 2054 ((Node.FocusedProperty) localOldOwner.focusedProperty()).notifyListeners(); 2055 } 2056 if (value != null) { 2057 ((Node.FocusedProperty) value.focusedProperty()).notifyListeners(); 2058 } 2059 PlatformLogger logger = Logging.getFocusLogger(); 2060 if (logger.isLoggable(Level.FINE)) { 2061 if (value == get()) { 2062 logger.fine("Changed focus from " 2063 + localOldOwner + " to " + value); 2064 } else { 2065 logger.fine("Changing focus from " 2066 + localOldOwner + " to " + value 2067 + " canceled by nested requestFocus"); 2068 } 2069 } 2070 } 2071 }; 2072 2073 public final Node getFocusOwner() { 2074 return focusOwner.get(); 2075 } 2076 2077 public final ReadOnlyObjectProperty<Node> focusOwnerProperty() { 2078 return focusOwner.getReadOnlyProperty(); 2079 } 2080 2081 // For testing. 2082 void focusCleanup() { 2083 scenePulseListener.focusCleanup(); 2084 } 2085 2086 private void processInputMethodEvent(InputMethodEvent e) { 2087 Node node = getFocusOwner(); 2088 if (node != null) { 2089 node.fireEvent(e); 2090 } 2091 } 2092 2093 /** 2094 * @treatAsPrivate implementation detail 2095 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2096 */ 2097 @Deprecated 2098 public void impl_enableInputMethodEvents(boolean enable) { 2099 if (impl_peer != null) { 2100 impl_peer.enableInputMethodEvents(enable); 2101 } 2102 } 2103 2104 /** 2105 * Returns true if this scene is quiescent, i.e. it has no activity 2106 * pending on it such as CSS processing or layout requests. 2107 * 2108 * Intended to be used for tests only 2109 * 2110 * @return boolean indicating whether the scene is quiescent 2111 */ 2112 boolean isQuiescent() { 2113 final Parent r = getRoot(); 2114 return !isFocusDirty() 2115 && (r == null || (r.cssFlag == CssFlags.CLEAN && 2116 r.layoutFlag == LayoutFlags.CLEAN)); 2117 } 2118 2119 /** 2120 * A listener for pulses, used for testing. If non-null, this is called at 2121 * the very end of ScenePulseListener.pulse(). 2122 * 2123 * Intended to be used for tests only 2124 */ 2125 Runnable testPulseListener = null; 2126 2127 /** 2128 * Set the specified dirty bit and mark the peer as dirty 2129 */ 2130 private void markDirty(DirtyBits dirtyBit) { 2131 setDirty(dirtyBit); 2132 if (impl_peer != null) { 2133 Toolkit.getToolkit().requestNextPulse(); 2134 } 2135 } 2136 2137 /** 2138 * Set the specified dirty bit 2139 */ 2140 private void setDirty(DirtyBits dirtyBit) { 2141 dirtyBits |= dirtyBit.getMask(); 2142 } 2143 2144 /** 2145 * Test the specified dirty bit 2146 */ 2147 private boolean isDirty(DirtyBits dirtyBit) { 2148 return ((dirtyBits & dirtyBit.getMask()) != 0); 2149 } 2150 2151 /** 2152 * Test whether the dirty bits are empty 2153 */ 2154 private boolean isDirtyEmpty() { 2155 return dirtyBits == 0; 2156 } 2157 2158 /** 2159 * Clear all dirty bits 2160 */ 2161 private void clearDirty() { 2162 dirtyBits = 0; 2163 } 2164 2165 private enum DirtyBits { 2166 FILL_DIRTY, 2167 ROOT_DIRTY, 2168 CAMERA_DIRTY, 2169 LIGHTS_DIRTY, 2170 CURSOR_DIRTY; 2171 2172 private int mask; 2173 2174 private DirtyBits() { 2175 mask = 1 << ordinal(); 2176 } 2177 2178 public final int getMask() { return mask; } 2179 } 2180 2181 private List<LightBase> lights = new ArrayList<>(); 2182 2183 // @param light must not be null 2184 final void addLight(LightBase light) { 2185 if (!lights.contains(light)) { 2186 lights.add(light); 2187 markDirty(DirtyBits.LIGHTS_DIRTY); 2188 } 2189 } 2190 2191 final void removeLight(LightBase light) { 2192 if (lights.remove(light)) { 2193 markDirty(DirtyBits.LIGHTS_DIRTY); 2194 } 2195 } 2196 2197 /** 2198 * PG Light synchronizer. 2199 */ 2200 private void syncLights() { 2201 if (!isDirty(DirtyBits.LIGHTS_DIRTY)) { 2202 return; 2203 } 2204 inSynchronizer = true; 2205 NGLightBase peerLights[] = impl_peer.getLights(); 2206 if (!lights.isEmpty() || (peerLights != null)) { 2207 if (lights.isEmpty()) { 2208 impl_peer.setLights(null); 2209 } else { 2210 if (peerLights == null || peerLights.length < lights.size()) { 2211 peerLights = new NGLightBase[lights.size()]; 2212 } 2213 int i = 0; 2214 for (; i < lights.size(); i++) { 2215 peerLights[i] = lights.get(i).impl_getPeer(); 2216 } 2217 // Clear the rest of the list 2218 while (i < peerLights.length && peerLights[i] != null) { 2219 peerLights[i++] = null; 2220 } 2221 impl_peer.setLights(peerLights); 2222 } 2223 } 2224 inSynchronizer = false; 2225 } 2226 2227 //INNER CLASSES 2228 2229 /******************************************************************************* 2230 * * 2231 * Scene Pulse Listener * 2232 * * 2233 ******************************************************************************/ 2234 2235 class ScenePulseListener implements TKPulseListener { 2236 2237 private boolean firstPulse = true; 2238 2239 /** 2240 * PG synchronizer. Called once per frame from the pulse listener. 2241 * This function calls the synchronizePGNode method on each node in 2242 * the dirty list. 2243 */ 2244 private void synchronizeSceneNodes() { 2245 Toolkit.getToolkit().checkFxUserThread(); 2246 2247 Scene.inSynchronizer = true; 2248 2249 // if dirtyNodes is null then that means this Scene has not yet been 2250 // synchronized, and so we will simply synchronize every node in the 2251 // scene and then create the dirty nodes array list 2252 if (Scene.this.dirtyNodes == null) { 2253 // must do this recursively 2254 syncAll(getRoot()); 2255 dirtyNodes = new Node[MIN_DIRTY_CAPACITY]; 2256 2257 } else { 2258 // This is not the first time this scene has been synchronized, 2259 // so we will only synchronize those nodes that need it 2260 for (int i = 0 ; i < dirtyNodesSize; ++i) { 2261 Node node = dirtyNodes[i]; 2262 dirtyNodes[i] = null; 2263 if (node.getScene() == Scene.this) { 2264 node.impl_syncPeer(); 2265 } 2266 } 2267 dirtyNodesSize = 0; 2268 } 2269 2270 Scene.inSynchronizer = false; 2271 } 2272 2273 /** 2274 * Recursive function for synchronizing every node in the scenegraph. 2275 * The return value is the number of nodes in the graph. 2276 */ 2277 private int syncAll(Node node) { 2278 node.impl_syncPeer(); 2279 int size = 1; 2280 if (node instanceof Parent) { 2281 Parent p = (Parent) node; 2282 final int childrenCount = p.getChildren().size(); 2283 2284 for (int i = 0; i < childrenCount; i++) { 2285 Node n = p.getChildren().get(i); 2286 if (n != null) { 2287 size += syncAll(n); 2288 } 2289 } 2290 } else if (node instanceof SubScene) { 2291 SubScene subScene = (SubScene)node; 2292 size += syncAll(subScene.getRoot()); 2293 } 2294 if (node.getClip() != null) { 2295 size += syncAll(node.getClip()); 2296 } 2297 2298 return size; 2299 } 2300 2301 private void synchronizeSceneProperties() { 2302 inSynchronizer = true; 2303 if (isDirty(DirtyBits.ROOT_DIRTY)) { 2304 impl_peer.setRoot(getRoot().impl_getPeer()); 2305 } 2306 2307 if (isDirty(DirtyBits.FILL_DIRTY)) { 2308 Toolkit tk = Toolkit.getToolkit(); 2309 impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill())); 2310 } 2311 2312 // new camera was set on the scene or old camera changed 2313 final Camera cam = getEffectiveCamera(); 2314 if (isDirty(DirtyBits.CAMERA_DIRTY)) { 2315 cam.impl_updatePeer(); 2316 impl_peer.setCamera((NGCamera) cam.impl_getPeer()); 2317 } 2318 2319 clearDirty(); 2320 inSynchronizer = false; 2321 } 2322 2323 /** 2324 * The focus is considered dirty if something happened to 2325 * the scene graph that may require the focus to be moved. 2326 * This must handle cases where (a) the focus owner may have 2327 * become ineligible to have the focus, and (b) where the focus 2328 * owner is null and a node may have become traversable and eligible. 2329 */ 2330 private void focusCleanup() { 2331 if (Scene.this.isFocusDirty()) { 2332 final Node oldOwner = Scene.this.getFocusOwner(); 2333 if (oldOwner == null) { 2334 Scene.this.focusInitial(); 2335 } else if (oldOwner.getScene() != Scene.this) { 2336 Scene.this.requestFocus(null); 2337 Scene.this.focusInitial(); 2338 } else if (!oldOwner.isCanReceiveFocus()) { 2339 Scene.this.requestFocus(null); 2340 Scene.this.focusIneligible(oldOwner); 2341 } 2342 Scene.this.setFocusDirty(false); 2343 } 2344 } 2345 2346 @Override 2347 public void pulse() { 2348 if (Scene.this.tracker != null) { 2349 Scene.this.tracker.pulse(); 2350 } 2351 if (firstPulse) { 2352 PerformanceTracker.logEvent("Scene - first repaint"); 2353 } 2354 2355 focusCleanup(); 2356 2357 if (PULSE_LOGGING_ENABLED) { 2358 long start = System.currentTimeMillis(); 2359 Scene.this.doCSSPass(); 2360 PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "CSS Pass"); 2361 2362 start = System.currentTimeMillis(); 2363 Scene.this.doLayoutPass(); 2364 PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Layout Pass"); 2365 } else { 2366 Scene.this.doCSSPass(); 2367 Scene.this.doLayoutPass(); 2368 } 2369 2370 boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty(); 2371 if (dirty) { 2372 getRoot().updateBounds(); 2373 if (impl_peer != null) { 2374 try { 2375 long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0; 2376 impl_peer.waitForRenderingToComplete(); 2377 impl_peer.waitForSynchronization(); 2378 if (PULSE_LOGGING_ENABLED) { 2379 PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Waiting for previous rendering"); 2380 } 2381 start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0; 2382 // synchronize scene properties 2383 syncLights(); 2384 synchronizeSceneProperties(); 2385 // Run the synchronizer 2386 synchronizeSceneNodes(); 2387 Scene.this.mouseHandler.pulse(); 2388 // Tell the scene peer that it needs to repaint 2389 impl_peer.markDirty(); 2390 if (PULSE_LOGGING_ENABLED) { 2391 PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Copy state to render graph"); 2392 } 2393 } finally { 2394 impl_peer.releaseSynchronization(true); 2395 } 2396 } else { 2397 long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0; 2398 synchronizeSceneProperties(); 2399 synchronizeSceneNodes(); 2400 Scene.this.mouseHandler.pulse(); 2401 if (PULSE_LOGGING_ENABLED) { 2402 PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Synchronize with null peer"); 2403 } 2404 2405 } 2406 2407 if (Scene.this.getRoot().cssFlag != CssFlags.CLEAN) { 2408 Scene.this.getRoot().impl_markDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS); 2409 } 2410 } 2411 2412 // required for image cursor created from animated image 2413 Scene.this.mouseHandler.updateCursorFrame(); 2414 2415 if (firstPulse) { 2416 if (PerformanceTracker.isLoggingEnabled()) { 2417 PerformanceTracker.logEvent("Scene - first repaint - layout complete"); 2418 if (PrismSettings.perfLogFirstPaintFlush) { 2419 PerformanceTracker.outputLog(); 2420 } 2421 if (PrismSettings.perfLogFirstPaintExit) { 2422 System.exit(0); 2423 } 2424 } 2425 firstPulse = false; 2426 } 2427 2428 if (testPulseListener != null) { 2429 testPulseListener.run(); 2430 } 2431 } 2432 } 2433 2434 /******************************************************************************* 2435 * * 2436 * Scene Peer Listener * 2437 * * 2438 ******************************************************************************/ 2439 2440 class ScenePeerListener implements TKSceneListener { 2441 @Override 2442 public void changedLocation(float x, float y) { 2443 if (x != Scene.this.getX()) { 2444 Scene.this.setX(x); 2445 } 2446 if (y != Scene.this.getY()) { 2447 Scene.this.setY(y); 2448 } 2449 } 2450 2451 @Override 2452 public void changedSize(float w, float h) { 2453 if (w != Scene.this.getWidth()) Scene.this.setWidth(w); 2454 if (h != Scene.this.getHeight()) Scene.this.setHeight(h); 2455 } 2456 2457 @Override 2458 public void mouseEvent(EventType<MouseEvent> type, double x, double y, double screenX, double screenY, 2459 MouseButton button, boolean popupTrigger, boolean synthesized, 2460 boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown, 2461 boolean primaryDown, boolean middleDown, boolean secondaryDown) 2462 { 2463 MouseEvent mouseEvent = new MouseEvent(type, x, y, screenX, screenY, button, 2464 0, // click count will be adjusted by clickGenerator later anyway 2465 shiftDown, controlDown, altDown, metaDown, 2466 primaryDown, middleDown, secondaryDown, synthesized, popupTrigger, false, null); 2467 impl_processMouseEvent(mouseEvent); 2468 } 2469 2470 2471 @Override 2472 public void keyEvent(KeyEvent keyEvent) 2473 { 2474 impl_processKeyEvent(keyEvent); 2475 } 2476 2477 @Override 2478 public void inputMethodEvent(EventType<InputMethodEvent> type, 2479 ObservableList<InputMethodTextRun> composed, String committed, 2480 int caretPosition) 2481 { 2482 InputMethodEvent inputMethodEvent = new InputMethodEvent( 2483 type, composed, committed, caretPosition); 2484 processInputMethodEvent(inputMethodEvent); 2485 } 2486 2487 public void menuEvent(double x, double y, double xAbs, double yAbs, 2488 boolean isKeyboardTrigger) { 2489 Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger); 2490 } 2491 2492 @Override 2493 public void scrollEvent( 2494 EventType<ScrollEvent> eventType, 2495 double scrollX, double scrollY, 2496 double totalScrollX, double totalScrollY, 2497 double xMultiplier, double yMultiplier, 2498 int touchCount, 2499 int scrollTextX, int scrollTextY, 2500 int defaultTextX, int defaultTextY, 2501 double x, double y, double screenX, double screenY, 2502 boolean _shiftDown, boolean _controlDown, 2503 boolean _altDown, boolean _metaDown, 2504 boolean _direct, boolean _inertia) { 2505 2506 ScrollEvent.HorizontalTextScrollUnits xUnits = scrollTextX > 0 ? 2507 ScrollEvent.HorizontalTextScrollUnits.CHARACTERS : 2508 ScrollEvent.HorizontalTextScrollUnits.NONE; 2509 2510 double xText = scrollTextX < 0 ? 0 : scrollTextX * scrollX; 2511 2512 ScrollEvent.VerticalTextScrollUnits yUnits = scrollTextY > 0 ? 2513 ScrollEvent.VerticalTextScrollUnits.LINES : 2514 (scrollTextY < 0 ? 2515 ScrollEvent.VerticalTextScrollUnits.PAGES : 2516 ScrollEvent.VerticalTextScrollUnits.NONE); 2517 2518 double yText = scrollTextY < 0 ? scrollY : scrollTextY * scrollY; 2519 2520 xMultiplier = defaultTextX > 0 && scrollTextX >= 0 2521 ? Math.round(xMultiplier * scrollTextX / defaultTextX) 2522 : xMultiplier; 2523 2524 yMultiplier = defaultTextY > 0 && scrollTextY >= 0 2525 ? Math.round(yMultiplier * scrollTextY / defaultTextY) 2526 : yMultiplier; 2527 2528 if (eventType == ScrollEvent.SCROLL_FINISHED) { 2529 x = scrollGesture.sceneCoords.getX(); 2530 y = scrollGesture.sceneCoords.getY(); 2531 screenX = scrollGesture.screenCoords.getX(); 2532 screenY = scrollGesture.screenCoords.getY(); 2533 } else if (Double.isNaN(x) || Double.isNaN(y) || 2534 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2535 if (cursorScenePos == null || cursorScreenPos == null) { 2536 return; 2537 } 2538 x = cursorScenePos.getX(); 2539 y = cursorScenePos.getY(); 2540 screenX = cursorScreenPos.getX(); 2541 screenY = cursorScreenPos.getY(); 2542 } 2543 2544 inMousePick = true; 2545 Scene.this.processGestureEvent(new ScrollEvent( 2546 eventType, 2547 x, y, screenX, screenY, 2548 _shiftDown, _controlDown, _altDown, _metaDown, 2549 _direct, _inertia, 2550 scrollX * xMultiplier, scrollY * yMultiplier, 2551 totalScrollX * xMultiplier, totalScrollY * yMultiplier, 2552 xMultiplier, yMultiplier, 2553 xUnits, xText, yUnits, yText, touchCount, pick(x, y)), 2554 scrollGesture); 2555 inMousePick = false; 2556 } 2557 2558 @Override 2559 public void zoomEvent( 2560 EventType<ZoomEvent> eventType, 2561 double zoomFactor, double totalZoomFactor, 2562 double x, double y, double screenX, double screenY, 2563 boolean _shiftDown, boolean _controlDown, 2564 boolean _altDown, boolean _metaDown, 2565 boolean _direct, boolean _inertia) { 2566 2567 if (eventType == ZoomEvent.ZOOM_FINISHED) { 2568 x = zoomGesture.sceneCoords.getX(); 2569 y = zoomGesture.sceneCoords.getY(); 2570 screenX = zoomGesture.screenCoords.getX(); 2571 screenY = zoomGesture.screenCoords.getY(); 2572 } else if (Double.isNaN(x) || Double.isNaN(y) || 2573 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2574 if (cursorScenePos == null || cursorScreenPos == null) { 2575 return; 2576 } 2577 x = cursorScenePos.getX(); 2578 y = cursorScenePos.getY(); 2579 screenX = cursorScreenPos.getX(); 2580 screenY = cursorScreenPos.getY(); 2581 } 2582 2583 inMousePick = true; 2584 Scene.this.processGestureEvent(new ZoomEvent(eventType, 2585 x, y, screenX, screenY, 2586 _shiftDown, _controlDown, _altDown, _metaDown, 2587 _direct, _inertia, 2588 zoomFactor, totalZoomFactor, pick(x, y)), 2589 zoomGesture); 2590 inMousePick = false; 2591 } 2592 2593 @Override 2594 public void rotateEvent( 2595 EventType<RotateEvent> eventType, double angle, double totalAngle, 2596 double x, double y, double screenX, double screenY, 2597 boolean _shiftDown, boolean _controlDown, 2598 boolean _altDown, boolean _metaDown, 2599 boolean _direct, boolean _inertia) { 2600 2601 if (eventType == RotateEvent.ROTATION_FINISHED) { 2602 x = rotateGesture.sceneCoords.getX(); 2603 y = rotateGesture.sceneCoords.getY(); 2604 screenX = rotateGesture.screenCoords.getX(); 2605 screenY = rotateGesture.screenCoords.getY(); 2606 } else if (Double.isNaN(x) || Double.isNaN(y) || 2607 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2608 if (cursorScenePos == null || cursorScreenPos == null) { 2609 return; 2610 } 2611 x = cursorScenePos.getX(); 2612 y = cursorScenePos.getY(); 2613 screenX = cursorScreenPos.getX(); 2614 screenY = cursorScreenPos.getY(); 2615 } 2616 2617 inMousePick = true; 2618 Scene.this.processGestureEvent(new RotateEvent( 2619 eventType, x, y, screenX, screenY, 2620 _shiftDown, _controlDown, _altDown, _metaDown, 2621 _direct, _inertia, angle, totalAngle, pick(x, y)), 2622 rotateGesture); 2623 inMousePick = false; 2624 2625 } 2626 2627 @Override 2628 public void swipeEvent( 2629 EventType<SwipeEvent> eventType, int touchCount, 2630 double x, double y, double screenX, double screenY, 2631 boolean _shiftDown, boolean _controlDown, 2632 boolean _altDown, boolean _metaDown, boolean _direct) { 2633 2634 if (Double.isNaN(x) || Double.isNaN(y) || 2635 Double.isNaN(screenX) || Double.isNaN(screenY)) { 2636 if (cursorScenePos == null || cursorScreenPos == null) { 2637 return; 2638 } 2639 x = cursorScenePos.getX(); 2640 y = cursorScenePos.getY(); 2641 screenX = cursorScreenPos.getX(); 2642 screenY = cursorScreenPos.getY(); 2643 } 2644 2645 inMousePick = true; 2646 Scene.this.processGestureEvent(new SwipeEvent( 2647 eventType, x, y, screenX, screenY, 2648 _shiftDown, _controlDown, _altDown, _metaDown, _direct, 2649 touchCount, pick(x, y)), 2650 swipeGesture); 2651 inMousePick = false; 2652 } 2653 2654 @Override 2655 public void touchEventBegin( 2656 long time, int touchCount, boolean isDirect, 2657 boolean _shiftDown, boolean _controlDown, 2658 boolean _altDown, boolean _metaDown) { 2659 2660 if (!isDirect) { 2661 nextTouchEvent = null; 2662 return; 2663 } 2664 nextTouchEvent = new TouchEvent( 2665 TouchEvent.ANY, null, null, 0, 2666 _shiftDown, _controlDown, _altDown, _metaDown); 2667 if (touchPoints == null || touchPoints.length != touchCount) { 2668 touchPoints = new TouchPoint[touchCount]; 2669 } 2670 touchPointIndex = 0; 2671 } 2672 2673 @Override 2674 public void touchEventNext( 2675 TouchPoint.State state, long touchId, 2676 int x, int y, int xAbs, int yAbs) { 2677 2678 inMousePick = true; 2679 if (nextTouchEvent == null) { 2680 // ignore indirect touch events 2681 return; 2682 } 2683 touchPointIndex++; 2684 int id = (state == TouchPoint.State.PRESSED 2685 ? touchMap.add(touchId) : touchMap.get(touchId)); 2686 if (state == TouchPoint.State.RELEASED) { 2687 touchMap.remove(touchId); 2688 } 2689 int order = touchMap.getOrder(id); 2690 2691 if (order >= touchPoints.length) { 2692 throw new RuntimeException("Too many touch points reported"); 2693 } 2694 2695 // pick target 2696 boolean isGrabbed = false; 2697 PickResult pickRes = pick(x, y); 2698 EventTarget pickedTarget = touchTargets.get(id); 2699 if (pickedTarget == null) { 2700 pickedTarget = pickRes.getIntersectedNode(); 2701 if (pickedTarget == null) { 2702 pickedTarget = Scene.this; 2703 } 2704 } else { 2705 isGrabbed = true; 2706 } 2707 2708 TouchPoint tp = new TouchPoint(id, state, 2709 x, y, xAbs, yAbs, pickedTarget, pickRes); 2710 2711 touchPoints[order] = tp; 2712 2713 if (isGrabbed) { 2714 tp.grab(pickedTarget); 2715 } 2716 if (tp.getState() == TouchPoint.State.PRESSED) { 2717 tp.grab(pickedTarget); 2718 touchTargets.put(tp.getId(), pickedTarget); 2719 } else if (tp.getState() == TouchPoint.State.RELEASED) { 2720 touchTargets.remove(tp.getId()); 2721 } 2722 inMousePick = false; 2723 } 2724 2725 @Override 2726 public void touchEventEnd() { 2727 if (nextTouchEvent == null) { 2728 // ignore indirect touch events 2729 return; 2730 } 2731 2732 if (touchPointIndex != touchPoints.length) { 2733 throw new RuntimeException("Wrong number of touch points reported"); 2734 } 2735 2736 Scene.this.processTouchEvent(nextTouchEvent, touchPoints); 2737 2738 if (touchMap.cleanup()) { 2739 // gesture finished 2740 touchEventSetId = 0; 2741 } 2742 } 2743 } 2744 2745 private class ScenePeerPaintListener implements TKScenePaintListener { 2746 @Override 2747 public void frameRendered() { 2748 // must use tracker with synchronization since this method is called on render thread 2749 synchronized (trackerMonitor) { 2750 if (Scene.this.tracker != null) { 2751 Scene.this.tracker.frameRendered(); 2752 } 2753 } 2754 } 2755 } 2756 2757 /******************************************************************************* 2758 * * 2759 * Drag and Drop * 2760 * * 2761 ******************************************************************************/ 2762 2763 class DropTargetListener implements TKDropTargetListener { 2764 2765 /* 2766 * This function is called when an drag operation enters a valid drop target. 2767 * This may be from either an internal or external dnd operation. 2768 */ 2769 @Override 2770 public TransferMode dragEnter(double x, double y, double screenX, double screenY, 2771 TransferMode transferMode, TKClipboard dragboard) 2772 { 2773 if (dndGesture == null) { 2774 dndGesture = new DnDGesture(); 2775 } 2776 Dragboard db = Dragboard.impl_createDragboard(dragboard); 2777 dndGesture.dragboard = db; 2778 DragEvent dragEvent = 2779 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2780 transferMode, null, null, pick(x, y)); 2781 return dndGesture.processTargetEnterOver(dragEvent); 2782 } 2783 2784 @Override 2785 public TransferMode dragOver(double x, double y, double screenX, double screenY, 2786 TransferMode transferMode) 2787 { 2788 if (Scene.this.dndGesture == null) { 2789 System.err.println("GOT A dragOver when dndGesture is null!"); 2790 return null; 2791 } else { 2792 if (dndGesture.dragboard == null) { 2793 throw new RuntimeException("dndGesture.dragboard is null in dragOver"); 2794 } 2795 DragEvent dragEvent = 2796 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2797 transferMode, null, null, pick(x, y)); 2798 return dndGesture.processTargetEnterOver(dragEvent); 2799 } 2800 } 2801 2802 @Override 2803 public void dragExit(double x, double y, double screenX, double screenY) { 2804 if (dndGesture == null) { 2805 System.err.println("GOT A dragExit when dndGesture is null!"); 2806 } else { 2807 if (dndGesture.dragboard == null) { 2808 throw new RuntimeException("dndGesture.dragboard is null in dragExit"); 2809 } 2810 DragEvent dragEvent = 2811 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2812 null, null, null, pick(x, y)); 2813 dndGesture.processTargetExit(dragEvent); 2814 if (dndGesture.source == null) { 2815 dndGesture.dragboard = null; 2816 dndGesture = null; 2817 } 2818 } 2819 } 2820 2821 2822 @Override 2823 public TransferMode drop(double x, double y, double screenX, double screenY, 2824 TransferMode transferMode) 2825 { 2826 if (dndGesture == null) { 2827 System.err.println("GOT A drop when dndGesture is null!"); 2828 return null; 2829 } else { 2830 if (dndGesture.dragboard == null) { 2831 throw new RuntimeException("dndGesture.dragboard is null in dragDrop"); 2832 } 2833 DragEvent dragEvent = 2834 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 2835 transferMode, null, null, pick(x, y)); 2836 TransferMode tm = dndGesture.processTargetDrop(dragEvent); 2837 if (dndGesture.source == null) { 2838 dndGesture.dragboard = null; 2839 dndGesture = null; 2840 } 2841 return tm; 2842 } 2843 } 2844 } 2845 2846 class DragGestureListener implements TKDragGestureListener { 2847 2848 @Override 2849 public void dragGestureRecognized(double x, double y, double screenX, double screenY, 2850 int button, TKClipboard dragboard) 2851 { 2852 Dragboard db = Dragboard.impl_createDragboard(dragboard); 2853 dndGesture = new DnDGesture(); 2854 dndGesture.dragboard = db; 2855 // TODO: support mouse buttons in DragEvent 2856 DragEvent dragEvent = new DragEvent(DragEvent.ANY, db, x, y, screenX, screenY, 2857 null, null, null, pick(x, y)); 2858 dndGesture.processRecognized(dragEvent); 2859 dndGesture = null; 2860 } 2861 } 2862 2863 /** 2864 * A Drag and Drop gesture has a lifespan that lasts from mouse 2865 * PRESSED event to mouse RELEASED event. 2866 */ 2867 class DnDGesture { 2868 private final double hysteresisSizeX = 2869 Toolkit.getToolkit().getMultiClickMaxX(); 2870 private final double hysteresisSizeY = 2871 Toolkit.getToolkit().getMultiClickMaxY(); 2872 2873 private EventTarget source = null; 2874 private Set<TransferMode> sourceTransferModes = null; 2875 private TransferMode acceptedTransferMode = null; 2876 private Dragboard dragboard = null; 2877 private EventTarget potentialTarget = null; 2878 private EventTarget target = null; 2879 private DragDetectedState dragDetected = DragDetectedState.NOT_YET; 2880 private double pressedX; 2881 private double pressedY; 2882 private List<EventTarget> currentTargets = new ArrayList<EventTarget>(); 2883 private List<EventTarget> newTargets = new ArrayList<EventTarget>(); 2884 private EventTarget fullPDRSource = null; 2885 2886 /** 2887 * Fires event on a given target or on scene if the node is null 2888 */ 2889 private void fireEvent(EventTarget target, Event e) { 2890 if (target != null) { 2891 Event.fireEvent(target, e); 2892 } 2893 } 2894 2895 /** 2896 * Called when DRAG_DETECTED event is going to be processed by 2897 * application 2898 */ 2899 private void processingDragDetected() { 2900 dragDetected = DragDetectedState.PROCESSING; 2901 } 2902 2903 /** 2904 * Called after DRAG_DETECTED event has been processed by application 2905 */ 2906 private void dragDetectedProcessed() { 2907 dragDetected = DragDetectedState.DONE; 2908 final boolean hasContent = (dragboard != null) && (dragboard.impl_contentPut()); 2909 if (hasContent) { 2910 /* start DnD */ 2911 Toolkit.getToolkit().startDrag(Scene.this.impl_peer, 2912 sourceTransferModes, 2913 new DragSourceListener(), 2914 dragboard); 2915 } else if (fullPDRSource != null) { 2916 /* start PDR */ 2917 Scene.this.mouseHandler.enterFullPDR(fullPDRSource); 2918 } 2919 2920 fullPDRSource = null; 2921 } 2922 2923 /** 2924 * Sets the default dragDetect value 2925 */ 2926 private void processDragDetection(MouseEvent mouseEvent) { 2927 2928 if (dragDetected != DragDetectedState.NOT_YET) { 2929 mouseEvent.setDragDetect(false); 2930 return; 2931 } 2932 2933 if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) { 2934 pressedX = mouseEvent.getSceneX(); 2935 pressedY = mouseEvent.getSceneY(); 2936 2937 mouseEvent.setDragDetect(false); 2938 2939 } else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) { 2940 2941 double deltaX = Math.abs(mouseEvent.getSceneX() - pressedX); 2942 double deltaY = Math.abs(mouseEvent.getSceneY() - pressedY); 2943 mouseEvent.setDragDetect(deltaX > hysteresisSizeX || 2944 deltaY > hysteresisSizeY); 2945 2946 } 2947 } 2948 2949 /** 2950 * This function is useful for drag gesture recognition from 2951 * within this Scene (as opposed to in the TK implementation... by the platform) 2952 */ 2953 private boolean process(MouseEvent mouseEvent, EventTarget target) { 2954 boolean continueProcessing = true; 2955 if (!PLATFORM_DRAG_GESTURE_INITIATION) { 2956 2957 if (dragDetected != DragDetectedState.DONE && 2958 (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED || 2959 mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) && 2960 mouseEvent.isDragDetect()) { 2961 2962 processingDragDetected(); 2963 2964 if (target != null) { 2965 final MouseEvent detectedEvent = mouseEvent.copyFor( 2966 mouseEvent.getSource(), target, 2967 MouseEvent.DRAG_DETECTED); 2968 2969 fireEvent(target, detectedEvent); 2970 } 2971 2972 dragDetectedProcessed(); 2973 } 2974 2975 if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED) { 2976 continueProcessing = false; 2977 } 2978 } 2979 return continueProcessing; 2980 } 2981 2982 /* 2983 * Called when a drag source is recognized. This occurs at the very start of 2984 * the publicly visible drag and drop API, as it is responsible for calling 2985 * the Node.onDragSourceRecognized function. 2986 */ 2987 private boolean processRecognized(DragEvent de) { 2988 MouseEvent me = new MouseEvent( 2989 MouseEvent.DRAG_DETECTED, de.getX(), de.getY(), 2990 de.getSceneX(), de.getScreenY(), MouseButton.PRIMARY, 1, 2991 false, false, false, false, false, true, false, false, false, 2992 false, de.getPickResult()); 2993 2994 processingDragDetected(); 2995 2996 final EventTarget target = de.getPickResult().getIntersectedNode(); 2997 fireEvent(target != null ? target : Scene.this, me); 2998 2999 dragDetectedProcessed(); 3000 3001 final boolean hasContent = dragboard != null 3002 && !dragboard.getContentTypes().isEmpty(); 3003 return hasContent; 3004 } 3005 3006 private void processDropEnd(DragEvent de) { 3007 if (source == null) { 3008 System.out.println("Scene.DnDGesture.processDropEnd() - UNEXPECTD - source is NULL"); 3009 return; 3010 } 3011 3012 de = new DragEvent(de.getSource(), source, DragEvent.DRAG_DONE, 3013 de.getDragboard(), de.getSceneX(), de.getSceneY(), 3014 de.getScreenX(), de.getScreenY(), 3015 de.getTransferMode(), source, target, de.getPickResult()); 3016 3017 Event.fireEvent(source, de); 3018 3019 tmpTargetWrapper.clear(); 3020 handleExitEnter(de, tmpTargetWrapper); 3021 3022 // at this point the drag and drop operation is completely over, so we 3023 // can tell the toolkit that it can clean up if needs be. 3024 Toolkit.getToolkit().stopDrag(dragboard); 3025 } 3026 3027 private TransferMode processTargetEnterOver(DragEvent de) { 3028 pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY()); 3029 final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget(); 3030 3031 if (dragboard == null) { 3032 dragboard = createDragboard(de, false); 3033 } 3034 3035 de = new DragEvent(de.getSource(), pickedTarget, de.getEventType(), 3036 dragboard, de.getSceneX(), de.getSceneY(), 3037 de.getScreenX(), de.getScreenY(), 3038 de.getTransferMode(), source, potentialTarget, de.getPickResult()); 3039 3040 handleExitEnter(de, tmpTargetWrapper); 3041 3042 de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_OVER, 3043 de.getDragboard(), de.getSceneX(), de.getSceneY(), 3044 de.getScreenX(), de.getScreenY(), 3045 de.getTransferMode(), source, potentialTarget, de.getPickResult()); 3046 3047 fireEvent(pickedTarget, de); 3048 3049 Object acceptingObject = de.getAcceptingObject(); 3050 potentialTarget = acceptingObject instanceof EventTarget 3051 ? (EventTarget) acceptingObject : null; 3052 acceptedTransferMode = de.getAcceptedTransferMode(); 3053 return acceptedTransferMode; 3054 } 3055 3056 private void processTargetActionChanged(DragEvent de) { 3057 // Do we want DRAG_TRANSFER_MODE_CHANGED event? 3058 // final Node pickedNode = Scene.this.mouseHandler.pickNode(de.getX(), de.getY()); 3059 // if (pickedNode != null && pickedNode.impl_isTreeVisible()) { 3060 // de = DragEvent.impl_copy(de.getSource(), pickedNode, source, 3061 // pickedNode, de, DragEvent.DRAG_TRANSFER_MODE_CHANGED); 3062 // 3063 // if (dragboard == null) { 3064 // dragboard = createDragboard(de); 3065 // } 3066 // dragboard = de.impl_getPlatformDragboard(); 3067 // 3068 // fireEvent(pickedNode, de); 3069 // } 3070 } 3071 3072 private void processTargetExit(DragEvent de) { 3073 if (dragboard == null) { 3074 // dragboard should have been created in processTargetEnterOver() 3075 throw new NullPointerException("dragboard is null in processTargetExit()"); 3076 } 3077 3078 if (currentTargets.size() > 0) { 3079 potentialTarget = null; 3080 tmpTargetWrapper.clear(); 3081 handleExitEnter(de, tmpTargetWrapper); 3082 } 3083 } 3084 3085 private TransferMode processTargetDrop(DragEvent de) { 3086 pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY()); 3087 final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget(); 3088 3089 de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_DROPPED, 3090 de.getDragboard(), de.getSceneX(), de.getSceneY(), 3091 de.getScreenX(), de.getScreenY(), 3092 acceptedTransferMode, source, potentialTarget, de.getPickResult()); 3093 3094 if (dragboard == null) { 3095 // dragboard should have been created in processTargetEnterOver() 3096 throw new NullPointerException("dragboard is null in processTargetDrop()"); 3097 } 3098 3099 handleExitEnter(de, tmpTargetWrapper); 3100 3101 fireEvent(pickedTarget, de); 3102 3103 Object acceptingObject = de.getAcceptingObject(); 3104 potentialTarget = acceptingObject instanceof EventTarget 3105 ? (EventTarget) acceptingObject : null; 3106 target = potentialTarget; 3107 3108 TransferMode result = de.isDropCompleted() ? 3109 de.getAcceptedTransferMode() : null; 3110 3111 tmpTargetWrapper.clear(); 3112 handleExitEnter(de, tmpTargetWrapper); 3113 3114 return result; 3115 } 3116 3117 private void handleExitEnter(DragEvent e, TargetWrapper target) { 3118 EventTarget currentTarget = 3119 currentTargets.size() > 0 ? currentTargets.get(0) : null; 3120 3121 if (target.getEventTarget() != currentTarget) { 3122 3123 target.fillHierarchy(newTargets); 3124 3125 int i = currentTargets.size() - 1; 3126 int j = newTargets.size() - 1; 3127 3128 while (i >= 0 && j >= 0 && currentTargets.get(i) == newTargets.get(j)) { 3129 i--; 3130 j--; 3131 } 3132 3133 for (; i >= 0; i--) { 3134 EventTarget t = currentTargets.get(i); 3135 if (potentialTarget == t) { 3136 potentialTarget = null; 3137 } 3138 e = e.copyFor(e.getSource(), t, source, 3139 potentialTarget, DragEvent.DRAG_EXITED_TARGET); 3140 Event.fireEvent(t, e); 3141 } 3142 3143 potentialTarget = null; 3144 for (; j >= 0; j--) { 3145 EventTarget t = newTargets.get(j); 3146 e = e.copyFor(e.getSource(), t, source, 3147 potentialTarget, DragEvent.DRAG_ENTERED_TARGET); 3148 Object acceptingObject = e.getAcceptingObject(); 3149 if (acceptingObject instanceof EventTarget) { 3150 potentialTarget = (EventTarget) acceptingObject; 3151 } 3152 Event.fireEvent(t, e); 3153 } 3154 3155 currentTargets.clear(); 3156 currentTargets.addAll(newTargets); 3157 } 3158 } 3159 3160 // function getIntendedTransferMode(e:MouseEvent):TransferMode { 3161 // return if (e.altDown) TransferMode.COPY else TransferMode.MOVE; 3162 // } 3163 3164 /* 3165 * Function that hooks into the key processing code in Scene to handle the 3166 * situation where a drag and drop event is taking place and the user presses 3167 * the escape key to cancel the drag and drop operation. 3168 */ 3169 private boolean processKey(KeyEvent e) { 3170 //note: this seems not to be called, the DnD cancelation is provided by platform 3171 if ((e.getEventType() == KeyEvent.KEY_PRESSED) && (e.getCode() == KeyCode.ESCAPE)) { 3172 3173 // cancel drag and drop 3174 DragEvent de = new DragEvent( 3175 source, source, DragEvent.DRAG_DONE, dragboard, 0, 0, 0, 0, 3176 null, source, null, null); 3177 if (source != null) { 3178 Event.fireEvent(source, de); 3179 } 3180 3181 tmpTargetWrapper.clear(); 3182 handleExitEnter(de, tmpTargetWrapper); 3183 3184 return false; 3185 } 3186 return true; 3187 } 3188 3189 /* 3190 * This starts the drag gesture running, creating the dragboard used for 3191 * the remainder of this drag and drop operation. 3192 */ 3193 private Dragboard startDrag(EventTarget source, Set<TransferMode> t) { 3194 if (dragDetected != DragDetectedState.PROCESSING) { 3195 throw new IllegalStateException("Cannot start drag and drop " 3196 + "outside of DRAG_DETECTED event handler"); 3197 } 3198 3199 if (t.isEmpty()) { 3200 dragboard = null; 3201 } else if (dragboard == null) { 3202 dragboard = createDragboard(null, true); 3203 } 3204 this.source = source; 3205 potentialTarget = source; 3206 sourceTransferModes = t; 3207 return dragboard; 3208 } 3209 3210 /* 3211 * This starts the full PDR gesture. 3212 */ 3213 private void startFullPDR(EventTarget source) { 3214 fullPDRSource = source; 3215 } 3216 3217 private Dragboard createDragboard(final DragEvent de, boolean isDragSource) { 3218 Dragboard dragboard = null; 3219 if (de != null) { 3220 dragboard = de.getDragboard(); 3221 if (dragboard != null) { 3222 return dragboard; 3223 } 3224 } 3225 TKClipboard dragboardPeer = impl_peer.createDragboard(isDragSource); 3226 return Dragboard.impl_createDragboard(dragboardPeer); 3227 } 3228 } 3229 3230 /** 3231 * State of a drag gesture with regards to DRAG_DETECTED event. 3232 */ 3233 private enum DragDetectedState { 3234 NOT_YET, 3235 PROCESSING, 3236 DONE 3237 } 3238 3239 class DragSourceListener implements TKDragSourceListener { 3240 3241 @Override 3242 public void dragDropEnd(double x, double y, double screenX, double screenY, 3243 TransferMode transferMode) 3244 { 3245 if (dndGesture != null) { 3246 if (dndGesture.dragboard == null) { 3247 throw new RuntimeException("dndGesture.dragboard is null in dragDropEnd"); 3248 } 3249 DragEvent dragEvent = 3250 new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY, 3251 transferMode, null, null, null); 3252 dndGesture.processDropEnd(dragEvent); 3253 dndGesture = null; 3254 } 3255 } 3256 } 3257 3258 /******************************************************************************* 3259 * * 3260 * Mouse Event Handling * 3261 * * 3262 ******************************************************************************/ 3263 3264 static class ClickCounter { 3265 Toolkit toolkit = Toolkit.getToolkit(); 3266 private int count; 3267 private boolean out; 3268 private boolean still; 3269 private Timeline timeout; 3270 private double pressedX, pressedY; 3271 3272 private void inc() { count++; } 3273 private int get() { return count; } 3274 private boolean isStill() { return still; } 3275 3276 private void clear() { 3277 count = 0; 3278 stopTimeout(); 3279 } 3280 3281 private void out() { 3282 out = true; 3283 stopTimeout(); 3284 } 3285 3286 private void applyOut() { 3287 if (out) clear(); 3288 out = false; 3289 } 3290 3291 private void moved(double x, double y) { 3292 if (Math.abs(x - pressedX) > toolkit.getMultiClickMaxX() || 3293 Math.abs(y - pressedY) > toolkit.getMultiClickMaxY()) { 3294 out(); 3295 still = false; 3296 } 3297 } 3298 3299 private void start(double x, double y) { 3300 pressedX = x; 3301 pressedY = y; 3302 out = false; 3303 3304 if (timeout != null) { 3305 timeout.stop(); 3306 } 3307 timeout = new Timeline(); 3308 timeout.getKeyFrames().add( 3309 new KeyFrame(new Duration(toolkit.getMultiClickTime()), 3310 new EventHandler<ActionEvent>() { 3311 @Override 3312 public void handle(ActionEvent event) { 3313 out = true; 3314 timeout = null; 3315 } 3316 3317 })); 3318 timeout.play(); 3319 still = true; 3320 } 3321 3322 private void stopTimeout() { 3323 if (timeout != null) { 3324 timeout.stop(); 3325 timeout = null; 3326 } 3327 } 3328 } 3329 3330 static class ClickGenerator { 3331 private ClickCounter lastPress = null; 3332 3333 private Map<MouseButton, ClickCounter> counters = 3334 new EnumMap<MouseButton, ClickCounter>(MouseButton.class); 3335 private List<EventTarget> pressedTargets = new ArrayList<EventTarget>(); 3336 private List<EventTarget> releasedTargets = new ArrayList<EventTarget>(); 3337 3338 public ClickGenerator() { 3339 for (MouseButton mb : MouseButton.values()) { 3340 if (mb != MouseButton.NONE) { 3341 counters.put(mb, new ClickCounter()); 3342 } 3343 } 3344 } 3345 3346 private MouseEvent preProcess(MouseEvent e) { 3347 for (ClickCounter cc : counters.values()) { 3348 cc.moved(e.getSceneX(), e.getSceneY()); 3349 } 3350 3351 ClickCounter cc = counters.get(e.getButton()); 3352 boolean still = lastPress != null ? lastPress.isStill() : false; 3353 3354 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { 3355 3356 if (! e.isPrimaryButtonDown()) { counters.get(MouseButton.PRIMARY).clear(); } 3357 if (! e.isSecondaryButtonDown()) { counters.get(MouseButton.SECONDARY).clear(); } 3358 if (! e.isMiddleButtonDown()) { counters.get(MouseButton.MIDDLE).clear(); } 3359 3360 cc.applyOut(); 3361 cc.inc(); 3362 cc.start(e.getSceneX(), e.getSceneY()); 3363 lastPress = cc; 3364 } 3365 3366 return new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(), 3367 e.getScreenX(), e.getScreenY(), e.getButton(), 3368 cc != null && e.getEventType() != MouseEvent.MOUSE_MOVED ? cc.get() : 0, 3369 e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(), 3370 e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(), 3371 e.isSynthesized(), e.isPopupTrigger(), still, e.getPickResult()); 3372 } 3373 3374 private void postProcess(MouseEvent e, TargetWrapper target, TargetWrapper pickedTarget) { 3375 3376 if (e.getEventType() == MouseEvent.MOUSE_RELEASED) { 3377 ClickCounter cc = counters.get(e.getButton()); 3378 3379 target.fillHierarchy(pressedTargets); 3380 pickedTarget.fillHierarchy(releasedTargets); 3381 int i = pressedTargets.size() - 1; 3382 int j = releasedTargets.size() - 1; 3383 3384 EventTarget clickedTarget = null; 3385 while (i >= 0 && j >= 0 && pressedTargets.get(i) == releasedTargets.get(j)) { 3386 clickedTarget = pressedTargets.get(i); 3387 i--; 3388 j--; 3389 } 3390 3391 if (clickedTarget != null) { 3392 MouseEvent click = new MouseEvent(null, clickedTarget, 3393 MouseEvent.MOUSE_CLICKED, e.getSceneX(), e.getSceneY(), 3394 e.getScreenX(), e.getScreenY(), e.getButton(), 3395 cc.get(), 3396 e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(), 3397 e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(), 3398 e.isSynthesized(), e.isPopupTrigger(), lastPress.isStill(), e.getPickResult()); 3399 Event.fireEvent(clickedTarget, click); 3400 } 3401 } 3402 } 3403 } 3404 3405 /** 3406 * Generates mouse exited event for a node which is going to be removed 3407 * and its children, where appropriate. 3408 * @param removing Node which is going to be removed 3409 */ 3410 void generateMouseExited(Node removing) { 3411 mouseHandler.handleNodeRemoval(removing); 3412 } 3413 3414 class MouseHandler { 3415 private TargetWrapper pdrEventTarget = new TargetWrapper(); // pdr - press-drag-release 3416 private boolean pdrInProgress = false; 3417 private boolean fullPDREntered = false; 3418 3419 private EventTarget currentEventTarget = null; 3420 private MouseEvent lastEvent; 3421 private boolean hover = false; 3422 3423 private boolean primaryButtonDown = false; 3424 private boolean secondaryButtonDown = false; 3425 private boolean middleButtonDown = false; 3426 3427 private EventTarget fullPDRSource = null; 3428 private TargetWrapper fullPDRTmpTargetWrapper = new TargetWrapper(); 3429 3430 /* lists needed for enter/exit events generation */ 3431 private final List<EventTarget> pdrEventTargets = new ArrayList<EventTarget>(); 3432 private final List<EventTarget> currentEventTargets = new ArrayList<EventTarget>(); 3433 private final List<EventTarget> newEventTargets = new ArrayList<EventTarget>(); 3434 3435 private final List<EventTarget> fullPDRCurrentEventTargets = new ArrayList<EventTarget>(); 3436 private final List<EventTarget> fullPDRNewEventTargets = new ArrayList<EventTarget>(); 3437 private EventTarget fullPDRCurrentTarget = null; 3438 3439 private Cursor currCursor; 3440 private CursorFrame currCursorFrame; 3441 private EventQueue queue = new EventQueue(); 3442 3443 private Runnable pickProcess = new Runnable() { 3444 3445 @Override 3446 public void run() { 3447 // Make sure this is run only if the peer is still alive 3448 // and there is an event to deliver 3449 if (Scene.this.impl_peer != null && lastEvent != null) { 3450 process(lastEvent, true); 3451 } 3452 } 3453 }; 3454 3455 private void pulse() { 3456 if (hover && lastEvent != null) { 3457 //Shouldn't run user code directly. User can call stage.showAndWait() and block the pulse. 3458 Platform.runLater(pickProcess); 3459 } 3460 } 3461 3462 private void clearPDREventTargets() { 3463 pdrInProgress = false; 3464 currentEventTarget = currentEventTargets.size() > 0 3465 ? currentEventTargets.get(0) : null; 3466 pdrEventTarget.clear(); 3467 } 3468 3469 public void enterFullPDR(EventTarget gestureSource) { 3470 fullPDREntered = true; 3471 fullPDRSource = gestureSource; 3472 fullPDRCurrentTarget = null; 3473 fullPDRCurrentEventTargets.clear(); 3474 } 3475 3476 public void exitFullPDR(MouseEvent e) { 3477 if (!fullPDREntered) { 3478 return; 3479 } 3480 fullPDREntered = false; 3481 for (int i = fullPDRCurrentEventTargets.size() - 1; i >= 0; i--) { 3482 EventTarget entered = fullPDRCurrentEventTargets.get(i); 3483 Event.fireEvent(entered, MouseEvent.copyForMouseDragEvent(e, 3484 entered, entered, 3485 MouseDragEvent.MOUSE_DRAG_EXITED_TARGET, 3486 fullPDRSource, e.getPickResult())); 3487 } 3488 fullPDRSource = null; 3489 fullPDRCurrentEventTargets.clear(); 3490 fullPDRCurrentTarget = null; 3491 } 3492 3493 private void handleNodeRemoval(Node removing) { 3494 if (lastEvent == null) { 3495 // this can happen only if everything has been exited anyway 3496 return; 3497 } 3498 3499 3500 if (currentEventTargets.contains(removing)) { 3501 int i = 0; 3502 EventTarget trg = null; 3503 while(trg != removing) { 3504 trg = currentEventTargets.get(i++); 3505 3506 queue.postEvent(lastEvent.copyFor(trg, trg, 3507 MouseEvent.MOUSE_EXITED_TARGET)); 3508 } 3509 currentEventTargets.subList(0, i).clear(); 3510 } 3511 3512 if (fullPDREntered && fullPDRCurrentEventTargets.contains(removing)) { 3513 int i = 0; 3514 EventTarget trg = null; 3515 while (trg != removing) { 3516 trg = fullPDRCurrentEventTargets.get(i++); 3517 3518 queue.postEvent( 3519 MouseEvent.copyForMouseDragEvent(lastEvent, trg, trg, 3520 MouseDragEvent.MOUSE_DRAG_EXITED_TARGET, 3521 fullPDRSource, lastEvent.getPickResult())); 3522 } 3523 3524 fullPDRCurrentEventTargets.subList(0, i).clear(); 3525 } 3526 3527 queue.fire(); 3528 3529 if (pdrInProgress && pdrEventTargets.contains(removing)) { 3530 int i = 0; 3531 EventTarget trg = null; 3532 while (trg != removing) { 3533 trg = pdrEventTargets.get(i++); 3534 3535 // trg.setHover(false) - already taken care of 3536 // by the code above which sent a mouse exited event 3537 ((Node) trg).setPressed(false); 3538 } 3539 pdrEventTargets.subList(0, i).clear(); 3540 3541 trg = pdrEventTargets.get(0); 3542 final PickResult res = pdrEventTarget.getResult(); 3543 if (trg instanceof Node) { 3544 pdrEventTarget.setNodeResult(new PickResult((Node) trg, 3545 res.getIntersectedPoint(), res.getIntersectedDistance())); 3546 } else { 3547 pdrEventTarget.setSceneResult(new PickResult(null, 3548 res.getIntersectedPoint(), res.getIntersectedDistance()), 3549 (Scene) trg); 3550 } 3551 } 3552 } 3553 3554 private void handleEnterExit(MouseEvent e, TargetWrapper pickedTarget) { 3555 if (pickedTarget.getEventTarget() != currentEventTarget || 3556 e.getEventType() == MouseEvent.MOUSE_EXITED) { 3557 3558 if (e.getEventType() == MouseEvent.MOUSE_EXITED) { 3559 newEventTargets.clear(); 3560 } else { 3561 pickedTarget.fillHierarchy(newEventTargets); 3562 } 3563 3564 int newTargetsSize = newEventTargets.size(); 3565 int i = currentEventTargets.size() - 1; 3566 int j = newTargetsSize - 1; 3567 int k = pdrEventTargets.size() - 1; 3568 3569 while (i >= 0 && j >= 0 && currentEventTargets.get(i) == newEventTargets.get(j)) { 3570 i--; 3571 j--; 3572 k--; 3573 } 3574 3575 final int memk = k; 3576 for (; i >= 0; i--, k--) { 3577 final EventTarget exitedEventTarget = currentEventTargets.get(i); 3578 if (pdrInProgress && 3579 (k < 0 || exitedEventTarget != pdrEventTargets.get(k))) { 3580 break; 3581 } 3582 queue.postEvent(e.copyFor( 3583 exitedEventTarget, exitedEventTarget, 3584 MouseEvent.MOUSE_EXITED_TARGET)); 3585 } 3586 3587 k = memk; 3588 for (; j >= 0; j--, k--) { 3589 final EventTarget enteredEventTarget = newEventTargets.get(j); 3590 if (pdrInProgress && 3591 (k < 0 || enteredEventTarget != pdrEventTargets.get(k))) { 3592 break; 3593 } 3594 queue.postEvent(e.copyFor( 3595 enteredEventTarget, enteredEventTarget, 3596 MouseEvent.MOUSE_ENTERED_TARGET)); 3597 } 3598 3599 currentEventTarget = pickedTarget.getEventTarget(); 3600 currentEventTargets.clear(); 3601 for (j++; j < newTargetsSize; j++) { 3602 currentEventTargets.add(newEventTargets.get(j)); 3603 } 3604 } 3605 queue.fire(); 3606 } 3607 3608 private void process(MouseEvent e, boolean onPulse) { 3609 Toolkit.getToolkit().checkFxUserThread(); 3610 Scene.inMousePick = true; 3611 3612 cursorScreenPos = new Point2D(e.getScreenX(), e.getScreenY()); 3613 cursorScenePos = new Point2D(e.getSceneX(), e.getSceneY()); 3614 3615 boolean gestureStarted = false; 3616 if (!onPulse) { 3617 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { 3618 if (!(primaryButtonDown || secondaryButtonDown || middleButtonDown)) { 3619 //old gesture ended and new one started 3620 gestureStarted = true; 3621 if (!PLATFORM_DRAG_GESTURE_INITIATION) { 3622 Scene.this.dndGesture = new DnDGesture(); 3623 } 3624 clearPDREventTargets(); 3625 } 3626 } else if (e.getEventType() == MouseEvent.MOUSE_MOVED) { 3627 // gesture ended 3628 clearPDREventTargets(); 3629 } else if (e.getEventType() == MouseEvent.MOUSE_ENTERED) { 3630 hover = true; 3631 } else if (e.getEventType() == MouseEvent.MOUSE_EXITED) { 3632 hover = false; 3633 } 3634 3635 primaryButtonDown = e.isPrimaryButtonDown(); 3636 secondaryButtonDown = e.isSecondaryButtonDown(); 3637 middleButtonDown = e.isMiddleButtonDown(); 3638 } 3639 3640 pick(tmpTargetWrapper, e.getSceneX(), e.getSceneY()); 3641 PickResult res = tmpTargetWrapper.getResult(); 3642 if (res != null) { 3643 e = new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(), 3644 e.getScreenX(), e.getScreenY(), e.getButton(), e.getClickCount(), 3645 e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(), 3646 e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(), 3647 e.isSynthesized(), e.isPopupTrigger(), e.isStillSincePress(), res); 3648 } 3649 3650 if (e.getEventType() == MouseEvent.MOUSE_EXITED) { 3651 tmpTargetWrapper.clear(); 3652 } 3653 3654 TargetWrapper target; 3655 if (pdrInProgress) { 3656 target = pdrEventTarget; 3657 } else { 3658 target = tmpTargetWrapper; 3659 } 3660 3661 if (gestureStarted) { 3662 pdrEventTarget.copy(target); 3663 pdrEventTarget.fillHierarchy(pdrEventTargets); 3664 } 3665 3666 if (!onPulse) { 3667 e = clickGenerator.preProcess(e); 3668 } 3669 3670 // enter/exit handling 3671 handleEnterExit(e, tmpTargetWrapper); 3672 3673 Cursor cursor = target.getCursor(); 3674 3675 //deliver event to the target node 3676 3677 if (Scene.this.dndGesture != null) { 3678 Scene.this.dndGesture.processDragDetection(e); 3679 } 3680 3681 if (fullPDREntered && e.getEventType() == MouseEvent.MOUSE_RELEASED) { 3682 processFullPDR(e, onPulse); 3683 } 3684 3685 if (target.getEventTarget() != null) { 3686 if (e.getEventType() != MouseEvent.MOUSE_ENTERED 3687 && e.getEventType() != MouseEvent.MOUSE_EXITED 3688 && !onPulse) { 3689 Event.fireEvent(target.getEventTarget(), e); 3690 } 3691 } 3692 3693 if (fullPDREntered && e.getEventType() != MouseEvent.MOUSE_RELEASED) { 3694 processFullPDR(e, onPulse); 3695 } 3696 3697 if (!onPulse) { 3698 clickGenerator.postProcess(e, target, tmpTargetWrapper); 3699 } 3700 3701 // handle drag and drop 3702 3703 if (!PLATFORM_DRAG_GESTURE_INITIATION && !onPulse) { 3704 if (Scene.this.dndGesture != null) { 3705 if (!Scene.this.dndGesture.process(e, target.getEventTarget())) { 3706 dndGesture = null; 3707 } 3708 } 3709 } 3710 3711 3712 if (cursor == null && hover) { 3713 cursor = Scene.this.getCursor(); 3714 } 3715 3716 updateCursor(cursor); 3717 updateCursorFrame(); 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 }