Bug 551589: SectionFactory

Change-Id: If990664a5651052dc660940c42a8cb0b429a9a38
Signed-off-by: Marcus Hoepfner <marcus.hoepfner@sap.com>
diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/widgets/AbstractCompositeFactory.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/widgets/AbstractCompositeFactory.java
index b83a7bf..88375a0 100644
--- a/bundles/org.eclipse.jface/src/org/eclipse/jface/widgets/AbstractCompositeFactory.java
+++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/widgets/AbstractCompositeFactory.java
@@ -36,7 +36,7 @@
 	 * @param factoryClass
 	 * @param controlCreator
 	 */
-	AbstractCompositeFactory(Class<F> factoryClass, WidgetSupplier<C, Composite> controlCreator) {
+	protected AbstractCompositeFactory(Class<F> factoryClass, WidgetSupplier<C, Composite> controlCreator) {
 		super(factoryClass, controlCreator);
 	}
 
diff --git a/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF
index cffba61..be580af 100644
--- a/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %name
 Bundle-SymbolicName: org.eclipse.ui.forms;singleton:=true
-Bundle-Version: 3.9.200.qualifier
+Bundle-Version: 3.10.0.qualifier
 Bundle-Vendor: %provider-name
 Bundle-Localization: plugin
 Export-Package: org.eclipse.ui.forms,
diff --git a/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/SectionFactory.java b/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/SectionFactory.java
new file mode 100644
index 0000000..1c916e6
--- /dev/null
+++ b/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/SectionFactory.java
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * Copyright (c) 2020 SAP SE and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Marcus Hoepfner (SAP SE) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.forms.widgets;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.eclipse.jface.widgets.AbstractCompositeFactory;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.forms.events.ExpansionEvent;
+import org.eclipse.ui.forms.events.IExpansionListener;
+
+/**
+ * This class provides a convenient shorthand for creating and initializing
+ * {@link Section}. This offers several benefits over creating Section normal
+ * way:
+ *
+ * <ul>
+ * <li>The same factory can be used many times to create several Section
+ * instances</li>
+ * <li>The setters on SectionFactory all return "this", allowing them to be
+ * chained</li>
+ * <li>SectionFactory accepts a Lambda for {@link ExpansionEvent} (see
+ * {@link #onExpanded(Consumer)}) and {@link #onExpanding(Consumer)}</li>
+ * </ul>
+ *
+ * Example usage:
+ *
+ * <pre>
+ * Section section = SectionFactory.newSection(Section.TWISTIE | Section.DESCRIPTION) //
+ * 		.title("My Section") //
+ * 		.description("My section created with a factory") //
+ * 		.onExpand(event -&gt; sectionExpanded(event)) //
+ * 		.create(parent);
+ * </pre>
+ * <p>
+ * The above example creates a section with a title, a description, registers an
+ * IExpansionListener and finally creates the section in "parent".
+ * </p>
+ *
+ * <pre>
+ * SectionFactory sectionFactory = SectionFactory.newSection(Section.TWISTIE);
+ * sectionFactory.title("Section 1").create(parent);
+ * sectionFactory.title("Section 2").create(parent);
+ * sectionFactory.title("Section 3").create(parent);
+ * </pre>
+ * <p>
+ * The above example creates three section using the same instance of
+ * SectionFactory.
+ * </p>
+ *
+ * @since 3.10
+ *
+ */
+public final class SectionFactory extends AbstractCompositeFactory<SectionFactory, Section> {
+
+	private SectionFactory(int style) {
+		super(SectionFactory.class, (Composite parent) -> new Section(parent, style));
+	}
+
+	/**
+	 * Creates a new SectionFactory with the given style. Refer to
+	 * {@link Section#Section(Composite, int)} for possible styles.
+	 *
+	 * @param style
+	 * @return a new SectionFactory instance
+	 */
+	public static SectionFactory newSection(int style) {
+		return new SectionFactory(style);
+	}
+
+	/**
+	 * Sets the title of the section. The title will act as a hyperlink and
+	 * activating it will toggle the client between expanded and collapsed state.
+	 *
+	 * @param title the new title string
+	 *
+	 * @see Section#setText(String)
+	 */
+	public SectionFactory title(String title) {
+		addProperty(section -> section.setText(title));
+		return this;
+	}
+
+	/**
+	 * Sets the description text. Has no effect if DESCRIPTION style was not used in
+	 * {@link #create(Composite)}
+	 *
+	 * @param description new description text; not <code>null</code>
+	 *
+	 * @see Section#setDescription(String)
+	 */
+	public SectionFactory description(String description) {
+		addProperty(section -> section.setDescription(description));
+		return this;
+	}
+
+	/**
+	 * Sets a function which must provide a description control for the given
+	 * Section. The control must not be <samp>null</samp> and must be a direct child
+	 * of the section.
+	 *
+	 * <p>
+	 * This method and <code>DESCRIPTION</code> style are mutually exclusive. Use
+	 * the method only if you want to create the description control yourself.
+	 * </p>
+	 *
+	 * @param controlFunction the function to create the description control
+	 *
+	 * @see Section#setDescriptionControl(Control)
+	 */
+	public SectionFactory description(Function<Section, Control> controlFunction) {
+		addProperty(section -> section.setDescriptionControl(controlFunction.apply(section)));
+		return this;
+	}
+
+	/**
+	 * Creates an {@link IExpansionListener} and registers it for the
+	 * expansionStateChanged event. If the section <i>was</i> expanded by the user
+	 * the given consumer is invoked. The {@link ExpansionEvent} is passed to the
+	 * consumer.
+	 *
+	 * @param consumer the consumer whose accept method is called
+	 * @return this
+	 *
+	 * @see Section#addExpansionListener(IExpansionListener)
+	 * @see IExpansionListener#expansionStateChanged(ExpansionEvent)
+	 */
+	public SectionFactory onExpanded(Consumer<ExpansionEvent> consumer) {
+		addProperty(
+				section -> section.addExpansionListener(IExpansionListener.expansionStateChangingAdapter(consumer)));
+		return this;
+	}
+
+	/**
+	 * Creates an {@link IExpansionListener} and registers it for the
+	 * expansionStateChanging event. If the section <i>is</i> expanded by the user
+	 * the given consumer is invoked. The {@link ExpansionEvent} is passed to the
+	 * consumer.
+	 *
+	 * @param consumer the consumer whose accept method is called
+	 * @return this
+	 *
+	 * @see Section#addExpansionListener(IExpansionListener)
+	 * @see IExpansionListener#expansionStateChanging(ExpansionEvent)
+	 */
+	public SectionFactory onExpanding(Consumer<ExpansionEvent> consumer) {
+		addProperty(
+				section -> section.addExpansionListener(IExpansionListener.expansionStateChangingAdapter(consumer)));
+		return this;
+	}
+}
\ No newline at end of file
diff --git a/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF
index 746c9ac..4675ab6 100755
--- a/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: Forms Test
 Bundle-SymbolicName: org.eclipse.ui.tests.forms;singleton:=true
-Bundle-Version: 3.8.0.qualifier
+Bundle-Version: 3.8.100.qualifier
 Require-Bundle: org.eclipse.ui,
  org.eclipse.core.runtime,
  org.eclipse.test.performance,
diff --git a/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/widgets/SectionFactoryTest.java b/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/widgets/SectionFactoryTest.java
new file mode 100644
index 0000000..bc3c65d
--- /dev/null
+++ b/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/widgets/SectionFactoryTest.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2020 SAP SE and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which accompanies this distribution,
+ * and is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors: Marcus Hoepfner (SAP SE) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.tests.forms.widgets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.forms.events.ExpansionEvent;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.SectionFactory;
+import org.eclipse.ui.forms.widgets.Twistie;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SectionFactoryTest {
+	protected static Shell shell;
+
+	@Before
+	public void setup() {
+		shell = new Shell();
+	}
+
+	@After
+	public void tearDown() {
+		shell.dispose();
+	}
+
+	@Test
+	public void createsSection() {
+		Section section = SectionFactory.newSection(SWT.NONE).create(shell);
+		assertEquals(shell, section.getParent());
+	}
+
+	@Test
+	public void createsSectionWithText() {
+		Section section = SectionFactory.newSection(SWT.NONE).title("test").create(shell);
+		assertEquals("test", section.getText());
+	}
+
+	@Test
+	public void createsSectionWithDescription() {
+		Section section = SectionFactory.newSection(Section.DESCRIPTION).description("test").create(shell);
+		assertEquals("test", section.getDescription());
+	}
+
+	@Test
+	public void createsSectionWithDescriptionControl() {
+		Section section = SectionFactory.newSection(SWT.NONE).description(parent -> new Label(parent, SWT.NONE))
+				.create(shell);
+		assertTrue(section.getDescriptionControl() instanceof Label);
+	}
+
+	@Test
+	public void addsSectionExpandListeners() {
+		final ExpansionEvent[] raisedEvents = new ExpansionEvent[1];
+		Section section = SectionFactory.newSection(Section.TWISTIE).onExpanded(e -> raisedEvents[0] = e)
+				.create(shell);
+		Control twistie = section.getChildren()[0];
+		assertTrue("Expected a twistie", twistie instanceof Twistie);
+		click(twistie);
+
+		assertNotNull(raisedEvents[0]);
+	}
+
+	@Test
+	public void addsSectionExpandingListener() {
+		final ExpansionEvent[] raisedEvents = new ExpansionEvent[1];
+		Section section = SectionFactory.newSection(Section.TWISTIE).onExpanding(e -> raisedEvents[0] = e)
+				.create(shell);
+		Control twistie = section.getChildren()[0];
+		assertTrue("Expected a twistie", twistie instanceof Twistie);
+		click(twistie);
+
+		assertNotNull(raisedEvents[0]);
+	}
+
+	private void click(Control twistie) {
+		Event event = new Event();
+		event.keyCode = SWT.ARROW_RIGHT;
+		twistie.notifyListeners(SWT.KeyDown, event);
+	}
+}
\ No newline at end of file
diff --git a/tests/org.eclipse.ui.tests.forms/pom.xml b/tests/org.eclipse.ui.tests.forms/pom.xml
index d1fdaf1..932aa01 100644
--- a/tests/org.eclipse.ui.tests.forms/pom.xml
+++ b/tests/org.eclipse.ui.tests.forms/pom.xml
@@ -20,6 +20,6 @@
   </parent>
   <groupId>org.eclipse.ui</groupId>
   <artifactId>org.eclipse.ui.tests.forms</artifactId>
-  <version>3.8.0-SNAPSHOT</version>
+  <version>3.8.100-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
 </project>