Chapter 21

Designing and Implementing Advanced Applications


CONTENTS


Java is a programming language that knows no bounds. Just as you can create advanced applications in other programming languages, you can do so in Java as well. This chapter takes you step by step through the development of an advanced application called Jompanion.

Jompanion is a companion editor for Java programming that includes many features found in advanced applications. Whereas previous chapters have focused primarily on programming concepts, this chapter focuses on design concepts. The design of your application's interface tells the user a lot about the program itself. Well-designed applications feature friendly graphical interfaces complete with pull-down menus, choice menus, and pop-up windows.

Because the Jompanion editor has a well-thought-out design, it is the perfect example for illustrating design issues. The editor also includes a complete set of advanced functions that you can use in just about any application you create.

Project Development and Planning

Although you can often create a small application on a whim, you should create advanced applications only with careful forethought. The more complex the project, the more involved your planning should be. For most projects, planning involves mapping out the steps necessary to complete the project on paper. For very complex projects you might want to use a project-management tool such as Microsoft Project to make the planning process easier.

Each step is generally allocated a time period, such as six days for planning or eight days for implementation. Some steps are dependent on other steps, meaning they cannot be started until other aspects of the project are completed. Other steps are not dependent on any other steps and can be performed at any time during the project's development. Therefore, when you map out the steps necessary to complete the project, you should always note which steps are dependent on others and which are independent.

After you plot out the steps that will take you through project completion, you can group the steps into phases that form the software development life cycle for the application. Generally, most projects include six phases:

The duration of each phase should be relevant to the size and complexity of the application you are developing. You might be able to develop and implement a project with limited scope in a single day, but for more advanced projects this process can take days, weeks, or months.

Although each phase should be performed in order, you can sometimes combine phases if it suits your needs. For Jompanion, I combined the requirements, specification, and planning phases into a general phase called development and planning.

Determining Requirements

When you begin a new project, the first thing you should do is develop a list of project needs. If you have not created a project folder, you should do so now. The project folder will hold everything related to the project. Generally, your project folder will have both a paper and an electronic form. The paper form of the folder can be a three-ring binder or a note pad. The electronic version of the folder should reside on your hard drive as a directory.

To help you develop a list of project requirements, you should first identify the purpose, scope, and target user for the application. Your purpose statement should describe the type of application you are creating and why you are creating it. The scope of the project relates to its size and functionality. The target user is the person or group for whom you are developing the application.

Often you will have primary and secondary target users. The primary target users are those who will use the application. The secondary target users are those who may purchase or review the application. For example, although children would use an educational application, adults would purchase it. The children using the application would want it to be fun. The adults buying the application would want it to help the children learn.

The purpose, scope, and target user for Jompanion are defined as follows:

Purpose:
A companion editor for Java programming
Scope:
An advanced application with many features
Target user:
Anyone who plans to do Java programming and wants an easy-to-use text editor.

Next, make a list of constraints for the project. Constraints that apply to most projects include duration, size, and budget. Normally, you will specify the duration of the project if you have a deadline. When you have a set amount of time or money to invest in the project, you will want to constrain the budget. You might also want to limit the size of the application, especially if performance and memory use are a concern. The initial constraints for Jompanion are

Project duration:
No more than 2 weeks
Project size:
25-50KB
Project budget:
40-50 hours

After you have determined the project constraints, you can develop a list of needs. Project needs include personnel, computer hardware and software, financial resources, and supplies. For Jompanion, the list of needs is small:

Defining the Necessary Objects

After determining project requirements, you should create an overview of the inner workings of the application by defining the necessary objects and the flow of data between the objects. The way you map out the objects and their flow will largely depend on the type of application you are developing, the modeling tools you are using, and the development philosophy at the organization where you work. For Jompanion, I borrowed some of the concepts used in structured analysis.

Preliminary modeling in structured analysis is done in the environmental model, which helps you define the interfaces between your application and the user. The environmental model includes three key components:

You will find that the components of the environmental model are useful for most projects and can be modified to suit almost any type of project. Earlier I defined a brief statement of purpose for Jompanion. I expanded upon it for use by management as follows:

Statement of Purpose:
The purpose of the Jompanion text editor is to be a companion editor for Java development. The editor includes advanced features that make it useful for development, but it uses minimal system resources.

The context diagram defines the external entities with which the application interacts. You will find that the more complex the system, the more useful the diagram. As you can see in Figure 21.1, the only entities Jompanion interacts with are the user and the file system.

Figure 21.1 : Context diagram for Jompanion.

The most useful component of the environmental model is the event list. The event list for Jompanion provides a complete list of events it should support. You use this list to help you define objects for your application. Events for Jompanion include

After you develop the environmental model for your application, you can go on to more detailed modeling. This is when you translate the event list into data flow, input, and output. Whether you use data-flow diagrams, state-transition diagrams, petri nets, or some other type of data models will primarily depend on the type of application you are developing.

Plotting the Project to Completion

After you have modeled the project, you can plan it. The first thing you should do in the planning stage is work out a schedule for the project. The schedule does not have to be absolute, however; the best schedules are flexible and include the necessary milestones, goals, and time allocations to take the project through to completion.

The purpose of the schedule is to get you thinking about the project. Plotting the project to completion helps you visualize the finished project and transforms it from an abstract idea to something more tangible and doable. The schedule for Jompanion is based on the materials developed in earlier stages of the project.

After you develop a schedule, reevaluate the project specifications. Are the constraints, needs, and functionality determined earlier realistic given the timetable you have developed? If the answer is yes, you can go on to designing the project.

If the answer is no, carefully review the project and see what you can modify. Sometimes this might mean going back to management and asking for more time. Other times it might mean postponing the development of nonessential functions.

Project Design

During the design phase, you use the preliminary materials developed in the previous phases to design the application. Because Java is an object-oriented language, it seems logical that you use object-oriented design methodology to create Java applications. The goal of object-oriented design is to determine the objects in an application and then to design the application in terms of those objects.

If you take a close look at the event list developed earlier in the section titled "Defining the Necessary Objects," you should see that it translates into a series of objects needed in the application. Start by grouping the events into object categories, such as functions and user interfaces. Then consider which object groups should be added to the main window. For Jompanion, these objects include a menu bar, choice menus, a text area, and the file you are editing. There must be containers for these objects in the application.

You also need to allow users to manipulate the main window of the application. In most applications, this is a feature of the method that displays the window. This method should describe either the attributes of the window or the call functions that describe those attributes. Some of the attributes you should consider for the main window of any application include

The next object to design is a menu bar with several menus. You can determine the type of menus by grouping the functions of the editor into common categories, as shown in Table 21.1.

Table 21.1. The MenuBar object.
ObjectComponent
Menu barFile menu
 Edit menu
 About menu

Each menu component contains menu items, which are objects that perform specific operations. These operations are driven by the events defined earlier in this chapter and help to define the methods needed in the application. Table 21.2 shows these operations.

Table 21.2. Menu functions.
ObjectMenu Item Purpose
File menuNewCreate a new file
 OpenOpen a file
 SaveSave current file
 Save AsSave current file with a new name
 ExitExit the editor
Edit menuFindFind a keyword or phrase
 ReplaceReplace a keyword or phrase
 Replace AllReplace all occurrences of a keyword or phrase
 CutCut text and save to a buffer
 CopyCopy text and save to a buffer
 PastePaste text from the buffer
About menuAboutProvide help or overview information

In Java, events the user can cause by clicking a mouse button are generally called action events. A set of related events, called key actions, are events that occur when the user presses a key. As you design the application, you should consider mapping certain key actions to events. To make the Jompanion editor easier to use, I decided to map most events to the function keys, thus letting the user press a function key to initiate an event. Table 21.3 shows how I mapped the function keys to events.

Table 21.3. Mapping key actions to events.
Function KeyRelated Event
f1Open
f2Save
f3Save As
f4Find
f5Replace
f6Replace All
f7Cut
f8Copy
f9Paste

The next two objects in the application are the choice menus that allow users to select the size and type of the font. (See Table 21.4.) When a user makes a selection, all text in the current document is displayed in the new font type or size.

Table 21.4. Choice menus.
Selection MenuSelection Menu Choices
Font TypeCourier
 System
 Helvetica
 Times Roman
Font Size6
 7
 8
 9
 . . .
 24

So far, I have accounted for all but two of the events specified earlier, but the application still lacks a mechanism to inform users when errors occur. Most applications display errors in pop-up windows, which sounds like a good idea. This revelation, however, leads to the question of where in the application other pop-up windows might be needed and what purpose they will serve. Pop-up windows that display information or accept user input are generally called dialog boxes; Table 21.5 shows the list of dialog boxes to be used in the Jompanion application.

Table 21.5. Dialog boxes for Jompanion.
Dialog BoxDescription
ErrorInforms the user when an error occurs
FindLets the user enter search text
HelpDisplays quick keys for the editor
Open FileAllows the user to open a file
ReplaceLets the user enter text to search for and the text with which to replace it
Replace AllLets the user replace all occurrences of a word or phrase
Save FileAllows the user to save a file

Now that you have identified the major objects used in the application, you can create a list of them. As you implement each object in the next phase, you can check off the object from the list to be sure that you have accounted for all the major functions of the application.

Key Object List:

