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 }