001    /*
002     * Copyright (c) 2003 JGoodies Karsten Lentzsch. All Rights Reserved.
003     *
004     * Redistribution and use in source and binary forms, with or without 
005     * modification, are permitted provided that the following conditions are met:
006     * 
007     *  o Redistributions of source code must retain the above copyright notice, 
008     *    this list of conditions and the following disclaimer. 
009     *     
010     *  o Redistributions in binary form must reproduce the above copyright notice, 
011     *    this list of conditions and the following disclaimer in the documentation 
012     *    and/or other materials provided with the distribution. 
013     *     
014     *  o Neither the name of JGoodies Karsten Lentzsch nor the names of 
015     *    its contributors may be used to endorse or promote products derived 
016     *    from this software without specific prior written permission. 
017     *     
018     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
019     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
020     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
021     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
022     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
023     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
024     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
025     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
027     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
028     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
029     */
030    package org.springframework.richclient.components;
031    
032    import java.awt.BorderLayout;
033    import java.awt.Color;
034    import java.awt.Component;
035    import java.awt.GradientPaint;
036    import java.awt.Graphics;
037    import java.awt.Graphics2D;
038    import java.awt.Insets;
039    import java.awt.LayoutManager;
040    import java.awt.Paint;
041    
042    import javax.swing.BorderFactory;
043    import javax.swing.Icon;
044    import javax.swing.JComponent;
045    import javax.swing.JLabel;
046    import javax.swing.JPanel;
047    import javax.swing.JToolBar;
048    import javax.swing.SwingConstants;
049    import javax.swing.UIManager;
050    import javax.swing.border.AbstractBorder;
051    
052    /**
053     * A <code>JPanel</code> subclass that has a drop shadow border and that
054     * provides a header with icon, title and tool bar.
055     * <p>
056     * 
057     * This class can be used to replace the <code>JInternalFrame</code>, for use
058     * outside of a <code>JDesktopPane</code>. The
059     * <code>SimpleInternalFrame</code> is less flexible but often more usable; it
060     * avoids overlapping windows and scales well up to IDE size. Several customers
061     * have reported that they and their clients feel much better with both the
062     * appearance and the UI feel.
063     * <p>
064     * 
065     * The SimpleInternalFrame provides the following bound properties:
066     * <i>frameIcon, title, toolBar, content, selected. </i>
067     * <p>
068     * 
069     * By default the SimpleInternalFrame is in <i>selected </i> state. If you don't
070     * do anything, multiple simple internal frames will be displayed as selected.
071     * 
072     * @author Karsten Lentzsch
073     * @version $Revision: 2105 $
074     * 
075     * @see javax.swing.JInternalFrame
076     * @see javax.swing.JDesktopPane
077     */
078    
079    public class SimpleInternalFrame extends JPanel {
080    
081        private static final long serialVersionUID = 663704388460231166L;
082    
083        private JLabel titleLabel;
084    
085        private GradientPanel gradientPanel;
086    
087        private JPanel headerPanel;
088    
089        private boolean isSelected;
090    
091        // Instance Creation ****************************************************
092    
093        public SimpleInternalFrame() {
094            this("");
095        }
096    
097        /**
098         * Constructs a <code>SimpleInternalFrame</code> with the specified title.
099         * 
100         * @param title
101         *            the initial title
102         */
103        public SimpleInternalFrame(String title) {
104            this(null, title, null, null);
105        }
106    
107        /**
108         * Constructs a <code>SimpleInternalFrame</code> with the specified icon,
109         * and title.
110         * 
111         * @param icon
112         *            the initial icon
113         * @param title
114         *            the initial title
115         */
116        public SimpleInternalFrame(Icon icon, String title) {
117            this(icon, title, null, null);
118        }
119    
120        /**
121         * Constructs a <code>SimpleInternalFrame</code> with the specified title,
122         * tool bar, and content panel.
123         * 
124         * @param title
125         *            the initial title
126         * @param bar
127         *            the initial tool bar
128         * @param content
129         *            the initial content pane
130         */
131        public SimpleInternalFrame(String title, JToolBar bar, JComponent content) {
132            this(null, title, bar, content);
133        }
134    
135        /**
136         * Constructs a <code>SimpleInternalFrame</code> with the specified icon,
137         * title, tool bar, and content panel.
138         * 
139         * @param icon
140         *            the initial icon
141         * @param title
142         *            the initial title
143         * @param bar
144         *            the initial tool bar
145         * @param content
146         *            the initial content pane
147         */
148        public SimpleInternalFrame(Icon icon, String title, JToolBar bar, JComponent content) {
149            super(new BorderLayout());
150            this.isSelected = false;
151            this.titleLabel = new JLabel(title, icon, SwingConstants.LEADING);
152            JPanel top = buildHeader(titleLabel, bar);
153    
154            add(top, BorderLayout.NORTH);
155            if (content != null) {
156                setContent(content);
157            }
158            setBorder(new ShadowBorder());
159            setSelected(true);
160            updateHeader();
161        }
162    
163        // Public API ***********************************************************
164    
165        /**
166         * Returns the frame's icon.
167         * 
168         * @return the frame's icon
169         */
170        public Icon getFrameIcon() {
171            return titleLabel.getIcon();
172        }
173    
174        /**
175         * Sets a new frame icon.
176         * 
177         * @param newIcon
178         *            the icon to be set
179         */
180        public void setFrameIcon(Icon newIcon) {
181            Icon oldIcon = getFrameIcon();
182            titleLabel.setIcon(newIcon);
183            firePropertyChange("frameIcon", oldIcon, newIcon);
184        }
185    
186        /**
187         * Returns the frame's title text.
188         * 
189         * @return String the current title text
190         */
191        public String getTitle() {
192            return titleLabel.getText();
193        }
194    
195        /**
196         * Sets a new title text.
197         * 
198         * @param newText
199         *            the title text tp be set
200         */
201        public void setTitle(String newText) {
202            String oldText = getTitle();
203            titleLabel.setText(newText);
204            firePropertyChange("title", oldText, newText);
205        }
206    
207        /**
208         * Returns the current toolbar, null if none has been set before.
209         * 
210         * @return the current toolbar - if any
211         */
212        public JToolBar getToolBar() {
213            return headerPanel.getComponentCount() > 1 ? (JToolBar)headerPanel.getComponent(1) : null;
214        }
215    
216        /**
217         * Sets a new tool bar in the header.
218         * 
219         * @param newToolBar
220         *            the tool bar to be set in the header
221         */
222        public void setToolBar(JToolBar newToolBar) {
223            JToolBar oldToolBar = getToolBar();
224            if (oldToolBar == newToolBar) {
225                return;
226            }
227            if (oldToolBar != null) {
228                headerPanel.remove(oldToolBar);
229            }
230            if (newToolBar != null) {
231                newToolBar.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
232                headerPanel.add(newToolBar, BorderLayout.EAST);
233            }
234            updateHeader();
235            firePropertyChange("toolBar", oldToolBar, newToolBar);
236        }
237    
238        /**
239         * Returns the content - null, if none has been set.
240         * 
241         * @return the current content
242         */
243        public Component getContent() {
244            return hasContent() ? getComponent(1) : null;
245        }
246    
247        /**
248         * Sets a new panel content; replaces any existing content, if existing.
249         * 
250         * @param newContent
251         *            the panel's new content
252         */
253        public void setContent(Component newContent) {
254            Component oldContent = getContent();
255            if (hasContent()) {
256                remove(oldContent);
257            }
258            add(newContent, BorderLayout.CENTER);
259            firePropertyChange("content", oldContent, newContent);
260        }
261    
262        /**
263         * Answers if the panel is currently selected (or in other words active) or
264         * not. In the selected state, the header background will be rendered
265         * differently.
266         * 
267         * @return boolean a boolean, where true means the frame is selected
268         *         (currently active) and false means it is not
269         */
270        public boolean isSelected() {
271            return isSelected;
272        }
273    
274        /**
275         * This panel draws its title bar differently if it is selected, which may
276         * be used to indicate to the user that this panel has the focus, or should
277         * get more attention than other simple internal frames.
278         * 
279         * @param newValue
280         *            a boolean, where true means the frame is selected (currently
281         *            active) and false means it is not
282         */
283        public void setSelected(boolean newValue) {
284            boolean oldValue = isSelected();
285            isSelected = newValue;
286            updateHeader();
287            firePropertyChange("selected", oldValue, newValue);
288        }
289    
290        // Building *************************************************************
291    
292        /**
293         * Creates and answers the header panel, that consists of: an icon, a title
294         * label, a tool bar, and a gradient background.
295         * 
296         * @param label
297         *            the label to paint the icon and text
298         * @param bar
299         *            the panel's tool bar
300         * @return the panel's built header area
301         */
302        private JPanel buildHeader(JLabel label, JToolBar bar) {
303            gradientPanel = new GradientPanel(new BorderLayout(), getHeaderBackground());
304            label.setOpaque(false);
305    
306            gradientPanel.add(label, BorderLayout.WEST);
307            gradientPanel.setBorder(BorderFactory.createEmptyBorder(3, 4, 3, 1));
308    
309            headerPanel = new JPanel(new BorderLayout());
310            headerPanel.add(gradientPanel, BorderLayout.CENTER);
311            setToolBar(bar);
312            headerPanel.setBorder(new RaisedHeaderBorder());
313            headerPanel.setOpaque(false);
314            return headerPanel;
315        }
316    
317        /**
318         * Updates the header.
319         */
320        private void updateHeader() {
321            gradientPanel.setBackground(getHeaderBackground());
322            gradientPanel.setOpaque(isSelected());
323            titleLabel.setForeground(getTextForeground(isSelected()));
324            headerPanel.repaint();
325        }
326    
327        /**
328         * Updates the UI. In addition to the superclass behavior, we need to update
329         * the header component.
330         */
331        public void updateUI() {
332            super.updateUI();
333            if (titleLabel != null) {
334                updateHeader();
335            }
336        }
337    
338        // Helper Code **********************************************************
339    
340        /**
341         * Checks and answers if the panel has a content component set.
342         * 
343         * @return true if the panel has a content, false if it's empty
344         */
345        private boolean hasContent() {
346            return getComponentCount() > 1;
347        }
348    
349        /**
350         * Determines and answers the header's text foreground color. Tries to
351         * lookup a special color from the L&amp;F. In case it is absent, it uses
352         * the standard internal frame forground.
353         * 
354         * @param selected
355         *            true to lookup the active color, false for the inactive
356         * @return the color of the foreground text
357         */
358        protected Color getTextForeground(boolean selected) {
359            Color c = UIManager.getColor(selected ? "SimpleInternalFrame.activeTitleForeground"
360                    : "SimpleInternalFrame.inactiveTitleForeground");
361            if (c != null) {
362                return c;
363            }
364            return UIManager.getColor(selected ? "InternalFrame.activeTitleForeground" : "Label.foreground");
365    
366        }
367    
368        /**
369         * Determines and answers the header's background color. Tries to lookup a
370         * special color from the L&amp;F. In case it is absent, it uses the
371         * standard internal frame background.
372         * 
373         * @return the color of the header's background
374         */
375        protected Color getHeaderBackground() {
376            Color c = UIManager.getColor("SimpleInternalFrame.activeTitleBackground");
377            
378            return c != null ? c : UIManager.getColor("InternalFrame.activeTitleBackground");
379        }
380    
381        // Helper Classes *******************************************************
382    
383        // A custom border for the raised header pseudo 3D effect.
384        private static class RaisedHeaderBorder extends AbstractBorder {
385    
386            private static final Insets INSETS = new Insets(1, 1, 1, 0);
387    
388            public Insets getBorderInsets(Component c) {
389                return INSETS;
390            }
391    
392            public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
393    
394                g.translate(x, y);
395                g.setColor(UIManager.getColor("controlLtHighlight"));
396                g.fillRect(0, 0, w, 1);
397                g.fillRect(0, 1, 1, h - 1);
398                g.setColor(UIManager.getColor("controlShadow"));
399                g.fillRect(0, h - 1, w, 1);
400                g.translate(-x, -y);
401            }
402        }
403    
404        // A panel with a horizontal gradient background.
405        private static class GradientPanel extends JPanel {
406    
407            private GradientPanel(LayoutManager lm, Color background) {
408                super(lm);
409                setBackground(background);
410            }
411    
412            public void paintComponent(Graphics g) {
413                super.paintComponent(g);
414                if (!isOpaque()) {
415                    return;
416                }
417                Color control = UIManager.getColor("control");
418                int width = getWidth();
419                int height = getHeight();
420    
421                Graphics2D g2 = (Graphics2D)g;
422                Paint storedPaint = g2.getPaint();
423                g2.setPaint(new GradientPaint(0, 0, getBackground(), width, 0, control));
424                g2.fillRect(0, 0, width, height);
425                g2.setPaint(storedPaint);
426            }
427        }
428    
429    }