Java Plug-In Code


Four classes support the communication between plug-in modules and the Hyperwire runtime class library: HwBasicUserPlugIn, HwVisualUserPlugIn, BasicRunService, and VisualRunService.

Plug-in modules that do processing only are called basic plug-ins. Plug-in modules with a visual component or interface that appears in the Layout view —and in the applet window—are called visual plug-ins. Basic plug-ins must subclass the Java class HwBasicUserPlugIn. Visual plug-ins must subclass the Java class HwVisualUserPlugIn. Methods in the basic class enable Hyperwire to communicate with the plug-in. Additional methods in the visual class handle the plug-in�s graphics and graphical interface.

In general, you should override only those methods that require special behavior on the part of your plug-in, and use the default implementation of all other methods. For example, most modules will override piEventAppletStart(), and most visual modules will override piDraw().

In addition to the basic and visual plug-in classes, two run-service interfaces enable plug-ins to obtain services and information from the Hyperwire runtime classes, send data through output ports, and so on. The run services are divided into a basic run service and a visual run service. These interface classes are implemented as part of Hyperwire; your plug-in can call their methods as it requires.

A plug-in inherits a single member variable called mRunService, which implements one or both of the run service interfaces. The plug-in must use this variable to call back into the Hyperwire runtime class library. For example, to make a call that sends a value through an output port, you might use

	mRunService.siPerformOutput("My Port", MyValue);

The value of mRunService is set in the base class implementation of piInit(). Make sure that plug-ins that override piInit() call super.piInit() to ensure that this variable is initialized correctly.

Method names in the basic and visual plug-in classes begin with the characters pi for plug-in. Method names in the basic and visual run service interfaces begin with the characters si for service interface.

The Interface with Hyperwire

This section is a general description of how the Hyperwire runtime controls the plug-in module's execution, how to create a plug-in's input and output ports, and how to manage a visual plug-in module.

Applet Life Cycle

The Hyperwire runtime uses the basic class to control the plug-in module's execution. The main methods that accomplish this correspond to the usual applet life cycle, as follows:

piInit() Called once to initialize the plug-in. The method should initialize its data, including module-specific properties, but it can�t assume that other modules are running yet.

All modules in the applet receive this call.

piEventAppletInit() Called when the applet player initializes the applet with an init() call. It sends a handle for the applet to the plug-in module. All modules in the applet receive this call, which is preceded by a call to piInit().
piRestoreInitialRunState() Called when the plug-in is reset. This occurs at the following times:

  • Immediately after the piInit() call, when the plug-in is created.

    All modules in the applet receive this call.

  • When a plug-in is contained by a module that was turned off and is now turned back on.

    All descendant modules of the container being turned on receive this call if their Reset When Parent On property is turned on (the default).

  • When a plug-in that was off is turned on.

    This happens only if the plug-in's Reset When Self On property is turned on (it is off by default).

  • When the module is explicitly reset via a Reset input port.

    Only the individual module receives this call.

This method should reinitialize the plug-in�s data, as necessary.

piEventAppletStart()
piEventAppletStop()
piEventAppletDestroy()
These three methods correspond exactly to start(), stop(), and destroy() calls from the applet player. They are unconditionally sent to all modules in the applet.

Note: Different browsers and applet players manage the applet life cycle in different ways, so the events that cause these functions to be called vary from browser to browser. In particular, calls to destroy(), and therefore to piEventAppletDestroy() vary considerably from browser to browser.

Event Handling

While the applet runs, the Hyperwire runtime can notify the plug-in of several kinds of events in addition to the life-cycle events controlled by the user of the applet player or browser. These events are mostly useful to visual modules, but some are broadcast to all kinds of modules. For example, a nonvisual module that works closely with a visual module might need to use an event such as a refresh daemon tick.

Some kinds of events, such as refresh ticks and key up or key down, are broadcast only to modules that register a request to receive them.

The following two tables show the plug-in methods that correspond to events. See "Visual Plug-In Modules" for more description of how a plug-in implements a visual module.

Events Broadcast to All Kinds of Modules
piEventParentMovedBy() Sent to all descendant modules of a container module that the user moved.
piEventRefreshDaemonTick() The refresh daemon is a Hyperwire object that generates up to 100 ticks per second. Modules that support animated images should use the refresh daemon to synchronize themselves with other Hyperwire modules.

A plug-in doesn't receive this call unless it requests use of the daemon by calling siPlugInWantsTicks(). All modules registered to use the daemon receive this call when a tick occurs.
piEventKeyDown()
piEventKeyUp()
A plug-in doesn�t receive these calls unless it requests notification of key events by calling siPlugInWantsKeyEvents(). All modules registered to receive key events receive this call when the applet user presses or releases a key.

Key events are not broadcast if input focus is currently on a UI widget such as an edit box.

