From 01fa2b19561f063c1e33a7eb6204628b841f3fd4 Mon Sep 17 00:00:00 2001
From: Alexander Diewald <diewald@fortiss.org>
Date: Thu, 28 Jun 2018 11:54:09 +0200
Subject: [PATCH] JavaFX: Add Single-Node View support.

* Modify the constructors such that AF3FXViewParts can be created
  that only contain a single FX node/control, and do not come along
  with a FXML layout file.
* Due to signature conflicts, the css location must be given in the
  constructor. The default case is to use "null" such that the
  OS-native look is used.
* Add a method "setChildNodeFinal" that must be used to add the FX node
  to the view. It is not possible to do this in constructors since the
  FX toolkit is not initialized at this point in time.

Issue-Ref: 3463
Issue-Url: https://af3-developer.fortiss.org/issues/3463
Signed-off-by: Alexander Diewald <diewald@fortiss.org>
---
 .../fortiss/tooling/common/ui/javafx/.ratings |  2 +-
 .../common/ui/javafx/AF3FXViewPart.java       | 93 ++++++++++++++-----
 2 files changed, 71 insertions(+), 24 deletions(-)

diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/.ratings
index 86b47393d..2e06d6b86 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/.ratings
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/.ratings
@@ -1 +1 @@
-AF3FXViewPart.java 9e119a36de6ab9e1a07cd416de0126a89514bd38 GREEN
+AF3FXViewPart.java 10100de78c542858792cb51a7c3cfe7e55ec95d7 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/AF3FXViewPart.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/AF3FXViewPart.java
index 9e119a36d..10100de78 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/AF3FXViewPart.java
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/AF3FXViewPart.java
@@ -16,11 +16,14 @@
 package org.fortiss.tooling.common.ui.javafx;
 
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 
 import javafx.application.Platform;
 import javafx.fxml.FXMLLoader;
+import javafx.scene.Node;
 import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
 import javafx.scene.layout.Pane;
 
 import org.apache.commons.lang3.SystemUtils;
