[ Team LiB ] Previous Section Next Section

11.13 Themes and the Metal Look-and-Feel

The default platform-independent look-and-feel for Swing applications is known as the Metal look-and-feel. One of the powerful but little-known features of Metal is that the fonts and colors it uses are easily customizable. All you have to do is pass a MetalTheme object to the static setCurrentTheme( ) method of MetalLookAndFeel. (These classes are defined in the infrequently used javax.swing.plaf.metal package.)

The MetalTheme class is abstract, so, in practice, you work with DefaultMetalTheme. This class has six methods that return the basic theme colors (really three shades each of a primary and a secondary color) and four methods that return the basic theme fonts. To define a new theme, all you have to do is subclass DefaultMetalTheme and override these methods to return the fonts and colors you want. (If you want more customizability than this, you have to subclass MetalTheme directly.)

Example 11-28 is a listing of ThemeManager.java. This example includes a subclass of DefaultMetalTheme, but defines it as an inner class of ThemeManager. The ThemeManager class provides the ability to read theme definitions (i.e., color and font specifications) from a GUIResourceBundle and defines methods for reading the name of a default theme and a list of names of all available themes from the bundle. Finally, ThemeManager can return a JMenu component that displays a list of available themes to the user and switches the current theme based on the user's selection.

ThemeManager also demonstrates a feature unrelated to DefaultMetalTheme: it includes the ability to tell Swing whether it should provide audio feedback for all, some, or none of the user actions for which Swing is capable of playing sounds. It does this by setting an undocumented property[4] with the UIManager.put( ) method.

[4] The properties are not documented within the javadoc API, but you can find out more by searching the web for the string "AuditoryCues.playList".

ThemeManager, and the JMenu component it creates, were used in the WebBrowser class of Example 11-21. Before you examine the ThemeManager code, take a look at the following lines excerpted from the WebBrowserResources.properties file, which define the set of available themes for the web browser:

# This property defines the property names of all available themes.
themelist: theme.metal theme.rose, theme.lime, theme.primary, theme.bigfont
# This property defines the name of the default property
defaultTheme: theme.metal

# This theme only has a name.  All font and color values are unchanged from
# the default Metal theme
theme.metal.name: Default Metal

# This theme uses shades of red/pink
theme.rose.name: Rose
theme.rose.primary: #905050
theme.rose.secondary: #906050
theme.rose.sounds: all

# This theme uses lime green colors
theme.lime.name: Lime
theme.lime.primary: #509050
theme.lime.secondary: #506060
theme.lime.sounds: none

# This theme uses bright primary colors
theme.primary.name: Primary Colors
theme.primary.primary: #202090
theme.primary.secondary: #209020
theme.primary.sounds: default

# This theme uses big fonts and the default colors
theme.bigfont.name: Big Fonts
theme.bigfont.controlFont: sansserif-bold-18
theme.bigfont.menuFont: sansserif-bold-18
theme.bigfont.smallFont: sansserif-plain-14
theme.bigfont.systemFont: sansserif-plain-14
theme.bigfont.userFont: sansserif-plain-14
theme.bigfont.titleFont: sansserif-bold-18

With these theme definitions, you should have no trouble understanding the resource-parsing code of ThemeManager. getThemeMenu( ) creates a JMenu populated by JRadioButtonMenuItem objects, rather than JMenuItem or Action objects, as we've seen earlier in this chapter. This emphasizes the fact that only one theme can be selected at a time. When the theme is changed, the setTheme( ) method uses a SwingUtilities method to propagate the change to all components within the frame. Finally, note that the Theme inner class doesn't use Font and Color objects, but uses FontUIResource and ColorUIResource objects instead. These classes are part of the javax.swing.plaf package and are trivial subclasses of Font and Color that implement the UIResource marker interface. This interface allows components to distinguish between property values assigned by the look-and-feel, which all implement UIResource, and property values assigned by the application. Based on this distinction, application settings can override look-and-feel settings, even when the look-and-feel (or theme) changes while the application is running.

Example 11-28. ThemeManager.java
package je3.gui;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.plaf.metal.DefaultMetalTheme;

/**
 * This class reads theme descriptions from a GUIResourceBundle and uses them
 * to specify colors and fonts for the Metal look-and-feel.  It also 
 * demonstrates an undocumented feature for turning Swing notification sounds
 * on and off.
 **/
public class ThemeManager {
    JFrame frame;                  // The frame which themes are applied to
    GUIResourceBundle resources;   // Properties describing the themes

    /** 
     * Build a ThemeManager for the frame and resource bundle.  If there
     * is a default theme specified, apply it to the frame
     **/
    public ThemeManager(JFrame frame, GUIResourceBundle resources) {
        this.frame = frame;
        this.resources = resources;
        String defaultName = getDefaultThemeName( );
        if (defaultName != null) setTheme(defaultName);
    }

    /** Look up the named theme, and apply it to the frame */
    public void setTheme(String themeName) {
        // Look up the theme in the resource bundle
        Theme theme = new Theme(resources, themeName);

        // Make it the current theme
        MetalLookAndFeel.setCurrentTheme(theme);

        // Reapply the Metal look-and-feel to install new theme
        try { UIManager.setLookAndFeel(new MetalLookAndFeel( )); }
        catch(UnsupportedLookAndFeelException e) {  }

        // If the theme has an audio playlist, then set it
        if (theme.playlist != null)
            UIManager.put("AuditoryCues.playList", theme.playlist);

        // Propagate the new l&f across the entire component tree of the frame
        SwingUtilities.updateComponentTreeUI(frame);
    }

    /** Get the "display name" or label of the named theme */
    public String getDisplayName(String themeName) {
        return resources.getString(themeName + ".name", null);
    }

