From 755c43ceb21005744ef154ac53cba414b5e23a1c Mon Sep 17 00:00:00 2001
From: Simon Barner <barner@fortiss.org>
Date: Thu, 15 Nov 2018 14:13:17 +0100
Subject: [PATCH] Improve EReferenceListPropertySectionBase and make list view
 reusable

* EReferenceListPropertySectionBase
 * Introduce ElementListViewerComposite: Reusable Composite that
   provides a ListViewer & add/remove buttons
 * Usability improvements: enabled state of buttons, auto-select
   next element after "remove" operation
* Introduce IListPropertySection
  * To be implemented by classes that want to embed
    ElementListViewerComposite
  * Provides interface to data model

Issue-Ref: 3470
Issue-Url: https://af3-developer.fortiss.org/issues/3470

Signed-off-by: Simon Barner <barner@fortiss.org>
---
 .../tooling/kernel/ui/extension/base/.ratings |   3 +-
 .../EReferenceListPropertySectionBase.java    | 221 +++++++++++++-----
 .../extension/base/IListPropertySection.java  |  47 ++++
 3 files changed, 210 insertions(+), 61 deletions(-)
 create mode 100644 org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/IListPropertySection.java

diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings
index 290af8e76..520e5f612 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings
@@ -2,9 +2,10 @@ ConstraintUIBases.java 3676a600e0866091db9798763c6eee97eec5b55b GREEN
 ContextMenuSubMenuContributorBase.java 6275d96fe8690d9d4744bcbaef3c7d14ba8e30ff GREEN
 DialogMessageHandler.java 8714da09a777c8557de0a5c48ff68c340f9fa91d GREEN
 EObjectActionBase.java 4ef9f8be59e64d4838acc9e268d418ba5d94fa1a GREEN
-EReferenceListPropertySectionBase.java b43469e81cf9f959230e9b2353460c49d8d6a7d3 YELLOW
+EReferenceListPropertySectionBase.java bbc5f6851842a9cb0af5e9898d9c6e197a36312a YELLOW
 EReferencePropertySectionBase.java 3347e99b2fc135dd4b3117407179900ef757092c GREEN
 EditorBase.java 9c09fff92945256bb8680992ae7bb2c78f47b150 GREEN
+IListPropertySection.java a093a8a625d291b8adecec5082e32748a2d99f9e YELLOW
 ModelEditorBindingBase.java 4c5ac569c0b6e7678fc8191096b26dfd09fdcb98 GREEN
 ModelElementHandlerBase.java 384727748f125c9d43f19d9c0eba4ba1be5a7a26 GREEN
 MultiEObjectActionBase.java 9e237d8ea640c4194e4877af4a9cfce88698e543 GREEN
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/EReferenceListPropertySectionBase.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/EReferenceListPropertySectionBase.java
index b43469e81..bbc5f6851 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/EReferenceListPropertySectionBase.java
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/EReferenceListPropertySectionBase.java
@@ -23,7 +23,11 @@ import java.util.List;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.EReference;
 import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.ListViewer;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
@@ -34,7 +38,6 @@ import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
 import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;
-import org.fortiss.tooling.kernel.ui.extension.base.EReferencePropertySectionBase;
 import org.fortiss.tooling.kernel.ui.presentation.ModelElementLabelProvider;
 
 /**
@@ -43,7 +46,7 @@ import org.fortiss.tooling.kernel.ui.presentation.ModelElementLabelProvider;
  * @author barner
  */
 public abstract class EReferenceListPropertySectionBase<I extends EObject, R extends EObject>
