# Chapter 20

# Designing and Implementing Advanced Applets

### CONTENTS

- Project Development and Planning
  - The Requirements Phase
  - The Specification/Analysis Phase
  - The Planning Phase
- Project Design
- The Implementation Phase
  - Containers for the Objects Used in the SpreadSheet Applet
  - Initializing the Primary Frame
  - Determining the State of the Applet
  - Running Applet Threads
  - Painting the Frame
  - Updating the Applet's Frame
  - Handling Events in the Applet
  - Updating the Current Cell's Value
  - Handling Formulas
  - Creating Cells
  - Preparing Cells for Display
  - Utility Methods
  - Parsing Formulas
  - Painting Cells
  - Building the Node Object
  - Building the Input Area
- The Complete Applet
- Putting the SpreadSheet Applet to Use
- Summary

Java is much more than a tool for animating objects. Whether you are on a corporate intranet or the Internet, you can use Java to solve your real-world business problems today. This chapter takes you step by step through the development of an advanced applet called `SpreadSheet`.

`SpreadSheet` is an amazing applet that implements a full-featured spreadsheet in less than 900 lines of code, using many of the advanced features of the Java programming language. Whereas previous chapters focus primarily on programming concepts, this chapter's emphasis is on design concepts. The design of your applet is the key to its power and reusability. Examining the `SpreadSheet` applet should give you a better understanding of using text and graphics, manipulating text strings, using mathematical formulas, and processing user input.

## Project Development and Planning

Creating quick solutions for problems is what most programmers and Web administrators do every day. However, quick solutions usually are not the best ones. If problems occur or you must modify the hack later, far too often you spend more time trying to rework your quick solution than it would have taken to develop the program the right way in the first place. As you begin to develop more advanced applets, you should take the time to develop and plan them carefully.

Planning involves determining the steps necessary to complete the project. After you plot out the steps that will take you through project completion, you can group the steps into phases. The duration of each phase should be relevant to the size and complexity of the applet you are developing. Although you may be able to develop and implement an intermediate applet in a single day, more advanced projects are developed and implemented over a period of days, weeks, or months. Most projects include six phases: requirements, specification/analysis, planning, design, implementation, and testing.

Note
Because some phases are dependent on others, usually you should perform each phase in order. However, you can sometimes combine phases to suit the needs of the project you are developing. For the `SpreadSheet` applet, I combined the requirements, specification, and planning phases into a general phase called development and planning.

### The Requirements Phase

The first step in the requirements phase is to develop a list of project needs by examining the purpose, scope, and audience of the project. Then you identify your reasonable expectations for the project. Finally, you translate these needs, goals, and purposes into project requirements.

The purpose statement should describe what type of applet 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 applet. Often you will have primary and secondary target users.

Tip
If you have not created a project folder, you should create one now. The project folder, generally kept in both paper and electronic form, will hold everything related to the project. The paper form of the folder can be a three-ring binder or notepad, and the electronic version can reside on your hard drive as a directory.

The purpose, scope, and target user for the `SpreadSheet` applet are defined as follows:

- Purpose: A spreadsheet that can be used interactively on the corporate intranet or the Internet.
- Scope: An advanced applet with many features.
- Target user: Employees in the customer service division and customers who want to perform spreadsheet calculations prior to making buy/sell decisions.

Now make a list of project constraints. Key constraints for most projects include duration, budget, and size. The project's duration is usually constrained by deadlines or milestones you must meet; its budget is usually constrained by the amount of time and money you have to invest in the project; and the size is usually constrained by user requirements, performance issues, and the budget as well. The initial constraints for the `SpreadSheet` applet are

- Project duration: 8-10 days
- Project budget: 25-40 hours
- Project size: Less than 1,000 lines

Now that you know the issues that are driving the development of the project, you can develop a list of needs for the project. Project needs include personnel, computer hardware and software, financial resources, and supplies. For the `SpreadSheet` applet, the list of needs is small:

- A computer with the Java Developer's Kit installed on it
- A Java-capable Web browser
- Disk space on hard drive
- A floppy disk to back up the project
- Daily time investment

### The Specification/Analysis Phase

During the specification/analysis phase, you determine the inner workings of the applet. You can do this in a traditional manner through specification diagrams such as data-flow diagrams or state-transition diagrams, or you can simply define the necessary objects and the flow of data among them. The methodology you use during this phase will depend largely on the type of applet you are developing, the modeling tools you are using, and the development philosophy at your organization.

For the `SpreadSheet` applet, 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 applet and the user. The environmental model includes three key components:

- Statement of purpose-Provides management with a brief description of the applet.
- Context diagram-Provides an overview of the interaction between the applet and outside entities.
- Event list-Provides a complete list of events that can occur and to which the applet must respond.

The great thing about the environmental model is that its components can be modified to meet the needs of just about any type of project. The first step in using this model is to define a purpose statement for the project. Because the statement of purpose is intended for management, it usually is no more than one paragraph. The brief statement of purpose defined in the "The Requirements Phase" section is expanded on for management use as follows:

- Statement of Purpose: The purpose of the `SpreadSheet` applet is to help customer service personnel and clients work through complex financial matters that require spreadsheet calculations. The spreadsheet includes advanced features that make it useful for solving formula-based math issues, such as summing rows and columns.

The second step in using the environmental model is to create a context diagram that defines the external entities with which the applet interacts. As with most applets, a Web browser or external viewer acts as an intermediary between the user and the applet. The level of detail you use in the context diagram is up to you. For complex applets, you might want to depict each class object that the main object interacts with on this diagram as well.

Looking Ahead
For the `SpreadSheet` applet, I chose not to develop a context diagram. For an example of a context diagram, see the section titled "Defining the Necessary Objects" in Chapter 21, "Designing and Implementing Advanced Applications."

Instead of developing a context diagram for the `SpreadSheet` applet, let's take a preliminary look at the objects needed on the applet's primary frame. Break down the applet frame into regions, such as a title bar, menu bar, display areas, and input areas. You can then break these objects down into their main components.

For the `SpreadSheet` applet, the main regions on the frame are a title bar, an input area, and a display area made up of the rows and columns of the spreadsheet. These rows and columns are, in turn, made up of individual cells.

To aid the design process, you can create a diagram that depicts the location of these objects on the applet's primary frame. Figure 20.1 shows the preliminary design of the `SpreadSheet` applet's primary frame.

Figure 20.1 : Sketching the objects for the SpreadSheet applet's primary frame.

The final step in using the environmental model is to create an event list, a list of events the applet should support. You will use this list to help define objects for the applet. In general, most events that occur in applets are user driven. However, the type of applet you are developing ultimately determines the type of events in your list.

Applets by their nature as Web-published programs have a common set of events related to the core methods of the `Applet` class. These events are handled explicitly by the applet when defined in the applet or implicitly by the `Applet` class when not defined in the applet. Common events for applets include the following:

- Initializing the applet
- Starting the applet
- Running applet threads
- Painting the applet
- Updating the applet
- Destroying the applet when finished

Most events for a spreadsheet are initiated when a user enters data, such as a label for a row or column, a mathematical formula, or a value for a cell. Because applets are published on the Web, events can also be triggered by parameters set in the HTML document used to display the applet. Events for the `SpreadSheet` applet include the following:

1. Initializing the applet
2. Accepting and processing parameter values
3. Starting the applet with default values
4. Running applet threads
5. Performing calculations
6. Creating the spreadsheet
7. Painting the spreadsheet
8. Accepting and process user input
9. Updating the spreadsheet
10. Destroying the applet when finished.

Now that you have completed the environmental model for your applet, you can go on to more detailed modeling. Here's where you translate the event list into the actual data flow between objects. The data flow should include input and output to key objects. The type of data models you use will depend primarily on the type and complexity of the applet you are developing.

### The Planning Phase

In the planning phase, you take the requirements and specifications developed in the previous phases and determine the steps necessary to complete the project and how long each step will take. Use the resulting schedule to define the necessary milestones, goals, and time allocations to take the project through to completion.

The plans for a small project could be basic-for example, a list of 15 steps with deadlines for completion of each step. In advanced projects, there could be hundreds of project steps, with multiple steps being performed simultaneously, or a handful of steps, with each step being performed one after the other. Some steps would be dependent on other steps, meaning they could not be started until certain aspects of the project were completed. Other steps would not be dependent on any other steps and could be performed at any time during the project's development.

The planning phase can be a reality check for project constraints and requirements. For example, after you plan each step of the project you discover that it will take at least five weeks to complete the project, yet management's deadline for project completion is 30 days. In this case, you may have to renegotiate the deadline, hire additional team members, or eliminate certain time-intensive parts of the project.

## Project Design

You design the layout of the project in detail during the design phase. Because applets include a common set of objects as well as unique objects, applet design is slightly different from application design. In most applets, the primary frame and associated objects are initialized in the `init()` method and drawn on the frame using the `paint()`method. Before the applet can be used, it must be started using the `start()` method. You can use threads if you use the `run()` method.

When the applet frame must be updated, the `update()`method is usually called. When users move to a different page or otherwise terminate the applet, the applet must be stopped and cleaned out of memory. The `stop()`method is responsible for stopping the applet, and the `destroy()`method is responsible for cleaning up after the applet.

Although most of these common objects can be called implicitly by the browser that displays the applet, you still should determine how these objects will be used and called by your applet. After you have identified the common objects, reexamine the primary frame components and event list developed in the specification phase. Each frame component and event should translate into an object you will need in the finished applet.

These objects can be grouped into object categories, such as functions and user interfaces. The user interface enables users to manipulate the primary frame of the applet. For applets, the primary frame and the user interface are usually created in the `init()` method or by methods that the `init()` method calls. Both the primary frame and the user interface should have attributes that make them easy to use, read, and understand.

As you design the primary frame and user interface in detail, keep the following attributes in mind:

- Initial frame size
- Color of the frame and borders
- Color, size, and font of text on the frame
- Location of the user interface
- Location of the menu bar
- Style of the title bar
- Size of the display area

Also keep in mind the main objects used on frame, which were identified in the specification phase. For the `SpreadSheet` applet, key objects on the primary frame include a title bar, an input area, and a display area. Following the top-down methodology of object-oriented design, you should now create a high-level design for each of these objects.

The `SpreadSheet` applet's title bar object is quite simple. It accepts an input parameter for the font type used to display the title text and displays the title centered on the frame.

The input area enables the user to enter new data for the current cell, which implies that you need a method to determine the current cell. Although the method used to display the input area is not complex, everything that happens after the user enters data into the input area is. At this stage, you should determine the data types the input area accepts and how the applet will determine the data type.

Generally, spreadsheets accept three types of data: labels, values, and formulas. Because the `SpreadSheet` applet is made for viewing on the Web, it accepts a fourth data type as well: URLs. The `SpreadSheet` applet determines the data type by a single character that proceeds the input value: Values are identified with the letter v, formulas with the letter f, labels with the letter l, and URLs with the letter u.

You will need objects to process the input. The main object should parse the input values and perform specific actions related to the data type. At this point, you do not have to determine these actions; however, you should note that you will need at least one object to handle each data type.

The most complex objects for data types are those that process formulas. To get a better understanding of how the applet will work, you might want to break down the formula object or at least determine the types of formulas the object will process. The `SpreadSheet` applet accepts formulas that use addition, subtraction, multiplication, and division. When the applet has finished processing the formula, you will need a method to recalculate values for cells that use formulas.

