bug 253753: dialog to edit the namespace declarations of the current
element

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
index 3097698..4c49e2b 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
@@ -794,6 +794,10 @@
 		listeners.fireEvent("attributeChanged", e);
 	}
 
+	public void fireNamespaceChanged(final DocumentEvent e) {
+		listeners.fireEvent("namespaceChanged", e);
+	}
+	
 	private void fireBeforeContentDeleted(final DocumentEvent e) {
 		listeners.fireEvent("beforeContentDeleted", e);
 	}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/DocumentListener.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/DocumentListener.java
index cc9314a..1e5092d 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/DocumentListener.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/DocumentListener.java
@@ -24,6 +24,14 @@
 	public void attributeChanged(DocumentEvent e);
 
 	/**
+	 * Called when a namespace delcaration is changed in one of the document's elements.
+	 * 
+	 * @param e
+	 *            the document event.
+	 */
+	public void namespaceChanged(DocumentEvent e);
+	
+	/**
 	 * Called before content is deleted from a document.
 	 * 
 	 * @param e
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Element.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Element.java
index eb95d37..f5182bf 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Element.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Element.java
@@ -322,11 +322,29 @@
 	public void declareNamespace(final String namespacePrefix, final String namespaceURI) {
 		if (namespaceURI == null || "".equals(namespaceURI.trim()))
 			return;
-		namespaceDeclarations.put(namespacePrefix, namespaceURI);
+		final String oldNamespaceURI = namespaceDeclarations.put(namespacePrefix, namespaceURI);
+		final Document document = getDocument();
+		if (document == null) // doc may be null, e.g. when we're cloning an element to produce a document fragment
+			return;
+
+		if (namespaceURI.equals(oldNamespaceURI)) // if we do not change anything, we do not fire anything
+			return;
+		
+		final IUndoableEdit edit = document.isUndoEnabled() ? new NamespaceChangeEdit(namespacePrefix, oldNamespaceURI, namespaceURI) : null;
+		document.fireNamespaceChanged(new DocumentEvent(document, this, getStartOffset(), 0, edit));
 	}
 	
 	public void removeNamespace(String namespacePrefix) {
-		namespaceDeclarations.remove(namespacePrefix);
+		final String oldNamespaceURI = namespaceDeclarations.remove(namespacePrefix);
+		final Document document = getDocument();
+		if (document == null) // doc may be null, e.g. when we're cloning an element to produce a document fragment
+			return;
+
+		if (oldNamespaceURI == null)
+			return; // we have actually removed nothing, so we should not tell anybody about it
+		
+		final IUndoableEdit edit = document.isUndoEnabled() ? new NamespaceChangeEdit(namespacePrefix, oldNamespaceURI, null) : null;
+		document.fireNamespaceChanged(new DocumentEvent(document, this, getStartOffset(), 0, edit));
 	}
 	
 	public void declareDefaultNamespace(final String namespaceURI) {
@@ -360,11 +378,11 @@
 	
 	private class AttributeChangeEdit implements IUndoableEdit {
 
-		private QualifiedName name;
-		private String oldValue;
-		private String newValue;
+		private final QualifiedName name;
+		private final String oldValue;
+		private final String newValue;
 
-		public AttributeChangeEdit(QualifiedName name, String oldValue, String newValue) {
+		public AttributeChangeEdit(final QualifiedName name, final String oldValue, final String newValue) {
 			this.name = name;
 			this.oldValue = oldValue;
 			this.newValue = newValue;
@@ -375,7 +393,7 @@
 		}
 
 		public void undo() throws CannotUndoException {
-			Document doc = (Document) getDocument();
+			final Document doc = (Document) getDocument();
 			try {
 				doc.setUndoEnabled(false);
 				setAttribute(name, oldValue);
@@ -387,7 +405,7 @@
 		}
 
 		public void redo() throws CannotRedoException {
-			Document doc = (Document) getDocument();
+			final Document doc = (Document) getDocument();
 			try {
 				doc.setUndoEnabled(false);
 				setAttribute(name, newValue);
@@ -398,5 +416,52 @@
 			}
 		}
 	}
+	
+	private class NamespaceChangeEdit implements IUndoableEdit {
+		
+		private final String prefix;
+		private final String oldUri;
+		private final String newUri;
+		
+		public NamespaceChangeEdit(final String prefix, final String oldUri, final String newUri) {
+			this.prefix = prefix;
+			this.oldUri = oldUri;
+			this.newUri = newUri;
+		}
+		
+		public boolean combine(IUndoableEdit edit) {
+			return false;
+		}
+
+		public void undo() throws CannotUndoException {
+			final Document doc = (Document) getDocument();
+			try {
+				doc.setUndoEnabled(false);
+				if (oldUri == null)
+					removeNamespace(prefix);
+				else
+					declareNamespace(prefix, oldUri);
+			} catch (DocumentValidationException ex) {
+				throw new CannotUndoException();
+			} finally {
+				doc.setUndoEnabled(true);
+			}
+		}
+		
+		public void redo() throws CannotRedoException {
+			final Document doc = (Document) getDocument();
+			try {
+				doc.setUndoEnabled(false);
+				if (newUri == null)
+					removeNamespace(prefix);
+				else
+					declareNamespace(prefix, newUri);
+			} catch (DocumentValidationException ex) {
+				throw new CannotUndoException();
+			} finally {
+				doc.setUndoEnabled(true);
+			}
+		}
+	}
 
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java
index 04ea3c9..233a947 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java
@@ -160,6 +160,16 @@
 			addEdit(e.getUndoableEdit(), getCaretOffset());
 		}
 
+		public void namespaceChanged(DocumentEvent e) {
+			invalidateElementBox(e.getParentElement());
+
+			if (beginWorkCount == 0)
+				VexWidgetImpl.this.relayout();
+
+			addEdit(e.getUndoableEdit(), getCaretOffset());
+			hostComponent.fireSelectionChanged();
+		}
+
 	};
 
 	/**
diff --git a/org.eclipse.vex.ui.tests/META-INF/MANIFEST.MF b/org.eclipse.vex.ui.tests/META-INF/MANIFEST.MF
index f4f8436..570874e 100644
--- a/org.eclipse.vex.ui.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.vex.ui.tests/META-INF/MANIFEST.MF
@@ -7,6 +7,7 @@
 Require-Bundle: org.eclipse.ui,
  org.eclipse.core.runtime,
  org.eclipse.core.resources,
+ org.eclipse.vex.core;bundle-version="[1.0.0,2.0.0)",
  org.eclipse.vex.ui;bundle-version="[1.0.0,2.0.0)",
  org.junit;bundle-version="3.8.1",
  org.eclipse.jface.text;bundle-version="3.0.0"
diff --git a/org.eclipse.vex.ui.tests/src/org/eclipse/vex/ui/internal/namespace/tests/EditNamespacesControllerTest.java b/org.eclipse.vex.ui.tests/src/org/eclipse/vex/ui/internal/namespace/tests/EditNamespacesControllerTest.java
new file mode 100644
index 0000000..ddc6775
--- /dev/null
+++ b/org.eclipse.vex.ui.tests/src/org/eclipse/vex/ui/internal/namespace/tests/EditNamespacesControllerTest.java
@@ -0,0 +1,157 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Florian Thienel 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:

+ * 		Florian Thienel - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.vex.ui.internal.namespace.tests;

+

+import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertFalse;

+import static org.junit.Assert.assertNotNull;

+import static org.junit.Assert.assertNull;

+import static org.junit.Assert.assertSame;

+import static org.junit.Assert.assertTrue;

+import static org.junit.Assert.fail;

+

+import java.util.List;

+

+import org.eclipse.core.runtime.QualifiedName;

+import org.eclipse.vex.core.internal.dom.Element;

+import org.eclipse.vex.ui.internal.namespace.EditNamespacesController;

+import org.eclipse.vex.ui.internal.namespace.EditableNamespaceDefinition;

+import org.junit.Test;

+

+/**

+ * @author Florian Thienel

+ */