-		extends EReferencePropertySectionBase<I, R> {
+		extends EReferencePropertySectionBase<I, R> implements IListPropertySection<I, R> {
 
 	/**
 	 * Element that has been selected the combo box provided by the base class
@@ -51,8 +54,144 @@ public abstract class EReferenceListPropertySectionBase<I extends EObject, R ext
 	 */
 	private R selectedElement;
 
-	/** {@link ListViewer} to show edited {@link EReference} list. */
-	private ListViewer elementListViewer;
+	/**
+	 * {@link Composite} providing a {@link ListViewer} and an add and a remove {@link Button} to
+	 * edit {@link EReference} list.
+	 */
+	public static class ElementListViewerComposite<I extends EObject, R extends EObject>
+			extends Composite {
+		/** {@link ListViewer} to show edited {@link EReference} list. */
+		private ListViewer listViewer;
+
+		/** {@link Button} to add an element to the {@link #listViewer}. */
+		private Button addButton;
+
+		/** {@link Button} to remove an element from the {@link #listViewer}. */
+		private Button removeButton;
+
+		/** Underlying {@link IListPropertySection}. */
+		private IListPropertySection<I, R> section;
+
+		/** Constructor. */
+		public ElementListViewerComposite(Composite parent, int style,
+				TabbedPropertySheetWidgetFactory wf, IListPropertySection<I, R> section) {
+			super(parent, style);
+			this.section = section;
+
+			GridLayout layout = new GridLayout(2, false);
+			layout.marginWidth = 0;
+			setLayout(layout);
+
+			listViewer = new ListViewer(this, SWT.BORDER | SWT.V_SCROLL);
+			GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+			gd.grabExcessHorizontalSpace = true;
+			gd.heightHint = 48;
+			listViewer.getList().setLayoutData(gd);
+			listViewer.setContentProvider(new ArrayContentProvider());
+			listViewer.setLabelProvider(new ModelElementLabelProvider());
+
+			listViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+				/** {@inheritDoc} */
+				@Override
+				public void selectionChanged(SelectionChangedEvent event) {
+					// refresh() does not work here. It is based on isRemoveButtonEnabled(), that
+					// queries the selection of the 'listViewer' has not been updated yet. When this
+					// listener is triggered, only the 'event' already contains the new selection.
+					removeButton.setEnabled(getFirstSelectedElement(event.getSelection()) != null);
+				}
+			});
+
+			Composite buttonComposite = wf.createComposite(this, SWT.NONE);
+			buttonComposite.setLayout(new FillLayout(SWT.VERTICAL));
+
+			addButton = wf.createButton(buttonComposite, "Add", SWT.NONE);
+			addButton.addSelectionListener(new SelectionAdapter() {
+				/** {@inheritDoc} */
+				@Override
+				public void widgetSelected(SelectionEvent e) {
+					I input = section.getSectionInput();
+					runAsCommand(input,
+							() -> section.addModelListElement(input, section.getSelectedElement()));
+					section.refresh();
+				}
+			});
+
+			removeButton = wf.createButton(buttonComposite, "Remove", SWT.NONE);
+			removeButton.addSelectionListener(new SelectionAdapter() {
+				/** {@inheritDoc} */
+
+				@Override
+				public void widgetSelected(SelectionEvent e) {
+					R selectedElement = getFirstSelectedElement(listViewer.getSelection());
+					if(selectedElement != null) {
+						I input = section.getSectionInput();
+						runAsCommand(input,
+								() -> section.removeModelListElement(input, selectedElement));
+						section.refresh();
+
+						List<R> elements = section.getModelListElements(input);
+						if(!elements.isEmpty()) {
+							// Select first element (which has been checked to exist)
+							listViewer.setSelection(new StructuredSelection(elements.get(0)));
+						}
+					}
+				}
+			});
+		}
+
+		/** Provides the first selected element of the give {@link ISelection}. */
+		@SuppressWarnings("unchecked")
+		private R getFirstSelectedElement(ISelection selection) {
+			return (R)checkAndPickFirst(selection, EObject.class);
+		}
+
+		/**
+		 * <p>
+		 * Predicate if the {@link #addButton} is currently enabled.
+		 * </p>
+		 * <p>
+		 * <b>NB: </b>Derived classes may override but must return the conjunction of their result
+		 * and this implementation.
+		 * </p>
+		 */
+		protected boolean isAddButtonEnabled() {
+			R selectedElement = section.getSelectedElement();
+			return selectedElement != null && !section
+					.getModelListElements(section.getSectionInput()).contains(selectedElement);
+		}
+
+		/**
+		 * <p>
+		 * Predicate if the {@link #removeButton} is currently enabled.
+		 * </p>
+		 * <p>
+		 * <b>NB: </b>Derived classes may override but must return the conjunction of their result
+		 * and this implementation.
+		 * </p>
+		 */
+		protected boolean isRemoveButtonEnabled() {
+			return getFirstSelectedElement(listViewer.getSelection()) != null;
+		}
+
+		/** Refreshes the input and the enabled state of the controls. */
+		public void refresh() {
+			addButton.setEnabled(false);
+			removeButton.setEnabled(false);
+
+			I input = section.getSectionInput();
+			if(input == null) {
+				return;
+			}
+
+			List<R> elements = section.getModelListElements(input);
+			listViewer.setInput(elements);
+			addButton.setEnabled(isAddButtonEnabled());
+			removeButton.setEnabled(isRemoveButtonEnabled());
+		}
+	}
+
+	/** {@link Composite} providing a {@link ListViewer} and an add and remove Buttons. */
+	private ElementListViewerComposite<I, R> listViewerComposite;
 
 	/** The edited object (updated in {@link #setSectionInput(Object)}). */
 	private I input = null;
