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