+public class EditNamespacesControllerTest {

+

+	@Test

+	public void populateFromElement() throws Exception {

+		final Element element = new Element(new QualifiedName("http://namespace/uri/default", "element"));

+		element.declareDefaultNamespace("http://namespace/uri/default");

+		element.declareNamespace("ns1", "http://namespace/uri/1");

+		element.declareNamespace("ns2", "http://namespace/uri/2");

+		final EditNamespacesController controller = new EditNamespacesController(element);

+

+		assertEquals("http://namespace/uri/default", controller.getDefaultNamespaceURI());

+

+		final List<EditableNamespaceDefinition> namespaces = controller.getNamespaceDefinitions();

+		assertEquals(2, namespaces.size());

+		assertContainsNamespaceDefinition(new EditableNamespaceDefinition("ns1", "http://namespace/uri/1"), namespaces);

+		assertContainsNamespaceDefinition(new EditableNamespaceDefinition("ns2", "http://namespace/uri/2"), namespaces);

+	}

+

+	@Test

+	public void defaultNamespaceNotNull() throws Exception {

+		final EditNamespacesController controller = new EditNamespacesController(new Element("element"));

+		assertNotNull(controller.getDefaultNamespaceURI());

+	}

+

+	@Test

+	public void editDefaultNamespace() throws Exception {

+		final Element element = new Element("element");

+		final EditNamespacesController controller = new EditNamespacesController(element);

+		controller.setDefaultNamespaceURI("http://namespace/uri/default");

+		assertEquals("http://namespace/uri/default", controller.getDefaultNamespaceURI());

+		assertNull(element.getDeclaredDefaultNamespaceURI());

+

+		controller.applyToElement();

+		assertEquals("http://namespace/uri/default", element.getDefaultNamespaceURI());

+		assertNull(element.getQualifiedName().getQualifier());

+	}

+

+	@Test

+	public void removeDefaultNamespace() throws Exception {

+		final Element element = new Element(new QualifiedName("http://namespace/uri/default", "element"));

+		element.declareDefaultNamespace("http://namespace/uri/default");

+		final EditNamespacesController controller = new EditNamespacesController(element);

+

+		controller.setDefaultNamespaceURI("");

+		controller.applyToElement();

+		assertNull(element.getDeclaredDefaultNamespaceURI());

+		assertEquals("http://namespace/uri/default", element.getQualifiedName().getQualifier());

+	}

+

+	@Test

+	public void addNamespaceDefinition() throws Exception {

+		final Element element = new Element("element");

+		final EditNamespacesController controller = new EditNamespacesController(element);

+

+		assertTrue(controller.getNamespaceDefinitions().isEmpty());

+		assertTrue(element.getDeclaredNamespacePrefixes().isEmpty());

+

+		final EditableNamespaceDefinition newDefinition = controller.addNamespaceDefinition();

+		assertNotNull(newDefinition);

+

+		assertEquals(1, controller.getNamespaceDefinitions().size());

+		assertSame(newDefinition, controller.getNamespaceDefinitions().get(0));

+		assertTrue(element.getDeclaredNamespacePrefixes().isEmpty());

+

+		newDefinition.setPrefix("ns1");

+		newDefinition.setUri("http://namespace/uri/1");

+

+		controller.applyToElement();

+		assertEquals(1, element.getDeclaredNamespacePrefixes().size());

+	}

+

+	@Test

+	public void removeNamespaceDefinition() throws Exception {

+		final Element element = new Element("element");

+		element.declareNamespace("ns1", "http://namespace/uri/1");

+		final EditNamespacesController controller = new EditNamespacesController(element);

+

+		controller.removeNamespaceDefinition(controller.getNamespaceDefinitions().get(0));

+

+		assertTrue(controller.getNamespaceDefinitions().isEmpty());

+		assertTrue(element.getDeclaredNamespacePrefixes().contains("ns1"));

+

+		controller.applyToElement();

+		assertTrue(element.getDeclaredNamespacePrefixes().isEmpty());

+	}

+

+	@Test

+	public void editNamespacePrefix() throws Exception {

+		final Element element = new Element("element");

+		element.declareNamespace("ns1", "http://namespace/uri/1");

+		final EditNamespacesController controller = new EditNamespacesController(element);

+		final EditableNamespaceDefinition definition = controller.getNamespaceDefinitions().get(0);

+

+		definition.setPrefix("ns2");

+		assertTrue(element.getDeclaredNamespacePrefixes().contains("ns1"));

+		assertEquals(1, element.getDeclaredNamespacePrefixes().size());

+

+		controller.applyToElement();

+		assertFalse(element.getDeclaredNamespacePrefixes().contains("ns1"));

+		assertTrue(element.getDeclaredNamespacePrefixes().contains("ns2"));

+		assertEquals(1, element.getDeclaredNamespacePrefixes().size());

+	}

+

+	@Test

+	public void editNamespaceUri() throws Exception {

+		final Element element = new Element("element");

+		element.declareNamespace("ns1", "http://namespace/uri/1");

+		final EditNamespacesController controller = new EditNamespacesController(element);

+		final EditableNamespaceDefinition definition = controller.getNamespaceDefinitions().get(0);

+

+		definition.setUri("http://namespace/uri/2");

+		assertEquals("http://namespace/uri/1", element.getNamespaceURI("ns1"));

+

+		controller.applyToElement();

+		assertEquals("http://namespace/uri/2", element.getNamespaceURI("ns1"));

+		assertEquals(1, element.getDeclaredNamespacePrefixes().size());

+	}

+

+	private static void assertContainsNamespaceDefinition(final EditableNamespaceDefinition expected, final List<EditableNamespaceDefinition> actualList) {

+		for (final EditableNamespaceDefinition definition : actualList)

+			if (expected.getPrefix().equals(definition.getPrefix()) && expected.getUri().equals(definition.getUri()))

+				return;

+		fail("namespace definition not found: " + expected.getPrefix() + "=" + expected.getUri());

+	}

+

+}

