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 86b47393dc290e086696323a5f07d0be07adb0bb..0cf1dac697e971d2c67647128a92cd5dc8451b10 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 96871186e27ace44069441dbf7ac8ebcd7753390 GREEN 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 9e119a36de6ab9e1a07cd416de0126a89514bd38..96871186e27ace44069441dbf7ac8ebcd7753390 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,17 +16,21 @@ 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; import org.eclipse.fx.ui.workbench3.FXViewPart; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.part.ViewPart; /** * Base class to create eclipse (SWT-based) views using JavaFX GUI elements. This class requires @@ -54,48 +58,49 @@ 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. + * Constructor that is intended for {@link ViewPart}s which host a single view/node/control. * * @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; + // Initializing the base constructor with "null" for the FXML file indicates a + // single-view/node/control usecase. + 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 +123,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 +153,7 @@ public abstract class AF3FXViewPart extends FXViewPart { // NO_UCD if(!isCustomStyleSheetApplied) { setNativeLook(root); } + Scene scene = new Scene(root); return scene; } @@ -145,9 +165,50 @@ public abstract class AF3FXViewPart extends FXViewPart { // NO_UCD } /** - * 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 - * looks are supported. + * Init method that is called after the JavaFX toolkit has been launched. The initialize method + * is needed for single-node/view/control {@link ViewPart}s and for controls in a FXML-based + * view that require an initial configuration. If no initial configuration is required place a + * stub. + * <p> + * For FXML, the initialize method is called via the JavaFX framework. Otherwise, it is called + * via reflection from this class. + */ + protected abstract void initialize(); + + /** + * 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 not break the UI experience. 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 looks are supported. */ private void setNativeLook(Pane pane) { if(SystemUtils.IS_OS_WINDOWS_10 || SystemUtils.IS_OS_WINDOWS_8) { diff --git a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/.ratings b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/.ratings index 79965691f84f615b0cba722708810a98539dd0f3..b9c74adc885a96f885f96455338aa033f0bc108b 100644 --- a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/.ratings +++ b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/.ratings @@ -1,5 +1,5 @@ AutoUndoCommandStack.java 6aa645a9ed6e6547539c376fda97284928c4f9d4 GREEN EMFTransactionalCommand.java ba4b5bead9768b6ce6c955b9238cd96cb722533c GREEN EclipseResourceStorageService.java 1b9722e31a5ec33e4c3f7bb171fc2ce587729bf8 GREEN -ModelContext.java 85ede185d4b232221a96d0bf37a4c676b105dc00 GREEN +ModelContext.java 220944231c0e036f29d4911d2658c33b2427b09f GREEN NonDirtyingEMFTransactionalCommand.java ec5f282603891096b09f2628155dd27e3a21c588 GREEN diff --git a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java index 85ede185d4b232221a96d0bf37a4c676b105dc00..220944231c0e036f29d4911d2658c33b2427b09f 100644 --- a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java +++ b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java @@ -34,6 +34,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.resources.IFile; @@ -151,7 +152,14 @@ class ModelContext implements ITopLevelElement, CommandStackListener { } }); - checkIDs(); + if(checkIDs()) { + try { + doSave(new NullProgressMonitor()); + } catch(IOException | CoreException e) { + error(ToolingKernelActivator.getDefault(), + "Error saving model file when fixing missing/duplicate IDs.", e); + } + } } /** {@inheritDoc} */ @@ -182,29 +190,35 @@ class ModelContext implements ITopLevelElement, CommandStackListener { } /** - * Checks whether all IDs are present and unique and updates {@link #maxId}. In case duplicate - * IDs have been detected (which could only be caused by a bug in AF3), these are removed and an - * error is logged. + * Checks whether all IDs are present and unique and updates {@link #maxId}. + * <ul> + * <li>Duplicate IDs indicate a programming error. They are removed and an error is logged</li> + * <li>Missing IDs are silently added.</li> + * </ul> + * + * @return {@code true} iff the model was modified because of missing/duplicated IDs (and hence + * must + * be saved). */ - private void checkIDs() { + private boolean checkIDs() { Set<Integer> ids = new HashSet<Integer>(); - boolean hadMissing = false; - boolean hadDuplicates = false; + // Store String representation of element with duplicated IDs before fixing it + Map<String, Integer> objWithIdProblem = new HashMap<String, Integer>(); for(Iterator<EObject> i = getRootModelElement().eAllContents(); i.hasNext();) { EObject eo = i.next(); if(eo instanceof IIdLabeled) { final IIdLabeled element = (IIdLabeled)eo; int id = element.getId(); if(id <= 0) { - hadMissing = true; + objWithIdProblem.put(element.toString(), null); } else { // Reset duplicate IDs. They will be regenerated together with missing IDs. This // is a safe operation since at this stage, references are still based on Java // references. Only in the persisted resource, references will be based on IDs, // which is why special care must be taken to ensure uniqueness. if(ids.contains(id)) { - hadDuplicates = true; + objWithIdProblem.put(element.toString(), element.getId()); runAsNonDirtyingCommand(() -> { element.setId(0); }); @@ -218,24 +232,31 @@ class ModelContext implements ITopLevelElement, CommandStackListener { maxId = max(0, maxId); - if(hadMissing || hadDuplicates) { + if(!objWithIdProblem.isEmpty()) { runAsNonDirtyingCommand(() -> { maxId = generateMissingIDs(getRootModelElement(), maxId); }); - try { - doSave(new NullProgressMonitor()); - } catch(IOException | CoreException e) { - error(ToolingKernelActivator.getDefault(), - "Error saving model file when fixing missing/duplicate IDs.", e); - } - } - if(hadDuplicates) { - String msg = "Duplicate IDs have been removed from \""; - msg += resource.getURI().lastSegment() + "\". "; - msg += "Please report this incident since it could result in corrupted model files."; + String msg = "The following missing/duplicate IDs have been fixed in \""; + msg += resource.getURI().lastSegment() + "\".\n"; + msg += "Please report this incident since it indicates a programming error "; + msg += "that could result in corrupted model files.\n"; + for(Entry<String, Integer> entry : objWithIdProblem.entrySet()) { + Integer id = entry.getValue(); + if(id == null) { + msg += " Missing ID added to "; + } else { + msg += " Duplicate ID " + id + " disambiguated for "; + } + String name = entry.getKey(); + msg += "element \"" + name + "\"\n"; + } error(ToolingKernelActivator.getDefault(), msg); + + return true; } + + return false; } /** {@inheritDoc} */