After mapping out most of the objects and events for the application, you can go on to more detailed design. Here you should work out the interaction between objects and determine the arguments objects will accept and pass. When you are comfortable with the design of the application and can visualize the flow of data from object to object, you can begin the implementation phase. The next section provides a detailed description of how Jompanion was transformed from a concept to a completed application.

Project Implementation

In the implementation phase, you create the source code for the project. For most advanced projects, this phase is actually a combined phase involving teams of programmers who implement and integrate the source code. Generally, each programming team is responsible for creating a specific set of objects with related functionality, such as the user interface or file I/O. After these object sets are created, they are integrated into the application.

The Java programming language shakes up this methodology by allowing programming teams to focus on higher-level functions, which can cut hundreds of hours off any advanced project. As you saw in the "Project Design" section, the Jompanion editor is a fairly advanced project, especially when you consider it has a completely graphical user interface. If Jompanion were programmed in C, the source code would easily run 10,000+ lines. Thanks to the class libraries in the Java API, the source code for Jompanion is just over 800 lines.

The sections that follow show you how to build the Jompanion editor following the design I have described. Step by step, each of the objects identified in the design is implemented. As with any program created in an object-oriented programming language, the key objects identified in the design phase need containers so they can be used throughout the program. The next section details those containers.

Containers for the Objects

One purpose of the design phase is to help you identify objects needed in the application. In the source code, you must create an instance of these objects before you can use them. You do this by declaring containers for the objects as instance variables. Most instance-variable declarations follow the main class declaration.

Whenever you use instance variables, you should try to determine if initial or default values are needed. In Jompanion, 20 of the key containers follow the Jompanion class declaration:

//container for the editing window in the application
  TextArea textArea;
  //container for the choice menu for font type
  //container for the size of the font as a string
  String fontSize;
  //container for the name of the current font
  String fontName = "Courier";
  //container for the size of the font as a number
  int fontNumeric = 12;

  Choice fontChoice;
  //container for the choice menu for font size
  Choice sizeChoice;
  //container for the file object
  private String fileName = null;
  //container for the about dialog box
  private AboutDialog aboutDialog = null;
  //container for the error dialog box
  private ErrorDialog errorDialog = null;
  //container for the find dialog box
  private FindDialog findDialog = null;
  //container for the replace dialog box
  private ReplaceDialog replaceDialog = null;
  //container for the replace all dialog box
  private ReplaceAllDialog replaceAllDialog = null;
  //container for the open file dialog box
  private FileDialog openDialog;
  //container for the save file dialog box
  private FileDialog saveDialog;
  //container for the about menu item
  private MenuItem aboutMenuItem;
  //container for the copy menu item
  private MenuItem copyMenuItem;
  //container for the cut menu item
  private MenuItem cutMenuItem;
  //container for the exit menu item
  private MenuItem exitMenuItem;
  //container for the find menu item
  private MenuItem findMenuItem;
  //container for the new menu item
  private MenuItem newMenuItem;
  //container for the open menu item
  private MenuItem openMenuItem;
  //container for the paste menu item
  private MenuItem pasteMenuItem;
  //container for the replace all menu item
  private MenuItem replaceAllMenuItem;
  //container for the replace menu item
  private MenuItem replaceMenuItem;
  //container for the save as menu item
  private MenuItem saveAsMenuItem;
  //container for the save menu item

Naturally, a text editor needs objects associated with the direct manipulation of text. Although the objects associated with the menus and dialog boxes are used to display the graphical user interface, they do not directly manipulate the text. You have already identified the key functions for manipulating text; now all you need to do is define containers to hold the objects used to manipulate text. These containers include the following:

//container for the text you are copying
  String copyString = "";
  //container for the text you are searching for
  String findString = "";
  //container for the text you are replacing the search text with
  String replaceString = "";
  //container for the size of the font as a string
  String fontSize = "";
  //container for the name of the current font
  String fontName = "Courier";
  //container for the size of the font as a number

Building the Main Window

The way you design and implement the main window of your application is extremely important. Users do not want to stare at a screen with colors that hurt their eyes, nor do they want to go through three or four menu levels to perform core functions such as copying text or opening a file. Users also do not like applications that are frustrating to use. For this reason, your main window should

As you add features to the application's main window, you should keep these concepts in mind. The key features for Jompanion's main window were identified in the design phase, including a menu bar, two choice menus, a text area, and a file object. Earlier, you should have also identified key attributes for the main window, such as the color of the background and text, the initial font type and size, and the size of the window.

The Jompanion() method shown in Listing 21.1 sets up the main window for the Jompanion editor. A call to super places the title bar on the window. The background color is set to white. The font size and type are set to the default values for the fontName and fontNumeric objects.