diff --git a/org.eclipse.vex.ui.tests/src/org/eclipse/vex/ui/tests/VexUiTestSuite.java b/org.eclipse.vex.ui.tests/src/org/eclipse/vex/ui/tests/VexUiTestSuite.java
index ac10c14..49088a8 100644
--- a/org.eclipse.vex.ui.tests/src/org/eclipse/vex/ui/tests/VexUiTestSuite.java
+++ b/org.eclipse.vex.ui.tests/src/org/eclipse/vex/ui/tests/VexUiTestSuite.java
@@ -17,6 +17,7 @@
 import org.eclipse.vex.ui.internal.config.tests.ConfigLoaderJobTest;
 import org.eclipse.vex.ui.internal.config.tests.ConfigurationRegistryTest;
 import org.eclipse.vex.ui.internal.editor.tests.FindReplaceTargetTest;
+import org.eclipse.vex.ui.internal.namespace.tests.EditNamespacesControllerTest;
 import org.eclipse.vex.ui.internal.tests.ResourceTrackerTest;
 
 public class VexUiTestSuite extends TestSuite {
@@ -29,6 +30,7 @@
 		super("Vex UI Tests"); //$NON-NLS-1$
 		addTest(new JUnit4TestAdapter(ConfigLoaderJobTest.class));
 		addTest(new JUnit4TestAdapter(ConfigurationRegistryTest.class));
+		addTest(new JUnit4TestAdapter(EditNamespacesControllerTest.class));
 		addTestSuite(IconTest.class);
 		addTestSuite(FindReplaceTargetTest.class);
 		addTestSuite(ResourceTrackerTest.class);
diff --git a/org.eclipse.vex.ui/META-INF/MANIFEST.MF b/org.eclipse.vex.ui/META-INF/MANIFEST.MF
index 8d500da..0513aaf 100644
--- a/org.eclipse.vex.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.vex.ui/META-INF/MANIFEST.MF
@@ -25,6 +25,7 @@
  org.eclipse.vex.ui.internal.config;x-internal:=true,
  org.eclipse.vex.ui.internal.editor;x-friends:="org.eclipse.vex.docbook",
  org.eclipse.vex.ui.internal.handlers;x-internal:=true,
+ org.eclipse.vex.ui.internal.namespace,
  org.eclipse.vex.ui.internal.outline;x-friends:="org.eclipse.vex.docbook",
  org.eclipse.vex.ui.internal.perspective;x-internal:=true,
  org.eclipse.vex.ui.internal.property;x-internal:=true,
diff --git a/org.eclipse.vex.ui/plugin.properties b/org.eclipse.vex.ui/plugin.properties
index 6ac359b..543c8c3 100644
--- a/org.eclipse.vex.ui/plugin.properties
+++ b/org.eclipse.vex.ui/plugin.properties
@@ -12,17 +12,17 @@
 pluginName= Vex UI
 providerName= Eclipse.org
 
-BuildProblemDecorator.name=Visual XML Plug-in Build Problem Decorator
+BuildProblemDecorator.name=Visual XML Plug-in Build Problem
 ConfigurationView.name=Visual XML Configuration
 DebugView.name=Visual XML Debug
-DocumentPerspective.name=Document
+DocumentPerspective.name=XML Authoring
 NewDocumentWizard.name=Document
 NewDocumentWizard.desc=Create an XML document using the Visual Editor for XML.
 NewPluginProjectWizard.name=Visual XML Plug-in Project
 NewPluginProjectWizard.desc=Create a Visual XML Plug-in project to register XML types and to associate styles to edit these types of files with the Visual XML Editor.
 NewWizardCategory.name=XML Authoring
 PluginProjectBuilder.name=Visual XML Plug-in Builder
-PluginProjectDecorator.name=Visual XML Plug-in Project Decorator
+PluginProjectDecorator.name=Visual XML Plug-in Project
 PluginProjectNature.name=Visual XML Plug-in Project Nature
 DoctypePropertyPage.name=Visual XML Document Type
 StylePropertyPage.name=Visual XML Style
@@ -57,6 +57,7 @@
 command.nextTableCell.name= Next Table Cell
 command.splitBlockElement.name= Split Block Element
 command.splitItem.name= Split Item
+command.editNamespaces.name= Edit Namespaces...
 
 menu.Document.name= &Document
 menu.Add.name= &Add
@@ -86,4 +87,5 @@
 menu.Column.MoveRight.name= Move Right
 menu.Column.Remove.name= Remove
 menu.Style.name= Style
+
 contentType.XmlDocument.name=XML Document

diff --git a/org.eclipse.vex.ui/plugin.xml b/org.eclipse.vex.ui/plugin.xml
index 2bb4fba..7e440c5 100644
--- a/org.eclipse.vex.ui/plugin.xml
+++ b/org.eclipse.vex.ui/plugin.xml
@@ -79,6 +79,11 @@
       </command>
       <command
             categoryId="org.eclipse.vex.ui.commands.category"
+            id="org.eclipse.vex.ui.EditNamespacesCommand"
+            name="%command.editNamespaces.name">
+      </command>
+      <command
+            categoryId="org.eclipse.vex.ui.commands.category"
             id="org.eclipse.vex.ui.DuplicateSelectionCommand"
             name="%command.duplicateSelection.name">
       </command>
@@ -229,6 +234,15 @@
          </activeWhen>
       </handler>
       <handler
+            class="org.eclipse.vex.ui.internal.namespace.EditNamespacesHandler"
+            commandId="org.eclipse.vex.ui.EditNamespacesCommand">
+         <activeWhen>
+            <reference
+                  definitionId="org.eclipse.vex.ui.activeVexEditor">
+            </reference>
+         </activeWhen>
+      </handler>
+      <handler
             class="org.eclipse.vex.ui.internal.handlers.DuplicateSelectionHandler"
             commandId="org.eclipse.vex.ui.DuplicateSelectionCommand">
          <activeWhen>
@@ -667,6 +681,9 @@
             <command
                   commandId="org.eclipse.vex.ui.ConvertElementCommand">
             </command>
+            <command
+                  commandId="org.eclipse.vex.ui.EditNamespacesCommand">
+            </command>
             <menu
                   label="%menu.Move.name">
                <command
@@ -745,6 +762,9 @@
          <command
                commandId="org.eclipse.vex.ui.RemoveTagCommand">
          </command>
+         <command
+               commandId="org.eclipse.vex.ui.EditNamespacesCommand">
+         </command>
          <separator
                name="org.eclipse.vex.ui.popup.vexTableItems"
                visible="true">
diff --git a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesController.java b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesController.java
new file mode 100644
index 0000000..31b216a
--- /dev/null
+++ b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesController.java
@@ -0,0 +1,89 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Florian Thienel 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:

+ * 		Florian Thienel - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.vex.ui.internal.namespace;

+

+import java.util.ArrayList;

+import java.util.HashSet;

+import java.util.List;

+

+import org.eclipse.vex.core.internal.dom.Element;

+

+/**

+ * @author Florian Thienel

+ */

+public class EditNamespacesController {

+

+	private final Element element;

+

+	private String defaultNamespaceURI;

+

+	private final List<EditableNamespaceDefinition> namespaceDefinitions;

+

+	public EditNamespacesController(final Element element) {

+		this.element = element;

+		defaultNamespaceURI = getDefaultNamespaceURI(element);

+		namespaceDefinitions = getNamespaceDefinitions(element);

+	}

+

+	private static String getDefaultNamespaceURI(final Element element) {

+		final String result = element.getDeclaredDefaultNamespaceURI();

+		if (result == null)

+			return "";

+		return result;

+	}

+

+	private static List<EditableNamespaceDefinition> getNamespaceDefinitions(final Element element) {

+		final ArrayList<EditableNamespaceDefinition> result = new ArrayList<EditableNamespaceDefinition>();

+		for (final String prefix : element.getDeclaredNamespacePrefixes())

+			result.add(new EditableNamespaceDefinition(prefix, element.getNamespaceURI(prefix)));

+		return result;

+	}

+

+	public String getDefaultNamespaceURI() {

+		return defaultNamespaceURI;

+	}

+

+	public void setDefaultNamespaceURI(final String defaultNamespaceURI) {

+		this.defaultNamespaceURI = defaultNamespaceURI;

+	}

+

+	public List<EditableNamespaceDefinition> getNamespaceDefinitions() {

+		return namespaceDefinitions;

+	}

+

+	public EditableNamespaceDefinition addNamespaceDefinition() {

+		final EditableNamespaceDefinition result = new EditableNamespaceDefinition();

+		namespaceDefinitions.add(result);

+		return result;

+	}

+

+	public void removeNamespaceDefinition(EditableNamespaceDefinition namespaceDefinition) {

+		namespaceDefinitions.remove(namespaceDefinition);

+	}

+	

+	public void applyToElement() {

+		if (defaultNamespaceURI == null || "".equals(defaultNamespaceURI))

+			element.removeDefaultNamespace();

+		else

+			element.declareDefaultNamespace(defaultNamespaceURI);

+

+		final HashSet<String> declaredPrefixes = new HashSet<String>();

+		for (final EditableNamespaceDefinition definition : namespaceDefinitions) {

+			element.declareNamespace(definition.getPrefix(), definition.getUri());

+			declaredPrefixes.add(definition.getPrefix());

+		}

+		

+		for (final String prefix : element.getDeclaredNamespacePrefixes())

+			if (!declaredPrefixes.contains(prefix))

+				element.removeNamespace(prefix);

+	}

+

+}

diff --git a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesDialog.java b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesDialog.java
new file mode 100644
index 0000000..c69f05f
--- /dev/null
+++ b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesDialog.java
@@ -0,0 +1,236 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Florian Thienel 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:

+ * 		Florian Thienel - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.vex.ui.internal.namespace;

+

+import org.eclipse.jface.dialogs.TitleAreaDialog;

+import org.eclipse.jface.layout.TableColumnLayout;

+import org.eclipse.jface.viewers.ArrayContentProvider;

+import org.eclipse.jface.viewers.CellEditor;

+import org.eclipse.jface.viewers.CellLabelProvider;

+import org.eclipse.jface.viewers.ColumnWeightData;

+import org.eclipse.jface.viewers.EditingSupport;

+import org.eclipse.jface.viewers.IStructuredSelection;

+import org.eclipse.jface.viewers.StructuredSelection;

+import org.eclipse.jface.viewers.TableViewer;

+import org.eclipse.jface.viewers.TableViewerColumn;

+import org.eclipse.jface.viewers.TextCellEditor;

+import org.eclipse.jface.viewers.ViewerCell;

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.events.ModifyEvent;

+import org.eclipse.swt.events.ModifyListener;

+import org.eclipse.swt.events.SelectionAdapter;

+import org.eclipse.swt.events.SelectionEvent;

+import org.eclipse.swt.graphics.Point;

+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.swt.widgets.Shell;

+import org.eclipse.swt.widgets.Text;

+import org.eclipse.vex.core.internal.dom.Element;

+

+/**

+ * @author Florian Thienel

+ */

+public class EditNamespacesDialog extends TitleAreaDialog {

+

+	private final EditNamespacesController controller;

+	private Text defaultNamespaceText;

+	private TableViewer namespacesTable;

+

+	public EditNamespacesDialog(final Shell parentShell, final Element element) {

+		super(parentShell);

+		this.controller = new EditNamespacesController(element);

+	}

+

+	@Override

+	protected void configureShell(final Shell newShell) {

+		super.configureShell(newShell);

+		newShell.setText("Edit Namespaces");

+	}

+

+	@Override

+	protected int getShellStyle() {

+		return super.getShellStyle() | SWT.RESIZE;

+	}

+

+	@Override

+	protected Point getInitialSize() {

+		final Point shellSize = super.getInitialSize();

+		return new Point(shellSize.x, Math.max(convertVerticalDLUsToPixels(200), shellSize.y));

+	}

+

+	@Override

+	protected Control createContents(final Composite parent) {

+		final Composite result = (Composite) super.createContents(parent);

+		setTitle("Edit Namespaces");

+		setMessage("Edit the namespaces of the selected element.");

+		return result;

+	}

+

+	@Override

+	protected Control createDialogArea(final Composite parent) {

+		final Composite superRoot = (Composite) super.createDialogArea(parent);

+		final Composite root = new Composite(superRoot, SWT.NONE);

+		root.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

+		root.setLayout(new GridLayout(2, false));

+

+		final Label defaultNamespaceLabel = new Label(root, SWT.NONE);

+		defaultNamespaceLabel.setText("Default Namespace");

+		defaultNamespaceLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));

+

+		defaultNamespaceText = new Text(root, SWT.SINGLE | SWT.BORDER);

+		defaultNamespaceText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));