The next object you should examine is the display area. It contains the rows and columns of the spreadsheet, which are made up of individual cells. You will need a method to create the cells. To determine how many cells to create and ultimately how many rows and columns the spreadsheet should have, either initialize the spreadsheet to a default size or let the spreadsheet size be updated as needed.

One way to allow the spreadsheet to be updated is to accept parameter values. The `SpreadSheet` applet accepts parameters for font size, base font type, number of columns, number of rows, and initial values for cells. Because the user can update data on the spreadsheet, you also need a method to update the cells. Each of these parameter values will need an associated container that holds its values in the applet.

Now that you have identified the major objects used in the applet, you can create a list of these objects. As you implement each object in the next phase, you can check the object off the list to be sure that you account for all the major functions of the applet. Here's the list:

- `init()` method
- `paint()` method
- `start()` method
- `run()` method
- `stop()` method
- `destroy()` method
- Title bar
- Input area
- Display area
- Rows and columns
- Cells
- Method to determine the current cell
- Method to process data types
- Method to process formulas
- Method to recalculate values for cells
- Method to create and display the cells
- Method to update the cells

After determining most of the objects for the applet, you can go on to more detailed design. Here you should work out the interaction between objects and determine the arguments that objects will accept and pass. When you are comfortable with the design of the applet and can visualize the flow of data from object to object, you can begin the implementation phase. The next section provides a detailed look at how the `SpreadSheet` applet is transformed from a concept to a completed applet.

## The Implementation Phase

The implementation phase is when you actually create the project you have developed. This phase is often the longest and usually involves teams of programmers who implement and integrate the source code. Usually, 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 applet.

In implementing the applet, start with the top-level objects, which usually relate to the user interface and the display, and work toward the lowest-level objects, which usually perform the key functions that the applet requires. The sections that follow demonstrate how to build the `SpreadSheet` applet.

### Containers for the Objects Used in the `SpreadSheet` Applet

Containers create instances of the objects identified in the design phase so the objects can be used throughout the applet. The containers used in most applets include the low-level objects used as placeholders for parameter values. To create a container, you use instance variables. Most instance variable declarations follow the main class declaration.

The containers used in the `SpreadSheet` applet are shown in Listing 20.1. As you can see, there is a container for each of the parameter values identified in the design phase. Other containers hold values for key objects in the spreadsheet. Some of these objects define spreadsheet attributes with initial values.

> Listing 20.1. Initial containers for the `SpreadSheet` applet.

> `public class SpreadSheet extends Applet {
> //container to hold the parameter value for title text
> String title;
> //container to hold the parameter value for base font
> String bfont;
> //container to hold the parameter value for title font
> String tfont;
> //container for the inputfont object
> Font inputFont;
> //container for the title font object
> Font titleFont;
> //container for the cell color object
> Color cellColor;
> //container for the input area's color object
> Color inputColor;
> //container for the parameter value for font size
> int fontSize;
> //container for the cell width object
> int cellWidth;
> //container for the cell height object
> int cellHeight;
> //container for the title height object
> int titleHeight;
> //container for the row label width object
> int rowLabelWidth;
> //container for the status object
> boolean isStopped = false;
> //container for the update object
> boolean fullUpdate = true;
> //container for the rows object
> int rows;
> //container for the columns object
> int columns;
> //container for the current key object
> int currentKey = -1;
> //container for the selected row object
> int selectedRow = -1;
> //container for the selected column object
> int selectedColumn = -1;
> //container for the input area
> SpreadSheetInput inputArea;
> //container for the individual cells
> Cell cells[][];
> //container for the current cell
> Cell current = null;`

### Initializing the Primary Frame

Before you implement the primary frame of an applet, you should carefully consider the type and style of the document with which the applet will be used. If you do not already have an HTML document in which you want to display the applet, you should design one. Depending on the type and style of the HTML document, you might want to make some changes to the design of your applet's interface. You also might want to place additional restrictions on the size of the applet.

For example, if your applet will be used on the corporate home page, you might want to mesh the style of the applet with the existing style of the home page or modify the style of both the applet and the home page so they work well together. Most conflicts between the home page style and the applet style relate to

- The colors used in the applet
- The display size of the applet in pixels
- The file size of the applet in kilobytes
- The appropriateness of the applet for the page

As you add features to the applet's frame window, keep these concepts in mind. Key features for the `SpreadSheet` applet's primary frame, identified in the design phase, are a title bar, an input area, and a display area. Earlier, you should have also identified key attributes for the primary frame, such as the initial frame size, the color of the frame and borders, and the style of the title bar.

Figure 20.2 shows how the `SpreadSheet` applet looks when completed and displayed in an HTML document. By the end of this chapter, you will be able to create this document.

Figure 20.2 : The main screen of the SpreadSheet applet.

The `SpreadSheet` applet's primary frame is initialized in the `init()` method. Because the `SpreadSheet` applet's `init()` method is fairly complex, it is broken down here into a series of steps.

#### Step 1: Initializing Values

The first step is to initialize containers for the necessary objects. As shown in Listing 20.2, two local containers are used for the method. The first container, `rs`, is a multipurpose read string holding parameter values. The second, `fs`, is a string used to hold the value for the font size parameter. The `init()` method is synchronized to prevent more than one thread from executing the method at the same time. In this way, the entire method is locked when it is being used by a thread.

After initializing the containers, initialize the color of active cells to white and set the color of the input area to blue. You can change this color value to anything you like. For example, to change the color value to yellow, use the following:

> `cellColor = Color.yellow;
> `

Tip
Although you could use a parameter value to set the color for the active cell and the input area, keep in mind that `Color` is an object. You would have to convert the string you obtain using the `getParameter()` method to an instance of the `Color` object.

> Listing 20.2. The first section of the `init()` method.

> `public synchronized void init() {
> String rs;
> String fs;
> cellColor = Color.white;
> inputColor = new Color(100, 100, 225);`

#### Step 2: Using Parameter Values

The `getParameter()` method is used to read values for key attributes of the spreadsheet set in the `<PARAM>` tag of the applet's HTML document. If no parameter value is set by the publisher, a default value is used. Similarly, your applets should set default values for all input parameters.

As shown in Listing 20.3, the first parameter the `SpreadSheet` applet checks for is the font size. If no font size is set in the `<PARAM>` tag, it is set to 10. If the `<PARAM>` tag contains a setting for the font size, the string read with the `getParameter()` method is converted to an integer using the `Integer.parseInt()` method, which you will use to convert all parameter values that you plan to manipulate as numbers.

The `fontSize` parameter value is used to set key spreadsheet attributes proportional to the font size. Keeping the size of objects used in the spreadsheet proportional to the current font size is essential to the readability and usability of the applet. The current font size affects every major display aspect of the spreadsheet, including the height and width of cells in the spreadsheet, the width of row labels, the height of the area used for titles, the font size used for titles, the position of the title, the position of row and column headers, the size of the input area, and the position of text in the input area.

For this reason, throughout the `SpreadSheet` applet, you will see the size of objects set proportional to the `fontSize` object. You can use this technique in your applets to keep the display consistent with the current font size.

The next parameter values the `SpreadSheet` applet checks for are the base font type and the title font type. Using these values, initialize the `inputFont` and `titleFont` objects. The default value for both font types, Courier, is used when no parameter value is set in the `<PARAM>` tag. When selecting a default font type, you should use one that is widely available on most systems, such as Courier, Helvetica, Times Roman, or System.

The applet also checks for a title for the spreadsheet. If no parameter value is set, the title is set to `Spreadsheet`.

The final parameter values the applet checks for are the number of rows and columns to be used in the spreadsheet. These must be integer values, so the string read using the `getParameter()` method is converted to an integer using the `parseInt()` method.

> Listing 20.3. Getting and using parameter values.

> `fs = getParameter("fontsize");
> if (fs == null) {
> fontSize = 10;
> } else {
> fontSize = Integer.parseInt(fs);
> }
> cellWidth = fontSize * 10;
> cellHeight = fontSize * 2;
> titleHeight = fontSize * 2;
> rowLabelWidth = fontSize * 3;
> bfont = getParameter("basefont");
> if (bfont == null) {
> bfont = "Courier";
> }
> inputFont = new Font(bfont, Font.PLAIN, fontSize);
> tfont = getParameter("titlefont");
> if (tfont == null) {
> tfont = "Courier";
> }
> titleFont = new Font(tfont, Font.BOLD, (fontSize + 2));
> title = getParameter("title");
> if (title == null) {
> title = "Spreadsheet";
> }
> rs = getParameter("rows");
> if (rs == null) {
> rows = 9;
> } else {
> rows = Integer.parseInt(rs);
> }
> rs = getParameter("columns");
> if (rs == null) {
> columns = 5;
> } else {
> columns = Integer.parseInt(rs);
> }`

#### Step 3: Initializing the Spreadsheet

Initializing an object as complex as a spreadsheet requires careful forethought, especially when its size can be dynamically updated using parameter values. To reduce the difficulty of this task, you could set the spreadsheet to a predetermined size, such as nine rows and five columns. Then, after you understand the way the spreadsheet is built, you could modify the code to allow the size of the spreadsheet to be updated as needed.

To create the spreadsheet, determine the number of cells needed. Then initialize the cells to default values and attributes. One way to handle this is to build an array of an array. The first array holds the row index. The second array holds the column index. Together, these indexes point to a particular cell in the spreadsheet.

To make it easier to set initial values for each cell using parameter values, the spreadsheet applet assumes each column is ordered alphabetically and each row is ordered numerically. Thus, the index value `a1` points to the cell in column 1, row 1, and the value `b4` points to the cell in column 2, row 4. Because Java integers begin with 0, you might be wondering what happened to values for column 0. All cells in column 0 are used to hold values for row headers. Similarly, all cells in row 0 are used to hold values for column headers. This way, when you create a spreadsheet with the parameter value for rows set to 3 and columns set to 4, you get a spreadsheet with 3 rows and 4 columns.

As shown in Listing 20.4, reading the parameter values for cells from the HTML document is accomplished with two conditional loops. The first loop continues until the row count is reached, and the second continues until the column count is reached. Within the second loop, the index values for the row and column are used to build an array of arrays.

Each new cell is created as an instance of the `Cell` object. Included in each new cell are parameters for the background color of the cell; the color of text in the cell; the width and height of the cell; an array containing the cell's index, such as `a1` or `b4`; and a value associated with the cell as read by the `getParameter()` method. Following the logic of the cell-building loops, the following parameter values could be used:

> `<PARAM NAME=a1 value="lWholesale">
> <PARAM NAME=a2 value="v20195">
> <PARAM NAME=a3 value="v7280">
> <PARAM NAME=a4 value="v6127">
> <PARAM NAME=a5 value="v9803">
> <PARAM NAME=a6 value="v4150">
> <PARAM NAME=a7 value="fA2+(A3/A4)">`

If you do not set a value for a particular cell in the spreadsheet, the `setUnparsedValue()` method is called to build a value for the `Cell` object. Just as the `setUnparsedValue()` method is called to add default values for undefined cells, the cell values provided to the `Cell` object are parsed as well. This parsing takes place in the `Cell` class.

