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 }