+		defaultNamespaceText.addModifyListener(new ModifyListener() {

+			public void modifyText(final ModifyEvent e) {

+				controller.setDefaultNamespaceURI(defaultNamespaceText.getText());

+			}

+		});

+

+		new Label(root, SWT.NONE); // just a placeholder

+

+		final Label namespaceTableLabel = new Label(root, SWT.NONE);

+		namespaceTableLabel.setText("Other Namespaces");

+		namespaceTableLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));

+

+		final Composite tableRoot = new Composite(root, SWT.NONE);

+		tableRoot.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3));

+		final TableColumnLayout tableColumnLayout = new TableColumnLayout();

+		tableRoot.setLayout(tableColumnLayout);

+

+		namespacesTable = new TableViewer(tableRoot, SWT.BORDER | SWT.FULL_SELECTION);

+		namespacesTable.getTable().setHeaderVisible(true);

+		namespacesTable.getTable().setLinesVisible(true);

+		namespacesTable.setContentProvider(ArrayContentProvider.getInstance());

+

+		final TableViewerColumn prefixColumn = new TableViewerColumn(namespacesTable, SWT.NONE);

+		prefixColumn.getColumn().setText("Prefix");

+		prefixColumn.setLabelProvider(new CellLabelProvider() {

+			@Override

+			public void update(final ViewerCell cell) {

+				cell.setText(((EditableNamespaceDefinition) cell.getElement()).getPrefix());

+			}

+		});