> Listing 20.4. Initializing the spreadsheet.

> `cells = new Cell[rows][columns];
> char l[] = new char[1];
> for (int i=0; i < rows; i++) {
> for (int j=0; j < columns; j++) {
> cells[i][j] = new Cell(this,
> Color.lightGray,
> Color.black,
> cellColor,
> cellWidth - (fontSize/4),
> cellHeight - (fontSize/4));
> l[0] = (char)((int)'a' + j);
> rs = getParameter("" + new String(l) + (i+1));
> if (rs != null) {
> cells[i][j].setUnparsedValue(rs);
> }
> }
> }`

#### Step 4: Sizing the Spreadsheet

Sizing a complex structure on the applet frame is a difficult task. You do not want your key objects to be larger than the frame or placed off the frame and thus off the screen. However, you do want the objects to fill the frame if possible. The best way to build the structure within your frame is to base it on the actual dimensions of the frame by creating a dimension object with the current size values and then allocating this area proportionally to each object as necessary.

Although this is the best way to size the objects, you do not always have this luxury and may have to size objects on the basis of some other constraint. The font size is a constraint in the `SpreadSheet` applet simply because you must be able to view the data in the cells no matter the font size.

The `inputArea` for the `SpreadSheet` applet is created as a new instance of the `SpreadSheetInput` object. The size of the `inputArea` is based on the current width minus two pixels, and its height is based on the current height minus two pixels.

The spreadsheet is then resized on the basis of the width and height of all component objects of the spreadsheet. As noted earlier, these objects are sized proportional to the `fontSize` object. The code used to initialize the input area and size the spreadsheet is shown in Listing 20.5.

> Listing 20.5. Sizing the spreadsheet.

> `Dimension d = size();
> inputArea = new SpreadSheetInput(null, this, d.width - 2, cellHeight - 2,
> inputColor, Color.white);
> resize(columns * cellWidth + rowLabelWidth,
> ((rows + 1) * cellHeight) + cellHeight + titleHeight);
> }`

### Determining the State of the Applet

After initializing the applet, implement the other common applet methods if necessary. Usually, your applet does not have to explicitly call the `start()`, `stop()`, and `destroy()` methods. You can better determine the behavior of your applet if you define these methods.

The `SpreadSheet` applet uses the `start()` and `stop()` methods to determine the state of the applet. When the applet is active, the boolean value `isStopped` is set to `false;` when inactive, the boolean value `isStopped` is set to `true`. The value `isStopped` is used later in the applet to stop it from repainting itself before exiting.

The `destroy` method is used to clean up after you halt the applet. If you have processes that could be active even after the applet exits, terminate them in your applet's `destroy()` method. Examples include any connections you made to remote hosts or documents you were accessing.

The `start()`, `stop()`, and `destroy()` methods for the `SpreadSheet` applet are shown in Listing 20.6.

> Listing 20.6. The state of the applet.

> `public void start() {
> isStopped = false;
> }
> public void stop() {
> isStopped = true;
> }
> public void destroy() {
> for (int i=0; i < rows; i++) {
> for (int j=0; j < columns; j++) {
> if (cells[i][j].type == Cell.URL) {
> cells[i][j].updaterThread.stop();
> }
> }
> }
> }`

### Running Applet Threads

The task of running applet threads is handled by the `run()` method. As shown in Listing 20.7, the `run()` method for the `SpreadSheet` applet is fairly lengthy and is handled in a separate class called `CellUpdater` that extends the `Thread` class. By extending the `Thread` class, the applet can use Java's multithreading capabilities to accept input from the token stream and display the data in the spreadsheet. This allows cells of the spreadsheet to be dynamically updated based on data coming from an input stream.

The function of the `while` loop in the `CellUpdater` class is to read string tokens passed from the input stream. As long as there are tokens to read, the `run()` method continues through the `while` loop. This causes the applet to call the `setTransientValue` method, which in turn sets a transient value flag. Before continuing the loop, the `repaint()` method is called to update the frame. When there are no more tokens to read, the `dataStream` object is closed and the `run()` method waits for the next token to be passed.

To use this threading feature of the `SpreadSheet` applet, define a cell as a URL type and specify the URL path to the document you want to read on the input stream.

> Listing 20.7. Running and updating the applet.

> `class CellUpdater extends Thread {
> Cell target;
> InputStream dataStream = null;
> StreamTokenizer tokenStream;
> public CellUpdater(Cell c) {
> super("cell updater");
> target = c;
> }
> public void run() {
> try {
> dataStream = new URL(target.app.getDocumentBase(),
> target.getValueString()).openStream();
> tokenStream = new StreamTokenizer(dataStream);
> tokenStream.eolIsSignificant(false);
> while (true) {
> switch (tokenStream.nextToken()) {
> case tokenStream.TT_EOF:
> dataStream.close();
> return;
> default:
> break;
> case tokenStream.TT_NUMBER:
> target.setTransientValue((float)tokenStream.nval);
> if (! target.app.isStopped && ! target.paused) {
> target.app.repaint();
> }
> break;
> }
> try {
> Thread.sleep(2000);
> } catch (InterruptedException e) {
> break;
> }
> }
> } catch (IOException e) {
> return;
> }
> }
> }`

### Painting the Frame

After you have initialized the spreadsheet, you must display it. This is usually done by means of the `paint()` method, which draws all the objects added during the initialization phase. As discussed in "Step 4: Sizing the Spreadsheet," the objects should be sized according to the dimensions of the frame or a key constraint such as the font size. You should also paint the objects in the order they are to be added to the frame.

The `SpreadSheet` applet uses many interesting techniques to place objects precisely on the frame. As shown in Listing 20.8, centering the title horizontally in the title area involves five steps:

1. Obtaining the dimension of the frame
2. Determining the pixel length of the title on the basis of the current font
3. Subtracting the length of the title from the width of the frame
4. Adding in a factor that accounts for differences in font size
5. Dividing the result by 2

The value you obtain by doing this is the x coordinate for the beginning of the title. You also need a y coordinate for the title. Because the height of the title area is relative to the font size used, the font size is used to help determine this coordinate.

Next, the `paint()` method draws the input area by simply filling a rectangle with a color based on the value of the `inputColor` object. The starting location for the upper-left corner of the input area is determined by moving vertically down the height of one cell. The width of the input area is set to the width of the frame, and the height is set to the height of one cell.

Finally, the `paint()` method draws the individual cells. To create them, use a series of draws. The first adds the horizontal lines for rows using the `draw3DRect` method of the `Graphics` class. As the 3D lines are drawn to the frame, blue numerals representing the row numbers are added as appropriate. The second draw adds the vertical lines for columns, again using the `draw3DRect` method. As the 3D lines are drawn to the frame, red letters representing the column letters are added as appropriate. Finally, data is added to the cells by painting the values associated with a cell precisely within the rectangles created by the previous draws.

The last section of code in the `paint()` method, although only a few lines, is important to the spreadsheet. The line of code using the `draw3DRect()` method draws a 3D line above the input area and on the left side of the frame. The next-to-last line calls the `paint()` method of the `inputArea` class, which ensures that the data associated with the currently selected cell is painted to the input area.

> Listing 20.8. Painting the frame.

> `public synchronized void paint(Graphics g) {
> int i, j;
> int cx, cy;
> char l[] = new char[1];
> Dimension d = size();
> //draws the title on the frame
> g.setFont(titleFont);
> i = g.getFontMetrics().stringWidth(title);
> g.drawString((title == null) ? "Spreadsheet" : title,
> (d.width - i + (fontSize*3/2)) / 2, (fontSize*3/2));
> //draws the input area on the frame
> g.setColor(inputColor);
> g.fillRect(0, cellHeight, d.width, cellHeight);
> //draws the lines for rows using 3d effect
> //adds row numbering in blue
> g.setFont(titleFont);
> for (i=0; i < rows+1; i++) {
> cy = (i+2) * cellHeight;
> g.setColor(getBackground());
> g.draw3DRect(0, cy, d.width, 2, true);
> if (i < rows) {
> g.setColor(Color.blue);
> g.drawString("" + (i+1), fontSize, cy + (fontSize *3/2));
> }
> }
> //draws the lines for columns using 3d effect
> //adds column alphas in red
> g.setColor(Color.red);
> for (i=0; i < columns; i++) {
> cx = i * cellWidth;
> g.setColor(getBackground());
> g.draw3DRect(cx + rowLabelWidth,
> 2 * cellHeight, 1, d.height, true);
> if (i < columns) {
> g.setColor(Color.red);
> l[0] = (char)((int)'A' + i);
> g.drawString(new String(l),
> cx + rowLabelWidth + (cellWidth / 2),
> d.height - 3);
> }
> }
> //paints the cell data
> for (i=0; i < rows; i++) {
> for (j=0; j < columns; j++) {
> cx = (j * cellWidth) + 2 + rowLabelWidth;
> cy = ((i+1) * cellHeight) + 2 + titleHeight;
> if (cells[i][j] != null) {
> cells[i][j].paint(g, cx, cy);
> }
> }
> }
> g.setColor(getBackground());
> //draws a 3d line for the input area and to the left side of the frame
> g.draw3DRect(0, titleHeight, d.width, d.height - titleHeight, false);
> //paints the data of the current cell to the input area
> inputArea.paint(g, 1, titleHeight + 1);
> }`

### Updating the Applet's Frame

When changes occur in the applet, you must repaint the applet's frame using the `repaint()` method of the `Applet` class. If you recall earlier discussions on repainting the applet's frame, you probably know that the `repaint()` method calls the `update()` method of the `Applet` class, which in turn clears the screen and calls the `paint()` method. The `paint` method then draws in the applet's frame.

Clearing and then drawing the frame produces a noticeable flicker. To reduce it, usually it's best to override the `update()` method of the `Applet` class. You do this by defining an `update()` method in your applet that does not clear the frame at all before painting it and, if possible, clears only the parts of the screen that have changed.

Listing 20.9 shows the `update()` method for the `SpreadSheet` applet. This method uses a boolean value called `full update` to determine whether to repaint the whole frame or just a portion of it. When only a portion must be repainted, the appropriate section is redrawn. The partial update occurs when the user selects a cell and the applet places the data associated with the cell in the input area and redraws the cell with a white background. When the entire applet frame needs to be updated, the `update()` method calls the `paint()` method directly without clearing the frame first.

> Listing 20.9. Updating the frame.

> `public void update(Graphics g) {
> if (! fullUpdate) {
> int cx, cy;
> g.setFont(titleFont);
> for (int i=0; i < rows; i++) {
> for (int j=0; j < columns; j++) {
> if (cells[i][j].needRedisplay) {
> cx = (j * cellWidth) + (fontSize*2) + rowLabelWidth;
> cy = ((i+1) * cellHeight) + (fontSize*2) + titleHeight;
> cells[i][j].paint(g, cx, cy);
> }
> }
> }
> } else {
> paint(g);
> fullUpdate = false;
> }
> }`

### Handling Events in the Applet

Updates to the `SpreadSheet` applet are driven by the occurrence of user events, such as keypresses and mouse button clicks. As discussed in previous chapters, events are usually handled with either the `action()` method or the `handleEvent()` method. The `action()` method of the `Components` class is the preferred way to handle a limited number of events. The `handleEvent` method of the `Event` class is the preferred way to handle multiple or complex series of events.

