Home > Articles > Programming > Java

  • Print
  • + Share This
From the author of Extending Java CSS with a New Property Type

Extending Java CSS with a New Property Type

Java CSS lets you extend its capabilities via the various registration methods of the com.sun.stylesheet.types.TypeManager class. For example, you can invoke the public static void registerTypeConverter(Class type, TypeConverter converter) method to register a new property type with a converter that converts style sheet strings to this type.

To see how this capability benefits you, suppose you've created a GPanel class that renders a gradient as its background. You add containers and components to this panel (adjusting opacity where necessary to ensure that the gradient shows through) to specify an application's user interface, and then install the gradient panel as the frame window's content pane. Listing 3 presents my version of GPanel.

Listing 3—GPanel.java.

// GPanel.java

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class GPanel extends JPanel
{
   private GradientPaint gp;

   private Gradient gradient;

   @Override
   public void paintComponent (Graphics g)
   {
      if (gp == null && gradient != null)
          gp = new GradientPaint (0, 0, gradient.start, 0, getHeight (),
                                  gradient.end);
      if (gp != null)
          ((Graphics2D) g).setPaint (gp);
      else
          g.setColor (getBackground ());
      g.fillRect (0, 0, getWidth (), getHeight ());
   }

   public void setGradient (Gradient gradient)
   {
      this.gradient = gradient;
      gp = null;
      repaint ();
   }

   public Gradient getGradient ()
   {
      return gradient;
   }
}

GPanel defaults to rendering its surface in its background color. However, it renders a gradient whenever its gradient property is set, via setGradient().

The gradient property consists of the gradient's start and end colors, which are encapsulated by the Gradient class in Listing 4.

Listing 4—Gradient.java.

// Gradient.java

import java.awt.Color;

public class Gradient
{
   public Color start;
   public Color end;

   public Gradient (Color start, Color end)
   {
      this.start = start;
      this.end = end;
   }
}

Let's assume that we want to refer to GPanel and its gradient property via a rule such as GPanel { gradient: #ffafaf, #ffc800 }. Java CSS recognizes the GPanel selector because it automatically recognizes JComponent subclasses. However, to make Java CSS recognize the declaration, we need to employ the GradientConverter class in Listing 5.

Listing 5—GradientConverter.java.

// GradientConverter.java

import java.awt.Color;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.sun.stylesheet.types.TypeConverter;

public class GradientConverter implements TypeConverter<Gradient>
{
   private Pattern pattern = Pattern.compile (
        "(#[A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d])"+
        "\\s*,\\s*"+
        "(#[A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d][A-Fa-f\\d])");

   public Gradient convertFromString (String string)
   {
      Matcher m = pattern.matcher (string);
      if (m.matches ())
          return new Gradient (Color.decode (m.group (1)),
                               Color.decode (m.group (2)));
      else
          throw new IllegalArgumentException ("unable to convert string '"+
                                              string+"' to Gradient");
   }
}

Java CSS requires GradientConverter to implement the TypeConverter<T> interface, and this interface's T convertFromString(String string) method to convert a string representation of the gradient property's value to a Gradient object, which this method must return. Regular expressions and a precompiled pattern are used to speed up the conversion process.

The final step in making GPanel and its gradient property available for use in an application's style sheet is to execute TypeManager.registerTypeConverter (Gradient.class, new GradientConverter ());, to introduce the gradient property and its type converter to Java CSS, prior to creating the application's user interface. Check out Listing 6.

Listing 6—TC.java.

// TC.java

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.GradientPaint;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import javax.swing.border.BevelBorder;
import javax.swing.border.Border;

import com.sun.stylesheet.Stylesheet;

import com.sun.stylesheet.css.CSSParser;

import com.sun.stylesheet.types.TypeManager;