+		prefixColumn.setEditingSupport(new EditingSupport(namespacesTable) {

+			@Override

+			protected CellEditor getCellEditor(final Object element) {

+				return new TextCellEditor(namespacesTable.getTable());

+			}

+

+			@Override

+			protected boolean canEdit(final Object element) {

+				return true;

+			}

+

+			@Override

+			protected Object getValue(final Object element) {

+				return ((EditableNamespaceDefinition) element).getPrefix();

+			}

+

+			@Override

+			protected void setValue(final Object element, final Object value) {

+				if (value != null)

+					((EditableNamespaceDefinition) element).setPrefix(value.toString());

+				else

+					((EditableNamespaceDefinition) element).setPrefix("");

+				namespacesTable.refresh(element);

+			}

+		});

+

+		final TableViewerColumn uriColumn = new TableViewerColumn(namespacesTable, SWT.NONE);

+		uriColumn.getColumn().setText("URI");

+		uriColumn.setLabelProvider(new CellLabelProvider() {

+			@Override

+			public void update(final ViewerCell cell) {

+				cell.setText(((EditableNamespaceDefinition) cell.getElement()).getUri());

+			}

+		});

+		uriColumn.setEditingSupport(new EditingSupport(namespacesTable) {

+			@Override

+			protected CellEditor getCellEditor(final Object element) {

+				return new TextCellEditor(namespacesTable.getTable());

+			}

+

+			@Override

+			protected boolean canEdit(final Object element) {

+				return true;

+			}

+

+			@Override

+			protected Object getValue(final Object element) {

+				return ((EditableNamespaceDefinition) element).getUri();

+			}

+

+			@Override

+			protected void setValue(final Object element, final Object value) {

+				if (value != null)

+					((EditableNamespaceDefinition) element).setUri(value.toString());

+				else

+					((EditableNamespaceDefinition) element).setUri("");

+				namespacesTable.refresh(element);

+			}

+		});