When an applet has only a few possible events that deal with the mouse or keypresses, you can call more direct methods that eliminate the necessity of nested `if` or `case` statements to check event type. These methods, like the `action()` method, are members of the `Component` class. Table 20.1 lists these events.

Table 20.1. Direct event handling with `Component` class methods.

Method | Description | Called with Values
`keyDown()` | Called if a key is pressed | `keyDown(Event evt, int x)`
`keyUp()` | Called if a key is released | `keyUp(Event evt, int x)`
`mouseDown()` | Called if the mouse button is down | `mouseDown(Event evt, int x, int y)`
`mouseDrag()` | Called if the mouse moves while a button is pressed | `mouseDrag(Event evt,` `int x, int y)`
`mouseEnter()` | Called when the mouse enters the component | `mouseEnter(Event evt,` `int x, int y)`
`mouseExit()` | Called when the mouse exits the component | `mouseExit(Event evt,` `int x, int y)`
`mouseMove()` | Called if the mouse moves while no buttons are pressed | `mouseMove(Event evt, int x, int y)`
`mouseUp()` | Called if the mouse button is up | `mouseUp(Event evt, int x, int y)`

The `SpreadSheet` applet uses two of these direct methods to check for events: `mouseDown` and `keyDown`. The handling of these events is shown in Listing 20.10.

When a user moves the mouse pointer over a cell and selects it by clicking the mouse button, the applet determines the exact cell the user selected based on the position of the mouse in the spreadsheet. As you can see from Listing 20.10, finding the selected cell is a lot of work.

The first conditional loop ensures that the y coordinate of the mouse pointer is in a valid section of the spreadsheet. If the y coordinate is less than the combined height of the title and input area, the selected row is set to `-1`. The nested `if` loop sets the value for the current cell to `null` when the user clicks the mouse button when the pointer is in the title area and the `deselect` method is called.

The second conditional loop ensures that the x coordinate of the mouse pointer is in a valid section of the spreadsheet. If the x coordinate is less than the width of the row label, the selected row is set to `-1`. The nested `if` loop sets the value for the current cell to `null` when the user clicks the mouse button when the pointer is in the row label area and the `deselect` method is called.

Next, the method determines the column/row coordinate for the selected cell. If the selected cell is greater than the number of rows or columns in the spreadsheet, the value for the current cell is set to `null` and the `deselect` method is called. If the column/row coordinate for the selected cell is valid, the value of the selected cell is displayed in the input area and the `select()` method is called to paint the current cell with a white background.

The final event checked for is a keypress. As long as a valid cell is selected, any character key you press on the keyboard is displayed in the input area. When you press Enter and a valid cell is selected, the value from the input area replaces the value of the current cell and the entire spreadsheet is updated. This is all handled by setting `fullUpdate` to `true` and calling the `keyDown()` method, which actually does the work of updating the spreadsheet as necessary.

You can use similar techniques to determine the selected object in your applets as well. The key is to narrow the valid range by eliminating areas of the frame that cannot be selected, such as the title bar, rows with header labels, and columns with header labels. When you narrow the selection area, you can determine the specific object the user selected.

> Listing 20.10. Handling applet events.

> `public boolean mouseDown(Event evt, int x, int y) {
> Cell cell;
> //ensures the y coordinate of the mouse pointer
> //is not in the title/input area
> if (y < (titleHeight + cellHeight)) {
> selectedRow = -1;
> if (y <= titleHeight && current != null) {
> current.deselect();
> current = null;
> }
> return true;
> }
> //ensures the x coordinate of the mouse pointer
> //is not in the row label area
> if (x < rowLabelWidth) {
> selectedRow = -1;
> if (current != null) {
> current.deselect();
> current = null;
> }
> return true;
> }
> //determines the row of the selected cell
> selectedRow = ((y - cellHeight - titleHeight) / cellHeight);
> //determines the column of the selected cell
> selectedColumn = (x - rowLabelWidth) / cellWidth;
> //ensures the row and column coordinate is valid
> if (selectedRow >= rows ||
> selectedColumn >= columns) {
> selectedRow = -1;
> if (current != null) {
> current.deselect();
> current = null;
> }
> //if the row-column coordinate is valid place the selected cell's value
> //in the input area
> } else {
> cell = cells[selectedRow][selectedColumn];
> inputArea.setText(new String(cell.getPrintString()));
> if (current != null) {
> current.deselect();
> }
> current = cell;
> current.select();
> requestFocus();
> fullUpdate = true;
> repaint();
> }
> return true;
> }
> //determine if a key is pressed
> public boolean keyDown(Event evt, int key) {
> fullUpdate=true;
> inputArea.keyDown(key);
> return true;
> }
> }`

### Updating the Current Cell's Value

User events drive changes in the spreadsheet. When a cell's value is changed, the spreadsheet must be updated to reflect this. As noted in the "Handling Events in the Applet" section in this chapter, the `keyDown()` method is called to handle the update. One of the high-level objects `keyDown` calls is the `setCurrentValue()` method of the `SpreadSheet` class, which takes the current value of the input area and assigns it to the active cell.

Two versions of the `setCurrentValue()` method are defined in the applet: The first sets values for floating-point numbers, and the second sets values for strings. This is a good example of overloading a method to handle multiple types of input. Listing 20.11 shows the code for updating the current cell's value.

> Listing 20.11. Updating the current cell's value.

> `public void setCurrentValue(float val) {
> if (selectedRow == -1 || selectedColumn == -1) {
> return;
> }
> cells[selectedRow][selectedColumn].setValue(val);
> repaint();
> }
> public void setCurrentValue(int type, String val) {
> if (selectedRow == -1 || selectedColumn == -1) {
> return;
> }
> cells[selectedRow][selectedColumn].setValue(type, val);
> repaint();
> }`

### Handling Formulas

The `SpreadSheet` class is responsible for most of the high-level functions of the applet. To complete this class, you need methods for calculating formulas and evaluating formulas.

Before the spreadsheet is displayed, the `recalculate()` method is called to convert any formulas the spreadsheet may have to numeric values. Without a call to this method, the spreadsheet makes no calculations, and the formula is displayed in the cell instead of the numeric value. To ensure that both the numeric value and the formula are available to the user, the `SpreadSheet` applet stores the formula and the value in separate buffers.

As you can see from Listing 20.12, the `recalculate()` method goes through each cell in the spreadsheet checking for the cell type `FORMULA`. If a cell contains a formula, the formula is evaluated using the `evaluateFormula()` method. The values returned from the `evaluateFormula()` method are passed to the `setRawValue()` method.

> Listing 20.12. Calculating formula values.

> `public void recalculate() {
> int i,j;
> //System.out.println("SpreadSheet.recalculate");
> for (i=0; i < rows; i++) {
> for (j=0; j < columns; j++) {
> if (cells[i][j] != null && cells[i][j].type == Cell.FORMULA) {
> cells[i][j].setRawValue(evaluateFormula(cells[i][j].parseRoot));
> cells[i][j].needRedisplay = true;
> }
> }
> }
> repaint();
> }`

The `SpreadSheet` applet has the capability to parse formulas with basic math functions like addition, subtraction, multiplication, and division by examining formula nodes. Nodes are defined in the applet as class-level objects that have a left value, an operand, and a right value.

The `evaluateFormula()` method shown in Listing 20.13 examines formula nodes looking for operands, values, and cell references. Many computer science majors will recognize the simple algorithm used to evaluate spreadsheet formulas as an expression tree. A simple recursive process is then used to evaluate the nodes of the tree. Values associated with these node components are returned to the `setRawValue()` method, which displays the numeric value of the cell as a string.

Tip
Recursion on trees is one of the basic algorithms in the programmer's toolkit. If this is the first time you have encountered an expression tree algorithm, take the time to learn how this is done. Start by examining the `evaluateFormula()` method and then examine the `parseFormula()` and `parseValue()` methods of the `Cell` class. You may be surprised at the number of uses for this algorithm.

> Listing 20.13. Evaluating formulas.

> `public float evaluateFormula(Node n) {
> float val = 0.0f;
> if (n == null) {
> return val;
> }
> switch (n.type) {
> case Node.OP:
> val = evaluateFormula(n.left);
> switch (n.op) {
> case '+':
> val += evaluateFormula(n.right);
> break;
> case '*':
> val *= evaluateFormula(n.right);
> break;
> case '-':
> val -= evaluateFormula(n.right);
> break;
> case '/':
> val /= evaluateFormula(n.right);
> break;
> }
> break;
> case Node.VALUE:
> return n.value;
> case Node.CELL:
> if (n == null) {
> } else {
> if (cells[n.row][n.column] == null) {
> } else {
> return cells[n.row][n.column].value;
> }
> }
> }
> return val;
> }`

### Creating Cells

The next class-level object in the spreadsheet is the `Cell` object, which is responsible for maintaining the attributes of a cell. Listing 20.14 shows the declarations for the `Cell` object's containers.

When new instances of the `Cell` class are created during the initialization of the frame, the `Cell` method is called and the initial attributes for the cell are set. These attributes include the background color, the foreground color, the color to use when the cell is selected, and the width and height of the cell. Other attributes of the cell are set to default values or are based on the value
associated with each cell as read by the `getParameter()` method.

> Listing 20.14. Containers for the `Cell` object.

> `class Cell {
> public static final int VALUE = 0; //initializes the VALUE container
> public static final int LABEL = 1; //initializes the LABEL container
> public static final int URL = 2; //initializes the URL container
> public static final int FORMULA = 3; //initializes the FORMULA container
> Node parseRoot; //sets the node for the cell
> boolean needRedisplay; //used to determine the need for redisplay
> boolean selected = false; //used to determine whether cell is selected
> boolean transientValue = false; //tracks need to set transient value
> public int type = Cell.VALUE; //sets the cell type
> String valueString = ""; //initializes the value string
> String printString = "v"; //initiliazes the print string
> float value; //container for a float called value
> Color bgColor; //container for the background color
> Color fgColor; //container for the foreground color
> Color highlightColor; //container for cell color when selected
> int width; //container for the width of the cell
> int height; //container for the height of the cell
> SpreadSheet app; //container for the SpreadSheet object
> CellUpdater updaterThread; //container for the CellUpdater object
> boolean paused = false; //used to determine if paused`

### Preparing Cells for Display

After the containers for `Cell` objects are initialized, a series of methods, shown in Listing 20.15, prepare the cell for display.

The `Cell()` method is called with values for the key attributes of the cell; it then associates these values with the `Cell` object. All cells of the spreadsheet must have a type, `valueString`, and `printString` associated with them. The first character of the value read by the `getParameter()` method character is used to set the cell type. The `valueString` excludes the first character and is used in calculations. The `printString` contains the value of the cell as it should be displayed to the input area when the cell is selected.

The `setUnparsedValue()` method sets the cell type. If the first character is a `v`, the type is set to `VALUE`; if it is an `l`, the type is set to `LABEL`; if it is a `u`, the type is set to URL; and if it is an `f`, the type is set to FORMULA.

`setValue()`is an overloaded method that builds the `valueString` and `printString` objects of all cells. The first version of the method accepts string values and, depending on cell type, calls associated methods. The second version accepts floating-point values and is called only when the cell type is `VALUE`. This overloaded method in turn calls the `setRawValue()` method that converts the floating-point value it is passed to a `String` object.

