org.docx4all.swing.text.WordMLEditorKit.java Source code

Java tutorial

Introduction

Here is the source code for org.docx4all.swing.text.WordMLEditorKit.java

Source

/*
 *  Copyright 2007, Plutext Pty Ltd.
 *   
 *  This file is part of Docx4all.
    
Docx4all is free software: you can redistribute it and/or modify
it under the terms of version 3 of the GNU General Public License 
as published by the Free Software Foundation.
    
Docx4all is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License   
along with Docx4all.  If not, see <http://www.gnu.org/licenses/>.
    
 */

package org.docx4all.swing.text;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.CaretEvent;
import javax.swing.event.EventListenerList;
import javax.swing.plaf.basic.BasicTextUI;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.Position;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.TextAction;
import javax.swing.text.View;
import javax.swing.text.DefaultStyledDocument.ElementSpec;
import javax.xml.bind.JAXBException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import net.sf.vfsjfilechooser.utils.VFSUtils;

import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.docx4all.swing.WordMLTextPane;
import org.docx4all.swing.event.InputAttributeEvent;
import org.docx4all.swing.event.InputAttributeListener;
import org.docx4all.ui.main.Constants;
import org.docx4all.ui.menu.HyperlinkMenu;
import org.docx4all.util.DocUtil;
import org.docx4all.util.SwingUtil;
import org.docx4all.util.XmlUtil;
import org.docx4all.xml.DocumentML;
import org.docx4all.xml.ElementML;
import org.docx4all.xml.ElementMLFactory;
import org.docx4all.xml.HyperlinkML;
import org.docx4all.xml.ParagraphML;
import org.docx4all.xml.RunContentML;
import org.docx4all.xml.RunDelML;
import org.docx4all.xml.RunInsML;
import org.docx4all.xml.RunML;
import org.docx4all.xml.SdtBlockML;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.plutext.client.Mediator;
import org.plutext.client.Util;

public class WordMLEditorKit extends DefaultEditorKit {
    private static Logger log = LoggerFactory.getLogger(WordMLEditorKit.class);

    /**
     * Name of the action to place a soft line break into
     * the document.  If there is a selection, it is removed before
     * the break is added.
     * @see #getActions
     */
    public static final String insertSoftBreakAction = "insert-soft-break";

    public static final String enterKeyTypedAction = "enter-key-typed-action";

    public static final String fontBoldAction = "font-bold";

    public static final String fontItalicAction = "font-italic";

    public static final String fontUnderlineAction = "font-underline";

    public static final String acceptRevisionAction = "accept-revision";

    public static final String acceptNonConflictingRevisionsAction = "accept-non-conflicting-revisions";

    public static final String rejectNonConflictingRevisionsAction = "reject-non-conflicting-revisions";

    public static final String rejectRevisionAction = "reject-revision";

    public static final String applyRemoteRevisionsInParaAction = "apply-remote-revisions-in-para";

    public static final String discardRemoteRevisionsInParaAction = "discard-remote-revisions-in-para";

    public static final String selectNextRevisionAction = "select-next-revision";

    public static final String selectPrevRevision = "select-prev-revision";

    public static final String changeIntoSdtAction = "change-into-sdt";

    public static final String removeSdtAction = "remove-sdt";

    public static final String insertEmptySdtAction = "insert-empty-sdt";

    public static final String mergeSdtAction = "merge-sdt";

    public static final String splitSdtAction = "split-sdt";

    public static final String createSdtOnEachParaAction = "create-sdt-on-each-para";

    public static final String createSdtOnStylesAction = "create-sdt-on-styles";

    public static final String createSdtOnSignedParaAction = "create-sdt-on-signed-para";

