Bug 102527 - A project preference page to configure natures

Change-Id: Ic75c1341c32f9fbfb022c27843d57bdb0028590e
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/build/org.eclipse.e4.ui.update/category.xml b/build/org.eclipse.e4.ui.update/category.xml
index b9ab278..195c0c9 100644
--- a/build/org.eclipse.e4.ui.update/category.xml
+++ b/build/org.eclipse.e4.ui.update/category.xml
@@ -41,4 +41,9 @@
    <bundle id="org.eclipse.e4.jdt.scope" version="0.0.1.qualifier">
       <category name="JDT"/>
    </bundle>
+
+   <category-def name="Misc" label="Misc"/>
+   <bundle id="org.eclipse.e4.ui.naturist" version="0.0.1.qualifier">
+      <category name="Misc"/>
+   </bundle>
 </site>
diff --git a/bundles/org.eclipse.e4.ui.naturist/.classpath b/bundles/org.eclipse.e4.ui.naturist/.classpath
new file mode 100644
index 0000000..0b1bcf9
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src/"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/bundles/org.eclipse.e4.ui.naturist/.gitignore b/bundles/org.eclipse.e4.ui.naturist/.gitignore
new file mode 100644
index 0000000..06ba672
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/.gitignore
@@ -0,0 +1,2 @@
+/bin
+/target/
diff --git a/bundles/org.eclipse.e4.ui.naturist/.project b/bundles/org.eclipse.e4.ui.naturist/.project
new file mode 100644
index 0000000..93a6ccf
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclise.e4.ui.naturist</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..c537b63
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.jdt.ui.prefs b/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..58b4e45
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,60 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_type_arguments=false
diff --git a/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.m2e.core.prefs b/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/bundles/org.eclipse.e4.ui.naturist/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.ui.naturist/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..2442fc4
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/META-INF/MANIFEST.MF
@@ -0,0 +1,14 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Project preference page to edit project natures
+Bundle-SymbolicName: org.eclipse.e4.ui.naturist;singleton:=true
+Bundle-Version: 0.0.1.qualifier
+Bundle-Vendor: Eclipse.org
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.core.resources,
+ org.eclipse.ui.ide,
+ org.eclipse.e4.core.contexts
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-ActivationPolicy: lazy
+Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.e4.ui.naturist/build.properties b/bundles/org.eclipse.e4.ui.naturist/build.properties
new file mode 100644
index 0000000..08bb920
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/build.properties
@@ -0,0 +1,6 @@
+source.. = src/
+output.. = target/classes
+bin.includes = META-INF/,\
+               .,\
+               plugin.xml,\
+               plugin.properties
diff --git a/bundles/org.eclipse.e4.ui.naturist/plugin.properties b/bundles/org.eclipse.e4.ui.naturist/plugin.properties
new file mode 100644
index 0000000..3b4d25f
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/plugin.properties
@@ -0,0 +1 @@
+PropertyPages.project.natures = Project Natures
\ No newline at end of file
diff --git a/bundles/org.eclipse.e4.ui.naturist/plugin.xml b/bundles/org.eclipse.e4.ui.naturist/plugin.xml
new file mode 100644
index 0000000..56ac4bb
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/plugin.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+   <extension point="org.eclipse.ui.propertyPages">
+   		<page
+            class="org.eclipse.ui.resources.natures.ProjectNaturesPage"
+            id="org.eclipse.ui.propertypages.project.natures"
+            name="%PropertyPages.project.natures"
+            >
+         <filter
+               name="open"
+               value="true">
+         </filter>
+         <enabledWhen>
+            <adapt type="org.eclipse.core.resources.IProject"/>
+         </enabledWhen>
+      </page>
+   </extension>
+</plugin>
diff --git a/bundles/org.eclipse.e4.ui.naturist/pom.xml b/bundles/org.eclipse.e4.ui.naturist/pom.xml
new file mode 100644
index 0000000..611569e
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/pom.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.eclipse.e4.ui</groupId>
+		<artifactId>e4-ui-aggregator</artifactId>
+		<version>0.17.0-SNAPSHOT</version>
+		<relativePath>../../</relativePath>
+	</parent>
+
+	<artifactId>org.eclipse.e4.ui.naturist</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>eclipse-plugin</packaging>
+</project>
diff --git a/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/Messages.java b/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/Messages.java
new file mode 100644
index 0000000..cf5d1d7
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/Messages.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2015-2016 Red Hat Inc.
+ * 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:
+ *     Mickael Istria (Red Hat Inc) - [102527] initial implementation
+ ******************************************************************************/
+package org.eclipse.ui.resources.natures;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+
+	static {
+		NLS.initializeMessages(Messages.class.getPackage().getName().replace('.', '/') + "/messages", Messages.class);
+	}
+
+	public static String ProjectNaturesPage_label;
+	public static String ProjectNaturesPage_missingNatureText;
+	public static String ProjectNaturesPage_addNature;
+	public static String ProjectNaturesPage_removeNature;
+	public static String ProjectNaturesPage_selectNatureToAddMessage;
+	public static String ProjectNaturesPage_selectNatureToAddTitle;
+	public static String ProjectNaturesPage_changeWarningTitle;
+	public static String ProjectNaturesPage_warningMessage;
+	public static String ProjectNaturesPage_changeWarningQuestion;
+
+}
diff --git a/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/ProjectNaturesPage.java b/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/ProjectNaturesPage.java
new file mode 100644
index 0000000..88b087e
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/ProjectNaturesPage.java
@@ -0,0 +1,358 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2016 Gunnar Wagenknecht 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:
+ *     IBM Corporation - initial API and implementation
+ *     Gunnar Wagenknecht - initial API and implementation
+ *     Mickael Istria (Red Hat Inc) - [102527] Reshaped UI, based on builders
+ ******************************************************************************/
+package org.eclipse.ui.resources.natures;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IProjectNatureDescriptor;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.log.Logger;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+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.Label;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.ElementListSelectionDialog;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
+import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
+import org.eclipse.ui.internal.progress.ProgressMonitorJobsDialog;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * Project property page for viewing and modifying the project natures.
+ *
+ * @since 3.3
+ */
+public class ProjectNaturesPage extends PropertyPage {
+
+	private static final String PLUGIN_ID = "org.eclipse.e4.ui.naturist"; //$NON-NLS-1$
+	public static final String HELP_CONTEXT_ID = PLUGIN_ID + ".project_natures_property_page_context"; //$NON-NLS-1$
+
+	private IProject project;
+	private List<String> naturesIdsWorkingCopy;
+
+	// widgets
+	private TableViewer activeNaturesList;
+
+	private boolean warningAlreadyShown = false;
+	private Logger logger;
+
+	/**
+	 * @see PreferencePage#createContents
+	 */
+	@Override
+	protected Control createContents(final Composite parent) {
+		Bundle bundle = FrameworkUtil.getBundle(this.getClass());
+		BundleContext bundleContext = bundle.getBundleContext();
+		logger = bundleContext.getService(bundleContext.getServiceReference(Logger.class));
+
+		PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), HELP_CONTEXT_ID);
+		Font font = parent.getFont();
+
+		Composite composite = new Composite(parent, SWT.NONE);
+		GridLayout layout = new GridLayout(1, false);
+		composite.setLayout(layout);
+		composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+		composite.setFont(font);
+
+		initialize();
+
+		Composite header = new Composite(composite, SWT.NONE);
+		header.setLayout(new GridLayout(2, false));
+		Label description = createDescriptionLabel(header);
+		description.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1));
+		header.setLayout(new GridLayout(2, false));
+		Label warningImageLabel = new Label(header, SWT.NONE);
+		warningImageLabel.setImage(header.getDisplay().getSystemImage(SWT.ICON_WARNING));
+		Label warningLabel = new Label(header, SWT.WRAP);
+		warningLabel.setText(Messages.ProjectNaturesPage_warningMessage);
+		GridData warningMessageLayoutData = new GridData(SWT.FILL, SWT.CENTER, true, false);
+		warningMessageLayoutData.widthHint = 400;
+		warningMessageLayoutData.verticalIndent = 10;
+		warningLabel.setLayoutData(warningMessageLayoutData);
+
+		Composite naturesComposite = new Composite(composite, SWT.NONE);
+		naturesComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+		naturesComposite.setLayout(new GridLayout(2, false));
+		this.activeNaturesList = new TableViewer(naturesComposite);
+		this.activeNaturesList.getTable().setFont(font);
+		this.activeNaturesList.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+		this.activeNaturesList.setLabelProvider(new NatureLabelProvider(this.project.getWorkspace()));
+		this.activeNaturesList.setContentProvider(new ArrayContentProvider());
+		try {
+			this.naturesIdsWorkingCopy = new ArrayList<String>();
+			this.naturesIdsWorkingCopy.addAll(Arrays.asList(project.getDescription().getNatureIds()));
+		} catch (CoreException ex) {
+			logger.log(IStatus.WARNING, "Error while loading project description for " + this.project.getName(), //$NON-NLS-1$
+					ex);
+		}
+		this.activeNaturesList.setInput(this.naturesIdsWorkingCopy);
+
+		Composite buttonComposite = new Composite(naturesComposite, SWT.NONE);
+		buttonComposite.setLayout(new GridLayout(1, false));
+		buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		Button addButton = new Button(buttonComposite, SWT.PUSH);
+		addButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
+		addButton.setText(Messages.ProjectNaturesPage_addNature);
+		addButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				if (!ProjectNaturesPage.this.warningAlreadyShown) {
+					if (!MessageDialog.openConfirm(getShell(), Messages.ProjectNaturesPage_changeWarningTitle,
+							Messages.ProjectNaturesPage_warningMessage + "\n\n" //$NON-NLS-1$
+									+ Messages.ProjectNaturesPage_changeWarningQuestion)) {
+						return;
+					}
+					ProjectNaturesPage.this.warningAlreadyShown = true;
+				}
+				ElementListSelectionDialog naturesSelectionDialog = new ElementListSelectionDialog(parent.getShell(),
+						new NatureLabelProvider(project.getWorkspace()));
+				naturesSelectionDialog.setMessage(Messages.ProjectNaturesPage_selectNatureToAddMessage);
+				naturesSelectionDialog.setTitle(Messages.ProjectNaturesPage_selectNatureToAddTitle);
+				List<IProjectNatureDescriptor> natures = new ArrayList<IProjectNatureDescriptor>();
+				for (IProjectNatureDescriptor nature : project.getWorkspace().getNatureDescriptors()) {
+					if (!naturesIdsWorkingCopy.contains(nature.getNatureId())) {
+						natures.add(nature);
+					}
+				}
+				naturesSelectionDialog.setElements(natures.toArray(new IProjectNatureDescriptor[natures.size()]));
+				if (naturesSelectionDialog.open() == Window.OK) {
+					for (Object item : naturesSelectionDialog.getResult()) {
+						IProjectNatureDescriptor nature = (IProjectNatureDescriptor) item;
+						naturesIdsWorkingCopy.add(nature.getNatureId());
+					}
+					ProjectNaturesPage.this.activeNaturesList.refresh();
+				}
+			}
+		});
+		final Button removeButton = new Button(buttonComposite, SWT.PUSH);
+		removeButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
+		removeButton.setText(Messages.ProjectNaturesPage_removeNature);
+		removeButton.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				if (!ProjectNaturesPage.this.warningAlreadyShown) {
+					if (!MessageDialog.openConfirm(getShell(), Messages.ProjectNaturesPage_changeWarningTitle,
+							Messages.ProjectNaturesPage_warningMessage + "\n\n" //$NON-NLS-1$
+									+ Messages.ProjectNaturesPage_changeWarningQuestion)) {
+						return;
+					}
+					ProjectNaturesPage.this.warningAlreadyShown = true;
+				}
+				IStructuredSelection selection = (IStructuredSelection) ProjectNaturesPage.this.activeNaturesList
+						.getSelection();
+				for (Object item : selection.toList()) {
+					String natureId = (String) item;
+					naturesIdsWorkingCopy.remove(natureId);
+				}
+				ProjectNaturesPage.this.activeNaturesList.refresh();
+			}
+		});
+		this.activeNaturesList.addSelectionChangedListener(new ISelectionChangedListener() {
+			@Override
+			public void selectionChanged(SelectionChangedEvent event) {
+				removeButton.setEnabled(!ProjectNaturesPage.this.activeNaturesList.getSelection().isEmpty());
+			}
+		});
+		this.activeNaturesList.setSelection(new StructuredSelection()); // Empty
+																		// selection
+
+		return composite;
+	}
+
+	private static class NatureLabelProvider extends LabelProvider {
+		private IWorkspace workspace;
+		private Map<String, Image> natureImages;
+
+		public NatureLabelProvider(IWorkspace workspace) {
+			this.workspace = workspace;
+			this.natureImages = new HashMap<String, Image>(workspace.getNatureDescriptors().length);
+		}
+
+		@Override
+		public String getText(Object element) {
+			IProjectNatureDescriptor nature = null;
+			if (element instanceof IProjectNatureDescriptor) {
+				nature = (IProjectNatureDescriptor) element;
+			} else if (element instanceof String) {
+				String natureId = (String) element;
+				nature = this.workspace.getNatureDescriptor(natureId);
+				if (nature == null) {
+					return getMissingNatureLabel(natureId);
+				}
+			} else {
+				return "Not a valid nature input " + element.toString(); //$NON-NLS-1$
+			}
+			return getNatureDescriptorLabel(nature);
+		}
+
+		@Override
+		public Image getImage(Object element) {
+			String natureId = null;
+			if (element instanceof IProjectNatureDescriptor) {
+				natureId = ((IProjectNatureDescriptor) element).getNatureId();
+			} else if (element instanceof String) {
+				natureId = (String) element;
+			} else {
+				return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
+			}
+			if (this.workspace.getNatureDescriptor(natureId) != null) {
+				if (!natureImages.containsKey(natureId)) {
+					ImageDescriptor image = IDEWorkbenchPlugin.getDefault().getProjectImageRegistry()
+							.getNatureImage(natureId);
+					if (image != null) {
+						this.natureImages.put(natureId, image.createImage());
+					} else {
+						// TODO a generic image?
+					}
+				}
+				return natureImages.get(natureId);
+			} else /* Unknown nature */ {
+				return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+			}
+
+		}
+
+		protected String getMissingNatureLabel(String natureId) {
+			return NLS.bind(Messages.ProjectNaturesPage_missingNatureText, natureId);
+		}
+
+		protected String getNatureDescriptorLabel(IProjectNatureDescriptor natureDescriptor) {
+			String label = natureDescriptor.getLabel();
+			if (label.trim().length() == 0)
+				return natureDescriptor.getNatureId();
+			return label;
+		}
+
+		@Override
+		public void dispose() {
+			for (Image image : natureImages.values()) {
+				image.dispose();
+			}
+			super.dispose();
+		}
+
+	}
+
+	/**
+	 * Handle the exception thrown when saving.
+	 *
+	 * @param e
+	 *            the exception
+	 */
+	protected void handle(InvocationTargetException e) {
+		IStatus error;
+		Throwable target = e.getTargetException();
+		if (target instanceof CoreException) {
+			error = ((CoreException) target).getStatus();
+		} else {
+			String msg = target.getMessage();
+			if (msg == null) {
+				msg = IDEWorkbenchMessages.Internal_error;
+			}
+			error = new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, 1, msg, target);
+		}
+		ErrorDialog.openError(getControl().getShell(), null, null, error);
+	}
+
+	/**
+	 * Initializes a ProjectReferencePage.
+	 */
+	private void initialize() {
+		project = (IProject) getElement().getAdapter(IResource.class);
+		noDefaultAndApplyButton();
+		setDescription(Messages.ProjectNaturesPage_label);
+	}
+
+	/**
+	 * @see PreferencePage#performOk
+	 */
+	@Override
+	public boolean performOk() {
+		List<String> originalNatureIds = null;
+		try {
+			originalNatureIds = Arrays.asList(this.project.getDescription().getNatureIds());
+		} catch (CoreException ex) {
+			logger.log(IStatus.WARNING, "Error while loading project description for " + this.project.getName(), //$NON-NLS-1$
+					ex);
+			originalNatureIds = new ArrayList<String>();
+		}
+		if (this.naturesIdsWorkingCopy.size() == originalNatureIds.size()
+				&& this.naturesIdsWorkingCopy.containsAll(originalNatureIds)) {
+			return true;
+		}
+
+		// set nature ids
+		IRunnableWithProgress runnable = new IRunnableWithProgress() {
+			@Override
+			public void run(IProgressMonitor monitor) throws InvocationTargetException {
+
+				try {
+					IProjectDescription description = project.getDescription();
+					description.setNatureIds(ProjectNaturesPage.this.naturesIdsWorkingCopy
+							.toArray(new String[ProjectNaturesPage.this.naturesIdsWorkingCopy.size()]));
+					project.setDescription(description, monitor);
+				} catch (CoreException e) {
+					throw new InvocationTargetException(e);
+				}
+			}
+		};
+		try {
+			new ProgressMonitorJobsDialog(getControl().getShell()).run(true, true, runnable);
+		} catch (InterruptedException e) {
+			// Ignore interrupted exceptions
+		} catch (InvocationTargetException e) {
+			handle(e);
+			return false;
+		}
+		return true;
+	}
+}
diff --git a/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/messages.properties b/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/messages.properties
new file mode 100644
index 0000000..ca68eb0
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.naturist/src/org/eclipse/ui/resources/natures/messages.properties
@@ -0,0 +1,20 @@
+################################################################################
+# Copyright (c) 2016 Red Hat Inc. 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:
+# - Mickael Istria (Red Hat Inc) - [102527] Reshaped UI, based on builders
+################################################################################
+# --- Project Natures ---
+ProjectNaturesPage_label = Projects may have natures assigned to enable additional functionalities.\nUse this page to specify which natures should be assigned to the project.
+ProjectNaturesPage_missingNatureText = {0} (missing)
+ProjectNaturesPage_addNature = &Add...
+ProjectNaturesPage_removeNature = &Remove
+ProjectNaturesPage_selectNatureToAddMessage = Select a Nature to add to the Project
+ProjectNaturesPage_selectNatureToAddTitle = Select Nature
+ProjectNaturesPage_changeWarningTitle = Confirm Project Nature update
+ProjectNaturesPage_warningMessage = Modifying natures is an advanced operation that can leave the project in the state requiring further extensive and non-obvious configuration changes before normal function is achieved. Do not attempt without a recent backup of the project.
+ProjectNaturesPage_changeWarningQuestion = Continue?
diff --git a/pom.xml b/pom.xml
index 55bb67d..2db0d56 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,8 @@
     <module>bundles/org.eclipse.e4.ui.importer.pde</module>
     <module>tests/org.eclipse.e4.ui.importer.tests</module>
 
+    <module>bundles/org.eclipse.e4.ui.naturist</module>
+
     <module>build/org.eclipse.e4.ui.update</module>
 
     <module>bundles/org.eclipse.e4.ui.macros</module>