You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-commits@xmlgraphics.apache.org by ca...@apache.org on 2007/11/13 01:40:58 UTC

svn commit: r594367 [2/9] - in /xmlgraphics/batik/trunk: ./ resources/org/apache/batik/apps/svgbrowser/resources/ resources/org/apache/batik/util/gui/resources/ sources-1.3/org/apache/batik/util/ sources-1.3/org/apache/batik/util/gui/ sources-1.4/org/a...

Copied: xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewer.java (from r594022, xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/DOMViewer.java)
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewer.java?p2=xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewer.java&p1=xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/DOMViewer.java&r1=594022&r2=594367&rev=594367&view=diff
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/DOMViewer.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewer.java Mon Nov 12 16:40:53 2007
@@ -16,18 +16,31 @@
    limitations under the License.
 
  */
-package org.apache.batik.util.gui;
+package org.apache.batik.apps.svgbrowser;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
-import java.awt.GridLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
-
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.ResourceBundle;
@@ -36,14 +49,21 @@
 import javax.swing.Action;
 import javax.swing.BorderFactory;
 import javax.swing.ImageIcon;
+import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
 import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
 import javax.swing.JScrollPane;
 import javax.swing.JSplitPane;
 import javax.swing.JTable;
 import javax.swing.JTextArea;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
 import javax.swing.JTree;
+import javax.swing.SwingUtilities;
 import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
 import javax.swing.table.AbstractTableModel;
@@ -52,24 +72,48 @@
 import javax.swing.tree.DefaultTreeModel;
 import javax.swing.tree.MutableTreeNode;
 import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
 
+import org.apache.batik.apps.svgbrowser.DOMDocumentTree.DOMDocumentTreeAdapter;
+import org.apache.batik.apps.svgbrowser.DOMDocumentTree.DOMDocumentTreeEvent;
+import org.apache.batik.apps.svgbrowser.DOMDocumentTree.DropCompletedInfo;
+import org.apache.batik.apps.svgbrowser.DropDownHistoryModel.RedoPopUpMenuModel;
+import org.apache.batik.apps.svgbrowser.DropDownHistoryModel.UndoPopUpMenuModel;
+import org.apache.batik.apps.svgbrowser.HistoryBrowser.DocumentCommandController;
+import org.apache.batik.apps.svgbrowser.NodePickerPanel.NameEditorDialog;
+import org.apache.batik.apps.svgbrowser.NodePickerPanel.NodePickerAdapter;
+import org.apache.batik.apps.svgbrowser.NodePickerPanel.NodePickerEvent;
+import org.apache.batik.apps.svgbrowser.NodeTemplates.NodeTemplateDescriptor;
 import org.apache.batik.bridge.svg12.ContentManager;
 import org.apache.batik.bridge.svg12.DefaultXBLManager;
 import org.apache.batik.dom.AbstractDocument;
+import org.apache.batik.dom.svg.SVGOMDocument;
 import org.apache.batik.dom.svg12.XBLOMContentElement;
+import org.apache.batik.dom.util.DOMUtilities;
+import org.apache.batik.dom.util.SAXDocumentFactory;
 import org.apache.batik.dom.xbl.NodeXBL;
 import org.apache.batik.dom.xbl.XBLManager;
+import org.apache.batik.util.SVGConstants;
+import org.apache.batik.util.XMLResourceDescriptor;
+import org.apache.batik.util.gui.DropDownComponent;
 import org.apache.batik.util.gui.resource.ActionMap;
 import org.apache.batik.util.gui.resource.ButtonFactory;
 import org.apache.batik.util.gui.resource.MissingListenerException;
 import org.apache.batik.util.resources.ResourceManager;
