Chapter 13. Adding an XML editor to your application

Table of Contents

1. Compiling and running the code sample
2. A multi-document, multi-view, XML editor contained in JFrame
3. A single-document, single-view, XML editor contained in a JPanel
4. Being notified when the user edit documents using the XML editor
5. Instructing the XML editor to perform an action

1. Compiling and running the code sample

Figure 13.1. The main window of the "EmbedDemo" code sample

The main window of the "EmbedDemo" code sample

  • Compile EmbedDemo by executing ant (see build.xml) in the samples/embed_demo/ directory.

  • Run EmbedDemo by executing ant run in the samples/embed_demo/ directory. Clicking any button of the first two rows creates and displays a different XML editor.

2. A multi-document, multi-view, XML editor contained in JFrame

An XMLFrame is a JFrame, containing a MultiDocApp. A MultiDocApp is a multi-document, possibly multiple views per document, XML editor similar to the desktop application. However the default GUI of a MultiDocApp, which is specified in MultiDocApp.xxe_gui, differs from the default GUI of the desktop application. A MultiDocApp has no OptionsCustomize Configuration submenu, no OptionsInstall Add-ons menu item, no Help menu, etc.

Excerpts from EmbedDemo.java:

    private void newXMLFrame() {
        final XMLFrame xmlFrame = new XMLFrame();1
        xmlFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);2
        xmlFrame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                QuitApplicationService quitService = 
                    xmlFrame.app.getQuitApplicationService();
                if (quitService != null) {
                    quitService.quitApplication(false);
                }
            }
        });
        xmlFrame.setSize(new Dimension(900, 700));3
        xmlFrame.setLocationRelativeTo(frame);
        xmlFrame.setVisible(true);
        ...
    }

1

Creating an XMLFrame is not different from creating a JFrame. Note that in order to keep the code sample short, the newly created XMLFrame is not given a title and an icon.

2

It is customary to specify what to do when the user clicks the Close button of the JFrame. In the case of an embedded XML editor, it is mandatory to invoke the QuitApplicationService. Failing to do so may cause some documents to remain write locked. More about the QuitApplicationService below.

3

the newly created XMLFrame is given a size and a location on screen and finally, is made visible.

3. A single-document, single-view, XML editor contained in a JPanel

Excerpts from EmbedDemo.java:

    private void newDocument() {
        final MyXMLEditor myEditor = new MyXMLEditor(frame);

        ...

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                myEditor.newDocument("XHTML/5.0", "HTML Page");
            }
        });
    }    

   private void openDocument() {
        final File file = ChooseFile.chooseOpenFile(frame, null, null);
        if (file == null) {
            return;
        }

        final MyXMLEditor myEditor = new MyXMLEditor(frame);

        ...

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                myEditor.openDocument(file);
            }
        });
    }

MyXMLEditor, which is part of this code sample, is a simple JFrame containing an XMLPanel2. Excerpts from MyXMLEditor.java:

    public MyXMLEditor(JFrame parentFrame) {
        super("JFrame Containing XMLPanel2");

        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                closeEditor();
            }
        });

        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        ...

        // workArea is a JPanel with a BorderLayout.
        Container workArea = getContentPane();

        xmlPanel = new XMLPanel2();
        workArea.add(xmlPanel);

        setSize(new Dimension(800, 600));
        setLocationRelativeTo(parentFrame);
        setVisible(true);
    }

An XMLPanel is a JPanel, containing a SingleDocApp. A SingleDocApp is a single-document, single document view, XML editor quite different from the desktop application. The default GUI of a SingleDocApp is specified in SingleDocApp.xxe_gui.

Figure 13.2. An XMLPanel

An XMLPanel

An XMLPanel2 is a variant of XMLPanel. By default, XMLPanel2 configures the SingleDocApp to use SingleDocApp2.xxe_gui as its GUI specification. This variant of SingleDocApp.xxe_gui simply adds New, Open, Save, Save As, Print, Close buttons to the tool bar of the XML editor.

Figure 13.3. An XMLPanel2

An XMLPanel2

4. Being notified when the user edit documents using the XML editor

A third-party application generally needs to be informed whenever the user creates, opens, saves, etc, documents in the embedded XML editor. This is done by implementing OpenedDocumentHook (or deriving OpenedDocumentHookBase) and registering it with the XML editor, that is, the App, by the means of addOpenedDocumentHook.

Excerpts from EmbedDemo.java:

    private IdentityHashMap<OpenedDocument,Boolean> openedDocs;

    ...

    private void addApp(App newApp) {
        ...
        newApp.addOpenedDocumentHook(new Hook());
        ...
    }    

    private final class Hook extends OpenedDocumentHookBase {
        @Override
        public void documentCreated(OpenedDocument openedDoc,
                                    HookStatus status) {1
            if (status != HookStatus.SUCCESS) {
                return;
            }
            openedDocs.put(openedDoc, Boolean.FALSE);
            updateInfo();
        }

        @Override
        public void documentOpened(OpenedDocument openedDoc,
                                   HookStatus status) {2
            if (status != HookStatus.SUCCESS) {
                return;
            }
            openedDocs.put(openedDoc, Boolean.FALSE);
            updateInfo();
        }

        @Override
        public void documentClosed(OpenedDocument openedDoc,
                                   HookStatus status) {3
            if (status != HookStatus.SUCCESS) {
                return;
            }
            openedDocs.remove(openedDoc);
            updateInfo();
        }
    }

    private void updateInfo() {4
        int modified = 0;
        for (Map.Entry<OpenedDocument,Boolean> entry : openedDocs.entrySet()) {
            if (entry.getValue().booleanValue()) {
                ++modified;
            }
        }

        infoField.setText(modified + " / " + openedDocs.size() + 
                          " modified documents.");
    }

