#

## Chapter 10

## Creating Application Channels

Although it is entirely possible to create a Marimba Castanet channel with an HTML Web site, a precompiled applet, or an already-built Bongo presentation, true channels are written and compiled in Java and implement several interfaces and events that allow them to coexist with the transmitter and the tuner. There's no other way to allow a channel to accept dynamic updates, to save local files to the user's channel directory, or to send feedback data to the transmitter. To take full advantage of the features Castanet gives you in channel development, you have to use an application channel.

In this chapter, you'll take the first step toward creating real Castanet channels. The channels you create in this chapter will have all the earmarks of a channel, including using the important interfaces and classes in the `marimba` package and implementing the correct methods and events in your Java code. In the following chapters, you'll build on what you learn in this chapter to add updating and other features.

### Working with Java

For this chapter, and for the remainder of this book, you'll be working almost exclusively with the Java language to create full-featured Castanet channels. Beyond this point, things are going to get fairly technical. So before you get in too deep, make sure that you already have the following tools and skills:

- You should have access to a Java development tool such as Sun's JDK or a tool such as Symantec's Cafe or Microsoft's Visual J++, and know how to use it.

> ####

> Note

> If you use a Java development environment other than Sun's JDK, Marimba recommends you recompile your files with the JDK before publishing them. There have been instances of channels that have thrown Class Not Found exceptions when compiled with third-party development environments that vanished once the code was recompiled.

- You should understand the Java language, including class and interface inheritance and how to construct classes and implement interfaces.
- You should have a passing understanding of Sun's AWT, including event management.

If you're missing any of this background, you definitely should become familiar with Java before going on.

> ####

> Note

> A great place to start learning Java is the book Teach Yourself Java in 21 Days, which I also wrote, and is published by Sams.net Publishing. The text of this book in HTML form is contained on the CD that came with this book.

> ####

> Note

> The 1.0 version of Castanet works only with Sun's 1.02 JDK, not with the newer JDK 1.1. Although future versions of Castanet will support the JDK 1.1 features, for the purposes of this book, you should make sure that you're running a 1.02-compatible compiler and bytecode interpreter.

### The `marimba.zip` and `bongo.zip` Libraries

The classes and interfaces you'll be working with in the next couple of chapters are part of the Marimba class libraries. They were installed when you installed the Castanet Transmitter and Bongo. After you start writing applications in Java that use any of the features of Castanet channels, you must make sure that your Java development environment knows about these libraries, usually by editing your `CLASSPATH` variable or setting the preferences to include these libraries.

The two Marimba class libraries are stored in the files marimba.zip and `bongo.zip`. Whether you need one or both depends on the sorts of channels you plan to create:

- If you plan to use any of the presentation capabilities of Bongo, you need both `marimba.zip` and `bongo.zip` in your `CLASSPATH`. They're both in the `lib` directory of your Bongo installation.
- If you plan to create channels without presentations (that is, adapt a standard AWT application or applet into a channel), the only library you need is marimba.zip. You can find it in the lib directory of your Castanet Transmitter installation.

Add the directory paths to each of these files to your `CLASSPATH`, or configure your development environment to find these files.

> ####

> WARNING

> Be careful which `marimba.zip` you use; the `marimba.zip` file in the transmitter installation is different from the `marimba.zip` file in the bongo installation. (It's a superset of the transmitter's version that includes the classes that let you use presentations in channels.) If you start out not using bongo for your channel and then switch, you'll also need to switch `marimba.zip` files. Or, avoid the problem altogther, install Bongo, and use the Bongo version all the time.

The `marimba.zip` and `bongo.zip` files provide several packages for general and utility classes you can use while developing your own channels. A full description of these packages is contained in Chapter 14, "An Overview of the Marimba Classes." You'll use the following packages most often:

