001/*
002 * $Id: BasicHeaderUI.java 3927 2011-02-22 16:34:11Z kleopatra $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021
022package org.jdesktop.swingx.plaf.basic;
023
024import java.awt.Color;
025import java.awt.Container;
026import java.awt.Font;
027import java.awt.GradientPaint;
028import java.awt.Graphics;
029import java.awt.GridBagConstraints;
030import java.awt.GridBagLayout;
031import java.awt.Insets;
032import java.awt.event.HierarchyBoundsAdapter;
033import java.awt.event.HierarchyBoundsListener;
034import java.awt.event.HierarchyEvent;
035import java.beans.PropertyChangeEvent;
036import java.beans.PropertyChangeListener;
037import java.util.logging.Logger;
038
039import javax.swing.JComponent;
040import javax.swing.JLabel;
041import javax.swing.UIManager;
042import javax.swing.plaf.ComponentUI;
043import javax.swing.plaf.UIResource;
044import javax.swing.plaf.basic.BasicHTML;
045import javax.swing.text.View;
046
047import org.jdesktop.swingx.JXHeader;
048import org.jdesktop.swingx.JXLabel;
049import org.jdesktop.swingx.JXHeader.IconPosition;
050import org.jdesktop.swingx.JXLabel.MultiLineSupport;
051import org.jdesktop.swingx.painter.MattePainter;
052import org.jdesktop.swingx.painter.Painter;
053import org.jdesktop.swingx.plaf.HeaderUI;
054import org.jdesktop.swingx.plaf.PainterUIResource;
055import org.jdesktop.swingx.plaf.UIManagerExt;
056
057/**
058 * Base implementation of <code>Header</code> UI. <p>
059 * 
060 * PENDING JW: This implementation is unusual in that it does not keep a reference
061 * to the component it controls. Typically, such is only the case if the ui is
062 * shared between instances. Historical? A consequence is that the un/install methods 
063 * need to carry the header as parameter. Which looks funny when at the same time 
064 * the children of the header are instance fields in this. Should think about cleanup:
065 * either get rid off the instance fields here, or reference the header and remove
066 * the param (would break subclasses).<p>
067 * 
068 * PENDING JW: keys for uidefaults are inconsistent - most have prefix "JXHeader." while
069 * defaultIcon has prefix "Header." <p>
070 * 
071 * @author rbair
072 * @author rah003
073 * @author Jeanette Winzenburg
074 */
075public class BasicHeaderUI extends HeaderUI {
076    @SuppressWarnings("unused")
077    private static final Logger LOG = Logger.getLogger(BasicHeaderUI.class
078            .getName());
079    // Implementation detail. Neeeded to expose getMultiLineSupport() method to allow restoring view
080    // lost after LAF switch
081    protected class DescriptionPane extends JXLabel {
082            @Override
083            public void paint(Graphics g) {
084                // switch off jxlabel default antialiasing
085                // JW: that cost me dearly to track down - it's the default foreground painter
086                // which is an AbstractPainter which has _global_ antialiased on by default
087                // and here the _text_ antialiased is turned off
088                // changed JXLabel default foregroundPainter to have antialiasing false by 
089                // default, so remove interference here
090                // part of fix for #920 - the other part is in JXLabel, fix 1164
091//                ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
092//                        RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
093                super.paint(g);
094            }
095
096            @Override
097            public MultiLineSupport getMultiLineSupport() {
098                return super.getMultiLineSupport();
099            }
100    }
101
102    protected JLabel titleLabel;
103    protected DescriptionPane descriptionPane;
104    protected JLabel imagePanel;
105    private PropertyChangeListener propListener;
106    private HierarchyBoundsListener boundsListener;
107    private Color gradientLightColor;
108    private Color gradientDarkColor;
109
110    /** Creates a new instance of BasicHeaderUI */
111    public BasicHeaderUI() {
112    }
113
114    /**
115     * Returns an instance of the UI delegate for the specified component.
116     * Each subclass must provide its own static <code>createUI</code>
117     * method that returns an instance of that UI delegate subclass.
118     * If the UI delegate subclass is stateless, it may return an instance
119     * that is shared by multiple components.  If the UI delegate is
120     * stateful, then it should return a new instance per component.
121     * The default implementation of this method throws an error, as it
122     * should never be invoked.
123     */
124    public static ComponentUI createUI(JComponent c) {
125        return new BasicHeaderUI();
126    }
127
128    /**
129     * Configures the specified component appropriate for the look and feel.
130     * This method is invoked when the <code>ComponentUI</code> instance is being installed
131     * as the UI delegate on the specified component.  This method should
132     * completely configure the component for the look and feel,
133     * including the following:
134     * <ol>
135     * <li>Install any default property values for color, fonts, borders,
136     *     icons, opacity, etc. on the component.  Whenever possible,
137     *     property values initialized by the client program should <i>not</i>
138     *     be overridden.
139     * <li>Install a <code>LayoutManager</code> on the component if necessary.
140     * <li>Create/add any required sub-components to the component.
141     * <li>Create/install event listeners on the component.
142     * <li>Create/install a <code>PropertyChangeListener</code> on the component in order
143     *     to detect and respond to component property changes appropriately.
144     * <li>Install keyboard UI (mnemonics, traversal, etc.) on the component.
145     * <li>Initialize any appropriate instance data.
146     * </ol>
147     * @param c the component where this UI delegate is being installed
148     *
149     * @see #uninstallUI
150     * @see javax.swing.JComponent#setUI
151     * @see javax.swing.JComponent#updateUI
152     */
153    @Override
154    public void installUI(JComponent c) {
155        super.installUI(c);
156        assert c instanceof JXHeader;
157        JXHeader header = (JXHeader)c;
158
159        installDefaults(header);
160        installComponents(header);
161        installListeners(header);
162    }
163
164    /**
165     * Reverses configuration which was done on the specified component during
166     * <code>installUI</code>.  This method is invoked when this
167     * <code>UIComponent</code> instance is being removed as the UI delegate
168     * for the specified component.  This method should undo the
169     * configuration performed in <code>installUI</code>, being careful to
170     * leave the <code>JComponent</code> instance in a clean state (no
171     * extraneous listeners, look-and-feel-specific property objects, etc.).
172     * This should include the following:
173     * <ol>
174     * <li>Remove any UI-set borders from the component.
175     * <li>Remove any UI-set layout managers on the component.
176     * <li>Remove any UI-added sub-components from the component.
177     * <li>Remove any UI-added event/property listeners from the component.
178     * <li>Remove any UI-installed keyboard UI from the component.
179     * <li>Nullify any allocated instance data objects to allow for GC.
180     * </ol>
181     * @param c the component from which this UI delegate is being removed;
182     *          this argument is often ignored,
183     *          but might be used if the UI object is stateless
184     *          and shared by multiple components
185     *
186     * @see #installUI
187     * @see javax.swing.JComponent#updateUI
188     */
189    @Override
190    public void uninstallUI(JComponent c) {
191        assert c instanceof JXHeader;
192        JXHeader header = (JXHeader)c;
193
194        uninstallListeners(header);
195        uninstallComponents(header);
196        uninstallDefaults(header);
197    }
198
199    /**
200     * Installs default header properties.
201     * <p>
202     * 
203     * NOTE: this method is called before the children are created, so must not
204     * try to access any of those!.
205     * 
206     * @param header the header to install.
207     */
208    protected void installDefaults(JXHeader header) {
209        gradientLightColor = UIManagerExt.getColor("JXHeader.startBackground");
210        if (gradientLightColor == null) {
211            // fallback to white
212            gradientLightColor = Color.WHITE;
213        }
214        gradientDarkColor = UIManagerExt.getColor("JXHeader.background");
215        // for backwards compatibility (mostly for substance and synthetica,
216        // I suspect) I'll fall back on the "control" color if
217        // JXHeader.background
218        // isn't specified.
219        if (gradientDarkColor == null) {
220            gradientDarkColor = UIManagerExt.getColor("control");
221        }
222
223        if (isUIInstallable(header.getBackgroundPainter())) {
224            header.setBackgroundPainter(createBackgroundPainter());
225        }
226
227        // title properties
228        if (isUIInstallable(header.getTitleFont())) {
229            Font titleFont = UIManager.getFont("JXHeader.titleFont");
230            // fallback to label font
231            header.setTitleFont(titleFont != null ? titleFont : UIManager
232                    .getFont("Label.font"));
233        }
234        if (isUIInstallable(header.getTitleForeground())) {
235            Color titleForeground = UIManagerExt
236                    .getColor("JXHeader.titleForeground");
237            // fallback to label foreground
238            header.setTitleForeground(titleForeground != null ? titleForeground
239                    : UIManagerExt.getColor("Label.foreground"));
240        }
241
242        // description properties
243        if (isUIInstallable(header.getDescriptionFont())) {
244            Font descFont = UIManager.getFont("JXHeader.descriptionFont");
245            // fallback to label font
246            header.setDescriptionFont(descFont != null ? descFont : UIManager
247                    .getFont("Label.font"));
248        }
249        if (isUIInstallable(header.getDescriptionForeground())) {
250            Color descForeground = UIManagerExt
251                    .getColor("JXHeader.descriptionForeground");
252            // fallback to label foreground
253            header.setDescriptionForeground(descForeground != null ? descForeground
254                    : UIManagerExt.getColor("Label.foreground"));
255        }
256        
257        // icon label properties
258        if (isUIInstallable(header.getIcon())) {
259            header.setIcon(UIManager.getIcon("Header.defaultIcon"));
260        }
261    }
262    
263    /**
264     * Uninstalls the given header's default properties. This implementation
265     * does nothing.
266     * 
267     * @param h the header to ininstall the properties from.
268     */
269    protected void uninstallDefaults(JXHeader h) {
270    }
271
272    /**
273     * Creates, configures, adds contained components.
274     * PRE: header's default properties must be set before calling this.
275     * 
276     * @param header the header to install the components into.
277     */
278    protected void installComponents(JXHeader header) {
279        titleLabel = new JLabel();
280        descriptionPane = new DescriptionPane();
281        imagePanel = new JLabel();
282        installComponentDefaults(header);
283        header.setLayout(new GridBagLayout());
284        resetLayout(header);
285    }
286
287    /**
288     * Unconfigures, removes and nulls contained components.
289     * 
290     * @param header the header to install the components into.
291     */
292    protected void uninstallComponents(JXHeader header) {
293        uninstallComponentDefaults(header);
294        header.remove(titleLabel);
295        header.remove(descriptionPane);
296        header.remove(imagePanel);
297        titleLabel = null;
298        descriptionPane = null;
299        imagePanel = null;
300    }
301
302    /**
303     * Configures the component default properties from the given header.
304     * 
305     * @param header the header to install the components into.
306     */
307    protected void installComponentDefaults(JXHeader header) {
308        // JW: force a not UIResource for properties which have ui default values
309        // like color, font, ??
310        titleLabel.setFont(getAsNotUIResource(header.getTitleFont()));
311        titleLabel.setForeground(getAsNotUIResource(header.getTitleForeground()));
312        titleLabel.setText(header.getTitle());
313        descriptionPane.setFont(getAsNotUIResource(header.getDescriptionFont()));
314        descriptionPane.setForeground(getAsNotUIResource(header.getDescriptionForeground()));
315        descriptionPane.setOpaque(false);
316        descriptionPane.setText(header.getDescription());
317        descriptionPane.setLineWrap(true);
318
319        imagePanel.setIcon(header.getIcon());
320
321    }
322    
323    /**
324     * Returns a Font based on the param which is not of type UIResource. 
325     * 
326     * @param font the base font
327     * @return a font not of type UIResource, may be null.
328     */
329    private Font getAsNotUIResource(Font font) {
330        if (!(font instanceof UIResource)) return font;
331        // PENDING JW: correct way to create another font instance?
332       return font.deriveFont(font.getAttributes());
333    }
334    
335    /**
336     * Returns a Color based on the param which is not of type UIResource. 
337     * 
338     * @param color the base color
339     * @return a color not of type UIResource, may be null.
340     */
341    private Color getAsNotUIResource(Color color) {
342        if (!(color instanceof UIResource)) return color;
343        // PENDING JW: correct way to create another color instance?
344        float[] rgb = color.getRGBComponents(null);
345        return new Color(rgb[0], rgb[1], rgb[2], rgb[3]);
346    }
347    
348    /**
349     * Checks and returns whether the given property should be replaced
350     * by the UI's default value.<p>
351     * 
352     * PENDING JW: move as utility method ... where?
353     * 
354     * @param property the property to check.
355     * @return true if the given property should be replaced by the UI#s
356     *   default value, false otherwise. 
357     */
358    private boolean isUIInstallable(Object property) {
359       return (property == null) || (property instanceof UIResource);
360    }
361    
362    /**
363     * Uninstalls component defaults. This implementation does nothing.
364     * 
365     * @param header the header to uninstall from.
366     */
367    protected void uninstallComponentDefaults(JXHeader header) {
368    }
369
370
371    protected void installListeners(final JXHeader header) {
372        propListener = new PropertyChangeListener() {
373            @Override
374            public void propertyChange(PropertyChangeEvent evt) {
375                onPropertyChange(header, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
376            }
377        };
378        boundsListener = new HierarchyBoundsAdapter() {
379            @Override
380            public void ancestorResized(HierarchyEvent e) {
381                if (header == e.getComponent()) {
382                    View v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey);
383                    // view might get lost on LAF change ...
384                    if (v == null) {
385                        descriptionPane.putClientProperty(BasicHTML.propertyKey, 
386                                MultiLineSupport.createView(descriptionPane));
387                        v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey);
388                    }
389                    if (v != null) {
390                        Container tla = header.getTopLevelAncestor();
391                        if (tla == null) {
392                            tla = header.getParent();
393                            while (tla.getParent() != null) {
394                                tla = tla.getParent();
395                            }
396                        }
397                        int h = Math.max(descriptionPane.getHeight(), tla.getHeight());
398                        int w = Math.min(tla.getWidth(), header.getParent().getWidth());
399                        // 35 = description pane insets, TODO: obtain dynamically
400                        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;
401                        v.setSize(w, h);
402                        descriptionPane.setSize(w, (int) Math.ceil(v.getPreferredSpan(View.Y_AXIS)));
403                    }
404                }
405            }};
406        header.addPropertyChangeListener(propListener);
407        header.addHierarchyBoundsListener(boundsListener);
408    }
409
410    protected void uninstallListeners(JXHeader h) {
411        h.removePropertyChangeListener(propListener);
412        h.removeHierarchyBoundsListener(boundsListener);
413    }
414
415    protected void onPropertyChange(JXHeader h, String propertyName, Object oldValue, final Object newValue) {
416        if ("title".equals(propertyName)) {
417            titleLabel.setText(h.getTitle());
418        } else if ("description".equals(propertyName)) {
419            descriptionPane.setText(h.getDescription());
420        } else if ("icon".equals(propertyName)) {
421            imagePanel.setIcon(h.getIcon());
422        } else if ("enabled".equals(propertyName)) {
423            boolean enabled = h.isEnabled();
424            titleLabel.setEnabled(enabled);
425            descriptionPane.setEnabled(enabled);
426            imagePanel.setEnabled(enabled);
427        } else if ("titleFont".equals(propertyName)) {
428            titleLabel.setFont((Font)newValue);
429        } else if ("descriptionFont".equals(propertyName)) {
430            descriptionPane.setFont((Font)newValue);
431        } else if ("titleForeground".equals(propertyName)) {
432            titleLabel.setForeground((Color)newValue);
433        } else if ("descriptionForeground".equals(propertyName)) {
434            descriptionPane.setForeground((Color)newValue);
435        } else if ("iconPosition".equals(propertyName)) {
436            resetLayout(h);
437        }
438    }
439
440    private void resetLayout(JXHeader h) {
441        h.remove(titleLabel);
442        h.remove(descriptionPane);
443        h.remove(imagePanel);
444        if (h.getIconPosition() == null || h.getIconPosition() == IconPosition.RIGHT) {
445            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));
446            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));
447            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));
448        } else {
449            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));
450            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));
451            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));
452        }
453    }
454    
455    
456
457    protected Painter createBackgroundPainter() {
458        MattePainter p = new MattePainter(new GradientPaint(0, 0, gradientLightColor, 1, 0, gradientDarkColor));
459        p.setPaintStretched(true);
460        return new PainterUIResource(p);
461    }
462    
463    
464}