001/*
002 * $Id: BasicTitledPanelUI.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.BorderLayout;
025import java.awt.Color;
026import java.awt.Container;
027import java.awt.Font;
028import java.awt.Graphics;
029import java.awt.GridBagConstraints;
030import java.awt.GridBagLayout;
031import java.awt.Insets;
032import java.beans.BeanInfo;
033import java.beans.Introspector;
034import java.beans.PropertyChangeEvent;
035import java.beans.PropertyChangeListener;
036import java.beans.PropertyDescriptor;
037import java.lang.reflect.Method;
038import java.util.logging.Level;
039import java.util.logging.Logger;
040
041import javax.swing.BorderFactory;
042import javax.swing.JComponent;
043import javax.swing.JLabel;
044import javax.swing.LookAndFeel;
045import javax.swing.UIManager;
046import javax.swing.plaf.BorderUIResource;
047import javax.swing.plaf.ComponentUI;
048import javax.swing.plaf.UIResource;
049
050import org.jdesktop.swingx.JXPanel;
051import org.jdesktop.swingx.JXTitledPanel;
052import org.jdesktop.swingx.SwingXUtilities;
053import org.jdesktop.swingx.plaf.TitledPanelUI;
054
055
056/**
057 * All TitledPanels contain a title section and a content section. The default
058 * implementation for the title section relies on a Gradient background. All
059 * title sections can have components embedded to the "left" or
060 * "right" of the Title.
061 *
062 * @author Richard Bair
063 * @author Jeanette Winzenburg
064 * @author rah003
065 *
066 */
067public class BasicTitledPanelUI extends TitledPanelUI {
068    private static final Logger LOG = Logger.getLogger(BasicTitledPanelUI.class.getName());
069    
070    /**
071     * JLabel used for the title in the Title section of the JTitledPanel.
072     */
073    protected JLabel caption;
074    /**
075     * The Title section panel.
076     */
077    protected JXPanel topPanel;
078    /**
079     * Listens to changes in the title of the JXTitledPanel component
080     */
081    protected PropertyChangeListener titleChangeListener;
082
083    protected JComponent left;
084    protected JComponent right;
085    
086    /** Creates a new instance of BasicTitledPanelUI */
087    public BasicTitledPanelUI() {
088    }
089    
090    /**
091     * Returns an instance of the UI delegate for the specified component.
092     * Each subclass must provide its own static <code>createUI</code>
093     * method that returns an instance of that UI delegate subclass.
094     * If the UI delegate subclass is stateless, it may return an instance
095     * that is shared by multiple components.  If the UI delegate is
096     * stateful, then it should return a new instance per component.
097     * The default implementation of this method throws an error, as it
098     * should never be invoked.
099     */
100    public static ComponentUI createUI(JComponent c) {
101        return new BasicTitledPanelUI();
102    }
103    /**
104     * Configures the specified component appropriate for the look and feel.
105     * This method is invoked when the <code>ComponentUI</code> instance is being installed
106     * as the UI delegate on the specified component.  This method should
107     * completely configure the component for the look and feel,
108     * including the following:
109     * <ol>
110     * <li>Install any default property values for color, fonts, borders,
111     *     icons, opacity, etc. on the component.  Whenever possible,
112     *     property values initialized by the client program should <i>not</i>
113     *     be overridden.
114     * <li>Install a <code>LayoutManager</code> on the component if necessary.
115     * <li>Create/add any required sub-components to the component.
116     * <li>Create/install event listeners on the component.
117     * <li>Create/install a <code>PropertyChangeListener</code> on the component in order
118     *     to detect and respond to component property changes appropriately.
119     * <li>Install keyboard UI (mnemonics, traversal, etc.) on the component.
120     * <li>Initialize any appropriate instance data.
121     * </ol>
122     * @param c the component where this UI delegate is being installed
123     *
124     * @see #uninstallUI
125     * @see javax.swing.JComponent#setUI
126     * @see javax.swing.JComponent#updateUI
127     */
128    @Override
129    public void installUI(JComponent c) {
130        assert c instanceof JXTitledPanel;
131        JXTitledPanel titledPanel = (JXTitledPanel)c;
132        installDefaults(titledPanel);
133        
134        caption = createAndConfigureCaption(titledPanel);
135        topPanel = createAndConfigureTopPanel(titledPanel);
136        
137        installComponents(titledPanel);
138        installListeners(titledPanel);
139    }
140
141    protected void installDefaults(JXTitledPanel titledPanel) {
142        installProperty(titledPanel, "titlePainter", UIManager.get("JXTitledPanel.titlePainter"));
143        installProperty(titledPanel, "titleForeground", UIManager.getColor("JXTitledPanel.titleForeground"));
144        installProperty(titledPanel, "titleFont", UIManager.getFont("JXTitledPanel.titleFont"));
145        LookAndFeel.installProperty(titledPanel, "opaque", false);
146    }
147
148    protected void uninstallDefaults(JXTitledPanel titledPanel) {
149    }
150
151    protected void installComponents(JXTitledPanel titledPanel) {
152        topPanel.add(caption, new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, 
153                GridBagConstraints.HORIZONTAL, getCaptionInsets(), 0, 0));
154        if (titledPanel.getClientProperty(JXTitledPanel.RIGHT_DECORATION) instanceof JComponent) {
155            setRightDecoration((JComponent) titledPanel.getClientProperty(JXTitledPanel.RIGHT_DECORATION));
156        }
157        if (titledPanel.getClientProperty(JXTitledPanel.LEFT_DECORATION) instanceof JComponent) {
158            setLeftDecoration((JComponent) titledPanel.getClientProperty(JXTitledPanel.LEFT_DECORATION));
159        }
160        // swingx#500
161        if (!(titledPanel.getLayout() instanceof BorderLayout)){
162            titledPanel.setLayout(new BorderLayout());
163        }
164        titledPanel.add(topPanel, BorderLayout.NORTH);
165        // fix #1063-swingx: must respect custom border
166        if (SwingXUtilities.isUIInstallable(titledPanel.getBorder())) {
167            // use uiresource border 
168            // old was: BorderFactory.createRaisedBevelBorder());
169            titledPanel.setBorder(BorderUIResource.getRaisedBevelBorderUIResource());
170        }
171    }
172    
173    protected void uninstallComponents(JXTitledPanel titledPanel) {
174        titledPanel.remove(topPanel);
175    }
176
177    protected Insets getCaptionInsets() {
178      return UIManager.getInsets("JXTitledPanel.captionInsets"); 
179    }
180    
181    protected JXPanel createAndConfigureTopPanel(JXTitledPanel titledPanel) {
182        JXPanel topPanel = new JXPanel();
183        topPanel.setBackgroundPainter(titledPanel.getTitlePainter());
184        topPanel.setBorder(BorderFactory.createEmptyBorder());
185        topPanel.setLayout(new GridBagLayout());
186        topPanel.setOpaque(false);
187        return topPanel;
188    }
189    
190    protected JLabel createAndConfigureCaption(final JXTitledPanel titledPanel) {
191        JLabel caption = new JLabel(titledPanel.getTitle()){
192            //#501
193            @Override
194            public void updateUI(){
195              super.updateUI();
196              setForeground(titledPanel.getTitleForeground());
197              setFont(titledPanel.getTitleFont());
198            } 
199          };
200        caption.setFont(titledPanel.getTitleFont());
201        caption.setForeground(titledPanel.getTitleForeground());
202        return caption;
203    }
204    
205    /**
206     * Reverses configuration which was done on the specified component during
207     * <code>installUI</code>.  This method is invoked when this
208     * <code>UIComponent</code> instance is being removed as the UI delegate
209     * for the specified component.  This method should undo the
210     * configuration performed in <code>installUI</code>, being careful to
211     * leave the <code>JComponent</code> instance in a clean state (no
212     * extraneous listeners, look-and-feel-specific property objects, etc.).
213     * This should include the following:
214     * <ol>
215     * <li>Remove any UI-set borders from the component.
216     * <li>Remove any UI-set layout managers on the component.
217     * <li>Remove any UI-added sub-components from the component.
218     * <li>Remove any UI-added event/property listeners from the component.
219     * <li>Remove any UI-installed keyboard UI from the component.
220     * <li>Nullify any allocated instance data objects to allow for GC.
221     * </ol>
222     * @param c the component from which this UI delegate is being removed;
223     *          this argument is often ignored,
224     *          but might be used if the UI object is stateless
225     *          and shared by multiple components
226     *
227     * @see #installUI
228     * @see javax.swing.JComponent#updateUI
229     */
230    @Override
231    public void uninstallUI(JComponent c) {
232        assert c instanceof JXTitledPanel;
233        JXTitledPanel titledPanel = (JXTitledPanel) c;
234        uninstallListeners(titledPanel);
235        // JW: this is needed to make the gradient paint work correctly...
236        // LF changes will remove the left/right components...
237        topPanel.removeAll();
238        titledPanel.remove(topPanel);
239        titledPanel.putClientProperty(JXTitledPanel.LEFT_DECORATION, left);
240        titledPanel.putClientProperty(JXTitledPanel.RIGHT_DECORATION, right);
241        caption =  null;
242        topPanel = null;
243        titledPanel = null;
244        left = null;
245        right = null;
246    }
247    
248    protected void installListeners(final JXTitledPanel titledPanel) {
249        titleChangeListener = new PropertyChangeListener() {
250            @Override
251            public void propertyChange(PropertyChangeEvent evt) {
252                if (evt.getPropertyName().equals("title")) {
253                    caption.setText((String)evt.getNewValue());
254                } else if (evt.getPropertyName().equals("titleForeground")) {
255                    caption.setForeground((Color)evt.getNewValue());
256                } else if (evt.getPropertyName().equals("titleFont")) {
257                    caption.setFont((Font)evt.getNewValue());
258                } else if ("titlePainter".equals(evt.getPropertyName())) {
259                    topPanel.setBackgroundPainter(titledPanel.getTitlePainter());
260                    topPanel.repaint();
261                }
262            }
263        };
264        titledPanel.addPropertyChangeListener(titleChangeListener);
265    }
266    
267    protected void uninstallListeners(JXTitledPanel titledPanel) {
268        titledPanel.removePropertyChangeListener(titleChangeListener);
269    }
270    
271    protected void installProperty(JComponent c, String propName, Object value) {
272        try {
273            BeanInfo bi = Introspector.getBeanInfo(c.getClass());
274            for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
275                if (pd.getName().equals(propName)) {
276                    Method m = pd.getReadMethod();
277                    Object oldVal = m.invoke(c);
278                    if (oldVal == null || oldVal instanceof UIResource) {
279                        m = pd.getWriteMethod();
280                        m.invoke(c, value);
281                    }
282                }
283            }
284        } catch (Exception e) {
285            LOG.log(Level.FINE, "Failed to install property " + propName, e);
286        }
287    }
288    
289    /**
290     * Paints the specified component appropriate for the look and feel.
291     * This method is invoked from the <code>ComponentUI.update</code> method when
292     * the specified component is being painted.  Subclasses should override
293     * this method and use the specified <code>Graphics</code> object to
294     * render the content of the component.<p>
295     * 
296     * PENDING JW: we don't need this, do we - remove!
297     *
298     * @param g the <code>Graphics</code> context in which to paint
299     * @param c the component being painted;
300     *          this argument is often ignored,
301     *          but might be used if the UI object is stateless
302     *          and shared by multiple components
303     *
304     * @see #update
305     */
306    @Override
307    public void paint(Graphics g, JComponent c) {
308        super.paint(g, c);
309    }
310    
311    /**
312     * Adds the given JComponent as a decoration on the right of the title
313     * @param decoration
314     */
315    @Override
316    public void setRightDecoration(JComponent decoration) {
317        if (right != null) topPanel.remove(right);
318        right = decoration;
319        if (right != null) {
320            topPanel.add(decoration, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, UIManager.getInsets("JXTitledPanel.rightDecorationInsets"), 0, 0));
321            
322        }
323    }
324    
325    @Override
326    public JComponent getRightDecoration() {
327        return right;
328    }
329    
330    /**
331     * Adds the given JComponent as a decoration on the left of the title
332     * @param decoration
333     */
334    @Override
335    public void setLeftDecoration(JComponent decoration) {
336        if (left != null) topPanel.remove(left);
337        left = decoration;
338        if (left != null) {
339            topPanel.add(left, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, UIManager.getInsets("JXTitledPanel.leftDecorationInsets"), 0, 0));
340        }
341    }
342    
343    @Override
344    public JComponent getLeftDecoration() {
345        return left;
346    }
347    
348    /**
349     * @return the Container acting as the title bar for this component
350     */
351    @Override
352    public Container getTitleBar() {
353        return topPanel;
354    }
355}