public class TC extends JFrame
{
   public TC ()
   {
      super ("Temp Converter");
      setDefaultCloseOperation (EXIT_ON_CLOSE);

      GPanel pnl = new GPanel ();
      pnl.setLayout (new BorderLayout ());

      JPanel pnlInput = new JPanel ();
      pnlInput.putClientProperty ("styleClass", "border");
      pnlInput.setOpaque (false);
      pnlInput.add (new JLabel ("Degrees:"));
      final JTextField txtInput = new JTextField (15);
      pnlInput.add (txtInput);
      pnl.add (pnlInput, BorderLayout.NORTH);

      JPanel pnlButtons = new JPanel ();
      pnlButtons.putClientProperty ("styleClass", "border");
      pnlButtons.setOpaque (false);
      JButton btnToCelsius = new JButton ("To Celsius");
      ActionListener alToCelsius;
      alToCelsius = new ActionListener ()
                    {
                        public void actionPerformed (ActionEvent ae)
                        {
                           try
                           {
                              double val;
                              val = Double.parseDouble (txtInput.getText ());
                              double res = (val-32.0)*5.0/9.0;
                              txtInput.setText (res+"");
                           }
                           catch (NumberFormatException nfe)
                           {
                              txtInput.setText ("error");
                           }
                        }
                    };
      btnToCelsius.addActionListener (alToCelsius);
      pnlButtons.add (btnToCelsius);
      JButton btnToFahrenheit = new JButton ("To Fahrenheit");
      ActionListener alToFahrenheit;
      alToFahrenheit = new ActionListener ()
                       {
                           public void actionPerformed (ActionEvent ae)
                           {
                              try
                              {
                                 double val;
                                 val = Double.parseDouble (txtInput.getText ());
                                 double res = val*9.0/5.0+32.0;
                                 txtInput.setText (res+"");
                              }
                              catch (NumberFormatException nfe)
                              {
                                 txtInput.setText ("error");
                              }
                           }
                       };
      btnToFahrenheit.addActionListener (alToFahrenheit);
      pnlButtons.add  (btnToFahrenheit);
      pnl.add (pnlButtons, BorderLayout.SOUTH);

      setContentPane (pnl);
   }

   public static void main (String [] args)
   {
      Runnable r;
      r = new Runnable ()
          {
              public void run ()
              {
                 TypeManager.registerTypeConverter (Gradient.class,
                                                    new GradientConverter ());
                 FileReader fr = null;
                 try
                 {
                     fr = new FileReader ("TC.css");
                     Stylesheet stylesheet = CSSParser.parse (fr);
                     TC tc = new TC ();
                     stylesheet.applyTo (tc);
                     tc.pack ();
                     tc.setResizable (false);
                     tc.setLocationRelativeTo (null);
                     tc.setVisible (true);
                 }
                 catch (FileNotFoundException fnfe)
                 {
                     System.err.println ("TC.css not found");
                 }
                 catch (IOException ioe)
                 {
                     System.err.println ("I/O problem: "+ioe.getMessage ());
                 }
                 finally
                 {
                     if (fr != null)
                         try { fr.close (); } catch (IOException ioe) {}
                 }
              }
          };
      EventQueue.invokeLater (r);
   }
}

At startup, this temperature-conversion application instantiates and registers a GradientConverter with the Gradient class. It then proceeds to parse file-based style information from TC.css via CSSParser's public static Stylesheet parse(Reader in) method. Listing 7 presents the contents of the TC.css-based style sheet.

Listing 7—TC.css.

GPanel
{
  gradient: #c0c0c0, #708090
}

JTextField
{
   border: BorderFactory.createBevelBorder (BevelBorder.LOWERED)
}

JButton
{
   border: BorderFactory.createCompoundBorder (
     BorderFactory.createBevelBorder (BevelBorder.RAISED),
     BorderFactory.createEmptyBorder (5, 5, 5, 5));
   preferredSize: 95, 25
}

JPanel.border
{
   border: BorderFactory.createCompoundBorder (
     BorderFactory.createEmptyBorder (10, 10, 10, 10),
     BorderFactory.createBevelBorder (BevelBorder.LOWERED,
                                      #c0c0c0, #404040))
}

If parsing succeeds, TC creates its user interface and uses the returned Stylesheet instance to apply style sheet settings to the UI. It then centers its frame window via the setLocationRelativeTo (null); method call, and displays the user interface. (Why can't you replace this method call with a style sheet setting?) Check out Figure 4.

Figure 4 Unstyled and styled versions of the temperature-conversion user interface.

  • + Share This
  • 🔖 Save To Your Account