- `marimba.channel`: Classes and interfaces for creating and using channels, including the special wrapper classes for playing applets as channels. This package is contained in marimba.zip (although the bongo-specific classes are missing from the transmitter's version of that library)
- `marimba.gui`: Classes particular to Bongo presentations and to using those presentations inside channels. This package includes all the Bongo widget classes. These classes are contained in the `bongo.zip` libraries.
- `marimba.plugin`: Classes for creating transmitter plug-ins. You'll learn about these in Chapter 12, "Creating Transmitter Plug-ins." These classes are in `marimba.zip`.
- `marimba.io`: Extensions to the standard Java input and output classes to handle channel-specific I/O; they are contained in `marimba.zip`.

> ####

> Note

> In addition to the class libraries, the Bongo and transmitter distributions come with source for many of these classes. Exploring the source can often help you figure out what's going on in a class or help you figure out what you want to do. Feel free to examine those source files as you create your own channels.

> In addition, the API documentation for these classes is available on Marimba's Web site (`http://www.marimba.com/doc/`) or as channels from `trans.marimba.com`. (They're called Bongo doc and Transmitter doc, respectively.)

### Anatomy of an Application Channel

Creating application channels in Java is quite similar to creating applets with Java's AWT. If you've any experience with the former, then channels aren't going to seem all that different. Many of the basic methods are the same, and the event and security models are nearly the same as with applets as well. Some basic differences do exist, however.

The heart of all application channels is the Application interface in the marimba.channel package. The Application interface provides four important methods for channels:

- `start()`
- `stop()`
- `setContext()`
- `handleEvent()`

I'll cover the first three methods in this chapter in some detail and touch on `handleEvent()` as it's used to interact with presentation widgets. In Chapter 11, "Managing Updates and User Information," you'll learn in detail about update events you can process inside `handleEvent()`.

To create a channel, your main channel class can inherit from any class (usually from a standard Java class like `Frame`) but must implement the Application interface. A basic class template for a channel, then, might look something like the one shown in Listing 10.1. (You'll learn more about the various parts of this template as the section progresses.)

Listing 10.1. A channel template.

    import marimba.channel.*;

    public class MyChannel extends Frame implements Application {

    ApplicationContext context;

    public void start() {

       // ... initialize the channel

    }

    public void stop();

       // ... clean up after the channel

    }

    setContext(ApplicationContext context) {

       this.context = context;

    }

    public boolean handleEvent(Event evt) {

       return super.handleEvent(evt);

    }

    }

The `start()` and `stop()` Methods

The two methods that drive a channel's life cycle are the `start()` and `stop()` methods.

The `start()` method is called when the channel first starts running. Unlike the `start()` method inside an applet, the channel's `start()` method is called only once, to initialize the channel. It's therefore more analogous to an applet's `init()` method or an application's `main()`. Here's the basic template for the `start()` method:

    public void start() {

       // ... initialize the channel

    }

The `stop()` method, analogously, is called once when the channel is about to be destroyed. Inside `stop()`, you add any code to clean up after your channel, including closing windows, killing threads, and releasing any other objects for garbage collection. You might also want to save any state for the channel or reset global values.

    public void stop();

       // ... clean up after the channel

    }

The `setContext()` Method and the Application Context