    /** Get the name of the default theme, or null */
    public String getDefaultThemeName( ) {
        return resources.getString("defaultTheme", null);
    }

    /**
     * Get the list of all known theme names.  The returned values are
     * theme property names, not theme display names.
     **/
    public String[  ] getAllThemeNames( ) {
        java.util.List names = resources.getStringList("themelist");
        return (String[  ]) names.toArray(new String[names.size( )]);
    }

    /**
     * Get a JMenu that lists all known themes by display name and
     * installs any selected theme.
     **/
    public JMenu getThemeMenu( ) {
        String[  ] names = getAllThemeNames( );
        String defaultName = getDefaultThemeName( );
        JMenu menu = new JMenu("Themes");
        ButtonGroup buttongroup = new ButtonGroup( );
        for(int i = 0; i < names.length; i++) {
            final String themeName = names[i];
            String displayName = getDisplayName(themeName);
            JMenuItem item = menu.add(new JRadioButtonMenuItem(displayName));
            buttongroup.add(item);
            if (themeName.equals(defaultName)) item.setSelected(true);
            item.addActionListener(new ActionListener( ) {
                    public void actionPerformed(ActionEvent event) {
                        setTheme(themeName);
                    }
                });
        }
        return menu;
    }

    /**
     * This class extends the DefaultMetalTheme class to return Color and
     * Font values read from a GUIResourceBundle
     **/
    public static class Theme extends DefaultMetalTheme {
        // These fields are the values returned by this Theme
        String displayName;
        FontUIResource controlFont, menuFont, smallFont;
        FontUIResource systemFont, userFont, titleFont;
        ColorUIResource primary1, primary2, primary3;
        ColorUIResource secondary1, secondary2, secondary3;
        Object playlist;  // auditory cues

        /**
         * This constructor reads all the values it needs from the
         * GUIResourceBundle.  It uses intelligent defaults if properties
         * are not specified.
         **/
        public Theme(GUIResourceBundle resources, String name) {
            // Use this theme object to get default font values from
            DefaultMetalTheme defaultTheme = new DefaultMetalTheme( );

            // Look up the display name of the theme
            displayName = resources.getString(name + ".name", null);

            // Look up the fonts for the theme
            Font control = resources.getFont(name + ".controlFont", null);
            Font menu = resources.getFont(name + ".menuFont", null);
            Font small = resources.getFont(name + ".smallFont", null);
            Font system = resources.getFont(name + ".systemFont", null);
            Font user = resources.getFont(name + ".userFont", null);
            Font title = resources.getFont(name + ".titleFont", null);

            // Convert fonts to FontUIResource, or get defaults
            if (control != null) controlFont = new FontUIResource(control);
            else controlFont = defaultTheme.getControlTextFont( );
            if (menu != null) menuFont = new FontUIResource(menu);
            else menuFont = defaultTheme.getMenuTextFont( );
            if (small != null) smallFont = new FontUIResource(small);
            else smallFont = defaultTheme.getSubTextFont( );
            if (system != null) systemFont = new FontUIResource(system);
            else systemFont = defaultTheme.getSystemTextFont( );
            if (user != null) userFont = new FontUIResource(user);
            else userFont = defaultTheme.getUserTextFont( );
            if (title != null) titleFont = new FontUIResource(title);
            else titleFont = defaultTheme.getWindowTitleFont( );

            // Look up primary and secondary colors
            Color primary = resources.getColor(name + ".primary", null);
            Color secondary = resources.getColor(name + ".secondary", null);

            // Derive all six colors from these two, using defaults if needed
            if (primary != null) primary1 = new ColorUIResource(primary);
            else primary1 = new ColorUIResource(102, 102, 153);
            primary2 = new ColorUIResource(primary1.brighter( ));
            primary3 = new ColorUIResource(primary2.brighter( ));
            if (secondary != null) secondary1 = new ColorUIResource(secondary);
            else secondary1 = new ColorUIResource(102, 102, 102);
            secondary2 = new ColorUIResource(secondary1.brighter( ));
            secondary3 = new ColorUIResource(secondary2.brighter( ));

            // Look up what type of sound is desired.  This property should
            // be one of the strings "all", "none", or "default".  These map to
            // undocumented UIManager properties.  playlist is an array of
            // strings, but we keep it as an opaque object.
            String sounds = resources.getString(name + ".sounds", "");
            if (sounds.equals("all"))
                playlist = UIManager.get("AuditoryCues.allAuditoryCues");
            else if (sounds.equals("none"))
                playlist = UIManager.get("AuditoryCues.noAuditoryCues");
            else if (sounds.equals("default"))
                playlist = UIManager.get("AuditoryCues.defaultCueList");
        }

        // These methods override DefaultMetalTheme and return the property
        // values we looked up and computed for this theme
        public String getName( ) { return displayName; }
        public FontUIResource getControlTextFont( ) { return controlFont;}
        public FontUIResource getSystemTextFont( ) { return systemFont;}
        public FontUIResource getUserTextFont( ) { return userFont;}
        public FontUIResource getMenuTextFont( ) { return menuFont;}
        public FontUIResource getWindowTitleFont( ) { return titleFont;}
        public FontUIResource getSubTextFont( ) { return smallFont;}
        protected ColorUIResource getPrimary1( ) { return primary1; } 
        protected ColorUIResource getPrimary2( ) { return primary2; }
        protected ColorUIResource getPrimary3( ) { return primary3; }
        protected ColorUIResource getSecondary1( ) { return secondary1; }
        protected ColorUIResource getSecondary2( ) { return secondary2; }
        protected ColorUIResource getSecondary3( ) { return secondary3; }
    }
}
    [ Team LiB ] Previous Section Next Section