/* * $Id: BasicHeaderUI.java 3483 2009-09-02 12:33:21Z kleopatra $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.hdesktop.swingx.plaf.basic; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.HierarchyBoundsAdapter; import java.awt.event.HierarchyBoundsListener; import java.awt.event.HierarchyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.View; import org.hdesktop.swingx.JXHeader; import org.hdesktop.swingx.JXLabel; import org.hdesktop.swingx.JXHeader.IconPosition; import org.hdesktop.swingx.painter.MattePainter; import org.hdesktop.swingx.painter.Painter; import org.hdesktop.swingx.plaf.HeaderUI; import org.hdesktop.swingx.plaf.PainterUIResource; import org.hdesktop.swingx.plaf.UIManagerExt; /** * Base implementation of <code>Header</code> UI. <p> * * PENDING JW: This implementation is unusual in that it does not keep a reference * to the component it controls. Typically, such is only the case if the ui is * shared between instances. Historical? A consequence is that the un/install methods * need to carry the header as parameter. Which looks funny when at the same time * the children of the header are instance fields in this. Should think about cleanup: * either get rid off the instance fields here, or reference the header and remove * the param (would break subclasses).<p> * * PENDING JW: keys for uidefaults are inconsistent - most have prefix "JXHeader." while * defaultIcon has prefix "Header." <p> * * @author rbair * @author rah003 * @author Jeanette Winzenburg */ public class BasicHeaderUI extends HeaderUI { @SuppressWarnings("unused") private static final Logger LOG = Logger.getLogger(BasicHeaderUI.class .getName()); // Implementation detail. Neeeded to expose getMultiLineSupport() method to allow restoring view // lost after LAF switch protected class DescriptionPane extends JXLabel { @Override public void paint(Graphics g) { // switch off jxlabel default antialiasing // JW: that cost me dearly to track down - it's the default foreground painter // which is an AbstractPainter which has _global_ antialiased on by default // and here the _text_ antialiased is turned off // changed JXLabel default foregroundPainter to have antialiasing false by // default, so remove interference here // part of fix for #920 - the other part is in JXLabel, fix 1164 // ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, // RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); super.paint(g); } @Override public MultiLineSupport getMultiLineSupport() { return super.getMultiLineSupport(); } } protected JLabel titleLabel; protected DescriptionPane descriptionPane; protected JLabel imagePanel; private PropertyChangeListener propListener; private HierarchyBoundsListener boundsListener; private Color gradientLightColor; private Color gradientDarkColor; /** Creates a new instance of BasicHeaderUI */ public BasicHeaderUI() { } /** * Returns an instance of the UI delegate for the specified component. * Each subclass must provide its own static <code>createUI</code> * method that returns an instance of that UI delegate subclass. * If the UI delegate subclass is stateless, it may return an instance * that is shared by multiple components. If the UI delegate is * stateful, then it should return a new instance per component. * The default implementation of this method throws an error, as it * should never be invoked. */ public static ComponentUI createUI(JComponent c) { return new BasicHeaderUI(); } /** * Configures the specified component appropriate for the look and feel. * This method is invoked when the <code>ComponentUI</code> instance is being installed * as the UI delegate on the specified component. This method should * completely configure the component for the look and feel, * including the following: * <ol> * <li>Install any default property values for color, fonts, borders, * icons, opacity, etc. on the component. Whenever possible, * property values initialized by the client program should <i>not</i> * be overridden. * <li>Install a <code>LayoutManager</code> on the component if necessary. * <li>Create/add any required sub-components to the component. * <li>Create/install event listeners on the component. * <li>Create/install a <code>PropertyChangeListener</code> on the component in order * to detect and respond to component property changes appropriately. * <li>Install keyboard UI (mnemonics, traversal, etc.) on the component. * <li>Initialize any appropriate instance data. * </ol> * @param c the component where this UI delegate is being installed * * @see #uninstallUI * @see javax.swing.JComponent#setUI * @see javax.swing.JComponent#updateUI */ @Override public void installUI(JComponent c) { super.installUI(c); assert c instanceof JXHeader; JXHeader header = (JXHeader)c; installDefaults(header); installComponents(header); installListeners(header); } /** * Reverses configuration which was done on the specified component during * <code>installUI</code>. This method is invoked when this * <code>UIComponent</code> instance is being removed as the UI delegate * for the specified component. This method should undo the * configuration performed in <code>installUI</code>, being careful to * leave the <code>JComponent</code> instance in a clean state (no * extraneous listeners, look-and-feel-specific property objects, etc.). * This should include the following: * <ol> * <li>Remove any UI-set borders from the component. * <li>Remove any UI-set layout managers on the component. * <li>Remove any UI-added sub-components from the component. * <li>Remove any UI-added event/property listeners from the component. * <li>Remove any UI-installed keyboard UI from the component. * <li>Nullify any allocated instance data objects to allow for GC. * </ol> * @param c the component from which this UI delegate is being removed; * this argument is often ignored, * but might be used if the UI object is stateless * and shared by multiple components * * @see #installUI * @see javax.swing.JComponent#updateUI */ @Override public void uninstallUI(JComponent c) { assert c instanceof JXHeader; JXHeader header = (JXHeader)c; uninstallListeners(header); uninstallComponents(header); uninstallDefaults(header); } /** * Installs default header properties. * <p> * * NOTE: this method is called before the children are created, so must not * try to access any of those!. * * @param header the header to install. */ protected void installDefaults(JXHeader header) { gradientLightColor = UIManagerExt.getColor("JXHeader.startBackground"); if (gradientLightColor == null) { // fallback to white gradientLightColor = Color.WHITE; } gradientDarkColor = UIManagerExt.getColor("JXHeader.background"); // for backwards compatibility (mostly for substance and synthetica, // I suspect) I'll fall back on the "control" color if // JXHeader.background // isn't specified. if (gradientDarkColor == null) { gradientDarkColor = UIManagerExt.getColor("control"); } if (isUIInstallable(header.getBackgroundPainter())) { header.setBackgroundPainter(createBackgroundPainter()); } // title properties if (isUIInstallable(header.getTitleFont())) { Font titleFont = UIManager.getFont("JXHeader.titleFont"); // fallback to label font header.setTitleFont(titleFont != null ? titleFont : UIManager .getFont("Label.font")); } if (isUIInstallable(header.getTitleForeground())) { Color titleForeground = UIManagerExt .getColor("JXHeader.titleForeground"); // fallback to label foreground header.setTitleForeground(titleForeground != null ? titleForeground : UIManagerExt.getColor("Label.foreground")); } // description properties if (isUIInstallable(header.getDescriptionFont())) { Font descFont = UIManager.getFont("JXHeader.descriptionFont"); // fallback to label font header.setDescriptionFont(descFont != null ? descFont : UIManager .getFont("Label.font")); } if (isUIInstallable(header.getDescriptionForeground())) { Color descForeground = UIManagerExt .getColor("JXHeader.descriptionForeground"); // fallback to label foreground header.setDescriptionForeground(descForeground != null ? descForeground : UIManagerExt.getColor("Label.foreground")); } // icon label properties if (isUIInstallable(header.getIcon())) { header.setIcon(UIManager.getIcon("Header.defaultIcon")); } } /** * Uninstalls the given header's default properties. This implementation * does nothing. * * @param h the header to ininstall the properties from. */ protected void uninstallDefaults(JXHeader h) { } /** * Creates, configures, adds contained components. * PRE: header's default properties must be set before calling this. * * @param header the header to install the components into. */ protected void installComponents(JXHeader header) { titleLabel = new JLabel(); descriptionPane = new DescriptionPane(); imagePanel = new JLabel(); installComponentDefaults(header); header.setLayout(new GridBagLayout()); resetLayout(header); } /** * Unconfigures, removes and nulls contained components. * * @param header the header to install the components into. */ protected void uninstallComponents(JXHeader header) { uninstallComponentDefaults(header); header.remove(titleLabel); header.remove(descriptionPane); header.remove(imagePanel); titleLabel = null; descriptionPane = null; imagePanel = null; } /** * Configures the component default properties from the given header. * * @param header the header to install the components into. */ protected void installComponentDefaults(JXHeader header) { // JW: force a not UIResource for properties which have ui default values // like color, font, ?? titleLabel.setFont(getAsNotUIResource(header.getTitleFont())); titleLabel.setForeground(getAsNotUIResource(header.getTitleForeground())); titleLabel.setText(header.getTitle()); descriptionPane.setFont(getAsNotUIResource(header.getDescriptionFont())); descriptionPane.setForeground(getAsNotUIResource(header.getDescriptionForeground())); descriptionPane.setOpaque(false); descriptionPane.setText(header.getDescription()); descriptionPane.setLineWrap(true); imagePanel.setIcon(header.getIcon()); } /** * Returns a Font based on the param which is not of type UIResource. * * @param font the base font * @return a font not of type UIResource, may be null. */ private Font getAsNotUIResource(Font font) { if (!(font instanceof UIResource)) return font; // PENDING JW: correct way to create another font instance? return font.deriveFont(font.getAttributes()); } /** * Returns a Color based on the param which is not of type UIResource. * * @param color the base color * @return a color not of type UIResource, may be null. */ private Color getAsNotUIResource(Color color) { if (!(color instanceof UIResource)) return color; // PENDING JW: correct way to create another color instance? float[] rgb = color.getRGBComponents(null); return new Color(rgb[0], rgb[1], rgb[2], rgb[3]); } /** * Checks and returns whether the given property should be replaced * by the UI's default value.<p> * * PENDING JW: move as utility method ... where? * * @param property the property to check. * @return true if the given property should be replaced by the UI#s * default value, false otherwise. */ private boolean isUIInstallable(Object property) { return (property == null) || (property instanceof UIResource); } /** * Uninstalls component defaults. This implementation does nothing. * * @param header the header to uninstall from. */ protected void uninstallComponentDefaults(JXHeader header) { } protected void installListeners(final JXHeader header) { propListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { onPropertyChange(header, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); } }; boundsListener = new HierarchyBoundsAdapter() { @Override public void ancestorResized(HierarchyEvent e) { if (header == e.getComponent()) { View v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey); // view might get lost on LAF change ... if (v == null) { descriptionPane.putClientProperty(BasicHTML.propertyKey, descriptionPane.getMultiLineSupport().createView(descriptionPane)); v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey); } if (v != null) { Container tla = header.getTopLevelAncestor(); if (tla == null) { tla = header.getParent(); while (tla.getParent() != null) { tla = tla.getParent(); } } int h = Math.max(descriptionPane.getHeight(), tla.getHeight()); int w = Math.min(tla.getWidth(), header.getParent().getWidth()); // 35 = description pane insets, TODO: obtain dynamically w -= 35 + header.getInsets().left + header.getInsets().right + descriptionPane.getInsets().left + descriptionPane.getInsets().right + imagePanel.getInsets().left + imagePanel.getInsets().right + imagePanel.getWidth() + descriptionPane.getBounds().x; v.setSize(w, h); descriptionPane.setSize(w, (int) Math.ceil(v.getPreferredSpan(View.Y_AXIS))); } } }}; header.addPropertyChangeListener(propListener); header.addHierarchyBoundsListener(boundsListener); } protected void uninstallListeners(JXHeader h) { h.removePropertyChangeListener(propListener); h.removeHierarchyBoundsListener(boundsListener); } protected void onPropertyChange(JXHeader h, String propertyName, Object oldValue, final Object newValue) { if ("title".equals(propertyName)) { titleLabel.setText(h.getTitle()); } else if ("description".equals(propertyName)) { descriptionPane.setText(h.getDescription()); } else if ("icon".equals(propertyName)) { imagePanel.setIcon(h.getIcon()); } else if ("enabled".equals(propertyName)) { boolean enabled = h.isEnabled(); titleLabel.setEnabled(enabled); descriptionPane.setEnabled(enabled); imagePanel.setEnabled(enabled); } else if ("titleFont".equals(propertyName)) { titleLabel.setFont((Font)newValue); } else if ("descriptionFont".equals(propertyName)) { descriptionPane.setFont((Font)newValue); } else if ("titleForeground".equals(propertyName)) { titleLabel.setForeground((Color)newValue); } else if ("descriptionForeground".equals(propertyName)) { descriptionPane.setForeground((Color)newValue); } else if ("iconPosition".equals(propertyName)) { resetLayout(h); } } private void resetLayout(JXHeader h) { h.remove(titleLabel); h.remove(descriptionPane); h.remove(imagePanel); if (h.getIconPosition() == null || h.getIconPosition() == IconPosition.RIGHT) { h.add(titleLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 12, 0, 11), 0, 0)); h.add(descriptionPane, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 24, 12, 11), 0, 0)); h.add(imagePanel, new GridBagConstraints(1, 0, 1, 2, 0.0, 1.0, GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, new Insets(12, 0, 11, 11), 0, 0)); } else { h.add(titleLabel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 12, 0, 11), 0, 0)); h.add(descriptionPane, new GridBagConstraints(1, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 24, 12, 11), 0, 0)); h.add(imagePanel, new GridBagConstraints(0, 0, 1, 2, 0.0, 1.0, GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, new Insets(12, 11, 0, 11), 0, 0)); } } protected Painter createBackgroundPainter() { MattePainter p = new MattePainter(new GradientPaint(0, 0, gradientLightColor, 1, 0, gradientDarkColor)); p.setPaintStretched(true); return new PainterUIResource(p); } }