    private static final Cursor MoveCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
    private static final Cursor DefaultCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);

    private static final javax.swing.text.ViewFactory defaultFactory = new ViewFactory();

    // TODO: Later implementation
    private static final Action[] defaultActions = {};

    private Cursor defaultCursor = DefaultCursor;
    private CaretListener caretListener;
    private MouseListener mouseListener;
    private ContentControlTracker contentControlTracker;
    private Mediator plutextClient;

    /**
     * This is the set of attributes used to store the
     * input attributes.  
     */
    private MutableAttributeSet inputAttributes;

    private DocumentElement currentRunE;

    private boolean inContentControlEdit;

    /**
     * The event listener list for this WordMLEditorKit.
     */
    private EventListenerList listenerList = new EventListenerList();

    /**
     * Constructs an WordMLEditorKit, creates a StyleContext, and loads the
     * style sheet.
     */
    public WordMLEditorKit() {
        caretListener = new CaretListener();
        mouseListener = new MouseListener();
        contentControlTracker = new ContentControlTracker();
        inContentControlEdit = false;

        inputAttributes = new SimpleAttributeSet() {
            public AttributeSet getResolveParent() {
                return (currentRunE != null) ? currentRunE.getAttributes() : null;
            }

            public Object clone() {
                return new SimpleAttributeSet(this);
            }
        };
    }

    public void saveCaretText() {
        log.debug("saveCaretText(): getCaretElement()=" + getCaretElement());
        if (getCaretElement() != null) {
            DocUtil.saveTextContentToElementML(getCaretElement());
        }
    }

    public final synchronized void beginContentControlEdit(WordMLTextPane editor) {
        inContentControlEdit = true;

        WordMLDocument doc = (WordMLDocument) editor.getDocument();
        doc.lockWrite();
        doc.setSnapshotFireBan(true);
    }

    public final synchronized void endContentControlEdit(WordMLTextPane editor) {
        WordMLDocument doc = (WordMLDocument) editor.getDocument();
        doc.setSnapshotFireBan(false);
        doc.unlockWrite();

        inContentControlEdit = false;
    }

    public final synchronized boolean isInContentControlEdit() {
        return inContentControlEdit;
    }

    public void addInputAttributeListener(InputAttributeListener listener) {
        listenerList.add(InputAttributeListener.class, listener);
    }

    public void removeInputAttributeListener(InputAttributeListener listener) {
        listenerList.remove(InputAttributeListener.class, listener);
    }

    /**
     * Get the MIME type of the data that this kit represents support for. This
     * kit supports the type <code>text/xml</code>.
     * 
     * @return the type
     */
    @Override
    public String getContentType() {
        return "application/xml";
    }

    /**
     * Fetch a factory that is suitable for producing views of any models that
     * are produced by this kit.
     * 
     * @return the factory
     */
    @Override
    public javax.swing.text.ViewFactory getViewFactory() {
        return defaultFactory;
    }

    /**
     * Create an uninitialized text storage model that is appropriate for this
     * type of editor.
     * 
     * @return the model
     */
    @Override
    public Document createDefaultDocument() {
        Document doc = new WordMLDocument();
        if (log.isDebugEnabled()) {
            log.debug("createDefaultDocument():");
            DocUtil.displayStructure(doc);
        }
        return doc;
    }

    @Override
    public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
        throw new UnsupportedOperationException();
    }

    /**
     * Creates a WordMLDocument from the given .docx file
     * 
     * @param f  The file to read from
     * @exception IOException on any I/O error
     */
    public WordMLDocument read(FileObject f) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("read(): File = " + VFSUtils.getFriendlyName(f.getName().getURI()));
        }

        //Default WordMLDocument has to be created prior to 
        //unmarshalling docx4j Document so that StyleDefinitionsPart's
        //liveStyles property will be populated correctly.
        //See: StyleDefinitionsPart.unmarshall(java.io.InputStream)
        WordMLDocument doc = (WordMLDocument) createDefaultDocument();
        List<ElementSpec> specs = DocUtil.getElementSpecs(ElementMLFactory.createDocumentML(f));
        doc.createElementStructure(specs);

        if (log.isDebugEnabled()) {
            DocUtil.displayStructure(specs);
            DocUtil.displayStructure(doc);
        }

        return doc;
    }

    /**
     * Write content from a document to the given stream in a format appropriate
     * for this kind of content handler.
     * 
     * @param out
     *            the stream to write to
     * @param doc
     *            the source for the write
     * @param pos
     *            the location in the document to fetch the content
     * @param len
     *            the amount to write out
     * @exception IOException
     *                on any I/O error
     * @exception BadLocationException
     *                if pos represents an invalid location within the document
     */
    @Override
    public void write(Writer out, Document doc, int pos, int len) throws IOException, BadLocationException {

        if (doc instanceof WordMLDocument) {
            // TODO: Later implementation
        } else {
            super.write(out, doc, pos, len);
        }
    }

    /**
     * Called when the kit is being installed into the a JEditorPane.
     * 
     * @param c
     *            the JEditorPane
     */
    @Override
    public void install(JEditorPane c) {
        super.install(c);

        c.addCaretListener(caretListener);
        c.addCaretListener(contentControlTracker);
        c.addMouseListener(mouseListener);
        c.addMouseMotionListener(mouseListener);
        c.addPropertyChangeListener(caretListener);
        caretListener.updateCaretElement(0, 0, c);

        initKeyBindings(c);
    }

    /**
     * Called when the kit is being removed from the JEditorPane. This is used
     * to unregister any listeners that were attached.
     * 
     * @param c
     *            the JEditorPane
     */
    public void deinstall(JEditorPane c) {
        super.deinstall(c);
        c.removeCaretListener(caretListener);
        c.removeCaretListener(contentControlTracker);
        c.removeMouseListener(mouseListener);
        c.removeMouseMotionListener(mouseListener);
        c.removePropertyChangeListener(caretListener);

        this.plutextClient = null;
    }

    public void initPlutextClient(WordMLTextPane editor) {
        WordMLDocument doc = (WordMLDocument) editor.getDocument();
        try {
            doc.readLock();
            this.plutextClient = new Mediator(editor);
        } finally {
            doc.readUnlock();
        }
    }

    public Mediator getPlutextClient() {
        return this.plutextClient;
    }

    /**
     * Fetches the command list for the editor. This is the list of commands
     * supported by the superclass augmented by the collection of commands
     * defined locally for style operations.
     * 
     * @return the command list
     */
    @Override
    public Action[] getActions() {
        return TextAction.augmentList(super.getActions(), WordMLEditorKit.defaultActions);
    }

    public void setDefaultCursor(Cursor cursor) {
        defaultCursor = cursor;
    }

    public Cursor getDefaultCursor() {
        return defaultCursor;
    }

    /**
     * Gets the input attributes for the pane.  When
     * the caret moves and there is no selection, the
     * input attributes are automatically mutated to 
     * reflect the character attributes of the current
     * caret location.  The styled editing actions 
     * use the input attributes to carry out their 
     * actions.
     *
     * @return the attribute set
     */
    public MutableAttributeSet getInputAttributesML() {
        return inputAttributes;
    }

    protected void createInputAttributes(WordMLDocument.TextElement element, MutableAttributeSet set,
            JEditorPane editor) {

        set.removeAttributes(set);
        if (element != null && element.getEndOffset() < element.getDocument().getLength()) {
            set.addAttributes(element.getAttributes());
            // set.removeAttribute(StyleConstants.ComponentAttribute);
            // set.removeAttribute(StyleConstants.IconAttribute);
            // set.removeAttribute(AbstractDocument.ElementNameAttribute);
            // set.removeAttribute(StyleConstants.ComposedTextAttribute);
        }
        fireInputAttributeChanged(new InputAttributeEvent(editor));
    }

    protected void fireInputAttributeChanged(InputAttributeEvent e) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == InputAttributeListener.class) {
                // Lazily create the event:
                // if (e == null)
                // e = new ListSelectionEvent(this, firstIndex, lastIndex);
                ((InputAttributeListener) listeners[i + 1]).inputAttributeChanged(e);
            }
        }
    }

    private WordMLDocument.TextElement getCaretElement() {
        return (WordMLDocument.TextElement) caretListener.caretElement;
    }

    private void refreshCaretElement(JEditorPane editor) {
        caretListener.caretElement = null;
        int start = editor.getSelectionStart();
        int end = editor.getSelectionEnd();

        WordMLDocument doc = (WordMLDocument) editor.getDocument();
        try {
            doc.readLock();
            caretListener.updateCaretElement(start, end, editor);
        } finally {
            doc.readUnlock();
        }
    }

    private void initKeyBindings(JEditorPane editor) {
        ActionMap myActionMap = new ActionMap();
        InputMap myInputMap = new InputMap();

        KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK);
        myActionMap.put(insertSoftBreakAction, new InsertSoftBreakAction(insertSoftBreakAction));
        myInputMap.put(ks, insertSoftBreakAction);

        ks = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
        myActionMap.put(enterKeyTypedAction, new EnterKeyTypedAction(enterKeyTypedAction));
        myInputMap.put(ks, enterKeyTypedAction);

        ks = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
        myActionMap.put(deleteNextCharAction, new DeleteNextCharAction(deleteNextCharAction));
        myInputMap.put(ks, deleteNextCharAction);

        ks = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0);
        myActionMap.put(deletePrevCharAction, new DeletePrevCharAction(deletePrevCharAction));
        myInputMap.put(ks, deletePrevCharAction);

        myActionMap.setParent(editor.getActionMap());
        myInputMap.setParent(editor.getInputMap());
        editor.setActionMap(myActionMap);
        editor.setInputMap(JComponent.WHEN_FOCUSED, myInputMap);
    }

    //**************************
    //*****  INNER CLASSES *****
    //**************************   
    public final static class MouseListener extends MouseAdapter {
        private final static DefaultHighlighter.DefaultHighlightPainter SDT_BACKGROUND_PAINTER = new DefaultHighlighter.DefaultHighlightPainter(
                new Color(189, 222, 255));
        private Object lastHighlight = null;
        private DocumentElement lastHighlightedE = null;

        public void mouseClicked(MouseEvent e) {
            WordMLTextPane editor = (WordMLTextPane) e.getSource();
            clearLastHighlight(editor);

            if (e.isControlDown()) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                int pos = getOffsetPosition(e);
                HyperlinkML ml = getHyperlinkML(doc, pos);
                if (ml != null) {
                    String path = (String) doc.getProperty(WordMLDocument.FILE_PATH_PROPERTY);
                    openLinkedDocument(ml, path);
                }
            }
        }

        public void mouseMoved(MouseEvent e) {
            WordMLTextPane editor = (WordMLTextPane) e.getSource();
            WordMLDocument doc = (WordMLDocument) editor.getDocument();
            int pos = getOffsetPosition(e);

            highlight(editor, pos);
            trackTooltip(editor, pos);

            HyperlinkML ml = getHyperlinkML(doc, pos);
            if (ml != null && e.isControlDown()) {
                editor.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            } else {
                editor.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
            }
        }

        private int getOffsetPosition(MouseEvent e) {
            WordMLTextPane editor = (WordMLTextPane) e.getSource();
            BasicTextUI ui = (BasicTextUI) editor.getUI();

            Point pt = new Point(e.getX(), e.getY());
            Position.Bias[] biasRet = new Position.Bias[1];
            return ui.viewToModel(editor, pt, biasRet);
        }

        private void trackTooltip(WordMLTextPane editor, int pos) {
            WordMLDocument doc = (WordMLDocument) editor.getDocument();
            DocumentElement elem = (DocumentElement) doc.getRunMLElement(pos);
            ElementML parent = elem.getElementML().getParent();
            StringBuilder tipText = null;
            if (parent instanceof RunDelML) {
                String author = ((RunDelML) parent).getAuthor();
                String text = getText(doc, elem);
                if (author != null && author.length() > 0 && text != null && text.length() > 0) {
                    tipText = new StringBuilder("<html><p><b>");
                    tipText.append(author.substring(0, 1).toUpperCase());
                    tipText.append(author.substring(1));
                    tipText.append(" deleted:</b></p><p>");
                    tipText.append(text);
                    tipText.append("</p></html>");
                }
            } else if (parent instanceof RunInsML) {
                String author = ((RunInsML) parent).getAuthor();
                String text = getText(doc, elem);
                if (author != null && author.length() > 0 && text != null && text.length() > 0) {
                    tipText = new StringBuilder("<html><p><b>");
                    tipText.append(author.substring(0, 1).toUpperCase());
                    tipText.append(author.substring(1));
                    tipText.append(" inserted:</b></p><p>");
                    tipText.append(text);
                    tipText.append("</p></html>");
                }
            } else if (parent instanceof HyperlinkML) {
                String text = ((HyperlinkML) parent).getTooltip();
                if (text == null || text.length() == 0) {
                    FileObject srcFile = null;
                    try {
                        String path = (String) doc.getProperty(WordMLDocument.FILE_PATH_PROPERTY);
                        srcFile = VFSUtils.getFileSystemManager().resolveFile(path);
                    } catch (FileSystemException exc) {
                        ;//ignore
                    }
                    text = HyperlinkML.encodeTarget((HyperlinkML) parent, srcFile, true);
                }
                tipText = new StringBuilder("<html><p>");
                tipText.append(text);
                tipText.append("</p><p><b>Ctrl+Click to follow link</b></p></html>");
            }

            if (tipText == null || tipText.length() == 0) {
                editor.setToolTipText(null);
            } else {
                editor.setToolTipText(tipText.toString());
            }
        }

        private String getText(WordMLDocument doc, DocumentElement elem) {
            String text = null;

            int start = elem.getStartOffset();
            int length = elem.getEndOffset() - start;
            try {
                text = doc.getText(start, length);
            } catch (BadLocationException exc) {
                ;//should not happen
            }

            return text;
        }

        private void highlight(WordMLTextPane editor, int pos) {
            WordMLDocument doc = (WordMLDocument) editor.getDocument();
            if (lastHighlightedE != null && lastHighlightedE.getStartOffset() == lastHighlightedE.getEndOffset()) {
                // invalid Element. This may happen when the Element
                // has been removed from the document structure.
                lastHighlightedE = null;
            }

            if (lastHighlightedE != null
                    && (lastHighlightedE.getStartOffset() <= pos && pos <= lastHighlightedE.getEndOffset())) {
                ;// do nothing
            } else {
                clearLastHighlight(editor);
                DocumentElement elem = (DocumentElement) doc.getSdtBlockMLElement(pos);
                if (elem != null && !WordMLStyleConstants.getBorderVisible(elem.getAttributes())) {
                    try {
                        Highlighter hl = editor.getHighlighter();
                        lastHighlight = hl.addHighlight(elem.getStartOffset(), elem.getEndOffset(),
                                SDT_BACKGROUND_PAINTER);
                        lastHighlightedE = elem;
                    } catch (BadLocationException exc) {
                        ;// should not happen
                    }
                }
            }
        }

        private void clearLastHighlight(WordMLTextPane editor) {
            Highlighter hl = editor.getHighlighter();
            if (lastHighlight != null) {
                hl.removeHighlight(lastHighlight);
            }
            lastHighlightedE = null;
        }

        private void openLinkedDocument(HyperlinkML linkML, String currentDocFilePath) {

            FileObject srcFile = null;
            try {
                srcFile = VFSUtils.getFileSystemManager().resolveFile(currentDocFilePath);
            } catch (FileSystemException exc) {
                ;//ignore
            }

            HyperlinkMenu.getInstance().openLinkedDocument(srcFile, linkML);
        }

        private HyperlinkML getHyperlinkML(WordMLDocument doc, int pos) {
            HyperlinkML theLink = null;

            DocumentElement elem = (DocumentElement) doc.getRunMLElement(pos);
            if (elem != null && elem.getStartOffset() < pos && pos < elem.getEndOffset()) {
                ElementML runML = elem.getElementML();
                if (runML.getParent() instanceof HyperlinkML) {
                    theLink = (HyperlinkML) runML.getParent();
                }
            }

            return theLink;
        }
    }// MouseListener inner class

    private class ContentControlTracker implements javax.swing.event.CaretListener, Serializable {
        private Position lastSdtBlockPosition;

        public void caretUpdate(CaretEvent evt) {
            WordMLTextPane editor = (WordMLTextPane) evt.getSource();
            if (editor.isInContentControlEdit()) {
                return;
            }

            int start = Math.min(evt.getDot(), evt.getMark());
            int end = Math.max(evt.getDot(), evt.getMark());

            if (start == end) {
                selectSdtBlock(editor, start);
            } else {
                View startV = SwingUtil.getSdtBlockView(editor, start);
                View endV = SwingUtil.getSdtBlockView(editor, end - 1);
                if (startV == endV && startV != null) {
                    selectSdtBlock(editor, start);
                } else {
                    resetLastSdtBlockPosition(editor);
                }
            }
        }

        private void selectSdtBlock(WordMLTextPane editor, int pos) {
            SdtBlockView currentSdt = SwingUtil.getSdtBlockView(editor, pos);

            SdtBlockView lastSdt = null;
            if (lastSdtBlockPosition != null) {
                int offset = lastSdtBlockPosition.getOffset();
                lastSdt = SwingUtil.getSdtBlockView(editor, offset);
            }

            if (currentSdt != lastSdt) {
                if (lastSdt != null) {
                    lastSdt.setBorderVisible(false);
                    editor.getUI().damageRange(editor, lastSdt.getStartOffset(), lastSdt.getEndOffset(),
                            Position.Bias.Forward, Position.Bias.Forward);
                    lastSdtBlockPosition = null;
                }
            }

            if (currentSdt != null) {
                currentSdt.setBorderVisible(true);
                editor.getUI().damageRange(editor, currentSdt.getStartOffset(), currentSdt.getEndOffset(),
                        Position.Bias.Forward, Position.Bias.Forward);

                try {
                    lastSdtBlockPosition = editor.getDocument().createPosition(pos);
                } catch (BadLocationException exc) {
                    lastSdtBlockPosition = null;// should not happen
                }
            }
        }

        private void resetLastSdtBlockPosition(WordMLTextPane editor) {
            if (lastSdtBlockPosition != null) {
                int offset = lastSdtBlockPosition.getOffset();
                SdtBlockView sdt = SwingUtil.getSdtBlockView(editor, offset);
                if (sdt != null) {
                    sdt.setBorderVisible(false);
                    editor.getUI().damageRange(editor, sdt.getStartOffset(), sdt.getEndOffset(),
                            Position.Bias.Forward, Position.Bias.Forward);
                }
                lastSdtBlockPosition = null;
            }
        }
    }// ContentControlTracker inner class

    private class CaretListener implements javax.swing.event.CaretListener, PropertyChangeListener, Serializable {
        private WordMLDocument.TextElement caretElement;

        public void caretUpdate(CaretEvent evt) {
            WordMLTextPane editor = (WordMLTextPane) evt.getSource();
            int start = Math.min(evt.getDot(), evt.getMark());
            int end = Math.max(evt.getDot(), evt.getMark());

            WordMLDocument doc = (WordMLDocument) editor.getDocument();
            try {
                doc.readLock();

                if (start != end) {
                    //Validate selected area if any
                    new TextSelector(doc, start, end - start);
                }
                updateCaretElement(start, end, editor);

                //            if (start == end) {
                //               selectSdtBlock(editor, start);
                //            } else {
                //               resetLastSdtBlockPosition(editor);
                //            }

            } catch (BadSelectionException exc) {
                UIManager.getLookAndFeel().provideErrorFeedback(editor);
                editor.setCaretPosition(start);
            } finally {
                doc.readUnlock();
            }
        }

        public void propertyChange(PropertyChangeEvent evt) {
            Object newValue = evt.getNewValue();
            Object source = evt.getSource();

            if ((source instanceof WordMLTextPane) && (newValue instanceof WordMLDocument)) {
                // New document will have changed selection to 0,0.
                WordMLDocument doc = (WordMLDocument) newValue;
                try {
                    doc.readLock();

                    updateCaretElement(0, 0, (WordMLTextPane) source);
                } finally {
                    doc.readUnlock();
                }
            }
        }

        void updateCaretElement(int start, int end, JEditorPane editor) {
            WordMLDocument doc = (WordMLDocument) editor.getDocument();
            Position.Bias bias = (start != end) ? Position.Bias.Forward : null;
            WordMLDocument.TextElement elem = DocUtil.getInputAttributeElement(doc, start, bias);

            if (caretElement != elem) {
                DocUtil.saveTextContentToElementML(caretElement);
                caretElement = elem;

                if (caretElement != null) {
                    WordMLEditorKit.this.currentRunE = (DocumentElement) caretElement.getParentElement();
                } else {
                    WordMLEditorKit.this.currentRunE = null;
                }
                createInputAttributes(caretElement, getInputAttributesML(), editor);
            }
        }

        //       private void selectSdtBlock(JEditorPane editor, int pos) {
        //         WordMLDocument doc = (WordMLDocument) editor.getDocument();
        //         
        //         BasicTextUI ui = (BasicTextUI) editor.getUI();
        //         View root = ui.getRootView(editor).getView(0);
        //
        //         SdtBlockView currentSdt = null;
        //         int idx = root.getViewIndex(pos, Position.Bias.Forward);
        //         View v = root.getView(idx);
        //         if (v instanceof SdtBlockView) {
        //            currentSdt = (SdtBlockView) v;
        //         }
        //
        //         SdtBlockView lastSdt = null;
        //         if (lastSdtBlockPosition != null) {
        //            idx = root.getViewIndex(lastSdtBlockPosition.getOffset(),
        //                  Position.Bias.Forward);
        //            lastSdt = (SdtBlockView) root.getView(idx);
        //         }
        //
        //         if (currentSdt != lastSdt) {
        //            if (lastSdt != null) {
        //               lastSdt.setBorderVisible(false);
        //               ui.damageRange(editor, lastSdt.getStartOffset(), lastSdt
        //                     .getEndOffset(), Position.Bias.Forward,
        //                     Position.Bias.Forward);
        //               lastSdtBlockPosition = null;
        //            }
        //         }
        //         
        //         if (currentSdt != null && !currentSdt.isBorderVisible()) {
        //            currentSdt.setBorderVisible(true);
        //            ui.damageRange(editor, currentSdt.getStartOffset(),
        //                  currentSdt.getEndOffset(), Position.Bias.Forward,
        //                  Position.Bias.Forward);
        //            try {
        //               lastSdtBlockPosition = doc.createPosition(pos);
        //            } catch (BadLocationException exc) {
        //               lastSdtBlockPosition = null;// should not happen
        //            }
        //         }
        //      }

        //       private void resetLastSdtBlockPosition(JEditorPane editor) {
        //          if (lastSdtBlockPosition != null) {
        //            BasicTextUI ui = (BasicTextUI) editor.getUI();
        //            View root = ui.getRootView(editor).getView(0);
        //
        //            int idx = 
        //               root.getViewIndex(
        //                  lastSdtBlockPosition.getOffset(), 
        //                  Position.Bias.Forward);
        //            SdtBlockView sdt = (SdtBlockView) root.getView(idx);
        //            sdt.setBorderVisible(false);
        //            ui.damageRange(
        //               editor, 
        //               sdt.getStartOffset(), 
        //               sdt.getEndOffset(), 
        //               Position.Bias.Forward,
        //               Position.Bias.Forward);
        //            lastSdtBlockPosition = null;
        //         }
        //       }

    }// CaretListener inner class

    public static class AcceptNonConflictingRevisionsAction extends TextAction {
        private Exception exc;

        public AcceptNonConflictingRevisionsAction() {
            super(acceptNonConflictingRevisionsAction);
            this.exc = null;
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            log.debug("AcceptNonConflictingRevisionsAction.actionPerformed():...");

            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                Mediator plutextClient = editor.getWordMLEditorKit().getPlutextClient();
                if (plutextClient != null && plutextClient.hasNonConflictingChanges()) {
                    log.debug("AcceptNonConflictingRevisionsAction.actionPerformed():"
                            + " plutextClient HAS non-conflicting changes");

                    int caretPos = editor.getCaretPosition();

                    try {
                        doc.lockWrite();

                        editor.saveCaretText();

                        int refreshStart = doc.getLength();
                        int refreshEnd = -1;

                        for (String id : plutextClient.getIdsOfNonConflictingChanges()) {
                            plutextClient.removeTrackedChangeType(id);

                            DocumentElement elem = Util.getDocumentElement(doc, id);
                            if (elem != null) {
                                refreshStart = Math.min(refreshStart, elem.getStartOffset());
                                refreshEnd = Math.max(refreshEnd, elem.getEndOffset());

                                ElementML sdt = elem.getElementML();
                                String temp = org.docx4j.XmlUtils.marshaltoString(sdt.getDocxObject(), false);
                                StreamSource src = new StreamSource(new StringReader(temp));

                                javax.xml.bind.util.JAXBResult result = new javax.xml.bind.util.JAXBResult(
                                        org.docx4j.jaxb.Context.jc);
                                XmlUtil.applyRemoteRevisions(src, result);

                                ElementML newSdt = new SdtBlockML((org.docx4j.wml.SdtBlock) result.getResult());
                                boolean notEmpty = (XmlUtil.getLastRunContentML(newSdt) != null);
                                if (notEmpty) {
                                    sdt.addSibling(newSdt, true);
                                }
                                sdt.delete();
                            }
                        } //for (id) loop

                        if (refreshStart < refreshEnd) {
                            caretPos = doc.getLength() - refreshEnd;
                            doc.refreshParagraphs(refreshStart, (refreshEnd - refreshStart));
                            caretPos = doc.getLength() - caretPos;
                        }

                    } catch (Exception exc) {
                        this.exc = exc;

                    } finally {
                        doc.unlockWrite();
                        editor.setCaretPosition(caretPos);
                    }
                } else {
                    log.debug("AcceptNonConflictingRevisionsAction.actionPerformed():"
                            + " NO plutextClient NOR non-conflicting changes");
                } //if (plutextClient != null && plutextClient.hasNonConflictingChanges())
            } //if (editor != null)

        } //actionPerformed()

        public Exception getThrownException() {
            return this.exc;
        }

        public boolean success() {
            return (this.exc == null);
        }

    }// AcceptNonConflictingRevisionsAction inner class

    public static class RejectNonConflictingRevisionsAction extends TextAction {
        private Exception exc;

        public RejectNonConflictingRevisionsAction() {
            super(acceptNonConflictingRevisionsAction);
            this.exc = null;
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            log.debug("RejectNonConflictingRevisionsAction.actionPerformed():...");

            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                Mediator plutextClient = editor.getWordMLEditorKit().getPlutextClient();
                if (plutextClient != null && plutextClient.hasNonConflictingChanges()) {
                    log.debug("RejectNonConflictingRevisionsAction.actionPerformed():"
                            + " plutextClient HAS non-conflicting changes");

                    int caretPos = editor.getCaretPosition();

                    try {
                        doc.lockWrite();

                        editor.saveCaretText();

                        int refreshStart = doc.getLength();
                        int refreshEnd = -1;

                        for (String id : plutextClient.getIdsOfNonConflictingChanges()) {
                            plutextClient.removeTrackedChangeType(id);

                            DocumentElement elem = Util.getDocumentElement(doc, id);
                            if (elem != null) {
                                refreshStart = Math.min(refreshStart, elem.getStartOffset());
                                refreshEnd = Math.max(refreshEnd, elem.getEndOffset());

                                ElementML sdt = elem.getElementML();
                                String temp = org.docx4j.XmlUtils.marshaltoString(sdt.getDocxObject(), false);
                                StreamSource src = new StreamSource(new StringReader(temp));

                                javax.xml.bind.util.JAXBResult result = new javax.xml.bind.util.JAXBResult(
                                        org.docx4j.jaxb.Context.jc);
                                XmlUtil.discardRemoteRevisions(src, result);

                                ElementML newSdt = new SdtBlockML((org.docx4j.wml.SdtBlock) result.getResult());
                                boolean notEmpty = (XmlUtil.getLastRunContentML(newSdt) != null);
                                if (notEmpty) {
                                    sdt.addSibling(newSdt, true);
                                }
                                sdt.delete();
                            }
                        } //for (id) loop

                        if (refreshStart < refreshEnd) {
                            caretPos = doc.getLength() - refreshEnd;
                            doc.refreshParagraphs(refreshStart, (refreshEnd - refreshStart));
                            caretPos = doc.getLength() - caretPos;
                        }

                    } catch (Exception exc) {
                        this.exc = exc;

                    } finally {
                        doc.unlockWrite();
                        editor.setCaretPosition(caretPos);
                    }
                } else {
                    log.debug("RejectNonConflictingRevisionsAction.actionPerformed():"
                            + " NO plutextClient NOR non-conflicting changes");
                } //if (plutextClient != null && plutextClient.hasNonConflictingChanges())
            } //if (editor != null)
        } //actionPerformed()

        public Exception getThrownException() {
            return this.exc;
        }

        public boolean success() {
            return (this.exc == null);
        }

    }// RejectNonConflictingRevisionsAction inner class

    public static class AcceptRevisionAction extends TextAction {
        private boolean success;

        public AcceptRevisionAction() {
            super(acceptRevisionAction);
            success = Boolean.FALSE;
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                try {
                    doc.lockWrite();

                    editor.saveCaretText();

                    int start = editor.getSelectionStart();
                    int end = editor.getSelectionEnd();
                    if (start < end && start == DocUtil.getRevisionStart(doc, start, SwingConstants.NEXT)
                            && end == DocUtil.getRevisionEnd(doc, start, SwingConstants.NEXT)) {

                        DocumentElement elem = (DocumentElement) doc.getRunMLElement(start);

                        //new caret position is calculated from end of document
                        end = doc.getLength() - elem.getEndOffset();

                        ElementML parent = elem.getElementML().getParent();
                        if (parent instanceof RunInsML) {
                            for (ElementML run : parent.getChildren()) {
                                ElementML copy = (ElementML) run.clone();
                                parent.addSibling(copy, false);
                            }
                            parent.delete();
                        } else if (parent instanceof RunDelML) {
                            parent.delete();
                        }

                        elem = (DocumentElement) doc.getSdtBlockMLElement(start);
                        if (elem != null) {
                            //if revision is in content control
                            SdtBlockML sdt = (SdtBlockML) elem.getElementML();
                            Mediator client = editor.getWordMLEditorKit().getPlutextClient();
                            if (client != null && !XmlUtil.containsTrackedChanges(sdt.getDocxObject())) {
                                String id = sdt.getSdtProperties().getPlutextId();
                                client.removeTrackedChangeType(id);
                            }

                            boolean isEmpty = (XmlUtil.getLastRunContentML(sdt) == null);
                            if (isEmpty) {
                                sdt.delete();

                                //new caret position is calculated from end of document
                                end = doc.getLength() - elem.getEndOffset();
                            }
                        }

                        doc.refreshParagraphs(start, 0);
                        editor.setCaretPosition(doc.getLength() - end);
                        success = Boolean.TRUE;
                    }
                } finally {
                    doc.unlockWrite();
                }
            }
        }

        public boolean success() {
            return success;
        }

    }// AcceptRevisionAction inner class

    public static class RejectRevisionAction extends TextAction {
        private boolean success;

        public RejectRevisionAction() {
            super(rejectRevisionAction);
            success = Boolean.FALSE;
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                try {
                    doc.lockWrite();

                    editor.saveCaretText();

                    int start = editor.getSelectionStart();
                    int end = editor.getSelectionEnd();
                    if (start < end && start == DocUtil.getRevisionStart(doc, start, SwingConstants.NEXT)
                            && end == DocUtil.getRevisionEnd(doc, start, SwingConstants.NEXT)) {

                        DocumentElement elem = (DocumentElement) doc.getRunMLElement(start);

                        //new caret position is calculated from end of document
                        end = doc.getLength() - elem.getEndOffset();

                        ElementML parent = elem.getElementML().getParent();
                        if (parent instanceof RunDelML) {
                            for (ElementML run : parent.getChildren()) {
                                ElementML copy = (ElementML) run.clone();
                                parent.addSibling(copy, false);
                            }
                            parent.delete();
                        } else if (parent instanceof RunInsML) {
                            parent.delete();
                        }

                        elem = (DocumentElement) doc.getSdtBlockMLElement(start);
                        if (elem != null) {
                            //if revision is in content control
                            SdtBlockML sdt = (SdtBlockML) elem.getElementML();
                            Mediator client = editor.getWordMLEditorKit().getPlutextClient();
                            if (client != null && !XmlUtil.containsTrackedChanges(sdt.getDocxObject())) {
                                String id = sdt.getSdtProperties().getPlutextId();
                                client.removeTrackedChangeType(id);
                            }

                            boolean isEmpty = (XmlUtil.getLastRunContentML(sdt) == null);
                            if (isEmpty) {
                                sdt.delete();

                                //new caret position is calculated from end of document
                                end = doc.getLength() - elem.getEndOffset();
                            }
                        }

                        doc.refreshParagraphs(start, 0);
                        editor.setCaretPosition(doc.getLength() - end);
                        success = Boolean.TRUE;
                    }
                } finally {
                    doc.unlockWrite();
                }
            }
        }

        public boolean success() {
            return success;
        }

    }// RejectRevisionAction inner class

    public static class ApplyRemoteRevisionsInParaAction extends TextAction {
        private boolean success;

        public ApplyRemoteRevisionsInParaAction() {
            super(applyRemoteRevisionsInParaAction);
            success = Boolean.FALSE;
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                try {
                    doc.lockWrite();

                    editor.saveCaretText();

                    int pos = editor.getCaretPosition();
                    DocumentElement elem = (DocumentElement) doc.getParagraphMLElement(pos, false);
                    ElementML oldPara = elem.getElementML();

                    int start = editor.getSelectionStart();
                    int end = editor.getSelectionEnd();
                    if (elem.getStartOffset() <= start && end <= elem.getEndOffset()
                            && XmlUtil.containsTrackedChanges(oldPara.getDocxObject())) {

                        DocumentElement sdtElem = (DocumentElement) doc.getSdtBlockMLElement(pos);

                        String temp = org.docx4j.XmlUtils.marshaltoString(oldPara.getDocxObject(), false);

                        StreamSource src = new StreamSource(new StringReader(temp));

                        StreamResult result = new StreamResult(new ByteArrayOutputStream());
                        XmlUtil.applyRemoteRevisions(src, result);

                        temp = result.getOutputStream().toString();

                        ElementML newPara = null;
                        try {
                            newPara = new ParagraphML(org.docx4j.XmlUtils.unmarshalString(temp));
                        } catch (JAXBException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        }
                        boolean notEmpty = (XmlUtil.getLastRunContentML(newPara) != null);
                        if (notEmpty) {
                            oldPara.addSibling(newPara, true);
                        }
                        oldPara.delete();

                        if (sdtElem != null) {
                            //if paragraph is in content control
                            SdtBlockML sdt = (SdtBlockML) sdtElem.getElementML();
                            Mediator client = editor.getWordMLEditorKit().getPlutextClient();
                            if (client != null && !XmlUtil.containsTrackedChanges(sdt.getDocxObject())) {
                                temp = sdt.getSdtProperties().getPlutextId();
                                client.removeTrackedChangeType(temp);
                            }

                            notEmpty = (XmlUtil.getLastRunContentML(sdt) != null);
                            if (!notEmpty) {
                                sdt.delete();
                            }
                        }

                        end = doc.getLength() - elem.getEndOffset();
                        doc.refreshParagraphs(pos, 0);
                        editor.setCaretPosition(doc.getLength() - end);
                        success = Boolean.TRUE;
                    }
                } finally {
                    doc.unlockWrite();
                }
            }
        }

        public boolean success() {
            return success;
        }

    }// ApplyRemoteRevisionsInParaAction inner class

    public static class DiscardRemoteRevisionsInParaAction extends TextAction {
        private boolean success;

        public DiscardRemoteRevisionsInParaAction() {
            super(discardRemoteRevisionsInParaAction);
            success = Boolean.FALSE;
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                try {
                    doc.lockWrite();

                    editor.saveCaretText();

                    int pos = editor.getCaretPosition();
                    DocumentElement elem = (DocumentElement) doc.getParagraphMLElement(pos, false);
                    ElementML oldPara = elem.getElementML();

                    int start = editor.getSelectionStart();
                    int end = editor.getSelectionEnd();
                    if (elem.getStartOffset() <= start && end <= elem.getEndOffset()
                            && XmlUtil.containsTrackedChanges(oldPara.getDocxObject())) {

                        DocumentElement sdtElem = (DocumentElement) doc.getSdtBlockMLElement(pos);

                        String temp = org.docx4j.XmlUtils.marshaltoString(oldPara.getDocxObject(), false);

                        StreamSource src = new StreamSource(new StringReader(temp));

                        StreamResult result = new StreamResult(new ByteArrayOutputStream());
                        XmlUtil.discardRemoteRevisions(src, result);

                        temp = result.getOutputStream().toString();

                        ElementML newPara = null;
                        try {
                            newPara = new ParagraphML(org.docx4j.XmlUtils.unmarshalString(temp));
                        } catch (JAXBException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        }
                        boolean notEmpty = (XmlUtil.getLastRunContentML(newPara) != null);
                        if (notEmpty) {
                            oldPara.addSibling(newPara, true);
                        }
                        oldPara.delete();

                        if (sdtElem != null) {
                            //if paragraph is in content control
                            SdtBlockML sdt = (SdtBlockML) sdtElem.getElementML();
                            Mediator client = editor.getWordMLEditorKit().getPlutextClient();
                            if (client != null && !XmlUtil.containsTrackedChanges(sdt.getDocxObject())) {
                                temp = sdt.getSdtProperties().getPlutextId();
                                client.removeTrackedChangeType(temp);
                            }

                            notEmpty = (XmlUtil.getLastRunContentML(sdt) != null);
                            if (!notEmpty) {
                                sdt.delete();
                            }
                        }

                        end = doc.getLength() - elem.getEndOffset();
                        doc.refreshParagraphs(pos, 0);
                        editor.setCaretPosition(doc.getLength() - end);
                        success = Boolean.TRUE;
                    }
                } finally {
                    doc.unlockWrite();
                }
            }
        }

        public boolean success() {
            return success;
        }

    }// DiscardRemoteRevisionsInParaAction inner class

    public static class SelectNextRevisionAction extends TextAction {
        public SelectNextRevisionAction() {
            super(selectNextRevisionAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                try {
                    doc.readLock();

                    int pos = editor.getCaretPosition();
                    editor.setCaretPosition(pos); //clear up selection

                    if (pos < doc.getLength()) {
                        int start = DocUtil.getRevisionStart(doc, pos, SwingConstants.NEXT);
                        if (start > -1) {
                            int end = DocUtil.getRevisionEnd(doc, start, SwingConstants.NEXT);
                            if (end > -1) {
                                editor.setCaretPosition(start);
                                editor.moveCaretPosition(end);
                            }
                        }
                    }
                } finally {
                    doc.readUnlock();
                }
            }
        }
    }// SelectNextRevisionAction inner class

    public static class SelectPrevRevisionAction extends TextAction {
        public SelectPrevRevisionAction() {
            super(selectPrevRevision);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            WordMLTextPane editor = (WordMLTextPane) getTextComponent(e);
            if (editor != null) {
                WordMLDocument doc = (WordMLDocument) editor.getDocument();
                try {
                    doc.readLock();

                    int pos = editor.getSelectionStart();
                    editor.setCaretPosition(pos); //clear up selection

                    int start = DocUtil.getRevisionStart(doc, pos, SwingConstants.PREVIOUS);
                    if (start > -1) {
                        int end = DocUtil.getRevisionEnd(doc, start, SwingConstants.NEXT);
                        if (end > -1) {
                            editor.setCaretPosition(start);
                            editor.moveCaretPosition(end);
                        }
                    }
                } finally {
                    doc.readUnlock();
                }
            }
        }
    }// SelectPrevRevisionAction inner class

    public abstract static class StyledTextAction extends javax.swing.text.StyledEditorKit.StyledTextAction {
        public StyledTextAction(String nm) {
            super(nm);
        }

        protected final void setRunMLAttributes(final WordMLTextPane editor, AttributeSet attrs, boolean replace) {

            Caret caret = editor.getCaret();
            int dot = caret.getDot();
            int mark = caret.getMark();

            WordMLEditorKit kit = (WordMLEditorKit) editor.getEditorKit();
            kit.saveCaretText();

            WordMLDocument doc = (WordMLDocument) editor.getDocument();

            int p0 = editor.getSelectionStart();
            int p1 = editor.getSelectionEnd();
            if (p0 == p1) {
                try {
                    int wordStart = DocUtil.getWordStart(editor, p0);
                    int wordEnd = DocUtil.getWordEnd(editor, p1);
                    if (wordStart < p0 && p0 < wordEnd) {
                        p0 = wordStart;
                        p1 = wordEnd;
                    }
                } catch (BadLocationException exc) {
                    ;//ignore
                }
            }

            if (p0 != p1) {
                try {
                    doc.setRunMLAttributes(p0, p1 - p0, attrs, replace);
                    editor.setCaretPosition(mark);
                    editor.moveCaretPosition(dot);
                } catch (BadLocationException exc) {
                    exc.printStackTrace();
                    ;//ignore
                }
            } else {
                MutableAttributeSet inputAttributes = (MutableAttributeSet) kit.getInputAttributesML();
                if (replace) {
                    inputAttributes.removeAttributes(inputAttributes);
                }
                inputAttributes.addAttributes(attrs);
                kit.fireInputAttributeChanged(new InputAttributeEvent(editor));
            }

            if (log.isDebugEnabled()) {
                DocUtil.displayStructure(doc);
            }
        }

        protected final void setParagraphMLAttributes(WordMLTextPane editor, AttributeSet attr, boolean replace) {

            Caret caret = editor.getCaret();
            int dot = caret.getDot();
            int mark = caret.getMark();

            WordMLEditorKit kit = (WordMLEditorKit) editor.getEditorKit();
            kit.saveCaretText();

            int p0 = editor.getSelectionStart();
            int p1 = editor.getSelectionEnd();

            WordMLDocument doc = (WordMLDocument) editor.getDocument();
            try {
                doc.setParagraphMLAttributes(p0, (p1 - p0), attr, replace);

                editor.setCaretPosition(mark);
                editor.moveCaretPosition(dot);
            } catch (BadLocationException exc) {
                exc.printStackTrace();//ignore
            }

            if (log.isDebugEnabled()) {
                DocUtil.displayStructure(doc);
            }
        }

        protected final void setRunStyle(WordMLTextPane editor, String styleId) {
            Caret caret = editor.getCaret();
            int dot = caret.getDot();
            int mark = caret.getMark();

            WordMLEditorKit kit = (WordMLEditorKit) editor.getEditorKit();
            kit.saveCaretText();

            WordMLDocument doc = (WordMLDocument) editor.getDocument();

            int p0 = editor.getSelectionStart();
            int p1 = editor.getSelectionEnd();
            if (p0 == p1) {
                try {
                    int wordStart = DocUtil.getWordStart(editor, p0);
                    int wordEnd = DocUtil.getWordEnd(editor, p1);
                    if (wordStart < p0 && p0 < wordEnd) {
                        p0 = wordStart;
                        p1 = wordEnd;
                    }
                } catch (BadLocationException exc) {
                    ;//ignore
                }
            }

            if (p0 != p1) {
                doc.setRunStyle(p0, p1 - p0, styleId);
                editor.setCaretPosition(mark);
                editor.moveCaretPosition(dot);
                kit.refreshCaretElement(editor);
            } else {
                Style style = doc.getStyleSheet().getReferredStyle(styleId);
                if (style != null) {
                    MutableAttributeSet inputAttributes = (MutableAttributeSet) kit.getInputAttributesML();
                    inputAttributes.removeAttributes(inputAttributes);
                    inputAttributes.addAttribute(WordMLStyleConstants.RStyleAttribute, styleId);
                    kit.fireInputAttributeChanged(new InputAttributeEvent(editor));
                }
            }

            if (log.isDebugEnabled()) {
                DocUtil.displayStructure(doc);
            }
        }

        protected final void setParagraphStyle(WordMLTextPane editor, String styleId) {
            Caret caret = editor.getCaret();
            int dot = caret.getDot();
            int mark = caret.getMark();

            WordMLEditorKit kit = (WordMLEditorKit) editor.getEditorKit();
            kit.saveCaretText();

            int p0 = editor.getSelectionStart();
            int p1 = editor.getSelectionEnd();
            WordMLDocument doc = (WordMLDocument) editor.getDocument();
            doc.setParagraphStyle(p0, (p1 - p0), styleId);

            editor.setCaretPosition(mark);
            editor.moveCaretPosition(dot);

            kit.refreshCaretElement(editor);

            if (log.isDebugEnabled()) {
                DocUtil.displayStructure(doc);
            }
        }

    }// StyledTextAction inner class

    public static class AlignmentAction extends StyledTextAction {

        /**
         * Creates a new AlignmentAction.
         *
         * @param nm the action name
         * @param a the alignment >= 0
         */
        private int _alignment;

        public AlignmentAction(String name, int alignment) {
            super(name);
            this._alignment = alignment;
        }

        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                int a = this._alignment;
                if ((e != null) && (e.getSource() == editor)) {
                    String s = e.getActionCommand();
                    try {
                        a = Integer.parseInt(s, 10);
                    } catch (NumberFormatException nfe) {
                    }
                }
                MutableAttributeSet attr = new SimpleAttributeSet();
                StyleConstants.setAlignment(attr, a);
                setParagraphMLAttributes((WordMLTextPane) editor, attr, false);
            }
        }
    }// AlignmentAction inner class

    public static class ApplyStyleAction extends StyledTextAction {
        private String styleName;

        public ApplyStyleAction(String nm, String styleName) {
            super(nm);
            this.styleName = styleName;
        }

        public void actionPerformed(ActionEvent e) {
            log.debug("ApplyStyleAction.actionPerformed():...");
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                if (this.styleName != null) {
                    WordMLDocument doc = (WordMLDocument) editor.getDocument();
                    Style s = doc.getStyleSheet().getReferredStyle(this.styleName);
                    String styleId = (String) s.getAttribute(WordMLStyleConstants.StyleIdAttribute);
                    String type = (String) s.getAttribute(WordMLStyleConstants.StyleTypeAttribute);
                    if (StyleSheet.PARAGRAPH_ATTR_VALUE.equals(type)) {
                        setParagraphStyle((WordMLTextPane) editor, styleId);
                    } else if (StyleSheet.CHARACTER_ATTR_VALUE.equals(type)) {
                        setRunStyle((WordMLTextPane) editor, styleId);
                    }
                } else {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                }
            }
        }
    }// ApplyStyleAction inner class

    public static class FontFamilyAction extends StyledTextAction {
        private String family;

        /**
         * Creates a new FontFamilyAction.
         * 
         * @param nm
         *            the action name
         * @param family
         *            the font family
         */
        public FontFamilyAction(String nm, String family) {
            super(nm);
            this.family = family;
        }

        /**
         * Sets the font family.
         * 
         * @param e
         *            the event
         */
        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                if (this.family != null) {
                    MutableAttributeSet attr = new SimpleAttributeSet();
                    StyleConstants.setFontFamily(attr, this.family);
                    setRunMLAttributes((WordMLTextPane) editor, attr, false);
                } else {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                }
            }
        }

    } //FontFamilyAction inner class

    public static class FontSizeAction extends StyledTextAction {

        /**
         * Creates a new FontSizeAction.
         * 
         * @param nm
         *            the action name
         * @param size
         *            the font size
         */
        public FontSizeAction(String nm, int size) {
            super(nm);
            this.size = size;
        }

        /**
         * Sets the font size.
         * 
         * @param e
         *            the action event
         */
        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                if (this.size != 0) {
                    MutableAttributeSet attr = new SimpleAttributeSet();
                    StyleConstants.setFontSize(attr, this.size);
                    setRunMLAttributes((WordMLTextPane) editor, attr, false);
                } else {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                }
            }
        }

        private int size;
    }

    public static class BoldAction extends StyledTextAction {
        private boolean isBold;

        public BoldAction(boolean isBold) {
            super(fontBoldAction);
            this.isBold = isBold;
        }

        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                SimpleAttributeSet sas = new SimpleAttributeSet();
                StyleConstants.setBold(sas, this.isBold);
                setRunMLAttributes((WordMLTextPane) editor, sas, false);
            }
        }
    }// BoldAction inner class

    public static class ItalicAction extends StyledTextAction {
        private boolean isItalic;

        public ItalicAction(boolean isItalic) {
            super(fontItalicAction);
            this.isItalic = isItalic;
        }

        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                SimpleAttributeSet sas = new SimpleAttributeSet();
                StyleConstants.setItalic(sas, this.isItalic);
                setRunMLAttributes((WordMLTextPane) editor, sas, false);
            }
        }
    }// ItalicAction inner class

    public static class UnderlineAction extends StyledTextAction {
        private boolean isUnderlined;

        public UnderlineAction(boolean isUnderlined) {
            super(fontUnderlineAction);
            this.isUnderlined = isUnderlined;
        }

        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                SimpleAttributeSet sas = new SimpleAttributeSet();
                StyleConstants.setUnderline(sas, this.isUnderlined);
                setRunMLAttributes((WordMLTextPane) editor, sas, false);
            }
        }
    }// UnderlineAction inner class

    private static class InsertSoftBreakAction extends StyledTextAction {
        public InsertSoftBreakAction(String name) {
            super(name);
        }

        public void actionPerformed(ActionEvent e) {
            JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                if ((!editor.isEditable()) || (!editor.isEnabled())) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                if (log.isDebugEnabled()) {
                    log.debug("InsertSoftBreakAction.actionPerformed()");
                }
            }
        }
    }// InsertSoftBreakAction inner class

    private static class EnterKeyTypedAction extends StyledTextAction {
        public EnterKeyTypedAction(String name) {
            super(name);
        }

        public void actionPerformed(ActionEvent e) {
            final JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                if ((!editor.isEditable()) || (!editor.isEnabled())) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLEditorKit kit = (WordMLEditorKit) editor.getEditorKit();
                kit.saveCaretText();

                int pos = editor.getCaretPosition();
                DocumentElement elem = kit.getCaretElement();
                if (elem != null && elem.getStartOffset() < pos && pos < elem.getEndOffset()) {
                    elem = (DocumentElement) elem.getParentElement();
                    ElementML runML = elem.getElementML();
                    if (runML.getParent() instanceof HyperlinkML) {
                        String path = (String) elem.getDocument().getProperty(WordMLDocument.FILE_PATH_PROPERTY);
                        openLinkedDocument((HyperlinkML) runML.getParent(), path);
                    } else {
                        editor.replaceSelection(Constants.NEWLINE);
                    }
                } else {
                    editor.replaceSelection(Constants.NEWLINE);
                }
            }
        }

        private void openLinkedDocument(HyperlinkML linkML, final String currentDocFilePath) {

            FileObject srcFile = null;
            try {
                srcFile = VFSUtils.getFileSystemManager().resolveFile(currentDocFilePath);
            } catch (FileSystemException exc) {
                ;//ignore
            }

            HyperlinkMenu.getInstance().openLinkedDocument(srcFile, linkML);
        }

    }// EnterKeyTypedAction inner class

    private static class DeleteNextCharAction extends StyledTextAction {

        /* Create this object with the appropriate identifier. */
        DeleteNextCharAction(String name) {
            super(deleteNextCharAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            final JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                if ((!editor.isEditable()) || (!editor.isEnabled())) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }
                WordMLDocument doc = (WordMLDocument) editor.getDocument();

                Caret caret = editor.getCaret();
                int dot = caret.getDot();
                int mark = caret.getMark();

                DocumentElement elem = (DocumentElement) doc.getCharacterElement(dot);
                WordMLEditorKit kit = (WordMLEditorKit) editor.getEditorKit();
                if (kit.getCaretElement() != elem) {
                    kit.saveCaretText();
                }
                elem = (DocumentElement) elem.getParentElement();

                if (log.isDebugEnabled()) {
                    log.debug("DeleteNextCharAction.actionPerformed(): dot=" + dot + " doc.getLength()="
                            + doc.getLength());
                }

                try {
                    if (dot != mark) {
                        doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
                        dot = Math.min(dot, mark);
                    } else if (((RunML) elem.getElementML()).getFldChar() != null) {
                        org.docx4j.wml.FldChar fldChar = ((RunML) elem.getElementML()).getFldChar();
                        ElementML fldComplex = elem.getElementML().getGodParent();
                        if (fldChar.getFldCharType() == org.docx4j.wml.STFldCharType.BEGIN) {
                            ElementML ml = null;
                            while (ml != fldComplex) {
                                elem = (DocumentElement) DocUtil.getFldComplexEnd(doc, elem.getEndOffset(),
                                        SwingConstants.NEXT);
                                ml = elem.getElementML().getGodParent();
                            }
                            selectLater(editor, dot, elem.getEndOffset());
                        } else if (fldChar.getFldCharType() == org.docx4j.wml.STFldCharType.END) {
                            mark = elem.getEndOffset();
                            ElementML ml = null;
                            while (ml != fldComplex) {
                                elem = (DocumentElement) DocUtil.getFldComplexStart(doc, elem.getEndOffset(),
                                        SwingConstants.PREVIOUS);
                                ml = elem.getElementML().getGodParent();
                            }
                            selectLater(editor, elem.getStartOffset(), mark);
                        } else {
                            //do nothing
                        }
                    } else if (dot < doc.getLength()) {
                        int delChars = 1;

                        String dotChars = doc.getText(dot, 2);
                        char c0 = dotChars.charAt(0);
                        char c1 = dotChars.charAt(1);

                        if (c0 >= '\uD800' && c0 <= '\uDBFF' && c1 >= '\uDC00' && c1 <= '\uDFFF') {
                            delChars = 2;
                        }

                        doc.remove(dot, delChars);
                    }

                    caret.setDot(dot);

                } catch (BadLocationException exc) {
                    ;// ignore
                }

            } //if (editor != null)

        }//actionPerformed()

        private void selectLater(final JEditorPane editor, final int start, final int end) {
            Runnable r = new Runnable() {
                public void run() {
                    editor.select(start, end);
                }
            };
            SwingUtilities.invokeLater(r);
        }
    }//DeleteNextCharAction()

    private static class DeletePrevCharAction extends StyledTextAction {

        /* Create this object with the appropriate identifier. */
        DeletePrevCharAction(String name) {
            super(deletePrevCharAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            final JEditorPane editor = getEditor(e);
            if (editor instanceof WordMLTextPane) {
                if ((!editor.isEditable()) || (!editor.isEnabled())) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                final WordMLDocument doc = (WordMLDocument) editor.getDocument();
                Caret caret = editor.getCaret();
                int dot = caret.getDot();
                int mark = caret.getMark();

                DocumentElement elem = (DocumentElement) doc.getCharacterElement(dot - 1);

                WordMLEditorKit kit = (WordMLEditorKit) editor.getEditorKit();
                if (kit.getCaretElement() != elem) {
                    kit.saveCaretText();
                }
                elem = (DocumentElement) elem.getParentElement();

                if (log.isDebugEnabled()) {
                    log.debug("DeletePrevCharAction.actionPerformed(): dot=" + dot + " doc.getLength()="
                            + doc.getLength());
                }

                try {
                    if (dot != mark) {
                        doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
                        dot = Math.min(dot, mark);

                    } else if (((RunML) elem.getElementML()).getFldChar() != null) {
                        org.docx4j.wml.FldChar fldChar = ((RunML) elem.getElementML()).getFldChar();
                        ElementML fldComplex = elem.getElementML().getGodParent();
                        if (fldChar.getFldCharType() == org.docx4j.wml.STFldCharType.BEGIN) {
                            ElementML ml = null;
                            while (ml != fldComplex) {
                                elem = (DocumentElement) DocUtil.getFldComplexEnd(doc, elem.getEndOffset(),
                                        SwingConstants.NEXT);
                                ml = elem.getElementML().getGodParent();
                            }
                            selectLater(editor, dot, elem.getEndOffset());
                        } else if (fldChar.getFldCharType() == org.docx4j.wml.STFldCharType.END) {
                            mark = elem.getEndOffset();
                            ElementML ml = null;
                            while (ml != fldComplex) {
                                elem = (DocumentElement) DocUtil.getFldComplexStart(doc, elem.getEndOffset(),
                                        SwingConstants.PREVIOUS);
                                ml = elem.getElementML().getGodParent();
                            }
                            selectLater(editor, elem.getStartOffset(), mark);
                        } else {
                            //do nothing
                        }

                    } else if (0 < dot && dot < doc.getLength()) {
                        int delChars = 1;

                        if (dot > 1) {
                            String dotChars = doc.getText(dot - 2, 2);
                            char c0 = dotChars.charAt(0);
                            char c1 = dotChars.charAt(1);

                            if (c0 >= '\uD800' && c0 <= '\uDBFF' && c1 >= '\uDC00' && c1 <= '\uDFFF') {
                                delChars = 2;
                            }
                        }

                        dot = dot - delChars;
                        doc.remove(dot, delChars);
                    }

                    caret.setDot(dot);

                } catch (BadLocationException exc) {
                    ;//ignore
                }
            } //if (editor != null)
        }//actionPerformed()

        private void selectLater(final JEditorPane editor, final int start, final int end) {
            Runnable r = new Runnable() {
                public void run() {
                    editor.select(start, end);
                }
            };
            SwingUtilities.invokeLater(r);
        }
    }//DeletePrevCharAction class

    public static class ChangeIntoSdtAction extends TextAction {
        /* Create this object with the appropriate identifier. */
        public ChangeIntoSdtAction() {
            super(insertEmptySdtAction);
        }

        /** 
         * NOTE:
         * The menu that is associated with this action 
         * should only be enabled if DocUtil.canChangeIntoSdt() returns true.
         * 
         */
        public void actionPerformed(ActionEvent e) {
            final JTextComponent editor = getTextComponent(e);
            if (editor instanceof WordMLTextPane) {
                if (!editor.isEditable() || !editor.isEnabled()) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLTextPane textpane = (WordMLTextPane) editor;
                WordMLEditorKit kit = (WordMLEditorKit) textpane.getEditorKit();
                kit.saveCaretText();

                int start = textpane.getSelectionStart();
                int end = textpane.getSelectionEnd();

                final WordMLDocument doc = (WordMLDocument) textpane.getDocument();
                int pos = doc.getLength() - textpane.getCaretPosition();

                try {
                    doc.lockWrite();

                    int offs = start;
                    DocumentElement elem = (DocumentElement) doc.getSdtBlockMLElement(offs);
                    //NOTE: Make sure that this Action is enabled after passing
                    //DocUtil.canChangeIntoSdt() method.
                    if (elem == null) {
                        SdtBlockML sdt = ElementMLFactory.createSdtBlockML();
                        elem = (DocumentElement) doc.getParagraphMLElement(offs, false);
                        if (offs == doc.getLength()) {
                            ;//do not change the last paragraph in the document
                        } else if (offs == elem.getStartOffset() && elem.getEndOffset() == end) {
                            //Search for the highest parent element 
                            //that is NOT the only child.
                            DocumentElement parent = (DocumentElement) elem.getParentElement();
                            if (elem.isTheOnlyChild()) {
                                while (parent.isTheOnlyChild()) {
                                    parent = (DocumentElement) parent.getParentElement();
                                }
                            }

                            //Search for parent's first descendant that
                            //can be changed into Sdt.
                            int idx = parent.getElementIndex(offs);
                            DocumentElement temp = (DocumentElement) parent.getElement(idx);
                            ElementML ml = (ElementML) temp.getElementML().clone();
                            while (!sdt.canAddChild(ml) || !temp.getElementML().canAddSibling(sdt, true)) {
                                parent = temp;
                                idx = parent.getElementIndex(offs);
                                temp = (DocumentElement) parent.getElement(idx);
                            }

                            //'temp' is the element that can be changed.
                            ml = temp.getElementML();
                            ml.addSibling(sdt, true);
                            ml.delete();
                            sdt.addChild(ml);

                        } else if (end <= elem.getEndOffset()) {
                            ElementML ml = elem.getElementML();
                            ml.addSibling(sdt, true);
                            ml.delete();
                            sdt.addChild(ml);

                        } else {
                            DocumentElement parent = (DocumentElement) elem.getParentElement();
                            if (end <= parent.getEndOffset()) {
                                //[offs, offs + length] has to be within parent's span.
                                int idx = parent.getElementIndex(offs);
                                int endIdx = parent.getElementIndex(end - 1);
                                while (idx <= endIdx) {
                                    DocumentElement temp = (DocumentElement) parent.getElement(idx);
                                    ElementML ml = temp.getElementML();
                                    sdt = ElementMLFactory.createSdtBlockML();
                                    ml.addSibling(sdt, true);
                                    ml.delete();
                                    sdt.addChild(ml);
                                    idx++;
                                }
                            } else {
                                //unchangeable
                            }
                        }
                    } //if (elem == null)

                    doc.refreshParagraphs(start, end - start);

                } finally {
                    doc.unlockWrite();
                    textpane.setCaretPosition(doc.getLength() - pos);
                }
            }
        }
    } //ChangeIntoSdtAction class

    public static class RemoveSdtAction extends TextAction {
        /* Create this object with the appropriate identifier. */
        public RemoveSdtAction() {
            super(removeSdtAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            final JTextComponent editor = getTextComponent(e);
            if (editor instanceof WordMLTextPane) {
                if (!editor.isEditable() || !editor.isEnabled()) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLTextPane textpane = (WordMLTextPane) editor;
                WordMLEditorKit kit = (WordMLEditorKit) textpane.getEditorKit();
                kit.saveCaretText();

                int start = textpane.getSelectionStart();
                int end = textpane.getSelectionEnd();

                final WordMLDocument doc = (WordMLDocument) textpane.getDocument();
                int pos = doc.getLength() - textpane.getCaretPosition();

                try {
                    doc.lockWrite();

                    int offset = start;
                    while (offset <= end) {
                        DocumentElement elem = (DocumentElement) doc.getSdtBlockMLElement(offset);
                        if (elem != null) {
                            ElementML sdt = elem.getElementML();
                            List<ElementML> children = new ArrayList<ElementML>(sdt.getChildren());
                            for (ElementML kid : children) {
                                kid.delete();
                                sdt.addSibling(kid, false);
                            }
                            sdt.delete();
                        } else {
                            elem = (DocumentElement) doc.getParagraphMLElement(offset, false);
                        }

                        offset = elem.getEndOffset();
                        if (offset == end) {
                            //finish
                            offset += 1;
                        }
                    }

                    doc.refreshParagraphs(start, end - start);

                } finally {
                    doc.unlockWrite();
                    textpane.setCaretPosition(doc.getLength() - pos);
                }
            }
        }
    } //RemoveSdtAction class

    public static class InsertEmptySdtAction extends TextAction {
        private boolean success = false;

        /* Create this object with the appropriate identifier. */
        public InsertEmptySdtAction() {
            super(insertEmptySdtAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            final JTextComponent editor = getTextComponent(e);
            if (editor instanceof WordMLTextPane) {
                if (!editor.isEditable() || !editor.isEnabled()
                        || editor.getSelectionStart() < editor.getSelectionEnd()) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLTextPane textpane = (WordMLTextPane) editor;
                WordMLEditorKit kit = (WordMLEditorKit) textpane.getEditorKit();
                kit.saveCaretText();

                final WordMLDocument doc = (WordMLDocument) textpane.getDocument();
                int offset = textpane.getCaretPosition();
                int pos = doc.getLength() - offset;

                try {
                    doc.lockWrite();

                    SdtBlockML sdt = ElementMLFactory.createSdtBlockML();
                    sdt.addChild(ElementMLFactory.createEmptyParagraphML());

                    DocumentElement elem = DocUtil.getElementToPasteAsSibling(doc, offset, sdt);
                    if (elem == null) {
                        return;
                    }

                    if (offset == elem.getStartOffset()) {
                        elem.getElementML().addSibling(sdt, false);

                        doc.refreshParagraphs(offset, 1);
                        pos += 1;
                        success = true;

                    } else if (offset == elem.getEndOffset() - 1) {
                        elem.getElementML().addSibling(sdt, true);

                        doc.refreshParagraphs(offset, 1);
                        success = true;

                    } else if (elem.getElementML() instanceof ParagraphML
                            && DocUtil.canSplitElementML(elem, offset - elem.getStartOffset())) {
                        DocUtil.splitElementML(elem, (offset - elem.getStartOffset()));
                        elem.getElementML().addSibling(sdt, true);

                        doc.refreshParagraphs(offset, 1);
                        pos += 1;
                        success = true;
                    }
                } finally {
                    doc.unlockWrite();
                    textpane.setCaretPosition(doc.getLength() - pos);
                }
            }
        }

        public boolean success() {
            return this.success;
        }

    } //InsertNewSdtAction class

    public static class MergeSdtAction extends TextAction {

        /* Create this object with the appropriate identifier. */
        public MergeSdtAction() {
            super(mergeSdtAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            final JTextComponent editor = getTextComponent(e);
            if (editor instanceof WordMLTextPane) {
                int start = editor.getSelectionStart();
                int end = editor.getSelectionEnd();

                if (!editor.isEditable() || !editor.isEnabled() || start == end) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLTextPane textpane = (WordMLTextPane) editor;
                WordMLEditorKit kit = (WordMLEditorKit) textpane.getEditorKit();
                kit.saveCaretText();

                final WordMLDocument doc = (WordMLDocument) textpane.getDocument();

                try {
                    doc.lockWrite();

                    if (DocUtil.canMergeSdt(doc, start, (end - start))) {
                        DocumentElement sdtBlockE = (DocumentElement) doc.getSdtBlockMLElement(start);
                        //Grab the last child of sdtBlockE
                        int idx = sdtBlockE.getElementCount() - 1;
                        DocumentElement elem = (DocumentElement) sdtBlockE.getElement(idx);
                        ElementML lastChild = elem.getElementML();

                        //for each selected sibling below sdtBlockE
                        //paste its content to 'lastChild'
                        DocumentElement parent = (DocumentElement) sdtBlockE.getParentElement();
                        int startIdx = parent.getElementIndex(sdtBlockE.getEndOffset());
                        int endIdx = parent.getElementIndex(end - 1);
                        while (startIdx <= endIdx) {
                            elem = (DocumentElement) parent.getElement(endIdx);
                            ElementML ml = elem.getElementML();
                            ml.delete();

                            if (ml instanceof SdtBlockML) {
                                for (int i = elem.getElementCount() - 1; 0 <= i; i--) {
                                    DocumentElement temp = (DocumentElement) elem.getElement(i);
                                    ml = temp.getElementML();
                                    ml.delete();
                                    lastChild.addSibling(ml, true);
                                }
                            } else {
                                lastChild.addSibling(ml, true);
                            }
                            endIdx--;
                        } //while (startIdx <= endIdx)

                        doc.refreshParagraphs(start, (end - start));
                    }
                } finally {
                    doc.unlockWrite();
                    DocumentElement sdtBlockE = (DocumentElement) doc.getSdtBlockMLElement(start);
                    if (sdtBlockE != null) {
                        start = sdtBlockE.getStartOffset();
                        end = sdtBlockE.getEndOffset();
                        textpane.setCaretPosition(start);
                        textpane.moveCaretPosition(end);
                    }
                }
            } //if (editor instanceof WordMLTextPane)
        } //actionPerformed()
    } //MergeSdtAction()

    public static class SplitSdtAction extends TextAction {

        /* Create this object with the appropriate identifier. */
        public SplitSdtAction() {
            super(splitSdtAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            final JTextComponent editor = getTextComponent(e);
            if (editor instanceof WordMLTextPane) {
                if (!editor.isEditable() || !editor.isEnabled()) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLTextPane textpane = (WordMLTextPane) editor;
                WordMLEditorKit kit = (WordMLEditorKit) textpane.getEditorKit();
                kit.saveCaretText();

                final WordMLDocument doc = (WordMLDocument) textpane.getDocument();
                try {
                    doc.lockWrite();

                    int pos = textpane.getCaretPosition();

                    if (DocUtil.canSplitSdt(doc, pos)) {
                        DocumentElement sdtBlockE = (DocumentElement) doc.getSdtBlockMLElement(pos);
                        ElementML sdt = sdtBlockE.getElementML();

                        SdtBlockML newSdt = ElementMLFactory.createSdtBlockML();

                        //Get the paragraph where the cursor is 
                        int idx = sdtBlockE.getElementIndex(pos);
                        DocumentElement temp = (DocumentElement) sdtBlockE.getElement(idx);

                        //Record the actual split position.
                        //The actual split position is at the beginning of paragraph
                        //containing cursor.
                        pos = temp.getStartOffset();

                        if (idx == 0) {
                            newSdt.addChild(ElementMLFactory.createEmptyParagraphML());
                            sdt.addSibling(newSdt, false);

                        } else {
                            sdt.addSibling(newSdt, true);

                            for (; idx < sdtBlockE.getElementCount(); idx++) {
                                temp = (DocumentElement) sdtBlockE.getElement(idx);
                                ElementML ml = temp.getElementML();
                                ml.delete();
                                newSdt.addChild(ml);
                            }
                        }

                        //Refresh document
                        pos = doc.getLength() - pos;
                        doc.refreshParagraphs(textpane.getCaretPosition(), 1);
                        textpane.setCaretPosition(doc.getLength() - pos);
                    }
                } finally {
                    doc.unlockWrite();
                }
            }
        }
    } //SplitSdtAction()

    public static class CreateSdtOnEachParaAction extends TextAction {

        /* Create this object with the appropriate identifier. */
        public CreateSdtOnEachParaAction() {
            super(createSdtOnEachParaAction);
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            final JTextComponent editor = getTextComponent(e);
            if (editor instanceof WordMLTextPane) {
                if (!editor.isEditable() || !editor.isEnabled()) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLTextPane textpane = (WordMLTextPane) editor;
                ((WordMLEditorKit) textpane.getEditorKit()).saveCaretText();

                final WordMLDocument doc = (WordMLDocument) textpane.getDocument();

                try {
                    doc.lockWrite();

                    boolean refresh = false;
                    SdtBlockML sdt = ElementMLFactory.createSdtBlockML();

                    DocumentElement root = (DocumentElement) doc.getDefaultRootElement();
                    DocumentML docML = (DocumentML) root.getElementML();
                    WordprocessingMLPackage wmlPackage = docML.getWordprocessingMLPackage();
                    XmlUtil.setPlutextGroupingProperty(wmlPackage, Constants.EACH_BLOCK_GROUPING_STRATEGY);

                    for (int i = 0; i < root.getElementCount() - 1; i++) {
                        DocumentElement elem = (DocumentElement) root.getElement(i);
                        ElementML ml = elem.getElementML();
                        ElementML parent = ml.getParent();
                        int idx = parent.getChildIndex(ml);

                        if (parent.canAddChild(sdt)) {
                            ml.delete();
                            if (sdt.canAddChild(ml)) {
                                sdt.addChild(ml);
                                ml = sdt;
                                refresh = true;
                                sdt = ElementMLFactory.createSdtBlockML();
                            }
                            parent.addChild(idx, ml);
                        }
                    } //for (i)

                    if (refresh) {
                        doc.refreshParagraphs(0, doc.getLength());
                    }
                } finally {
                    doc.unlockWrite();
                }
            }
        }
    } //CreateSdtOnEachParaAction inner class

    public static class CreateSdtOnStylesAction extends CreateSdtOnSignedParaAction {
        public CreateSdtOnStylesAction(List<Integer> positionsOfStyledParagraphs, boolean mergeSingleParas) {
            super(positionsOfStyledParagraphs, mergeSingleParas);
        }
    } //CreateSdtOnStylesAction inner class

    public static class CreateSdtOnSignedParaAction extends TextAction {
        private List<Integer> signaturePositions;
        //A flag to indicate that newly created Sdt(s) that
        //only contain one single paragraph have to be merged into next Sdt.
        //This feature is currently being used by CreateSdtOnStylesAction.
        //This CreateSdtOnSignedParaAction is not using it.
        private boolean mergeSingleParas;

        public CreateSdtOnSignedParaAction(List<Integer> signaturePositions, boolean mergeSingleParas) {
            super(createSdtOnSignedParaAction);
            this.signaturePositions = signaturePositions;
            this.mergeSingleParas = mergeSingleParas;
            if (this.signaturePositions.get(0).intValue() != 0) {
                //Make this.signaturePositions always start from 0(zero)
                //because each signature position will be a sign to 
                //this action for creating a new Sdt.
                this.signaturePositions.add(0, Integer.valueOf(0));
            }
        }

        /** The operation to perform when this action is triggered. */
        public void actionPerformed(ActionEvent e) {
            if (log.isDebugEnabled()) {
                log.debug("CreateSdtOnSignedParaAction.actionPerformed(): Start...");
            }

            final JTextComponent editor = getTextComponent(e);
            if (editor instanceof WordMLTextPane) {
                if (!editor.isEditable() || !editor.isEnabled()) {
                    UIManager.getLookAndFeel().provideErrorFeedback(editor);
                    return;
                }

                WordMLTextPane textpane = (WordMLTextPane) editor;
                WordMLEditorKit kit = (WordMLEditorKit) textpane.getEditorKit();
                kit.saveCaretText();

                final WordMLDocument doc = (WordMLDocument) textpane.getDocument();
                try {
                    doc.lockWrite();

                    final DocumentElement root = (DocumentElement) doc.getDefaultRootElement();
                    DocumentML docML = (DocumentML) root.getElementML();
                    WordprocessingMLPackage wmlPackage = docML.getWordprocessingMLPackage();
                    XmlUtil.setPlutextGroupingProperty(wmlPackage, Constants.OTHER_GROUPING_STRATEGY);

                    int listIdx = 0;
                    int elemIdx = 0;
                    int signedElemIdx = root.getElementIndex(this.signaturePositions.get(0));
                    SdtBlockML sdt = null;

                    while (elemIdx < root.getElementCount() - 1) {
                        DocumentElement elem = (DocumentElement) root.getElement(elemIdx);
                        ElementML ml = elem.getElementML();

                        if (elemIdx == signedElemIdx) {
                            if (sdt != null && sdt.getChildrenCount() == 1 && this.mergeSingleParas) {
                                //do not create a new sdt
                                //but use this current sdt
                                //to collect (merge with) 
                                //elem.getElementML(). 
                                ml.delete();
                                sdt.addChild(ml);
                            } else {
                                //create a new sdt
                                sdt = ElementMLFactory.createSdtBlockML();
                                ml.addSibling(sdt, false);
                                ml.delete();
                                sdt.addChild(ml);
                            }

                            //Remove signature, if any.
                            //Note that paragraph at signedElemIdx == 0
                            //may not contain signature.
                            //See: this action's constructor.
                            RunContentML run = XmlUtil.getFirstRunContentML(ml);
                            String text = run.getTextContent();
                            if (text.startsWith(Constants.GROUPING_SIGNATURE)) {
                                text = text.substring(2);
                                run.setTextContent(text);
                            }

                            if (listIdx + 1 <= this.signaturePositions.size() - 1) {
                                listIdx++;
                                signedElemIdx = root.getElementIndex(this.signaturePositions.get(listIdx));
                            }
                        } else {
                            ml.delete();
                            //put in sdt
                            sdt.addChild(ml);
                        }

                        elemIdx++;
                    } //while loop

                    doc.refreshParagraphs(0, doc.getLength());

                    if (log.isDebugEnabled()) {
                        log.debug("CreateSdtOnSignedParaAction.actionPerformed(): Resulting Structure...");
                        DocUtil.displayStructure(doc);
                    }
                } finally {
                    doc.unlockWrite();
                }
            }
        }

    } //CreateSdtOnSignedParagraphAction()

}// WordMLEditorKit class