Listing 21.1. The Jompanion() method.
/**
 * This method Adds all the essential object interfaces to the application
 * and displays the application as well.
 */
  public Jompanion() {

    super("Jompanion
    ÂWritten by William R. Stanek (c) 1996");

    setBackground(Color.white);
    setFont(new Font(fontName, Font.PLAIN, fontNumeric));

    AddMenu();
    AddChoice();
    AddTextArea();
    AddDialog();

    Dimension d;
    d = Toolkit.getDefaultToolkit().getScreenSize();
    resize(d.width, d.height);

    show();
    textArea.requestFocus();
  }

Next, the key objects are added to the screen by calling their associated methods. You should always call these objects in the order you want them to be placed on the screen. The AddMenu() method adds the menu bar to the window. The AddChoice() method adds the two choice menus to the window. The AddTextArea()method adds the text area to the window. The AddDialog() method sets up the file I/O dialog box for File objects.

After the key objects are constructed, the window size is set to the current screen size. This is done by obtaining the dimensions for the user's screen and then resizing the main window to the screen size. You can also use this technique to set the initial window size proportional to the user's screen size. The final step is to display the main window. Figure 21.2 shows Jompanion's main window.

Figure 21.2 : Jompanion's main window.

Adding the Menu Bar with Pull-down Menus

The first step in creating the objects used in Jompanion's main window is to build the menu bar. The menu bar contains pull-down menus from which users can make selections. Although you can put as many pull-down menus as you want on a single menu bar, you should try to limit the number of pull-down menus to 10 or less. By the same token, you should also try to limit the number of menu items on any single pull-down menu.

When determining the number of items for a menu, keep in mind that you should balance the length of the menus if possible. A good rule of thumb is to limit menu items to roughly two times the number of menus. Thus, instead of building a menu bar with three pull-down menus and roughly 30 menu items per pull-down, you could create a menu bar with seven pull-down menus and roughly 14 menu items per pull-down.

As shown in Listing 21.2, the menu bar for Jompanion was created by logically grouping the necessary functions of the editor into three menus. The first menu, called File, contains file-related menu items. The second menu, Edit, contains all the editing functions. The third menu, About, contains a menu item that provides information on function-key assignments. Although you should try to balance the length of items in menus, for some menus like Jompanion's About menu it doesn't make sense to add other items to the menu.

To make the menu more readable and quicker to scan, menu items should be logically grouped and ordered into subcategories as well. You can separate subcategories in a pull-down menu using the addSeparator() method, which adds a graphical rule to the menu that serves to visually separate categories of menu items. Jompanion's File menu is divided into three subcategories of file-related functions with two separators. Similarly, the Edit menu is divided into two subcategories of editing functions with one separator.

After all menus and menu items are added to the menu bar, the menu bar is set for display using the setMenuBar()method. Keep in mind that the menu bar is not actually displayed until the show() method is invoked. Figure 21.3 shows Jompanion with the Edit menu activated.

Figure 21.3 : Jompanion's Edit menu.


Listing 21.2. Adding the menu bar.
/**
 * Menu Creation Routines:
 * File: New, Open (f1), Save (f2), Save As (f3), Exit
 * Edit: Find (f4), Replace (f5), Replace All (f6),
 *       Cut (f7), Copy (f8), Paste (f9)
 * About: About Jompanion
*/

  private void AddMenu() {

    Menu menu;
    MenuBar menuBar = new MenuBar();
    //Sets up the File menu
    menu = new Menu("File");
    newMenuItem = new MenuItem("New");
    menu.add(newMenuItem);
    openMenuItem = new MenuItem("Open  (f1)");
    menu.add(openMenuItem);
    menu.addSeparator();
    saveMenuItem = new MenuItem("Save  (f2)");
    menu.add(saveMenuItem);
    saveAsMenuItem = new MenuItem("Save As  (f3)");
    menu.add(saveAsMenuItem);
    menu.addSeparator();
    exitMenuItem = new MenuItem("Exit");
    menu.add(exitMenuItem);
    menuBar.add(menu);
    //sets up the Edit menu
    menu = new Menu("Edit");
    findMenuItem = new MenuItem("Find  (f4)");
    menu.add(findMenuItem);
    replaceMenuItem = new MenuItem("Replace  (f5)");
    menu.add(replaceMenuItem);
    replaceAllMenuItem = new MenuItem("ReplaceAll  (f6)");
    menu.add(replaceAllMenuItem);
    menu.addSeparator();
    cutMenuItem = new MenuItem("Cut  (f7)");
    menu.add(cutMenuItem);
    copyMenuItem = new MenuItem("Copy  (f8)");
    menu.add(copyMenuItem);
    pasteMenuItem = new MenuItem("Paste  (f9)");
    menu.add(pasteMenuItem);
    menuBar.add(menu);
    //sets up the About menu
    menu = new Menu("About");
    aboutMenuItem = new MenuItem("About Jompanion");
    menu.add(aboutMenuItem);
    menuBar.add(menu);
    setMenuBar(menuBar);
  }

Adding the Choice Menus

The second step in creating the objects used in Jompanion's main window is to create the two choice menus identified in the design phase. Unlike menus that are added to an application via a menu bar, choice menus are added as individual objects. Although you could group the choice menus on a panel, the choice menus used in Jompanion are added directly to the main window. This is done by declaring instances of the Choice() method for the fontChoice and fontSize menu objects, adding items to the menus, and then using the add() method to add the choice menus to the main window.

Tip
If you have not set the layout for the current window, the choice menus will not display. Although you could set the layout in Jompanion's addChoice() method, the layout is currently set in the addText() method. Either way, the choice menus and the text area are placed onto the screen with the same layout.

When you design a choice menu, you should use clear organization. Notice in Listing 21.3 that Jompanion uses both an alphabetic and a numeric list. Generally, choice menus with alphabetic lists should be in alphabetic order, and choice menus with numeric lists should be in numeric order.


Listing 21.3. Adding the choice menus.
/**
 * AddChoice creates the choice boxes for font type and size.
 * Once the choice boxes are built, they are added to the application.
 */
  private void AddChoice() {

    //set up choice box for font type
    fontChoice = new Choice();
    fontChoice.addItem("Courier");
    fontChoice.addItem("Helvetica");
    fontChoice.addItem("System");
    fontChoice.addItem("TimesRoman");
    fontChoice.select(0);
    add(fontChoice);

    //set up choice box for font size
    sizeChoice = new Choice();
    sizeChoice.addItem("8");
    sizeChoice.addItem("10");
    sizeChoice.addItem("12");
    sizeChoice.addItem("14");
    sizeChoice.addItem("16");
    sizeChoice.addItem("18");
    sizeChoice.addItem("20");
    sizeChoice.addItem("22");
    sizeChoice.addItem("24");
    sizeChoice.addItem("26");
    sizeChoice.addItem("28");
    sizeChoice.addItem("30");
    sizeChoice.select(2);
    add(sizeChoice);
    }

You should always set appropriate default values that mirror the initial settings for the application, especially when the menu affects the screen display. If the default values are not set, the first choice in the menu is displayed. The default value for the fontChoice menu object in Jompanion is set to Courier, and the default value for the fontSize menu object is set to 12. These defaults mirror the initial settings for font type and font size specified in the variable declarations. The select() method is used to set the default value.

Adding the Text Area

The third step in creating the objects used in Jompanion's main window is to build the text area. This is done using the TextArea() method. Because the TextArea() method only allows you to set the size of the text area in terms of rows and columns, the optimal size of the text area is difficult to determine and ultimately depends on the display mode of the end-user's computer and the font type and size being used.

Jompanion sets the size of the text area to 24 rows and 80 columns. For users with a 640¥480 display mode or a 13" Macintosh screen, 24 rows by 80 columns is probably a good size for the text area. However, this means that the text area will not fill the screen on computer systems with larger display modes. Because the text area is for editing and previewing text files, the boolean value SetEditable is set to true before the text area is displayed.

The method in Jompanion that calls the TextArea() method is called AddTextArea, which is defined as follows:

/**
 * AddTextArea creates the text area for your edit sessions.
 */
  private void AddTextArea() {
    setLayout(new FlowLayout(FlowLayout.LEFT));
    textArea = new TextArea(24, 80);
    textArea.setEditable(true);
    add(textArea);
  }

Adding File Objects

The final step in creating the objects used in Jompanion's main window is to initialize the dialog boxes for opening and saving files. This is done by creating new instances of FileDialog. The first dialog box is set with a value of LOAD, which allows the user to open files using the FileDialog method and its associated pop-up window. The second dialog box is set with a value of SAVE, which allows the user to save files using the FileDialog method and its associated pop-up window.

The method in Jompanion that calls the FileDialog() method is called AddDialog and is defined as follows:

/**
 * AddDialog initializes the dialog boxes for opening and saving files.
 */
  private void AddDialog() {
    openDialog = new FileDialog(this, "Open File",FileDialog.LOAD);
    saveDialog = new FileDialog(this, "Save File",FileDialog.SAVE);
  }

Setting the Wheels in Motion

Although the Jompanion() method builds the main window and calls the methods that add objects to the window, it is not the method that sets the whole application in motion. This task is handled by the main() method, which is the first method invoked when any application starts. The purpose of Jompanion's main() method is to call the Jompanion() method.

To increase the functionality of the application as a whole, you can allow the application to accept a filename as a parameter. For this reason, Jompanion's main() method lets you pass the name of a file you want to open when the application starts. In addition to being able to open a file in the current directory simply by entering the filename, you can also specify the path to the file when you invoke Jompanion from the command line.

The code for the main() method follows:

  //the main method of the application invokes the Jompanion method
  public static void main(String args[]) {
    Jompanion n = new Jompanion();
    if (args.length == 1)
      if (n.read(args[0])) n.fileName = args[0];
  }
}

All events that occur after the application starts and the main window displays are user driven. User-driven events occur when the user makes a selection on a menu, presses a function key, or causes an error.

Handling Events

The best way to handle events in applications is with the handleEvent() method. Most of the events that your application handles should have been identified when you created the event list during the design phase. Normally, appropriate actions for each of these events should be defined in your application's handleEvent() method. However, two particular events-error handling and window resizing-are generally not handled at this level. Because error handling is particular to a class or method, events related to errors are usually handled at a lower level. Because window resizing is particular to each window used in the application, window resizing is also handled at a lower level.

Updating the font type and font size based on a choice menu involves converting argument values. For font type, the value of the argument related to the target event is converted to a string. The string contains the name of the font the user selected and is used as an input value for the Font() method. Converting the font size is a bit more tricky. First, a string called fontSize is set to the value of the target argument; then the string is converted to an integer value using the parseInt() method. Because this method throws an exception, the exception is checked for and corrected if it occurs.

The handleEvent() method plays a crucial role in the behavior of your application. If there is a flaw in your event-handling logic, your program will not behave as you expect. The first draft of Jompanion's handleEvent method for events related to font type and size contains a logic flaw that causes the font setting to not get updated as you would expect. Because the values for font type and font size are set in separate if...then statements in the code that follows, the application forgets the value of the current fontName object when you set value for the fontSize object and forgets the value of the current fontSize object when you set the value for the fontName object:

if(event.target == fontChoice) {
              String fontName = event.arg.toString();
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              }
          if(event.target == sizeChoice) {
              String fontSize = event.arg.toString();
              try {
                    fontNumeric = Integer.parseInt(fontSize);

                 } catch (NumberFormatException e) {
                   fontNumeric = 12;     // If error occurs set font size to 12
                }
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              }

The logic flaw is corrected by setting the fontName and fontSize values in the same if...then statement, as shown in the code sample that follows. To do this, the two related events are combined using a logical or. Thus when the user makes a selection on either choice menu, the values for both the fontName object and fontSize object are set:

if(event.target == fontChoice || event.target == sizeChoice){
              String fontName = event.arg.toString();
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              String fontSize = event.arg.toString();
              try {
                    fontNumeric = Integer.parseInt(fontSize);

                 } catch (NumberFormatException e) {
                   fontNumeric = 12;     // If error occurs set font size to 12
                }
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              }

Jompanion's event list includes 16 items. Except for error handling and window resizing, each of these events is handled by the method shown in Listing 21.4. When a user closes the main window, the WINDOW_DESTROY event occurs and the program exits. When a user makes a selection from a pull-down or choice menu, an ACTION_EVENT event occurs. If the event is an instance of a MenuItem object, a method that corresponds to the selected menu item is called, such as cut() or paste(). If the value associated with the event is an instance of fontChoice or fontSize, a new font type or size is set for the text area. When a user presses a key, a KEY_ACTION event occurs, and the method associated with the key is called.


Listing 21.4. Handling events from the menu bar.
/**
 * Handle Events:
 * Event.WINDOW_DESTROY: ensures clean exit
 * Event.ACTION_EVENT: Events from menu and choice boxes
 * Event.KEY_ACTION: Press function keys
*/

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        System.exit(0);
      case Event.ACTION_EVENT:
        if(event.target instanceof MenuItem) {
          if(event.target == aboutMenuItem) showAboutDialog();
          else if(event.target == copyMenuItem) copy();
          else if(event.target == cutMenuItem) cut();
          else if(event.target == exitMenuItem) System.exit(0);
          else if(event.target == findMenuItem) showFindDialog();
          else if(event.target == newMenuItem) startNewWindow();
          else if(event.target == openMenuItem) openFile();
          else if(event.target == pasteMenuItem) paste();
          else if(event.target == replaceAllMenuItem) showReplaceAllDialog();
          else if(event.target == replaceMenuItem) showRepDialog();
          else if(event.target == saveAsMenuItem) saveAsFile();
          else if(event.target == saveMenuItem) saveOpenedFile();
          }
          if(event.target == fontChoice || event.target == sizeChoice){
              String fontName = event.arg.toString();
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              String fontSize = event.arg.toString();
              try {
                    fontNumeric = Integer.parseInt(fontSize);

                 } catch (NumberFormatException e) {
                   fontNumeric = 12;     // If error occurs set font size to 12
                }
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              }
        return(true);
          if(event.target == fontChoice || event.target == sizeChoice){
              String fontName = event.arg.toString();
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              String fontSize = event.arg.toString();
              try {
                    fontNumeric = Integer.parseInt(fontSize);

                 } catch (NumberFormatException e) {
                   fontNumeric = 12;     // If error occurs set font size to 12
                }
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              }
      case Event.KEY_ACTION:
        if(event.key == Event.f1) openFile();
        else if (event.key == Event.f2) saveOpenedFile();
        else if (event.key == Event.f3) saveAsFile();
        else if (event.key == Event.f4) showFindDialog();
        else if (event.key == Event.f5) showRepDialog();
        else if (event.key == Event.f6) showReplaceAllDialog();
        else if (event.key == Event.f7) cut();
        else if (event.key == Event.f8) copy();
        else if (event.key == Event.f9) paste();
    }
    return(false);
  }

