/* * $Id: BasicStatusBarUI.java 3249 2009-02-04 19:53:56Z kschaefe $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.hdesktop.swingx.plaf.basic; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.LayoutManager2; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.plaf.BorderUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import org.hdesktop.swingx.JXStatusBar; import org.hdesktop.swingx.JXStatusBar.Constraint; import org.hdesktop.swingx.plaf.StatusBarUI; import org.hdesktop.swingx.plaf.UIManagerExt; /** * * @author rbair * @author Karl Schaefer */ public class BasicStatusBarUI extends StatusBarUI { private class Handler implements MouseListener, MouseMotionListener, PropertyChangeListener { private Window window = SwingUtilities.getWindowAncestor(statusBar); private int handleBoundary = getHandleBoundary(); private boolean validPress = false; private Point startingPoint; private int getHandleBoundary() { Border border = statusBar.getBorder(); if (border == null || !statusBar.isResizeHandleEnabled()) { return 0; } if (statusBar.getComponentOrientation().isLeftToRight()) { return border.getBorderInsets(statusBar).right; } else { return border.getBorderInsets(statusBar).left; } } private boolean isHandleAreaPoint(Point point) { if (window == null || window.isMaximumSizeSet()) { return false; } if (statusBar.getComponentOrientation().isLeftToRight()) { return point.x >= statusBar.getWidth() - handleBoundary; } else { return point.x <= handleBoundary; } } /** * {@inheritDoc} */ public void mouseClicked(MouseEvent e) { //does nothing } /** * {@inheritDoc} */ public void mouseEntered(MouseEvent e) { if (isHandleAreaPoint(e.getPoint())) { if (statusBar.getComponentOrientation().isLeftToRight()) { window.setCursor(Cursor.getPredefinedCursor( Cursor.SE_RESIZE_CURSOR)); } else { window.setCursor(Cursor.getPredefinedCursor( Cursor.SW_RESIZE_CURSOR)); } } else { window.setCursor(null); } } /** * {@inheritDoc} */ public void mouseExited(MouseEvent e) { if (!validPress) { window.setCursor(null); } } /** * {@inheritDoc} */ public void mousePressed(MouseEvent e) { validPress = SwingUtilities.isLeftMouseButton(e) && isHandleAreaPoint(e.getPoint()); startingPoint = e.getPoint(); SwingUtilities.convertPointToScreen(startingPoint, statusBar); } /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { validPress = !SwingUtilities.isLeftMouseButton(e); window.validate(); window.setCursor(null); } /** * {@inheritDoc} */ public void mouseDragged(MouseEvent e) { if (validPress) { Rectangle wb = window.getBounds(); Point p = e.getPoint(); SwingUtilities.convertPointToScreen(p, statusBar); wb.height += (p.y - startingPoint.y); if (statusBar.getComponentOrientation().isLeftToRight()) { wb.width += (p.x - startingPoint.x); } else { wb.x += (p.x - startingPoint.x); wb.width += (startingPoint.x - p.x); } window.setBounds(wb); window.validate(); startingPoint = p; } } /** * {@inheritDoc} */ public void mouseMoved(MouseEvent e) { if (isHandleAreaPoint(e.getPoint())) { if (statusBar.getComponentOrientation().isLeftToRight()) { window.setCursor(Cursor.getPredefinedCursor( Cursor.SE_RESIZE_CURSOR)); } else { window.setCursor(Cursor.getPredefinedCursor( Cursor.SW_RESIZE_CURSOR)); } } else { window.setCursor(null); } } /** * {@inheritDoc} */ public void propertyChange(PropertyChangeEvent evt) { if ("ancestor".equals(evt.getPropertyName())) { window = SwingUtilities.getWindowAncestor(statusBar); boolean useResizeHandle = statusBar.getParent() != null && statusBar.getRootPane() != null && (statusBar.getParent() == statusBar.getRootPane() || statusBar.getParent() == statusBar.getRootPane().getContentPane()); statusBar.setResizeHandleEnabled(useResizeHandle); } else if ("border".equals(evt.getPropertyName())) { handleBoundary = getHandleBoundary(); } else if ("componentOrientation".equals(evt.getPropertyName())) { handleBoundary = getHandleBoundary(); } else if ("resizeHandleEnabled".equals(evt.getPropertyName())) { //TODO disable handle display handleBoundary = getHandleBoundary(); } } } public static final String AUTO_ADD_SEPARATOR = new StringBuffer("auto-add-separator").toString(); /** * Used to help reduce the amount of trash being generated */ private static Insets TEMP_INSETS; /** * The one and only JXStatusBar for this UI delegate */ protected JXStatusBar statusBar; protected MouseListener mouseListener; protected MouseMotionListener mouseMotionListener; protected PropertyChangeListener propertyChangeListener; private Handler handler; /** Creates a new instance of BasicStatusBarUI */ public BasicStatusBarUI() { } /** * Returns an instance of the UI delegate for the specified component. * Each subclass must provide its own static <code>createUI</code> * method that returns an instance of that UI delegate subclass. * If the UI delegate subclass is stateless, it may return an instance * that is shared by multiple components. If the UI delegate is * stateful, then it should return a new instance per component. * The default implementation of this method throws an error, as it * should never be invoked. */ public static ComponentUI createUI(JComponent c) { return new BasicStatusBarUI(); } /** * {@inheritDoc} */ @Override public void installUI(JComponent c) { assert c instanceof JXStatusBar; statusBar = (JXStatusBar)c; installDefaults(statusBar); installListeners(statusBar); // only set the layout manager if the layout manager of the component is // null or a UIResource. Do not replace custom layout managers. LayoutManager m = statusBar.getLayout(); if (m == null || m instanceof UIResource) { statusBar.setLayout(createLayout()); } } protected void installDefaults(JXStatusBar sb) { //only set the border if it is an instanceof UIResource //In other words, only replace the border if it has not been //set by the developer. UIResource is the flag we use to indicate whether //the value was set by the UIDelegate, or by the developer. Border b = statusBar.getBorder(); if (b == null || b instanceof UIResource) { statusBar.setBorder(createBorder()); } LookAndFeel.installProperty(sb, "opaque", Boolean.TRUE); } private Handler getHandler() { if (handler == null) { handler = new Handler(); } return handler; } /** * Creates a {@code MouseListener} which will be added to the * status bar. If this method returns null then it will not * be added to the status bar. * <p> * Subclasses may override this method to return instances of their own * MouseEvent handlers. * * @return an instance of a {@code MouseListener} or null */ protected MouseListener createMouseListener() { return getHandler(); } /** * Creates a {@code MouseMotionListener} which will be added to the * status bar. If this method returns null then it will not * be added to the status bar. * <p> * Subclasses may override this method to return instances of their own * MouseEvent handlers. * * @return an instance of a {@code MouseMotionListener} or null */ protected MouseMotionListener createMouseMotionListener() { return getHandler(); } /** * Creates a {@code PropertyChangeListener} which will be added to the * status bar. If this method returns null then it will not * be added to the status bar. * <p> * Subclasses may override this method to return instances of their own * PropertyChangeEvent handlers. * * @return an instance of a {@code PropertyChangeListener} or null */ protected PropertyChangeListener createPropertyChangeListener() { return getHandler(); } /** * Create and install the listeners for the status bar. * This method is called when the UI is installed. */ protected void installListeners(JXStatusBar sb) { if ((mouseListener = createMouseListener()) != null) { statusBar.addMouseListener(mouseListener); } if ((mouseMotionListener = createMouseMotionListener()) != null) { statusBar.addMouseMotionListener(mouseMotionListener); } if ((propertyChangeListener = createPropertyChangeListener()) != null) { statusBar.addPropertyChangeListener(propertyChangeListener); } } /** * {@inheritDoc} */ @Override public void uninstallUI(JComponent c) { assert c instanceof JXStatusBar; uninstallDefaults(statusBar); uninstallListeners(statusBar); if (statusBar.getLayout() instanceof UIResource) { statusBar.setLayout(null); } } protected void uninstallDefaults(JXStatusBar sb) { if (sb.getBorder() instanceof UIResource) { sb.setBorder(null); } } /** * Remove the installed listeners from the status bar. * The number and types of listeners removed in this method should be * the same that were added in <code>installListeners</code> */ protected void uninstallListeners(JXStatusBar sb) { if (mouseListener != null) { statusBar.removeMouseListener(mouseListener); } if (mouseMotionListener != null) { statusBar.removeMouseMotionListener(mouseMotionListener); } if (propertyChangeListener != null) { statusBar.removePropertyChangeListener(propertyChangeListener); } } @Override public void paint(Graphics g, JComponent c) { //paint the background if opaque if (statusBar.isOpaque()) { Graphics2D g2 = (Graphics2D)g; paintBackground(g2, statusBar); } if (includeSeparators()) { //now paint the separators TEMP_INSETS = getSeparatorInsets(TEMP_INSETS); for (int i=0; i<statusBar.getComponentCount()-1; i++) { Component comp = statusBar.getComponent(i); int x = comp.getX() + comp.getWidth() + TEMP_INSETS.left; int y = TEMP_INSETS.top; int w = getSeparatorWidth() - TEMP_INSETS.left - TEMP_INSETS.right; int h = c.getHeight() - TEMP_INSETS.top - TEMP_INSETS.bottom; paintSeparator((Graphics2D)g, statusBar, x, y, w, h); } } } //----------------------------------------------------- Extension Points protected void paintBackground(Graphics2D g, JXStatusBar bar) { if (bar.isOpaque()) { g.setColor(bar.getBackground()); g.fillRect(0, 0, bar.getWidth(), bar.getHeight()); } } protected void paintSeparator(Graphics2D g, JXStatusBar bar, int x, int y, int w, int h) { Color fg = UIManagerExt.getSafeColor("Separator.foreground", Color.BLACK); Color bg = UIManagerExt.getSafeColor("Separator.background", Color.WHITE); x += w / 2; g.setColor(fg); g.drawLine(x, y, x, h); g.setColor(bg); g.drawLine(x+1, y, x+1, h); } protected Insets getSeparatorInsets(Insets insets) { if (insets == null) { insets = new Insets(0, 0, 0, 0); } insets.top = 4; insets.left = 4; insets.bottom = 2; insets.right = 4; return insets; } protected int getSeparatorWidth() { return 10; } protected boolean includeSeparators() { Boolean b = (Boolean)statusBar.getClientProperty(AUTO_ADD_SEPARATOR); return b == null || b; } protected BorderUIResource createBorder() { return new BorderUIResource(BorderFactory.createEmptyBorder(4, 5, 4, 22)); } protected LayoutManager createLayout() { //This is in the UI delegate because the layout //manager takes into account spacing for the separators between components return new LayoutManager2() { private Map<Component,Constraint> constraints = new HashMap<Component,Constraint>(); public void addLayoutComponent(String name, Component comp) {addLayoutComponent(comp, null);} public void removeLayoutComponent(Component comp) {constraints.remove(comp);} public Dimension minimumLayoutSize(Container parent) {return preferredLayoutSize(parent);} public Dimension maximumLayoutSize(Container target) {return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);} public float getLayoutAlignmentX(Container target) {return .5f;} public float getLayoutAlignmentY(Container target) {return .5f;} public void invalidateLayout(Container target) {} public void addLayoutComponent(Component comp, Object constraint) { //we accept an Insets, a ResizeBehavior, or a Constraint. if (constraint instanceof Insets) { constraint = new Constraint((Insets)constraint); } else if (constraint instanceof Constraint.ResizeBehavior) { constraint = new Constraint((Constraint.ResizeBehavior)constraint); } constraints.put(comp, (Constraint)constraint); } public Dimension preferredLayoutSize(Container parent) { Dimension prefSize = new Dimension(); int count = 0; for (Component comp : constraints.keySet()) { Constraint c = constraints.get(comp); Dimension d = comp.getPreferredSize(); int prefWidth = 0; if (c != null) { Insets i = c.getInsets(); d.width += i.left + i.right; d.height += i.top + i.bottom; prefWidth = c.getFixedWidth(); } prefSize.height = Math.max(prefSize.height, d.height); prefSize.width += Math.max(d.width, prefWidth); //If this is not the last component, add extra space between each //component (for the separator). count++; if (includeSeparators() && constraints.size() < count) { prefSize.width += getSeparatorWidth(); } } Insets insets = parent.getInsets(); prefSize.height += insets.top + insets.bottom; prefSize.width += insets.left + insets.right; return prefSize; } public void layoutContainer(Container parent) { /* * Layout algorithm: * If the parent width is less than the sum of the preferred * widths of the components (including separators), where * preferred width means either the component preferred width + * constraint insets, or fixed width + constraint insets, then * simply layout the container from left to right and let the * right hand components flow off the parent. * * Otherwise, lay out each component according to its preferred * width except for components with a FILL constraint. For these, * resize them evenly for each FILL constraint. */ //the insets of the parent component. Insets parentInsets = parent.getInsets(); //the available width for putting components. int availableWidth = parent.getWidth() - parentInsets.left - parentInsets.right; if (includeSeparators()) { //remove from availableWidth the amount of space the separators will take availableWidth -= (parent.getComponentCount() - 1) * getSeparatorWidth(); } //the preferred widths of all of the components -- where preferred //width mean the preferred width after calculating fixed widths and //constraint insets int[] preferredWidths = new int[parent.getComponentCount()]; int sumPreferredWidths = 0; for (int i=0; i<preferredWidths.length; i++) { preferredWidths[i] = getPreferredWidth(parent.getComponent(i)); sumPreferredWidths += preferredWidths[i]; } //if the availableWidth is greater than the sum of preferred //sizes, then adjust the preferred width of each component that //has a FILL constraint, to evenly use up the extra space. if (availableWidth > sumPreferredWidths) { //the number of components with a fill constraint int numFilledComponents = 0; for (Component comp : parent.getComponents()) { Constraint c = constraints.get(comp); if (c != null && c.getResizeBehavior() == Constraint.ResizeBehavior.FILL) { numFilledComponents++; } } if (numFilledComponents > 0) { //calculate the share of free space each FILL component will take availableWidth -= sumPreferredWidths; double weight = 1.0 / (double)numFilledComponents; int share = (int)(availableWidth * weight); int remaining = numFilledComponents; for (int i=0; i<parent.getComponentCount(); i++) { Component comp = parent.getComponent(i); Constraint c = constraints.get(comp); if (c != null && c.getResizeBehavior() == Constraint.ResizeBehavior.FILL) { if (remaining > 1) { preferredWidths[i] += share; availableWidth -= share; } else { preferredWidths[i] += availableWidth; } remaining--; } } } } //now lay out the components int nextX = parentInsets.left; int height = parent.getHeight() - parentInsets.top - parentInsets.bottom; for (int i=0; i<parent.getComponentCount(); i++) { Component comp = parent.getComponent(i); Constraint c = constraints.get(comp); Insets insets = c == null ? new Insets(0,0,0,0) : c.getInsets(); int width = preferredWidths[i] - (insets.left + insets.right); int x = nextX + insets.left; int y = parentInsets.top + insets.top; comp.setSize(width, height); comp.setLocation(x, y); nextX = x + width + insets.right; //If this is not the last component, add extra space //for the separator if (includeSeparators() && i < parent.getComponentCount() - 1) { nextX += getSeparatorWidth(); } } } /** * @return the "preferred" width, where that means either * comp.getPreferredSize().width + constraintInsets, or * constraint.fixedWidth + constraintInsets. */ private int getPreferredWidth(Component comp) { Constraint c = constraints.get(comp); if (c == null) { return comp.getPreferredSize().width; } else { Insets insets = c.getInsets(); assert insets != null; if (c.getFixedWidth() <= 0) { return comp.getPreferredSize().width + insets.left + insets.right; } else { return c.getFixedWidth() + insets.left + insets.right; } } } }; } }