@@ -67,60 +206,30 @@ public abstract class EReferenceListPropertySectionBase<I extends EObject, R ext
 	public void createControls(Composite parent, TabbedPropertySheetPage aTabbedPropertySheetPage) {
 		super.createControls(parent, aTabbedPropertySheetPage);
 
-		TabbedPropertySheetWidgetFactory wf = getWidgetFactory();
-		Composite listComposite = wf.createComposite(composite, SWT.NONE);
-		GridLayout layout = new GridLayout(2, false);
-		layout.marginWidth = 0;
-		listComposite.setLayout(layout);
-
-		elementListViewer = new ListViewer(listComposite, SWT.BORDER | SWT.V_SCROLL);
-		GridData gd = new GridData(GridData.FILL_HORIZONTAL);
-		gd.grabExcessHorizontalSpace = true;
-		gd.heightHint = 48;
-		elementListViewer.getList().setLayoutData(gd);
-		elementListViewer.setContentProvider(new ArrayContentProvider());
-		elementListViewer.setLabelProvider(new ModelElementLabelProvider());
-
-		Composite buttonComposite = wf.createComposite(listComposite, SWT.NONE);
-		buttonComposite.setLayout(new FillLayout(SWT.VERTICAL));
-		Button addButton = wf.createButton(buttonComposite, "Add", SWT.NONE);
-		addButton.addSelectionListener(new SelectionAdapter() {
-			/** {@inheritDoc} */
-			@Override
-			public void widgetSelected(SelectionEvent e) {
-				if(selectedElement != null &&
-						!getModelListElements(input).contains(selectedElement)) {
-					runAsCommand(input, () -> addModelListElement(input, selectedElement));
-					refresh();
-				}
-			}
-		});
-
-		Button removeButton = wf.createButton(buttonComposite, "Remove", SWT.NONE);
-		removeButton.addSelectionListener(new SelectionAdapter() {
-			/** {@inheritDoc} */
-			@SuppressWarnings("unchecked")
-			@Override
-			public void widgetSelected(SelectionEvent e) {
-				runAsCommand(input, () -> removeModelListElement(input,
-						(R)checkAndPickFirst(elementListViewer.getSelection(), EObject.class)));
-				refresh();
-			}
-		});
+		listViewerComposite =
+				new ElementListViewerComposite<>(composite, SWT.NONE, getWidgetFactory(), this);
+		createFormEntry(listViewerComposite, "");
+	}
+
+	// Implementation of IListPropertySection
+	/** {@inheritDoc} */
+	@Override
+	public final R getSelectedElement() {
+		return selectedElement;
+	}
 
-		createFormEntry(listComposite, "");
+	/** {@inheritDoc} */
+	@Override
+	public final I getSectionInput() {
+		return input;
 	}
 
+	// Implementation of PropertySectionBase
 	/** {@inheritDoc} */
 	@Override
 	public void refresh() {
 		super.refresh();
-
-		if(input == null) {
-			return;
-		}
-
-		elementListViewer.setInput(getModelListElements(input));
+		listViewerComposite.refresh();
 	}
 
 	/** {@inheritDoc} */
@@ -131,6 +240,7 @@ public abstract class EReferenceListPropertySectionBase<I extends EObject, R ext
 		this.input = (I)input;
 	}
 
+	// Implementation of EReferencePropertySectionBase
 	/** {@inheritDoc} */
 	@Override
 	protected final EObject getModelValue(I input) {
@@ -149,13 +259,4 @@ public abstract class EReferenceListPropertySectionBase<I extends EObject, R ext
 		// for possible "add" operation.
 		selectedElement = newValue;
 	}
-
-	/** Returns the {@link List} of elements in the edited {@link EReference} list. */
-	protected abstract List<R> getModelListElements(I input);
-
-	/** Adds the given {@code element} to the edited {@link EReference} list. */
-	protected abstract void addModelListElement(I input, R element);
-
-	/** Removes the given {@code element} from the edited {@link EReference} list. */
-	protected abstract void removeModelListElement(I input, R element);
 }
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/IListPropertySection.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/IListPropertySection.java
new file mode 100644
index 000000000..a093a8a62
--- /dev/null
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/IListPropertySection.java
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2018 fortiss GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.kernel.ui.extension.base;
+
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+
+/**
+ * Interface for property sections in which a list of elements can be edited.
+ * 
+ * @author barner
+ */
+public interface IListPropertySection<I extends EObject, R extends EObject> {
+
+	/** Returns the {@link List} of elements in the edited {@link EReference} list. */
+	public List<R> getModelListElements(I input);
+
+	/** Adds the given {@code element} to the edited {@link EReference} list. */
+	public void addModelListElement(I input, R element);
+
+	/** Removes the given {@code element} from the edited {@link EReference} list. */
+	public void removeModelListElement(I input, R element);
+
+	/** Returns the currently selected element of the viewer used to edit the element list. */
+	public R getSelectedElement();
+
+	/** Returns the input model that contains the edited list. */
+	public I getSectionInput();
+
+	/** Updates the list viewer and the property section that embeds it. */
+	public void refresh();
+}
-- 
GitLab