001    /*
002     * $Id: BasicTitledPanelUI.java 3307 2009-03-23 13:28:30Z 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    
022    package org.jdesktop.swingx.plaf.basic;
023    
024    import java.awt.BorderLayout;
025    import java.awt.Color;
026    import java.awt.Container;
027    import java.awt.Font;
028    import java.awt.Graphics;
029    import java.awt.GridBagConstraints;
030    import java.awt.GridBagLayout;
031    import java.awt.Insets;
032    import java.beans.BeanInfo;
033    import java.beans.Introspector;
034    import java.beans.PropertyChangeEvent;
035    import java.beans.PropertyChangeListener;
036    import java.beans.PropertyDescriptor;
037    import java.lang.reflect.Method;
038    import java.util.logging.Level;
039    import java.util.logging.Logger;
040    
041    import javax.swing.BorderFactory;
042    import javax.swing.JComponent;
043    import javax.swing.JLabel;
044    import javax.swing.LookAndFeel;
045    import javax.swing.UIManager;
046    import javax.swing.plaf.BorderUIResource;
047    import javax.swing.plaf.ComponentUI;
048    import javax.swing.plaf.UIResource;
049    
050    import org.jdesktop.swingx.JXPanel;
051    import org.jdesktop.swingx.JXTitledPanel;
052    import org.jdesktop.swingx.SwingXUtilities;
053    import 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     */
067    public 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                public void propertyChange(PropertyChangeEvent evt) {
251                    if (evt.getPropertyName().equals("title")) {
252                        caption.setText((String)evt.getNewValue());
253                    } else if (evt.getPropertyName().equals("titleForeground")) {
254                        caption.setForeground((Color)evt.getNewValue());
255                    } else if (evt.getPropertyName().equals("titleFont")) {
256                        caption.setFont((Font)evt.getNewValue());
257                    } else if ("titlePainter".equals(evt.getPropertyName())) {
258                        topPanel.setBackgroundPainter(titledPanel.getTitlePainter());
259                        topPanel.repaint();
260                    }
261                }
262            };
263            titledPanel.addPropertyChangeListener(titleChangeListener);
264        }
265        
266        protected void uninstallListeners(JXTitledPanel titledPanel) {
267            titledPanel.removePropertyChangeListener(titleChangeListener);
268        }
269        
270        protected void installProperty(JComponent c, String propName, Object value) {
271            try {
272                BeanInfo bi = Introspector.getBeanInfo(c.getClass());
273                for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
274                    if (pd.getName().equals(propName)) {
275                        Method m = pd.getReadMethod();
276                        Object oldVal = m.invoke(c);
277                        if (oldVal == null || oldVal instanceof UIResource) {
278                            m = pd.getWriteMethod();
279                            m.invoke(c, value);
280                        }
281                    }
282                }
283            } catch (Exception e) {
284                LOG.log(Level.FINE, "Failed to install property " + propName, e);
285            }
286        }
287        
288        /**
289         * Paints the specified component appropriate for the look and feel.
290         * This method is invoked from the <code>ComponentUI.update</code> method when
291         * the specified component is being painted.  Subclasses should override
292         * this method and use the specified <code>Graphics</code> object to
293         * render the content of the component.<p>
294         * 
295         * PENDING JW: we don't need this, do we - remove!
296         *
297         * @param g the <code>Graphics</code> context in which to paint
298         * @param c the component being painted;
299         *          this argument is often ignored,
300         *          but might be used if the UI object is stateless
301         *          and shared by multiple components
302         *
303         * @see #update
304         */
305        @Override
306        public void paint(Graphics g, JComponent c) {
307            super.paint(g, c);
308        }
309        
310        /**
311         * Adds the given JComponent as a decoration on the right of the title
312         * @param decoration
313         */
314        @Override
315        public void setRightDecoration(JComponent decoration) {
316            if (right != null) topPanel.remove(right);
317            right = decoration;
318            if (right != null) {
319                topPanel.add(decoration, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, UIManager.getInsets("JXTitledPanel.rightDecorationInsets"), 0, 0));
320                
321            }
322        }
323        
324        @Override
325        public JComponent getRightDecoration() {
326            return right;
327        }
328        
329        /**
330         * Adds the given JComponent as a decoration on the left of the title
331         * @param decoration
332         */
333        @Override
334        public void setLeftDecoration(JComponent decoration) {
335            if (left != null) topPanel.remove(left);
336            left = decoration;
337            if (left != null) {
338                topPanel.add(left, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, UIManager.getInsets("JXTitledPanel.leftDecorationInsets"), 0, 0));
339            }
340        }
341        
342        @Override
343        public JComponent getLeftDecoration() {
344            return left;
345        }
346        
347        /**
348         * @return the Container acting as the title bar for this component
349         */
350        @Override
351        public Container getTitleBar() {
352            return topPanel;
353        }
354    }