If the Application interface is the heart of the channel, the application context is its brain. The application context is, most simply, an object that implements the ApplicationContext interface, also part of the `marimba.channel` package. What the application context actually provides, however, is a set of methods for interacting with the tuner, finding out information about the channel and its properties (for example, its parameters or how often it's been configured to do updates), and to write log and property files for use by transmitter plug-ins. Chances are good that, at some point in the lifetime of your channel, you'll want to get access to this information.

When your channel is first executed by the Castanet tuner, the `setContext()` method is called just before the `start()` method is called. The `setContext()` method is called with an existing `ApplicationContext` object that contains the channel's application context. In the body of your `setContext()` method, you should store that context somewhere so that you can use it later. A typical template for `setContext()` inside your channel might look something like this:

    setContext(ApplicationContext context) {

       this.context = context;

    }

With the application context stored in the context instance variable, you can then call its methods by referring to that variable.

The `handleEvent()` Method

Worth mentioning at this point is the `handleEvent()` method, which is the fourth method the Application interface provides. `handleEvent()`, like the standard AWT `handleEvent()` in java.awt.Component, is used to manage user events that occur inside the application. Inside `handleEvent()`, you should trap any typical events (mouse clicks, key presses), but also manage update events (which you'll learn about in the next chapter).

With normal Java applications, you would test for the `WINDOW_DESTROY` event and then call `System.exit()`. For channels, you should call the `stop()` method on the application context, which manages the existing process (including calling the `stop()` method for the channel). Here, then, is a basic template for `handleEvent()`:

    public boolean handleEvent(Event evt) {

       switch (evt.id) {

          // ... trap different events

          case WINDOW_DESTROY:

             context.stop();

             return true;

       }

       return super.handleEvent(evt);

    }

Useful Classes for Creating Channels

Implementing a basic framework for an application channel–or converting a Java application to a channel–doesn't require a whole lot of work. The simple steps you learned in the preceding section are really all you need to follow to get your Java application to work in the channel mechanism. But before you actually start mucking with code, you should know about several classes in the `marimba.channel` package that make this job even easier.

The `ApplicationFrame` and `ApplicationPlayerFrame` classes contain a basic implementation of everything you need to run a basic channel, including basic implementations of `start()`, `stop()`, `setContext()`, and `handleEvent()`.

`ApplicationFrame` is a subclass of the AWT class `Frame`, which implements simple default behavior for all the basic methods, and contains a context variable to hold the context. In fact, the framework in Listing 10.1 is similar to the one provided by the `ApplicationFrame`. If your Java application normally subclasses from `Frame` (to put up a window and use AWT components), subclassing from `ApplicationFrame` instead gives you all the additional channel behavior you need.

`ApplicationPlayerFrame` is a special class for incorporating a Bongo presentation into a Java application that is also a channel. This latter case is one of the easier ways to construct channels by tying together Bongo for the presentation, the Application interface and Application context for the channel behavior, and your own Java code for the meat of the channel. `ApplicationPlayerFrame` inherits from the Bongo class `PlayerFrame`–a special class that the tuner uses to play presentations as channels. `PlayerFrame`, in turn, inherits from `jav.awt.Frame`. So with `ApplicationPlayerFrame`, you have access to the full AWT hierarchy as well as Bongo widgets.

If you subclass either of these classes for your own channels, don't forget to call your superclass's `start()`, `stop()`, and `setContext()` methods, if you override them.

You can find more information about `ApplicationPlayerFrame` in the section "Creating Channels That Use Bongo Presentations."

Using the Application Context

In the `setContext()` method for your application channel, the current application context is usually assigned to an instance variable called `context`. With that context object in hand, you can call various methods that let you get information about the channel and perform tuner operations such as requesting a channel update or a restart. You'll learn about many of the context methods as we proceed through this chapter and the next, but Table 10.1 contains a quick summary of many of the more important methods so you know the sorts of things you can do from inside your own channel:

Table 10.1. ApplicationContext methods.

Method name | What it does
`channelFileExists(String)` | Returns `true` if the given relative path exists in the channel.
`getChannelName()` | Returns the full name of the channel.
`getChannelStatus(String)` | Returns the current channel status ( one of `unsubscribed`, `subscribed`, or `running`).
`getParameter(String)` | Returns the value of an application parameter, as stored in the `properties.txt` or`parameters.txt` file for the channel (and set in the Castanet Publish).
`getServerName()` | Returns the name of the server (transmitter) for this channel. The transmitter name is of the form `hostname:port`.
`listChannelDirectory(String)` | Returns an array of strings representing the names of files in the given directory. To list the entire channel, use `.` as the argument. Returns null if the path does not exist.
`listChannels()` | Returns an array of strings representing all the subscribed channels.
`removeChannel(String)` | Remove this channel.
`restart()` | Restart the channel.
`startChannel(String, String)` | Start some other channel. The first argument is the same of the transmitter; the second is the name of the channel.
`stop()` | Stop the channel.
`subscribeChannel(String, String)` | Subscribe to a channel (don't start it). The arguments are the name of a transmitter and the name of the channel.
`unsubscribeChannel(String)` | Unsubscribe from the named channel.
`update()` | Update the channel.

Notes on the Channel Security Model

The security model for application channels is similar to that of applets. Application channels have their own security manager, which restricts the kinds of operations you can perform inside your channel. If you're used to working with applets, the security model is actually more relaxed; you now can save state to the user's local disk in specific directories. If you've been working primarily with stand-alone applications, however, be aware of the channel security restrictions:

- Channels cannot make network connections to any systems other than the original transmitter from which they came.
- Channels cannot read or write files except to a specific directory for that purpose.
- Channels cannot automatically execute local programs or native libraries on the local system. (Installing native code into the tuner is possible, but that requires a conscious effort by the user of that tuner. You'll find more details on this subject in Appendix C, "Tuner Extensions.")
- Channels cannot create threads in ThreadGroups other than the one in which they were created.

### Creating Channels That Use Bongo Presentations

Probably the easiest way to construct a channel from scratch is to create its interface in Bongo and then build the channel around that interface. Castanet provides both a class to do this–`ApplicationPlayerFrame`–and the infrastructure in the tuner to make linking together an interface and a Java channel program extremely easy–almost magically so, particularly if you're used to doing a lot of hand-coding in the AWT.

Even if you're used to working in the AWT, using a combination of Bongo and Java has several significant advantages over using AWT alone:

- Bongo's visual tools make creating user interfaces easy. Bongo is also extensible, so you can create and integrate your own widgets into the Bongo model.
- Bongo UI files are stored separately from the rest of the Java code–and can be modified independently of that code without your needing to recompile anything. For larger projects, this capability allows UI designers to work independently of programmers, and the UI designers don't need to know extensive details about Java itself.
- Bongo uses its own graphics and updating model, which means that you no longer have to keep track of painting and repainting. Bongo's model also uses double-buffering, which makes for faster and more efficient screen updates.
- You can write all the event management code for your Bongo widgets in Java, or you can combine Bongo's built-in scripting with external event management. The two interact seamlessly, allowing you to create basic operations entirely in Bongo and shift the more complicated operations to Java itself.

The disadvantage, of course, is that by using Bongo you must also include all the Bongo classes with your channel when you publish it, as you do with Bongo presentations. As with the presentations, however, the zip files are only downloaded once and shared between channels that use them.

Combining Java and Presentations

The following are the four steps to creating a channel that uses a Bongo presentation:

- Create the interface using Bongo, and make sure that you name all the widgets you plan to modify or change. You can use scripting inside Bongo for some basic operations; scripting does not interfere with your Java code.
- Create a main Java application class that is a subclass of `ApplicationPlayerFrame`.
- Inside the Java class, use special methods to interact with the UI widgets in the presentation.
- Publish the channel with references in the channel's properties to both the main Java application class and the presentation file.

These steps are easier than they sound. Much of the linkage between the Java code and the presentation is automatic and managed by the tuner infrastructure and the application context. Let me go over the steps in more detail, and then I'll walk you through a simple application so that you can see the results.

Using the `ApplicationPlayerFrame`

As you learned earlier in this chapter, the `ApplicationPlayerFrame` class provides the basic channel structure you need to make your Java code interoperate with Castanet as a channel, including implementing the Application interface and storing the default application context in an instance variable called `context`.

In addition, the `ApplicationPlayerFrame` class also gives you access to the methods and utilities in a class called `PlayerUtil`. `PlayerUtil` is a Bongo-related class (from the `marimba.gui` package) with which you can gain access to various widgets inside the presentation. The `ApplicationPlayerFrame` class keeps track of the `PlayerUtil` object for the current presentation in the instance variable called `util`.

To use `ApplicationPlayerFrame`, simply create a Java class that extends it. Note that you need to import marimba.channel (for the application channel-related classes), marimba.gui (for the Bongo-related classes), and typically java.awt as well. Listing 10.2 shows a simple template.

Listing 10.2. A template for an `ApplicationPlayerFrame` class.

`import java.awt.*;
import marimba.channel.*;
import marimba.gui.*;`

`
public class MyChannelPresentation extends ApplicationPlayerFrame {
...
}`

Note that you don't have to implement the Application interface; the `ApplicationPlayerFrame` class does this job for you. In fact, `ApplicationPlayerFrame` also includes basic versions of each of the four channel methods. You need to override them only if you need to do special things to initialize the application–for example, to read in saved state, to set up a network connection to the transmitter, or to create other windows that the application might use. In many simple cases, the only method you ever need to override is `handleEvent()` to process input from the users of your channel.

Linking Java and the Presentation Together

Note that nowhere in the preceding section did I mention opening the Bongo presentation file or otherwise creating an instance of a class to hold it, or using any methods to display the presentation onscreen. This was not an oversight. If you use `ApplicationPlayerFrame`, you don't have to do any of that work by hand in your channel class. Castanet and the `ApplicationPlayerFrame` class manage that process through the channel properties when you publish the channel. Figure 10.1 shows the general properties for an application channel that uses a presentation.

Figure 10.1. Application channel properties.

> ####

> Note

> The `bongo.zip` library is in the class path field for the reasons you learned in Chapter 9. You must include the bongo libraries with every channel that uses Bongo features. See the section titled "Putting It All Together: Publishing and Testing" for more.

If you simply include the name of the presentation in these properties, the `ApplicationPlayerFrame` class and the tuner open and display the presentation for you. Bongo also manages the display and updating of the screen. The only thing you have to concentrate on in your Java code is processing input from your users and interacting with the widgets in the presentation to produce a result. (I told you this process was easy.)

To interact with the widgets in the presentation, you intercept events from the presentation in your `handleEvent()` method. Bongo gets first crack at any events, so if built-in scripts are included in the presentation, these scripts can handle a lot of the simple events. But no scripts are included in the presentation, or if a particular event isn't covered by a script, you can intercept and handle that event in your Java code.

In addition to handling simple events, however, you also need a way to get values out of and put values back into the widgets in the presentation. You do so using that `PlayerUtil` object I mentioned earlier; it's stored in the `util` instance variable.

The `PlayerUtil` object provides a set of useful methods that you can use to get and set values and properties of various widgets. For example, if you have a text box widget named `Password` in your presentation, you can use the following line of Java code to extract the text from that text box widget:

`String passwd = util.getText("Password");`

To get ahold of a widget object to call more specific widget methods, you can use the `getWidget()` method and cast that widget object to the appropriate class:

`SliderWidget slider = (SliderWidget)util.getWidget("volume");
slider.setValue(0); // mute the volume`

Keep in mind that widget names are case sensitive. Make sure that the name you use in your Java code matches that in your presentation; otherwise, the connection between the two does not work.

In your Java code, you might also find the `getName()` method useful; it's defined on all widgets. In events in particular, you may have a reference to a widget (the target of an event) where you don't know the actual name of the widget. Using the `getName()` method and the `equals()` test, you can test to see whether the widget you have is indeed the one you want:

    if ( ((Widget)evt.target).getName.equals("volume")) {

       // ... the event is coming from the widget named "volume."

    }

Table 10.2 shows a few of the `PlayerUtil` methods that you may find useful in your Java code. You can call them via the `util` instance variable (for example, `util.getValue()`). Keep in mind that by getting a reference to the widget itself via `getWidget()`, you can call any method that any widget supports.

> ####

> Note

> Which methods do the widgets support? I don't have the space to go into all of them. The online API documentation for Bongo and The Official Marimba Guide to Bongo (Sams.net Publishing) have lots of information about these methods, as well as the complete set of methods defined in `PlayerUtil`.

Table 10.2. `PlayerUtil` methods.

Method name | What it does
`getText(String)` | Gets the text from the text box widget named in the argument. Returns a String object.
`setText(String, String)` | Sets the text in the text box widget (named in the first argument) to the text in the second argument. Returns void.
`clearText(String)` | Clears the text box named in the argument.
`getValue(String)` | Gets the value of the widget named in the argument. Returns an Object. (Different widgets have different values; you usually need to covert the return value to something useful.)
`setValue(String, Object)` | Sets the value of the widget named in the first argument to the Object in the second argument (usually a String or a Number object). Returns void.
`getBoolean(String)` | Gets the value of the Checkbox or Option widget named in the argument. Returns true or false.
`setBoolean(String, boolean)` | Sets the value of the Checkbox or Option widget named in the argument to the value of the second argument (true or false). Returns void.
`getChoice(String)` | Gets the value of the Choice widget named in the argument. Returns a string.
`setChoice(String,String)` | Sets the value of the Choice widget named in the first argument to the string in the second argument. Returns void.
`show(String)` | Shows the widget named in the argument.
`show(String, boolean)` | Shows or hides the argument named in the first argument. If the second argument is true, shows the widget; if false, hides the widget.
`getWidget(String)` | Gets the widget object named by the argument. Returns a Widget object (which you most likely need to cast to a more specific widget class).

An Example: The Thermometer Channel

Let me show you a simple example to demonstrate how easy it is to create a channel that uses both a Bongo presentation and the `ApplicationPlayerFrame` class. This example is fairly simple. It doesn't do anything you couldn't do in Bongo alone, but it serves to demonstrate just how easy it is to put together a channel with `ApplicationPlayerFrame`.

The example in this section is a simple temperature converter to convert between Fahrenheit and Celsius temperatures and vice versa. The interface contains two spin box widgets for typing in the temperature directly (or incrementing it by one) and a slider for choosing the temperature via more direct means.

The Presentation

Figure 10.2 shows the presentation I created in Bongo for this example. Three widgets here are of note:

- The slider on the left has a range of 32 to 212 (it's a Fahrenheit slider); it's named `slider`.
- The spin box widget in the top right has a minimum of 32, a maximum of 212, and is named `fahr`.
- The spin box widget in the lower right has a minimum of 0, a maximum of 100, and is named `cels`.

Figure 10.2. Thermometer presentation.

I saved this presentation as `Thermometer.gui` and stored it in my channel directory (which I called Thermometer as well).

The Java Code

Also inside my channel directory, I created a Java file called `ThermometerApplication.java`. The code for that application is shown in Listing 10.3.

> ####

> Note

> The code for this example, and for most of the examples in this book, is available on the CD-ROM that comes with the book. You don't have to retype it all in from here.

Listing 10.3. The ThermometerApplication code.

     1: import java.awt.*;

     2: import marimba.channel.*;

     3: import marimba.gui.*;

     4:

     5: public class ThermometerApplication extends ApplicationPlayerFrame {

     6:

     7:  public boolean handleEvent(Event evt) {

     8:

     9:    if ((evt.id == Event.ACTION_EVENT) && (evt.target instanceof Widget)) {

    10:     Widget targ = (Widget)evt.target;

    11:

    12:     // redirect target to spinbox if user has changed text

    13:     if (targ instanceof SpinBoxTextWidget)

    14:       targ = ((SpinBoxTextWidget)targ).getOwner();

    15:

    16:     if (targ instanceof ValueWidget) {

    17:        int temp = ((ValueWidget)targ).value;

    18:       int newtemp = 0;

    19:

    20:       // user has changed value of fahrenheit spinbox

    21:        if (targ.getName().equals("fahr")) {

    22:          newtemp = (temp - 32) * 5 / 9;

    23:          SpinBoxWidget dest = (SpinBoxWidget)util.getWidget("cels");

    24:          dest.setValue(newtemp);

    25:          util.setValue("slider",String.valueOf(temp));

    26:          return true;

    27:

    28:       // user has changed value of celsius spinbox

    29:        } else if (targ.getName().equals("cels")) {

    30:          newtemp = temp * 9 / 5 + 32;

    31:          util.setValue("fahr", String.valueOf(newtemp));

    32:          util.setValue("slider",String.valueOf(newtemp));

    33:          return true;

    34:

    35:       // user has changed  slider

    36:        } else if (targ.getName().equals("slider")) {

    37:          newtemp = (temp - 32) * 5 / 9;

    38:          util.setValue("fahr",String.valueOf(temp));

    39:          util.setValue("cels",String.valueOf(newtemp));

    40:          return true;

    41:      }

    42:    }

    43:  }

    44:  return super.handleEvent(evt);

    45:}

    46:

    47:}

Note the following about this example:

- Inside the class, I didn't need to override `start()`, `stop()`, or `setContext()`. Because I don't need to change their behavior, I can rely on the default definitions from the `ApplicationPlayerFrame` class. The only method I've overridden in this class is `handleEvent()`.
- The`handleEvent()` method tests for events of type `ACTION_EVENT` (line 9), and then only if those actions are triggered by widgets. These actions are triggered by the various widgets; most of the time that's what you're interested in when you're interacting with a Bongo presentation. This `handleEvent()` method could be easily extended to catch other events or even other action events that are not generated by widgets.
- The `handleEvent()` method manages three specific events: when the users change the value of the Fahrenheit spin box, when they change the value of the Celsius spin box, or when they adjust the slider. You can test for each different widget by name (lines 21, 29, and 36) and then act accordingly.
- In the first set of tests (lines 20 through 26), I showed two different ways of accessing the values in the widgets. The first way uses the `getWidget()` method to get the actual spin box widget called `cels` and then sets the value using the `setValue()` method for that spin box (lines 23 and 24). The second method, in line 25 and throughout the rest of the method, is somewhat easier. Here all you have to do is use `util.setValue ()` with the name of the widget and the value (converted to a String object).
- As with all `handleEvent()` methods, if you actually process the event, you should return true from the method. If you don't, you should pass the method up to the parent to make sure that the event has a chance to percolate through the entire hierarchy. Note the line in 44 where I pass the event on.
- The lines in 12 through 14 are there to work around a widget bug. The spin box widget is made up of both a slider-like widget and a text box, and the text box events do not percolate unless the event target is reset to the spin box that contains that text box. This will be fixed in a later version of Bongo (and might be fixed by the time you read this).

With the Java code written, all you have to do is compile it. Make sure that you have the `bongo.zip` and `marimba.zip` libraries from the Bongo installation available to your Java compiler or to your development environment.

Putting It All Together: Publishing and Testing

The final step is simply to publish the channel. The channel properties determine the link between the Java code and the presentation, so you cannot simply run the Java class and have it work. You have to run it from inside the Castanet framework.

Fire up Castanet Publish and add the development directory (called Thermometer) to the list of Channels Under Development. Then edit the properties for that channel. Next, switch to the General properties. Figure 10.3 shows the properties for the temperature converter channel.

Figure 10.3. Properties for the Temperature Converter channel.

The type of channel is Application; this is true whether or not your channel uses a presentation. As long as it implements the application interface, it's an application channel. The main class for the application is the one just created and the one that has the `start()` and `stop()` methods in it; here it's called `ThermometerApplication` (don't include the `.class` extension in the properties). And, finally, the presentation file that goes along with this channel is the Thermometer.gui file.

One other entry is important to note here: the file `bongo.zip`, which is included in the classpath field of the properties. This library should also be copied to your channel directory and published along with the channel. Whereas your users always have access to the channel classes through the tuner (the classes in `marimba.zip`), they might not have access to the bongo classes. If your channel uses any of the classes for Bongo presentations, including `ApplicationPlayerFrame`, you must publish the `bongo.zip` file along with your channel and include it here in the properties. This way, you can guarantee that your channel will work on your users' machines.

> ####

> Note

> Although you need to compile your files against the `marimba.zip` library, you don't need to include it with your channel. `marimba.zip` comes with every tuner; `bongo.zip` does not.

After you publish the channel, you can subscribe to it via a tuner and launch it just as you would any other channel. The tuner controls start up your main class, run the `start()` and `setContext()` methods, and open and display the presentation. If you've set up the properties correctly, you should now be able to interact with your channel.

If your channel runs into difficulties, you have to go back to your original code, make changes, republish the channel, and then update and relaunch it from the tuner to make sure that your changes work. See "Debugging Hints" for ideas on how to help with the debugging process for presentation-based or any kind of channel.

Using Multiple Presentations in a Channel

Setting up an application to use one presentation works exceptionally well when you have only one presentation. But what if your application uses multiple presentations, with different presentations appearing at a time?

The `PlayerUtil` class, which is available in the `util` instance variable, can help with this situation. The `setPresentation()` method allows you to open and set the current presentation for your application. However, inside a channel, you have to indicate the presentation file you want to open as a URL from the current channel directory. Here's a snippet of code to do just that:

    try {

       util.setPresentation(new URL(context.getBase(), "mynewspresentation.gui"));

    catch (MalformedURLException e) {

       e.printStacTrace();

    }

Each time you work with URLs, you have to enclose your statements inside a `try` and `catch` block to catch any URL exceptions that might occur. The line here that contains the call to `setPresentation()` gets the current URL from the application context (using the `getBase()` method) and then appends the name of the presentation file to the end of that. (This example assumes that your GUI file is in the top level of the channel directory.)

If you want to pop up a new window with its own presentation and its own event management inside your application, then you have to worry about subclassing the class `PlayerFrame`. You use `PlayerFrame` to present Bongo presentations inside a channel window. It's the same class that allows the tuner to run a Bongo presentation as a channel without any other code. When you subclass the `PlayerFrame` class, be sure to create a constructor that takes an application context as an argument. And when you create the new `PlayerFrame` instance, pass in the current application context to that window so that you have access to it from that class. Also in that constructor, use the `setPresentation()` method to open the presentation you want to use for that window. Here's a simple example that opens a window for the presentation `present.gui`:

    import java.awt.*;

    import java.net.*;

    import marimba.gui.*;

    import marimba.channel.*;

    public class NewFrame  extends PlayerFrame {

    MainFrame parent;

    public NewFrame (MainFrame parent, ApplicationContext context) {

        super();

        this.parent = parent;

        try {

          util.setPresentation(new URL(context.getBase(), "present.gui"));

       catch (MalformedURLException e) {

          e.printStackTrace();

       }

    }

    public boolean handleEvent(Event evt) {

       // deal with events from this presentation

    }

    {

Official Marimba Guide to Bongo, also published by Sams.net, contains examples of how to create subclasses of `PlayerFrame` and use them for your own presentations.

### Converting Stand-alone Java Applications to Channels

If you do decide to stick with pure Java for your channels, you've already learned most of what you need to do to convert your existing framework to a channel-compatible framework. The following are the specific operations you need to do:

- Implement the `marimba.channel.Application` interface.
- Instead of a `main()` method or a constructor that initializes the application, do all your initialization inside the `start()` method and all your cleaning up inside `stop()`. The tuner uses these methods to start and stop your channel.
- Implement a `setContext()` method to store the `ApplicationContext` object that is passed to you from the tuner. An instance variable called `context` is good for this example.
- To shut down the application (either via a menu item or a `WINDOW_DESTROY` event), instead of calling `System.exit()`, call `context.stop()`. Calling this method shut down things in the tuner cleanly. Make sure that in your `stop()` method you close all your windows, kill all your threads, and release any other objects you're using. The same runtime is used for all channels, so if you're messy, you can have an impact on someone else's channel.
- Parameters to the application should be passed to the channel via the `parameters.txt` file in the channel. You can use the `context.getParameter()` method to read and process those parameters, instead of using `System.getProperty()`.
- Access all other channel files by URL using the `context.getBase()` method. All data and media files should be relative to this URL.

You'll learn about adding update and file-saving features to channels in the next chapter.

### Converting Applets to Applications

Because of the special class that the tuner uses to run applets as applications (`marimba.channel.AppletViewer`), there's little you need to do to convert an applet to a channel, unless you want to go whole hog and port your applet code altogether (in which case, many of the same rules as for applications apply. Here are some things to keep in mind:

- You can gain access to the methods in the channel's application context through the use of the `getAppletContext()` method (a standard `java.applet.Applet class method`), by casting that result to `marimba.channel.AppletContext` and then by calling `getApplicationContext()` on that object, like this:

`AppicationContext context =`

`((AppletContext)getAppletContext()).getApplicationContext();`

You can then use that context and call its methods just as if the applet was a real channel. More about this in the next chapter.

- If you do decide to port your applet code to a channel, keep in mind that the definitions for the `start()` and `stop()` methods are slightly different, because channels do not start and stop running repeatedly the way an applet does. The `start()` in channels is closer in definition to `init()` in applets; `stop()` is more like `destroy()`.
- Be careful of the methods such as `showStatus()` that take advantage of browser features. These methods operate differently when the applet is run as a channel. (`showStatus()` messages, for example, show up in the status line of the tuner's Java console, not in the tuner or channel's windows.)

### Debugging Hints

Because the process of writing-compiling-publishing-updating-testing channels can be really slow, debugging becomes particularly important. It can keep you from spending all your time in the process and not enough in actual code. Unfortunately, few tools are currently available for debugging a channel before it's published. Until better tools are available, here are some suggestions for debugging channels:

- Scripting can be done inside Bongo and tested much faster than the same operation in a Java application. For simple operations, consider using a combination of scripting and Java event handling.
- Both Bongo and the tuner have a Java console, which can display error messages and print output such as that produced by `System.out.println()`.
- All Bongo widgets have a `list()` method, which prints all the values and properties for a widget to help with debugging.

### Summary

Congratulations! After only 10 chapters, you've finally created your first real Castanet channel. In this chapter, you learned all about creating application channels, which are Java programs that are run from inside the tuner and can respond to updates and other tuner features.

The `marimba.channel` package contains the classes and interfaces you'll most likely use as your create your own channels: the Application interface; the `ApplicationPlayerFrame` class, which you can use to create application channels that use Bongo presentations; and the `ApplicationFrame` class for converting pure Java applications to channels. In addition, `marimba.channel` contains the definition of the application context (the ApplicationContext interface), which is used to interact with the tuner and the local channel directory.

With the basic framework for the channel in place, the next step is to add features specific to channels to that application. Read on to the next chapter for details!