Building the Utility Functions

At the heart of your application is a core set of functions, called utility functions, that perform most of the real work. These functions manipulate the text, graphics, audio, video, or other objects used by your application. For Jompanion, there are two sets of core functions.

The first set of core functions relates to text manipulation. The cut() method allows the user to cut selected text and place it in the buffer. This is done by setting a string equal to the value of the selected text and then calling the replaceText method of the textArea class. The values passed to the replaceText method tell it to replace the selected text from start to finish with an empty value, which effectively cuts the text from the current editing session. If you do not set a buffer, your cut() method is nothing more than the Delete key on the user's keyboard.

To update the text area after the cut, the requestFocus() method is called. The cut() method follows:

  //cuts selected text and places it on the buffer
  public void cut() {
    copyString = textArea.getSelectedText();
    textArea.replaceText("", textArea.getSelectionStart(),
                                          &nbs p;textArea.getSelectionEnd());
    textArea.requestFocus();
    }

The second core function allows the user to copy selected text to the buffer. This is done simply by setting a string equal to the value of the selected text. The copy()method follows:

  //copies selected text to the buffer
  public void copy() {
    copyString = textArea.getSelectedText();
    textArea.requestFocus();
  }

The third core function allows the user to paste text from the buffer. Before you paste an object to the current window, you should check to make sure the object is in the buffer. This is done in the past() method by making sure the length of the copyString object-the buffer-is greater than zero. If the copyString is set to a value, the insertText method of the textArea class is called. The values passed to the insertText method tell it to add the buffer text at the current cursor location.

The paste() method follows:

//pastes text from the buffer to the screen
  public void paste() {
    if (copyString.length() > 0)
      textArea.insertText(copyString,
textArea.getSelectionStart());
    textArea.requestFocus();
  }

The next set of core functions relates to file I/O. The openFile() method allows the user to open a text file. This is done by displaying the openDialog box initialized earlier as an instance of fileDialog. When the user enters a filename, the method checks the validity of the filename and then calls Jompanion's read() method. If the file is successfully read, a value is set for the fileName variable and the file is displayed in the text area. The openFile() method follows:

//method for opening files with a specified name
  private void openFile() {
    String filename;
    openDialog.show();
    filename = openDialog.getFile();
    if (filename != null) {
      filename = check(filename);
      if (read(filename)) fileName = filename;
    }
    textArea.requestFocus();
  }

The Save menu item allows the user to save a currently opened file. For this reason, the next file I/O function depends on a value being set for the fileName variable, which means the user opened a file and it is in the editing window. If this value is not set, the saveOpenedFile() method invokes the showErrorDialog to inform the user that he has made an error. If this value is set, the saveOpenedFile() method writes the file to the disk. The code for the method is as follows:

//method saves a file you opened previously.
  //method produces error if you did not open a file previously.
  public void saveOpenedFile() {
    if (fileName == null) {
      showErrorDialog("You did not previously open a file. Use Save As.");
    } else {
      write(fileName);
      textArea.requestFocus();
    }
  }

When the user selects the Save As menu item, the saveAsFile() method is called. This method allows the user to save the current editing session to a named file. To do this, the user enters a name for the file in the saveDialog box initialized earlier as an instance of fileDialog. Provided that a filename is entered and is valid for the system, the current editing session is saved by calling Jompanion's write() method. The code for the saveAsFile() method follows:

//method saves the file you are currently editing
  private void saveAsFile() {
    String filename;
    saveDialog.show();
    filename = saveDialog.getFile();
    if (filename != null) {
      filename = check(filename);
      if (write(filename)) fileName = filename;
    }
    textArea.requestFocus();
  }

When the user selects the New menu item, the startNewWindow() method is called. There are several ways you could start a new window. One way would be to clear the current editing window, as this method does:

  //creates a new editing session by deleting any previous text
  private void startNewWindow() {
    fileName = null;
    textArea.setText("");
    textArea.requestFocus();
  }\

The problem with clearing the current window is that the user could possibly lose hours of work simply by selecting the wrong menu item. Therefore, a better way to start a new window is to invoke a new instance of the Jompanion() method, which actually does create a new window. This is done as follows:

  //creates a new editing session by deleting any previous text
  private void startNewWindow() {
    Jompanion n = new Jompanion();
  }

Handling File I/O

After opening a File dialog box that allows the user to open or save text files, Jompanion checks the validity of the filename and then invokes related methods for reading and writing files. Jompanion's read() and write() methods extend corresponding methods in the java.io package and return a boolean value that indicates success or failure.

Appropriately, a method called check is used to check the validity of filenames. Although the method currently only checks for a bug that appends erroneous characters to the end of the filename, you could easily modify this method to always save files with a particular extension, such as .java or .txt. The check() method follows:

//method checks the validity of file names and specifically for a bug
  //fixes the error if it occurs
  private String check(String filename) {
    if (filename.endsWith(".*.*")) {
      filename = filename.substring(0, filename.length()-4);
    }
    return(filename);
  }

Jompanion's read() method, shown in Listing 21.5, reads data input from the buffered input stream one line at a time. Provided that the line contains text, the read() method reads the line, inserts a null character at the end of the line, and continues to read until it reaches the end of the file. When the end of the file is reached, the file is closed and the read() method returns the boolean value true to the caller.


Listing 21.5. The read() method.
  //method handles reading files from the file system
  private boolean read(String filename) {

    String line;
    FileInputStream in = null;
    DataInputStream dataIn = null;
    BufferedInputStream bis = null;
    StringBuffer buffer = new StringBuffer();

    try {
      in = new FileInputStream(filename);
      bis = new BufferedInputStream(in);
      dataIn = new DataInputStream(bis);
    } catch(Throwable e) {
      showErrorDialog("Can't open \""+filename+"\"");
      return(false);
    }

    try {
      while ((line = dataIn.readLine()) != null) {
        buffer.append(line + "\n");
      }
      in.close();
      textArea.setText(buffer.toString());
    } catch(IOException e) {
      showErrorDialog("Can't read \""+filename+"\"");
      return(false);
    }

    return(true);
  }

This method catches two errors that occur in different stages of the read process. The first error occurs when Jompanion cannot open the file; the second occurs when Jompanion cannot read a line in the open file. In either case, read() calls the showErrorDialog object, passes it an appropriate error message, and returns the boolean value false to the caller.

Jompanion's write() method, shown in Listing 21.6, is very different from the read() method. As long as there are characters to read from the text area, the write() method writes to a named output file. When the end of the file is reached, the output file is closed and the write() method returns the boolean value true to the caller.

The write() method catches two errors that occur in different stages of the read process. The first error occurs when Jompanion cannot open the output stream file; the second occurs when Jompanion cannot read a line from the text area and write it to the output file. In either case, write() calls the showErrorDialog object, passes it an appropriate error message, and returns the boolean value false to the caller.


Listing 21.6. The write() method.
  //method handles writing files to the file system
  private boolean write(String filename) {

    FileOutputStream os = null;

    try {
      os = new FileOutputStream(filename);
    } catch (Throwable e) {
      showErrorDialog("Can't write \""+filename+"\"");
      return(false);
    }

    try {
      String s = textArea.getText();
      int len = s.length();
      for (int i=0; i<len; i++) {
         os.write(s.charAt(i));
      }
      os.close();
    } catch(IOException e) {
      showErrorDialog("Can't write \""+filename+"\"");
      return(false);
    }

    return(true);
  }

Displaying the Dialog Boxes

As you have probably noticed, the previous sections have not covered the methods for finding and replacing text or for displaying the related dialog boxes. Following the top-down design structure used in Jompanion, you should create the user interface before creating the methods that actually do the work of finding and replacing text. For an advanced project, the best way to create the user interface for dialog boxes is in two stages. First, you create methods that show the dialog boxes; then you create classes or methods that build the dialog boxes.

If you refer back to Jompanion's handleEvent() method, you will see that when a user selects a menu item, methods that show the related dialog boxes are called directly if appropriate. Jompanion uses five dialog boxes in all. The dialog boxes for find, replace, replace all, and about are called directly when a user selects a related item from a pull-down menu or presses an appropriate function key. The error dialog box is invoked only when an error occurs.

As you can see in Listing 21.7, the methods for displaying the dialog boxes are very similar. The showFindDialog() method checks to make sure it was not called with a null value. If it was, it does not display the dialog box. Otherwise, it checks to see if text is selected in the editing window, uses the selected text as the initial string for the find, and then creates a new instance of Jompanion's FindDialog class.