+

+		tableColumnLayout.setColumnData(prefixColumn.getColumn(), new ColumnWeightData(1, 100, true));

+		tableColumnLayout.setColumnData(uriColumn.getColumn(), new ColumnWeightData(3, 100, true));

+

+		final Button addNamespaceButton = new Button(root, SWT.PUSH);

+		addNamespaceButton.setText("Add");

+		addNamespaceButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

+		addNamespaceButton.addSelectionListener(new SelectionAdapter() {

+			@Override

+			public void widgetSelected(final SelectionEvent e) {

+				addNamespacePressed();

+			}

+		});

+

+		final Button removeNamespaceButton = new Button(root, SWT.PUSH);

+		removeNamespaceButton.setText("Remove");

+		removeNamespaceButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

+		removeNamespaceButton.addSelectionListener(new SelectionAdapter() {

+			@Override

+			public void widgetSelected(final SelectionEvent e) {

+				removeNamespacePressed();

+			}

+		});

+

+		populateFromController();

+

+		return superRoot;

+	}

+

+	private void populateFromController() {

+		defaultNamespaceText.setText(controller.getDefaultNamespaceURI());

+		namespacesTable.setInput(controller.getNamespaceDefinitions());

+	}

+

+	private void addNamespacePressed() {

+		final EditableNamespaceDefinition newDefinition = controller.addNamespaceDefinition();

+		namespacesTable.refresh();

+		namespacesTable.setSelection(new StructuredSelection(newDefinition), true);

+	}