Events Broadcast to Visual Modules Only
piEventAboutToShow()
piEventAboutToHide()
These calls notify a visual module that it is about to become visible or hidden.
piEventDragTranslate() Tells a visual module that the user has dragged it. Corresponds to the standard output port End Position Change.
piMouseDown() Corresponds to the standard Button Down output port.
piMouseUp() Corresponds to the standard Button Up output port.
piMouseEntered() Corresponds to the standard Mouse Enter output port.
piMouseLeft() Corresponds to the standard Mouse Leave output port.
piMouseMove() Tells the plug-in that the mouse has moved within its bounding box.
piMouseDrag() Tells the plug-in that the user has dragged within its bounding box. (Applies only to nonmovable modules.)
The drag-translate and mouse event calls let a visual module react to the user's mouse actions. A plug-in should override only those methods that call for special behavior. For example, a plug-in might have an implementation of piMouseUp() so it can respond to mouse clicks within its area, but ignore the other kinds of mouse activity.

Input Ports

A plug-in module receives parameters from its input ports just as standard Hyperwire modules do. To support input ports, do the following:

Input methods must be public because the Hyperwire runtime calls them. Input method parameters can be of any type. However, the user (author) can specify literal constants only for data types that are recognized by Hyperwire. If you want the author to be able to specify literal constants in the Wire editor, make sure the parameter type is supported by Hyperwire. For example, an author can�t specify a constant for a custom class. Types that can be constant parameters fall into two categories: Java types and native Hyperwire types. The native Hyperwire types require special handling.

Java Types that Can Be Constant Parameters in Hyperwire:

byte
char
double
float
int
long
short
boolean
java.lang.String
Point
Rectangle
Color
java.net.URL
Font
ReferencePoint

Hyperwire Types that Can Be Constant Parameters in Hyperwire:

You can use a number of Hyperwire classes as data types for ports and wires. If you declare an input port that uses one of these Hyperwire data types, you must "unwrap" the object to get the Java object it contains. All of the Hyperwire data types have a method called getValue() that returns the Java version of the datum. For example:

int j;

j = HwInt.getValue();

Hyperwire Type Description
HwInt Integer
HwFloat Single-precision floating point
HwBoolean Boolean
HwPoint 2D point
HwRectangle Rectangle
HwColor RGB color
Point3 3D point
HwMatrix Array

These are the same classes you use to "wrap" native Java objects before passing them with the siPerformOutput() family of functions, as described in the next section.

Output Ports

A plug-in module sends parameters through its output ports, as standard Hyperwire modules do. To support output ports, do the following:

Hyperwire "wraps" all data passed on wires in special Hyperwire classes. You can�t pass Java values directly to siPerformOutput(). Instead, you must "wrap" data so it has a type that other Hyperwire modules can handle. The Hyperwire runtime provides a number of wrapper classes for casting output data.

Type to send Hyperwire wrapper Example
int HwInt HwInt.from(1996)
long HwLong HwLong.from(123456789000L)
char HwCharacter HwCharacter.from('X')
String HwString HwString.from("Vout")
boolean HwBoolean HwBoolean.from(true)
RGB color data HwColor HwColor.from(255, 0, 0)
Double-precision floating point HwDouble HwDouble.from(3.0)
Single-precision floating point HwFloat HwFloat.from(3.0F)
Logical font HwFont HwFont.from("Helvetica")
Module reference HwModule HwModule.from(myParent)
Point HwPoint HwPoint.from(new Point(1,2))
Rectangle HwRectangle HwRectangle.from(new Rectangle(0, 0, 320, 240))
All other classes HwLObject HwLObject.from(new Foo())

Note: When a standard output event such as mouse up, mouse down, and so on, occurs, the Hyperwire runtime calls the appropriate method in the applet's visual modules. If you want your plug-in to send output data when one of these events occurs, override the corresponding method and call siPerformOutput() in your implementation. See "Event Handling."

Module-Specific Properties

The Hyperwire user—the author creating a plug-in—sets a module's properties by using its Properties dialog. Module-specific properties appear in the module�s tab (the first tab in the dialog). Hyperwire saves property settings in the Hyperwire title (.ttl) file, so the author's settings persist from session to session.

To create properties specific to your plug-in, do the following:

Note: You can specify a URL as a module-specific property. Your plug-in code can treat the URL as a string. Its MDF entry casts the string to a URL in Java. You set the field as you do other fields, in the kxPersistentFields block of your plug-in's MDF file. For example:

   kxPersistentFields =
      {
         .
         .
         .
         kxField MyURL =
            {
               kxPublicNames = "My URL"
               kxJavaSignature = "LURL"
               kxAccessor = fiSetMyURL
               kxDefault = URL("mypagehere.htm")
               kxDescription = "HTML page"
            }
         .
         .
         .
   }

In your plug-in's accessor method, treat the URL as a string. For example:

	public void fiSetMyURL(String url)

Visual Plug-In Modules

To create a visual module, subclass HwVisualUserPlugIn. This class extends the basic plug-in class with methods that are specific to visual modules. Several MDF file entries also help you to set up a visual module.