Listing 21.7. Displaying the dialog boxes.
/**
 * Show Dialog Boxes
 * Find, Replace, Replace All, About, and Error
*/

  //displays the find dialog box which has one input field.
  //Currently selected text is placed in the input field.
  public void showFindDialog() {
    if (findDialog != null) findDialog.dispose();
    String sel = textArea.getSelectedText();
    if (sel.length() > 0) findString = sel;
    findDialog = new FindDialog(this, findString);
    findDialog.show();
    findDialog.setFocus();
  }

  //displays the replace dialog box which has two input fields.
  //Currently selected text is placed in the find input field.
  //Previously searched and replaced text is placed in the input field.
  public void showRepDialog() {
    if (replaceDialog != null) replaceDialog.dispose();
    String sel = textArea.getSelectedText();
    if (sel.length() > 0) findString = sel;
    replaceDialog = new ReplaceDialog(this, findString,
replaceString);
    replaceDialog.show();
    replaceDialog.setFocus();
  }

  //displays the replace all dialog box which has two input fields.
  //Currently selected text is placed in the find input field.
  //Previously searched and replaced text is placed in the input field.
  public void showReplaceAllDialog() {
    if (replaceAllDialog != null) replaceAllDialog.dispose();
    String sel = textArea.getSelectedText();
    if (sel.length() > 0) findString = sel;
    replaceAllDialog = new ReplaceAllDialog(this, findString,
replaceString);
    replaceAllDialog.show();
    replaceAllDialog.setFocus();
  }

  //displays the about dialog box
  private void showAboutDialog() {
    if (aboutDialog != null) aboutDialog.dispose();
    aboutDialog = new AboutDialog(this);
    aboutDialog.show();
  }

  //displays the error dialog box and the current error
  public void showErrorDialog(String message) {
    if (errorDialog != null) errorDialog.dispose();
    errorDialog = new ErrorDialog(this, message);
    errorDialog.show();
  }

Although the showReplaceDialog()and showReplaceAllDialog() methods behave similarly, you should note that new instances of their related classes are passed two strings: findString and replaceString. Because the findString and replaceString objects are declared in the Jompanion class and are not access restricted, they are accessible to any of the methods and classes in Jompanion. This allows text placed in these strings to be buffered, which provides additional functionality for the user.

When the user finds text using the Find dialog box, the find string is buffered. If the user later selects Replace or Replace All from the Edit menu, the current value of the find string is placed on the appropriate line of the dialog box. However, because the showReplaceDialog() and the showReplaceAllDialog() methods check for selected text and assign this value to the findString object, currently selected text always replaces any previously buffered value for the findString object.

Similarly, Jompanion remembers the value of the replaceString object, which makes it easier to follow a single replace with a replace all. You should consider using buffers in your applications as well.

Because the showAboutDialog() and the showErrorDialog() methods do not allow the user to manipulate objects, they are set up differently. The showAboutDialog() method simply creates a new instance of Jompanion's AboutDialog class and displays it. The showErrorDialog() method creates a new instance of Jompanion's ErrorDialog class. The string passed to the ErrorDialog class contains a message related to the error that occurred.

Building the Dialog Boxes

Because the objects that build Jompanion's dialog boxes contain several methods and discrete functionality, I created them as class-level objects. Declaring these objects within class structures provides the added advantage of reducing the overhead associated with the dialog boxes until they are actually needed and making it easier to handle the distinct functionality of each dialog box.

The FindDialog Class

The FindDialog class, shown in Listing 21.8, is the first of five related classes that extend the Dialog class of the AWT package. The purpose of the FindDialog class is to build the Find dialog box, handle related events, and set the focus of the editing window. These three functions are controlled by separate methods. Figure 21.4 shows Jompanion with the Find dialog box active.

Figure 21.4 : Jompanion's Find dialog box.


Listing 21.8. The FindDialog class.
/**
 * The FindDialog Class sets up the find dialog box
 * and handles related events.
*/
class FindDialog extends Dialog {

  Jompanion parent;
  TextField textField;

  public FindDialog(Jompanion parent, String text) {

    super(parent, "Find", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    Label l;
    l = new Label("Find What:");
    p.setLayout(null);
    l.reshape(0,25,110,30);
    p.add(l);

    textField = new TextField(text);
    textField.reshape(111,25,200,30);
    p.add(textField);

    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,340,100);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
      case Event.ACTION_EVENT:
        if(event.target == textField) {
          dispose();
          if (textField.getText().length() > 0) {
            parent.setFindString(textField.getText());
            parent.findText();
            parent.textArea.requestFocus();
            return(true);
          }
        } else {
          parent.showErrorDialog("Find Error");
          parent.textArea.requestFocus();
          return(true);
        }
    }
    return(false);
  }

  public void setFocus() {
    textField.requestFocus();
    textField.select(0, textField.getText().length());
  }
}

As you design your own dialog boxes for your applications, you should consider carefully their style, size, and placement. Generally, the dialog box should have a style that makes it easy to use and read. Its size should be appropriate for the contents of the box and should rarely display at more than half of the current screen size. The dialog box should also display in an appropriate location, such as centered on the current window.

The FindDialog() method builds the dialog box as a panel with a single text field labeled with the keywords Find What. The style of the Find dialog box is controlled by setting the background color, positioning the label and the text area within the panel so it is easy to read and use, and defining the layout. The size of the Find dialog box is set so that it is just large enough to display the label and the text area. Finally, the dialog box is placed so that it is displayed in the upper portion of the window and to the right of the pull-down menus. To ensure the user cannot resize the dialog box, the boolean value for the setResizable() method is set to false.

The handleEvent() method handles two events: the closing of the dialog box and the pressing of the Enter key with the cursor in the text field. When the user closes the dialog box, the WINDOW_DESTROY event occurs and the Find dialog box is closed. Because text fields are a single line of text, pressing the Enter key within the text field causes an ACTION_EVENT to occur. To process the related event, check for a target event that is an instance of the TextField object. In this class, the instance of the TextField object is declared as textField.

When an event related to object textField occurs, the handleEvent() method checks the validity of the input by checking the length of the text in the text field to ensure it is greater than zero. If the length is greater than zero, the findText() method of the parent class is called. Otherwise, an error occurs and an appropriate message is displayed by calling the showErrorDialog() method of the parent.

Note
Even when the user presses Enter and leaves the text field empty, the length of the text field is greater than zero. This is because the carriage-return and line-feed characters are generally associated with the Enter key. Therefore, it is virtually impossible for an error to occur. (However, a well-designed program checks for all possible errors and handles them appropriately if they occur.)

The final method declared in the FindDialog class is the setFocus() method. This method shows the location of the text the user searched for and then selects the search text-an easy way to highlight the results of the find.

The ReplaceDialog Class

Although the design of the ReplaceDialog class is very similar to that of the FindDialog class, the ReplaceDialog class is more complex because it uses multiple input fields. (See Listing 21.9.) The logic used to handle events from multiple text fields is extremely important. If your logic is not precise, the application will not behave as you expect. Figure 21.5 shows Jompanion with the Replace dialog box active.

Figure 21.5 : Jompanion's Replace dialog box.


Listing 21.9. The ReplaceDialog class.
/**
 * The ReplaceDialog Class sets up the replace dialog box
 * and handles related events.
*/
class ReplaceDialog extends Dialog {

  Jompanion parent;
  TextField fromField, toField;

  public ReplaceDialog(Jompanion parent, String fromString, String
toString) {

    super(parent, "Replace", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    Label l;
    l = new Label("Find What:");
    p.setLayout(null);

    l.reshape(0,25,130,30);
    p.add(l);

    fromField = new TextField(fromString);
    fromField.reshape(131,25,200,30);
    p.add(fromField);

    l = new Label("Replace With:");
    l.reshape(0,60,130,30);
    p.add(l);

    toField = new TextField(toString);
    toField.reshape(131,60,200,30);
    p.add(toField);

    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,360,150);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
      case Event.ACTION_EVENT:
        if(event.target == fromField) {
          toField.requestFocus();
          return(true);
        } else if(event.target == toField) {
          dispose();
          if (fromField.getText().length() > 0) {
            parent.setFindString(fromField.getText());
            parent.setReplaceString(toField.getText());
            parent.replaceText();
            parent.textArea.requestFocus();
            return(true);
          }
        } else {
          parent.showErrorDialog("Replace Error");
          parent.textArea.requestFocus();
          return(true);
        }
    }
    return(false);
  }

  public void setFocus() {
    fromField.requestFocus();
    fromField.select(0, fromField.getText().length());
  }
}

The ReplaceDialog class declares two instances of the TextField object that tell the application where to search from and the point to search to. The fromField object allows the user to input search text. The toField object allows the user to input the replacement text.

Before the handleEvent() method processes the events related to these fields, it checks the name of the field in which the user pressed Enter. If the event is related to the fromField object, the focus is reset to the toField object, which allows the user to press Enter to get to the next input field. If the event is related to the toField object and the length of the input text is greater than zero, the replaceText() method of the parent class is called. Otherwise, an error occurs and an appropriate message is displayed by calling the showErrorDialog() method of the parent.

Tip
When you use multiple text fields for input values that are dependent on each other, you should always reset the focus to the next field until the user is in the final input field. When the user is in the final input field, you should then process all input values.

The ReplaceAllDialog Class

The ReplaceAllDialog class is shown in Listing 21.10. This class is almost identical to the ReplaceDialog class, but there is one important difference in the way this class functions. Instead of replacing a single instance of the search text, the ReplaceAllDialog class replaces all instances of the search text.