+

+	private void removeNamespacePressed() {

+		final IStructuredSelection selection = (IStructuredSelection) namespacesTable.getSelection();

+		if (selection.isEmpty())

+			return;

+		final EditableNamespaceDefinition selectedDefinition = (EditableNamespaceDefinition) selection.getFirstElement();

+		controller.removeNamespaceDefinition(selectedDefinition);

+		namespacesTable.refresh();

+	}

+	

+	protected void okPressed() {

+		controller.applyToElement();

+		super.okPressed();

+	};

+}

diff --git a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesHandler.java b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesHandler.java
new file mode 100644
index 0000000..f5b6962
--- /dev/null
+++ b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditNamespacesHandler.java
@@ -0,0 +1,31 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Florian Thienel 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:

+ * 		Florian Thienel - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.vex.ui.internal.namespace;

+

+import org.eclipse.core.commands.ExecutionException;

+import org.eclipse.jface.window.Window;

+import org.eclipse.vex.ui.internal.handlers.AbstractVexWidgetHandler;

+import org.eclipse.vex.ui.internal.swt.VexWidget;

+

+/**

+ * @author Florian Thienel

+ */

+public class EditNamespacesHandler extends AbstractVexWidgetHandler {

+

+	@Override

+	public void execute(final VexWidget widget) throws ExecutionException {

+		final EditNamespacesDialog dialog = new EditNamespacesDialog(widget.getShell(), widget.getCurrentElement());

+		if (dialog.open() == Window.OK) {

+			; // TODO maybe we have to refresh something in the widget... 

+		}

+	}

+

+}

diff --git a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditableNamespaceDefinition.java b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditableNamespaceDefinition.java
new file mode 100644
index 0000000..fe072c0
--- /dev/null
+++ b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/internal/namespace/EditableNamespaceDefinition.java
@@ -0,0 +1,47 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Florian Thienel 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:

+ * 		Florian Thienel - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.vex.ui.internal.namespace;

+

+/**

+ * @author Florian Thienel

+ */

+public class EditableNamespaceDefinition {

+	

+	private String prefix;

+	

+	private String uri;

+	

+	public EditableNamespaceDefinition() {

+		this("", "");

+	}

+	

+	public EditableNamespaceDefinition(final String prefix, final String uri) {

+		this.prefix = prefix;

+		this.uri = uri;

+	}

+	

+	public String getPrefix() {

+		return prefix;

+	}

+	

+	public void setPrefix(String prefix) {

+		this.prefix = prefix;

+	}

+	

+	public String getUri() {

+		return uri;

+	}

+	

+	public void setUri(String uri) {

+		this.uri = uri;

+	}

+

+}