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 }