001    /*
002     * $Id: BasicHeaderUI.java 3166 2009-01-02 13:27:18Z rah003 $
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    
022    package org.jdesktop.swingx.plaf.basic;
023    
024    import java.awt.Color;
025    import java.awt.Container;
026    import java.awt.Font;
027    import java.awt.GradientPaint;
028    import java.awt.Graphics;
029    import java.awt.Graphics2D;
030    import java.awt.GridBagConstraints;
031    import java.awt.GridBagLayout;
032    import java.awt.Insets;
033    import java.awt.RenderingHints;
034    import java.awt.event.HierarchyBoundsAdapter;
035    import java.awt.event.HierarchyBoundsListener;
036    import java.awt.event.HierarchyEvent;
037    import java.beans.PropertyChangeEvent;
038    import java.beans.PropertyChangeListener;
039    import java.util.logging.Logger;
040    
041    import javax.swing.JComponent;
042    import javax.swing.JLabel;
043    import javax.swing.UIManager;
044    import javax.swing.plaf.ComponentUI;
045    import javax.swing.plaf.UIResource;
046    import javax.swing.plaf.basic.BasicHTML;
047    import javax.swing.text.View;
048    
049    import org.jdesktop.swingx.JXHeader;
050    import org.jdesktop.swingx.JXLabel;
051    import org.jdesktop.swingx.JXHeader.IconPosition;
052    import org.jdesktop.swingx.painter.MattePainter;
053    import org.jdesktop.swingx.painter.Painter;
054    import org.jdesktop.swingx.plaf.HeaderUI;
055    import org.jdesktop.swingx.plaf.PainterUIResource;
056    import org.jdesktop.swingx.plaf.UIManagerExt;
057    
058    /**
059     * Base implementation of <code>Header</code> UI. <p>
060     * 
061     * PENDING JW: This implementation is unusual in that it does not keep a reference
062     * to the component it controls. Typically, such is only the case if the ui is
063     * shared between instances. Historical? A consequence is that the un/install methods 
064     * need to carry the header as parameter. Which looks funny when at the same time 
065     * the children of the header are instance fields in this. Should think about cleanup:
066     * either get rid off the instance fields here, or reference the header and remove
067     * the param (would break subclasses).<p>
068     * 
069     * PENDING JW: keys for uidefaults are inconsistent - most have prefix "JXHeader." while
070     * defaultIcon has prefix "Header." <p>
071     * 
072     * @author rbair
073     * @author rah003
074     * @author Jeanette Winzenburg
075     */
076    public class BasicHeaderUI extends HeaderUI {
077        @SuppressWarnings("unused")
078        private static final Logger LOG = Logger.getLogger(BasicHeaderUI.class
079                .getName());
080        // Implementation detail. Neeeded to expose getMultiLineSupport() method to allow restoring view
081        // lost after LAF switch
082        protected class DescriptionPane extends JXLabel {
083                @Override
084                public void paint(Graphics g) {
085    //                LOG.info(getText() + ": all hints " + ((Graphics2D)g).getRenderingHints()
086    //                        + "\n     " + ": aliased " + ((Graphics2D)g).getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING));
087                    // switch off jxlabel default antialiasing
088                    // JW: that cost me dearly to track down - it's the default foreground painter
089                    // which is an AbstractPainter which has _global_ antialiased on by default
090                    // and here the _text_ antialiased is turned off
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                public void propertyChange(PropertyChangeEvent evt) {
374                    onPropertyChange(header, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
375                }
376            };
377            boundsListener = new HierarchyBoundsAdapter() {
378                @Override
379                public void ancestorResized(HierarchyEvent e) {
380                    if (header == e.getComponent()) {
381                        View v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey);
382                        // view might get lost on LAF change ...
383                        if (v == null) {
384                            descriptionPane.putClientProperty(BasicHTML.propertyKey, 
385                                    descriptionPane.getMultiLineSupport().createView(descriptionPane));
386                            v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey);
387                        }
388                        if (v != null) {
389                            Container tla = header.getTopLevelAncestor();
390                            if (tla == null) {
391                                tla = header.getParent();
392                                while (tla.getParent() != null) {
393                                    tla = tla.getParent();
394                                }
395                            }
396                            int h = Math.max(descriptionPane.getHeight(), tla.getHeight());
397                            int w = Math.min(tla.getWidth(), header.getParent().getWidth());
398                            // 35 = description pane insets, TODO: obtain dynamically
399                            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;
400                            v.setSize(w, h);
401                            descriptionPane.setSize(w, (int) Math.ceil(v.getPreferredSpan(View.Y_AXIS)));
402                        }
403                    }
404                }};
405            header.addPropertyChangeListener(propListener);
406            header.addHierarchyBoundsListener(boundsListener);
407        }
408    
409        protected void uninstallListeners(JXHeader h) {
410            h.removePropertyChangeListener(propListener);
411            h.removeHierarchyBoundsListener(boundsListener);
412        }
413    
414        protected void onPropertyChange(JXHeader h, String propertyName, Object oldValue, final Object newValue) {
415            if ("title".equals(propertyName)) {
416                titleLabel.setText(h.getTitle());
417            } else if ("description".equals(propertyName)) {
418                descriptionPane.setText(h.getDescription());
419            } else if ("icon".equals(propertyName)) {
420                imagePanel.setIcon(h.getIcon());
421            } else if ("enabled".equals(propertyName)) {
422                boolean enabled = h.isEnabled();
423                titleLabel.setEnabled(enabled);
424                descriptionPane.setEnabled(enabled);
425                imagePanel.setEnabled(enabled);
426            } else if ("titleFont".equals(propertyName)) {
427                titleLabel.setFont((Font)newValue);
428            } else if ("descriptionFont".equals(propertyName)) {
429                descriptionPane.setFont((Font)newValue);
430            } else if ("titleForeground".equals(propertyName)) {
431                titleLabel.setForeground((Color)newValue);
432            } else if ("descriptionForeground".equals(propertyName)) {
433                descriptionPane.setForeground((Color)newValue);
434            } else if ("iconPosition".equals(propertyName)) {
435                resetLayout(h);
436            }
437        }
438    
439        private void resetLayout(JXHeader h) {
440            h.remove(titleLabel);
441            h.remove(descriptionPane);
442            h.remove(imagePanel);
443            if (h.getIconPosition() == null || h.getIconPosition() == IconPosition.RIGHT) {
444                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));
445                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));
446                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));
447            } else {
448                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));
449                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));
450                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));
451            }
452        }
453        
454        
455    
456        protected Painter createBackgroundPainter() {
457            MattePainter p = new MattePainter(new GradientPaint(0, 0, gradientLightColor, 1, 0, gradientDarkColor));
458            p.setPaintStretched(true);
459            return new PainterUIResource(p);
460        }
461        
462        
463    }