1

This method is invoked after a document has been created in the XML editor.

2

This method is invoked after a document has been opened in the XML editor.

3

This method is invoked after a document has been closed in the XML editor.

4

All the above methods keep a tally of OpenedDocuments in order to display a line of information (number of opened documents; number of documents having unsaved changes) at the bottom of the main window of the "EmbedDemo" code sample.

In some cases, the third-party application needs more fine grained information about what happens in the embedded XML editor. This is done by implementing AppPart2 (or deriving AppPart2Base) and registering it with the XML editor, that is, the App, by the means of putPart. Such AppParts or AppPart2s are similar to the “hidden” —workers only; having no user interface— parts described in Section 4.7, “The hidden child element of layout in XMLmind XML Editor - Customizing the User Interface.

Excerpts from EmbedDemo.java:

    private void addApp(App newApp) {
        ...
        newApp.putPart(new Part2(), "EmbedDemo.Part2");
        ...
    }    

    private final class Part2 extends AppPart2Base {
        @Override
        public void saveStateChanged() {1
            saveStateChanged(app.getActiveOpenedDocument());
        }

        @Override
        public void saveStateChanged(OpenedDocument openedDoc) {2
            openedDocs.put(openedDoc, new Boolean(openedDoc.isSaveNeeded()));
            updateInfo();
        }
    }

1

This method is invoked after the save state of the active OpenedDocument (see getActiveOpenedDocument) changes.

2

This method is invoked after the save state of any OpenedDocument other than the active one changes.

5. Instructing the XML editor to perform an action

Instructing the XML editor, that is the App, to perform an action can be done as follows:

AppAction action = app.getAction(action_name);
if (action != null) {
    action.doIt();
}

The above code has the same effect on the App as the user selecting the menu item or clicking the tool bar button invoking the AppAction called action_name.

As explained in XMLmind XML Editor - Customizing the User Interface, stock AppActions are all defined in the DesktopApp.xxe_gui GUI specification file. Excerpts from DesktopApp.xxe_gui:

  <action name="quitAction" label="_Quit"
          accelerator="mod Q">
    <class>com.xmlmind.xmleditapp.app.part.QuitAction</class>
  </action>

For example, instructing the XML editor to quit can be done as follows:

AppAction action = app.getAppAction("quitAction");
if (action != null) {
    action.doIt();
}

Note that using doIt to perform an action does not allow to parameterize the behavior of this action or to known whether the action has succeeded or failed. For example, using doIt, there is now way to force the XML editor to quit even when there are modified documents. That's why, for the most commonly used actions, there is a more flexible way to instruct the XML editor to do something: App “services”.

Table 13.1. App “services”

InterfaceObtaining an ImplementationDescription
NewDocumentServiceapp.getNewDocumentService()Create a new document.
OpenDocumentServiceapp.getOpenDocumentService()Open specified document.
EditDocumentServiceapp.getEditDocumentService()

Unlike OpenDocumentService which systematically reopens specified document, an EditDocumentService simply makes active specified document when this document is already opened in the XML editor.

Moreover if specified URL has a fragment, once the document has been opened or made active by this service, the document view is automatically scrolled to show the node pointed to by the fragment.

CompareRevisionsServiceapp.getCompareRevisionsService()Compare specified OpenedDocuments using the Compare tool in XMLmind XML Editor - Online Help.
ValidateDocumentServiceapp.getValidateDocumentService()Validate specified OpenedDocument.
SaveDocumentServiceapp.getSaveDocumentService()Save specified OpenedDocument.
CloseDocumentServiceapp.getCloseDocumentService()Close specified OpenedDocument.
QuitApplicationServiceapp.getQuitApplicationService()Quit the application.

Example: invoking QuitApplicationService. Excerpts from EmbedDemo.java:

        xmlFrame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                QuitApplicationService quitService = 
                    xmlFrame.app.getQuitApplicationService();
                if (quitService != null) {
                    quitService.quitApplication(/*discardChanges*/ false);
                }
            }
        });

Example: invoking NewDocumentService and OpenDocumentService. Excerpts from MyXMLEditor.java:

    public boolean newDocument(String categoryName, String templateName) {
        NewDocumentService newService =
            xmlPanel.app.getNewDocumentService();
        return (newService != null &&
                newService.newDocument(categoryName, templateName, 
                                       /*saveURL*/ null, 
                                       /*createSaveFile*/ false));
    }

    public boolean openDocument(File file) {
        URL url = FileUtil.fileToURL(file);

        OpenDocumentService openService =
            xmlPanel.app.getOpenDocumentService();
        return (openService != null &&
                openService.openDocument(url, /*readOnly*/ false, 
                                         /*settings*/ null));
    }