001    /*
002     * $Id: BasicStatusBarUI.java 3249 2009-02-04 19:53:56Z kschaefe $
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.Component;
026    import java.awt.Container;
027    import java.awt.Cursor;
028    import java.awt.Dimension;
029    import java.awt.Graphics;
030    import java.awt.Graphics2D;
031    import java.awt.Insets;
032    import java.awt.LayoutManager;
033    import java.awt.LayoutManager2;
034    import java.awt.Point;
035    import java.awt.Rectangle;
036    import java.awt.Window;
037    import java.awt.event.MouseEvent;
038    import java.awt.event.MouseListener;
039    import java.awt.event.MouseMotionListener;
040    import java.beans.PropertyChangeEvent;
041    import java.beans.PropertyChangeListener;
042    import java.util.HashMap;
043    import java.util.Map;
044    
045    import javax.swing.BorderFactory;
046    import javax.swing.JComponent;
047    import javax.swing.LookAndFeel;
048    import javax.swing.SwingUtilities;
049    import javax.swing.UIManager;
050    import javax.swing.border.Border;
051    import javax.swing.plaf.BorderUIResource;
052    import javax.swing.plaf.ComponentUI;
053    import javax.swing.plaf.UIResource;
054    
055    import org.jdesktop.swingx.JXStatusBar;
056    import org.jdesktop.swingx.JXStatusBar.Constraint;
057    import org.jdesktop.swingx.plaf.StatusBarUI;
058    import org.jdesktop.swingx.plaf.UIManagerExt;
059    
060    /**
061     *
062     * @author rbair
063     * @author Karl Schaefer
064     */
065    public class BasicStatusBarUI extends StatusBarUI {
066        private class Handler implements MouseListener, MouseMotionListener, PropertyChangeListener {
067            private Window window = SwingUtilities.getWindowAncestor(statusBar);
068            private int handleBoundary = getHandleBoundary();
069            private boolean validPress = false;
070            private Point startingPoint;
071            
072            private int getHandleBoundary() {
073                Border border = statusBar.getBorder();
074                
075                if (border == null || !statusBar.isResizeHandleEnabled()) {
076                    return 0;
077                }
078                
079                if (statusBar.getComponentOrientation().isLeftToRight()) {
080                    return border.getBorderInsets(statusBar).right;
081                } else {
082                    return border.getBorderInsets(statusBar).left;
083                }
084            }
085            
086            private boolean isHandleAreaPoint(Point point) {
087                if (window == null || window.isMaximumSizeSet()) {
088                    return false;
089                }
090                
091                if (statusBar.getComponentOrientation().isLeftToRight()) {
092                    return point.x >= statusBar.getWidth() - handleBoundary;
093                } else {
094                    return point.x <= handleBoundary;
095                }
096            }
097            
098            /**
099             * {@inheritDoc}
100             */
101            public void mouseClicked(MouseEvent e) {
102                //does nothing
103            }
104    
105            /**
106             * {@inheritDoc}
107             */
108            public void mouseEntered(MouseEvent e) {
109                if (isHandleAreaPoint(e.getPoint())) {
110                    if (statusBar.getComponentOrientation().isLeftToRight()) {
111                        window.setCursor(Cursor.getPredefinedCursor(
112                                Cursor.SE_RESIZE_CURSOR));
113                    } else {
114                        window.setCursor(Cursor.getPredefinedCursor(
115                                Cursor.SW_RESIZE_CURSOR));
116                    }
117                } else {
118                    window.setCursor(null);
119                }
120            }
121    
122            /**
123             * {@inheritDoc}
124             */
125            public void mouseExited(MouseEvent e) {
126                if (!validPress) {
127                    window.setCursor(null);
128                }
129            }
130    
131            /**
132             * {@inheritDoc}
133             */
134            public void mousePressed(MouseEvent e) {
135                validPress = SwingUtilities.isLeftMouseButton(e) && isHandleAreaPoint(e.getPoint()); 
136                startingPoint = e.getPoint();
137                SwingUtilities.convertPointToScreen(startingPoint, statusBar);
138            }
139    
140            /**
141             * {@inheritDoc}
142             */
143            public void mouseReleased(MouseEvent e) {
144                validPress = !SwingUtilities.isLeftMouseButton(e);
145                window.validate();
146                window.setCursor(null);
147            }
148    
149            /**
150             * {@inheritDoc}
151             */
152            public void mouseDragged(MouseEvent e) {
153                if (validPress) {
154                    Rectangle wb = window.getBounds();
155                    Point p = e.getPoint();
156                    SwingUtilities.convertPointToScreen(p, statusBar);
157                    
158                    wb.height += (p.y - startingPoint.y);
159                    if (statusBar.getComponentOrientation().isLeftToRight()) {
160                        wb.width += (p.x - startingPoint.x);
161                    } else {
162                        wb.x += (p.x - startingPoint.x);
163                        wb.width += (startingPoint.x - p.x);
164                    }
165                    
166                    window.setBounds(wb);
167                    window.validate();
168                    startingPoint = p;
169                }
170            }
171    
172            /**
173             * {@inheritDoc}
174             */
175            public void mouseMoved(MouseEvent e) {
176                if (isHandleAreaPoint(e.getPoint())) {
177                    if (statusBar.getComponentOrientation().isLeftToRight()) {
178                        window.setCursor(Cursor.getPredefinedCursor(
179                                Cursor.SE_RESIZE_CURSOR));
180                    } else {
181                        window.setCursor(Cursor.getPredefinedCursor(
182                                Cursor.SW_RESIZE_CURSOR));
183                    }
184                } else {
185                    window.setCursor(null);
186                }
187            }
188    
189            /**
190             * {@inheritDoc}
191             */
192            public void propertyChange(PropertyChangeEvent evt) {
193                if ("ancestor".equals(evt.getPropertyName())) {
194                    window = SwingUtilities.getWindowAncestor(statusBar);
195                    
196                    boolean useResizeHandle = statusBar.getParent() != null
197                            && statusBar.getRootPane() != null
198                            && (statusBar.getParent() == statusBar.getRootPane()
199                            || statusBar.getParent() == statusBar.getRootPane().getContentPane());
200                    statusBar.setResizeHandleEnabled(useResizeHandle);
201                } else if ("border".equals(evt.getPropertyName())) {
202                    handleBoundary = getHandleBoundary();
203                } else if ("componentOrientation".equals(evt.getPropertyName())) {
204                    handleBoundary = getHandleBoundary();
205                } else if ("resizeHandleEnabled".equals(evt.getPropertyName())) {
206                    //TODO disable handle display
207                    handleBoundary = getHandleBoundary();
208                }
209            }
210        }
211        
212        public static final String AUTO_ADD_SEPARATOR = new StringBuffer("auto-add-separator").toString();
213        /**
214         * Used to help reduce the amount of trash being generated
215         */
216        private static Insets TEMP_INSETS;
217        /**
218         * The one and only JXStatusBar for this UI delegate
219         */
220        protected JXStatusBar statusBar;
221        
222        protected MouseListener mouseListener;
223        
224        protected MouseMotionListener mouseMotionListener;
225        
226        protected PropertyChangeListener propertyChangeListener;
227        
228        private Handler handler;
229        
230        /** Creates a new instance of BasicStatusBarUI */
231        public BasicStatusBarUI() {
232        }
233        
234        /**
235         * Returns an instance of the UI delegate for the specified component.
236         * Each subclass must provide its own static <code>createUI</code>
237         * method that returns an instance of that UI delegate subclass.
238         * If the UI delegate subclass is stateless, it may return an instance
239         * that is shared by multiple components.  If the UI delegate is
240         * stateful, then it should return a new instance per component.
241         * The default implementation of this method throws an error, as it
242         * should never be invoked.
243         */
244        public static ComponentUI createUI(JComponent c) {
245            return new BasicStatusBarUI();
246        }
247        
248        /**
249         * {@inheritDoc}
250         */
251        @Override
252        public void installUI(JComponent c) {
253            assert c instanceof JXStatusBar;
254            statusBar = (JXStatusBar)c;
255            
256            installDefaults(statusBar);
257            installListeners(statusBar);
258            
259            // only set the layout manager if the layout manager of the component is
260            // null or a UIResource. Do not replace custom layout managers.
261            LayoutManager m = statusBar.getLayout();
262            if (m == null || m instanceof UIResource) {
263                statusBar.setLayout(createLayout());
264            }
265        }
266        
267        protected void installDefaults(JXStatusBar sb) {
268            //only set the border if it is an instanceof UIResource
269            //In other words, only replace the border if it has not been
270            //set by the developer. UIResource is the flag we use to indicate whether
271            //the value was set by the UIDelegate, or by the developer.
272            Border b = statusBar.getBorder();
273            if (b == null || b instanceof UIResource) {
274                statusBar.setBorder(createBorder());
275            }
276            
277            LookAndFeel.installProperty(sb, "opaque", Boolean.TRUE);
278        }
279        
280        private Handler getHandler() {
281            if (handler == null) {
282                handler = new Handler();
283            }
284            
285            return handler;
286        }
287        
288        /**
289         * Creates a {@code MouseListener} which will be added to the 
290         * status bar. If this method returns null then it will not 
291         * be added to the status bar.
292         * <p>
293         * Subclasses may override this method to return instances of their own
294         * MouseEvent handlers.
295         *
296         * @return an instance of a {@code MouseListener} or null
297         */
298        protected MouseListener createMouseListener() {
299            return getHandler();
300        }
301        
302        /**
303         * Creates a {@code MouseMotionListener} which will be added to the 
304         * status bar. If this method returns null then it will not 
305         * be added to the status bar.
306         * <p>
307         * Subclasses may override this method to return instances of their own
308         * MouseEvent handlers.
309         *
310         * @return an instance of a {@code MouseMotionListener} or null
311         */
312        protected MouseMotionListener createMouseMotionListener() {
313            return getHandler();
314        }
315        
316        /**
317         * Creates a {@code PropertyChangeListener} which will be added to the 
318         * status bar. If this method returns null then it will not 
319         * be added to the status bar.
320         * <p>
321         * Subclasses may override this method to return instances of their own
322         * PropertyChangeEvent handlers.
323         *
324         * @return an instance of a {@code PropertyChangeListener} or null
325         */
326        protected PropertyChangeListener createPropertyChangeListener() {
327            return getHandler();
328        }
329        
330        /**
331         * Create and install the listeners for the status bar.
332         * This method is called when the UI is installed.
333         */
334        protected void installListeners(JXStatusBar sb) {
335            if ((mouseListener = createMouseListener()) != null) {
336                statusBar.addMouseListener(mouseListener);
337            }
338            
339            if ((mouseMotionListener = createMouseMotionListener()) != null) {
340                statusBar.addMouseMotionListener(mouseMotionListener);
341            }
342            
343            if ((propertyChangeListener = createPropertyChangeListener()) != null) {
344                statusBar.addPropertyChangeListener(propertyChangeListener);
345            }
346        }
347        
348        /**
349         * {@inheritDoc}
350         */
351        @Override
352        public void uninstallUI(JComponent c) {
353            assert c instanceof JXStatusBar;
354    
355            uninstallDefaults(statusBar);
356            uninstallListeners(statusBar);
357            
358            if (statusBar.getLayout() instanceof UIResource) {
359                statusBar.setLayout(null);
360            }
361        }
362        
363        protected void uninstallDefaults(JXStatusBar sb) {
364            if (sb.getBorder() instanceof UIResource) {
365                sb.setBorder(null);
366            }
367        }
368        
369        /**
370         * Remove the installed listeners from the status bar.
371         * The number and types of listeners removed in this method should be
372         * the same that were added in <code>installListeners</code>
373         */
374        protected void uninstallListeners(JXStatusBar sb) {
375            if (mouseListener != null) {
376                statusBar.removeMouseListener(mouseListener);
377            }
378            
379            if (mouseMotionListener != null) {
380                statusBar.removeMouseMotionListener(mouseMotionListener);
381            }
382            
383            if (propertyChangeListener != null) {
384                statusBar.removePropertyChangeListener(propertyChangeListener);
385            }
386        }
387        
388        @Override
389        public void paint(Graphics g, JComponent c) {
390            //paint the background if opaque
391            if (statusBar.isOpaque()) {
392                Graphics2D g2 = (Graphics2D)g;
393                paintBackground(g2, statusBar);
394            }
395            
396            if (includeSeparators()) {
397                //now paint the separators
398                TEMP_INSETS = getSeparatorInsets(TEMP_INSETS);
399                for (int i=0; i<statusBar.getComponentCount()-1; i++) {
400                    Component comp = statusBar.getComponent(i);
401                    int x = comp.getX() + comp.getWidth() + TEMP_INSETS.left;
402                    int y = TEMP_INSETS.top;
403                    int w = getSeparatorWidth() - TEMP_INSETS.left - TEMP_INSETS.right;
404                    int h = c.getHeight() - TEMP_INSETS.top - TEMP_INSETS.bottom;
405    
406                    paintSeparator((Graphics2D)g, statusBar, x, y, w, h);
407                }
408            }
409        }
410        
411        //----------------------------------------------------- Extension Points
412        protected void paintBackground(Graphics2D g, JXStatusBar bar) {
413            if (bar.isOpaque()) {
414                g.setColor(bar.getBackground());
415                g.fillRect(0, 0, bar.getWidth(), bar.getHeight());
416            }
417        }
418        
419        protected void paintSeparator(Graphics2D g, JXStatusBar bar, int x, int y, int w, int h) {
420            Color fg = UIManagerExt.getSafeColor("Separator.foreground", Color.BLACK);
421            Color bg = UIManagerExt.getSafeColor("Separator.background", Color.WHITE);
422            
423            x += w / 2;
424            g.setColor(fg);
425            g.drawLine(x, y, x, h);
426            
427            g.setColor(bg);
428            g.drawLine(x+1, y, x+1, h);
429        }
430        
431        protected Insets getSeparatorInsets(Insets insets) {
432            if (insets == null) {
433                insets = new Insets(0, 0, 0, 0);
434            }
435            
436            insets.top = 4;
437            insets.left = 4;
438            insets.bottom = 2;
439            insets.right = 4;
440            
441            return insets;
442        }
443        
444        protected int getSeparatorWidth() {
445            return 10;
446        }
447        
448        protected boolean includeSeparators() {
449            Boolean b = (Boolean)statusBar.getClientProperty(AUTO_ADD_SEPARATOR);
450            return b == null || b;
451        }
452        
453        protected BorderUIResource createBorder() {
454            LookAndFeel laf = UIManager.getLookAndFeel();
455            int rightEdge = laf != null && laf.getClass().getPackage().getName()
456                .toLowerCase().contains("windows") ? 22 : 5;
457            return new BorderUIResource(BorderFactory.createEmptyBorder(4, 5, 4, 
458                rightEdge));
459        }
460        
461        protected LayoutManager createLayout() {
462            //This is in the UI delegate because the layout
463            //manager takes into account spacing for the separators between components
464            return new LayoutManager2() {
465                private Map<Component,Constraint> constraints = new HashMap<Component,Constraint>();
466                
467                public void addLayoutComponent(String name, Component comp) {addLayoutComponent(comp, null);}
468                public void removeLayoutComponent(Component comp) {constraints.remove(comp);}
469                public Dimension minimumLayoutSize(Container parent) {return preferredLayoutSize(parent);}
470                public Dimension maximumLayoutSize(Container target) {return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);}
471                public float getLayoutAlignmentX(Container target) {return .5f;}
472                public float getLayoutAlignmentY(Container target) {return .5f;}
473                public void invalidateLayout(Container target) {}
474                
475                public void addLayoutComponent(Component comp, Object constraint) {
476                    //we accept an Insets, a ResizeBehavior, or a Constraint.
477                    if (constraint instanceof Insets) {
478                        constraint = new Constraint((Insets)constraint);
479                    } else if (constraint instanceof Constraint.ResizeBehavior) {
480                        constraint = new Constraint((Constraint.ResizeBehavior)constraint);
481                    }
482                    
483                    constraints.put(comp, (Constraint)constraint);
484                }
485                
486                public Dimension preferredLayoutSize(Container parent) {
487                    Dimension prefSize = new Dimension();
488                    int count = 0;
489                    for (Component comp : constraints.keySet()) {
490                        Constraint c = constraints.get(comp);
491                        Dimension d = comp.getPreferredSize();
492                        int prefWidth = 0;
493                        if (c != null) {
494                            Insets i = c.getInsets();
495                            d.width += i.left + i.right;
496                            d.height += i.top + i.bottom;
497                            prefWidth = c.getFixedWidth();
498                        }
499                        prefSize.height = Math.max(prefSize.height, d.height);
500                        prefSize.width += Math.max(d.width, prefWidth);
501                        
502                        //If this is not the last component, add extra space between each
503                        //component (for the separator).
504                        count++;
505                        if (includeSeparators() && constraints.size() < count) {
506                            prefSize.width += getSeparatorWidth();
507                        }
508                    }
509                    
510                    Insets insets = parent.getInsets();
511                    prefSize.height += insets.top + insets.bottom;
512                    prefSize.width += insets.left + insets.right;
513                    return prefSize;
514                }
515                
516                public void layoutContainer(Container parent) {
517                    /*
518                     * Layout algorithm:
519                     *      If the parent width is less than the sum of the preferred
520                     *      widths of the components (including separators), where
521                     *      preferred width means either the component preferred width + 
522                     *      constraint insets, or fixed width + constraint insets, then
523                     *      simply layout the container from left to right and let the
524                     *      right hand components flow off the parent.
525                     *
526                     *      Otherwise, lay out each component according to its preferred
527                     *      width except for components with a FILL constraint. For these,
528                     *      resize them evenly for each FILL constraint.
529                     */
530                    
531                    //the insets of the parent component.
532                    Insets parentInsets = parent.getInsets();
533                    //the available width for putting components.
534                    int availableWidth = parent.getWidth() - parentInsets.left - parentInsets.right;
535                    if (includeSeparators()) {
536                        //remove from availableWidth the amount of space the separators will take
537                        availableWidth -= (parent.getComponentCount() - 1) * getSeparatorWidth();
538                    }
539                    
540                    //the preferred widths of all of the components -- where preferred
541                    //width mean the preferred width after calculating fixed widths and
542                    //constraint insets
543                    int[] preferredWidths = new int[parent.getComponentCount()];
544                    int sumPreferredWidths = 0;
545                    for (int i=0; i<preferredWidths.length; i++) {
546                        preferredWidths[i] = getPreferredWidth(parent.getComponent(i));
547                        sumPreferredWidths += preferredWidths[i];
548                    }
549                    
550                    //if the availableWidth is greater than the sum of preferred
551                    //sizes, then adjust the preferred width of each component that
552                    //has a FILL constraint, to evenly use up the extra space.
553                    if (availableWidth > sumPreferredWidths) {
554                        //the number of components with a fill constraint
555                        int numFilledComponents = 0;
556                        for (Component comp : parent.getComponents()) {
557                            Constraint c = constraints.get(comp);
558                            if (c != null && c.getResizeBehavior() == Constraint.ResizeBehavior.FILL) {
559                                numFilledComponents++;
560                            }
561                        }
562                        
563                        if (numFilledComponents > 0) {
564                            //calculate the share of free space each FILL component will take
565                            availableWidth -= sumPreferredWidths;
566                            double weight = 1.0 / (double)numFilledComponents;
567                            int share = (int)(availableWidth * weight);
568                            int remaining = numFilledComponents;
569                            for (int i=0; i<parent.getComponentCount(); i++) {
570                                Component comp = parent.getComponent(i);
571                                Constraint c = constraints.get(comp);
572                                if (c != null && c.getResizeBehavior() == Constraint.ResizeBehavior.FILL) {
573                                    if (remaining > 1) {
574                                        preferredWidths[i] += share;
575                                        availableWidth -= share;
576                                    } else {
577                                        preferredWidths[i] += availableWidth;
578                                    }
579                                    remaining--;
580                                }
581                            }
582                        }
583                    }
584                    
585                    //now lay out the components
586                    int nextX = parentInsets.left;
587                    int height = parent.getHeight() - parentInsets.top - parentInsets.bottom;
588                    for (int i=0; i<parent.getComponentCount(); i++) {
589                        Component comp = parent.getComponent(i);
590                        Constraint c = constraints.get(comp);
591                        Insets insets = c == null ? new Insets(0,0,0,0) : c.getInsets();
592                        int width = preferredWidths[i] - (insets.left + insets.right);
593                        int x = nextX + insets.left;
594                        int y = parentInsets.top + insets.top;
595                        comp.setSize(width, height);
596                        comp.setLocation(x, y);
597                        nextX = x + width + insets.right;
598                        //If this is not the last component, add extra space
599                        //for the separator
600                        if (includeSeparators() && i < parent.getComponentCount() - 1) {
601                            nextX += getSeparatorWidth();
602                        }
603                    }
604                }
605                
606                /**
607                 * @return the "preferred" width, where that means either 
608                 *         comp.getPreferredSize().width + constraintInsets, or
609                 *         constraint.fixedWidth + constraintInsets.
610                 */
611                private int getPreferredWidth(Component comp) {
612                    Constraint c = constraints.get(comp);
613                    if (c == null) {
614                        return comp.getPreferredSize().width;
615                    } else {
616                        Insets insets = c.getInsets();
617                        assert insets != null;
618                        if (c.getFixedWidth() <= 0) {
619                            return comp.getPreferredSize().width + insets.left + insets.right;
620                        } else {
621                            return c.getFixedWidth() + insets.left + insets.right;
622                        }
623                    }
624                }
625                
626            };
627        }
628    }