JawGadget
 
 


Fifth example

Purpose: Demonstrate the use of Graphics2D to create artist inspired dynamic JawGadgets. For this purpose, display a digital clock with an round-edged frame and a big green close button.The clock is updated every second. The clock image is semi-transparent, as long as the mouse cursor is outside. When the mouse cursor enters the picture, it becomes opaque and is on top of all other windows. When the mouse cursor leaves the picture, it becomes semi-transparent again.

// GadgetEx5.java
// Using Graphics2D

import ch.aplu.jaw.*;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.text.*;
import java.io.*;

public class GadgetEx5 extends JPanel implements NativeMouseListener
{
  interface State
  {
    int ENTERING = 0;
    int ENTERED = 1;
    int LEAVING = 2;
    int LEFT = 3;
    int QUITTING = 4;
  }

  private final String bmp = "clock.bmp";
  private JawGadget jg = new JawGadget();
  private volatile int state = State.LEFT;
  private String time = "";

  public GadgetEx5()
  {
    int sec = 0;
    int oldSec = 0;
    SimpleDateFormat formatter =
        new SimpleDateFormat("HH:mm:ss");
    setPreferredSize(new Dimension(450180));
    JFrame frame = packComponent(this);

    jg.addNativeMouseListener(this, NativeMouse.enter |
                                    NativeMouse.leave |
                                    NativeMouse.lDClick);

    // Event loop
    while (state != State.QUITTING)
    {
      synchronized(this)  // Should not be interrupted by callback
      {
        switch (state)
        {
          case State.ENTERING:
            state = State.ENTERED;
            jg.showImage(bmp, 100true);
            break;

          case State.LEAVING:
            state = State.LEFT;
            jg.showImage(bmp, 30false);
            break;
        }

        // Get current date/date
        Calendar now = Calendar.getInstance();
        sec = now.get(Calendar.SECOND);

        // Update every second
        if (sec != oldSec)
        {
          oldSec = sec;
          time = formatter.format(now.getTime());

          // Call repaint and create the BMP image file
          jg.writeBMP(this, bmp);

          // Display (dependant on current appearance)
          if (state == State.LEFT)
            state = State.LEAVING;
          if (state == State.ENTERED)
            state = State.ENTERING;
        }
      }

      // Do not consume too much CPU time, so sleep a while
      jg.sleep(10);
    }

    // Cleanup
    new File(bmp).delete();
    frame.dispose();
    jg.destroy();
  }

  // Callback method
  public void mouseEvent(NativeMouse mouse)
  {
    switch (mouse.getEvent())
    {
      case NativeMouse.enter:
        state = State.ENTERING;
        break;

      case NativeMouse.leave:
        state = State.LEAVING;
        break;

      case NativeMouse.lDClick:
        state = State.QUITTING;
        break;
    }
  }

  // Override paintComponent()
  public void paintComponent(Graphics g)
  {
    super.paintComponent(g);
    Graphics2D g2D = (Graphics2D)g;
    drawImage(g2D, getSize().width, getSize().height);
  }

  private void drawImage(Graphics2D g2D, int width, int height)
  {
    // Draw white background
    g2D.setColor(Color.white);
    g2D.fillRect(00, width, height);

    // Draw frame
    drawFrame(g2D, width, height);

    // Draw text
    Font font = new Font("Serif"Font.PLAIN, 96);
    g2D.setFont(font);
    g2D.drawString(time, 55120);
  }

  private void drawFrame(Graphics2D g2D, int width, int height)
  {
    // Select pen width
    BasicStroke stroke = new BasicStroke(3);
    g2D.setStroke(stroke);
    // Select pen color
    g2D.setPaint(Color.green);
    // Draw circle
    Arc2D.Double arc =
      new Arc2D.Double(202040400360Arc2D.CHORD);
    g2D.fill(arc);
    // Draw cross
    g2D.setPaint(Color.black);
    Line2D line = new Line2D.Double(30305050);
    g2D.draw(line);
    line = new Line2D.Double(50303050);
    g2D.draw(line);
    // Select new pen width
    stroke = new BasicStroke(10);
    g2D.setStroke(stroke);
    // Draw rectangle
    RoundRectangle2D.Double rectangle =
      new RoundRectangle2D.Double(1010, width-20,
                                          height-205050);
    g2D.draw(rectangle);
  }

  private JFrame packComponent(JComponent component)
  {
    JFrame f = new JFrame();
    f.getContentPane().add(component);
    f.pack();
    return f;
  }

  public static void main(String args[])
  {
    new GadgetEx5();
  }
}

 

Discussion: As appropriate for event driven programs, we use a state machine with 5 states (in Java 5+ an enumeration type should be declared). The application extends JPanel and overrides, as usual, paintComponent(), where the graphics operations take place. Any of the sophisticated Graphics2D methods may be used. Every second a new picture is generated and written to a temporary BMP file. By calling showImage() the content of this picture is displayed as a JawGadget with selectable opacity.

We must be careful to handle the asynchronous operations: the mouse callbacks may happen any time. Do never execute lengthy code in the callback methods, just set the state variable. All real work is done in an event loop, which runs sequentially.

Keep in mind, that writeBMP() also executes paintComponent(), so repaint() is not necessary to render the image. After creating the BMP file, the state variable is set to LEAVING or ENTERING to enforce the call of the appropriate showImage() the next time the event loop restarts.

In order to save CPU time, a polling program loop, like our event loop, should always give up the thread a while, simply by calling Thread.sleep().

To conform to the Java guidelines we declare state volatile, because it is modified by the callback method, which runs in another thread. The event loop is synchronized because it is a critical block, where no callbacks are allowed (mouseEvent() is synchronized in the calling routine).