Bug 546654: [UIBuilder] provide a module to dynamically build UIs Change-Id: If616b1cb47e012ab1946052bf7ea7e061b7a9895
diff --git a/plugins/org.eclipse.ease.modules.charting/src/org/eclipse/ease/modules/charting/ChartingModule.java b/plugins/org.eclipse.ease.modules.charting/src/org/eclipse/ease/modules/charting/ChartingModule.java index 73c00f1..8e14ebe 100644 --- a/plugins/org.eclipse.ease.modules.charting/src/org/eclipse/ease/modules/charting/ChartingModule.java +++ b/plugins/org.eclipse.ease.modules.charting/src/org/eclipse/ease/modules/charting/ChartingModule.java
@@ -28,6 +28,8 @@ import org.eclipse.nebula.visualization.xygraph.figures.Axis; import org.eclipse.nebula.visualization.xygraph.figures.Trace; import org.eclipse.nebula.visualization.xygraph.figures.XYGraph; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IWorkbenchPage; @@ -69,6 +71,27 @@ } /** + * Create a new chart on a given parent composite. + * + * @param parent + * composite to create chart into + * @param figureId + * name of the figure to be created + * @return new chart instance + */ + @WrapToScript + public Chart createChart(Composite parent, @ScriptParameter(defaultValue = ScriptParameter.NULL) final String figureId) { + final String secondaryId = (figureId == null) ? "Figure " + Integer.toString(fFigureIterator++) : figureId; + + Display.getDefault().syncExec(() -> { + fChart = new Chart(parent, SWT.NONE); + fXYGraph = fChart.setPlotTitle(secondaryId); + }); + + return fChart; + } + + /** * Series will be created with the name seriesName. If the name is not given then Series will have the name "Series id" where id is the current number of * the Series Iterator. Series Iterator start at 1 and with every new Series is incremented by one. If Series is already created with seriesName then that * series will be set as currently active Series and on that Series methods will be performed. If there is no active Figure then Figure will be created and
diff --git a/plugins/org.eclipse.ease.modules.platform/META-INF/MANIFEST.MF b/plugins/org.eclipse.ease.modules.platform/META-INF/MANIFEST.MF index 17b8005..4ee454b 100644 --- a/plugins/org.eclipse.ease.modules.platform/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.ease.modules.platform/META-INF/MANIFEST.MF
@@ -18,7 +18,9 @@ org.eclipse.equinox.p2.metadata;bundle-version="[2.2.0,3.0.0)", org.eclipse.equinox.p2.engine;bundle-version="[2.3.0,3.0.0)", org.eclipse.e4.core.services;bundle-version="[1.2.1,3.0.0)", - org.eclipse.osgi.services;bundle-version="[3.4.0,4.0.0)" + org.eclipse.osgi.services;bundle-version="[3.4.0,4.0.0)", + org.eclipse.e4.ui.model.workbench, + org.eclipse.e4.ui.workbench Bundle-Version: 0.7.0.qualifier Bundle-Name: Platform modules (Incubation) Bundle-ManifestVersion: 2
diff --git a/plugins/org.eclipse.ease.modules.platform/plugin.xml b/plugins/org.eclipse.ease.modules.platform/plugin.xml index df86009..b385ce4 100644 --- a/plugins/org.eclipse.ease.modules.platform/plugin.xml +++ b/plugins/org.eclipse.ease.modules.platform/plugin.xml
@@ -30,6 +30,13 @@ name="Build" visible="true"> </module> + <module + category="org.eclipse.ease.category.system" + class="org.eclipse.ease.modules.platform.uibuilder.UIBuilderModule" + id="org.eclipse.ease.modules.platform.uibuilder" + name="UI Builder" + visible="true"> + </module> </extension> <extension point="org.eclipse.ease.ui.shell">
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/UIModule.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/UIModule.java index e7107a6..b1cd990 100644 --- a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/UIModule.java +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/UIModule.java
@@ -530,7 +530,7 @@ * name of view * @return view ID or <code>null</code> */ - private static String getIDForName(final String name) { + public static String getIDForName(final String name) { String id = null; final IViewRegistry viewRegistry = PlatformUI.getWorkbench().getViewRegistry();
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/CompositeRenderer.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/CompositeRenderer.java new file mode 100644 index 0000000..6ff4127 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/CompositeRenderer.java
@@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; + +public class CompositeRenderer implements IRenderer, PaintListener { + + private final Composite fParent; + private ViewModel fViewModel; + private boolean fShowGrid = false; + + public CompositeRenderer(Composite parent) { + fParent = parent; + } + + @Override + public void setViewModel(ViewModel viewModel) { + fViewModel = viewModel; + } + + @Override + public IPlaceHolder createPlaceHolder(Point position) { + final LabelPlaceHolder placeHolder = new LabelPlaceHolder(fParent, position.y, position.x); + placeHolder.setVisible(fShowGrid); + return placeHolder; + } + + public Composite getParent() { + return fParent; + } + + private GridLayout getLayout() { + return (GridLayout) getParent().getLayout(); + } + + public void update() { + Control predecessor = null; + for (final Object element : fViewModel.getElements()) { + if (element instanceof Control) { + ((Control) element).moveBelow(predecessor); + predecessor = (Control) element; + } + } + + getLayout().numColumns = fViewModel.getColumnCount(); + + getParent().layout(true); + } + + public void setShowGrid(boolean showGrid) { + fShowGrid = showGrid; + + if (showGrid) + getParent().addPaintListener(this); + else + getParent().removePaintListener(this); + + for (final Object element : fViewModel.getElements()) { + if (element instanceof LabelPlaceHolder) + ((LabelPlaceHolder) element).setVisible(showGrid); + } + + getParent().layout(true); + } + + @Override + public void paintControl(PaintEvent e) { + e.gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED)); + + final List<Rectangle> columns = new ArrayList<>(); + while (columns.size() < fViewModel.getColumnCount()) + columns.add(new Rectangle(0, 0, 0, 0)); + + final List<Rectangle> rows = new ArrayList<>(); + while (rows.size() < fViewModel.getRowCount()) + rows.add(new Rectangle(0, 0, 0, 0)); + + for (final Object element : fViewModel.getElements()) { + if (element instanceof Control) { + final Rectangle bounds = ((Control) element).getBounds(); + final Point columnRowLocation = fViewModel.indexToPoint(fViewModel.getElements().indexOf(element)); + + final Rectangle column = columns.get(columnRowLocation.x - 1); + final Rectangle row = rows.get(columnRowLocation.y - 1); + + if (((GridData) ((Control) element).getLayoutData()).horizontalSpan == 1) + merge(bounds, column); + + if (((GridData) ((Control) element).getLayoutData()).verticalSpan == 1) + merge(bounds, row); + } + } + + final Rectangle firstRow = rows.get(0); + final Rectangle lastRow = rows.get(rows.size() - 1); + for (final Rectangle rectangle : columns) { + rectangle.y = firstRow.y; + rectangle.height = (lastRow.y + lastRow.height) - firstRow.y; + + e.gc.drawRectangle(rectangle.x - 3, rectangle.y - 3, rectangle.width + 5, rectangle.height + 5); + } + + final Rectangle firstColumn = columns.get(0); + final Rectangle lastColumn = columns.get(columns.size() - 1); + for (final Rectangle rectangle : rows) { + rectangle.x = firstColumn.x; + rectangle.width = (lastColumn.x + lastColumn.width) - firstColumn.x; + + e.gc.drawRectangle(rectangle.x - 3, rectangle.y - 3, rectangle.width + 5, rectangle.height + 5); + } + } + + private void merge(Rectangle source, Rectangle target) { + if (target.x == 0) + target.x = source.x; + + if (target.y == 0) + target.y = source.y; + + if (target.width == 0) + target.width = source.width; + + if (target.height == 0) + target.height = source.height; + + target.add(source); + } +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/DialogRunnable.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/DialogRunnable.java new file mode 100644 index 0000000..446a612 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/DialogRunnable.java
@@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ +package org.eclipse.ease.modules.platform.uibuilder; + +import org.eclipse.swt.widgets.Composite; + +public abstract class DialogRunnable implements Runnable { + + private Composite fComposite; + + private ScriptableDialog fDialog; + + public void setDialog(ScriptableDialog dialog) { + fDialog = dialog; + } + + public ScriptableDialog getDialog() { + return fDialog; + } + + public void setComposite(Composite composite) { + fComposite = composite; + } + + public Composite getComposite() { + return fComposite; + } +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/IPlaceHolder.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/IPlaceHolder.java new file mode 100644 index 0000000..e463c59 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/IPlaceHolder.java
@@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +/** + * Pure marker interface for empty layout cells. + */ +public interface IPlaceHolder { +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/IRenderer.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/IRenderer.java new file mode 100644 index 0000000..09ebb16 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/IRenderer.java
@@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import org.eclipse.swt.graphics.Point; + +public interface IRenderer { + + public IPlaceHolder createPlaceHolder(Point position); + + public void setViewModel(ViewModel viewModel); +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/LabelPlaceHolder.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/LabelPlaceHolder.java new file mode 100644 index 0000000..b91cd61 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/LabelPlaceHolder.java
@@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; + +public class LabelPlaceHolder extends Label implements IPlaceHolder { + + public LabelPlaceHolder(Composite composite, int row, int column) { + super(composite, SWT.NONE); + setText(column + "/" + row); + setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); + + setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY)); + } + + @Override + protected void checkSubclass() { + } +} \ No newline at end of file
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/Location.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/Location.java new file mode 100644 index 0000000..75078ad --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/Location.java
@@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; + +public class Location { + private static final Pattern LAYOUT_PATTERN = Pattern.compile("(?:(\\d+)(?:-(\\d+))?/(\\d+)(?:-(\\d+))?)?(?:\\s*([<>ox])(!?))?(?:\\s*([\\^vox])(!?))?"); + + public static int DYNAMIC_POSITION = -1; + + private final Point fPosition = new Point(DYNAMIC_POSITION, DYNAMIC_POSITION); + private final GridData fGridData = new GridData(); + + public Location() { + this(null); + } + + public Location(String layout) { + if (layout == null) + layout = "<^"; + + final Matcher matcher = LAYOUT_PATTERN.matcher(layout); + if (matcher.matches()) { + if (matcher.group(1) != null) { + + fPosition.x = Integer.parseInt(matcher.group(1)); + fPosition.y = Integer.parseInt(matcher.group(3)); + + fGridData.horizontalSpan = (matcher.group(2) != null) ? ((Integer.parseInt(matcher.group(2)) - fPosition.x) + 1) : 1; + fGridData.verticalSpan = (matcher.group(4) != null) ? ((Integer.parseInt(matcher.group(4)) - fPosition.y) + 1) : 1; + + // sanity checks + if (fPosition.x < 1) + throw new IllegalArgumentException("Column cannot be < 1"); + + if (fPosition.y < 1) + throw new IllegalArgumentException("Row cannot be < 1"); + + if (fGridData.horizontalSpan < 1) + throw new IllegalArgumentException("Column span cannot be < 1"); + + if (fGridData.verticalSpan < 1) + throw new IllegalArgumentException("Row span cannot be < 1"); + } + + if (matcher.group(5) != null) { + switch (matcher.group(5)) { + case "x": + fGridData.horizontalAlignment = SWT.CENTER; + break; + case ">": + fGridData.horizontalAlignment = SWT.RIGHT; + break; + case "o": + fGridData.horizontalAlignment = SWT.FILL; + break; + case "<": + // fall through + default: + fGridData.horizontalAlignment = SWT.LEFT; + break; + } + + } else + fGridData.horizontalAlignment = SWT.LEFT; + + fGridData.grabExcessHorizontalSpace = ("!".equals(matcher.group(6))); + + if (matcher.group(7) != null) { + switch (matcher.group(7)) { + case "x": + fGridData.verticalAlignment = SWT.CENTER; + break; + case "v": + fGridData.verticalAlignment = SWT.BOTTOM; + break; + case "o": + fGridData.verticalAlignment = SWT.FILL; + break; + case "^": + // fall through + default: + fGridData.verticalAlignment = SWT.TOP; + break; + } + + } else + fGridData.verticalAlignment = SWT.TOP; + + fGridData.grabExcessVerticalSpace = ("!".equals(matcher.group(8))); + + } else + throw new IllegalArgumentException("Cannot parse location: " + layout); + } + + public Point getPosition() { + return fPosition; + } + + public GridData getLayoutData() { + return fGridData; + } +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/Reference.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/Reference.java new file mode 100644 index 0000000..b0b1af8 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/Reference.java
@@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +public class Reference { + private final Object fDelegate; + + public Reference(Object delegate) { + fDelegate = delegate; + } + + public Object getDelegate() { + return fDelegate; + } + + @Override + public String toString() { + return "@" + fDelegate.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((fDelegate == null) ? 0 : fDelegate.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Reference other = (Reference) obj; + if (fDelegate == null) { + if (other.fDelegate != null) + return false; + } else if (!fDelegate.equals(other.fDelegate)) + return false; + return true; + } +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/ScriptableDialog.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/ScriptableDialog.java new file mode 100644 index 0000000..57bc1a8 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/ScriptableDialog.java
@@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ +package org.eclipse.ease.modules.platform.uibuilder; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class ScriptableDialog extends TitleAreaDialog { + + private Composite fContainer; + private final DialogRunnable fRunnableOnCreation; + + private final Map<Object, Object> fData = new HashMap<>(); + private final Map<Control, StructuredViewer> fViewers = new HashMap<>(); + + private String fMessageText = null; + private String fTitleText = null; + private Point fInitialSize = new Point(450, 450); + + /** + * Create the dialog. + * + * @param parentShell + */ + public ScriptableDialog(Shell parentShell, DialogRunnable runnableOnCreation) { + super(parentShell); + fRunnableOnCreation = runnableOnCreation; + } + + /** + * Create contents of the dialog. + * + * @param parent + */ + @Override + protected Control createDialogArea(Composite parent) { + final Composite area = (Composite) super.createDialogArea(parent); + fContainer = new Composite(area, SWT.NONE); + fContainer.setLayoutData(new GridData(GridData.FILL_BOTH)); + + if (fTitleText != null) + setTitle(fTitleText); + + if (fMessageText != null) + setMessage(fMessageText); + + fRunnableOnCreation.setComposite(fContainer); + fRunnableOnCreation.setDialog(this); + fRunnableOnCreation.run(); + + return area; + } + + public Composite getContainer() { + return fContainer; + } + + /** + * Create contents of the button bar. + * + * @param parent + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); + } + + /** + * Return the initial size of the dialog. + */ + @Override + protected Point getInitialSize() { + return fInitialSize; + } + + public void setInitialSize(int x, int y) { + fInitialSize = new Point(x, y); + } + + @Override + protected void okPressed() { + storeChildren(fContainer); + + super.okPressed(); + } + + private void storeChildren(Composite container) { + for (final Control child : container.getChildren()) { + + if (child instanceof Text) + fData.put(child, ((Text) child).getText()); + + else if (child instanceof Button) + fData.put(child, ((Button) child).getSelection()); + + else if (child instanceof Combo) { + + final StructuredViewer viewer = fViewers.get(child); + if (viewer != null) + fData.put(viewer, viewer.getStructuredSelection().getFirstElement()); + + } else if (child instanceof List) { + + final StructuredViewer viewer = fViewers.get(child); + if (viewer != null) + fData.put(viewer, viewer.getStructuredSelection().toArray()); + + } else if (child instanceof Composite) + // do this after all others as eg combo is a composite + storeChildren((Composite) child); + } + } + + public Object getData(Object control) { + return fData.get(control); + } + + public void setMessageText(String messageText) { + fMessageText = messageText; + } + + public void setTitleText(String titleText) { + fTitleText = titleText; + } + + public void registerViewer(Control control, StructuredViewer viewer) { + fViewers.put(control, viewer); + } +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/UIBuilderModule.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/UIBuilderModule.java new file mode 100644 index 0000000..b9c7e6ee --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/UIBuilderModule.java
@@ -0,0 +1,529 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import java.util.List; + +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.basic.MBasicFactory; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.model.application.ui.basic.MPartStack; +import org.eclipse.e4.ui.workbench.IWorkbench; +import org.eclipse.e4.ui.workbench.modeling.EModelService; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; +import org.eclipse.ease.modules.AbstractScriptModule; +import org.eclipse.ease.modules.ScriptParameter; +import org.eclipse.ease.modules.WrapToScript; +import org.eclipse.ease.modules.platform.UIModule; +import org.eclipse.ease.tools.RunnableWithResult; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ComboViewer; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.ListViewer; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbenchListener; +import org.eclipse.ui.PlatformUI; + +/** + * Create dynamic views by adding SWT controls via script commands to a dynamic {@link GridLayout}. Each element allow to provide layout data, which accepts a + * fairly simple syntax: <coordinates> <horizontal align> <vertical align> + * + * <dl> + * <dd>coordinates</dd> + * <dt>Provides X/Y coordinates for elements in the grid where 1/1 denotes the upper left corner. 2/1 would create the element in the second column of the first + * row. Coordinates may also provide rowSpan/columnSpan information: 1-3/2-3 would create the element spanning columns 1 to 3 in rows 2 to 3. + * {@module #showGrid(boolean)} adds visual indications to help in layouting.</dt> + * + * <dd>horizontal alignment</dd> + * <dt> + * <ul> + * <li>< ... align left</li> + * <li>x ... align center</li> + * <li>> ... align right</li> + * <li>o ... fill</li> + * <li>o! ... grab horizontal space (! may be added to any alignment)</li> + * </ul> + * </dt> + * + * <dd>vertical alignment</dd> + * <dt> + * <ul> + * <li>^ ... align top</li> + * <li>x ... align middle</li> + * <li>v ... align bottom</li> + * <li>o ... fill</li> + * <li>o! ... grab vertical space (! may be added to any alignment)</li> + * </ul> + * </dt> Most methods of this module deal with SWT components. To access them the code needs to be run in the UI thread. See {@link UIModule} for more + * information. + * </dl> + */ +public class UIBuilderModule extends AbstractScriptModule { + + private ViewModel fViewModel; + private CompositeRenderer fRenderer; + private ScriptableDialog fScriptableDialog; + + /** + * Create a view with scripted content. Automatically sets the active composite for further commands. This view will not be stored when the workbench gets + * closed. + * + * @param title + * view title + * @param onTopOfView + * name/ID of an existing view to put this view on top of + * @return view instance + * @throws Throwable + * when the view cannot be created + */ + @WrapToScript + public MPart createView(String title, @ScriptParameter(defaultValue = ScriptParameter.NULL) String onTopOfView) throws Throwable { + final RunnableWithResult<MPart> runnable = new RunnableWithResult<MPart>() { + + @Override + public void runWithTry() throws Throwable { + final EPartService partService = PlatformUI.getWorkbench().getService(EPartService.class); + final IWorkbench workbench = PlatformUI.getWorkbench().getService(IWorkbench.class); + final MApplication mApplication = workbench.getApplication(); + + final EModelService modelService = PlatformUI.getWorkbench().getService(EModelService.class); + + // create part + final MPart part = MBasicFactory.INSTANCE.createPart(); + part.setLabel(title); + part.setElementId("org.eclipse.ease.view.dynamic:1"); + part.setCloseable(true); + part.getPersistedState().put(IWorkbench.PERSIST_STATE, Boolean.FALSE.toString()); + + // find stack to put it into + final List<MPartStack> stacks = modelService.findElements(mApplication, null, MPartStack.class, null); + stacks.get(0).getChildren().add(part); + partService.showPart(part, PartState.ACTIVATE); + + setResult(part); + + setComposite((Composite) part.getWidget()); + + // make sure to close this window before we terminate. Eclipse would store it in its layout actually destroying all layout data. + PlatformUI.getWorkbench().addWorkbenchListener(new IWorkbenchListener() { + + @Override + public boolean preShutdown(org.eclipse.ui.IWorkbench workbench, boolean forced) { + partService.hidePart(part, true); + return true; + } + + @Override + public void postShutdown(org.eclipse.ui.IWorkbench workbench) { + } + }); + } + }; + + Display.getDefault().syncExec(runnable); + + return runnable.getResultFromTry(); + } + + /** + * Create a dialog with scripted content. To populate the dialog we need to provide a callback method that creates UI elements for the dialog area. After + * the setup the dialog can be shown by calling dialog.open(). To retrieve input data after the dialog got closed call dialog.getData(uiComponent). + * + * @param layoutCode + * script code to be called when dialog window is created + * @param title + * dialog title text displayed in header area + * @param message + * dialog info message displayed in header area + * @return dialog instance + * @throws Throwable + * when dialog cannot be created. + */ + @WrapToScript + public ScriptableDialog createDialog(Object layoutCode, @ScriptParameter(defaultValue = "Dialog") String title, + @ScriptParameter(defaultValue = ScriptParameter.NULL) String message) throws Throwable { + return runInUIThread(new RunnableWithResult<ScriptableDialog>() { + @Override + public void runWithTry() throws Throwable { + final ScriptableDialog dialog = new ScriptableDialog(Display.getDefault().getActiveShell(), new DialogRunnable() { + + @Override + public void run() { + UIBuilderModule.this.setComposite(getComposite()); + fScriptableDialog = getDialog(); + getScriptEngine().inject(layoutCode); + fScriptableDialog = null; + } + }); + + dialog.setTitleText(title); + dialog.setMessageText(message); + + setResult(dialog); + } + }); + } + + /** + * Sets the active composite for further commands. The composite layout will be set to {@link GridLayout} if not already done. + * + * @param composite + * composite to be used + */ + @WrapToScript + public void setComposite(Composite composite) { + if ((composite.getLayout() == null) || !(composite.getLayout() instanceof GridLayout)) { + final GridLayout gridLayout = new GridLayout(); + gridLayout.numColumns = 1; + + composite.setLayout(gridLayout); + } + + fRenderer = new CompositeRenderer(composite); + fViewModel = new ViewModel(fRenderer); + } + + /** + * Get the active composite. + * + * @return active composite or <code>null</code> + */ + @WrapToScript + public Composite getComposite() { + return fRenderer.getParent(); + } + + /** + * Set the minimum column count. If the current column count is already equal or higher then this method does nothing. + * + * @param columns + * minimum amount of columns + */ + @WrapToScript + public void setColumnCount(int columns) { + Display.getDefault().syncExec(() -> { + fViewModel.setColumnCount(columns); + fRenderer.update(); + }); + } + + /** + * Create a label. + * + * @param label + * label text + * @param layout + * layout data (see module documentation) + * @return label instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public Label createLabel(String label, @ScriptParameter(defaultValue = "<") String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<Label>() { + @Override + public void runWithTry() throws Throwable { + final Label swtLabel = new Label(fRenderer.getParent(), SWT.NONE); + swtLabel.setText(label); + + fViewModel.insertElement(swtLabel, new Location(layout)); + fRenderer.update(); + + setResult(swtLabel); + } + }); + } + + /** + * Create a separator. + * + * @param horizontal + * <code>true</code> for horizontal, <code>false</code> for vertical + * @param layout + * layout data (see module documentation) + * @return separator instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public Label createSeparator(@ScriptParameter(defaultValue = "true") boolean horizontal, @ScriptParameter(defaultValue = "o") String layout) + throws Throwable { + return runInUIThread(new RunnableWithResult<Label>() { + @Override + public void runWithTry() throws Throwable { + final Label swtLabel = new Label(fRenderer.getParent(), SWT.SEPARATOR | (horizontal ? SWT.HORIZONTAL : SWT.VERTICAL)); + + fViewModel.insertElement(swtLabel, new Location(layout)); + fRenderer.update(); + + setResult(swtLabel); + } + }); + } + + /** + * Create a single line text input. + * + * @param layout + * layout data (see module documentation) + * @return text instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public Text createText(@ScriptParameter(defaultValue = "o!") String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<Text>() { + @Override + public void runWithTry() throws Throwable { + final Text text = new Text(fRenderer.getParent(), SWT.BORDER); + + fViewModel.insertElement(text, new Location(layout)); + fRenderer.update(); + + setResult(text); + } + }); + } + + /** + * Create a multi line text input. + * + * @param layout + * layout data (see module documentation) + * @return text instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public Text createTextBox(@ScriptParameter(defaultValue = "o!") String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<Text>() { + @Override + public void runWithTry() throws Throwable { + final Text text = new Text(fRenderer.getParent(), SWT.BORDER | SWT.MULTI | SWT.WRAP); + + fViewModel.insertElement(text, new Location(layout)); + fRenderer.update(); + + setResult(text); + } + }); + } + + /** + * Create a push button. + * + * @param label + * button text + * @param callback + * callback code when button gets pressed + * @param layout + * layout data (see module documentation) + * @return button instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public Button createButton(String label, Object callback, @ScriptParameter(defaultValue = ScriptParameter.NULL) String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<Button>() { + @Override + public void runWithTry() throws Throwable { + final Button button = new Button(fRenderer.getParent(), SWT.NONE); + button.setText(label); + + button.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + getScriptEngine().inject(callback); + } + }); + + fViewModel.insertElement(button, new Location(layout)); + fRenderer.update(); + + setResult(button); + } + }); + } + + /** + * Create a checkbox. + * + * @param label + * checkbox text + * @param callback + * callback code when the checkbox gets ticked/unticked + * @param layout + * layout data (see module documentation) + * @return checkbox instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public Button createCheckBox(String label, @ScriptParameter(defaultValue = ScriptParameter.NULL) Object callback, + @ScriptParameter(defaultValue = ScriptParameter.NULL) String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<Button>() { + @Override + public void runWithTry() throws Throwable { + final Button button = new Button(fRenderer.getParent(), SWT.CHECK); + button.setText(label); + + if (callback != null) { + button.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + getScriptEngine().inject(callback); + } + }); + } + + fViewModel.insertElement(button, new Location(layout)); + fRenderer.update(); + + setResult(button); + } + }); + } + + /** + * Create a combo box. The first element will automatically be selected. + * + * @param elements + * combo elements to display. + * @param callback + * callback code when selection changes + * @param layout + * layout data (see module documentation) + * @return combo viewer instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public ComboViewer createCombo(String[] elements, @ScriptParameter(defaultValue = ScriptParameter.NULL) Object callback, + @ScriptParameter(defaultValue = ScriptParameter.NULL) String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<ComboViewer>() { + @Override + public void runWithTry() throws Throwable { + final ComboViewer comboViewer = new ComboViewer(fRenderer.getParent()); + comboViewer.setLabelProvider(new LabelProvider()); + comboViewer.setContentProvider(ArrayContentProvider.getInstance()); + comboViewer.setInput(elements); + + comboViewer.setSelection(new StructuredSelection(elements[0])); + + if (callback != null) { + comboViewer.addSelectionChangedListener(event -> getScriptEngine().inject(callback)); + } + + fViewModel.insertElement(comboViewer.getControl(), new Location(layout)); + fRenderer.update(); + + if (fScriptableDialog != null) + fScriptableDialog.registerViewer(comboViewer.getControl(), comboViewer); + + setResult(comboViewer); + } + }); + } + + /** + * Create a list viewer. + * + * @param elements + * list elements to display + * @param callback + * callback when selection changes + * @param layout + * layout data (see module documentation) + * @return list viewer instance + * @throws Throwable + * on any SWT error + */ + @WrapToScript + public ListViewer createList(String[] elements, @ScriptParameter(defaultValue = ScriptParameter.NULL) Object callback, + @ScriptParameter(defaultValue = ScriptParameter.NULL) String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<ListViewer>() { + @Override + public void runWithTry() throws Throwable { + final ListViewer listViewer = new ListViewer(fRenderer.getParent()); + listViewer.setLabelProvider(new LabelProvider()); + listViewer.setContentProvider(ArrayContentProvider.getInstance()); + listViewer.setInput(elements); + + if (callback != null) { + listViewer.addSelectionChangedListener(event -> getScriptEngine().inject(callback)); + } + + fViewModel.insertElement(listViewer.getControl(), new Location(layout)); + fRenderer.update(); + + if (fScriptableDialog != null) + fScriptableDialog.registerViewer(listViewer.getControl(), listViewer); + + setResult(listViewer); + } + }); + } + + /** + * Add a generic control. + * + * @param control + * control to add + * @param layout + * layout data (see module documentation) + * @return control instance + * @throws Throwable + * when control cannot be added + */ + @WrapToScript + public Control addControl(Control control, @ScriptParameter(defaultValue = ScriptParameter.NULL) String layout) throws Throwable { + return runInUIThread(new RunnableWithResult<Control>() { + @Override + public void runWithTry() throws Throwable { + fViewModel.insertElement(control, new Location(layout)); + fRenderer.update(); + + setResult(control); + } + }); + } + + /** + * Display a grid aiding in layouting. When the grid is displayed, empty cells do show a label with their location coordinates. + * + * @param showGrid + * <code>true</code> to display. + */ + @WrapToScript + public void showGrid(@ScriptParameter(defaultValue = "true") boolean showGrid) { + Display.getDefault().syncExec(() -> fRenderer.setShowGrid(showGrid)); + } + + private <T> T runInUIThread(RunnableWithResult<T> runnable) throws Throwable { + Display.getDefault().syncExec(runnable); + + return runnable.getResultFromTry(); + } +}
diff --git a/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/ViewModel.java b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/ViewModel.java new file mode 100644 index 0000000..453b5b2 --- /dev/null +++ b/plugins/org.eclipse.ease.modules.platform/src/org/eclipse/ease/modules/platform/uibuilder/ViewModel.java
@@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Control; + +public class ViewModel { + + private final List<Object> fElements = new ArrayList<>(); + private int fRows = 0; + private int fColumns = 0; + + private final IRenderer fRenderer; + + public ViewModel(IRenderer renderer) { + fRenderer = renderer; + fRenderer.setViewModel(this); + } + + public List<Object> getElements() { + return fElements; + } + + public void insertElement(Object element, Location location) { + + if ((location.getPosition().x == Location.DYNAMIC_POSITION) || (location.getPosition().y == Location.DYNAMIC_POSITION)) { + // find position to insert + + // default + location.getPosition().x = 1; + location.getPosition().y = 1; + + for (int index = fElements.size() - 1; index >= 0; index--) { + if (!(fElements.get(index) instanceof IPlaceHolder) && !(fElements.get(index) instanceof Reference)) { + final Point target = indexToPoint(index + 1); + location.getPosition().x = target.x; + location.getPosition().y = target.y; + break; + } + } + } + + // now we got a fixed position to insert + ensureColumnCount((location.getPosition().x + location.getLayoutData().horizontalSpan) - 1); + ensureRowCount((location.getPosition().y + location.getLayoutData().verticalSpan) - 1); + + splitSpanElements(new Rectangle(location.getPosition().x, location.getPosition().y, location.getLayoutData().horizontalSpan, + location.getLayoutData().verticalSpan)); + + replaceElementAt(location.getPosition(), element); + if ((location.getLayoutData().horizontalSpan > 1) || (location.getLayoutData().verticalSpan > 1)) { + final Reference reference = new Reference(element); + for (int width = 0; width < location.getLayoutData().horizontalSpan; width++) { + for (int height = 0; height < location.getLayoutData().verticalSpan; height++) { + if ((width > 0) || (height > 0)) + replaceElementAt(new Point(location.getPosition().x + width, location.getPosition().y + height), reference); + } + } + } + + if (element instanceof Control) + ((Control) element).setLayoutData(location.getLayoutData()); + } + + public int getColumnCount() { + return fColumns; + } + + public int getRowCount() { + return fRows; + } + + private void replaceElementAt(Point position, Object element) { + final int index = pointToIndex(position); + final Object oldElement = fElements.remove(index); + if (oldElement instanceof Control) + ((Control) oldElement).dispose(); + + fElements.add(index, element); + } + + private void splitSpanElements(Rectangle rectangle) { + for (int column = rectangle.x; column < (rectangle.x + rectangle.width); column++) { + for (int row = rectangle.y; row < (rectangle.y + rectangle.height); row++) { + final Object element = fElements.get(pointToIndex(new Point(column, row))); + if (element instanceof Reference) + splitElement(((Reference) element).getDelegate()); + } + } + } + + private void splitElement(Object elementToSplit) { + for (int index = 0; index < fElements.size(); index++) { + if (fElements.get(index).equals(elementToSplit)) { + + final Point position = indexToPoint(index); + replaceElementAt(position, fRenderer.createPlaceHolder(position)); + + } else if ((fElements.get(index) instanceof Reference) && (((Reference) fElements.get(index)).getDelegate().equals(elementToSplit))) { + final Point position = indexToPoint(index); + replaceElementAt(position, fRenderer.createPlaceHolder(position)); + } + } + } + + public Point indexToPoint(int index) { + return new Point((index % fColumns) + 1, (index / fColumns) + 1); + } + + private int pointToIndex(Point point) { + return (((point.y - 1) * fColumns) + point.x) - 1; + } + + private void ensureColumnCount(int columns) { + while (fColumns < columns) + addColumn(); + } + + private void addColumn() { + if (fElements.isEmpty()) { + final IPlaceHolder placeHolder = fRenderer.createPlaceHolder(new Point(1, 1)); + fElements.add(placeHolder); + fRows = 1; + fColumns = 1; + + } else { + for (int row = fRows - 1; row >= 0; row--) { + final Point position = new Point(fColumns + 1, row + 1); + final IPlaceHolder placeHolder = fRenderer.createPlaceHolder(position); + fElements.add(pointToIndex(position), placeHolder); + } + fColumns++; + } + } + + private void ensureRowCount(int rows) { + while (fRows < rows) + addRow(); + } + + private void addRow() { + if (fElements.isEmpty()) { + final IPlaceHolder placeHolder = fRenderer.createPlaceHolder(new Point(1, 1)); + fElements.add(placeHolder); + fRows = 1; + fColumns = 1; + + } else { + fRows++; + for (int column = 1; column <= fColumns; column++) { + final IPlaceHolder placeHolder = fRenderer.createPlaceHolder(new Point(column, fRows)); + fElements.add(placeHolder); + } + } + } + + public void setColumnCount(int columns) { + ensureColumnCount(columns); + } +}
diff --git a/tests/org.eclipse.ease.modules.platform.test/src/org/eclipse/ease/modules/platform/uibuilder/LocationTest.java b/tests/org.eclipse.ease.modules.platform.test/src/org/eclipse/ease/modules/platform/uibuilder/LocationTest.java new file mode 100644 index 0000000..072313f --- /dev/null +++ b/tests/org.eclipse.ease.modules.platform.test/src/org/eclipse/ease/modules/platform/uibuilder/LocationTest.java
@@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.swt.SWT; +import org.junit.Test; + +public class LocationTest { + + @Test + public void nullLocation() { + final Location location = new Location(null); + assertEquals(Location.DYNAMIC_POSITION, location.getPosition().x); + assertEquals(Location.DYNAMIC_POSITION, location.getPosition().y); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(1, location.getLayoutData().horizontalSpan); + assertFalse(location.getLayoutData().grabExcessHorizontalSpace); + + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertEquals(1, location.getLayoutData().verticalSpan); + assertFalse(location.getLayoutData().grabExcessVerticalSpace); + } + + @Test + public void emptyLocation() { + final Location location = new Location(""); + assertEquals(Location.DYNAMIC_POSITION, location.getPosition().x); + assertEquals(Location.DYNAMIC_POSITION, location.getPosition().y); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(1, location.getLayoutData().horizontalSpan); + assertFalse(location.getLayoutData().grabExcessHorizontalSpace); + + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertEquals(1, location.getLayoutData().verticalSpan); + assertFalse(location.getLayoutData().grabExcessVerticalSpace); + } + + @Test + public void bounds1x1() { + final Location location = new Location("1/1"); + assertEquals(1, location.getPosition().x); + assertEquals(1, location.getPosition().y); + + assertEquals(1, location.getLayoutData().horizontalSpan); + assertEquals(1, location.getLayoutData().verticalSpan); + } + + @Test + public void bounds2x1() { + final Location location = new Location("2/1"); + assertEquals(2, location.getPosition().x); + assertEquals(1, location.getPosition().y); + + assertEquals(1, location.getLayoutData().horizontalSpan); + assertEquals(1, location.getLayoutData().verticalSpan); + } + + @Test + public void bounds1x2() { + final Location location = new Location("1/2"); + assertEquals(1, location.getPosition().x); + assertEquals(2, location.getPosition().y); + + assertEquals(1, location.getLayoutData().horizontalSpan); + assertEquals(1, location.getLayoutData().verticalSpan); + } + + @Test + public void boundsSpan() { + final Location location = new Location("3-12/4-7"); + assertEquals(3, location.getPosition().x); + assertEquals(4, location.getPosition().y); + + assertEquals(10, location.getLayoutData().horizontalSpan); + assertEquals(4, location.getLayoutData().verticalSpan); + } + + @Test + public void horizontalLayoutLeft() { + final Location location = new Location("<"); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessHorizontalSpace); + } + + @Test + public void horizontalLayoutCenter() { + final Location location = new Location("x"); + + assertEquals(SWT.CENTER, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessHorizontalSpace); + } + + @Test + public void horizontalLayoutRight() { + final Location location = new Location(">"); + + assertEquals(SWT.RIGHT, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessHorizontalSpace); + } + + @Test + public void horizontalLayoutFill() { + final Location location = new Location("o"); + + assertEquals(SWT.FILL, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessHorizontalSpace); + } + + @Test + public void horizontalLayoutGrab() { + final Location location = new Location("o!"); + + assertEquals(SWT.FILL, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertTrue(location.getLayoutData().grabExcessHorizontalSpace); + } + + @Test + public void verticalLayoutTop() { + final Location location = new Location("^"); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.TOP, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessVerticalSpace); + } + + @Test + public void verticalLayoutMiddle() { + final Location location = new Location("< x"); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.CENTER, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessVerticalSpace); + } + + @Test + public void verticalLayoutBottom() { + final Location location = new Location("v"); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.BOTTOM, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessVerticalSpace); + } + + @Test + public void verticalLayoutFill() { + final Location location = new Location("< o"); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.FILL, location.getLayoutData().verticalAlignment); + assertFalse(location.getLayoutData().grabExcessVerticalSpace); + } + + @Test + public void verticalLayoutGrab() { + final Location location = new Location("< o!"); + + assertEquals(SWT.LEFT, location.getLayoutData().horizontalAlignment); + assertEquals(SWT.FILL, location.getLayoutData().verticalAlignment); + assertTrue(location.getLayoutData().grabExcessVerticalSpace); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidColumn() { + new Location("0"); + } +}
diff --git a/tests/org.eclipse.ease.modules.platform.test/src/org/eclipse/ease/modules/platform/uibuilder/ViewModelTest.java b/tests/org.eclipse.ease.modules.platform.test/src/org/eclipse/ease/modules/platform/uibuilder/ViewModelTest.java new file mode 100644 index 0000000..3a2d38c --- /dev/null +++ b/tests/org.eclipse.ease.modules.platform.test/src/org/eclipse/ease/modules/platform/uibuilder/ViewModelTest.java
@@ -0,0 +1,252 @@ +/******************************************************************************* + * Copyright (c) 2019 Christian Pontesegger and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian Pontesegger - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ease.modules.platform.uibuilder; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.eclipse.swt.graphics.Point; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ViewModelTest { + + private static IRenderer fRenderer; + + private static class TextPlaceHolder implements IPlaceHolder { + private final Point fPosition; + + public TextPlaceHolder(Point position) { + fPosition = position; + } + + public TextPlaceHolder(int column, int row) { + this(new Point(column, row)); + } + + @Override + public String toString() { + return fPosition.x + "/" + fPosition.y; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((fPosition == null) ? 0 : fPosition.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final TextPlaceHolder other = (TextPlaceHolder) obj; + if (fPosition == null) { + if (other.fPosition != null) + return false; + } else if (!fPosition.equals(other.fPosition)) + return false; + return true; + } + } + + @BeforeClass + public static void classSetup() { + fRenderer = new IRenderer() { + + @Override + public void setViewModel(ViewModel viewModel) { + } + + @Override + public IPlaceHolder createPlaceHolder(Point position) { + return new TextPlaceHolder(position); + } + }; + } + + @Test + public void emptyModel() { + final ViewModel viewModel = new ViewModel(fRenderer); + assertTrue(viewModel.getElements().isEmpty()); + assertEquals(0, viewModel.getColumnCount()); + assertEquals(0, viewModel.getRowCount()); + } + + @Test + public void singleElementOn1x1() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.insertElement("Element1x1", new Location("1/1")); + assertEquals(1, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Element1x1" }, viewModel.getElements().toArray()); + + assertEquals(1, viewModel.getColumnCount()); + assertEquals(1, viewModel.getRowCount()); + } + + @Test + public void singleColumn1x3() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.insertElement("Element1x1", new Location("")); + viewModel.insertElement("Element1x2", new Location("")); + viewModel.insertElement("Element1x3", new Location("")); + assertEquals(3, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Element1x1", "Element1x2", "Element1x3" }, viewModel.getElements().toArray()); + + assertEquals(1, viewModel.getColumnCount()); + assertEquals(3, viewModel.getRowCount()); + } + + @Test + public void singleRow3x1() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.setColumnCount(3); + viewModel.insertElement("Element1x1", new Location("")); + viewModel.insertElement("Element2x1", new Location("")); + viewModel.insertElement("Element3x1", new Location("")); + assertEquals(3, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Element1x1", "Element2x1", "Element3x1" }, viewModel.getElements().toArray()); + + assertEquals(3, viewModel.getColumnCount()); + assertEquals(1, viewModel.getRowCount()); + } + + @Test + public void insertionGaps() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.setColumnCount(2); + viewModel.insertElement("Element2x3", new Location("2/3")); + + assertEquals(6, viewModel.getElements().size()); + assertArrayEquals(new Object[] { new TextPlaceHolder(1, 1), new TextPlaceHolder(2, 1), new TextPlaceHolder(1, 2), new TextPlaceHolder(2, 2), + new TextPlaceHolder(1, 3), "Element2x3" }, viewModel.getElements().toArray()); + + assertEquals(2, viewModel.getColumnCount()); + assertEquals(3, viewModel.getRowCount()); + } + + @Test + public void insertRandomOrder() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.setColumnCount(3); + viewModel.insertElement("Element2x2", new Location("2/2")); + viewModel.insertElement("Element1x1", new Location("1/1")); + viewModel.insertElement("Element3x1", new Location("3/1")); + viewModel.insertElement("Element1x2", new Location("1/2")); + viewModel.insertElement("Element3x2", new Location("3/2")); + viewModel.insertElement("Element2x1", new Location("2/1")); + + assertEquals(6, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Element1x1", "Element2x1", "Element3x1", "Element1x2", "Element2x2", "Element3x2" }, + viewModel.getElements().toArray()); + + assertEquals(3, viewModel.getColumnCount()); + assertEquals(2, viewModel.getRowCount()); + } + + @Test + public void replaceElement() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.insertElement("Element1x1", new Location("1/1")); + viewModel.insertElement("Replacement", new Location("1/1")); + + assertEquals(1, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Replacement" }, viewModel.getElements().toArray()); + + assertEquals(1, viewModel.getColumnCount()); + assertEquals(1, viewModel.getRowCount()); + } + + @Test + public void extendColumns() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.setColumnCount(2); + viewModel.insertElement("Element1x1", new Location("1/1")); + viewModel.insertElement("Element2x1", new Location("2/1")); + viewModel.insertElement("Element1x2", new Location("1/2")); + viewModel.insertElement("Element2x2", new Location("2/2")); + + viewModel.setColumnCount(3); + + assertEquals(6, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Element1x1", "Element2x1", new TextPlaceHolder(3, 1), "Element1x2", "Element2x2", new TextPlaceHolder(3, 2) }, + viewModel.getElements().toArray()); + + assertEquals(3, viewModel.getColumnCount()); + assertEquals(2, viewModel.getRowCount()); + } + + @Test + public void horizontalSpanElement() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.setColumnCount(3); + viewModel.insertElement("Element1x1", new Location("1/1")); + viewModel.insertElement("Element2-3x1", new Location("2-3/1")); + viewModel.insertElement("Element1x2", new Location("1/2")); + viewModel.insertElement("Element2x2", new Location("2/2")); + viewModel.insertElement("Element3x2", new Location("3/2")); + + assertEquals(6, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Element1x1", "Element2-3x1", new Reference("Element2-3x1"), "Element1x2", "Element2x2", "Element3x2" }, + viewModel.getElements().toArray()); + + assertEquals(3, viewModel.getColumnCount()); + assertEquals(2, viewModel.getRowCount()); + } + + @Test + public void verticalSpanElement() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.setColumnCount(2); + viewModel.insertElement("Element1x1", new Location("1/1")); + viewModel.insertElement("Element2x1", new Location("2/1")); + viewModel.insertElement("Element1x2-3", new Location("1/2-3")); + viewModel.insertElement("Element2x2", new Location("2/2")); + viewModel.insertElement("Element2x3", new Location("2/3")); + + assertEquals(6, viewModel.getElements().size()); + assertArrayEquals(new Object[] { "Element1x1", "Element2x1", "Element1x2-3", "Element2x2", new Reference("Element1x2-3"), "Element2x3" }, + viewModel.getElements().toArray()); + + assertEquals(2, viewModel.getColumnCount()); + assertEquals(3, viewModel.getRowCount()); + } + + @Test + public void replaceSpanElement() { + final ViewModel viewModel = new ViewModel(fRenderer); + viewModel.setColumnCount(3); + viewModel.insertElement("Element1x1", new Location("1/1")); + viewModel.insertElement("Element2x1", new Location("2/1")); + viewModel.insertElement("Element3x1", new Location("3/1")); + viewModel.insertElement("Element1x2", new Location("1/2")); + viewModel.insertElement("Element2-3x2-3", new Location("2-3/2-3")); + viewModel.insertElement("Element1x3", new Location("1/3")); + + viewModel.insertElement("Replacement", new Location("3-4/2")); + + assertEquals(12, viewModel.getElements().size()); + assertArrayEquals( + new Object[] { "Element1x1", "Element2x1", "Element3x1", new TextPlaceHolder(4, 1), "Element1x2", new TextPlaceHolder(2, 2), "Replacement", + new Reference("Replacement"), "Element1x3", new TextPlaceHolder(2, 3), new TextPlaceHolder(3, 3), new TextPlaceHolder(4, 3) }, + viewModel.getElements().toArray()); + + assertEquals(4, viewModel.getColumnCount()); + assertEquals(3, viewModel.getRowCount()); + } +}