HomeAPIGuideSF Project Home

JAPI: How to develop Swing applications with JAPI

This part of the guide demonstrates how to develop a Swing application using the JAPI library from scratch.

Our example application shall be a simple text editor without any advanced features. Examples are good if examples are easy to understand, so we want to keep things simple.

Screenshots of sample applicationScreenshot of sample editor application

Step 1: Identify Use Cases

The first step is to identify the use cases of the application. The following use cases are involved:

The use cases Inserting text, Overwriting text and Deleting text are already handled automatically by the Swing component JTextPane which we will to use for our text editor application. A JTextPane can also be attached to a DefaultEditorKit which handles Cutting text to the clipboard, Copying text to the clipboard and Pasting text from the clipboard.

Step 2: Defining the package

Our package will be net.sf.japi.examples.editor. Our main class will be net.sf.japi.examples.editor.Editor. The first version will look like this:

package net.sf,japi.examples.editor;

/** A Text Editor application.
 * This is an example for developing an application with JAPI.
 * @author Christian Hujer
 */
public class Editor {

    /** Main program.
     * @param args command line arguments (currently ignored)
     */
    public static void main(final String... args) {
        new Editor();
    }

} // class Editor

Step 3: Menu Concept

We will define the menu structure:

Step 4: Defining the menu

In Swing, a complete application menu is defined as a JMenuBar which contains JMenus, these JMenus contain JMenuItems as entries or other JMenus as sub menus.

In JAPI, creating a complete application menu is a one liner in the Java code. The complete menu is defined in a properties file named action.properties.

editor.menuBar=file edit

This line defines that the menu bar consists of two menus named file and edit. The names file and edit are only internally used.

file.menu=fileNew fileOpen fileSave fileSaveAs fileQuit
file.text=File
file.mnemonic=F

These lines define that the menu file is labelled "File", with "F" as mnemonic, and that the file action is a menu consisting of other actions. These other actions are fileNew, fileOpen, fileSave, fileSaveAs and fileQuit.

fileNew.text=New
fileNew.mnemonic=N
fileNew.accel=ctrl pressed N
fileNew.shortdescription=Creates a new file

These lines define that the action "fileNew" is a menu item labelled "New", with "N" as mnemonic, "Ctrl+N" as keyboard shortcut and "Creates a new file" as tooltip text. The other actions and menus are defined analoguous.

It's also possible to store "normal" properties in this property file. Let's store a property for the frame title.

frame.title=Editor (JAPI usage example)

Step 5: Using the defined menu

package net.sf.japi.examples.editor;

import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import javax.swing.JFrame;
import net.sf.japi.swing.ActionFactory;

/** A Text Editor application.
 * This is an example for developing an application with JAPI.
 * @author Christian Hujer
 */
public class Editor {

    /** Action Factory. */
    private static final ActionFactory ACTION_FACTORY = getFactory("net.sf.japi.examples.editor");

    /** Main program.
     * @param args command line arguments (currently ignored)
     */
    public static void main(final String... args) {
        new Editor();
    }

    /** Application frame. */
    private JFrame frame = new JFrame(ACTION_FACTORY.getString("frame.title"));

    /** Editor component. */
    private JTextPane textPane = new JTextPane();

    /** FileChooser for opening and saving files. */
    private JFileChooser fileChooser = new JFileChooser();

    /** Create the Editor. */
    public Editor() {
        frame.setJMenuBar(ACTION_FACTORY.createMenuBar(true, "editor", this));
        frame.add(ACTION_FACTORY.createToolBar(this, "editor"), NORTH);
        frame.add(new JScrollPane(textPane));
        frame.pack();
        frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        frame.setVisible(true);
    }

} // class Editor

As you see, programming the menu is just a one liner. But how is code attached to the actions attached to the menu items? See next step.

Step 6: Attaching Code to the actions

The name of an action is at the same time the name of the method that's invoked using Reflection. Such action methods must be public instance methods with empty parameter list and void return type. These methods may declare any Exception you want. Reflection will be used to find, independently of declared exceptions, a declarative exception handler for any exception thrown by the action method. The action methods should be annotated as @ActionMethod. In future this will be used for a tool that performs a static code analysis and reports action methods not covered with action definitions and action definitions not covered with action methods.

Step 6.1: Implementing fileNew

    /** Action method.
     * @used
     */
    @ActionMethod public void fileNew() {
        textPane.setText("");
        file = null;
    }

Step 6.2: Implementing fileOpen

    /** Action method.
     * @used
     */
    @ActionMethod public void fileOpen() throws FileNotFoundException, IOException {
        if (fileChooser.showOpenDialog(frame) == APPROVE_OPTION) {
            final StringBuilder newText = new StringBuilder();
            final File newFile = fileChooser.getSelectedFile();
            final FileReader in = new FileReader(newFile);
            try {
                final char[] buf = new char[4096];
                for (int charsRead; (charsRead = in.read(buf)) != -1; ) {
                    newText.append(buf, 0, charsRead);
                }
                textPane.setText(newText.toString());
                file = newFile;
            } finally {
                in.close();
            }
        }
    }

