- Getting Started with Java CSS
- Java CSS Fundamentals
- Extending Java CSS with a New Property Type
- Conclusion
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 3GPanel.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 4Gradient.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 5GradientConverter.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 6TC.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 7TC.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.