The programming trick used to accomplish this involves changing only a single line of the original ReplaceDialog class. The line of code reading

parent.replaceText();

is changed to

while (parent.replaceText()) ;

The new line of code says that while the replaceText() method returns a true value, continue to loop. Therefore, as long as the replaceText() method finds occurrences of the search text, the search text is replaced.


Listing 21.10. The ReplaceAllDialog class.
/**
 * The ReplaceAllDialog Class sets up the dialog box for replacing
 * all occurrences of a word or phrase and handles related events.
*/
class ReplaceAllDialog extends Dialog {

  Jompanion parent;
  TextField fromAllField, toAllField;

  public ReplaceAllDialog(Jompanion parent, String fromAllString, String
toAllString) {

    super(parent, "Replace All", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    Label l;
    l = new Label("Find All:");
    p.setLayout(null);

    l.reshape(0,25,130,30);
    p.add(l);

    fromAllField = new TextField(fromAllString);
    fromAllField.reshape(131,25,200,30);
    p.add(fromAllField);

    l = new Label("Replace With:");
    l.reshape(0,60,130,30);
    p.add(l);

    toAllField = new TextField(toAllString);
    toAllField.reshape(131,60,200,30);
    p.add(toAllField);

    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,360,150);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
      case Event.ACTION_EVENT:
        if(event.target == fromAllField) {
          toAllField.requestFocus();
          return(true);
        } else if(event.target == toAllField) {
          dispose();
          if (fromAllField.getText().length() > 0) {
            parent.setFindString(fromAllField.getText());
            parent.setReplaceString(toAllField.getText());
            while (parent.replaceText()) ;
            parent.textArea.requestFocus();
            return(true);
          }
        } else {
          parent.showErrorDialog("Replace All Error");
          parent.textArea.requestFocus();
          return(true);
        }
    }
    return(false);
  }

  public void setFocus() {
    fromAllField.requestFocus();
    fromAllField.select(0, fromAllField.getText().length());
  }
}

The AboutDialog Class

The purpose of the AboutDialog class (shown in Listing 21.11) is to build the About dialog box, which displays the mapping of the function keys. The About dialog box is designed so that it can be placed in the bottom of the viewing area for easy reference. For this reason, the height of the box is set to 120 pixels and the width of the box is set to 640 pixels. A grid layout is used to align the nine entries for function keys into easy-to-read columns. To ensure the entries are logically ordered when viewed, they are organized by column in the code. Figure 21.6 shows Jompanion with the About dialog box active.

Figure 21.6 : Jompanion's About dialog box.


Listing 21.11. The AboutDialog class.
/**
 * The AboutDialog Class sets up the dialog box that displays
 * information about Jompanion and handles related events
*/
class AboutDialog extends Dialog {

  Jompanion parent;

  public AboutDialog(Jompanion parent) {
    super(parent, "Jompanion from the Complete Guide to Java", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    p.setLayout(new GridLayout(3, 3));
    p.add(new Label("f1 - Open Files"));
    p.add(new Label("f4 - Find"));
    p.add(new Label("f7 - Cut"));
    p.add(new Label("f2 - Save"));
    p.add(new Label("f5 - Replace"));
    p.add(new Label("f8 - Copy"));
    p.add(new Label("f3 - Save As"));
    p.add(new Label("f6 - Replace All"));
    p.add(new Label("f9 - Paste"));
    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(50,440,640,120);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
    }
    return(false);
  }
}

The ErrorDialog Class

The purpose of the ErrorDialog class is to build a dialog box that displays errors. (See Listing 21.12.) To ensure the dialog box gets the reader's immediate attention, it has a yellow background and a bold font.


Listing 21.12. The ErrorDialog class.
/**
 * The ErrorDialog Class sets up the dialog box that displays
 * errors to the user and handles the closing of the window
*/
class ErrorDialog extends Dialog {

  Jompanion parent;
  String message;

  public ErrorDialog(Jompanion parent, String message) {
    super(parent, "Error", true);
    setBackground(Color.yellow);
    this.parent = parent;
    this.message = message;

    Panel p;
    p = new Panel();
    p.add(new Label(message));
    p.setFont(new Font("System", Font.BOLD, 12));
    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,420,100);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
    }
    return(false);
  }
}

Text-Manipulation Objects

Now that the user interface for finding and replacing text is built, you can define the associated text-manipulation objects. As with other parts of the application, it is easiest if you start with the basic objects and work to the advanced objects. As Listing 21.13 shows, Jompanion includes two basic text-manipulation objects and two advanced ones.


Listing 21.13. Text-manipulation objects.
  //sets the find string
  public void setFindString(String str) {
    findString = str;
  }

  //sets the replace string
  public void setReplaceString(String str) {
    replaceString = str;
  }

  //handles the search for text in the find input field
  public void findText() {

    String s;
    int spoint, index;

    textArea.requestFocus();
    if (findString.length() == 0) return;

    s = textArea.getText();
    if (s.length() == 0) return;

    spoint = textArea.getSelectionEnd();
    index = s.substring(spoint).indexOf(findString);
    if (index >= 0) {
      int i = spoint + index;
      textArea.select(i, i + findString.length());
    }
  }

  //handles replacing text
  public boolean replaceText() {
    String s;
    int spoint, index;

    textArea.requestFocus();
    if (findString.length() == 0) return(false);

    s = textArea.getText();
    if (s.length() == 0) return(false);

    spoint = textArea.getSelectionStart();
    index = s.substring(spoint).indexOf(findString);
    if (index >= 0) {
      int i = spoint + index;
      int j = i + findString.length();
      textArea.replaceText(replaceString, i, j);
      textArea.select(i, i+replaceString.length());
      return(true);
    }
    return(false);
  }

The purpose of the setFindString() and setReplaceString() methods is simply to set a value for the find and replace strings. These functions are created as separate methods so they can be called from wherever they are needed in the application.

The findText()method is called by the FindDialog class. The purpose of this method is to search for text entered in the Find input field by setting an index point within the document and searching forward through the document until the next instance of the text is found. The getSelectionEnd() method is used to determine the starting location of the index point. By placing the index after the current occurrence of the search text, the user can select text and then find the next occurrence of the selected text. If the index point was placed before the currently selected text using the getSelectionStart()method, the application would consider the currently selected text as the next occurrence of the search text, which is flawed logic.

The replaceText() method is called by both the ReplaceDialog class and the ReplaceAllDialog class. The purpose of this method is to search for text entered in the find input field and replace it with text in the replace input field. To ensure a currently selected occurrence of the find text is replaced, the index point for the start of the search is set with the getSelectionStart() method. If the index point was placed after the currently selected text using the getSelectionEnd() method, the application would never replace an instance of the currently selected text, which is flawed logic.

The Complete Project

Although Jompanion is a fairly advanced application, its sound design helped to ensure that the development and implementation of the project went smoothly. Listing 21.14 shows the complete code for the Jompanion application. Although typing in the code line by line forces you to study each line of code, you can go straight to the CD-ROM and access the complete source code if you choose.


Listing 21.14. The finished application.
import java.awt.*;
import java.io.*;

/**
 * Peter Norton's Guide to Java Programming
 * Jompanion, a companion text editor for Java
 * with many features including search, replace, replace all
 * cut, copy and paste. Jompanion also lets you set font type
 * and size.
*/
public class Jompanion extends Frame {

  //set up variables for use throughout Jompanion

  //container for the editing window in the application
  TextArea textArea;
  //container for the choice menu for font type