Step 7: Adding precreated actions

For the edit actions Cut, Copy and Paste it would be nice if they were consistent with those provided by the editor kit. In fact it would be even better if we would use the Actions defined by the editor kit. That's of course also possible with ActionFactory. You can register any number of ActionProviders with an ActionFactory. An ActionProvider is used to lookup actions using their names (keys) before ActionFactory will create its own. All we need to do is have our Editor implement ActionProvider and make sure it provides the right Actions.

    /** The supported editor action names and their corresponding kit action names. */
    private static final Map<String, String> editorActionNames = new HashMap<String, String>();
    static {
        editorActionNames.put("editCut",       DefaultEditorKit.cutAction);
        editorActionNames.put("editCopy",      DefaultEditorKit.copyAction);
        editorActionNames.put("editPaste",     DefaultEditorKit.pasteAction);
        editorActionNames.put("editSelectAll", DefaultEditorKit.selectAllAction);
    };

    /** {@inheritDoc} */
    @Nullable public Action getAction(final String key) {
        for (final Action action : textPane.getActions()) {
            final String realKey = editorActionNames.get(key);
            if (realKey != null && realKey.equals(action.getValue(Action.NAME))) {
                ACTION_FACTORY.initAction(true, action, key);
                return action;
            }
        }
        return null;
    }

Complete source

Java Source: net/sf/japi/examples/editor/Editor.java

package net.sf.japi.examples.editor;

import static java.awt.BorderLayout.NORTH;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import javax.swing.Action;
import javax.swing.JFileChooser;
import static javax.swing.JFileChooser.APPROVE_OPTION;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import javax.swing.text.DefaultEditorKit;
import org.jetbrains.annotations.Nullable;
import net.sf.japi.swing.ActionFactory;
import static net.sf.japi.swing.ActionFactory.getFactory;
import net.sf.japi.swing.ActionMethod;
import net.sf.japi.swing.ActionProvider;

public class Editor implements ActionProvider {

   
/** Action Factory. */
   
private static final ActionFactory ACTION_FACTORY = getFactory("net.sf.japi.examples.editor");

   
/** The supported editor action names and their corresponding kit action names. */
   
private static final Map<String, String> editorActionNames = new HashMap<String, String>();
   
static {
       
editorActionNames.put("editCut",       DefaultEditorKit.cutAction);
        editorActionNames.put
("editCopy",      DefaultEditorKit.copyAction);
        editorActionNames.put
("editPaste",     DefaultEditorKit.pasteAction);
        editorActionNames.put
("editSelectAll", DefaultEditorKit.selectAllAction);
   
};

   
/** Application frame. */
   
private final JFrame frame = new JFrame(ACTION_FACTORY.getString("frame.title"));

   
/** Editor component. */
   
private final JTextPane textPane = new JTextPane();

   
/** FileChooser for opening and saving files. */
   
private final JFileChooser fileChooser = new JFileChooser();

   
/** Currently opened file.
     * Maybe
<code>null</code> in case the current document was not already saved.
     */
   
private File file;

   
/** Create the Editor. */
   
public Editor() {
       
ACTION_FACTORY.addActionProvider(this);
        frame.setJMenuBar
(ACTION_FACTORY.createMenuBar(true, "editor", this));
        frame.add
(ACTION_FACTORY.createToolBar(this, "editor"), NORTH);
        frame.add
(new JScrollPane(textPane));
        frame.pack
();
        frame.setDefaultCloseOperation
(DISPOSE_ON_CLOSE);
        frame.setVisible
(true);
   
}

   
/** {@inheritDoc} */
   
@Nullable public Action getAction(final String key) {
       
for (final Action action : textPane.getActions()) {
           
final String realKey = editorActionNames.get(key);
           
if (realKey != null && realKey.equals(action.getValue(Action.NAME))) {
               
ACTION_FACTORY.initAction(true, action, key);
               
return action;
           
}
        }
       
return null;
   
}

   
/** Action method.
     * @used
     */
   
@ActionMethod public void fileNew() {
       
textPane.setText("");
        file =
null;
   
}

   
/** Action method.
     * @used
     */
   
@ActionMethod public void fileOpen() throws FileNotFoundException, IOException {
       
if (fileChooser.showOpenDialog(frame) == APPROVE_OPTION) {
           
final StringBuilder newText = new StringBuilder();
           
final File newFile = fileChooser.getSelectedFile();
           
final FileReader in = new FileReader(newFile);
           
try {
               
final char[] buf = new char[4096];
               
for (int charsRead; (charsRead = in.read(buf)) != -1; ) {
                   
newText.append(buf, 0, charsRead);
               
}
               
textPane.setText(newText.toString());
                file = newFile;
           
} finally {
               
in.close();
           
}
        }
    }

   
/** Action method.
     * @used
     */
   
@ActionMethod public void fileQuit() {
       
if (ACTION_FACTORY.showQuestionDialog(frame, "reallyQuit")) {
           
frame.dispose();
       
}
    }

   
/** Action method.
     * @used
     */
   
@ActionMethod public void fileSave() throws IOException {
       
if (file == null) {
           
fileSaveAs();
       
} else {
           
final FileWriter out = new FileWriter(file);
           
try {
               
out.write(textPane.getText());
           
} finally {
               
out.close();
           
}
        }
    }

   
/** Action method.
     * @used
     */
   
@ActionMethod public void fileSaveAs() throws IOException {
       
if (fileChooser.showSaveDialog(frame) == APPROVE_OPTION) {
           
final File newFile = fileChooser.getSelectedFile();
           
final FileWriter out = new FileWriter(newFile);
           
try {
               
out.write(textPane.getText());
                file = newFile;
           
} finally {
               
out.close();
           
}
        }
    }

   
/** Main program.
     *
@param args command line arguments
     */
   
public static void main(final String... args) {
       
new Editor();
   
}

}
// class Editor

