001    /*
002     * $Id: JXStatusBar.java,v 1.6 2006/05/14 08:12:18 dmouse Exp $
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;
023    
024    
025    import java.awt.Component;
026    import java.awt.Container;
027    import java.awt.Dimension;
028    import java.awt.Insets;
029    import java.awt.LayoutManager2;
030    import java.util.HashMap;
031    import java.util.Map;
032    import javax.swing.JSeparator;
033    import javax.swing.SwingConstants;
034    import org.jdesktop.swingx.plaf.JXStatusBarAddon;
035    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
036    import org.jdesktop.swingx.plaf.StatusBarUI;
037    
038    /**
039     * <p>A container for {@link javax.swing.JComponent}s that is typically placed at
040     * the bottom of a form and runs the entire width of the form. There are 3
041     * important functions that JXStatusBar provides (reasons why we created it
042     * in the first place). First, JXStatusBar provides a hook for a pluggable look.
043     * There is a definite look associated with status bars on windows, for instance.
044     * By implementing a concrete subclass of JXPanel, we provide a way for the
045     * pluggable look and feel system to modify the look of the status bar automatically.</p>
046     *
047     * <p>Second, JXStatusBar comes with its own layout manager. Each item is added to
048     * the JXStatusBar with a JXStatusBar.Constraint as the constraint argument. The
049     * JXStatusBar.Constraint contains an Insets object, as well as a "weight". The weight
050     * is used the same as the GridBagLayout.</p>
051     *
052     * <p>Finally, JXStatusBar contains a built in JPopupMenu. This menu allows the
053     * user to hide/show any of the items on the status bar. It also may optionally
054     * contain an "add..." action that will allow the user to select from a handful
055     * of prepacked beans that can be placed on the status bar.</p>
056     *
057     * <p>Constructing a JXStatusBar is very straitforward:
058     * <pre><code>
059     *      JXStatusBar bar = new JXStatusBar();
060     *      JLabel statusLabel = new JLabel("Ready");
061     *      bar.add(statusLabel, new JXStatusBar.Constraints(1.0); //weight of 0.0 and no insets
062     *      JProgressBar pbar = new JProgressBar();
063     *      bar.add(pbar); //weight of 0.0 and no insets
064     * </code></pre></p>
065     *
066     * <p>Two common use cases for status bars is tracking application status and
067     * progress. JXStatusBar does not manage these tasks, but instead special components
068     * exist or can be created that do manage these tasks. For instance, if you application
069     * has a TaskManager or some other repository of currently running jobs, you could
070     * easily create a TaskManagerProgressBar that tracks those jobs. This component
071     * could then be added to the JXStatusBar like any other component.</p>
072     *
073     * @author pdoubleya
074     * @author rbair
075     */
076    public class JXStatusBar extends JXPanel {
077        /**
078         * @see #getUIClassID // *
079         * @see #readObject
080         */
081        static public final String uiClassID = "StatusBarUI";
082        
083        /**
084         * Initialization that would ideally be moved into various look and feel
085         * classes.
086         */
087        static {
088            LookAndFeelAddons.contribute(new JXStatusBarAddon());
089        }
090        
091    //    //TODO this should be part of the plaf UI delegate
092    //    private Insets margin = new Insets(3, 3, 3, 3);
093        
094        public JXStatusBar() {
095            super();
096            setLayout(new Layout());
097        }
098    
099        /**
100         * Returns the look and feel (L&F) object that renders this component.
101         * 
102         * @return the StatusBarUI object that renders this component
103         */
104        @Override
105        public StatusBarUI getUI() {
106            return (StatusBarUI) ui;
107        }
108    
109        /**
110         * Sets the look and feel (L&F) object that renders this component.
111         * 
112         * @param ui
113         *            the StatusBarUI L&F object
114         * @see javax.swing.UIDefaults#getUI
115         * @beaninfo bound: true hidden: true attribute: visualUpdate true
116         *           description: The UI object that implements the Component's
117         *           LookAndFeel.
118         */
119        public void setUI(StatusBarUI ui) {
120            super.setUI(ui);
121        }
122    
123        /**
124         * Returns a string that specifies the name of the L&F class that renders
125         * this component.
126         * 
127         * @return "StatusBarUI"
128         * @see javax.swing.JComponent#getUIClassID
129         * @see javax.swing.UIDefaults#getUI
130         * @beaninfo expert: true description: A string that specifies the name of
131         *           the L&F class.
132         */
133        @Override
134        public String getUIClassID() {
135            return uiClassID;
136        }
137    
138        /**
139         * Notification from the <code>UIManager</code> that the L&F has changed.
140         * Replaces the current UI object with the latest version from the
141         * <code>UIManager</code>.
142         * 
143         * @see javax.swing.JComponent#updateUI
144         */
145        @Override
146        public void updateUI() {
147            setUI((StatusBarUI) LookAndFeelAddons
148                    .getUI(this, StatusBarUI.class));
149        }
150    
151    //    @Override
152    //    public Insets getInsets() {
153    //        Insets i = super.getInsets();
154    //        i.top += margin.top;
155    //        i.left += margin.left;
156    //        i.right += margin.right;
157    //        i.bottom += margin.bottom;
158    //        return i;
159    //    }
160    //    
161    //    @Override
162    //    public Insets getInsets(Insets insets) {
163    //        insets = super.getInsets(insets);
164    //        insets.top += margin.top;
165    //        insets.left += margin.left;
166    //        insets.right += margin.right;
167    //        insets.bottom += margin.bottom;
168    //        return insets;
169    //    }
170        
171        public void addSeparator() {
172            JSeparator sep = new JSeparator(SwingConstants.VERTICAL);
173            add(sep, new Constraint(new Insets(1, 5, 1, 5)));
174        }
175        
176        public static class Constraint {
177            private Insets insets;
178            private double weight;
179            
180            public Constraint(Insets insets) {
181                this(0.0, insets);
182            }
183            
184            public Constraint(double weight) {
185                this(weight, new Insets(0,0,0,0));
186            }
187            
188            public Constraint(double weight, Insets insets) {
189                this.weight = weight;
190                this.insets = insets;
191            }
192            
193            public double getWeight() {
194                return weight;
195            }
196            
197            public Insets getInsets() {
198                return new Insets(insets.top, insets.left, insets.bottom, insets.right);
199            }
200        }
201        
202        private static class Layout implements LayoutManager2 {
203            private Map<Component,Constraint> constraints = new HashMap<Component,Constraint>();
204            
205            public void addLayoutComponent(String name, Component comp) {
206                addLayoutComponent(comp, null);
207            }
208    
209            public void addLayoutComponent(Component comp, Object constraint) {
210                constraints.put(comp, (Constraint)constraint);
211            }
212    
213            public void removeLayoutComponent(Component comp) {
214                constraints.remove(comp);
215            }
216    
217            public Dimension preferredLayoutSize(Container parent) {
218                Dimension prefSize = new Dimension();
219                for (Component comp : constraints.keySet()) {
220                    Dimension d = comp.getPreferredSize();
221                    Constraint c = constraints.get(comp);
222                    if (c != null) {
223                        Insets i = c.getInsets();
224                        d.width += i.left + i.right;
225                        d.height += i.top + i.bottom;
226                    }
227                    prefSize.height = Math.max(prefSize.height, d.height);
228                    prefSize.width += d.width;
229                }
230                
231                Insets insets = parent.getInsets();
232                prefSize.height += insets.top + insets.bottom;
233                prefSize.width += insets.left + insets.right;
234                return prefSize;
235            }
236    
237            public Dimension minimumLayoutSize(Container parent) {
238                return preferredLayoutSize(parent);
239            }
240    
241            public Dimension maximumLayoutSize(Container target) {
242                return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
243            }
244    
245            public float getLayoutAlignmentX(Container target) {
246                return .5f;
247            }
248    
249            public float getLayoutAlignmentY(Container target) {
250                return .5f;
251            }
252    
253            public void invalidateLayout(Container target) {
254                //I don't hold on to any state, so nothing to do here
255            }
256    
257            public void layoutContainer(Container parent) {
258                //find out the maximum weight of all the visible components
259                double maxWeight = 0.0;
260                for (Component comp : parent.getComponents()) {
261                    if (comp.isVisible()) {
262                        Constraint c = constraints.get(comp);
263                        maxWeight += c == null ? 0.0 : c.getWeight();
264                    }
265                }
266                maxWeight = maxWeight == 0 ? 1.0 : maxWeight; //don't let maxWeight be 0
267                
268                //the amount of available space. If positive, it will be split up among
269                //all visible components that have a positive weight
270                //If negative, then no weights will be configured
271                Insets parentInsets = parent.getInsets();
272                int availableSpace = parent.getWidth() - preferredLayoutSize(parent).width;
273                //the next X location to place a component at
274                int nextX = parentInsets.left;
275                int height = parent.getHeight() - parentInsets.top - parentInsets.bottom;
276                
277                //now lay out each visible component
278                for (Component comp : parent.getComponents()) {
279                    if (comp.isVisible()) {
280                        Constraint c = constraints.get(comp);
281                        double weight = c == null ? 0.0 : c.getWeight();
282                        Insets insets = c == null ? new Insets(0,0,0,0) : c.getInsets();
283                        
284                        int spaceToTake = availableSpace > 0 ? 
285                            (int)((weight/maxWeight) * availableSpace) : 0;
286                        availableSpace -= spaceToTake;
287                        
288                        int width = comp.getPreferredSize().width + spaceToTake;
289    
290                        int x = nextX + insets.left;
291                        int y = parentInsets.top + insets.top;
292                        comp.setSize(width, height);
293                        comp.setLocation(x, y);
294                        nextX = x + width + insets.right;
295                    }
296                }
297            }
298        }
299    }