  //container for the size of the font as a string
  String fontSize;
  //container for the name of the current font
  String fontName = "Courier";
  //container for the size of the font as a number
  int fontNumeric = 12;
  Choice fontChoice;
  //container for the choice menu for font size
  Choice sizeChoice;
  //container for the file object
  private String fileName = null;
  //container for the about dialog box
  private AboutDialog aboutDialog = null;
  //container for the error dialog box
  private ErrorDialog errorDialog = null;
  //container for the find dialog box
  private FindDialog findDialog = null;
  //container for the replace dialog box
  private ReplaceDialog replaceDialog = null;
  //container for the replace all dialog box
  private ReplaceAllDialog replaceAllDialog = null;
  //container for the open file dialog box
  private FileDialog openDialog;
  //container for the save file dialog box
  private FileDialog saveDialog;
  //container for the about menu item
  private MenuItem aboutMenuItem;
  //container for the copy menu item
  private MenuItem copyMenuItem;
  //container for the cut menu item
  private MenuItem cutMenuItem;
  //container for the exit menu item
  private MenuItem exitMenuItem;
  //container for the find menu item
  private MenuItem findMenuItem;
  //container for the new menu item
  private MenuItem newMenuItem;
  //container for the open menu item
  private MenuItem openMenuItem;
  //container for the paste menu item
  private MenuItem pasteMenuItem;
  //container for the replace all menu item
  private MenuItem replaceAllMenuItem;
  //container for the replace menu item
  private MenuItem replaceMenuItem;
  //container for the save as menu item
  private MenuItem saveAsMenuItem;
  //container for the save menu item
  private MenuItem saveMenuItem;
/**
 * This method Adds all the essential object interfaces to the application
 * and displays the application as well.
 */
  public Jompanion() {

    super("Jompanion
    ÂWritten by William R. Stanek (c) 1996");

    setBackground(Color.white);
    setFont(new Font(fontName, Font.PLAIN, fontNumeric));

    AddMenu();
    AddChoice();
    AddTextArea();
    AddDialog();

    Dimension d;
    d = Toolkit.getDefaultToolkit().getScreenSize();
    resize(d.width, d.height);

    show();
    textArea.requestFocus();
  }

/**
 * Menu Creation Routines:
 * File: New, Open (f1), Save (f2), Save As (f3), Exit
 * Edit: Find (f4), Replace (f5), Replace All (f6),
 *       Cut (f7), Copy (f8), Paste (f9)
 * About: About Jompanion
*/

  private void AddMenu() {

    Menu menu;
    MenuBar menuBar = new MenuBar();

    //Sets up the File menu
    menu = new Menu("File");
    newMenuItem = new MenuItem("New");
    menu.add(newMenuItem);
    openMenuItem = new MenuItem("Open  (f1)");
    menu.add(openMenuItem);
    menu.addSeparator();
    saveMenuItem = new MenuItem("Save  (f2)");
    menu.add(saveMenuItem);
    saveAsMenuItem = new MenuItem("Save As  (f3)");
    menu.add(saveAsMenuItem);
    menu.addSeparator();
    exitMenuItem = new MenuItem("Exit");
    menu.add(exitMenuItem);
    menuBar.add(menu);

    //sets up the Edit menu
    menu = new Menu("Edit");
    findMenuItem = new MenuItem("Find  (f4)");
    menu.add(findMenuItem);
    replaceMenuItem = new MenuItem("Replace  (f5)");
    menu.add(replaceMenuItem);
    replaceAllMenuItem = new MenuItem("ReplaceAll  (f6)");
    menu.add(replaceAllMenuItem);
    menu.addSeparator();
    cutMenuItem = new MenuItem("Cut  (f7)");
    menu.add(cutMenuItem);
    copyMenuItem = new MenuItem("Copy  (f8)");
    menu.add(copyMenuItem);
    pasteMenuItem = new MenuItem("Paste  (f9)");
    menu.add(pasteMenuItem);
    menuBar.add(menu);

    //sets up the About menu
    menu = new Menu("About");
    aboutMenuItem = new MenuItem("About Jompanion");
    menu.add(aboutMenuItem);
    menuBar.add(menu);

    setMenuBar(menuBar);
  }

/**
 * AddChoice creates the choice boxes for font type and size.
 * Once the choice boxes are built, they are added to the application.
 */
  private void AddChoice() {

    //set up choice box for font type
    fontChoice = new Choice();
    fontChoice.addItem("Courier");
    fontChoice.addItem("Helvetica");
    fontChoice.addItem("System");
    fontChoice.addItem("TimesRoman");
    fontChoice.select(0);
    add(fontChoice);

    //set up choice box for font size
    sizeChoice = new Choice();
    sizeChoice.addItem("8");
    sizeChoice.addItem("10");
    sizeChoice.addItem("12");
    sizeChoice.addItem("14");
    sizeChoice.addItem("16");
    sizeChoice.addItem("18");
    sizeChoice.addItem("20");
    sizeChoice.addItem("22");
    sizeChoice.addItem("24");
    sizeChoice.addItem("26");
    sizeChoice.addItem("28");
    sizeChoice.addItem("30");
    sizeChoice.select(2);
    add(sizeChoice);
    }

/**
 * AddTextArea creates the text area for your edit sessions.
 */
  private void AddTextArea() {
    setLayout(new FlowLayout(FlowLayout.LEFT));
    textArea = new TextArea(24, 80);
    textArea.setEditable(true);
    add(textArea);
  }

/**
 * AddDialog adds the dialog boxes for opening and saving files.
 */
  private void AddDialog() {
    openDialog = new FileDialog(this, "Open File",FileDialog.LOAD);
    saveDialog = new FileDialog(this, "Save File",FileDialog.SAVE);
  }


/**
 * Handle Events:
 * Event.WINDOW_DESTROY: ensures clean exit
 * Event.ACTION_EVENT: Events from menu and choice boxes
 * Event.KEY_ACTION: Press function keys
*/

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        System.exit(0);
      case Event.ACTION_EVENT:
        if(event.target instanceof MenuItem) {
          if(event.target == aboutMenuItem) showAboutDialog();
          else if(event.target == copyMenuItem) copy();
          else if(event.target == cutMenuItem) cut();
          else if(event.target == exitMenuItem) System.exit(0);
          else if(event.target == findMenuItem) showFindDialog();
          else if(event.target == newMenuItem) startNewWindow();
          else if(event.target == openMenuItem) openFile();
          else if(event.target == pasteMenuItem) paste();
          else if(event.target == replaceAllMenuItem) showReplaceAllDialog();
          else if(event.target == replaceMenuItem) showRepDialog();
          else if(event.target == saveAsMenuItem) saveAsFile();
          else if(event.target == saveMenuItem) saveOpenedFile();
          }
          if(event.target == fontChoice || event.target == sizeChoice){
              String fontName = event.arg.toString();
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              String fontSize = event.arg.toString();
              try {
                    fontNumeric = Integer.parseInt(fontSize);

                 } catch (NumberFormatException e) {
                   fontNumeric = 12;     // If error occurs set font size to 12
                }
              textArea.setFont(new Font(fontName, Font.PLAIN, fontNumeric));
              }
        return(true);
      case Event.KEY_ACTION:
        if(event.key == Event.f1) openFile();
        else if (event.key == Event.f2) saveOpenedFile();
        else if (event.key == Event.f3) saveAsFile();
        else if (event.key == Event.f4) showFindDialog();
        else if (event.key == Event.f5) showRepDialog();
        else if (event.key == Event.f6) showReplaceAllDialog();
        else if (event.key == Event.f7) cut();
        else if (event.key == Event.f8) copy();
        else if (event.key == Event.f9) paste();
    }
    return(false);
  }

/**
 * Utility Functions:
 * Copy, Cut, Paste
 * Open file, SaveAs, Save
 * New
*/

  //cuts selected text and places it on the buffer
  public void cut() {
    copyString = textArea.getSelectedText();
    textArea.replaceText("", textArea.getSelectionStart(),
                                          &nbs p;textArea.getSelectionEnd());
    textArea.requestFocus();
    }

  //copies selected text to the buffer
  public void copy() {
    copyString = textArea.getSelectedText();
    textArea.requestFocus();
  }

  //pastes text from the buffer to the screen
  public void paste() {
    if (copyString.length() > 0)
      textArea.insertText(copyString,
textArea.getSelectionStart());
    textArea.requestFocus();
  }

  //method for opening files with a specified name
  private void openFile() {
    String filename;
    openDialog.show();
    filename = openDialog.getFile();
    if (filename != null) {
      filename = check(filename);
      if (read(filename)) fileName = filename;
    }
    textArea.requestFocus();
  }

  //method saves the file you are currently editing
  private void saveAsFile() {
    String filename;
    saveDialog.show();
    filename = saveDialog.getFile();
    if (filename != null) {
      filename = check(filename);
      if (write(filename)) fileName = filename;
    }
    textArea.requestFocus();
  }

  //method saves a file you opened previously.
  //method produces error if you did not open a file previously.
  public void saveOpenedFile() {
    if (fileName == null) {
      showErrorDialog("You did not previously open a file. Use Save As.");
    } else {
      write(fileName);
      textArea.requestFocus();
    }
  }

  //creates a new editing session by deleting any previous text
  private void startNewWindow() {
    fileName = null;
    textArea.setText("");
    textArea.requestFocus();
  }

