Chapter 14. More control over the embedded XML editor

Table of Contents

1. Embedding the equivalent of the desktop application
2. Under the hood
3. Deriving your own class from a subclass of App

1. Embedding the equivalent of the desktop application

Embedding the equivalent of the desktop application is done using the following code snippet. Excerpts from EmbedDemo.java:

        Map<String,Object> appSpecs = StandAloneAppSpecs.createFromResources();
        EmbeddedApp app = new EmbeddedApp(appSpecs);

        app.setHostComponent(new JFrame());

        app.initialize(/*singleStep*/ false);

A EmbeddedApp is an XML editor almost identical to the desktop application. However, the default GUI of an EmbeddedApp, which is specified in EmbeddedApp.xxe_gui, differs from the default GUI of the desktop application. A EmbeddedApp has no OptionsInstall Add-ons menu item and no HelpCheck for Updates menu item.

2. Under the hood

Notice in the above code snippet that:

  1. There is no easy to use XXEFrame class similar to XMLPanel, XMLPanel2 and XMLFrame.

  2. There is no code which specifies what to do when the user clicks the Close button of the JFrame.

  3. There is no code which sets the location and size of the JFrame and which makes it visible.

Concerning points #2 and #3, such code is not needed because EmbeddedApp being the equivalent of a normally stand-alone, desktop application automatically takes care of this. For example, the location and size of the JFrame are restored after loading last saved values from XXE's standard user preferences file (XXE_user_preferences_dir/preferences.properties).

Concerning point #1, XMLPanel, XMLPanel2 and XMLFrame are merely convenience classes. For example, the constructor of XMLPanel is equivalent to:

Map<String,Object> appSpecs = AppSpecs.createFromResources();1

SingleDocApp app = new SingleDocApp(appSpecs);2

app.setHostComponent(new JPanel());3

app.initialize();4

1

A map containing entries such as guiCustomizationURLsKey, configurationURLsKey, xmlCatalogURLsKey, spellCheckerPluginClassNamesKey, etc, must be used to specify how to assemble and configure an App.

Utility classes AppSpecs and StandAloneAppSpecs have methods (AppSpecs.createFromResources, StandAloneAppSpecs.createFromFiles, etc) which automatically populate such map either by

2

The constructor of an App always takes a specification map as its first argument.

3

Method setHostComponent associates a JComponent (JPanel, JFrame, JApplet, etc) to the newly created, but not yet initialized, App.

4

Method initialize actually initializes the App. Executing this method may take a long time for the first App instance (but it's quick for subsequent instances), that's why there is a variant, initialize(boolean), which provides the end-user with a minimal feedback (waitCursor).

Understanding how an App is created and initialized is useful because it gives you more control over the embedded XML editor. For example, the following code snippet forces the the embedded desktop application to always use 0,0 900x700 as its initial location and size and also to display an empty DocBook chapter immediately after it shows up.

Excerpts from EmbedDemo.java (setCommandLineArguments is normally commented out in this file):

    private void newEmbeddedApp() {
        Map<String,Object> appSpecs = StandAloneAppSpecs.createFromResources();
        setCommandLineArguments(appSpecs);

        EmbeddedApp app = new EmbeddedApp(appSpecs);
        app.setHostComponent(new JFrame());
        app.initialize(/*singleStep*/ false);

        ...
    }    

    private void setCommandLineArguments(Map<String,Object> appSpecs) {
        appSpecs.put(AppSpecKeys.commandLineArgumentsKey, new String[] {
            "-putpref", "geometry", "0 0 900 700",
            "-new", "DocBook", "Chapter", "-"
        });
    }

3. Deriving your own class from a subclass of App

If you embed XMLPanel, XMLPanel2 or EmbeddedApp in your application, you'll probably never need to subclass App. However in some cases, you'll have to derive your own class from a subclass of App. Fortunately, in most use cases, this is quite simple.

Excerpts from EmbedDemo.java (alternateNewXMLFrame is normally commented out in this file):

    private void alternateNewXMLFrame() {
        final JFrame myFrame = new JFrame();

        Map<String,Object> appSpecs = AppSpecs.createFromResources();

        final MultiDocApp myApp = new MultiDocApp(appSpecs) {
            @Override
            protected void showTitle(String title) {1
                myFrame.setTitle(title);
            }

            @Override
            protected void loadPreferences(Preferences prefs) {2
                try {
                    FileInputStream in = 
                        new FileInputStream(preferencesFile());
                    prefs.properties.loadFromXML(in);
                    in.close();
                } catch (IOException ignored) {}
            }

            @Override
            public void storePreferences(Preferences prefs) {3
                try {
                    FileOutputStream out = 
                        new FileOutputStream(preferencesFile());
                    prefs.properties.storeToXML(out, /*comment*/ null);
                    out.flush();
                    out.close();
                } catch (IOException ignored) {}
            }

            private File preferencesFile() {
                return new File(System.getProperty("user.home") +
                                File.separator + "MyPreferences.properties");
            }

            @Override
            public void dispose() {4
                super.dispose();

                infoField.setText(this + " has been disposed!");
                
                Timer timer = 
                    new Timer(5000 /*ms*/, new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            updateInfo();
                        }
                    });
                timer.setRepeats(false);
                timer.start();
            }
        };

        myApp.setHostComponent(myFrame);
        myApp.initialize(/*singleStep*/ false);

        myFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        ...SAME CODE AS newXMLFrame()...
        myFrame.setVisible(true);

        ...
    }

1

The default implementation of showTitle does nothing at all. The above implementation gives a title to the JFrame automatically reflecting the active opened document (method App.getActiveOpenedDocument) and its current save state (method OpenedDocument.isSaveNeeded).

2

The default implementation of loadPreferences does nothing at all. The above implementation loads the user preferences (A Preferences object is in fact a thin wrapper around an java.util.Properties object) from file "User_home_dir/MyPreferences.properties".

3

The default implementation of storePreferences does nothing at all. The above implementation saves the user preferences (A Preferences object is in fact a thin wrapper around an java.util.Properties object) from file "User_home_dir/MyPreferences.properties".

4

There is a way to know whether an App has been disposed (app.getState()==App.State.DISPOSED), but there is no way to be notified when this happens, hence the need to override method dispose.