@Override
 public void undo() {
   final FXOMDocument fxomDocument = getEditorController().getFxomDocument();
   fxomDocument.beginUpdate();
   subJob.undo();
   fxomDocument.endUpdate();
 }
 @Override
 public void execute() {
   final FXOMDocument fxomDocument = getEditorController().getFxomDocument();
   assert isExecutable();
   fxomDocument.beginUpdate();
   subJob.execute();
   fxomDocument.endUpdate();
 }
  private void updateContentGroup() {

    /*
     * fxomRoot
     */

    final String statusMessageText, statusStyleClass;
    contentGroup.getChildren().clear();

    if (fxomDocument == null) {
      statusMessageText = "FXOMDocument is null"; // NOI18N
      statusStyleClass = "stage-prompt"; // NOI18N
    } else if (fxomDocument.getFxomRoot() == null) {
      statusMessageText = I18N.getString("content.label.status.invitation");
      statusStyleClass = "stage-prompt"; // NOI18N
    } else {
      final Object userSceneGraph = fxomDocument.getSceneGraphRoot();
      if (userSceneGraph instanceof Node) {
        final Node rootNode = (Node) userSceneGraph;
        assert rootNode.getParent() == null;
        contentGroup.getChildren().add(rootNode);
        layoutContent(true /* applyCSS */);
        if (layoutException == null) {
          statusMessageText = ""; // NOI18N
          statusStyleClass = "stage-prompt-default"; // NOI18N
        } else {
          contentGroup.getChildren().clear();
          statusMessageText = I18N.getString("content.label.status.cannot.display");
          statusStyleClass = "stage-prompt"; // NOI18N
        }
      } else {
        statusMessageText = I18N.getString("content.label.status.cannot.display");
        statusStyleClass = "stage-prompt"; // NOI18N
      }
    }

    backgroundPane.setText(statusMessageText);
    backgroundPane.getStyleClass().clear();
    backgroundPane.getStyleClass().add(statusStyleClass);

    // If layoutException != null, then this layout call is required
    // so that backgroundPane updates its message... Strange...
    backgroundPane.layout();

    adjustWorkspace();
  }
  private void constructContextMenuMap() {
    if (contextMenuMap == null) {
      contextMenuMap = new LinkedHashMap<>();

      // Build the ContextMenu item from the library builtin items
      final String contextMenuFxmlPath = "builtin/ContextMenu.fxml"; // NOI18N
      final URL contextMenuFxmlURL = BuiltinLibrary.class.getResource(contextMenuFxmlPath);
      assert contextMenuFxmlURL != null;

      final AbstractSelectionGroup asg = getEditorController().getSelection().getGroup();
      assert asg instanceof ObjectSelectionGroup; // Because of (1)
      final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg;

      try {
        final String contextMenuFxmlText = FXOMDocument.readContentFromURL(contextMenuFxmlURL);

        final FXOMDocument fxomDocument = getEditorController().getFxomDocument();
        final Library library = getEditorController().getLibrary();
        for (FXOMObject fxomObject : osg.getItems()) {
          final FXOMDocument contextMenuDocument =
              new FXOMDocument(
                  contextMenuFxmlText, contextMenuFxmlURL, library.getClassLoader(), null);

          assert contextMenuDocument != null;
          final FXOMObject contextMenuObject = contextMenuDocument.getFxomRoot();
          assert contextMenuObject != null;
          contextMenuObject.moveToFxomDocument(fxomDocument);
          assert contextMenuDocument.getFxomRoot() == null;

          contextMenuMap.put(fxomObject, contextMenuObject);
        }
      } catch (IOException x) {
        throw new IllegalStateException("Bug in " + getClass().getSimpleName(), x); // NOI18N
      }
    }
  }
  private void updateScalingGroup() {
    if (scalingGroup != null) {
      final double actualScaling;
      if (fxomDocument == null) {
        actualScaling = 1.0;
      } else if (fxomDocument.getSceneGraphRoot() == null) {
        actualScaling = 1.0;
      } else {
        actualScaling = scaling;
      }
      scalingGroup.setScaleX(actualScaling);
      scalingGroup.setScaleY(actualScaling);

      if (Platform.isSupported(ConditionalFeature.SCENE3D)) {
        scalingGroup.setScaleZ(actualScaling);
      }
      // else {
      //      leave scaleZ unchanged else it breaks zooming when running
      //      with the software pipeline (see DTL-6661).
      // }
    }
  }
  private void adjustWorkspace() {
    final Bounds backgroundBounds, extensionBounds;

    final Object userSceneGraph;
    if (fxomDocument == null) {
      userSceneGraph = null;
    } else {
      userSceneGraph = fxomDocument.getSceneGraphRoot();
    }
    if ((userSceneGraph instanceof Node) && (layoutException == null)) {
      final Node rootNode = (Node) userSceneGraph;

      final Bounds rootBounds = rootNode.getLayoutBounds();

      if (rootBounds.isEmpty()
          || (rootBounds.getWidth() == 0.0)
          || (rootBounds.getHeight() == 0.0)) {
        backgroundBounds = new BoundingBox(0.0, 0.0, 0.0, 0.0);
        extensionBounds = new BoundingBox(0.0, 0.0, 0.0, 0.0);
      } else {
        final double scale;
        if ((rootBounds.getDepth() > 0) && autoResize3DContent) {
          // Content is 3D
          final double scaleX = AUTORESIZE_SIZE / rootBounds.getWidth();
          final double scaleY = AUTORESIZE_SIZE / rootBounds.getHeight();
          final double scaleZ = AUTORESIZE_SIZE / rootBounds.getDepth();
          scale = Math.min(scaleX, Math.min(scaleY, scaleZ));
        } else {
          scale = 1.0;
        }
        contentGroup.setScaleX(scale);
        contentGroup.setScaleY(scale);
        contentGroup.setScaleZ(scale);

        final Bounds contentBounds = rootNode.localToParent(rootBounds);
        backgroundBounds =
            new BoundingBox(
                0.0,
                0.0,
                contentBounds.getMinX() + contentBounds.getWidth(),
                contentBounds.getMinY() + contentBounds.getHeight());

        final Bounds unclippedRootBounds = computeUnclippedBounds(rootNode);
        assert unclippedRootBounds.getHeight() != 0.0;
        assert unclippedRootBounds.getWidth() != 0.0;
        assert rootNode.getParent() == contentGroup;

        final Bounds unclippedContentBounds = rootNode.localToParent(unclippedRootBounds);
        extensionBounds = computeExtensionBounds(backgroundBounds, unclippedContentBounds);
      }
    } else {
      backgroundBounds = new BoundingBox(0.0, 0.0, 320.0, 150.0);
      extensionBounds = new BoundingBox(0.0, 0.0, 0.0, 0.0);
    }

    backgroundPane.setPrefWidth(backgroundBounds.getWidth());
    backgroundPane.setPrefHeight(backgroundBounds.getHeight());
    extensionRect.setX(extensionBounds.getMinX());
    extensionRect.setY(extensionBounds.getMinY());
    extensionRect.setWidth(extensionBounds.getWidth());
    extensionRect.setHeight(extensionBounds.getHeight());

    contentSubScene.setWidth(contentGroup.getLayoutBounds().getWidth());
    contentSubScene.setHeight(contentGroup.getLayoutBounds().getHeight());
  }