The `setTransientValue()`method sets a transient value associated with the cell. This value is set only when a number is passed through the token stream.

> Listing 20.15. Preparing the cell for display.

> `//sets attributes for the Cell objects as passed
> public Cell(SpreadSheet app,
> Color bgColor,
> Color fgColor,
> Color highlightColor,
> int width,
> int height) {
> this.app = app;
> this.bgColor = bgColor;
> this.fgColor = fgColor;
> this.highlightColor = highlightColor;
> this.width = width;
> this.height = height;
> needRedisplay = true;
> }
> //sets the valueString by converting the numeric value of the cell to a string
> public void setRawValue(float f) {
> valueString = Float.toString(f);
> value = f;
> }
> //sets the cell type for values, formulas, labels and URLs
> public void setUnparsedValue(String s) {
> switch (s.charAt(0)) {
> case 'v':
> setValue(Cell.VALUE, s.substring(1));
> break;
> case 'f':
> setValue(Cell.FORMULA, s.substring(1));
> break;
> case 'l':
> setValue(Cell.LABEL, s.substring(1));
> break;
> case 'u':
> setValue(Cell.URL, s.substring(1));
> break;
> }
> }
> //sets the printString by prepending a type indicator to the valueString
> public void setValue(float f) {
> setRawValue(f);
> printString = "v" + valueString;
> type = Cell.VALUE;
> paused = false;
> app.recalculate();
> needRedisplay = true;
> }
> //sets the printString by prepending a type indicator to the valueString
> public void setValue(int type, String s) {
> paused = false;
> if (this.type == Cell.URL) {
> updaterThread.stop();
> updaterThread = null;
> }
> valueString = new String(s);
> this.type = type;
> needRedisplay = true;
> switch (type) {
> case Cell.VALUE:
> setValue(Float.valueOf(s).floatValue());
> break;
> case Cell.LABEL:
> printString = "l" + valueString;
> break;
> case Cell.URL:
> printString = "u" + valueString;
> updaterThread = new CellUpdater(this);
> updaterThread.start();
> break;
> case Cell.FORMULA:
> parseFormula(valueString, parseRoot = new Node());
> printString = "f" + valueString;
> break;
> }
> app.recalculate();
> }
> //sets a transient value flag
> public void setTransientValue(float f) {
> transientValue = true;
> value = f;
> needRedisplay = true;
> app.recalculate();
> }`

### Utility Methods

Four utility methods, shown in Listing 20.16, handle low-level tasks for `Cell` objects: The `getPrintString()`method returns the value of the `printString` associated with the cell; the `getValueString()`method returns the value of the `valueString` associated with the cell; the `select()`method sets flags to highlight the cell when the frame is next painted; and the `deselect()` method sets flags to reset the cell's background to its original color when the frame is next painted.

> Listing 20.16. Utility methods of the `Cell` class.

> `public String getPrintString() {
> return printString;
> }
> public String getValueString() {
> return valueString;
> }
> public void select() {
> selected = true;
> paused = true;
> }
> public void deselect() {
> selected = false;
> paused = false;
> needRedisplay = true;
> }`

### Parsing Formulas

Parsing formulas associated with a cell is a difficult task handled by two methods: `parseFormula` and `parseValue`. These two methods take cell formulas apart piece by piece by examining formula nodes. As you examine Listing 20.17, study the algorithm that builds and dissects the nodes. Recall that modes are defined in the applet as class-level objects that have a left value, an operand, and a right value. (See the section "Handling Formulas" for a listing of valid operands and how they are used.)

The values associated with a node can be either implicit or explicit. Explicit node values are numbers that you want to add, multiply, divide, or subtract. Implicit values are numbers associated with a particular cell. Reference implicit values in formulas using the column/row coordinate of the cell.

A node that has only a left value for it is still valid. Using these concepts of nodes, the basic syntax for formulas is

> `value
> value operand value`

where each instance of `value` can be an actual value or a reference to a particular cell of the spreadsheet, such as

> `A2 * 4`

or

> `A2 + B2`

To allow for more complex formulas, the `parseFormula` and `parseValue` methods allow you to define subformulas for the right-hand value of the original node using parentheses. Because subformulas are treated as nodes, they also can have a left value, an operand, and a right value. You can continue to nest subformulas as long as you follow the concept of nodes. Using subformulas, you can create formulas with the following syntax:

> `value operand (value operand value)
> value operand (value operand (value operand value))`

You can continue to nest subformulas as long as you follow the basic node structure of `value operand value`. As discussed earlier, the algorithm that makes this work is a recursive procedure on an expression tree.

> Listing 20.17. Parsing formulas.

> `/**
> * Parse a spreadsheet formula. The syntax is defined as:
> *
> * formula -> value
> * formula -> value op value
> * value -> '(' formula ')'
> * value -> cell
> * value -> <number>
> * op -> '+' | '*' | '/' | '-'
> * cell -> <letter><number>
> */
> public String parseFormula(String formula, Node node) {
> String subformula;
> String restFormula;
> float value;
> int length = formula.length();
> Node left;
> Node right;
> char op;
> if (formula == null) {
> return null;
> }
> subformula = parseValue(formula, node);
> if (subformula == null || subformula.length() == 0) {
> //System.out.println("Parse succeeded");
> return null;
> }
> if (subformula == formula) {
> return formula;
> }
> // parse an operator and then another value
> switch (op = subformula.charAt(0)) {
> case 0:
> return null;
> case ')':
> return subformula;
> case '+':
> case '*':
> case '-':
> case '/':
> restFormula = subformula.substring(1);
> subformula = parseValue(restFormula, right=new Node());
> if (subformula != restFormula) {
> left = new Node(node);
> node.left = left;
> node.right = right;
> node.op = op;
> node.type = Node.OP;
> return subformula;
> } else {
> return formula;
> }
> default:
> return formula;
> }
> }
> public String parseValue(String formula, Node node) {
> char c = formula.charAt(0);
> String subformula;
> String restFormula;
> float value;
> int row;
> int column;
> restFormula = formula;
> if (c == '(') {
> restFormula = formula.substring(1);
> subformula = parseFormula(restFormula, node);
> if (subformula == null ||
> subformula.length() == restFormula.length()) {
> return formula;
> } else if (! (subformula.charAt(0) == ')')) {
> return formula;
> }
> restFormula = subformula;
> } else if (c >= '0' && c <= '9') {
> int i;
> try {
> value = Float.valueOf(formula).floatValue();
> } catch (NumberFormatException e) {
> return formula;
> }
> for (i=0; i < formula.length(); i++) {
> c = formula.charAt(i);
> if ((c < '0' || c > '9') && c != '.') {
> break;
> }
> }
> node.type = Node.VALUE;
> node.value = value;
> restFormula = formula.substring(i);
> // " rest = " + restFormula);
> return restFormula;
> } else if (c >= 'A' && c <= 'Z') {
> int i;
> column = c - 'A';
> restFormula = formula.substring(1);
> row = Float.valueOf(restFormula).intValue();
> for (i=0; i < restFormula.length(); i++) {
> c = restFormula.charAt(i);
> if (c < '0' || c > '9') {
> break;
> }
> }
> node.row = row - 1;
> node.column = column;
> node.type = Node.CELL;
> if (i == restFormula.length()) {
> restFormula = null;
> } else {
> restFormula = restFormula.substring(i);
> if (restFormula.charAt(0) == 0) {
> return null;
> }
> }
> }
> return restFormula;
> }`

### Painting Cells

The `paint()` method of the `Cell` class sets the colors for spreadsheet cells. If the selected flag is set, the background color of the cell is set to the value associated with the `highlightColor` object-white. If it is not set, the background color of the cell is set to the value associated with the `bgColor` object-gray. When the background color is set, the rectangle associated with the cell is painted.

When new cell objects are created, the `fgcolor` attribute is set to black. This attribute is used to set the font color for each cell. If the cell type is `VALUE` or `LABEL`, the font color is set to black; if the cell type is `FORMULA`, it is set to red; and if the cell type is `URL`, it is set to blue. When the color for data associated with a cell is set, the data is drawn to the frame using the `drawString()` method. Listing 20.18 shows the `paint()` method of the `Cell` class.

> Listing 20.18. Painting cells.

> `public void paint(Graphics g, int x, int y) {
> if (selected) {
> g.setColor(highlightColor);
> } else {
> g.setColor(bgColor);
> }
> g.fillRect(x, y, width - 1, height);
> if (valueString != null) {
> switch (type) {
> case Cell.VALUE:
> case Cell.LABEL:
> g.setColor(fgColor);
> break;
> case Cell.FORMULA:
> g.setColor(Color.red);
> break;
> case Cell.URL:
> g.setColor(Color.blue);
> break;
> }
> if (transientValue){
> g.drawString("" + value, x, y + (height / 2) + 5);
> } else {
> if (valueString.length() > 14) {
> g.drawString(valueString.substring(0, 14),
> x, y + (height / 2) + 5);
> } else {
> g.drawString(valueString, x, y + (height / 2) + 5);
> }
> }
> }
> needRedisplay = false;
> }
> }`

### Building the Node Object

You use the `Node` class of the `SpreadSheet` applet to build the nodes used in formulas. As you can see in Listing 20.19, nodes are not complex objects, and building a node is rather easy when you understand that all nodes have a left value, an operand, and a right value associated with them.

To track the types of values associated with nodes, three static variables are used: The `OP` variable is set for operands, the `VALUE` variable is set for numeric values, and the `CELL` variable is set for references to cell objects.

The `Node` class includes an overloaded method called `Node()`. The first version of the `Node()` method builds a generic node that is used when parsing subformulas. The second version is used when parsing formulas.

> Listing 20.19. Building nodes.

> `class Node {
> //set for node components that are operands
> public static final int OP = 0;
> //set for node components that are values
> public static final int VALUE = 1;
> //set for node components that are cell references
> public static final int CELL = 2;
> int type;
> Node left; //the lefthand value for the node
> Node right; //the righthand value for the node
> int row; //the row coordinate of the selected cell
> int column; //the column coordinate of the selected cell
> float value; //the current value
> char op; //the operand +, -, /, *
> public Node() {
> left = null;
> right = null;
> value = 0;
> row = -1;
> column = -1;
> op = 0;
> type = Node.VALUE;
> }
> public Node(Node n) {
> left = n.left;
> right = n.right;
> value = n.value;
> row = n.row;
> column = n.column;
> op = n.op;
> type = n.type;
> }
> }`

### Building the Input Area

The input area is the final object needed in the `SpreadSheet` applet. Although it could have been built using a simple `textField` object, a more advanced approach is to build an input area that behaves and looks exactly as you want it to.

Note
You will find that there are many times when control over the behavior and style of the input area is needed in an advanced applet. To do this, you must build your own input area.

When building your own input area, there are many things you should consider. Most are obvious and relate to why you wanted to create your own input area in the first place. Style considerations include the following:

- The size of the input area
- The color of the background
- The font type, size, and color of text input

Behavior considerations include the following:

- How the input area is activated
- What the accepted key values are
- Whether any input must be parsed
- What the maximum length of the input is
- Whether the Enter key causes the applet to update its display

Two class-level objects are used to build the input area for the `SpreadSheet` applet: `SpreadSheetInput` and `InputField`.

