001 /* SwingUtilities.java --
002 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing;
040
041 import java.applet.Applet;
042 import java.awt.Component;
043 import java.awt.Container;
044 import java.awt.FontMetrics;
045 import java.awt.Frame;
046 import java.awt.Graphics;
047 import java.awt.Insets;
048 import java.awt.KeyboardFocusManager;
049 import java.awt.Point;
050 import java.awt.Rectangle;
051 import java.awt.Shape;
052 import java.awt.Window;
053 import java.awt.event.ActionEvent;
054 import java.awt.event.InputEvent;
055 import java.awt.event.KeyEvent;
056 import java.awt.event.MouseEvent;
057 import java.lang.reflect.InvocationTargetException;
058
059 import javax.accessibility.Accessible;
060 import javax.accessibility.AccessibleStateSet;
061 import javax.swing.plaf.ActionMapUIResource;
062 import javax.swing.plaf.InputMapUIResource;
063 import javax.swing.plaf.basic.BasicHTML;
064 import javax.swing.text.View;
065
066 /**
067 * A number of static utility functions which are
068 * useful when drawing swing components, dispatching events, or calculating
069 * regions which need painting.
070 *
071 * @author Graydon Hoare (graydon@redhat.com)
072 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
073 */
074 public class SwingUtilities
075 implements SwingConstants
076 {
077 /**
078 * This frame should be used as parent for JWindow or JDialog
079 * that doesn't an owner
080 */
081 private static OwnerFrame ownerFrame;
082
083 private SwingUtilities()
084 {
085 // Do nothing.
086 }
087
088 /**
089 * Calculates the portion of the component's bounds which is inside the
090 * component's border insets. This area is usually the area a component
091 * should confine its painting to. The coordinates are returned in terms
092 * of the <em>component's</em> coordinate system, where (0,0) is the
093 * upper left corner of the component's bounds.
094 *
095 * @param c the component to measure the bounds of (if <code>null</code>,
096 * this method returns <code>null</code>).
097 * @param r a carrier to store the return value in (if <code>null</code>, a
098 * new <code>Rectangle</code> instance is created).
099 *
100 * @return The calculated area inside the component and its border insets.
101 */
102 public static Rectangle calculateInnerArea(JComponent c, Rectangle r)
103 {
104 if (c == null)
105 return null;
106 r = c.getBounds(r);
107 Insets i = c.getInsets();
108 r.x = i.left;
109 r.width = r.width - i.left - i.right;
110 r.y = i.top;
111 r.height = r.height - i.top - i.bottom;
112 return r;
113 }
114
115 /**
116 * Returns the focus owner or <code>null</code> if <code>comp</code> is not
117 * the focus owner or a parent of it.
118 *
119 * @param comp the focus owner or a parent of it
120 *
121 * @return the focus owner, or <code>null</code>
122 *
123 * @deprecated 1.4 Replaced by
124 * <code>KeyboardFocusManager.getFocusOwner()</code>.
125 */
126 public static Component findFocusOwner(Component comp)
127 {
128 // Get real focus owner.
129 Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager()
130 .getFocusOwner();
131
132 // Check if comp is the focus owner or a parent of it.
133 Component tmp = focusOwner;
134
135 while (tmp != null)
136 {
137 if (tmp == comp)
138 return focusOwner;
139
140 tmp = tmp.getParent();
141 }
142
143 return null;
144 }
145
146 /**
147 * Returns the <code>Accessible</code> child of the specified component
148 * which appears at the supplied <code>Point</code>. If there is no
149 * child located at that particular pair of co-ordinates, null is returned
150 * instead.
151 *
152 * @param c the component whose children may be found at the specified
153 * point.
154 * @param p the point at which to look for the existence of children
155 * of the specified component.
156 * @return the <code>Accessible</code> child at the point, <code>p</code>,
157 * or null if there is no child at this point.
158 * @see javax.accessibility.AccessibleComponent#getAccessibleAt
159 */
160 public static Accessible getAccessibleAt(Component c, Point p)
161 {
162 return c.getAccessibleContext().getAccessibleComponent().getAccessibleAt(p);
163 }
164
165 /**
166 * <p>
167 * Returns the <code>Accessible</code> child of the specified component
168 * that has the supplied index within the parent component. The indexing
169 * of the children is zero-based, making the first child have an index of
170 * 0.
171 * </p>
172 * <p>
173 * Caution is advised when using this method, as its operation relies
174 * on the behaviour of varying implementations of an abstract method.
175 * For greater surety, direct use of the AWT component implementation
176 * of this method is advised.
177 * </p>
178 *
179 * @param c the component whose child should be returned.
180 * @param i the index of the child within the parent component.
181 * @return the <code>Accessible</code> child at index <code>i</code>
182 * in the component, <code>c</code>.
183 * @see javax.accessibility.AccessibleContext#getAccessibleChild
184 * @see java.awt.Component.AccessibleAWTComponent#getAccessibleChild
185 */
186 public static Accessible getAccessibleChild(Component c, int i)
187 {
188 return c.getAccessibleContext().getAccessibleChild(i);
189 }
190
191 /**
192 * <p>
193 * Returns the number of <code>Accessible</code> children within
194 * the supplied component.
195 * </p>
196 * <p>
197 * Caution is advised when using this method, as its operation relies
198 * on the behaviour of varying implementations of an abstract method.
199 * For greater surety, direct use of the AWT component implementation
200 * of this method is advised.
201 * </p>
202 *
203 * @param c the component whose children should be counted.
204 * @return the number of children belonging to the component,
205 * <code>c</code>.
206 * @see javax.accessibility.AccessibleContext#getAccessibleChildrenCount
207 * @see java.awt.Component.AccessibleAWTComponent#getAccessibleChildrenCount
208 */
209 public static int getAccessibleChildrenCount(Component c)
210 {
211 return c.getAccessibleContext().getAccessibleChildrenCount();
212 }
213
214 /**
215 * <p>
216 * Returns the zero-based index of the specified component
217 * within its parent. If the component doesn't have a parent,
218 * -1 is returned.
219 * </p>
220 * <p>
221 * Caution is advised when using this method, as its operation relies
222 * on the behaviour of varying implementations of an abstract method.
223 * For greater surety, direct use of the AWT component implementation
224 * of this method is advised.
225 * </p>
226 *
227 * @param c the component whose parental index should be found.
228 * @return the index of the component within its parent, or -1
229 * if the component doesn't have a parent.
230 * @see javax.accessibility.AccessibleContext#getAccessibleIndexInParent
231 * @see java.awt.Component.AccessibleAWTComponent#getAccessibleIndexInParent
232 */
233 public static int getAccessibleIndexInParent(Component c)
234 {
235 return c.getAccessibleContext().getAccessibleIndexInParent();
236 }
237
238 /**
239 * <p>
240 * Returns a set of <code>AccessibleState</code>s, which represent
241 * the state of the supplied component.
242 * </p>
243 * <p>
244 * Caution is advised when using this method, as its operation relies
245 * on the behaviour of varying implementations of an abstract method.
246 * For greater surety, direct use of the AWT component implementation
247 * of this method is advised.
248 * </p>
249 *
250 * @param c the component whose accessible state should be retrieved.
251 * @return a set of <code>AccessibleState</code> objects, which represent
252 * the state of the supplied component.
253 * @see javax.accessibility.AccessibleContext#getAccessibleStateSet
254 * @see java.awt.Component.AccessibleAWTComponent#getAccessibleStateSet
255 */
256 public static AccessibleStateSet getAccessibleStateSet(Component c)
257 {
258 return c.getAccessibleContext().getAccessibleStateSet();
259 }
260
261 /**
262 * Calculates the bounds of a component in the component's own coordinate
263 * space. The result has the same height and width as the component's
264 * bounds, but its location is set to (0,0).
265 *
266 * @param aComponent The component to measure
267 *
268 * @return The component's bounds in its local coordinate space
269 */
270 public static Rectangle getLocalBounds(Component aComponent)
271 {
272 Rectangle bounds = aComponent.getBounds();
273 return new Rectangle(0, 0, bounds.width, bounds.height);
274 }
275
276 /**
277 * If <code>comp</code> is a RootPaneContainer, return its JRootPane.
278 * Otherwise call <code>getAncestorOfClass(JRootPane.class, a)</code>.
279 *
280 * @param comp The component to get the JRootPane of
281 *
282 * @return a suitable JRootPane for <code>comp</code>, or <code>null</code>
283 *
284 * @see javax.swing.RootPaneContainer#getRootPane
285 * @see #getAncestorOfClass
286 */
287 public static JRootPane getRootPane(Component comp)
288 {
289 if (comp instanceof RootPaneContainer)
290 return ((RootPaneContainer)comp).getRootPane();
291 else
292 return (JRootPane) getAncestorOfClass(JRootPane.class, comp);
293 }
294
295 /**
296 * Returns the least ancestor of <code>comp</code> which has the
297 * specified name.
298 *
299 * @param name The name to search for
300 * @param comp The component to search the ancestors of
301 *
302 * @return The nearest ancestor of <code>comp</code> with the given
303 * name, or <code>null</code> if no such ancestor exists
304 *
305 * @see java.awt.Component#getName
306 * @see #getAncestorOfClass
307 */
308 public static Container getAncestorNamed(String name, Component comp)
309 {
310 while (comp != null && (comp.getName() != name))
311 comp = comp.getParent();
312 return (Container) comp;
313 }
314
315 /**
316 * Returns the least ancestor of <code>comp</code> which is an instance
317 * of the specified class.
318 *
319 * @param c The class to search for
320 * @param comp The component to search the ancestors of
321 *
322 * @return The nearest ancestor of <code>comp</code> which is an instance
323 * of the given class, or <code>null</code> if no such ancestor exists
324 *
325 * @see #getAncestorOfClass
326 * @see #windowForComponent
327 */
328 public static Container getAncestorOfClass(Class<?> c, Component comp)
329 {
330 while (comp != null && (! c.isInstance(comp)))
331 comp = comp.getParent();
332 return (Container) comp;
333 }
334
335 /**
336 * Returns the first ancestor of <code>comp</code> that is a {@link Window}
337 * or <code>null</code> if <code>comp</code> is not contained in a
338 * {@link Window}.
339 *
340 * This is equivalent to calling
341 * <code>getAncestorOfClass(Window, comp)</code> or
342 * <code>windowForComponent(comp)</code>.
343 *
344 * @param comp the component for which we are searching the ancestor Window
345 *
346 * @return the first ancestor Window of <code>comp</code> or
347 * <code>null</code> if <code>comp</code> is not contained in a Window
348 */
349 public static Window getWindowAncestor(Component comp)
350 {
351 return (Window) getAncestorOfClass(Window.class, comp);
352 }
353
354 /**
355 * Equivalent to calling <code>getAncestorOfClass(Window, comp)</code>.
356 *
357 * @param comp The component to search for an ancestor window
358 *
359 * @return An ancestral window, or <code>null</code> if none exists
360 */
361 public static Window windowForComponent(Component comp)
362 {
363 return (Window) getAncestorOfClass(Window.class, comp);
364 }
365
366 /**
367 * Returns the "root" of the component tree containint <code>comp</code>
368 * The root is defined as either the <em>least</em> ancestor of
369 * <code>comp</code> which is a {@link Window}, or the <em>greatest</em>
370 * ancestor of <code>comp</code> which is a {@link Applet} if no {@link
371 * Window} ancestors are found.
372 *
373 * @param comp The component to search for a root
374 *
375 * @return The root of the component's tree, or <code>null</code>
376 */
377 public static Component getRoot(Component comp)
378 {
379 Applet app = null;
380 Window win = null;
381
382 while (comp != null)
383 {
384 if (win == null && comp instanceof Window)
385 win = (Window) comp;
386 else if (comp instanceof Applet)
387 app = (Applet) comp;
388 comp = comp.getParent();
389 }
390
391 if (win != null)
392 return win;
393 return app;
394 }
395
396 /**
397 * Return true if a descends from b, in other words if b is an ancestor of a.
398 *
399 * @param a The child to search the ancestry of
400 * @param b The potential ancestor to search for
401 * @return true if a is a descendent of b, false otherwise
402 */
403 public static boolean isDescendingFrom(Component a, Component b)
404 {
405 while (true)
406 {
407 if (a == null || b == null)
408 return false;
409 if (a == b)
410 return true;
411 a = a.getParent();
412 }
413 }
414
415 /**
416 * Returns the deepest descendent of parent which is both visible and
417 * contains the point <code>(x,y)</code>. Returns parent when either
418 * parent is not a container, or has no children which contain
419 * <code>(x,y)</code>. Returns <code>null</code> when either
420 * <code>(x,y)</code> is outside the bounds of parent, or parent is
421 * <code>null</code>.
422 *
423 * @param parent The component to search the descendents of
424 * @param x Horizontal coordinate to search for
425 * @param y Vertical coordinate to search for
426 *
427 * @return A component containing <code>(x,y)</code>, or
428 * <code>null</code>
429 *
430 * @see java.awt.Container#findComponentAt(int, int)
431 */
432 public static Component getDeepestComponentAt(Component parent, int x, int y)
433 {
434 if (parent == null || (! parent.contains(x, y)))
435 return null;
436
437 if (! (parent instanceof Container))
438 return parent;
439
440 Container c = (Container) parent;
441 return c.findComponentAt(x, y);
442 }
443
444 /**
445 * Converts a point from a component's local coordinate space to "screen"
446 * coordinates (such as the coordinate space mouse events are delivered
447 * in). This operation is equivalent to translating the point by the
448 * location of the component (which is the origin of its coordinate
449 * space).
450 *
451 * @param p The point to convert
452 * @param c The component which the point is expressed in terms of
453 *
454 * @see #convertPointFromScreen
455 */
456 public static void convertPointToScreen(Point p, Component c)
457 {
458 Point c0 = c.getLocationOnScreen();
459 p.translate(c0.x, c0.y);
460 }
461
462 /**
463 * Converts a point from "screen" coordinates (such as the coordinate
464 * space mouse events are delivered in) to a component's local coordinate
465 * space. This operation is equivalent to translating the point by the
466 * negation of the component's location (which is the origin of its
467 * coordinate space).
468 *
469 * @param p The point to convert
470 * @param c The component which the point should be expressed in terms of
471 */
472 public static void convertPointFromScreen(Point p, Component c)
473 {
474 Point c0 = c.getLocationOnScreen();
475 p.translate(-c0.x, -c0.y);
476 }
477
478 /**
479 * Converts a point <code>(x,y)</code> from the coordinate space of one
480 * component to another. This is equivalent to converting the point from
481 * <code>source</code> space to screen space, then back from screen space
482 * to <code>destination</code> space. If exactly one of the two
483 * Components is <code>null</code>, it is taken to refer to the root
484 * ancestor of the other component. If both are <code>null</code>, no
485 * transformation is done.
486 *
487 * @param source The component which the point is expressed in terms of
488 * @param x Horizontal coordinate of point to transform
489 * @param y Vertical coordinate of point to transform
490 * @param destination The component which the return value will be
491 * expressed in terms of
492 *
493 * @return The point <code>(x,y)</code> converted from the coordinate space of the
494 * source component to the coordinate space of the destination component
495 *
496 * @see #convertPointToScreen
497 * @see #convertPointFromScreen
498 * @see #convertRectangle
499 * @see #getRoot
500 */
501 public static Point convertPoint(Component source, int x, int y,
502 Component destination)
503 {
504 Point pt = new Point(x, y);
505
506 if (source == null && destination == null)
507 return pt;
508
509 if (source == null)
510 source = getRoot(destination);
511
512 if (destination == null)
513 destination = getRoot(source);
514
515 if (source.isShowing() && destination.isShowing())
516 {
517 convertPointToScreen(pt, source);
518 convertPointFromScreen(pt, destination);
519 }
520
521 return pt;
522 }
523
524 public static Point convertPoint(Component source, Point aPoint, Component destination)
525 {
526 return convertPoint(source, aPoint.x, aPoint.y, destination);
527 }
528
529 /**
530 * Converts a rectangle from the coordinate space of one component to
531 * another. This is equivalent to converting the rectangle from
532 * <code>source</code> space to screen space, then back from screen space
533 * to <code>destination</code> space. If exactly one of the two
534 * Components is <code>null</code>, it is taken to refer to the root
535 * ancestor of the other component. If both are <code>null</code>, no
536 * transformation is done.
537 *
538 * @param source The component which the rectangle is expressed in terms of
539 * @param rect The rectangle to convert
540 * @param destination The component which the return value will be
541 * expressed in terms of
542 *
543 * @return A new rectangle, equal in size to the input rectangle, but
544 * with its position converted from the coordinate space of the source
545 * component to the coordinate space of the destination component
546 *
547 * @see #convertPointToScreen
548 * @see #convertPointFromScreen
549 * @see #convertPoint(Component, int, int, Component)
550 * @see #getRoot
551 */
552 public static Rectangle convertRectangle(Component source,
553 Rectangle rect,
554 Component destination)
555 {
556 Point pt = convertPoint(source, rect.x, rect.y, destination);
557 return new Rectangle(pt.x, pt.y, rect.width, rect.height);
558 }
559
560 /**
561 * Convert a mouse event which refrers to one component to another. This
562 * includes changing the mouse event's coordinate space, as well as the
563 * source property of the event. If <code>source</code> is
564 * <code>null</code>, it is taken to refer to <code>destination</code>'s
565 * root component. If <code>destination</code> is <code>null</code>, the
566 * new event will remain expressed in <code>source</code>'s coordinate
567 * system.
568 *
569 * @param source The component the mouse event currently refers to
570 * @param sourceEvent The mouse event to convert
571 * @param destination The component the new mouse event should refer to
572 *
573 * @return A new mouse event expressed in terms of the destination
574 * component's coordinate space, and with the destination component as
575 * its source
576 *
577 * @see #convertPoint(Component, int, int, Component)
578 */
579 public static MouseEvent convertMouseEvent(Component source,
580 MouseEvent sourceEvent,
581 Component destination)
582 {
583 Point newpt = convertPoint(source, sourceEvent.getX(), sourceEvent.getY(),
584 destination);
585
586 return new MouseEvent(destination, sourceEvent.getID(),
587 sourceEvent.getWhen(), sourceEvent.getModifiersEx(),
588 newpt.x, newpt.y, sourceEvent.getClickCount(),
589 sourceEvent.isPopupTrigger(), sourceEvent.getButton());
590 }
591
592 /**
593 * Recursively walk the component tree under <code>comp</code> calling
594 * <code>updateUI</code> on each {@link JComponent} found. This causes
595 * the entire tree to re-initialize its UI delegates.
596 *
597 * @param comp The component to walk the children of, calling <code>updateUI</code>
598 */
599 public static void updateComponentTreeUI(Component comp)
600 {
601 updateComponentTreeUIImpl(comp);
602 if (comp instanceof JComponent)
603 {
604 JComponent jc = (JComponent) comp;
605 jc.revalidate();
606 }
607 else
608 {
609 comp.invalidate();
610 comp.validate();
611 }
612 comp.repaint();
613 }
614
615 /**
616 * Performs the actual work for {@link #updateComponentTreeUI(Component)}.
617 * This calls updateUI() on c if it is a JComponent, and then walks down
618 * the component tree and calls this method on each child component.
619 *
620 * @param c the component to update the UI
621 */
622 private static void updateComponentTreeUIImpl(Component c)
623 {
624 if (c instanceof JComponent)
625 {
626 JComponent jc = (JComponent) c;
627 jc.updateUI();
628 }
629
630 Component[] components = null;
631 if (c instanceof JMenu)
632 components = ((JMenu) c).getMenuComponents();
633 else if (c instanceof Container)
634 components = ((Container) c).getComponents();
635 if (components != null)
636 {
637 for (int i = 0; i < components.length; ++i)
638 updateComponentTreeUIImpl(components[i]);
639 }
640 }
641
642 /**
643 * <p>Layout a "compound label" consisting of a text string and an icon
644 * which is to be placed near the rendered text. Once the text and icon
645 * are laid out, the text rectangle and icon rectangle parameters are
646 * altered to store the calculated positions.</p>
647 *
648 * <p>The size of the text is calculated from the provided font metrics
649 * object. This object should be the metrics of the font you intend to
650 * paint the label with.</p>
651 *
652 * <p>The position values control where the text is placed relative to
653 * the icon. The horizontal position value should be one of the constants
654 * <code>LEADING</code>, <code>TRAILING</code>, <code>LEFT</code>,
655 * <code>RIGHT</code> or <code>CENTER</code>. The vertical position value
656 * should be one fo the constants <code>TOP</code>, <code>BOTTOM</code>
657 * or <code>CENTER</code>.</p>
658 *
659 * <p>The text-icon gap value controls the number of pixels between the
660 * icon and the text.</p>
661 *
662 * <p>The alignment values control where the text and icon are placed, as
663 * a combined unit, within the view rectangle. The horizontal alignment
664 * value should be one of the constants <code>LEADING</code>,
665 * <code>TRAILING</code>, <code>LEFT</code>, <code>RIGHT</code> or
666 * <code>CENTER</code>. The vertical alignment valus should be one of the
667 * constants <code>TOP</code>, <code>BOTTOM</code> or
668 * <code>CENTER</code>.</p>
669 *
670 * <p>If the <code>LEADING</code> or <code>TRAILING</code> constants are
671 * given for horizontal alignment or horizontal text position, they are
672 * interpreted relative to the provided component's orientation property,
673 * a constant in the {@link java.awt.ComponentOrientation} class. For
674 * example, if the component's orientation is <code>LEFT_TO_RIGHT</code>,
675 * then the <code>LEADING</code> value is a synonym for <code>LEFT</code>
676 * and the <code>TRAILING</code> value is a synonym for
677 * <code>RIGHT</code></p>
678 *
679 * <p>If the text and icon are equal to or larger than the view
680 * rectangle, the horizontal and vertical alignment values have no
681 * affect.</p>
682 *
683 * @param c A component used for its orientation value
684 * @param fm The font metrics used to measure the text
685 * @param text The text to place in the compound label
686 * @param icon The icon to place next to the text
687 * @param verticalAlignment The vertical alignment of the label relative
688 * to its component
689 * @param horizontalAlignment The horizontal alignment of the label
690 * relative to its component
691 * @param verticalTextPosition The vertical position of the label's text
692 * relative to its icon
693 * @param horizontalTextPosition The horizontal position of the label's
694 * text relative to its icon
695 * @param viewR The view rectangle, specifying the area which layout is
696 * constrained to
697 * @param iconR A rectangle which is modified to hold the laid-out
698 * position of the icon
699 * @param textR A rectangle which is modified to hold the laid-out
700 * position of the text
701 * @param textIconGap The distance between text and icon
702 *
703 * @return The string of characters, possibly truncated with an elipsis,
704 * which is laid out in this label
705 */
706
707 public static String layoutCompoundLabel(JComponent c,
708 FontMetrics fm,
709 String text,
710 Icon icon,
711 int verticalAlignment,
712 int horizontalAlignment,
713 int verticalTextPosition,
714 int horizontalTextPosition,
715 Rectangle viewR,
716 Rectangle iconR,
717 Rectangle textR,
718 int textIconGap)
719 {
720
721 // Fix up the orientation-based horizontal positions.
722
723 boolean ltr = true;
724 if (c != null && ! c.getComponentOrientation().isLeftToRight())
725 ltr = false;
726
727 switch (horizontalTextPosition)
728 {
729 case LEADING:
730 horizontalTextPosition = ltr ? LEFT : RIGHT;
731 break;
732 case TRAILING:
733 horizontalTextPosition = ltr ? RIGHT : LEFT;
734 break;
735 default:
736 // Nothing to do in the other cases.
737 }
738
739 // Fix up the orientation-based alignments.
740 switch (horizontalAlignment)
741 {
742 case LEADING:
743 horizontalAlignment = ltr ? LEFT : RIGHT;
744 break;
745 case TRAILING:
746 horizontalAlignment = ltr ? RIGHT : LEFT;
747 break;
748 default:
749 // Nothing to do in the other cases.
750 }
751
752 return layoutCompoundLabelImpl(c, fm, text, icon,
753 verticalAlignment,
754 horizontalAlignment,
755 verticalTextPosition,
756 horizontalTextPosition,
757 viewR, iconR, textR, textIconGap);
758 }
759
760 /**
761 * <p>Layout a "compound label" consisting of a text string and an icon
762 * which is to be placed near the rendered text. Once the text and icon
763 * are laid out, the text rectangle and icon rectangle parameters are
764 * altered to store the calculated positions.</p>
765 *
766 * <p>The size of the text is calculated from the provided font metrics
767 * object. This object should be the metrics of the font you intend to
768 * paint the label with.</p>
769 *
770 * <p>The position values control where the text is placed relative to
771 * the icon. The horizontal position value should be one of the constants
772 * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>. The
773 * vertical position value should be one fo the constants
774 * <code>TOP</code>, <code>BOTTOM</code> or <code>CENTER</code>.</p>
775 *
776 * <p>The text-icon gap value controls the number of pixels between the
777 * icon and the text.</p>
778 *
779 * <p>The alignment values control where the text and icon are placed, as
780 * a combined unit, within the view rectangle. The horizontal alignment
781 * value should be one of the constants <code>LEFT</code>, <code>RIGHT</code> or
782 * <code>CENTER</code>. The vertical alignment valus should be one of the
783 * constants <code>TOP</code>, <code>BOTTOM</code> or
784 * <code>CENTER</code>.</p>
785 *
786 * <p>If the text and icon are equal to or larger than the view
787 * rectangle, the horizontal and vertical alignment values have no
788 * affect.</p>
789 *
790 * <p>Note that this method does <em>not</em> know how to deal with
791 * horizontal alignments or positions given as <code>LEADING</code> or
792 * <code>TRAILING</code> values. Use the other overloaded variant of this
793 * method if you wish to use such values.
794 *
795 * @param fm The font metrics used to measure the text
796 * @param text The text to place in the compound label
797 * @param icon The icon to place next to the text
798 * @param verticalAlignment The vertical alignment of the label relative
799 * to its component
800 * @param horizontalAlignment The horizontal alignment of the label
801 * relative to its component
802 * @param verticalTextPosition The vertical position of the label's text
803 * relative to its icon
804 * @param horizontalTextPosition The horizontal position of the label's
805 * text relative to its icon
806 * @param viewR The view rectangle, specifying the area which layout is
807 * constrained to
808 * @param iconR A rectangle which is modified to hold the laid-out
809 * position of the icon
810 * @param textR A rectangle which is modified to hold the laid-out
811 * position of the text
812 * @param textIconGap The distance between text and icon
813 *
814 * @return The string of characters, possibly truncated with an elipsis,
815 * which is laid out in this label
816 */
817
818 public static String layoutCompoundLabel(FontMetrics fm,
819 String text,
820 Icon icon,
821 int verticalAlignment,
822 int horizontalAlignment,
823 int verticalTextPosition,
824 int horizontalTextPosition,
825 Rectangle viewR,
826 Rectangle iconR,
827 Rectangle textR,
828 int textIconGap)
829 {
830 return layoutCompoundLabelImpl(null, fm, text, icon, verticalAlignment,
831 horizontalAlignment, verticalTextPosition,
832 horizontalTextPosition, viewR, iconR, textR,
833 textIconGap);
834 }
835
836 /**
837 * <p>Layout a "compound label" consisting of a text string and an icon
838 * which is to be placed near the rendered text. Once the text and icon
839 * are laid out, the text rectangle and icon rectangle parameters are
840 * altered to store the calculated positions.</p>
841 *
842 * <p>The size of the text is calculated from the provided font metrics
843 * object. This object should be the metrics of the font you intend to
844 * paint the label with.</p>
845 *
846 * <p>The position values control where the text is placed relative to
847 * the icon. The horizontal position value should be one of the constants
848 * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>. The
849 * vertical position value should be one fo the constants
850 * <code>TOP</code>, <code>BOTTOM</code> or <code>CENTER</code>.</p>
851 *
852 * <p>The text-icon gap value controls the number of pixels between the
853 * icon and the text.</p>
854 *
855 * <p>The alignment values control where the text and icon are placed, as
856 * a combined unit, within the view rectangle. The horizontal alignment
857 * value should be one of the constants <code>LEFT</code>, <code>RIGHT</code> or
858 * <code>CENTER</code>. The vertical alignment valus should be one of the
859 * constants <code>TOP</code>, <code>BOTTOM</code> or
860 * <code>CENTER</code>.</p>
861 *
862 * <p>If the text and icon are equal to or larger than the view
863 * rectangle, the horizontal and vertical alignment values have no
864 * affect.</p>
865 *
866 * <p>Note that this method does <em>not</em> know how to deal with
867 * horizontal alignments or positions given as <code>LEADING</code> or
868 * <code>TRAILING</code> values. Use the other overloaded variant of this
869 * method if you wish to use such values.
870 *
871 * @param fm The font metrics used to measure the text
872 * @param text The text to place in the compound label
873 * @param icon The icon to place next to the text
874 * @param verticalAlignment The vertical alignment of the label relative
875 * to its component
876 * @param horizontalAlignment The horizontal alignment of the label
877 * relative to its component
878 * @param verticalTextPosition The vertical position of the label's text
879 * relative to its icon
880 * @param horizontalTextPosition The horizontal position of the label's
881 * text relative to its icon
882 * @param viewR The view rectangle, specifying the area which layout is
883 * constrained to
884 * @param iconR A rectangle which is modified to hold the laid-out
885 * position of the icon
886 * @param textR A rectangle which is modified to hold the laid-out
887 * position of the text
888 * @param textIconGap The distance between text and icon
889 *
890 * @return The string of characters, possibly truncated with an elipsis,
891 * which is laid out in this label
892 */
893 private static String layoutCompoundLabelImpl(JComponent c,
894 FontMetrics fm,
895 String text,
896 Icon icon,
897 int verticalAlignment,
898 int horizontalAlignment,
899 int verticalTextPosition,
900 int horizontalTextPosition,
901 Rectangle viewR,
902 Rectangle iconR,
903 Rectangle textR,
904 int textIconGap)
905 {
906
907 // Work out basic height and width.
908
909 if (icon == null)
910 {
911 textIconGap = 0;
912 iconR.width = 0;
913 iconR.height = 0;
914 }
915 else
916 {
917 iconR.width = icon.getIconWidth();
918 iconR.height = icon.getIconHeight();
919 }
920
921 if (text == null || text.equals(""))
922 {
923 textIconGap = 0;
924 textR.width = 0;
925 textR.height = 0;
926 text = "";
927 }
928 else
929 {
930 int availableWidth = viewR.width;
931 if (horizontalTextPosition != CENTER)
932 availableWidth -= iconR.width + textIconGap;
933 View html = c == null ? null
934 : (View) c.getClientProperty(BasicHTML.propertyKey);
935 if (html != null)
936 {
937 textR.width = (int) html.getPreferredSpan(View.X_AXIS);
938 textR.width = Math.min(availableWidth, textR.width);
939 textR.height = (int) html.getPreferredSpan(View.Y_AXIS);
940 }
941 else
942 {
943 int fromIndex = 0;
944 textR.width = fm.stringWidth(text);
945 textR.height = fm.getHeight();
946 if (textR.width > availableWidth)
947 {
948 text = clipString(c, fm, text, availableWidth);
949 textR.width = fm.stringWidth(text);
950 }
951 }
952 }
953
954 // Work out the position of text, assuming the top-left coord
955 // starts at (0,0). We will fix that up momentarily, after these
956 // "position" decisions are made and we look at alignment.
957
958 switch (verticalTextPosition)
959 {
960 case TOP:
961 textR.y = horizontalTextPosition == CENTER ?
962 - textR.height - textIconGap : 0;
963 break;
964 case BOTTOM:
965 textR.y = horizontalTextPosition == CENTER ?
966 iconR.height + textIconGap : iconR.height - textR.height;
967 break;
968 case CENTER:
969 textR.y = iconR.height / 2 - textR.height / 2;
970 break;
971 }
972
973 switch (horizontalTextPosition)
974 {
975 case LEFT:
976 textR.x = -(textR.width + textIconGap);
977 break;
978 case RIGHT:
979 textR.x = iconR.width + textIconGap;
980 break;
981 case CENTER:
982 textR.x = iconR.width / 2 - textR.width / 2;
983 break;
984 }
985
986 // The two rectangles are laid out correctly now, but only assuming
987 // that their upper left corner is at (0,0). If we have any alignment other
988 // than TOP and LEFT, we need to adjust them.
989
990 // These coordinates specify the rectangle that contains both the
991 // icon and text. Move it so that it fullfills the alignment properties.
992 int lx = Math.min(iconR.x, textR.x);
993 int lw = Math.max(iconR.x + iconR.width, textR.x + textR.width) - lx;
994 int ly = Math.min(iconR.y, textR.y);
995 int lh = Math.max(iconR.y + iconR.height, textR.y + textR.height) - ly;
996 int horizontalAdjustment = 0;
997 int verticalAdjustment = 0;
998 switch (verticalAlignment)
999 {
1000 case TOP:
1001 verticalAdjustment = viewR.y - ly;
1002 break;
1003 case BOTTOM:
1004 verticalAdjustment = viewR.y + viewR.height - ly - lh;
1005 break;
1006 case CENTER:
1007 verticalAdjustment = viewR.y + viewR.height / 2 - ly - lh / 2;
1008 break;
1009 }
1010 switch (horizontalAlignment)
1011 {
1012 case LEFT:
1013 horizontalAdjustment = viewR.x - lx;
1014 break;
1015 case RIGHT:
1016 horizontalAdjustment = viewR.x + viewR.width - lx - lw;
1017 break;
1018 case CENTER:
1019 horizontalAdjustment = (viewR.x + (viewR.width / 2)) - (lx + (lw / 2));
1020 break;
1021 }
1022 iconR.x += horizontalAdjustment;
1023 iconR.y += verticalAdjustment;
1024
1025 textR.x += horizontalAdjustment;
1026 textR.y += verticalAdjustment;
1027
1028 return text;
1029 }
1030
1031 /**
1032 * The method clips the specified string so that it fits into the
1033 * available width. It is only called when the text really doesn't fit,
1034 * so we don't need to check that again.
1035 *
1036 * @param c the component
1037 * @param fm the font metrics
1038 * @param text the text
1039 * @param availableWidth the available width
1040 *
1041 * @return the clipped string
1042 */
1043 private static String clipString(JComponent c, FontMetrics fm, String text,
1044 int availableWidth)
1045 {
1046 String dots = "...";
1047 int dotsWidth = fm.stringWidth(dots);
1048 char[] string = text.toCharArray();
1049 int endIndex = string.length;
1050 while (fm.charsWidth(string, 0, endIndex) + dotsWidth > availableWidth
1051 && endIndex > 0)
1052 endIndex--;
1053 String clipped;
1054 if (string.length >= endIndex + 3)
1055 {
1056 string[endIndex] = '.';
1057 string[endIndex + 1] = '.';
1058 string[endIndex + 2] = '.';
1059 clipped = new String(string, 0, endIndex + 3);
1060 }
1061 else
1062 {
1063 char[] clippedChars = new char[string.length + 3];
1064 System.arraycopy(string, 0, clippedChars, 0, string.length);
1065 clippedChars[endIndex] = '.';
1066 clippedChars[endIndex + 1] = '.';
1067 clippedChars[endIndex + 2] = '.';
1068 clipped = new String(clippedChars, 0, endIndex + 3);
1069 }
1070 return clipped;
1071 }
1072
1073 /**
1074 * Calls {@link java.awt.EventQueue#invokeLater} with the
1075 * specified {@link Runnable}.
1076 */
1077 public static void invokeLater(Runnable doRun)
1078 {
1079 java.awt.EventQueue.invokeLater(doRun);
1080 }
1081
1082 /**
1083 * Calls {@link java.awt.EventQueue#invokeAndWait} with the
1084 * specified {@link Runnable}.
1085 */
1086 public static void invokeAndWait(Runnable doRun)
1087 throws InterruptedException,
1088 InvocationTargetException
1089 {
1090 java.awt.EventQueue.invokeAndWait(doRun);
1091 }
1092
1093 /**
1094 * Calls {@link java.awt.EventQueue#isDispatchThread()}.
1095 *
1096 * @return <code>true</code> if the current thread is the current AWT event
1097 * dispatch thread.
1098 */
1099 public static boolean isEventDispatchThread()
1100 {
1101 return java.awt.EventQueue.isDispatchThread();
1102 }
1103
1104 /**
1105 * This method paints the given component at the given position and size.
1106 * The component will be reparented to the container given.
1107 *
1108 * @param g The Graphics object to draw with.
1109 * @param c The Component to draw
1110 * @param p The Container to reparent to.
1111 * @param x The x coordinate to draw at.
1112 * @param y The y coordinate to draw at.
1113 * @param w The width of the drawing area.
1114 * @param h The height of the drawing area.
1115 */
1116 public static void paintComponent(Graphics g, Component c, Container p,
1117 int x, int y, int w, int h)
1118 {
1119 Container parent = c.getParent();
1120 if (parent != null)
1121 parent.remove(c);
1122 if (p != null)
1123 p.add(c);
1124
1125 Shape savedClip = g.getClip();
1126
1127 g.setClip(x, y, w, h);
1128 g.translate(x, y);
1129
1130 c.paint(g);
1131
1132 g.translate(-x, -y);
1133 g.setClip(savedClip);
1134 }
1135
1136 /**
1137 * This method paints the given component in the given rectangle.
1138 * The component will be reparented to the container given.
1139 *
1140 * @param g The Graphics object to draw with.
1141 * @param c The Component to draw
1142 * @param p The Container to reparent to.
1143 * @param r The rectangle that describes the drawing area.
1144 */
1145 public static void paintComponent(Graphics g, Component c,
1146 Container p, Rectangle r)
1147 {
1148 paintComponent(g, c, p, r.x, r.y, r.width, r.height);
1149 }
1150
1151 /**
1152 * This method returns the common Frame owner used in JDialogs or
1153 * JWindow when no owner is provided.
1154 *
1155 * @return The common Frame
1156 */
1157 static Window getOwnerFrame(Window owner)
1158 {
1159 Window result = owner;
1160 if (result == null)
1161 {
1162 if (ownerFrame == null)
1163 ownerFrame = new OwnerFrame();
1164 result = ownerFrame;
1165 }
1166 return result;
1167 }
1168
1169 /**
1170 * Checks if left mouse button was clicked.
1171 *
1172 * @param event the event to check
1173 *
1174 * @return true if left mouse was clicked, false otherwise.
1175 */
1176 public static boolean isLeftMouseButton(MouseEvent event)
1177 {
1178 return ((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0);
1179 }
1180
1181 /**
1182 * Checks if middle mouse button was clicked.
1183 *
1184 * @param event the event to check
1185 *
1186 * @return true if middle mouse was clicked, false otherwise.
1187 */
1188 public static boolean isMiddleMouseButton(MouseEvent event)
1189 {
1190 return ((event.getModifiersEx() & InputEvent.BUTTON2_DOWN_MASK)
1191 == InputEvent.BUTTON2_DOWN_MASK);
1192 }
1193
1194 /**
1195 * Checks if right mouse button was clicked.
1196 *
1197 * @param event the event to check
1198 *
1199 * @return true if right mouse was clicked, false otherwise.
1200 */
1201 public static boolean isRightMouseButton(MouseEvent event)
1202 {
1203 return ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK)
1204 == InputEvent.BUTTON3_DOWN_MASK);
1205 }
1206
1207 /**
1208 * This frame should be used when constructing a Window/JDialog without
1209 * a parent. In this case, we are forced to use this frame as a window's
1210 * parent, because we simply cannot pass null instead of parent to Window
1211 * constructor, since doing it will result in NullPointerException.
1212 */
1213 private static class OwnerFrame extends Frame
1214 {
1215 public void setVisible(boolean b)
1216 {
1217 // Do nothing here.
1218 }
1219
1220 public boolean isShowing()
1221 {
1222 return true;
1223 }
1224 }
1225
1226 public static boolean notifyAction(Action action,
1227 KeyStroke ks,
1228 KeyEvent event,
1229 Object sender,
1230 int modifiers)
1231 {
1232 if (action != null && action.isEnabled())
1233 {
1234 String name = (String) action.getValue(Action.ACTION_COMMAND_KEY);
1235 if (name == null
1236 && event.getKeyChar() != KeyEvent.CHAR_UNDEFINED)
1237 name = new String(new char[] {event.getKeyChar()});
1238 action.actionPerformed(new ActionEvent(sender,
1239 ActionEvent.ACTION_PERFORMED,
1240 name, modifiers));
1241 return true;
1242 }
1243 return false;
1244 }
1245
1246 /**
1247 * <p>Change the shared, UI-managed {@link ActionMap} for a given
1248 * component. ActionMaps are arranged in a hierarchy, in order to
1249 * encourage sharing of common actions between components. The hierarchy
1250 * unfortunately places UI-managed ActionMaps at the <em>end</em> of the
1251 * parent-pointer chain, as illustrated:</p>
1252 *
1253 * <pre>
1254 * [{@link javax.swing.JComponent#getActionMap()}]
1255 * --> [{@link javax.swing.ActionMap}]
1256 * parent --> [{@link javax.swing.text.JTextComponent.KeymapActionMap}]
1257 * parent --> [{@link javax.swing.plaf.ActionMapUIResource}]
1258 * </pre>
1259 *
1260 * <p>Our goal with this method is to replace the first ActionMap along
1261 * this chain which is an instance of {@link ActionMapUIResource}, since
1262 * these are the ActionMaps which are supposed to be shared between
1263 * components.</p>
1264 *
1265 * <p>If the provided ActionMap is <code>null</code>, we interpret the
1266 * call as a request to remove the UI-managed ActionMap from the
1267 * component's ActionMap parent chain.</p>
1268 */
1269 public static void replaceUIActionMap(JComponent component,
1270 ActionMap uiActionMap)
1271 {
1272 ActionMap child = component.getActionMap();
1273 if (child == null)
1274 component.setActionMap(uiActionMap);
1275 else
1276 {
1277 ActionMap parent = child.getParent();
1278 while (parent != null && !(parent instanceof ActionMapUIResource))
1279 {
1280 child = parent;
1281 parent = child.getParent();
1282 }
1283 // Sanity check to avoid loops.
1284 if (child != uiActionMap)
1285 child.setParent(uiActionMap);
1286 }
1287 }
1288
1289 /**
1290 * <p>Change the shared, UI-managed {@link InputMap} for a given
1291 * component. InputMaps are arranged in a hierarchy, in order to
1292 * encourage sharing of common input mappings between components. The
1293 * hierarchy unfortunately places UI-managed InputMaps at the
1294 * <em>end</em> of the parent-pointer chain, as illustrated:</p>
1295 *
1296 * <pre>
1297 * [{@link javax.swing.JComponent#getInputMap()}]
1298 * --> [{@link javax.swing.InputMap}]
1299 * parent --> [{@link javax.swing.text.JTextComponent.KeymapWrapper}]
1300 * parent --> [{@link javax.swing.plaf.InputMapUIResource}]
1301 * </pre>
1302 *
1303 * <p>Our goal with this method is to replace the first InputMap along
1304 * this chain which is an instance of {@link InputMapUIResource}, since
1305 * these are the InputMaps which are supposed to be shared between
1306 * components.</p>
1307 *
1308 * <p>If the provided InputMap is <code>null</code>, we interpret the
1309 * call as a request to remove the UI-managed InputMap from the
1310 * component's InputMap parent chain.</p>
1311 */
1312 public static void replaceUIInputMap(JComponent component,
1313 int condition,
1314 InputMap uiInputMap)
1315 {
1316 InputMap child = component.getInputMap(condition);
1317 if (child == null)
1318 component.setInputMap(condition, uiInputMap);
1319 else
1320 {
1321 InputMap parent = child.getParent();
1322 while (parent != null && !(parent instanceof InputMapUIResource))
1323 {
1324 child = parent;
1325 parent = parent.getParent();
1326 }
1327 // Sanity check to avoid loops.
1328 if (child != uiInputMap)
1329 child.setParent(uiInputMap);
1330 }
1331 }
1332
1333 /**
1334 * Subtracts a rectangle from another and return the area as an array
1335 * of rectangles.
1336 * Returns the areas of rectA which are not covered by rectB.
1337 * If the rectangles do not overlap, or if either parameter is
1338 * <code>null</code>, a zero-size array is returned.
1339 * @param rectA The first rectangle
1340 * @param rectB The rectangle to subtract from the first
1341 * @return An array of rectangles representing the area in rectA
1342 * not overlapped by rectB
1343 */
1344 public static Rectangle[] computeDifference(Rectangle rectA, Rectangle rectB)
1345 {
1346 if (rectA == null || rectB == null)
1347 return new Rectangle[0];
1348
1349 Rectangle[] r = new Rectangle[4];
1350 int x1 = rectA.x;
1351 int y1 = rectA.y;
1352 int w1 = rectA.width;
1353 int h1 = rectA.height;
1354 int x2 = rectB.x;
1355 int y2 = rectB.y;
1356 int w2 = rectB.width;
1357 int h2 = rectB.height;
1358
1359 // (outer box = rectA)
1360 // -------------
1361 // |_____0_____|
1362 // | |rectB| |
1363 // |_1|_____|_2|
1364 // | 3 |
1365 // -------------
1366 int H0 = (y2 > y1) ? y2 - y1 : 0; // height of box 0
1367 int H3 = (y2 + h2 < y1 + h1) ? y1 + h1 - y2 - h2 : 0; // height box 3
1368 int W1 = (x2 > x1) ? x2 - x1 : 0; // width box 1
1369 int W2 = (x1 + w1 > x2 + w2) ? x1 + w1 - x2 - w2 : 0; // w. box 2
1370 int H12 = (H0 + H3 < h1) ? h1 - H0 - H3 : 0; // height box 1 & 2
1371
1372 if (H0 > 0)
1373 r[0] = new Rectangle(x1, y1, w1, H0);
1374 else
1375 r[0] = null;
1376
1377 if (W1 > 0 && H12 > 0)
1378 r[1] = new Rectangle(x1, y1 + H0, W1, H12);
1379 else
1380 r[1] = null;
1381
1382 if (W2 > 0 && H12 > 0)
1383 r[2] = new Rectangle(x2 + w2, y1 + H0, W2, H12);
1384 else
1385 r[2] = null;
1386
1387 if (H3 > 0)
1388 r[3] = new Rectangle(x1, y1 + H0 + H12, w1, H3);
1389 else
1390 r[3] = null;
1391
1392 // sort out null objects
1393 int n = 0;
1394 for (int i = 0; i < 4; i++)
1395 if (r[i] != null)
1396 n++;
1397 Rectangle[] out = new Rectangle[n];
1398 for (int i = 3; i >= 0; i--)
1399 if (r[i] != null)
1400 out[--n] = r[i];
1401
1402 return out;
1403 }
1404
1405 /**
1406 * Calculates the intersection of two rectangles. The result is stored
1407 * in <code>rect</code>. This is basically the same
1408 * like {@link Rectangle#intersection(Rectangle)}, only that it does not
1409 * create new Rectangle instances. The tradeoff is that you loose any data in
1410 * <code>rect</code>.
1411 *
1412 * @param x upper-left x coodinate of first rectangle
1413 * @param y upper-left y coodinate of first rectangle
1414 * @param w width of first rectangle
1415 * @param h height of first rectangle
1416 * @param rect a Rectangle object of the second rectangle
1417 *
1418 * @throws NullPointerException if rect is null
1419 *
1420 * @return a rectangle corresponding to the intersection of the
1421 * two rectangles. An empty rectangle is returned if the rectangles
1422 * do not overlap
1423 */
1424 public static Rectangle computeIntersection(int x, int y, int w, int h,
1425 Rectangle rect)
1426 {
1427 int x2 = (int) rect.x;
1428 int y2 = (int) rect.y;
1429 int w2 = (int) rect.width;
1430 int h2 = (int) rect.height;
1431
1432 int dx = (x > x2) ? x : x2;
1433 int dy = (y > y2) ? y : y2;
1434 int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
1435 int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
1436
1437 if (dw >= 0 && dh >= 0)
1438 rect.setBounds(dx, dy, dw, dh);
1439 else
1440 rect.setBounds(0, 0, 0, 0);
1441
1442 return rect;
1443 }
1444
1445 /**
1446 * Calculates the width of a given string.
1447 *
1448 * @param fm the <code>FontMetrics</code> object to use
1449 * @param str the string
1450 *
1451 * @return the width of the the string.
1452 */
1453 public static int computeStringWidth(FontMetrics fm, String str)
1454 {
1455 return fm.stringWidth(str);
1456 }
1457
1458 /**
1459 * Calculates the union of two rectangles. The result is stored in
1460 * <code>rect</code>. This is basically the same as
1461 * {@link Rectangle#union(Rectangle)} except that it avoids creation of new
1462 * Rectangle objects. The tradeoff is that you loose any data in
1463 * <code>rect</code>.
1464 *
1465 * @param x upper-left x coodinate of first rectangle
1466 * @param y upper-left y coodinate of first rectangle
1467 * @param w width of first rectangle
1468 * @param h height of first rectangle
1469 * @param rect a Rectangle object of the second rectangle
1470 *
1471 * @throws NullPointerException if rect is null
1472 *
1473 * @return a rectangle corresponding to the union of the
1474 * two rectangles; a rectangle encompassing both is returned if the
1475 * rectangles do not overlap
1476 */
1477 public static Rectangle computeUnion(int x, int y, int w, int h,
1478 Rectangle rect)
1479 {
1480 int x2 = (int) rect.x;
1481 int y2 = (int) rect.y;
1482 int w2 = (int) rect.width;
1483 int h2 = (int) rect.height;
1484
1485 int dx = (x < x2) ? x : x2;
1486 int dy = (y < y2) ? y : y2;
1487 int dw = (x + w > x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
1488 int dh = (y + h > y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
1489
1490 if (dw >= 0 && dh >= 0)
1491 rect.setBounds(dx, dy, dw, dh);
1492 else
1493 rect.setBounds(0, 0, 0, 0);
1494 return rect;
1495 }
1496
1497 /**
1498 * Tests if a rectangle contains another.
1499 * @param a first rectangle
1500 * @param b second rectangle
1501 * @return true if a contains b, false otherwise
1502 * @throws NullPointerException
1503 */
1504 public static boolean isRectangleContainingRectangle(Rectangle a, Rectangle b)
1505 {
1506 // Note: zero-size rects inclusive, differs from Rectangle.contains()
1507 return b.width >= 0 && b.height >= 0 && b.width >= 0 && b.height >= 0
1508 && b.x >= a.x && b.x + b.width <= a.x + a.width && b.y >= a.y
1509 && b.y + b.height <= a.y + a.height;
1510 }
1511
1512 /**
1513 * Returns the InputMap that is provided by the ComponentUI of
1514 * <code>component</code> for the specified condition.
1515 *
1516 * @param component the component for which the InputMap is returned
1517 * @param cond the condition that specifies which of the three input
1518 * maps should be returned, may be
1519 * {@link JComponent#WHEN_IN_FOCUSED_WINDOW},
1520 * {@link JComponent#WHEN_FOCUSED} or
1521 * {@link JComponent#WHEN_ANCESTOR_OF_FOCUSED_COMPONENT}
1522 *
1523 * @return The input map.
1524 */
1525 public static InputMap getUIInputMap(JComponent component, int cond)
1526 {
1527 if (UIManager.getUI(component) != null)
1528 // we assume here that the UI class sets the parent of the component's
1529 // InputMap, which is the correct behaviour. If it's not, then
1530 // this can be considered a bug
1531 return component.getInputMap(cond).getParent();
1532 else
1533 return null;
1534 }
1535
1536 /**
1537 * Returns the ActionMap that is provided by the ComponentUI of
1538 * <code>component</code>.
1539 *
1540 * @param component the component for which the ActionMap is returned
1541 */
1542 public static ActionMap getUIActionMap(JComponent component)
1543 {
1544 if (UIManager.getUI(component) != null)
1545 // we assume here that the UI class sets the parent of the component's
1546 // ActionMap, which is the correct behaviour. If it's not, then
1547 // this can be considered a bug
1548 return component.getActionMap().getParent();
1549 else
1550 return null;
1551 }
1552
1553 /**
1554 * Processes key bindings for the component that is associated with the
1555 * key event. Note that this method does not make sense for
1556 * JComponent-derived components, except when
1557 * {@link JComponent#processKeyEvent(KeyEvent)} is overridden and super is
1558 * not called.
1559 *
1560 * This method searches through the component hierarchy of the component's
1561 * top-level container to find a <code>JComponent</code> that has a binding
1562 * for the key event in the WHEN_IN_FOCUSED_WINDOW scope.
1563 *
1564 * @param ev the key event
1565 *
1566 * @return <code>true</code> if a binding has been found and processed,
1567 * <code>false</code> otherwise
1568 *
1569 * @since 1.4
1570 */
1571 public static boolean processKeyBindings(KeyEvent ev)
1572 {
1573 Component c = ev.getComponent();
1574 KeyStroke s = KeyStroke.getKeyStrokeForEvent(ev);
1575 KeyboardManager km = KeyboardManager.getManager();
1576 return km.processKeyStroke(c, s, ev);
1577 }
1578
1579 /**
1580 * Returns a string representing one of the horizontal alignment codes
1581 * defined in the {@link SwingConstants} interface. The following table
1582 * lists the constants and return values:
1583 * <p>
1584 * <table border="0">
1585 * <tr>
1586 * <th>Code:</th><th>Returned String:</th>
1587 * </tr>
1588 * <tr>
1589 * <td>{@link SwingConstants#CENTER}</td>
1590 * <td><code>"CENTER"</code></td>
1591 * </tr>
1592 * <tr>
1593 * <td>{@link SwingConstants#LEFT}</td>
1594 * <td><code>"LEFT"</code></td>
1595 * </tr>
1596 * <tr>
1597 * <td>{@link SwingConstants#RIGHT}</td>
1598 * <td><code>"RIGHT"</code></td>
1599 * </tr>
1600 * <tr>
1601 * <td>{@link SwingConstants#LEADING}</td>
1602 * <td><code>"LEADING"</code></td>
1603 * </tr>
1604 * <tr>
1605 * <td>{@link SwingConstants#TRAILING}</td>
1606 * <td><code>"TRAILING"</code></td>
1607 * </tr>
1608 * </table>
1609 * </p>
1610 * If the supplied code is not one of those listed, this methods will throw
1611 * an {@link IllegalArgumentException}.
1612 *
1613 * @param code the code.
1614 *
1615 * @return A string representing the given code.
1616 */
1617 static String convertHorizontalAlignmentCodeToString(int code)
1618 {
1619 switch (code)
1620 {
1621 case SwingConstants.CENTER:
1622 return "CENTER";
1623 case SwingConstants.LEFT:
1624 return "LEFT";
1625 case SwingConstants.RIGHT:
1626 return "RIGHT";
1627 case SwingConstants.LEADING:
1628 return "LEADING";
1629 case SwingConstants.TRAILING:
1630 return "TRAILING";
1631 default:
1632 throw new IllegalArgumentException("Unrecognised code: " + code);
1633 }
1634 }
1635
1636 /**
1637 * Returns a string representing one of the vertical alignment codes
1638 * defined in the {@link SwingConstants} interface. The following table
1639 * lists the constants and return values:
1640 * <p>
1641 * <table border="0">
1642 * <tr>
1643 * <th>Code:</th><th>Returned String:</th>
1644 * </tr>
1645 * <tr>
1646 * <td>{@link SwingConstants#CENTER}</td>
1647 * <td><code>"CENTER"</code></td>
1648 * </tr>
1649 * <tr>
1650 * <td>{@link SwingConstants#TOP}</td>
1651 * <td><code>"TOP"</code></td>
1652 * </tr>
1653 * <tr>
1654 * <td>{@link SwingConstants#BOTTOM}</td>
1655 * <td><code>"BOTTOM"</code></td>
1656 * </tr>
1657 * </table>
1658 * </p>
1659 * If the supplied code is not one of those listed, this methods will throw
1660 * an {@link IllegalArgumentException}.
1661 *
1662 * @param code the code.
1663 *
1664 * @return A string representing the given code.
1665 */
1666 static String convertVerticalAlignmentCodeToString(int code)
1667 {
1668 switch (code)
1669 {
1670 case SwingConstants.CENTER:
1671 return "CENTER";
1672 case SwingConstants.TOP:
1673 return "TOP";
1674 case SwingConstants.BOTTOM:
1675 return "BOTTOM";
1676 default:
1677 throw new IllegalArgumentException("Unrecognised code: " + code);
1678 }
1679 }
1680
1681 /**
1682 * Returns a string representing one of the default operation codes
1683 * defined in the {@link WindowConstants} interface. The following table
1684 * lists the constants and return values:
1685 * <p>
1686 * <table border="0">
1687 * <tr>
1688 * <th>Code:</th><th>Returned String:</th>
1689 * </tr>
1690 * <tr>
1691 * <td>{@link WindowConstants#DO_NOTHING_ON_CLOSE}</td>
1692 * <td><code>"DO_NOTHING_ON_CLOSE"</code></td>
1693 * </tr>
1694 * <tr>
1695 * <td>{@link WindowConstants#HIDE_ON_CLOSE}</td>
1696 * <td><code>"HIDE_ON_CLOSE"</code></td>
1697 * </tr>
1698 * <tr>
1699 * <td>{@link WindowConstants#DISPOSE_ON_CLOSE}</td>
1700 * <td><code>"DISPOSE_ON_CLOSE"</code></td>
1701 * </tr>
1702 * <tr>
1703 * <td>{@link WindowConstants#EXIT_ON_CLOSE}</td>
1704 * <td><code>"EXIT_ON_CLOSE"</code></td>
1705 * </tr>
1706 * </table>
1707 * </p>
1708 * If the supplied code is not one of those listed, this method will throw
1709 * an {@link IllegalArgumentException}.
1710 *
1711 * @param code the code.
1712 *
1713 * @return A string representing the given code.
1714 */
1715 static String convertWindowConstantToString(int code)
1716 {
1717 switch (code)
1718 {
1719 case WindowConstants.DO_NOTHING_ON_CLOSE:
1720 return "DO_NOTHING_ON_CLOSE";
1721 case WindowConstants.HIDE_ON_CLOSE:
1722 return "HIDE_ON_CLOSE";
1723 case WindowConstants.DISPOSE_ON_CLOSE:
1724 return "DISPOSE_ON_CLOSE";
1725 case WindowConstants.EXIT_ON_CLOSE:
1726 return "EXIT_ON_CLOSE";
1727 default:
1728 throw new IllegalArgumentException("Unrecognised code: " + code);
1729 }
1730 }
1731
1732 /**
1733 * Converts a rectangle in the coordinate system of a child component into
1734 * a rectangle of one of it's Ancestors. The result is stored in the input
1735 * rectangle.
1736 *
1737 * @param comp the child component
1738 * @param r the rectangle to convert
1739 * @param ancestor the ancestor component
1740 */
1741 static void convertRectangleToAncestor(Component comp, Rectangle r,
1742 Component ancestor)
1743 {
1744 if (comp == ancestor)
1745 return;
1746
1747 r.x += comp.getX();
1748 r.y += comp.getY();
1749
1750 Component parent = comp.getParent();
1751 if (parent != null && parent != ancestor)
1752 convertRectangleToAncestor(parent, r, ancestor);
1753 }
1754 }