1 /*
   2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.app;
  33 
  34 import com.oracle.javafx.scenebuilder.app.about.AboutWindowController;
  35 import com.oracle.javafx.scenebuilder.kit.editor.EditorController;
  36 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController;
  37 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.handles.AbstractGenericHandles;
  38 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.handles.AbstractHandles;
  39 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.util.CardinalPoint;
  40 import com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy.AbstractHierarchyPanelController;
  41 import com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy.HierarchyItem;
  42 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup;
  43 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection;
  44 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument;
  45 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject;
  46 import java.io.File;
  47 import java.io.IOException;
  48 import java.util.Collections;
  49 import java.util.List;
  50 import java.util.Set;
  51 import javafx.geometry.Bounds;
  52 import javafx.geometry.Point2D;
  53 import javafx.scene.Node;
  54 import javafx.scene.Parent;
  55 import javafx.scene.Scene;
  56 import javafx.scene.control.Cell;
  57 import javafx.scene.control.TreeItem;
  58 
  59 /**
  60  * This class groups the entry points reserved to QE testing.
  61  *
  62  * Design consideration
  63  *
  64  * This class tries to hide SB internal architecture as much as possible;
  65  * for example, an FXML document is represented by a DocumentWindowController
  66  * instance in SB; however, in this class, the FXML document is
  67  * identified by the Scene instance holding the document window contents..
  68  *
  69  * However some internals must be disclosed:
  70  *
  71  * - FXOMObject : represents a design object ; it is paired with an object
  72  *   in the user scene graph ; FXOMObject.getSceneGraphObject() returns the
  73  *   matching scene graph object : sometimes it's a plain Node (eg Button),
  74  *   sometimes not (eg a Tab, a TableColumn...).
  75  *
  76  * - ...
  77  *
  78  */
  79 public class SceneBuilderTest {
  80 
  81     /**
  82      * Performs [File/New] menu command and returns the Scene instance
  83      * holding the new document window.
  84      *
  85      * @return the scene instance holding the new document window (never null).
  86      */
  87     public static Scene newFxmlFile() {
  88         final DocumentWindowController newWindow
  89                 = SceneBuilderApp.getSingleton().makeNewWindow();
  90         newWindow.openWindow();
  91         return newWindow.getScene();
  92     }
  93 
  94     /**
  95      * Performs [File/Open] menu command with the file passed in argument.
  96      * If an error happens, the method throws the corresponding exception
  97      * (in place of displaying an alert dialog).
  98      *
  99      * @param fxmlFile fxml file to be opened (never null)
 100      * @return the scene instance holding the new document window (never null).
 101      * @throws IOException if the open operation has failed.
 102      */
 103     public static Scene openFxmlFile(File fxmlFile) throws IOException {
 104         assert fxmlFile != null;
 105 
 106         final DocumentWindowController newWindow
 107                 = SceneBuilderApp.getSingleton().makeNewWindow();
 108         newWindow.loadFromFile(fxmlFile);
 109         newWindow.openWindow();
 110         return newWindow.getScene();
 111     }
 112 
 113     /**
 114      * Returns the root of the [user scene graph] ie the scene graph
 115      * constructed from the content of the FXML file. If documentScene does
 116      * not match any document window, returns null.
 117      *
 118      * Note: the returned is an [Object] because an FXML file is not limited
 119      * to javafx.scene.Node.
 120      *
 121      * @param documentScene a scene holding a document window
 122      *
 123      * @return the user scene graph root or null if documentScene does
 124      *         not hold a document window
 125      */
 126     public static Object getUserSceneGraphRoot(Scene documentScene) {
 127         assert documentScene != null;
 128 
 129         final Object result;
 130         final FXOMDocument fxomDocument = lookupFxomDocument(documentScene);
 131         if (fxomDocument == null) {
 132             result = null;
 133         } else {
 134             result = fxomDocument.getSceneGraphRoot();
 135         }
 136 
 137         return result;
 138     }
 139 
 140 
 141     /**
 142      * Returns the set of selected objects. Each selected object is represented
 143      * by an FXOMObject instance.
 144      *
 145      * @param documentScene a scene holding a document window
 146      * @return the set of selected objects or null if documentScene does
 147      *         not hold a document window
 148      */
 149     public static Set<FXOMObject> findSelectedObjects(Scene documentScene) {
 150         assert documentScene != null;
 151 
 152         final Set<FXOMObject> result;
 153         final DocumentWindowController dwc = lookupWindowController(documentScene);
 154         if (dwc == null) {
 155             result = null;
 156         } else {
 157             final Selection selection = dwc.getEditorController().getSelection();
 158             if (selection.getGroup() instanceof ObjectSelectionGroup) {
 159                 final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup();
 160                 result = Collections.unmodifiableSet(osg.getItems());
 161             } else {
 162                 // TODO(elp) : will implement later
 163                 result = Collections.emptySet();
 164             }
 165         }
 166 
 167         return result;
 168     }
 169 
 170     /**
 171      * Returns the fxom object matching a given node in the content panel.
 172      * Returns null if nothing is found.
 173      *
 174      * @param node a node part of the content panel (never null)
 175      * @return null or the matching fxom object
 176      */
 177     public static FXOMObject fxomObjectFromContentPanelNode(Node node) {
 178         assert node != null;
 179         assert node.getScene() != null;
 180 
 181         final FXOMObject result;
 182         final DocumentWindowController dwc = lookupWindowController(node.getScene());
 183         if (dwc == null) {
 184             result = null;
 185         } else {
 186             final Bounds b = node.getLayoutBounds();
 187             final double midX = (b.getMinX() + b.getMaxX()) / 2.0;
 188             final double midY = (b.getMinY() + b.getMaxY()) / 2.0;
 189             final Point2D nodeCenter = node.localToScene(midX, midY, true /* rootScene */);
 190 
 191             final ContentPanelController cpc = dwc.getContentPanelController();
 192             result = cpc.searchWithNode(node, nodeCenter.getX(), nodeCenter.getY());
 193         }
 194 
 195         return result;
 196     }
 197 
 198     /**
 199      * Returns the node in content panel matching a given fxom object.
 200      * This method invokes FXOMObject.getSceneGraphObject() and checks if
 201      * it is a Node. If it's not, it returns null.
 202      *
 203      * @param documentScene a scene holding a document window
 204      * @param fxomObject an fxom object (never null)
 205      * @return null or the matching node in content panel
 206      */
 207     public static Node fxomObjectToContentPanelNode(
 208             Scene documentScene, FXOMObject fxomObject) {
 209         assert documentScene != null;
 210         assert fxomObject != null;
 211 
 212         final Node result;
 213         if (fxomObject.getSceneGraphObject() instanceof Node) {
 214             result = (Node) fxomObject.getSceneGraphObject();
 215         } else {
 216             result = null;
 217         }
 218         return result;
 219     }
 220 
 221     /**
 222      * Returns the fxom object matching a given node in the hierarchy panel.
 223      * Returns null if nothing is found.
 224      * This method lookups for a Cell object ancestor of the specified node parameter
 225      * and returns the associated FXOMObject.
 226      * If there is no Cell object ancestor, it returns null.
 227      *
 228      * @param node a node part of the hierarchy panel (never null)
 229      * @return null or the matching fxom object
 230      */
 231     public static FXOMObject fxomObjectFromHierarchyPanelNode(Node node) {
 232         assert node != null;
 233         assert node.getScene() != null;
 234 
 235         final FXOMObject result;
 236         final DocumentWindowController dwc = lookupWindowController(node.getScene());
 237         if (dwc == null) {
 238             result = null;
 239         } else {
 240             Parent parent = node.getParent();
 241             Cell<?> cell = null;
 242             while (parent != null) {
 243                 if (parent instanceof Cell) {
 244                     cell = (Cell<?>) parent;
 245                     break;
 246                 }
 247             }
 248             // A cell has been found
 249             if (cell != null) {
 250                 assert cell.isEmpty() == false;
 251                 if (cell.isVisible()) {
 252                     final Object item = cell.getItem();
 253                     assert item instanceof HierarchyItem;
 254                     final HierarchyItem hierarchyItem = (HierarchyItem) item;
 255                     result = hierarchyItem.getFxomObject();
 256                 } else {
 257                     result = null;
 258                 }
 259             } else {
 260                 result = null;
 261             }
 262         }
 263 
 264         return result;
 265     }
 266 
 267     /**
 268      * Returns the node in hierarchy panel matching a given fxom object.
 269      * Returns null if the FXOMObject is currently not displayed by hierarchy
 270      * panel.
 271      * The returned Node is a Cell object.
 272      *
 273      * @param documentScene a scene holding a document window
 274      * @param fxomObject an fxom object (never null)
 275      * @return null or the matching node in hierarchy panel
 276      */
 277     public static Node fxomObjectToHierarchyPanelNode(
 278             Scene documentScene, FXOMObject fxomObject) {
 279         assert documentScene != null;
 280         assert fxomObject != null;
 281 
 282         final Node result;
 283         final DocumentWindowController dwc = lookupWindowController(documentScene);
 284         if (dwc == null) {
 285             result = null;
 286         } else {
 287             final EditorController ec = dwc.getEditorController();
 288             assert fxomObject.getFxomDocument() == ec.getFxomDocument();
 289 
 290             final AbstractHierarchyPanelController hpc = dwc.getHierarchyPanelController();
 291             assert hpc != null;
 292             assert hpc.getPanelControl() != null;
 293             if (hpc.getPanelControl().isVisible()) {
 294                 final TreeItem<HierarchyItem> treeItem = hpc.lookupTreeItem(fxomObject);
 295                 if (treeItem != null) {
 296                     result = hpc.getCell(treeItem);
 297                 } else {
 298                     result = null;
 299                 }
 300             } else {
 301                 result = null;
 302             }
 303         }
 304 
 305         return result;
 306     }
 307 
 308     /**
 309      * Looks for the TreeItem corresponding to the specified FXOM object.
 310      * If a TreeItem has been found, scroll to this TreeItem within the hierarchy panel.
 311      *
 312      * @param documentScene
 313      * @param fxomObject
 314      */
 315     public static void revealInHierarchyPanel(
 316             Scene documentScene, FXOMObject fxomObject) {
 317         assert documentScene != null;
 318         assert fxomObject != null;
 319         final DocumentWindowController dwc = lookupWindowController(documentScene);
 320         if (dwc != null) {
 321             final EditorController ec = dwc.getEditorController();
 322             assert fxomObject.getFxomDocument() == ec.getFxomDocument();
 323 
 324             final AbstractHierarchyPanelController hpc
 325                     = dwc.getHierarchyPanelController();
 326             assert hpc != null;
 327             assert hpc.getPanelControl() != null;
 328             // First expand the hierarchy tree
 329             expandAllTreeItems(hpc.getRoot());
 330             // Then look for the fxom object
 331             if (hpc.getPanelControl().isVisible()) {
 332                 final TreeItem<HierarchyItem> treeItem
 333                         = hpc.lookupTreeItem(fxomObject);
 334                 if (treeItem != null) {
 335                     hpc.scrollTo(treeItem);
 336                 }
 337             }
 338         }
 339     }
 340 
 341     /**
 342      * Returns the node representing a resize handle.
 343      *
 344      * @param documentScene a scene holding a document window
 345      * @param fxomObject one of the selected fxom object
 346      * @param cp the cardinal point of the target handle
 347      * @return null or the node representing the handle
 348      */
 349     public static Node lookupResizeHandle(
 350             Scene documentScene, FXOMObject fxomObject, CardinalPoint cp) {
 351         assert documentScene != null;
 352         assert fxomObject != null;
 353 
 354         final Node result;
 355         final DocumentWindowController dwc = lookupWindowController(documentScene);
 356         if (dwc == null) {
 357             result = null;
 358         } else {
 359             final EditorController ec = dwc.getEditorController();
 360 
 361             assert fxomObject.getFxomDocument() == ec.getFxomDocument();
 362             assert ec.getSelection().isSelected(fxomObject);
 363 
 364             final ContentPanelController cpc = dwc.getContentPanelController();
 365             final AbstractHandles<?> h = cpc.lookupHandles(fxomObject);
 366             if (h instanceof AbstractGenericHandles<?>) {
 367                 final AbstractGenericHandles<?> gh = (AbstractGenericHandles<?>) h;
 368                 result = gh.getHandleNode(cp);
 369             } else {
 370                 result = null;
 371             }
 372         }
 373 
 374         return result;
 375     }
 376 
 377     /**
 378      * Returns the version string.
 379      * It has the format 'Version: [major].[minor]-b[ii], Changeset: [someValue]'.
 380      * <br>A typical value is 'Version: 2.0-b07, Changeset: 8a5ccd834b5f'.
 381      *
 382      * @return a version string. It is never null: in the case something weird
 383      * would occur when constructing the proper value then what is returned is
 384      * 'UNSET'.
 385      */
 386     public static String getVersionString() {
 387         AboutWindowController awc = new AboutWindowController();
 388         return awc.getBuildInfo();
 389     }
 390 
 391 
 392     /**
 393      * Closes the preview window associated to a document window.
 394      * Performs nothing if documentScene is not a scene associated to a
 395      * document window or if preview window is not opened.
 396      *
 397      * @param documentScene a scene holding a document window
 398      */
 399     public static void closePreviewWindow(Scene documentScene) {
 400         final DocumentWindowController dwc = lookupWindowController(documentScene);
 401         if (dwc != null) {
 402             dwc.getPreviewWindowController().closeWindow();
 403         }
 404     }
 405 
 406     /**
 407      * Starts the application in test mode.
 408      * In this mode, no files are opened at application startup.
 409      *
 410      * @param args arguments to SceneBuilderApp.main()
 411      */
 412     public static void startApplication(String[] args) {
 413         SceneBuilderApp.main(args);
 414     }
 415 
 416     /*
 417      * Private
 418      */
 419 
 420     private static FXOMDocument lookupFxomDocument(Scene documentScene) {
 421         final FXOMDocument result;
 422 
 423         final DocumentWindowController dwc = lookupWindowController(documentScene);
 424         if (dwc == null) {
 425             result = null;
 426         } else {
 427             result = dwc.getEditorController().getFxomDocument();
 428         }
 429 
 430         return result;
 431     }
 432 
 433     private static DocumentWindowController lookupWindowController(Scene documentScene) {
 434         DocumentWindowController result = null;
 435 
 436         final SceneBuilderApp app = SceneBuilderApp.getSingleton();
 437         for (DocumentWindowController c : app.getDocumentWindowControllers()) {
 438             if (c.getScene() == documentScene) {
 439                 result = c;
 440                 break;
 441             }
 442         }
 443 
 444         return result;
 445     }
 446 
 447     private static <T> void expandAllTreeItems(final TreeItem<T> parentTreeItem) {
 448         if (parentTreeItem != null) {
 449             parentTreeItem.setExpanded(true);
 450             final List<TreeItem<T>> children = parentTreeItem.getChildren();
 451             if (children != null) {
 452                 for (TreeItem<T> child : children) {
 453                     expandAllTreeItems(child);
 454                 }
 455             }
 456         }
 457     }
 458 }