#### The `SpreadSheetInput` Class

The `SpreadSheetInput` class, shown in Listing 20.20, extends the `InputField` class, which is defined next. The class includes two fairly basic methods.

The `SpreadSheetInput()` method passes the values with which it was called to the `InputField` class. The `selected()` method calls the `setCurrentValue` method with the appropriate parameters so the value of the input area can be passed back to the currently selected cell. Thus, the cell is updated when the user makes a change and presses Enter.

> Listing 20.20. The `SpreadSheetInput` class.

> `class SpreadSheetInput extends InputField {
> public SpreadSheetInput(String initValue,
> SpreadSheet app,
> int width,
> int height,
> Color bgColor,
> Color fgColor) {
> super(initValue, app, width, height, bgColor, fgColor);
> }
> public void selected() {
> float f;
> switch (sval.charAt(0)) {
> case 'v':
> try {
> f = Float.valueOf(sval.substring(1)).floatValue();
> ((SpreadSheet)app).setCurrentValue(f);
> } catch (NumberFormatException e) {
> System.out.println("Not a float...");
> }
> break;
> case 'l':
> ((SpreadSheet)app).setCurrentValue(Cell.LABEL, sval.substring(1));
> break;
> case 'u':
> ((SpreadSheet)app).setCurrentValue(Cell.URL, sval.substring(1));
> break;
> case 'f':
> ((SpreadSheet)app).setCurrentValue(Cell.FORMULA, sval.substring(1));
> break;
> }
> }
> }`

#### The `InputField` Class

As you can see in Listing 20.21, the `InputField` class is more complex than the `SpreadSheetInput` class, primarily because the `InputField` object defines the style and behavior of the input area.

The first section of the `InputField` class initializes containers that determine the style of the input area and limit its behavior. The maximum number of characters the input area accepts is 50. Each character entered into the input area is stored in an array. When the user adds characters to the input area by either selecting a cell or entering data, the array grows. When the user deletes characters, the array shrinks.

The `InputField()` method is called to set the attributes for the input area. In the unlikely case that the method is called with an initial value set, the string buffer is set to this value.

The `setText()` method updates the input area when a cell is selected or when text is entered or deleted by reading the string buffer character by character. The `paint()` method overrides the `paint()` method of the applet class and draws values to the input area.

The heart of this class is the event handler. As with other event handlers in the `SpreadSheet` applet, this one checks for events directly using methods of the `Component` class. Here, the `keyDown` event is checked to determine when a key is pressed.

The `keyDown()` method converts the value of the pressed key to an integer and then checks for three specific key types using their integer values. The integer value of the delete key is `8`. When the Delete key is pressed, one character is deleted from the `buffer[]` array. The integer value of the Enter key is `10`. When the Enter key is pressed, the current value of the `buffer[]` array is passed back to the cell. This happens behind the scenes after the empty `selected()` method of the `InputField` class is called. By default, the `keyDown()` method stores the value of any keypress other than Delete or Enter to the `buffer[]` array.

> Listing 20.21. The `InputField` class.

> `class InputField {
> int maxchars = 50; //the maximum characters for the input field
> int cursorPos = 0; //index to the current cursor position
> Applet app; //instance of the applet object
> String sval; //container for string values
> char buffer[]; //an array of characters up to maxchars in length
> int nChars; //the number of characters in the input area
> int width; //width of the input area
> int height; //height of the input area
> Color bgColor; //background color of the input area
> Color fgColor; //font color for text in the input area
> public InputField(String initValue, Applet app, int width, int height,
> Color bgColor, Color fgColor) {
> this.width = width;
> this.height = height;
> this.bgColor = bgColor;
> this.fgColor = fgColor;
> this.app = app;
> buffer = new char[maxchars];
> nChars = 0;
> if (initValue != null) {
> initValue.getChars(0, initValue.length(), this.buffer, 0);
> nChars = initValue.length();
> }
> sval = initValue;
> }
> public void setText(String val) {
> int i;
> for (i=0; i < maxchars; i++) {
> buffer[i] = 0;
> }
> sval = new String(val);
> if (val == null) {
> sval = "";
> nChars = 0;
> buffer[0] = 0;
> } else {
> sval.getChars(0, sval.length(), buffer, 0);
> nChars = val.length();
> sval = new String(buffer);
> }
> }
> public void paint(Graphics g, int x, int y) {
> g.setColor(bgColor);
> g.fillRect(x, y, width, height);
> if (sval != null) {
> g.setColor(fgColor);
> g.drawString(sval, x, y + (height / 2) + 3);
> }
> }
> public void keyDown(int key) {
> if (nChars < maxchars) {
> switch (key) {
> case 8: // delete
> --nChars;
> if (nChars < 0) {
> nChars = 0;
> }
> buffer[nChars] = 0;
> sval = new String(new String(buffer));
> break;
> case 10: // return
> selected();
> break;
> default:
> buffer[nChars++] = (char)key;
> sval = new String(new String(buffer));
> break;
> }
> }
> app.repaint();
> }
> public void selected() {
> }
> }`

## The Complete Applet

Listing 21.22 shows the complete code for the `SpreadSheet` applet. Although you can go straight to the CD-ROM and access the complete source code, typing it in line by line forces you to study each line of code.

> Listing 20.22. The `SpreadSheet` applet.

