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