This section introduces a fairly comprehensive Swing application and details its various components. This application is used throughout the rest of this book as the foundation application (so you can run each sample without having to create new windows, set up the menu bar, and perform other mundane tasks). For that reason, be sure to code and compile the application and have the source code handy before diving into future chapters.
Tip
For those of you who don’t like large code blocks, especially when it’s mostly GUI setup code, understand that this section is a necessary evil; it’s the basis for several future applications, all of which are made up of more interesting code fragments and concepts.
This application also demonstrates techniques that factor out and partition portions of your application to increase team development, as well as an introduction to concepts (such as dynamic class loading) used in later chapters. In this example, these modules are referred to as plug-ins. If you’ve used an application such as Adobe Photoshop or the NetBeans development environment, you’ve been exposed to applications that serve as a framework and then load other modules. Example 4-1 shows the entire class listing.
Example 4-1. The sample Swing application
package com.wiverson.macosbook; import javax.swing.JMenuItem; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JMenu; import java.awt.Cursor; import java.awt.BorderLayout; import java.util.Hashtable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import javax.swing.KeyStroke; public class SimpleEdit extends javax.swing.JFrame { // Used to set the number for new untitled windows private static int newWindows = -1; // Used to track all of the currently installed plugins private static Hashtable plugins = null; // The initial plugin configuration private static String[] argsconfig; /** Creates a new instance of SimpleEdit */ public SimpleEdit( ) { init( ); } /* --------------------- Application APIs ------------------------------------ */ /** Used by tools to get the text actual text area. * This wouldn't generally be recommended, but in this * case it's ok. * * In general, you'd want to use something to make the * interface more opaque (thereby freeing up options to * switch to a different underlying toolkit), but in this * case it would cost readability (since everyone can look * up a JTextArea). */ public javax.swing.JTextArea getJTextArea( ) { return this.mainTextArea; } /** Used by tools to get the current text */ public String getDocumentText( ) { return this.mainTextArea.getText( ); } /** Used by tools to set the current text */ public void setDocumentText(String in) { mainTextArea.setText(in); mainTextArea.setCaretPosition(mainTextArea.getText().length( )); mainTextArea.requestFocus( ); } /** Used by tools to add to the current text */ public void appendDocumentText(String in) { setDocumentText(mainTextArea.getText( ) + in); } /** Used by tools to set the status text at the bottom * of a frame. */ public void setStatusText(String in) { this.mainStatusText.setText(in); } /* --------------------- Initialization ------------------------------------ */ // Sets up and creates a "pristine" window environment private void init( ) { if(newWindows++ < 0) setTitle("Untitled"); else setTitle("Untitled-" + newWindows); initPlugins( ); initComponents( ); initMenuBar( ); } /* --------------------- Initialization: Plugins ---------------------------- */ // Installs all plugins as currently defined by the // private argsconfig. private void initPlugins( ) { if(plugins != null) return; if(argsconfig == null) return; if(argsconfig.length == 0) return; plugins = new Hashtable( ); for(int i = 0; i < argsconfig.length; i++) { // This may very well fail, as we are going // to be loading classes by name, which is // prone to errors (e.g. typos, etc.) try { // This requests the classloader to find a // given class by name. We are using this to // implement a plugin architecture, based on // expecting classes to implement a specific // interface (SimpleEditPlugin). If the class // can be loaded and cast without failure, // we are good to go. Class myClass = Class.forName(argsconfig[i]); SimpleEditPlugin myPlugin = (SimpleEditPlugin)myClass.getConstructor(null).newInstance(null); // Don't add the plugin if already installed. Allows for // eventual support for dynamically adding new plugins later. // Calls the Plugin init if this is the first time // it's being loaded. if(plugins.containsKey(myPlugin.getAction( ))) { return; } else { myPlugin.init(this); } // If we made it this far, the plugin has been loaded // and initialized, so it's ok to add to the list of // valid plugins. plugins.put(myPlugin.getAction( ), myPlugin); } catch(Exception e) { // This is not really adequate for a quality client // application, but it's acceptable for our purposes. System.out.println("Couldn't load Plugin: " + argsconfig[i]); System.out.println(e.getMessage( )); e.printStackTrace( ); } } } /* --------------------- Initialization: GUI Components---------------------- */ // The main visual components private javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane( ); private javax.swing.JTextArea mainTextArea = new javax.swing.JTextArea( ); private javax.swing.JToolBar mainToolBar = new javax.swing.JToolBar( ); private javax.swing.JTextField mainStatusText= new javax.swing.JTextField( ); private void initComponents( ) { this.getContentPane( ).setBackground(java.awt.Color.white); this.getContentPane().setLayout(new BorderLayout( )); this.getContentPane( ).add(mainScrollPane, BorderLayout.CENTER); this.getContentPane( ).add(mainToolBar, BorderLayout.NORTH); this.getContentPane( ).add(mainStatusText, BorderLayout.SOUTH); // This text field serves two purposes. It provides useful information // to the user, and also serves as a graceful "bump" for the Mac OS // grow box on the Mac OS platform. mainStatusText.setText("Ready."); mainStatusText.setCursor( Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); mainScrollPane.setViewportView(mainTextArea); mainTextArea.setEditable(true); mainTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); mainTextArea.setFont( new java.awt.Font("serif", java.awt.Font.PLAIN, 12) ); // Perhaps a tool might be added later to control this dynamically? mainTextArea.setLineWrap(true); // Generally looks terrible on all platforms, and requires // a fair amount of work to get it to work right. mainToolBar.setFloatable(false); initToolBar(mainToolBar, this); // Determine the offset value and stagger new windows // (with a reset every ten windows). A somewhat // unscientific mechanism, but it works well enough. int top_offset = 0; if((newWindows % 10) > 0) { top_offset =((this.newWindows) % 10) * 20 + 20; this.setLocation( new Double(getLocation().getX() + top_offset - 20).intValue( ), new Double(getLocation().getY() + top_offset).intValue( ) ); } int bottom_offset = 0; if (top_offset > 0) bottom_offset = top_offset - 20; // In a later chapter, we can use the JDirect and the // Carbon API GetAvailableWindowPositioningBounds( ) // to properly position this. java.awt.Dimension screensize = java.awt.Toolkit.getDefaultToolkit().getScreenSize( ); screensize = new java.awt.Dimension(640, screensize.height -128 - bottom_offset); this.setSize(screensize); } // Default items that always appear on the toolbar. // null items are treated as separators. String[] toolbarItems = {"New", "Open", null, "Timestamp"}; private void initToolBar(javax.swing.JToolBar myToolBar, SimpleEdit myFrame) { JButton newButton; for(int i = 0; i < toolbarItems.length; i++) { if(toolbarItems[i] != null) { // It would be nice to provide icons // instead of just text labels. newButton = new JButton(toolbarItems[i]); // Used to track the targets more easily newButton.putClientProperty("window", myFrame); newButton.addActionListener(actionListenerHandler); myToolBar.add(newButton); } else { myToolBar.add(new javax.swing.JToolBar.Separator( )); } } // Load all plugins into the toolbar if(plugins != null) if(plugins.size( ) > 0) { java.util.Enumeration e = plugins.elements( ); SimpleEditPlugin currentPlugin; while(e.hasMoreElements( )) { currentPlugin = (SimpleEditPlugin)e.nextElement( ); newButton = new JButton(currentPlugin.getAction( )); // We are using Swing client properties to // track additional information without having // to subclass - in effect, using the // client properties mechanism as a form of // delegation. newButton.putClientProperty("window", myFrame); newButton.putClientProperty("plugin", currentPlugin); newButton.addActionListener(actionListenerHandler); myToolBar.add(newButton); } } } /* --------------------- Initialization: Menu Bar ---------------------------- */ // The menu bar for the window private javax.swing.JMenuBar mainMenuBar = new javax.swing.JMenuBar( ); // The menus attached to the menu bar private JMenu menuFile = new JMenu( ); private JMenu menuEdit = new JMenu( ); private JMenu menuTools = new JMenu( ); // A Hashtable holding all of the default menu items, keyed by title protected static Hashtable menuItemsHashtable = new Hashtable( ); /* * The items to be installed into the menus. * Each item consists of an identification string and * a corresponding virtual key. * * For a "real" application, the default item titles * and virtual keys would be loaded from resource bundles, * and ideally the user would be able to configure their * own toolbar and menu structure. * * For this demonstration, however, this is adequate. */ private Object[][] fileItems = { {"New", new Integer(KeyEvent.VK_N)}, {"Open", new Integer(KeyEvent.VK_O)}, {"Close", new Integer(KeyEvent.VK_W)}, {null, null}, {"Save", new Integer(KeyEvent.VK_S)}, {"Revert to Saved", null}, {null, null}, {"Print...", new Integer(KeyEvent.VK_P)}, {"Print Setup...", null} }; private Object[][] editItems = { {"Undo", new Integer(KeyEvent.VK_Z)}, {"Redo", new Integer(KeyEvent.VK_Y)}, {null, null}, {"Cut", new Integer(KeyEvent.VK_X)}, {"Copy", new Integer(KeyEvent.VK_C)}, {"Paste", new Integer(KeyEvent.VK_V)}, {"Delete", null}, {"Select All", new Integer(KeyEvent.VK_A)} }; private Object[][] toolItems = { {"Timestamp", null} }; private void dispatchEvent(ActionEvent evt, String tag) { SimpleEdit myFrame = null; SimpleEditPlugin myPlugin = null; if(evt.getSource( ) instanceof JComponent) { myFrame = (SimpleEdit) (((JComponent)evt.getSource( )).getClientProperty("window")); myPlugin = (SimpleEditPlugin) (((JComponent)evt.getSource( )).getClientProperty("plugin")); } // If it's a plugin, hand off to the plugin to handle if(myPlugin != null) { myPlugin.doAction(myFrame, evt); return; } // Handle minimal required functionality. // It could legitimately be argued that even this // functionality should be split off into an // overarching set of plugin functionality... // but this is adequate for now, and reinforces // the notion of certain "default" services. if(tag.compareTo("New") == 0) doNew( ); if(tag.compareTo("Close") == 0) doClose(myFrame); if(tag.compareTo("Timestamp") == 0) doTimestamp(myFrame); } /* * Default event processing. */ private void doNew( ) { (new SimpleEdit()).show( ); } private void doTimestamp(SimpleEdit myFrame) { if(myFrame != null) myFrame.mainTextArea.setText(myFrame.mainTextArea.getText( ) + System.getProperty("line.separator") + new java.util.Date( ) + " : "); myFrame.mainTextArea.setCaretPosition( myFrame.mainTextArea.getText().length( )); myFrame.mainTextArea.requestFocus( ); } // Used to track the number of open windows, and // automatically quit when they are all closed. private static int openWindows = 0; // Overrides the default hide to see how many windows are currently // showing. If none are visible, quit the app. /** Hides the window. If no windows are visible, terminates quietly. */ public void hide( ) { super.hide( ); openWindows--; if(openWindows == 0) System.exit(0); } public void show( ) { super.show( ); openWindows++; // All ready to go, go ahead and get ready for input. this.appendDocumentText(""); } private void doClose(SimpleEdit myFrame) { myFrame.hide( ); } /* This variable is used to track the default accelerator * key for this platform. */ private int preferredMetaKey = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask( ); private void setupMenu(JMenu myMenu, Object[][] menuconfig, SimpleEdit thisFrame) { JMenuItem currentMenuItem; for(int i = 0; i < menuconfig.length; i++) { if(menuconfig[i][0] != null) { currentMenuItem = new JMenuItem( ); currentMenuItem.setLabel((String)menuconfig[i][0]); if(menuconfig[i][1] != null) { int keyCode = ((Integer)menuconfig[i][1]).intValue( ); KeyStroke key = KeyStroke.getKeyStroke(keyCode, preferredMetaKey); currentMenuItem.setAccelerator(key); } currentMenuItem.setEnabled(false); currentMenuItem.setActionCommand((String)menuconfig[i][0]); currentMenuItem.putClientProperty("window", thisFrame); currentMenuItem.addActionListener(actionListenerHandler); // Put the menu item into the menu hash to add handlers later menuItemsHashtable.put((String)menuconfig[i][0], currentMenuItem); myMenu.add(currentMenuItem); } else { javax.swing.JSeparator sep = new javax.swing.JSeparator( ); myMenu.add(sep); } } } // A single default ActionListener that punts to dispatchEvent( ). private ActionListener actionListenerHandler = new ActionListener( ) { public void actionPerformed(ActionEvent evt) { Object src = evt.getSource( ); if(src instanceof JMenuItem) { String input = ((JMenuItem)src).getLabel( ); dispatchEvent(evt, input); } if(src instanceof JButton) { String input = ((JButton)src).getLabel( ); dispatchEvent(evt, input); } } }; private void initMenuBar( ) { mainMenuBar = new javax.swing.JMenuBar( ); menuFile = new JMenu("File"); setupMenu(menuFile, fileItems, this); mainMenuBar.add(menuFile); menuEdit = new JMenu("Edit"); setupMenu(menuEdit, editItems, this); mainMenuBar.add(menuEdit); menuTools = new JMenu("Tools"); setupMenu(menuTools, toolItems, this); mainMenuBar.add(menuTools); JMenuItem newMenuItem; if(plugins != null) if(plugins.size( ) > 0) { java.util.Enumeration e = plugins.elements( ); SimpleEditPlugin currentPlugin; while(e.hasMoreElements( )) { currentPlugin = (SimpleEditPlugin)e.nextElement( ); newMenuItem = new JMenuItem( ); newMenuItem.setLabel(currentPlugin.getAction( )); newMenuItem.setEnabled(true); newMenuItem.setActionCommand(currentPlugin.getAction( )); newMenuItem.putClientProperty("window", this); newMenuItem.putClientProperty("plugin", currentPlugin); newMenuItem.addActionListener(actionListenerHandler); menuTools.add(newMenuItem); } } ((JMenuItem)menuItemsHashtable.get("New")).setEnabled(true); ((JMenuItem)menuItemsHashtable.get("Timestamp")).setEnabled(true); ((JMenuItem)menuItemsHashtable.get("Close")).setEnabled(true); setJMenuBar(mainMenuBar);
} /* ----------------- The Main Method: Menu Bar ---------------------------- */ public static void main(String[] args) { argsconfig = args; (new SimpleEdit()).show( ); } }
If you’re new to
Swing, you may wish to look up the reference material for imported
classes. You’ll also see several other classes named
explicitly in the code (like
javax.swing.JScrollPane
) rather than imported,
chiefly for self-documentation (and also because Swing occasionally
has duplicate names in multiple packages). You’ll
also notice that the example class extends JFrame
,
a pretty standard technique in Swing, and calls an initialization
routine in its constructor.
Using an init( )
method instead of working in the
constructor allows you to recycle an object instead of allocating a
new one:
// Create the object the first time SimpleEdit editor = new SimpleEdit( ); // Do some work that changes the state of the editor object // This takes more time and memory, depending on the JVM editor = new SimpleEdit( ); // This approach is better and faster (instead of using new) editor.init( );
Using an init( )
method is not actually required
in this implementation, but it’s a good pattern for
instances in which you might reuse the object without creating a new
one. For example, you could use the init( )
method
to reset the window to a “pristine”
state before loading a file from disk.
After the constructor,
you’ll see several methods that aid in text
manipulation (such as getDocumentText( )
or
getJTextArea( )
). While these examples could be
considered utility methods, they form the backbone of this
application’s application programming interface
(API). In other words, other applications could use this class as a
module, with text editing capability, through these methods. The
class provides a notebook-style framework that you could use in a
code editor or journaling software, for instance. As a result, these
utility methods become very important—applications using this
class will depend on the methods for interaction with
SimpleEdit
.
Moving back into the variable declaration section of the class, you’ll see a private counter that keeps track of new and untitled document names:
// Used to set the number for new untitled windows private static int newWindows = -1;
This counter determines the offset at which to place the document
window, as well as the window identifier (to help the user keep
multiple untitled windows distinct). It is also critical for the
init( )
method:
// Sets up and creates a "pristine" window environment private void init( ) { if(newWindows++ < 0) setTitle("Untitled"); else setTitle("Untitled-" + newWindows); initPlugins( ); initComponents( ); initMenuBar( ); }
What happens next is interesting: the
init( )
method calls initPlugins( )
, which then loads any plug-ins that the class is
instructed to bring online (more on that in a minute). Of course, I
just told you that this class has an API that other applications can
use for accessing it. This means that the sample class suddenly has
dual uses: as a framework to be used by other applications (basically
a plug-in), and as an application in its own right, able to load its
own plug-ins. This is a common concept in GUI programming: components
are both containers and units that are contained. The API methods
become more critical as applications external to the class use them
to operate on the framework, and plug-ins internal to it might need
to call back to them.
In the initPlugins( )
method, you’ll see much of the heavy lifting
performed as plug-ins are loaded. The plug-ins are specified by a set
of command-line arguments that specify the class name of each desired
plug-in. The JVM class loader then has to find the class by name.
Classes are loaded and
then cached in a Hashtable
(called, not
surprisingly, plugins
). This improves performance:
the plug-ins are loaded only when the application is first launched.
Traditionally, Mac OS users often expect delays when an application
initially starts, but once they begin to work, lengthy delays are
unacceptable.
You’ll note that the
plug-in classes are cast
to the type
SimpleEditPlugin
(the next section deals
with this interface, so don’t get too impatient).
Plug-ins are expected to implement this interface. Plug-in authors
are given this interface and the application APIs, which are
sufficient for writing additional modules that this framework can
use.
Next comes the
initComponents( )
method, which creates the core interface of
the application’s main window. It’s
fairly straightforward. Check the Swing documentation for details on
how the specific APIs are used.
This method then delegates additional GUI processing to
initToolbar( )
, which does what it says it does:
it deals with the application toolbar and its buttons. The
application uses both the default application actions and the plug-in
configuration options to create a user toolbar.
The menu bar configuration is handled via multiple-dimension arrays:
private Object[][] fileItems = { {"New", new Integer(KeyEvent.VK_N)}, {"Open", new Integer(KeyEvent.VK_O)}, {"Close", new Integer(KeyEvent.VK_W)}, {null, null}, {"Save", new Integer(KeyEvent.VK_S)}, {"Revert to Saved", null}, {null, null}, {"Print...", new Integer(KeyEvent.VK_P)}, {"Print Setup...", null} }; private Object[][] editItems = { {"Undo", new Integer(KeyEvent.VK_Z)}, {"Redo", new Integer(KeyEvent.VK_Y)}, {null, null}, {"Cut", new Integer(KeyEvent.VK_X)}, {"Copy", new Integer(KeyEvent.VK_C)}, {"Paste", new Integer(KeyEvent.VK_V)}, {"Delete", null}, {"Select All", new Integer(KeyEvent.VK_A)} }; private Object[][] toolItems = { {"Timestamp", null} };
This technique isn’t very object-oriented, but it lays out the menu visually in the code, making it easy to see how the menu bar will look when rendered graphically.
Items defined as {null
, null}
are treated as separator bars. The action name is defined by the
first element in each mini-array, and the second
Integer
element specifies the keyboard accelerator
constant.
A lot of code is associated with getting these menu bars to actually
do something useful, beginning with the dispatchEvent( )
method. The event handling is performed mainly
by a single handler, with Swing client property values used to store
and handle event dispatching. Basically, the framework handles the
most basic actions (calling upon the doNew( )
,
doTimeStamp( )
, and doClose( )
methods), while everything else is handed off to the appropriate
plug-in to handle on its own.
Once event handling is dealt with, add this trick to your toolkit:
/* This variable is used to track the default accelerator * key for this platform. */ private int preferredMetaKey = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask( );
This little-known API is critical for proper cross-platform user interface application development. Mac OS X users expect to use the Meta key, officially referred to as the Command key (although I still tend to call it the “open apple” key). Windows applications, however, typically use the Control key. This application allows both options to map to the same UI action.
Many Java applications are hardcoded to use the control character as their default keyboard accelerator. Ironically, a user (or developer) used to Windows would consider this situation an ordinary feature: the same application running on two different platforms uses the same keyboard commands. However, it’s terrible for any real Mac OS X user. Use this API when determining the default keyboard shortcuts and, if possible, include an interface that reassigns keyboard commands.
Finally, the menu can actually be created with the
setupMenu( )
and initMenuBar( )
methods
(the latter of which was called in the init( )
method). These methods use the arrays defined earlier to set up the
menus, and the various event handlers to react to them. Some
additional Swing client properties assist with event dispatching.
This menu bar is created and set for each window whenever a window is created. This serves an important purpose: on Windows and other windowing toolkits, each window is given a specific menu bar within the bounds of the window itself. On Mac OS X, a proper application has a single global menu bar shared by all windows. This application doesn’t care which model is selected, and functions identically on all platforms.
You learned about event processing
and the dispatchEvent( )
method earlier, but now
let’s look at the doClose( )
method and its two
helpers, hide( )
and show( )
:
public void hide( ) { super.hide( ); openWindows--; if(openWindows == 0) System.exit(0); } public void show( ) { super.show( ); openWindows++; // All ready to go, go ahead and get ready for input. this.appendDocumentText(""); } private void doClose(SimpleEdit myFrame) { myFrame.hide( ); }
Note that the application terminates automatically when the final window is closed. This termination provides a relatively seamless user experience, minimizing the user’s awareness of the application as a process that runs independently of any documents.
Tip
To get an idea of how this scenario affects your system, open Calculator, Address Book, and Internet Explorer. Now close out all your windows; note that while Calculator and Address Book are no longer running, Internet Explorer still waits for a “Quit” command. The example application behaves like Calculator and Address Book, assuming that when all windows are closed, the application should follow suit.
Last but not least, at the end of the code lies a simple
main( )
method:
public static void main(String[] args) { argsconfig = args; (new SimpleEdit()).show( ); }
With a solid set of methods already in hand, the main( )
method only has to configure the application and open a
single window. It calls the constructor for the
SimpleEdit
class, which in turn calls the
init( )
method, which then sets up the various
plug-ins for the application.
The
SimpleEditPlugin
interface allows the SimpleEdit
application to deal with plug-ins at runtime, assuming that they
implement this public interface. I’ve left the
discussion of this interface for the end of this chapter so that you
first learn how to use Swing with Mac OS X and don’t
get too hung up in inheritance and polymorphism.
This application demonstrates how to use dynamic class loading to let
the same system add additional functionality without changing the
core application. Chapter 5 uses a similar
technique to isolate Mac OS X-specific code from the rest of the
application. In a nutshell, the SimpleEditPlugin
interface abstracts plug-in-specific (or platform-specific) details
from SimpleEdit, and lets SimpleEdit handle all plug-ins generically.
The code shown in the initPlugins( )
method (see Example 4-1) relies on the fact that
each plug-in implements a specific interface so it can easily call
methods on a loaded class, as shown here:
Class myClass = Class.forName(argsconfig[i]); SimpleEditPlugin myPlugin = (SimpleEditPlugin)myClass.getConstructor(null).newInstance(null);
Example 4-2 shows the actual
SimpleEditPlugin
interface. As you can see, it is
remarkably simple.
Example 4-2. The SimpleEditPlugin interface
package com.wiverson.macosbook; public interface SimpleEditPlugin { // Returns a list of actions which will be registered // The tool will then be notified if an action is // selected. public String getAction( ); // Notification of an action which was registered by // this tool. public void doAction(SimpleEdit frame, java.awt.event.ActionEvent evt); // Called once when the plugin is first loaded public void init(SimpleEdit frame); }
Code that implements this interface is loaded by SimpleEdit when it is launched with one or more command-line parameters with the fully qualified class name. As long as the interface is followed, SimpleEdit can interact with the plug-in.
An example of a simple plug-in is one that simply beeps when the user invokes it from the menu bar or toolbar. Example 4-3 shows how easily you can code up such a plug-in.
Example 4-3. A beeping plug-in
package com.wiverson.macosbook.plugin; import com.wiverson.macosbook.SimpleEdit; public class BeepPlugin implements com.wiverson.macosbook.SimpleEditPlugin { public BeepPlugin( ) { } public void doAction(SimpleEdit frame, java.awt.event.ActionEvent evt) { java.awt.Toolkit.getDefaultToolkit().beep( ); frame.setStatusText("Beep!"); } public String getAction( ) { return "Beep"; } public void init(SimpleEdit frame) { frame.setStatusText("Loaded BeepPlugin"); } }
Assuming that this plug-in was compiled successfully, it is possible to launch SimpleEdit with this installed plug-in by executing the following command:
java com.wiverson.macosbook.SimpleEdit com.wiverson.macosbook.plugin. BeepPlugin
Once the SimpleEdit application starts up and detects a command-line
argument, it loads the named plug-in. The getAction( )
method tells SimpleEdit which text to display in the user interface,
and when the user selects this option, the doAction( )
method is called. The arguments passed in allow the plug-in to affect
the window and text area by working with the passed-in SimpleEdit
object. In this manner, the plug-in is isolated from all user
interface details of SimpleEdit.
In later chapters we will use this plug-in architecture to add everything from spellcheck capabilities to communication with web services. The next chapter uses this mechanism to add support for Apple-specific extensions to the Java platform.
Get Mac OS X for Java Geeks now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.