> `/*
> * @(#)SpreadSheet.java 1.17 95/03/09 Sami Shaio
> *
> * Copyright (c) 1994-1995 Sun Microsystems, Inc. All Rights Reserved.
> *
> * Permission to use, copy, modify, and distribute this software
> * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
> * without fee is hereby granted.
> * Please refer to the file http://java.sun.com/copy_trademarks.html
> * for further important copyright and trademark information and to
> * http://java.sun.com/licensing.html for further important licensing
> * information for the Java (tm) Technology.
> *
> * Modified for Peter Norton's Guide to Programming Java by William R. Stanek
> * 21 March 1996
> * @version 2.02
> *
> * These modifications include:
> *
> * Added parameter support for publisher defined title fonts, base fonts
> * for columns and rows, and font sizes.
> * Updated the title, column and row positions/sizes to make them dynamic and
> * thus support publisher defined font types and sizes.
> * Changed display characteristics.
> * Eliminates a display bug.
> * Adds comment descriptions to methods
> * Streamlined some class objects
> *
> * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
> * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, IncLUDING BUT NOT LIMITED
> * TO THE IMPLIED WARRANTIES OF MERchANTABILITY, FITNESS FOR A
> * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
> * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
> * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
> *
> * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
> * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
> * PERFORMAncE, SUch AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
> * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
> * SUPPORT MAchINES, OR WEAPONS SYSTEMS, IN WHIch THE FAILURE OF THE
> * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
> * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). SUN
> * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
> * HIGH RISK ACTIVITIES.
> */
> import java.applet.Applet;
> import java.awt.*;
> import java.io.*;
> import java.lang.*;
> import java.net.*;
> public class SpreadSheet extends Applet {
> //container to hold the parameter value for title text
> String title;
> //container to hold the parameter value for base font
> String bfont;
> //container to hold the parameter value for title font
> String tfont;
> //container for the inputfont object
> Font inputFont;
> //container for the title font object
> Font titleFont;
> //container for the cell color object
> Color cellColor;
> //container for the input area's color object
> Color inputColor;
> //container for the parameter value for font size
> int fontSize;
> //container for the cell width object
> int cellWidth;
> //container for the cell height object
> int cellHeight;
> //container for the title height object
> int titleHeight;
> //container for the row label width object
> int rowLabelWidth;
> //container for the status object
> boolean isStopped = false;
> //container for the update object
> boolean fullUpdate = true;
> //container for the rows object
> int rows;
> //container for the columns object
> int columns;
> //container for the current key object
> int currentKey = -1;
> //container for the selected row object
> int selectedRow = -1;
> //container for the selected column object
> int selectedColumn = -1;
> //container for the input area
> SpreadSheetInput inputArea;
> //container for the individual cells
> Cell cells[][];
> //container for the current cell
> Cell current = null;
> public synchronized void init() {
> String rs;
> String fs;
> cellColor = Color.white;
> inputColor = new Color(100, 100, 225);
> fs = getParameter("fontsize");
> if (fs == null) {
> fontSize = 10;
> } else {
> fontSize = Integer.parseInt(fs);
> }
> cellWidth = fontSize * 10;
> cellHeight = fontSize * 2;
> titleHeight = fontSize * 2;
> rowLabelWidth = fontSize * 3;
> bfont = getParameter("basefont");
> if (bfont == null) {
> bfont = "Courier";
> }
> inputFont = new Font(bfont, Font.PLAIN, fontSize);
> tfont = getParameter("titlefont");
> if (tfont == null) {
> tfont = "Courier";
> }
> titleFont = new Font(tfont, Font.BOLD, (fontSize + 2));
> title = getParameter("title");
> if (title == null) {
> title = "Spreadsheet";
> }
> rs = getParameter("rows");
> if (rs == null) {
> rows = 9;
> } else {
> rows = Integer.parseInt(rs);
> }
> rs = getParameter("columns");
> if (rs == null) {
> columns = 5;
> } else {
> columns = Integer.parseInt(rs);
> }
> cells = new Cell[rows][columns];
> char l[] = new char[1];
> for (int i=0; i < rows; i++) {
> for (int j=0; j < columns; j++) {
> cells[i][j] = new Cell(this,
> Color.lightGray,
> Color.black,
> cellColor,
> cellWidth - (fontSize/4),
> cellHeight - (fontSize/4));
> l[0] = (char)((int)'a' + j);
> rs = getParameter("" + new String(l) + (i+1));
> if (rs != null) {
> cells[i][j].setUnparsedValue(rs);
> }
> }
> }
> Dimension d = size();
> inputArea = new SpreadSheetInput(null, this, d.width - 2,
> cellHeight - 2, inputColor, Color.white);
> resize(columns * cellWidth + rowLabelWidth,
> ((rows + 1) * cellHeight) + cellHeight + titleHeight);
> }
> public void setCurrentValue(float val) {
> if (selectedRow == -1 || selectedColumn == -1) {
> return;
> }
> cells[selectedRow][selectedColumn].setValue(val);
> repaint();
> }
> public void stop() {
> isStopped = true;
> }
> public void start() {
> isStopped = false;
> }
> public void destroy() {
> for (int i=0; i < rows; i++) {
> for (int j=0; j < columns; j++) {
> if (cells[i][j].type == Cell.URL) {
> cells[i][j].updaterThread.stop();
> }
> }
> }
> }
> public void setCurrentValue(int type, String val) {
> if (selectedRow == -1 || selectedColumn == -1) {
> return;
> }
> cells[selectedRow][selectedColumn].setValue(type, val);
> repaint();
> }
> public void update(Graphics g) {
> if (! fullUpdate) {
> int cx, cy;
> g.setFont(titleFont);
> for (int i=0; i < rows; i++) {
> for (int j=0; j < columns; j++) {
> if (cells[i][j].needRedisplay) {
> cx = (j * cellWidth) + (fontSize*2) + rowLabelWidth;
> cy = ((i+1) * cellHeight) + (fontSize*2) + titleHeight;
> cells[i][j].paint(g, cx, cy);
> }
> }
> }
> } else {
> paint(g);
> fullUpdate = false;
> }
> }
> public void recalculate() {
> int i,j;
> //System.out.println("SpreadSheet.recalculate");
> for (i=0; i < rows; i++) {
> for (j=0; j < columns; j++) {
> if (cells[i][j] != null && cells[i][j].type == Cell.FORMULA) {
> cells[i][j].setRawValue(evaluateFormula
> Â(cells[i][j].parseRoot));
> cells[i][j].needRedisplay = true;
> }
> }
> }
> repaint();
> }
> public float evaluateFormula(Node n) {
> float val = 0.0f;
> //System.out.println("evaluateFormula:");
> //n.print(3);
> if (n == null) {
> //System.out.println("Null node");
> return val;
> }
> switch (n.type) {
> case Node.OP:
> val = evaluateFormula(n.left);
> switch (n.op) {
> case '+':
> val += evaluateFormula(n.right);
> break;
> case '*':
> val *= evaluateFormula(n.right);
> break;
> case '-':
> val -= evaluateFormula(n.right);
> break;
> case '/':
> val /= evaluateFormula(n.right);
> break;
> }
> break;
> case Node.VALUE:
> //System.out.println("=>" + n.value);
> return n.value;
> case Node.CELL:
> if (n == null) {
> //System.out.println("NULL at 192");
> } else {
> if (cells[n.row][n.column] == null) {
> //System.out.println("NULL at 193");
> } else {
> //System.out.println("=>" + cells[n.row][n.column].value);
> return cells[n.row][n.column].value;
> }
> }
> }
> //System.out.println("=>" + val);
> return val;
> }
> public synchronized void paint(Graphics g) {
> int i, j;
> int cx, cy;
> char l[] = new char[1];
> Dimension d = size();
> //draws the title on the frame
> g.setFont(titleFont);
> i = g.getFontMetrics().stringWidth(title);
> g.drawString((title == null) ? "Spreadsheet" : title,
> (d.width - i + (fontSize*3/2)) / 2, (fontSize*3/2));
> //draws the input area on the frame
> g.setColor(inputColor);
> g.fillRect(0, cellHeight, d.width, cellHeight);
> //draws the lines for rows using 3d effect
> //adds row numbering in blue
> g.setFont(titleFont);
> for (i=0; i < rows+1; i++) {
> cy = (i+2) * cellHeight;
> g.setColor(getBackground());
> g.draw3DRect(0, cy, d.width, 2, true);
> if (i < rows) {
> g.setColor(Color.blue);
> g.drawString("" + (i+1), fontSize, cy + (fontSize *3/2));
> }
> }
> //draws the lines for columns using 3d effect
> //adds column alphas in red
> g.setColor(Color.red);
> for (i=0; i < columns; i++) {
> cx = i * cellWidth;
> g.setColor(getBackground());
> g.draw3DRect(cx + rowLabelWidth,
> 2 * cellHeight, 1, d.height, true);
> if (i < columns) {
> g.setColor(Color.red);
> l[0] = (char)((int)'A' + i);
> g.drawString(new String(l),
> cx + rowLabelWidth + (cellWidth / 2),
> d.height - 3);
> }
> }
> //paints the cell data
> for (i=0; i < rows; i++) {
> for (j=0; j < columns; j++) {
> cx = (j * cellWidth) + 2 + rowLabelWidth;
> cy = ((i+1) * cellHeight) + 2 + titleHeight;
> if (cells[i][j] != null) {
> cells[i][j].paint(g, cx, cy);
> }
> }
> }
> g.setColor(getBackground());
> //draws a 3d line for the input area and to the left side of the frame
> g.draw3DRect(0, titleHeight, d.width, d.height - titleHeight, false);
> //paints the data of the current cell to the input area
> inputArea.paint(g, 1, titleHeight + 1);
> }
> public boolean mouseDown(Event evt, int x, int y) {
> Cell cell;
> //ensures the y coordinate of the mouse pointer
> //is not in the title/input area
> if (y < (titleHeight + cellHeight)) {
> selectedRow = -1;
> if (y <= titleHeight && current != null) {
> current.deselect();
> current = null;
> }
> return true;
> }
> //ensures the x coordinate of the mouse pointer
> //is not in the row label area
> if (x < rowLabelWidth) {
> selectedRow = -1;
> if (current != null) {
> current.deselect();
> current = null;
> }
> return true;
> }
> //determines the row of the selected cell
> selectedRow = ((y - cellHeight - titleHeight) / cellHeight);
> //determines the column of the selected cell
> selectedColumn = (x - rowLabelWidth) / cellWidth;
> //ensures the row and column coordinate is valid
> if (selectedRow >= rows ||
> selectedColumn >= columns) {
> selectedRow = -1;
> if (current != null) {
> current.deselect();
> current = null;
> }
> //if the row-column coordinate is valid place the selected cells value
> //in the input area
> } else {
> cell = cells[selectedRow][selectedColumn];
> inputArea.setText(new String(cell.getPrintString()));
> if (current != null) {
> current.deselect();
> }
> current = cell;
> current.select();
> requestFocus();
> fullUpdate = true;
> repaint();
> }
> return true;
> }
> //determine if a key is pressed
> public boolean keyDown(Event evt, int key) {
> fullUpdate=true;
> inputArea.keyDown(key);
> return true;
> }
> }
> class CellUpdater extends Thread {
> Cell target;
> InputStream dataStream = null;
> StreamTokenizer tokenStream;
> public CellUpdater(Cell c) {
> super("cell updater");
> target = c;
> }
> public void run() {
> try {
> dataStream = new URL(target.app.getDocumentBase(),
> target.getValueString()).openStream();
> tokenStream = new StreamTokenizer(dataStream);
> tokenStream.eolIsSignificant(false);
> while (true) {
> switch (tokenStream.nextToken()) {
> case tokenStream.TT_EOF:
> dataStream.close();
> return;
> default:
> break;
> case tokenStream.TT_NUMBER:
> target.setTransientValue((float)tokenStream.nval);
> if (! target.app.isStopped && ! target.paused) {
> target.app.repaint();
> }
> break;
> }
> try {
> Thread.sleep(2000);
> } catch (InterruptedException e) {
> break;
> }
> }
> } catch (IOException e) {
> return;
> }
> }
> }
> class Cell {
> public static final int VALUE = 0; //initializes the VALUE container
> public static final int LABEL = 1; //initializes the LABEL container
> public static final int URL = 2; //initializes the URL container
> public static final int FORMULA = 3; //initializes the FORMULA container
> Node parseRoot; //sets the node for the cell
> boolean needRedisplay; //used to determine the need for redisplay
> boolean selected = false; //used to determine whether cell is selected
> boolean transientValue = false; //tracks need to set transient value
> public int type = Cell.VALUE; //sets the cell type
> String valueString = ""; //initializes the value string
> String printString = "v"; //initiliazes the print string
> float value; //container for a float called value
> Color bgColor; //container for the background color
> Color fgColor; //container for the foreground color
> Color highlightColor; //container for cell color when selected
> int width; //container for the width of the cell
> int height; //container for the height of the cell
> SpreadSheet app; //container for the SpreadSheet object
> CellUpdater updaterThread; //container for the CellUpdater object
> boolean paused = false; //used to determine if paused
> public Cell(SpreadSheet app,
> Color bgColor,
> Color fgColor,
> Color highlightColor,
> int width,
> int height) {
> this.app = app;
> this.bgColor = bgColor;
> this.fgColor = fgColor;
> this.highlightColor = highlightColor;
> this.width = width;
> this.height = height;
> needRedisplay = true;
> }
> public void setRawValue(float f) {
> valueString = Float.toString(f);
> value = f;
> }
> public void setValue(float f) {
> setRawValue(f);
> printString = "v" + valueString;
> type = Cell.VALUE;
> paused = false;
> app.recalculate();
> needRedisplay = true;
> }
> public void setTransientValue(float f) {
> transientValue = true;
> value = f;
> needRedisplay = true;
> app.recalculate();
> }
> public void setUnparsedValue(String s) {
> switch (s.charAt(0)) {
> case 'v':
> setValue(Cell.VALUE, s.substring(1));
> break;
> case 'f':
> setValue(Cell.FORMULA, s.substring(1));
> break;
> case 'l':
> setValue(Cell.LABEL, s.substring(1));
> break;
> case 'u':
> setValue(Cell.URL, s.substring(1));
> break;
> }
> }
> /**
> * Parse a spreadsheet formula. The syntax is defined as:
> *
> * formula -> value
> * formula -> value op value
> * value -> '(' formula ')'
> * value -> cell
> * value -> <number>
> * op -> '+' | '*' | '/' | '-'
> * cell -> <letter><number>
> */
> public String parseFormula(String formula, Node node) {
> String subformula;
> String restFormula;
> float value;
> int length = formula.length();
> Node left;
> Node right;
> char op;
> if (formula == null) {
> return null;
> }
> subformula = parseValue(formula, node);
> //System.out.println("subformula = " + subformula);
> if (subformula == null || subformula.length() == 0) {
> //System.out.println("Parse succeeded");
> return null;
> }
> if (subformula == formula) {
> //System.out.println("Parse failed");
> return formula;
> }
> // parse an operator and then another value
> switch (op = subformula.charAt(0)) {
> case 0:
> //System.out.println("Parse succeeded");
> return null;
> case ')':
> //System.out.println("Returning subformula=" + subformula);
> return subformula;
> case '+':
> case '*':
> case '-':
> case '/':
> restFormula = subformula.substring(1);
> subformula = parseValue(restFormula, right=new Node());
> //System.out.println("subformula(2) = " + subformula);
> if (subformula != restFormula) {
> //System.out.println("Parse succeeded");
> left = new Node(node);
> node.left = left;
> node.right = right;
> node.op = op;
> node.type = Node.OP;
> //node.print(3);
> return subformula;
> } else {
> //System.out.println("Parse failed");
> return formula;
> }
> default:
> //System.out.println("Parse failed (bad operator): " + subformula);
> return formula;
> }
> }
> public String parseValue(String formula, Node node) {
> char c = formula.charAt(0);
> String subformula;
> String restFormula;
> float value;
> int row;
> int column;
> //System.out.println("parseValue: " + formula);
> restFormula = formula;
> if (c == '(') {
> //System.out.println("parseValue(" + formula + ")");
> restFormula = formula.substring(1);
> subformula = parseFormula(restFormula, node);
> //System.out.println("rest=(" + subformula + ")");
> if (subformula == null ||
> subformula.length() == restFormula.length()) {
> //System.out.println("Failed");
> return formula;
> } else if (! (subformula.charAt(0) == ')')) {
> //System.out.println("Failed (missing parentheses)");
> return formula;
> }
> restFormula = subformula;
> } else if (c >= '0' && c <= '9') {
> int i;
> //System.out.println("formula=" + formula);
> try {
> value = Float.valueOf(formula).floatValue();
> } catch (NumberFormatException e) {
> //System.out.println("Failed (number format error)");
> return formula;
> }
> for (i=0; i < formula.length(); i++) {
> c = formula.charAt(i);
> if ((c < '0' || c > '9') && c != '.') {
> break;
> }
> }
> node.type = Node.VALUE;
> node.value = value;
> //node.print(3);
> restFormula = formula.substring(i);
> //System.out.println("value= " + value + " i=" + i +
> // " rest = " + restFormula);
> return restFormula;
> } else if (c >= 'A' && c <= 'Z') {
> int i;
> column = c - 'A';
> restFormula = formula.substring(1);
> row = Float.valueOf(restFormula).intValue();
> //System.out.println("row = " + row + " column = " + column);
> for (i=0; i < restFormula.length(); i++) {
> c = restFormula.charAt(i);
> if (c < '0' || c > '9') {
> break;
> }
> }
> node.row = row - 1;
> node.column = column;
> node.type = Node.CELL;
> //node.print(3);
> if (i == restFormula.length()) {
> restFormula = null;
> } else {
> restFormula = restFormula.substring(i);
> if (restFormula.charAt(0) == 0) {
> return null;
> }
> }
> }
> return restFormula;
> }
> public void setValue(int type, String s) {
> paused = false;
> if (this.type == Cell.URL) {
> updaterThread.stop();
> updaterThread = null;
> }
> valueString = new String(s);
> this.type = type;
> needRedisplay = true;
> switch (type) {
> case Cell.VALUE:
> setValue(Float.valueOf(s).floatValue());
> break;
> case Cell.LABEL:
> printString = "l" + valueString;
> break;
> case Cell.URL:
> printString = "u" + valueString;
> updaterThread = new CellUpdater(this);
> updaterThread.start();
> break;
> case Cell.FORMULA:
> parseFormula(valueString, parseRoot = new Node());
> printString = "f" + valueString;
> break;
> }
> app.recalculate();
> }
> public String getValueString() {
> return valueString;
> }
> public String getPrintString() {
> return printString;
> }
> public void select() {
> selected = true;
> paused = true;
> }
> public void deselect() {
> selected = false;
> paused = false;
> needRedisplay = true;
> }
> public void paint(Graphics g, int x, int y) {
> if (selected) {
> g.setColor(highlightColor);
> } else {
> g.setColor(bgColor);
> }
> g.fillRect(x, y, width - 1, height);
> if (valueString != null) {
> switch (type) {
> case Cell.VALUE:
> case Cell.LABEL:
> g.setColor(fgColor);
> break;
> case Cell.FORMULA:
> g.setColor(Color.red);
> break;
> case Cell.URL:
> g.setColor(Color.blue);
> break;
> }
> if (transientValue){
> g.drawString("" + value, x, y + (height / 2) + 5);
> } else {
> if (valueString.length() > 14) {
> g.drawString(valueString.substring(0, 14),
> x, y + (height / 2) + 5);
> } else {
> g.drawString(valueString, x, y + (height / 2) + 5);
> }
> }
> }
> needRedisplay = false;
> }
> }
> class Node {
> //set for node components that are operands
> public static final int OP = 0;
> //set for node components that are values
> public static final int VALUE = 1;
> //set for node components that are cell references
> public static final int CELL = 2;
> int type;
> Node left; //the lefthand value for the node
> Node right; //the righthand value for the node
> int row; //the row coordinate of the selected cell
> int column; //the column coordinate of the selected cell
> float value; //the current value
> char op; //the operand +, -, /, *
> public Node() {
> left = null;
> right = null;
> value = 0;
> row = -1;
> column = -1;
> op = 0;
> type = Node.VALUE;
> }
> public Node(Node n) {
> left = n.left;
> right = n.right;
> value = n.value;
> row = n.row;
> column = n.column;
> op = n.op;
> type = n.type;
> }
> public void indent(int ind) {
> for (int i = 0; i < ind; i++) {
> System.out.print(" ");
> }
> }
> public void print(int indentLevel) {
> char l[] = new char[1];
> indent(indentLevel);
> System.out.println("NODE type=" + type);
> indent(indentLevel);
> switch (type) {
> case Node.VALUE:
> System.out.println(" value=" + value);
> break;
> case Node.CELL:
> l[0] = (char)((int)'A' + column);
> System.out.println(" cell=" + new String(l) + (row+1));
> break;
> case Node.OP:
> System.out.println(" op=" + op);
> left.print(indentLevel + 3);
> right.print(indentLevel + 3);
> break;
> }
> }
> }
> class InputField {
> int maxchars = 50; //the maximum characters for the input field
> int cursorPos = 0; //index to the current cursor position
> Applet app; //instance of the applet object
> String sval; //container for string values
> char buffer[]; //an array of characters up to maxchars in length
> int nChars; //the number of characters in the input area
> int width; //width of the input area
> int height; //height of the input area
> Color bgColor; //background color of the input area
> Color fgColor; //font color for text in the input area
> public InputField(String initValue, Applet app, int width, int height,
> Color bgColor, Color fgColor) {
> this.width = width;
> this.height = height;
> this.bgColor = bgColor;
> this.fgColor = fgColor;
> this.app = app;
> buffer = new char[maxchars];
> nChars = 0;
> if (initValue != null) {
> initValue.getChars(0, initValue.length(), this.buffer, 0);
> nChars = initValue.length();
> }
> sval = initValue;
> }
> public void setText(String val) {
> int i;
> for (i=0; i < maxchars; i++) {
> buffer[i] = 0;
> }
> sval = new String(val);
> if (val == null) {
> sval = "";
> nChars = 0;
> buffer[0] = 0;
> } else {
> sval.getChars(0, sval.length(), buffer, 0);
> nChars = val.length();
> sval = new String(buffer);
> }
> }
> public void paint(Graphics g, int x, int y) {
> g.setColor(bgColor);
> g.fillRect(x, y, width, height);
> if (sval != null) {
> g.setColor(fgColor);
> g.drawString(sval, x, y + (height / 2) + 3);
> }
> }
> public void keyDown(int key) {
> if (nChars < maxchars) {
> switch (key) {
> case 8: // delete
> --nChars;
> if (nChars < 0) {
> nChars = 0;
> }
> buffer[nChars] = 0;
> sval = new String(new String(buffer));
> break;
> case 10: // return
> selected();
> break;
> default:
> buffer[nChars++] = (char)key;
> sval = new String(new String(buffer));
> break;
> }
> }
> app.repaint();
> }
> public void selected() {
> }
> }
> class SpreadSheetInput extends InputField {
> public SpreadSheetInput(String initValue,
> SpreadSheet app,
> int width,
> int height,
> Color bgColor,
> Color fgColor) {
> super(initValue, app, width, height, bgColor, fgColor);
> }
> public void selected() {
> float f;
> switch (sval.charAt(0)) {
> case 'v':
> try {
> f = Float.valueOf(sval.substring(1)).floatValue();
> ((SpreadSheet)app).setCurrentValue(f);
> } catch (NumberFormatException e) {
> System.out.println("Not a float...");
> }
> break;
> case 'l':
> ((SpreadSheet)app).setCurrentValue(Cell.LABEL, sval.substring(1));
> break;
> case 'u':
> ((SpreadSheet)app).setCurrentValue(Cell.URL, sval.substring(1));
> break;
> case 'f':
> ((SpreadSheet)app).setCurrentValue(Cell.FORMULA, sval.substring(1));
> break;
> }
> }
> }`