@@ -54,48 +57,47 @@ public abstract class AF3FXViewPart extends FXViewPart { // NO_UCD
 	/** References the stub that defines the FXML file defining the layout and controller. */
 	private Class<? extends AF3FXViewPart> viewerClass;
 
+	/** References the root {@link Pane} to which GUI elements must be added to be displayed. */
+	private Pane root;
+
 	/**
 	 * Constructor. Allows to pass the view's FXML definition file along with style.
 	 * 
 	 * @param viewerClass
-	 *            Stub class that defines the FXML file defining the layout and controller
-	 * @param fXMLLocation
-	 *            location of the fXML file relative to the source (resource) folders.
+	 *            Class implementing the {@link AF3FXViewPart} to load resources from its containing
+	 *            bundle.
+	 * @param cssLocation
+	 *            style sheet file path for the appearance settings relative to the source
+	 *            (resource) folders. Pass {@code null} if the OS-native look shall be used.
 	 * @throws Exception
 	 *             if the given FXML file path is not given or if it cannot be found in any of the
 	 *             source (resource) folders.
 	 */
-	public AF3FXViewPart(Class<? extends AF3FXViewPart> viewerClass, String fXMLLocation)
+	public AF3FXViewPart(Class<? extends AF3FXViewPart> viewerClass, String cssLocation)
 			throws Exception {
-		if(fXMLLocation == null) {
-			throw new Exception(
-					"The location of the FXML file defining the gui elements was not given," +
-							"or it cannot be found at the given location (" + fXMLLocation + ") " +
-							"The view will NOT be created.");
-		}
-
-		Platform.setImplicitExit(false);
-		this.viewerClass = viewerClass;
-		this.fXMLLocation = fXMLLocation;
+		this(viewerClass, null, cssLocation);
 	}
 
 	/**
 	 * Constructor. Allows to pass the view's FXML definition file.
 	 * 
 	 * @param viewerClass
-	 *            Stub class that defines the FXML file defining the layout and controller
+	 *            Class implementing the {@link AF3FXViewPart} to load resources from its containing
+	 *            bundle.
 	 * @param fXMLLocation
 	 *            location of the fXML file relative to the source (resource) folders.
 	 * @param cssLocation
 	 *            style sheet file path for the appearance settings relative to the source
-	 *            (resource) folders.
+	 *            (resource) folders. Pass {@code null} if the OS-native look shall be used.
 	 * @throws Exception
 	 *             if the given FXML file path is not given or if it cannot be found in any of the
 	 *             source (resource) folders.
 	 */
 	public AF3FXViewPart(Class<? extends AF3FXViewPart> viewerClass, String fXMLLocation,
 			String cssLocation) throws Exception {
-		this(viewerClass, fXMLLocation);
+		Platform.setImplicitExit(false);
+		this.viewerClass = viewerClass;
+		this.fXMLLocation = fXMLLocation;
 		this.cssLocation = cssLocation;
 	}
 
@@ -118,14 +120,28 @@ public abstract class AF3FXViewPart extends FXViewPart { // NO_UCD
 	/** {@inheritDoc} */
 	@Override
 	protected Scene createFxScene() {
-		Pane root = null;
 		FXMLLoader loader = new FXMLLoader();
 		loader.setClassLoader(viewerClass.getClassLoader());
-		loader.setLocation(viewerClass.getResource(fXMLLocation));
-		try {
-			root = (Pane)loader.load();
-		} catch(IOException e) {
-			throw new RuntimeException("Cannot load the resource located at " + fXMLLocation, e);
+		if(fXMLLocation != null) {
+			loader.setLocation(viewerClass.getResource(fXMLLocation));
+			try {
+				root = (Pane)loader.load();
+			} catch(IOException e) {
+				throw new RuntimeException("Cannot load the resource located at " + fXMLLocation, e);
+			}
+		} else {
+			root = new AnchorPane();
+			try {
+				viewerClass.getMethod("initialize").invoke(this);
+			} catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException |
+					SecurityException e) {
+				throw new RuntimeException("The initialize method of the View " +
+						viewerClass.getSimpleName() + " threw an exception!", e);
+			} catch(NoSuchMethodException e) {
+				throw new RuntimeException("For single Controller views, an initialize method is "
+						+ "required that creates the control! This cannot be done in constructors "
+						+ "since the JavaFX toolkit is not initialized in that stage.", e);
+			}
 		}
 
 		if(cssLocation != null) {
@@ -134,6 +150,7 @@ public abstract class AF3FXViewPart extends FXViewPart { // NO_UCD
 		if(!isCustomStyleSheetApplied) {
 			setNativeLook(root);
 		}
+
 		Scene scene = new Scene(root);
 		return scene;
 	}
@@ -144,6 +161,36 @@ public abstract class AF3FXViewPart extends FXViewPart { // NO_UCD
 		// Not needed.
 	}
 
+	/**
+	 * Adds a single {@link Node} to this view. It embeds the given {@link Node} to the
+	 * {@link AnchorPane} of this view and stretches it to the borders of the view.
+	 * <p>
+	 * NOTE: This method may only be used for single-control views, not FXML-based ones.
+	 * 
+	 * @param fxNode
+	 *            {@link Node} to be added to the view.
+	 */
+	protected void setChildNodeFinal(Node fxNode) {
+		if(fXMLLocation != null) {
+			throw new RuntimeException("This method is intended for single-control views. It may"
+					+ " not be used for FXML-based views.");
+		}
+		if(!root.getChildren().isEmpty()) {
+			throw new RuntimeException("The method setChildNodeFinal was invoked more than once."
+					+ " This method is intended for single-control views. If more controls"
+					+ "shall be added, please define an FXML layout file and call the appropriate"
+					+ " constructors.");
+		}
+
+		root.getChildren().add(fxNode);
+		// Stretch the node to the borders of the view. In the createScene method, we a create a
+		// root pane that is an AnchorPane, so we can safely use these calls here.
+		AnchorPane.setTopAnchor(fxNode, 0.0);
+		AnchorPane.setRightAnchor(fxNode, 0.0);
+		AnchorPane.setBottomAnchor(fxNode, 0.0);
+		AnchorPane.setLeftAnchor(fxNode, 0.0);
+	}
+
 	/**
 	 * Sets the native look of the target platform to break the UI. Most of the UI code is based on
 	 * SWT which uses a platform native look. Currently, Windows 8&10, Windows 7, and MacOS X native
-- 
GitLab