Action Properties: net/sf/japi/examples/editor/action.properties

# Action properties for Editor, non-localizable information.
# This file MUST be ISO-8859-1
ActionFactory.additionalBundles=net.sf.japi.examples.editor.messages

editor.menubar=file edit
editor.toolbar=fileNew fileOpen fileSave fileSaveAs - editCut editCopy editPaste

file.menu=fileNew fileOpen fileSave fileSaveAs fileQuit
edit.menu=editCut editCopy editPaste

fileNew.icon=general/New16
fileOpen.icon=general/Open16
fileOpen.exception.java.io.FileNotFoundException.messagetype=ERROR_MESSAGE
fileOpen.exception.java.io.IOException.messagetype=ERROR_MESSAGE
fileSave.icon=general/Save16
fileSave.exception.java.io.IOException.messagetype=ERROR_MESSAGE
fileSaveAs.icon=general/SaveAs16
fileSaveAs.exception.java.io.IOException.messagetype=ERROR_MESSAGE
editCut.icon=general/Cut16
editCopy.icon=general/Copy16
editPaste.icon=general/Paste16

Message Properties: net/sf/japi/examples/editor/messages.properties

# Action properties for Editor, localizable information.
# This file MUST be ISO-8859-1
#
frame.title=Text Editor (JAPI usage example)

file.text=File
file.mnemonic=F

fileNew.text=New
fileNew.mnemonic=N
fileNew.accel=ctrl pressed N
fileNew.shortdescription=Creates a new empty text document.

fileOpen.text=Open...
fileOpen.mnemonic=O
fileOpen.accel=ctrl pressed O
fileOpen.shortdescription=Opens an existing text document.
fileOpen.exception.java.io.FileNotFoundException.title=File not found
fileOpen.exception.java.io.FileNotFoundException.message=<html>The file couldn''t be opened. Reason:<br>{0}
fileOpen.exception.java.io.IOException.title=I/O error
fileOpen.exception.java.io.IOException.message=<html>I/O error while reading. Reason:<br>{0}


fileSave.text=Save
fileSave.mnemonic=S
fileSave.accel=ctrl pressed S
fileSave.shortdescription=Saves the current document.
fileSave.exception.java.io.IOException.title=I/O error
fileSave.exception.java.io.IOException.message=<html>I/O error while writing. Reason:<br>{0}

fileSaveAs.text=Save As...
fileSaveAs.mnemonic=A
fileSaveAs.accel=ctrl shift pressed S
fileSaveAs.shortdescription=Saves the current document in a new filename.
fileSaveAs.exception.java.io.IOException.title=I/O error
fileSaveAs.exception.java.io.IOException.message=<html>I/O error while writing. Reason:<br>{0}

fileQuit.text=Quit
fileQuit.mnemonic=Q
fileQuit.accel=ctrl pressed Q
fileQuit.shortdescription=Quits the text editor.

edit.text=Edit
edit.mnemonic=E

editCut.text=Cut
editCut.mnemonic=C
editCut.accel=ctrl pressed X
editCut.shortdescription=Cuts the current selection to the clipboard

editCopy.text=Copy
editCopy.mnemonic=O
editCopy.accel=ctrl pressed C
editCopy.shortdescription=Copies the current selection to the clipboard

editPaste.text=Paste
editPaste.mnemonic=P
editPaste.accel=ctrl pressed V
editPaste.shortdescription=Pastes over the current selection from the clipboard

editSelectAll.text=Select All
editSelectAll.mnemonic=A
editSelectAll.accel=ctrl pressed A
editSelectAll.shortdescription=Selects the entire editor content

reallyQuit.title=Really Quit?
reallyQuit.message=Do you really want to quit the editor?

SourceForge.net LogoSupport This ProjectValid HTML 4.01!Valid CSS! Feedback: webmaster
$Date: 2006-08-23 00:59:42 +0200 (Mi, 23 Aug 2006) $