A visual module must refresh its image when the user moves it, when it has been obscured by another window and must be redisplayed, and so on. Usually the system handles most of the repainting, but it requires some assistance from the plug-in code. There are two main ways for a visual plug-in to handle refresh situations:

Note: The visual method piEventDragTranslate() notifies the plug-in that the user has dragged the module. This call is not a refresh request, but the plug-in can override this method so it returns a clipping rectangle describing the area that needs to be repainted because of the drag. The Hyperwire runtime then uses this information to initiate a refresh request.

Clipping rectangles, mouse coordinates, bounding boxes, and so on are given in pixel coordinates relative to the applet, with (0,0) as the upper-left corner of the applet window. The exceptions are the visual run service methods siGetRelativeRect(), which returns the plug-in's bounding rectangle relative to its parent, and siGetScreenRect(), which returns the plug-in's position relative to the entire screen. For these methods, (0,0) is still the upper-left corner, either of the parent or the screen.

The MDF entries for setting up visual plug-in modules are as follows:

kxSize Sets the default module size in the Layout view.
kxIsFixedSize Specifies whether the author can change the module size.
kxDrawImage Specifies an image to draw within the module.
kxDrawImageIsStretched Specifies whether the image is stretched to fit kxSize.
kxDrawImageIsLabelled Specifies a label that appears within the module in Layout view.
kxDrawImageHasBorder Specifies whether the module appears within a border.
kxLayoutViewProxyClass For visual modules, must be set to equal AuthorRepresentation.

Radio Button Modules

You can set up modules that are mutually exclusive within an applet or container. That is, only one module is active at a time, as with the radio buttons used by many dialogs. You do this as follows:

Synchronizing Modules with the Refresh Daemon

Modules that support timed activity such as animation must synchronize themselves with other timed modules. Hyperwire synchronizes objects with a refresh daemon object. The refresh daemon generates up to 100 ticks per second. To synchronize itself with the refresh daemon, your plug-in code must do the following:

When the plug-in is registered, the Hyperwire runtime calls piEventRefreshDaemonTick() every time a tick occurs. The implementation of piEventRefreshDaemonTick() must store the time value (timeNow) passed to it. When it is called again, it must compare the new time value with the previous value. Based on the amount of time that has elapsed, the method must do what it needs to do to synchronize itself. For example, if the plug-in displays multiple frames of an animation, a long elapsed time might cause it to skip some frames.

If the plug-in is a visual plug-in, piEventRefreshDaemonTick() must notify the refresh daemon of the area that it has changed visually. It does this by calling the refresh daemon's invalidateRect() method. This method can take either a Rectangle object or a set of two points:

   public synchronized void invalidateRect(Rectangle r);

   public void invalidateRect(int x, int y, int width, int height)

For example:

   public void piEventRefreshDaemonTick(long timeNow, RefreshDaemon worldMover)
      throws HwException
      {
      if (prevTime < 0)   // Initialized to -1 so this is evaluated
                          // only the first time this function is called
         prevTime = timeNow;
      long deltaTime = timeNow - prevTime;
      prevTime = timeNow;

      Rectangle newBox = ((VisualRunService) mRunService).siGetAbsRect();

      // Do processing here to make the plug-in "catch up"
      .
      .
      .
      worldMover.invalidateRect(Box);
      }

The sample plug-in WavyPlugIn.java shows a more extensive example of synchronizing animation using the refresh daemon.

Creating a Subwindow

To have the plug-in module run in a window of its own, rather than the applet player window, include the following line in the MDF file:
   kxFlags = kxEmbeddedWindow

This kxFlags setting notifies Hyperwire that the plug-in is implemented as a subwindow that is always on top of other, non-window modules. You must manage the subwindow using the Java Abstract Window Toolkit (AWT). Hyperwire doesn't use piDraw() to refresh the subwindow.

Subclassing Hyperwire Modules

A plug-in module can subclass a standard Hyperwire module. To do so, go through the following steps:

  1. Write a Java plug-in class that subclasses a standard Hyperwire module.

  2. Compile the subclass and install it and its associated files in the locations Hyperwire requires (Location of the Plug-In Components).

  3. Make a copy of the Hyperwire class's MDF file and save it under your plug-in's name in your subdirectory of \Hyperwire\Modules.

  4. Edit the MDF file so its kxPlugInClass field names your class instead of the standard class.

  5. Add RedFieldsEditor to the list of Properties dialog editors (tabs) in the MDF file's kxPropertyEditors entry. This controls the Properties dialog tab for module-specific settings.

    If your plug-in has module-specific settings, its class must contain accessor methods that match the kxPersistentFields block in the MDF file.

  6. In the MDF file, define any custom inputs (kxInputPorts) and outputs (kxOutputPorts) you are adding to the class.
  7. Register the plug-in module.

For an example of a subclassed Hyperwire object, in this case Circle, see WobblyCirclePlugIn.java. The MDF file for this document is in Wobble Ellipse.mdf.

Continue to "MDF File Format"

Return to Contents