/**
 * Show Dialog Boxes
 * Find, Replace, Replace All, About, and Error
*/

  //displays the find dialog box which has one input field.
  //Currently selected text is placed in the input field.
  public void showFindDialog() {
    if (findDialog != null) findDialog.dispose();
    String sel = textArea.getSelectedText();
    if (sel.length() > 0) findString = sel;
    findDialog = new FindDialog(this, findString);
    findDialog.show();
    findDialog.setFocus();
  }

  //displays the replace dialog box which has two input fields.
  //Currently selected text is placed in the find input field.
  //Previously searched and replaced text is placed in the input field.
  public void showRepDialog() {
    if (replaceDialog != null) replaceDialog.dispose();
    String sel = textArea.getSelectedText();
    if (sel.length() > 0) findString = sel;
    replaceDialog = new ReplaceDialog(this, findString,
replaceString);
    replaceDialog.show();
    replaceDialog.setFocus();
  }

  //displays the replace all dialog box which has two input fields.
  //Currently selected text is placed in the find input field.
  //Previously searched and replaced text is placed in the input field.
  public void showReplaceAllDialog() {
    if (replaceAllDialog != null) replaceAllDialog.dispose();
    String sel = textArea.getSelectedText();
    if (sel.length() > 0) findString = sel;
    replaceAllDialog = new ReplaceAllDialog(this, findString,
replaceString);
    replaceAllDialog.show();
    replaceAllDialog.setFocus();
  }

  //displays the about dialog box
  private void showAboutDialog() {
    if (aboutDialog != null) aboutDialog.dispose();
    aboutDialog = new AboutDialog(this);
    aboutDialog.show();
  }

  //displays the error dialog box and the current error
  public void showErrorDialog(String message) {
    if (errorDialog != null) errorDialog.dispose();
    errorDialog = new ErrorDialog(this, message);
    errorDialog.show();
  }

  //method handles reading files from the file system
  private boolean read(String filename) {

    String line;
    FileInputStream in = null;
    DataInputStream dataIn = null;
    BufferedInputStream bis = null;
    StringBuffer buffer = new StringBuffer();

    try {
      in = new FileInputStream(filename);
      bis = new BufferedInputStream(in);
      dataIn = new DataInputStream(bis);
    } catch(Throwable e) {
      showErrorDialog("Can't open \""+filename+"\"");
      return(false);
    }

    try {
      while ((line = dataIn.readLine()) != null) {
        buffer.append(line + "\n");
      }
      in.close();
      textArea.setText(buffer.toString());
    } catch(IOException e) {
      showErrorDialog("Can't read \""+filename+"\"");
      return(false);
    }

    return(true);
  }

  //method handles writing files to the file system
  private boolean write(String filename) {

    FileOutputStream os = null;

    try {
      os = new FileOutputStream(filename);
    } catch (Throwable e) {
      showErrorDialog("Can't write \""+filename+"\"");
      return(false);
    }

    try {
      String s = textArea.getText();
      int len = s.length();
      for (int i=0; i<len; i++) {
         os.write(s.charAt(i));
      }
      os.close();
    } catch(IOException e) {
      showErrorDialog("Can't write \""+filename+"\"");
      return(false);
    }

    return(true);
  }

  //method checks the validity of file names and specifically for a bug
  //fixes the error if it occurs
  private String check(String filename) {
    if (filename.endsWith(".*.*")) {
      filename = filename.substring(0, filename.length()-4);
    }
    return(filename);
  }

  //sets the find string
  public void setFindString(String str) {
    findString = str;
  }

  //sets the replace string
  public void setReplaceString(String str) {
    replaceString = str;
  }

  //handles the search for text in the find input field
  public void findText() {

    String s;
    int spoint, index;

    textArea.requestFocus();
    if (findString.length() == 0) return;

    s = textArea.getText();
    if (s.length() == 0) return;

    spoint = textArea.getSelectionEnd();
    index = s.substring(spoint).indexOf(findString);
    if (index >= 0) {
      int i = spoint + index;
      textArea.select(i, i + findString.length());
    }
  }

  //handles replacing text
  public boolean replaceText() {
    String s;
    int spoint, index;

    textArea.requestFocus();
    if (findString.length() == 0) return(false);

    s = textArea.getText();
    if (s.length() == 0) return(false);

    spoint = textArea.getSelectionStart();
    index = s.substring(spoint).indexOf(findString);
    if (index >= 0) {
      int i = spoint + index;
      int j = i + findString.length();
      textArea.replaceText(replaceString, i, j);
      textArea.select(i, i+replaceString.length());
      return(true);
    }
    return(false);
  }

  //the main method of the application invokes the Jompanion method
  public static void main(String args[]) {
    Jompanion n = new Jompanion();
    if (args.length == 1)
      if (n.read(args[0])) n.fileName = args[0];
  }
}

/**
 * The FindDialog Class sets up the find dialog box
 * and handles related events.
*/
class FindDialog extends Dialog {

  Jompanion parent;
  TextField textField;

  public FindDialog(Jompanion parent, String text) {

    super(parent, "Find", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    Label l;
    l = new Label("Find What:");
    p.setLayout(null);
    l.reshape(0,25,110,30);
    p.add(l);

    textField = new TextField(text);
    textField.reshape(111,25,200,30);
    p.add(textField);

    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,340,100);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
      case Event.ACTION_EVENT:
        if(event.target == textField) {
          dispose();
          if (textField.getText().length() > 0) {
            parent.setFindString(textField.getText());
            parent.findText();
            parent.textArea.requestFocus();
            return(true);
          }
        } else {
          parent.showErrorDialog("Find Error");
          parent.textArea.requestFocus();
          return(true);
        }
    }
    return(false);
  }

  public void setFocus() {
    textField.requestFocus();
    textField.select(0, textField.getText().length());
  }
}

/**
 * The ReplaceDialog Class sets up the replace dialog box
 * and handles related events.
*/
class ReplaceDialog extends Dialog {

  Jompanion parent;
  TextField fromField, toField;

  public ReplaceDialog(Jompanion parent, String fromString, String
toString) {

    super(parent, "Replace", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    Label l;
    l = new Label("Find What:");
    p.setLayout(null);

    l.reshape(0,25,130,30);
    p.add(l);

    fromField = new TextField(fromString);
    fromField.reshape(131,25,200,30);
    p.add(fromField);

    l = new Label("Replace With:");
    l.reshape(0,60,130,30);
    p.add(l);

    toField = new TextField(toString);
    toField.reshape(131,60,200,30);
    p.add(toField);

    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,360,150);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
      case Event.ACTION_EVENT:
        if(event.target == fromField) {
          toField.requestFocus();
          return(true);
        } else if(event.target == toField) {
          dispose();
          if (fromField.getText().length() > 0) {
            parent.setFindString(fromField.getText());
            parent.setReplaceString(toField.getText());
            parent.replaceText();
            parent.textArea.requestFocus();
            return(true);
          }
        } else {
          parent.showErrorDialog("Replace Error");
          parent.textArea.requestFocus();
          return(true);
        }
    }
    return(false);
  }

  public void setFocus() {
    fromField.requestFocus();
    fromField.select(0, fromField.getText().length());
  }
}

/**
 * The ReplaceAllDialog Class sets up the dialog box for replacing
 * all occurrences of a word or phrase and handles related events.
*/
class ReplaceAllDialog extends Dialog {

  Jompanion parent;
  TextField fromAllField, toAllField;

  public ReplaceAllDialog(Jompanion parent, String fromAllString, String
toAllString) {

    super(parent, "Replace All", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    Label l;
    l = new Label("Find All:");
    p.setLayout(null);

    l.reshape(0,25,130,30);
    p.add(l);

    fromAllField = new TextField(fromAllString);
    fromAllField.reshape(131,25,200,30);
    p.add(fromAllField);

    l = new Label("Replace With:");
    l.reshape(0,60,130,30);
    p.add(l);

    toAllField = new TextField(toAllString);
    toAllField.reshape(131,60,200,30);
    p.add(toAllField);

    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,360,150);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
      case Event.ACTION_EVENT:
        if(event.target == fromAllField) {
          toAllField.requestFocus();
          return(true);
        } else if(event.target == toAllField) {
          dispose();
          if (fromAllField.getText().length() > 0) {
            parent.setFindString(fromAllField.getText());
            parent.setReplaceString(toAllField.getText());
            while (parent.replaceText()) ;
            parent.textArea.requestFocus();
            return(true);
          }
        } else {
          parent.showErrorDialog("Replace All Error");
          parent.textArea.requestFocus();
          return(true);
        }
    }
    return(false);
  }
  public void setFocus() {
    fromAllField.requestFocus();
    fromAllField.select(0, fromAllField.getText().length());
  }
}

/**
 * The AboutDialog Class sets up the dialog box that displays
 * information about Jompanion and handles related events
*/
class AboutDialog extends Dialog {

  Jompanion parent;

  public AboutDialog(Jompanion parent) {
    super(parent, "Jompanion from the Complete Guide to Java", true);
    setBackground(Color.white);
    this.parent = parent;

    Panel p;
    p = new Panel();
    p.setLayout(new GridLayout(3, 3));
    p.add(new Label("f1 - Open Files"));
    p.add(new Label("f4 - Find"));
    p.add(new Label("f7 - Cut"));
    p.add(new Label("f2 - Save"));
    p.add(new Label("f5 - Replace"));
    p.add(new Label("f8 - Copy"));
    p.add(new Label("f3 - Save As"));
    p.add(new Label("f6 - Replace All"));
    p.add(new Label("f9 - Paste"));
    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(50,440,640,120);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
    }
    return(false);
  }
}

/**
 * The ErrorDialog Class sets up the dialog box that displays
 * errors to the user and handles the closing of the window
*/
class ErrorDialog extends Dialog {

  Jompanion parent;
  String message;

  public ErrorDialog(Jompanion parent, String message) {
    super(parent, "Error", true);
    setBackground(Color.yellow);
    this.parent = parent;
    this.message = message;

    Panel p;
    p = new Panel();
    p.add(new Label(message));
    p.setFont(new Font("System", Font.BOLD, 12));
    add("Center", p);

    Dimension d;
    d = parent.size();
    reshape(200,50,420,100);
    setResizable(false);
  }

  public boolean handleEvent(Event event) {
    switch(event.id) {
      case Event.WINDOW_DESTROY:
        dispose();
        parent.textArea.requestFocus();
        return(true);
    }
    return(false);
  }
}

After you understand how the application works, you should try your hand at upgrading it. Some of the additional features you may want to add to Jompanion include

Note
The Meta key is used primarily on Sun UNIX workstations. On a Windows 95-compatible keyboard, this key would be your Windows key.

Summary

Developing advanced applications involves careful planning. For most projects, planning involves mapping out the steps necessary to complete the project and allocating a time period for implementing each step. After you plot out the steps that will take you through project completion, you can group the steps into phases. These phases form the software development life cycle for the application.

A top-down methodology was used to develop and implement the Jompanion editor. Using this type of methodology, high-level objects are developed before the low-level ones. For Jompanion, the first step in design and implementation involved developing the main window. After the main window was developed, you looked at the next level of objects and so on, working your way to the low-level objects that performed the actual manipulation of text. In following this design approach, you used sound object-oriented design techniques and should now be ready to create your own advanced applications in Java.