## Putting the `SpreadSheet` Applet to Use

To use the `SpreadSheet` applet, you need to create an HTML document that calls the applet. The document can be as simple or as complex as you want it to be. The code for a simple HTML document that causes the `SpreadSheet` applet to use default values is shown in Listing 20.23. This document is shown in Figure 20.3.

Figure 20.3 : A document using the SpreadSheet applet's defaultsettings.

> Listing 20.23. A simple HTML document using the `SpreadSheet` applet.

> `<HTML>
> <HEAD>
> <TITLE>Default SpreadSheet</TITLE>
> </HEAD>
> <BODY>
> <APPLET CODE="SpreadSheet.class" WIDTH=400 HEIGHT=250>
> </APPLET>
> </BODY>
> </HTML>`

Although your HTML document does not have to set parameter values for the spreadsheet, the best way to see how the applet behaves is to experiment with parameter values. Listing 20.24 shows how an advanced spreadsheet was built using the `SpreadSheet` applet. This document is shown earlier in this chapter as Figure 20.2.

> Listing 20.24. A more advanced example using the `SpreadSheet` applet.

> `<HTML>
> <HEAD>
> <TITLE>SpreadSheet</TITLE>
> </HEAD>
> <BODY>
> <HR>
> <APPLET CODE="SpreadSheet.class" WIDTH=750 HEIGHT=400>
> <PARAM NAME=title value="Java Powered Spreadsheet">
> <PARAM NAME=columns value="5">
> <PARAM NAME=rows value="7">
> <PARAM NAME=fontsize value="14">
> <PARAM NAME=basefont value="TimesRoman">
> <PARAM NAME=titlefont value="Helvetica">
> <PARAM NAME=a1 value="lWholesale">
> <PARAM NAME=a2 value="v20195">
> <PARAM NAME=a3 value="v7280">
> <PARAM NAME=a4 value="v6127">
> <PARAM NAME=a5 value="v9803">
> <PARAM NAME=a6 value="v4150">
> <PARAM NAME=a7 value="fA2+(A3+(A4+(A5+A6)))">
> <PARAM NAME=b1 value="lShipping">
> <PARAM NAME=b2 value="v1500">
> <PARAM NAME=b3 value="v350">
> <PARAM NAME=b4 value="v729">
> <PARAM NAME=b5 value="v830">
> <PARAM NAME=b6 value="v125">
> <PARAM NAME=b7 value="fB2+(B3+(B4+(B5+B6)))">
> <PARAM NAME=c1 value="lPackaging">
> <PARAM NAME=c2 value="v120">
> <PARAM NAME=c3 value="v35">
> <PARAM NAME=c4 value="v48">
> <PARAM NAME=c5 value="v52">
> <PARAM NAME=c6 value="v12">
> <PARAM NAME=c7 value="fC2+(C3+(C4+(C5+C6)))">
> <PARAM NAME=d1 value="lTotal Cost">
> <PARAM NAME=d2 value="fA2+(B2+C2)">
> <PARAM NAME=d3 value="fA3+(B3+C3)">
> <PARAM NAME=d4 value="fA4+(B4+C4)">
> <PARAM NAME=d5 value="fA5+(B5+C5)">
> <PARAM NAME=d6 value="fA6+(B6+C6)">
> <PARAM NAME=d7 value="fD2+(D3+(D4+(D5+D6)))">
> <PARAM NAME=e1 value="lCost/Expense">
> <PARAM NAME=e2 value="fA2/(B2+C2)">
> <PARAM NAME=e3 value="fA3/(B3+C3)">
> <PARAM NAME=e4 value="fA4/(B4+C4)">
> <PARAM NAME=e5 value="fA5/(B5+C5)">
> <PARAM NAME=e6 value="fA6/(B6+C6)">
> </APPLET>
> <HR>
> <A HREF="SpreadSheet.java">The source.</A>
> </BODY>
> </HTML>`

## Summary

After completing this chapter, you should have a good understanding of how to develop advanced applets using the Java programming language. Although you may be able to develop and implement some applets in a single day, more advanced projects are developed and implemented over a period of days, weeks, or months. Most advanced projects include six phases: requirements, specification/analysis, planning, design, implementation, and testing.

The `SpreadSheet` applet developed in this chapter has many characteristics of advanced applets. Now that you have examined it, you should have a better understanding of using text and graphics, manipulating text strings, using mathematical formulas, and processing user input.