diff options
author | tromey <tromey@138bc75d-0d04-0410-961f-82ee72b054a4> | 2007-01-09 19:58:05 +0000 |
---|---|---|
committer | tromey <tromey@138bc75d-0d04-0410-961f-82ee72b054a4> | 2007-01-09 19:58:05 +0000 |
commit | 65bf3316cf384588453604be6b4f0ed3751a8b0f (patch) | |
tree | 996a5f57d4a68c53473382e45cb22f574cb3e4db /libjava/classpath/java/awt | |
parent | 8fc56618a84446beccd45b80381cdfe0e94050df (diff) | |
download | gcc-65bf3316cf384588453604be6b4f0ed3751a8b0f.tar.gz |
Merged gcj-eclipse branch to trunk.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@120621 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libjava/classpath/java/awt')
89 files changed, 7319 insertions, 4177 deletions
diff --git a/libjava/classpath/java/awt/AWTEvent.java b/libjava/classpath/java/awt/AWTEvent.java index a6151b424c1..3f4027c2c05 100644 --- a/libjava/classpath/java/awt/AWTEvent.java +++ b/libjava/classpath/java/awt/AWTEvent.java @@ -97,6 +97,11 @@ public abstract class AWTEvent extends EventObject protected boolean consumed; /** + * Used for implementing a simple linked list in EventQueue. + */ + transient AWTEvent queueNext; + + /** * Who knows? It's in the serial version. * * @serial No idea what this is for. diff --git a/libjava/classpath/java/awt/AWTEventMulticaster.java b/libjava/classpath/java/awt/AWTEventMulticaster.java index f7b9163cf67..07a6ffc2002 100644 --- a/libjava/classpath/java/awt/AWTEventMulticaster.java +++ b/libjava/classpath/java/awt/AWTEventMulticaster.java @@ -1175,16 +1175,17 @@ public class AWTEventMulticaster * @throws IllegalArgumentException if type is Void.TYPE * @since 1.4 */ - public static EventListener[] getListeners(EventListener l, Class type) + public static <T extends EventListener> T[] getListeners(EventListener l, + Class<T> type) { - ArrayList list = new ArrayList(); + ArrayList<EventListener> list = new ArrayList<EventListener>(); if (l instanceof AWTEventMulticaster) ((AWTEventMulticaster) l).getListeners(list, type); else if (type.isInstance(l)) list.add(l); EventListener[] r = (EventListener[]) Array.newInstance(type, list.size()); list.toArray(r); - return r; + return (T[]) r; } /** diff --git a/libjava/classpath/java/awt/AWTKeyStroke.java b/libjava/classpath/java/awt/AWTKeyStroke.java index 0e06225219f..527e85873e0 100644 --- a/libjava/classpath/java/awt/AWTKeyStroke.java +++ b/libjava/classpath/java/awt/AWTKeyStroke.java @@ -1,5 +1,5 @@ /* AWTKeyStroke.java -- an immutable key stroke - Copyright (C) 2002, 2004, 2005 Free Software Foundation + Copyright (C) 2002, 2004, 2005 Free Software Foundation This file is part of GNU Classpath. @@ -65,6 +65,7 @@ import java.util.StringTokenizer; * no-arg constructor (of any accessibility). * * @author Eric Blake (ebb9@email.byu.edu) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) * @see #getAWTKeyStroke(char) * @since 1.4 * @status updated to 1.4 @@ -85,13 +86,15 @@ public class AWTKeyStroke implements Serializable * under the assumption that garbage collection of a new keystroke is * easy when we find the old one that it matches in the cache. */ - private static final LinkedHashMap cache = new LinkedHashMap(11, 0.75f, true) + private static final LinkedHashMap<AWTKeyStroke,AWTKeyStroke> cache = + new LinkedHashMap<AWTKeyStroke,AWTKeyStroke>(11, 0.75f, true) { /** The largest the keystroke cache can grow. */ private static final int MAX_CACHE_SIZE = 2048; /** Prune stale entries. */ - protected boolean removeEldestEntry(Map.Entry eldest) + protected boolean removeEldestEntry(Map.Entry<AWTKeyStroke,AWTKeyStroke> + eldest) { // XXX - FIXME Use Map.Entry, not just Entry as gcj 3.1 workaround. return size() > MAX_CACHE_SIZE; } @@ -114,7 +117,7 @@ public class AWTKeyStroke implements Serializable * * @see #getAWTKeyStroke(String) */ - static final HashMap vktable = new HashMap(); + static final HashMap<String,Object> vktable = new HashMap<String,Object>(); static { // Using reflection saves the hassle of keeping this in sync with KeyEvent, @@ -229,7 +232,7 @@ public class AWTKeyStroke implements Serializable * @throws IllegalArgumentException subclass doesn't have no-arg constructor * @throws ClassCastException subclass doesn't extend AWTKeyStroke */ - protected static void registerSubclass(final Class subclass) + protected static void registerSubclass(final Class<?> subclass) { if (subclass == null) throw new IllegalArgumentException(); @@ -252,7 +255,8 @@ public class AWTKeyStroke implements Serializable throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { - Constructor c = subclass.getDeclaredConstructor(null); + Constructor<?> c = + subclass.getDeclaredConstructor((Class<?>[])null); c.setAccessible(true); // Create a new instance, to make sure that we can, and // to cause any ClassCastException. @@ -595,7 +599,7 @@ public class AWTKeyStroke implements Serializable */ protected Object readResolve() throws ObjectStreamException { - AWTKeyStroke s = (AWTKeyStroke) cache.get(this); + AWTKeyStroke s = cache.get(this); if (s != null) return s; cache.put(this, this); diff --git a/libjava/classpath/java/awt/BasicStroke.java b/libjava/classpath/java/awt/BasicStroke.java index 160a3eb0f74..ef6a7f0b7b5 100644 --- a/libjava/classpath/java/awt/BasicStroke.java +++ b/libjava/classpath/java/awt/BasicStroke.java @@ -43,6 +43,7 @@ import gnu.java.awt.java2d.LineSegment; import gnu.java.awt.java2d.QuadSegment; import gnu.java.awt.java2d.Segment; +import java.awt.geom.FlatteningPathIterator; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; @@ -486,8 +487,157 @@ public class BasicStroke implements Stroke private Shape dashedStroke(PathIterator pi) { - GeneralPath out = new GeneralPath(); - return out; + // The choice of (flatnessSq == width / 3) is made to be consistent with + // the flattening in CubicSegment.getDisplacedSegments + FlatteningPathIterator flat = new FlatteningPathIterator(pi, + Math.sqrt(width / 3)); + + // Holds the endpoint of the current segment (or piece of a segment) + double[] coords = new double[2]; + + // Holds end of the last segment + double x, y, x0, y0; + x = x0 = y = y0 = 0; + + // Various useful flags + boolean pathOpen = false; + boolean dashOn = true; + boolean offsetting = (phase != 0); + + // How far we are into the current dash + double distance = 0; + int dashIndex = 0; + + // And variables to hold the final output + GeneralPath output = new GeneralPath(); + Segment[] p; + + // Iterate over the FlatteningPathIterator + while (! flat.isDone()) + { + switch (flat.currentSegment(coords)) + { + case PathIterator.SEG_MOVETO: + x0 = x = coords[0]; + y0 = y = coords[1]; + + if (pathOpen) + { + capEnds(); + convertPath(output, start); + start = end = null; + pathOpen = false; + } + + break; + + case PathIterator.SEG_LINETO: + boolean segmentConsumed = false; + + while (! segmentConsumed) + { + // Find the total remaining length of this segment + double segLength = Math.sqrt((x - coords[0]) * (x - coords[0]) + + (y - coords[1]) + * (y - coords[1])); + boolean spanBoundary = true; + double[] segmentEnd = null; + + // The current segment fits entirely inside the current dash + if ((offsetting && distance + segLength <= phase) + || distance + segLength <= dash[dashIndex]) + { + spanBoundary = false; + } + + // Otherwise, we need to split the segment in two, as this + // segment spans a dash boundry + else + { + segmentEnd = (double[]) coords.clone(); + + // Calculate the remaining distance in this dash, + // and coordinates of the dash boundary + double reqLength; + if (offsetting) + reqLength = phase - distance; + else + reqLength = dash[dashIndex] - distance; + + coords[0] = x + ((coords[0] - x) * reqLength / segLength); + coords[1] = y + ((coords[1] - y) * reqLength / segLength); + } + + if (offsetting || ! dashOn) + { + // Dash is off, or we are in offset - treat this as a + // moveTo + x0 = x = coords[0]; + y0 = y = coords[1]; + + if (pathOpen) + { + capEnds(); + convertPath(output, start); + start = end = null; + pathOpen = false; + } + } + else + { + // Dash is on - treat this as a lineTo + p = (new LineSegment(x, y, coords[0], coords[1])).getDisplacedSegments(width / 2.0); + + if (! pathOpen) + { + start = p[0]; + end = p[1]; + pathOpen = true; + } + else + addSegments(p); + + x = coords[0]; + y = coords[1]; + } + + // Update variables depending on whether we spanned a + // dash boundary or not + if (! spanBoundary) + { + distance += segLength; + segmentConsumed = true; + } + else + { + if (offsetting) + offsetting = false; + dashOn = ! dashOn; + distance = 0; + coords = segmentEnd; + + if (dashIndex + 1 == dash.length) + dashIndex = 0; + else + dashIndex++; + + // Since the value of segmentConsumed is still false, + // the next run of the while loop will complete the segment + } + } + break; + + // This is a flattened path, so we don't need to deal with curves + } + flat.next(); + } + + if (pathOpen) + { + capEnds(); + convertPath(output, start); + } + return output; } /** @@ -611,9 +761,13 @@ public class BasicStroke implements Stroke p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()}; dx = p1[0] - p0[0]; dy = p1[1] - p0[1]; - l = Math.sqrt(dx * dx + dy * dy); - dx = (2.0/3.0)*width*dx/l; - dy = (2.0/3.0)*width*dy/l; + if (dx != 0 && dy != 0) + { + l = Math.sqrt(dx * dx + dy * dy); + dx = (2.0/3.0)*width*dx/l; + dy = (2.0/3.0)*width*dy/l; + } + c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy); c2 = new Point2D.Double(b.P1.getX() + dx, b.P1.getY() + dy); a.add(new CubicSegment(a.last.P2, c1, c2, b.P1)); diff --git a/libjava/classpath/java/awt/Button.java b/libjava/classpath/java/awt/Button.java index ae897a2f75f..4c246c7650b 100644 --- a/libjava/classpath/java/awt/Button.java +++ b/libjava/classpath/java/awt/Button.java @@ -352,11 +352,11 @@ removeActionListener(ActionListener listener) * * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == ActionListener.class) - return getActionListeners(); - return (EventListener[]) Array.newInstance(listenerType, 0); + return (T[]) getActionListeners(); + return (T[]) Array.newInstance(listenerType, 0); } /*************************************************************************/ diff --git a/libjava/classpath/java/awt/CardLayout.java b/libjava/classpath/java/awt/CardLayout.java index fcb05215af9..2e3feece8d2 100644 --- a/libjava/classpath/java/awt/CardLayout.java +++ b/libjava/classpath/java/awt/CardLayout.java @@ -225,6 +225,8 @@ public class CardLayout implements LayoutManager2, Serializable */ public Dimension maximumLayoutSize (Container target) { + if (target == null) + return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); // The JCL says that this returns Integer.MAX_VALUE for both // dimensions. But that just seems wrong to me. return getSize (target, MAX); @@ -361,7 +363,7 @@ public class CardLayout implements LayoutManager2, Serializable */ public String toString () { - return getClass ().getName () + "[" + hgap + "," + vgap + "]"; + return getClass ().getName () + "[hgap=" + hgap + ",vgap=" + vgap + "]"; } /** @@ -401,11 +403,11 @@ public class CardLayout implements LayoutManager2, Serializable { if (comps[i].isVisible ()) { - if (what == NEXT) + if (choice == i) { - choice = i + 1; - if (choice == num) - choice = 0; + // Do nothing if we're already looking at the right + // component. + return; } else if (what == PREV) { @@ -413,17 +415,20 @@ public class CardLayout implements LayoutManager2, Serializable if (choice < 0) choice = num - 1; } - else if (choice == i) + else if (what == NEXT) { - // Do nothing if we're already looking at the right - // component. - return; + choice = i + 1; + if (choice == num) + choice = 0; } comps[i].setVisible (false); if (choice >= 0) break; - } + } else + { + comps[i].setVisible(true); + } } if (choice >= 0 && choice < num) diff --git a/libjava/classpath/java/awt/CheckboxMenuItem.java b/libjava/classpath/java/awt/CheckboxMenuItem.java index 2df621b71b7..c886fc67203 100644 --- a/libjava/classpath/java/awt/CheckboxMenuItem.java +++ b/libjava/classpath/java/awt/CheckboxMenuItem.java @@ -318,11 +318,11 @@ paramString() * @exception ClassCastException If listenerType doesn't specify a class or * interface that implements java.util.EventListener. */ - public EventListener[] getListeners (Class listenerType) + public <T extends EventListener> T[] getListeners (Class<T> listenerType) { if (listenerType == ItemListener.class) return AWTEventMulticaster.getListeners (item_listeners, listenerType); - + return super.getListeners (listenerType); } diff --git a/libjava/classpath/java/awt/Choice.java b/libjava/classpath/java/awt/Choice.java index 104e30a7e82..ae89b9e9954 100644 --- a/libjava/classpath/java/awt/Choice.java +++ b/libjava/classpath/java/awt/Choice.java @@ -255,8 +255,8 @@ public class Choice extends Component /** * Adds the specified item to this choice box. * - * This method is oboslete since Java 2 platform 1.1. Please use @see add - * instead. + * This method is oboslete since Java 2 platform 1.1. Please use + * {@link #add(String)} instead. * * @param item The item to add. * @@ -320,9 +320,6 @@ public class Choice extends Component */ public synchronized void remove(int index) { - if ((index < 0) || (index > getItemCount())) - throw new IllegalArgumentException("Bad index: " + index); - pItems.removeElementAt(index); if (peer != null) @@ -332,14 +329,14 @@ public class Choice extends Component selectedIndex = -1; else { - if( selectedIndex > index ) - selectedIndex--; - else if( selectedIndex == index ) - selectedIndex = 0; - - if( peer != null ) - ((ChoicePeer)peer).select( selectedIndex ); - } + if( selectedIndex > index ) + selectedIndex--; + else if( selectedIndex == index ) + selectedIndex = 0; + + if( peer != null ) + ((ChoicePeer)peer).select( selectedIndex ); + } } /** @@ -523,11 +520,11 @@ public class Choice extends Component * * @since 1.3 */ - public EventListener[] getListeners (Class listenerType) + public <T extends EventListener> T[] getListeners (Class<T> listenerType) { if (listenerType == ItemListener.class) return AWTEventMulticaster.getListeners (item_listeners, listenerType); - + return super.getListeners (listenerType); } diff --git a/libjava/classpath/java/awt/Color.java b/libjava/classpath/java/awt/Color.java index b0312924170..c3d04c04976 100644 --- a/libjava/classpath/java/awt/Color.java +++ b/libjava/classpath/java/awt/Color.java @@ -534,14 +534,31 @@ public class Color implements Paint, Serializable { // Do not inline getRGB() to this.value, because of SystemColor. int value = getRGB(); - int red = (value & RED_MASK) >> 16; - int green = (value & GREEN_MASK) >> 8; - int blue = value & BLUE_MASK; - // We have to special case 0-2 because they won't scale by division. - red = red < 3 ? 3 : (int) Math.min(255, red / BRIGHT_SCALE); - green = green < 3 ? 3 : (int) Math.min(255, green / BRIGHT_SCALE); - blue = blue < 3 ? 3 : (int) Math.min(255, blue / BRIGHT_SCALE); - return new Color(red, green, blue, 255); + int[] hues = new int[3]; + hues[0] = (value & RED_MASK) >> 16; + hues[1] = (value & GREEN_MASK) >> 8; + hues[2] = value & BLUE_MASK; + + // (0,0,0) is a special case. + if (hues[0] == 0 && hues[1] == 0 && hues[2] ==0) + { + hues[0] = 3; + hues[1] = 3; + hues[2] = 3; + } + else + { + for (int index = 0; index < 3; index++) + { + + if (hues[index] > 2) + hues[index] = (int) Math.min(255, hues[index]/0.7f); + if (hues[index] == 1 || hues[index] == 2) + hues[index] = 4; + } + } + + return new Color(hues[0], hues[1], hues[2], 255); } /** diff --git a/libjava/classpath/java/awt/Component.java b/libjava/classpath/java/awt/Component.java index 44f277ac783..b6eadabbb5c 100644 --- a/libjava/classpath/java/awt/Component.java +++ b/libjava/classpath/java/awt/Component.java @@ -39,6 +39,10 @@ exception statement from your version. */ package java.awt; +//import gnu.java.awt.dnd.peer.gtk.GtkDropTargetContextPeer; + +import gnu.java.awt.ComponentReshapeEvent; + import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; import java.awt.event.AdjustmentEvent; @@ -577,7 +581,7 @@ public abstract class Component transient ComponentPeer peer; /** The preferred component orientation. */ - transient ComponentOrientation orientation = ComponentOrientation.UNKNOWN; + transient ComponentOrientation componentOrientation = ComponentOrientation.UNKNOWN; /** * The associated graphics configuration. @@ -698,6 +702,9 @@ public abstract class Component public void setDropTarget(DropTarget dt) { this.dropTarget = dt; + + if (peer != null) + dropTarget.addNotify(peer); } /** @@ -741,16 +748,23 @@ public abstract class Component */ public Toolkit getToolkit() { - if (peer != null) + // Only heavyweight peers can handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) { - Toolkit tk = peer.getToolkit(); - if (tk != null) - return tk; + comp = comp.parent; + p = comp == null ? null : comp.peer; } - // Get toolkit for lightweight component. - if (parent != null) - return parent.getToolkit(); - return Toolkit.getDefaultToolkit(); + + Toolkit tk = null; + if (p != null) + { + tk = peer.getToolkit(); + } + if (tk == null) + tk = Toolkit.getDefaultToolkit(); + return tk; } /** @@ -809,10 +823,7 @@ public abstract class Component */ public boolean isShowing() { - if (! visible || peer == null) - return false; - - return parent == null ? false : parent.isShowing(); + return visible && peer != null && (parent == null || parent.isShowing()); } /** @@ -959,14 +970,14 @@ public abstract class Component // case lightweight components are not initially painted -- // Container.paint first calls isShowing () before painting itself // and its children. - if(!isVisible()) + if(! visible) { // Need to lock the tree here to avoid races and inconsistencies. synchronized (getTreeLock()) { visible = true; // Avoid NullPointerExceptions by creating a local reference. - ComponentPeer currentPeer=peer; + ComponentPeer currentPeer = peer; if (currentPeer != null) { currentPeer.show(); @@ -978,7 +989,7 @@ public abstract class Component // The JDK repaints the component before invalidating the parent. // So do we. - if (isLightweight()) + if (peer instanceof LightweightPeer) repaint(); } @@ -1025,7 +1036,7 @@ public abstract class Component */ public void hide() { - if (isVisible()) + if (visible) { // Need to lock the tree here to avoid races and inconsistencies. synchronized (getTreeLock()) @@ -1033,7 +1044,7 @@ public abstract class Component visible = false; // Avoid NullPointerExceptions by creating a local reference. - ComponentPeer currentPeer=peer; + ComponentPeer currentPeer = peer; if (currentPeer != null) { currentPeer.hide(); @@ -1168,31 +1179,80 @@ public abstract class Component */ public Font getFont() { - Font f = font; - if (f != null) - return f; + Font f; + synchronized (getTreeLock()) + { + f = getFontImpl(); + } + return f; + } - Component p = parent; - if (p != null) - return p.getFont(); - return null; + /** + * Implementation of getFont(). This is pulled out of getFont() to prevent + * client programs from overriding this. This method is executed within + * a tree lock, so we can assume that the hierarchy doesn't change in + * between. + * + * @return the font of this component + */ + private final Font getFontImpl() + { + Font f = font; + if (f == null) + { + Component p = parent; + if (p != null) + f = p.getFontImpl(); + else + f = new Font("Dialog", Font.PLAIN, 12); + } + return f; } /** * Sets the font for this component to the specified font. This is a bound * property. * - * @param newFont the new font for this component + * @param f the new font for this component * * @see #getFont() */ - public void setFont(Font newFont) + public void setFont(Font f) { - Font oldFont = font; - font = newFont; - if (peer != null) - peer.setFont(font); + Font oldFont; + Font newFont; + // Synchronize on the tree because getFontImpl() relies on the hierarchy + // not beeing changed. + synchronized (getTreeLock()) + { + // Synchronize on this here to guarantee thread safety wrt to the + // property values. + synchronized (this) + { + oldFont = font; + font = f; + newFont = f; + } + // Create local variable here for thread safety. + ComponentPeer p = peer; + if (p != null) + { + // The peer receives the real font setting, which can depend on + // the parent font when this component's font has been set to null. + f = getFont(); + if (f != null) + { + p.setFont(f); + peerFont = f; + } + } + } + + // Fire property change event. firePropertyChange("font", oldFont, newFont); + + // Invalidate when necessary as font changes can change the size of the + // component. if (valid) invalidate(); } @@ -1292,8 +1352,26 @@ public abstract class Component // component. synchronized (getTreeLock()) { - // We know peer != null here. - return peer.getLocationOnScreen(); + // Only a heavyweight peer can answer the question for the screen + // location. So we are going through the hierarchy until we find + // one and add up the offsets while doing so. + int offsX = 0; + int offsY = 0; + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + offsX += comp.x; + offsY += comp.y; + comp = comp.parent; + p = comp == null ? null: comp.peer; + } + // Now we have a heavyweight component. + assert ! (p instanceof LightweightPeer); + Point loc = p.getLocationOnScreen(); + loc.x += offsX; + loc.y += offsY; + return loc; } } @@ -1533,7 +1611,17 @@ public abstract class Component } } - private void notifyReshape(boolean resized, boolean moved) + /** + * Sends notification to interested listeners about resizing and/or moving + * the component. If this component has interested + * component listeners or the corresponding event mask enabled, then + * COMPONENT_MOVED and/or COMPONENT_RESIZED events are posted to the event + * queue. + * + * @param resized true if the component has been resized, false otherwise + * @param moved true if the component has been moved, false otherwise + */ + void notifyReshape(boolean resized, boolean moved) { // Only post an event if this component actually has a listener // or has this event explicitly enabled. @@ -1544,43 +1632,16 @@ public abstract class Component if (moved) { ComponentEvent ce = new ComponentEvent(this, - ComponentEvent.COMPONENT_MOVED); + ComponentEvent.COMPONENT_MOVED); getToolkit().getSystemEventQueue().postEvent(ce); } if (resized) { ComponentEvent ce = new ComponentEvent(this, - ComponentEvent.COMPONENT_RESIZED); + ComponentEvent.COMPONENT_RESIZED); getToolkit().getSystemEventQueue().postEvent(ce); } } - else - { - // Otherwise we might need to notify child components when this is - // a Container. - if (this instanceof Container) - { - Container cont = (Container) this; - if (resized) - { - for (int i = 0; i < cont.getComponentCount(); i++) - { - Component child = cont.getComponent(i); - child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_RESIZED, - this, parent, 0); - } - } - if (moved) - { - for (int i = 0; i < cont.getComponentCount(); i++) - { - Component child = cont.getComponent(i); - child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_MOVED, - this, parent, 0); - } - } - } - } } /** @@ -2023,7 +2084,32 @@ public abstract class Component */ public void validate() { - valid = true; + if (! valid) + { + // Synchronize on the tree here as this might change the layout + // of the hierarchy. + synchronized (getTreeLock()) + { + // Create local variables for thread safety. + ComponentPeer p = peer; + if (p != null) + { + // Possibly update the peer's font. + Font newFont = getFont(); + Font oldFont = peerFont; + // Only update when the font really changed. + if (newFont != oldFont + && (oldFont == null || ! oldFont.equals(newFont))) + { + p.setFont(newFont); + peerFont = newFont; + } + // Let the peer perform any layout. + p.layout(); + } + } + valid = true; + } } /** @@ -2063,21 +2149,28 @@ public abstract class Component */ public Graphics getGraphics() { - if (peer != null) + // Only heavyweight peers can handle this. + ComponentPeer p = peer; + Graphics g = null; + if (p instanceof LightweightPeer) { - Graphics gfx = peer.getGraphics(); - // Create peer for lightweights. - if (gfx == null && parent != null) + if (parent != null) { - gfx = parent.getGraphics(); - gfx.clipRect(getX(), getY(), getWidth(), getHeight()); - gfx.translate(getX(), getY()); - return gfx; + g = parent.getGraphics(); + if (g != null) + { + g.translate(x, y); + g.setClip(0, 0, width, height); + g.setFont(getFont()); + } } - gfx.setFont(font); - return gfx; } - return null; + else + { + if (p != null) + g = p.getGraphics(); + } + return g; } /** @@ -2091,8 +2184,16 @@ public abstract class Component */ public FontMetrics getFontMetrics(Font font) { - return peer == null ? getToolkit().getFontMetrics(font) - : peer.getFontMetrics(font); + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + return p == null ? getToolkit().getFontMetrics(font) + : p.getFontMetrics(font); } /** @@ -2111,8 +2212,18 @@ public abstract class Component public void setCursor(Cursor cursor) { this.cursor = cursor; - if (peer != null) - peer.setCursor(cursor); + + // Only heavyweight peers handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + if (p != null) + p.setCursor(cursor); } /** @@ -2190,9 +2301,14 @@ public abstract class Component */ public void paintAll(Graphics g) { - if (! visible) - return; - paint(g); + if (isShowing()) + { + validate(); + if (peer instanceof LightweightPeer) + paint(g); + else + peer.paint(g); + } } /** @@ -2263,36 +2379,32 @@ public abstract class Component // Let the nearest heavyweight parent handle repainting for lightweight // components. - // This goes up the hierarchy until we hit - // a heavyweight component that handles this and translates the - // rectangle while doing so. - - // We perform some boundary checking to restrict the paint - // region to this component. - int px = (x < 0 ? 0 : x); - int py = (y < 0 ? 0 : y); - int pw = width; - int ph = height; - Component par = this; - while (par != null && p instanceof LightweightPeer) + // We need to recursivly call repaint() on the parent here, since + // a (lightweight) parent component might have overridden repaint() + // to perform additional custom tasks. + + if (p instanceof LightweightPeer) { - px += par.x; - py += par.y; // We perform some boundary checking to restrict the paint // region to this component. - pw = Math.min(pw, par.width); - ph = Math.min(ph, par.height); - par = par.parent; - p = par.peer; + if (parent != null) + { + int px = this.x + Math.max(0, x); + int py = this.y + Math.max(0, y); + int pw = Math.min(this.width, width); + int ph = Math.min(this.height, height); + parent.repaint(tm, px, py, pw, ph); + } } - - // Now send an UPDATE event to the heavyweight component that we've found. - if (par != null && par.isVisible() && p != null && pw > 0 && ph > 0) + else { - assert ! (p instanceof LightweightPeer); - PaintEvent pe = new PaintEvent(par, PaintEvent.UPDATE, - new Rectangle(px, py, pw, ph)); - getToolkit().getSystemEventQueue().postEvent(pe); + // Now send an UPDATE event to the heavyweight component that we've found. + if (isVisible() && p != null && width > 0 && height > 0) + { + PaintEvent pe = new PaintEvent(this, PaintEvent.UPDATE, + new Rectangle(x, y, width, height)); + getToolkit().getSystemEventQueue().postEvent(pe); + } } } @@ -2380,11 +2492,22 @@ public abstract class Component */ public Image createImage(ImageProducer producer) { + // Only heavyweight peers can handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + // Sun allows producer to be null. - if (peer != null) - return peer.createImage(producer); + Image im; + if (p != null) + im = p.createImage(producer); else - return getToolkit().createImage(producer); + im = getToolkit().createImage(producer); + return im; } /** @@ -2400,10 +2523,17 @@ public abstract class Component Image returnValue = null; if (!GraphicsEnvironment.isHeadless ()) { - if (isLightweight () && parent != null) - returnValue = parent.createImage (width, height); - else if (peer != null) - returnValue = peer.createImage (width, height); + // Only heavyweight peers can handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + if (p != null) + returnValue = p.createImage(width, height); } return returnValue; } @@ -2419,9 +2549,19 @@ public abstract class Component */ public VolatileImage createVolatileImage(int width, int height) { - if (peer != null) - return peer.createVolatileImage(width, height); - return null; + // Only heavyweight peers can handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + VolatileImage im = null; + if (p != null) + im = p.createVolatileImage(width, height); + return im; } /** @@ -2440,9 +2580,19 @@ public abstract class Component ImageCapabilities caps) throws AWTException { - if (peer != null) - return peer.createVolatileImage(width, height); - return null; + // Only heavyweight peers can handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + VolatileImage im = null; + if (p != null) + im = peer.createVolatileImage(width, height); + return im; } /** @@ -2472,10 +2622,21 @@ public abstract class Component public boolean prepareImage(Image image, int width, int height, ImageObserver observer) { - if (peer != null) - return peer.prepareImage(image, width, height, observer); + // Only heavyweight peers handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + boolean retval; + if (p != null) + retval = p.prepareImage(image, width, height, observer); else - return getToolkit().prepareImage(image, width, height, observer); + retval = getToolkit().prepareImage(image, width, height, observer); + return retval; } /** @@ -2509,9 +2670,21 @@ public abstract class Component public int checkImage(Image image, int width, int height, ImageObserver observer) { - if (peer != null) - return peer.checkImage(image, width, height, observer); - return getToolkit().checkImage(image, width, height, observer); + // Only heavyweight peers handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + int retval; + if (p != null) + retval = p.checkImage(image, width, height, observer); + else + retval = getToolkit().checkImage(image, width, height, observer); + return retval; } /** @@ -2657,14 +2830,6 @@ public abstract class Component */ public final void dispatchEvent(AWTEvent e) { - Event oldEvent = translateEvent(e); - if (oldEvent != null) - postEvent (oldEvent); - - // Give toolkit a chance to dispatch the event - // to globally registered listeners. - Toolkit.getDefaultToolkit().globalDispatchEvent(e); - // Some subclasses in the AWT package need to override this behavior, // hence the use of dispatchEventImpl(). dispatchEventImpl(e); @@ -2715,9 +2880,12 @@ public abstract class Component */ public synchronized void addComponentListener(ComponentListener listener) { - componentListener = AWTEventMulticaster.add(componentListener, listener); - if (componentListener != null) - enableEvents(AWTEvent.COMPONENT_EVENT_MASK); + if (listener != null) + { + componentListener = AWTEventMulticaster.add(componentListener, + listener); + newEventsOnly = true; + } } /** @@ -2763,9 +2931,11 @@ public abstract class Component */ public synchronized void addFocusListener(FocusListener listener) { - focusListener = AWTEventMulticaster.add(focusListener, listener); - if (focusListener != null) - enableEvents(AWTEvent.FOCUS_EVENT_MASK); + if (listener != null) + { + focusListener = AWTEventMulticaster.add(focusListener, listener); + newEventsOnly = true; + } } /** @@ -2810,16 +2980,19 @@ public abstract class Component */ public synchronized void addHierarchyListener(HierarchyListener listener) { - hierarchyListener = AWTEventMulticaster.add(hierarchyListener, listener); - if (hierarchyListener != null) - enableEvents(AWTEvent.HIERARCHY_EVENT_MASK); - - // Need to lock the tree, otherwise we might end up inconsistent. - synchronized (getTreeLock()) + if (listener != null) { - numHierarchyListeners++; - if (parent != null) - parent.updateHierarchyListenerCount(AWTEvent.HIERARCHY_EVENT_MASK, 1); + hierarchyListener = AWTEventMulticaster.add(hierarchyListener, + listener); + newEventsOnly = true; + // Need to lock the tree, otherwise we might end up inconsistent. + synchronized (getTreeLock()) + { + numHierarchyListeners++; + if (parent != null) + parent.updateHierarchyListenerCount(AWTEvent.HIERARCHY_EVENT_MASK, + 1); + } } } @@ -2876,19 +3049,20 @@ public abstract class Component public synchronized void addHierarchyBoundsListener(HierarchyBoundsListener listener) { - hierarchyBoundsListener = - AWTEventMulticaster.add(hierarchyBoundsListener, listener); - if (hierarchyBoundsListener != null) - enableEvents(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK); - - // Need to lock the tree, otherwise we might end up inconsistent. - synchronized (getTreeLock()) + if (listener != null) { - numHierarchyBoundsListeners++; - if (parent != null) - parent.updateHierarchyListenerCount - (AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK, - 1); + hierarchyBoundsListener = + AWTEventMulticaster.add(hierarchyBoundsListener, listener); + newEventsOnly = true; + + // Need to lock the tree, otherwise we might end up inconsistent. + synchronized (getTreeLock()) + { + numHierarchyBoundsListeners++; + if (parent != null) + parent.updateHierarchyListenerCount + (AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK, 1); + } } } @@ -2956,7 +3130,7 @@ public abstract class Component case HierarchyEvent.ANCESTOR_RESIZED: enabled = hierarchyBoundsListener != null || (eventMask & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0; - break; + break; default: assert false : "Should not reach here"; } @@ -2981,9 +3155,11 @@ public abstract class Component */ public synchronized void addKeyListener(KeyListener listener) { - keyListener = AWTEventMulticaster.add(keyListener, listener); - if (keyListener != null) - enableEvents(AWTEvent.KEY_EVENT_MASK); + if (listener != null) + { + keyListener = AWTEventMulticaster.add(keyListener, listener); + newEventsOnly = true; + } } /** @@ -3028,9 +3204,11 @@ public abstract class Component */ public synchronized void addMouseListener(MouseListener listener) { - mouseListener = AWTEventMulticaster.add(mouseListener, listener); - if (mouseListener != null) - enableEvents(AWTEvent.MOUSE_EVENT_MASK); + if (listener != null) + { + mouseListener = AWTEventMulticaster.add(mouseListener, listener); + newEventsOnly = true; + } } /** @@ -3075,9 +3253,12 @@ public abstract class Component */ public synchronized void addMouseMotionListener(MouseMotionListener listener) { - mouseMotionListener = AWTEventMulticaster.add(mouseMotionListener, listener); - if (mouseMotionListener != null) - enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK); + if (listener != null) + { + mouseMotionListener = AWTEventMulticaster.add(mouseMotionListener, + listener); + newEventsOnly = true; + } } /** @@ -3124,9 +3305,12 @@ public abstract class Component */ public synchronized void addMouseWheelListener(MouseWheelListener listener) { - mouseWheelListener = AWTEventMulticaster.add(mouseWheelListener, listener); - if (mouseWheelListener != null) - enableEvents(AWTEvent.MOUSE_WHEEL_EVENT_MASK); + if (listener != null) + { + mouseWheelListener = AWTEventMulticaster.add(mouseWheelListener, + listener); + newEventsOnly = true; + } } /** @@ -3174,9 +3358,12 @@ public abstract class Component */ public synchronized void addInputMethodListener(InputMethodListener listener) { - inputMethodListener = AWTEventMulticaster.add(inputMethodListener, listener); - if (inputMethodListener != null) - enableEvents(AWTEvent.INPUT_METHOD_EVENT_MASK); + if (listener != null) + { + inputMethodListener = AWTEventMulticaster.add(inputMethodListener, + listener); + newEventsOnly = true; + } } /** @@ -3235,29 +3422,29 @@ public abstract class Component * @see #getPropertyChangeListeners() * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == ComponentListener.class) - return getComponentListeners(); + return (T[]) getComponentListeners(); if (listenerType == FocusListener.class) - return getFocusListeners(); + return (T[]) getFocusListeners(); if (listenerType == HierarchyListener.class) - return getHierarchyListeners(); + return (T[]) getHierarchyListeners(); if (listenerType == HierarchyBoundsListener.class) - return getHierarchyBoundsListeners(); + return (T[]) getHierarchyBoundsListeners(); if (listenerType == KeyListener.class) - return getKeyListeners(); + return (T[]) getKeyListeners(); if (listenerType == MouseListener.class) - return getMouseListeners(); + return (T[]) getMouseListeners(); if (listenerType == MouseMotionListener.class) - return getMouseMotionListeners(); + return (T[]) getMouseMotionListeners(); if (listenerType == MouseWheelListener.class) - return getMouseWheelListeners(); + return (T[]) getMouseWheelListeners(); if (listenerType == InputMethodListener.class) - return getInputMethodListeners(); + return (T[]) getInputMethodListeners(); if (listenerType == PropertyChangeListener.class) - return getPropertyChangeListeners(); - return (EventListener[]) Array.newInstance(listenerType, 0); + return (T[]) getPropertyChangeListeners(); + return (T[]) Array.newInstance(listenerType, 0); } /** @@ -3302,18 +3489,49 @@ public abstract class Component */ protected final void enableEvents(long eventsToEnable) { + // Update the counter for hierarchy (bounds) listeners. + if ((eventsToEnable & AWTEvent.HIERARCHY_EVENT_MASK) != 0 + && (eventMask & AWTEvent.HIERARCHY_EVENT_MASK) == 0) + { + // Need to lock the tree, otherwise we might end up inconsistent. + synchronized (getTreeLock()) + { + numHierarchyListeners++; + if (parent != null) + parent.updateHierarchyListenerCount + (AWTEvent.HIERARCHY_EVENT_MASK, + 1); + } + } + if ((eventsToEnable & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0 + && (eventMask & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) == 0) + { + // Need to lock the tree, otherwise we might end up inconsistent. + synchronized (getTreeLock()) + { + numHierarchyBoundsListeners++; + if (parent != null) + parent.updateHierarchyListenerCount + (AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK, + 1); + } + } + eventMask |= eventsToEnable; - // TODO: Unlike Sun's implementation, I think we should try and - // enable/disable events at the peer (gtk/X) level. This will avoid - // clogging the event pipeline with useless mousemove events that - // we arn't interested in, etc. This will involve extending the peer - // interface, but thats okay because the peer interfaces have been - // deprecated for a long time, and no longer feature in the - // API specification at all. - if (isLightweight() && parent != null) - parent.enableEvents(eventsToEnable); - else if (peer != null) - peer.setEventMask(eventMask); + newEventsOnly = true; + + // Only heavyweight peers handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + if (p != null) + p.setEventMask(eventMask); + } /** @@ -3326,8 +3544,48 @@ public abstract class Component */ protected final void disableEvents(long eventsToDisable) { + // Update the counter for hierarchy (bounds) listeners. + if ((eventsToDisable & AWTEvent.HIERARCHY_EVENT_MASK) != 0 + && (eventMask & AWTEvent.HIERARCHY_EVENT_MASK) != 0) + { + // Need to lock the tree, otherwise we might end up inconsistent. + synchronized (getTreeLock()) + { + numHierarchyListeners--; + if (parent != null) + parent.updateHierarchyListenerCount + (AWTEvent.HIERARCHY_EVENT_MASK, + -1); + } + } + if ((eventsToDisable & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0 + && (eventMask & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0) + { + // Need to lock the tree, otherwise we might end up inconsistent. + synchronized (getTreeLock()) + { + numHierarchyBoundsListeners--; + if (parent != null) + parent.updateHierarchyListenerCount + (AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK, + -1); + } + } + eventMask &= ~eventsToDisable; - // forward new event mask to peer? + + // Only heavyweight peers handle this. + ComponentPeer p = peer; + Component comp = this; + while (p instanceof LightweightPeer) + { + comp = comp.parent; + p = comp == null ? null : comp.peer; + } + + if (p != null) + p.setEventMask(eventMask); + } /** @@ -3343,19 +3601,42 @@ public abstract class Component */ protected AWTEvent coalesceEvents(AWTEvent existingEvent, AWTEvent newEvent) { + AWTEvent coalesced = null; switch (existingEvent.id) { case MouseEvent.MOUSE_MOVED: case MouseEvent.MOUSE_DRAGGED: // Just drop the old (intermediate) event and return the new one. - return newEvent; + MouseEvent me1 = (MouseEvent) existingEvent; + MouseEvent me2 = (MouseEvent) newEvent; + if (me1.getModifiers() == me2.getModifiers()) + coalesced = newEvent; + break; case PaintEvent.PAINT: case PaintEvent.UPDATE: - return coalescePaintEvents((PaintEvent) existingEvent, - (PaintEvent) newEvent); + // For heavyweights the EventQueue should ask the peer. + if (peer == null || peer instanceof LightweightPeer) + { + PaintEvent pe1 = (PaintEvent) existingEvent; + PaintEvent pe2 = (PaintEvent) newEvent; + Rectangle r1 = pe1.getUpdateRect(); + Rectangle r2 = pe2.getUpdateRect(); + if (r1.contains(r2)) + coalesced = existingEvent; + else if (r2.contains(r1)) + coalesced = newEvent; + } + else + { + // Replace the event and let the heavyweight figure out the expanding + // of the repaint area. + coalesced = newEvent; + } + break; default: - return null; + coalesced = null; } + return coalesced; } /** @@ -3877,16 +4158,29 @@ public abstract class Component peer = getToolkit().createComponent(this); else if (parent != null && parent.isLightweight()) new HeavyweightInLightweightListener(parent); - /* Now that all the children has gotten their peers, we should - have the event mask needed for this component and its - lightweight subcomponents. */ + // Now that all the children has gotten their peers, we should + // have the event mask needed for this component and its + //lightweight subcomponents. peer.setEventMask(eventMask); - /* We do not invalidate here, but rather leave that job up to - the peer. For efficiency, the peer can choose not to - invalidate if it is happy with the current dimensions, - etc. */ - if (dropTarget != null) - dropTarget.addNotify(peer); + + // We used to leave the invalidate() to the peer. However, I put it + // back here for 2 reasons: 1) The RI does call invalidate() from + // addNotify(); 2) The peer shouldn't be bother with validation too + // much. + invalidate(); + + if (dropTarget != null) + dropTarget.addNotify(peer); + + // Fetch the peerFont for later installation in validate(). + peerFont = getFont(); + + // Notify hierarchy listeners. + long flags = HierarchyEvent.DISPLAYABILITY_CHANGED; + if (isHierarchyVisible()) + flags |= HierarchyEvent.SHOWING_CHANGED; + fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED, this, parent, + flags); } } @@ -3911,11 +4205,19 @@ public abstract class Component ComponentPeer tmp = peer; peer = null; + peerFont = null; if (tmp != null) { tmp.hide(); tmp.dispose(); } + + // Notify hierarchy listeners. + long flags = HierarchyEvent.DISPLAYABILITY_CHANGED; + if (isHierarchyVisible()) + flags |= HierarchyEvent.SHOWING_CHANGED; + fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED, this, parent, + flags); } } @@ -4034,7 +4336,8 @@ public abstract class Component * @see KeyboardFocusManager#UP_CYCLE_TRAVERSAL_KEYS * @since 1.4 */ - public void setFocusTraversalKeys(int id, Set keystrokes) + public void setFocusTraversalKeys(int id, + Set<? extends AWTKeyStroke> keystrokes) { if (keystrokes == null) { @@ -4126,14 +4429,14 @@ public abstract class Component * * @since 1.4 */ - public Set getFocusTraversalKeys (int id) + public Set<AWTKeyStroke> getFocusTraversalKeys (int id) { if (id != KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS && id != KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS && id != KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS) throw new IllegalArgumentException(); - Set s = null; + Set<AWTKeyStroke> s = null; if (focusTraversalKeys != null) s = focusTraversalKeys[id]; @@ -5060,8 +5363,8 @@ p * <li>the set of backward traversal keys public void setComponentOrientation(ComponentOrientation o) { - ComponentOrientation oldOrientation = orientation; - orientation = o; + ComponentOrientation oldOrientation = componentOrientation; + componentOrientation = o; firePropertyChange("componentOrientation", oldOrientation, o); } @@ -5073,7 +5376,7 @@ p * <li>the set of backward traversal keys */ public ComponentOrientation getComponentOrientation() { - return orientation; + return componentOrientation; } /** @@ -5358,7 +5661,7 @@ p * <li>the set of backward traversal keys oldKey = Event.UP; break; default: - oldKey = (int) ((KeyEvent) e).getKeyChar(); + oldKey = ((KeyEvent) e).getKeyChar(); } translated = new Event (target, when, oldID, @@ -5401,9 +5704,23 @@ p * <li>the set of backward traversal keys * * @param e the event to dispatch */ - void dispatchEventImpl(AWTEvent e) { + // Update the component's knowledge about the size. + // Important: Please look at the big comment in ComponentReshapeEvent + // to learn why we did it this way. If you change this code, make + // sure that the peer->AWT bounds update still works. + // (for instance: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29448 ) + if (e instanceof ComponentReshapeEvent) + { + ComponentReshapeEvent reshape = (ComponentReshapeEvent) e; + x = reshape.x; + y = reshape.y; + width = reshape.width; + height = reshape.height; + return; + } + // Retarget focus events before dispatching it to the KeyboardFocusManager // in order to handle lightweight components properly. boolean dispatched = false; @@ -5416,11 +5733,21 @@ p * <li>the set of backward traversal keys if (! dispatched) { - if (eventTypeEnabled (e.id)) + // Give toolkit a chance to dispatch the event + // to globally registered listeners. + Toolkit.getDefaultToolkit().globalDispatchEvent(e); + + if (newEventsOnly) { - if (e.id != PaintEvent.PAINT && e.id != PaintEvent.UPDATE) + if (eventTypeEnabled(e.id)) processEvent(e); } + else + { + Event oldEvent = translateEvent(e); + if (oldEvent != null) + postEvent (oldEvent); + } if (peer != null) peer.handleEvent(e); } @@ -5493,42 +5820,23 @@ p * <li>the set of backward traversal keys } /** - * Coalesce paint events. Current heuristic is: Merge if the union of - * areas is less than twice that of the sum of the areas. The X server - * tend to create a lot of paint events that are adjacent but not - * overlapping. - * - * <pre> - * +------+ - * | +-----+ ...will be merged - * | | | - * | | | - * +------+ | - * +-----+ + * Returns <code>true</code> when this component and all of its ancestors + * are visible, <code>false</code> otherwise. * - * +---------------+--+ - * | | | ...will not be merged - * +---------------+ | - * | | - * | | - * | | - * | | - * | | - * +--+ - * </pre> - * - * @param queuedEvent the first paint event - * @param newEvent the second paint event - * @return the combined paint event, or null + * @return <code>true</code> when this component and all of its ancestors + * are visible, <code>false</code> otherwise */ - private PaintEvent coalescePaintEvents(PaintEvent queuedEvent, - PaintEvent newEvent) + boolean isHierarchyVisible() { - Rectangle r1 = queuedEvent.getUpdateRect(); - Rectangle r2 = newEvent.getUpdateRect(); - Rectangle union = r1.union(r2); - newEvent.setUpdateRect(union); - return newEvent; + boolean visible = isVisible(); + Component comp = parent; + while (comp != null && visible) + { + comp = comp.parent; + if (comp != null) + visible = visible && comp.isVisible(); + } + return visible; } /** @@ -5671,7 +5979,7 @@ p * <li>the set of backward traversal keys */ public void componentHidden(ComponentEvent event) { - if (!isShowing()) + if (isShowing()) peer.hide(); } } diff --git a/libjava/classpath/java/awt/Container.java b/libjava/classpath/java/awt/Container.java index 409d164a13c..83d9f7b78c5 100644 --- a/libjava/classpath/java/awt/Container.java +++ b/libjava/classpath/java/awt/Container.java @@ -39,11 +39,11 @@ exception statement from your version. */ package java.awt; -import java.awt.event.ComponentListener; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.HierarchyEvent; import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; import java.awt.peer.ComponentPeer; import java.awt.peer.ContainerPeer; import java.awt.peer.LightweightPeer; @@ -69,10 +69,11 @@ import javax.accessibility.Accessible; * * @author original author unknown * @author Eric Blake (ebb9@email.byu.edu) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) * * @since 1.0 * - * @status still missing 1.4 support + * @status still missing 1.4 support, some generics from 1.5 */ public class Container extends Component { @@ -86,8 +87,6 @@ public class Container extends Component Component[] component; LayoutManager layoutMgr; - Dimension maxSize; - /** * @since 1.4 */ @@ -208,10 +207,12 @@ public class Container extends Component */ public Insets insets() { - if (peer == null) - return new Insets (0, 0, 0, 0); - - return ((ContainerPeer) peer).getInsets (); + Insets i; + if (peer == null || peer instanceof LightweightPeer) + i = new Insets (0, 0, 0, 0); + else + i = ((ContainerPeer) peer).getInsets (); + return i; } /** @@ -324,23 +325,6 @@ public class Container extends Component // we are. if (comp.parent != null) comp.parent.remove(comp); - comp.parent = this; - - if (peer != null) - { - // Notify the component that it has a new parent. - comp.addNotify(); - - if (comp.isLightweight ()) - { - enableEvents (comp.eventMask); - if (!isLightweight ()) - enableEvents (AWTEvent.PAINT_EVENT_MASK); - } - } - - // Invalidate the layout of the added component and its ancestors. - comp.invalidate(); if (component == null) component = new Component[4]; // FIXME, better initial size? @@ -365,6 +349,9 @@ public class Container extends Component ++ncomponents; } + // Give the new component a parent. + comp.parent = this; + // Update the counter for Hierarchy(Bounds)Listeners. int childHierarchyListeners = comp.numHierarchyListeners; if (childHierarchyListeners > 0) @@ -375,6 +362,18 @@ public class Container extends Component updateHierarchyListenerCount(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK, childHierarchyListeners); + // Invalidate the layout of this container. + if (valid) + invalidate(); + + // Create the peer _after_ the component has been added, so that + // the peer gets to know about the component hierarchy. + if (peer != null) + { + // Notify the component that it has a new parent. + comp.addNotify(); + } + // Notify the layout manager. if (layoutMgr != null) { @@ -394,13 +393,15 @@ public class Container extends Component // We previously only sent an event when this container is showing. // Also, the event was posted to the event queue. A Mauve test shows // that this event is not delivered using the event queue and it is - // also sent when the container is not showing. - ContainerEvent ce = new ContainerEvent(this, - ContainerEvent.COMPONENT_ADDED, - comp); - ContainerListener[] listeners = getContainerListeners(); - for (int i = 0; i < listeners.length; i++) - listeners[i].componentAdded(ce); + // also sent when the container is not showing. + if (containerListener != null + || (eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0) + { + ContainerEvent ce = new ContainerEvent(this, + ContainerEvent.COMPONENT_ADDED, + comp); + dispatchEvent(ce); + } // Notify hierarchy listeners. comp.fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED, comp, @@ -417,17 +418,15 @@ public class Container extends Component { synchronized (getTreeLock ()) { - Component r = component[index]; + if (index < 0 || index >= ncomponents) + throw new ArrayIndexOutOfBoundsException(); - ComponentListener[] list = r.getComponentListeners(); - for (int j = 0; j < list.length; j++) - r.removeComponentListener(list[j]); - - r.removeNotify(); + Component r = component[index]; + if (peer != null) + r.removeNotify(); - System.arraycopy(component, index + 1, component, index, - ncomponents - index - 1); - component[--ncomponents] = null; + if (layoutMgr != null) + layoutMgr.removeLayoutComponent(r); // Update the counter for Hierarchy(Bounds)Listeners. int childHierarchyListeners = r.numHierarchyListeners; @@ -439,20 +438,23 @@ public class Container extends Component updateHierarchyListenerCount(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK, -childHierarchyListeners); - invalidate(); + r.parent = null; - if (layoutMgr != null) - layoutMgr.removeLayoutComponent(r); + System.arraycopy(component, index + 1, component, index, + ncomponents - index - 1); + component[--ncomponents] = null; - r.parent = null; + if (valid) + invalidate(); - if (isShowing ()) + if (containerListener != null + || (eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0) { // Post event to notify of removing the component. ContainerEvent ce = new ContainerEvent(this, - ContainerEvent.COMPONENT_REMOVED, - r); - getToolkit().getSystemEventQueue().postEvent(ce); + ContainerEvent.COMPONENT_REMOVED, + r); + dispatchEvent(ce); } // Notify hierarchy listeners. @@ -496,36 +498,51 @@ public class Container extends Component // super.removeAll() ). // By doing it this way, user code cannot prevent the correct // removal of components. - for ( int index = 0; index < ncomponents; index++) + while (ncomponents > 0) { - Component r = component[index]; + ncomponents--; + Component r = component[ncomponents]; + component[ncomponents] = null; - ComponentListener[] list = r.getComponentListeners(); - for (int j = 0; j < list.length; j++) - r.removeComponentListener(list[j]); - - r.removeNotify(); + if (peer != null) + r.removeNotify(); if (layoutMgr != null) layoutMgr.removeLayoutComponent(r); r.parent = null; - if (isShowing ()) + // Send ContainerEvent if necessary. + if (containerListener != null + || (eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0) { // Post event to notify of removing the component. ContainerEvent ce = new ContainerEvent(this, ContainerEvent.COMPONENT_REMOVED, r); - - getToolkit().getSystemEventQueue().postEvent(ce); + dispatchEvent(ce); } - } - + + // Update the counter for Hierarchy(Bounds)Listeners. + int childHierarchyListeners = r.numHierarchyListeners; + if (childHierarchyListeners > 0) + updateHierarchyListenerCount(AWTEvent.HIERARCHY_EVENT_MASK, + -childHierarchyListeners); + int childHierarchyBoundsListeners = r.numHierarchyBoundsListeners; + if (childHierarchyBoundsListeners > 0) + updateHierarchyListenerCount(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK, + -childHierarchyListeners); + + + // Send HierarchyEvent if necessary. + fireHierarchyEvent(HierarchyEvent.HIERARCHY_CHANGED, r, this, + HierarchyEvent.PARENT_CHANGED); + + } + + if (valid) invalidate(); - - ncomponents = 0; } } @@ -590,11 +607,20 @@ public class Container extends Component */ public void validate() { - synchronized (getTreeLock ()) + ComponentPeer p = peer; + if (! valid && p != null) { - if (! isValid() && peer != null) + ContainerPeer cPeer = null; + if (p instanceof ContainerPeer) + cPeer = (ContainerPeer) peer; + synchronized (getTreeLock ()) { + if (cPeer != null) + cPeer.beginValidate(); validateTree(); + valid = true; + if (cPeer != null) + cPeer.endValidate(); } } } @@ -602,24 +628,20 @@ public class Container extends Component /** * Recursively invalidates the container tree. */ - void invalidateTree() + private final void invalidateTree() { synchronized (getTreeLock()) { - super.invalidate(); // Clean cached layout state. for (int i = 0; i < ncomponents; i++) { Component comp = component[i]; - comp.invalidate(); if (comp instanceof Container) ((Container) comp).invalidateTree(); + else if (comp.valid) + comp.invalidate(); } - - if (layoutMgr != null && layoutMgr instanceof LayoutManager2) - { - LayoutManager2 lm2 = (LayoutManager2) layoutMgr; - lm2.invalidateLayout(this); - } + if (valid) + invalidate(); } } @@ -629,40 +651,36 @@ public class Container extends Component */ protected void validateTree() { - if (valid) - return; - - ContainerPeer cPeer = null; - if (peer != null && ! (peer instanceof LightweightPeer)) + if (!valid) { - cPeer = (ContainerPeer) peer; - cPeer.beginValidate(); - } - - for (int i = 0; i < ncomponents; ++i) - { - Component comp = component[i]; - - if (comp.getPeer () == null) - comp.addNotify(); - } - - doLayout (); - for (int i = 0; i < ncomponents; ++i) - { - Component comp = component[i]; + ContainerPeer cPeer = null; + if (peer instanceof ContainerPeer) + { + cPeer = (ContainerPeer) peer; + cPeer.beginLayout(); + } - if (! comp.isValid()) + doLayout (); + for (int i = 0; i < ncomponents; ++i) { - if (comp instanceof Container) + Component comp = component[i]; + + if (comp instanceof Container && ! (comp instanceof Window) + && ! comp.valid) { ((Container) comp).validateTree(); } else { - component[i].validate(); + comp.validate(); } } + + if (cPeer != null) + { + cPeer = (ContainerPeer) peer; + cPeer.endLayout(); + } } /* children will call invalidate() when they are layed out. It @@ -670,19 +688,15 @@ public class Container extends Component until after the children have been layed out. */ valid = true; - if (cPeer != null) - cPeer.endValidate(); } public void setFont(Font f) { - if( (f != null && (font == null || !font.equals(f))) - || f == null) + Font oldFont = getFont(); + super.setFont(f); + Font newFont = getFont(); + if (newFont != oldFont && (oldFont == null || ! oldFont.equals(newFont))) { - super.setFont(f); - // FIXME: Although it might make more sense to invalidate only - // those children whose font == null, Sun invalidates all children. - // So we'll do the same. invalidateTree(); } } @@ -784,8 +798,9 @@ public class Container extends Component LayoutManager l = layoutMgr; if (l instanceof LayoutManager2) maxSize = ((LayoutManager2) l).maximumLayoutSize(this); - else + else { maxSize = super.maximumSizeImpl(); + } size = maxSize; } } @@ -920,8 +935,8 @@ public class Container extends Component */ public void paintComponents(Graphics g) { - paint(g); - visitChildren(g, GfxPaintAllVisitor.INSTANCE, true); + if (isShowing()) + visitChildren(g, GfxPaintAllVisitor.INSTANCE, false); } /** @@ -943,7 +958,12 @@ public class Container extends Component */ public synchronized void addContainerListener(ContainerListener listener) { - containerListener = AWTEventMulticaster.add(containerListener, listener); + if (listener != null) + { + containerListener = AWTEventMulticaster.add(containerListener, + listener); + newEventsOnly = true; + } } /** @@ -985,10 +1005,10 @@ public class Container extends Component * * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == ContainerListener.class) - return getContainerListeners(); + return (T[]) getContainerListeners(); return super.getListeners(listenerType); } @@ -1247,8 +1267,14 @@ public class Container extends Component { synchronized (getTreeLock ()) { - for (int i = 0; i < ncomponents; ++i) - component[i].removeNotify(); + int ncomps = ncomponents; + Component[] comps = component; + for (int i = ncomps - 1; i >= 0; --i) + { + Component comp = comps[i]; + if (comp != null) + comp.removeNotify(); + } super.removeNotify(); } } @@ -1345,7 +1371,8 @@ public class Container extends Component * * @since 1.4 */ - public void setFocusTraversalKeys(int id, Set keystrokes) + public void setFocusTraversalKeys(int id, + Set<? extends AWTKeyStroke> keystrokes) { if (id != KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS && id != KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS && @@ -1433,7 +1460,8 @@ public class Container extends Component if (focusTraversalKeys == null) focusTraversalKeys = new Set[4]; - keystrokes = Collections.unmodifiableSet (new HashSet (keystrokes)); + keystrokes = + Collections.unmodifiableSet(new HashSet<AWTKeyStroke>(keystrokes)); firePropertyChange (name, focusTraversalKeys[id], keystrokes); focusTraversalKeys[id] = keystrokes; @@ -1451,7 +1479,7 @@ public class Container extends Component * * @since 1.4 */ - public Set getFocusTraversalKeys (int id) + public Set<AWTKeyStroke> getFocusTraversalKeys (int id) { if (id != KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS && id != KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS && @@ -1866,6 +1894,7 @@ public class Container extends Component bounds.height); try { + g2.setFont(comp.getFont()); visitor.visit(comp, g2); } finally @@ -1874,20 +1903,40 @@ public class Container extends Component } } + /** + * Overridden to dispatch events to lightweight descendents. + * + * @param e the event to dispatch. + */ void dispatchEventImpl(AWTEvent e) { - boolean dispatched = - LightweightDispatcher.getInstance().dispatchEvent(e); - if (! dispatched) + LightweightDispatcher dispatcher = LightweightDispatcher.getInstance(); + if (! isLightweight() && dispatcher.dispatchEvent(e)) { - if ((e.id <= ContainerEvent.CONTAINER_LAST - && e.id >= ContainerEvent.CONTAINER_FIRST) - && (containerListener != null - || (eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0)) - processEvent(e); - else - super.dispatchEventImpl(e); + // Some lightweight descendent got this event dispatched. Consume + // it and let the peer handle it. + e.consume(); + ComponentPeer p = peer; + if (p != null) + p.handleEvent(e); } + else + { + super.dispatchEventImpl(e); + } + } + + /** + * This is called by the lightweight dispatcher to avoid recursivly + * calling into the lightweight dispatcher. + * + * @param e the event to dispatch + * + * @see LightweightDispatcher#redispatch(MouseEvent, Component, int) + */ + void dispatchNoLightweight(AWTEvent e) + { + super.dispatchEventImpl(e); } /** @@ -2004,6 +2053,43 @@ public class Container extends Component parent.updateHierarchyListenerCount(type, delta); } + /** + * Notifies interested listeners about resizing or moving the container. + * This performs the super behaviour (sending component events) and + * additionally notifies any hierarchy bounds listeners on child components. + * + * @param resized true if the component has been resized, false otherwise + * @param moved true if the component has been moved, false otherwise + */ + void notifyReshape(boolean resized, boolean moved) + { + // Notify component listeners. + super.notifyReshape(resized, moved); + + if (ncomponents > 0) + { + // Notify hierarchy bounds listeners. + if (resized) + { + for (int i = 0; i < getComponentCount(); i++) + { + Component child = getComponent(i); + child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_RESIZED, + this, parent, 0); + } + } + if (moved) + { + for (int i = 0; i < getComponentCount(); i++) + { + Component child = getComponent(i); + child.fireHierarchyEvent(HierarchyEvent.ANCESTOR_MOVED, + this, parent, 0); + } + } + } + } + private void addNotifyContainerChildren() { synchronized (getTreeLock ()) @@ -2011,12 +2097,6 @@ public class Container extends Component for (int i = ncomponents; --i >= 0; ) { component[i].addNotify(); - if (component[i].isLightweight ()) - { - enableEvents(component[i].eventMask); - if (peer != null && !isLightweight ()) - enableEvents (AWTEvent.PAINT_EVENT_MASK); - } } } } diff --git a/libjava/classpath/java/awt/Dialog.java b/libjava/classpath/java/awt/Dialog.java index 55c3371e622..7df2f523c86 100644 --- a/libjava/classpath/java/awt/Dialog.java +++ b/libjava/classpath/java/awt/Dialog.java @@ -97,6 +97,11 @@ public class Dialog extends Window private EventQueue eq2 = null; /** + * The number used to generate the name returned by getName. + */ + private static transient long next_dialog_number; + + /** * Initializes a new instance of <code>Dialog</code> with the specified * parent, that is resizable and not modal, and which has no title. * @@ -190,6 +195,7 @@ public class Dialog extends Window visible = false; setLayout(new BorderLayout()); + setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } /** @@ -273,6 +279,7 @@ public class Dialog extends Window visible = false; setLayout(new BorderLayout()); + setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } /** @@ -530,5 +537,19 @@ public class Dialog extends Window accessibleContext = new AccessibleAWTDialog(); return accessibleContext; } + + /** + * Generate a unique name for this <code>Dialog</code>. + * + * @return A unique name for this <code>Dialog</code>. + */ + String generateName() + { + return "dialog" + getUniqueLong(); + } + private static synchronized long getUniqueLong() + { + return next_dialog_number++; + } } diff --git a/libjava/classpath/java/awt/EventQueue.java b/libjava/classpath/java/awt/EventQueue.java index 235ad2ac17c..74dbd5fb67d 100644 --- a/libjava/classpath/java/awt/EventQueue.java +++ b/libjava/classpath/java/awt/EventQueue.java @@ -38,10 +38,16 @@ exception statement from your version. */ package java.awt; +import gnu.java.awt.LowPriorityEvent; +import gnu.java.awt.peer.NativeEventLoopRunningEvent; + import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.InputMethodEvent; import java.awt.event.InvocationEvent; +import java.awt.event.PaintEvent; +import java.awt.peer.ComponentPeer; +import java.awt.peer.LightweightPeer; import java.lang.reflect.InvocationTargetException; import java.util.EmptyStackException; @@ -61,11 +67,47 @@ import java.util.EmptyStackException; */ public class EventQueue { - private static final int INITIAL_QUEUE_DEPTH = 8; - private AWTEvent[] queue = new AWTEvent[INITIAL_QUEUE_DEPTH]; + /** + * Indicates events that are processed with normal priority. This is normally + * all events except PaintEvents. + */ + private static final int NORM_PRIORITY = 0; + + /** + * Indicates events that are processed with lowes priority. This is normally + * all PaintEvents and LowPriorityEvents. + */ + private static final int LOW_PRIORITY = 1; - private int next_in = 0; // Index where next event will be added to queue - private int next_out = 0; // Index of next event to be removed from queue + /** + * Implements the actual queue. EventQueue has 2 internal queues for + * different priorities: + * 1 PaintEvents are always dispatched with low priority. + * 2. All other events are dispatched with normal priority. + * + * This makes sure that the actual painting (output) is performed _after_ all + * available input has been processed and that the paint regions are + * coalesced as much as possible. + */ + private class Queue + { + /** + * The first item in the queue. This is where events are popped from. + */ + AWTEvent queueHead; + + /** + * The last item. This is where events are posted to. + */ + AWTEvent queueTail; + } + + /** + * The three internal event queues. + * + * @see Queue + */ + private Queue[] queues; private EventQueue next; private EventQueue prev; @@ -73,31 +115,25 @@ public class EventQueue private long lastWhen = System.currentTimeMillis(); private EventDispatchThread dispatchThread = new EventDispatchThread(this); - private boolean shutdown = false; - - synchronized private void setShutdown (boolean b) - { - shutdown = b; - } + private boolean nativeLoopRunning = false; - synchronized boolean isShutdown () + private boolean isShutdown () { - if (shutdown) - return true; - // This is the exact self-shutdown condition specified in J2SE: // http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/AWTThreadIssues.html - - // FIXME: check somewhere that the native queue is empty - if (peekEvent() == null) - { - Frame[] frames = Frame.getFrames(); - for (int i = 0; i < frames.length; ++i) - if (frames[i].isDisplayable()) - return false; - return true; - } - return false; + + if (nativeLoopRunning) + return false; + + if (peekEvent() != null) + return false; + + Frame[] frames = Frame.getFrames(); + for (int i = 0; i < frames.length; ++i) + if (frames[i].isDisplayable()) + return false; + + return true; } /** @@ -105,6 +141,9 @@ public class EventQueue */ public EventQueue() { + queues = new Queue[2]; + queues[NORM_PRIORITY] = new Queue(); + queues[LOW_PRIORITY] = new Queue(); } /** @@ -122,31 +161,67 @@ public class EventQueue if (next != null) return next.getNextEvent(); - while (next_in == next_out) - { - // We are not allowed to return null from this method, yet it - // is possible that we actually have run out of native events - // in the enclosing while() loop, and none of the native events - // happened to cause AWT events. We therefore ought to check - // the isShutdown() condition here, before risking a "native - // wait". If we check it before entering this function we may - // wait forever for events after the shutdown condition has - // arisen. + AWTEvent res = getNextEventImpl(true); + while (res == null) + { if (isShutdown()) - throw new InterruptedException(); + { + // Explicitly set dispathThread to null. If we don't do + // this, there is a race condition where dispatchThread + // can be != null even after the event dispatch thread has + // stopped running. If that happens, then the + // dispatchThread == null check in postEventImpl will + // fail, and a new event dispatch thread will not be + // created, leaving invokeAndWaits waiting indefinitely. + dispatchThread = null; + + // Interrupt the event dispatch thread. + throw new InterruptedException(); + } wait(); + res = getNextEventImpl(true); } - AWTEvent res = queue[next_out]; - - if (++next_out == queue.length) - next_out = 0; return res; } /** + * Fetches and possibly removes the next event from the internal queues. + * This method returns immediately. When all queues are empty, this returns + * <code>null</code>: + * + * @param remove <true> when the event should be removed from the queue, + * <code>false</code> otherwise + * + * @return the next event or <code>null</code> when all internal queues + * are empty + */ + private AWTEvent getNextEventImpl(boolean remove) + { + AWTEvent next = null; + for (int i = 0; i < queues.length && next == null; i++) + { + Queue q = queues[i]; + if (q.queueHead != null) + { + // Got an event, remove it. + next = q.queueHead; + if (remove) + { + // Unlink event from the queue. + q.queueHead = next.queueNext; + if (q.queueHead == null) + q.queueTail = null; + next.queueNext = null; + } + } + } + return next; + } + + /** * Returns the next event in the queue without removing it from the queue. * This method will block until an event is available or until the thread * is interrupted. @@ -160,10 +235,7 @@ public class EventQueue if (next != null) return next.peekEvent(); - if (next_in != next_out) - return queue[next_out]; - else - return null; + return getNextEventImpl(false); } /** @@ -184,14 +256,18 @@ public class EventQueue if (next != null) return next.peekEvent(id); - int i = next_out; - while (i != next_in) + AWTEvent evt = null; + for (int i = 0; i < queues.length && evt == null; i++) { - AWTEvent qevt = queue[i]; - if (qevt.id == id) - return qevt; + Queue q = queues[i]; + evt = q.queueHead; + while (evt != null && evt.id != id) + evt = evt.queueNext; + // At this point we either have found an event (evt != null -> exit + // for loop), or we have found no event (evt == null -> search next + // internal queue). } - return null; + return evt; } /** @@ -201,7 +277,42 @@ public class EventQueue * * @exception NullPointerException If event is null. */ - public synchronized void postEvent(AWTEvent evt) + public void postEvent(AWTEvent evt) + { + postEventImpl(evt); + } + + /** + * Sorts events to their priority and calls + * {@link #postEventImpl(AWTEvent, int)}. + * + * @param evt the event to post + */ + private synchronized final void postEventImpl(AWTEvent evt) + { + int priority = NORM_PRIORITY; + if (evt instanceof PaintEvent || evt instanceof LowPriorityEvent) + priority = LOW_PRIORITY; + // TODO: Maybe let Swing RepaintManager events also be processed with + // low priority. + if (evt instanceof NativeEventLoopRunningEvent) + { + nativeLoopRunning = ((NativeEventLoopRunningEvent) evt).isRunning(); + notify(); + return; + } + postEventImpl(evt, priority); + } + + /** + * Actually performs the event posting. This is needed because the + * RI doesn't use the public postEvent() method when transferring events + * between event queues in push() and pop(). + * + * @param evt the event to post + * @param priority the priority of the event + */ + private final void postEventImpl(AWTEvent evt, int priority) { if (evt == null) throw new NullPointerException(); @@ -212,52 +323,71 @@ public class EventQueue return; } - /* Check for any events already on the queue with the same source - and ID. */ - int i = next_out; - while (i != next_in) + Object source = evt.getSource(); + + Queue q = queues[priority]; + if (source instanceof Component) { - AWTEvent qevt = queue[i]; - Object src; - if (qevt.id == evt.id - && (src = qevt.getSource()) == evt.getSource() - && src instanceof Component) + // For PaintEvents, ask the ComponentPeer to coalesce the event + // when the component is heavyweight. + Component comp = (Component) source; + ComponentPeer peer = comp.peer; + if (peer != null && evt instanceof PaintEvent + && ! (peer instanceof LightweightPeer)) + peer.coalescePaintEvent((PaintEvent) evt); + + // Check for any events already on the queue with the same source + // and ID. + AWTEvent previous = null; + for (AWTEvent qevt = q.queueHead; qevt != null; qevt = qevt.queueNext) { - /* If there are, call coalesceEvents on the source component - to see if they can be combined. */ - Component srccmp = (Component) src; - AWTEvent coalesced_evt = srccmp.coalesceEvents(qevt, evt); - if (coalesced_evt != null) + Object src = qevt.getSource(); + if (qevt.id == evt.id && src == comp) { - /* Yes. Replace the existing event with the combined event. */ - queue[i] = coalesced_evt; - return; + // If there are, call coalesceEvents on the source component + // to see if they can be combined. + Component srccmp = (Component) src; + AWTEvent coalescedEvt = srccmp.coalesceEvents(qevt, evt); + if (coalescedEvt != null) + { + // Yes. Replace the existing event with the combined event. + if (qevt != coalescedEvt) + { + if (previous != null) + { + assert previous.queueNext == qevt; + previous.queueNext = coalescedEvt; + } + else + { + assert q.queueHead == qevt; + q.queueHead = coalescedEvt; + } + coalescedEvt.queueNext = qevt.queueNext; + if (q.queueTail == qevt) + q.queueTail = coalescedEvt; + qevt.queueNext = null; + } + return; + } } - break; + previous = qevt; } - if (++i == queue.length) - i = 0; } - queue[next_in] = evt; - if (++next_in == queue.length) - next_in = 0; - - if (next_in == next_out) + if (q.queueHead == null) { - /* Queue is full. Extend it. */ - AWTEvent[] oldQueue = queue; - queue = new AWTEvent[queue.length * 2]; - - int len = oldQueue.length - next_out; - System.arraycopy(oldQueue, next_out, queue, 0, len); - if (next_out != 0) - System.arraycopy(oldQueue, 0, queue, len, next_out); - - next_out = 0; - next_in = oldQueue.length; + // We have an empty queue. Set this event both as head and as tail. + q.queueHead = evt; + q.queueTail = evt; } - + else + { + // Note: queueTail should not be null here. + q.queueTail.queueNext = evt; + q.queueTail = evt; + } + if (dispatchThread == null || !dispatchThread.isAlive()) { dispatchThread = new EventDispatchThread(this); @@ -287,15 +417,15 @@ public class EventQueue throw new Error("Can't call invokeAndWait from event dispatch thread"); EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); - Thread current = Thread.currentThread(); + Object notifyObject = new Object(); - InvocationEvent ie = - new InvocationEvent(eq, runnable, current, true); + InvocationEvent ie = + new InvocationEvent(eq, runnable, notifyObject, true); - synchronized (current) + synchronized (notifyObject) { eq.postEvent(ie); - current.wait(); + notifyObject.wait(); } Exception exception; @@ -387,17 +517,26 @@ public class EventQueue if (dispatchThread == null) dispatchThread = new EventDispatchThread(this); - int i = next_out; - while (i != next_in) + synchronized (newEventQueue) { - newEventQueue.postEvent(queue[i]); - next_out = i; - if (++i == queue.length) - i = 0; + // The RI transfers the events without calling the new eventqueue's + // push(), but using getNextEvent(). + while (peekEvent() != null) + { + try + { + newEventQueue.postEventImpl(getNextEvent()); + } + catch (InterruptedException ex) + { + // What should we do with this? + ex.printStackTrace(); + } + } + newEventQueue.prev = this; } next = newEventQueue; - newEventQueue.prev = this; } /** Transfer any pending events from this queue back to the parent queue that @@ -408,36 +547,49 @@ public class EventQueue */ protected void pop() throws EmptyStackException { - if (prev == null) - throw new EmptyStackException(); - /* The order is important here, we must get the prev lock first, or deadlock could occur as callers usually get here following prev's next pointer, and thus obtain prev's lock before trying to get this lock. */ - synchronized (prev) + EventQueue previous = prev; + if (previous == null) + throw new EmptyStackException(); + synchronized (previous) { - prev.next = next; - if (next != null) - next.prev = prev; - synchronized (this) { - int i = next_out; - while (i != next_in) + EventQueue nextQueue = next; + if (nextQueue != null) { - prev.postEvent(queue[i]); - next_out = i; - if (++i == queue.length) - i = 0; + nextQueue.pop(); + } + else + { + previous.next = null; + + // The RI transfers the events without calling the new eventqueue's + // push(), so this should be OK and most effective. + while (peekEvent() != null) + { + try + { + previous.postEventImpl(getNextEvent()); + } + catch (InterruptedException ex) + { + // What should we do with this? + ex.printStackTrace(); + } + } + prev = null; + // Tell our EventDispatchThread that it can end + // execution. + if (dispatchThread != null) + { + dispatchThread.interrupt(); + dispatchThread = null; + } } - // Empty the queue so it can be reused - next_in = 0; - next_out = 0; - - setShutdown(true); - dispatchThread = null; - this.notifyAll(); } } } diff --git a/libjava/classpath/java/awt/FileDialog.java b/libjava/classpath/java/awt/FileDialog.java index f02d06be2c9..21117700d6e 100644 --- a/libjava/classpath/java/awt/FileDialog.java +++ b/libjava/classpath/java/awt/FileDialog.java @@ -95,6 +95,11 @@ private FilenameFilter filter; */ private int mode; +/** + * The number used to generate the name returned by getName. + */ +private static transient long next_file_dialog_number; + /*************************************************************************/ /* @@ -300,7 +305,11 @@ getFile() public synchronized void setFile(String file) { - this.file = file; + if ("".equals(file)) + this.file = null; + else + this.file = file; + if (peer != null) { FileDialogPeer f = (FileDialogPeer) peer; @@ -366,5 +375,22 @@ paramString() ",mode=" + mode + "," + super.paramString()); } +/** + * Generate a unique name for this <code>FileDialog</code>. + * + * @return A unique name for this <code>FileDialog</code>. + */ +String +generateName() +{ + return "filedlg" + getUniqueLong(); +} + +private static synchronized long +getUniqueLong() +{ + return next_file_dialog_number++; +} + } // class FileDialog diff --git a/libjava/classpath/java/awt/FlowLayout.java b/libjava/classpath/java/awt/FlowLayout.java index 8c99195289a..70c98a29a74 100644 --- a/libjava/classpath/java/awt/FlowLayout.java +++ b/libjava/classpath/java/awt/FlowLayout.java @@ -337,7 +337,10 @@ public class FlowLayout implements LayoutManager, Serializable Insets ins = parent.getInsets (); - w += (num + 1) * hgap + ins.left + ins.right; + if (num == 0) + w += 2 * hgap + ins.left + ins.right; + else + w += (num + 1) * hgap + ins.left + ins.right; h += 2 * vgap + ins.top + ins.bottom; return new Dimension (w, h); diff --git a/libjava/classpath/java/awt/Font.java b/libjava/classpath/java/awt/Font.java index 1c22ce7b48f..29b87d6af99 100644 --- a/libjava/classpath/java/awt/Font.java +++ b/libjava/classpath/java/awt/Font.java @@ -44,6 +44,7 @@ import gnu.java.awt.peer.ClasspathFontPeer; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.LineMetrics; +import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; @@ -351,7 +352,7 @@ public class Font implements Serializable this.name = peer.getName(this); } - public Font(Map attrs) + public Font(Map<? extends AttributedCharacterIterator.Attribute, ?> attrs) { this(null, attrs); } @@ -797,7 +798,7 @@ public class Font implements Serializable * * @since 1.2 */ - public Font deriveFont(Map attributes) + public Font deriveFont(Map<? extends AttributedCharacterIterator.Attribute, ?> attributes) { return peer.deriveFont(this, attributes); } @@ -811,7 +812,7 @@ public class Font implements Serializable * @see java.text.AttributedCharacterIterator.Attribute * @see java.awt.font.TextAttribute */ - public Map getAttributes() + public Map<TextAttribute, ?> getAttributes() { return peer.getAttributes(this); } @@ -890,7 +891,7 @@ public class Font implements Serializable * * @see java.awt.font.TextAttribute */ - public static Font getFont(Map attributes) + public static Font getFont(Map<? extends AttributedCharacterIterator.Attribute, ?> attributes) { return getFontFromToolkit(null, attributes); } @@ -1086,7 +1087,8 @@ public class Font implements Serializable */ public Rectangle2D getStringBounds(String str, FontRenderContext frc) { - return getStringBounds(str, 0, str.length() - 1, frc); + char[] chars = str.toCharArray(); + return getStringBounds(chars, 0, chars.length, frc); } /** @@ -1114,8 +1116,8 @@ public class Font implements Serializable public Rectangle2D getStringBounds(String str, int begin, int limit, FontRenderContext frc) { - return peer.getStringBounds(this, new StringCharacterIterator(str), begin, - limit, frc); + String sub = str.substring(begin, limit); + return getStringBounds(sub, frc); } /** @@ -1143,7 +1145,16 @@ public class Font implements Serializable public Rectangle2D getStringBounds(CharacterIterator ci, int begin, int limit, FontRenderContext frc) { - return peer.getStringBounds(this, ci, begin, limit, frc); + int start = ci.getBeginIndex(); + int end = ci.getEndIndex(); + char[] chars = new char[limit - start]; + ci.setIndex(start); + for (int index = 0; index < chars.length; index++) + { + chars[index] = ci.current(); + ci.next(); + } + return getStringBounds(chars, 0, chars.length, frc); } /** @@ -1171,9 +1182,10 @@ public class Font implements Serializable public Rectangle2D getStringBounds(char[] chars, int begin, int limit, FontRenderContext frc) { - return peer.getStringBounds(this, - new StringCharacterIterator(new String(chars)), - begin, limit, frc); + String str = new String(chars, begin, limit - begin); + TextLayout layout = new TextLayout(str, this, frc); + return new Rectangle2D.Float(0, -layout.getAscent(), layout.getAdvance(), + layout.getDescent() + layout.getLeading()); } /** diff --git a/libjava/classpath/java/awt/Frame.java b/libjava/classpath/java/awt/Frame.java index 542013671aa..d5cc7f53197 100644 --- a/libjava/classpath/java/awt/Frame.java +++ b/libjava/classpath/java/awt/Frame.java @@ -340,13 +340,16 @@ public class Frame extends Window implements MenuContainer parent.remove(menuBar); menuBar.setParent(this); - if (peer != null) - { - if (menuBar != null) - menuBar.addNotify(); - invalidateTree(); - ((FramePeer) peer).setMenuBar(menuBar); - } + // Create local copy for thread safety. + FramePeer p = (FramePeer) peer; + if (p != null) + { + if (menuBar != null) + menuBar.addNotify(); + if (valid) + invalidate(); + p.setMenuBar(menuBar); + } } } @@ -485,7 +488,10 @@ public class Frame extends Window implements MenuContainer private static void noteFrame(Frame f) { - weakFrames.add(new WeakReference(f)); + synchronized (weakFrames) + { + weakFrames.add(new WeakReference(f)); + } } public static Frame[] getFrames() @@ -533,8 +539,7 @@ public class Frame extends Window implements MenuContainer public int getState() { - // FIXME: State might have changed in the peer... Must check. - return (state & ICONIFIED) != 0 ? ICONIFIED : NORMAL; + return (getExtendedState() & ICONIFIED) != 0 ? ICONIFIED : NORMAL; } /** @@ -542,7 +547,13 @@ public class Frame extends Window implements MenuContainer */ public void setExtendedState(int state) { - this.state = state; + if (getToolkit().isFrameStateSupported(state)) + { + this.state = state; + FramePeer p = (FramePeer) peer; + if (p != null) + p.setState(state); + } } /** @@ -550,6 +561,9 @@ public class Frame extends Window implements MenuContainer */ public int getExtendedState() { + FramePeer p = (FramePeer) peer; + if (p != null) + state = p.getState(); return state; } diff --git a/libjava/classpath/java/awt/Graphics2D.java b/libjava/classpath/java/awt/Graphics2D.java index ada13edc512..e0a1b4756f5 100644 --- a/libjava/classpath/java/awt/Graphics2D.java +++ b/libjava/classpath/java/awt/Graphics2D.java @@ -183,7 +183,7 @@ public abstract class Graphics2D extends Graphics * @see #getComposite() */ public abstract void setComposite(Composite comp); - + /** * Sets the paint to be used for subsequent drawing operations. * @@ -227,14 +227,14 @@ public abstract class Graphics2D extends Graphics * * @see #addRenderingHints(Map) */ - public abstract void setRenderingHints(Map hints); + public abstract void setRenderingHints(Map<?,?> hints); /** * Adds/updates the rendering hint. * * @param hints the hints to add or update. */ - public abstract void addRenderingHints(Map hints); + public abstract void addRenderingHints(Map<?,?> hints); /** * Returns the current rendering hints. diff --git a/libjava/classpath/java/awt/GridBagLayout.java b/libjava/classpath/java/awt/GridBagLayout.java index d84b7d6df6c..0415c7bd3bb 100644 --- a/libjava/classpath/java/awt/GridBagLayout.java +++ b/libjava/classpath/java/awt/GridBagLayout.java @@ -46,6 +46,7 @@ import java.util.Hashtable; /** * @author Michael Koch (konqueror@gmx.de) * @author Jeroen Frijters (jeroen@frijters.net) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) */ public class GridBagLayout implements Serializable, LayoutManager2 @@ -62,8 +63,8 @@ public class GridBagLayout // REMAINDER constraints. // Constraints kept in comptable are never modified, and constraints // kept in internalcomptable can be modified internally only. - protected Hashtable comptable; - private Hashtable internalcomptable; + protected Hashtable<Component,GridBagConstraints> comptable; + private Hashtable<Component,GridBagConstraints> internalcomptable; protected GridBagLayoutInfo layoutInfo; protected GridBagConstraints defaultConstraints; @@ -74,8 +75,8 @@ public class GridBagLayout public GridBagLayout () { - this.comptable = new Hashtable(); - this.internalcomptable = new Hashtable(); + this.comptable = new Hashtable<Component,GridBagConstraints>(); + this.internalcomptable = new Hashtable<Component,GridBagConstraints>(); this.defaultConstraints= new GridBagConstraints(); } @@ -320,6 +321,16 @@ public class GridBagLayout } /** + * Return a string representation of this GridBagLayout. + * + * @return a string representation + */ + public String toString() + { + return getClass().getName(); + } + + /** * Move and resize a rectangle according to a set of grid bag * constraints. The x, y, width and height fields of the * rectangle argument are adjusted to the new values. @@ -489,16 +500,18 @@ public class GridBagLayout // Guaranteed to contain the last component added to the given row // or column, whose gridwidth/height is not REMAINDER. - HashMap lastInRow = new HashMap(); - HashMap lastInCol = new HashMap(); + HashMap<Integer,Component> lastInRow = new HashMap<Integer,Component>(); + HashMap<Integer,Component> lastInCol = new HashMap<Integer,Component>(); Component[] components = parent.getComponents(); // Components sorted by gridwidths/heights, // smallest to largest, with REMAINDER and RELATIVE at the end. // These are useful when determining sizes and weights. - ArrayList sortedByWidth = new ArrayList(components.length); - ArrayList sortedByHeight = new ArrayList(components.length); + ArrayList<Component> sortedByWidth = + new ArrayList<Component>(components.length); + ArrayList<Component> sortedByHeight = + new ArrayList<Component>(components.length); // STEP 1: first we figure out how many rows/columns for (int i = 0; i < components.length; i++) @@ -763,7 +776,7 @@ public class GridBagLayout // STEP 3: Determine sizes and weights for columns. for (int i = 0; i < sortedByWidth.size(); i++) { - Component component = (Component) sortedByWidth.get(i); + Component component = sortedByWidth.get(i); // If component is not visible we dont have to care about it. if (!component.isVisible()) @@ -877,7 +890,8 @@ public class GridBagLayout * width. Otherwise, sort by height. * FIXME: Use a better sorting algorithm. */ - private void sortBySpan (Component component, int span, ArrayList list, boolean sortByWidth) + private void sortBySpan (Component component, int span, + ArrayList<Component> list, boolean sortByWidth) { if (span == GridBagConstraints.REMAINDER || span == GridBagConstraints.RELATIVE) diff --git a/libjava/classpath/java/awt/GridLayout.java b/libjava/classpath/java/awt/GridLayout.java index a6836681da5..65e09aa59f8 100644 --- a/libjava/classpath/java/awt/GridLayout.java +++ b/libjava/classpath/java/awt/GridLayout.java @@ -289,7 +289,7 @@ public class GridLayout implements LayoutManager, Serializable public String toString () { return (getClass ().getName () + "[" - + ",hgap=" + hgap + ",vgap=" + vgap + + "hgap=" + hgap + ",vgap=" + vgap + ",rows=" + rows + ",cols=" + cols + "]"); } diff --git a/libjava/classpath/java/awt/KeyboardFocusManager.java b/libjava/classpath/java/awt/KeyboardFocusManager.java index eacbceb7d50..cd138269e0b 100644 --- a/libjava/classpath/java/awt/KeyboardFocusManager.java +++ b/libjava/classpath/java/awt/KeyboardFocusManager.java @@ -1,5 +1,5 @@ /* KeyboardFocusManager.java -- manage component focusing via the keyboard - Copyright (C) 2002, 2004 Free Software Foundation + Copyright (C) 2002, 2004, 2005 Free Software Foundation This file is part of GNU Classpath. @@ -555,7 +555,9 @@ public abstract class KeyboardFocusManager * @see #UP_CYCLE_TRAVERSAL_KEYS * @see #DOWN_CYCLE_TRAVERSAL_KEYS */ - public void setDefaultFocusTraversalKeys (int id, Set keystrokes) + public void setDefaultFocusTraversalKeys (int id, + Set<? extends AWTKeyStroke> + keystrokes) { if (id != KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS && id != KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS && @@ -627,7 +629,7 @@ public abstract class KeyboardFocusManager * @see #UP_CYCLE_TRAVERSAL_KEYS * @see #DOWN_CYCLE_TRAVERSAL_KEYS */ - public Set getDefaultFocusTraversalKeys (int id) + public Set<AWTKeyStroke> getDefaultFocusTraversalKeys (int id) { if (id < FORWARD_TRAVERSAL_KEYS || id > DOWN_CYCLE_TRAVERSAL_KEYS) throw new IllegalArgumentException (); @@ -989,9 +991,9 @@ public abstract class KeyboardFocusManager * @return A list of explicitly registered key event dispatchers. * @see KeyboardFocusManager#addKeyEventDispatcher(java.awt.KeyEventDispatcher) */ - protected List getKeyEventDispatchers () + protected List<KeyEventDispatcher> getKeyEventDispatchers () { - return (List) keyEventDispatchers.clone (); + return (List<KeyEventDispatcher>) keyEventDispatchers.clone (); } /** @@ -1046,9 +1048,9 @@ public abstract class KeyboardFocusManager * @return A list of explicitly registered key event post processors. * @see KeyboardFocusManager#addKeyEventPostProcessor(java.awt.KeyEventPostProcessor) */ - protected List getKeyEventPostProcessors () + protected List<KeyEventPostProcessor> getKeyEventPostProcessors () { - return (List) keyEventPostProcessors.clone (); + return (List<KeyEventPostProcessor>) keyEventPostProcessors.clone (); } /** diff --git a/libjava/classpath/java/awt/LightweightDispatcher.java b/libjava/classpath/java/awt/LightweightDispatcher.java index 3ea3f90a643..04196bd7796 100644 --- a/libjava/classpath/java/awt/LightweightDispatcher.java +++ b/libjava/classpath/java/awt/LightweightDispatcher.java @@ -38,7 +38,10 @@ exception statement from your version. */ package java.awt; +import java.awt.event.InputEvent; import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.peer.LightweightPeer; import java.util.WeakHashMap; /** @@ -49,7 +52,7 @@ import java.util.WeakHashMap; * * @author Roman Kennke (kennke@aicas.com) */ -class LightweightDispatcher +final class LightweightDispatcher { /** @@ -60,26 +63,17 @@ class LightweightDispatcher private static WeakHashMap instances = new WeakHashMap(); /** - * The component that is the start of a mouse dragging. All MOUSE_DRAGGED - * events that follow the initial press must have the source set to this, - * as well as the MOUSE_RELEASED event following the dragging. - */ - private Component dragTarget; - - /** - * Stores the button number which started the drag operation. This is needed - * because we want to handle only one drag operation and only the button that - * started the dragging should be able to stop it (by a button release). - */ - private int dragButton; - - /** * The last mouse event target. If the target changes, additional * MOUSE_ENTERED and MOUSE_EXITED events must be dispatched. */ private Component lastTarget; /** + * The current mouseEventTarget. + */ + private Component mouseEventTarget; + + /** * Returns an instance of LightweightDispatcher for the current thread's * thread group. * @@ -113,9 +107,9 @@ class LightweightDispatcher * * @param event the event */ - public boolean dispatchEvent(AWTEvent event) + public boolean dispatchEvent(final AWTEvent event) { - if (event instanceof MouseEvent && event.getSource() instanceof Window) + if (event instanceof MouseEvent) { MouseEvent mouseEvent = (MouseEvent) event; return handleMouseEvent(mouseEvent); @@ -130,151 +124,49 @@ class LightweightDispatcher * @param ev the mouse event * @return whether or not we found a lightweight that handled the event. */ - private boolean handleMouseEvent(MouseEvent ev) + private boolean handleMouseEvent(final MouseEvent ev) { - Window window = (Window) ev.getSource(); - // Find the target for the mouse event. We first seach the deepest - // component at the specified location. The we go up to its parent and - // try to find a neighbor of the deepest component that is suitable as - // mouse event target (it must be showing, at that location and have either - // a MouseListener or MouseMotionListener installed). If no such component - // is found, then we walk up the container hierarchy and find the next - // container that has a MouseListener or MouseMotionListener installed. - Component deepest = window.findComponentAt(ev.getX(), ev.getY()); - if (deepest == null) - return false; - Container parent = deepest.getParent(); - Point loc = ev.getPoint(); - loc = convertPointToChild(window, loc, parent); - Component target = null; - if (parent != null) - { - target = findTarget(parent, loc); - while (target == null && parent != null) - { - if (parent.mouseListener != null - || parent.mouseMotionListener != null - || (parent.eventMask - & (AWTEvent.MOUSE_EVENT_MASK - | AWTEvent.MOUSE_MOTION_EVENT_MASK)) != 0) - { - target = parent; - } - else - parent = parent.getParent(); - } - } - if (target == null || target.isLightweight()) + Container container = (Container) ev.getSource(); + Component target = findTarget(container, ev.getX(), ev.getY()); + trackEnterExit(target, ev); + int id = ev.getID(); + + // Dont update the mouseEventTarget when dragging. Also, MOUSE_CLICKED + // must be dispatched to the original target of MOUSE_PRESSED, so don't + // update in this case either. + if (! isDragging(ev) && id != MouseEvent.MOUSE_CLICKED) + mouseEventTarget = (target != container) ? target : null; + + if (mouseEventTarget != null) { - // Dispatch additional MOUSE_EXITED and MOUSE_ENTERED if event target - // is different from the last event target. - if (target != lastTarget) + switch (id) { - if (lastTarget != null) - { - Point p1 = convertPointToChild(window, ev.getPoint(), - lastTarget); - MouseEvent mouseExited = - new MouseEvent(lastTarget, MouseEvent.MOUSE_EXITED, - ev.getWhen(), ev.getModifiers(), p1.x, p1.y, - ev.getClickCount(), ev.isPopupTrigger()); - //System.err.println("event: " + mouseExited); - lastTarget.dispatchEvent(mouseExited); - } - - // If a target exists dispatch the MOUSE_ENTERED event. - // Experimenting shows that the MOUSE_ENTERED is also dispatched - // when the mouse is dragging. - if (target != null) - { - Point p = convertPointToChild(window, ev.getPoint(), target); - MouseEvent mouseEntered = - new MouseEvent(target, - MouseEvent.MOUSE_ENTERED, ev.getWhen(), - ev.getModifiers(), p.x, p.y, ev.getClickCount(), - ev.isPopupTrigger()); - //System.err.println("event: " + mouseEntered); - target.dispatchEvent(mouseEntered); - } - } - - switch (ev.getID()) - { - case MouseEvent.MOUSE_PRESSED: - // Handle the start of a drag operation or discard the event if - // one is already in progress. This prevents focus changes with the - // other mouse buttons when one is used for dragging. - if (dragTarget == null) - { - lastTarget = dragTarget = target; - - // Save the button that started the drag operation. - dragButton = ev.getButton(); - } - else - return false; - + case MouseEvent.MOUSE_ENTERED: + case MouseEvent.MOUSE_EXITED: + // This is already handled in trackEnterExit(). break; + case MouseEvent.MOUSE_PRESSED: case MouseEvent.MOUSE_RELEASED: - // Stop the drag operation only when the button that started - // it was released. - if (dragTarget != null && dragButton == ev.getButton()) - { - // Only post MOUSE_RELEASED to dragTarget (set in - // MOUSE_PRESSED) when the dragTarget is actually visible. - // Otherwise post the event to the normal target. - if (dragTarget.isVisible()) - target = dragTarget; - dragTarget = null; - } - - lastTarget = target; + case MouseEvent.MOUSE_MOVED: + redispatch(ev, mouseEventTarget, id); break; case MouseEvent.MOUSE_CLICKED: - // When we receive a MOUSE_CLICKED, we set the target to the - // previous target, which must have been a MOUSE_RELEASED event. - // This is necessary for the case when the MOUSE_RELEASED has - // caused the original target (like an internal component) go - // away. - // This line is the reason why it is not possible to move the - // 'lastTarget = target' assignment before the switch-statement. - target = lastTarget; + // MOUSE_CLICKED must be dispatched to the original target of + // MOUSE_PRESSED. + if (target == mouseEventTarget) + redispatch(ev, mouseEventTarget, id); break; case MouseEvent.MOUSE_DRAGGED: - // We consider only dragTarget for redispatching the event still - // we have to act in a way that the newly found target component - // was handled. - lastTarget = target; - target = dragTarget; + if (isDragging(ev)) + redispatch(ev, mouseEventTarget, id); break; - default: - // Only declare current target as the old value in all other - // cases. - lastTarget = target; - break; - } - - if (target != null) - { - Point targetCoordinates = convertPointToChild(window, - ev.getPoint(), - target); - int dx = targetCoordinates.x - ev.getX(); - int dy = targetCoordinates.y - ev.getY(); - ev.translatePoint(dx, dy); - ev.setSource(target); - target.dispatchEvent(ev); - - // We reset the event, so that the normal event dispatching is not - // influenced by this modified event. - ev.setSource(window); - ev.translatePoint(-dx, -dy); + case MouseEvent.MOUSE_WHEEL: + redispatch(ev, mouseEventTarget, id); } - - return true; + ev.consume(); } - else - return false; + + return ev.isConsumed(); } /** @@ -290,58 +182,180 @@ class LightweightDispatcher * @return the actual receiver of the mouse event, or null, if no such * component has been found */ - private Component findTarget(Container c, Point loc) + private Component findTarget(final Container c, final int x, final int y) { - int numComponents = c.getComponentCount(); Component target = null; - if (c != null) + + // First we check the children of the container. + + // Note: It is important that we use the package private Container + // fields ncomponents and component here. There are applications + // that override getComponentCount() + // and getComponent() to hide internal components, which makes + // the LightweightDispatcher not work correctly in these cases. + // As a positive sideeffect this is slightly more efficient. + int nChildren = c.ncomponents; + for (int i = 0; i < nChildren && target == null; i++) { - for (int i = 0; i < numComponents; i++) + Component child = c.component[i]; + int childX = x - child.x; + int childY = y - child.y; + if (child != null && child.visible + && child.peer instanceof LightweightPeer + && child.contains(childX, childY)) { - Component child = c.getComponent(i); - if (child.isShowing()) + // Check if there's a deeper possible target. + if (child instanceof Container) { - if (child.contains(loc.x - child.getX(), loc.y - child.getY()) - && (child.mouseListener != null - || child.mouseMotionListener != null - || (child.eventMask - & (AWTEvent.MOUSE_EVENT_MASK - | AWTEvent.MOUSE_MOTION_EVENT_MASK)) != 0)) - { - target = child; - break; - } + Component deeper = findTarget((Container) child, + childX, childY); + if (deeper != null) + target = deeper; } + // Check if the child itself is interested in mouse events. + else if (isMouseListening(child)) + target = child; } } + + // Check the container itself, if we didn't find a target yet. + if (target == null && c.contains(x, y) && isMouseListening(c)) + target = c; + return target; } /** - * Converts a point in the parent's coordinate system to a child coordinate - * system. The resulting point is stored in the same Point object and - * returned. + * Checks if the specified component would be interested in a mouse event. + * + * @param c the component to check + * + * @return <code>true</code> if the component has mouse listeners installed, + * <code>false</code> otherwise + */ + private boolean isMouseListening(final Component c) + { + // Note: It is important to NOT check if the component is listening + // for a specific event (for instance, mouse motion events). The event + // gets dispatched to the component if the component is listening + // for ANY mouse event, even when the component is not listening for the + // specific type of event. There are applications that depend on this + // (sadly). + return c.mouseListener != null + || c.mouseMotionListener != null + || c.mouseWheelListener != null + || (c.eventMask & AWTEvent.MOUSE_EVENT_MASK) != 0 + || (c.eventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0 + || (c.eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0; + } + + /** + * Tracks MOUSE_ENTERED and MOUSE_EXIT as well as MOUSE_MOVED and + * MOUSE_DRAGGED and creates synthetic MOUSE_ENTERED and MOUSE_EXITED for + * lightweight component.s + * + * @param target the current mouse event target + * @param ev the mouse event + */ + private void trackEnterExit(final Component target, final MouseEvent ev) + { + int id = ev.getID(); + if (target != lastTarget) + { + if (lastTarget != null) + redispatch(ev, lastTarget, MouseEvent.MOUSE_EXITED); + if (id == MouseEvent.MOUSE_EXITED) + ev.consume(); + if (target != null) + redispatch(ev, target, MouseEvent.MOUSE_ENTERED); + if (id == MouseEvent.MOUSE_ENTERED) + ev.consume(); + lastTarget = target; + } + + } + + /** + * Redispatches the specified mouse event to the specified target with the + * specified id. * - * @param parent the parent component - * @param p the point - * @param child the child component + * @param ev the mouse event + * @param target the new target + * @param id the new id + */ + private void redispatch(MouseEvent ev, Component target, int id) + { + Component source = ev.getComponent(); + if (target != null) + { + // Translate coordinates. + int x = ev.getX(); + int y = ev.getY(); + for (Component c = target; c != null && c != source; c = c.getParent()) + { + x -= c.x; + y -= c.y; + } + + // Retarget event. + MouseEvent retargeted; + if (id == MouseEvent.MOUSE_WHEEL) + { + MouseWheelEvent mwe = (MouseWheelEvent) ev; + retargeted = new MouseWheelEvent(target, id, ev.getWhen(), + ev.getModifiers() + | ev.getModifiersEx(), x, y, + ev.getClickCount(), + ev.isPopupTrigger(), + mwe.getScrollType(), + mwe.getScrollAmount(), + mwe.getWheelRotation()); + } + else + { + retargeted = new MouseEvent(target, id, ev.getWhen(), + ev.getModifiers() | ev.getModifiersEx(), + x, y, ev.getClickCount(), + ev.isPopupTrigger(), ev.getButton()); + } + + if (target == source) + ((Container) target).dispatchNoLightweight(retargeted); + else + target.dispatchEvent(retargeted); + } + } + + /** + * Determines if we are in the middle of a drag operation, that is, if + * any of the buttons is held down. + * + * @param ev the mouse event to check * - * @return the translated point + * @return <code>true</code> if we are in the middle of a drag operation, + * <code>false</code> otherwise */ - private Point convertPointToChild(Component parent, Point p, - Component child) + private boolean isDragging(MouseEvent ev) { - int offX = 0; - int offY = 0; - Component comp = child; - while (comp != null && comp != parent) + int mods = ev.getModifiersEx(); + int id = ev.getID(); + if (id == MouseEvent.MOUSE_PRESSED || id == MouseEvent.MOUSE_RELEASED) { - offX += comp.getX(); - offY += comp.getY(); - comp = comp.getParent(); + switch (ev.getButton()) + { + case MouseEvent.BUTTON1: + mods ^= InputEvent.BUTTON1_DOWN_MASK; + break; + case MouseEvent.BUTTON2: + mods ^= InputEvent.BUTTON2_DOWN_MASK; + break; + case MouseEvent.BUTTON3: + mods ^= InputEvent.BUTTON3_DOWN_MASK; + break; + } } - p.x -= offX; - p.y -= offY; - return p; + return (mods & (InputEvent.BUTTON1_DOWN_MASK + | InputEvent.BUTTON2_DOWN_MASK + | InputEvent.BUTTON3_DOWN_MASK)) != 0; } } diff --git a/libjava/classpath/java/awt/List.java b/libjava/classpath/java/awt/List.java index 86270234345..df8bffa1945 100644 --- a/libjava/classpath/java/awt/List.java +++ b/libjava/classpath/java/awt/List.java @@ -1,5 +1,5 @@ /* List.java -- A listbox widget - Copyright (C) 1999, 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 1999, 2002, 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -54,1005 +54,932 @@ import javax.accessibility.AccessibleState; import javax.accessibility.AccessibleStateSet; /** - * Class that implements a listbox widget - * - * @author Aaron M. Renn (arenn@urbanophile.com) - */ + * Class that implements a listbox widget + * + * @author Aaron M. Renn (arenn@urbanophile.com) + */ public class List extends Component implements ItemSelectable, Accessible { -/* - * Static Variables - */ - -/** - * The number used to generate the name returned by getName. - */ -private static transient long next_list_number; - -// Serialization constant -private static final long serialVersionUID = -3304312411574666869L; - -/*************************************************************************/ - -/* - * Instance Variables - */ - -// FIXME: Need read/writeObject - -/** - * @serial The items in the list. - */ -private Vector items = new Vector(); - -/** - * @serial Indicates whether or not multiple items can be selected - * simultaneously. - */ -private boolean multipleMode; - -/** - * @serial The number of rows in the list. This is set on creation - * only and cannot be modified. - */ -private int rows; + /** + * The number used to generate the name returned by getName. + */ + private static transient long next_list_number; -/** - * @serial An array of the item indices that are selected. - */ -private int[] selected; + // Serialization constant + private static final long serialVersionUID = -3304312411574666869L; -/** - * @serial An index value used by <code>makeVisible()</code> and - * <code>getVisibleIndex</code>. - */ -private int visibleIndex; + // FIXME: Need read/writeObject -// The list of ItemListeners for this object. -private ItemListener item_listeners; + /** + * @serial The items in the list. + */ + private Vector items = new Vector(); -// The list of ActionListeners for this object. -private ActionListener action_listeners; + /** + * @serial Indicates whether or not multiple items can be selected + * simultaneously. + */ + private boolean multipleMode; + /** + * @serial The number of rows in the list. This is set on creation + * only and cannot be modified. + */ + private int rows; -/*************************************************************************/ + /** + * @serial An array of the item indices that are selected. + */ + private int[] selected; -/* - * Constructors - */ + /** + * @serial An index value used by <code>makeVisible()</code> and + * <code>getVisibleIndex</code>. + */ + private int visibleIndex = -1; -/** - * Initializes a new instance of <code>List</code> with no visible lines - * and multi-select disabled. - * - * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. - */ -public -List() -{ - this(4, false); -} + // The list of ItemListeners for this object. + private ItemListener item_listeners; -/*************************************************************************/ + // The list of ActionListeners for this object. + private ActionListener action_listeners; -/** - * Initializes a new instance of <code>List</code> with the specified - * number of visible lines and multi-select disabled. - * - * @param rows The number of visible rows in the list. - * - * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. - */ -public -List(int rows) -{ - this(rows, false); -} + /** + * Initializes a new instance of <code>List</code> with no visible lines + * and multi-select disabled. + * + * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. + * @since 1.1 + */ + public List() + { + this(4, false); + } -/*************************************************************************/ + /** + * Initializes a new instance of <code>List</code> with the specified + * number of visible lines and multi-select disabled. + * + * @param rows The number of visible rows in the list. + * + * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. + */ + public List(int rows) + { + this(rows, false); + } -/** - * Initializes a new instance of <code>List</code> with the specified - * number of lines and the specified multi-select setting. - * - * @param rows The number of visible rows in the list. - * @param multipleMode <code>true</code> if multiple lines can be selected - * simultaneously, <code>false</code> otherwise. - * - * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. - */ -public -List(int rows, boolean multipleMode) -{ - if (rows == 0) - this.rows = 4; - else - this.rows = rows; + /** + * Initializes a new instance of <code>List</code> with the specified + * number of lines and the specified multi-select setting. + * + * @param rows The number of visible rows in the list. + * @param multipleMode <code>true</code> if multiple lines can be selected + * simultaneously, <code>false</code> otherwise. + * + * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. + */ + public List(int rows, boolean multipleMode) + { + if (rows == 0) + this.rows = 4; + else + this.rows = rows; - this.multipleMode = multipleMode; - selected = new int[0]; - - if (GraphicsEnvironment.isHeadless()) - throw new HeadlessException (); -} - -/*************************************************************************/ - -/* - * Instance Variables - */ + this.multipleMode = multipleMode; + selected = new int[0]; -/** - * Returns the number of items in this list. - * - * @return The number of items in this list. - */ -public int -getItemCount() -{ - return countItems (); -} - -/*************************************************************************/ + if (GraphicsEnvironment.isHeadless()) + throw new HeadlessException(); + + } -/** - * Returns the number of items in this list. - * - * @return The number of items in this list. - * - * @deprecated This method is deprecated in favor of - * <code>getItemCount()</code> - */ -public int -countItems() -{ - return items.size (); -} + /** + * Returns the number of items in this list. + * + * @return The number of items in this list. + * + * @since 1.1 + */ + public int getItemCount() + { + return countItems(); + } -/*************************************************************************/ + /** + * Returns the number of items in this list. + * + * @return The number of items in this list. + * + * @deprecated This method is deprecated in favor of + * <code>getItemCount()</code> + */ + public int countItems() + { + return items.size(); + } -/** - * Returns the complete list of items. - * - * @return The complete list of items in the list. - */ -public synchronized String[] -getItems() -{ - String[] l_items = new String[getItemCount()]; + /** + * Returns the complete list of items. + * + * @return The complete list of items in the list. + * + * @since 1.1 + */ + public synchronized String[] getItems() + { + String[] l_items = new String[getItemCount()]; - items.copyInto(l_items); - return(l_items); -} - -/*************************************************************************/ - -/** - * Returns the item at the specified index. - * - * @param index The index of the item to retrieve. - * - * @exception IndexOutOfBoundsException If the index value is not valid. - */ -public String -getItem(int index) -{ - return((String)items.elementAt(index)); -} - -/*************************************************************************/ - -/** - * Returns the number of visible rows in the list. - * - * @return The number of visible rows in the list. - */ -public int -getRows() -{ - return(rows); -} - -/*************************************************************************/ - -/** - * Tests whether or not multi-select mode is enabled. - * - * @return <code>true</code> if multi-select mode is enabled, - * <code>false</code> otherwise. - */ -public boolean -isMultipleMode() -{ - return allowsMultipleSelections (); -} - -/*************************************************************************/ - -/** - * Tests whether or not multi-select mode is enabled. - * - * @return <code>true</code> if multi-select mode is enabled, - * <code>false</code> otherwise. - * - * @deprecated This method is deprecated in favor of - * <code>isMultipleMode()</code>. - */ -public boolean -allowsMultipleSelections() -{ - return multipleMode; -} - -/*************************************************************************/ - -/** - * This method enables or disables multiple selection mode for this - * list. - * - * @param multipleMode <code>true</code> to enable multiple mode, - * <code>false</code> otherwise. - */ -public void -setMultipleMode(boolean multipleMode) -{ - setMultipleSelections (multipleMode); -} - -/*************************************************************************/ - -/** - * This method enables or disables multiple selection mode for this - * list. - * - * @param multipleMode <code>true</code> to enable multiple mode, - * <code>false</code> otherwise. - * - * @deprecated - */ -public void -setMultipleSelections(boolean multipleMode) -{ - this.multipleMode = multipleMode; - - ListPeer peer = (ListPeer) getPeer (); - if (peer != null) - peer.setMultipleMode (multipleMode); -} - -/*************************************************************************/ - -/** - * Returns the minimum size of this component. - * - * @return The minimum size of this component. - */ -public Dimension -getMinimumSize() -{ - return getMinimumSize (getRows ()); -} - -/*************************************************************************/ - -/** - * Returns the minimum size of this component. - * - * @return The minimum size of this component. - * - * @deprecated This method is deprecated in favor of - * <code>getMinimumSize</code>. - */ -public Dimension -minimumSize() -{ - return minimumSize (getRows ()); -} - -/*************************************************************************/ - -/** - * Returns the minimum size of this component assuming it had the specified - * number of rows. - * - * @param rows The number of rows to size for. - * - * @return The minimum size of this component. - */ -public Dimension -getMinimumSize(int rows) -{ - return minimumSize (rows); -} - -/*************************************************************************/ - -/** - * Returns the minimum size of this component assuming it had the specified - * number of rows. - * - * @param rows The number of rows to size for. - * - * @return The minimum size of this component. - * - * @deprecated This method is deprecated in favor of - * <code>getMinimumSize(int)</code>> - */ -public Dimension -minimumSize(int rows) -{ - ListPeer peer = (ListPeer) getPeer (); - if (peer != null) - return peer.minimumSize (rows); - else - return new Dimension (0, 0); -} - -/*************************************************************************/ - -/** - * Returns the preferred size of this component. - * - * @return The preferred size of this component. - */ -public Dimension -getPreferredSize() -{ - return getPreferredSize (getRows ()); -} - -/*************************************************************************/ - -/** - * Returns the preferred size of this component. - * - * @return The preferred size of this component. - * - * @deprecated This method is deprecated in favor of - * <code>getPreferredSize</code>. - */ -public Dimension -preferredSize() -{ - return preferredSize (getRows ()); -} - -/*************************************************************************/ - -/** - * Returns the preferred size of this component assuming it had the specified - * number of rows. - * - * @param rows The number of rows to size for. - * - * @return The preferred size of this component. - */ -public Dimension -getPreferredSize(int rows) -{ - return preferredSize (rows); -} - -/*************************************************************************/ - -/** - * Returns the preferred size of this component assuming it had the specified - * number of rows. - * - * @param rows The number of rows to size for. - * - * @return The preferred size of this component. - * - * @deprecated This method is deprecated in favor of - * <code>getPreferredSize(int)</code>> - */ -public Dimension -preferredSize(int rows) -{ - ListPeer peer = (ListPeer) getPeer (); - if (peer != null) - return peer.preferredSize (rows); - else - return getSize(); -} - -/*************************************************************************/ - -/** - * This method adds the specified item to the end of the list. - * - * @param item The item to add to the list. - */ -public void -add(String item) -{ - add (item, -1); -} - -/*************************************************************************/ - -/** - * This method adds the specified item to the end of the list. - * - * @param item The item to add to the list. - * - * @deprecated Use add() instead. - */ -public void -addItem(String item) -{ - addItem (item, -1); -} - -/*************************************************************************/ - -/** - * Adds the specified item to the specified location in the list. - * If the desired index is -1 or greater than the number of rows - * in the list, then the item is added to the end. - * - * @param item The item to add to the list. - * @param index The location in the list to add the item, or -1 to add - * to the end. - */ -public void -add(String item, int index) -{ - addItem (item, index); -} - -/*************************************************************************/ - -/** - * Adds the specified item to the specified location in the list. - * If the desired index is -1 or greater than the number of rows - * in the list, then the item is added to the end. - * - * @param item The item to add to the list. - * @param index The location in the list to add the item, or -1 to add - * to the end. - * - * @deprecated Use add() instead. - */ -public void -addItem(String item, int index) -{ - if ((index == -1) || (index >= items.size ())) - items.addElement (item); - else - items.insertElementAt (item, index); - - ListPeer peer = (ListPeer) getPeer (); - if (peer != null) - peer.add (item, index); -} - -/*************************************************************************/ - -/** - * Deletes the item at the specified index. - * - * @param index The index of the item to delete. - * - * @exception IllegalArgumentException If the index is not valid - * - * @deprecated - */ -public void -delItem(int index) throws IllegalArgumentException -{ - items.removeElementAt (index); - - ListPeer peer = (ListPeer) getPeer (); - if (peer != null) - peer.delItems (index, index); -} - -/*************************************************************************/ - -/** - * Deletes the item at the specified index. - * - * @param index The index of the item to delete. - * - * @exception IllegalArgumentException If the index is not valid - */ -public void -remove(int index) throws IllegalArgumentException -{ - delItem (index); -} - -/*************************************************************************/ - -/** - * Deletes all items in the specified index range. - * - * @param start The beginning index of the range to delete. - * @param end The ending index of the range to delete. - * - * @exception IllegalArgumentException If the indexes are not valid - * - * @deprecated This method is deprecated for some unknown reason. - */ -public synchronized void -delItems(int start, int end) throws IllegalArgumentException -{ - if ((start < 0) || (start >= items.size())) - throw new IllegalArgumentException("Bad list start index value: " + start); - - if ((start < 0) || (start >= items.size())) - throw new IllegalArgumentException("Bad list start index value: " + start); - - if (start > end) - throw new IllegalArgumentException("Start is greater than end!"); - - // We must run the loop in reverse direction. - for (int i = end; i >= start; --i) - items.removeElementAt (i); - if (peer != null) - { - ListPeer l = (ListPeer) peer; - l.delItems (start, end); - } -} - -/*************************************************************************/ + items.copyInto(l_items); + return(l_items); + } -/** - * Deletes the first occurrence of the specified item from the list. - * - * @param item The item to delete. - * - * @exception IllegalArgumentException If the specified item does not exist. - */ -public synchronized void -remove(String item) throws IllegalArgumentException -{ - int index = items.indexOf(item); - if (index == -1) - throw new IllegalArgumentException("List element to delete not found"); + /** + * Returns the item at the specified index. + * + * @param index The index of the item to retrieve. + * + * @exception IndexOutOfBoundsException If the index value is not valid. + */ + public String getItem(int index) + { + return((String) items.elementAt(index)); + } - remove(index); -} + /** + * Returns the number of visible rows in the list. + * + * @return The number of visible rows in the list. + */ + public int getRows() + { + return(rows); + } -/*************************************************************************/ + /** + * Tests whether or not multi-select mode is enabled. + * + * @return <code>true</code> if multi-select mode is enabled, + * <code>false</code> otherwise. + * + * @since 1.1 + */ + public boolean isMultipleMode() + { + return allowsMultipleSelections (); + } -/** - * Deletes all of the items from the list. - */ -public synchronized void -removeAll() -{ - clear (); -} + /** + * Tests whether or not multi-select mode is enabled. + * + * @return <code>true</code> if multi-select mode is enabled, + * <code>false</code> otherwise. + * + * @deprecated This method is deprecated in favor of + * <code>isMultipleMode()</code>. + */ + public boolean allowsMultipleSelections() + { + return multipleMode; + } -/*************************************************************************/ + /** + * This method enables or disables multiple selection mode for this + * list. + * + * @param multipleMode <code>true</code> to enable multiple mode, + * <code>false</code> otherwise. + * + * @since 1.1 + */ + public void setMultipleMode(boolean multipleMode) + { + setMultipleSelections (multipleMode); + } -/** - * Deletes all of the items from the list. - * - * @deprecated This method is deprecated in favor of <code>removeAll()</code>. - */ -public void -clear() -{ - items.clear(); + /** + * This method enables or disables multiple selection mode for this + * list. + * + * @param multipleMode <code>true</code> to enable multiple mode, + * <code>false</code> otherwise. + * + * @deprecated + */ + public void setMultipleSelections(boolean multipleMode) + { + this.multipleMode = multipleMode; - ListPeer peer = (ListPeer) getPeer (); - if (peer != null) - peer.removeAll (); -} + ListPeer peer = (ListPeer) getPeer(); + if (peer != null) + peer.setMultipleMode(multipleMode); + + } -/*************************************************************************/ + /** + * Returns the minimum size of this component. + * + * @return The minimum size of this component. + * + * @since 1.1 + */ + public Dimension getMinimumSize() + { + return getMinimumSize(getRows()); + } -/** - * Replaces the item at the specified index with the specified item. - * - * @param item The new item value. - * @param index The index of the item to replace. - * - * @exception ArrayIndexOutOfBoundsException If the index is not valid. - */ -public synchronized void -replaceItem(String item, int index) throws ArrayIndexOutOfBoundsException -{ - if ((index < 0) || (index >= items.size())) - throw new ArrayIndexOutOfBoundsException("Bad list index: " + index); + /** + * Returns the minimum size of this component. + * + * @return The minimum size of this component. + * + * @deprecated This method is deprecated in favor of + * <code>getMinimumSize</code>. + */ + public Dimension minimumSize() + { + return minimumSize(getRows()); + } - items.insertElementAt(item, index + 1); - items.removeElementAt (index); + /** + * Returns the minimum size of this component assuming it had the specified + * number of rows. + * + * @param rows The number of rows to size for. + * + * @return The minimum size of this component. + * + * @since 1.1 + */ + public Dimension getMinimumSize(int rows) + { + return minimumSize(rows); + } - if (peer != null) - { - ListPeer l = (ListPeer) peer; + /** + * Returns the minimum size of this component assuming it had the specified + * number of rows. + * + * @param rows The number of rows to size for. + * + * @return The minimum size of this component. + * + * @deprecated This method is deprecated in favor of + * <code>getMinimumSize(int)</code>> + */ + public Dimension minimumSize(int rows) + { + ListPeer peer = (ListPeer) getPeer(); + if (peer != null) + return peer.minimumSize(rows); + else + return new Dimension(0, 0); + } - /* We add first and then remove so that the selected - item remains the same */ - l.add (item, index + 1); - l.delItems (index, index); - } -} + /** + * Returns the preferred size of this component. + * + * @return The preferred size of this component. + * + * @since 1.1 + */ + public Dimension getPreferredSize() + { + return getPreferredSize(getRows()); + } -/*************************************************************************/ + /** + * Returns the preferred size of this component. + * + * @return The preferred size of this component. + * + * @deprecated This method is deprecated in favor of + * <code>getPreferredSize</code>. + */ + public Dimension preferredSize() + { + return preferredSize(getRows()); + } -/** - * Returns the index of the currently selected item. -1 will be returned - * if there are no selected rows or if there are multiple selected rows. - * - * @return The index of the selected row. - */ -public synchronized int -getSelectedIndex() -{ - if (peer != null) - { - ListPeer l = (ListPeer) peer; - selected = l.getSelectedIndexes (); - } + /** + * Returns the preferred size of this component assuming it had the specified + * number of rows. + * + * @param rows The number of rows to size for. + * + * @return The preferred size of this component. + * + * @since 1.1 + */ + public Dimension getPreferredSize(int rows) + { + return preferredSize(rows); + } - if (selected == null || selected.length != 1) - return -1; - return selected[0]; -} + /** + * Returns the preferred size of this component assuming it had the specified + * number of rows. + * + * @param rows The number of rows to size for. + * + * @return The preferred size of this component. + * + * @deprecated This method is deprecated in favor of + * <code>getPreferredSize(int)</code>> + */ + public Dimension preferredSize(int rows) + { + ListPeer peer = (ListPeer)getPeer(); + if (peer != null) + return peer.preferredSize(rows); + else + return getSize(); + } -/*************************************************************************/ + /** + * This method adds the specified item to the end of the list. + * + * @param item The item to add to the list. + * + * @since 1.1 + */ + public void add(String item) + { + add (item, -1); + } -/** - * Returns an array containing the indexes of the rows that are - * currently selected. - * - * @return A list of indexes of selected rows. - */ -public synchronized int[] -getSelectedIndexes() -{ - if (peer != null) - { - ListPeer l = (ListPeer) peer; - selected = l.getSelectedIndexes (); - } - return selected; -} + /** + * This method adds the specified item to the end of the list. + * + * @param item The item to add to the list. + * + * @deprecated Use add() instead. + */ + public void addItem(String item) + { + addItem(item, -1); + } -/*************************************************************************/ + /** + * Adds the specified item to the specified location in the list. + * If the desired index is -1 or greater than the number of rows + * in the list, then the item is added to the end. + * + * @param item The item to add to the list. + * @param index The location in the list to add the item, or -1 to add + * to the end. + * + * @since 1.1 + */ + public void add(String item, int index) + { + addItem(item, index); + } -/** - * Returns the item that is currently selected, or <code>null</code> if there - * is no item selected. FIXME: What happens if multiple items selected? - * - * @return The selected item, or <code>null</code> if there is no - * selected item. - */ -public synchronized String -getSelectedItem() -{ - int index = getSelectedIndex(); - if (index == -1) - return(null); + /** + * Adds the specified item to the specified location in the list. + * If the desired index is -1 or greater than the number of rows + * in the list, then the item is added to the end. + * + * @param item The item to add to the list. + * @param index The location in the list to add the item, or -1 to add + * to the end. + * + * @deprecated Use add() instead. + */ + public void addItem(String item, int index) + { + if (item == null) + item = ""; + + if (index < -1) + index = -1; + + if ((index == -1) || (index >= items.size ())) + items.addElement (item); + else + items.insertElementAt(item, index); + + ListPeer peer = (ListPeer) getPeer(); + if (peer != null) + peer.add (item, index); + } - return((String)items.elementAt(index)); -} + /** + * Deletes the item at the specified index. + * + * @param index The index of the item to delete. + * + * @exception IllegalArgumentException If the index is not valid + * + * @deprecated + */ + public void delItem(int index) throws IllegalArgumentException + { + boolean selected = false; + if (isSelected(index)) + { + selected = true; + deselect(index); + } + + items.removeElementAt (index); + + if (selected) + select(index); -/*************************************************************************/ + ListPeer peer = (ListPeer) getPeer(); + if (peer != null) + peer.delItems (index, index); + } -/** - * Returns the list of items that are currently selected in this list. - * - * @return The list of currently selected items. - */ -public synchronized String[] -getSelectedItems() -{ - int[] indexes = getSelectedIndexes(); - if (indexes == null) - return(new String[0]); + /** + * Deletes the item at the specified index. + * + * @param index The index of the item to delete. + * + * @exception IllegalArgumentException If the index is not valid + * + * @since 1.1 + */ + public void remove(int index) throws IllegalArgumentException + { + delItem(index); + } - String[] retvals = new String[indexes.length]; - if (retvals.length > 0) - for (int i = 0 ; i < retvals.length; i++) - retvals[i] = (String)items.elementAt(indexes[i]); + /** + * Deletes all items in the specified index range. + * + * @param start The beginning index of the range to delete. + * @param end The ending index of the range to delete. + * + * @exception IllegalArgumentException If the indexes are not valid + * + * @deprecated This method is deprecated for some unknown reason. + */ + public synchronized void delItems(int start, int end) + throws IllegalArgumentException + { + // We must run the loop in reverse direction. + for (int i = end; i >= start; --i) + items.removeElementAt (i); + if (peer != null) + { + ListPeer l = (ListPeer) peer; + l.delItems (start, end); + } + } - return(retvals); -} + /** + * Deletes the first occurrence of the specified item from the list. + * + * @param item The item to delete. + * + * @exception IllegalArgumentException If the specified item does not exist. + * + * @since 1.1 + */ + public synchronized void remove(String item) throws IllegalArgumentException + { + int index = items.indexOf(item); + if (index == -1) + throw new IllegalArgumentException("List element to delete not found"); -/*************************************************************************/ + remove(index); + } -/** - * Returns the list of items that are currently selected in this list as - * an array of type <code>Object[]</code> instead of <code>String[]</code>. - * - * @return The list of currently selected items. - */ -public synchronized Object[] -getSelectedObjects() -{ - int[] indexes = getSelectedIndexes(); - if (indexes == null) - return(new Object[0]); + /** + * Deletes all of the items from the list. + * + * @since 1.1 + */ + public synchronized void removeAll() + { + clear(); + } - Object[] retvals = new Object[indexes.length]; - if (retvals.length > 0) - for (int i = 0 ; i < retvals.length; i++) - retvals[i] = items.elementAt(indexes[i]); + /** + * Deletes all of the items from the list. + * + * @deprecated This method is deprecated in favor of <code>removeAll()</code>. + */ + public void clear() + { + items.clear(); - return(retvals); -} + ListPeer peer = (ListPeer) getPeer(); + if (peer != null) + peer.removeAll(); + + selected = new int[0]; + } -/*************************************************************************/ + /** + * Replaces the item at the specified index with the specified item. + * + * @param item The new item value. + * @param index The index of the item to replace. + * + * @exception ArrayIndexOutOfBoundsException If the index is not valid. + */ + public synchronized void replaceItem(String item, int index) + throws ArrayIndexOutOfBoundsException + { + if ((index < 0) || (index >= items.size())) + throw new ArrayIndexOutOfBoundsException("Bad list index: " + index); -/** - * Tests whether or not the specified index is selected. - * - * @param index The index to test. - * - * @return <code>true</code> if the index is selected, <code>false</code> - * otherwise. - */ -public boolean -isIndexSelected(int index) -{ - return isSelected (index); -} + items.insertElementAt(item, index + 1); + items.removeElementAt (index); -/*************************************************************************/ + if (peer != null) + { + ListPeer l = (ListPeer) peer; -/** - * Tests whether or not the specified index is selected. - * - * @param index The index to test. - * - * @return <code>true</code> if the index is selected, <code>false</code> - * otherwise. - * - * @deprecated This method is deprecated in favor of - * <code>isIndexSelected(int)</code>. - */ -public boolean -isSelected(int index) -{ - int[] indexes = getSelectedIndexes (); + /* We add first and then remove so that the selected + item remains the same */ + l.add (item, index + 1); + l.delItems (index, index); + } + } - for (int i = 0; i < indexes.length; i++) - if (indexes[i] == index) - return true; + /** + * Returns the index of the currently selected item. -1 will be returned + * if there are no selected rows or if there are multiple selected rows. + * + * @return The index of the selected row. + */ + public synchronized int getSelectedIndex() + { + if (peer != null) + { + ListPeer l = (ListPeer) peer; + selected = l.getSelectedIndexes (); + } - return false; -} + if (selected == null || selected.length != 1) + return -1; + + return selected[0]; + } -/*************************************************************************/ + /** + * Returns an array containing the indexes of the rows that are + * currently selected. + * + * @return A list of indexes of selected rows. + */ + public synchronized int[] getSelectedIndexes() + { + if (peer != null) + { + ListPeer l = (ListPeer) peer; + selected = l.getSelectedIndexes(); + } + + return selected; + } -/** - * This method ensures that the item at the specified index is visible. - * - * @param index The index of the item to be made visible. - */ -public synchronized void -makeVisible(int index) throws IllegalArgumentException -{ - visibleIndex = index; - if (peer != null) - { - ListPeer l = (ListPeer) peer; - l.makeVisible (index); - } -} + /** + * Returns the item that is currently selected, or <code>null</code> if there + * is no item selected. FIXME: What happens if multiple items selected? + * + * @return The selected item, or <code>null</code> if there is no + * selected item. + */ + public synchronized String getSelectedItem() + { + int index = getSelectedIndex(); + if (index == -1) + return(null); -/*************************************************************************/ + return((String) items.elementAt(index)); + } -/** - * Returns the index of the last item that was made visible via the - * <code>makeVisible()</code> method. - * - * @return The index of the last item made visible via the - * <code>makeVisible()</code> method. - */ -public int -getVisibleIndex() -{ - return(visibleIndex); -} + /** + * Returns the list of items that are currently selected in this list. + * + * @return The list of currently selected items. + */ + public synchronized String[] getSelectedItems() + { + int[] indexes = getSelectedIndexes(); + if (indexes == null) + return(new String[0]); -/*************************************************************************/ + String[] retvals = new String[indexes.length]; + if (retvals.length > 0) + for (int i = 0 ; i < retvals.length; i++) + retvals[i] = (String)items.elementAt(indexes[i]); -/** - * Makes the item at the specified index selected. - * - * @param index The index of the item to select. - */ -public synchronized void -select(int index) -{ - ListPeer lp = (ListPeer)getPeer(); - if (lp != null) - lp.select(index); -} + return(retvals); + } -/*************************************************************************/ + /** + * Returns the list of items that are currently selected in this list as + * an array of type <code>Object[]</code> instead of <code>String[]</code>. + * + * @return The list of currently selected items. + */ + public synchronized Object[] getSelectedObjects() + { + int[] indexes = getSelectedIndexes(); + if (indexes == null) + return(new Object[0]); -/** - * Makes the item at the specified index not selected. - * - * @param index The index of the item to unselect. - */ -public synchronized void -deselect(int index) -{ - ListPeer lp = (ListPeer)getPeer(); - if (lp != null) - lp.deselect(index); -} + Object[] retvals = new Object[indexes.length]; + if (retvals.length > 0) + for (int i = 0 ; i < retvals.length; i++) + retvals[i] = items.elementAt(indexes[i]); -/*************************************************************************/ + return(retvals); + } -/** - * Notifies this object to create its native peer. - */ -public void -addNotify() -{ - if (peer == null) - peer = getToolkit ().createList (this); - super.addNotify (); -} + /** + * Tests whether or not the specified index is selected. + * + * @param index The index to test. + * + * @return <code>true</code> if the index is selected, <code>false</code> + * otherwise. + * + * @since 1.1 + */ + public boolean isIndexSelected(int index) + { + return isSelected(index); + } -/*************************************************************************/ + /** + * Tests whether or not the specified index is selected. + * + * @param index The index to test. + * + * @return <code>true</code> if the index is selected, <code>false</code> + * otherwise. + * + * @deprecated This method is deprecated in favor of + * <code>isIndexSelected(int)</code>. + */ + public boolean isSelected(int index) + { + int[] indexes = getSelectedIndexes(); -/** - * Notifies this object to destroy its native peer. - */ -public void -removeNotify() -{ - super.removeNotify(); -} + for (int i = 0; i < indexes.length; i++) + if (indexes[i] == index) + return true; -/*************************************************************************/ + return false; + } -/** - * Adds the specified <code>ActionListener</code> to the list of - * registered listeners for this object. - * - * @param listener The listener to add. - */ -public synchronized void -addActionListener(ActionListener listener) -{ - action_listeners = AWTEventMulticaster.add(action_listeners, listener); -} + /** + * This method ensures that the item at the specified index is visible. + * + * @param index The index of the item to be made visible. + */ + public synchronized void makeVisible(int index) + throws IllegalArgumentException + { + visibleIndex = index; + if (peer != null) + { + ListPeer l = (ListPeer) peer; + l.makeVisible (index); + } + } -/*************************************************************************/ + /** + * Returns the index of the last item that was made visible via the + * <code>makeVisible()</code> method. + * + * @return The index of the last item made visible via the + * <code>makeVisible()</code> method. + */ + public int getVisibleIndex() + { + return visibleIndex; + } -/** - * Removes the specified <code>ActionListener</code> from the list of - * registers listeners for this object. - * - * @param listener The listener to remove. - */ -public synchronized void -removeActionListener(ActionListener listener) -{ - action_listeners = AWTEventMulticaster.remove(action_listeners, listener); -} + /** + * Makes the item at the specified index selected. + * + * @param index The index of the item to select. + */ + public synchronized void select(int index) + { + ListPeer lp = (ListPeer) getPeer(); + if (lp != null) + lp.select(index); + + if (selected != null) + { + boolean found = false; + for (int i = 0; i < selected.length; i++) + { + if (selected[i] == index) + found = true; + } + if (! found) + { + if (! isMultipleMode()) + { + selected = new int[] { index }; + return; + } + int[] temp = new int[selected.length + 1]; + System.arraycopy(selected, 0, temp, 0, selected.length); + temp[selected.length] = index; + selected = temp; + } + } + else + { + selected = new int[1]; + selected[0] = index; + } + } -/*************************************************************************/ + /** + * Makes the item at the specified index not selected. + * + * @param index The index of the item to unselect. + */ + public synchronized void deselect(int index) + { + if (isSelected(index)) + { + ListPeer lp = (ListPeer)getPeer(); + if (lp != null) + lp.deselect(index); + + int[] temp = new int[selected.length - 1]; + for (int i = 0; i < temp.length; i++) + { + if (selected[i] != index) + temp[i] = selected[i]; + else + { + System.arraycopy(selected, i + 1, temp, i, + selected.length - i - 1); + break; + } + } + selected = temp; + } + } -/** - * Adds the specified <code>ItemListener</code> to the list of - * registered listeners for this object. - * - * @param listener The listener to add. - */ -public synchronized void -addItemListener(ItemListener listener) -{ - item_listeners = AWTEventMulticaster.add(item_listeners, listener); -} + /** + * Notifies this object to create its native peer. + */ + public void addNotify() + { + if (peer == null) + peer = getToolkit ().createList(this); + super.addNotify (); + } -/*************************************************************************/ + /** + * Notifies this object to destroy its native peer. + */ + public void removeNotify() + { + super.removeNotify(); + } -/** - * Removes the specified <code>ItemListener</code> from the list of - * registers listeners for this object. - * - * @param listener The listener to remove. - */ -public synchronized void -removeItemListener(ItemListener listener) -{ - item_listeners = AWTEventMulticaster.remove(item_listeners, listener); -} + /** + * Adds the specified <code>ActionListener</code> to the list of + * registered listeners for this object. + * + * @param listener The listener to add. + * + * @since 1.1 + */ + public synchronized void addActionListener(ActionListener listener) + { + action_listeners = AWTEventMulticaster.add(action_listeners, listener); + } -/*************************************************************************/ + /** + * Removes the specified <code>ActionListener</code> from the list of + * registers listeners for this object. + * + * @param listener The listener to remove. + * + * @since 1.1 + */ + public synchronized void removeActionListener(ActionListener listener) + { + action_listeners = AWTEventMulticaster.remove(action_listeners, listener); + } -/** - * Processes the specified event for this object. If the event is an - * instance of <code>ActionEvent</code> then the - * <code>processActionEvent()</code> method is called. Similarly, if the - * even is an instance of <code>ItemEvent</code> then the - * <code>processItemEvent()</code> method is called. Otherwise the - * superclass method is called to process this event. - * - * @param event The event to process. - */ -protected void -processEvent(AWTEvent event) -{ - if (event instanceof ActionEvent) - processActionEvent((ActionEvent)event); - else if (event instanceof ItemEvent) - processItemEvent((ItemEvent)event); - else - super.processEvent(event); -} + /** + * Adds the specified <code>ItemListener</code> to the list of + * registered listeners for this object. + * + * @param listener The listener to add. + * + * @since 1.1 + */ + public synchronized void addItemListener(ItemListener listener) + { + item_listeners = AWTEventMulticaster.add(item_listeners, listener); + } -/*************************************************************************/ + /** + * Removes the specified <code>ItemListener</code> from the list of + * registers listeners for this object. + * + * @param listener The listener to remove. + * + * @since 1.1 + */ + public synchronized void removeItemListener(ItemListener listener) + { + item_listeners = AWTEventMulticaster.remove(item_listeners, listener); + } -/** - * This method processes the specified event by dispatching it to any - * registered listeners. Note that this method will only get called if - * action events are enabled. This will happen automatically if any - * listeners are added, or it can be done "manually" by calling - * the <code>enableEvents()</code> method. - * - * @param event The event to process. - */ -protected void -processActionEvent(ActionEvent event) -{ - if (action_listeners != null) - action_listeners.actionPerformed(event); -} + /** + * Processes the specified event for this object. If the event is an + * instance of <code>ActionEvent</code> then the + * <code>processActionEvent()</code> method is called. Similarly, if the + * even is an instance of <code>ItemEvent</code> then the + * <code>processItemEvent()</code> method is called. Otherwise the + * superclass method is called to process this event. + * + * @param event The event to process. + * + * @since 1.1 + */ + protected void processEvent(AWTEvent event) + { + if (event instanceof ActionEvent) + processActionEvent((ActionEvent)event); + else if (event instanceof ItemEvent) + processItemEvent((ItemEvent)event); + else + super.processEvent(event); + } -/*************************************************************************/ + /** + * This method processes the specified event by dispatching it to any + * registered listeners. Note that this method will only get called if + * action events are enabled. This will happen automatically if any + * listeners are added, or it can be done "manually" by calling + * the <code>enableEvents()</code> method. + * + * @param event The event to process. + * + * @since 1.1 + */ + protected void processActionEvent(ActionEvent event) + { + if (action_listeners != null) + action_listeners.actionPerformed(event); + } -/** - * This method processes the specified event by dispatching it to any - * registered listeners. Note that this method will only get called if - * item events are enabled. This will happen automatically if any - * listeners are added, or it can be done "manually" by calling - * the <code>enableEvents()</code> method. - * - * @param event The event to process. - */ -protected void -processItemEvent(ItemEvent event) -{ - if (item_listeners != null) - item_listeners.itemStateChanged(event); -} + /** + * This method processes the specified event by dispatching it to any + * registered listeners. Note that this method will only get called if + * item events are enabled. This will happen automatically if any + * listeners are added, or it can be done "manually" by calling + * the <code>enableEvents()</code> method. + * + * @param event The event to process. + * + * @since 1.1 + */ + protected void processItemEvent(ItemEvent event) + { + if (item_listeners != null) + item_listeners.itemStateChanged(event); + } -void -dispatchEventImpl(AWTEvent e) -{ - if (e.id <= ItemEvent.ITEM_LAST - && e.id >= ItemEvent.ITEM_FIRST - && (item_listeners != null - || (eventMask & AWTEvent.ITEM_EVENT_MASK) != 0)) - processEvent(e); - else if (e.id <= ActionEvent.ACTION_LAST + void dispatchEventImpl(AWTEvent e) + { + if (e.id <= ItemEvent.ITEM_LAST + && e.id >= ItemEvent.ITEM_FIRST + && (item_listeners != null + || (eventMask & AWTEvent.ITEM_EVENT_MASK) != 0)) + processEvent(e); + else if (e.id <= ActionEvent.ACTION_LAST && e.id >= ActionEvent.ACTION_FIRST && (action_listeners != null - || (eventMask & AWTEvent.ACTION_EVENT_MASK) != 0)) - processEvent(e); - else - super.dispatchEventImpl(e); -} - -/*************************************************************************/ + || (eventMask & AWTEvent.ACTION_EVENT_MASK) != 0)) + processEvent(e); + else + super.dispatchEventImpl(e); + } -/** - * Returns a debugging string for this object. - * - * @return A debugging string for this object. - */ -protected String -paramString() -{ - return "multiple=" + multipleMode + ",rows=" + rows + super.paramString(); -} + /** + * Returns a debugging string for this object. + * + * @return A debugging string for this object. + */ + protected String paramString() + { + return "multiple=" + multipleMode + ",rows=" + rows + super.paramString(); + } /** * Returns an array of all the objects currently registered as FooListeners @@ -1061,12 +988,14 @@ paramString() * * @exception ClassCastException If listenerType doesn't specify a class or * interface that implements java.util.EventListener. + * + * @since 1.3 */ - public EventListener[] getListeners (Class listenerType) + public <T extends EventListener> T[] getListeners (Class<T> listenerType) { if (listenerType == ActionListener.class) return AWTEventMulticaster.getListeners (action_listeners, listenerType); - + if (listenerType == ItemListener.class) return AWTEventMulticaster.getListeners (item_listeners, listenerType); @@ -1075,6 +1004,8 @@ paramString() /** * Returns all action listeners registered to this object. + * + * @since 1.4 */ public ActionListener[] getActionListeners () { @@ -1083,6 +1014,8 @@ paramString() /** * Returns all action listeners registered to this object. + * + * @since 1.4 */ public ItemListener[] getItemListeners () { diff --git a/libjava/classpath/java/awt/Menu.java b/libjava/classpath/java/awt/Menu.java index f900d929574..cef04a38e1a 100644 --- a/libjava/classpath/java/awt/Menu.java +++ b/libjava/classpath/java/awt/Menu.java @@ -54,38 +54,28 @@ import javax.accessibility.AccessibleRole; public class Menu extends MenuItem implements MenuContainer, Serializable { -/* - * Static Variables - */ - -/** - * The number used to generate the name returned by getName. - */ -private static transient long next_menu_number; - -// Serialization Constant -private static final long serialVersionUID = -8809584163345499784L; - -/*************************************************************************/ + /** + * The number used to generate the name returned by getName. + */ + private static transient long next_menu_number; -/* - * Instance Variables - */ + // Serialization Constant + private static final long serialVersionUID = -8809584163345499784L; -/** - * @serial The actual items in the menu - */ -private Vector items = new Vector(); + /** + * @serial The actual items in the menu + */ + private Vector items = new Vector(); -/** - * @serial Flag indicating whether or not this menu is a tear off - */ -private boolean tearOff; + /** + * @serial Flag indicating whether or not this menu is a tear off + */ + private boolean tearOff; -/** - * @serial Indicates whether or not this is a help menu. - */ -private boolean isHelpMenu; + /** + * @serial Indicates whether or not this is a help menu. + */ + private boolean isHelpMenu; /* * @serial Unused in this implementation, but present in Sun's @@ -93,371 +83,316 @@ private boolean isHelpMenu; */ private int menuSerializedDataVersion = 1; -static final transient String separatorLabel = "-"; - -/*************************************************************************/ - -/* - * Constructors - */ - -/** - * Initializes a new instance of <code>Menu</code> with no label and that - * is not a tearoff; - * - * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. - */ -public -Menu() -{ -} - -/*************************************************************************/ - -/** - * Initializes a new instance of <code>Menu</code> that is not a tearoff and - * that has the specified label. - * - * @param label The menu label. - * - * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. - */ -public -Menu(String label) -{ - this(label, false); -} - -/*************************************************************************/ - -/** - * Initializes a new instance of <code>Menu</code> with the specified - * label and tearoff status. - * - * @param label The label for this menu - * @param isTearOff <code>true</code> if this menu is a tear off menu, - * <code>false</code> otherwise. - * - * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. - */ -public -Menu(String label, boolean isTearOff) -{ - super(label); + static final transient String separatorLabel = "-"; - tearOff = isTearOff; + /** + * Initializes a new instance of <code>Menu</code> with no label and that + * is not a tearoff; + * + * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. + */ + public Menu() + { + } - if (label.equals("Help")) - isHelpMenu = true; + /** + * Initializes a new instance of <code>Menu</code> that is not a tearoff and + * that has the specified label. + * + * @param label The menu label. + * + * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. + */ + public Menu(String label) + { + this(label, false); + } - if (GraphicsEnvironment.isHeadless()) - throw new HeadlessException (); -} + /** + * Initializes a new instance of <code>Menu</code> with the specified + * label and tearoff status. + * + * @param label The label for this menu + * @param isTearOff <code>true</code> if this menu is a tear off menu, + * <code>false</code> otherwise. + * + * @exception HeadlessException If GraphicsEnvironment.isHeadless() is true. + */ + public Menu(String label, boolean isTearOff) + { + super(label); -/*************************************************************************/ + tearOff = isTearOff; -/* - * Instance Methods - */ + if (label.equals("Help")) + isHelpMenu = true; -/** - * Tests whether or not this menu is a tearoff. - * - * @return <code>true</code> if this menu is a tearoff, <code>false</code> - * otherwise. - */ -public boolean -isTearOff() -{ - return(tearOff); -} + if (GraphicsEnvironment.isHeadless()) + throw new HeadlessException(); + } -/*************************************************************************/ + /** + * Tests whether or not this menu is a tearoff. + * + * @return <code>true</code> if this menu is a tearoff, <code>false</code> + * otherwise. + */ + public boolean isTearOff() + { + return(tearOff); + } -/** - * Returns the number of items in this menu. - * - * @return The number of items in this menu. - */ -public int -getItemCount() -{ - return countItems (); -} + /** + * Returns the number of items in this menu. + * + * @return The number of items in this menu. + */ + public int getItemCount() + { + return countItems(); + } -/** - * Returns the number of items in this menu. - * - * @return The number of items in this menu. - * - * @deprecated As of JDK 1.1, replaced by getItemCount(). - */ -public int countItems () -{ - return items.size (); -} + /** + * Returns the number of items in this menu. + * + * @return The number of items in this menu. + * + * @deprecated As of JDK 1.1, replaced by getItemCount(). + */ + public int countItems() + { + return items.size(); + } -/*************************************************************************/ - -/** - * Returns the item at the specified index. - * - * @return The item at the specified index. - * - * @exception ArrayIndexOutOfBoundsException If the index value is not valid. - */ -public MenuItem -getItem(int index) -{ - return((MenuItem)items.elementAt(index)); -} - -/*************************************************************************/ - -/** - * Adds the specified item to this menu. If it was previously part of - * another menu, it is first removed from that menu. - * - * @param item The new item to add. - * - * @return The item that was added. - */ -public MenuItem -add(MenuItem item) -{ - MenuContainer parent = item.getParent(); - if (parent != null) - parent.remove(item); - - items.addElement(item); - item.setParent(this); + /** + * Returns the item at the specified index. + * + * @param index the item index. + * + * @return The item at the specified index. + * + * @exception ArrayIndexOutOfBoundsException If the index value is not valid. + */ + public MenuItem getItem(int index) + { + return((MenuItem) items.elementAt(index)); + } - if (peer != null) - { - item.addNotify(); - MenuPeer mp = (MenuPeer) peer; - mp.addItem(item); - } + /** + * Adds the specified item to this menu. If it was previously part of + * another menu, it is first removed from that menu. + * + * @param item The new item to add. + * + * @return The item that was added. + */ + public MenuItem add(MenuItem item) + { + MenuContainer parent = item.getParent(); + if (parent != null) + parent.remove(item); - return item; -} + items.addElement(item); + item.setParent(this); -/*************************************************************************/ + if (peer != null) + { + item.addNotify(); + MenuPeer mp = (MenuPeer) peer; + mp.addItem(item); + } -/** - * Add an item with the specified label to this menu. - * - * @param label The label of the menu item to add. - */ -public void -add(String label) -{ - add(new MenuItem(label)); -} - -/*************************************************************************/ - -/** - * Inserts the specified menu item into this menu at the specified index. - * - * @param item The menu item to add. - * @param index The index of the menu item. - * - * @exception IllegalArgumentException If the index is less than zero. - * @exception ArrayIndexOutOfBoundsException If the index is otherwise invalid. - */ -public void -insert(MenuItem item, int index) -{ - if (index < 0) - throw new IllegalArgumentException("Index is less than zero"); + return item; + } - int count = getItemCount (); + /** + * Add an item with the specified label to this menu. + * + * @param label The label of the menu item to add. + */ + public void add(String label) + { + add(new MenuItem(label)); + } - if (index >= count) - add(item); - else - { - MenuContainer parent = item.getParent(); - if (parent != null) - parent.remove(item); + /** + * Inserts the specified menu item into this menu at the specified index. If + * the index is greater than or equal to the number of items already in the + * menu, the new item is added as the last item in the menu. + * + * @param item The menu item to add (<code>null</code> not permitted). + * @param index The index of the menu item (>= 0). + * + * @throws IllegalArgumentException if the index is less than zero. + * @throws NullPointerException if <code>item</code> is <code>null</code>. + */ + public void insert(MenuItem item, int index) + { + if (index < 0) + throw new IllegalArgumentException("Index is less than zero"); + + int count = getItemCount(); + + if (index >= count) + add(item); + else + { + MenuContainer parent = item.getParent(); + if (parent != null) + parent.remove(item); - items.insertElementAt(item, index); - item.setParent(this); - - MenuPeer peer = (MenuPeer) getPeer(); - if (peer == null) - return; + items.insertElementAt(item, index); + item.setParent(this); - for (int i = count - 1; i >= index; i--) - peer.delItem(i); + MenuPeer peer = (MenuPeer) getPeer(); + if (peer == null) + return; - item.addNotify(); - peer.addItem(item); + for (int i = count - 1; i >= index; i--) + peer.delItem(i); - for (int i = index; i < count; i++) - peer.addItem((MenuItem) items.elementAt (i)); - } - -} + item.addNotify(); + peer.addItem(item); -/*************************************************************************/ + // bear in mind that count is the number of items *before* the new + // item was added + for (int i = index + 1; i <= count; i++) + peer.addItem((MenuItem) items.elementAt(i)); + } -/** - * Inserts an item with the specified label into this menu at the specified index. - * - * @param label The label of the item to add. - * @param index The index of the menu item. - * - * @exception IllegalArgumentException If the index is less than zero. - * @exception ArrayIndexOutOfBoundsException If the index is otherwise invalid. - */ -public void -insert(String label, int index) -{ - insert(new MenuItem(label), index); -} - -/*************************************************************************/ - -/** - * Adds a separator bar at the current menu location. - */ -public void -addSeparator() -{ - add(new MenuItem(separatorLabel)); -} - -/*************************************************************************/ - -/** - * Inserts a separator bar at the specified index value. - * - * @param index The index at which to insert a separator bar. - * - * @exception IllegalArgumentException If the index is less than zero. - * @exception ArrayIndexOutOfBoundsException If the index is otherwise invalid. - */ -public void -insertSeparator(int index) -{ - insert(new MenuItem(separatorLabel), index); -} + } -/*************************************************************************/ + /** + * Inserts an item with the specified label into this menu at the specified + * index. If the index is greater than or equal to the number of items + * already in the menu, the new item is added as the last item in the menu. + * + * @param label The label of the item to add. + * @param index The index of the menu item (>= 0). + * + * @throws IllegalArgumentException If the index is less than zero. + */ + public void insert(String label, int index) + { + insert(new MenuItem(label), index); + } -/** - * Deletes the item at the specified index from this menu. - * - * @param index The index of the item to remove. - * - * @exception ArrayIndexOutOfBoundsException If the index is otherwise invalid. - */ -public synchronized void -remove(int index) -{ - MenuItem item = (MenuItem) items.remove(index); + /** + * Adds a separator bar at the current menu location. + */ + public void addSeparator() + { + add(new MenuItem(separatorLabel)); + } - MenuPeer mp = (MenuPeer) getPeer(); - if (mp != null) - { - mp.delItem(index); - item.removeNotify(); - } - item.setParent(null); -} + /** + * Inserts a separator bar at the specified index value. + * + * @param index The index at which to insert a separator bar. + * + * @exception IllegalArgumentException If the index is less than zero. + * @exception ArrayIndexOutOfBoundsException If the index is otherwise invalid. + */ + public void insertSeparator(int index) + { + insert(new MenuItem(separatorLabel), index); + } -/*************************************************************************/ + /** + * Deletes the item at the specified index from this menu. + * + * @param index The index of the item to remove. + * + * @exception ArrayIndexOutOfBoundsException If the index is otherwise invalid. + */ + public synchronized void remove(int index) + { + MenuItem item = (MenuItem) items.remove(index); + + MenuPeer mp = (MenuPeer) getPeer(); + if (mp != null) + { + mp.delItem(index); + item.removeNotify(); + } + item.setParent(null); + } -/** - * Removes the specifed item from the menu. If the specified component - * does not exist, this method does nothing. - * - * @param item The component to remove. - */ -public void -remove(MenuComponent item) -{ - int index = items.indexOf(item); - if (index == -1) - return; + /** + * Removes the specifed item from the menu. If the specified component + * does not exist, this method does nothing. + * + * @param item The component to remove. + */ + public void remove(MenuComponent item) + { + int index = items.indexOf(item); + if (index == -1) + return; - remove(index); -} + remove(index); + } -/*************************************************************************/ + /** + * Removes all the elements from this menu. + */ + public synchronized void removeAll() + { + int count = getItemCount(); + for(int i = 0; i < count; i++) + { + // We must always remove item 0. + remove(0); + } + } -/** - * Removes all the elements from this menu. - */ -public synchronized void -removeAll() -{ - int count = getItemCount(); - for(int i = 0; i < count; i++) + /** + * Creates the native peer for this object. + */ + public void addNotify() + { + MenuPeer peer = (MenuPeer) getPeer(); + if (peer == null) + { + peer = getToolkit().createMenu(this); + setPeer(peer); + } + + Enumeration e = items.elements(); + while (e.hasMoreElements()) { - // We must always remove item 0. - remove(0); + MenuItem mi = (MenuItem)e.nextElement(); + mi.addNotify(); + peer.addItem(mi); } -} -/*************************************************************************/ + super.addNotify(); + } -/** - * Creates the native peer for this object. - */ -public void -addNotify() -{ - MenuPeer peer = (MenuPeer) getPeer(); - if (peer == null) + /** + * Destroys the native peer for this object. + */ + public void removeNotify() + { + Enumeration e = items.elements(); + while (e.hasMoreElements()) { - peer = getToolkit().createMenu(this); - setPeer(peer); + MenuItem mi = (MenuItem) e.nextElement(); + mi.removeNotify(); } - - Enumeration e = items.elements(); - while (e.hasMoreElements()) - { - MenuItem mi = (MenuItem)e.nextElement(); - mi.addNotify(); - peer.addItem(mi); + super.removeNotify(); } - super.addNotify (); -} - -/*************************************************************************/ - -/** - * Destroys the native peer for this object. - */ -public void -removeNotify() -{ - Enumeration e = items.elements(); - while (e.hasMoreElements()) + /** + * Returns a debugging string for this menu. + * + * @return A debugging string for this menu. + */ + public String paramString() { - MenuItem mi = (MenuItem) e.nextElement(); - mi.removeNotify(); + return (",tearOff=" + tearOff + ",isHelpMenu=" + isHelpMenu + + super.paramString()); } - super.removeNotify(); -} - -/*************************************************************************/ - -/** - * Returns a debugging string for this menu. - * - * @return A debugging string for this menu. - */ -public String -paramString() -{ - return (",tearOff=" + tearOff + ",isHelpMenu=" + isHelpMenu - + super.paramString()); -} /** * Basic Accessibility class for Menu. Details get provided in derived diff --git a/libjava/classpath/java/awt/MenuBar.java b/libjava/classpath/java/awt/MenuBar.java index bd658cde6e3..6737d0419f2 100644 --- a/libjava/classpath/java/awt/MenuBar.java +++ b/libjava/classpath/java/awt/MenuBar.java @@ -272,7 +272,7 @@ public class MenuBar extends MenuComponent * * @return a list of all shortcuts for the menus in this menu bar */ - public synchronized Enumeration shortcuts() + public synchronized Enumeration<MenuShortcut> shortcuts() { Vector shortcuts = new Vector(); Enumeration e = menus.elements(); diff --git a/libjava/classpath/java/awt/MenuItem.java b/libjava/classpath/java/awt/MenuItem.java index 7cbc9219f54..b80ee4c1a5e 100644 --- a/libjava/classpath/java/awt/MenuItem.java +++ b/libjava/classpath/java/awt/MenuItem.java @@ -523,11 +523,11 @@ removeActionListener(ActionListener l) * ClassClassException is thrown. * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == ActionListener.class) - return getActionListeners(); - return (EventListener[]) Array.newInstance(listenerType, 0); + return (T[]) getActionListeners(); + return (T[]) Array.newInstance(listenerType, 0); } /*************************************************************************/ diff --git a/libjava/classpath/java/awt/MenuShortcut.java b/libjava/classpath/java/awt/MenuShortcut.java index adfd1d3187a..259cbf1ae98 100644 --- a/libjava/classpath/java/awt/MenuShortcut.java +++ b/libjava/classpath/java/awt/MenuShortcut.java @@ -38,6 +38,8 @@ exception statement from your version. */ package java.awt; +import java.awt.event.KeyEvent; + /** * This class implements a keyboard accelerator for a menu item. * @@ -70,6 +72,8 @@ private int key; */ private boolean usesShift; +private String keyName; + /*************************************************************************/ /** @@ -99,6 +103,7 @@ MenuShortcut(int key, boolean usesShift) { this.key = key; this.usesShift = usesShift; + setKeyName(key); } /*************************************************************************/ @@ -181,7 +186,11 @@ equals(Object obj) public String toString() { - return(getClass().getName() + "[" + paramString () + "]"); + String temp = "Ctrl+"; + if (usesShift) + temp = temp + "Shift+"; + temp = temp + keyName; + return temp; } public int @@ -204,4 +213,224 @@ paramString() return "key=" + key + ",usesShift=" + usesShift; } -} // class MenuShortcut +private void +setKeyName(int key) +{ + if (key == '\n') + keyName = "Enter"; + else if (key == '\b') + keyName = "Backspace"; + else if (key == '\t') + keyName = "Tab"; + else if (key == ' ') + keyName = "Space"; + else if (key == ',') + keyName = "Comma"; + else if (key == '.') + keyName = "Period"; + else if (key == '/') + keyName = "Slash"; + else if (key == '\\') + keyName = "Back Slash"; + else if (key == ';') + keyName = "Semicolon"; + else if (key == '=') + keyName = "Equals"; + else if (key == '[') + keyName = "Open Bracket"; + else if (key == ']') + keyName = "Close Bracket"; + else if (key == '0') + keyName = "0"; + else if (key == '1') + keyName = "1"; + else if (key == '2') + keyName = "2"; + else if (key == '3') + keyName = "3"; + else if (key == '4') + keyName = "4"; + else if (key == '5') + keyName = "5"; + else if (key == '6') + keyName = "6"; + else if (key == '7') + keyName = "7"; + else if (key == '8') + keyName = "8"; + else if (key == '9') + keyName = "9"; + else if (key == 'A') + keyName = "A"; + else if (key == 'B') + keyName = "B"; + else if (key == 'C') + keyName = "C"; + else if (key == 'D') + keyName = "D"; + else if (key == 'E') + keyName = "E"; + else if (key == 'F') + keyName = "F"; + else if (key == 'G') + keyName = "G"; + else if (key == 'H') + keyName = "H"; + else if (key == 'I') + keyName = "I"; + else if (key == 'J') + keyName = "J"; + else if (key == 'K') + keyName = "K"; + else if (key == 'L') + keyName = "L"; + else if (key == 'M') + keyName = "M"; + else if (key == 'N') + keyName = "N"; + else if (key == 'O') + keyName = "O"; + else if (key == 'P') + keyName = "P"; + else if (key == 'Q') + keyName = "Q"; + else if (key == 'R') + keyName = "R"; + else if (key == 'S') + keyName = "S"; + else if (key == 'T') + keyName = "T"; + else if (key == 'U') + keyName = "U"; + else if (key == 'V') + keyName = "V"; + else if (key == 'W') + keyName = "W"; + else if (key == 'X') + keyName = "X"; + else if (key == 'Y') + keyName = "Y"; + else if (key == 'Z') + keyName = "Z"; + else if (key == 3) + keyName = "Cancel"; + else if (key == 12) + keyName = "Clear"; + else if (key == 16) + keyName = "Shift"; + else if (key == 17) + keyName = "Ctrl"; + else if (key == 18) + keyName = "Alt"; + else if (key == 19) + keyName = "Pause"; + else if (key == 20) + keyName = "Caps Lock"; + else if (key == 21) + keyName = "Kana"; + else if (key == 24) + keyName = "Final"; + else if (key == 25) + keyName = "Kanji"; + else if (key == 27) + keyName = "Escape"; + else if (key == 28) + keyName = "Convert"; + else if (key == 29) + keyName = "No Convert"; + else if (key == 30) + keyName = "Accept"; + else if (key == 31) + keyName = "Mode Change"; + else if (key == 33) + keyName = "Page Up"; + else if (key == 34) + keyName = "Page Down"; + else if (key == 35) + keyName = "End"; + else if (key == 36) + keyName = "Home"; + else if (key == 37) + keyName = "Left"; + else if (key == 38) + keyName = "Up"; + else if (key == 39) + keyName = "Right"; + else if (key == 40) + keyName = "Down"; + else if (key == 96) + keyName = "NumPad-0"; + else if (key == 97) + keyName = "NumPad-1"; + else if (key == 98) + keyName = "NumPad-2"; + else if (key == 99) + keyName = "NumPad-3"; + else if (key == 100) + keyName = "NumPad-4"; + else if (key == 101) + keyName = "NumPad-5"; + else if (key == 102) + keyName = "NumPad-6"; + else if (key == 103) + keyName = "NumPad-7"; + else if (key == 104) + keyName = "NumPad-8"; + else if (key == 105) + keyName = "NumPad-9"; + else if (key == 106) + keyName = "NumPad *"; + else if (key == 107) + keyName = "NumPad +"; + else if (key == 108) + keyName = "NumPad ,"; + else if (key == 109) + keyName = "NumPad -"; + else if (key == 110) + keyName = "NumPad ."; + else if (key == 111) + keyName = "NumPad /"; + else if (key == 112) + keyName = "F1"; + else if (key == 113) + keyName = "F2"; + else if (key == 114) + keyName = "F3"; + else if (key == 115) + keyName = "F4"; + else if (key == 116) + keyName = "F5"; + else if (key == 117) + keyName = "F6"; + else if (key == 118) + keyName = "F7"; + else if (key == 119) + keyName = "F8"; + else if (key == 120) + keyName = "F9"; + else if (key == 121) + keyName = "F10"; + else if (key == 122) + keyName = "F11"; + else if (key == 123) + keyName = "F12"; + else if (key == 127) + keyName = "Delete"; + else if (key == 144) + keyName = "Num Lock"; + else if (key == 145) + keyName = "Scroll Lock"; + else if (key == 154) + keyName = "Print Screen"; + else if (key == 155) + keyName = "Insert"; + else if (key == 156) + keyName = "Help"; + else if (key == 157) + keyName = "Meta"; + else if (key == 192) + keyName = "Back Quote"; + else if (key == 222) + keyName = "Quote"; +} +} // class MenuShortcut diff --git a/libjava/classpath/java/awt/Rectangle.java b/libjava/classpath/java/awt/Rectangle.java index c4ba6ba1488..ac2494ee078 100644 --- a/libjava/classpath/java/awt/Rectangle.java +++ b/libjava/classpath/java/awt/Rectangle.java @@ -1,5 +1,5 @@ /* Rectangle.java -- represents a graphics rectangle - Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation + Copyright (C) 1999, 2000, 2001, 2002, 2006, Free Software Foundation This file is part of GNU Classpath. @@ -119,7 +119,6 @@ public class Rectangle extends Rectangle2D implements Shape, Serializable * coordinates of the specified rectangle. * * @param r the rectangle to copy from - * @throws NullPointerException if r is null * @since 1.1 */ public Rectangle(Rectangle r) @@ -168,7 +167,6 @@ public class Rectangle extends Rectangle2D implements Shape, Serializable * * @param p the upper left corner of the rectangle * @param d the width and height of the rectangle - * @throws NullPointerException if p or d is null */ public Rectangle(Point p, Dimension d) { @@ -185,7 +183,7 @@ public class Rectangle extends Rectangle2D implements Shape, Serializable * @param p the upper left corner of the rectangle */ public Rectangle(Point p) - { + { x = p.x; y = p.y; } @@ -198,7 +196,7 @@ public class Rectangle extends Rectangle2D implements Shape, Serializable * @param d the width and height of the rectangle */ public Rectangle(Dimension d) - { + { width = d.width; height = d.height; } @@ -299,8 +297,10 @@ public class Rectangle extends Rectangle2D implements Shape, Serializable } /** - * Updates this rectangle to have the specified dimensions, as rounded to - * integers. + * Updates this rectangle to have the specified dimensions, rounded to the + * integer precision used by this class (the values are rounded "outwards" so + * that the stored rectangle completely encloses the specified double + * precision rectangle). * * @param x the new X coordinate of the upper left hand corner * @param y the new Y coordinate of the upper left hand corner @@ -310,10 +310,10 @@ public class Rectangle extends Rectangle2D implements Shape, Serializable */ public void setRect(double x, double y, double width, double height) { - this.x = (int) x; - this.y = (int) y; - this.width = (int) width; - this.height = (int) height; + this.x = (int) Math.floor(x); + this.y = (int) Math.floor(y); + this.width = (int) Math.ceil(x + width) - this.x; + this.height = (int) Math.ceil(y + height) - this.y; } /** diff --git a/libjava/classpath/java/awt/RenderingHints.java b/libjava/classpath/java/awt/RenderingHints.java index 0e1db72b75f..ce327e36947 100644 --- a/libjava/classpath/java/awt/RenderingHints.java +++ b/libjava/classpath/java/awt/RenderingHints.java @@ -54,7 +54,8 @@ import java.util.Set; * @author Rolf W. Rasmussen (rolfwr@ii.uib.no) * @author Eric Blake (ebb9@email.byu.edu) */ -public class RenderingHints implements Map, Cloneable +public class RenderingHints + implements Map<Object,Object>, Cloneable { /** * The base class used to represent keys. @@ -550,7 +551,7 @@ public class RenderingHints implements Map, Cloneable * @param init a map containing a collection of hints (<code>null</code> * permitted). */ - public RenderingHints(Map init) + public RenderingHints(Map<Key,?> init) { if (init != null) putAll(init); @@ -704,7 +705,7 @@ public class RenderingHints implements Map, Cloneable * @throws IllegalArgumentException if the map contains a value that is * not compatible with its key. */ - public void putAll(Map m) + public void putAll(Map<?,?> m) { // preprocess map to generate appropriate exceptions Iterator iterator = m.keySet().iterator(); @@ -723,7 +724,7 @@ public class RenderingHints implements Map, Cloneable * * @return A set of keys. */ - public Set keySet() + public Set<Object> keySet() { return hintMap.keySet(); } @@ -735,7 +736,7 @@ public class RenderingHints implements Map, Cloneable * * @return A collection of values. */ - public Collection values() + public Collection<Object> values() { return hintMap.values(); } @@ -745,7 +746,7 @@ public class RenderingHints implements Map, Cloneable * * @return A set of entries. */ - public Set entrySet() + public Set<Map.Entry<Object,Object>> entrySet() { return Collections.unmodifiableSet(hintMap.entrySet()); } diff --git a/libjava/classpath/java/awt/ScrollPane.java b/libjava/classpath/java/awt/ScrollPane.java index 65ce484b88d..ec9746f9339 100644 --- a/libjava/classpath/java/awt/ScrollPane.java +++ b/libjava/classpath/java/awt/ScrollPane.java @@ -338,10 +338,15 @@ getVScrollbarWidth() * Returns the current scroll position of the viewport. * * @return The current scroll position of the viewport. + * + * @throws NullPointerException if the scrollpane does have a child. */ public Point getScrollPosition() { + if (getComponentCount() == 0) + throw new NullPointerException(); + int x = 0; int y = 0; @@ -380,20 +385,35 @@ setScrollPosition(Point scrollPosition) throws IllegalArgumentException * @param x The new X coordinate of the scroll position. * @param y The new Y coordinate of the scroll position. * + * @throws NullPointerException if scrollpane does not have a child. + * * @exception IllegalArgumentException If the specified value is outside * the legal scrolling range. */ public void setScrollPosition(int x, int y) { + if (getComponentCount() == 0) + throw new NullPointerException("child is null"); + + if (x > (int) (getComponent(0).getWidth() - getViewportSize().getWidth())) + x = (int) (getComponent(0).getWidth() - getViewportSize().getWidth()); + if (y > (int) (getComponent(0).getHeight() - getViewportSize().getHeight())) + y = (int) (getComponent(0).getHeight() - getViewportSize().getHeight()); + + if (x < 0) + x = 0; + if (y < 0) + y = 0; + Adjustable h = getHAdjustable(); Adjustable v = getVAdjustable(); - + if (h != null) h.setValue(x); if (v != null) v.setValue(y); - + ScrollPanePeer spp = (ScrollPanePeer)getPeer(); if (spp != null) spp.setScrollPosition(x, y); @@ -414,7 +434,7 @@ addNotify() super.addNotify(); Component[] list = getComponents(); - if (list != null && list.length > 0 && ! (list[0] instanceof Panel)) + if (list != null && list.length > 0 && list[0].isLightweight()) { Panel panel = new Panel(); panel.setLayout(new BorderLayout()); @@ -453,9 +473,7 @@ removeNotify() if ((list != null) && (list.length > 0)) remove(list[0]); - super.addImpl(component, constraints, -1); - - doLayout(); + super.addImpl(component, constraints, index); } /*************************************************************************/ @@ -507,6 +525,8 @@ layout() p.y = dim.height; setScrollPosition (p); + + list[0].setLocation(new Point()); } } @@ -518,11 +538,12 @@ layout() * not have layout managers. * * @param layoutManager Ignored + * @throws AWTError Always throws this error when called. */ public final void setLayout(LayoutManager layoutManager) { - return; + throw new AWTError("ScrollPane controls layout"); } /*************************************************************************/ @@ -553,16 +574,37 @@ paramString() + getX() + "," + getY() + "," + getWidth() + "x" + getHeight() + "," - + "ScrollPosition=(" + scrollPosition.getX() + "," - + scrollPosition.getY() + ")," + + getIsValidString() + "," + + "ScrollPosition=(" + scrollPosition.x + "," + + scrollPosition.y + ")," + "Insets=(" + insets.top + "," + insets.left + "," + insets.bottom + "," + insets.right + ")," - + "ScrollbarDisplayPolicy=" + getScrollbarDisplayPolicy() + "," + + "ScrollbarDisplayPolicy=" + getScrollbarDisplayPolicyString() + "," + "wheelScrollingEnabled=" + isWheelScrollingEnabled(); } +private String +getScrollbarDisplayPolicyString() +{ + if (getScrollbarDisplayPolicy() == 0) + return "as-needed"; + else if (getScrollbarDisplayPolicy() == 1) + return "always"; + else + return "never"; +} + +private String +getIsValidString() +{ + if (isValid()) + return "valid"; + else + return "invalid"; +} + /** * Tells whether or not an event is enabled. * diff --git a/libjava/classpath/java/awt/ScrollPaneAdjustable.java b/libjava/classpath/java/awt/ScrollPaneAdjustable.java index 21b58c36ede..ca61801bf51 100644 --- a/libjava/classpath/java/awt/ScrollPaneAdjustable.java +++ b/libjava/classpath/java/awt/ScrollPaneAdjustable.java @@ -145,14 +145,26 @@ public class ScrollPaneAdjustable this.blockIncrement = blockIncrement; } - public void setMaximum (int maximum) + /** + * This method should never be called. + * + * @param maximum The maximum value to be set. + * @throws AWTError Always throws this error when called. + */ + public void setMaximum (int maximum) throws AWTError { - this.maximum = maximum; + throw new AWTError("Can be set by scrollpane only"); } + /** + * This method should never be called. + * + * @param minimum The minimum value to be set. + * @throws AWTError Always throws this error when called. + */ public void setMinimum (int minimum) { - this.minimum = minimum; + throw new AWTError("Can be set by scrollpane only"); } public void setUnitIncrement (int unitIncrement) @@ -171,20 +183,36 @@ public class ScrollPaneAdjustable maximum = value; } + /** + * This method should never be called. + * + * @param visibleAmount The visible amount to be set. + * @throws AWTError Always throws this error when called. + */ public void setVisibleAmount (int visibleAmount) { - this.visibleAmount = visibleAmount; + throw new AWTError("Can be set by scrollpane only"); } public String paramString () { - return ("scrollpane=" + sp + ", orientation=" + orientation - + ", value=" + value + ", minimum=" + minimum - + ", maximum=" + maximum + ", visibleAmount=" + visibleAmount - + ", unitIncrement=" + unitIncrement - + ", blockIncrement=" + blockIncrement); + return paramStringHelper() + + ",[" + getMinimum() + ".." + getMaximum() + + "],val=" + getValue() + + ",vis=" + getVisibleAmount() + + ",unit=" + getUnitIncrement() + + ",block=" + getBlockIncrement() + + ",isAdjusting=" + valueIsAdjusting; } + private String paramStringHelper() + { + if (getOrientation() == HORIZONTAL) + return "horizontal"; + else + return "vertical"; + } + public String toString() { return getClass().getName() + "[" + paramString() + "]"; @@ -209,5 +237,6 @@ public class ScrollPaneAdjustable { this.valueIsAdjusting = valueIsAdjusting; } + } // class ScrollPaneAdjustable diff --git a/libjava/classpath/java/awt/Scrollbar.java b/libjava/classpath/java/awt/Scrollbar.java index c0788370b21..0cba512f605 100644 --- a/libjava/classpath/java/awt/Scrollbar.java +++ b/libjava/classpath/java/awt/Scrollbar.java @@ -341,17 +341,22 @@ public class Scrollbar extends Component implements Accessible, Adjustable public synchronized void setValues(int value, int visibleAmount, int minimum, int maximum) { - if (maximum < minimum) - maximum = minimum; + if (visibleAmount <= 0) + visibleAmount = 1; + + if (maximum <= minimum) + maximum = minimum + 1; if (value < minimum) value = minimum; - if (value > maximum) - value = maximum; - if (visibleAmount > maximum - minimum) visibleAmount = maximum - minimum; + + // According to documentation, the actual maximum + // value is (maximum - visibleAmount) + if (value > maximum - visibleAmount) + value = maximum - visibleAmount; ScrollbarPeer peer = (ScrollbarPeer) getPeer(); if (peer != null @@ -362,30 +367,7 @@ public class Scrollbar extends Component implements Accessible, Adjustable this.value = value; this.visibleAmount = visibleAmount; this.minimum = minimum; - this.maximum = maximum; - - int range = maximum - minimum; - if (lineIncrement > range) - { - if (range == 0) - lineIncrement = 1; - else - lineIncrement = range; - - if (peer != null) - peer.setLineIncrement(lineIncrement); - } - - if (pageIncrement > range) - { - if (range == 0) - pageIncrement = 1; - else - pageIncrement = range; - - if (peer != null) - peer.setPageIncrement(pageIncrement); - } + this.maximum = maximum; } /** @@ -437,19 +419,13 @@ public class Scrollbar extends Component implements Accessible, Adjustable { if (lineIncrement < 0) throw new IllegalArgumentException("Unit increment less than zero."); - - int range = maximum - minimum; - if (lineIncrement > range) - { - if (range == 0) - lineIncrement = 1; - else - lineIncrement = range; - } - - if (lineIncrement == this.lineIncrement) + + if (lineIncrement == 0) + lineIncrement = 1; + + if (lineIncrement == this.lineIncrement) return; - + this.lineIncrement = lineIncrement; ScrollbarPeer peer = (ScrollbarPeer) getPeer(); @@ -507,15 +483,9 @@ public class Scrollbar extends Component implements Accessible, Adjustable if (pageIncrement < 0) throw new IllegalArgumentException("Block increment less than zero."); - int range = maximum - minimum; - if (pageIncrement > range) - { - if (range == 0) - pageIncrement = 1; - else - pageIncrement = range; - } - + if (pageIncrement == 0) + pageIncrement = 1; + if (pageIncrement == this.pageIncrement) return; @@ -647,7 +617,7 @@ public class Scrollbar extends Component implements Accessible, Adjustable * @exception ClassCastException If listenerType doesn't specify a class or * interface that implements java.util.EventListener. */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == AdjustmentListener.class) return AWTEventMulticaster.getListeners(adjustment_listeners, diff --git a/libjava/classpath/java/awt/TextArea.java b/libjava/classpath/java/awt/TextArea.java index 7e3463ab849..30b278d0c29 100644 --- a/libjava/classpath/java/awt/TextArea.java +++ b/libjava/classpath/java/awt/TextArea.java @@ -284,11 +284,7 @@ public class TextArea extends TextComponent implements java.io.Serializable } /** - * Retrieve the minimum size for this text area, considering the - * text area's current row and column values. A text area's minimum - * size depends on the number of rows and columns of text it would - * prefer to display, and on the size of the font in which the text - * would be displayed. + * Retrieve the minimum size for this text area. * * @return The minimum size for this text field. */ @@ -298,11 +294,8 @@ public class TextArea extends TextComponent implements java.io.Serializable } /** - * Retrieve the minimum size that this text area would have if its - * row and column values were equal to those specified. A text - * area's minimum size depends on the number of rows and columns of - * text it would prefer to display, and on the size of the font in - * which the text would be displayed. + * Retrieve the minimum size for this text area. If the minimum + * size has been set, then rows and columns are used in the calculation. * * @param rows The number of rows to use in the minimum size * calculation. @@ -317,12 +310,8 @@ public class TextArea extends TextComponent implements java.io.Serializable } /** - * Retrieve the minimum size for this text area, considering the - * text area's current row and column values. A text area's minimum - * size depends on the number of rows and columns of text it would - * prefer to display, and on the size of the font in which the text - * would be displayed. - * + * Retrieve the minimum size for this text area. + * * @return The minimum size for this text area. * * @deprecated This method is deprecated in favor of @@ -334,11 +323,8 @@ public class TextArea extends TextComponent implements java.io.Serializable } /** - * Retrieve the minimum size that this text area would have if its - * row and column values were equal to those specified. A text - * area's minimum size depends on the number of rows and columns of - * text it would prefer to display, and on the size of the font in - * which the text would be displayed. + * Retrieve the minimum size for this text area. If the minimum + * size has been set, then rows and columns are used in the calculation. * * @param rows The number of rows to use in the minimum size * calculation. @@ -352,21 +338,18 @@ public class TextArea extends TextComponent implements java.io.Serializable */ public Dimension minimumSize (int rows, int columns) { + if (isMinimumSizeSet()) + return new Dimension(minSize); + TextAreaPeer peer = (TextAreaPeer) getPeer (); - - // Sun returns Dimension (0,0) in this case. if (peer == null) - return new Dimension (0, 0); + return new Dimension (getWidth(), getHeight()); return peer.getMinimumSize (rows, columns); } /** - * Retrieve the preferred size for this text area, considering the - * text area's current row and column values. A text area's preferred - * size depends on the number of rows and columns of text it would - * prefer to display, and on the size of the font in which the text - * would be displayed. + * Retrieve the preferred size for this text area. * * @return The preferred size for this text field. */ @@ -376,11 +359,8 @@ public class TextArea extends TextComponent implements java.io.Serializable } /** - * Retrieve the preferred size that this text area would have if its - * row and column values were equal to those specified. A text - * area's preferred size depends on the number of rows and columns - * of text it would prefer to display, and on the size of the font - * in which the text would be displayed. + * Retrieve the preferred size for this text area. If the preferred + * size has been set, then rows and columns are used in the calculation. * * @param rows The number of rows to use in the preferred size * calculation. @@ -395,11 +375,7 @@ public class TextArea extends TextComponent implements java.io.Serializable } /** - * Retrieve the preferred size for this text area, considering the - * text area's current row and column values. A text area's preferred - * size depends on the number of rows and columns of text it would - * prefer to display, and on the size of the font in which the text - * would be displayed. + * Retrieve the preferred size for this text area. * * @return The preferred size for this text field. * @@ -412,11 +388,8 @@ public class TextArea extends TextComponent implements java.io.Serializable } /** - * Retrieve the preferred size that this text area would have if its - * row and column values were equal to those specified. A text - * area's preferred size depends on the number of rows and columns - * of text it would prefer to display, and on the size of the font - * in which the text would be displayed. + * Retrieve the preferred size for this text area. If the preferred + * size has been set, then rows and columns are used in the calculation. * * @param rows The number of rows to use in the preferred size * calculation. @@ -430,11 +403,12 @@ public class TextArea extends TextComponent implements java.io.Serializable */ public Dimension preferredSize (int rows, int columns) { + if (isPreferredSizeSet()) + return new Dimension(prefSize); + TextAreaPeer peer = (TextAreaPeer) getPeer (); - - // Sun returns Dimension (0,0) in this case. if (peer == null) - return new Dimension (0, 0); + return new Dimension (getWidth(), getHeight()); return peer.getPreferredSize (rows, columns); } diff --git a/libjava/classpath/java/awt/TextComponent.java b/libjava/classpath/java/awt/TextComponent.java index f811122f2b2..8fdd94139fc 100644 --- a/libjava/classpath/java/awt/TextComponent.java +++ b/libjava/classpath/java/awt/TextComponent.java @@ -391,7 +391,9 @@ public class TextComponent extends Component */ public synchronized void setSelectionStart(int selectionStart) { - select(selectionStart, getSelectionEnd()); + select(selectionStart, + (getSelectionEnd() < selectionStart) + ? selectionStart : getSelectionEnd()); } /** @@ -610,7 +612,7 @@ public class TextComponent extends Component * @exception ClassCastException If listenerType doesn't specify a class or * interface that implements java.util.EventListener. */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == TextListener.class) return AWTEventMulticaster.getListeners(textListener, listenerType); diff --git a/libjava/classpath/java/awt/TextField.java b/libjava/classpath/java/awt/TextField.java index b76f393a0b6..b1df66f7cdc 100644 --- a/libjava/classpath/java/awt/TextField.java +++ b/libjava/classpath/java/awt/TextField.java @@ -264,9 +264,12 @@ public class TextField extends TextComponent */ public Dimension minimumSize(int columns) { + if (isMinimumSizeSet()) + return new Dimension(minSize); + TextFieldPeer peer = (TextFieldPeer) getPeer (); if (peer == null) - return null; // FIXME: What do we do if there is no peer? + return new Dimension(getWidth(), getHeight()); return peer.getMinimumSize (columns); } @@ -316,10 +319,13 @@ public class TextField extends TextComponent */ public Dimension preferredSize(int columns) { + if (isPreferredSizeSet()) + return new Dimension(prefSize); + TextFieldPeer peer = (TextFieldPeer) getPeer (); if (peer == null) - return new Dimension (0, 0); - + return new Dimension (getWidth(), getHeight()); + return peer.getPreferredSize (columns); } @@ -422,7 +428,7 @@ public class TextField extends TextComponent * * @since 1.3 */ - public EventListener[] getListeners (Class listenerType) + public <T extends EventListener> T[] getListeners (Class<T> listenerType) { if (listenerType == ActionListener.class) return AWTEventMulticaster.getListeners (action_listeners, listenerType); diff --git a/libjava/classpath/java/awt/Toolkit.java b/libjava/classpath/java/awt/Toolkit.java index 2842091c139..69040722e72 100644 --- a/libjava/classpath/java/awt/Toolkit.java +++ b/libjava/classpath/java/awt/Toolkit.java @@ -41,6 +41,7 @@ package java.awt; import gnu.classpath.SystemProperties; import gnu.java.awt.peer.GLightweightPeer; +import gnu.java.awt.peer.headless.HeadlessToolkit; import java.awt.datatransfer.Clipboard; import java.awt.dnd.DragGestureEvent; @@ -51,6 +52,7 @@ import java.awt.dnd.peer.DragSourceContextPeer; import java.awt.event.AWTEventListener; import java.awt.event.AWTEventListenerProxy; import java.awt.event.KeyEvent; +import java.awt.font.TextAttribute; import java.awt.im.InputMethodHighlight; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; @@ -86,6 +88,7 @@ import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; +import java.util.Hashtable; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; @@ -119,7 +122,8 @@ public abstract class Toolkit /** The toolkit properties. */ private static Properties props = new Properties(); - protected final Map desktopProperties = new Properties(); + protected final Map<String,Object> desktopProperties = + new Hashtable<String,Object>(); protected final PropertyChangeSupport desktopPropsSupport = new PropertyChangeSupport(this); @@ -131,6 +135,11 @@ public abstract class Toolkit AWTEventListenerProxy[] awtEventListeners; /** + * The shared peer for all lightweight components. + */ + private GLightweightPeer lightweightPeer; + + /** * Default constructor for subclasses. */ public Toolkit() @@ -379,7 +388,9 @@ public abstract class Toolkit */ protected LightweightPeer createComponent(Component target) { - return new GLightweightPeer(target); + if (lightweightPeer == null) + lightweightPeer = new GLightweightPeer(); + return lightweightPeer; } /** @@ -540,10 +551,11 @@ public abstract class Toolkit * * @throws AWTError If the toolkit cannot be loaded. */ - public static Toolkit getDefaultToolkit() + public static synchronized Toolkit getDefaultToolkit() { if (toolkit != null) return toolkit; + String toolkit_name = SystemProperties.getProperty("awt.toolkit", default_toolkit_name); try @@ -573,8 +585,18 @@ public abstract class Toolkit } catch (Throwable t) { - AWTError e = new AWTError("Cannot load AWT toolkit: " + toolkit_name); - throw (AWTError) e.initCause(t); + // Check for the headless property. + if (GraphicsEnvironment.isHeadless()) + { + toolkit = new HeadlessToolkit(); + return toolkit; + } + else + { + AWTError e = new AWTError("Cannot load AWT toolkit: " + + toolkit_name); + throw (AWTError) e.initCause(t); + } } } @@ -964,8 +986,8 @@ public abstract class Toolkit /** * @since 1.3 */ - public DragGestureRecognizer - createDragGestureRecognizer(Class recognizer, DragSource ds, + public <T extends DragGestureRecognizer> T + createDragGestureRecognizer(Class<T> recognizer, DragSource ds, Component comp, int actions, DragGestureListener l) { @@ -1252,7 +1274,8 @@ public abstract class Toolkit /** * @since 1.3 */ - public abstract Map mapInputMethodHighlight(InputMethodHighlight highlight); + public abstract Map<TextAttribute,?> + mapInputMethodHighlight(InputMethodHighlight highlight); /** * Initializes the accessibility framework. In particular, this loads the diff --git a/libjava/classpath/java/awt/Window.java b/libjava/classpath/java/awt/Window.java index 8885821811d..41dff5577e0 100644 --- a/libjava/classpath/java/awt/Window.java +++ b/libjava/classpath/java/awt/Window.java @@ -1,5 +1,5 @@ /* Window.java -- - Copyright (C) 1999, 2000, 2002, 2003, 2004, 2006 Free Software Foundation + Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005 Free Software Foundation This file is part of GNU Classpath. @@ -140,7 +140,7 @@ public class Window extends Container implements Accessible this(); graphicsConfiguration = gc; } - + /** * Initializes a new instance of <code>Window</code> with the specified * parent. The window will initially be invisible. @@ -250,13 +250,13 @@ public class Window extends Container implements Accessible /** * Shows on-screen this window and any of its owned windows for whom * isVisible returns true. + * @specnote: Deprecated starting in 1.5. */ + @Deprecated public void show() { synchronized (getTreeLock()) { - if (parent != null && ! parent.isDisplayable()) - parent.addNotify(); if (peer == null) addNotify(); @@ -298,11 +298,24 @@ public class Window extends Container implements Accessible if (initialFocusOwner != null) initialFocusOwner.requestFocusInWindow(); + // Post WINDOW_OPENED from here. + if (windowListener != null + || (eventMask & AWTEvent.WINDOW_EVENT_MASK) != 0) + { + WindowEvent ev = new WindowEvent(this, + WindowEvent.WINDOW_OPENED); + Toolkit tk = Toolkit.getDefaultToolkit(); + tk.getSystemEventQueue().postEvent(ev); + } shown = true; } } } + /** + * @specnote: Deprecated starting in 1.5. + */ + @Deprecated public void hide() { // Hide visible owned windows. @@ -349,9 +362,15 @@ public class Window extends Container implements Accessible component[i].removeNotify(); this.removeNotify(); - // Post a WINDOW_CLOSED event. - WindowEvent we = new WindowEvent(this, WindowEvent.WINDOW_CLOSED); - getToolkit().getSystemEventQueue().postEvent(we); + // Post WINDOW_CLOSED from here. + if (windowListener != null + || (eventMask & AWTEvent.WINDOW_EVENT_MASK) != 0) + { + WindowEvent ev = new WindowEvent(this, + WindowEvent.WINDOW_CLOSED); + Toolkit tk = Toolkit.getDefaultToolkit(); + tk.getSystemEventQueue().postEvent(ev); + } } } @@ -479,7 +498,11 @@ public class Window extends Container implements Accessible */ public synchronized void addWindowListener(WindowListener listener) { - windowListener = AWTEventMulticaster.add(windowListener, listener); + if (listener != null) + { + newEventsOnly = true; + windowListener = AWTEventMulticaster.add(windowListener, listener); + } } /** @@ -536,7 +559,12 @@ public class Window extends Container implements Accessible */ public void addWindowFocusListener (WindowFocusListener wfl) { - windowFocusListener = AWTEventMulticaster.add (windowFocusListener, wfl); + if (wfl != null) + { + newEventsOnly = true; + windowFocusListener = AWTEventMulticaster.add (windowFocusListener, + wfl); + } } /** @@ -546,7 +574,12 @@ public class Window extends Container implements Accessible */ public void addWindowStateListener (WindowStateListener wsl) { - windowStateListener = AWTEventMulticaster.add (windowStateListener, wsl); + if (wsl != null) + { + newEventsOnly = true; + windowStateListener = AWTEventMulticaster.add (windowStateListener, + wsl); + } } /** @@ -577,42 +610,21 @@ public class Window extends Container implements Accessible * * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == WindowListener.class) - return getWindowListeners(); + return (T[]) getWindowListeners(); return super.getListeners(listenerType); } void dispatchEventImpl(AWTEvent e) { - // Make use of event id's in order to avoid multiple instanceof tests. - if (e.id <= WindowEvent.WINDOW_LAST - && e.id >= WindowEvent.WINDOW_FIRST - && (windowListener != null - || windowFocusListener != null - || windowStateListener != null - || (eventMask & AWTEvent.WINDOW_EVENT_MASK) != 0)) - processEvent(e); - else + if (e.getID() == ComponentEvent.COMPONENT_RESIZED) { - if (peer != null && (e.id == ComponentEvent.COMPONENT_RESIZED - || e.id == ComponentEvent.COMPONENT_MOVED)) - { - Rectangle bounds = peer.getBounds(); - x = bounds.x; - y = bounds.y; - height = bounds.height; - width = bounds.width; - - if (e.id == ComponentEvent.COMPONENT_RESIZED) - { - invalidate(); - validate(); - } - } - super.dispatchEventImpl(e); + invalidate(); + validate(); } + super.dispatchEventImpl(e); } /** @@ -626,7 +638,28 @@ public class Window extends Container implements Accessible protected void processEvent(AWTEvent evt) { if (evt instanceof WindowEvent) - processWindowEvent((WindowEvent) evt); + { + WindowEvent we = (WindowEvent) evt; + switch (evt.getID()) + { + case WindowEvent.WINDOW_OPENED: + case WindowEvent.WINDOW_CLOSED: + case WindowEvent.WINDOW_CLOSING: + case WindowEvent.WINDOW_ICONIFIED: + case WindowEvent.WINDOW_DEICONIFIED: + case WindowEvent.WINDOW_ACTIVATED: + case WindowEvent.WINDOW_DEACTIVATED: + processWindowEvent(we); + break; + case WindowEvent.WINDOW_GAINED_FOCUS: + case WindowEvent.WINDOW_LOST_FOCUS: + processWindowFocusEvent(we); + break; + case WindowEvent.WINDOW_STATE_CHANGED: + processWindowStateEvent(we); + break; + } + } else super.processEvent(evt); } @@ -641,54 +674,35 @@ public class Window extends Container implements Accessible */ protected void processWindowEvent(WindowEvent evt) { - int id = evt.getID(); - - if (id == WindowEvent.WINDOW_GAINED_FOCUS - || id == WindowEvent.WINDOW_LOST_FOCUS) - processWindowFocusEvent (evt); - else if (id == WindowEvent.WINDOW_STATE_CHANGED) - processWindowStateEvent (evt); - else + if (windowListener != null) { - if (windowListener != null) - { - switch (evt.getID()) - { - case WindowEvent.WINDOW_ACTIVATED: - windowListener.windowActivated(evt); - break; - - case WindowEvent.WINDOW_CLOSED: - windowListener.windowClosed(evt); - break; - - case WindowEvent.WINDOW_CLOSING: - windowListener.windowClosing(evt); - break; - - case WindowEvent.WINDOW_DEACTIVATED: - windowListener.windowDeactivated(evt); - break; - - case WindowEvent.WINDOW_DEICONIFIED: - windowListener.windowDeiconified(evt); - break; - - case WindowEvent.WINDOW_ICONIFIED: - windowListener.windowIconified(evt); - break; - - case WindowEvent.WINDOW_OPENED: - windowListener.windowOpened(evt); - break; - - default: - break; - } - } + switch (evt.getID()) + { + case WindowEvent.WINDOW_ACTIVATED: + windowListener.windowActivated(evt); + break; + case WindowEvent.WINDOW_CLOSED: + windowListener.windowClosed(evt); + break; + case WindowEvent.WINDOW_CLOSING: + windowListener.windowClosing(evt); + break; + case WindowEvent.WINDOW_DEACTIVATED: + windowListener.windowDeactivated(evt); + break; + case WindowEvent.WINDOW_DEICONIFIED: + windowListener.windowDeiconified(evt); + break; + case WindowEvent.WINDOW_ICONIFIED: + windowListener.windowIconified(evt); + break; + case WindowEvent.WINDOW_OPENED: + windowListener.windowOpened(evt); + break; + } } } - + /** * Identifies if this window is active. The active window is a Frame or * Dialog that has focus or owns the active window. @@ -1233,6 +1247,42 @@ public class Window extends Container implements Accessible return "win" + getUniqueLong(); } + /** + * Overridden to handle WindowEvents. + * + * @return <code>true</code> when the specified event type is enabled, + * <code>false</code> otherwise + */ + boolean eventTypeEnabled(int type) + { + boolean enabled = false; + switch (type) + { + case WindowEvent.WINDOW_OPENED: + case WindowEvent.WINDOW_CLOSED: + case WindowEvent.WINDOW_CLOSING: + case WindowEvent.WINDOW_ICONIFIED: + case WindowEvent.WINDOW_DEICONIFIED: + case WindowEvent.WINDOW_ACTIVATED: + case WindowEvent.WINDOW_DEACTIVATED: + enabled = ((eventMask & AWTEvent.WINDOW_EVENT_MASK) != 0) + || windowListener != null; + break; + case WindowEvent.WINDOW_GAINED_FOCUS: + case WindowEvent.WINDOW_LOST_FOCUS: + enabled = ((eventMask & AWTEvent.WINDOW_FOCUS_EVENT_MASK) != 0) + || windowFocusListener != null; + break; + case WindowEvent.WINDOW_STATE_CHANGED: + enabled = ((eventMask & AWTEvent.WINDOW_STATE_EVENT_MASK) != 0) + || windowStateListener != null; + break; + default: + enabled = super.eventTypeEnabled(type); + } + return enabled; + } + private static synchronized long getUniqueLong() { return next_window_number++; diff --git a/libjava/classpath/java/awt/datatransfer/DataFlavor.java b/libjava/classpath/java/awt/datatransfer/DataFlavor.java index 0228cd5786d..baaf43d85bc 100644 --- a/libjava/classpath/java/awt/datatransfer/DataFlavor.java +++ b/libjava/classpath/java/awt/datatransfer/DataFlavor.java @@ -38,14 +38,13 @@ exception statement from your version. */ package java.awt.datatransfer; -import gnu.classpath.NotImplementedException; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInput; import java.io.ObjectOutput; +import java.io.OptionalDataException; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; @@ -76,8 +75,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * deals with bytes not chars. Use <code>getRederForText()</code>. */ public static final DataFlavor plainTextFlavor = - new DataFlavor(java.io.InputStream.class, - "text/plain; charset=unicode", + new DataFlavor("text/plain; charset=unicode; class=java.io.InputStream", "plain unicode text"); /** @@ -94,8 +92,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * element of the list being a <code>java.io.File</code>. */ public static final DataFlavor javaFileListFlavor = - new DataFlavor(java.util.List.class, - "application/x-java-file-list; class=java.util.List", + new DataFlavor("application/x-java-file-list; class=java.util.List", "Java File List"); /** @@ -132,10 +129,10 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ // The MIME type for this flavor - private final String mimeType; + private MimeType mimeType; // The representation class for this flavor - private final Class representationClass; + private Class<?> representationClass; // The human readable name of this flavor private String humanPresentableName; @@ -156,8 +153,8 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * * @exception ClassNotFoundException If the class cannot be loaded. */ - protected static final Class tryToLoadClass(String className, - ClassLoader classLoader) + protected static final Class<?> tryToLoadClass(String className, + ClassLoader classLoader) throws ClassNotFoundException { // Bootstrap @@ -198,62 +195,6 @@ public class DataFlavor implements java.io.Externalizable, Cloneable throw new ClassNotFoundException(className); } - private static Class getRepresentationClassFromMimeThrows(String mimeString, - ClassLoader classLoader) - throws ClassNotFoundException - { - String classname = getParameter("class", mimeString); - if (classname != null) - return tryToLoadClass(classname, classLoader); - else - return java.io.InputStream.class; - } - - // Same as above, but wraps any ClassNotFoundExceptions - private static Class getRepresentationClassFromMime(String mimeString, - ClassLoader classLoader) - { - try - { - return getRepresentationClassFromMimeThrows(mimeString, classLoader); - } - catch(ClassNotFoundException cnfe) - { - IllegalArgumentException iae; - iae = new IllegalArgumentException("mimeString: " - + mimeString - + " classLoader: " - + classLoader); - iae.initCause(cnfe); - throw iae; - } - } - - /** - * Returns the value of the named MIME type parameter, or <code>null</code> - * if the parameter does not exist. Given the parameter name and the mime - * string. - * - * @param paramName The name of the parameter. - * @param mimeString The mime string from where the name should be found. - * - * @return The value of the parameter or null. - */ - private static String getParameter(String paramName, String mimeString) - { - int idx = mimeString.indexOf(paramName + "="); - if (idx == -1) - return(null); - - String value = mimeString.substring(idx + paramName.length() + 1); - - idx = value.indexOf(";"); - if (idx == -1) - return(value); - else - return(value.substring(0, idx)); - } - /** * XXX - Currently returns <code>plainTextFlavor</code>. */ @@ -321,32 +262,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public DataFlavor() { - mimeType = null; - representationClass = null; - humanPresentableName = null; - } - - /** - * Private constructor. - */ - private DataFlavor(Class representationClass, - String mimeType, - String humanPresentableName) - { - this.representationClass = representationClass; - this.mimeType = mimeType; - - // Do some simple validity checks - String type = getPrimaryType() + "/" + getSubType(); - if (type.indexOf(' ') != -1 - || type.indexOf('=') != -1 - || type.indexOf(';') != -1) - throw new IllegalArgumentException(mimeType); - - if (humanPresentableName != null) - this.humanPresentableName = humanPresentableName; - else - this.humanPresentableName = mimeType; + // Used for deserialization only, nothing to do here. } /** @@ -359,13 +275,23 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * @param representationClass The representation class for this object. * @param humanPresentableName The display name of the object. */ - public DataFlavor(Class representationClass, String humanPresentableName) + public DataFlavor(Class<?> representationClass, String humanPresentableName) { - this(representationClass, - "application/x-java-serialized-object" - + "; class=" - + representationClass.getName(), - humanPresentableName); + if (representationClass == null) + throw new NullPointerException("representationClass must not be null"); + try + { + mimeType = new MimeType(javaSerializedObjectMimeType); + } + catch (MimeTypeParseException ex) + { + // Must not happen as we use a constant string. + assert false; + } + if (humanPresentableName == null) + humanPresentableName = javaSerializedObjectMimeType; + this.humanPresentableName = humanPresentableName; + this.representationClass = representationClass; } /** @@ -390,8 +316,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable ClassLoader classLoader) throws ClassNotFoundException { - this(getRepresentationClassFromMimeThrows(mimeType, classLoader), - mimeType, humanPresentableName); + init(mimeType, humanPresentableName, classLoader); } /** @@ -412,8 +337,17 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public DataFlavor(String mimeType, String humanPresentableName) { - this(getRepresentationClassFromMime (mimeType, null), - mimeType, humanPresentableName); + try + { + init(mimeType, humanPresentableName, getClass().getClassLoader()); + } + catch (ClassNotFoundException ex) + { + IllegalArgumentException iae = + new IllegalArgumentException("Class not found: " + ex.getMessage()); + iae.initCause(ex); + throw iae; + } } /** @@ -432,8 +366,54 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public DataFlavor(String mimeType) throws ClassNotFoundException { - this(getRepresentationClassFromMimeThrows(mimeType, null), - mimeType, null); + init(mimeType, null, getClass().getClassLoader()); + } + + /** + * Called by various constructors to initialize this object. + * + * @param mime the mime string + * @param humanPresentableName the human presentable name + * @param loader the class loader to use for loading the representation + * class + */ + private void init(String mime, String humanPresentableName, + ClassLoader loader) + throws ClassNotFoundException + { + if (mime == null) + throw new NullPointerException("The mime type must not be null"); + try + { + mimeType = new MimeType(mime); + } + catch (MimeTypeParseException ex) + { + IllegalArgumentException iae = + new IllegalArgumentException("Invalid mime type"); + iae.initCause(ex); + throw iae; + } + String className = mimeType.getParameter("class"); + if (className == null) + { + if (mimeType.getBaseType().equals(javaSerializedObjectMimeType)) + throw new IllegalArgumentException("Serialized object type must have" + + " a representation class parameter"); + else + representationClass = java.io.InputStream.class; + } + else + representationClass = tryToLoadClass(className, loader); + mimeType.addParameter("class", representationClass.getName()); + + if (humanPresentableName == null) + { + humanPresentableName = mimeType.getParameter("humanPresentableName"); + if (humanPresentableName == null) + humanPresentableName = mimeType.getBaseType(); + } + this.humanPresentableName = humanPresentableName; } /** @@ -443,7 +423,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public String getMimeType() { - return(mimeType); + return(mimeType.toString()); } /** @@ -451,7 +431,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * * @return The representation class for this flavor. */ - public Class getRepresentationClass() + public Class<?> getRepresentationClass() { return(representationClass); } @@ -473,11 +453,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public String getPrimaryType() { - int idx = mimeType.indexOf("/"); - if (idx == -1) - return(mimeType); - - return(mimeType.substring(0, idx)); + return(mimeType.getPrimaryType()); } /** @@ -487,15 +463,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public String getSubType() { - int start = mimeType.indexOf("/"); - if (start == -1) - return ""; - - int end = mimeType.indexOf(";", start + 1); - if (end == -1) - return mimeType.substring(start + 1); - else - return mimeType.substring(start + 1, end); + return mimeType.getSubType(); } /** @@ -511,7 +479,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable if ("humanPresentableName".equals(paramName)) return getHumanPresentableName(); - return getParameter(paramName, mimeType); + return mimeType.getParameter(paramName); } /** @@ -537,16 +505,22 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public boolean isMimeTypeEqual(String mimeType) { - String mime = getMimeType(); - int i = mime.indexOf(";"); - if (i != -1) - mime = mime.substring(0, i); - - i = mimeType.indexOf(";"); - if (i != -1) - mimeType = mimeType.substring(0, i); - - return mime.equals(mimeType); + if (mimeType == null) + throw new NullPointerException("mimeType must not be null"); + boolean equal = false; + try + { + if (this.mimeType != null) + { + MimeType other = new MimeType(mimeType); + equal = this.mimeType.matches(other); + } + } + catch (MimeTypeParseException ex) + { + // Return false in this case. + } + return equal; } /** @@ -571,7 +545,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public boolean isMimeTypeSerializedObject() { - return mimeType.startsWith(javaSerializedObjectMimeType); + return isMimeTypeEqual(javaSerializedObjectMimeType); } /** @@ -617,8 +591,8 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public boolean isFlavorSerializedObjectType() { - // FIXME: What is the diff between this and isMimeTypeSerializedObject? - return(mimeType.startsWith(javaSerializedObjectMimeType)); + return isRepresentationClassSerializable() + && isMimeTypeEqual(javaSerializedObjectMimeType); } /** @@ -629,7 +603,9 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public boolean isFlavorRemoteObjectType() { - return(mimeType.startsWith(javaRemoteObjectMimeType)); + return isRepresentationClassRemote() + && isRepresentationClassSerializable() + && isMimeTypeEqual(javaRemoteObjectMimeType); } /** @@ -770,7 +746,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable */ public int hashCode() { - return mimeType.toLowerCase().hashCode() ^ representationClass.hashCode(); + return mimeType.toString().hashCode() ^ representationClass.hashCode(); } /** @@ -822,9 +798,17 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * @exception IOException If an error occurs. */ public void writeExternal(ObjectOutput stream) - throws IOException, NotImplementedException + throws IOException { - // FIXME: Implement me + if (mimeType != null) + { + mimeType.addParameter("humanPresentableName", humanPresentableName); + stream.writeObject(mimeType); + mimeType.removeParameter("humanPresentableName"); + } + else + stream.writeObject(null); + stream.writeObject(representationClass); } @@ -838,9 +822,34 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * cannot be found. */ public void readExternal(ObjectInput stream) - throws IOException, ClassNotFoundException, NotImplementedException + throws IOException, ClassNotFoundException { - // FIXME: Implement me + mimeType = (MimeType) stream.readObject(); + String className = null; + if (mimeType != null) + { + humanPresentableName = + mimeType.getParameter("humanPresentableName"); + mimeType.removeParameter("humanPresentableName"); + className = mimeType.getParameter("class"); + if (className == null) + throw new IOException("No class in mime type"); + } + try + { + representationClass = (Class) stream.readObject(); + } + catch (OptionalDataException ex) + { + if (ex.eof && ex.length == 0) + { + if (className != null) + representationClass = tryToLoadClass(className, + getClass().getClassLoader()); + } + else + throw ex; + } } /** @@ -861,7 +870,7 @@ public class DataFlavor implements java.io.Externalizable, Cloneable * * @since 1.3 */ - public final Class getDefaultRepresentationClass() + public final Class<?> getDefaultRepresentationClass() { return java.io.InputStream.class; } diff --git a/libjava/classpath/java/awt/datatransfer/FlavorMap.java b/libjava/classpath/java/awt/datatransfer/FlavorMap.java index 59718c4513c..8842c8e55e3 100644 --- a/libjava/classpath/java/awt/datatransfer/FlavorMap.java +++ b/libjava/classpath/java/awt/datatransfer/FlavorMap.java @@ -58,7 +58,7 @@ public interface FlavorMap * * @return A <code>Map</code> of native data types. */ - Map getNativesForFlavors (DataFlavor[] flavors); + Map<DataFlavor, String> getNativesForFlavors (DataFlavor[] flavors); /** * Maps the specified native type names to <code>DataFlavor</code>'s. @@ -71,5 +71,5 @@ public interface FlavorMap * * @return A <code>Map</code> of data flavors. */ - Map getFlavorsForNatives (String[] natives); + Map<String, DataFlavor> getFlavorsForNatives (String[] natives); } diff --git a/libjava/classpath/java/awt/datatransfer/FlavorTable.java b/libjava/classpath/java/awt/datatransfer/FlavorTable.java index 11cdda06ca7..f6c43af8374 100644 --- a/libjava/classpath/java/awt/datatransfer/FlavorTable.java +++ b/libjava/classpath/java/awt/datatransfer/FlavorTable.java @@ -59,7 +59,7 @@ public interface FlavorTable extends FlavorMap * @param flavor the flavor to look up, or null to return all natives * @return the sorted list of natives */ - List getNativesForFlavor(DataFlavor flavor); + List<String> getNativesForFlavor(DataFlavor flavor); /** * Returns a list of flavors corresponding to the given String native. The @@ -69,5 +69,5 @@ public interface FlavorTable extends FlavorMap * @param name the native name to look up, or null to return all flavors * @return the sorted list of flavors */ - List getFlavorsForNative(String name); + List<DataFlavor> getFlavorsForNative(String name); } diff --git a/libjava/classpath/java/awt/datatransfer/MimeType.java b/libjava/classpath/java/awt/datatransfer/MimeType.java new file mode 100644 index 00000000000..438d78e9e73 --- /dev/null +++ b/libjava/classpath/java/awt/datatransfer/MimeType.java @@ -0,0 +1,281 @@ +/* MimeType.java -- A helper class for mime handling in DataFlavor + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.awt.datatransfer; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.StringTokenizer; + +/** + * A helper class for mime handling in DataFlavor. + * + * A Mauve test for DataFlavor.writeExternal() shows that a non-public + * class java.awt.datatransfer.MimeType gets serialized. This class + * is mainly here for serialization compatibility. Of course, + * now that we have it here, we can just as well implement some + * mime handling facility here. + */ +class MimeType + implements Externalizable +{ + + /** + * The primary type. + */ + private String primaryType; + + /** + * The subtype. + */ + private String subType; + + /** + * Additional parameters to be appended to the mime string. + */ + private HashMap parameters; + + /** + * This is only here for deserialization. + */ + public MimeType() + { + parameters = new HashMap(); + } + + /** + * Creates a new MimeType object. + * + * @param mime the mime type + */ + MimeType(String mime) + throws MimeTypeParseException + { + this(); + parse(mime); + } + + /** + * Adds a mime parameter. + * + * @param param the parameter key + * @param value the parameter value + */ + void addParameter(String param, String value) + { + parameters.put(param, value); + } + + /** + * Removes the parameter with the specified key. + * + * @param param the parameter to remove + */ + void removeParameter(String param) + { + parameters.remove(param); + } + + /** + * Returns the parameter for the <code>key</code>. + * + * @param key the parameter key + * + * @return the parameter for the <code>key</code> + */ + String getParameter(String key) + { + return (String) parameters.get(key); + } + + /** + * Returns the primary type. + * + * @return the primary type + */ + String getPrimaryType() + { + return primaryType; + } + + String getSubType() + { + return subType; + } + + /** + * Returns the base type of this mime type. This is the primary + * type plus the subtype, separated by '/'. + * + * @return the base type of this mime type + */ + String getBaseType() + { + return primaryType + '/' + subType; + } + + /** + * Returns <code>true</code> if this mime type and another mime type + * match. This will be true when their primary types are equal, and their + * subtypes are equal (or when either subtype is * ). + * + * @param other the other mime type + * + * @return <code>true</code> if the mime types match, <code>false</code> + * otherwise + */ + boolean matches(MimeType other) + { + boolean match = false; + if (other != null) + { + match = primaryType.equals(other.primaryType) + && (subType.equals("*") || other.subType.equals("*") + || subType.equals(other.subType)); + } + return match; + } + + /** + * Serializes the mime type. + * + * @param in the input stream to read from + * + * @throws ClassNotFoundException not thrown here + * @throws IOException when something goes wrong on the input stream, + * or when the mime type can't be parsed + */ + public void readExternal(ObjectInput in) + throws ClassNotFoundException, IOException + { + String mime = in.readUTF(); + parameters.clear(); + try + { + parse(mime); + } + catch (MimeTypeParseException ex) + { + IOException ioEx = new IOException(); + ioEx.initCause(ex); + throw ioEx; + } + } + + /** + * Serializes this mime type. + * + * @param out the output stream + * + * @throws IOException when something goes wrong on the output stream + */ + public void writeExternal(ObjectOutput out) + throws IOException + { + out.writeUTF(toString()); + } + + /** + * Creates a string representation of this mime type. + * + * @return a string representation of this mime type + */ + public String toString() + { + StringBuilder s = new StringBuilder(); + s.append(primaryType); + s.append('/'); + s.append(subType); + if (parameters.size() > 0) + { + Set entries = parameters.entrySet(); + for (Iterator i = entries.iterator(); i.hasNext();) + { + s.append("; "); + Map.Entry entry = (Map.Entry) i.next(); + s.append(entry.getKey()); + s.append('='); + s.append(entry.getValue()); + } + } + return s.toString(); + } + + /** + * Parses the specified mime type string and initializes the fields + * of this object. + * + * @param mime the mime type string + */ + private void parse(String mime) + throws MimeTypeParseException + { + // FIXME: Maybe implement more sophisticated mime string parsing according + // to RFC 2045 and 2046. + StringTokenizer tokenizer = new StringTokenizer(mime); + try + { + primaryType = tokenizer.nextToken("/"); + subType = tokenizer.nextToken("/;"); + } + catch (NoSuchElementException ex) + { + throw new MimeTypeParseException("Expected / separator"); + } + + // Add any parameters. + while (tokenizer.hasMoreTokens()) + { + String keyValuePair = tokenizer.nextToken(";"); + int i = keyValuePair.indexOf('='); + if (i == -1) + throw new MimeTypeParseException("Expected = as parameter separator"); + String key = keyValuePair.substring(0, i).trim(); + String value = keyValuePair.substring(i + 1).trim(); + parameters.put(key, value); + } + } + +} diff --git a/libjava/classpath/java/awt/datatransfer/SystemFlavorMap.java b/libjava/classpath/java/awt/datatransfer/SystemFlavorMap.java index a80665aee98..e163fe067e2 100644 --- a/libjava/classpath/java/awt/datatransfer/SystemFlavorMap.java +++ b/libjava/classpath/java/awt/datatransfer/SystemFlavorMap.java @@ -98,9 +98,9 @@ public final class SystemFlavorMap implements FlavorMap, FlavorTable * * @return A <code>Map</code> of native data types to data flavors. */ - public Map getNativesForFlavors (DataFlavor[] flavors) + public Map<DataFlavor, String> getNativesForFlavors (DataFlavor[] flavors) { - return new HashMap(); + return new HashMap<DataFlavor, String>(); } /** @@ -114,9 +114,9 @@ public final class SystemFlavorMap implements FlavorMap, FlavorTable * * @return A <code>Map</code> of data flavors to native type names. */ - public Map getFlavorsForNatives (String[] natives) + public Map<String, DataFlavor> getFlavorsForNatives (String[] natives) { - return new HashMap(); + return new HashMap<String, DataFlavor>(); } /** @@ -263,13 +263,13 @@ public final class SystemFlavorMap implements FlavorMap, FlavorTable * specified native and a DataFlavor whose MIME type is a decoded * version of the native. */ - public List getFlavorsForNative (String nat) + public List<DataFlavor> getFlavorsForNative (String nat) throws NotImplementedException { throw new Error ("Not implemented"); } - public List getNativesForFlavor (DataFlavor flav) + public List<String> getNativesForFlavor (DataFlavor flav) throws NotImplementedException { throw new Error ("Not implemented"); diff --git a/libjava/classpath/java/awt/dnd/DragGestureEvent.java b/libjava/classpath/java/awt/dnd/DragGestureEvent.java index 351ae540072..2a22abb12aa 100644 --- a/libjava/classpath/java/awt/dnd/DragGestureEvent.java +++ b/libjava/classpath/java/awt/dnd/DragGestureEvent.java @@ -59,7 +59,7 @@ public class DragGestureEvent extends EventObject private Component component; private final Point origin; private final int action; - private List events; + private List<InputEvent> events; private DragGestureRecognizer dgr; /** @@ -71,15 +71,15 @@ public class DragGestureEvent extends EventObject * @throws IllegalArgumentException - if input parameters are null */ public DragGestureEvent(DragGestureRecognizer dgr, int action, Point origin, - List events) - { + List<? extends InputEvent> events) + { super(dgr); if (origin == null || events == null || dgr == null) throw new IllegalArgumentException(); - + this.origin = origin; this.action = action; - this.events = events; + this.events = (List<InputEvent>) events; this.dgr = dgr; this.component = dgr.getComponent(); this.dragSource = dgr.getDragSource(); @@ -130,7 +130,7 @@ public class DragGestureEvent extends EventObject * * @return an iterator representation of the List of events. */ - public Iterator iterator() + public Iterator<InputEvent> iterator() { return events.iterator(); } @@ -155,7 +155,7 @@ public class DragGestureEvent extends EventObject { return events.toArray(array); } - + /** * Gets the user's preferred action. * diff --git a/libjava/classpath/java/awt/dnd/DragGestureRecognizer.java b/libjava/classpath/java/awt/dnd/DragGestureRecognizer.java index 32bbc56da5d..3973e528481 100644 --- a/libjava/classpath/java/awt/dnd/DragGestureRecognizer.java +++ b/libjava/classpath/java/awt/dnd/DragGestureRecognizer.java @@ -1,5 +1,5 @@ /* DragGestureRecognizer.java -- - Copyright (C) 2002,2006 Free Software Foundation, Inc. + Copyright (C) 2002, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,8 +38,6 @@ exception statement from your version. */ package java.awt.dnd; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Point; import java.awt.event.InputEvent; @@ -52,6 +50,8 @@ import java.util.TooManyListenersException; /** * STUBBED + * @author Michael Koch (konqueror@gmx.de) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) * @since 1.2 */ public abstract class DragGestureRecognizer implements Serializable @@ -65,7 +65,7 @@ public abstract class DragGestureRecognizer implements Serializable protected Component component; protected transient DragGestureListener dragGestureListener; protected int sourceActions; - protected ArrayList events = new ArrayList(); + protected ArrayList<InputEvent> events = new ArrayList<InputEvent>(); protected DragGestureRecognizer(DragSource ds, Component c, int sa, DragGestureListener dgl) @@ -127,11 +127,12 @@ public abstract class DragGestureRecognizer implements Serializable return events.size() > 0 ? (InputEvent) events.get(0) : null; } + /** + * Resets the recognizer. If a gesture is currently recognize, discard it. + */ public void resetRecognizer() - throws NotImplementedException { - events = new ArrayList(); - // FIXME: Not implemented fully. + events.clear(); } /** @@ -164,6 +165,7 @@ public abstract class DragGestureRecognizer implements Serializable if(dragGestureListener != null) dragGestureListener.dragGestureRecognized (new DragGestureEvent(this, dragAction, p, events)); + resetRecognizer(); } protected void appendEvent(InputEvent e) diff --git a/libjava/classpath/java/awt/dnd/DragSource.java b/libjava/classpath/java/awt/dnd/DragSource.java index 48fa2388ee2..cd4a93a3e3f 100644 --- a/libjava/classpath/java/awt/dnd/DragSource.java +++ b/libjava/classpath/java/awt/dnd/DragSource.java @@ -105,16 +105,15 @@ public class DragSource implements Serializable ds = null; throw new HeadlessException(); } - + if (ds == null) ds = new DragSource(); return ds; } public static boolean isDragImageSupported() - throws NotImplementedException { - // FIXME: Implement this + // In all cases, Sun returns false here. return false; } @@ -140,8 +139,6 @@ public class DragSource implements Serializable // This function sends the same message to the context, which then forwards // it to the peer, passing itself as a parameter. Now, the native system has // access to the Transferable through the context. - - // FIXME: Add check to determine if dragging. try { @@ -228,15 +225,16 @@ public class DragSource implements Serializable { return flavorMap; } - - public DragGestureRecognizer createDragGestureRecognizer(Class recognizer, - Component c, - int actions, - DragGestureListener dgl) + + public <T extends DragGestureRecognizer> T + createDragGestureRecognizer(Class<T> recognizer, + Component c, + int actions, + DragGestureListener dgl) { - return Toolkit.getDefaultToolkit().createDragGestureRecognizer(recognizer, - this, c, - actions, dgl); + return (T) Toolkit.getDefaultToolkit().createDragGestureRecognizer(recognizer, + this, c, + actions, dgl); } public DragGestureRecognizer createDefaultDragGestureRecognizer(Component c, @@ -299,23 +297,23 @@ public class DragSource implements Serializable /** * @since 1.4 */ - public EventListener[] getListeners (Class listenerType) + public <T extends EventListener> T[] getListeners (Class<T> listenerType) { if (listenerType == DragSourceListener.class) return DnDEventMulticaster.getListeners (dragSourceListener, - listenerType); + listenerType); if (listenerType == DragSourceMotionListener.class) return DnDEventMulticaster.getListeners (dragSourceMotionListener, - listenerType); + listenerType); // Return an empty EventListener array. - return new EventListener [0]; + return (T[]) new EventListener [0]; } /** * TODO - * @return + * @return TODO * * @since 1.5 */ @@ -323,6 +321,6 @@ public class DragSource implements Serializable throws NotImplementedException { // FIXME: Not implemented. - return 4; + return 8; } } // class DragSource diff --git a/libjava/classpath/java/awt/dnd/DragSourceContext.java b/libjava/classpath/java/awt/dnd/DragSourceContext.java index 1fee5c0c304..ed1cbaa4454 100644 --- a/libjava/classpath/java/awt/dnd/DragSourceContext.java +++ b/libjava/classpath/java/awt/dnd/DragSourceContext.java @@ -38,8 +38,6 @@ exception statement from your version. */ package java.awt.dnd; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Cursor; import java.awt.Image; @@ -268,7 +266,8 @@ public class DragSourceContext for (int i = 0; i < dsl.length; i++) dsl[i].dragExit(e); - updateCurrentCursor(0, 0, DEFAULT); + updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, + DEFAULT); } /** @@ -340,26 +339,45 @@ public class DragSourceContext * @param status - the status of the cursor (constant). */ protected void updateCurrentCursor(int dropOp, int targetAct, int status) - throws NotImplementedException { - // FIXME: Not implemented fully - if (!useCustomCursor) + if (! useCustomCursor) { - Cursor cursor = null; + Cursor newCursor = null; switch (status) { + default: + targetAct = DnDConstants.ACTION_NONE; case ENTER: - break; case CHANGED: - break; case OVER: - break; - default: - break; + int action = dropOp & targetAct; + if (action == DnDConstants.ACTION_NONE) + { + if ((dropOp & DnDConstants.ACTION_LINK) != 0) + newCursor = DragSource.DefaultLinkNoDrop; + else if ((dropOp & DnDConstants.ACTION_MOVE) != 0) + newCursor = DragSource.DefaultMoveNoDrop; + else + newCursor = DragSource.DefaultCopyNoDrop; + } + else + { + if ((dropOp & DnDConstants.ACTION_LINK) != 0) + newCursor = DragSource.DefaultLinkDrop; + else if ((dropOp & DnDConstants.ACTION_MOVE) != 0) + newCursor = DragSource.DefaultMoveDrop; + else + newCursor = DragSource.DefaultCopyDrop; + } } - this.cursor = cursor; - peer.setCursor(cursor); + if (cursor == null || ! cursor.equals(newCursor)) + { + cursor = newCursor; + DragSourceContextPeer p = peer; + if (p != null) + p.setCursor(cursor); + } } } } // class DragSourceContext diff --git a/libjava/classpath/java/awt/dnd/DropTarget.java b/libjava/classpath/java/awt/dnd/DropTarget.java index a3650567f09..63be5ac046d 100644 --- a/libjava/classpath/java/awt/dnd/DropTarget.java +++ b/libjava/classpath/java/awt/dnd/DropTarget.java @@ -38,13 +38,14 @@ exception statement from your version. */ package java.awt.dnd; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; +import java.awt.Insets; import java.awt.Point; +import java.awt.Rectangle; import java.awt.datatransfer.FlavorMap; +import java.awt.datatransfer.SystemFlavorMap; import java.awt.dnd.peer.DropTargetPeer; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -54,6 +55,8 @@ import java.io.Serializable; import java.util.EventListener; import java.util.TooManyListenersException; +import javax.swing.Timer; + /** * @author Michael Koch * @since 1.2 @@ -69,30 +72,87 @@ public class DropTarget protected static class DropTargetAutoScroller implements ActionListener { + /** + * The threshold that keeps the autoscroller running. + */ + private static final int HYSTERESIS = 10; + + /** + * The initial timer delay. + */ + private static final int DELAY = 100; + private Component component; private Point point; - + + /** + * The timer that triggers autoscrolling. + */ + private Timer timer; + + /** + * The outer region of the scroller. This is the component's size. + */ + private Rectangle outer; + + /** + * The inner region of the scroller. This is the component size without + * the autoscroll insets. + */ + private Rectangle inner; + protected DropTargetAutoScroller (Component c, Point p) { component = c; point = p; + timer = new Timer(DELAY, this); + timer.setCoalesce(true); + timer.start(); } protected void updateLocation (Point newLocn) { + Point previous = point; point = newLocn; + if (Math.abs(point.x - previous.x) > HYSTERESIS + || Math.abs(point.y - previous.y) > HYSTERESIS) + { + if (timer.isRunning()) + timer.stop(); + } + else + { + if (! timer.isRunning()) + timer.start(); + } } protected void stop () - throws NotImplementedException { - // FIXME: implement this + timer.start(); } public void actionPerformed (ActionEvent e) - throws NotImplementedException { - // FIXME: implement this + Autoscroll autoScroll = (Autoscroll) component; + + // First synchronize the inner and outer rectangles. + Insets i = autoScroll.getAutoscrollInsets(); + int width = component.getWidth(); + int height = component.getHeight(); + if (width != outer.width || height != outer.height) + outer.setBounds(0, 0, width, height); + if (inner.x != i.left || inner.y != i.top) + inner.setLocation(i.left, i.top); + int inWidth = width - i.left - i.right; + int inHeight = height - i.top - i.bottom; + if (inWidth != inner.width || inHeight != inner.height) + inner.setSize(inWidth, inHeight); + + // Scroll if the outer rectangle contains the location, but the + // inner doesn't. + if (outer.contains(point) && ! inner.contains(point)) + autoScroll.autoscroll(point); } } @@ -113,7 +173,7 @@ public class DropTarget */ public DropTarget () { - this (null, 0, null, true, null); + this (null, DnDConstants.ACTION_COPY_OR_MOVE, null, true, null); } /** @@ -124,7 +184,7 @@ public class DropTarget */ public DropTarget (Component c, DropTargetListener dtl) { - this (c, 0, dtl, true, null); + this (c, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true, null); } /** @@ -164,7 +224,11 @@ public class DropTarget setComponent(c); setDefaultActions(i); dropTargetListener = dtl; - flavorMap = fm; + + if (fm == null) + flavorMap = SystemFlavorMap.getDefaultFlavorMap(); + else + flavorMap = fm; setActive (b); @@ -177,6 +241,8 @@ public class DropTarget */ public void setComponent (Component c) { + if (component != null) + clearAutoscroll(); component = c; } @@ -207,6 +273,8 @@ public class DropTarget public void setActive (boolean active) { this.active = active; + if (! active) + clearAutoscroll(); } public boolean isActive() @@ -225,8 +293,14 @@ public class DropTarget public void addDropTargetListener (DropTargetListener dtl) throws TooManyListenersException { + if (dtl == null) + return; + + if (dtl.equals(this)) + throw new IllegalArgumentException(); + if (dropTargetListener != null) - throw new TooManyListenersException (); + throw new TooManyListenersException(); dropTargetListener = dtl; } @@ -239,30 +313,47 @@ public class DropTarget public void dragEnter(DropTargetDragEvent dtde) { - if (dropTargetListener != null) - dropTargetListener.dragEnter(dtde); + if (active) + { + if (dropTargetListener != null) + dropTargetListener.dragEnter(dtde); + initializeAutoscrolling(dtde.getLocation()); + } } public void dragOver(DropTargetDragEvent dtde) { - if (dropTargetListener != null) - dropTargetListener.dragOver(dtde); + if (active) + { + if (dropTargetListener != null) + dropTargetListener.dragOver(dtde); + updateAutoscroll(dtde.getLocation()); + } } public void dropActionChanged(DropTargetDragEvent dtde) { - if (dropTargetListener != null) - dropTargetListener.dropActionChanged(dtde); + if (active) + { + if (dropTargetListener != null) + dropTargetListener.dropActionChanged(dtde); + updateAutoscroll(dtde.getLocation()); + } } public void dragExit(DropTargetEvent dte) { - if (dropTargetListener != null) - dropTargetListener.dragExit(dte); + if (active) + { + if (dropTargetListener != null) + dropTargetListener.dragExit(dte); + clearAutoscroll(); + } } public void drop(DropTargetDropEvent dtde) { + clearAutoscroll(); if (dropTargetListener != null) dropTargetListener.drop(dtde); } @@ -321,15 +412,13 @@ public class DropTarget protected DropTarget.DropTargetAutoScroller createDropTargetAutoScroller (Component c, Point p) { - if (autoscroller == null) - autoscroller = new DropTarget.DropTargetAutoScroller (c, p); - - return autoscroller; + return new DropTarget.DropTargetAutoScroller (c, p); } protected void initializeAutoscrolling(Point p) { - createDropTargetAutoScroller (component, p); + if (component instanceof Autoscroll) // Checks for null too. + autoscroller = createDropTargetAutoScroller (component, p); } protected void updateAutoscroll(Point dragCursorLocn) @@ -340,6 +429,10 @@ public class DropTarget protected void clearAutoscroll() { - autoscroller = null; + if (autoscroller != null) + { + autoscroller.stop(); + autoscroller = null; + } } } // class DropTarget diff --git a/libjava/classpath/java/awt/dnd/DropTargetContext.java b/libjava/classpath/java/awt/dnd/DropTargetContext.java index 31945c34bb1..d970e2e0881 100644 --- a/libjava/classpath/java/awt/dnd/DropTargetContext.java +++ b/libjava/classpath/java/awt/dnd/DropTargetContext.java @@ -1,5 +1,5 @@ /* DropTargetContext.java -- - Copyright (C) 2002, 2003, 2004, 2006, Free Software Foundation + Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation This file is part of GNU Classpath. @@ -49,6 +49,7 @@ import java.util.List; /** * @author Michael Koch (konqueror@gmx.de) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) * @since 1.2 */ public class DropTargetContext implements Serializable @@ -128,51 +129,51 @@ public class DropTargetContext implements Serializable * * @exception InvalidDnDOperationException If a drop is not outstanding. */ - public void dropComplete(boolean success) + public void dropComplete (boolean success) { if (dtcp != null) dtcp.dropComplete(success); } - protected void acceptDrag(int dragOperation) + protected void acceptDrag (int dragOperation) { if (dtcp != null) dtcp.acceptDrag(dragOperation); } - protected void rejectDrag() + protected void rejectDrag () { if (dtcp != null) dtcp.rejectDrag(); } - protected void acceptDrop(int dropOperation) + protected void acceptDrop (int dropOperation) { if (dtcp != null) dtcp.acceptDrop(dropOperation); } - protected void rejectDrop() + protected void rejectDrop () { if (dtcp != null) dtcp.rejectDrop(); } - protected DataFlavor[] getCurrentDataFlavors() + protected DataFlavor[] getCurrentDataFlavors () { if (dtcp != null) dtcp.getTransferDataFlavors(); return null; } - protected List getCurrentDataFlavorsAsList() + protected List<DataFlavor> getCurrentDataFlavorsAsList () { - return Arrays.asList(getCurrentDataFlavors()); + return Arrays.asList(getCurrentDataFlavors ()); } - protected boolean isDataFlavorSupported(DataFlavor flavor) + protected boolean isDataFlavorSupported (DataFlavor flavor) { - return getCurrentDataFlavorsAsList().contains(flavor); + return getCurrentDataFlavorsAsList().contains (flavor); } /** diff --git a/libjava/classpath/java/awt/dnd/DropTargetDragEvent.java b/libjava/classpath/java/awt/dnd/DropTargetDragEvent.java index 89bf1778a71..58feb438767 100644 --- a/libjava/classpath/java/awt/dnd/DropTargetDragEvent.java +++ b/libjava/classpath/java/awt/dnd/DropTargetDragEvent.java @@ -108,7 +108,7 @@ public class DropTargetDragEvent extends DropTargetEvent return context.getCurrentDataFlavors (); } - public List getCurrentDataFlavorsAsList () + public List<DataFlavor> getCurrentDataFlavorsAsList () { return context.getCurrentDataFlavorsAsList (); } @@ -147,7 +147,6 @@ public class DropTargetDragEvent extends DropTargetEvent */ public Transferable getTransferable() { - // FIXME: Not implemented - return null; + return context.getTransferable(); } } // class DropTargetDragEvent diff --git a/libjava/classpath/java/awt/dnd/DropTargetDropEvent.java b/libjava/classpath/java/awt/dnd/DropTargetDropEvent.java index 9754bb11ef5..dd85ef712c5 100644 --- a/libjava/classpath/java/awt/dnd/DropTargetDropEvent.java +++ b/libjava/classpath/java/awt/dnd/DropTargetDropEvent.java @@ -1,5 +1,5 @@ /* DropTargetDropEvent.java -- - Copyright (C) 2002 Free Software Foundation, Inc. + Copyright (C) 2002, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -123,7 +123,7 @@ public class DropTargetDropEvent extends DropTargetEvent return context.getCurrentDataFlavors(); } - public List getCurrentDataFlavorsAsList() + public List<DataFlavor> getCurrentDataFlavorsAsList() { return context.getCurrentDataFlavorsAsList(); } diff --git a/libjava/classpath/java/awt/dnd/DropTargetEvent.java b/libjava/classpath/java/awt/dnd/DropTargetEvent.java index f75f756d037..cb2aec6402e 100644 --- a/libjava/classpath/java/awt/dnd/DropTargetEvent.java +++ b/libjava/classpath/java/awt/dnd/DropTargetEvent.java @@ -41,6 +41,10 @@ import java.util.EventObject; public class DropTargetEvent extends EventObject { + + /** + * Serialization identifier for Sun 1.5 compatability + */ private static final long serialVersionUID = 2821229066521922993L; protected DropTargetContext context; diff --git a/libjava/classpath/java/awt/event/ComponentEvent.java b/libjava/classpath/java/awt/event/ComponentEvent.java index ba9c2a5b3f2..6d478055aa5 100644 --- a/libjava/classpath/java/awt/event/ComponentEvent.java +++ b/libjava/classpath/java/awt/event/ComponentEvent.java @@ -1,5 +1,5 @@ /* ComponentEvent.java -- notification of events for components - Copyright (C) 1999, 2002, 2005 Free Software Foundation, Inc. + Copyright (C) 1999, 2002, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -114,24 +114,27 @@ public class ComponentEvent extends AWTEvent */ public String paramString() { + StringBuffer s = new StringBuffer(); + // Unlike Sun, we don't throw NullPointerException or ClassCastException // when source was illegally changed. - switch (id) - { - case COMPONENT_MOVED: - return "COMPONENT_MOVED " - + (source instanceof Component - ? ((Component) source).getBounds() : (Object) ""); - case COMPONENT_RESIZED: - return "COMPONENT_RESIZED " - + (source instanceof Component - ? ((Component) source).getBounds() : (Object) ""); - case COMPONENT_SHOWN: - return "COMPONENT_SHOWN"; - case COMPONENT_HIDDEN: - return "COMPONENT_HIDDEN"; - default: - return "unknown type"; - } + if (id == COMPONENT_MOVED) + s.append("COMPONENT_MOVED "); + else if (id == COMPONENT_RESIZED) + s.append("COMPONENT_RESIZED "); + else if (id == COMPONENT_SHOWN) + s.append("COMPONENT_SHOWN "); + else if (id == COMPONENT_HIDDEN) + s.append("COMPONENT_HIDDEN "); + else + return "unknown type"; + + s.append("(").append(getComponent().getX()).append(",") + .append(getComponent().getY()).append(" ") + .append(getComponent().getWidth()).append("x") + .append(getComponent().getHeight()).append(")"); + + return s.toString(); } + } // class ComponentEvent diff --git a/libjava/classpath/java/awt/font/FontRenderContext.java b/libjava/classpath/java/awt/font/FontRenderContext.java index c50e5e5092a..c25bae3ec16 100644 --- a/libjava/classpath/java/awt/font/FontRenderContext.java +++ b/libjava/classpath/java/awt/font/FontRenderContext.java @@ -117,8 +117,12 @@ public class FontRenderContext */ public int hashCode () { - // FIXME: check what SUN does here. - return affineTransform == null ? 0 : affineTransform.hashCode (); + int code = ( isAntiAliased ? 1 : 0 ) + ( usesFractionalMetrics ? 2 : 0 ); + + if( affineTransform != null && !affineTransform.isIdentity() ) + code ^= affineTransform.hashCode(); + + return code; } public boolean isAntiAliased () diff --git a/libjava/classpath/java/awt/font/TextHitInfo.java b/libjava/classpath/java/awt/font/TextHitInfo.java index 2b23e1963cd..f6fee1addae 100644 --- a/libjava/classpath/java/awt/font/TextHitInfo.java +++ b/libjava/classpath/java/awt/font/TextHitInfo.java @@ -81,6 +81,9 @@ public final class TextHitInfo public boolean equals(TextHitInfo hitInfo) { + if (hitInfo == null) + return false; + return (charIndex == hitInfo.getCharIndex ()) && (leadingEdge == hitInfo.isLeadingEdge ()); } @@ -97,7 +100,7 @@ public final class TextHitInfo public static TextHitInfo beforeOffset(int offset) { - return new TextHitInfo (offset, false); + return new TextHitInfo ((offset - 1), false); } public static TextHitInfo afterOffset(int offset) diff --git a/libjava/classpath/java/awt/font/TextLayout.java b/libjava/classpath/java/awt/font/TextLayout.java index b1473f25564..dc0e537eba9 100644 --- a/libjava/classpath/java/awt/font/TextLayout.java +++ b/libjava/classpath/java/awt/font/TextLayout.java @@ -38,18 +38,18 @@ exception statement from your version. */ package java.awt.font; -import gnu.classpath.NotImplementedException; - import java.awt.Font; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.text.CharacterIterator; import java.text.AttributedCharacterIterator; import java.text.Bidi; +import java.util.ArrayList; import java.util.Map; /** @@ -57,19 +57,86 @@ import java.util.Map; */ public final class TextLayout implements Cloneable { - private GlyphVector[] runs; - private Font font; + /** + * Holds the layout data that belongs to one run of characters. + */ + private class Run + { + /** + * The actual glyph vector. + */ + GlyphVector glyphVector; + + /** + * The font for this text run. + */ + Font font; + + /** + * The start of the run. + */ + int runStart; + + /** + * The end of the run. + */ + int runEnd; + + /** + * The layout location of the beginning of the run. + */ + float location; + + /** + * Initializes the Run instance. + * + * @param gv the glyph vector + * @param start the start index of the run + * @param end the end index of the run + */ + Run(GlyphVector gv, Font f, int start, int end) + { + glyphVector = gv; + font = f; + runStart = start; + runEnd = end; + } + + /** + * Returns <code>true</code> when this run is left to right, + * <code>false</code> otherwise. + * + * @return <code>true</code> when this run is left to right, + * <code>false</code> otherwise + */ + boolean isLeftToRight() + { + return (glyphVector.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) == 0; + } + } + + /** + * The laid out character runs. + */ + private Run[] runs; + private FontRenderContext frc; - private String string; + private char[] string; + private int offset; + private int length; private Rectangle2D boundsCache; private LineMetrics lm; /** - * Start and end character indices of the runs. - * First index is the run number, second is 0 or 1 for the starting - * and ending character index of the run, respectively. + * The total advance of this text layout. This is cache for maximum + * performance. + */ + private float totalAdvance = -1F; + + /** + * The cached natural bounds. */ - private int[][] runIndices; + private Rectangle2D naturalBounds; /** * Character indices. @@ -88,66 +155,98 @@ public final class TextLayout implements Cloneable private boolean hasWhitespace = false; /** + * The {@link Bidi} object that is used for reordering and by + * {@link #getCharacterLevel(int)}. + */ + private Bidi bidi; + + /** + * Mpas the logical position of each individual character in the original + * string to its visual position. + */ + private int[] logicalToVisual; + + /** + * Maps visual positions of a character to its logical position + * in the original string. + */ + private int[] visualToLogical; + + /** + * The cached hashCode. + */ + private int hash; + + /** * The default caret policy. */ - public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy(); + public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = + new CaretPolicy(); /** * Constructs a TextLayout. */ - public TextLayout (String string, Font font, FontRenderContext frc) + public TextLayout (String str, Font font, FontRenderContext frc) { - this.font = font; this.frc = frc; - this.string = string; - lm = font.getLineMetrics(string, frc); + string = str.toCharArray(); + offset = 0; + length = this.string.length; + lm = font.getLineMetrics(this.string, offset, length, frc); // Get base direction and whitespace info getStringProperties(); - if( Bidi.requiresBidi( string.toCharArray(), 0, string.length() ) ) + if (Bidi.requiresBidi(string, offset, offset + length)) { - Bidi bidi = new Bidi( string, leftToRight ? - Bidi.DIRECTION_LEFT_TO_RIGHT : - Bidi.DIRECTION_RIGHT_TO_LEFT ); + bidi = new Bidi(str, leftToRight ? Bidi.DIRECTION_LEFT_TO_RIGHT + : Bidi.DIRECTION_RIGHT_TO_LEFT ); int rc = bidi.getRunCount(); byte[] table = new byte[ rc ]; for(int i = 0; i < table.length; i++) table[i] = (byte)bidi.getRunLevel(i); - runs = new GlyphVector[ rc ]; - runIndices = new int[rc][2]; - for(int i = 0; i < runs.length; i++) + runs = new Run[rc]; + for(int i = 0; i < rc; i++) { - runIndices[i][0] = bidi.getRunStart( i ); - runIndices[i][1] = bidi.getRunLimit( i ); - if( runIndices[i][0] != runIndices[i][1] ) // no empty runs. + int start = bidi.getRunStart(i); + int end = bidi.getRunLimit(i); + if(start != end) // no empty runs. { - runs[i] = font.layoutGlyphVector - ( frc, string.toCharArray(), - runIndices[i][0], runIndices[i][1], - ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT : - Font.LAYOUT_RIGHT_TO_LEFT ); - } + GlyphVector gv = font.layoutGlyphVector(frc, + string, start, end, + ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT + : Font.LAYOUT_RIGHT_TO_LEFT ); + runs[i] = new Run(gv, font, start, end); + } } Bidi.reorderVisually( table, 0, runs, 0, runs.length ); + // Clean up null runs. + ArrayList cleaned = new ArrayList(rc); + for (int i = 0; i < rc; i++) + { + if (runs[i] != null) + cleaned.add(runs[i]); + } + runs = new Run[cleaned.size()]; + runs = (Run[]) cleaned.toArray(runs); } else { - runs = new GlyphVector[ 1 ]; - runIndices = new int[1][2]; - runIndices[0][0] = 0; - runIndices[0][1] = string.length(); - runs[ 0 ] = font.layoutGlyphVector( frc, string.toCharArray(), - 0, string.length(), - leftToRight ? - Font.LAYOUT_LEFT_TO_RIGHT : - Font.LAYOUT_RIGHT_TO_LEFT ); + GlyphVector gv = font.layoutGlyphVector( frc, string, offset, length, + leftToRight ? Font.LAYOUT_LEFT_TO_RIGHT + : Font.LAYOUT_RIGHT_TO_LEFT ); + Run run = new Run(gv, font, 0, length); + runs = new Run[]{ run }; } setCharIndices(); + setupMappings(); + layoutRuns(); } - public TextLayout (String string, Map attributes, FontRenderContext frc) + public TextLayout (String string, + Map<? extends AttributedCharacterIterator.Attribute, ?> attributes, + FontRenderContext frc) { this( string, new Font( attributes ), frc ); } @@ -165,7 +264,6 @@ public final class TextLayout implements Cloneable */ TextLayout(TextLayout t, int startIndex, int endIndex) { - font = t.font; frc = t.frc; boundsCache = null; lm = t.lm; @@ -173,30 +271,35 @@ public final class TextLayout implements Cloneable if( endIndex > t.getCharacterCount() ) endIndex = t.getCharacterCount(); - string = t.string.substring( startIndex, endIndex ); + string = t.string; + offset = startIndex + offset; + length = endIndex - startIndex; int startingRun = t.charIndices[startIndex][0]; int nRuns = 1 + t.charIndices[endIndex - 1][0] - startingRun; - runIndices = new int[ nRuns ][2]; - runs = new GlyphVector[ nRuns ]; + runs = new Run[nRuns]; for( int i = 0; i < nRuns; i++ ) { - GlyphVector run = t.runs[ i + startingRun ]; + Run run = t.runs[i + startingRun]; + GlyphVector gv = run.glyphVector; + Font font = run.font; // Copy only the relevant parts of the first and last runs. int beginGlyphIndex = (i > 0) ? 0 : t.charIndices[startIndex][1]; - int numEntries = ( i < nRuns - 1) ? run.getNumGlyphs() : + int numEntries = ( i < nRuns - 1) ? gv.getNumGlyphs() : 1 + t.charIndices[endIndex - 1][1] - beginGlyphIndex; - int[] codes = run.getGlyphCodes(beginGlyphIndex, numEntries, null); - runs[ i ] = font.createGlyphVector( frc, codes ); - runIndices[ i ][0] = t.runIndices[i + startingRun][0] - startIndex; - runIndices[ i ][1] = t.runIndices[i + startingRun][1] - startIndex; + int[] codes = gv.getGlyphCodes(beginGlyphIndex, numEntries, null); + gv = font.createGlyphVector(frc, codes); + runs[i] = new Run(gv, font, run.runStart - startIndex, + run.runEnd - startIndex); } - runIndices[ nRuns - 1 ][1] = endIndex - 1; + runs[nRuns - 1].runEnd = endIndex - 1; setCharIndices(); + setupMappings(); determineWhiteSpace(); + layoutRuns(); } private void setCharIndices() @@ -207,16 +310,53 @@ public final class TextLayout implements Cloneable for(int run = 0; run < runs.length; run++) { currentChar = -1; - for( int gi = 0; gi < runs[ run ].getNumGlyphs(); gi++) - { - if( runs[ run ].getGlyphCharIndex( gi ) != currentChar ) - { - charIndices[ i ][0] = run; - charIndices[ i ][1] = gi; - currentChar = runs[ run ].getGlyphCharIndex( gi ); - i++; - } - } + Run current = runs[run]; + GlyphVector gv = current.glyphVector; + for( int gi = 0; gi < gv.getNumGlyphs(); gi++) + { + if( gv.getGlyphCharIndex( gi ) != currentChar ) + { + charIndices[ i ][0] = run; + charIndices[ i ][1] = gi; + currentChar = gv.getGlyphCharIndex( gi ); + i++; + } + } + } + } + + /** + * Initializes the logicalToVisual and visualToLogial maps. + */ + private void setupMappings() + { + int numChars = getCharacterCount(); + logicalToVisual = new int[numChars]; + visualToLogical = new int[numChars]; + int lIndex = 0; + int vIndex = 0; + // We scan the runs in visual order and set the mappings accordingly. + for (int i = 0; i < runs.length; i++) + { + Run run = runs[i]; + if (run.isLeftToRight()) + { + for (lIndex = run.runStart; lIndex < run.runEnd; lIndex++) + { + logicalToVisual[lIndex] = vIndex; + visualToLogical[vIndex] = lIndex; + vIndex++; + } + } + else + { + for (lIndex = run.runEnd - 1; lIndex >= run.runStart; lIndex--) + { + logicalToVisual[lIndex] = vIndex; + visualToLogical[vIndex] = lIndex; + vIndex++; + } + } } } @@ -253,11 +393,11 @@ public final class TextLayout implements Cloneable private void getStringProperties() { boolean gotDirection = false; - int i = 0; - + int i = offset; + int endOffs = offset + length; leftToRight = true; - while( i < string.length() && !gotDirection ) - switch( Character.getDirectionality( string.charAt( i++ ) ) ) + while( i < endOffs && !gotDirection ) + switch( Character.getDirectionality(string[i++]) ) { case Character.DIRECTIONALITY_LEFT_TO_RIGHT: case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: @@ -280,28 +420,30 @@ public final class TextLayout implements Cloneable { // Determine if there's whitespace in the thing. // Ignore trailing chars. - int i = string.length() - 1; + int i = offset + length - 1; hasWhitespace = false; - while( i >= 0 && Character.isWhitespace( string.charAt(i) ) ) + while( i >= offset && Character.isWhitespace( string[i] ) ) i--; // Check the remaining chars - while( i >= 0 ) - if( Character.isWhitespace( string.charAt(i--) ) ) + while( i >= offset ) + if( Character.isWhitespace( string[i--] ) ) hasWhitespace = true; } protected Object clone () { - return new TextLayout( string, font, frc ); + return new TextLayout( this, 0, length); } public void draw (Graphics2D g2, float x, float y) { for(int i = 0; i < runs.length; i++) { - g2.drawGlyphVector(runs[i], x, y); - Rectangle2D r = runs[i].getLogicalBounds(); - x += r.getWidth(); + Run run = runs[i]; + GlyphVector gv = run.glyphVector; + g2.drawGlyphVector(gv, x, y); + Rectangle2D r = gv.getLogicalBounds(); + x += r.getWidth(); } } @@ -326,9 +468,16 @@ public final class TextLayout implements Cloneable public float getAdvance () { - float totalAdvance = 0f; - for(int i = 0; i < runs.length; i++) - totalAdvance += runs[i].getLogicalBounds().getWidth(); + if (totalAdvance == -1F) + { + totalAdvance = 0f; + for(int i = 0; i < runs.length; i++) + { + Run run = runs[i]; + GlyphVector gv = run.glyphVector; + totalAdvance += gv.getLogicalBounds().getWidth(); + } + } return totalAdvance; } @@ -363,27 +512,33 @@ public final class TextLayout implements Cloneable double advance = 0; for( int i = 0; i < ri; i++ ) - advance += runs[i].getLogicalBounds().getWidth(); + { + Run run = runs[i]; + GlyphVector gv = run.glyphVector; + advance += gv.getLogicalBounds().getWidth(); + } for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ ) { + Run run = runs[i]; + GlyphVector gv = run.glyphVector; int dg; if( i == charIndices[ secondEndpoint - 1 ][0] ) dg = charIndices[ secondEndpoint - 1][1]; else - dg = runs[i].getNumGlyphs() - 1; + dg = gv.getNumGlyphs() - 1; for( int j = 0; j <= dg; j++ ) { - Rectangle2D r2 = (runs[i].getGlyphVisualBounds( j )). + Rectangle2D r2 = (gv.getGlyphVisualBounds( j )). getBounds2D(); - Point2D p = runs[i].getGlyphPosition( j ); + Point2D p = gv.getGlyphPosition( j ); r2.setRect( advance + r2.getX(), r2.getY(), r2.getWidth(), r2.getHeight() ); gp.append(r2, false); } - advance += runs[i].getLogicalBounds().getWidth(); + advance += gv.getLogicalBounds().getWidth(); } return gp; } @@ -397,46 +552,138 @@ public final class TextLayout implements Cloneable public float[] getCaretInfo (TextHitInfo hit) { - return getCaretInfo(hit, getBounds()); + return getCaretInfo(hit, getNaturalBounds()); } public float[] getCaretInfo (TextHitInfo hit, Rectangle2D bounds) - throws NotImplementedException { - throw new Error ("not implemented"); + float[] info = new float[2]; + int index = hit.getCharIndex(); + boolean leading = hit.isLeadingEdge(); + // For the boundary cases we return the boundary runs. + Run run; + + if (index >= length) + { + info[0] = getAdvance(); + info[1] = 0; + } + else + { + if (index < 0) + { + run = runs[0]; + index = 0; + leading = true; + } + else + run = findRunAtIndex(index); + + int glyphIndex = index - run.runStart; + Shape glyphBounds = run.glyphVector.getGlyphLogicalBounds(glyphIndex); + Rectangle2D glyphRect = glyphBounds.getBounds2D(); + if (isVertical()) + { + if (leading) + info[0] = (float) glyphRect.getMinY(); + else + info[0] = (float) glyphRect.getMaxY(); + } + else + { + if (leading) + info[0] = (float) glyphRect.getMinX(); + else + info[0] = (float) glyphRect.getMaxX(); + } + info[0] += run.location; + info[1] = run.font.getItalicAngle(); + } + return info; } - public Shape getCaretShape (TextHitInfo hit) + public Shape getCaretShape(TextHitInfo hit) { - return getCaretShape( hit, getBounds() ); + return getCaretShape(hit, getBounds()); } - public Shape getCaretShape (TextHitInfo hit, Rectangle2D bounds) - throws NotImplementedException + public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds) { - throw new Error ("not implemented"); + // TODO: Handle vertical shapes somehow. + float[] info = getCaretInfo(hit); + float x1 = info[0]; + float y1 = (float) bounds.getMinY(); + float x2 = info[0]; + float y2 = (float) bounds.getMaxY(); + if (info[1] != 0) + { + // Shift x1 and x2 according to the slope. + x1 -= y1 * info[1]; + x2 -= y2 * info[1]; + } + GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 2); + path.moveTo(x1, y1); + path.lineTo(x2, y2); + return path; } - public Shape[] getCaretShapes (int offset) + public Shape[] getCaretShapes(int offset) { - return getCaretShapes( offset, getBounds() ); + return getCaretShapes(offset, getNaturalBounds()); } - public Shape[] getCaretShapes (int offset, Rectangle2D bounds) - throws NotImplementedException + public Shape[] getCaretShapes(int offset, Rectangle2D bounds) { - throw new Error ("not implemented"); + return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY); + } + + public Shape[] getCaretShapes(int offset, Rectangle2D bounds, + CaretPolicy policy) + { + // The RI returns a 2-size array even when there's only one + // shape in it. + Shape[] carets = new Shape[2]; + TextHitInfo hit1 = TextHitInfo.afterOffset(offset); + int caretHit1 = hitToCaret(hit1); + TextHitInfo hit2 = hit1.getOtherHit(); + int caretHit2 = hitToCaret(hit2); + if (caretHit1 == caretHit2) + { + carets[0] = getCaretShape(hit1); + carets[1] = null; // The RI returns null in this seldom case. + } + else + { + Shape caret1 = getCaretShape(hit1); + Shape caret2 = getCaretShape(hit2); + TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this); + if (strong == hit1) + { + carets[0] = caret1; + carets[1] = caret2; + } + else + { + carets[0] = caret2; + carets[1] = caret1; + } + } + return carets; } public int getCharacterCount () { - return string.length(); + return length; } public byte getCharacterLevel (int index) - throws NotImplementedException { - throw new Error ("not implemented"); + byte level; + if( bidi == null ) + level = 0; + else + level = (byte) bidi.getLevelAt(index); + return level; } public float getDescent () @@ -481,19 +728,21 @@ public final class TextLayout implements Cloneable double advance = 0; for( int i = 0; i < ri; i++ ) - advance += runs[i].getLogicalBounds().getWidth(); + advance += runs[i].glyphVector.getLogicalBounds().getWidth(); for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ ) { + Run run = runs[i]; + GlyphVector gv = run.glyphVector; int dg; // last index in this run to use. if( i == charIndices[ secondEndpoint - 1 ][0] ) dg = charIndices[ secondEndpoint - 1][1]; else - dg = runs[i].getNumGlyphs() - 1; + dg = gv.getNumGlyphs() - 1; for(; gi <= dg; gi++ ) { - Rectangle2D r2 = (runs[i].getGlyphLogicalBounds( gi )). + Rectangle2D r2 = (gv.getGlyphLogicalBounds( gi )). getBounds2D(); if( r == null ) r = r2; @@ -502,7 +751,7 @@ public final class TextLayout implements Cloneable } gi = 0; // reset glyph index into run for next run. - advance += runs[i].getLogicalBounds().getWidth(); + advance += gv.getLogicalBounds().getWidth(); } return r; @@ -510,33 +759,137 @@ public final class TextLayout implements Cloneable public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint, TextHitInfo secondEndpoint) - throws NotImplementedException { - throw new Error ("not implemented"); + // Check parameters. + checkHitInfo(firstEndpoint); + checkHitInfo(secondEndpoint); + + // Convert to visual and order correctly. + int start = hitToCaret(firstEndpoint); + int end = hitToCaret(secondEndpoint); + if (start > end) + { + // Swap start and end so that end >= start. + int temp = start; + start = end; + end = temp; + } + + // Now walk through the visual indices and mark the included pieces. + boolean[] include = new boolean[length]; + for (int i = start; i < end; i++) + { + include[visualToLogical[i]] = true; + } + + // Count included runs. + int numRuns = 0; + boolean in = false; + for (int i = 0; i < length; i++) + { + if (include[i] != in) // At each run in/out point we toggle the in var. + { + in = ! in; + if (in) // At each run start we count up. + numRuns++; + } + } + + // Put together the ranges array. + int[] ranges = new int[numRuns * 2]; + int index = 0; + in = false; + for (int i = 0; i < length; i++) + { + if (include[i] != in) + { + ranges[index] = i; + index++; + in = ! in; + } + } + // If the last run ends at the very end, include that last bit too. + if (in) + ranges[index] = length; + + return ranges; + } + + public TextHitInfo getNextLeftHit(int offset) + { + return getNextLeftHit(offset, DEFAULT_CARET_POLICY); } - public TextHitInfo getNextLeftHit (int offset) - throws NotImplementedException + public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy) { - throw new Error ("not implemented"); + if (policy == null) + throw new IllegalArgumentException("Null policy not allowed"); + if (offset < 0 || offset > length) + throw new IllegalArgumentException("Offset out of bounds"); + + TextHitInfo hit1 = TextHitInfo.afterOffset(offset); + TextHitInfo hit2 = hit1.getOtherHit(); + + TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this); + TextHitInfo next = getNextLeftHit(strong); + TextHitInfo ret = null; + if (next != null) + { + TextHitInfo next2 = getVisualOtherHit(next); + ret = policy.getStrongCaret(next2, next, this); + } + return ret; } public TextHitInfo getNextLeftHit (TextHitInfo hit) - throws NotImplementedException { - throw new Error ("not implemented"); + checkHitInfo(hit); + int index = hitToCaret(hit); + TextHitInfo next = null; + if (index != 0) + { + index--; + next = caretToHit(index); + } + return next; } - public TextHitInfo getNextRightHit (int offset) - throws NotImplementedException + public TextHitInfo getNextRightHit(int offset) { - throw new Error ("not implemented"); + return getNextRightHit(offset, DEFAULT_CARET_POLICY); } - public TextHitInfo getNextRightHit (TextHitInfo hit) - throws NotImplementedException + public TextHitInfo getNextRightHit(int offset, CaretPolicy policy) { - throw new Error ("not implemented"); + if (policy == null) + throw new IllegalArgumentException("Null policy not allowed"); + if (offset < 0 || offset > length) + throw new IllegalArgumentException("Offset out of bounds"); + + TextHitInfo hit1 = TextHitInfo.afterOffset(offset); + TextHitInfo hit2 = hit1.getOtherHit(); + + TextHitInfo next = getNextRightHit(policy.getStrongCaret(hit1, hit2, this)); + TextHitInfo ret = null; + if (next != null) + { + TextHitInfo next2 = getVisualOtherHit(next); + ret = policy.getStrongCaret(next2, next, this); + } + return ret; + } + + public TextHitInfo getNextRightHit(TextHitInfo hit) + { + checkHitInfo(hit); + int index = hitToCaret(hit); + TextHitInfo next = null; + if (index < length) + { + index++; + next = caretToHit(index); + } + return next; } public Shape getOutline (AffineTransform tx) @@ -545,8 +898,9 @@ public final class TextLayout implements Cloneable GeneralPath gp = new GeneralPath(); for(int i = 0; i < runs.length; i++) { - gp.append( runs[i].getOutline( x, 0f ), false ); - Rectangle2D r = runs[i].getLogicalBounds(); + GlyphVector gv = runs[i].glyphVector; + gp.append( gv.getOutline( x, 0f ), false ); + Rectangle2D r = gv.getLogicalBounds(); x += r.getWidth(); } if( tx != null ) @@ -562,27 +916,28 @@ public final class TextLayout implements Cloneable return 0f; // No trailing whitespace - if( !Character.isWhitespace( string.charAt( string.length() -1 ) ) ) + if( !Character.isWhitespace( string[offset + length - 1]) ) return getAdvance(); // Get length of all runs up to the last for(int i = 0; i < runs.length - 1; i++) - totalAdvance += runs[i].getLogicalBounds().getWidth(); + totalAdvance += runs[i].glyphVector.getLogicalBounds().getWidth(); - int lastRun = runIndices[ runs.length - 1 ][0]; - int j = string.length() - 1; - while( j >= lastRun && Character.isWhitespace( string.charAt( j ) ) ) j--; + int lastRun = runs[runs.length - 1].runStart; + int j = length - 1; + while( j >= lastRun && Character.isWhitespace( string[j] ) ) j--; if( j < lastRun ) return totalAdvance; // entire last run is whitespace int lastNonWSChar = j - lastRun; j = 0; - while( runs[ runs.length - 1 ].getGlyphCharIndex( j ) + while( runs[ runs.length - 1 ].glyphVector.getGlyphCharIndex( j ) <= lastNonWSChar ) { - totalAdvance += runs[ runs.length - 1 ].getGlyphLogicalBounds( j ). - getBounds2D().getWidth(); + totalAdvance += runs[ runs.length - 1 ].glyphVector + .getGlyphLogicalBounds( j ) + .getBounds2D().getWidth(); j ++; } @@ -599,15 +954,129 @@ public final class TextLayout implements Cloneable public Shape getVisualHighlightShape (TextHitInfo firstEndpoint, TextHitInfo secondEndpoint, Rectangle2D bounds) - throws NotImplementedException { - throw new Error ("not implemented"); + GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + Shape caret1 = getCaretShape(firstEndpoint, bounds); + path.append(caret1, false); + Shape caret2 = getCaretShape(secondEndpoint, bounds); + path.append(caret2, false); + // Append left (top) bounds to selection if necessary. + int c1 = hitToCaret(firstEndpoint); + int c2 = hitToCaret(secondEndpoint); + if (c1 == 0 || c2 == 0) + { + path.append(left(bounds), false); + } + // Append right (bottom) bounds if necessary. + if (c1 == length || c2 == length) + { + path.append(right(bounds), false); + } + return path.getBounds2D(); + } + + /** + * Returns the shape that makes up the left (top) edge of this text layout. + * + * @param b the bounds + * + * @return the shape that makes up the left (top) edge of this text layout + */ + private Shape left(Rectangle2D b) + { + GeneralPath left = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + left.append(getCaretShape(TextHitInfo.beforeOffset(0)), false); + if (isVertical()) + { + float y = (float) b.getMinY(); + left.append(new Line2D.Float((float) b.getMinX(), y, + (float) b.getMaxX(), y), false); + } + else + { + float x = (float) b.getMinX(); + left.append(new Line2D.Float(x, (float) b.getMinY(), + x, (float) b.getMaxY()), false); + } + return left.getBounds2D(); + } + + /** + * Returns the shape that makes up the right (bottom) edge of this text + * layout. + * + * @param b the bounds + * + * @return the shape that makes up the right (bottom) edge of this text + * layout + */ + private Shape right(Rectangle2D b) + { + GeneralPath right = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + right.append(getCaretShape(TextHitInfo.afterOffset(length)), false); + if (isVertical()) + { + float y = (float) b.getMaxY(); + right.append(new Line2D.Float((float) b.getMinX(), y, + (float) b.getMaxX(), y), false); + } + else + { + float x = (float) b.getMaxX(); + right.append(new Line2D.Float(x, (float) b.getMinY(), + x, (float) b.getMaxY()), false); + } + return right.getBounds2D(); } public TextHitInfo getVisualOtherHit (TextHitInfo hit) - throws NotImplementedException { - throw new Error ("not implemented"); + checkHitInfo(hit); + int hitIndex = hit.getCharIndex(); + + int index; + boolean leading; + if (hitIndex == -1 || hitIndex == length) + { + // Boundary case. + int visual; + if (isLeftToRight() == (hitIndex == -1)) + visual = 0; + else + visual = length - 1; + index = visualToLogical[visual]; + if (isLeftToRight() == (hitIndex == -1)) + leading = isCharacterLTR(index); // LTR. + else + leading = ! isCharacterLTR(index); // RTL. + } + else + { + // Normal case. + int visual = logicalToVisual[hitIndex]; + boolean b; + if (isCharacterLTR(hitIndex) == hit.isLeadingEdge()) + { + visual--; + b = false; + } + else + { + visual++; + b = true; + } + if (visual >= 0 && visual < length) + { + index = visualToLogical[visual]; + leading = b == isLeftToRight(); + } + else + { + index = b == isLeftToRight() ? length : -1; + leading = index == length; + } + } + return leading ? TextHitInfo.leading(index) : TextHitInfo.trailing(index); } /** @@ -622,16 +1091,19 @@ public final class TextLayout implements Cloneable int nglyphs = 0; // # of whitespace chars // determine last non-whitespace char. - int lastNWS = string.length() - 1; - while( Character.isWhitespace( string.charAt( lastNWS ) ) ) lastNWS--; + int lastNWS = offset + length - 1; + while( Character.isWhitespace( string[lastNWS] ) ) lastNWS--; // locations of the glyphs. - int[] wsglyphs = new int[string.length() * 10]; + int[] wsglyphs = new int[length * 10]; for(int run = 0; run < runs.length; run++ ) - for(int i = 0; i < runs[run].getNumGlyphs(); i++ ) + { + Run current = runs[run]; + for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ ) { - int cindex = runIndices[run][0] + runs[run].getGlyphCharIndex( i ); - if( Character.isWhitespace( string.charAt( cindex ) ) ) + int cindex = current.runStart + + current.glyphVector.getGlyphCharIndex( i ); + if( Character.isWhitespace( string[cindex] ) ) // && cindex < lastNWS ) { wsglyphs[ nglyphs * 2 ] = run; @@ -639,34 +1111,144 @@ public final class TextLayout implements Cloneable nglyphs++; } } - + } deltaW = deltaW / nglyphs; // Change in width per whitespace glyph double w = 0; int cws = 0; // Shift all characters for(int run = 0; run < runs.length; run++ ) - for(int i = 0; i < runs[ run ].getNumGlyphs(); i++ ) - { - if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i ) - { - cws++; // update 'current whitespace' - w += deltaW; // increment the shift - } - Point2D p = runs[ run ].getGlyphPosition( i ); - p.setLocation( p.getX() + w, p.getY() ); - runs[ run ].setGlyphPosition( i, p ); - } + { + Run current = runs[run]; + for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ ) + { + if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i ) + { + cws++; // update 'current whitespace' + w += deltaW; // increment the shift + } + Point2D p = current.glyphVector.getGlyphPosition( i ); + p.setLocation( p.getX() + w, p.getY() ); + current.glyphVector.setGlyphPosition( i, p ); + } + } } public TextHitInfo hitTestChar (float x, float y) { - return hitTestChar(x, y, getBounds()); + return hitTestChar(x, y, getNaturalBounds()); } + /** + * Finds the character hit at the specified point. This 'clips' this + * text layout against the specified <code>bounds</code> rectangle. That + * means that in the case where a point is outside these bounds, this method + * returns the leading edge of the first character or the trailing edge of + * the last character. + * + * @param x the X location to test + * @param y the Y location to test + * @param bounds the bounds to test against + * + * @return the character hit at the specified point + */ public TextHitInfo hitTestChar (float x, float y, Rectangle2D bounds) - throws NotImplementedException { - throw new Error ("not implemented"); + // Check bounds. + if (isVertical()) + { + if (y < bounds.getMinY()) + return TextHitInfo.leading(0); + else if (y > bounds.getMaxY()) + return TextHitInfo.trailing(getCharacterCount() - 1); + } + else + { + if (x < bounds.getMinX()) + return TextHitInfo.leading(0); + else if (x > bounds.getMaxX()) + return TextHitInfo.trailing(getCharacterCount() - 1); + } + + TextHitInfo hitInfo = null; + if (isVertical()) + { + // Search for the run at the location. + // TODO: Perform binary search for maximum efficiency. However, we + // need the run location laid out statically to do that. + int numRuns = runs.length; + Run hitRun = null; + for (int i = 0; i < numRuns && hitRun == null; i++) + { + Run run = runs[i]; + Rectangle2D lBounds = run.glyphVector.getLogicalBounds(); + if (lBounds.getMinY() + run.location <= y + && lBounds.getMaxY() + run.location >= y) + hitRun = run; + } + // Now we have (hopefully) found a run that hits. Now find the + // right character. + if (hitRun != null) + { + GlyphVector gv = hitRun.glyphVector; + for (int i = hitRun.runStart; + i < hitRun.runEnd && hitInfo == null; i++) + { + int gi = i - hitRun.runStart; + Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi) + .getBounds2D(); + if (lBounds.getMinY() + hitRun.location <= y + && lBounds.getMaxY() + hitRun.location >= y) + { + // Found hit. Now check if we are leading or trailing. + boolean leading = true; + if (lBounds.getCenterY() + hitRun.location <= y) + leading = false; + hitInfo = leading ? TextHitInfo.leading(i) + : TextHitInfo.trailing(i); + } + } + } + } + else + { + // Search for the run at the location. + // TODO: Perform binary search for maximum efficiency. However, we + // need the run location laid out statically to do that. + int numRuns = runs.length; + Run hitRun = null; + for (int i = 0; i < numRuns && hitRun == null; i++) + { + Run run = runs[i]; + Rectangle2D lBounds = run.glyphVector.getLogicalBounds(); + if (lBounds.getMinX() + run.location <= x + && lBounds.getMaxX() + run.location >= x) + hitRun = run; + } + // Now we have (hopefully) found a run that hits. Now find the + // right character. + if (hitRun != null) + { + GlyphVector gv = hitRun.glyphVector; + for (int i = hitRun.runStart; + i < hitRun.runEnd && hitInfo == null; i++) + { + int gi = i - hitRun.runStart; + Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi) + .getBounds2D(); + if (lBounds.getMinX() + hitRun.location <= x + && lBounds.getMaxX() + hitRun.location >= x) + { + // Found hit. Now check if we are leading or trailing. + boolean leading = true; + if (lBounds.getCenterX() + hitRun.location <= x) + leading = false; + hitInfo = leading ? TextHitInfo.leading(i) + : TextHitInfo.trailing(i); + } + } + } + } + return hitInfo; } public boolean isLeftToRight () @@ -680,18 +1262,127 @@ public final class TextLayout implements Cloneable } public int hashCode () - throws NotImplementedException { - throw new Error ("not implemented"); + // This is implemented in sync to equals(). + if (hash == 0 && runs.length > 0) + { + hash = runs.length; + for (int i = 0; i < runs.length; i++) + hash ^= runs[i].glyphVector.hashCode(); + } + return hash; } public String toString () { - return "TextLayout [string:"+string+", Font:"+font+" Rendercontext:"+ + return "TextLayout [string:"+ new String(string, offset, length) + +" Rendercontext:"+ frc+"]"; } /** + * Returns the natural bounds of that text layout. This is made up + * of the ascent plus descent and the text advance. + * + * @return the natural bounds of that text layout + */ + private Rectangle2D getNaturalBounds() + { + if (naturalBounds == null) + naturalBounds = new Rectangle2D.Float(0.0F, -getAscent(), getAdvance(), + getAscent() + getDescent()); + return naturalBounds; + } + + private void checkHitInfo(TextHitInfo hit) + { + if (hit == null) + throw new IllegalArgumentException("Null hit info not allowed"); + int index = hit.getInsertionIndex(); + if (index < 0 || index > length) + throw new IllegalArgumentException("Hit index out of range"); + } + + private int hitToCaret(TextHitInfo hit) + { + int index = hit.getCharIndex(); + int ret; + if (index < 0) + ret = isLeftToRight() ? 0 : length; + else if (index >= length) + ret = isLeftToRight() ? length : 0; + else + { + ret = logicalToVisual[index]; + if (hit.isLeadingEdge() != isCharacterLTR(index)) + ret++; + } + return ret; + } + + private TextHitInfo caretToHit(int index) + { + TextHitInfo hit; + if (index == 0 || index == length) + { + if ((index == length) == isLeftToRight()) + hit = TextHitInfo.leading(length); + else + hit = TextHitInfo.trailing(-1); + } + else + { + int logical = visualToLogical[index]; + boolean leading = isCharacterLTR(logical); // LTR. + hit = leading ? TextHitInfo.leading(logical) + : TextHitInfo.trailing(logical); + } + return hit; + } + + private boolean isCharacterLTR(int index) + { + byte level = getCharacterLevel(index); + return (level & 1) == 0; + } + + /** + * Finds the run that holds the specified (logical) character index. This + * returns <code>null</code> when the index is not inside the range. + * + * @param index the index of the character to find + * + * @return the run that holds the specified character + */ + private Run findRunAtIndex(int index) + { + Run found = null; + // TODO: Can we do better than linear searching here? + for (int i = 0; i < runs.length && found == null; i++) + { + Run run = runs[i]; + if (run.runStart <= index && run.runEnd > index) + found = run; + } + return found; + } + + /** + * Computes the layout locations for each run. + */ + private void layoutRuns() + { + float loc = 0.0F; + float lastWidth = 0.0F; + for (int i = 0; i < runs.length; i++) + { + runs[i].location = loc; + Rectangle2D bounds = runs[i].glyphVector.getLogicalBounds(); + loc += isVertical() ? bounds.getHeight() : bounds.getWidth(); + } + } + + /** * Inner class describing a caret policy */ public static class CaretPolicy @@ -703,9 +1394,25 @@ public final class TextLayout implements Cloneable public TextHitInfo getStrongCaret(TextHitInfo hit1, TextHitInfo hit2, TextLayout layout) - throws NotImplementedException { - throw new Error ("not implemented"); + byte l1 = layout.getCharacterLevel(hit1.getCharIndex()); + byte l2 = layout.getCharacterLevel(hit2.getCharIndex()); + TextHitInfo strong; + if (l1 == l2) + { + if (hit2.isLeadingEdge() && ! hit1.isLeadingEdge()) + strong = hit2; + else + strong = hit1; + } + else + { + if (l1 < l2) + strong = hit1; + else + strong = hit2; + } + return strong; } } } diff --git a/libjava/classpath/java/awt/geom/AffineTransform.java b/libjava/classpath/java/awt/geom/AffineTransform.java index 55b6883553d..5bc51ddee81 100644 --- a/libjava/classpath/java/awt/geom/AffineTransform.java +++ b/libjava/classpath/java/awt/geom/AffineTransform.java @@ -1401,10 +1401,10 @@ public class AffineTransform implements Cloneable, Serializable * documented, but appears to be the same as: * <pre> * long l = Double.doubleToLongBits(getScaleX()); - * l = l * 31 + Double.doubleToLongBits(getShearY()); * l = l * 31 + Double.doubleToLongBits(getShearX()); - * l = l * 31 + Double.doubleToLongBits(getScaleY()); * l = l * 31 + Double.doubleToLongBits(getTranslateX()); + * l = l * 31 + Double.doubleToLongBits(getShearY()); + * l = l * 31 + Double.doubleToLongBits(getScaleY()); * l = l * 31 + Double.doubleToLongBits(getTranslateY()); * return (int) ((l >> 32) ^ l); * </pre> @@ -1413,12 +1413,12 @@ public class AffineTransform implements Cloneable, Serializable */ public int hashCode() { - long l = Double.doubleToLongBits(m00); - l = l * 31 + Double.doubleToLongBits(m10); - l = l * 31 + Double.doubleToLongBits(m01); - l = l * 31 + Double.doubleToLongBits(m11); - l = l * 31 + Double.doubleToLongBits(m02); - l = l * 31 + Double.doubleToLongBits(m12); + long l = Double.doubleToLongBits(m00); + l = l * 31 + Double.doubleToLongBits(m01); + l = l * 31 + Double.doubleToLongBits(m02); + l = l * 31 + Double.doubleToLongBits(m10); + l = l * 31 + Double.doubleToLongBits(m11); + l = l * 31 + Double.doubleToLongBits(m12); return (int) ((l >> 32) ^ l); } diff --git a/libjava/classpath/java/awt/geom/Arc2D.java b/libjava/classpath/java/awt/geom/Arc2D.java index eff34a08144..8d5b01cd5bb 100644 --- a/libjava/classpath/java/awt/geom/Arc2D.java +++ b/libjava/classpath/java/awt/geom/Arc2D.java @@ -774,14 +774,9 @@ public abstract class Arc2D extends RectangularShape y = a.getY(); w = a.getWidth(); h = a.getHeight(); - double start = a.getAngleStart() * (Math.PI / 180); - double extent = a.getAngleExtent() * (Math.PI / 180); + double start = Math.toRadians(a.getAngleStart()); + double extent = Math.toRadians(a.getAngleExtent()); - if (extent < 0) - { - extent = -extent; - start = 2 * Math.PI - extent + start; - } this.start = start; this.extent = extent; @@ -790,11 +785,11 @@ public abstract class Arc2D extends RectangularShape limit = -1; else if (extent == 0) limit = type; - else if (extent <= Math.PI / 2.0) + else if (Math.abs(extent) <= Math.PI / 2.0) limit = type + 1; - else if (extent <= Math.PI) + else if (Math.abs(extent) <= Math.PI) limit = type + 2; - else if (extent <= 3.0 * (Math.PI / 2.0)) + else if (Math.abs(extent) <= 3.0 * (Math.PI / 2.0)) limit = type + 3; else limit = type + 4; @@ -909,9 +904,20 @@ public abstract class Arc2D extends RectangularShape double kappa = (Math.sqrt(2.0) - 1.0) * (4.0 / 3.0); double quad = (Math.PI / 2.0); - double curr_begin = start + (current - 1) * quad; - double curr_extent = Math.min((start + extent) - curr_begin, quad); - double portion_of_a_quadrant = curr_extent / quad; + double curr_begin; + double curr_extent; + if (extent > 0) + { + curr_begin = start + (current - 1) * quad; + curr_extent = Math.min((start + extent) - curr_begin, quad); + } + else + { + curr_begin = start - (current - 1) * quad; + curr_extent = Math.max((start + extent) - curr_begin, -quad); + } + + double portion_of_a_quadrant = Math.abs(curr_extent / quad); double x0 = xmid + rx * Math.cos(curr_begin); double y0 = ymid - ry * Math.sin(curr_begin); @@ -932,7 +938,11 @@ public abstract class Arc2D extends RectangularShape // will *subtract* the y value of this control vector from our first // point. cvec[0] = 0; - cvec[1] = len; + if (extent > 0) + cvec[1] = len; + else + cvec[1] = -len; + trans.scale(rx, ry); trans.rotate(angle); trans.transform(cvec, 0, cvec, 0, 1); @@ -942,7 +952,11 @@ public abstract class Arc2D extends RectangularShape // control vector #2 would, ideally, be sticking out and to the // right, in a first quadrant arc segment. again, subtraction of y. cvec[0] = 0; - cvec[1] = -len; + if (extent > 0) + cvec[1] = -len; + else + cvec[1] = len; + trans.rotate(curr_extent); trans.transform(cvec, 0, cvec, 0, 1); coords[2] = x1 + cvec[0]; diff --git a/libjava/classpath/java/awt/geom/GeneralPath.java b/libjava/classpath/java/awt/geom/GeneralPath.java index e0ca8e18357..1e9ede5ee67 100644 --- a/libjava/classpath/java/awt/geom/GeneralPath.java +++ b/libjava/classpath/java/awt/geom/GeneralPath.java @@ -86,7 +86,7 @@ public final class GeneralPath implements Shape, Cloneable public static final int WIND_EVEN_ODD = java.awt.geom.PathIterator.WIND_EVEN_ODD; - /** Same constant as {@link PathIterator.WIND_NON_ZERO}. */ + /** Same constant as {@link PathIterator#WIND_NON_ZERO}. */ public static final int WIND_NON_ZERO = java.awt.geom.PathIterator.WIND_NON_ZERO; @@ -140,7 +140,11 @@ public final class GeneralPath implements Shape, Cloneable /** * Constructs a GeneralPath with a specific winding rule * and the default initial capacity (20). - * @param rule the winding rule (WIND_NON_ZERO or WIND_EVEN_ODD) + * @param rule the winding rule ({@link #WIND_NON_ZERO} or + * {@link #WIND_EVEN_ODD}) + * + * @throws IllegalArgumentException if <code>rule</code> is not one of the + * listed values. */ public GeneralPath(int rule) { @@ -151,8 +155,12 @@ public final class GeneralPath implements Shape, Cloneable * Constructs a GeneralPath with a specific winding rule * and the initial capacity. The initial capacity should be * the approximate number of path segments to be used. - * @param rule the winding rule (WIND_NON_ZERO or WIND_EVEN_ODD) + * @param rule the winding rule ({@link #WIND_NON_ZERO} or + * {@link #WIND_EVEN_ODD}) * @param capacity the inital capacity, in path segments + * + * @throws IllegalArgumentException if <code>rule</code> is not one of the + * listed values. */ public GeneralPath(int rule, int capacity) { @@ -169,7 +177,10 @@ public final class GeneralPath implements Shape, Cloneable /** * Constructs a GeneralPath from an arbitrary shape object. * The Shapes PathIterator path and winding rule will be used. - * @param s the shape + * + * @param s the shape (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>shape</code> is <code>null</code>. */ public GeneralPath(Shape s) { @@ -183,6 +194,9 @@ public final class GeneralPath implements Shape, Cloneable /** * Adds a new point to a path. + * + * @param x the x-coordinate. + * @param y the y-coordinate. */ public void moveTo(float x, float y) { @@ -263,6 +277,11 @@ public final class GeneralPath implements Shape, Cloneable * Appends the segments of a Shape to the path. If <code>connect</code> is * true, the new path segments are connected to the existing one with a line. * The winding rule of the Shape is ignored. + * + * @param s the shape (<code>null</code> not permitted). + * @param connect whether to connect the new shape to the existing path. + * + * @throws NullPointerException if <code>s</code> is <code>null</code>. */ public void append(Shape s, boolean connect) { @@ -276,7 +295,7 @@ public final class GeneralPath implements Shape, Cloneable * PathIterator#SEG_LINETO} segment. * * @param iter the PathIterator specifying which segments shall be - * appended. + * appended (<code>null</code> not permitted). * * @param connect <code>true</code> for substituting the initial * {@link PathIterator#SEG_MOVETO} segment by a {@link @@ -327,6 +346,8 @@ public final class GeneralPath implements Shape, Cloneable /** * Returns the path’s current winding rule. + * + * @return {@link #WIND_EVEN_ODD} or {@link #WIND_NON_ZERO}. */ public int getWindingRule() { @@ -338,6 +359,8 @@ public final class GeneralPath implements Shape, Cloneable * considered ’inside’ or ’outside’ the path * on drawing. Valid rules are WIND_EVEN_ODD for an even-odd winding rule, * or WIND_NON_ZERO for a non-zero winding rule. + * + * @param rule the rule ({@link #WIND_EVEN_ODD} or {@link #WIND_NON_ZERO}). */ public void setWindingRule(int rule) { @@ -348,6 +371,8 @@ public final class GeneralPath implements Shape, Cloneable /** * Returns the current appending point of the path. + * + * @return The point. */ public Point2D getCurrentPoint() { @@ -367,6 +392,8 @@ public final class GeneralPath implements Shape, Cloneable /** * Applies a transform to the path. + * + * @param xform the transform (<code>null</code> not permitted). */ public void transform(AffineTransform xform) { @@ -706,6 +733,8 @@ public final class GeneralPath implements Shape, Cloneable /** * Helper method - ensure the size of the data arrays, * otherwise, reallocate new ones twice the size + * + * @param size the minimum array size. */ private void ensureSize(int size) { diff --git a/libjava/classpath/java/awt/geom/RectangularShape.java b/libjava/classpath/java/awt/geom/RectangularShape.java index 8f66dabf2e7..3ee1615413f 100644 --- a/libjava/classpath/java/awt/geom/RectangularShape.java +++ b/libjava/classpath/java/awt/geom/RectangularShape.java @@ -326,15 +326,12 @@ public abstract class RectangularShape implements Shape, Cloneable /** * Returns a bounding box for this shape, in integer format. Notice that you - * may get a tighter bound with getBounds2D. If the frame is empty, the - * box is the default empty box at the origin. + * may get a tighter bound with getBounds2D. * * @return a bounding box */ public Rectangle getBounds() { - if (isEmpty()) - return new Rectangle(); double x = getX(); double y = getY(); double maxx = Math.ceil(x + getWidth()); diff --git a/libjava/classpath/java/awt/geom/RoundRectangle2D.java b/libjava/classpath/java/awt/geom/RoundRectangle2D.java index ac0e6f8128a..ac4d89fff80 100644 --- a/libjava/classpath/java/awt/geom/RoundRectangle2D.java +++ b/libjava/classpath/java/awt/geom/RoundRectangle2D.java @@ -1,5 +1,5 @@ /* RoundRectangle2D.java -- represents a rectangle with rounded corners - Copyright (C) 2000, 2002, 2003, 2004 Free Software Foundation + Copyright (C) 2000, 2002, 2003, 2004, 2006, Free Software Foundation This file is part of GNU Classpath. @@ -37,7 +37,6 @@ exception statement from your version. */ package java.awt.geom; -import java.util.NoSuchElementException; /** This class implements a rectangle with rounded corners. @@ -46,13 +45,29 @@ import java.util.NoSuchElementException; */ public abstract class RoundRectangle2D extends RectangularShape { - /** Return the arc height of this round rectangle. */ + /** + * Return the arc height of this round rectangle. The arc height and width + * control the roundness of the corners of the rectangle. + * + * @return The arc height. + * + * @see #getArcWidth() + */ public abstract double getArcHeight(); - /** Return the arc width of this round rectangle. */ + /** + * Return the arc width of this round rectangle. The arc width and height + * control the roundness of the corners of the rectangle. + * + * @return The arc width. + * + * @see #getArcHeight() + */ public abstract double getArcWidth(); - /** Set the values of this round rectangle + /** + * Set the values of this round rectangle. + * * @param x The x coordinate * @param y The y coordinate * @param w The width @@ -63,14 +78,16 @@ public abstract class RoundRectangle2D extends RectangularShape public abstract void setRoundRect(double x, double y, double w, double h, double arcWidth, double arcHeight); - /** Create a RoundRectangle2D. This is protected because this class + /** + * Create a RoundRectangle2D. This is protected because this class * is abstract and cannot be instantiated. */ protected RoundRectangle2D() { } - /** Return true if this object contains the specified point. + /** + * Return true if this object contains the specified point. * @param x The x coordinate * @param y The y coordinate */ @@ -106,7 +123,8 @@ public abstract class RoundRectangle2D extends RectangularShape return dx * dx + dy * dy <= 1.0; } - /** Return true if this object contains the specified rectangle + /** + * Return true if this object contains the specified rectangle * @param x The x coordinate * @param y The y coordinate * @param w The width @@ -120,176 +138,185 @@ public abstract class RoundRectangle2D extends RectangularShape && contains(x + w, y)); } - /** Return a new path iterator which iterates over this rectangle. + /** + * Return a new path iterator which iterates over this rectangle. + * * @param at An affine transform to apply to the object */ - public PathIterator getPathIterator(final AffineTransform at) + public PathIterator getPathIterator(final AffineTransform at) { - final double minx = getX(); - final double miny = getY(); - final double maxx = minx + getWidth(); - final double maxy = miny + getHeight(); - final double arcwidth = getArcWidth(); - final double archeight = getArcHeight(); - return new PathIterator() + double arcW = Math.min(getArcWidth(), getWidth()); + double arcH = Math.min(getArcHeight(), getHeight()); + + // check for special cases... + if (arcW <= 0 || arcH <= 0) + { + Rectangle2D r = new Rectangle2D.Double(getX(), getY(), getWidth(), + getHeight()); + return r.getPathIterator(at); + } + else if (arcW >= getWidth() && arcH >= getHeight()) + { + Ellipse2D e = new Ellipse2D.Double(getX(), getY(), getWidth(), + getHeight()); + return e.getPathIterator(at); + } + + // otherwise return the standard case... + return new PathIterator() + { + double x = getX(); + double y = getY(); + double w = getWidth(); + double h = getHeight(); + double arcW = Math.min(getArcWidth(), w); + double arcH = Math.min(getArcHeight(), h); + Arc2D.Double arc = new Arc2D.Double(); + PathIterator corner; + int step = -1; + + public int currentSegment(double[] coords) + { + if (corner != null) // steps 1, 3, 5 and 7 + { + int r = corner.currentSegment(coords); + if (r == SEG_MOVETO) + r = SEG_LINETO; + return r; + } + if (step == -1) + { + // move to the start position + coords[0] = x + w - arcW / 2; + coords[1] = y; + } + else if (step == 0) + { + // top line + coords[0] = x + arcW / 2; + coords[1] = y; + } + else if (step == 2) + { + // left line + coords[0] = x; + coords[1] = y + h - arcH / 2; + } + else if (step == 4) + { + // bottom line + coords[0] = x + w - arcW / 2; + coords[1] = y + h; + } + else if (step == 6) + { + // right line + coords[0] = x + w; + coords[1] = y + arcH / 2; + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return step == -1 ? SEG_MOVETO : SEG_LINETO; + } + + public int currentSegment(float[] coords) { + if (corner != null) // steps 1, 3, 5 and 7 + { + int r = corner.currentSegment(coords); + if (r == SEG_MOVETO) + r = SEG_LINETO; + return r; + } + if (step == -1) + { + // move to the start position + coords[0] = (float) (x + w - arcW / 2); + coords[1] = (float) y; + } + else if (step == 0) + { + // top line + coords[0] = (float) (x + arcW / 2); + coords[1] = (float) y; + } + else if (step == 2) + { + // left line + coords[0] = (float) x; + coords[1] = (float) (y + h - arcH / 2); + } + else if (step == 4) + { + // bottom line + coords[0] = (float) (x + w - arcW / 2); + coords[1] = (float) (y + h); + } + else if (step == 6) + { + // right line + coords[0] = (float) (x + w); + coords[1] = (float) (y + arcH / 2); + } + if (at != null) + at.transform(coords, 0, coords, 0, 1); + return step == -1 ? SEG_MOVETO : SEG_LINETO; + } + + public int getWindingRule() { + return WIND_NON_ZERO; + } + + public boolean isDone() { + return step >= 8; + } + + public void next() { - /** We iterate counterclockwise around the rectangle, starting in the - * upper right. This variable tracks our current point, which - * can be on either side of a given corner. */ - private int current = 0; - - /** Child path iterator, used for corners. */ - private PathIterator corner; - - /** This is used when rendering the corners. We re-use the arc - * for each corner. */ - private Arc2D arc = new Arc2D.Double(); - - /** Temporary array used by getPoint. */ - private double[] temp = new double[2]; - - public int getWindingRule() - { - return WIND_NON_ZERO; - } - - public boolean isDone() - { - return current > 9; - } - - private void getPoint(int val) - { - switch (val) - { - case 0: - case 8: - temp[0] = maxx; - temp[1] = miny + archeight; - break; - case 7: - temp[0] = maxx; - temp[1] = maxy - archeight; - break; - case 6: - temp[0] = maxx - arcwidth; - temp[1] = maxy; - break; - case 5: - temp[0] = minx + arcwidth; - temp[1] = maxy; - break; - case 4: - temp[0] = minx; - temp[1] = maxy - archeight; - break; - case 3: - temp[0] = minx; - temp[1] = miny + archeight; - break; - case 2: - temp[0] = minx + arcwidth; - temp[1] = miny; - break; - case 1: - temp[0] = maxx - arcwidth; - temp[1] = miny; - break; - } - } - - public void next() - { - if (current >= 8) - ++current; - else if (corner != null) - { - // We're iterating through the corner. Work on the child - // iterator; if it finishes, reset and move to the next - // point along the rectangle. - corner.next(); - if (corner.isDone()) - { - corner = null; - ++current; - } - } - else - { - // Make an arc between this point on the rectangle and - // the next one, and then iterate over this arc. - getPoint(current); - double x1 = temp[0]; - double y1 = temp[1]; - getPoint(current + 1); - Rectangle2D.Double r = new Rectangle2D.Double(Math.min(x1, - temp[0]), - Math.min(y1, - temp[1]), - Math.abs(x1 - - temp[0]), - Math.abs(y1 - - temp[1])); - arc.setArc(r, (current >> 1) * 90.0, 90.0, Arc2D.OPEN); - corner = arc.getPathIterator(at); - } - } - - public int currentSegment(float[] coords) - { - if (corner != null) - { - int r = corner.currentSegment(coords); - if (r == SEG_MOVETO) - r = SEG_LINETO; - return r; - } - - if (current < 9) - { - getPoint(current); - coords[0] = (float) temp[0]; - coords[1] = (float) temp[1]; - } - else if (current == 9) - return SEG_CLOSE; - else - throw new NoSuchElementException("rect iterator out of bounds"); - - if (at != null) - at.transform(coords, 0, coords, 0, 1); - return current == 0 ? SEG_MOVETO : SEG_LINETO; - } - - public int currentSegment(double[] coords) - { - if (corner != null) - { - int r = corner.currentSegment(coords); - if (r == SEG_MOVETO) - r = SEG_LINETO; - return r; - } - - if (current < 9) - { - getPoint(current); - coords[0] = temp[0]; - coords[1] = temp[1]; - } - else if (current == 9) - return SEG_CLOSE; - else - throw new NoSuchElementException("rect iterator out of bounds"); - - if (at != null) - at.transform(coords, 0, coords, 0, 1); - return current == 0 ? SEG_MOVETO : SEG_LINETO; - } - }; + if (corner != null) + { + corner.next(); + if (corner.isDone()) + { + corner = null; + step++; + } + } + else + { + step++; + if (step == 1) + { + // create top left corner + arc.setArc(x, y, arcW, arcH, 90, 90, Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + else if (step == 3) + { + // create bottom left corner + arc.setArc(x, y + h - arcH, arcW, arcH, 180, 90, + Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + else if (step == 5) + { + // create bottom right corner + arc.setArc(x + w - arcW, y + h - arcH, arcW, arcH, 270, 90, + Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + else if (step == 7) + { + // create top right corner + arc.setArc(x + w - arcW, y, arcW, arcH, 0, 90, Arc2D.OPEN); + corner = arc.getPathIterator(at); + } + } + } + }; } - /** Return true if the given rectangle intersects this shape. + /** + * Return true if the given rectangle intersects this shape. * @param x The x coordinate * @param y The y coordinate * @param w The width @@ -302,7 +329,8 @@ public abstract class RoundRectangle2D extends RectangularShape || contains(x + w, y)); } - /** Set the boundary of this round rectangle. + /** + * Set the boundary of this round rectangle. * @param x The x coordinate * @param y The y coordinate * @param w The width @@ -314,7 +342,8 @@ public abstract class RoundRectangle2D extends RectangularShape setRoundRect(x, y, w, h, getArcWidth(), getArcHeight()); } - /** Set the values of this round rectangle to be the same as those + /** + * Set the values of this round rectangle to be the same as those * of the argument. * @param rr The round rectangle to copy */ @@ -324,8 +353,10 @@ public abstract class RoundRectangle2D extends RectangularShape rr.getArcWidth(), rr.getArcHeight()); } - /** A subclass of RoundRectangle which keeps its parameters as - * doubles. */ + /** + * A subclass of RoundRectangle which keeps its parameters as + * doubles. + */ public static class Double extends RoundRectangle2D { /** The height of the corner arc. */ @@ -346,12 +377,15 @@ public abstract class RoundRectangle2D extends RectangularShape /** The height of this object. */ public double height; - /** Construct a new instance, with all parameters set to 0. */ + /** + * Construct a new instance, with all parameters set to 0. + */ public Double() { } - /** Construct a new instance with the given arguments. + /** + * Construct a new instance with the given arguments. * @param x The x coordinate * @param y The y coordinate * @param w The width @@ -422,8 +456,10 @@ public abstract class RoundRectangle2D extends RectangularShape } } // class Double - /** A subclass of RoundRectangle which keeps its parameters as - * floats. */ + /** + * A subclass of RoundRectangle which keeps its parameters as + * floats. + */ public static class Float extends RoundRectangle2D { /** The height of the corner arc. */ @@ -444,12 +480,15 @@ public abstract class RoundRectangle2D extends RectangularShape /** The height of this object. */ public float height; - /** Construct a new instance, with all parameters set to 0. */ + /** + * Construct a new instance, with all parameters set to 0. + */ public Float() { } - /** Construct a new instance with the given arguments. + /** + * Construct a new instance with the given arguments. * @param x The x coordinate * @param y The y coordinate * @param w The width @@ -508,6 +547,18 @@ public abstract class RoundRectangle2D extends RectangularShape return width <= 0 || height <= 0; } + /** + * Sets the dimensions for this rounded rectangle. + * + * @param x the x-coordinate of the top left corner. + * @param y the y-coordinate of the top left corner. + * @param w the width of the rectangle. + * @param h the height of the rectangle. + * @param arcWidth the arc width. + * @param arcHeight the arc height. + * + * @see #setRoundRect(double, double, double, double, double, double) + */ public void setRoundRect(float x, float y, float w, float h, float arcWidth, float arcHeight) { diff --git a/libjava/classpath/java/awt/im/InputContext.java b/libjava/classpath/java/awt/im/InputContext.java index 3806736df3d..c81993215e8 100644 --- a/libjava/classpath/java/awt/im/InputContext.java +++ b/libjava/classpath/java/awt/im/InputContext.java @@ -1,5 +1,5 @@ /* InputContext.java -- provides the context for text input - Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -76,6 +76,7 @@ import java.util.Locale; * java.awt.im.spi.InputMethodDescriptor. * * @author Eric Blake (ebb9@email.byu.edu) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) * @see Component#getInputContext() * @see Component#enableInputMethods(boolean) * @since 1.2 @@ -86,7 +87,9 @@ public class InputContext /** * The list of installed input method descriptors. */ - private static final ArrayList descriptors = new ArrayList(); + private static final ArrayList<InputMethodDescriptor> descriptors + = new ArrayList<InputMethodDescriptor>(); + static { Enumeration e; @@ -123,7 +126,7 @@ public class InputContext { if (line.charAt(0) != '#') { - Class c = Class.forName(line); + Class<?> c = Class.forName(line); descriptors.add((InputMethodDescriptor) c.newInstance()); } line = in.readLine().trim(); @@ -143,7 +146,8 @@ public class InputContext private InputMethod im; /** Map of locales to the most recently selected input method. */ - private final HashMap recent = new HashMap(); + private final HashMap<Locale,InputMethod> recent + = new HashMap<Locale,InputMethod>(); /** The list of acceptable character subsets. */ private Character.Subset[] subsets; diff --git a/libjava/classpath/java/awt/im/InputMethodHighlight.java b/libjava/classpath/java/awt/im/InputMethodHighlight.java index 6fbe42fe549..a2ee86d44fc 100644 --- a/libjava/classpath/java/awt/im/InputMethodHighlight.java +++ b/libjava/classpath/java/awt/im/InputMethodHighlight.java @@ -41,6 +41,7 @@ import java.awt.Toolkit; import java.text.Annotation; import java.text.AttributedCharacterIterator; import java.util.Map; +import java.awt.font.TextAttribute; /** * This describes the highlight attributes of text composed in an input method. @@ -95,7 +96,7 @@ public class InputMethodHighlight private final int variation; /** The unmodifiable map of rendering styles. */ - private final Map style; + private final Map<TextAttribute, ?> style; /** * Create an input method highlight style, with variation 0 and null style @@ -134,7 +135,7 @@ public class InputMethodHighlight * @since 1.3 */ public InputMethodHighlight(boolean selected, int state, int variation, - Map style) + Map<TextAttribute, ?> style) { if (state != RAW_TEXT && state != CONVERTED_TEXT) throw new IllegalArgumentException(); @@ -181,7 +182,7 @@ public class InputMethodHighlight * @return the style map * @since 1.3 */ - public Map getStyle() + public Map<TextAttribute, ?> getStyle() { return style; } diff --git a/libjava/classpath/java/awt/im/spi/InputMethodContext.java b/libjava/classpath/java/awt/im/spi/InputMethodContext.java index 17ec4f8f7ee..aed21e8d3b4 100644 --- a/libjava/classpath/java/awt/im/spi/InputMethodContext.java +++ b/libjava/classpath/java/awt/im/spi/InputMethodContext.java @@ -1,5 +1,5 @@ /* InputMethodContext.java -- communication between an input method and client - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -53,6 +53,7 @@ import javax.swing.JFrame; * {@link InputMethod#setInputMethodContext(InputMethodContext)}. * * @author Eric Blake (ebb9@email.byu.edu) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) * @since 1.3 * @status updated to 1.4 */ diff --git a/libjava/classpath/java/awt/image/AffineTransformOp.java b/libjava/classpath/java/awt/image/AffineTransformOp.java index bb4b795231b..849c5b05048 100644 --- a/libjava/classpath/java/awt/image/AffineTransformOp.java +++ b/libjava/classpath/java/awt/image/AffineTransformOp.java @@ -1,6 +1,6 @@ /* AffineTransformOp.java -- This class performs affine transformation between two images or rasters in 2 dimensions. - Copyright (C) 2004 Free Software Foundation + Copyright (C) 2004, 2006 Free Software Foundation This file is part of GNU Classpath. @@ -39,6 +39,7 @@ exception statement from your version. */ package java.awt.image; import java.awt.Graphics2D; +import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; @@ -48,10 +49,14 @@ import java.awt.geom.Rectangle2D; import java.util.Arrays; /** - * This class performs affine transformation between two images or - * rasters in 2 dimensions. + * AffineTransformOp performs matrix-based transformations (translations, + * scales, flips, rotations, and shears). + * + * If interpolation is required, nearest neighbour, bilinear, and bicubic + * methods are available. * * @author Olga Rodimina (rodimina@redhat.com) + * @author Francis Kung (fkung@redhat.com) */ public class AffineTransformOp implements BufferedImageOp, RasterOp { @@ -74,6 +79,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp * * @param xform AffineTransform that will applied to the source image * @param interpolationType type of interpolation used + * @throws ImagingOpException if the transform matrix is noninvertible */ public AffineTransformOp (AffineTransform xform, int interpolationType) { @@ -102,6 +108,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp * * @param xform AffineTransform that will applied to the source image * @param hints rendering hints that will be used during transformation + * @throws ImagingOpException if the transform matrix is noninvertible */ public AffineTransformOp (AffineTransform xform, RenderingHints hints) { @@ -112,185 +119,165 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp } /** - * Creates empty BufferedImage with the size equal to that of the - * transformed image and correct number of bands. The newly created + * Creates a new BufferedImage with the size equal to that of the + * transformed image and the correct number of bands. The newly created * image is created with the specified ColorModel. - * If the ColorModel is equal to null, then image is created - * with the ColorModel of the source image. + * If a ColorModel is not specified, an appropriate ColorModel is used. * - * @param src source image - * @param destCM color model for the destination image - * @return new compatible destination image + * @param src the source image. + * @param destCM color model for the destination image (can be null). + * @return a new compatible destination image. */ public BufferedImage createCompatibleDestImage (BufferedImage src, ColorModel destCM) { + if (destCM != null) + return new BufferedImage(destCM, + createCompatibleDestRaster(src.getRaster()), + src.isAlphaPremultiplied(), null); + + // This behaviour was determined by Mauve testcases, and is compatible + // with the reference implementation + if (src.getType() == BufferedImage.TYPE_INT_ARGB_PRE + || src.getType() == BufferedImage.TYPE_4BYTE_ABGR + || src.getType() == BufferedImage.TYPE_4BYTE_ABGR_PRE) + return new BufferedImage(src.getWidth(), src.getHeight(), src.getType()); - // if destCm is not specified, use color model of the source image - - if (destCM == null) - destCM = src.getColorModel (); - - return new BufferedImage (destCM, - createCompatibleDestRaster (src.getRaster ()), - src.isAlphaPremultiplied (), - null); - + else + return new BufferedImage(src.getWidth(), src.getHeight(), + BufferedImage.TYPE_INT_ARGB); } /** - * Creates empty WritableRaster with the size equal to the transformed - * source raster and correct number of bands + * Creates a new WritableRaster with the size equal to the transformed + * source raster and correct number of bands . * - * @param src source raster - * @throws RasterFormatException if resulting width or height of raster is 0 - * @return new compatible raster + * @param src the source raster. + * @throws RasterFormatException if resulting width or height of raster is 0. + * @return a new compatible raster. */ public WritableRaster createCompatibleDestRaster (Raster src) { - Rectangle rect = (Rectangle) getBounds2D (src); + Rectangle2D rect = getBounds2D(src); - // throw RasterFormatException if resulting width or height of the - // transformed raster is 0 - - if (rect.getWidth () == 0 || rect.getHeight () == 0) + if (rect.getWidth() == 0 || rect.getHeight() == 0) throw new RasterFormatException("width or height is 0"); - return src.createCompatibleWritableRaster ((int) rect.getWidth (), - (int) rect.getHeight ()); + return src.createCompatibleWritableRaster((int) rect.getWidth(), + (int) rect.getHeight()); } /** * Transforms source image using transform specified at the constructor. - * The resulting transformed image is stored in the destination image. + * The resulting transformed image is stored in the destination image if one + * is provided; otherwise a new BufferedImage is created and returned. * * @param src source image * @param dst destination image - * @return transformed source image + * @throws IllegalArgumentException if the source and destination image are + * the same + * @return transformed source image. */ public final BufferedImage filter (BufferedImage src, BufferedImage dst) { - if (dst == src) - throw new IllegalArgumentException ("src image cannot be the same as the dst image"); - - // If the destination image is null, then BufferedImage is - // created with ColorModel of the source image + throw new IllegalArgumentException("src image cannot be the same as " + + "the dst image"); + // If the destination image is null, then use a compatible BufferedImage if (dst == null) - dst = createCompatibleDestImage(src, src.getColorModel ()); - - // FIXME: Must check if color models of src and dst images are the same. - // If it is not, then source image should be converted to color model - // of the destination image + dst = createCompatibleDestImage(src, null); - Graphics2D gr = (Graphics2D) dst.createGraphics (); - gr.setRenderingHints (hints); - gr.drawImage (src, transform, null); + Graphics2D gr = (Graphics2D) dst.createGraphics(); + gr.setRenderingHints(hints); + gr.drawImage(src, transform, null); return dst; - } /** * Transforms source raster using transform specified at the constructor. - * The resulting raster is stored in the destination raster. + * The resulting raster is stored in the destination raster if it is not + * null, otherwise a new raster is created and returned. * * @param src source raster * @param dst destination raster - * @return transformed raster + * @throws IllegalArgumentException if the source and destination are not + * compatible + * @return transformed raster. */ - public final WritableRaster filter (Raster src, WritableRaster dst) + public final WritableRaster filter(Raster src, WritableRaster dst) { + // Initial checks if (dst == src) throw new IllegalArgumentException("src image cannot be the same as" - + " the dst image"); + + " the dst image"); if (dst == null) dst = createCompatibleDestRaster(src); if (src.getNumBands() != dst.getNumBands()) throw new IllegalArgumentException("src and dst must have same number" - + " of bands"); + + " of bands"); - double[] dpts = new double[dst.getWidth() * 2]; - double[] pts = new double[dst.getWidth() * 2]; + // Optimization for rasters that can be represented in the RGB colormodel: + // wrap the rasters in images, and let Cairo do the transformation + if (ColorModel.getRGBdefault().isCompatibleSampleModel(src.getSampleModel()) + && ColorModel.getRGBdefault().isCompatibleSampleModel(dst.getSampleModel())) + { + WritableRaster src2 = Raster.createWritableRaster(src.getSampleModel(), + src.getDataBuffer(), + new Point(src.getMinX(), + src.getMinY())); + BufferedImage iSrc = new BufferedImage(ColorModel.getRGBdefault(), + src2, false, null); + BufferedImage iDst = new BufferedImage(ColorModel.getRGBdefault(), dst, + false, null); + + return filter(iSrc, iDst).getRaster(); + } + + // Otherwise, we need to do the transformation in java code... + // Create arrays to hold all the points + double[] dstPts = new double[dst.getHeight() * dst.getWidth() * 2]; + double[] srcPts = new double[dst.getHeight() * dst.getWidth() * 2]; + + // Populate array with all points in the *destination* raster + int i = 0; for (int x = 0; x < dst.getWidth(); x++) - { - dpts[2 * x] = x + dst.getMinX(); - dpts[2 * x + 1] = x; - } - Rectangle srcbounds = src.getBounds(); - if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) - { - for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++) - { - try { - transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2); - } catch (NoninvertibleTransformException e) { - // Can't happen since the constructor traps this - e.printStackTrace(); - } - - for (int x = 0; x < dst.getWidth(); x++) - { - if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1])) - continue; - dst.setDataElements(x + dst.getMinX(), y, - src.getDataElements((int)pts[2 * x], - (int)pts[2 * x + 1], - null)); - } - } - } - else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) - { - double[] tmp = new double[4 * src.getNumBands()]; - for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++) { - try { - transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2); - } catch (NoninvertibleTransformException e) { - // Can't happen since the constructor traps this - e.printStackTrace(); - } - - for (int x = 0; x < dst.getWidth(); x++) - { - if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1])) - continue; - int xx = (int)pts[2 * x]; - int yy = (int)pts[2 * x + 1]; - double dx = (pts[2 * x] - xx); - double dy = (pts[2 * x + 1] - yy); - - // TODO write this more intelligently - if (xx == src.getMinX() + src.getWidth() - 1 || - yy == src.getMinY() + src.getHeight() - 1) + for (int y = 0; y < dst.getHeight(); y++) { - // bottom or right edge - Arrays.fill(tmp, 0); - src.getPixel(xx, yy, tmp); + dstPts[i++] = x; + dstPts[i++] = y; } - else - { - // Normal case - src.getPixels(xx, yy, 2, 2, tmp); - for (int b = 0; b < src.getNumBands(); b++) - tmp[b] = dx * dy * tmp[b] - + (1 - dx) * dy * tmp[b + src.getNumBands()] - + dx * (1 - dy) * tmp[b + 2 * src.getNumBands()] - + (1 - dx) * (1 - dy) * tmp[b + 3 * src.getNumBands()]; - } - dst.setPixel(x, y, tmp); - } } - } - else - { - // Bicubic - throw new UnsupportedOperationException("not implemented yet"); - } + Rectangle srcbounds = src.getBounds(); + + // Use an inverse transform to map each point in the destination to + // a point in the source. Note that, while all points in the destination + // matrix are integers, this is not necessarily true for points in the + // source (hence why interpolation is required) + try + { + AffineTransform inverseTx = transform.createInverse(); + inverseTx.transform(dstPts, 0, srcPts, 0, dstPts.length / 2); + } + catch (NoninvertibleTransformException e) + { + // Shouldn't happen since the constructor traps this + throw new ImagingOpException(e.getMessage()); + } + + // Different interpolation methods... + if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) + filterNearest(src, dst, dstPts, srcPts); + else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) + filterBilinear(src, dst, dstPts, srcPts); + + else // bicubic + filterBicubic(src, dst, dstPts, srcPts); + return dst; } @@ -314,27 +301,22 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp */ public final Rectangle2D getBounds2D (Raster src) { - // determine new size for the transformed raster. - // Need to calculate transformed coordinates of the lower right - // corner of the raster. The upper left corner is always (0,0) - - double x2 = (double) src.getWidth () + src.getMinX (); - double y2 = (double) src.getHeight () + src.getMinY (); - Point2D p2 = getPoint2D (new Point2D.Double (x2,y2), null); - - Rectangle2D rect = new Rectangle (0, 0, (int) p2.getX (), (int) p2.getY ()); - return rect.getBounds (); + return transform.createTransformedShape(src.getBounds()).getBounds2D(); } /** - * Returns interpolation type used during transformations + * Returns interpolation type used during transformations. * * @return interpolation type */ public final int getInterpolationType () { - if(hints.containsValue (RenderingHints.VALUE_INTERPOLATION_BILINEAR)) + if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) return TYPE_BILINEAR; + + else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC)) + return TYPE_BICUBIC; + else return TYPE_NEAREST_NEIGHBOR; } @@ -355,7 +337,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp /** * Returns rendering hints that are used during transformation. * - * @return rendering hints + * @return the rendering hints used in this Op. */ public final RenderingHints getRenderingHints () { @@ -366,10 +348,261 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp * Returns transform used in transformation between source and destination * image. * - * @return transform + * @return the transform used in this Op. */ public final AffineTransform getTransform () { return transform; } + + /** + * Perform nearest-neighbour filtering + * + * @param src the source raster + * @param dst the destination raster + * @param dpts array of points on the destination raster + * @param pts array of corresponding points on the source raster + */ + private void filterNearest(Raster src, WritableRaster dst, double[] dpts, + double[] pts) + { + Rectangle srcbounds = src.getBounds(); + + // For all points on the destination raster, copy the value from the + // corrosponding (rounded) source point + for (int i = 0; i < dpts.length; i += 2) + { + int srcX = (int) Math.round(pts[i]) + src.getMinX(); + int srcY = (int) Math.round(pts[i + 1]) + src.getMinY(); + + if (srcbounds.contains(srcX, srcY)) + dst.setDataElements((int) dpts[i] + dst.getMinX(), + (int) dpts[i + 1] + dst.getMinY(), + src.getDataElements(srcX, srcY, null)); + } + } + + /** + * Perform bilinear filtering + * + * @param src the source raster + * @param dst the destination raster + * @param dpts array of points on the destination raster + * @param pts array of corresponding points on the source raster + */ + private void filterBilinear(Raster src, WritableRaster dst, double[] dpts, + double[] pts) + { + Rectangle srcbounds = src.getBounds(); + + Object xyarr = null; + Object xp1arr = null; + Object yp1arr = null; + Object xyp1arr = null; + + double xy; + double xp1; + double yp1; + double xyp1; + + double[] result = new double[src.getNumBands()]; + + // For all points in the destination raster, use bilinear interpolation + // to find the value from the corrosponding source points + for (int i = 0; i < dpts.length; i += 2) + { + int srcX = (int) Math.round(pts[i]) + src.getMinX(); + int srcY = (int) Math.round(pts[i + 1]) + src.getMinY(); + + if (srcbounds.contains(srcX, srcY)) + { + // Corner case at the bottom or right edge; use nearest neighbour + if (pts[i] >= src.getWidth() - 1 + || pts[i + 1] >= src.getHeight() - 1) + dst.setDataElements((int) dpts[i] + dst.getMinX(), + (int) dpts[i + 1] + dst.getMinY(), + src.getDataElements(srcX, srcY, null)); + + // Standard case, apply the bilinear formula + else + { + int x = (int) Math.floor(pts[i] + src.getMinX()); + int y = (int) Math.floor(pts[i + 1] + src.getMinY()); + double xdiff = pts[i] + src.getMinX() - x; + double ydiff = pts[i + 1] + src.getMinY() - y; + + // Get surrounding pixels used in interpolation... optimized + // to use the smallest datatype possible. + if (src.getTransferType() == DataBuffer.TYPE_DOUBLE + || src.getTransferType() == DataBuffer.TYPE_FLOAT) + { + xyarr = src.getPixel(x, y, (double[])xyarr); + xp1arr = src.getPixel(x+1, y, (double[])xp1arr); + yp1arr = src.getPixel(x, y+1, (double[])yp1arr); + xyp1arr = src.getPixel(x+1, y+1, (double[])xyp1arr); + } + else + { + xyarr = src.getPixel(x, y, (int[])xyarr); + xp1arr = src.getPixel(x+1, y, (int[])xp1arr); + yp1arr = src.getPixel(x, y+1, (int[])yp1arr); + xyp1arr = src.getPixel(x+1, y+1, (int[])xyp1arr); + } + // using + // array[] pixels = src.getPixels(x, y, 2, 2, pixels); + // instead of doing four individual src.getPixel() calls + // should be faster, but benchmarking shows that it's not... + + // Run interpolation for each band + for (int j = 0; j < src.getNumBands(); j++) + { + // Pull individual sample values out of array + if (src.getTransferType() == DataBuffer.TYPE_DOUBLE + || src.getTransferType() == DataBuffer.TYPE_FLOAT) + { + xy = ((double[])xyarr)[j]; + xp1 = ((double[])xp1arr)[j]; + yp1 = ((double[])yp1arr)[j]; + xyp1 = ((double[])xyp1arr)[j]; + } + else + { + xy = ((int[])xyarr)[j]; + xp1 = ((int[])xp1arr)[j]; + yp1 = ((int[])yp1arr)[j]; + xyp1 = ((int[])xyp1arr)[j]; + } + + // If all four samples are identical, there's no need to + // calculate anything + if (xy == xp1 && xy == yp1 && xy == xyp1) + result[j] = xy; + + // Run bilinear interpolation formula + else + result[j] = (xy * (1-xdiff) + xp1 * xdiff) + * (1-ydiff) + + (yp1 * (1-xdiff) + xyp1 * xdiff) + * ydiff; + } + + dst.setPixel((int)dpts[i] + dst.getMinX(), + (int)dpts[i+1] + dst.getMinY(), + result); + } + } + } + } + + /** + * Perform bicubic filtering + * based on http://local.wasp.uwa.edu.au/~pbourke/colour/bicubic/ + * + * @param src the source raster + * @param dst the destination raster + * @param dpts array of points on the destination raster + * @param pts array of corresponding points on the source raster + */ + private void filterBicubic(Raster src, WritableRaster dst, double[] dpts, + double[] pts) + { + Rectangle srcbounds = src.getBounds(); + double[] result = new double[src.getNumBands()]; + Object pixels = null; + + // For all points on the destination raster, perform bicubic interpolation + // from corrosponding source points + for (int i = 0; i < dpts.length; i += 2) + { + if (srcbounds.contains((int) Math.round(pts[i]) + src.getMinX(), + (int) Math.round(pts[i + 1]) + src.getMinY())) + { + int x = (int) Math.floor(pts[i] + src.getMinX()); + int y = (int) Math.floor(pts[i + 1] + src.getMinY()); + double dx = pts[i] + src.getMinX() - x; + double dy = pts[i + 1] + src.getMinY() - y; + Arrays.fill(result, 0); + + for (int m = - 1; m < 3; m++) + for (int n = - 1; n < 3; n++) + { + // R(x) = ( P(x+2)^3 - 4 P(x+1)^3 + 6 P(x)^3 - 4 P(x-1)^3 ) / 6 + double r1 = 0; + double r2 = 0; + + // Calculate R(m - dx) + double rx = m - dx + 2; + r1 += rx * rx * rx; + + rx = m - dx + 1; + if (rx > 0) + r1 -= 4 * rx * rx * rx; + + rx = m - dx; + if (rx > 0) + r1 += 6 * rx * rx * rx; + + rx = m - dx - 1; + if (rx > 0) + r1 -= 4 * rx * rx * rx; + + r1 /= 6; + + // Calculate R(dy - n); + rx = dy - n + 2; + if (rx > 0) + r2 += rx * rx * rx; + + rx = dy - n + 1; + if (rx > 0) + r2 -= 4 * rx * rx * rx; + + rx = dy - n; + if (rx > 0) + r2 += 6 * rx * rx * rx; + + rx = dy - n - 1; + if (rx > 0) + r2 -= 4 * rx * rx * rx; + + r2 /= 6; + + // Calculate F(i+m, j+n) R(m - dx) R(dy - n) + // Check corner cases + int srcX = x + m; + if (srcX >= src.getMinX() + src.getWidth()) + srcX = src.getMinX() + src.getWidth() - 1; + else if (srcX < src.getMinX()) + srcX = src.getMinX(); + + int srcY = y + n; + if (srcY >= src.getMinY() + src.getHeight()) + srcY = src.getMinY() + src.getHeight() - 1; + else if (srcY < src.getMinY()) + srcY = src.getMinY(); + + // Calculate once for each band, using the smallest + // datatype possible + if (src.getTransferType() == DataBuffer.TYPE_DOUBLE + || src.getTransferType() == DataBuffer.TYPE_FLOAT) + { + pixels = src.getPixel(srcX, srcY, (double[])pixels); + for (int j = 0; j < result.length; j++) + result[j] += ((double[])pixels)[j] * r1 * r2; + } + else + { + pixels = src.getPixel(srcX, srcY, (int[])pixels); + for (int j = 0; j < result.length; j++) + result[j] += ((int[])pixels)[j] * r1 * r2; + } + } + + // Put it all together + dst.setPixel((int)dpts[i] + dst.getMinX(), + (int)dpts[i+1] + dst.getMinY(), + result); + } + } + } } diff --git a/libjava/classpath/java/awt/image/BandCombineOp.java b/libjava/classpath/java/awt/image/BandCombineOp.java index 634125ed23f..d9ce16fad07 100644 --- a/libjava/classpath/java/awt/image/BandCombineOp.java +++ b/libjava/classpath/java/awt/image/BandCombineOp.java @@ -1,4 +1,5 @@ -/* Copyright (C) 2004 Free Software Foundation +/* BandCombineOp.java - perform a combination on the bands of a raster + Copyright (C) 2004, 2006 Free Software Foundation This file is part of GNU Classpath. @@ -36,10 +37,10 @@ exception statement from your version. */ package java.awt.image; -import java.awt.Point; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.util.Arrays; /** * Filter Raster pixels by applying a matrix. @@ -53,6 +54,9 @@ import java.awt.geom.Rectangle2D; * for the destination. Therefore the destination Raster must contain the * same number of bands as the number of rows in the filter matrix. * + * This Op assumes that samples are integers; floating point sample types will + * be rounded to their nearest integer value during filtering. + * * @author Jerry Quinn (jlquinn@optonline.net) */ public class BandCombineOp implements RasterOp @@ -65,52 +69,74 @@ public class BandCombineOp implements RasterOp * * @param matrix The matrix to filter pixels with. * @param hints Rendering hints to apply. Ignored. + * @throws ArrayIndexOutOfBoundsException if the matrix is invalid */ public BandCombineOp(float[][] matrix, RenderingHints hints) { - this.matrix = matrix; + this.matrix = new float[matrix.length][]; + int width = matrix[0].length; + for (int i = 0; i < matrix.length; i++) + { + this.matrix[i] = new float[width + 1]; + for (int j = 0; j < width; j++) + this.matrix[i][j] = matrix[i][j]; + + // The reference implementation pads the array with a trailing zero... + this.matrix[i][width] = 0; + } + this.hints = hints; } /** - * Filter Raster pixels through a matrix. - * - * Applies the Op matrix to source pixes to produce dest pixels. Each row - * of the matrix is multiplied by the src pixel components to produce the - * dest pixel. If matrix is one more than the number of bands in the src, - * the last element is implicitly multiplied by 1, i.e. added to the sum - * for that dest component. - * - * If dest is null, a suitable Raster is created. This implementation uses - * createCompatibleDestRaster. + * Filter Raster pixels through a matrix. Applies the Op matrix to source + * pixes to produce dest pixels. Each row of the matrix is multiplied by the + * src pixel components to produce the dest pixel. If matrix is one more than + * the number of bands in the src, the last element is implicitly multiplied + * by 1, i.e. added to the sum for that dest component. If dest is null, a + * suitable Raster is created. This implementation uses + * createCompatibleDestRaster. * * @param src The source Raster. - * @param dest The destination Raster, or null. - * @returns The destination Raster or an allocated Raster. + * @param dest The destination Raster, or null. + * @throws IllegalArgumentException if the destination raster is incompatible + * with the source raster. + * @return The filtered Raster. * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, - *java.awt.image.WritableRaster) + * java.awt.image.WritableRaster) */ public WritableRaster filter(Raster src, WritableRaster dest) { if (dest == null) dest = createCompatibleDestRaster(src); - + else if (dest.getNumBands() != src.getNumBands() + || dest.getTransferType() != src.getTransferType()) + throw new IllegalArgumentException("Destination raster is incompatible with source raster"); + // Filter the pixels - float[] spix = new float[matrix[0].length]; - float[] dpix = new float[matrix.length]; + int[] spix = new int[matrix[0].length - 1]; + int[] spix2 = new int[matrix[0].length - 1]; + int[] dpix = new int[matrix.length]; for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++) for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++) - { - // In case matrix rows have implicit translation - spix[spix.length - 1] = 1.0f; - src.getPixel(x, y, spix); - for (int i = 0; i < matrix.length; i++) { - dpix[i] = 0; - for (int j = 0; j < matrix[0].length; j++) - dpix[i] += spix[j] * matrix[i][j]; + // In case matrix rows have implicit translation + spix[spix.length - 1] = 1; + src.getPixel(x, y, spix); + + // Do not re-calculate if pixel is identical to the last one + // (ie, blocks of the same colour) + if (!Arrays.equals(spix, spix2)) + { + System.arraycopy(spix, 0, spix2, 0, spix.length); + for (int i = 0; i < matrix.length; i++) + { + dpix[i] = 0; + for (int j = 0; j < matrix[0].length - 1; j++) + dpix[i] += spix[j] * (int)matrix[i][j]; + } + } + dest.setPixel(x, y, dpix); } - dest.setPixel(x, y, dpix); - } return dest; } @@ -125,28 +151,48 @@ public class BandCombineOp implements RasterOp /** * Creates a new WritableRaster that can be used as the destination for this - * Op. This implementation creates a Banded Raster with data type FLOAT. - * @see - *java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster) + * Op. The number of bands in the source raster must equal the number of rows + * in the op matrix, which must also be equal to either the number of columns + * or (columns - 1) in the matrix. + * + * @param src The source raster. + * @return A compatible raster. + * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster) + * @throws IllegalArgumentException if the raster is incompatible with the + * matrix. */ public WritableRaster createCompatibleDestRaster(Raster src) { - return Raster.createBandedRaster(DataBuffer.TYPE_FLOAT, src.getWidth(), - src.getHeight(), matrix.length, - new Point(src.getMinX(), src.getMinY())); + // Destination raster must have same number of bands as source + if (src.getNumBands() != matrix.length) + throw new IllegalArgumentException("Number of rows in matrix specifies an " + + "incompatible number of bands"); + + // We use -1 and -2 because we previously padded the rows with a trailing 0 + if (src.getNumBands() != matrix[0].length - 1 + && src.getNumBands() != matrix[0].length - 2) + throw new IllegalArgumentException("Incompatible number of bands: " + + "the number of bands in the raster must equal the number of " + + "columns in the matrix, optionally minus one"); + + return src.createCompatibleWritableRaster(); } - /** Return corresponding destination point for source point. + /** + * Return corresponding destination point for source point. Because this is + * not a geometric operation, it simply returns a copy of the source. * - * LookupOp will return the value of src unchanged. * @param src The source point. * @param dst The destination point. + * @return dst The destination point. * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, *java.awt.geom.Point2D) */ public final Point2D getPoint2D(Point2D src, Point2D dst) { - if (dst == null) return (Point2D)src.clone(); + if (dst == null) + return (Point2D)src.clone(); + dst.setLocation(src); return dst; } @@ -159,7 +205,11 @@ public class BandCombineOp implements RasterOp return hints; } - /** Return the matrix for this Op. */ + /** + * Return the matrix used in this operation. + * + * @return The matrix used in this operation. + */ public final float[][] getMatrix() { return matrix; diff --git a/libjava/classpath/java/awt/image/BufferedImage.java b/libjava/classpath/java/awt/image/BufferedImage.java index 76848db0833..ef3141d0ead 100644 --- a/libjava/classpath/java/awt/image/BufferedImage.java +++ b/libjava/classpath/java/awt/image/BufferedImage.java @@ -38,6 +38,7 @@ exception statement from your version. */ package java.awt.image; +import gnu.java.awt.Buffers; import gnu.java.awt.ComponentDataBlitOp; import java.awt.Graphics; @@ -79,27 +80,37 @@ public class BufferedImage extends Image TYPE_BYTE_BINARY = 12, TYPE_BYTE_INDEXED = 13; - static final int[] bits3 = { 8, 8, 8 }; - static final int[] bits4 = { 8, 8, 8, 8 }; - static final int[] bits1byte = { 8 }; - static final int[] bits1ushort = { 16 }; - - static final int[] masks_int = { 0x00ff0000, - 0x0000ff00, - 0x000000ff, - DataBuffer.TYPE_INT }; - static final int[] masks_565 = { 0xf800, - 0x07e0, - 0x001f, - DataBuffer.TYPE_USHORT}; - static final int[] masks_555 = { 0x7c00, - 0x03e0, - 0x001f, - DataBuffer.TYPE_USHORT}; - - Vector observers; + /** + * Vector of TileObservers (or null) + */ + Vector tileObservers; /** + * The image's WritableRaster + */ + WritableRaster raster; + + /** + * The associated ColorModel + */ + ColorModel colorModel; + + /** + * The image's properties (or null) + */ + Hashtable properties; + + /** + * Whether alpha is premultiplied + */ + boolean isPremultiplied; + + /** + * The predefined type, if any. + */ + int type; + + /** * Creates a new <code>BufferedImage</code> with the specified width, height * and type. Valid <code>type</code> values are: * @@ -119,155 +130,181 @@ public class BufferedImage extends Image * <li>{@link #TYPE_BYTE_INDEXED}</li> * </ul> * - * @param w the width (must be > 0). - * @param h the height (must be > 0). + * @param width the width (must be > 0). + * @param height the height (must be > 0). * @param type the image type (see the list of valid types above). * - * @throws IllegalArgumentException if <code>w</code> or <code>h</code> is - * less than or equal to zero. + * @throws IllegalArgumentException if <code>width</code> or + * <code>height</code> is less than or equal to zero. * @throws IllegalArgumentException if <code>type</code> is not one of the * specified values. */ - public BufferedImage(int w, int h, int type) + public BufferedImage(int width, int height, int type) { + SampleModel sm = null; ColorModel cm = null; - - boolean alpha = false; - boolean premultiplied = false; - switch (type) - { - case TYPE_4BYTE_ABGR_PRE: - case TYPE_INT_ARGB_PRE: - premultiplied = true; - // fall through - case TYPE_INT_ARGB: - case TYPE_4BYTE_ABGR: - alpha = true; - } - - ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); - switch (type) + boolean premultiplied = (type == BufferedImage.TYPE_INT_ARGB_PRE || + type == BufferedImage.TYPE_4BYTE_ABGR_PRE); + + switch( type ) { - case TYPE_INT_RGB: - case TYPE_INT_ARGB: - case TYPE_INT_ARGB_PRE: - case TYPE_USHORT_565_RGB: - case TYPE_USHORT_555_RGB: - int[] masks = null; - switch (type) - { - case TYPE_INT_RGB: - case TYPE_INT_ARGB: - case TYPE_INT_ARGB_PRE: - masks = masks_int; - break; - case TYPE_USHORT_565_RGB: - masks = masks_565; - break; - case TYPE_USHORT_555_RGB: - masks = masks_555; - break; - } - - cm = new DirectColorModel(cs, - 32, // 32 bits in an int - masks[0], // r - masks[1], // g - masks[2], // b - alpha ? 0xff000000 : 0, - premultiplied, - masks[3] // data type - ); + case BufferedImage.TYPE_INT_RGB: + sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT, + width, height, + new int[]{ 0x00FF0000, + 0x0000FF00, + 0x000000FF } ) ; + cm = new DirectColorModel( 24, 0xff0000, 0xff00, 0xff ); break; - case TYPE_INT_BGR: - String msg = - "FIXME: Programmer is confused. Why (and how) does a " + - "TYPE_INT_BGR image use ComponentColorModel to store " + - "8-bit values? Is data type TYPE_INT or TYPE_BYTE. What " + - "is the difference between TYPE_INT_BGR and TYPE_3BYTE_BGR?"; - throw new UnsupportedOperationException(msg); - - case TYPE_3BYTE_BGR: - case TYPE_4BYTE_ABGR: - case TYPE_4BYTE_ABGR_PRE: - case TYPE_BYTE_GRAY: - case TYPE_USHORT_GRAY: - int[] bits = null; - int dataType = DataBuffer.TYPE_BYTE; - switch (type) { - case TYPE_3BYTE_BGR: - bits = bits3; - break; - case TYPE_4BYTE_ABGR: - case TYPE_4BYTE_ABGR_PRE: - bits = bits4; - break; - case TYPE_BYTE_GRAY: - bits = bits1byte; - cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); - break; - case TYPE_USHORT_GRAY: - bits = bits1ushort; - cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); - dataType = DataBuffer.TYPE_USHORT; - break; - } - cm = new ComponentColorModel(cs, bits, alpha, premultiplied, - alpha ? - Transparency.TRANSLUCENT: - Transparency.OPAQUE, - dataType); + case BufferedImage.TYPE_3BYTE_BGR: + sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE, + width, height, + 3, width * 3, + new int[]{ 2, 1, 0 } ); + cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + false, false, + BufferedImage.OPAQUE, + DataBuffer.TYPE_BYTE); + break; + + case BufferedImage.TYPE_INT_ARGB: + case BufferedImage.TYPE_INT_ARGB_PRE: + sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT, + width, height, + new int[]{ 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000 } ); + if (premultiplied) + cm = new DirectColorModel( ColorSpace.getInstance(ColorSpace.CS_sRGB), + 32, 0xff0000, 0xff00, 0xff, 0xff000000, + true, + Buffers.smallestAppropriateTransferType(32)); + else + cm = new DirectColorModel( 32, 0xff0000, 0xff00, 0xff, 0xff000000 ); + break; + + case BufferedImage.TYPE_4BYTE_ABGR: + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + sm = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, + width, height, + 4, 4*width, + new int[]{3, 2, 1, 0}); + cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + true, premultiplied, + BufferedImage.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + break; + + case BufferedImage.TYPE_INT_BGR: + sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT, + width, height, + new int[]{ 0x000000FF, + 0x0000FF00, + 0x00FF0000 } ) ; + cm = new DirectColorModel( 24, 0xff, 0xff00, 0xff0000 ); + break; + + case BufferedImage.TYPE_USHORT_565_RGB: + sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT, + width, height, + new int[]{ 0xF800, + 0x7E0, + 0x1F } ) ; + cm = new DirectColorModel( 16, 0xF800, 0x7E0, 0x1F ); + break; + case BufferedImage.TYPE_USHORT_555_RGB: + sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT, + width, height, + new int[]{ 0x7C00, + 0x3E0, + 0x1F } ) ; + cm = new DirectColorModel( 15, 0x7C00, 0x3E0, 0x1F ); break; - case TYPE_BYTE_BINARY: - byte[] vals = { 0, (byte) 0xff }; - cm = new IndexColorModel(8, 2, vals, vals, vals); + + case BufferedImage.TYPE_BYTE_INDEXED: + cm = createDefaultIndexedColorModel( false ); + + case BufferedImage.TYPE_BYTE_GRAY: + sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE, + width, height, + 1, width, new int[]{ 0 } ); + break; + + case BufferedImage.TYPE_USHORT_GRAY: + sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_USHORT, + width, height, + 1, width, new int[]{ 0 } ); + break; + + case BufferedImage.TYPE_BYTE_BINARY: + cm = createDefaultIndexedColorModel( true ); + sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, + width, height, 1); break; - case TYPE_BYTE_INDEXED: - String msg2 = "type not implemented yet"; - throw new UnsupportedOperationException(msg2); - // FIXME: build color-cube and create color model + default: - throw new IllegalArgumentException("Unknown image type " + type); + sm = null; } + + if( sm == null ) + throw new IllegalArgumentException("Unknown predefined image type."); - init(cm, - cm.createCompatibleWritableRaster(w, h), - premultiplied, - null, // no properties - type - ); + if( cm == null ) // only for the grayscale types + { + int buftype; + int[] bits = new int[1]; + if( type == BufferedImage.TYPE_BYTE_GRAY ) + { + buftype = DataBuffer.TYPE_BYTE; + bits[0] = 8; + } + else + { + buftype = DataBuffer.TYPE_USHORT; + bits[0] = 16; + } + ColorSpace graySpace = ColorSpace.getInstance( ColorSpace.CS_GRAY ); + + cm = new ComponentColorModel( graySpace, bits, false, false, + Transparency.OPAQUE, buftype ); + } + + init( cm, + Raster.createWritableRaster(sm, new Point( 0, 0 ) ), + premultiplied, + null, // no properties + type ); } public BufferedImage(int w, int h, int type, IndexColorModel indexcolormodel) { if ((type != TYPE_BYTE_BINARY) && (type != TYPE_BYTE_INDEXED)) - throw new IllegalArgumentException("type must be binary or indexed"); + throw new IllegalArgumentException("Type must be TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED"); + if( indexcolormodel.getMapSize() > 16 && type == TYPE_BYTE_BINARY ) + throw new IllegalArgumentException("Type TYPE_BYTE_BINARY cannot have a larger than 16-color palette."); + if( indexcolormodel.getMapSize() > 256 ) + throw new IllegalArgumentException("Byte type cannot have a larger than 256-color palette."); - init(indexcolormodel, - indexcolormodel.createCompatibleWritableRaster(w, h), - false, // not premultiplied (guess) - null, // no properties - type); + init( indexcolormodel, + indexcolormodel.createCompatibleWritableRaster(w, h), + indexcolormodel.isAlphaPremultiplied(), + null, // no properties + type ); } public BufferedImage(ColorModel colormodel, WritableRaster writableraster, boolean premultiplied, - Hashtable properties) + Hashtable<?,?> properties) { init(colormodel, writableraster, premultiplied, properties, TYPE_CUSTOM); - // TODO: perhaps try to identify type? } - WritableRaster raster; - ColorModel colorModel; - Hashtable properties; - boolean isPremultiplied; - int type; - + private void init(ColorModel cm, WritableRaster writableraster, boolean premultiplied, @@ -280,12 +317,48 @@ public class BufferedImage extends Image isPremultiplied = premultiplied; this.type = type; } - - //public void addTileObserver(TileObserver tileobserver) {} + + /** + * Creates the default palettes for the predefined indexed color types + * (256-color or black-and-white) + * + * @param binary - If <code>true</code>, a black and white palette, + * otherwise a default 256-color palette is returned. + */ + private IndexColorModel createDefaultIndexedColorModel( boolean binary ) + { + if( binary ) + { + byte[] t = new byte[]{ 0, (byte)255 }; + return new IndexColorModel( 1, 2, t, t, t ); + } + + byte[] r = new byte[256]; + byte[] g = new byte[256]; + byte[] b = new byte[256]; + int index = 0; + for( int i = 0; i < 6; i++ ) + for( int j = 0; j < 6; j++ ) + for( int k = 0; k < 6; k++ ) + { + r[ index ] = (byte)(i * 51); + g[ index ] = (byte)(j * 51); + b[ index ] = (byte)(k * 51); + index++; + } + while( index < 256 ) + { + r[ index ] = g[ index ] = b[ index ] = + (byte)(18 + (index - 216) * 6); + index++; + } + return new IndexColorModel( 8, 256, r, g, b ); + } public void coerceData(boolean premultiplied) { colorModel = colorModel.coerceData(raster, premultiplied); + isPremultiplied = premultiplied; } public WritableRaster copyData(WritableRaster dest) @@ -555,7 +628,7 @@ public class BufferedImage extends Image }; } - public Vector getSources() + public Vector<RenderedImage> getSources() { return null; } @@ -726,10 +799,10 @@ public class BufferedImage extends Image */ public void addTileObserver (TileObserver to) { - if (observers == null) - observers = new Vector (); + if (tileObservers == null) + tileObservers = new Vector (); - observers.add (to); + tileObservers.add (to); } /** @@ -741,10 +814,10 @@ public class BufferedImage extends Image */ public void removeTileObserver (TileObserver to) { - if (observers == null) + if (tileObservers == null) return; - observers.remove (to); + tileObservers.remove (to); } /** diff --git a/libjava/classpath/java/awt/image/ColorConvertOp.java b/libjava/classpath/java/awt/image/ColorConvertOp.java index 1f85a5ecd99..e6c85412d34 100644 --- a/libjava/classpath/java/awt/image/ColorConvertOp.java +++ b/libjava/classpath/java/awt/image/ColorConvertOp.java @@ -38,7 +38,10 @@ exception statement from your version. */ package java.awt.image; +import gnu.java.awt.Buffers; + import java.awt.Graphics2D; +import java.awt.Point; import java.awt.RenderingHints; import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; @@ -47,9 +50,9 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; /** - * ColorConvertOp is a filter for converting an image from one colorspace to - * another colorspace. The filter can convert the image through a sequence - * of colorspaces or just from source to destination. + * ColorConvertOp is a filter for converting images or rasters between + * colorspaces, either through a sequence of colorspaces or just from source to + * destination. * * Color conversion is done on the color components without alpha. Thus * if a BufferedImage has alpha premultiplied, this is divided out before @@ -63,24 +66,22 @@ import java.awt.geom.Rectangle2D; */ public class ColorConvertOp implements BufferedImageOp, RasterOp { - private ColorSpace srccs; - private ColorSpace dstcs; private RenderingHints hints; - private ICC_Profile[] profiles; + private ICC_Profile[] profiles = null; private ColorSpace[] spaces; - private boolean rasterValid; /** - * Convert BufferedImage through a ColorSpace. + * Convert a BufferedImage through a ColorSpace. * - * This filter version is only valid for BufferedImages. The source image - * is converted to cspace. If the destination is not null, it is then - * converted to the destination colorspace. Normally this filter will only - * be used with a null destination. + * Objects created with this constructor can be used to convert + * BufferedImage's to a destination ColorSpace. Attempts to convert Rasters + * with this constructor will result in an IllegalArgumentException when the + * filter(Raster, WritableRaster) method is called. * * @param cspace The target color space. - * @param hints Rendering hints to use in conversion, or null. + * @param hints Rendering hints to use in conversion, if any (may be null) + * @throws NullPointerException if the ColorSpace is null. */ public ColorConvertOp(ColorSpace cspace, RenderingHints hints) { @@ -88,9 +89,27 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp throw new NullPointerException(); spaces = new ColorSpace[]{cspace}; this.hints = hints; - rasterValid = false; } + /** + * Convert from a source colorspace to a destination colorspace. + * + * This constructor takes two ColorSpace arguments as the source and + * destination color spaces. It is usually used with the + * filter(Raster, WritableRaster) method, in which case the source colorspace + * is assumed to correspond to the source Raster, and the destination + * colorspace with the destination Raster. + * + * If used with BufferedImages that do not match the source or destination + * colorspaces specified here, there is an implicit conversion from the + * source image to the source ColorSpace, or the destination ColorSpace to + * the destination image. + * + * @param srcCspace The source ColorSpace. + * @param dstCspace The destination ColorSpace. + * @param hints Rendering hints to use in conversion, if any (may be null). + * @throws NullPointerException if any ColorSpace is null. + */ public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace, RenderingHints hints) { @@ -101,61 +120,77 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp } /** - * Convert from a source image destination image color space. + * Convert from a source colorspace to a destinatino colorspace. * * This constructor builds a ColorConvertOp from an array of ICC_Profiles. - * The source image will be converted through the sequence of color spaces + * The source will be converted through the sequence of color spaces * defined by the profiles. If the sequence of profiles doesn't give a - * well-defined conversion, throws IllegalArgumentException. - * - * NOTE: Sun's docs don't clearly define what a well-defined conversion is - * - or perhaps someone smarter can come along and sort it out. + * well-defined conversion, an IllegalArgumentException is thrown. * - * For BufferedImages, when the first and last profiles match the - * requirements of the source and destination color space respectively, the - * corresponding conversion is unnecessary. TODO: code this up. I don't - * yet understand how you determine this. + * If used with BufferedImages that do not match the source or destination + * colorspaces specified here, there is an implicit conversion from the + * source image to the source ColorSpace, or the destination ColorSpace to + * the destination image. * * For Rasters, the first and last profiles must have the same number of * bands as the source and destination Rasters, respectively. If this is * not the case, or there fewer than 2 profiles, an IllegalArgumentException * will be thrown. * - * @param profiles - * @param hints + * @param profiles An array of ICC_Profile's to convert through. + * @param hints Rendering hints to use in conversion, if any (may be null). + * @throws NullPointerException if the profile array is null. + * @throws IllegalArgumentException if the array is not a well-defined + * conversion. */ public ColorConvertOp(ICC_Profile[] profiles, RenderingHints hints) { if (profiles == null) throw new NullPointerException(); + this.hints = hints; this.profiles = profiles; - // TODO: Determine if this is well-defined. + // Create colorspace array with space for src and dest colorspace + // Note that the ICC_ColorSpace constructor will throw an + // IllegalArgumentException if the profile is invalid; thus we check + // for a "well defined conversion" spaces = new ColorSpace[profiles.length]; for (int i = 0; i < profiles.length; i++) spaces[i] = new ICC_ColorSpace(profiles[i]); } - /** Convert from source image color space to destination image color space. + /** + * Convert from source color space to destination color space. * * Only valid for BufferedImage objects, this Op converts from the source - * color space to the destination color space. The destination can't be - * null for this operation. + * image's color space to the destination image's color space. * - * @param hints Rendering hints to use during conversion, or null. + * The destination in the filter(BufferedImage, BufferedImage) method cannot + * be null for this operation, and it also cannot be used with the + * filter(Raster, WritableRaster) method. + * + * @param hints Rendering hints to use in conversion, if any (may be null). */ public ColorConvertOp(RenderingHints hints) { - this.hints = hints; - srccs = null; - dstcs = null; - rasterValid = false; + this.hints = hints; + spaces = new ColorSpace[0]; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, - java.awt.image.BufferedImage) + /** + * Converts the source image using the conversion path specified in the + * constructor. The resulting image is stored in the destination image if one + * is provided; otherwise a new BufferedImage is created and returned. + * + * The source and destination BufferedImage (if one is supplied) must have + * the same dimensions. + * + * @param src The source image. + * @param dst The destination image. + * @throws IllegalArgumentException if the rasters and/or color spaces are + * incompatible. + * @return The transformed image. */ public final BufferedImage filter(BufferedImage src, BufferedImage dst) { @@ -163,129 +198,241 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp // For now we just suck it up and create intermediate buffers. if (dst == null && spaces.length == 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Not enough color space information " + + "to complete conversion."); + + if (dst != null + && (src.getHeight() != dst.getHeight() || src.getWidth() != dst.getWidth())) + throw new IllegalArgumentException("Source and destination images have " + + "different dimensions"); // Make sure input isn't premultiplied by alpha if (src.isAlphaPremultiplied()) - { - BufferedImage tmp = createCompatibleDestImage(src, src.getColorModel()); - copyimage(src, tmp); - tmp.coerceData(false); - src = tmp; - } + { + BufferedImage tmp = createCompatibleDestImage(src, src.getColorModel()); + copyimage(src, tmp); + tmp.coerceData(false); + src = tmp; + } - ColorModel scm = src.getColorModel(); + // Convert through defined intermediate conversions + BufferedImage tmp; for (int i = 0; i < spaces.length; i++) - { - BufferedImage tmp = createCompatibleDestImage(src, scm); - copyimage(src, tmp); - src = tmp; - } + { + if (src.getColorModel().getColorSpace().getType() != spaces[i].getType()) + { + tmp = createCompatibleDestImage(src, + createCompatibleColorModel(src, + spaces[i])); + copyimage(src, tmp); + src = tmp; + } + } - // Intermediate conversions leave result in src + // No implicit conversion to destination type needed; return result from the + // last intermediate conversions (which was left in src) if (dst == null) - return src; - - // Apply final conversion - copyimage(src, dst); - + dst = src; + + // Implicit conversion to destination image's color space + else + copyimage(src, dst); + return dst; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel) + /** + * Converts the source raster using the conversion path specified in the + * constructor. The resulting raster is stored in the destination raster if + * one is provided; otherwise a new WritableRaster is created and returned. + * + * This operation is not valid with every constructor of this class; see + * the constructors for details. Further, the source raster must have the + * same number of bands as the source ColorSpace, and the destination raster + * must have the same number of bands as the destination ColorSpace. + * + * The source and destination raster (if one is supplied) must also have the + * same dimensions. + * + * @param src The source raster. + * @param dest The destination raster. + * @throws IllegalArgumentException if the rasters and/or color spaces are + * incompatible. + * @return The transformed raster. + */ + public final WritableRaster filter(Raster src, WritableRaster dest) + { + // Various checks to ensure that the rasters and color spaces are compatible + if (spaces.length < 2) + throw new IllegalArgumentException("Not enough information about " + + "source and destination colorspaces."); + + if (spaces[0].getNumComponents() != src.getNumBands() + || (dest != null && spaces[spaces.length - 1].getNumComponents() != dest.getNumBands())) + throw new IllegalArgumentException("Source or destination raster " + + "contains the wrong number of bands."); + + if (dest != null + && (src.getHeight() != dest.getHeight() || src.getWidth() != dest.getWidth())) + throw new IllegalArgumentException("Source and destination rasters " + + "have different dimensions"); + + // Need to iterate through each color space. + // spaces[0] corresponds to the ColorSpace of the source raster, and + // spaces[spaces.length - 1] corresponds to the ColorSpace of the + // destination, with any number (or zero) of intermediate conversions. + + for (int i = 0; i < spaces.length - 2; i++) + { + WritableRaster tmp = createCompatibleDestRaster(src, spaces[i + 1], + false, + src.getTransferType()); + copyraster(src, spaces[i], tmp, spaces[i + 1]); + src = tmp; + } + + // The last conversion is done outside of the loop so that we can + // use the dest raster supplied, instead of creating our own temp raster + if (dest == null) + dest = createCompatibleDestRaster(src, spaces[spaces.length - 1], false, + DataBuffer.TYPE_BYTE); + copyraster(src, spaces[spaces.length - 2], dest, spaces[spaces.length - 1]); + + return dest; + } + + /** + * Creates an empty BufferedImage with the size equal to the source and the + * correct number of bands for the conversion defined in this Op. The newly + * created image is created with the specified ColorModel, or if no ColorModel + * is supplied, an appropriate one is chosen. + * + * @param src The source image. + * @param dstCM A color model for the destination image (may be null). + * @throws IllegalArgumentException if an appropriate colormodel cannot be + * chosen with the information given. + * @return The new compatible destination image. */ public BufferedImage createCompatibleDestImage(BufferedImage src, - ColorModel dstCM) + ColorModel dstCM) { - // FIXME: set properties to those in src + if (dstCM == null && spaces.length == 0) + throw new IllegalArgumentException("Don't know the destination " + + "colormodel"); + + if (dstCM == null) + { + dstCM = createCompatibleColorModel(src, spaces[spaces.length - 1]); + } + return new BufferedImage(dstCM, - src.getRaster().createCompatibleWritableRaster(), - src.isPremultiplied, - null); + createCompatibleDestRaster(src.getRaster(), + dstCM.getColorSpace(), + src.getColorModel().hasAlpha, + dstCM.getTransferType()), + src.isPremultiplied, null); } - public final ICC_Profile[] getICC_Profiles() + /** + * Creates a new WritableRaster with the size equal to the source and the + * correct number of bands. + * + * Note, the new Raster will always use a BYTE storage size, regardless of + * the color model or defined destination; this is for compatibility with + * the reference implementation. + * + * @param src The source Raster. + * @throws IllegalArgumentException if there isn't enough colorspace + * information to create a compatible Raster. + * @return The new compatible destination raster. + */ + public WritableRaster createCompatibleDestRaster(Raster src) { - return profiles; - } + if (spaces.length < 2) + throw new IllegalArgumentException("Not enough destination colorspace " + + "information"); - /** Return the rendering hints for this op. */ - public final RenderingHints getRenderingHints() - { - return hints; + // Create a new raster with the last ColorSpace in the conversion + // chain, and with no alpha (implied) + return createCompatibleDestRaster(src, spaces[spaces.length-1], false, + DataBuffer.TYPE_BYTE); } - /* (non-Javadoc) - * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster) + /** + * Returns the array of ICC_Profiles used to create this Op, or null if the + * Op was created using ColorSpace arguments. + * + * @return The array of ICC_Profiles, or null. */ - public final WritableRaster filter(Raster src, WritableRaster dest) + public final ICC_Profile[] getICC_Profiles() { - if (!rasterValid) - throw new IllegalArgumentException(); - - // Need to iterate through each color space - there must be at least 2 - for (int i = 1; i < spaces.length - 1; i++) - { - // FIXME: this is wrong. tmp needs to have the same number of bands as - // spaces[i] has. - WritableRaster tmp = createCompatibleDestRaster(src); - copyraster(src, spaces[i - 1], tmp, spaces[i]); - src = tmp; - } - - // FIXME: this is wrong. dst needs to have the same number of bands as - // spaces[i] has. - if (dest == null) - dest = createCompatibleDestRaster(src); - copyraster(src, spaces[spaces.length - 2], - dest, spaces[spaces.length - 1]); - - return dest; + return profiles; } - /* (non-Javadoc) - * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster) + /** + * Returns the rendering hints for this op. + * + * @return The rendering hints for this Op, or null. */ - public WritableRaster createCompatibleDestRaster(Raster src) + public final RenderingHints getRenderingHints() { - return src.createCompatibleWritableRaster(); + return hints; } - /** Return corresponding destination point for source point. + /** + * Returns the corresponding destination point for a source point. + * Because this is not a geometric operation, the destination and source + * points will be identical. * - * LookupOp will return the value of src unchanged. * @param src The source point. - * @param dst The destination point. - * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D) + * @param dst The transformed destination point. + * @return The transformed destination point. */ public final Point2D getPoint2D(Point2D src, Point2D dst) { - if (dst == null) return (Point2D)src.clone(); + if (dst == null) + return (Point2D)src.clone(); + dst.setLocation(src); return dst; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage) + /** + * Returns the corresponding destination boundary of a source boundary. + * Because this is not a geometric operation, the destination and source + * boundaries will be identical. + * + * @param src The source boundary. + * @return The boundaries of the destination. */ public final Rectangle2D getBounds2D(BufferedImage src) { return src.getRaster().getBounds(); } - /* (non-Javadoc) - * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster) + /** + * Returns the corresponding destination boundary of a source boundary. + * Because this is not a geometric operation, the destination and source + * boundaries will be identical. + * + * @param src The source boundary. + * @return The boundaries of the destination. */ public final Rectangle2D getBounds2D(Raster src) { return src.getBounds(); } - - // According to Sven de Marothy, we need to copy the src into the dest - // using Graphics2D, in order to use the rendering hints. + + /** + * Copy a source image to a destination image, respecting their colorspaces + * and performing colorspace conversions if necessary. + * + * @param src The source image. + * @param dst The destination image. + */ private void copyimage(BufferedImage src, BufferedImage dst) { + // This is done using Graphics2D in order to respect the rendering hints. Graphics2D gg = dst.createGraphics(); // If no hints are set there is no need to call @@ -297,13 +444,23 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp gg.dispose(); } - private void copyraster(Raster src, ColorSpace scs, WritableRaster dst, - ColorSpace dcs) + /** + * Copy a source raster to a destination raster, performing a colorspace + * conversion between the two. The conversion will respect the + * KEY_COLOR_RENDERING rendering hint if one is present. + * + * @param src The source raster. + * @param scs The colorspace of the source raster. + * @dst The destination raster. + * @dcs The colorspace of the destination raster. + */ + private void copyraster(Raster src, ColorSpace scs, WritableRaster dst, ColorSpace dcs) { float[] sbuf = new float[src.getNumBands()]; - if (hints.get(RenderingHints.KEY_COLOR_RENDERING) == - RenderingHints.VALUE_COLOR_RENDER_QUALITY) + if (hints != null + && hints.get(RenderingHints.KEY_COLOR_RENDERING) == + RenderingHints.VALUE_COLOR_RENDER_QUALITY) { // use cie for accuracy for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++) @@ -321,4 +478,60 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp } } + /** + * This method creates a color model with the same colorspace and alpha + * settings as the source image. The created color model will always be a + * ComponentColorModel and have a BYTE transfer type. + * + * @param img The source image. + * @param cs The ColorSpace to use. + * @return A color model compatible with the source image. + */ + private ColorModel createCompatibleColorModel(BufferedImage img, ColorSpace cs) + { + // The choice of ComponentColorModel and DataBuffer.TYPE_BYTE is based on + // Mauve testing of the reference implementation. + return new ComponentColorModel(cs, + img.getColorModel().hasAlpha(), + img.isAlphaPremultiplied(), + img.getColorModel().getTransparency(), + DataBuffer.TYPE_BYTE); + } + + /** + * This method creates a compatible Raster, given a source raster, colorspace, + * alpha value, and transfer type. + * + * @param src The source raster. + * @param cs The ColorSpace to use. + * @param hasAlpha Whether the raster should include a component for an alpha. + * @param transferType The size of a single data element. + * @return A compatible WritableRaster. + */ + private WritableRaster createCompatibleDestRaster(Raster src, ColorSpace cs, + boolean hasAlpha, + int transferType) + { + // The use of a PixelInterleavedSampleModel weas determined using mauve + // tests, based on the reference implementation + + int numComponents = cs.getNumComponents(); + if (hasAlpha) + numComponents++; + + int[] offsets = new int[numComponents]; + for (int i = 0; i < offsets.length; i++) + offsets[i] = i; + + DataBuffer db = Buffers.createBuffer(transferType, + src.getWidth() * src.getHeight() * numComponents, + 1); + return new WritableRaster(new PixelInterleavedSampleModel(transferType, + src.getWidth(), + src.getHeight(), + numComponents, + numComponents * src.getWidth(), + offsets), + db, new Point(src.getMinX(), src.getMinY())); + } } diff --git a/libjava/classpath/java/awt/image/ColorModel.java b/libjava/classpath/java/awt/image/ColorModel.java index 9e559db37d8..e2873c5d71f 100644 --- a/libjava/classpath/java/awt/image/ColorModel.java +++ b/libjava/classpath/java/awt/image/ColorModel.java @@ -624,40 +624,40 @@ public abstract class ColorModel implements Transparency return cspace; } - // Typically overridden public ColorModel coerceData(WritableRaster raster, - boolean isAlphaPremultiplied) + boolean isAlphaPremultiplied) { - if (this.isAlphaPremultiplied == isAlphaPremultiplied) - return this; + // This method should always be overridden, but is not abstract. + throw new UnsupportedOperationException(); + } + protected void coerceDataWorker(WritableRaster raster, + boolean isAlphaPremultiplied) + { int w = raster.getWidth(); int h = raster.getHeight(); int x = raster.getMinX(); int y = raster.getMinY(); - int size = w*h; + int size = w * h; int numColors = getNumColorComponents(); int numComponents = getNumComponents(); - int alphaScale = (1<<getComponentSize(numColors)) - 1; + int alphaScale = (1 << getComponentSize(numColors)) - 1; double[] pixels = raster.getPixels(x, y, w, h, (double[]) null); - for (int i=0; i<size; i++) + for (int i = 0; i < size; i++) { - double alpha = pixels[i*numComponents+numColors]*alphaScale; - for (int c=0; c<numColors; c++) - { - int offset = i*numComponents+c; - if (isAlphaPremultiplied) - pixels[offset] = pixels[offset]/alpha; - else - pixels[offset] = pixels[offset]*alpha; - } + double alpha = pixels[i * numComponents + numColors] / alphaScale; + for (int c = 0; c < numColors; c++) + { + int offset = i * numComponents + c; + if (isAlphaPremultiplied) + pixels[offset] = Math.round(pixels[offset] * alpha); + else + pixels[offset] = Math.round(pixels[offset] / alpha); + } } - - raster.setPixels(0, 0, w, h, pixels); - // FIXME: what can we return? - return null; + raster.setPixels(0, 0, w, h, pixels); } /** diff --git a/libjava/classpath/java/awt/image/ComponentColorModel.java b/libjava/classpath/java/awt/image/ComponentColorModel.java index f56688f9362..2096800b20d 100644 --- a/libjava/classpath/java/awt/image/ComponentColorModel.java +++ b/libjava/classpath/java/awt/image/ComponentColorModel.java @@ -42,9 +42,11 @@ import gnu.java.awt.Buffers; import java.awt.Point; import java.awt.color.ColorSpace; +import java.util.Arrays; public class ComponentColorModel extends ColorModel { + // Find sum of all elements of the array. private static int sum(int[] values) { int sum = 0; @@ -52,6 +54,22 @@ public class ComponentColorModel extends ColorModel sum += values[i]; return sum; } + + // Create an appropriate array of bits, given a colorspace (ie, number of + // bands), size of the storage data type, and presence of an alpha band. + private static int[] findBits(ColorSpace colorSpace, int transferType, + boolean hasAlpha) + { + int[] bits; + if (hasAlpha) + bits = new int[colorSpace.getNumComponents()+1]; + else + bits = new int[colorSpace.getNumComponents()]; + + Arrays.fill(bits, DataBuffer.getDataTypeSize(transferType)); + + return bits; + } public ComponentColorModel(ColorSpace colorSpace, int[] bits, boolean hasAlpha, @@ -84,8 +102,8 @@ public class ComponentColorModel extends ColorModel boolean isAlphaPremultiplied, int transparency, int transferType) { - this(colorSpace, null, hasAlpha, isAlphaPremultiplied, - transparency, transferType); + this(colorSpace, findBits(colorSpace, transferType, hasAlpha), hasAlpha, + isAlphaPremultiplied, transparency, transferType); } public int getRed(int pixel) @@ -288,17 +306,16 @@ public class ComponentColorModel extends ColorModel public ColorModel coerceData(WritableRaster raster, boolean isAlphaPremultiplied) { - if (this.isAlphaPremultiplied == isAlphaPremultiplied) + if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha()) return this; /* TODO: provide better implementation based on the assumptions we can make due to the specific type of the color model. */ - super.coerceData(raster, isAlphaPremultiplied); + super.coerceDataWorker(raster, isAlphaPremultiplied); - return new ComponentColorModel(cspace, bits, hasAlpha(), - isAlphaPremultiplied, // argument - transparency, transferType); + return new ComponentColorModel(cspace, hasAlpha, isAlphaPremultiplied, + transparency, transferType); } public boolean isCompatibleRaster(Raster raster) diff --git a/libjava/classpath/java/awt/image/ConvolveOp.java b/libjava/classpath/java/awt/image/ConvolveOp.java index ffb834874fa..cf30e762599 100644 --- a/libjava/classpath/java/awt/image/ConvolveOp.java +++ b/libjava/classpath/java/awt/image/ConvolveOp.java @@ -38,7 +38,6 @@ exception statement from your version. */ package java.awt.image; -import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; @@ -51,11 +50,13 @@ import java.awt.geom.Rectangle2D; * with elements in the kernel to compute a new pixel. * * Each band in a Raster is convolved and copied to the destination Raster. + * For BufferedImages, convolution is applied to all components. Color + * conversion will be applied if needed. * - * For BufferedImages, convolution is applied to all components. If the - * source is not premultiplied, the data will be premultiplied before - * convolving. Premultiplication will be undone if the destination is not - * premultiplied. Color conversion will be applied if needed. + * Note that this filter ignores whether the source or destination is alpha + * premultiplied. The reference spec states that data will be premultiplied + * prior to convolving and divided back out afterwards (if needed), but testing + * has shown that this is not the case with their implementation. * * @author jlquinn@optonline.net */ @@ -104,59 +105,83 @@ public class ConvolveOp implements BufferedImageOp, RasterOp hints = null; } - - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, - * java.awt.image.BufferedImage) + /** + * Converts the source image using the kernel specified in the + * constructor. The resulting image is stored in the destination image if one + * is provided; otherwise a new BufferedImage is created and returned. + * + * The source and destination BufferedImage (if one is supplied) must have + * the same dimensions. + * + * @param src The source image. + * @param dst The destination image. + * @throws IllegalArgumentException if the rasters and/or color spaces are + * incompatible. + * @return The convolved image. */ public final BufferedImage filter(BufferedImage src, BufferedImage dst) { if (src == dst) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Source and destination images " + + "cannot be the same."); if (dst == null) dst = createCompatibleDestImage(src, src.getColorModel()); // Make sure source image is premultiplied BufferedImage src1 = src; - if (!src.isPremultiplied) + // The spec says we should do this, but mauve testing shows that Sun's + // implementation does not check this. + /* + if (!src.isAlphaPremultiplied()) { src1 = createCompatibleDestImage(src, src.getColorModel()); src.copyData(src1.getRaster()); src1.coerceData(true); } + */ BufferedImage dst1 = dst; - if (!src.getColorModel().equals(dst.getColorModel())) + if (src1.getColorModel().getColorSpace().getType() != dst.getColorModel().getColorSpace().getType()) dst1 = createCompatibleDestImage(src, src.getColorModel()); filter(src1.getRaster(), dst1.getRaster()); + // Since we don't coerceData above, we don't need to divide it back out. + // This is wrong (one mauve test specifically tests converting a non- + // premultiplied image to a premultiplied image, and it shows that Sun + // simply ignores the premultipled flag, contrary to the spec), but we + // mimic it for compatibility. + /* + if (! dst.isAlphaPremultiplied()) + dst1.coerceData(false); + */ + + // Convert between color models if needed if (dst1 != dst) - { - // Convert between color models. - // TODO Check that premultiplied alpha is handled correctly here. - Graphics2D gg = dst.createGraphics(); - gg.setRenderingHints(hints); - gg.drawImage(dst1, 0, 0, null); - gg.dispose(); - } - + new ColorConvertOp(hints).filter(dst1, dst); + return dst; } - /* (non-Javadoc) - * @see - * java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, - * java.awt.image.ColorModel) + /** + * Creates an empty BufferedImage with the size equal to the source and the + * correct number of bands. The new image is created with the specified + * ColorModel, or if no ColorModel is supplied, an appropriate one is chosen. + * + * @param src The source image. + * @param dstCM A color model for the destination image (may be null). + * @return The new compatible destination image. */ public BufferedImage createCompatibleDestImage(BufferedImage src, - ColorModel dstCM) + ColorModel dstCM) { - // FIXME: set properties to those in src - return new BufferedImage(dstCM, - src.getRaster().createCompatibleWritableRaster(), - src.isPremultiplied, null); + if (dstCM != null) + return new BufferedImage(dstCM, + src.getRaster().createCompatibleWritableRaster(), + src.isAlphaPremultiplied(), null); + + return new BufferedImage(src.getWidth(), src.getHeight(), src.getType()); } /* (non-Javadoc) @@ -168,6 +193,8 @@ public class ConvolveOp implements BufferedImageOp, RasterOp } /** + * Get the edge condition for this Op. + * * @return The edge condition. */ public int getEdgeCondition() @@ -185,9 +212,22 @@ public class ConvolveOp implements BufferedImageOp, RasterOp return (Kernel) kernel.clone(); } - /* (non-Javadoc) - * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, - * java.awt.image.WritableRaster) + /** + * Converts the source raster using the kernel specified in the constructor. + * The resulting raster is stored in the destination raster if one is + * provided; otherwise a new WritableRaster is created and returned. + * + * If the convolved value for a sample is outside the range of [0-255], it + * will be clipped. + * + * The source and destination raster (if one is supplied) cannot be the same, + * and must also have the same dimensions. + * + * @param src The source raster. + * @param dest The destination raster. + * @throws IllegalArgumentException if the rasters identical. + * @throws ImagingOpException if the convolution is not possible. + * @return The transformed raster. */ public final WritableRaster filter(Raster src, WritableRaster dest) { @@ -209,6 +249,11 @@ public class ConvolveOp implements BufferedImageOp, RasterOp int top = kernel.getYOrigin(); int bottom = Math.max(kHeight - top - 1, 0); + // Calculate max sample values for clipping + int[] maxValue = src.getSampleModel().getSampleSize(); + for (int i = 0; i < maxValue.length; i++) + maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1; + // process the region that is reachable... int regionW = src.width - left - right; int regionH = src.height - top - bottom; @@ -228,7 +273,14 @@ public class ConvolveOp implements BufferedImageOp, RasterOp v += tmp[tmp.length - i - 1] * kvals[i]; // FIXME: in the above line, I've had to reverse the order of // the samples array to make the tests pass. I haven't worked - // out why this is necessary. + // out why this is necessary. + + // This clipping is is undocumented, but determined by testing. + if (v > maxValue[b]) + v = maxValue[b]; + else if (v < 0) + v = 0; + dest.setSample(x + kernel.getXOrigin(), y + kernel.getYOrigin(), b, v); } @@ -310,13 +362,14 @@ public class ConvolveOp implements BufferedImageOp, RasterOp return src.getBounds(); } - /** Return corresponding destination point for source point. + /** + * Returns the corresponding destination point for a source point. Because + * this is not a geometric operation, the destination and source points will + * be identical. * - * ConvolveOp will return the value of src unchanged. * @param src The source point. - * @param dst The destination point. - * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, - * java.awt.geom.Point2D) + * @param dst The transformed destination point. + * @return The transformed destination point. */ public final Point2D getPoint2D(Point2D src, Point2D dst) { diff --git a/libjava/classpath/java/awt/image/CropImageFilter.java b/libjava/classpath/java/awt/image/CropImageFilter.java index 4fcfdec4475..53b41562044 100644 --- a/libjava/classpath/java/awt/image/CropImageFilter.java +++ b/libjava/classpath/java/awt/image/CropImageFilter.java @@ -91,11 +91,12 @@ public class CropImageFilter extends ImageFilter * * @param props the list of properties associated with this image */ - public void setProperties(Hashtable props) + public void setProperties(Hashtable<?, ?> props) { - props.put("filters", "CropImageFilter"); - if (consumer != null) - consumer.setProperties(props); + Hashtable<Object, Object> prop2 = (Hashtable<Object, Object>) props; + prop2.put("filters", "CropImageFilter"); + if (consumer != null) + consumer.setProperties(prop2); } /** diff --git a/libjava/classpath/java/awt/image/DirectColorModel.java b/libjava/classpath/java/awt/image/DirectColorModel.java index 579dc97dfc7..dab15319fce 100644 --- a/libjava/classpath/java/awt/image/DirectColorModel.java +++ b/libjava/classpath/java/awt/image/DirectColorModel.java @@ -393,20 +393,20 @@ public class DirectColorModel extends PackedColorModel return Buffers.getData(buffer); } - public final ColorModel coerceData (WritableRaster raster, - boolean isAlphaPremultiplied) + public ColorModel coerceData (WritableRaster raster, + boolean isAlphaPremultiplied) { - if (this.isAlphaPremultiplied == isAlphaPremultiplied) + if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha()) return this; /* TODO: provide better implementation based on the assumptions we can make due to the specific type of the color model. */ - super.coerceData(raster, isAlphaPremultiplied); - - return new ComponentColorModel(cspace, bits, hasAlpha(), - isAlphaPremultiplied, // argument - transparency, transferType); + super.coerceDataWorker(raster, isAlphaPremultiplied); + + return new DirectColorModel(cspace, pixel_bits, getRedMask(), + getGreenMask(), getBlueMask(), getAlphaMask(), + isAlphaPremultiplied, transferType); } public boolean isCompatibleRaster(Raster raster) diff --git a/libjava/classpath/java/awt/image/ImageConsumer.java b/libjava/classpath/java/awt/image/ImageConsumer.java index fc5ed11e5ca..11f64f97867 100644 --- a/libjava/classpath/java/awt/image/ImageConsumer.java +++ b/libjava/classpath/java/awt/image/ImageConsumer.java @@ -136,7 +136,7 @@ public interface ImageConsumer * * @param props the list of properties associated with this image */ - void setProperties(Hashtable props); + void setProperties(Hashtable<?,?> props); /** * This <code>ColorModel</code> should indicate the model used by diff --git a/libjava/classpath/java/awt/image/ImageFilter.java b/libjava/classpath/java/awt/image/ImageFilter.java index c39c4a428f9..0ead45a4ae0 100644 --- a/libjava/classpath/java/awt/image/ImageFilter.java +++ b/libjava/classpath/java/awt/image/ImageFilter.java @@ -49,180 +49,178 @@ import java.util.Hashtable; */ public class ImageFilter implements ImageConsumer, Cloneable { - /** - * The consumer this filter is filtering an image data stream for. - * It is initialized in the method <code>getFilterInstance</code>. - */ - protected ImageConsumer consumer = null; - - /** - * The <code>ImageConsumer</code> can use this method to request - * the pixels be delivered in top-down, left-right order. - * <br> - * The filter can respond in three different ways. - * <ul> - * <li>The default behavior is to forward the request to the - * <code>ImageProducer</code> - * using the method <code>requestTopDownLeftRightResend</code> - * and using the filter as the consumer.</li> - * <li>The filter has the pixels and can retransmit them in the - * top-down, left-right order.</li> - * <li>The filter can do nothing when this method is called.</li> - * </ul> - */ - public void resendTopDownLeftRight(ImageProducer ip) - { - ip.requestTopDownLeftRightResend(this); - } - - /** - * By default, returns a shallow copy of the object created by - * <code>Object.clone()</code> - * - * @see java.lang.Object#clone () - */ - public Object clone() - { - try - { - return super.clone(); - } - catch (CloneNotSupportedException e) - { - // This should never happen as this class implements the - // Cloneable interface. - throw new InternalError (); - } - } - - /** - * This is the only method which can set the - * <code>ImageConsumer</code> for this filter. By default a clone - * of this filter with the appropriate consumer set is returned. - * - * @see #clone () - */ - public ImageFilter getFilterInstance(ImageConsumer ic) - { - if ( ic == null ) - throw new IllegalArgumentException("null argument for ImageFilter.getFilterInstance(ImageConsumer)"); - - consumer = ic; - ImageFilter f = (ImageFilter)clone(); - consumer = null; - return f; - } - - /** - * An <code>ImageProducer</code> indicates the size of the image - * being produced using this method. A filter can override this - * method to intercept these calls from the producer in order to - * change either the width or the height before in turn calling - * the consumer's <code>setDimensions</code> method. - * - * @param width the width of the image - * @param height the height of the image - */ - public void setDimensions(int width, int height) - { - if (consumer != null) - consumer.setDimensions(width, height); - } - - /** - * An <code>ImageProducer</code> can set a list of properties - * associated with this image by using this method. - * - * @param props the list of properties associated with this image - */ - public void setProperties(Hashtable props) - { - props.put("filters", "ImageFilter"); - if (consumer != null) - consumer.setProperties(props); - } - - /** - * Override this method to process calls to this method from the - * <code>ImageProducer</code>. By default the <code>setColorModel</code> - * method of the consumer is called with the specified <code>model</code>. - * - * @param model the color model to be used most often by setPixels - * @see ColorModel */ - public void setColorModel(ColorModel model) - { - if (consumer != null) - consumer.setColorModel(model); - } - - /** - * The <code>ImageProducer</code> should call this method with a - * bit mask of hints from any of <code>RANDOMPIXELORDER</code>, - * <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>, - * <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the - * <code>ImageConsumer</code> interface. - * - * @param flags a bit mask of hints - * @see ImageConsumer - */ - public void setHints(int flags) - { - if (consumer != null) - consumer.setHints(flags); - } - - /** - * This function delivers a rectangle of pixels where any - * pixel(m,n) is stored in the array as a <code>byte</code> at - * index (n * scansize + m + offset). - * - * @param x the x coordinate of the rectangle - * @param y the y coordinate of the rectangle - * @param w the width of the rectangle - * @param h the height of the rectangle - * @param model the <code>ColorModel</code> used to translate the pixels - * @param pixels the array of pixel values - * @param offset the index of the first pixels in the <code>pixels</code> array - * @param scansize the width to use in extracting pixels from the <code>pixels</code> array - */ - public void setPixels(int x, int y, int w, int h, - ColorModel model, byte[] pixels, int offset, int scansize) - { - if (consumer != null) - consumer.setPixels(x, y, w, h, model, pixels, offset, scansize); - } - - /** - * This function delivers a rectangle of pixels where any - * pixel(m,n) is stored in the array as an <code>int</code> at - * index (n * scansize + m + offset). - * - * @param x the x coordinate of the rectangle - * @param y the y coordinate of the rectangle - * @param w the width of the rectangle - * @param h the height of the rectangle - * @param model the <code>ColorModel</code> used to translate the pixels - * @param pixels the array of pixel values - * @param offset the index of the first pixels in the <code>pixels</code> array - * @param scansize the width to use in extracting pixels from the <code>pixels</code> array - */ - public void setPixels(int x, int y, int w, int h, - ColorModel model, int[] pixels, int offset, int scansize) - { - if (consumer != null) - consumer.setPixels(x, y, w, h, model, pixels, offset, scansize); - } - - /** - * The <code>ImageProducer</code> calls this method to indicate a - * single frame or the entire image is complete. The method is - * also used to indicate an error in loading or producing the - * image. - */ - public void imageComplete(int status) - { - if (consumer != null) - consumer.imageComplete(status); - } + /** + * The consumer this filter is filtering an image data stream for. + * It is initialized in the method <code>getFilterInstance</code>. + */ + protected ImageConsumer consumer = null; + + /** + * The <code>ImageConsumer</code> can use this method to request + * the pixels be delivered in top-down, left-right order. + * <br> + * The filter can respond in three different ways. + * <ul> + * <li>The default behavior is to forward the request to the + * <code>ImageProducer</code> + * using the method <code>requestTopDownLeftRightResend</code> + * and using the filter as the consumer.</li> + * <li>The filter has the pixels and can retransmit them in the + * top-down, left-right order.</li> + * <li>The filter can do nothing when this method is called.</li> + * </ul> + */ + public void resendTopDownLeftRight(ImageProducer ip) + { + ip.requestTopDownLeftRightResend(this); + } + + /** + * By default, returns a shallow copy of the object created by + * <code>Object.clone()</code> + * + * @see java.lang.Object#clone () + */ + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + // This should never happen as this class implements the + // Cloneable interface. + throw new InternalError (); + } + } + + /** + * This is the only method which can set the + * <code>ImageConsumer</code> for this filter. By default a clone + * of this filter with the appropriate consumer set is returned. + * + * @see #clone () + */ + public ImageFilter getFilterInstance(ImageConsumer ic) + { + ImageFilter f = (ImageFilter)clone(); + f.consumer = ic; + return f; + } + + /** + * An <code>ImageProducer</code> indicates the size of the image + * being produced using this method. A filter can override this + * method to intercept these calls from the producer in order to + * change either the width or the height before in turn calling + * the consumer's <code>setDimensions</code> method. + * + * @param width the width of the image + * @param height the height of the image + */ + public void setDimensions(int width, int height) + { + consumer.setDimensions(width, height); + } + + /** + * An <code>ImageProducer</code> can set a list of properties + * associated with this image by using this method. + * + * @param props the list of properties associated with this image + */ + public void setProperties(Hashtable<?,?> props) + { + Hashtable copy = (Hashtable) props.clone(); + Object o = copy.get("filters"); + if (o == null) + copy.put("filters", toString()); + else if (o instanceof String) + copy.put("filters", ((String) o) + toString()); + + consumer.setProperties(copy); + } + + /** + * Override this method to process calls to this method from the + * <code>ImageProducer</code>. By default the <code>setColorModel</code> + * method of the consumer is called with the specified <code>model</code>. + * + * @param model the color model to be used most often by setPixels + * + * @see ColorModel + */ + public void setColorModel(ColorModel model) + { + consumer.setColorModel(model); + } + + /** + * The <code>ImageProducer</code> should call this method with a + * bit mask of hints from any of <code>RANDOMPIXELORDER</code>, + * <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>, + * <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the + * <code>ImageConsumer</code> interface. + * + * @param flags a bit mask of hints + * @see ImageConsumer + */ + public void setHints(int flags) + { + consumer.setHints(flags); + } + + /** + * This function delivers a rectangle of pixels where any + * pixel(m,n) is stored in the array as a <code>byte</code> at + * index (n * scansize + m + offset). + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @param model the <code>ColorModel</code> used to translate the pixels + * @param pixels the array of pixel values + * @param offset the index of the first pixels in the <code>pixels</code> array + * @param scansize the width to use in extracting pixels from the <code>pixels</code> array + */ + public void setPixels(int x, int y, int w, int h, + ColorModel model, byte[] pixels, int offset, + int scansize) + { + consumer.setPixels(x, y, w, h, model, pixels, offset, scansize); + } + + /** + * This function delivers a rectangle of pixels where any + * pixel(m,n) is stored in the array as an <code>int</code> at + * index (n * scansize + m + offset). + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @param model the <code>ColorModel</code> used to translate the pixels + * @param pixels the array of pixel values + * @param offset the index of the first pixels in the <code>pixels</code> array + * @param scansize the width to use in extracting pixels from the <code>pixels</code> array + */ + public void setPixels(int x, int y, int w, int h, + ColorModel model, int[] pixels, int offset, + int scansize) + { + consumer.setPixels(x, y, w, h, model, pixels, offset, scansize); + } + + /** + * The <code>ImageProducer</code> calls this method to indicate a + * single frame or the entire image is complete. The method is + * also used to indicate an error in loading or producing the + * image. + */ + public void imageComplete(int status) + { + consumer.imageComplete(status); + } } - diff --git a/libjava/classpath/java/awt/image/IndexColorModel.java b/libjava/classpath/java/awt/image/IndexColorModel.java index 299b4dc0d86..46879cc98c9 100644 --- a/libjava/classpath/java/awt/image/IndexColorModel.java +++ b/libjava/classpath/java/awt/image/IndexColorModel.java @@ -134,10 +134,6 @@ public class IndexColorModel extends ColorModel if (size < 1) throw new IllegalArgumentException("size < 1"); map_size = size; - if (0 <= trans && trans < size) { - this.trans = trans; - transparency = BITMASK; - } rgb = new int[size]; for (int i = 0; i < size; i++) { @@ -146,6 +142,9 @@ public class IndexColorModel extends ColorModel | ((greens[i] & 0xff) << 8) | (blues[i] & 0xff)); } + + setTransparentPixel(trans); + // Generate a bigint with 1's for every pixel validBits = validBits.setBit(size).subtract(BigInteger.ONE); } @@ -275,8 +274,6 @@ public class IndexColorModel extends ColorModel throw new IllegalArgumentException("size < 1"); map_size = size; opaque = !hasAlpha; - if (0 <= trans && trans < size) - this.trans = trans; rgb = new int[size]; if (hasAlpha) @@ -318,6 +315,8 @@ public class IndexColorModel extends ColorModel transparency = BITMASK; } + setTransparentPixel(trans); + // Generate a bigint with 1's for every pixel validBits = validBits.setBit(size).subtract(BigInteger.ONE); } @@ -361,9 +360,6 @@ public class IndexColorModel extends ColorModel throw new IllegalArgumentException("size < 1"); map_size = size; opaque = !hasAlpha; - if (0 <= trans && trans < size) - this.trans = trans; - rgb = new int[size]; if (!hasAlpha) for (int i = 0; i < size; i++) @@ -371,6 +367,8 @@ public class IndexColorModel extends ColorModel else System.arraycopy(cmap, start, rgb, 0, size); + setTransparentPixel(trans); + // Generate a bigint with 1's for every pixel validBits = validBits.setBit(size).subtract(BigInteger.ONE); } @@ -584,12 +582,7 @@ public class IndexColorModel extends ColorModel */ public final int getAlpha(int pixel) { - if (opaque && pixel != trans) - return 255; - if ((pixel == trans && trans != -1) || pixel >= map_size) - return 0; - - return (0xFF000000 & rgb[pixel]) >> 24; + return (rgb[pixel] >> 24) & 0xFF; } /** @@ -694,4 +687,43 @@ public class IndexColorModel extends ColorModel return im; } + + /** + * Creates a {@link SampleModel} that is compatible to this color model. + * This will be a {@link MultiPixelPackedSampleModel} for bits/pixel of + * 1, 2 or 4, or a {@link ComponentColorModel} for the other cases. + * + * @param w the width of the sample model to create + * @param h the height of the sample model to create + * + * @return a compatible sample model + */ + public SampleModel createCompatibleSampleModel(int w, int h) + { + SampleModel sm; + if (pixel_bits == 1 || pixel_bits == 2 || pixel_bits == 4) + sm = new MultiPixelPackedSampleModel(transferType, w, h, pixel_bits); + else + sm = new ComponentSampleModel(transferType, w, h, 1, w, new int[]{0}); + return sm; + } + + /** + * Sets the transparent pixel. This is called by the various constructors. + * + * @param t the transparent pixel + */ + private void setTransparentPixel(int t) + { + if (t >= 0 && t < map_size) + { + rgb[t] &= 0xffffff; // Make the value transparent. + trans = t; + if (transparency == OPAQUE) + { + transparency = BITMASK; + hasAlpha = true; + } + } + } } diff --git a/libjava/classpath/java/awt/image/LookupOp.java b/libjava/classpath/java/awt/image/LookupOp.java index 46e72fe6183..5b0cf783168 100644 --- a/libjava/classpath/java/awt/image/LookupOp.java +++ b/libjava/classpath/java/awt/image/LookupOp.java @@ -38,7 +38,6 @@ exception statement from your version. */ package java.awt.image; -import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; @@ -67,7 +66,8 @@ public class LookupOp implements BufferedImageOp, RasterOp private LookupTable lut; private RenderingHints hints; - /** Construct a new LookupOp. + /** + * Construct a new LookupOp using the given LookupTable. * * @param lookup LookupTable to use. * @param hints Rendering hints (can be null). @@ -78,16 +78,40 @@ public class LookupOp implements BufferedImageOp, RasterOp this.hints = hints; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, java.awt.image.BufferedImage) + /** + * Converts the source image using the lookup table specified in the + * constructor. The resulting image is stored in the destination image if one + * is provided; otherwise a new BufferedImage is created and returned. + * + * The source image cannot use an IndexColorModel, and the destination image + * (if one is provided) must have the same size. + * + * @param src The source image. + * @param dst The destination image. + * @throws IllegalArgumentException if the rasters and/or color spaces are + * incompatible. + * @throws ArrayIndexOutOfBoundsException if a pixel in the source is not + * contained in the LookupTable. + * @return The convolved image. */ public final BufferedImage filter(BufferedImage src, BufferedImage dst) { if (src.getColorModel() instanceof IndexColorModel) throw new IllegalArgumentException("LookupOp.filter: IndexColorModel " + "not allowed"); + + if (lut.getNumComponents() != 1 + && lut.getNumComponents() != src.getColorModel().getNumComponents() + && lut.getNumComponents() != src.getColorModel().getNumColorComponents()) + throw new IllegalArgumentException("LookupOp.filter: Incompatible " + + "lookup table and source image"); + if (dst == null) - dst = createCompatibleDestImage(src, src.getColorModel()); + dst = createCompatibleDestImage(src, null); + + else if (src.getHeight() != dst.getHeight() || src.getWidth() != dst.getWidth()) + throw new IllegalArgumentException("Source and destination images are " + + "different sizes."); // Set up for potential colormodel mismatch BufferedImage tgt; @@ -116,33 +140,35 @@ public class LookupOp implements BufferedImageOp, RasterOp sr.getPixel(x, y, dbuf); System.arraycopy(dbuf, 0, tmp, 0, tmpBands); dr.setPixel(x, y, lut.lookupPixel(tmp, dbuf)); + + /* The reference implementation does not use LookupTable.lookupPixel, + * but rather it seems to copy the table into a native array. The + * effect of this (a probable bug in their implementation) is that + * an out-of-bounds lookup on a ByteLookupTable will *not* throw an + * out of bounds exception, but will instead return random garbage. + * A bad lookup on a ShortLookupTable, however, will throw an + * exception. + * + * Instead of mimicing this behaviour, we always throw an + * ArrayOutofBoundsException by virtue of using + * LookupTable.lookupPixle. + */ } } - else if (lut.getNumComponents() != 1 - && - lut.getNumComponents() != src.getColorModel().getNumComponents()) - throw new IllegalArgumentException("LookupOp.filter: " - + "Incompatible lookup " - + "table and source image"); - - // No alpha to ignore - int[] dbuf = new int[src.getColorModel().getNumComponents()]; - - // Filter the pixels - for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++) - for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++) - dr.setPixel(x, y, lut.lookupPixel(sr.getPixel(x, y, dbuf), dbuf)); - - if (tgt != dst) + else { - // Convert between color models. - // TODO Check that premultiplied alpha is handled correctly here. - Graphics2D gg = dst.createGraphics(); - gg.setRenderingHints(hints); - gg.drawImage(tgt, 0, 0, null); - gg.dispose(); + // No alpha to ignore + int[] dbuf = new int[src.getColorModel().getNumComponents()]; + + // Filter the pixels + for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++) + for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++) + dr.setPixel(x, y, lut.lookupPixel(sr.getPixel(x, y, dbuf), dbuf)); } + if (tgt != dst) + new ColorConvertOp(hints).filter(tgt, dst); + return dst; } @@ -160,18 +186,27 @@ public class LookupOp implements BufferedImageOp, RasterOp public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { - // FIXME: set properties to those in src - return new BufferedImage(dstCM, - src.getRaster().createCompatibleWritableRaster(), - src.isPremultiplied, null); + if (dstCM != null) + return new BufferedImage(dstCM, + src.getRaster().createCompatibleWritableRaster(), + src.isAlphaPremultiplied(), null); + + // This is a strange exception, done for compatibility with the reference + // (as demonstrated by a mauve testcase) + int imgType = src.getType(); + if (imgType == BufferedImage.TYPE_USHORT_GRAY) + imgType = BufferedImage.TYPE_BYTE_GRAY; + + return new BufferedImage(src.getWidth(), src.getHeight(), imgType); } - /** Return corresponding destination point for source point. + /** + * Returns the corresponding destination point for a given source point. + * + * This Op will return the source point unchanged. * - * LookupOp will return the value of src unchanged. * @param src The source point. * @param dst The destination point. - * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D) */ public final Point2D getPoint2D(Point2D src, Point2D dst) { @@ -182,7 +217,11 @@ public class LookupOp implements BufferedImageOp, RasterOp return dst; } - /** Return the LookupTable for this op. */ + /** + * Return the LookupTable for this op. + * + * @return The lookup table. + */ public final LookupTable getTable() { return lut; @@ -196,7 +235,8 @@ public class LookupOp implements BufferedImageOp, RasterOp return hints; } - /** Filter a raster through a lookup table. + /** + * Filter a raster through a lookup table. * * Applies the lookup table for this Rasterop to each pixel of src and * puts the results in dest. If dest is null, a new Raster is created and @@ -206,8 +246,9 @@ public class LookupOp implements BufferedImageOp, RasterOp * @param dest The destination raster. * @return The WritableRaster with the filtered pixels. * @throws IllegalArgumentException if lookup table has more than one - * component but not the same as src and dest. - * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster) + * component but not the same as src and dest. + * @throws ArrayIndexOutOfBoundsException if a pixel in the source is not + * contained in the LookupTable. */ public final WritableRaster filter(Raster src, WritableRaster dest) { @@ -216,12 +257,13 @@ public class LookupOp implements BufferedImageOp, RasterOp dest = createCompatibleDestRaster(src); else if (src.getNumBands() != dest.getNumBands()) - throw new IllegalArgumentException(); - - if (lut.getNumComponents() != 1 - && lut.getNumComponents() != src.getNumBands()) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Source and destination rasters " + + "are incompatible."); + if (lut.getNumComponents() != 1 + && lut.getNumComponents() != src.getNumBands()) + throw new IllegalArgumentException("Lookup table is incompatible with " + + "this raster."); // Allocate pixel storage. int[] tmp = new int[src.getNumBands()]; @@ -230,6 +272,19 @@ public class LookupOp implements BufferedImageOp, RasterOp for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++) for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++) dest.setPixel(x, y, lut.lookupPixel(src.getPixel(x, y, tmp), tmp)); + + /* The reference implementation does not use LookupTable.lookupPixel, + * but rather it seems to copy the table into a native array. The + * effect of this (a probable bug in their implementation) is that + * an out-of-bounds lookup on a ByteLookupTable will *not* throw an + * out of bounds exception, but will instead return random garbage. + * A bad lookup on a ShortLookupTable, however, will throw an + * exception. + * + * Instead of mimicing this behaviour, we always throw an + * ArrayOutofBoundsException by virtue of using + * LookupTable.lookupPixle. + */ return dest; } diff --git a/libjava/classpath/java/awt/image/MemoryImageSource.java b/libjava/classpath/java/awt/image/MemoryImageSource.java index 95cd4081922..83a03ca449c 100644 --- a/libjava/classpath/java/awt/image/MemoryImageSource.java +++ b/libjava/classpath/java/awt/image/MemoryImageSource.java @@ -1,5 +1,5 @@ /* MemoryImageSource.java -- Java class for providing image data - Copyright (C) 1999, 2004 Free Software Foundation, Inc. + Copyright (C) 1999, 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -41,6 +41,9 @@ package java.awt.image; import java.util.Hashtable; import java.util.Vector; +/** + * An image producer that delivers image data from an array. + */ public class MemoryImageSource implements ImageProducer { private boolean animated = false; @@ -73,10 +76,19 @@ public class MemoryImageSource implements ImageProducer } /** - * Constructs an ImageProducer from memory + * Constructs an ImageProducer from memory. + * + * @param w the image width. + * @param h the image height. + * @param cm the color model. + * @param pix the image data. + * @param off the offset to the first pixel in the array. + * @param scan the number of array elements from a pixel on one row to the + * corresponding pixel on the next row. + * @param props image properties (<code>null</code> permitted). */ public MemoryImageSource(int w, int h, ColorModel cm, byte[] pix, int off, - int scan, Hashtable props) + int scan, Hashtable<?,?> props) { width = w; height = h; @@ -106,10 +118,19 @@ public class MemoryImageSource implements ImageProducer } /** - Constructs an ImageProducer from memory - */ + * Constructs an ImageProducer from memory + * + * @param w the image width. + * @param h the image height. + * @param cm the color model. + * @param pix the image data. + * @param off the offset to the first pixel in the array. + * @param scan the number of array elements from a pixel on one row to the + * corresponding pixel on the next row. + * @param props image properties (<code>null</code> permitted). + */ public MemoryImageSource(int w, int h, ColorModel cm, int[] pix, int off, - int scan, Hashtable props) + int scan, Hashtable<?,?> props) { width = w; height = h; @@ -122,16 +143,32 @@ public class MemoryImageSource implements ImageProducer } /** - * Constructs an ImageProducer from memory using the default RGB ColorModel + * Constructs an ImageProducer from memory using the default RGB ColorModel. + * + * @param w the image width. + * @param h the image height. + * @param pix the image data. + * @param off the offset to the first pixel in the array. + * @param scan the number of array elements from a pixel on one row to the + * corresponding pixel on the next row. + * @param props image properties (<code>null</code> permitted). + */ public MemoryImageSource(int w, int h, int[] pix, int off, int scan, - Hashtable props) + Hashtable<?,?> props) { this(w, h, ColorModel.getRGBdefault(), pix, off, scan, props); } /** - * Constructs an ImageProducer from memory using the default RGB ColorModel + * Constructs an ImageProducer from memory using the default RGB ColorModel. + * + * @param w the image width. + * @param h the image height. + * @param pix the image data. + * @param off the offset to the first pixel in the array. + * @param scan the number of array elements from a pixel on one row to the + * corresponding pixel on the next row. */ public MemoryImageSource(int w, int h, int[] pix, int off, int scan) { @@ -141,6 +178,8 @@ public class MemoryImageSource implements ImageProducer /** * Used to register an <code>ImageConsumer</code> with this * <code>ImageProducer</code>. + * + * @param ic the image consumer. */ public synchronized void addConsumer(ImageConsumer ic) { @@ -153,6 +192,8 @@ public class MemoryImageSource implements ImageProducer /** * Used to determine if the given <code>ImageConsumer</code> is * already registered with this <code>ImageProducer</code>. + * + * @param ic the image consumer. */ public synchronized boolean isConsumer(ImageConsumer ic) { @@ -164,6 +205,8 @@ public class MemoryImageSource implements ImageProducer /** * Used to remove an <code>ImageConsumer</code> from the list of * registered consumers for this <code>ImageProducer</code>. + * + * @param ic the image consumer. */ public synchronized void removeConsumer(ImageConsumer ic) { @@ -197,6 +240,8 @@ public class MemoryImageSource implements ImageProducer * Used to register an <code>ImageConsumer</code> with this * <code>ImageProducer</code> and then request that this producer * resend the image data in the order top-down, left-right. + * + * @param ic the image consumer. */ public void requestTopDownLeftRightResend(ImageConsumer ic) { @@ -219,7 +264,7 @@ public class MemoryImageSource implements ImageProducer * sending animation. If this flag is set then full buffers are sent * in the newPixels methods instead of just regions. * - * @param fullbuffers - a flag indicating whether to send the full buffers + * @param fullbuffers a flag indicating whether to send the full buffers */ public synchronized void setFullBufferUpdates(boolean fullbuffers) { @@ -260,6 +305,11 @@ public class MemoryImageSource implements ImageProducer /** * Send an animation frame to the image consumers containing the specified * pixels unless setFullBufferUpdates is set. + * + * @param x the x-coordinate. + * @param y the y-coordinate. + * @param w the width. + * @param h the height. */ public synchronized void newPixels(int x, int y, int w, int h) { @@ -306,6 +356,12 @@ public class MemoryImageSource implements ImageProducer * * If framenotify is set then a notification is sent when the frame * is sent otherwise no status is sent. + * + * @param x the x-coordinate. + * @param y the y-coordinate. + * @param w the width. + * @param h the height. + * @param framenotify send notification? */ public synchronized void newPixels(int x, int y, int w, int h, boolean framenotify) diff --git a/libjava/classpath/java/awt/image/PixelGrabber.java b/libjava/classpath/java/awt/image/PixelGrabber.java index 70a80af3cf9..fc5e0efc0a5 100644 --- a/libjava/classpath/java/awt/image/PixelGrabber.java +++ b/libjava/classpath/java/awt/image/PixelGrabber.java @@ -55,7 +55,7 @@ public class PixelGrabber implements ImageConsumer ColorModel model = ColorModel.getRGBdefault(); int hints; - Hashtable props; + Hashtable<?,?> props; int int_pixel_buffer[]; boolean ints_delivered = false; @@ -442,7 +442,7 @@ public class PixelGrabber implements ImageConsumer * @param props a list of properties associated with the image being * produced */ - public synchronized void setProperties(Hashtable props) + public synchronized void setProperties(Hashtable<?,?> props) { this.props = props; } diff --git a/libjava/classpath/java/awt/image/RGBImageFilter.java b/libjava/classpath/java/awt/image/RGBImageFilter.java index ecfed0674f1..c777fecd901 100644 --- a/libjava/classpath/java/awt/image/RGBImageFilter.java +++ b/libjava/classpath/java/awt/image/RGBImageFilter.java @@ -46,228 +46,220 @@ package java.awt.image; */ public abstract class RGBImageFilter extends ImageFilter { - protected ColorModel origmodel; + protected ColorModel origmodel; - protected ColorModel newmodel; + protected ColorModel newmodel; - /** - Specifies whether to apply the filter to the index entries of the - IndexColorModel. Subclasses should set this to true if the filter - does not depend on the pixel's coordinate. - */ - protected boolean canFilterIndexColorModel = false; + /** + * Specifies whether to apply the filter to the index entries of the + * IndexColorModel. Subclasses should set this to true if the filter + * does not depend on the pixel's coordinate. + */ + protected boolean canFilterIndexColorModel = false; - /** - Construct new RGBImageFilter. - */ - public RGBImageFilter() - { - } + /** + * Construct new RGBImageFilter. + */ + public RGBImageFilter() + { + } - /** - * Sets the ColorModel used to filter with. If the specified ColorModel is IndexColorModel - * and canFilterIndexColorModel is true, we subsitute the ColorModel for a filtered one - * here and in setPixels whenever the original one appears. Otherwise overrides the default - * ColorModel of ImageProducer and specifies the default RGBColorModel - * - * @param model the color model to be used most often by setPixels - * @see ColorModel */ - public void setColorModel(ColorModel model) - { - origmodel = model; - newmodel = model; + /** + * Sets the ColorModel used to filter with. If the specified ColorModel is + * IndexColorModel and canFilterIndexColorModel is true, we subsitute the + * ColorModel for a filtered one here and in setPixels whenever the original + * one appears. Otherwise overrides the default ColorModel of ImageProducer + * and specifies the default RGBColorModel + * + * @param model the color model to be used most often by setPixels + * + * @see ColorModel + */ + public void setColorModel(ColorModel model) + { + if ((model instanceof IndexColorModel) && canFilterIndexColorModel) + { + ColorModel newCM = filterIndexColorModel((IndexColorModel) model); + substituteColorModel(model, newCM); + consumer.setColorModel(newmodel); + } + else + { + consumer.setColorModel(ColorModel.getRGBdefault()); + } + } - if( ( model instanceof IndexColorModel) && canFilterIndexColorModel ) { - newmodel = filterIndexColorModel( (IndexColorModel) model ); - if (consumer != null) - consumer.setColorModel(newmodel); - } - else { - if (consumer != null) - consumer.setColorModel(ColorModel.getRGBdefault()); - } - } - - /** - Registers a new ColorModel to subsitute for the old ColorModel when - setPixels encounters the a pixel with the old ColorModel. The pixel - remains unchanged except for a new ColorModel. - - @param oldcm the old ColorModel - @param newcm the new ColorModel - */ - public void substituteColorModel(ColorModel oldcm, - ColorModel newcm) - { - origmodel = oldcm; - newmodel = newcm; - } - - /** - Filters an IndexColorModel through the filterRGB function. Uses - coordinates of -1 to indicate its filtering an index and not a pixel. - - @param icm an IndexColorModel to filter - */ - public IndexColorModel filterIndexColorModel(IndexColorModel icm) - { - int len = icm.getMapSize(), rgb; - byte reds[] = new byte[len], greens[] = new byte[len], blues[] = new byte[len], alphas[] = new byte[len]; - - icm.getAlphas( alphas ); - icm.getReds( reds ); - icm.getGreens( greens ); - icm.getBlues( blues ); - - for( int i = 0; i < len; i++ ) - { - rgb = filterRGB( -1, -1, makeColor ( alphas[i], reds[i], greens[i], blues[i] ) ); - alphas[i] = (byte)(( 0xff000000 & rgb ) >> 24); - reds[i] = (byte)(( 0xff0000 & rgb ) >> 16); - greens[i] = (byte)(( 0xff00 & rgb ) >> 8); - blues[i] = (byte)(0xff & rgb); - } - return new IndexColorModel( icm.getPixelSize(), len, reds, greens, blues, alphas ); - } - - private int makeColor( byte a, byte r, byte g, byte b ) - { - return ( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b ); - } - - /** - This functions filters a set of RGB pixels through filterRGB. - - @param x the x coordinate of the rectangle - @param y the y coordinate of the rectangle - @param w the width of the rectangle - @param h the height of the rectangle - @param pixels the array of pixel values - @param offset the index of the first pixels in the <code>pixels</code> array - @param scansize the width to use in extracting pixels from the <code>pixels</code> array - */ - public void filterRGBPixels(int x, int y, int w, int h, int[] pixels, - int offset, int scansize) - { - for (int yp = 0; yp < h; yp++) - { - for (int xp = 0; xp < w; xp++) - { - pixels[offset + xp] = filterRGB(xp + x, yp + y, pixels[offset + xp]); - } - offset += scansize; - } - } - - - /** - * If the ColorModel is the same ColorModel which as already converted - * then it converts it the converted ColorModel. Otherwise it passes the - * array of pixels through filterRGBpixels. - * - * @param x the x coordinate of the rectangle - * @param y the y coordinate of the rectangle - * @param w the width of the rectangle - * @param h the height of the rectangle - * @param model the <code>ColorModel</code> used to translate the pixels - * @param pixels the array of pixel values - * @param offset the index of the first pixels in the <code>pixels</code> array - * @param scansize the width to use in extracting pixels from the <code>pixels</code> array - */ - public void setPixels(int x, int y, int w, int h, - ColorModel model, byte[] pixels, - int offset, int scansize) - { - if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel) - { - if (consumer != null) - consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize); - } - else - { - int intPixels[] = - convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize ); - filterRGBPixels( x, y, w, h, intPixels, offset, scansize ); - if (consumer != null) - consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), intPixels, offset, scansize); - } - } - - /** - * This function delivers a rectangle of pixels where any - * pixel(m,n) is stored in the array as an <code>int</code> at - * index (n * scansize + m + offset). - * - * @param x the x coordinate of the rectangle - * @param y the y coordinate of the rectangle - * @param w the width of the rectangle - * @param h the height of the rectangle - * @param model the <code>ColorModel</code> used to translate the pixels - * @param pixels the array of pixel values - * @param offset the index of the first pixels in the <code>pixels</code> array - * @param scansize the width to use in extracting pixels from the <code>pixels</code> array - */ - public void setPixels(int x, int y, int w, int h, - ColorModel model, int[] pixels, - int offset, int scansize) - { - if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel) - { - if (consumer != null) - consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize); - } - else - { - //FIXME: Store the filtered pixels in a separate temporary buffer? - convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize ); - filterRGBPixels( x, y, w, h, pixels, offset, scansize ); - if (consumer != null) - consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset, scansize); - } - } - - private int[] convertColorModelToDefault(int x, int y, int w, int h, - ColorModel model, byte pixels[], - int offset, int scansize) - { - int intPixels[] = new int[pixels.length]; - for (int i = 0; i < pixels.length; i++) - intPixels[i] = makeColorbyDefaultCM(model, pixels[i]); - return intPixels; - } + /** + * Registers a new ColorModel to subsitute for the old ColorModel when + * setPixels encounters the a pixel with the old ColorModel. The pixel + * remains unchanged except for a new ColorModel. + * + * @param oldcm the old ColorModel + * @param newcm the new ColorModel + */ + public void substituteColorModel(ColorModel oldcm, ColorModel newcm) + { + origmodel = oldcm; + newmodel = newcm; + } - private void convertColorModelToDefault(int x, int y, int w, int h, - ColorModel model, int pixels[], - int offset, int scansize) - { - for (int i = 0; i < pixels.length; i++) - pixels[i] = makeColorbyDefaultCM(model, pixels[i]); - } + /** + * Filters an IndexColorModel through the filterRGB function. Uses + * coordinates of -1 to indicate its filtering an index and not a pixel. + * + * @param icm an IndexColorModel to filter + */ + public IndexColorModel filterIndexColorModel(IndexColorModel icm) + { + int len = icm.getMapSize(); + byte[] reds = new byte[len]; + byte[] greens = new byte[len]; + byte[] blues = new byte[len]; + byte[] alphas = new byte[len]; - private int makeColorbyDefaultCM(ColorModel model, byte rgb) - { - return makeColor( model.getAlpha( rgb ) * 4, model.getRed( rgb ) * 4, model.getGreen( rgb ) * 4, model.getBlue( rgb ) * 4 ); - } + icm.getAlphas( alphas ); + icm.getReds( reds ); + icm.getGreens( greens ); + icm.getBlues( blues ); - private int makeColorbyDefaultCM(ColorModel model, int rgb) - { - return makeColor( model.getAlpha( rgb ), model.getRed( rgb ), model.getGreen( rgb ), model.getBlue( rgb ) ); - } + int transparent = icm.getTransparentPixel(); + boolean needAlpha = false; + for( int i = 0; i < len; i++ ) + { + int rgb = filterRGB(-1, -1, icm.getRGB(i)); + alphas[i] = (byte) (rgb >> 24); + if (alphas[i] != ((byte) 0xff) && i != transparent) + needAlpha = true; + reds[i] = (byte) (rgb >> 16); + greens[i] = (byte) (rgb >> 8); + blues[i] = (byte) (rgb); + } + IndexColorModel newIcm; + if (needAlpha) + newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens, + blues, alphas); + else + newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens, + blues, transparent); + return newIcm; + } - private int makeColor( int a, int r, int g, int b ) - { - return (int)( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b ); - } + /** + * This functions filters a set of RGB pixels through filterRGB. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @param pixels the array of pixel values + * @param offset the index of the first pixels in the + * <code>pixels</code> array + * @param scansize the width to use in extracting pixels from the + * <code>pixels</code> array + */ + public void filterRGBPixels(int x, int y, int w, int h, int[] pixels, + int offset, int scansize) + { + int index = offset; + for (int yp = 0; yp < h; yp++) + { + for (int xp = 0; xp < w; xp++) + { + pixels[index] = filterRGB(xp + x, yp + y, pixels[index]); + index++; + } + index += scansize - w; + } + consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset, + scansize); + } + /** + * If the ColorModel is the same ColorModel which as already converted + * then it converts it the converted ColorModel. Otherwise it passes the + * array of pixels through filterRGBpixels. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @param model the <code>ColorModel</code> used to translate the pixels + * @param pixels the array of pixel values + * @param offset the index of the first pixels in the <code>pixels</code> + * array + * @param scansize the width to use in extracting pixels from the + * <code>pixels</code> array + */ + public void setPixels(int x, int y, int w, int h, ColorModel model, + byte[] pixels, int offset, int scansize) + { + if (model == origmodel) + { + consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize); + } + else + { + int[] filtered = new int[w]; + int index = offset; + for (int yp = 0; yp < h; yp++) + { + for (int xp = 0; xp < w; xp++) + { + filtered[xp] = model.getRGB((pixels[index] & 0xff)); + index++; + } + index += scansize - w; + filterRGBPixels(x, y + yp, w, 1, filtered, 0, w); + } + } + } - /** - Filters a single pixel from the default ColorModel. + /** + * This function delivers a rectangle of pixels where any + * pixel(m,n) is stored in the array as an <code>int</code> at + * index (n * scansize + m + offset). + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param w the width of the rectangle + * @param h the height of the rectangle + * @param model the <code>ColorModel</code> used to translate the pixels + * @param pixels the array of pixel values + * @param offset the index of the first pixels in the <code>pixels</code> + * array + * @param scansize the width to use in extracting pixels from the + * <code>pixels</code> array + */ + public void setPixels(int x, int y, int w, int h, ColorModel model, + int[] pixels, int offset, int scansize) + { + if (model == origmodel) + { + consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize); + } + else + { + int[] filtered = new int[w]; + int index = offset; + for (int yp = 0; yp < h; yp++) + { + for (int xp = 0; xp < w; xp++) + { + filtered[xp] = model.getRGB((pixels[index] & 0xff)); + index++; + } + index += scansize - w; + filterRGBPixels(x, y + yp, w, 1, filtered, 0, w); + } + } + } - @param x x-coordinate - @param y y-coordinate - @param rgb color - */ - public abstract int filterRGB(int x, - int y, - int rgb); + /** + * Filters a single pixel from the default ColorModel. + * + * @param x x-coordinate + * @param y y-coordinate + * @param rgb color + */ + public abstract int filterRGB(int x, int y, int rgb); } diff --git a/libjava/classpath/java/awt/image/Raster.java b/libjava/classpath/java/awt/image/Raster.java index 160f8be8b51..d63e156f6ae 100644 --- a/libjava/classpath/java/awt/image/Raster.java +++ b/libjava/classpath/java/awt/image/Raster.java @@ -511,9 +511,10 @@ public class Raster int height, int childMinX, int childMinY, int[] bandList) { - /* FIXME: Throw RasterFormatException if child bounds extends - beyond the bounds of this raster. */ - + if (parentX < minX || parentX + width > minX + this.width + || parentY < minY || parentY + height > minY + this.height) + throw new RasterFormatException("Child raster extends beyond parent"); + SampleModel sm = (bandList == null) ? sampleModel : sampleModel.createSubsetSampleModel(bandList); diff --git a/libjava/classpath/java/awt/image/RenderedImage.java b/libjava/classpath/java/awt/image/RenderedImage.java index b35f8602129..067e9b98e38 100644 --- a/libjava/classpath/java/awt/image/RenderedImage.java +++ b/libjava/classpath/java/awt/image/RenderedImage.java @@ -46,7 +46,7 @@ import java.util.Vector; */ public interface RenderedImage { - Vector getSources(); + Vector<RenderedImage> getSources(); Object getProperty(String name); String[] getPropertyNames(); ColorModel getColorModel(); diff --git a/libjava/classpath/java/awt/image/ReplicateScaleFilter.java b/libjava/classpath/java/awt/image/ReplicateScaleFilter.java index 6d5099dead2..5ba03f182f6 100644 --- a/libjava/classpath/java/awt/image/ReplicateScaleFilter.java +++ b/libjava/classpath/java/awt/image/ReplicateScaleFilter.java @@ -46,6 +46,7 @@ import java.util.Hashtable; * exact method is not defined by Sun but some sort of fast Box filter should * probably be correct. * <br> + * Currently this filter does nothing and needs to be implemented. * * @author C. Brian Jones (cbj@gnu.org) */ @@ -116,11 +117,11 @@ public class ReplicateScaleFilter extends ImageFilter } else if (destWidth < 0) { - destWidth = (int) (width * ((double) destHeight / srcHeight)); + destWidth = width * destHeight / srcHeight; } else if (destHeight < 0) { - destHeight = (int) (height * ((double) destWidth / srcWidth)); + destHeight = height * destWidth / srcWidth; } if (consumer != null) @@ -133,11 +134,12 @@ public class ReplicateScaleFilter extends ImageFilter * * @param props the list of properties associated with this image */ - public void setProperties(Hashtable props) + public void setProperties(Hashtable<?, ?> props) { - props.put("filters", "ReplicateScaleFilter"); - if (consumer != null) - consumer.setProperties(props); + Hashtable<Object, Object> prop2 = (Hashtable<Object, Object>) props; + prop2.put("filters", "ReplicateScaleFilter"); + if (consumer != null) + consumer.setProperties(prop2); } /** @@ -157,19 +159,35 @@ public class ReplicateScaleFilter extends ImageFilter public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int offset, int scansize) { - double rx = ((double) srcWidth) / destWidth; - double ry = ((double) srcHeight) / destHeight; - - int destScansize = (int) Math.round(scansize / rx); - - byte[] destPixels = replicatePixels(x, y, w, h, - model, pixels, offset, scansize, - rx, ry, destScansize); - - if (consumer != null) - consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry), - (int) Math.ceil(w/rx), (int) Math.ceil(h/ry), - model, destPixels, 0, destScansize); + if (srcrows == null || srccols == null) + setupSources(); + int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth); + int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight); + byte[] pix; + if (outpixbuf != null && outpixbuf instanceof byte[]) + { + pix = (byte[]) outpixbuf; + } + else + { + pix = new byte[destWidth]; + outpixbuf = pix; + } + int sy, sx; + for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++) + { + int offs = offset + scansize * (sy - y); + int xx; + for (xx = dx1; (sx = srccols[xx]) < x + w; xx++) + { + pix[xx] = pixels[offs + sx - x]; + } + if (xx > dx1) + { + consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1, + destWidth); + } + } } /** @@ -189,59 +207,52 @@ public class ReplicateScaleFilter extends ImageFilter public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int offset, int scansize) { - double rx = ((double) srcWidth) / destWidth; - double ry = ((double) srcHeight) / destHeight; - - int destScansize = (int) Math.round(scansize / rx); - - int[] destPixels = replicatePixels(x, y, w, h, - model, pixels, offset, scansize, - rx, ry, destScansize); - - if (consumer != null) - consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry), - (int) Math.ceil(w/rx), (int) Math.ceil(h/ry), - model, destPixels, 0, destScansize); - } - - private byte[] replicatePixels(int srcx, int srcy, int srcw, int srch, - ColorModel model, byte[] srcPixels, - int srcOffset, int srcScansize, - double rx, double ry, int destScansize) - { - byte[] destPixels = - new byte[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)]; - - int a, b; - for (int i = 0; i < destPixels.length; i++) - { - a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize; - b = (int) ((i % destScansize) * rx); - if ((a + b + srcOffset) < srcPixels.length) - destPixels[i] = srcPixels[a + b + srcOffset]; - } - - return destPixels; + if (srcrows == null || srccols == null) + setupSources(); + int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth); + int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight); + int[] pix; + if (outpixbuf != null && outpixbuf instanceof int[]) + { + pix = (int[]) outpixbuf; + } + else + { + pix = new int[destWidth]; + outpixbuf = pix; + } + int sy, sx; + for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++) + { + int offs = offset + scansize * (sy - y); + int xx; + for (xx = dx1; (sx = srccols[xx]) < x + w; xx++) + { + pix[xx] = pixels[offs + sx - x]; + } + if (xx > dx1) + { + consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1, + destWidth); + } + } } - private int[] replicatePixels(int srcx, int srcy, int srcw, int srch, - ColorModel model, int[] srcPixels, - int srcOffset, int srcScansize, - double rx, double ry, int destScansize) - { - int[] destPixels = - new int[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)]; - - int a, b; - for (int i = 0; i < destPixels.length; i++) - { - a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize; - b = (int) ((i % destScansize) * rx); - if ((a + b + srcOffset) < srcPixels.length) - destPixels[i] = srcPixels[a + b + srcOffset]; - } - - return destPixels; - } + /** + * Sets up the srcrows and srccols arrays. + */ + private void setupSources() + { + srcrows = new int[destHeight + 1]; + for (int y = 0; y <= destHeight; y++) + { + srcrows[y] = (2 * y * srcHeight + srcHeight) / (2 * destHeight); + } + srccols = new int[destWidth + 1]; + for (int x = 0; x <= destWidth; x++) + { + srccols[x] = (2 * x * srcWidth + srcWidth) / (2 * destWidth); + } + } } diff --git a/libjava/classpath/java/awt/image/RescaleOp.java b/libjava/classpath/java/awt/image/RescaleOp.java index d5b29693caf..d56b12cb952 100644 --- a/libjava/classpath/java/awt/image/RescaleOp.java +++ b/libjava/classpath/java/awt/image/RescaleOp.java @@ -1,4 +1,4 @@ -/* Copyright (C) 2004 Free Software Foundation +/* Copyright (C) 2004, 2006 Free Software Foundation This file is part of GNU Classpath. @@ -43,7 +43,23 @@ import java.awt.geom.Rectangle2D; import java.util.Arrays; /** + * RescaleOp is a filter that changes each pixel by a scaling factor and offset. + * + * For filtering Rasters, either one scaling factor and offset can be specified, + * which will be applied to all bands; or a scaling factor and offset can be + * specified for each band. + * + * For BufferedImages, the scaling may apply to both color and alpha components. + * If only one scaling factor is provided, or if the number of factors provided + * equals the number of color components, the scaling is performed on all color + * components. Otherwise, the scaling is performed on all components including + * alpha. Alpha premultiplication is ignored. + * + * After filtering, if color conversion is necessary, the conversion happens, + * taking alpha premultiplication into account. + * * @author Jerry Quinn (jlquinn@optonline.net) + * @author Francis Kung (fkung@redhat.com) */ public class RescaleOp implements BufferedImageOp, RasterOp { @@ -51,15 +67,43 @@ public class RescaleOp implements BufferedImageOp, RasterOp private float[] offsets; private RenderingHints hints = null; + /** + * Create a new RescaleOp object using the given scale factors and offsets. + * + * The length of the arrays must be equal to the number of bands (or number of + * data or color components) of the raster/image that this Op will be used on, + * otherwise an IllegalArgumentException will be thrown when calling the + * filter method. + * + * @param scaleFactors an array of scale factors. + * @param offsets an array of offsets. + * @param hints any rendering hints to use (can be null). + * @throws NullPointerException if the scaleFactors or offsets array is null. + */ public RescaleOp(float[] scaleFactors, float[] offsets, RenderingHints hints) { - this.scale = scaleFactors; - this.offsets = offsets; + int length = Math.min(scaleFactors.length, offsets.length); + + scale = new float[length]; + System.arraycopy(scaleFactors, 0, this.scale, 0, length); + + this.offsets = new float[length]; + System.arraycopy(offsets, 0, this.offsets, 0, length); + this.hints = hints; } + /** + * Create a new RescaleOp object using the given scale factor and offset. + * + * The same scale factor and offset will be used on all bands/components. + * + * @param scaleFactor the scale factor to use. + * @param offset the offset to use. + * @param hints any rendering hints to use (can be null). + */ public RescaleOp(float scaleFactor, float offset, RenderingHints hints) @@ -69,22 +113,47 @@ public class RescaleOp implements BufferedImageOp, RasterOp this.hints = hints; } + /** + * Returns the scaling factors. This method accepts an optional array, which + * will be used to store the factors if not null (this avoids allocating a + * new array). If this array is too small to hold all the scaling factors, + * the array will be filled and the remaining factors discarded. + * + * @param scaleFactors array to store the scaling factors in (can be null). + * @return an array of scaling factors. + */ public final float[] getScaleFactors(float[] scaleFactors) { if (scaleFactors == null) scaleFactors = new float[scale.length]; - System.arraycopy(scale, 0, scaleFactors, 0, scale.length); + System.arraycopy(scale, 0, scaleFactors, 0, Math.min(scale.length, + scaleFactors.length)); return scaleFactors; } + /** + * Returns the offsets. This method accepts an optional array, which + * will be used to store the offsets if not null (this avoids allocating a + * new array). If this array is too small to hold all the offsets, the array + * will be filled and the remaining factors discarded. + * + * @param offsets array to store the offsets in (can be null). + * @return an array of offsets. + */ public final float[] getOffsets(float[] offsets) { if (offsets == null) offsets = new float[this.offsets.length]; - System.arraycopy(this.offsets, 0, offsets, 0, this.offsets.length); + System.arraycopy(this.offsets, 0, offsets, 0, Math.min(this.offsets.length, + offsets.length)); return offsets; } + /** + * Returns the number of scaling factors / offsets. + * + * @return the number of scaling factors / offsets. + */ public final int getNumFactors() { return scale.length; @@ -98,36 +167,74 @@ public class RescaleOp implements BufferedImageOp, RasterOp return hints; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, java.awt.image.BufferedImage) + /** + * Converts the source image using the scale factors and offsets specified in + * the constructor. The resulting image is stored in the destination image if + * one is provided; otherwise a new BufferedImage is created and returned. + * + * The source image cannot use an IndexColorModel, and the destination image + * (if one is provided) must have the same size. + * + * If the final value of a sample is beyond the range of the color model, it + * will be clipped to the appropriate maximum / minimum. + * + * @param src The source image. + * @param dst The destination image. + * @throws IllegalArgumentException if the rasters and/or color spaces are + * incompatible. + * @return The rescaled image. */ public final BufferedImage filter(BufferedImage src, BufferedImage dst) { - // TODO Make sure premultiplied alpha is handled correctly. - // TODO See that color conversion is handled. - // TODO figure out how to use rendering hints. - if (scale.length != offsets.length) - throw new IllegalArgumentException(); + // Initial checks + if (scale.length != 1 + && scale.length != src.getColorModel().getNumComponents() + && (scale.length != src.getColorModel().getNumColorComponents())) + throw new IllegalArgumentException("Source image has wrong number of " + + "bands for these scaling factors."); - ColorModel scm = src.getColorModel(); - if (dst == null) dst = createCompatibleDestImage(src, null); + if (dst == null) + dst = createCompatibleDestImage(src, null); + else if (src.getHeight() != dst.getHeight() + || src.getWidth() != dst.getWidth()) + throw new IllegalArgumentException("Source and destination images are " + + "different sizes."); - WritableRaster wsrc = src.getRaster(); - WritableRaster wdst = dst.getRaster(); - - // Share constant across colors except alpha - if (scale.length == 1 || scale.length == scm.getNumColorComponents()) + // Prepare for possible colorspace conversion + BufferedImage dst2 = dst; + if (dst.getColorModel().getColorSpace().getType() != src.getColorModel().getColorSpace().getType()) + dst2 = createCompatibleDestImage(src, src.getColorModel()); + + // Figure out how many bands to scale + int numBands = scale.length; + if (scale.length == 1) + numBands = src.getColorModel().getNumColorComponents(); + boolean[] bands = new boolean[numBands]; + // this assumes the alpha, if present, is the last band + Arrays.fill(bands, true); + + // Perform rescaling + filter(src.getRaster(), dst2.getRaster(), bands); + + // Copy alpha band if needed (ie if it exists and wasn't scaled) + // NOTE: This assumes the alpha component is the last band! + if (src.getColorModel().hasAlpha() + && numBands == src.getColorModel().getNumColorComponents()) { - // Construct a raster that doesn't include an alpha band. - int[] subbands = new int[scm.getNumColorComponents()]; - for (int i=0; i < subbands.length; i++) subbands[i] = i; - wsrc = - wsrc.createWritableChild(wsrc.minX, wsrc.minY, wsrc.width, wsrc.height, - wsrc.minX, wsrc.minY, subbands); + + dst2.getRaster().setSamples(0, 0, src.getWidth(), src.getHeight(), + numBands, + src.getRaster().getSamples(0, 0, + src.getWidth(), + src.getHeight(), + numBands, + (int[]) null)); } - // else all color bands - filter(wsrc, wdst); + // Perform colorspace conversion if needed + if (dst != dst2) + new ColorConvertOp(hints).filter(dst2, dst); + return dst; } @@ -136,50 +243,106 @@ public class RescaleOp implements BufferedImageOp, RasterOp */ public final WritableRaster filter(Raster src, WritableRaster dest) { - if (dest == null) dest = src.createCompatibleWritableRaster(); - // Required sanity checks - if (src.numBands != dest.numBands || scale.length != offsets.length) - throw new IllegalArgumentException(); if (scale.length != 1 && scale.length != src.numBands) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Number of rasters is incompatible " + + "with the number of scaling " + + "factors provided."); - // Create scaling arrays if needed - float[] lscale = scale; - float[] loff = offsets; - if (scale.length == 1) - { - lscale = new float[src.numBands]; - Arrays.fill(lscale, scale[0]); - loff = new float[src.numBands]; - Arrays.fill(loff, offsets[0]); - } + if (dest == null) + dest = src.createCompatibleWritableRaster(); + else if (src.getHeight() != dest.getHeight() + || src.getWidth() != dest.getWidth()) + throw new IllegalArgumentException("Source and destination rasters are " + + "different sizes."); + else if (src.numBands != dest.numBands) + throw new IllegalArgumentException("Source and destination rasters " + + "are incompatible."); + + // Filter all bands + boolean[] bands = new boolean[src.getNumBands()]; + Arrays.fill(bands, true); + return filter(src, dest, bands); + } + + /** + * Perform raster-based filtering on a selected number of bands. + * + * The length of the bands array should equal the number of bands; a true + * element indicates filtering should happen on the corresponding band, while + * a false element will skip the band. + * + * The rasters are assumed to be compatible and non-null. + * + * @param src the source raster. + * @param dest the destination raster. + * @param bands an array indicating which bands to filter. + * @throws NullPointerException if any parameter is null. + * @throws ArrayIndexOutOfBoundsException if the bands array is too small. + * @return the destination raster. + */ + private WritableRaster filter(Raster src, WritableRaster dest, boolean[] bands) + { + int[] values = new int[src.getHeight() * src.getWidth()]; + float scaleFactor, offset; + + // Find max sample value, to be used for clipping later + int[] maxValue = src.getSampleModel().getSampleSize(); + for (int i = 0; i < maxValue.length; i++) + maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1; + + // TODO: can this be optimized further? + // Filter all samples of all requested bands + for (int band = 0; band < bands.length; band++) + if (bands[band]) + { + values = src.getSamples(src.getMinX(), src.getMinY(), src.getWidth(), + src.getHeight(), band, values); - // TODO The efficiency here can be improved for various data storage - // patterns, aka SampleModels. - float[] pixel = new float[src.numBands]; - for (int y = src.minY; y < src.height + src.minY; y++) - for (int x = src.minX; x < src.width + src.minX; x++) - { - src.getPixel(x, y, pixel); - for (int b = 0; b < src.numBands; b++) - pixel[b] = pixel[b] * lscale[b] + loff[b]; - dest.setPixel(x, y, pixel); - } + if (scale.length == 1) + { + scaleFactor = scale[0]; + offset = offsets[0]; + } + else + { + scaleFactor = scale[band]; + offset = offsets[band]; + } + + for (int i = 0; i < values.length; i++) + { + values[i] = (int) (values[i] * scaleFactor + offset); + + // Clip if needed + if (values[i] < 0) + values[i] = 0; + if (values[i] > maxValue[band]) + values[i] = maxValue[band]; + } + + dest.setSamples(dest.getMinX(), dest.getMinY(), dest.getWidth(), + dest.getHeight(), band, values); + } + return dest; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel) + /* + * (non-Javadoc) + * + * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, + * java.awt.image.ColorModel) */ public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { - if (dstCM == null) dstCM = src.getColorModel(); - WritableRaster wr = src.getRaster().createCompatibleWritableRaster(); - BufferedImage image - = new BufferedImage(dstCM, wr, src.isPremultiplied, null); - return image; + if (dstCM == null) + return new BufferedImage(src.getWidth(), src.getHeight(), src.getType()); + + return new BufferedImage(dstCM, + src.getRaster().createCompatibleWritableRaster(), + src.isAlphaPremultiplied(), null); } /* (non-Javadoc) @@ -209,9 +372,13 @@ public class RescaleOp implements BufferedImageOp, RasterOp /* (non-Javadoc) * @see java.awt.image.BufferedImageOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D) */ - public final Point2D getPoint2D(Point2D src, Point2D dst) { - if (dst == null) dst = (Point2D) src.clone(); - else dst.setLocation(src); + public final Point2D getPoint2D(Point2D src, Point2D dst) + { + if (dst == null) + dst = (Point2D) src.clone(); + else + dst.setLocation(src); + return dst; } diff --git a/libjava/classpath/java/awt/image/SampleModel.java b/libjava/classpath/java/awt/image/SampleModel.java index cb352bb4d85..506e78a9b87 100644 --- a/libjava/classpath/java/awt/image/SampleModel.java +++ b/libjava/classpath/java/awt/image/SampleModel.java @@ -246,9 +246,7 @@ public abstract class SampleModel public void setDataElements(int x, int y, int w, int h, Object obj, DataBuffer data) { - int size = w * h; int numDataElements = getNumDataElements(); - int dataSize = numDataElements * size; Object pixelData; switch (getTransferType()) @@ -257,25 +255,34 @@ public abstract class SampleModel pixelData = new byte[numDataElements]; break; case DataBuffer.TYPE_USHORT: + case DataBuffer.TYPE_SHORT: pixelData = new short[numDataElements]; break; case DataBuffer.TYPE_INT: pixelData = new int[numDataElements]; break; + case DataBuffer.TYPE_FLOAT: + pixelData = new float[numDataElements]; + break; + case DataBuffer.TYPE_DOUBLE: + pixelData = new double[numDataElements]; + break; default: - // Seems like the only sensible thing to do. - throw new ClassCastException(); + // The RI silently igores invalid types. + pixelData = null; } - int inOffset = 0; - for (int yy = y; yy < (y + h); yy++) + int inOffset = 0; + if (pixelData != null) { - for (int xx = x; xx < (x + w); xx++) + for (int yy=y; yy<(y+h); yy++) { - System.arraycopy(obj, inOffset, pixelData, 0, - numDataElements); - setDataElements(xx, yy, pixelData, data); - inOffset += numDataElements; + for (int xx=x; xx<(x+w); xx++) + { + System.arraycopy(obj, inOffset, pixelData, 0, numDataElements); + setDataElements(xx, yy, pixelData, data); + inOffset += numDataElements; + } } } } diff --git a/libjava/classpath/java/awt/image/SinglePixelPackedSampleModel.java b/libjava/classpath/java/awt/image/SinglePixelPackedSampleModel.java index a37fc0bba3f..9ed948c54f3 100644 --- a/libjava/classpath/java/awt/image/SinglePixelPackedSampleModel.java +++ b/libjava/classpath/java/awt/image/SinglePixelPackedSampleModel.java @@ -412,110 +412,31 @@ public class SinglePixelPackedSampleModel extends SampleModel return (samples & bitMasks[b]) >>> bitOffsets[b]; } - /** - * This method implements a more efficient way to set data elements than the - * default implementation of the super class. It sets the data elements line - * by line instead of pixel by pixel. - * - * @param x The x-coordinate of the data elements in <code>obj</code>. - * @param y The y-coordinate of the data elements in <code>obj</code>. - * @param w The width of the data elements in <code>obj</code>. - * @param h The height of the data elements in <code>obj</code>. - * @param obj The primitive array containing the data elements to set. - * @param data The DataBuffer to store the data elements into. - * @see java.awt.image.SampleModel#setDataElements(int, int, int, int, - * java.lang.Object, java.awt.image.DataBuffer) - */ - public void setDataElements(int x, int y, int w, int h, - Object obj, DataBuffer data) - { - - Object pixelData; - switch (getTransferType()) - { - case DataBuffer.TYPE_BYTE: - pixelData = ((DataBufferByte) data).getData(); - break; - case DataBuffer.TYPE_USHORT: - pixelData = ((DataBufferUShort) data).getData(); - break; - case DataBuffer.TYPE_INT: - pixelData = ((DataBufferInt) data).getData(); - break; - default: - // Seems like the only sensible thing to do. - throw new ClassCastException(); - } - - int inOffset = 0; - int dataOffset = scanlineStride*y + x + data.getOffset(); - for (int yy=y; yy<(y+h); yy++) - { - System.arraycopy(obj,inOffset,pixelData,dataOffset,w); - dataOffset += scanlineStride; - inOffset += w; - } - } - - public void setDataElements(int x, int y, Object obj, DataBuffer data) { - int offset = scanlineStride*y + x + data.getOffset(); int transferType = getTransferType(); - if (getTransferType() != data.getDataType()) - { - throw new IllegalArgumentException("transfer type ("+ - getTransferType()+"), "+ - "does not match data "+ - "buffer type (" + - data.getDataType() + - ")."); - } - - try - { - switch (transferType) - { - case DataBuffer.TYPE_BYTE: - { - DataBufferByte out = (DataBufferByte) data; - byte[] in = (byte[]) obj; - out.getData()[offset] = in[0]; - return; - } - case DataBuffer.TYPE_USHORT: - { - DataBufferUShort out = (DataBufferUShort) data; - short[] in = (short[]) obj; - out.getData()[offset] = in[0]; - return; - } - case DataBuffer.TYPE_INT: - { - DataBufferInt out = (DataBufferInt) data; - int[] in = (int[]) obj; - out.getData()[offset] = in[0]; - return; - } - // FIXME: Fill in the other possible types. - default: - throw new InternalError(); - } - } - catch (ArrayIndexOutOfBoundsException aioobe) - { - String msg = "While writing data elements" + - ", x="+x+", y="+y+ - ", width="+width+", height="+height+ - ", scanlineStride="+scanlineStride+ - ", offset="+offset+ - ", data.getSize()="+data.getSize()+ - ", data.getOffset()="+data.getOffset()+ - ": " + - aioobe; - throw new ArrayIndexOutOfBoundsException(msg); - } + switch (transferType) + { + case DataBuffer.TYPE_BYTE: + { + byte[] in = (byte[]) obj; + data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xff); + break; + } + case DataBuffer.TYPE_USHORT: + { + short[] in = (short[]) obj; + data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xffff); + break; + } + case DataBuffer.TYPE_INT: + { + int[] in = (int[]) obj; + data.setElem(y * scanlineStride + x, in[0]); + break; + } + } } /** diff --git a/libjava/classpath/java/awt/image/WritableRaster.java b/libjava/classpath/java/awt/image/WritableRaster.java index 473c6fe41f9..bf8db140c7f 100644 --- a/libjava/classpath/java/awt/image/WritableRaster.java +++ b/libjava/classpath/java/awt/image/WritableRaster.java @@ -136,8 +136,9 @@ public class WritableRaster extends Raster { // This mirrors the code from the super class - // FIXME: Throw RasterFormatException if child bounds extends - // beyond the bounds of this raster. + if (parentX < minX || parentX + w > minX + width + || parentY < minY || parentY + h > minY + height) + throw new RasterFormatException("Child raster extends beyond parent"); SampleModel sm = (bandList == null) ? sampleModel : @@ -149,6 +150,25 @@ public class WritableRaster extends Raster sampleModelTranslateY + childMinY - parentY), this); } + + public Raster createChild(int parentX, int parentY, int width, + int height, int childMinX, int childMinY, + int[] bandList) + { + if (parentX < minX || parentX + width > minX + this.width + || parentY < minY || parentY + height > minY + this.height) + throw new RasterFormatException("Child raster extends beyond parent"); + + SampleModel sm = (bandList == null) ? + sampleModel : + sampleModel.createSubsetSampleModel(bandList); + + return new WritableRaster(sm, dataBuffer, + new Rectangle(childMinX, childMinY, width, height), + new Point(sampleModelTranslateX + childMinX - parentX, + sampleModelTranslateY + childMinY - parentY), + this); + } public void setDataElements(int x, int y, Object inData) { diff --git a/libjava/classpath/java/awt/image/renderable/ParameterBlock.java b/libjava/classpath/java/awt/image/renderable/ParameterBlock.java index 879d3c4fb90..e484d6b8713 100644 --- a/libjava/classpath/java/awt/image/renderable/ParameterBlock.java +++ b/libjava/classpath/java/awt/image/renderable/ParameterBlock.java @@ -45,20 +45,20 @@ import java.util.Vector; public class ParameterBlock implements Cloneable, Serializable { private static final long serialVersionUID = -7577115551785240750L; - protected Vector sources; - protected Vector parameters; + protected Vector<Object> sources; + protected Vector<Object> parameters; public ParameterBlock() { - this(new Vector(), new Vector()); + this(new Vector<Object>(), new Vector<Object>()); } - public ParameterBlock(Vector sources) + public ParameterBlock(Vector<Object> sources) { - this(sources, new Vector()); + this(sources, new Vector<Object>()); } - public ParameterBlock(Vector sources, Vector parameters) + public ParameterBlock(Vector<Object> sources, Vector<Object> parameters) { this.sources = sources; this.parameters = parameters; @@ -80,9 +80,9 @@ public class ParameterBlock implements Cloneable, Serializable { ParameterBlock pb = (ParameterBlock) shallowClone(); if (sources != null) - pb.sources = (Vector) sources.clone(); + pb.sources = (Vector<Object>) sources.clone(); if (parameters != null) - pb.parameters = (Vector) parameters.clone(); + pb.parameters = (Vector<Object>) parameters.clone(); return pb; } @@ -119,12 +119,12 @@ public class ParameterBlock implements Cloneable, Serializable return sources.size(); } - public Vector getSources() + public Vector<Object> getSources() { return sources; } - public void setSources(Vector sources) + public void setSources(Vector<Object> sources) { this.sources = sources; } @@ -140,12 +140,12 @@ public class ParameterBlock implements Cloneable, Serializable return parameters.size(); } - public Vector getParameters() + public Vector<Object> getParameters() { return parameters; } - public void setParameters(Vector parameters) + public void setParameters(Vector<Object> parameters) { this.parameters = parameters; } diff --git a/libjava/classpath/java/awt/image/renderable/RenderableImage.java b/libjava/classpath/java/awt/image/renderable/RenderableImage.java index 45d2eb7ebf0..c2f6ad8b058 100644 --- a/libjava/classpath/java/awt/image/renderable/RenderableImage.java +++ b/libjava/classpath/java/awt/image/renderable/RenderableImage.java @@ -46,7 +46,7 @@ public interface RenderableImage { String HINTS_OBSERVED = "HINTS_OBSERVED"; - Vector getSources(); + Vector<RenderableImage> getSources(); Object getProperty(String name); String[] getPropertyNames(); boolean isDynamic(); diff --git a/libjava/classpath/java/awt/image/renderable/RenderableImageOp.java b/libjava/classpath/java/awt/image/renderable/RenderableImageOp.java index 5385a82a317..b9d0cd39e6e 100644 --- a/libjava/classpath/java/awt/image/renderable/RenderableImageOp.java +++ b/libjava/classpath/java/awt/image/renderable/RenderableImageOp.java @@ -55,7 +55,7 @@ public class RenderableImageOp implements RenderableImage this.block = (ParameterBlock) block.clone(); } - public Vector getSources() + public Vector<RenderableImage> getSources() { if (block.sources == null) return null; |