Index: java/awt/Container.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/awt/Container.java,v retrieving revision 1.30 diff -u -b -B -r1.30 Container.java --- java/awt/Container.java 3 Feb 2004 17:17:29 -0000 1.30 +++ java/awt/Container.java 6 Feb 2004 12:54:58 -0000 @@ -52,6 +52,7 @@ import java.util.EventListener; import java.util.Set; import javax.accessibility.Accessible; +import javax.swing.SwingUtilities; /** * A generic window toolkit object that acts as a container for other objects. @@ -1541,20 +1542,12 @@ nativeContainer = c; } - void dispose() - { - } - void enableEvents(long l) { eventMask |= l; } - void mouseExit (MouseEvent me, int x, int y) - { - } - - void acquireComponentForMouseEvent (MouseEvent me) + void acquireComponentForMouseEvent(MouseEvent me) { int x = me.getX (); int y = me.getY (); @@ -1558,37 +1551,50 @@ { int x = me.getX (); int y = me.getY (); - Component candidate = mouseEventTarget; - boolean candidate_is_container_with_children = - ((candidate != null) - && (candidate instanceof Container) - && (((Container)candidate).getComponentCount () > 0)); - - boolean candidate_does_not_contain_point = - ((candidate != null) - && (! candidate.contains (x - candidate.getX (), - y - candidate.getY ()))); - - if (candidate == null - || candidate_is_container_with_children - || candidate_does_not_contain_point) + while(candidate != null) + { + if (candidate.isShowing()) + { + // Convert our point to the candidate's parent's space. + Point cp = SwingUtilities.convertPoint(nativeContainer, x, y, candidate); + + // If the event lands inside candidate, we have a hit. + if (candidate.contains(cp.x, cp.y)) { - // Try to reacquire. - candidate = nativeContainer.findComponentAt (x, y); + // If candidate has children, we refine the hit. + if (candidate instanceof Container && + ((Container)candidate).getComponentCount() > 0) + candidate = SwingUtilities.getDeepestComponentAt(candidate, cp.x, cp.y); + break; + } + } + // If candidate isn't showing or doesn't contain point, we back out a level. + candidate = candidate.getParent(); + } + + if (candidate == null) + { + // We either lost, or never had, a candidate; acquire from our native. + candidate = + SwingUtilities.getDeepestComponentAt(nativeContainer, x, y); } + + // If our candidate is new, inform the old target we're leaving. if (mouseEventTarget != null + && mouseEventTarget.isShowing() && mouseEventTarget != candidate) { - int nx = x - mouseEventTarget.getX (); - int ny = y - mouseEventTarget.getY (); + Point tp = + SwingUtilities.convertPoint(nativeContainer, + x, y, mouseEventTarget); MouseEvent exited = new MouseEvent (mouseEventTarget, MouseEvent.MOUSE_EXITED, me.getWhen (), me.getModifiers (), - nx, ny, + tp.x, tp.y, me.getClickCount (), me.isPopupTrigger (), me.getButton ()); @@ -1596,25 +1602,22 @@ mouseEventTarget = null; } + // If we have a candidate, maybe enter it. if (candidate != null) { - // Possibly set new state. if (candidate.isLightweight() + && candidate.isShowing() && candidate != nativeContainer && candidate != mouseEventTarget) { - mouseEventTarget = candidate; - - int nx = x - mouseEventTarget.getX (); - int ny = y - mouseEventTarget.getY (); - - // If acquired, enter it. + Point cp = SwingUtilities.convertPoint(nativeContainer, + x, y, candidate); MouseEvent entered = new MouseEvent (mouseEventTarget, MouseEvent.MOUSE_ENTERED, me.getWhen (), me.getModifiers (), - nx, ny, + cp.x, cp.y, me.getClickCount (), me.isPopupTrigger (), me.getButton ()); @@ -1623,37 +1626,30 @@ } } - boolean handleEvent (AWTEvent e) + boolean handleEvent(AWTEvent e) { - if ((eventMask & e.getID ()) == 0) + if ((eventMask & e.getID()) == 0) return false; if (e instanceof MouseEvent) { MouseEvent me = (MouseEvent) e; - acquireComponentForMouseEvent (me); + acquireComponentForMouseEvent(me); - // Avoid dispatching an ENTERED event twice + // Avoid dispatching an ENTERED event twice. if (mouseEventTarget != null + && mouseEventTarget.isShowing() && e.getID() != MouseEvent.MOUSE_ENTERED) { - // Calculate point translation for the event target. - // We use absolute location on screen rather than relative - // location because the event target might be a nested child. - Point parentLocation = nativeContainer.getLocationOnScreen(); - Point childLocation = mouseEventTarget.getLocationOnScreen(); - me.translatePoint(parentLocation.x - childLocation.x, - parentLocation.y - childLocation.y); - - Component oldSource = (Component) me.getSource (); - me.setSource (mouseEventTarget); - mouseEventTarget.dispatchEvent (me); - me.setSource (oldSource); + MouseEvent newEvt = + SwingUtilities.convertMouseEvent(nativeContainer, me, + mouseEventTarget); + mouseEventTarget.dispatchEvent(newEvt); } } else if (e instanceof KeyEvent && focus != null) { - focus.processKeyEvent ((KeyEvent) e); + focus.processKeyEvent((KeyEvent) e); } return e.isConsumed(); Index: javax/swing/SwingUtilities.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/SwingUtilities.java,v retrieving revision 1.6 diff -u -b -B -r1.6 SwingUtilities.java --- javax/swing/SwingUtilities.java 27 Jan 2004 09:46:25 -0000 1.6 +++ javax/swing/SwingUtilities.java 6 Feb 2004 12:54:58 -0000 @@ -35,10 +35,11 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ - package javax.swing; +import java.applet.Applet; import java.awt.Component; +import java.awt.ComponentOrientation; import java.awt.Container; import java.awt.EventQueue; import java.awt.Font; @@ -55,81 +56,623 @@ import javax.accessibility.Accessible; import javax.accessibility.AccessibleStateSet; + +/** + * This class contains a number of static utility functions which are + * useful when drawing swing components, dispatching events, or calculating + * regions which need painting. + * + * @author Graydon Hoare (graydon&064;redhat.com) + */ public class SwingUtilities implements SwingConstants { - public static FontMetrics getFontMetrics (Font font) + + /** + * Calculates the portion of the base rectangle which is inside the + * insets. + * + * @param base The rectangle to apply the insets to + * @param insets The insets to apply to the base rectangle + * @param ret A rectangle to use for storing the return value, or + * null + * + * @return The calculated area inside the base rectangle and its insets, + * either stored in ret or a new Rectangle if ret is null + * + * @see #calculateInnerArea + */ + public static Rectangle calculateInsetArea(Rectangle base, Insets insets, + Rectangle ret) { - return Toolkit.getDefaultToolkit ().getFontMetrics (font); + if (ret == null) + ret = new Rectangle(); + ret.setBounds(base.x + insets.left, base.y + insets.top, + base.width - (insets.left + insets.right), + base.height - (insets.top + insets.bottom)); + return ret; } - public static JRootPane getRootPane (Component a) + /** + * Calculates the portion of the component's bounds which is inside the + * component's border insets. This area is usually the area a component + * should confine its painting to. + * + * @param c The component to measure the bounds of + * @param r A Rectangle to store the return value in, or + * null + * + * @return The calculated area inside the component and its border + * insets + * + * @see #calculateInsetArea + */ + public static Rectangle calculateInnerArea(JComponent c, Rectangle r) { - if (a instanceof JRootPane) - return (JRootPane) a; + return calculateInsetArea(c.getBounds(), c.getInsets(), r); + } - a = a.getParent(); + /** + * Calculates the bounds of a component in the component's own coordinate + * space. The result has the same height and width as the component's + * bounds, but its location is set to (0,0). + * + * @param aComponent The component to measure + * + * @return The component's bounds in its local coordinate space + */ + public static Rectangle getLocalBounds(Component aComponent) + { + Rectangle bounds = aComponent.getBounds(); + return new Rectangle(0, 0, bounds.x, bounds.y); + } + + /** + * Returns the font metrics object for a given font. The metrics can be + * used to calculate crude bounding boxes and positioning information, + * for laying out components with textual elements. + * + * @param font The font to get metrics for + * + * @return The font's metrics + * + * @see java.awt.font.GlyphMetrics + */ + public static FontMetrics getFontMetrics(Font font) + { + return Toolkit.getDefaultToolkit().getFontMetrics(font); + } + + /** + * If comp is a RootPaneContainer, return its JRootPane. + * Otherwise call getAncestorOfClass(JRootPane.class, a). + * + * @param comp The component to get the JRootPane of + * + * @return a suitable JRootPane for comp, or null + * + * @see javax.swing.RootPaneContainer#getRootPane + * @see #getAncestorOfClass + */ + public static JRootPane getRootPane(Component comp) + { + if (comp instanceof RootPaneContainer) + return ((RootPaneContainer)comp).getRootPane(); + else + return (JRootPane) getAncestorOfClass(JRootPane.class, comp); + } + + /** + * Returns the least ancestor of comp which has the + * specified name. + * + * @param name The name to search for + * @param comp The component to search the ancestors of + * + * @return The nearest ancestor of comp with the given + * name, or null if no such ancestor exists + * + * @see java.awt.Component#getName + * @see #getAncestorOfClass + */ + public static Container getAncestorNamed(String name, Component comp) + { + while (comp != null && (comp.getName() != name)) + comp = comp.getParent(); + return (Container) comp; + } + + /** + * Returns the least ancestor of comp which is an instance + * of the specified class. + * + * @param c The class to search for + * @param comp The component to search the ancestors of + * + * @return The nearest ancestor of comp which is an instance + * of the given class, or null if no such ancestor exists + * + * @see #getAncestorOfClass + * @see #windowForComponent + * @see + * + */ + public static Container getAncestorOfClass(Class c, Component comp) + { + while (comp != null && (! c.isInstance(comp))) + comp = comp.getParent(); + return (Container) comp; + } + + /** + * Equivalent to calling getAncestorOfClass(Window, comp). + * + * @param comp The component to search for an ancestor window + * + * @return An ancestral window, or null if none exists + */ + public static Window windowForComponent(Component comp) + { + return (Window) getAncestorOfClass(Window.class, comp); + } - if (a != null) + /** + * Returns the "root" of the component tree containint comp + * The root is defined as either the least ancestor of + * comp which is a address@hidden Window}, or the greatest + * ancestor of comp which is a address@hidden Applet} if no address@hidden + * Window} ancestors are found. + * + * @param comp The component to search for a root + * + * @return The root of the component's tree, or null + */ + public static Component getRoot(Component comp) { - return getRootPane(a); + Applet app = null; + Window win = null; + + while (comp != null) + { + if (win == null && comp instanceof Window) + win = (Window) comp; + else if (comp instanceof Applet) + app = (Applet) comp; + comp = comp.getParent(); + } + + if (win != null) + return win; + else + return app; } + /** + * Return true if a descends from b, in other words if b is an + * ancestor of a. + * + * @param a The child to search the ancestry of + * @param b The potential ancestor to search for + * + * @return true if a is a descendent of b, false otherwise + */ + public static boolean isDescendingFrom(Component a, Component b) + { + while (true) + { + if (a == null || b == null) + return false; + if (a == b) + return true; + a = a.getParent(); + } + } + + /** + * Returns the deepest descendent of parent which is both visible and + * contains the point (x,y). Returns parent when either + * parent is not a container, or has no children which contain + * (x,y). Returns null when either + * (x,y) is outside the bounds of parent, or parent is + * null. + * + * @param parent The component to search the descendents of + * @param x Horizontal coordinate to search for + * @param y Vertical coordinate to search for + * + * @return A component containing (x,y), or + * null + * + * @see java.awt.Container#findComponentAt + */ + public static Component getDeepestComponentAt(Component parent, int x, int y) + { + if (parent == null || (! parent.contains(x, y))) return null; + + if (! (parent instanceof Container)) + return parent; + + Container c = (Container) parent; + return c.findComponentAt(x, y); } - public static void updateComponentTreeUI(JFrame comp) + /** + * Converts a point from a component's local coordinate space to "screen" + * coordinates (such as the coordinate space mouse events are delivered + * in). This operation is equivalent to translating the point by the + * location of the component (which is the origin of its coordinate + * space). + * + * @param p The point to convert + * @param c The component which the point is expressed in terms of + * + * @see convertPointFromScreen + */ + public static void convertPointToScreen(Point p, Component c) { + Point c0 = c.getLocationOnScreen(); + p.translate(c0.x, c0.y); } - public static String layoutCompoundLabel(JComponent c, - FontMetrics fm, - String text, - Icon i, - int vert_a, - int hor_i, - int vert_text_pos, - int hor_text_pos, - Rectangle vr, - Rectangle ir, - Rectangle tr, - int gap) + /** + * Converts a point from "screen" coordinates (such as the coordinate + * space mouse events are delivered in) to a component's local coordinate + * space. This operation is equivalent to translating the point by the + * negation of the component's location (which is the origin of its + * coordinate space). + * + * @param p The point to convert + * @param c The component which the point should be expressed in terms of + */ + public static void convertPointFromScreen(Point p, Component c) + { + Point c0 = c.getLocationOnScreen(); + p.translate(-c0.x, -c0.y); + } + + /** + * Converts a point (x,y) from the coordinate space of one + * component to another. This is equivalent to converting the point from + * source space to screen space, then back from screen space + * to destination space. If exactly one of the two + * Components is null, it is taken to refer to the root + * ancestor of the other component. If both are null, no + * transformation is done. + * + * @param source The component which the point is expressed in terms of + * @param x Horizontal coordinate of point to transform + * @param y Vertical coordinate of point to transform + * @param destination The component which the return value will be + * expressed in terms of + * + * @return The point (x,y) converted from the coordinate space of the + * source component to the coordinate space of the destination component + * + * @see #convertPointToScreen + * @see #convertPointFromScreen + * @see #convertRectangle + * @see #getRoot + */ + public static Point convertPoint(Component source, int x, int y, + Component destination) + { + Point pt = new Point(x, y); + + if (source == null && destination == null) + return pt; + + if (source == null) + source = getRoot(destination); + + if (destination == null) + destination = getRoot(source); + + convertPointToScreen(pt, source); + convertPointFromScreen(pt, destination); + + return pt; + } + + + /** + * Converts a rectangle from the coordinate space of one component to + * another. This is equivalent to converting the rectangle from + * source space to screen space, then back from screen space + * to destination space. If exactly one of the two + * Components is null, it is taken to refer to the root + * ancestor of the other component. If both are null, no + * transformation is done. + * + * @param source The component which the rectangle is expressed in terms of + * @param rect The rectangle to convert + * @param destination The component which the return value will be + * expressed in terms of + * + * @return A new rectangle, equal in size to the input rectangle, but + * with its position converted from the coordinate space of the source + * component to the coordinate space of the destination component + * + * @see #convertPointToScreen + * @see #convertPointFromScreen + * @see #convertPoint + * @see #getRoot + */ + public static Rectangle convertRectangle(Component source, + Rectangle rect, + Component destination) { - // view rect 'vr' already ok, - // we need to compute ir (icon rect) and tr (text-rect) + Point pt = convertPoint(source, rect.x, rect.y, destination); + return new Rectangle(pt.x, pt.y, rect.width, rect.height); + } - int next_x = vr.x; - int next_y = vr.y; + /** + * Convert a mouse event which refrers to one component to another. This + * includes changing the mouse event's coordinate space, as well as the + * source property of the event. If source is + * null, it is taken to refer to destination's + * root component. If destination is null, the + * new event will remain expressed in source's coordinate + * system. + * + * @param source The component the mouse event currently refers to + * @param sourceEvent The mouse event to convert + * @param destination The component the new mouse event should refer to + * + * @return A new mouse event expressed in terms of the destination + * component's coordinate space, and with the destination component as + * its source + * + * @see #convertPoint + */ + public static MouseEvent convertMouseEvent(Component source, + MouseEvent sourceEvent, + Component destination) + { + Point newpt = convertPoint(source, sourceEvent.getX(), sourceEvent.getY(), + destination); - ir.height = ir.width = ir.y = ir.x = 0; + return new MouseEvent(destination, sourceEvent.getID(), + sourceEvent.getWhen(), sourceEvent.getModifiers(), + newpt.x, newpt.y, sourceEvent.getClickCount(), + sourceEvent.isPopupTrigger(), sourceEvent.getButton()); + } - if (i != null) + /** + * Recursively walk the component tree under comp calling + * updateUI on each address@hidden JComponent} found. This causes + * the entire tree to re-initialize its UI delegates. + * + * @param comp The component to walk the children of, calling updateUI + */ + public static void updateComponentTreeUI(Component comp) { - ir.x = vr.x; - ir.y = vr.y; - ir.width = i.getIconWidth(); - ir.height = i.getIconWidth(); + if (comp == null) + return; + if (comp instanceof Container) + { + Component[] children = ((Container)comp).getComponents(); + for (int i = 0; i < children.length; ++i) + updateComponentTreeUI(children[i]); + } - next_x += gap + i.getIconWidth(); - next_y += gap + i.getIconHeight(); + if (comp instanceof JComponent) + ((JComponent)comp).updateUI(); } - tr.x = next_x; - tr.y = vr.y; // + (vr.height/2); + /** + *

Layout a "compound label" consisting of a text string and an icon + * which is to be placed near the rendered text. Once the text and icon + * are laid out, the text rectangle and icon rectangle parameters are + * altered to store the calculated positions.

+ * + *

The size of the text is calculated from the provided font metrics + * object. This object should be the metrics of the font you intend to + * paint the label with.

+ * + *

The position values control where the text is placed relative to + * the icon. The horizontal position value should be one of the constants + * LEFT, RIGHT or CENTER. The + * vertical position value should be one fo the constants + * TOP, BOTTOM, CENTER.

+ * + *

The text-icon gap value controls the number of pixels between the + * icon and the text.

+ * + *

The alignment values control where the text and icon are placed, as + * a combined unit, within the view rectangle. The horizontal alignment + * value should be one of the constants LEADING, + * TRAILING, LEFT, RIGHT or + * CENTER. The vertical alignment valus should be one of the + * constants TOP, BOTTOM or + * CENTER.

+ * + *

If the LEADING or TRAILING constants are + * given for horizontal alignment, they are interpreted relative to the + * provided component's orientation property, a constant in the address@hidden + * java.awt.ComponentOrientation} class. For example, if the component's + * orientation is LEFT_TO_RIGHT, then the + * LEADING alignment is a synonym for LEFT and + * the TRAILING alignment is a synonym for + * RIGHT

+ * + *

If the text and icon are equal to or larger than the view + * rectangle, the horizontal and vertical alignment values have no + * affect.

+ * + * @param c A component used for its orientation value + * @param fm The font metrics used to measure the text + * @param text The text to place in the compound label + * @param icon The icon to place next to the text + * @param verticalAlignment The vertical alignment of the label relative + * to its component + * @param horizontalAlignment The horizontal alignment of the label + * relative to its component + * @param verticalTextPosition The vertical position of the label's text + * relative to its icon + * @param horizontalTextPosition The horizontal position of the label's + * text relative to its icon + * @param viewR The view rectangle, specifying the area which layout is + * constrained to + * @param iconR A rectangle which is modified to hold the laid-out + * position of the icon + * @param textR A rectangle which is modified to hold the laid-out + * position of the text + * @param textIconGap The distance between text and icon + * + * @return The string of characters, possibly truncated with an elipsis, + * which is laid out in this label + */ + public static String layoutCompoundLabel(JComponent c, + FontMetrics fm, + String text, + Icon icon, + int verticalAlignment, + int horizontalAlignment, + int verticalTextPosition, + int horizontalTextPosition, + Rectangle viewR, + Rectangle iconR, + Rectangle textR, + int textIconGap) + { - tr.width = fm.stringWidth(text); - tr.height = fm.getHeight(); // + fm.getAscent()/2; + // Work out basic height and width. - return text; + if (icon == null) + { + textIconGap = 0; + iconR.width = 0; + iconR.height = 0; } + else + { + iconR.width = icon.getIconWidth(); + iconR.height = icon.getIconWidth(); + } + textR.width = fm.stringWidth(text); + textR.height = fm.getHeight(); -} + // Work out the position of text and icon, assuming the top-left coord + // starts at (0,0). We will fix that up momentarily, after these + // "position" decisions are made and we look at alignment. + + switch (horizontalTextPosition) + { + case LEFT: + textR.x = 0; + iconR.x = textR.width + textIconGap; + break; + case RIGHT: + iconR.x = 0; + textR.x = iconR.width + textIconGap; + break; + case CENTER: + int centerLine = Math.max(textR.width, iconR.width) / 2; + textR.x = centerLine - textR.width/2; + iconR.x = centerLine - iconR.width/2; + break; + } + + switch (verticalTextPosition) + { + case TOP: + textR.y = 0; + iconR.y = textR.height + textIconGap; + break; + case BOTTOM: + iconR.y = 0; + textR.y = iconR.height + textIconGap; + break; + case CENTER: + int centerLine = Math.max(textR.height, iconR.height) / 2; + textR.y = centerLine - textR.height/2; + iconR.y = centerLine - iconR.height/2; + break; + } + // Fix up the orientation-based alignments. + if (horizontalAlignment == LEADING) + { + if (c.getComponentOrientation() == ComponentOrientation.LEFT_TO_RIGHT) + horizontalAlignment = LEFT; + else if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) + horizontalAlignment = RIGHT; + } + else if (horizontalAlignment == TRAILING) + { + if (c.getComponentOrientation() == ComponentOrientation.LEFT_TO_RIGHT) + horizontalAlignment = RIGHT; + else if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) + horizontalAlignment = LEFT; + } + + // The two rectangles are laid out correctly now, but only assuming + // that their upper left corner is at (0,0). If we have any alignment other + // than TOP and LEFT, we need to adjust them. + + Rectangle u = textR.union(iconR); + int horizontalAdjustment = viewR.x; + int verticalAdjustment = viewR.y; + switch (verticalAlignment) + { + case TOP: + break; + case BOTTOM: + verticalAdjustment += (viewR.height - u.height); + break; + case CENTER: + verticalAdjustment += ((viewR.height/2) - (u.height/2)); + break; + } + switch (horizontalAlignment) + { + case LEFT: + break; + case RIGHT: + horizontalAdjustment += (viewR.width - u.width); + break; + case CENTER: + horizontalAdjustment += ((viewR.width/2) - (u.width/2)); + break; + } + iconR.x += horizontalAdjustment; + iconR.y += verticalAdjustment; + textR.x += horizontalAdjustment; + textR.y += verticalAdjustment; + return text; + } + /** + * Calls address@hidden java.awt.EventQueue.invokeLater} with the + * specified address@hidden Runnable}. + */ + public static void invokeLater(Runnable doRun) + { + java.awt.EventQueue.invokeLater(doRun); + } + /** + * Calls address@hidden java.awt.EventQueue.invokeAndWait} with the + * specified address@hidden Runnable}. + */ + public static void invokeAndWait(Runnable doRun) + throws InterruptedException, + InvocationTargetException + { + java.awt.EventQueue.invokeAndWait(doRun); + } + /** + * Calls address@hidden java.awt.EventQueue.isEventDispatchThread}. + */ + public static boolean isEventDispatchThread() + { + return java.awt.EventQueue.isDispatchThread(); + } +} Index: javax/swing/plaf/basic/BasicGraphicsUtils.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java,v retrieving revision 1.6 diff -u -b -B -r1.6 BasicGraphicsUtils.java --- javax/swing/plaf/basic/BasicGraphicsUtils.java 27 Jan 2004 09:46:25 -0000 1.6 +++ javax/swing/plaf/basic/BasicGraphicsUtils.java 6 Feb 2004 12:54:59 -0000 @@ -614,8 +614,8 @@ b.getToolkit().getFontMetrics(b.getFont()), // see comment above b.getText(), b.getIcon(), - b.getVerticalAlignment(), - b.getHorizontalAlignment(), + SwingUtilities.TOP, // important: + SwingUtilities.LEFT, // large vrect, stick to the top left b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect,