/* * @(#)SynthLookAndFeel.java 1.45 04/05/07 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.swing.plaf.synth; import sun.swing.DefaultLookup; import java.awt.*; import java.awt.event.*; import java.beans.*; import java.io.*; import java.lang.ref.*; import java.text.*; import java.util.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.plaf.*; import javax.swing.plaf.basic.*; import sun.awt.AppContext; import sun.swing.plaf.synth.*; /** * SynthLookAndFeel provides the basis for creating a customized look and * feel. SynthLookAndFeel does not directly provide a look, all painting is * delegated. * You need to either provide a configuration file, by way of the * {@link #load} method, or provide your own {@link SynthStyleFactory} * to {@link #setStyleFactory}. Refer to the * package summary for an example of * loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for * an example of providing your own SynthStyleFactory to * setStyleFactory. *

* Warning: * This class implements {@link Serializable} as a side effect of it * extending {@link BasicLookAndFeel}. It is not intended to be serialized. * An attempt to serialize it will * result in {@link NotSerializableException}. * * @serial exclude * @version 1.45, 05/07/04 * @since 1.5 * @author Scott Violet */ public class SynthLookAndFeel extends BasicLookAndFeel { /** * Used in a handful of places where we need an empty Insets. */ static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource( 0, 0, 0, 0); /** * AppContext key to get the current SynthStyleFactory. */ private static final Object STYLE_FACTORY_KEY = new StringBuffer("com.sun.java.swing.plaf.gtk.StyleCache"); /** * The last SynthStyleFactory that was asked for from AppContext * lastContext. */ private static SynthStyleFactory lastFactory; /** * If this is true it indicates there is more than one AppContext active * and that we need to make sure in getStyleCache the requesting * AppContext matches that of lastContext before returning * it. */ private static boolean multipleApps; /** * AppContext lastLAF came from. */ private static AppContext lastContext; // Refer to setSelectedUI static ComponentUI selectedUI; // Refer to setSelectedUI static int selectedUIState; /** * SynthStyleFactory for the this SynthLookAndFeel. */ private SynthStyleFactory factory; /** * Map of defaults table entries. This is populated via the load * method. */ private Map defaultsMap; /** * Used by the renderers. For the most part the renderers are implemented * as Labels, which is problematic in so far as they are never selected. * To accomodate this SynthLabelUI checks if the current * UI matches that of selectedUI (which this methods sets), if * it does, then a state as set by this method is returned. This provides * a way for labels to have a state other than selected. */ static void setSelectedUI(ComponentUI uix, boolean selected, boolean focused, boolean enabled) { selectedUI = uix; selectedUIState = 0; if (selected) { selectedUIState = SynthConstants.SELECTED; if (focused) { selectedUIState |= SynthConstants.FOCUSED; } } else { selectedUIState = SynthConstants.FOCUSED; if (enabled) { selectedUIState |= SynthConstants.ENABLED; } else { selectedUIState |= SynthConstants.DISABLED; } } } /** * Clears out the selected UI that was last set in setSelectedUI. */ static void resetSelectedUI() { selectedUI = null; } /** * Sets the SynthStyleFactory that the UI classes provided by * synth will use to obtain a SynthStyle. * * @param cache SynthStyleFactory the UIs should use. */ public static void setStyleFactory(SynthStyleFactory cache) { // We assume the setter is called BEFORE the getter has been invoked // for a particular AppContext. synchronized(SynthLookAndFeel.class) { AppContext context = AppContext.getAppContext(); if (!multipleApps && context != lastContext && lastContext != null) { multipleApps = true; } lastFactory = cache; lastContext = context; context.put(STYLE_FACTORY_KEY, cache); } } /** * Returns the current SynthStyleFactory. * * @return SynthStyleFactory */ public static SynthStyleFactory getStyleFactory() { synchronized(SynthLookAndFeel.class) { if (!multipleApps) { return lastFactory; } AppContext context = AppContext.getAppContext(); if (lastContext == context) { return lastFactory; } lastContext = context; lastFactory = (SynthStyleFactory)AppContext.getAppContext().get (STYLE_FACTORY_KEY); return lastFactory; } } /** * Returns the component state for the specified component. This should * only be used for Components that don't have any special state beyond * that of ENABLED, DISABLED or FOCUSED. For example, buttons shouldn't * call into this method. */ static int getComponentState(Component c) { if (c.isEnabled()) { if (c.isFocusOwner()) { return SynthUI.ENABLED | SynthUI.FOCUSED; } return SynthUI.ENABLED; } return SynthUI.DISABLED; } /** * Gets a SynthStyle for the specified region of the specified component. * This is not for general consumption, only custom UIs should call this * method. * * @param c JComponent to get the SynthStyle for * @param region Identifies the region of the specified component * @return SynthStyle to use. */ public static SynthStyle getStyle(JComponent c, Region region) { return getStyleFactory().getStyle(c, region); } /** * Returns true if the Style should be updated in response to the * specified PropertyChangeEvent. This forwards to * shouldUpdateStyleOnAncestorChanged as necessary. */ static boolean shouldUpdateStyle(PropertyChangeEvent event) { String eName = event.getPropertyName(); if ("name" == eName) { // Always update on a name change return true; } if ("ancestor" == eName && event.getNewValue() != null) { // Only update on an ancestor change when getting a valid // parent and the LookAndFeel wants this. LookAndFeel laf = UIManager.getLookAndFeel(); return (laf instanceof SynthLookAndFeel && ((SynthLookAndFeel)laf). shouldUpdateStyleOnAncestorChanged()); } return false; } /** * A convience method that will reset the Style of StyleContext if * necessary. * * @return newStyle */ static SynthStyle updateStyle(SynthContext context, SynthUI ui) { SynthStyle newStyle = getStyle(context.getComponent(), context.getRegion()); SynthStyle oldStyle = context.getStyle(); if (newStyle != oldStyle) { if (oldStyle != null) { oldStyle.uninstallDefaults(context); } context.setStyle(newStyle); newStyle.installDefaults(context, ui); } return newStyle; } /** * Updates the style associated with c, and all its children. * This is a lighter version of * SwingUtilities.updateComponentTreeUI. * * @param c Component to update style for. */ public static void updateStyles(Component c) { _updateStyles(c); c.repaint(); } // Implementation for updateStyles private static void _updateStyles(Component c) { if (c instanceof JComponent) { // Yes, this is hacky. A better solution is to get the UI // and cast, but JComponent doesn't expose a getter for the UI // (each of the UIs do), making that approach impractical. String name = c.getName(); c.setName(null); if (name != null) { c.setName(name); } ((JComponent)c).revalidate(); } Component[] children = null; if (c instanceof JMenu) { children = ((JMenu)c).getMenuComponents(); } else if (c instanceof Container) { children = ((Container)c).getComponents(); } if (children != null) { for(int i = 0; i < children.length; i++) { updateStyles(children[i]); } } } /** * Returns the Region for the JComponent c. * * @param c JComponent to fetch the Region for * @return Region corresponding to c */ public static Region getRegion(JComponent c) { return Region.getRegion(c); } /** * A convenience method to return where the foreground should be * painted for the Component identified by the passed in * AbstractSynthContext. */ static Insets getPaintingInsets(SynthContext state, Insets insets) { if (state.isSubregion()) { insets = state.getStyle().getInsets(state, insets); } else { insets = state.getComponent().getInsets(insets); } return insets; } /** * A convenience method that handles painting of the background. * All SynthUI implementations should override update and invoke * this method. */ static void update(SynthContext state, Graphics g) { paintRegion(state, g, null); } /** * A convenience method that handles painting of the background for * subregions. All SynthUI's that have subregions should invoke * this method, than paint the foreground. */ static void updateSubregion(SynthContext state, Graphics g, Rectangle bounds) { paintRegion(state, g, bounds); } private static void paintRegion(SynthContext state, Graphics g, Rectangle bounds) { JComponent c = state.getComponent(); SynthStyle style = state.getStyle(); int x, y, width, height; if (bounds == null) { x = 0; y = 0; width = c.getWidth(); height = c.getHeight(); } else { x = bounds.x; y = bounds.y; width = bounds.width; height = bounds.height; } // Fill in the background, if necessary. boolean subregion = state.isSubregion(); if ((subregion && style.isOpaque(state)) || (!subregion && c.isOpaque())) { g.setColor(style.getColor(state, ColorType.BACKGROUND)); g.fillRect(x, y, width, height); } } static boolean isLeftToRight(Component c) { return c.getComponentOrientation().isLeftToRight(); } /** * Returns the ui that is of type klass, or null if * one can not be found. */ static Object getUIOfType(ComponentUI ui, Class klass) { if (klass.isInstance(ui)) { return ui; } return null; } /** * Creates the Synth look and feel ComponentUI for * the passed in JComponent. * * @param c JComponent to create the ComponentUI for * @return ComponentUI to use for c */ public static ComponentUI createUI(JComponent c) { String key = c.getUIClassID().intern(); if (key == "ButtonUI") { return SynthButtonUI.createUI(c); } else if (key == "CheckBoxUI") { return SynthCheckBoxUI.createUI(c); } else if (key == "CheckBoxMenuItemUI") { return SynthCheckBoxMenuItemUI.createUI(c); } else if (key == "ColorChooserUI") { return SynthColorChooserUI.createUI(c); } else if (key == "ComboBoxUI") { return SynthComboBoxUI.createUI(c); } else if (key == "DesktopPaneUI") { return SynthDesktopPaneUI.createUI(c); } else if (key == "DesktopIconUI") { return SynthDesktopIconUI.createUI(c); } else if (key == "EditorPaneUI") { return SynthEditorPaneUI.createUI(c); } else if (key == "FileChooserUI") { return SynthFileChooserUI.createUI(c); } else if (key == "FormattedTextFieldUI") { return SynthFormattedTextFieldUI.createUI(c); } else if (key == "InternalFrameUI") { return SynthInternalFrameUI.createUI(c); } else if (key == "LabelUI") { return SynthLabelUI.createUI(c); } else if (key == "ListUI") { return SynthListUI.createUI(c); } else if (key == "MenuBarUI") { return SynthMenuBarUI.createUI(c); } else if (key == "MenuUI") { return SynthMenuUI.createUI(c); } else if (key == "MenuItemUI") { return SynthMenuItemUI.createUI(c); } else if (key == "OptionPaneUI") { return SynthOptionPaneUI.createUI(c); } else if (key == "PanelUI") { return SynthPanelUI.createUI(c); } else if (key == "PasswordFieldUI") { return SynthPasswordFieldUI.createUI(c); } else if (key == "PopupMenuSeparatorUI") { return SynthSeparatorUI.createUI(c); } else if (key == "PopupMenuUI") { return SynthPopupMenuUI.createUI(c); } else if (key == "ProgressBarUI") { return SynthProgressBarUI.createUI(c); } else if (key == "RadioButtonUI") { return SynthRadioButtonUI.createUI(c); } else if (key == "RadioButtonMenuItemUI") { return SynthRadioButtonMenuItemUI.createUI(c); } else if (key == "RootPaneUI") { return SynthRootPaneUI.createUI(c); } else if (key == "ScrollBarUI") { return SynthScrollBarUI.createUI(c); } else if (key == "ScrollPaneUI") { return SynthScrollPaneUI.createUI(c); } else if (key == "SeparatorUI") { return SynthSeparatorUI.createUI(c); } else if (key == "SliderUI") { return SynthSliderUI.createUI(c); } else if (key == "SpinnerUI") { return SynthSpinnerUI.createUI(c); } else if (key == "SplitPaneUI") { return SynthSplitPaneUI.createUI(c); } else if (key == "TabbedPaneUI") { return SynthTabbedPaneUI.createUI(c); } else if (key == "TableUI") { return SynthTableUI.createUI(c); } else if (key == "TableHeaderUI") { return SynthTableHeaderUI.createUI(c); } else if (key == "TextAreaUI") { return SynthTextAreaUI.createUI(c); } else if (key == "TextFieldUI") { return SynthTextFieldUI.createUI(c); } else if (key == "TextPaneUI") { return SynthTextPaneUI.createUI(c); } else if (key == "ToggleButtonUI") { return SynthToggleButtonUI.createUI(c); } else if (key == "ToolBarSeparatorUI") { return SynthSeparatorUI.createUI(c); } else if (key == "ToolBarUI") { return SynthToolBarUI.createUI(c); } else if (key == "ToolTipUI") { return SynthToolTipUI.createUI(c); } else if (key == "TreeUI") { return SynthTreeUI.createUI(c); } else if (key == "ViewportUI") { return SynthViewportUI.createUI(c); } return null; } /** * Creates a SynthLookAndFeel. *

* For the returned SynthLookAndFeel to be useful you need to * invoke load to specify the set of * SynthStyles, or invoke setStyleFactory. * * @see #load * @see #setStyleFactory */ public SynthLookAndFeel() { factory = new DefaultSynthStyleFactory(); } /** * Loads the set of SynthStyles that will be used by * this SynthLookAndFeel. resourceBase is * used to resolve any path based resources, for example an * Image would be resolved by * resourceBase.getResource(path). Refer to * Synth File Format * for more information. * * @param input InputStream to load from * @param resourceBase Used to resolve any images or other resources * @throws ParseException If there is an error in parsing * @throws IllegalArgumentException if input or resourceBase is null */ public void load(InputStream input, Class resourceBase) throws ParseException, IllegalArgumentException { if (defaultsMap == null) { defaultsMap = new HashMap(); } new SynthParser().parse(input, (DefaultSynthStyleFactory)factory, resourceBase, defaultsMap); } /** * Called by UIManager when this look and feel is installed. */ public void initialize() { super.initialize(); DefaultLookup.setDefaultLookup(new SynthDefaultLookup()); setStyleFactory(factory); } /** * Called by UIManager when this look and feel is uninstalled. */ public void uninitialize() { // We should uninstall the StyleFactory here, but unfortunately // there are a handful of things that retain references to the // LookAndFeel and expect things to work super.uninitialize(); } /** * Returns the defaults for this SynthLookAndFeel. * * @return Defaults able. */ public UIDefaults getDefaults() { UIDefaults table = new UIDefaults(); Region.registerUIs(table); table.setDefaultLocale(Locale.getDefault()); table.addResourceBundle( "com.sun.swing.internal.plaf.basic.resources.basic" ); table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth"); // These need to be defined for JColorChooser to work. table.put("ColorChooser.swatchesRecentSwatchSize", new Dimension(10, 10)); table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED); table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10)); // These are needed for PopupMenu. table.put("PopupMenu.selectedWindowInputMapBindings", new Object[] { "ESCAPE", "cancel", "DOWN", "selectNext", "KP_DOWN", "selectNext", "UP", "selectPrevious", "KP_UP", "selectPrevious", "LEFT", "selectParent", "KP_LEFT", "selectParent", "RIGHT", "selectChild", "KP_RIGHT", "selectChild", "ENTER", "return", "SPACE", "return" }); table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft", new Object[] { "LEFT", "selectChild", "KP_LEFT", "selectChild", "RIGHT", "selectParent", "KP_RIGHT", "selectParent", }); if (defaultsMap != null) { table.putAll(defaultsMap); } return table; } /** * Returns true, SynthLookAndFeel is always supported. * * @return true. */ public boolean isSupportedLookAndFeel() { return true; } /** * Returns false, SynthLookAndFeel is not a native look and feel. * * @return true. */ public boolean isNativeLookAndFeel() { return false; } /** * Returns a textual description of SynthLookAndFeel. * * @return textual description of synth. */ public String getDescription() { return "Synth look and feel"; } /** * Return a short string that identifies this look and feel. * * @return a short string identifying this look and feel. */ public String getName() { return "Synth look and feel"; } /** * Return a string that identifies this look and feel. * * @return a short string identifying this look and feel. */ public String getID() { return "Synth"; } /** * Returns whether or not the UIs should update their * SynthStyles from the SynthStyleFactory * when the ancestor of the JComponent changes. A subclass * that provided a SynthStyleFactory that based the * return value from getStyle off the containment hierarchy * would override this method to return true. * * @return whether or not the UIs should update their * SynthStyles from the SynthStyleFactory * when the ancestor changed. */ public boolean shouldUpdateStyleOnAncestorChanged() { return false; } private void writeObject(java.io.ObjectOutputStream out) throws IOException { throw new NotSerializableException(this.getClass().getName()); } }