+
+import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.css.CSSStyleDeclaration;
 import org.w3c.dom.css.ViewCSS;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.events.MutationEvent;
 
 /**
  * The components of this class are used to view a DOM tree.
@@ -78,11 +122,12 @@
  * @version $Id$
  */
 public class DOMViewer extends JFrame implements ActionMap {
+
     /**
      * The resource file name
      */
     protected static final String RESOURCE =
-        "org.apache.batik.util.gui.resources.DOMViewerMessages";
+        "org.apache.batik.apps.svgbrowser.resources.DOMViewerMessages";
 
     /**
      * The resource bundle
@@ -105,9 +150,14 @@
     protected Map listeners = new HashMap();
 
     /**
+     * The button factory.
+     */
+    protected ButtonFactory buttonFactory;
+
+    /**
      * The panel.
      */
-    protected Panel panel = new Panel();
+    protected Panel panel;
 
     /**
      * Whether to show text nodes that contain only whitespace in the tree.
@@ -115,39 +165,97 @@
     protected boolean showWhitespace = true;
 
     /**
+     * Whether "capturing click" tool is enabled. If enabled, the element being
+     * clicked on is found and selected in the DOMViewer's document tree
+     */
+    protected boolean isCapturingClickEnabled;
+
+    /**
+     * The DOMViewer controller.
+     */
+    protected DOMViewerController domViewerController;
+
+    /**
+     * Manages the element selection on the canvas.
+     */
+    protected ElementOverlayManager elementOverlayManager;
+
+    /**
+     * Whether painting the overlay on the canvas is enabled.
+     */
+    protected boolean isElementOverlayEnabled = true;
+
+    /**
+     * The history browsing manager. Manages undo / redo.
+     */
+    protected HistoryBrowserInterface historyBrowserInterface;
+
+    /**
+     * Whether the DOMViewer can be used for editing the document.
+     */
+    protected boolean canEdit = true;
+
+    /**
+     * The button for enabling and disabling the overlay.
+     */
+    protected JToggleButton overlayButton;
+
+    /**
      * Creates a new DOMViewer panel.
      */
-    public DOMViewer() {
+    public DOMViewer(DOMViewerController controller) {
         super(resources.getString("Frame.title"));
         setSize(resources.getInteger("Frame.width"),
                 resources.getInteger("Frame.height"));
 
+        domViewerController = controller;
+
+        elementOverlayManager = domViewerController.createSelectionManager();
+        if (elementOverlayManager != null) {
+            elementOverlayManager
+                    .setController(new DOMViewerElementOverlayController());
+        }
+        historyBrowserInterface =
+            new HistoryBrowserInterface
+                (new DocumentCommandController(controller));
+
         listeners.put("CloseButtonAction", new CloseButtonAction());
+        listeners.put("UndoButtonAction", new UndoButtonAction());
+        listeners.put("RedoButtonAction", new RedoButtonAction());
+        listeners.put("CapturingClickButtonAction",
+                      new CapturingClickButtonAction());
+        listeners.put("OverlayButtonAction", new OverlayButtonAction());
 
+        // Create the main panel
+        panel = new Panel();
         getContentPane().add(panel);
 
         JPanel p = new JPanel(new BorderLayout());
-
-        JCheckBox cb = new JCheckBox("Show Whitespace Text Nodes");
+        JCheckBox cb =
+            new JCheckBox(resources.getString("ShowWhitespaceCheckbox.text"));
         cb.setSelected(showWhitespace);
         cb.addItemListener(new ItemListener() {
-                public void itemStateChanged(ItemEvent ie) {
-                    setShowWhitespace
-                        (ie.getStateChange() == ItemEvent.SELECTED);
-                }
-            });
-
+            public void itemStateChanged(ItemEvent ie) {
+                setShowWhitespace(ie.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
         p.add(cb, BorderLayout.WEST);
-
-
-        ButtonFactory bf = new ButtonFactory(bundle, this);
-        p.add(bf.createJButton("CloseButton"), BorderLayout.EAST);
-        getContentPane().add( p, BorderLayout.SOUTH );
+        p.add(getButtonFactory().createJButton("CloseButton"),
+              BorderLayout.EAST);
+        getContentPane().add(p, BorderLayout.SOUTH);
+
+        // Set the document
+        Document document = domViewerController.getDocument();
+        if (document != null) {
+//            panel
+//                    .setDocument(document, (ViewCSS) document
+//                            .getDocumentElement());
+            panel.setDocument(document, null);
+        }
     }
 
     /**
-     * Sets whether to show text nodes that contain only whitespace
-     * in the tree.
+     * Sets whether to show text nodes that contain only whitespace in the tree.
      */
     public void setShowWhitespace(boolean state) {
         showWhitespace = state;
@@ -170,34 +278,234 @@
     }
 
     /**
-     * Returns the action associated with the given string
-     * or null on error
-     * @param key the key mapped with the action to get
-     * @throws MissingListenerException if the action is not found
+     * Whether the document can be edited using the DOMViewer.
+     *
+     * @return True if the document can be edited throught the DOMViewer
+     */
+    public boolean canEdit() {
+        return domViewerController.canEdit() && canEdit;
+    }
+
+    /**
+     * Enables / disables the DOMViewer to be used to edit the documents.
+     *
+     * @param canEdit
+     *            True - The DOMViewer can be used to edit the documents
+     */
+    public void setEditable(boolean canEdit) {
+        this.canEdit = canEdit;
+    }
+
+    /**
+     * Selects and scrolls to the given node in the document tree.
+     *
+     * @param node
+     *            The node to be selected
+     */
+    public void selectNode(Node node) {
+        panel.selectNode(node);
+    }
+
+    /**
+     * Resets the history.
+     */
+    public void resetHistory() {
+        historyBrowserInterface.getHistoryBrowser().resetHistory();
+    }
+
+    /**
+     * Gets buttonFactory.
+     */
+    private ButtonFactory getButtonFactory() {
+        if (buttonFactory == null) {
+            buttonFactory = new ButtonFactory(bundle, this);
+        }
+        return buttonFactory;
+    }
+
+    // ActionMap
+
+    /**
+     * Returns the action associated with the given string or null on error
+     *
+     * @param key
+     *            the key mapped with the action to get
+     * @throws MissingListenerException
+     *             if the action is not found
      */
     public Action getAction(String key) throws MissingListenerException {
         return (Action)listeners.get(key);
     }
 
     /**
+     * Groups the document changes that were made out of the document into a
+     * single change and adds this change to the history browser.
+     */
+    private void addChangesToHistory() {
+        historyBrowserInterface.performCurrentCompoundCommand();
+    }
+
+    /**
      * The action associated with the 'Close' button of the viewer panel
      */
     protected class CloseButtonAction extends AbstractAction {
         public void actionPerformed(ActionEvent e) {
+            panel.tree.setSelectionRow(0);
             dispose();
         }
     }
 
     /**
+     * The action associated with the 'Undo' dropdown button of the viewer panel
+     */
+    protected class UndoButtonAction extends AbstractAction {
+        public void actionPerformed(ActionEvent e) {
+            addChangesToHistory();
+            historyBrowserInterface.getHistoryBrowser().undo();
+        }
+    }
+
+    /**
+     * The action associated with the 'Redo' dropdown button of the viewer panel
+     */
+    protected class RedoButtonAction extends AbstractAction {
+        public void actionPerformed(ActionEvent e) {
+            addChangesToHistory();
+            historyBrowserInterface.getHistoryBrowser().redo();
+        }
+    }
+
+    /**
+     * The action associated with the 'Capturing-click' toggle button of the
+     * viewer panel.
+     */
+    protected class CapturingClickButtonAction extends AbstractAction {
+        public void actionPerformed(ActionEvent e) {
+            JToggleButton btn = (JToggleButton) e.getSource();
+            isCapturingClickEnabled = btn.isSelected();
+            if (!isCapturingClickEnabled) {
+                btn.setToolTipText
+                    (resources.getString("CapturingClickButton.tooltip"));
+            } else {
+                btn.setToolTipText
+                    (resources.getString("CapturingClickButton.disableText"));
+            }
+        }
+    }
+
+    /**
+     * Toggles the element highlighting overlay.
+     */
+    protected void toggleOverlay() {
+        isElementOverlayEnabled = overlayButton.isSelected();
+        if (!isElementOverlayEnabled) {
+            overlayButton.setToolTipText
+                (resources.getString("OverlayButton.tooltip"));
+        } else {
+            overlayButton.setToolTipText
+                (resources.getString("OverlayButton.disableText"));
+        }
+        // Refresh overlay
+        if (elementOverlayManager != null) {
+            elementOverlayManager.setOverlayEnabled(isElementOverlayEnabled);
+            elementOverlayManager.repaint();
+        }
+    }
+
+    /**
+     * The action associated with the 'overlay' toggle button of the viewer
+     * panel.
+     */
+    protected class OverlayButtonAction extends AbstractAction {
+        public void actionPerformed(ActionEvent e) {
+            toggleOverlay();
+        }
+    }
+
+    /**
+     * NodePickerController implementation.
+     */
+    protected class DOMViewerNodePickerController
+            implements NodePickerController {
+
+        public boolean isEditable() {
+            return DOMViewer.this.canEdit();
+        }
+
+        public boolean canEdit(Element el) {
+            if (panel == null || panel.document == null
+                    || panel.document.getDocumentElement() != el) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * DOMDocumentTreeController implementation.
+     */
+    protected class DOMViewerDOMDocumentTreeController
+            implements DOMDocumentTreeController {
+
+        public boolean isDNDSupported() {
+            return canEdit();
+        }
+    }
+
+    /**
+     * ElementOverlayController implementation.
+     */
+    protected class DOMViewerElementOverlayController
+            implements ElementOverlayController {
+
+        public boolean isOverlayEnabled() {
+            return canEdit() && isElementOverlayEnabled;
+        }
+    }
+
+    /**
      * The panel that contains the viewer.
      */
     public class Panel extends JPanel {
+
+        // DOM Mutation event names.
+        public static final String NODE_INSERTED = "DOMNodeInserted";
+        public static final String NODE_REMOVED = "DOMNodeRemoved";
+        public static final String ATTRIBUTE_MODIFIED = "DOMAttrModified";
+        public static final String CHAR_DATA_MODIFIED = "DOMCharacterDataModified";
+
         /**
          * The DOM document.
          */
         protected Document document;
 
         /**
+         * "Node inserted" DOM Mutation Listener.
+         */
+        protected EventListener nodeInsertion;
+
+        /**
+         * "Node removed" DOM Mutation Listener.
+         */
+        protected EventListener nodeRemoval;
+
+        /**
+         * "Attribute modified" DOM Mutation Listener.
+         */
+        protected EventListener attrModification;
+
+        /**
+         * "Character data modified" DOM Mutation Listener.
+         */
+        protected EventListener charDataModification;
+
+        /**
+         * Capturing "click" event type listener. Allows the "capturing click"
+         * option
+         */
+        protected EventListener capturingListener;
+
+        /**
          * The ViewCSS object associated with the document.
          */
         protected ViewCSS viewCSS;
@@ -205,7 +513,7 @@
         /**
          * The tree.
          */
-        protected JTree tree;
+        protected DOMDocumentTree tree;
 
         /**
          * The split pane.
@@ -218,53 +526,246 @@
         protected JPanel rightPanel = new JPanel(new BorderLayout());
 
         /**
-         * The attributes table.
+         * The properties table.
          */
-        protected JTable attributesTable = new JTable();
+        protected JTable propertiesTable = new JTable();
 
         /**
-         * The properties table.
+         * The panel to show the nodes attributes.
          */
-        protected JTable propertiesTable = new JTable();
+        protected NodePickerPanel attributePanel =
+            new NodePickerPanel(new DOMViewerNodePickerController());
+        {
+            attributePanel.addListener(new NodePickerAdapter() {
+
+                public void updateElement(NodePickerEvent event) {
+                    String result = event.getResult();
+                    Element targetElement = (Element) event.getContextNode();
+                    Element newElem = wrapAndParse(result, targetElement);
+
+                    addChangesToHistory();
+
+                    AbstractCompoundCommand cmd = historyBrowserInterface
+                            .createNodeChangedCommand(newElem);
+                    Node parent = targetElement.getParentNode();
+                    Node nextSibling = targetElement.getNextSibling();
+                    cmd.addCommand(historyBrowserInterface
+                            .createRemoveChildCommand(parent, targetElement));
+                    cmd.addCommand(historyBrowserInterface
+                            .createInsertChildCommand(parent, nextSibling,
+                                    newElem));
+                    historyBrowserInterface.performCompoundUpdateCommand(cmd);
+
+                    attributePanel.setPreviewElement(newElem);
+                    //selectNewNode(newElem);
+                }
+
+                public void addNewElement(NodePickerEvent event) {
+                    String result = event.getResult();
+                    Element targetElement = (Element) event.getContextNode();
+                    Element newElem = wrapAndParse(result, targetElement);
+
+                    addChangesToHistory();
+
+                    historyBrowserInterface.appendChild(targetElement,
+                            newElem);
+
+                    attributePanel.setPreviewElement(newElem);
+                    //selectNewNode(newElem);
+                }
+
+                /**
+                 * Parses the given string into an element after editing, or
+                 * adding new element. The element that should be parsed has to
+                 * have all the active prefixes set, so it has to be wrapped
+                 * with the wrapper element that has all the mentioned prefixes
+                 * bound to the appopriate namespaces. Wrapper element string
+                 * has all the active prefixes set using the parentNode.
+                 *
+                 * @param toParse
+                 *            The string that should be parsed into svg element
+                 * @param startingNode
+                 *            The node from where to start looking the prefixes
+                 * @return The parsed element
+                 */
+                private Element wrapAndParse(String toParse, Node startingNode) {
+                    // Fill the prefix map
+                    Map prefixMap = new HashMap();
+                    int j = 0;
+                    for (Node currentNode = startingNode;
+                         currentNode != null;
+                         currentNode = currentNode.getParentNode()) {
+                        NamedNodeMap nMap = currentNode.getAttributes();
+                        for (int i = 0; nMap != null && i < nMap.getLength(); i++) {
+                            Attr atr = (Attr) nMap.item(i);
+                            String prefix = atr.getPrefix();
+                            String localName = atr.getLocalName();
+                            String namespaceURI = atr.getValue();
+                            if (prefix != null
+                                    && prefix.equals(SVGConstants.XMLNS_PREFIX)) {
+                                String attrName = SVGConstants.XMLNS_PREFIX
+                                        + ":" + localName;
+                                if (!prefixMap.containsKey(attrName)) {
+                                    prefixMap.put(attrName, namespaceURI);
+                                }
+                            }
+                            // Start from parentNode searching for the xmlns
+                            if ((j != 0 || currentNode == document
+                                    .getDocumentElement())
+                                    && atr.getNodeName().equals(
+                                            SVGConstants.XMLNS_PREFIX)
+                                    && !prefixMap
+                                            .containsKey(SVGConstants.XMLNS_PREFIX)) {
+                                prefixMap.put(SVGConstants.XMLNS_PREFIX, atr
+                                        .getNodeValue());
+                            }
+                        }
+                        j++;
+                    }
+                    Document doc = panel.document;
+                    SAXDocumentFactory df = new SAXDocumentFactory(
+                            doc.getImplementation(),
+                            XMLResourceDescriptor.getXMLParserClassName());
+                    URL urlObj = null;
+                    if (doc instanceof SVGOMDocument) {
+                        urlObj = ((SVGOMDocument) doc).getURLObject();
+                    }
+                    String uri = (urlObj == null) ? "" : urlObj.toString();
+                    Node node = DOMUtilities.parseXML(toParse, doc, uri,
+                            prefixMap, SVGConstants.SVG_SVG_TAG, df);
+                    return (Element) node.getFirstChild();
+                }
+
+                /**
+                 * Selects the given element. Needs to be wrapped with the
+                 * UpdateManager to wait for the previous command to be executed
+                 * by HistoryBrowser
+                 *
+                 * @param elem
+                 *            The element to select
+                 */
+                private void selectNewNode(final Element elem) {
+                    domViewerController.performUpdate(new Runnable() {
+                        public void run() {
+                            selectNode(elem);
+                        };
+                    });
+                }
+            });
+        }
 
         /**
-         * The element panel.
+         * The layout for the attribute panel.
          */
-        protected JPanel elementPanel = new JPanel(new GridLayout(2, 1));
+        protected GridBagConstraints attributePanelLayout =
+            new GridBagConstraints();
         {
-            JScrollPane pane = new JScrollPane();
-            pane.setBorder(BorderFactory.createCompoundBorder
-                           (BorderFactory.createEmptyBorder(2, 0, 2, 2),
-                            BorderFactory.createCompoundBorder
-                            (BorderFactory.createTitledBorder
-                             (BorderFactory.createEmptyBorder(),
-                              resources.getString("AttributesPanel.title")),
-                             BorderFactory.createLoweredBevelBorder())));
-            pane.getViewport().add(attributesTable);
+            attributePanelLayout.gridx = 1;
+            attributePanelLayout.gridy = 1;
+            attributePanelLayout.gridheight = 2;
+            attributePanelLayout.weightx = 1.0;
+            attributePanelLayout.weighty = 1.0;
+            attributePanelLayout.fill = GridBagConstraints.BOTH;
+        }
+
+        /**
+         * The layout for the properties table.
+         */
+        protected GridBagConstraints propertiesTableLayout =
+            new GridBagConstraints();
+        {
+            propertiesTableLayout.gridx = 1;
+            propertiesTableLayout.gridy = 3;
+            propertiesTableLayout.weightx = 1.0;
+            propertiesTableLayout.weighty = 1.0;
+            propertiesTableLayout.fill = GridBagConstraints.BOTH;
+        }
 
+        /**
+         * The element panel.
+         */
+        protected JPanel elementPanel = new JPanel(new GridBagLayout());
+        {
             JScrollPane pane2 = new JScrollPane();
-            pane2.setBorder(BorderFactory.createCompoundBorder
-                            (BorderFactory.createEmptyBorder(2, 0, 2, 2),
-                             BorderFactory.createCompoundBorder
-                             (BorderFactory.createTitledBorder
-                              (BorderFactory.createEmptyBorder(),
-                               resources.getString("CSSValuesPanel.title")),
-                              BorderFactory.createLoweredBevelBorder())));
+            pane2.setBorder(BorderFactory.createCompoundBorder(BorderFactory
+                    .createEmptyBorder(2, 0, 2, 2), BorderFactory
+                    .createCompoundBorder(BorderFactory.createTitledBorder(
+                            BorderFactory.createEmptyBorder(), resources
+                                    .getString("CSSValuesPanel.title")),
+                            BorderFactory.createLoweredBevelBorder())));
             pane2.getViewport().add(propertiesTable);
 
-            elementPanel.add(pane);
-            elementPanel.add(pane2);
+            // 2/3 attributePanel, 1/3 propertiesTable
+            elementPanel.add(attributePanel, attributePanelLayout);
+            elementPanel.add(pane2, propertiesTableLayout);
         }
 
         /**
          * The CharacterData panel text area.
          */
-        protected JTextArea characterData = new JTextArea();
+        protected class CharacterPanel extends JPanel {
+
+            /**
+             * Associated node.
+             */
+            protected Node node;
+
+            /**
+             * The text area.
+             */
+            protected JTextArea textArea = new JTextArea();
+
+            /**
+             * Constructor.
+             * @param layout    The LayoutManager
+             */
+            public CharacterPanel(BorderLayout layout) {
+                super(layout);
+            }
+
+            /**
+             * Gets the textArea.
+             *
+             * @return textArea
+             */
+            public JTextArea getTextArea() {
+                return textArea;
+            }
+
+            /**
+             * Sets the textArea.
+             *
+             * @param textArea
+             *            the text area to set
+             */
+            public void setTextArea(JTextArea textArea) {
+                this.textArea = textArea;
+            }
+
+            /**
+             * Gets the node.
+             *
+             * @return node
+             */
+            public Node getNode() {
+                return node;
+            }
+
+            /**
+             * Sets the node.
+             *
+             * @param node
+             *            the node to set
+             */
+            public void setNode(Node node) {
+                this.node = node;
+            }
+        }
 
         /**
          * The CharacterData node panel.
          */
-        protected JPanel characterDataPanel = new JPanel(new BorderLayout());
+        protected CharacterPanel characterDataPanel = new CharacterPanel(new BorderLayout());
         {
             characterDataPanel.setBorder
                 (BorderFactory.createCompoundBorder
@@ -275,9 +776,30 @@
                     resources.getString("CDataPanel.title")),
                    BorderFactory.createLoweredBevelBorder())));
             JScrollPane pane = new JScrollPane();
-            pane.getViewport().add(characterData);
+            JTextArea textArea = new JTextArea();
+            characterDataPanel.setTextArea(textArea);
+            pane.getViewport().add(textArea);
             characterDataPanel.add(pane);
-            characterData.setEditable(false);
+
+            textArea.setEditable(true);
+            textArea.addFocusListener(new FocusAdapter() {
+                public void focusLost(FocusEvent e) {
+                    if (canEdit()) {
+                        Node contextNode = characterDataPanel.getNode();
+                        String newValue = characterDataPanel.getTextArea()
+                                .getText();
+                        switch (contextNode.getNodeType()) {
+                        case Node.COMMENT_NODE:
+                        case Node.TEXT_NODE:
+                        case Node.CDATA_SECTION_NODE:
+                            addChangesToHistory();
+                            historyBrowserInterface.setNodeValue(contextNode,
+                                    newValue);
+                            break;
+                        }
+                    }
+                }
+            });
         }
 
         /**
@@ -313,13 +835,92 @@
                       (BorderFactory.createEmptyBorder(),
                        resources.getString("DOMViewerPanel.title")));
 
+            JToolBar tb =
+                new JToolBar(resources.getString("DOMViewerToolbar.name"));
+
+            // Undo
+            JButton undoButton = getButtonFactory().createJButton("UndoButton");
+            undoButton.setDisabledIcon
+                (new ImageIcon
+                    (getClass().getResource(resources.getString("UndoButton.disabledIcon"))));
+            DropDownComponent undoDD = new DropDownComponent(undoButton);
+            undoDD.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
+            undoDD.setMaximumSize(new Dimension(44, 25));
+            undoDD.setPreferredSize(new Dimension(44, 25));
+            tb.add(undoDD);
+            UndoPopUpMenuModel undoModel = new UndoPopUpMenuModel(undoDD
+                    .getPopupMenu(), historyBrowserInterface);
+            undoDD.getPopupMenu().setModel(undoModel);
+
+            // Redo
+            JButton redoButton = getButtonFactory().createJButton("RedoButton");
+            redoButton.setDisabledIcon
+                (new ImageIcon
+                    (getClass().getResource(resources.getString("RedoButton.disabledIcon"))));
+            DropDownComponent redoDD = new DropDownComponent(redoButton);
+            redoDD.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
+            redoDD.setMaximumSize(new Dimension(44, 25));
+            redoDD.setPreferredSize(new Dimension(44, 25));
+            tb.add(redoDD);
+            RedoPopUpMenuModel redoModel = new RedoPopUpMenuModel(redoDD
+                    .getPopupMenu(), historyBrowserInterface);
+            redoDD.getPopupMenu().setModel(redoModel);
+
+            // Capturing click toggle button
+            JToggleButton capturingClickButton = getButtonFactory()
+                    .createJToolbarToggleButton("CapturingClickButton");
+            capturingClickButton.setEnabled(true);
+            capturingClickButton.setPreferredSize(new Dimension(32, 25));
+            tb.add(capturingClickButton);
+
+            // Overlay toggle button
+            overlayButton =
+                getButtonFactory().createJToolbarToggleButton("OverlayButton");
+            overlayButton.setEnabled(true);
+            overlayButton.setPreferredSize(new Dimension(32, 25));
+            tb.add(overlayButton);
+            overlayButton.doClick();
+
+            // Add toolbar
+            add(tb, BorderLayout.NORTH);
+
+            // DOMDocumentTree
             TreeNode root;
             root = new DefaultMutableTreeNode
                 (resources.getString("EmptyDocument.text"));
-            tree = new JTree(root);
+            tree = new DOMDocumentTree
+                (root, new DOMViewerDOMDocumentTreeController());
             tree.setCellRenderer(new NodeRenderer());
             tree.putClientProperty("JTree.lineStyle", "Angled");
+            // Add the listeners to DOMDocumentTree
+            tree.addListener(new DOMDocumentTreeAdapter() {
 
+                public void dropCompleted(DOMDocumentTreeEvent event) {
+                    DropCompletedInfo info = (DropCompletedInfo) event
+                            .getSource();
+
+                    addChangesToHistory();
+
+                    AbstractCompoundCommand cmd = historyBrowserInterface
+                            .createNodesDroppedCommand(info.getChildren());
+
+                    int n = info.getChildren().size();
+                    for (int i = 0; i < n; i++) {
+                        Node node = (Node) info.getChildren().get(i);
+                        // If the node has its ancestor in selected nodes,
+                        // it should stay as it's child
+                        if (!DOMUtilities.isAnyNodeAncestorOf(info
+                                .getChildren(), node)) {
+                            cmd.addCommand(historyBrowserInterface
+                                    .createInsertChildCommand(info.getParent(),
+                                            info.getSibling(), node));
+                        }
+                    }
+                    historyBrowserInterface.performCompoundUpdateCommand(cmd);
+                }
+            });
+            tree.addTreeSelectionListener(new DOMTreeSelectionListener());
+            tree.addMouseListener(new TreePopUpListener());
             JScrollPane treePane = new JScrollPane();
             treePane.setBorder(BorderFactory.createCompoundBorder
                                (BorderFactory.createEmptyBorder(2, 2, 2, 0),
@@ -336,8 +937,6 @@
             int loc = resources.getInteger("SplitPane.dividerLocation");
             splitPane.setDividerLocation(loc);
             add(splitPane);
-
-            tree.addTreeSelectionListener(new DOMTreeSelectionListener());
         }
 
         /**
@@ -351,10 +950,23 @@
          * Sets the document to display and its ViewCSS.
          */
         public void setDocument(Document doc, ViewCSS view) {
+            if (document != null) {
+                if (document != doc) {
+                    removeDomMutationListeners(document);
+                    addDomMutationListeners(doc);
+                    removeCapturingListener(document);
+                    addCapturingListener(doc);
+                }
+            }
+            else {
+                addDomMutationListeners(doc);
+                addCapturingListener(doc);
+            }
+            resetHistory();
             document = doc;
-            viewCSS  = view;
+            viewCSS = view;
             TreeNode root = createTree(doc, showWhitespace);
-            ((DefaultTreeModel)tree.getModel()).setRoot(root);
+            ((DefaultTreeModel) tree.getModel()).setRoot(root);
             if (rightPanel.getComponentCount() != 0) {
                 rightPanel.remove(0);
                 splitPane.revalidate();
@@ -363,6 +975,426 @@
         }
 
         /**
+         * Registers DOM Mutation Listener on the given document.
+         *
+         * @param doc
+         *            The given document
+         */
+        protected void addDomMutationListeners(Document doc) {
+            EventTarget target = (EventTarget) doc;
+            nodeInsertion = new NodeInsertionHandler();
+            target.addEventListener(NODE_INSERTED, nodeInsertion, true);
+            nodeRemoval = new NodeRemovalHandler();
+            target.addEventListener(NODE_REMOVED, nodeRemoval, true);
+            attrModification = new AttributeModificationHandler();
+            target.addEventListener(ATTRIBUTE_MODIFIED, attrModification,
+                    true);
+            charDataModification = new CharDataModificationHandler();
+            target.addEventListener(CHAR_DATA_MODIFIED,
+                    charDataModification, true);
+        }
+
+        /**
+         * Removes previously registered mutation listeners from the document.
+         *
+         * @param doc
+         *            The document
+         */
+        protected void removeDomMutationListeners(Document doc) {
+            if (doc != null) {
+                EventTarget target = (EventTarget) doc;
+                target.removeEventListener(NODE_INSERTED, nodeInsertion, true);
+                target.removeEventListener(NODE_REMOVED, nodeRemoval, true);
+                target.removeEventListener(ATTRIBUTE_MODIFIED,
+                        attrModification, true);
+                target.removeEventListener(CHAR_DATA_MODIFIED,
+                        charDataModification, true);
+            }
+        }
+
+        /**
+         * Registers capturing "click" listener on the document element of the
+         * given document.
+         *
+         * @param doc
+         *            The given document
+         */
+        protected void addCapturingListener(Document doc) {
+            EventTarget target = (EventTarget) doc.getDocumentElement();
+            capturingListener = new CapturingClickHandler();
+            target.addEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE,
+                    capturingListener, true);
+        }
+
+        /**
+         * Removes previously registered capturing "click" event listener from
+         * the document element of the given document.
+         *
+         * @param doc
+         *            The given document
+         */
+        protected void removeCapturingListener(Document doc) {
+            if (doc != null) {
+                EventTarget target = (EventTarget) doc.getDocumentElement();
+                target.removeEventListener(SVGConstants.SVG_CLICK_EVENT_TYPE,
+                        capturingListener, true);
+            }
+        }
+
+        /**
+         * Handles "DOMNodeInserted" event.
+         */
+        protected class NodeInsertionHandler implements EventListener {
+
+            public void handleEvent(final Event evt) {
+                Runnable runnable = new Runnable() {
+                    public void run() {
+                        MutationEvent mevt = (MutationEvent) evt;
+                        Node targetNode = (Node) mevt.getTarget();
+                        DefaultMutableTreeNode parentNode = findNode(tree,
+                                targetNode.getParentNode());
+                        DefaultMutableTreeNode insertedNode =
+                            (DefaultMutableTreeNode)
+                            createTree(targetNode, showWhitespace);
+                        DefaultTreeModel model =
+                            (DefaultTreeModel) tree.getModel();
+                        DefaultMutableTreeNode newParentNode =
+                            (DefaultMutableTreeNode)
+                            createTree(targetNode.getParentNode(),
+                                       showWhitespace);
+                        // Finds where to insert the node in the tree
+                        int index = findIndexToInsert(parentNode, newParentNode);
+                        if (index != -1) {
+                            model.insertNodeInto
+                                (insertedNode, parentNode, index);
+                        }
+                        attributePanel.updateOnDocumentChange(mevt.getType(),
+                                                              targetNode);
+                    }
+                };
+                refreshGUI(runnable);
+                registerDocumentChange((MutationEvent)evt);
+            }
+
+            /**
+             * Compares the children of the two tree nodes and returns the index
+             * of the first difference.
+             *
+             * @param parentNode
+             *            The old tree node
+             * @param newParentNode
+             *            The new tree node
+             * @return first child that differs
+             */
+            protected int findIndexToInsert
+                    (DefaultMutableTreeNode parentNode,
+                     DefaultMutableTreeNode newParentNode) {
+                int index = -1;
+                if (parentNode == null || newParentNode == null) {
+                    return index;
+                }
+                // Finds the index where to insert new node
+                Enumeration oldChildren = parentNode.children();
+                Enumeration newChildren = newParentNode.children();
+                int count = 0;
+                while (oldChildren.hasMoreElements()) {
+                    // Current DefaultMutableTreeNode node being processed
+                    DefaultMutableTreeNode currentOldChild =
+                        (DefaultMutableTreeNode) oldChildren.nextElement();
+                    DefaultMutableTreeNode currentNewChild =
+                        (DefaultMutableTreeNode) newChildren.nextElement();
+                    Node oldChild =
+                        ((NodeInfo) currentOldChild.getUserObject()).getNode();
+                    Node newChild =
+                        ((NodeInfo) currentNewChild.getUserObject()).getNode();
+                    if (oldChild != newChild) {
+                        return count;
+                    }
+                    count++;
+                }
+                return count;
+            }
+        }
+
+        /**
+         * Handles "DOMNodeRemoved" event.
+         */
+        protected class NodeRemovalHandler implements EventListener {
+
+            public void handleEvent(final Event evt) {
+                Runnable runnable = new Runnable() {
+                    public void run() {
+                        MutationEvent mevt = (MutationEvent) evt;
+                        Node targetNode = (Node) mevt.getTarget();
+                        DefaultMutableTreeNode treeNode = findNode(tree,
+                                targetNode);
+                        DefaultTreeModel model = (DefaultTreeModel) tree
+                                .getModel();
+                        if (treeNode != null) {
+                            model.removeNodeFromParent(treeNode);
+                        }
+                        attributePanel.updateOnDocumentChange(mevt.getType(),
+                                targetNode);
+                    }
+                };
+                refreshGUI(runnable);
+                registerDocumentChange((MutationEvent)evt);
+            }
+        }
+
+        /**
+         * Handles "DOMAttrModified" event.
+         */
+        protected class AttributeModificationHandler implements EventListener {
+
+            public void handleEvent(final Event evt) {
+                Runnable runnable = new Runnable() {
+                    public void run() {
+                        MutationEvent mevt = (MutationEvent) evt;
+                        Element targetElement = (Element) mevt.getTarget();
+                        // Repaint the target node
+                        DefaultTreeModel model = (DefaultTreeModel) tree
+                                .getModel();
+
+                        model.nodeChanged(findNode(tree, targetElement));
+                        attributePanel.updateOnDocumentChange(mevt.getType(),
+                                targetElement);
+                    }
+                };
+                refreshGUI(runnable);
+                registerDocumentChange((MutationEvent) evt);
+            }
+        }
+
+        /**
+         * Handles "DOMCharacterDataModified" event.
+         */
+        protected class CharDataModificationHandler implements EventListener {
+            public void handleEvent(final Event evt) {
+                Runnable runnable = new Runnable() {
+                    public void run() {
+                        MutationEvent mevt = (MutationEvent) evt;
+                        Node targetNode = (Node) mevt.getTarget();
+                        if (characterDataPanel.getNode() == targetNode) {
+                            characterDataPanel.getTextArea().setText(
+                                    targetNode.getNodeValue());
+                            attributePanel.updateOnDocumentChange(mevt
+                                    .getType(), targetNode);
+                        }
+                    }
+                };
+                refreshGUI(runnable);
+                if (characterDataPanel.getNode() == evt.getTarget()) {
+                    registerDocumentChange((MutationEvent) evt);
+                }
+            }
+        }
+
+        /**
+         * Checks whether the DOMViewer can be used to edit the document and if
+         * true refreshes the DOMViewer after the DOM Mutation event occured.
+         *
+         * @param runnable
+         *            The runnable to invoke for refresh
+         */
+        protected void refreshGUI(Runnable runnable) {
+            if (canEdit()) {
+                try {
+                    SwingUtilities.invokeAndWait(runnable);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                } catch (InvocationTargetException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        /**
+         * Adds the "DOMNodeInserted" Mutation event to current history
+         * browser's interface compound command
+         *
+         * @param mevt
+         *            The Mutation Event
+         */
+        protected void registerNodeInserted(MutationEvent mevt) {
+            Node targetNode = (Node) mevt.getTarget();
+            historyBrowserInterface.addToCurrentCompoundCommand(
+                historyBrowserInterface.createNodeInsertedCommand(
+                        targetNode.getParentNode(),
+                            targetNode.getNextSibling(),targetNode));
+        }
+
+        /**
+         * Adds the "DOMNodeRemoved" Mutation event to current history browser's
+         * interface compound command
+         *
+         * @param mevt
+         *            The Mutation Event
+         */
+        protected void registerNodeRemoved(MutationEvent mevt) {
+            Node targetNode = (Node) mevt.getTarget();
+            historyBrowserInterface.addToCurrentCompoundCommand
+                (historyBrowserInterface.createNodeRemovedCommand
+                    (mevt.getRelatedNode(),
+                     targetNode.getNextSibling(),
+                     targetNode));
+        }
+
+        /**
+         * Adds the "DOMAttrModified" Mutation event, of the
+         * MutationEvent.ADDITION type to current history browser's
+         * interface compound command
+         *
+         * @param mevt
+         *            The Mutation Event
+         */
+
+        protected void registerAttributeAdded(MutationEvent mevt) {
+            Element targetElement = (Element) mevt.getTarget();
+            historyBrowserInterface.addToCurrentCompoundCommand
+                (historyBrowserInterface.createAttributeAddedCommand
+                    (targetElement,
+                     mevt.getAttrName(),
+                     mevt.getNewValue(),
+                     null));
+        }
+
+        /**
+         * Adds the "DOMAttrModified" Mutation event, of the
+         * MutationEvent.REMOVAL type to current history browser's
+         * interface compound command
+         *
+         * @param mevt
+         *            The Mutation Event
+         */
+
+        protected void registerAttributeRemoved(MutationEvent mevt) {
+            Element targetElement = (Element) mevt.getTarget();
+            historyBrowserInterface.addToCurrentCompoundCommand
+                (historyBrowserInterface.createAttributeRemovedCommand
+                    (targetElement,
+                     mevt.getAttrName(),
+                     mevt.getPrevValue(),
+                     null));
+        }
+
+        /**
+         * Adds the "DOMAttrModified" Mutation event, of the
+         * MutationEvent.MODIFICATION type to current history browser's
+         * interface compound command
+         *
+         * @param mevt
+         *            The Mutation Event
+         */
+        protected void registerAttributeModified(MutationEvent mevt) {
+            Element targetElement = (Element) mevt.getTarget();
+            historyBrowserInterface.addToCurrentCompoundCommand
+                (historyBrowserInterface.createAttributeModifiedCommand
+                    (targetElement,
+                     mevt.getAttrName(),
+                     mevt.getPrevValue(),
+                     mevt.getNewValue(),
+                     null));
+        }
+
+        /**
+         * Checks what type of the "DOMAttrModified" mutation event occured, and
+         * invokes the appropriate method to register the change.
+         *
+         * @param mevt
+         *            The Mutation Event
+         */
+        protected void registerAttributeChanged(MutationEvent mevt) {
+            switch (mevt.getAttrChange()) {
+                case MutationEvent.ADDITION:
+                    registerAttributeAdded(mevt);
+                    break;
+                case MutationEvent.REMOVAL:
+                    registerAttributeRemoved(mevt);
+                    break;
+                case MutationEvent.MODIFICATION:
+                    registerAttributeModified(mevt);
+                    break;
+                default:
+                    registerAttributeModified(mevt);
+                    break;
+            }
+        }
+
+        /**
+         * Adds the "DOMCharDataModified" Mutation event to current history
+         * browser's interface compound command
+         *
+         * @param mevt
+         *            The Mutation Event
+         */
+        protected void registerCharDataModified(MutationEvent mevt) {
+            Node targetNode = (Node) mevt.getTarget();
+            historyBrowserInterface.addToCurrentCompoundCommand
+                (historyBrowserInterface.createCharDataModifiedCommand
+                    (targetNode,
+                     mevt.getPrevValue(),
+                     mevt.getNewValue()));
+        }
+
+        /**
+         * Checks if the document change that occured should be registered. If
+         * the document change has occured out of the DOMViewer, the state of
+         * the history browser should be HistoryBrowserState.IDLE. Otherwise, if
+         * the DOMViewer caused the change, one of the following states is
+         * active: HistoryBrowserState.EXECUTING, HistoryBrowserState.UNDOING,
+         * HistoryBrowserState.REDOING. This method should be invoked while the
+         * document change is occuring (in mutation event handlers), otherwise,
+         * if put in another thread, the state flag becomes invalid. Also checks
+         * if the DOMViewer can be used to edit the document. If not, then the
+         * change shouldn't be registered by the HistoryBrowser
+         *
+         * @return True if the DOMViewer can edit the document and the history
+         *         browser state is IDLE at the moment
+         */
+        protected boolean shouldRegisterDocumentChange() {
+            return canEdit() &&
+                historyBrowserInterface.getHistoryBrowser().getState()
+                    == HistoryBrowser.IDLE;
+        }
+
+        /**
+         * Puts the document change in the current history browser's interface
+         * compound command if the document change occured outside of the
+         * DOMViewer.
+         *
+         * @param mevt
+         *            The info on the event. Use to describe the document change
+         *            to the history browser
+         */
+        protected void registerDocumentChange(MutationEvent mevt) {
+            if (shouldRegisterDocumentChange()) {
+                String type = mevt.getType();
+                if (type.equals(NODE_INSERTED)) {
+                    registerNodeInserted(mevt);
+                } else if (type.equals(NODE_REMOVED)) {
+                    registerNodeRemoved(mevt);
+                } else if (type.equals(ATTRIBUTE_MODIFIED)) {
+                    registerAttributeChanged(mevt);
+                } else if (type.equals(CHAR_DATA_MODIFIED)) {
+                    registerCharDataModified(mevt);
+                }
+            }
+        }
+
+        /**
+         * Handles the capturing "mouseclick" event.
+         */
+        protected class CapturingClickHandler implements EventListener {
+            public void handleEvent(Event evt) {
+                if (isCapturingClickEnabled) {
+                    Element targetElement = (Element) evt.getTarget();
+                    selectNode(targetElement);
+                }
+            }
+        }
+
+        /**
          * Creates a swing tree from a DOM document.
          */
         protected MutableTreeNode createTree(Node node,
@@ -413,17 +1445,332 @@
         }
 
         /**
+         * Finds and returns the node in the tree that represents the given node
+         * in the document.
+         *
+         * @param theTree
+         *            The given JTree
+         * @param node
+         *            The given org.w3c.dom.Node
+         * @return Node or null if not found
+         */
+        protected DefaultMutableTreeNode findNode(JTree theTree, Node node) {
+            DefaultMutableTreeNode root = (DefaultMutableTreeNode) theTree
+                    .getModel().getRoot();
+            Enumeration treeNodes = root.breadthFirstEnumeration();
+            while (treeNodes.hasMoreElements()) {
+                DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) treeNodes
+                        .nextElement();
+                NodeInfo userObject = (NodeInfo) currentNode.getUserObject();
+                if (userObject.getNode() == node) {
+                    return currentNode;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Finds and selects the given node in the document tree.
+         *
+         * @param targetNode
+         *            The node to be selected
+         */
+        public void selectNode(final Node targetNode) {
+            SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    DefaultMutableTreeNode node = findNode(tree, targetNode);
+                    if (node != null) {
+                        TreeNode[] path = node.getPath();
+                        TreePath tp = new TreePath(path);
+                        // Changes the selection
+                        tree.setSelectionPath(tp);
+                        // Expands and scrolls the TreePath to visible if
+                        // needed
+                        tree.scrollPathToVisible(tp);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Tree popup listener.
+         */
+        protected class TreePopUpListener extends MouseAdapter {
+
+            /**
+             * The actual pop-up menu.
+             */
+            protected JPopupMenu treePopupMenu;
+
+            /**
+             * Creates new pop up listener.
+             */
+            public TreePopUpListener() {
+                treePopupMenu = new JPopupMenu();
+
+                // "Insert new node" menu
+                treePopupMenu.add(createTemplatesMenu(resources
+                        .getString("ContextMenuItem.insertNewNode")));
+
+                // "Create new element..." item
+                JMenuItem item = new JMenuItem(resources
+                        .getString("ContextMenuItem.createNewElement"));
+                treePopupMenu.add(item);
+                item.addActionListener(new TreeNodeAdder());
+
+                // "Remove selection" item
+                item = new JMenuItem(resources
+                        .getString("ContextMenuItem.removeSelection"));
+                item.addActionListener(new TreeNodeRemover());
+                treePopupMenu.add(item);
+            }
+
+            public void mouseReleased(MouseEvent e) {
+                // Show the pop-up component
+                if (e.isPopupTrigger() && e.getClickCount() == 1) {
+                    if (tree.getSelectionPaths() != null) {
+                        showPopUp(e);
+                    }
+                }
+            }
+
+            // Handles selection on the tree
+            public void mousePressed(MouseEvent e) {
+                JTree sourceTree = (JTree) e.getSource();
+                TreePath targetPath = sourceTree.getPathForLocation(e.getX(), e
+                        .getY());
+                if (!e.isControlDown() && !e.isShiftDown()) {
+                    sourceTree.setSelectionPath(targetPath);
+                } else {
+                    sourceTree.addSelectionPath(targetPath);
+                }
+                // Show the pop-up component
+                if (e.isPopupTrigger() && e.getClickCount() == 1) {
+                    showPopUp(e);
+                }
+            }
+
+            /**
+             * Shows this popup menu if the DOMViewer can be used to edti the
+             * document.
+             */
+            private void showPopUp(MouseEvent e) {
+                if (canEdit()) {
+                    TreePath path = tree.getPathForLocation(e.getX(), e.getY());
+                    // Don't show the pop up menu for the root element
+                    if (path != null && path.getPathCount() > 1) {
+                        treePopupMenu.show((Component) e.getSource(), e.getX(),
+                                e.getY());
+                    }
+                }
+            }
+        }
+
+        /**
+         * Handles tree pop-up menu action for adding new node.
+         */
+        protected class TreeNodeAdder implements ActionListener {
+
+            public void actionPerformed(ActionEvent e) {
+                // Prompt for the node name
+                NameEditorDialog nameEditorDialog = new NameEditorDialog(
+                        DOMViewer.this);
+                nameEditorDialog.setLocationRelativeTo(DOMViewer.this);
+                int results = nameEditorDialog.showDialog();
+                if (results == NameEditorDialog.OK_OPTION) {
+                    Element elementToAdd = document.createElementNS(
+                            SVGConstants.SVG_NAMESPACE_URI, nameEditorDialog
+                                    .getResults());
+                    if (rightPanel.getComponentCount() != 0) {
+                        rightPanel.remove(0);
+                    }
+                    rightPanel.add(elementPanel);
+
+                    // Pass the parent node as the selected one
+                    TreePath[] treePaths = tree.getSelectionPaths();
+                    if (treePaths != null) {
+                        TreePath treePath = treePaths[treePaths.length - 1];
+                        DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath
+                                .getLastPathComponent();
+                        NodeInfo nodeInfo = (NodeInfo) node.getUserObject();
+                        // Enter the attributePanel 'add new element' mode
+                        attributePanel.enterAddNewElementMode(elementToAdd,
+                                nodeInfo.getNode());
+                    }
+                }
+            }
+        }
+
+        /**
+         * Parser for the Element template.
+         */
+        protected class NodeTemplateParser implements ActionListener {
+
+            /**
+             * The string to parse.
+             */
+            protected String toParse;
+
+            /**
+             * The node type.
+             */
+            protected short nodeType;
+
+            /**
+             * Constructor.
+             *
+             * @param toParse
+             *            The string to parse
+             */
+            public NodeTemplateParser(String toParse, short nodeType) {
+                this.toParse = toParse;
+                this.nodeType = nodeType;
+            }
+
+            public void actionPerformed(ActionEvent e) {
+                Node nodeToAdd = null;
+                switch (nodeType) {
+                case Node.ELEMENT_NODE:
+                    URL urlObj = null;
+                    if (document instanceof SVGOMDocument) {
+                        urlObj = ((SVGOMDocument) document).getURLObject();
+                    }
+                    String uri = (urlObj == null) ? "" : urlObj.toString();
+                    Map prefixes = new HashMap();
+                    prefixes.put(SVGConstants.XMLNS_PREFIX,
+                            SVGConstants.SVG_NAMESPACE_URI);
+                    prefixes.put(SVGConstants.XMLNS_PREFIX + ":"
+                            + SVGConstants.XLINK_PREFIX,
+                            SVGConstants.XLINK_NAMESPACE_URI);
+                    SAXDocumentFactory df = new SAXDocumentFactory(document
+                            .getImplementation(), XMLResourceDescriptor
+                            .getXMLParserClassName());
+                    DocumentFragment documentFragment = (DocumentFragment) DOMUtilities
+                            .parseXML(toParse, document, uri, prefixes,
+                                    SVGConstants.SVG_SVG_TAG, df);
+                    nodeToAdd = documentFragment.getFirstChild();
+                    break;
+                case Node.TEXT_NODE:
+                    nodeToAdd = document.createTextNode(toParse);
+                    break;
+                case Node.COMMENT_NODE:
+                    nodeToAdd = document.createComment(toParse);
+                    break;
+                case Node.CDATA_SECTION_NODE:
+                    nodeToAdd = document.createCDATASection(toParse);
+                }
+
+                // Append the new node to the parentNode
+                TreePath[] treePaths = tree.getSelectionPaths();
+                if (treePaths != null) {
+                    TreePath treePath = treePaths[treePaths.length - 1];
+                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath
+                            .getLastPathComponent();
+                    NodeInfo nodeInfo = (NodeInfo) node.getUserObject();
+
+                    addChangesToHistory();
+
+                    historyBrowserInterface.appendChild(nodeInfo.getNode(),
+                            nodeToAdd);
+                }
+            }
+        }
+
+        /**
+         * Creates JMenu menu using {@link NodeTemplates}.
+         *
+         * @param name
+         *            The name of the submenu
+         * @return The JMenu submenu
+         */
+        protected JMenu createTemplatesMenu(String name) {
+            NodeTemplates templates = new NodeTemplates();
+            JMenu submenu = new JMenu(name);
+
+            // Create submenus
+            HashMap menuMap = new HashMap();
+            ArrayList categoriesList = templates.getCategories();
+            int n = categoriesList.size();
+            for (int i = 0; i < n; i++) {
+                String category = categoriesList.get(i).toString();
+                JMenu currentMenu = new JMenu(category);
+                submenu.add(currentMenu);
+                // Add submenus to the map
+                menuMap.put(category, currentMenu);
+            }
+
+            // Sort the value list and then iterate through node templates
+            Collection values = templates.getNodeTemplatesMap().values();
+            List valuesAsList = Collections.list(Collections.enumeration(values));
+            Collections.sort(valuesAsList, new Comparator() {
+                public int compare(Object o1, Object o2) {
+                    NodeTemplateDescriptor n1 = (NodeTemplateDescriptor) o1;
+                    NodeTemplateDescriptor n2 = (NodeTemplateDescriptor) o2;
+                    return n1.getName().compareTo(n2.getName());
+                }
+            });
+            Iterator iter = valuesAsList.iterator();
+            while (iter.hasNext()) {
+                NodeTemplateDescriptor desc = (NodeTemplateDescriptor) iter
+                        .next();
+                String toParse = desc.getXmlValue();
+                short nodeType = desc.getType();
+                String nodeCategory = desc.getCategory();
+                JMenuItem currentItem = new JMenuItem(desc.getName());
+                currentItem.addActionListener(new NodeTemplateParser(toParse,
+                        nodeType));
+                JMenu currentSubmenu = (JMenu)menuMap.get(nodeCategory);
+                currentSubmenu.add(currentItem);
+            }
+            return submenu;
+        }
+
+        /**
+         * Handles tree pop-up menu action for removing nodes.
+         */
+        protected class TreeNodeRemover implements ActionListener {
+
+            public void actionPerformed(ActionEvent e) {
+                addChangesToHistory();
+
+                AbstractCompoundCommand cmd = historyBrowserInterface
+                        .createRemoveSelectedTreeNodesCommand(null);
+                TreePath[] treePaths = tree.getSelectionPaths();
+                for (int i = 0; treePaths != null && i < treePaths.length; i++) {
+                    TreePath treePath = treePaths[i];
+                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath
+                            .getLastPathComponent();
+                    NodeInfo nodeInfo = (NodeInfo) node.getUserObject();
+                    if (DOMUtilities.isParentOf(nodeInfo.getNode(),
+                            nodeInfo.getNode().getParentNode())) {
+                        cmd.addCommand(historyBrowserInterface
+                                .createRemoveChildCommand(nodeInfo.getNode()
+                                        .getParentNode(), nodeInfo.getNode()));
+                    }
+                }
+                historyBrowserInterface.performCompoundUpdateCommand(cmd);
+            }
+        }
+
+        /**
          * To listen to the tree selection.
          */
         protected class DOMTreeSelectionListener
-            implements TreeSelectionListener {
+                implements TreeSelectionListener {
+
             /**
              * Called when the selection changes.
              */
             public void valueChanged(TreeSelectionEvent ev) {
+                // Manages the selection overlay
+                if (elementOverlayManager != null) {
+                    handleElementSelection(ev);
+                }
+
                 DefaultMutableTreeNode mtn;
-                mtn =
-                    (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
+                mtn = (DefaultMutableTreeNode)
+                    tree.getLastSelectedPathComponent();
+
                 if (mtn == null) {
                     return;
                 }
@@ -434,25 +1781,29 @@
 
                 Object nodeInfo = mtn.getUserObject();
                 if (nodeInfo instanceof NodeInfo) {
-                    Node node = ((NodeInfo)nodeInfo).getNode();
+                    Node node = ((NodeInfo) nodeInfo).getNode();
                     switch (node.getNodeType()) {
                     case Node.DOCUMENT_NODE:
                         documentInfo.setText
-                            (createDocumentText((Document)node));
+                            (createDocumentText((Document) node));
                         rightPanel.add(documentInfoPanel);
                         break;
                     case Node.ELEMENT_NODE:
-                        attributesTable.setModel(new NodeAttributesModel(node));
                         propertiesTable.setModel(new NodeCSSValuesModel(node));
+                        attributePanel.promptForChanges();
+                        attributePanel.setPreviewElement((Element) node);
                         rightPanel.add(elementPanel);
                         break;
                     case Node.COMMENT_NODE:
                     case Node.TEXT_NODE:
                     case Node.CDATA_SECTION_NODE:
-                        characterData.setText(node.getNodeValue());
+                        characterDataPanel.setNode(node);
+                        characterDataPanel.getTextArea().setText
+                            (node.getNodeValue());
                         rightPanel.add(characterDataPanel);
                     }
                 }
+
                 splitPane.revalidate();
                 splitPane.repaint();
             }
@@ -473,31 +1824,61 @@
                 }
                 return result;
             }
+
+            /**
+             * Processes element selection overlay.
+             *
+             * @param ev
+             *            Tree selection event
+             */
+            protected void handleElementSelection(TreeSelectionEvent ev) {
+                TreePath[] paths = ev.getPaths();
+                for (int i = 0; i < paths.length; i++) {
+                    TreePath path = paths[i];
+                    DefaultMutableTreeNode mtn =
+                        (DefaultMutableTreeNode) path.getLastPathComponent();
+                    Object nodeInfo = mtn.getUserObject();
+                    if (nodeInfo instanceof NodeInfo) {
+                        Node node = ((NodeInfo) nodeInfo).getNode();
+                        if (node.getNodeType() == Node.ELEMENT_NODE) {
+                            if (ev.isAddedPath(path)) {
+                                elementOverlayManager.addElement
+                                    ((Element) node);
+                            } else {
+                                elementOverlayManager.removeElement
+                                    ((Element) node);
+                            }
+                        }
+                    }
+                }
+                elementOverlayManager.repaint();
+            }
         }
 
         /**
          * To render the tree nodes.
          */
         protected class NodeRenderer extends DefaultTreeCellRenderer {
+
             /**
              * The icon used to represent elements.
              */
-            ImageIcon elementIcon;
+            protected ImageIcon elementIcon;
 
             /**
              * The icon used to represent comments.
              */
-            ImageIcon commentIcon;
+            protected ImageIcon commentIcon;
 
             /**
              * The icon used to represent processing instructions.
              */
-            ImageIcon piIcon;
+            protected ImageIcon piIcon;
 
             /**
              * The icon used to represent text.
              */
-            ImageIcon textIcon;
+            protected ImageIcon textIcon;
 
             /**
              * Creates a new NodeRenderer object.
@@ -560,71 +1941,10 @@
         }
 
         /**
-         * To display the attributes of a DOM node attributes in a table.
-         */
-        protected class NodeAttributesModel extends AbstractTableModel {
-            /**
-             * The node.
-             */
-            protected Node node;
-
-            /**
-             * Creates a new NodeAttributesModel object.
-             */
-            public NodeAttributesModel(Node n) {
-                node = n;
-            }
-
-            /**
-             * Returns the name to give to a column.
-             */
-            public String getColumnName(int col) {
-                if (col == 0) {
-                    return resources.getString("AttributesTable.column1");
-                } else {
-                    return resources.getString("AttributesTable.column2");
-                }
-            }
-
-            /**
-             * Returns the number of columns in the table.
-             */
-            public int getColumnCount() {
-                return 2;
-            }
-
-            /**
-             * Returns the number of rows in the table.
-             */
-            public int getRowCount() {
-                return node.getAttributes().getLength();
-            }
-
-            /**
-             * Whether the given cell is editable.
-             */
-            public boolean isCellEditable(int row, int col) {
-                return false;
-            }
-
-            /**
-             * Returns the value of the given cell.
-             */
-            public Object getValueAt(int row, int col) {
-                NamedNodeMap map = node.getAttributes();
-                Node n = map.item(row);
-                if (col == 0) {
-                    return n.getNodeName();
-                } else {
-                    return n.getNodeValue();
-                }
-            }
-        }
-
-        /**
          * To display the CSS properties of a DOM node in a table.
          */
         protected class NodeCSSValuesModel extends AbstractTableModel {
+
             /**
              * The node.
              */
@@ -735,7 +2055,8 @@
          */
         public String toString() {
             if (node instanceof Element) {
-                String id = ((Element)node).getAttribute("id");
+                Element e = (Element) node;
+                String id = e.getAttribute(SVGConstants.SVG_ID_ATTRIBUTE);
                 if (id.length() != 0) {
                     return node.getNodeName() + " \"" + id + "\"";
                 }
@@ -748,6 +2069,7 @@
      * To store the node information for a shadow tree.
      */
     protected static class ShadowNodeInfo extends NodeInfo {
+
         /**
          * Creates a new ShadowNodeInfo object.
          */
@@ -768,6 +2090,7 @@
      * selected content.
      */
     protected static class ContentNodeInfo extends NodeInfo {
+
         /**
          * Creates a new ContentNodeInfo object.
          */

Added: xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewerController.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewerController.java?rev=594367&view=auto
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewerController.java (added)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/apps/svgbrowser/DOMViewerController.java Mon Nov 12 16:40:53 2007
@@ -0,0 +1,77 @@
+/*
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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.apache.batik.apps.svgbrowser;
+
+import org.apache.batik.swing.gvt.Overlay;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+/**
+ * Provides the information needed for the DOMViewer to show and edit the
+ * document.
+ */
+public interface DOMViewerController {
+
+    /**
+     * Performs the document update.
+     *
+     * @param runnable
+     *            The runnable that contains the update
+     */
+    void performUpdate(Runnable r);
+
+    /**
+     * Creates the ElementSelectionManager to manage the selection overlay on
+     * the canvas.
+     *
+     * @return ElementSelectionManager
+     */
+    ElementOverlayManager createSelectionManager();
+
+    /**
+     * Removes the given selection overlay from the canvas.
+     *
+     * @param selectionOverlay
+     *            The given selection overlay
+     */
+    void removeSelectionOverlay(Overlay selectionOverlay);
+
+    /**
+     * Gets the document for the DOMViewer to show.
+     *
+     * @return the document
+     */
+    Document getDocument();
+
+    /**
+     * Selects the given node in the DOMViewer's document tree.
+     *
+     * @param node
+     *            The node to select
+     */
+    void selectNode(Node node);
+
+    /**
+     * Checks whether the DOMViewer should be allowed to edit the document.
+     *
+     * @return True for non static documents, when UpdateManager is available
+     */
+    boolean canEdit();
+}