diff options
Diffstat (limited to 'javax/swing')
83 files changed, 7013 insertions, 3601 deletions
diff --git a/javax/swing/AbstractButton.java b/javax/swing/AbstractButton.java index 63f827a1a..ed8daca27 100644 --- a/javax/swing/AbstractButton.java +++ b/javax/swing/AbstractButton.java @@ -187,9 +187,32 @@ public abstract class AbstractButton extends JComponent */ public void stateChanged(ChangeEvent ev) { - AbstractButton.this.fireStateChanged(); + getEventHandler().stateChanged(ev); + } + } + + /** + * The combined event handler for ActionEvent, ChangeEvent and + * ItemEvent. This combines ButtonChangeListener, ActionListener + */ + private class EventHandler + implements ActionListener, ChangeListener, ItemListener + { + public void actionPerformed(ActionEvent ev) + { + fireActionPerformed(ev); + } + + public void stateChanged(ChangeEvent ev) + { + fireStateChanged(); repaint(); } + + public void itemStateChanged(ItemEvent ev) + { + fireItemStateChanged(ev); + } } /** The icon displayed by default. */ @@ -264,16 +287,29 @@ public abstract class AbstractButton extends JComponent */ int mnemonicIndex; - /** Listener the button uses to receive ActionEvents from its model. */ + /** + * Listener the button uses to receive ActionEvents from its model. + */ protected ActionListener actionListener; - /** Listener the button uses to receive ItemEvents from its model. */ + /** + * Listener the button uses to receive ItemEvents from its model. + */ protected ItemListener itemListener; - /** Listener the button uses to receive ChangeEvents from its model. */ + /** + * Listener the button uses to receive ChangeEvents from its model. + */ protected ChangeListener changeListener; /** + * The event handler for ActionEvent, ItemEvent and ChangeEvent. + * This replaces the above three handlers and combines them + * into one for efficiency. + */ + private EventHandler eventHandler; + + /** * The time in milliseconds in which clicks get coalesced into a single * <code>ActionEvent</code>. */ @@ -855,10 +891,6 @@ public abstract class AbstractButton extends JComponent */ public AbstractButton() { - actionListener = createActionListener(); - changeListener = createChangeListener(); - itemListener = createItemListener(); - horizontalAlignment = CENTER; horizontalTextPosition = TRAILING; verticalAlignment = CENTER; @@ -900,15 +932,21 @@ public abstract class AbstractButton extends JComponent if (model != null) { model.removeActionListener(actionListener); + actionListener = null; model.removeChangeListener(changeListener); + changeListener = null; model.removeItemListener(itemListener); + itemListener = null; } ButtonModel old = model; model = newModel; if (model != null) { + actionListener = createActionListener(); model.addActionListener(actionListener); + changeListener = createChangeListener(); model.addChangeListener(changeListener); + itemListener = createItemListener(); model.addItemListener(itemListener); } firePropertyChange(MODEL_CHANGED_PROPERTY, old, model); @@ -1923,13 +1961,7 @@ public abstract class AbstractButton extends JComponent */ protected ActionListener createActionListener() { - return new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - AbstractButton.this.fireActionPerformed(e); - } - }; + return getEventHandler(); } /** @@ -1995,7 +2027,7 @@ public abstract class AbstractButton extends JComponent */ protected ChangeListener createChangeListener() { - return new ButtonChangeListener(); + return getEventHandler(); } /** @@ -2021,13 +2053,7 @@ public abstract class AbstractButton extends JComponent */ protected ItemListener createItemListener() { - return new ItemListener() - { - public void itemStateChanged(ItemEvent e) - { - AbstractButton.this.fireItemStateChanged(e); - } - }; + return getEventHandler(); } /** @@ -2490,4 +2516,17 @@ public abstract class AbstractButton extends JComponent super.setUIProperty(propertyName, value); } } + + /** + * Returns the combined event handler. The instance is created if + * necessary. + * + * @return the combined event handler + */ + EventHandler getEventHandler() + { + if (eventHandler == null) + eventHandler = new EventHandler(); + return eventHandler; + } } diff --git a/javax/swing/DefaultButtonModel.java b/javax/swing/DefaultButtonModel.java index 51a241760..c0eaea239 100644 --- a/javax/swing/DefaultButtonModel.java +++ b/javax/swing/DefaultButtonModel.java @@ -425,7 +425,7 @@ public class DefaultButtonModel implements ButtonModel, Serializable public void setRollover(boolean r) { // if this call does not represent a CHANGE in state, then return - if ((r && isRollover()) || (!r && !isRollover())) + if (r == isRollover()) return; // cannot set ROLLOVER property unless button is enabled diff --git a/javax/swing/JComponent.java b/javax/swing/JComponent.java index 25fc9b424..0b4631f6c 100644 --- a/javax/swing/JComponent.java +++ b/javax/swing/JComponent.java @@ -69,6 +69,7 @@ import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.beans.VetoableChangeSupport; import java.io.Serializable; +import java.util.ArrayList; import java.util.EventListener; import java.util.Hashtable; import java.util.Locale; @@ -503,27 +504,6 @@ public abstract class JComponent extends Container implements Serializable } } - /** - * An explicit value for the component's preferred size; if not set by a - * user, this is calculated on the fly by delegating to the {@link - * ComponentUI#getPreferredSize} method on the {@link #ui} property. - */ - Dimension preferredSize; - - /** - * An explicit value for the component's minimum size; if not set by a - * user, this is calculated on the fly by delegating to the {@link - * ComponentUI#getMinimumSize} method on the {@link #ui} property. - */ - Dimension minimumSize; - - /** - * An explicit value for the component's maximum size; if not set by a - * user, this is calculated on the fly by delegating to the {@link - * ComponentUI#getMaximumSize} method on the {@link #ui} property. - */ - Dimension maximumSize; - /** * A value between 0.0 and 1.0 indicating the preferred horizontal * alignment of the component, relative to its siblings. The values @@ -687,7 +667,7 @@ public abstract class JComponent extends Container implements Serializable * Indicates whether we are calling paintDoubleBuffered() from * paintImmadiately (RepaintManager) or from paint() (AWT refresh). */ - static private boolean isRepainting = false; + static boolean isRepainting = false; /** * Listeners for events other than {@link PropertyChangeEvent} are @@ -783,6 +763,13 @@ public abstract class JComponent extends Container implements Serializable */ public static final int WHEN_IN_FOCUSED_WINDOW = 2; + + /** + * Used to optimize painting. This is set in paintImmediately2() to specify + * the exact component path to be painted by paintChildren. + */ + Component paintChild; + /** * Indicates if the opaque property has been set by a client program or by * the UI. @@ -1270,37 +1257,38 @@ public abstract class JComponent extends Container implements Serializable } /** - * Get the component's maximum size. If the {@link #maximumSize} property - * has been explicitly set, it is returned. If the {@link #maximumSize} + * Get the component's maximum size. If the <code>maximumSize</code> property + * has been explicitly set, it is returned. If the <code>maximumSize</code> * property has not been set but the {@link #ui} property has been, the * result of {@link ComponentUI#getMaximumSize} is returned. If neither * property has been set, the result of {@link Container#getMaximumSize} * is returned. * - * @return The maximum size of the component + * @return the maximum size of the component * - * @see #maximumSize - * @see #setMaximumSize + * @see Component#setMaximumSize + * @see Component#getMaximumSize() + * @see Component#isMaximumSizeSet() + * @see ComponentUI#getMaximumSize(JComponent) */ public Dimension getMaximumSize() { - if (maximumSize != null) - return maximumSize; - - if (ui != null) + Dimension size = null; + if (isMaximumSizeSet()) + size = super.getMaximumSize(); + else { - Dimension s = ui.getMaximumSize(this); - if (s != null) - return s; + if (ui != null) + size = ui.getMaximumSize(this); + if (size == null) + size = super.getMaximumSize(); } - - Dimension p = super.getMaximumSize(); - return p; + return size; } /** - * Get the component's minimum size. If the {@link #minimumSize} property - * has been explicitly set, it is returned. If the {@link #minimumSize} + * Get the component's minimum size. If the <code>minimumSize</code> property + * has been explicitly set, it is returned. If the <code>minimumSize</code> * property has not been set but the {@link #ui} property has been, the * result of {@link ComponentUI#getMinimumSize} is returned. If neither * property has been set, the result of {@link Container#getMinimumSize} @@ -1308,97 +1296,57 @@ public abstract class JComponent extends Container implements Serializable * * @return The minimum size of the component * - * @see #minimumSize - * @see #setMinimumSize + * @see Component#setMinimumSize + * @see Component#getMinimumSize() + * @see Component#isMinimumSizeSet() + * @see ComponentUI#getMinimumSize(JComponent) */ public Dimension getMinimumSize() { - if (minimumSize != null) - return minimumSize; - - if (ui != null) + Dimension size = null; + if (isMinimumSizeSet()) + size = super.getMinimumSize(); + else { - Dimension s = ui.getMinimumSize(this); - if (s != null) - return s; + if (ui != null) + size = ui.getMinimumSize(this); + if (size == null) + size = super.getMinimumSize(); } - - Dimension p = super.getMinimumSize(); - return p; + return size; } /** - * Get the component's preferred size. If the {@link #preferredSize} - * property has been explicitly set, it is returned. If the {@link - * #preferredSize} property has not been set but the {@link #ui} property - * has been, the result of {@link ComponentUI#getPreferredSize} is + * Get the component's preferred size. If the <code>preferredSize</code> + * property has been explicitly set, it is returned. If the + * <code>preferredSize</code> property has not been set but the {@link #ui} + * property has been, the result of {@link ComponentUI#getPreferredSize} is * returned. If neither property has been set, the result of {@link * Container#getPreferredSize} is returned. * * @return The preferred size of the component * - * @see #preferredSize - * @see #setPreferredSize + * @see Component#setPreferredSize + * @see Component#getPreferredSize() + * @see Component#isPreferredSizeSet() + * @see ComponentUI#getPreferredSize(JComponent) */ public Dimension getPreferredSize() { - Dimension prefSize = null; - if (preferredSize != null) - prefSize = new Dimension(preferredSize); - - else if (ui != null) + Dimension size = null; + if (isPreferredSizeSet()) + size = super.getPreferredSize(); + else { - Dimension s = ui.getPreferredSize(this); - if (s != null) - prefSize = s; + if (ui != null) + size = ui.getPreferredSize(this); + if (size == null) + size = super.getPreferredSize(); } - - if (prefSize == null) - prefSize = super.getPreferredSize(); - - return prefSize; + return size; } /** - * Checks if a maximum size was explicitely set on the component. - * - * @return <code>true</code> if a maximum size was set, - * <code>false</code> otherwise - * - * @since 1.3 - */ - public boolean isMaximumSizeSet() - { - return maximumSize != null; - } - - /** - * Checks if a minimum size was explicitely set on the component. - * - * @return <code>true</code> if a minimum size was set, - * <code>false</code> otherwise - * - * @since 1.3 - */ - public boolean isMinimumSizeSet() - { - return minimumSize != null; - } - - /** - * Checks if a preferred size was explicitely set on the component. - * - * @return <code>true</code> if a preferred size was set, - * <code>false</code> otherwise - * - * @since 1.3 - */ - public boolean isPreferredSizeSet() - { - return preferredSize != null; - } - - /** * Return the value of the <code>nextFocusableComponent</code> property. * * @return The current value of the property, or <code>null</code> @@ -1850,7 +1798,7 @@ public abstract class JComponent extends Container implements Serializable && rm.isDoubleBufferingEnabled()) { Rectangle clip = g.getClipBounds(); - paintDoubleBuffered(clip); + paintDoubleBuffered(clip.x, clip.y, clip.width, clip.height); } else { @@ -1865,8 +1813,22 @@ public abstract class JComponent extends Container implements Serializable dragBuffer = null; } - if (g.getClip() == null) - g.setClip(0, 0, getWidth(), getHeight()); + Rectangle clip = g.getClipBounds(); + int clipX, clipY, clipW, clipH; + if (clip == null) + { + clipX = 0; + clipY = 0; + clipW = getWidth(); + clipH = getHeight(); + } + else + { + clipX = clip.x; + clipY = clip.y; + clipW = clip.width; + clipH = clip.height; + } if (dragBuffer != null && dragBufferInitialized) { g.drawImage(dragBuffer, 0, 0, this); @@ -1874,14 +1836,51 @@ public abstract class JComponent extends Container implements Serializable else { Graphics g2 = getComponentGraphics(g); - paintComponent(g2); - paintBorder(g2); + if (! isOccupiedByChild(clipX, clipY, clipW, clipH)) + { + paintComponent(g2); + paintBorder(g2); + } paintChildren(g2); } } } /** + * Determines if a region of this component is completely occupied by + * an opaque child component, in which case we don't need to bother + * painting this component at all. + * + * @param x the area, x coordinate + * @param y the area, y coordinate + * @param w the area, width + * @param h the area, height + * + * @return <code>true</code> if the specified area is completely covered + * by a child component, <code>false</code> otherwise + */ + private boolean isOccupiedByChild(int x, int y, int w, int h) + { + boolean occupied = false; + int count = getComponentCount(); + for (int i = 0; i < count; i++) + { + Component child = getComponent(i); + int cx = child.getX(); + int cy = child.getY(); + int cw = child.getWidth(); + int ch = child.getHeight(); + if (child.isVisible() && x >= cx && x + w <= cx + cw && y >= cy + && y + h <= cy + ch) + { + occupied = child.isOpaque(); + break; + } + } + return occupied; + } + + /** * Initializes the drag buffer by creating a new image and painting this * component into it. */ @@ -1940,7 +1939,14 @@ public abstract class JComponent extends Container implements Serializable // Need to lock the tree to avoid problems with AWT and concurrency. synchronized (getTreeLock()) { - for (int i = getComponentCount() - 1; i >= 0; i--) + // Fast forward to the child to paint, if set by + // paintImmediately2() + int i = getComponentCount() - 1; + if (paintChild != null && paintChild.isOpaque()) + { + for (; i >= 0 && getComponent(i) != paintChild; i--); + } + for (; i >= 0; i--) { Component child = getComponent(i); if (child != null && child.isLightweight() @@ -1958,7 +1964,8 @@ public abstract class JComponent extends Container implements Serializable Rectangle clip = g.getClipBounds(); // A copy. SwingUtilities.computeIntersection(cx, cy, cw, ch, clip); - if (isCompletelyObscured(i, clip)) + if (isCompletelyObscured(i, clip.x, clip.y, + clip.width, clip.height)) continue; // Continues the for-loop. } Graphics cg = g.create(cx, cy, cw, ch); @@ -1984,12 +1991,15 @@ public abstract class JComponent extends Container implements Serializable * of its siblings. * * @param index the index of the child component - * @param rect the region to check + * @param x the region to check, x coordinate + * @param y the region to check, y coordinate + * @param w the region to check, width + * @param h the region to check, height * * @return <code>true</code> if the region is completely obscured by a * sibling, <code>false</code> otherwise */ - private boolean isCompletelyObscured(int index, Rectangle rect) + private boolean isCompletelyObscured(int index, int x, int y, int w, int h) { boolean obscured = false; for (int i = index - 1; i >= 0 && obscured == false; i--) @@ -1998,10 +2008,10 @@ public abstract class JComponent extends Container implements Serializable if (sib.isVisible()) { Rectangle sibRect = sib.getBounds(rectCache); - if (sib.isOpaque() && rect.x >= sibRect.x - && (rect.x + rect.width) <= (sibRect.x + sibRect.width) - && rect.y >= sibRect.y - && (rect.y + rect.height) <= (sibRect.y + sibRect.height)) + if (sib.isOpaque() && x >= sibRect.x + && (x + w) <= (sibRect.x + sibRect.width) + && y >= sibRect.y + && (y + h) <= (sibRect.y + sibRect.height)) { obscured = true; } @@ -2011,6 +2021,39 @@ public abstract class JComponent extends Container implements Serializable } /** + * Checks if a component/rectangle is partially obscured by one of its + * siblings. + * Note that this doesn't check for completely obscured, this is + * done by isCompletelyObscured() and should probably also be checked. + * + * @param i the component index from which to start searching + * @param x the x coordinate of the rectangle to check + * @param y the y coordinate of the rectangle to check + * @param w the width of the rectangle to check + * @param h the height of the rectangle to check + * + * @return <code>true</code> if the rectangle is partially obscured + */ + private boolean isPartiallyObscured(int i, int x, int y, int w, int h) + { + boolean obscured = false; + for (int j = i - 1; j >= 0 && ! obscured; j--) + { + Component sibl = getComponent(j); + if (sibl.isVisible()) + { + Rectangle rect = sibl.getBounds(rectCache); + if (!(x + w <= rect.x) + || (y + h <= rect.y) + || (x >= rect.x + rect.width) + || (y >= rect.y + rect.height)) + obscured = true; + } + } + return obscured; + } + + /** * Paint the component's body. This usually means calling {@link * ComponentUI#update} on the {@link #ui} property of the component, if * it is non-<code>null</code>. You may override this if you wish to @@ -2050,7 +2093,26 @@ public abstract class JComponent extends Container implements Serializable */ public void paintImmediately(int x, int y, int w, int h) { - paintImmediately(new Rectangle(x, y, w, h)); + // Find opaque parent and call paintImmediately2() on it. + if (isShowing()) + { + Component c = this; + Component p; + while (c != null && ! c.isOpaque()) + { + p = c.getParent(); + if (p != null) + { + x += c.getX(); + y += c.getY(); + c = p; + } + } + if (c instanceof JComponent) + ((JComponent) c).paintImmediately2(x, y, w, h); + else + c.repaint(x, y, w, h); + } } /** @@ -2073,60 +2135,177 @@ public abstract class JComponent extends Container implements Serializable */ public void paintImmediately(Rectangle r) { - // Try to find a root pane for this component. - //Component root = findPaintRoot(r); - Component root = findPaintRoot(r); - // If no paint root is found, then this component is completely overlapped - // by another component and we don't need repainting. - if (root == null|| !root.isShowing()) - return; - SwingUtilities.convertRectangleToAncestor(this, r, root); - if (root instanceof JComponent) - ((JComponent) root).paintImmediately2(r); - else - root.repaint(r.x, r.y, r.width, r.height); + paintImmediately(r.x, r.y, r.width, r.height); } /** * Performs the actual work of paintImmediatly on the repaint root. * - * @param r the area to be repainted + * @param x the area to be repainted, X coordinate + * @param y the area to be repainted, Y coordinate */ - void paintImmediately2(Rectangle r) + void paintImmediately2(int x, int y, int w, int h) { - isRepainting = true; + // Optimization for components that are always painted on top. + boolean onTop = onTop() && isOpaque(); + + // Fetch the RepaintManager. RepaintManager rm = RepaintManager.currentManager(this); - if (rm.isDoubleBufferingEnabled() && isPaintingDoubleBuffered()) - paintDoubleBuffered(r); - else - paintSimple(r); - isRepainting = false; + + // The painting clip; + int paintX = x; + int paintY = y; + int paintW = w; + int paintH = h; + + // If we should paint buffered or not. + boolean haveBuffer = false; + + // The component that is finally triggered for painting. + JComponent paintRoot = this; + + // Stores the component and all its parents. This will be used to limit + // the actually painted components in paintChildren by setting + // the field paintChild. + int pIndex = -1; + int pCount = 0; + ArrayList components = new ArrayList(); + + // Offset to subtract from the paintRoot rectangle when painting. + int offsX = 0; + int offsY = 0; + + // The current component and its child. + Component child; + Container c; + + // Find appropriate paint root. + for (c = this, child = null; + c != null && ! (c instanceof Window) && ! (c instanceof Applet); + child = c, c = c.getParent()) + { + JComponent jc = c instanceof JComponent ? (JComponent) c : null; + components.add(c); + if (! onTop && jc != null && ! jc.isOptimizedDrawingEnabled()) + { + // Check obscured state of the child. + // Generally, we have 3 cases here: + // 1. Not obscured. No need to paint from the parent. + // 2. Partially obscured. Paint from the parent. + // 3. Completely obscured. No need to paint anything. + if (c != this) + { + int count = c.getComponentCount(); + int i = 0; + for (; i < count && c.getComponent(i) != child; i++); + + if (jc.isCompletelyObscured(i, paintX, paintY, paintW, paintH)) + return; // No need to paint anything. + else if (jc.isPartiallyObscured(i, paintX, paintY, paintW, + paintH)) + { + // Paint from parent. + paintRoot = jc; + pIndex = pCount; + offsX = 0; + offsY = 0; + haveBuffer = false; + } + } + } + pCount++; + // Check if component is double buffered. + if (rm.isDoubleBufferingEnabled() && jc != null + && jc.isDoubleBuffered()) + { + haveBuffer = true; + } + + // Clip the paint region with the parent. + if (! onTop) + { + paintX = Math.max(0, paintX); + paintY = Math.max(0, paintY); + paintW = Math.min(c.getWidth(), paintW + paintX) - paintX; + paintH = Math.min(c.getHeight(), paintH + paintY) - paintY; + int dx = c.getX(); + int dy = c.getY(); + paintX += dx; + paintY += dy; + offsX += dx; + offsY += dy; + } + } + if (c != null && c.getPeer() != null && paintW > 0 && paintH > 0) + { + isRepainting = true; + paintX -= offsX; + paintY -= offsY; + + // Set the painting path so that paintChildren paints only what we + // want. + if (paintRoot != this) + { + for (int i = pIndex; i > 0; i--) + { + Component paintParent = (Component) components.get(i); + if (paintParent instanceof JComponent) + ((JComponent) paintParent).paintChild = + (Component) components.get(i - 1); + } + } + + // Actually trigger painting. + if (haveBuffer) + paintRoot.paintDoubleBuffered(paintX, paintY, paintW, + paintH); + else + { + Graphics g = paintRoot.getGraphics(); + try + { + g.setClip(paintX, paintY, paintW, paintH); + paintRoot.paint(g); + } + finally + { + g.dispose(); + } + } + + // Reset the painting path. + if (paintRoot != this) + { + for (int i = pIndex; i > 0; i--) + { + Component paintParent = (Component) components.get(i); + if (paintParent instanceof JComponent) + ((JComponent) paintParent).paintChild = null; + } + } + + isRepainting = false; + } } /** - * Returns true if we must paint double buffered, that is, when this - * component or any of it's ancestors are double buffered. + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. * - * @return true if we must paint double buffered, that is, when this - * component or any of it's ancestors are double buffered + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others */ - private boolean isPaintingDoubleBuffered() + boolean onTop() { - boolean doubleBuffered = isDoubleBuffered(); - Component parent = getParent(); - while (! doubleBuffered && parent != null) - { - doubleBuffered = parent instanceof JComponent - && ((JComponent) parent).isDoubleBuffered(); - parent = parent.getParent(); - } - return doubleBuffered; + return false; } /** * Performs double buffered repainting. */ - private void paintDoubleBuffered(Rectangle r) + private void paintDoubleBuffered(int x, int y, int w, int h) { RepaintManager rm = RepaintManager.currentManager(this); @@ -2143,7 +2322,7 @@ public abstract class JComponent extends Container implements Serializable //Rectangle targetClip = SwingUtilities.convertRectangle(this, r, root); Graphics g2 = buffer.getGraphics(); clipAndTranslateGraphics(root, this, g2); - g2.clipRect(r.x, r.y, r.width, r.height); + g2.clipRect(x, y, w, h); g2 = getComponentGraphics(g2); paintingDoubleBuffered = true; try @@ -2164,7 +2343,7 @@ public abstract class JComponent extends Container implements Serializable } // Paint the buffer contents on screen. - rm.commitBuffer(this, r); + rm.commitBuffer(this, x, y, w, h); } /** @@ -2577,7 +2756,7 @@ public abstract class JComponent extends Container implements Serializable KeyEvent e, int condition, boolean pressed) - { + { if (isEnabled()) { Action act = null; @@ -2591,7 +2770,7 @@ public abstract class JComponent extends Container implements Serializable if (cmd instanceof ActionListenerProxy) act = (Action) cmd; else - act = (Action) getActionMap().get(cmd); + act = getActionMap().get(cmd); } } if (act != null && act.isEnabled()) @@ -2741,9 +2920,25 @@ public abstract class JComponent extends Container implements Serializable */ public void scrollRectToVisible(Rectangle r) { - Component p = getParent(); - if (p instanceof JComponent) - ((JComponent) p).scrollRectToVisible(r); + // Search nearest JComponent. + int xOffs = getX(); + int yOffs = getY(); + Component p; + for (p = getParent(); p != null && ! (p instanceof JComponent); + p = p.getParent()) + { + xOffs += p.getX(); + yOffs += p.getY(); + } + if (p != null) + { + r.x += xOffs; + r.y += yOffs; + JComponent jParent = (JComponent) p; + jParent.scrollRectToVisible(r); + r.x -= xOffs; + r.y -= yOffs; + } } /** @@ -2862,57 +3057,6 @@ public abstract class JComponent extends Container implements Serializable } /** - * Set the value of the {@link #maximumSize} property. The passed value is - * copied, the later direct changes on the argument have no effect on the - * property value. - * - * @param max The new value of the property - */ - public void setMaximumSize(Dimension max) - { - Dimension oldMaximumSize = maximumSize; - if (max != null) - maximumSize = new Dimension(max); - else - maximumSize = null; - firePropertyChange("maximumSize", oldMaximumSize, maximumSize); - } - - /** - * Set the value of the {@link #minimumSize} property. The passed value is - * copied, the later direct changes on the argument have no effect on the - * property value. - * - * @param min The new value of the property - */ - public void setMinimumSize(Dimension min) - { - Dimension oldMinimumSize = minimumSize; - if (min != null) - minimumSize = new Dimension(min); - else - minimumSize = null; - firePropertyChange("minimumSize", oldMinimumSize, minimumSize); - } - - /** - * Set the value of the {@link #preferredSize} property. The passed value is - * copied, the later direct changes on the argument have no effect on the - * property value. - * - * @param pref The new value of the property - */ - public void setPreferredSize(Dimension pref) - { - Dimension oldPreferredSize = preferredSize; - if (pref != null) - preferredSize = new Dimension(pref); - else - preferredSize = null; - firePropertyChange("preferredSize", oldPreferredSize, preferredSize); - } - - /** * Set the specified component to be the next component in the * focus cycle, overriding the {@link FocusTraversalPolicy} for * this component. @@ -3571,130 +3715,6 @@ public abstract class JComponent extends Container implements Serializable jc.fireAncestorEvent(ancestor, id); } } - - /** - * Finds a suitable paint root for painting this component. This method first - * checks if this component is overlapped using - * {@link #findOverlapFreeParent(Rectangle)}. The returned paint root is then - * feeded to {@link #findOpaqueParent(Component)} to find the nearest opaque - * component for this paint root. If no paint is necessary, then we return - * <code>null</code>. - * - * @param c the clip of this component - * - * @return the paint root or <code>null</code> if no painting is necessary - */ - private Component findPaintRoot(Rectangle c) - { - Component p = findOverlapFreeParent(c); - if (p == null) - return null; - Component root = findOpaqueParent(p); - return root; - } - - /** - * Scans the containment hierarchy upwards for components that overlap the - * this component in the specified clip. This method returns - * <code>this</code>, if no component overlaps this component. It returns - * <code>null</code> if another component completely covers this component - * in the specified clip (no repaint necessary). If another component partly - * overlaps this component in the specified clip, then the parent of this - * component is returned (this is the component that must be used as repaint - * root). For efficient lookup, the method - * {@link #isOptimizedDrawingEnabled()} is used. - * - * @param clip the clip of this component - * - * @return the paint root, or <code>null</code> if no paint is necessary - */ - private Component findOverlapFreeParent(Rectangle clip) - { - Rectangle currentClip = clip; - Component found = this; - Container parent = this; - - while (parent != null && !(parent instanceof Window)) - { - Container newParent = parent.getParent(); - if (newParent == null || newParent instanceof Window) - break; - // If the parent is optimizedDrawingEnabled, then its children are - // tiled and cannot have an overlapping child. Go directly to next - // parent. - if ((newParent instanceof JComponent - && ((JComponent) newParent).isOptimizedDrawingEnabled())) - - { - parent = newParent; - continue; - } - - // If the parent is not optimizedDrawingEnabled, we must check if the - // parent or some neighbor overlaps the current clip. - - // This is the current clip converted to the parent's coordinate - // system. TODO: We can do this more efficiently by succesively - // cumulating the parent-child translations. - Rectangle target = SwingUtilities.convertRectangle(found, - currentClip, - newParent); - - // We have an overlap if either: - // - The new parent itself doesn't completely cover the clip - // (this can be the case with viewports). - // - If some higher-level (than the current) children of the new parent - // intersect the target rectangle. - Rectangle parentRect = SwingUtilities.getLocalBounds(newParent); - boolean haveOverlap = - ! SwingUtilities.isRectangleContainingRectangle(parentRect, target); - if (! haveOverlap) - { - Component child; - for (int i = 0; (child = newParent.getComponent(i)) != parent && !haveOverlap; i++) - { - Rectangle childRect = child.getBounds(); - haveOverlap = target.intersects(childRect); - } - } - if (haveOverlap) - { - found = newParent; - currentClip = target; - } - parent = newParent; - } - //System.err.println("overlapfree parent: " + found); - return found; - } - - /** - * Finds the nearest component to <code>c</code> (upwards in the containment - * hierarchy), that is opaque. If <code>c</code> itself is opaque, - * this returns <code>c</code> itself. - * - * @param c the start component for the search - * @return the nearest component to <code>c</code> (upwards in the containment - * hierarchy), that is opaque; If <code>c</code> itself is opaque, - * this returns <code>c</code> itself - */ - private Component findOpaqueParent(Component c) - { - Component found = c; - while (true) - { - if ((found instanceof JComponent) && ((JComponent) found).isOpaque()) - break; - else if (!(found instanceof JComponent) && !found.isLightweight()) - break; - Container p = found.getParent(); - if (p == null) - break; - else - found = p; - } - return found; - } /** * This is the method that gets called when the WHEN_IN_FOCUSED_WINDOW map diff --git a/javax/swing/JEditorPane.java b/javax/swing/JEditorPane.java index 4ae3c5a1c..a5efa07df 100644 --- a/javax/swing/JEditorPane.java +++ b/javax/swing/JEditorPane.java @@ -56,6 +56,7 @@ import javax.accessibility.AccessibleStateSet; import javax.accessibility.AccessibleText; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; +import javax.swing.plaf.TextUI; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultEditorKit; import javax.swing.text.Document; @@ -695,10 +696,28 @@ public class JEditorPane extends JTextComponent public Dimension getPreferredSize() { Dimension pref = super.getPreferredSize(); - if (getScrollableTracksViewportWidth()) - pref.width = getUI().getMinimumSize(this).width; - if (getScrollableTracksViewportHeight()) - pref.height = getUI().getMinimumSize(this).height; + Container parent = getParent(); + if (parent instanceof JViewport) + { + JViewport vp = (JViewport) getParent(); + TextUI ui = getUI(); + Dimension min = null; + if (! getScrollableTracksViewportWidth()) + { + min = ui.getMinimumSize(this); + int vpWidth = vp.getWidth(); + if (vpWidth != 0 && vpWidth < min.width) + pref.width = min.width; + } + if (! getScrollableTracksViewportHeight()) + { + if (min == null) + min = ui.getMinimumSize(this); + int vpHeight = vp.getHeight(); + if (vpHeight != 0 && vpHeight < min.height) + pref.height = min.height; + } + } return pref; } @@ -716,8 +735,11 @@ public class JEditorPane extends JTextComponent // Tests show that this returns true when the parent is a JViewport // and has a height > minimum UI height. Container parent = getParent(); + int height = parent.getHeight(); + TextUI ui = getUI(); return parent instanceof JViewport - && parent.getHeight() > getUI().getMinimumSize(this).height; + && height >= ui.getMinimumSize(this).height + && height <= ui.getMaximumSize(this).height; } /** diff --git a/javax/swing/JList.java b/javax/swing/JList.java index 1cff9553a..ff1b23921 100644 --- a/javax/swing/JList.java +++ b/javax/swing/JList.java @@ -1946,72 +1946,74 @@ public class JList extends JComponent implements Accessible, Scrollable public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - ListUI lui = this.getUI(); + int unit = -1; if (orientation == SwingConstants.VERTICAL) { - if (direction > 0) + int row = getFirstVisibleIndex(); + if (row == -1) + unit = 0; + else if (direction > 0) { - // Scrolling down - Point bottomLeft = new Point(visibleRect.x, - visibleRect.y + visibleRect.height); - int curIdx = lui.locationToIndex(this, bottomLeft); - Rectangle curBounds = lui.getCellBounds(this, curIdx, curIdx); - if (curBounds.y + curBounds.height == bottomLeft.y) + // Scrolling down. + Rectangle bounds = getCellBounds(row, row); + if (bounds != null) + unit = bounds.height - (visibleRect.y - bounds.y); + else + unit = 0; + } + else + { + // Scrolling up. + Rectangle bounds = getCellBounds(row, row); + // First row. + if (row == 0 && bounds.y == visibleRect.y) + unit = 0; // No need to scroll. + else if (bounds.y == visibleRect.y) { - // we are at the exact bottom of the current cell, so we - // are being asked to scroll to the end of the next one - if (curIdx + 1 < model.getSize()) - { - // there *is* a next item in the list - Rectangle nxtBounds = lui.getCellBounds(this, curIdx + 1, curIdx + 1); - return nxtBounds.height; - } + // Scroll to previous row. + Point loc = bounds.getLocation(); + loc.y--; + int prev = locationToIndex(loc); + Rectangle prevR = getCellBounds(prev, prev); + if (prevR == null || prevR.y >= bounds.y) + unit = 0; // For multicolumn lists. else - { - // no next item, no advance possible - return 0; - } + unit = prevR.height; } else - { - // we are part way through an existing cell, so we are being - // asked to scroll to the bottom of it - return (curBounds.y + curBounds.height) - bottomLeft.y; - } + unit = visibleRect.y - bounds.y; } - else + } + else if (orientation == SwingConstants.HORIZONTAL && getLayoutOrientation() != VERTICAL) + { + // Horizontal scrolling. + int i = locationToIndex(visibleRect.getLocation()); + if (i != -1) { - // scrolling up - Point topLeft = new Point(visibleRect.x, visibleRect.y); - int curIdx = lui.locationToIndex(this, topLeft); - Rectangle curBounds = lui.getCellBounds(this, curIdx, curIdx); - if (curBounds.y == topLeft.y) + Rectangle b = getCellBounds(i, i); + if (b != null) { - // we are at the exact top of the current cell, so we - // are being asked to scroll to the top of the previous one - if (curIdx > 0) + if (b.x != visibleRect.x) { - // there *is* a previous item in the list - Rectangle nxtBounds = lui.getCellBounds(this, curIdx - 1, curIdx - 1); - return -nxtBounds.height; + if (direction < 0) + unit = Math.abs(b.x - visibleRect.x); + else + unit = b.width + b.x - visibleRect.x; } else - { - // no previous item, no advance possible - return 0; - } + unit = b.width; } - else - { - // we are part way through an existing cell, so we are being - // asked to scroll to the top of it - return curBounds.y - topLeft.y; - } } } - // FIXME: handle horizontal scrolling (also wrapping?) - return 1; + if (unit == -1) + { + // This fallback seems to be used by the RI for the degenerate cases + // not covered above. + Font f = getFont(); + unit = f != null ? f.getSize() : 1; + } + return unit; } /** @@ -2040,10 +2042,120 @@ public class JList extends JComponent implements Accessible, Scrollable public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - if (orientation == VERTICAL) - return visibleRect.height * direction; - else - return visibleRect.width * direction; + int block = -1; + if (orientation == SwingConstants.VERTICAL) + { + // Default block scroll. Special cases are handled below for + // better usability. + block = visibleRect.height; + if (direction > 0) + { + // Scroll down. + // Scroll so that after scrolling the last line aligns with + // the lower boundary of the visible area. + Point p = new Point(visibleRect.x, + visibleRect.y + visibleRect.height - 1); + int last = locationToIndex(p); + if (last != -1) + { + Rectangle lastR = getCellBounds(last, last); + if (lastR != null) + { + block = lastR.y - visibleRect.y; + if (block == 0&& last < getModel().getSize() - 1) + block = lastR.height; + } + } + } + else + { + // Scroll up. + // Scroll so that after scrolling the first line aligns with + // the upper boundary of the visible area. + Point p = new Point(visibleRect.x, + visibleRect.y - visibleRect.height); + int newFirst = locationToIndex(p); + if (newFirst != -1) + { + int first = getFirstVisibleIndex(); + if (first == -1) + first = locationToIndex(visibleRect.getLocation()); + Rectangle newFirstR = getCellBounds(newFirst, newFirst); + Rectangle firstR = getCellBounds(first, first); + if (newFirstR != null && firstR != null) + { + // Search first item that would left the current first + // item visible when scrolled to. + while (newFirstR.y + visibleRect.height + < firstR.y + firstR.height + && newFirstR.y < firstR.y) + { + newFirst++; + newFirstR = getCellBounds(newFirst, newFirst); + } + block = visibleRect.y - newFirstR.y; + if (block <= 0 && newFirstR.y > 0) + { + newFirst--; + newFirstR = getCellBounds(newFirst, newFirst); + if (newFirstR != null) + block = visibleRect.y - newFirstR.y; + } + } + } + } + } + else if (orientation == SwingConstants.HORIZONTAL + && getLayoutOrientation() != VERTICAL) + { + // Default block increment. Special cases are handled below for + // better usability. + block = visibleRect.width; + if (direction > 0) + { + // Scroll right. + Point p = new Point(visibleRect.x + visibleRect.width + 1, + visibleRect.y); + int last = locationToIndex(p); + if (last != -1) + { + Rectangle lastR = getCellBounds(last, last); + if (lastR != null) + { + block = lastR.x - visibleRect.x; + if (block < 0) + block += lastR.width; + else if (block == 0 && last < getModel().getSize() - 1) + block = lastR.width; + } + } + } + else + { + // Scroll left. + Point p = new Point(visibleRect.x - visibleRect.width, + visibleRect.y); + int first = locationToIndex(p); + if (first != -1) + { + Rectangle firstR = getCellBounds(first, first); + if (firstR != null) + { + if (firstR.x < visibleRect.x - visibleRect.width) + { + if (firstR.x + firstR.width > visibleRect.x) + block = visibleRect.x - firstR.x; + else + block = visibleRect.x - firstR.x - firstR.width; + } + else + block = visibleRect.x - firstR.x; + } + } + } + } + + return block; } /** diff --git a/javax/swing/JMenu.java b/javax/swing/JMenu.java index 68f31e335..7e627f118 100644 --- a/javax/swing/JMenu.java +++ b/javax/swing/JMenu.java @@ -39,7 +39,14 @@ exception statement from your version. */ package javax.swing; import java.awt.Component; +import java.awt.Dimension; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Insets; import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -53,6 +60,8 @@ import javax.accessibility.Accessible; import javax.accessibility.AccessibleContext; import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleSelection; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.plaf.MenuItemUI; @@ -71,6 +80,36 @@ import javax.swing.plaf.MenuItemUI; */ public class JMenu extends JMenuItem implements Accessible, MenuElement { + /** + * Receives notifications when the JMenu's ButtonModel is changed and + * fires menuSelected or menuDeselected events when appropriate. + */ + private class MenuChangeListener + implements ChangeListener + { + /** + * Indicates the last selected state. + */ + private boolean selected; + + /** + * Receives notification when the JMenu's ButtonModel changes. + */ + public void stateChanged(ChangeEvent ev) + { + ButtonModel m = (ButtonModel) ev.getSource(); + boolean s = m.isSelected(); + if (s != selected) + { + if (s) + fireMenuSelected(); + else + fireMenuDeselected(); + selected = s; + } + } + } + private static final long serialVersionUID = 4227225638931828014L; /** A Popup menu associated with this menu, which pops up when menu is selected */ @@ -87,11 +126,20 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement /* PopupListener */ protected WinListener popupListener; - /** Location at which popup menu associated with this menu will be - displayed */ + /** + * Location at which popup menu associated with this menu will be + * displayed + */ private Point menuLocation; /** + * The ChangeListener for the ButtonModel. + * + * @see MenuChangeListener + */ + private ChangeListener menuChangeListener; + + /** * Creates a new JMenu object. */ public JMenu() @@ -188,7 +236,7 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public JMenuItem add(String text) { - return getPopupMenu().add(text); + return add(new JMenuItem(text)); } /** @@ -200,7 +248,10 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public JMenuItem add(Action action) { - return getPopupMenu().add(action); + JMenuItem i = createActionComponent(action); + i.setAction(action); + add(i); + return i; } /** @@ -323,7 +374,18 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public void setModel(ButtonModel model) { + ButtonModel oldModel = getModel(); + if (oldModel != null && menuChangeListener != null) + oldModel.removeChangeListener(menuChangeListener); + super.setModel(model); + + if (model != null) + { + if (menuChangeListener == null) + menuChangeListener = new MenuChangeListener(); + model.addChangeListener(menuChangeListener); + } } /** @@ -372,7 +434,8 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement if (popup && isShowing()) { // Set location as determined by getPopupLocation(). - Point loc = getPopupMenuOrigin(); + Point loc = menuLocation == null ? getPopupMenuOrigin() + : menuLocation; getPopupMenu().show(this, loc.x, loc.y); } else @@ -381,26 +444,109 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement } /** - * Returns origin point of the popup menu + * Returns origin point of the popup menu. This takes the screen bounds + * into account and places the popup where it fits best. * - * @return Point containing + * @return the origin of the popup menu */ protected Point getPopupMenuOrigin() { - Point point; - - // if menu in the menu bar + // The menu's screen location and size. + Point screenLoc = getLocationOnScreen(); + Dimension size = getSize(); + + // Determine the popup's size. + JPopupMenu popup = getPopupMenu(); + Dimension popupSize = popup.getSize(); + if (popupSize.width == 0 || popupSize.height == 0) + popupSize = popup.getPreferredSize(); + + // Determine screen bounds. + Toolkit tk = Toolkit.getDefaultToolkit(); + Rectangle screenBounds = new Rectangle(tk.getScreenSize()); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice gd = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + Insets screenInsets = tk.getScreenInsets(gc); + screenBounds.x -= screenInsets.left; + screenBounds.width -= screenInsets.left + screenInsets.right; + screenBounds.y -= screenInsets.top; + screenBounds.height -= screenInsets.top + screenInsets.bottom; + screenLoc.x -= screenInsets.left; + screenLoc.y -= screenInsets.top; + + Point point = new Point(); if (isTopLevelMenu()) - point = new Point(0, this.getHeight()); - - // if submenu + { + // If menu in the menu bar. + int xOffset = UIManager.getInt("Menu.menuPopupOffsetX"); + int yOffset = UIManager.getInt("Menu.menuPopupOffsetY"); + // Determine X location. + if (getComponentOrientation().isLeftToRight()) + { + // Prefer popup to the right. + point.x = xOffset; + // Check if it fits, otherwise place popup wherever it fits. + if (screenLoc.x + point.x + popupSize.width + > screenBounds.width + screenBounds.width + && screenBounds.width - size.width + < 2 * (screenLoc.x - screenBounds.x)) + // Popup to the right if there's not enough room. + point.x = size.width - xOffset - popupSize.width; + } + else + { + // Prefer popup to the left. + point.x = size.width - xOffset - popupSize.width; + if (screenLoc.x + point.x < screenBounds.x + && screenBounds.width - size.width + > 2 * (screenLoc.x - screenBounds.x)) + // Popup to the left if there's not enough room. + point.x = xOffset; + } + // Determine Y location. Prefer popping down. + point.y = size.height + yOffset; + if (screenLoc.y + point.y + popupSize.height >= screenBounds.height + && screenBounds.height - size.height + < 2 * (screenLoc.y - screenBounds.y)) + // Position above if there's not enough room below. + point.y = - yOffset - popupSize.height; + } else { + // If submenu. int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX"); int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY"); - int x = getWidth() + xOffset; - int y = yOffset; - point = new Point(x, y); + // Determine X location. + if (getComponentOrientation().isLeftToRight()) + { + // Prefer popup to the right. + point.x = size.width + xOffset; + if (screenLoc.x + point.x + popupSize.width + >= screenBounds.x + screenBounds.width + && screenBounds.width - size.width + < 2 * (screenLoc.x - screenBounds.x)) + // Position to the left if there's not enough room on the right. + point.x = - xOffset - popupSize.width; + } + else + { + // Prefer popup on the left side. + point.x = - xOffset - popupSize.width; + if (screenLoc.x + point.x < screenBounds.x + && screenBounds.width - size.width + > 2 * (screenLoc.x - screenBounds.x)) + // Popup to the right if there's not enough room. + point.x = size.width + xOffset; + } + // Determine Y location. Prefer popping down. + point.y = yOffset; + if (screenLoc.y + point.y + popupSize.height + >= screenBounds.y + screenBounds.height + && screenBounds.height - size.height + < 2 * (screenLoc.y - screenBounds.y)) + // Pop up if there's not enough room below. + point.y = size.height - yOffset - popupSize.height; } return point; } @@ -442,6 +588,8 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement public void setMenuLocation(int x, int y) { menuLocation = new Point(x, y); + if (popupMenu != null) + popupMenu.setLocation(x, y); } /** @@ -559,7 +707,7 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement if (getPopupMenu() == null || getMenuComponentCount() == 0) return null; - return (Component) popupMenu.getComponentAtIndex(index); + return popupMenu.getComponentAtIndex(index); } /** diff --git a/javax/swing/JMenuItem.java b/javax/swing/JMenuItem.java index f7f93bf00..324d61cd4 100644 --- a/javax/swing/JMenuItem.java +++ b/javax/swing/JMenuItem.java @@ -833,4 +833,20 @@ public class JMenuItem extends AbstractButton implements Accessible, return AccessibleRole.MENU_ITEM; } } + + /** + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. + * + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others + */ + boolean onTop() + { + return SwingUtilities.getAncestorOfClass(JInternalFrame.class, this) + == null; + } + } diff --git a/javax/swing/JPopupMenu.java b/javax/swing/JPopupMenu.java index 2e59d4767..1ae8adad0 100644 --- a/javax/swing/JPopupMenu.java +++ b/javax/swing/JPopupMenu.java @@ -902,6 +902,20 @@ public class JPopupMenu extends JComponent implements Accessible, MenuElement } } + /** + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. + * + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others + */ + boolean onTop() + { + return true; + } + protected class AccessibleJPopupMenu extends AccessibleJComponent { private static final long serialVersionUID = 7423261328879849768L; diff --git a/javax/swing/JTabbedPane.java b/javax/swing/JTabbedPane.java index 5c8d04748..a500b9264 100644 --- a/javax/swing/JTabbedPane.java +++ b/javax/swing/JTabbedPane.java @@ -760,11 +760,7 @@ public class JTabbedPane extends JComponent implements Serializable, this.tabPlacement = tabPlacement; layoutPolicy = tabLayoutPolicy; - changeEvent = new ChangeEvent(this); - changeListener = createChangeListener(); - - model = new DefaultSingleSelectionModel(); - model.addChangeListener(changeListener); + setModel(new DefaultSingleSelectionModel()); updateUI(); } @@ -877,16 +873,24 @@ public class JTabbedPane extends JComponent implements Serializable, /** * This method changes the model property of the JTabbedPane. * - * @param model The new model to use with the JTabbedPane. + * @param m The new model to use with the JTabbedPane. */ - public void setModel(SingleSelectionModel model) + public void setModel(SingleSelectionModel m) { - if (model != this.model) + if (m != model) { SingleSelectionModel oldModel = this.model; - this.model.removeChangeListener(changeListener); - this.model = model; - this.model.addChangeListener(changeListener); + if (oldModel != null && changeListener != null) + oldModel.removeChangeListener(changeListener); + + model = m; + + if (model != null) + { + if (changeListener != null) + changeListener = createChangeListener(); + model.addChangeListener(changeListener); + } firePropertyChange("model", oldModel, this.model); } } diff --git a/javax/swing/JTable.java b/javax/swing/JTable.java index efa1a80bc..28cc6728d 100644 --- a/javax/swing/JTable.java +++ b/javax/swing/JTable.java @@ -3303,10 +3303,21 @@ public class JTable public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - if (orientation == SwingConstants.VERTICAL) - return visibleRect.height * direction; + int block; + if (orientation == SwingConstants.HORIZONTAL) + { + block = visibleRect.width; + } else - return visibleRect.width * direction; + { + int rowHeight = getRowHeight(); + if (rowHeight > 0) + block = Math.max(rowHeight, // Little hack for useful rounding. + (visibleRect.height / rowHeight) * rowHeight); + else + block = visibleRect.height; + } + return block; } /** @@ -3345,24 +3356,40 @@ public class JTable * The values greater than one means that more mouse wheel or similar * events were generated, and hence it is better to scroll the longer * distance. - * @author Audrius Meskauskas (audriusa@bioinformatics.org) + * + * @author Roman Kennke (kennke@aicas.com) */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - int h = (rowHeight + rowMargin); - int delta = h * direction; - - // Round so that the top would start from the row boundary - if (orientation == SwingConstants.VERTICAL) + int unit; + if (orientation == SwingConstants.HORIZONTAL) + unit = 100; + else { - // Completely expose the top row - int near = ((visibleRect.y + delta + h / 2) / h) * h; - int diff = visibleRect.y + delta - near; - delta -= diff; + unit = getRowHeight(); + // The following adjustment doesn't work for variable height rows. + // It fully exposes partially visible rows in the scrolling direction. + if (rowHeights == null) + { + if (direction > 0) + { + // Scroll down. + // How much pixles are exposed from the last item? + int exposed = (visibleRect.y + visibleRect.height) % unit; + if (exposed > 0 && exposed < unit - 1) + unit = unit - exposed - 1; + } + else + { + // Scroll up. + int exposed = visibleRect.y % unit; + if (exposed > 0 && exposed < unit) + unit = exposed; + } + } } - return delta; - // TODO when scrollng horizontally, scroll into the column boundary. + return unit; } /** diff --git a/javax/swing/JTextPane.java b/javax/swing/JTextPane.java index c0a5f80cf..05968fc8c 100644 --- a/javax/swing/JTextPane.java +++ b/javax/swing/JTextPane.java @@ -214,20 +214,11 @@ public class JTextPane */ public void insertIcon(Icon icon) { - SimpleAttributeSet atts = new SimpleAttributeSet(); - atts.addAttribute(StyleConstants.IconAttribute, icon); - atts.addAttribute(StyleConstants.NameAttribute, - StyleConstants.IconElementName); - try - { - getDocument().insertString(getCaret().getDot(), " ", atts); - } - catch (BadLocationException ex) - { - AssertionError err = new AssertionError("Unexpected bad location"); - err.initCause(ex); - throw err; - } + MutableAttributeSet inputAtts = getInputAttributes(); + inputAtts.removeAttributes(inputAtts); + StyleConstants.setIcon(inputAtts, icon); + replaceSelection(" "); + inputAtts.removeAttributes(inputAtts); } /** diff --git a/javax/swing/JToolTip.java b/javax/swing/JToolTip.java index 836c122c6..3153894da 100644 --- a/javax/swing/JToolTip.java +++ b/javax/swing/JToolTip.java @@ -225,4 +225,18 @@ public class JToolTip extends JComponent implements Accessible { setUI((ToolTipUI) UIManager.getUI(this)); } + + /** + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. + * + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others + */ + boolean onTop() + { + return true; + } } diff --git a/javax/swing/JTree.java b/javax/swing/JTree.java index 32acca643..c76a9b783 100644 --- a/javax/swing/JTree.java +++ b/javax/swing/JTree.java @@ -1684,29 +1684,52 @@ public class JTree extends JComponent implements Scrollable, Accessible public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - int delta; + int delta = 0; // Round so that the top would start from the row boundary if (orientation == SwingConstants.VERTICAL) { - // One pixel down, otherwise picks another row too high. - int row = getClosestRowForLocation(visibleRect.x, visibleRect.y + 1); - row = row + direction; - if (row < 0) - row = 0; - - Rectangle newTop = getRowBounds(row); - delta = newTop.y - visibleRect.y; + int row = getClosestRowForLocation(0, visibleRect.y); + if (row != -1) + { + Rectangle b = getRowBounds(row); + if (b.y != visibleRect.y) + { + if (direction < 0) + delta = Math.max(0, visibleRect.y - b.y); + else + delta = b.y + b.height - visibleRect.height; + } + else + { + if (direction < 0) + { + if (row != 0) + { + b = getRowBounds(row - 1); + delta = b.height; + } + } + else + delta = b.height; + } + } } else - delta = direction * rowHeight == 0 ? 20 : rowHeight; + // The RI always returns 4 for HORIZONTAL scrolling. + delta = 4; return delta; } public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - return getScrollableUnitIncrement(visibleRect, orientation, direction); + int block; + if (orientation == SwingConstants.VERTICAL) + block = visibleRect.height; + else + block = visibleRect.width; + return block; } public boolean getScrollableTracksViewportHeight() diff --git a/javax/swing/JViewport.java b/javax/swing/JViewport.java index 7cf393996..babffb7ce 100644 --- a/javax/swing/JViewport.java +++ b/javax/swing/JViewport.java @@ -942,10 +942,10 @@ public class JViewport extends JComponent implements Accessible * * @param r the rectangle to paint */ - void paintImmediately2(Rectangle r) + void paintImmediately2(int x, int y, int w, int h) { isPaintRoot = true; - super.paintImmediately2(r); + super.paintImmediately2(x, y, w, h); isPaintRoot = false; } } diff --git a/javax/swing/RepaintManager.java b/javax/swing/RepaintManager.java index 29bf98fbe..2d29e9076 100644 --- a/javax/swing/RepaintManager.java +++ b/javax/swing/RepaintManager.java @@ -38,6 +38,7 @@ exception statement from your version. */ package javax.swing; +import java.applet.Applet; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; @@ -49,7 +50,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.Map; import java.util.Set; import java.util.WeakHashMap; @@ -197,19 +197,6 @@ public class RepaintManager private WeakHashMap offscreenBuffers; /** - * Indicates if the RepaintManager is currently repainting an area. - */ - private boolean repaintUnderway; - - /** - * This holds buffer commit requests when the RepaintManager is working. - * This maps Component objects (the top level components) to Rectangle - * objects (the area of the corresponding buffer that must be blitted on - * the component). - */ - private HashMap commitRequests; - - /** * The maximum width and height to allocate as a double buffer. Requests * beyond this size are ignored. * @@ -232,8 +219,6 @@ public class RepaintManager doubleBufferMaximumSize = new Dimension(2000,2000); doubleBufferingEnabled = true; offscreenBuffers = new WeakHashMap(); - repaintUnderway = false; - commitRequests = new HashMap(); } /** @@ -398,8 +383,6 @@ public class RepaintManager if (w <= 0 || h <= 0 || !component.isShowing()) return; - Component parent = component.getParent(); - component.computeVisibleRect(rectCache); SwingUtilities.computeIntersection(x, y, w, h, rectCache); @@ -557,7 +540,6 @@ public class RepaintManager compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots); } - repaintUnderway = true; for (Iterator i = repaintRoots.iterator(); i.hasNext();) { JComponent comp = (JComponent) i.next(); @@ -567,13 +549,11 @@ public class RepaintManager comp.paintImmediately(damaged); } dirtyComponentsWork.clear(); - repaintUnderway = false; - commitRemainingBuffers(); } - + /** * Compiles a list of components that really get repainted. This is called - * once for each component in the dirtyComponents HashMap, each time with + * once for each component in the dirtyRegions HashMap, each time with * another <code>dirty</code> parameter. This searches up the component * hierarchy of <code>dirty</code> to find the highest parent that is also * marked dirty and merges the dirty regions. @@ -588,6 +568,29 @@ public class RepaintManager Component current = dirty; Component root = dirty; + // This will contain the dirty region in the root coordinate system, + // possibly clipped by ancestor's bounds. + Rectangle originalDirtyRect = (Rectangle) dirtyRegions.get(dirty); + rectCache.setBounds(originalDirtyRect); + + // The bounds of the current component. + int x = dirty.getX(); + int y = dirty.getY(); + int w = dirty.getWidth(); + int h = dirty.getHeight(); + + // Do nothing if dirty region is clipped away by the component's bounds. + rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache); + if (rectCache.isEmpty()) + return; + + // The cumulated offsets. + int dx = 0; + int dy = 0; + // The actual offset for the found root. + int rootDx = 0; + int rootDy = 0; + // Search the highest component that is also marked dirty. Component parent; while (true) @@ -597,10 +600,29 @@ public class RepaintManager break; current = parent; + // Update the offset. + dx += x; + dy += y; + rectCache.x += x; + rectCache.y += y; + + x = current.getX(); + y = current.getY(); + w = current.getWidth(); + h = current.getHeight(); + rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache); + + // Don't paint if the dirty regions is clipped away by any of + // its ancestors. + if (rectCache.isEmpty()) + return; + // We can skip to the next up when this parent is not dirty. if (dirtyRegions.containsKey(parent)) { root = current; + rootDx = dx; + rootDy = dy; } } @@ -608,15 +630,16 @@ public class RepaintManager // the are different. if (root != dirty) { - Rectangle dirtyRect = (Rectangle) dirtyRegions.get(dirty); - dirtyRect = SwingUtilities.convertRectangle(dirty, dirtyRect, root); - Rectangle rootRect = (Rectangle) dirtyRegions.get(root); - SwingUtilities.computeUnion(dirtyRect.x, dirtyRect.y, dirtyRect.width, - dirtyRect.height, rootRect); + rectCache.x += rootDx - dx; + rectCache.y += rootDy - dy; + Rectangle dirtyRect = (Rectangle) dirtyRegions.get(root); + SwingUtilities.computeUnion(rectCache.x, rectCache.y, rectCache.width, + rectCache.height, dirtyRect); } // Adds the root to the roots set. - roots.add(root); + if (! roots.contains(root)) + roots.add(root); } /** @@ -643,130 +666,40 @@ public class RepaintManager width = Math.min(doubleBufferMaximumSize.width, width); int height = Math.max(proposedHeight, root.getHeight()); height = Math.min(doubleBufferMaximumSize.height, height); - buffer = root.createImage(width, height); + buffer = component.createImage(width, height); offscreenBuffers.put(root, buffer); } return buffer; } /** - * Blits the back buffer of the specified root component to the screen. If - * the RepaintManager is currently working on a paint request, the commit - * requests are queued up and committed at once when the paint request is - * done (by {@link #commitRemainingBuffers}). This is package private because - * it must get called by JComponent. + * Blits the back buffer of the specified root component to the screen. + * This is package private because it must get called by JComponent. * * @param comp the component to be painted - * @param area the area to paint on screen, in comp coordinates - */ - void commitBuffer(Component comp, Rectangle area) - { - // Determine the component that we finally paint the buffer upon. - // We need to paint on the nearest heavyweight component, so that Swing - // hierarchies inside (non-window) heavyweights get painted correctly. - // Otherwise we would end up blitting the backbuffer behind the heavyweight - // which is wrong. - Component root = getHeavyweightParent(comp); - // FIXME: Optimize this. - Rectangle rootRect = SwingUtilities.convertRectangle(comp, area, root); - - // We synchronize on dirtyComponents here because that is what - // paintDirtyRegions also synchronizes on while painting. - synchronized (dirtyComponents) - { - // If the RepaintManager is not currently painting, then directly - // blit the requested buffer on the screen. - if (true || ! repaintUnderway) - { - blitBuffer(root, rootRect); - } - - // Otherwise queue this request up, until all the RepaintManager work - // is done. - else - { - if (commitRequests.containsKey(root)) - SwingUtilities.computeUnion(rootRect.x, rootRect.y, - rootRect.width, rootRect.height, - (Rectangle) commitRequests.get(root)); - else - commitRequests.put(root, rootRect); - } - } - } - - /** - * Copies the buffer to the screen. Note that the root component here is - * not necessarily the component with which the offscreen buffer is - * associated. The offscreen buffers are always allocated for the toplevel - * windows. However, painted is performed on lower-level heavyweight - * components too, if they contain Swing components. - * - * @param root the heavyweight component to blit upon - * @param rootRect the rectangle in the root component's coordinate space + * @param x the area to paint on screen, in comp coordinates + * @param y the area to paint on screen, in comp coordinates + * @param w the area to paint on screen, in comp coordinates + * @param h the area to paint on screen, in comp coordinates */ - private void blitBuffer(Component root, Rectangle rootRect) + void commitBuffer(Component comp, int x, int y, int w, int h) { - if (! root.isShowing()) - return; - - // Find the Window from which we use the backbuffer. - Component bufferRoot = root; - Rectangle bufferRect = rootRect.getBounds(); - if (!(bufferRoot instanceof Window)) + Component root = comp; + while (root != null + && ! (root instanceof Window || root instanceof Applet)) { - bufferRoot = SwingUtilities.getWindowAncestor(bufferRoot); - SwingUtilities.convertRectangleToAncestor(root, rootRect, bufferRoot); + x += root.getX(); + y += root.getY(); + root = root.getParent(); } Graphics g = root.getGraphics(); - Image buffer = (Image) offscreenBuffers.get(bufferRoot); + Image buffer = (Image) offscreenBuffers.get(root); // Make sure we have a sane clip at this point. - g.clipRect(rootRect.x, rootRect.y, rootRect.width, rootRect.height); - g.drawImage(buffer, rootRect.x - bufferRect.x, rootRect.y - bufferRect.y, - root); + g.clipRect(x, y, w, h); + g.drawImage(buffer, 0, 0, root); g.dispose(); - - } - - /** - * Finds and returns the nearest heavyweight parent for the specified - * component. If the component isn't contained inside a heavyweight parent, - * this returns null. - * - * @param comp the component - * - * @return the nearest heavyweight parent for the specified component or - * null if the component has no heavyweight ancestor - */ - private Component getHeavyweightParent(Component comp) - { - while (comp != null && comp.isLightweight()) - comp = comp.getParent(); - return comp; - } - - /** - * Commits the queued up back buffers to screen all at once. - */ - private void commitRemainingBuffers() - { - // We synchronize on dirtyComponents here because that is what - // paintDirtyRegions also synchronizes on while painting. - synchronized (dirtyComponents) - { - Set entrySet = commitRequests.entrySet(); - Iterator i = entrySet.iterator(); - while (i.hasNext()) - { - Map.Entry entry = (Map.Entry) i.next(); - Component root = (Component) entry.getKey(); - Rectangle area = (Rectangle) entry.getValue(); - blitBuffer(root, area); - i.remove(); - } - } } /** diff --git a/javax/swing/ScrollPaneLayout.java b/javax/swing/ScrollPaneLayout.java index 8ce8fd86f..2a16f26ea 100644 --- a/javax/swing/ScrollPaneLayout.java +++ b/javax/swing/ScrollPaneLayout.java @@ -46,6 +46,8 @@ import java.awt.LayoutManager; import java.awt.Rectangle; import java.io.Serializable; +import javax.swing.border.Border; + /** * ScrollPaneLayout * @author Andrew Selkirk @@ -277,6 +279,16 @@ public class ScrollPaneLayout width += rowHead.getPreferredSize().width; if (colHead != null && colHead.isVisible()) height += colHead.getPreferredSize().height; + + // Add insets of viewportBorder if present. + Border vpBorder = sc.getViewportBorder(); + if (vpBorder != null) + { + Insets i = vpBorder.getBorderInsets(sc); + width += i.left + i.right; + height += i.top + i.bottom; + } + Insets i = sc.getInsets(); return new Dimension(width + i.left + i.right, height + i.left + i.right); @@ -300,6 +312,15 @@ public class ScrollPaneLayout != JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) height += sc.getHorizontalScrollBar().getMinimumSize().height; + // Add insets of viewportBorder if present. + Border vpBorder = sc.getViewportBorder(); + if (vpBorder != null) + { + i = vpBorder.getBorderInsets(sc); + width += i.left + i.right; + height += i.top + i.bottom; + } + return new Dimension(width, height); } @@ -342,6 +363,15 @@ public class ScrollPaneLayout int y1 = 0, y2 = 0, y3 = 0, y4 = 0; Rectangle scrollPaneBounds = SwingUtilities.calculateInnerArea(sc, null); + // If there is a viewportBorder, remove its insets from the available + // space. + Border vpBorder = sc.getViewportBorder(); + Insets vpi; + if (vpBorder != null) + vpi = vpBorder.getBorderInsets(sc); + else + vpi = new Insets(0, 0, 0, 0); + x1 = scrollPaneBounds.x; y1 = scrollPaneBounds.y; x4 = scrollPaneBounds.x + scrollPaneBounds.width; @@ -404,7 +434,9 @@ public class ScrollPaneLayout // now set the layout if (viewport != null) - viewport.setBounds(new Rectangle(x2, y2, x3 - x2, y3 - y2)); + viewport.setBounds(new Rectangle(x2 + vpi.left, y2 + vpi.top, + x3 - x2 - vpi.left - vpi.right, + y3 - y2 - vpi.top - vpi.bottom)); if (colHead != null) colHead.setBounds(new Rectangle(x2, y1, x3 - x2, y2 - y1)); @@ -415,7 +447,7 @@ public class ScrollPaneLayout if (showVsb) { vsb.setVisible(true); - vsb.setBounds(new Rectangle(x3, y2, x4 - x3, y3 - y2)); + vsb.setBounds(new Rectangle(x3, y2, x4 - x3, y3 - y2 )); } else if (vsb != null) vsb.setVisible(false); @@ -423,7 +455,7 @@ public class ScrollPaneLayout if (showHsb) { hsb.setVisible(true); - hsb.setBounds(new Rectangle(x2, y3, x3 - x2, y4 - y3)); + hsb.setBounds(new Rectangle(x2 , y3, x3 - x2, y4 - y3)); } else if (hsb != null) hsb.setVisible(false); diff --git a/javax/swing/SwingUtilities.java b/javax/swing/SwingUtilities.java index 2823367ce..6ff0b3346 100644 --- a/javax/swing/SwingUtilities.java +++ b/javax/swing/SwingUtilities.java @@ -40,7 +40,6 @@ package javax.swing; import java.applet.Applet; import java.awt.Component; -import java.awt.ComponentOrientation; import java.awt.Container; import java.awt.FontMetrics; import java.awt.Frame; @@ -721,38 +720,35 @@ public class SwingUtilities // Fix up the orientation-based horizontal positions. - if (horizontalTextPosition == LEADING) - { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalTextPosition = RIGHT; - else - horizontalTextPosition = LEFT; - } - else if (horizontalTextPosition == TRAILING) + boolean ltr = true; + if (c != null && ! c.getComponentOrientation().isLeftToRight()) + ltr = false; + + switch (horizontalTextPosition) { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalTextPosition = LEFT; - else - horizontalTextPosition = RIGHT; + case LEADING: + horizontalTextPosition = ltr ? LEFT : RIGHT; + break; + case TRAILING: + horizontalTextPosition = ltr ? RIGHT : LEFT; + break; + default: + // Nothing to do in the other cases. } // Fix up the orientation-based alignments. - - if (horizontalAlignment == LEADING) - { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalAlignment = RIGHT; - else - horizontalAlignment = LEFT; - } - else if (horizontalAlignment == TRAILING) + switch (horizontalAlignment) { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalAlignment = LEFT; - else - horizontalAlignment = RIGHT; + case LEADING: + horizontalAlignment = ltr ? LEFT : RIGHT; + break; + case TRAILING: + horizontalAlignment = ltr ? RIGHT : LEFT; + break; + default: + // Nothing to do in the other cases. } - + return layoutCompoundLabelImpl(c, fm, text, icon, verticalAlignment, horizontalAlignment, @@ -921,104 +917,108 @@ public class SwingUtilities iconR.width = icon.getIconWidth(); iconR.height = icon.getIconHeight(); } + if (text == null || text.equals("")) { textIconGap = 0; textR.width = 0; textR.height = 0; + text = ""; } else { + int availableWidth = viewR.width; + if (horizontalTextPosition != CENTER) + availableWidth -= iconR.width + textIconGap; View html = c == null ? null : (View) c.getClientProperty(BasicHTML.propertyKey); if (html != null) { textR.width = (int) html.getPreferredSpan(View.X_AXIS); + textR.width = Math.min(availableWidth, textR.width); textR.height = (int) html.getPreferredSpan(View.Y_AXIS); } else { int fromIndex = 0; textR.width = fm.stringWidth(text); - textR.height = fm.getHeight(); - while (text.indexOf('\n', fromIndex) != -1) + textR.height = fm.getHeight(); + if (textR.width > availableWidth) { - textR.height += fm.getHeight(); - fromIndex = text.indexOf('\n', fromIndex) + 1; + text = clipString(c, fm, text, availableWidth); + textR.width = fm.stringWidth(text); } } } - // Work out the position of text and icon, assuming the top-left coord + // Work out the position of text, assuming the top-left coord // starts at (0,0). We will fix that up momentarily, after these // "position" decisions are made and we look at alignment. - switch (horizontalTextPosition) + switch (verticalTextPosition) { - case LEFT: - textR.x = 0; - iconR.x = textR.width + textIconGap; + case TOP: + textR.y = horizontalTextPosition == CENTER ? + - textR.height - textIconGap : 0; break; - case RIGHT: - iconR.x = 0; - textR.x = iconR.width + textIconGap; + case BOTTOM: + textR.y = horizontalTextPosition == CENTER ? + iconR.height + textIconGap : iconR.height - textR.height; break; case CENTER: - int centerLine = Math.max(textR.width, iconR.width) / 2; - textR.x = centerLine - textR.width/2; - iconR.x = centerLine - iconR.width/2; + textR.y = iconR.height / 2 - textR.height / 2; break; } - switch (verticalTextPosition) + switch (horizontalTextPosition) { - case TOP: - textR.y = 0; - iconR.y = (horizontalTextPosition == CENTER - ? textR.height + textIconGap : 0); + case LEFT: + textR.x = -(textR.width + textIconGap); break; - case BOTTOM: - iconR.y = 0; - textR.y = (horizontalTextPosition == CENTER - ? iconR.height + textIconGap - : Math.max(iconR.height - textR.height, 0)); + case RIGHT: + textR.x = iconR.width + textIconGap; break; case CENTER: - int centerLine = Math.max(textR.height, iconR.height) / 2; - textR.y = centerLine - textR.height/2; - iconR.y = centerLine - iconR.height/2; + textR.x = iconR.width / 2 - textR.width / 2; break; } + // The two rectangles are laid out correctly now, but only assuming // that their upper left corner is at (0,0). If we have any alignment other // than TOP and LEFT, we need to adjust them. - Rectangle u = textR.union(iconR); - int horizontalAdjustment = viewR.x; - int verticalAdjustment = viewR.y; + // These coordinates specify the rectangle that contains both the + // icon and text. Move it so that it fullfills the alignment properties. + int lx = Math.min(iconR.x, textR.x); + int lw = Math.max(iconR.x + iconR.width, textR.x + textR.width) - lx; + int ly = Math.min(iconR.y, textR.y); + int lh = Math.max(iconR.y + iconR.height, textR.y + textR.height) - ly; + int horizontalAdjustment = 0; + int verticalAdjustment = 0; switch (verticalAlignment) { case TOP: + verticalAdjustment = viewR.y - ly; break; case BOTTOM: - verticalAdjustment += (viewR.height - u.height); + verticalAdjustment = viewR.y + viewR.height - ly - lh; break; case CENTER: - verticalAdjustment += ((viewR.height/2) - (u.height/2)); + verticalAdjustment = viewR.y + viewR.height / 2 - ly - lh / 2; break; } switch (horizontalAlignment) { case LEFT: + horizontalAdjustment = viewR.x - lx; break; case RIGHT: - horizontalAdjustment += (viewR.width - u.width); + horizontalAdjustment = viewR.x + viewR.width - lx - lw; break; case CENTER: - horizontalAdjustment += ((viewR.width/2) - (u.width/2)); + horizontalAdjustment = (viewR.x + (viewR.width / 2)) - (lx + (lw / 2)); break; } - iconR.x += horizontalAdjustment; iconR.y += verticalAdjustment; @@ -1028,6 +1028,48 @@ public class SwingUtilities return text; } + /** + * The method clips the specified string so that it fits into the + * available width. It is only called when the text really doesn't fit, + * so we don't need to check that again. + * + * @param c the component + * @param fm the font metrics + * @param text the text + * @param availableWidth the available width + * + * @return the clipped string + */ + private static String clipString(JComponent c, FontMetrics fm, String text, + int availableWidth) + { + String dots = "..."; + int dotsWidth = fm.stringWidth(dots); + char[] string = text.toCharArray(); + int endIndex = string.length; + while (fm.charsWidth(string, 0, endIndex) + dotsWidth > availableWidth + && endIndex > 0) + endIndex--; + String clipped; + if (string.length >= endIndex + 3) + { + string[endIndex] = '.'; + string[endIndex + 1] = '.'; + string[endIndex + 2] = '.'; + clipped = new String(string, 0, endIndex + 3); + } + else + { + char[] clippedChars = new char[string.length + 3]; + System.arraycopy(string, 0, clippedChars, 0, string.length); + clippedChars[endIndex] = '.'; + clippedChars[endIndex + 1] = '.'; + clippedChars[endIndex + 2] = '.'; + clipped = new String(clippedChars, 0, endIndex + 3); + } + return clipped; + } + /** * Calls {@link java.awt.EventQueue#invokeLater} with the * specified {@link Runnable}. diff --git a/javax/swing/event/EventListenerList.java b/javax/swing/event/EventListenerList.java index f76dfa3fe..1568039f0 100644 --- a/javax/swing/event/EventListenerList.java +++ b/javax/swing/event/EventListenerList.java @@ -37,6 +37,9 @@ exception statement from your version. */ package javax.swing.event; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.util.EventListener; @@ -304,4 +307,51 @@ public class EventListenerList } return buf.toString(); } + + /** + * Serializes an instance to an ObjectOutputStream. + * + * @param out the stream to serialize to + * + * @throws IOException if something goes wrong + */ + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + for (int i = 0; i < listenerList.length; i += 2) + { + Class cl = (Class) listenerList[i]; + EventListener l = (EventListener) listenerList[i + 1]; + if (l != null && l instanceof Serializable) + { + out.writeObject(cl.getName()); + out.writeObject(l); + } + } + // Write end marker. + out.writeObject(null); + } + + /** + * Deserializes an instance from an ObjectInputStream. + * + * @param in the input stream + * + * @throws ClassNotFoundException if a serialized class can't be found + * @throws IOException if something goes wrong + */ + private <T extends EventListener> void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException + { + listenerList = NO_LISTENERS; + in.defaultReadObject(); + Object type; + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + while ((type = in.readObject()) != null) + { + EventListener l = (EventListener) in.readObject(); + add(((Class<T>) Class.forName((String) type, true, cl)), (T) l); + } + } } diff --git a/javax/swing/filechooser/FileSystemView.java b/javax/swing/filechooser/FileSystemView.java index 84b80dd40..26ca4860c 100644 --- a/javax/swing/filechooser/FileSystemView.java +++ b/javax/swing/filechooser/FileSystemView.java @@ -37,6 +37,8 @@ exception statement from your version. */ package javax.swing.filechooser; +import gnu.classpath.NotImplementedException; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -169,16 +171,12 @@ public abstract class FileSystemView * @return A default {@link FileSystemView} appropriate for the platform. */ public static FileSystemView getFileSystemView() + throws NotImplementedException { if (defaultFileSystemView == null) { - if (File.separator.equals("/")) - defaultFileSystemView = new UnixFileSystemView(); - // FIXME: need to implement additional views - // else if (File.Separator.equals("\")) - // return new Win32FileSystemView(); - // else - // return new GenericFileSystemView(); + // FIXME: We need to support other file systems too. + defaultFileSystemView = new UnixFileSystemView(); } return defaultFileSystemView; } diff --git a/javax/swing/plaf/basic/BasicButtonListener.java b/javax/swing/plaf/basic/BasicButtonListener.java index 042192b62..c99de2c70 100644 --- a/javax/swing/plaf/basic/BasicButtonListener.java +++ b/javax/swing/plaf/basic/BasicButtonListener.java @@ -54,15 +54,79 @@ import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.AbstractButton; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.ButtonModel; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ButtonUI; -public class BasicButtonListener implements MouseListener, MouseMotionListener, - FocusListener, ChangeListener, PropertyChangeListener +public class BasicButtonListener + implements MouseListener, MouseMotionListener, FocusListener, ChangeListener, + PropertyChangeListener { + /** + * Implements the keyboard action for Swing buttons. + */ + private class ButtonAction + extends AbstractAction + { + /** + * The key for pressed action. + */ + static final String PRESSED = "pressed"; + + /** + * The key for released action. + */ + static final String RELEASED = "released"; + + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + Object cmd = getValue("__command__"); + AbstractButton b = (AbstractButton) event.getSource(); + ButtonModel m = b.getModel(); + if (PRESSED.equals(cmd)) + { + m.setArmed(true); + m.setPressed(true); + if (! b.isFocusOwner()) + b.requestFocus(); + } + else if (RELEASED.equals(cmd)) + { + m.setPressed(false); + m.setArmed(false); + } + } + + /** + * Indicates if this action is enabled. + * + * @param source the source of the action + * + * @return <code>true</code> when enabled, <code>false</code> otherwise + */ + public boolean isEnabled(Object source) + { + boolean enabled = true; + if (source instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) source; + enabled = b.isEnabled(); + } + return enabled; + } + } + public BasicButtonListener(AbstractButton b) { // Do nothing here. @@ -86,16 +150,25 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, false, false); TextLayout layout = new TextLayout(text, b.getFont(), frc); b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout); + + // Update HTML renderer. + BasicHTML.updateRenderer(b, b.getText()); } - if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)) + else if (property.equals(AbstractButton.CONTENT_AREA_FILLED_CHANGED_PROPERTY)) { - BasicHTML.updateRenderer(b, b.getText()); + checkOpacity(b); } } - + + /** + * Checks the <code>contentAreaFilled</code> property and updates the + * opaque property of the button. + * + * @param b the button to check + */ protected void checkOpacity(AbstractButton b) { - // TODO: What should be done here? + b.setOpaque(b.isContentAreaFilled()); } public void focusGained(FocusEvent e) @@ -120,6 +193,26 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, public void installKeyboardActions(JComponent c) { + ButtonUI ui = ((AbstractButton) c).getUI(); + if (ui instanceof BasicButtonUI) + { + // Install InputMap. + BasicButtonUI basicUI = (BasicButtonUI) ui; + String prefix = basicUI.getPropertyPrefix(); + InputMap focusInputMap = + (InputMap) UIManager.get(prefix + "focusInputMap"); + SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, + focusInputMap); + + ActionMap am = (ActionMap) UIManager.get(prefix + "actionMap"); + if (am == null) + { + am = createDefaultActionMap(); + UIManager.put(prefix + "actionMap", am); + } + SwingUtilities.replaceUIActionMap(c, am); + } + c.getActionMap().put("pressed", new AbstractAction() { @@ -146,31 +239,46 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, } }); } - + + /** + * Creates and returns the default action map for Swing buttons. + * + * @return the default action map for Swing buttons + */ + private ActionMap createDefaultActionMap() + { + Action action = new ButtonAction(); + ActionMapUIResource am = new ActionMapUIResource(); + am.put(ButtonAction.PRESSED, action); + am.put(ButtonAction.RELEASED, action); + return am; + } + public void uninstallKeyboardActions(JComponent c) { - c.getActionMap().put("pressed", null); - c.getActionMap().put("released", null); + SwingUtilities.replaceUIActionMap(c, null); + SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null); } public void stateChanged(ChangeEvent e) { - // TODO: What should be done here, if anything? + // Need to repaint when the button state changes. + ((AbstractButton) e.getSource()).repaint(); } public void mouseMoved(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } public void mouseDragged(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } public void mouseClicked(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** diff --git a/javax/swing/plaf/basic/BasicButtonUI.java b/javax/swing/plaf/basic/BasicButtonUI.java index cdaec2543..e2493d156 100644 --- a/javax/swing/plaf/basic/BasicButtonUI.java +++ b/javax/swing/plaf/basic/BasicButtonUI.java @@ -42,12 +42,13 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Insets; import java.awt.Rectangle; +import java.beans.PropertyChangeListener; import javax.swing.AbstractButton; import javax.swing.ButtonModel; import javax.swing.Icon; -import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.LookAndFeel; @@ -64,6 +65,34 @@ import javax.swing.text.View; public class BasicButtonUI extends ButtonUI { /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle viewR = new Rectangle(); + + /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle iconR = new Rectangle(); + + /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle textR = new Rectangle(); + + /** + * The shared button UI. + */ + private static BasicButtonUI sharedUI; + + /** + * The shared BasicButtonListener. + */ + private static BasicButtonListener sharedListener; + + /** * A constant used to pad out elements in the button's layout and * preferred size calculations. */ @@ -87,7 +116,9 @@ public class BasicButtonUI extends ButtonUI */ public static ComponentUI createUI(final JComponent c) { - return new BasicButtonUI(); + if (sharedUI == null) + sharedUI = new BasicButtonUI(); + return sharedUI; } /** @@ -154,14 +185,29 @@ public class BasicButtonUI extends ButtonUI protected void installDefaults(AbstractButton b) { String prefix = getPropertyPrefix(); + // Install colors and font. LookAndFeel.installColorsAndFont(b, prefix + "background", prefix + "foreground", prefix + "font"); + // Install border. LookAndFeel.installBorder(b, prefix + "border"); + + // Install margin property. if (b.getMargin() == null || b.getMargin() instanceof UIResource) b.setMargin(UIManager.getInsets(prefix + "margin")); - b.setIconTextGap(UIManager.getInt(prefix + "textIconGap")); - b.setInputMap(JComponent.WHEN_FOCUSED, - (InputMap) UIManager.get(prefix + "focusInputMap")); + + // Install rollover property. + Object rollover = UIManager.get(prefix + "rollover"); + if (rollover != null) + LookAndFeel.installProperty(b, "rolloverEnabled", rollover); + + // Fetch default textShiftOffset. + defaultTextShiftOffset = UIManager.getInt(prefix + "textShiftOffset"); + + // Make button opaque if needed. + if (b.isContentAreaFilled()) + LookAndFeel.installProperty(b, "opaque", Boolean.TRUE); + else + LookAndFeel.installProperty(b, "opaque", Boolean.FALSE); } /** @@ -171,21 +217,10 @@ public class BasicButtonUI extends ButtonUI */ protected void uninstallDefaults(AbstractButton b) { - if (b.getFont() instanceof UIResource) - b.setFont(null); - if (b.getForeground() instanceof UIResource) - b.setForeground(null); - if (b.getBackground() instanceof UIResource) - b.setBackground(null); - if (b.getBorder() instanceof UIResource) - b.setBorder(null); - b.setIconTextGap(defaultTextIconGap); - if (b.getMargin() instanceof UIResource) - b.setMargin(null); + // The other properties aren't uninstallable. + LookAndFeel.uninstallBorder(b); } - protected BasicButtonListener listener; - /** * Creates and returns a new instance of {@link BasicButtonListener}. This * method provides a hook to make it easy for subclasses to install a @@ -197,7 +232,13 @@ public class BasicButtonUI extends ButtonUI */ protected BasicButtonListener createButtonListener(AbstractButton b) { - return new BasicButtonListener(b); + // Note: The RI always returns a new instance here. However, + // the BasicButtonListener class is perfectly suitable to be shared + // between multiple buttons, so we return a shared instance here + // for efficiency. + if (sharedListener == null) + sharedListener = new BasicButtonListener(b); + return sharedListener; } /** @@ -207,12 +248,15 @@ public class BasicButtonUI extends ButtonUI */ protected void installListeners(AbstractButton b) { - listener = createButtonListener(b); - b.addChangeListener(listener); - b.addPropertyChangeListener(listener); - b.addFocusListener(listener); - b.addMouseListener(listener); - b.addMouseMotionListener(listener); + BasicButtonListener listener = createButtonListener(b); + if (listener != null) + { + b.addChangeListener(listener); + b.addPropertyChangeListener(listener); + b.addFocusListener(listener); + b.addMouseListener(listener); + b.addMouseMotionListener(listener); + } } /** @@ -222,21 +266,29 @@ public class BasicButtonUI extends ButtonUI */ protected void uninstallListeners(AbstractButton b) { - b.removeChangeListener(listener); - b.removePropertyChangeListener(listener); - b.removeFocusListener(listener); - b.removeMouseListener(listener); - b.removeMouseMotionListener(listener); + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + { + b.removeChangeListener(listener); + b.removePropertyChangeListener(listener); + b.removeFocusListener(listener); + b.removeMouseListener(listener); + b.removeMouseMotionListener(listener); + } } protected void installKeyboardActions(AbstractButton b) { - listener.installKeyboardActions(b); + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + listener.installKeyboardActions(b); } protected void uninstallKeyboardActions(AbstractButton b) { - listener.uninstallKeyboardActions(b); + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + listener.uninstallKeyboardActions(b); } /** @@ -254,6 +306,9 @@ public class BasicButtonUI extends ButtonUI { AbstractButton b = (AbstractButton) c; installDefaults(b); + // It is important to install the listeners before installing + // the keyboard actions, because the keyboard actions + // are actually installed on the listener instance. installListeners(b); installKeyboardActions(b); BasicHTML.updateRenderer(b, b.getText()); @@ -378,44 +433,50 @@ public class BasicButtonUI extends ButtonUI { AbstractButton b = (AbstractButton) c; - Rectangle tr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle vr = new Rectangle(); + Insets i = c.getInsets(); + viewR.x = i.left; + viewR.y = i.top; + viewR.width = c.getWidth() - i.left - i.right; + viewR.height = c.getHeight() - i.top - i.bottom; + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; Font f = c.getFont(); - g.setFont(f); + Icon icon = b.getIcon(); + String text = b.getText(); + text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), + text, icon, + b.getVerticalAlignment(), + b.getHorizontalAlignment(), + b.getVerticalTextPosition(), + b.getHorizontalTextPosition(), + viewR, iconR, textR, + text == null ? 0 + : b.getIconTextGap()); - if (b.isBorderPainted()) - SwingUtilities.calculateInnerArea(b, vr); - else - vr = SwingUtilities.getLocalBounds(b); - String text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), - b.getText(), - currentIcon(b), - b.getVerticalAlignment(), - b.getHorizontalAlignment(), - b.getVerticalTextPosition(), - b.getHorizontalTextPosition(), - vr, ir, tr, - b.getIconTextGap() - + defaultTextShiftOffset); - - if ((b.getModel().isArmed() && b.getModel().isPressed()) - || b.isSelected()) + ButtonModel model = b.getModel(); + if (model.isArmed() && model.isPressed()) paintButtonPressed(g, b); - - paintIcon(g, c, ir); + + if (icon != null) + paintIcon(g, c, iconR); if (text != null) { View html = (View) b.getClientProperty(BasicHTML.propertyKey); if (html != null) - html.paint(g, tr); + html.paint(g, textR); else - paintText(g, b, tr, text); + paintText(g, b, textR, text); } if (b.isFocusOwner() && b.isFocusPainted()) - paintFocus(g, b, vr, tr, ir); + paintFocus(g, b, viewR, textR, iconR); } /** @@ -455,7 +516,16 @@ public class BasicButtonUI extends ButtonUI Icon i = currentIcon(b); if (i != null) - i.paintIcon(c, g, iconRect.x, iconRect.y); + { + ButtonModel m = b.getModel(); + if (m.isPressed() && m.isArmed()) + { + int offs = getTextShiftOffset(); + i.paintIcon(c, g, iconRect.x + offs, iconRect.y + offs); + } + else + i.paintIcon(c, g, iconRect.x, iconRect.y); + } } /** @@ -524,4 +594,31 @@ public class BasicButtonUI extends ButtonUI textRect.y + fm.getAscent()); } } + + /** + * A helper method that finds the BasicButtonListener for the specified + * button. This is there because this UI class is stateless and + * shared for all buttons, and thus can't store the listener + * as instance field. (We store our shared instance in sharedListener, + * however, subclasses may override createButtonListener() and we would + * be lost in this case). + * + * @param b the button + * + * @return the UI event listener + */ + private BasicButtonListener getButtonListener(AbstractButton b) + { + // The listener gets installed as PropertyChangeListener, + // so look for it in the list of property change listeners. + PropertyChangeListener[] listeners = b.getPropertyChangeListeners(); + BasicButtonListener l = null; + for (int i = 0; listeners != null && l == null && i < listeners.length; + i++) + { + if (listeners[i] instanceof BasicButtonListener) + l = (BasicButtonListener) listeners[i]; + } + return l; + } } diff --git a/javax/swing/plaf/basic/BasicFileChooserUI.java b/javax/swing/plaf/basic/BasicFileChooserUI.java index e641a1c10..e1f8e4b28 100644 --- a/javax/swing/plaf/basic/BasicFileChooserUI.java +++ b/javax/swing/plaf/basic/BasicFileChooserUI.java @@ -444,10 +444,10 @@ public class BasicFileChooserUI extends FileChooserUI setDirectory(null); } lastSelected = path; - parentPath = path.substring(0, path.lastIndexOf("/") + 1); + parentPath = f.getParent(); if (f.isFile()) - setFileName(path.substring(path.lastIndexOf("/") + 1)); + setFileName(f.getName()); else if (filechooser.getFileSelectionMode() != JFileChooser.FILES_ONLY) setFileName(path); @@ -827,9 +827,9 @@ public class BasicFileChooserUI extends FileChooserUI installComponents(fc); installListeners(fc); - Object path = filechooser.getCurrentDirectory(); + File path = filechooser.getCurrentDirectory(); if (path != null) - parentPath = path.toString().substring(path.toString().lastIndexOf("/")); + parentPath = path.getParent(); } } diff --git a/javax/swing/plaf/basic/BasicGraphicsUtils.java b/javax/swing/plaf/basic/BasicGraphicsUtils.java index 1e84be932..4c270682d 100644 --- a/javax/swing/plaf/basic/BasicGraphicsUtils.java +++ b/javax/swing/plaf/basic/BasicGraphicsUtils.java @@ -748,7 +748,6 @@ public class BasicGraphicsUtils } } - /** * Determines the preferred width and height of an AbstractButton, * given the gap between the button’s text and icon. @@ -769,24 +768,31 @@ public class BasicGraphicsUtils public static Dimension getPreferredButtonSize(AbstractButton b, int textIconGap) { - Rectangle contentRect; - Rectangle viewRect; - Rectangle iconRect = new Rectangle(); - Rectangle textRect = new Rectangle(); - Insets insets = b.getInsets(); - - viewRect = new Rectangle(); - - /* java.awt.Toolkit.getFontMetrics is deprecated. However, it - * seems not obvious how to get to the correct FontMetrics object - * otherwise. The real problem probably is that the method - * javax.swing.SwingUtilities.layoutCompundLabel should take a - * LineMetrics, not a FontMetrics argument. But fixing this that - * would change the public API. - */ + // These cached rectangles are use here and in BasicButtonUI.paint(), + // so these two methods must never be executed concurrently. Maybe + // we must use other Rectangle instances here. OTOH, Swing is + // designed to be not thread safe, and every layout and paint operation + // should be performed from the EventDispatchThread, so it _should_ be + // OK to do this optimization. + Rectangle viewRect = BasicButtonUI.viewR; + viewRect.x = 0; + viewRect.y = 0; + viewRect.width = Short.MAX_VALUE; + viewRect.height = Short.MAX_VALUE; + Rectangle iconRect = BasicButtonUI.iconR; + iconRect.x = 0; + iconRect.y = 0; + iconRect.width = 0; + iconRect.height = 0; + Rectangle textRect = BasicButtonUI.textR; + textRect.x = 0; + textRect.y = 0; + textRect.width = 0; + textRect.height = 0; + SwingUtilities.layoutCompoundLabel( b, // for the component orientation - b.getToolkit().getFontMetrics(b.getFont()), // see comment above + b.getFontMetrics(b.getFont()), // see comment above b.getText(), b.getIcon(), b.getVerticalAlignment(), @@ -804,13 +810,12 @@ public class BasicGraphicsUtils * +------------------------+ +------------------------+ */ - contentRect = textRect.union(iconRect); - - return new Dimension(insets.left - + contentRect.width - + insets.right + b.getHorizontalAlignment(), - insets.top - + contentRect.height - + insets.bottom); + Rectangle contentRect = + SwingUtilities.computeUnion(textRect.x, textRect.y, textRect.width, + textRect.height, iconRect); + + Insets insets = b.getInsets(); + return new Dimension(insets.left + contentRect.width + insets.right, + insets.top + contentRect.height + insets.bottom); } } diff --git a/javax/swing/plaf/basic/BasicHTML.java b/javax/swing/plaf/basic/BasicHTML.java index 98c9cb277..6e26d5355 100644 --- a/javax/swing/plaf/basic/BasicHTML.java +++ b/javax/swing/plaf/basic/BasicHTML.java @@ -48,6 +48,7 @@ import java.io.StringReader; import javax.swing.JComponent; import javax.swing.SwingConstants; import javax.swing.event.DocumentEvent; +import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.EditorKit; @@ -107,6 +108,7 @@ public class BasicHTML editorKit = kit; document = doc; setView(view); + setSize(view.getPreferredSpan(X_AXIS), view.getPreferredSpan(Y_AXIS)); } /** @@ -151,6 +153,14 @@ public class BasicHTML } /** + * Overridden to forward to real view. + */ + public void setSize(float w, float h) + { + view.setSize(w, h); + } + + /** * Returns the real root view, regardless of the index. * * @param index not used here @@ -346,6 +356,22 @@ public class BasicHTML { return document; } + + /** + * Overridden to return null, as a RootView has no attributes on its own. + */ + public AttributeSet getAttributes() + { + return null; + } + + /** + * Overridden to provide an element for the view. + */ + public Element getElement() + { + return view.getElement(); + } } /** diff --git a/javax/swing/plaf/basic/BasicLabelUI.java b/javax/swing/plaf/basic/BasicLabelUI.java index 304e13ad7..1ec020b1c 100644 --- a/javax/swing/plaf/basic/BasicLabelUI.java +++ b/javax/swing/plaf/basic/BasicLabelUI.java @@ -119,13 +119,37 @@ public class BasicLabelUI extends LabelUI implements PropertyChangeListener { JLabel lab = (JLabel) c; Insets insets = lab.getInsets(); - FontMetrics fm = lab.getFontMetrics(lab.getFont()); - layoutCL(lab, fm, lab.getText(), lab.getIcon(), vr, ir, tr); - Rectangle cr = SwingUtilities.computeUnion(tr.x, tr.y, tr.width, tr.height, - ir); - return new Dimension(insets.left + cr.width + insets.right, insets.top - + cr.height + insets.bottom); - + int insetsX = insets.left + insets.right; + int insetsY = insets.top + insets.bottom; + Icon icon = lab.getIcon(); + String text = lab.getText(); + Dimension ret; + if (icon == null && text == null) + ret = new Dimension(insetsX, insetsY); + else if (icon != null && text == null) + ret = new Dimension(icon.getIconWidth() + insetsX, + icon.getIconHeight() + insetsY); + else + { + FontMetrics fm = lab.getFontMetrics(lab.getFont()); + ir.x = 0; + ir.y = 0; + ir.width = 0; + ir.height = 0; + tr.x = 0; + tr.y = 0; + tr.width = 0; + tr.height = 0; + vr.x = 0; + vr.y = 0; + vr.width = Short.MAX_VALUE; + vr.height = Short.MAX_VALUE; + layoutCL(lab, fm, text, icon, vr, ir, tr); + Rectangle cr = SwingUtilities.computeUnion(tr.x, tr.y, tr.width, + tr.height, ir); + ret = new Dimension(cr.width + insetsX, cr.height + insetsY); + } + return ret; } /** @@ -166,13 +190,20 @@ public class BasicLabelUI extends LabelUI implements PropertyChangeListener { JLabel b = (JLabel) c; FontMetrics fm = g.getFontMetrics(); - vr = SwingUtilities.calculateInnerArea(c, vr); - - if (vr.width < 0) - vr.width = 0; - if (vr.height < 0) - vr.height = 0; + Insets i = c.getInsets(); + vr.x = i.left; + vr.y = i.right; + vr.width = c.getWidth() - i.left + i.right; + vr.height = c.getHeight() - i.top + i.bottom; + ir.x = 0; + ir.y = 0; + ir.width = 0; + ir.height = 0; + tr.x = 0; + tr.y = 0; + tr.width = 0; + tr.height = 0; Icon icon = (b.isEnabled()) ? b.getIcon() : b.getDisabledIcon(); String text = layoutCL(b, fm, b.getText(), icon, vr, ir, tr); diff --git a/javax/swing/plaf/basic/BasicLookAndFeel.java b/javax/swing/plaf/basic/BasicLookAndFeel.java index c056a2403..76d67b002 100644 --- a/javax/swing/plaf/basic/BasicLookAndFeel.java +++ b/javax/swing/plaf/basic/BasicLookAndFeel.java @@ -1218,10 +1218,10 @@ public abstract class BasicLookAndFeel extends LookAndFeel "ctrl UP", "requestFocus", "ctrl KP_UP", "requestFocus" }), - "TabbedPane.background", new ColorUIResource(light), + "TabbedPane.background", new ColorUIResource(192, 192, 192), "TabbedPane.contentBorderInsets", new InsetsUIResource(2, 2, 3, 3), - "TabbedPane.darkShadow", new ColorUIResource(shadow), - "TabbedPane.focus", new ColorUIResource(darkShadow), + "TabbedPane.darkShadow", new ColorUIResource(Color.black), + "TabbedPane.focus", new ColorUIResource(Color.black), "TabbedPane.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { KeyStroke.getKeyStroke("ctrl DOWN"), "requestFocusForVisibleComponent", KeyStroke.getKeyStroke("KP_UP"), "navigateUp", @@ -1235,17 +1235,16 @@ public abstract class BasicLookAndFeel extends LookAndFeel KeyStroke.getKeyStroke("DOWN"), "navigateDown" }), "TabbedPane.font", new FontUIResource("Dialog", Font.PLAIN, 12), - "TabbedPane.foreground", new ColorUIResource(darkShadow), - "TabbedPane.highlight", new ColorUIResource(highLight), - "TabbedPane.light", new ColorUIResource(highLight), + "TabbedPane.foreground", new ColorUIResource(Color.black), + "TabbedPane.highlight", new ColorUIResource(Color.white), + "TabbedPane.light", new ColorUIResource(192, 192, 192), "TabbedPane.selectedTabPadInsets", new InsetsUIResource(2, 2, 2, 1), - "TabbedPane.shadow", new ColorUIResource(shadow), - "TabbedPane.tabbedPaneContentBorderInsets", new InsetsUIResource(3, 2, 1, 2), - "TabbedPane.tabbedPaneTabPadInsets", new InsetsUIResource(1, 1, 1, 1), + "TabbedPane.shadow", new ColorUIResource(128, 128, 128), "TabbedPane.tabsOpaque", Boolean.TRUE, "TabbedPane.tabAreaInsets", new InsetsUIResource(3, 2, 0, 2), "TabbedPane.tabInsets", new InsetsUIResource(0, 4, 1, 4), "TabbedPane.tabRunOverlay", new Integer(2), + "TabbedPane.tabsOverlapBorder", Boolean.FALSE, "TabbedPane.textIconGap", new Integer(4), "Table.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { "ctrl DOWN", "selectNextRowChangeLead", diff --git a/javax/swing/plaf/basic/BasicMenuItemUI.java b/javax/swing/plaf/basic/BasicMenuItemUI.java index 6110aca66..85eefb9ee 100644 --- a/javax/swing/plaf/basic/BasicMenuItemUI.java +++ b/javax/swing/plaf/basic/BasicMenuItemUI.java @@ -449,6 +449,7 @@ public class BasicMenuItemUI extends MenuItemUI // Layout the menu item. The result gets stored in the rectangle // fields of this class. + resetRectangles(null); layoutMenuItem(m, accelText); // The union of the text and icon areas is the label area. @@ -700,6 +701,8 @@ public class BasicMenuItemUI extends MenuItemUI // Layout menu item. The result gets stored in the rectangle fields // of this class. + resetRectangles(m); + layoutMenuItem(m, accelText); // Paint the background. @@ -1271,6 +1274,33 @@ public class BasicMenuItemUI extends MenuItemUI } /** + * Resets the cached layout rectangles. If <code>i</code> is not null, then + * the view rectangle is set to the inner area of the component, otherwise + * it is set to (0, 0, Short.MAX_VALUE, Short.MAX_VALUE), this is needed + * for layouting. + * + * @param i the component for which to initialize the rectangles + */ + private void resetRectangles(JMenuItem i) + { + // Reset rectangles. + iconRect.setBounds(0, 0, 0, 0); + textRect.setBounds(0, 0, 0, 0); + accelRect.setBounds(0, 0, 0, 0); + checkIconRect.setBounds(0, 0, 0, 0); + arrowIconRect.setBounds(0, 0, 0, 0); + if (i == null) + viewRect.setBounds(0, 0, Short.MAX_VALUE, Short.MAX_VALUE); + else + { + Insets insets = i.getInsets(); + viewRect.setBounds(insets.left, insets.top, + i.getWidth() - insets.left - insets.right, + i.getHeight() - insets.top - insets.bottom); + } + } + + /** * A helper method that lays out the menu item. The layout is stored * in the fields of this class. * @@ -1282,21 +1312,6 @@ public class BasicMenuItemUI extends MenuItemUI int width = m.getWidth(); int height = m.getHeight(); - // Reset rectangles. - iconRect.setBounds(0, 0, 0, 0); - textRect.setBounds(0, 0, 0, 0); - accelRect.setBounds(0, 0, 0, 0); - checkIconRect.setBounds(0, 0, 0, 0); - arrowIconRect.setBounds(0, 0, 0, 0); - viewRect.setBounds(0, 0, width, height); - - // Substract insets to the view rect. - Insets insets = m.getInsets(); - viewRect.x += insets.left; - viewRect.y += insets.top; - viewRect.width -= insets.left + insets.right; - viewRect.height -= insets.top + insets.bottom; - // Fetch the fonts. Font font = m.getFont(); FontMetrics fm = m.getFontMetrics(font); diff --git a/javax/swing/plaf/basic/BasicRadioButtonUI.java b/javax/swing/plaf/basic/BasicRadioButtonUI.java index aed4d69d6..802b25339 100644 --- a/javax/swing/plaf/basic/BasicRadioButtonUI.java +++ b/javax/swing/plaf/basic/BasicRadioButtonUI.java @@ -129,9 +129,22 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI { AbstractButton b = (AbstractButton) c; - Rectangle tr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle vr = new Rectangle(); + Insets i = b.getInsets(); + Rectangle tr = textR; + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + Rectangle ir = iconR; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; + Rectangle vr = viewR; + viewR.x = i.left; + viewR.y = i.right; + viewR.width = b.getWidth() - i.left - i.right; + viewR.height = b.getHeight() - i.top - i.bottom; Font f = c.getFont(); @@ -149,13 +162,12 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI currentIcon = getDefaultIcon(); } - SwingUtilities.calculateInnerArea(b, vr); String text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), b.getText(), currentIcon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), vr, ir, tr, b.getIconTextGap() + defaultTextShiftOffset); - + currentIcon.paintIcon(c, g, ir.x, ir.y); if (text != null) @@ -174,17 +186,25 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI // The other icon properties are ignored. AbstractButton b = (AbstractButton) c; - Rectangle contentRect; - Rectangle viewRect; - Rectangle iconRect = new Rectangle(); - Rectangle textRect = new Rectangle(); Insets insets = b.getInsets(); - + + String text = b.getText(); Icon i = b.getIcon(); if (i == null) i = getDefaultIcon(); - viewRect = new Rectangle(); + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; + viewR.x = 0; + viewR.y = 0; + viewR.width = Short.MAX_VALUE; + viewR.height = Short.MAX_VALUE; SwingUtilities.layoutCompoundLabel( b, // for the component orientation @@ -195,17 +215,14 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), - viewRect, iconRect, textRect, + viewR, iconR, textR, defaultTextIconGap + defaultTextShiftOffset); - contentRect = textRect.union(iconRect); - - return new Dimension(insets.left - + contentRect.width - + insets.right + b.getHorizontalAlignment(), - insets.top - + contentRect.height - + insets.bottom); + Rectangle r = SwingUtilities.computeUnion(textR.x, textR.y, textR.width, + textR.height, iconR); + + return new Dimension(insets.left + r.width + insets.right, + insets.top + r.height + insets.bottom); } /** diff --git a/javax/swing/plaf/basic/BasicScrollBarUI.java b/javax/swing/plaf/basic/BasicScrollBarUI.java index 78e5168fc..ee246cbba 100644 --- a/javax/swing/plaf/basic/BasicScrollBarUI.java +++ b/javax/swing/plaf/basic/BasicScrollBarUI.java @@ -1228,8 +1228,12 @@ public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, */ protected void scrollByBlock(int direction) { + if (direction > 0) scrollbar.setValue(scrollbar.getValue() + scrollbar.getBlockIncrement(direction)); + else + scrollbar.setValue(scrollbar.getValue() + - scrollbar.getBlockIncrement(direction)); } /** @@ -1239,8 +1243,12 @@ public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, */ protected void scrollByUnit(int direction) { - scrollbar.setValue(scrollbar.getValue() - + scrollbar.getUnitIncrement(direction)); + if (direction > 0) + scrollbar.setValue(scrollbar.getValue() + + scrollbar.getUnitIncrement(direction)); + else + scrollbar.setValue(scrollbar.getValue() + - scrollbar.getUnitIncrement(direction)); } /** diff --git a/javax/swing/plaf/basic/BasicScrollPaneUI.java b/javax/swing/plaf/basic/BasicScrollPaneUI.java index a0616a8c1..69e352711 100644 --- a/javax/swing/plaf/basic/BasicScrollPaneUI.java +++ b/javax/swing/plaf/basic/BasicScrollPaneUI.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; @@ -54,7 +52,6 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; -import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JComponent; @@ -69,11 +66,13 @@ import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.border.Border; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ScrollPaneUI; +import javax.swing.plaf.UIResource; /** * A UI delegate for the {@link JScrollPane} component. @@ -281,7 +280,7 @@ public class BasicScrollPaneUI extends ScrollPaneUI // Scroll non scrollables. delta = wheel * SCROLL_NON_SCROLLABLES; } - scroll(bar, delta); + scroll(bar, wheel > 0 ? delta : -delta); } // If not, try to scroll horizontally else @@ -436,16 +435,24 @@ public class BasicScrollPaneUI extends ScrollPaneUI "ScrollPane.foreground", "ScrollPane.font"); LookAndFeel.installBorder(p, "ScrollPane.border"); + + // Install Viewport border. + Border vpBorder = p.getViewportBorder(); + if (vpBorder == null || vpBorder instanceof UIResource) + { + vpBorder = UIManager.getBorder("ScrollPane.viewportBorder"); + p.setViewportBorder(vpBorder); + } + p.setOpaque(true); } protected void uninstallDefaults(JScrollPane p) { - p.setForeground(null); - p.setBackground(null); - p.setFont(null); - p.setBorder(null); - scrollpane = null; + LookAndFeel.uninstallBorder(p); + Border vpBorder = p.getViewportBorder(); + if (vpBorder != null && vpBorder instanceof UIResource) + p.setViewportBorder(null); } public void installUI(final JComponent c) @@ -809,8 +816,12 @@ public class BasicScrollPaneUI extends ScrollPaneUI public void paint(Graphics g, JComponent c) { - // do nothing; the normal painting-of-children algorithm, along with - // ScrollPaneLayout, does all the relevant work. + Border vpBorder = scrollpane.getViewportBorder(); + if (vpBorder != null) + { + Rectangle r = scrollpane.getViewportBorderBounds(); + vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height); + } } /** diff --git a/javax/swing/plaf/basic/BasicSplitPaneDivider.java b/javax/swing/plaf/basic/BasicSplitPaneDivider.java index 06d32984e..95468caa9 100644 --- a/javax/swing/plaf/basic/BasicSplitPaneDivider.java +++ b/javax/swing/plaf/basic/BasicSplitPaneDivider.java @@ -38,12 +38,15 @@ exception statement from your version. */ package javax.swing.plaf.basic; +import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; @@ -52,7 +55,7 @@ import java.beans.PropertyChangeListener; import javax.swing.JButton; import javax.swing.JSplitPane; -import javax.swing.SwingConstants; +import javax.swing.UIManager; import javax.swing.border.Border; /** @@ -72,6 +75,207 @@ public class BasicSplitPaneDivider extends Container implements PropertyChangeListener { /** + * The buttons used as one touch buttons. + */ + private class BasicOneTouchButton + extends JButton + { + /** + * Denotes a left button. + */ + static final int LEFT = 0; + + /** + * Denotes a right button. + */ + static final int RIGHT = 1; + + /** + * The x points for the arrow. + */ + private int[] xpoints; + + /** + * The y points for the arrow. + */ + private int[] ypoints; + + /** + * Either LEFT or RIGHT. + */ + private int direction; + + /** + * Creates a new instance. + * + * @param dir either LEFT or RIGHT + */ + BasicOneTouchButton(int dir) + { + direction = dir; + xpoints = new int[3]; + ypoints = new int[3]; + } + + /** + * Never allow borders. + */ + public void setBorder(Border b) + { + } + + /** + * Never allow focus traversal. + */ + public boolean isFocusTraversable() + { + return false; + } + + /** + * Paints the one touch button. + */ + public void paint(Graphics g) + { + if (splitPane != null) + { + // Fill background. + g.setColor(splitPane.getBackground()); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Draw arrow. + int size; + if (direction == LEFT) + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + size = Math.min(getHeight(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size / 2; + xpoints[2] = size; + ypoints[0] = size; + ypoints[1] = 0; + ypoints[2] = size; + } + else + { + size = Math.min(getWidth(), ONE_TOUCH_SIZE); + xpoints[0] = size; + xpoints[1] = 0; + xpoints[2] = size; + ypoints[0] = 0; + ypoints[1] = size / 2; + ypoints[2] = size; + } + } + else + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + size = Math.min(getHeight(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size / 2; + xpoints[2] = size; + ypoints[0] = 0; + ypoints[1] = size; + ypoints[2] = 0; + } + else + { + size = Math.min(getWidth(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size; + xpoints[2] = 0; + ypoints[0] = 0; + ypoints[1] = size / 2; + ypoints[2] = size; + } + } + g.setColor(Color.BLACK); + g.fillPolygon(xpoints, ypoints, 3); + } + } + } + + /** + * Listens for actions on the one touch buttons. + */ + private class OneTouchAction + implements ActionListener + { + + public void actionPerformed(ActionEvent ev) + { + Insets insets = splitPane.getInsets(); + int lastLoc = splitPane.getLastDividerLocation(); + int currentLoc = splitPaneUI.getDividerLocation(splitPane); + int newLoc; + + if (ev.getSource() == leftButton) + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + if (currentLoc + >= splitPane.getHeight() - insets.bottom - getHeight()) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = insets.top; + } + } + else + { + if (currentLoc + >= splitPane.getWidth() - insets.right - getWidth()) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = insets.left; + } + } + } + else + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + if (currentLoc == insets.top) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = splitPane.getHeight() - insets.top - getHeight(); + } + } + else + { + if (currentLoc == insets.left) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = splitPane.getWidth() - insets.left - getWidth(); + } + } + } + if (currentLoc != newLoc) + { + splitPane.setDividerLocation(newLoc); + splitPane.setLastDividerLocation(currentLoc); + } + } + } + + /** * Determined using the <code>serialver</code> tool of Apple/Sun JDK 1.3.1 * on MacOS X 10.1.5. */ @@ -161,6 +365,14 @@ public class BasicSplitPaneDivider extends Container transient int currentDividerLocation = 1; /** + * Indicates if the ont touch buttons are laid out centered or at the + * top/left. + * + * Package private to avoid accessor method. + */ + boolean centerOneTouchButtons; + + /** * Constructs a new divider. * * @param ui the UI delegate of the enclosing <code>JSplitPane</code>. @@ -170,6 +382,8 @@ public class BasicSplitPaneDivider extends Container setLayout(new DividerLayout()); setBasicSplitPaneUI(ui); setDividerSize(splitPane.getDividerSize()); + centerOneTouchButtons = + UIManager.getBoolean("SplitPane.centerOneTouchButtons"); } /** @@ -202,7 +416,8 @@ public class BasicSplitPaneDivider extends Container addMouseMotionListener(mouseHandler); hiddenDivider = splitPaneUI.getNonContinuousLayoutDivider(); orientation = splitPane.getOrientation(); - oneTouchExpandableChanged(); + if (splitPane.isOneTouchExpandable()) + oneTouchExpandableChanged(); } } @@ -293,7 +508,12 @@ public class BasicSplitPaneDivider extends Container */ public Dimension getPreferredSize() { - return getLayout().preferredLayoutSize(this); + Dimension d; + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + d = new Dimension(getDividerSize(), 1); + else + d = new Dimension(1, getDividerSize()); + return d; } /** @@ -320,11 +540,9 @@ public class BasicSplitPaneDivider extends Container else if (e.getPropertyName().equals(JSplitPane.ORIENTATION_PROPERTY)) { orientation = splitPane.getOrientation(); - if (splitPane.isOneTouchExpandable()) - { - layout(); - repaint(); - } + invalidate(); + if (splitPane != null) + splitPane.revalidate(); } else if (e.getPropertyName().equals(JSplitPane.DIVIDER_SIZE_PROPERTY)) dividerSize = splitPane.getDividerSize(); @@ -345,11 +563,6 @@ public class BasicSplitPaneDivider extends Container dividerSize = getSize(); border.paintBorder(this, g, 0, 0, dividerSize.width, dividerSize.height); } - if (splitPane.isOneTouchExpandable()) - { - ((BasicArrowButton) rightButton).paint(g); - ((BasicArrowButton) leftButton).paint(g); - } } /** @@ -361,31 +574,23 @@ public class BasicSplitPaneDivider extends Container if (splitPane.isOneTouchExpandable()) { leftButton = createLeftOneTouchButton(); - rightButton = createRightOneTouchButton(); - add(leftButton); - add(rightButton); + if (leftButton != null) + leftButton.addActionListener(new OneTouchAction()); - leftButton.addMouseListener(mouseHandler); - rightButton.addMouseListener(mouseHandler); + rightButton = createRightOneTouchButton(); + if (rightButton != null) + rightButton.addActionListener(new OneTouchAction()); - // Set it to 1. - currentDividerLocation = 1; - } - else - { + // Only add them when both are non-null. if (leftButton != null && rightButton != null) - { - leftButton.removeMouseListener(mouseHandler); - rightButton.removeMouseListener(mouseHandler); - - remove(leftButton); - remove(rightButton); - leftButton = null; - rightButton = null; + { + add(leftButton); + add(rightButton); } } - layout(); - repaint(); + invalidate(); + if (splitPane != null) + splitPane.revalidate(); } /** @@ -396,12 +601,9 @@ public class BasicSplitPaneDivider extends Container */ protected JButton createLeftOneTouchButton() { - int dir = SwingConstants.WEST; - if (orientation == JSplitPane.VERTICAL_SPLIT) - dir = SwingConstants.NORTH; - JButton button = new BasicArrowButton(dir); - button.setBorder(null); - + JButton button = new BasicOneTouchButton(BasicOneTouchButton.LEFT); + button.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + button.setRequestFocusEnabled(false); return button; } @@ -413,11 +615,9 @@ public class BasicSplitPaneDivider extends Container */ protected JButton createRightOneTouchButton() { - int dir = SwingConstants.EAST; - if (orientation == JSplitPane.VERTICAL_SPLIT) - dir = SwingConstants.SOUTH; - JButton button = new BasicArrowButton(dir); - button.setBorder(null); + JButton button = new BasicOneTouchButton(BasicOneTouchButton.RIGHT); + button.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + button.setRequestFocusEnabled(false); return button; } @@ -521,25 +721,6 @@ public class BasicSplitPaneDivider extends Container */ public void mousePressed(MouseEvent e) { - if (splitPane.isOneTouchExpandable()) - { - if (e.getSource() == leftButton) - { - currentDividerLocation--; - if (currentDividerLocation < 0) - currentDividerLocation = 0; - moveDividerTo(currentDividerLocation); - return; - } - else if (e.getSource() == rightButton) - { - currentDividerLocation++; - if (currentDividerLocation > 2) - currentDividerLocation = 2; - moveDividerTo(currentDividerLocation); - return; - } - } isDragging = true; currentDividerLocation = 1; if (orientation == JSplitPane.HORIZONTAL_SPLIT) @@ -797,10 +978,64 @@ public class BasicSplitPaneDivider extends Container */ public void layoutContainer(Container c) { - if (splitPane.isOneTouchExpandable()) + if (leftButton != null && rightButton != null + && c == BasicSplitPaneDivider.this) { - changeButtonOrientation(); - positionButtons(); + if (splitPane.isOneTouchExpandable()) + { + Insets insets = getInsets(); + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + { + int size = getWidth() - insets.left - insets.right; + size = Math.max(size, 0); + size = Math.min(size, ONE_TOUCH_SIZE); + int x, y; + if (centerOneTouchButtons) + { + y = insets.top; + x = (getWidth() - size) / 2; + } + else + { + x = insets.left; + y = 0; + } + + leftButton.setBounds(x, y + ONE_TOUCH_OFFSET, size, + size * 2); + rightButton.setBounds(x, y + ONE_TOUCH_OFFSET + + ONE_TOUCH_SIZE * 2, size, size * 2); + } + else + { + int size = getHeight() - insets.top - insets.bottom; + size = Math.max(size, 0); + size = Math.min(size, ONE_TOUCH_SIZE); + int x, y; + if (centerOneTouchButtons) + { + x = insets.left; + y = (getHeight() - size) / 2; + } + else + { + x = 0; + y = insets.top; + } + leftButton.setBounds(x + ONE_TOUCH_OFFSET, y, size * 2, + size); + rightButton.setBounds(x + ONE_TOUCH_OFFSET + + ONE_TOUCH_SIZE * 2, y, size * 2, + size); + } + } + else + { + // The JDK sets this bounds for disabled one touch buttons, so + // do we. + leftButton.setBounds(-5, -5, 1, 1); + rightButton.setBounds(-5, -5, 1, 1); + } } } @@ -838,50 +1073,5 @@ public class BasicSplitPaneDivider extends Container // Do nothing. } - /** - * This method changes the button orientation when the orientation of the - * SplitPane changes. - */ - private void changeButtonOrientation() - { - if (orientation == JSplitPane.HORIZONTAL_SPLIT) - { - ((BasicArrowButton) rightButton).setDirection(SwingConstants.EAST); - ((BasicArrowButton) leftButton).setDirection(SwingConstants.WEST); - } - else - { - ((BasicArrowButton) rightButton).setDirection(SwingConstants.SOUTH); - ((BasicArrowButton) leftButton).setDirection(SwingConstants.NORTH); - } - } - - /** - * This method sizes and positions the buttons. - */ - private void positionButtons() - { - int w = 0; - int h = 0; - if (orientation == JSplitPane.HORIZONTAL_SPLIT) - { - rightButton.setLocation(ONE_TOUCH_OFFSET, ONE_TOUCH_OFFSET); - leftButton.setLocation(ONE_TOUCH_OFFSET, - ONE_TOUCH_OFFSET + 2 * ONE_TOUCH_SIZE); - w = dividerSize - 2 * ONE_TOUCH_OFFSET; - h = 2 * ONE_TOUCH_SIZE; - } - else - { - leftButton.setLocation(ONE_TOUCH_OFFSET, ONE_TOUCH_OFFSET); - rightButton.setLocation(ONE_TOUCH_OFFSET + 2 * ONE_TOUCH_SIZE, - ONE_TOUCH_OFFSET); - h = dividerSize - 2 * ONE_TOUCH_OFFSET; - w = 2 * ONE_TOUCH_SIZE; - } - Dimension dims = new Dimension(w, h); - leftButton.setSize(dims); - rightButton.setSize(dims); - } } } diff --git a/javax/swing/plaf/basic/BasicSplitPaneUI.java b/javax/swing/plaf/basic/BasicSplitPaneUI.java index 2d5955974..6ef4c08ce 100644 --- a/javax/swing/plaf/basic/BasicSplitPaneUI.java +++ b/javax/swing/plaf/basic/BasicSplitPaneUI.java @@ -63,6 +63,7 @@ import javax.swing.JSlider; import javax.swing.JSplitPane; import javax.swing.KeyStroke; import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ActionMapUIResource; @@ -107,13 +108,34 @@ public class BasicSplitPaneUI extends SplitPaneUI protected int[] sizes = new int[3]; /** + * This is used to determine if we are vertical or horizontal layout. + * In the JDK, the BasicVerticalLayoutManager seems to have no more + * methods implemented (as of JDK5), so we keep this state here. + */ + private int axis; + + /** * Creates a new instance. This is package private because the reference * implementation has no public constructor either. Still, we need to * call it from BasicVerticalLayoutManager. */ BasicHorizontalLayoutManager() { - // Nothing to do here. + this(SwingConstants.HORIZONTAL); + } + + /** + * Creates a new instance for a specified axis. This is provided for + * compatibility, since the BasicVerticalLayoutManager seems to have + * no more implementation in the RI, according to the specs. So + * we handle all the axis specific stuff here. + * + * @param a the axis, either SwingConstants#HORIZONTAL, + * or SwingConstants#VERTICAL + */ + BasicHorizontalLayoutManager(int a) + { + axis = a; } /** @@ -167,7 +189,12 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getAvailableSize(Dimension containerSize, Insets insets) { - return containerSize.width - insets.left - insets.right; + int size; + if (axis == SwingConstants.HORIZONTAL) + size = containerSize.width - insets.left - insets.right; + else + size = containerSize.height - insets.top - insets.bottom; + return size; } /** @@ -180,9 +207,15 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getInitialLocation(Insets insets) { + int loc = 0; if (insets != null) - return insets.left; - return 0; + { + if (axis == SwingConstants.HORIZONTAL) + loc = insets.left; + else + loc = insets.top; + } + return loc; } /** @@ -195,7 +228,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public float getLayoutAlignmentX(Container target) { - return target.getAlignmentX(); + return 0.0f; } /** @@ -208,7 +241,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public float getLayoutAlignmentY(Container target) { - return target.getAlignmentY(); + return 0.0f; } /** @@ -220,10 +253,19 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getPreferredSizeOfComponent(Component c) { + int size = 0; Dimension dims = c.getPreferredSize(); - if (dims != null) - return dims.width; - return 0; + if (axis == SwingConstants.HORIZONTAL) + { + if (dims != null) + size = dims.width; + } + else + { + if (dims != null) + size = dims.height; + } + return size; } /** @@ -235,7 +277,12 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getSizeOfComponent(Component c) { - return c.getWidth(); + int size; + if (axis == SwingConstants.HORIZONTAL) + size = c.getHeight(); + else + size = c.getWidth(); + return size; } /** @@ -313,27 +360,31 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public Dimension minimumLayoutSize(Container target) { + Dimension dim = new Dimension(); if (target instanceof JSplitPane) { JSplitPane split = (JSplitPane) target; - Insets insets = target.getInsets(); - - int height = 0; - int width = 0; + int primary = 0; + int secondary = 0; for (int i = 0; i < components.length; i++) { - if (components[i] == null) - continue; - Dimension dims = components[i].getMinimumSize(); - if (dims != null) + if (components[i] != null) { - width += dims.width; - height = Math.max(height, dims.height); + Dimension dims = components[i].getMinimumSize(); + primary += axis == SwingConstants.HORIZONTAL ? dims.width + : dims.height; + int sec = axis == SwingConstants.HORIZONTAL ? dims.height + : dims.width; + secondary = Math.max(sec, secondary); } } - return new Dimension(width, height); + int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; + int height = axis == SwingConstants.VERTICAL ? secondary : primary; + + Insets i = splitPane.getInsets(); + dim.setSize(width + i.left + i.right, height + i.top + i.bottom); } - return null; + return dim; } /** @@ -347,28 +398,31 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public Dimension preferredLayoutSize(Container target) { + Dimension dim = new Dimension(); if (target instanceof JSplitPane) { JSplitPane split = (JSplitPane) target; - Insets insets = target.getInsets(); - - int height = 0; - int width = 0; + int primary = 0; + int secondary = 0; for (int i = 0; i < components.length; i++) { - if (components[i] == null) - continue; - Dimension dims = components[i].getPreferredSize(); - if (dims != null) + if (components[i] != null) { - width += dims.width; - if (!(components[i] instanceof BasicSplitPaneDivider)) - height = Math.max(height, dims.height); + Dimension dims = components[i].getPreferredSize(); + primary += axis == SwingConstants.HORIZONTAL ? dims.width + : dims.height; + int sec = axis == SwingConstants.HORIZONTAL ? dims.height + : dims.width; + secondary = Math.max(sec, secondary); } } - return new Dimension(width, height); + int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; + int height = axis == SwingConstants.VERTICAL ? secondary : primary; + + Insets i = splitPane.getInsets(); + dim.setSize(width + i.left + i.right, height + i.top + i.bottom); } - return null; + return dim; } /** @@ -425,11 +479,23 @@ public class BasicSplitPaneUI extends SplitPaneUI protected void setComponentToSize(Component c, int size, int location, Insets insets, Dimension containerSize) { - int w = size; - int h = containerSize.height - insets.top - insets.bottom; - int x = location; - int y = insets.top; - c.setBounds(x, y, w, h); + if (insets != null) + { + if (axis == SwingConstants.HORIZONTAL) + c.setBounds(location, insets.top, size, + containerSize.height - insets.top - insets.bottom); + else + c.setBounds(insets.left, location, + containerSize.width - insets.left - insets.right, + size); + } + else + { + if (axis == SwingConstants.HORIZONTAL) + c.setBounds(location, 0, size, containerSize.height); + else + c.setBounds(0, location, containerSize.width, size); + } } /** @@ -462,7 +528,6 @@ public class BasicSplitPaneUI extends SplitPaneUI resetSizeAt(1); } components[2] = divider; - resetSizeAt(2); } /** @@ -485,10 +550,13 @@ public class BasicSplitPaneUI extends SplitPaneUI int minimumSizeOfComponent(int index) { Dimension dims = components[index].getMinimumSize(); + int size = 0; if (dims != null) - return dims.width; - else - return 0; + if (axis == SwingConstants.HORIZONTAL) + size = dims.width; + else + size = dims.height; + return size; } } //end BasicHorizontalLayoutManager @@ -504,163 +572,11 @@ public class BasicSplitPaneUI extends SplitPaneUI extends BasicHorizontalLayoutManager { /** - * This method returns the height of the container minus the top and - * bottom inset. - * - * @param containerSize The size of the container. - * @param insets The insets of the container. - * - * @return The height minus top and bottom inset. - */ - protected int getAvailableSize(Dimension containerSize, Insets insets) - { - return containerSize.height - insets.top - insets.bottom; - } - - /** - * This method returns the top inset. - * - * @param insets The Insets to use. - * - * @return The top inset. - */ - protected int getInitialLocation(Insets insets) - { - return insets.top; - } - - /** - * This method returns the preferred height of the component. - * - * @param c The component to measure. - * - * @return The preferred height of the component. + * Creates a new instance. */ - protected int getPreferredSizeOfComponent(Component c) + public BasicVerticalLayoutManager() { - Dimension dims = c.getPreferredSize(); - if (dims != null) - return dims.height; - return 0; - } - - /** - * This method returns the current height of the component. - * - * @param c The component to measure. - * - * @return The current height of the component. - */ - protected int getSizeOfComponent(Component c) - { - return c.getHeight(); - } - - /** - * This method returns the minimum layout size. The minimum height is the - * sum of all the components' minimum heights. The minimum width is the - * maximum of all the components' minimum widths. - * - * @param container The container to measure. - * - * @return The minimum size. - */ - public Dimension minimumLayoutSize(Container container) - { - if (container instanceof JSplitPane) - { - JSplitPane split = (JSplitPane) container; - Insets insets = container.getInsets(); - - int height = 0; - int width = 0; - for (int i = 0; i < components.length; i++) - { - if (components[i] == null) - continue; - Dimension dims = components[i].getMinimumSize(); - if (dims != null) - { - height += dims.height; - width = Math.max(width, dims.width); - } - } - return new Dimension(width, height); - } - return null; - } - - /** - * This method returns the preferred layout size. The preferred height is - * the sum of all the components' preferred heights. The preferred width - * is the maximum of all the components' preferred widths. - * - * @param container The container to measure. - * - * @return The preferred size. - */ - public Dimension preferredLayoutSize(Container container) - { - if (container instanceof JSplitPane) - { - JSplitPane split = (JSplitPane) container; - Insets insets = container.getInsets(); - - int height = 0; - int width = 0; - for (int i = 0; i < components.length; i++) - { - if (components[i] == null) - continue; - Dimension dims = components[i].getPreferredSize(); - if (dims != null) - { - height += dims.height; - width = Math.max(width, dims.width); - } - } - return new Dimension(width, height); - } - return null; - } - - /** - * This method sets the bounds of the given component. The y coordinate is - * the location given. The x coordinate is the left inset. The height is - * the size given. The width is the container size minus the left and - * right inset. - * - * @param c The component to set bounds for. - * @param size The height. - * @param location The y coordinate. - * @param insets The insets to use. - * @param containerSize The container's size. - */ - protected void setComponentToSize(Component c, int size, int location, - Insets insets, Dimension containerSize) - { - int y = location; - int x = insets.left; - int h = size; - int w = containerSize.width - insets.left - insets.right; - c.setBounds(x, y, w, h); - } - - /** - * This method returns the minimum height of the component at the given - * index. - * - * @param index The index of the component to check. - * - * @return The minimum height of the given component. - */ - int minimumSizeOfComponent(int index) - { - Dimension dims = components[index].getMinimumSize(); - if (dims != null) - return dims.height; - else - return 0; + super(SwingConstants.VERTICAL); } } @@ -1007,8 +923,10 @@ public class BasicSplitPaneUI extends SplitPaneUI nonContinuousLayoutDivider = createDefaultNonContinuousLayoutDivider(); splitPane.add(divider, JSplitPane.DIVIDER); - // There is no need to add the nonContinuousLayoutDivider - splitPane.setDividerSize(UIManager.getInt("SplitPane.dividerSize")); + // There is no need to add the nonContinuousLayoutDivider. + dividerSize = UIManager.getInt("SplitPane.dividerSize"); + splitPane.setDividerSize(dividerSize); + divider.setDividerSize(dividerSize); splitPane.setOpaque(true); } diff --git a/javax/swing/plaf/basic/BasicTabbedPaneUI.java b/javax/swing/plaf/basic/BasicTabbedPaneUI.java index 11f25167d..21dcf0d29 100644 --- a/javax/swing/plaf/basic/BasicTabbedPaneUI.java +++ b/javax/swing/plaf/basic/BasicTabbedPaneUI.java @@ -76,7 +76,6 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; -import javax.swing.plaf.PanelUI; import javax.swing.plaf.TabbedPaneUI; import javax.swing.plaf.UIResource; import javax.swing.text.View; @@ -252,7 +251,16 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { public void mouseReleased(MouseEvent e) { - // Nothing to do here. + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } } /** @@ -264,6 +272,16 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants public void mousePressed(MouseEvent e) { Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + int placement = tabPane.getTabPlacement(); if (s == incrButton) @@ -298,47 +316,61 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if(!decrButton.isEnabled()) return; - // The scroll location may be zero but the offset - // greater than zero because of an adjustement to - // make a partially visible tab completely visible. - if (currentScrollLocation > 0) - currentScrollLocation--; + // The scroll location may be zero but the offset + // greater than zero because of an adjustement to + // make a partially visible tab completely visible. + if (currentScrollLocation > 0) + currentScrollLocation--; - // Set the offset back to 0 and recompute it. - currentScrollOffset = 0; - - switch (placement) - { - case JTabbedPane.TOP: - case JTabbedPane.BOTTOM: - // Take the tab area inset into account. - if (currentScrollLocation > 0) - currentScrollOffset = getTabAreaInsets(placement).left; - // Recompute scroll offset. - for (int i = 0; i < currentScrollLocation; i++) - currentScrollOffset += rects[i].width; - break; - default: - // Take the tab area inset into account. - if (currentScrollLocation > 0) - currentScrollOffset = getTabAreaInsets(placement).top; + // Set the offset back to 0 and recompute it. + currentScrollOffset = 0; + + switch (placement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + // Take the tab area inset into account. + if (currentScrollLocation > 0) + currentScrollOffset = getTabAreaInsets(placement).left; + // Recompute scroll offset. + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].width; + break; + default: + // Take the tab area inset into account. + if (currentScrollLocation > 0) + currentScrollOffset = getTabAreaInsets(placement).top; + + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].height; + } - for (int i = 0; i < currentScrollLocation; i++) - currentScrollOffset += rects[i].height; - } + updateViewPosition(); + updateButtons(); - updateViewPosition(); - updateButtons(); - - tabPane.repaint(); - } else if (tabPane.isEnabled()) + tabPane.repaint(); + } + else if (tabPane.isEnabled()) { int index = tabForCoordinate(tabPane, e.getX(), e.getY()); + if (!tabPane.isEnabledAt(index)) + return; + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT && s == panel) + { scrollTab(index, placement); + + tabPane.setSelectedIndex(index); + tabPane.repaint(); + } + else + { + tabPane.setSelectedIndex(index); + tabPane.revalidate(); + tabPane.repaint(); + } - tabPane.setSelectedIndex(index); } } @@ -347,11 +379,22 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * Receives notification when the mouse pointer has entered the tabbed * pane. * - * @param ev the mouse event + * @param e the mouse event */ - public void mouseEntered(MouseEvent ev) + public void mouseEntered(MouseEvent e) { - int tabIndex = tabForCoordinate(tabPane, ev.getX(), ev.getY()); + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + + int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); setRolloverTab(tabIndex); } @@ -359,10 +402,21 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * Receives notification when the mouse pointer has exited the tabbed * pane. * - * @param ev the mouse event + * @param e the mouse event */ - public void mouseExited(MouseEvent ev) + public void mouseExited(MouseEvent e) { + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + setRolloverTab(-1); } @@ -374,9 +428,37 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void mouseMoved(MouseEvent ev) { + Object s = ev.getSource(); + + if (tabPane != ev.getSource()) + { + ev.setSource(tabPane); + tabPane.dispatchEvent(ev); + + ev.setSource(s); + } + int tabIndex = tabForCoordinate(tabPane, ev.getX(), ev.getY()); setRolloverTab(tabIndex); } + + /** Modifies the mouse event to originate from + * the tabbed pane and redispatches it. + * + * @param me + */ + void redispatchEvent(MouseEvent me) + { + me.setSource(tabPane); + Point viewPos = viewport.getViewPosition(); + viewPos.x -= viewport.getX(); + viewPos.y -= viewport.getY(); + me.translatePoint(-viewPos.x, -viewPos.y); + tabPane.dispatchEvent(me); + + me.translatePoint(viewPos.x, viewPos.y); + } + } /** @@ -396,20 +478,56 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void propertyChange(PropertyChangeEvent e) { - if (e.getPropertyName().equals("tabLayoutPolicy")) - { - currentScrollLocation = currentScrollOffset = 0; - - layoutManager = createLayoutManager(); - - tabPane.setLayout(layoutManager); - } - else if (e.getPropertyName().equals("tabPlacement") - && tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + out: { - incrButton = createIncreaseButton(); - decrButton = createDecreaseButton(); + if (e.getPropertyName().equals("tabLayoutPolicy")) + { + currentScrollLocation = currentScrollOffset = 0; + + layoutManager = createLayoutManager(); + + tabPane.setLayout(layoutManager); + } + else if (e.getPropertyName().equals("tabPlacement") + && tabPane.getTabLayoutPolicy() + == JTabbedPane.SCROLL_TAB_LAYOUT) + { + incrButton = createIncreaseButton(); + decrButton = createDecreaseButton(); + + // If the tab placement value was changed of a tabbed pane + // in SCROLL_TAB_LAYOUT mode we investigate the change to + // implement the following behavior which was observed in + // the RI: + // The scrolling offset will be reset if we change to + // a direction which is orthogonal to the current + // direction and stays the same if it is parallel. + + int oldPlacement = ((Integer) e.getOldValue()).intValue(); + int newPlacement = ((Integer) e.getNewValue()).intValue(); + switch (newPlacement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + if (oldPlacement == JTabbedPane.TOP + || oldPlacement == JTabbedPane.BOTTOM) + break out; + + currentScrollOffset = getTabAreaInsets(newPlacement).left; + break; + default: + if (oldPlacement == JTabbedPane.LEFT + || oldPlacement == JTabbedPane.RIGHT) + break out; + + currentScrollOffset = getTabAreaInsets(newPlacement).top; + } + + updateViewPosition(); + updateButtons(); + } } + tabPane.revalidate(); tabPane.repaint(); } @@ -784,6 +902,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants default: tabAreaHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); + compX = insets.left + contentBorderInsets.left; compY = tabAreaHeight + insets.top + contentBorderInsets.top; } @@ -838,7 +957,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected void normalizeTabRuns(int tabPlacement, int tabCount, int start, int max) { - Insets tabAreaInsets = getTabAreaInsets(tabPlacement); if (tabPlacement == SwingUtilities.TOP || tabPlacement == SwingUtilities.BOTTOM) { @@ -1325,7 +1443,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { super.layoutContainer(pane); int tabCount = tabPane.getTabCount(); - Point p = null; if (tabCount == 0) return; int tabPlacement = tabPane.getTabPlacement(); @@ -1512,7 +1629,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void updateUI() { - setUI((PanelUI) new ScrollingPanelUI()); + setUI(new ScrollingPanelUI()); } } @@ -1892,15 +2009,19 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants final void updateViewPosition() { Point p = viewport.getViewPosition(); - + + // The unneeded coordinate must be set to zero + // in order to correctly handle placement changes. switch (tabPane.getTabPlacement()) { case JTabbedPane.LEFT: case JTabbedPane.RIGHT: + p.x = 0; p.y = currentScrollOffset; break; default: p.x = currentScrollOffset; + p.y = 0; } viewport.setViewPosition(p); @@ -2331,7 +2452,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants paintTabBorder(g, tabPlacement, tabIndex, rect.x, rect.y, rect.width, rect.height, isSelected); - // Layout label. FontMetrics fm = getFontMetrics(); Icon icon = getIconForTab(tabIndex); @@ -2369,7 +2489,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected) { - SwingUtilities.layoutCompoundLabel(metrics, title, icon, + // Reset the icon and text rectangles, as the result is not specified + // when the locations are not (0,0). + textRect.x = 0; + textRect.y = 0; + textRect.width = 0; + textRect.height = 0; + iconRect.x = 0; + iconRect.y = 0; + iconRect.width = 0; + iconRect.height = 0; + SwingUtilities.layoutCompoundLabel(tabPane, metrics, title, icon, SwingConstants.CENTER, SwingConstants.CENTER, SwingConstants.CENTER, @@ -2764,7 +2894,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int width = tabPane.getWidth(); int height = tabPane.getHeight(); Insets insets = tabPane.getInsets(); - Insets tabAreaInsets = getTabAreaInsets(tabPlacement); // Calculate coordinates of content area. int x = insets.left; @@ -2869,8 +2998,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int endgap = rects[selectedIndex].y + rects[selectedIndex].height - currentScrollOffset; - int diff = 0; - if (tabPlacement == SwingConstants.LEFT && startgap >= 0) { g.drawLine(x, y, x, startgap); @@ -2957,8 +3084,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int endgap = rects[selectedIndex].y + rects[selectedIndex].height - currentScrollOffset; - int diff = 0; - if (tabPlacement == SwingConstants.RIGHT && startgap >= 0) { g.setColor(shadow); @@ -2988,8 +3113,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants } /** - * This method returns the tab bounds for the given index. - * + * <p>This method returns the bounds of a tab for the given index + * and shifts it by the current scrolling offset if the tabbed + * pane is in scrolling tab layout mode.</p> + * + * <p>Subclassses should retrievs a tab's bounds by this method + * if they want to find out whether the tab is currently visible.</p> + * * @param pane The JTabbedPane. * @param i The index to look for. * @@ -3000,6 +3130,26 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // Need to re-layout container if tab does not exist. if (i >= rects.length) layoutManager.layoutContainer(pane); + + // Properly shift coordinates if scrolling has taken + // place. + if (pane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + { + Rectangle r = new Rectangle(rects[i]); + + switch(pane.getTabPlacement()) + { + case SwingConstants.TOP: + case SwingConstants.BOTTOM: + r.x -= currentScrollOffset; + break; + default: + r.y -= currentScrollOffset; + } + + return r; + } + return rects[i]; } @@ -3048,7 +3198,10 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants } /** - * This method returns the tab bounds in the given rectangle. + * <p>This method returns the tab bounds in the given rectangle.</p> + * + * <p>The returned rectangle will be shifted by the current scroll + * offset if the tabbed pane is in scrolling tab layout mode.</p>. * * @param tabIndex The index to get bounds for. * @param dest The rectangle to store bounds in. @@ -3324,21 +3477,20 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Icon icon = getIconForTab(tabIndex); Insets insets = getTabInsets(tabPlacement, tabIndex); - int width = 0; + int width = insets.bottom + insets.right + 3; if (icon != null) { - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - layoutLabel(tabPlacement, getFontMetrics(), tabIndex, - tabPane.getTitleAt(tabIndex), icon, vr, ir, tr, - tabIndex == tabPane.getSelectedIndex()); - width = tr.union(ir).width; + width += icon.getIconWidth() + textIconGap; } - else - width = metrics.stringWidth(tabPane.getTitleAt(tabIndex)); - width += insets.left + insets.right; + View v = getTextViewForTab(tabIndex); + if (v != null) + width += v.getPreferredSpan(View.X_AXIS); + else + { + String label = tabPane.getTitleAt(tabIndex); + width += metrics.stringWidth(label); + } return width; } @@ -3377,7 +3529,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Insets insets = getTabAreaInsets(tabPlacement); int tabAreaHeight = horizRunCount * maxTabHeight - - (horizRunCount - 1) * tabRunOverlay; + - (horizRunCount - 1) + * getTabRunOverlay(tabPlacement); tabAreaHeight += insets.top + insets.bottom; @@ -3399,7 +3552,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Insets insets = getTabAreaInsets(tabPlacement); int tabAreaWidth = vertRunCount * maxTabWidth - - (vertRunCount - 1) * tabRunOverlay; + - (vertRunCount - 1) + * getTabRunOverlay(tabPlacement); tabAreaWidth += insets.left + insets.right; diff --git a/javax/swing/plaf/basic/BasicTableHeaderUI.java b/javax/swing/plaf/basic/BasicTableHeaderUI.java index abe7cab43..8a8eeb837 100644 --- a/javax/swing/plaf/basic/BasicTableHeaderUI.java +++ b/javax/swing/plaf/basic/BasicTableHeaderUI.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; @@ -415,9 +413,8 @@ public class BasicTableHeaderUI extends TableHeaderUI } protected void installKeyboardActions() - throws NotImplementedException { - // TODO: Implement this properly. + // AFAICS, the RI does nothing here. } /** @@ -448,9 +445,8 @@ public class BasicTableHeaderUI extends TableHeaderUI } protected void uninstallKeyboardActions() - throws NotImplementedException { - // TODO: Implement this properly. + // AFAICS, the RI does nothing here. } /** diff --git a/javax/swing/plaf/basic/BasicTextUI.java b/javax/swing/plaf/basic/BasicTextUI.java index 8e9c8c949..dc30347f5 100644 --- a/javax/swing/plaf/basic/BasicTextUI.java +++ b/javax/swing/plaf/basic/BasicTextUI.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.plaf.basic; +import gnu.classpath.SystemProperties; + import java.awt.Color; import java.awt.Container; import java.awt.Dimension; @@ -71,6 +73,7 @@ import javax.swing.plaf.InputMapUIResource; import javax.swing.plaf.TextUI; import javax.swing.plaf.UIResource; import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import javax.swing.text.DefaultCaret; @@ -120,6 +123,140 @@ public abstract class BasicTextUI extends TextUI } } + private static class FocusHandler + implements FocusListener + { + public void focusGained(FocusEvent e) + { + // Nothing to do here. + } + public void focusLost(FocusEvent e) + { + JTextComponent textComponent = (JTextComponent) e.getComponent(); + // Integrates Swing text components with the system clipboard: + // The idea is that if one wants to copy text around X11-style + // (select text and middle-click in the target component) the focus + // will move to the new component which gives the old focus owner the + // possibility to paste its selection into the clipboard. + if (!e.isTemporary() + && textComponent.getSelectionStart() + != textComponent.getSelectionEnd()) + { + SecurityManager sm = System.getSecurityManager(); + try + { + if (sm != null) + sm.checkSystemClipboardAccess(); + + Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection(); + if (cb != null) + { + StringSelection selection = new StringSelection( + textComponent.getSelectedText()); + cb.setContents(selection, selection); + } + } + catch (SecurityException se) + { + // Not allowed to access the clipboard: Ignore and + // do not access it. + } + catch (HeadlessException he) + { + // There is no AWT: Ignore and do not access the + // clipboard. + } + catch (IllegalStateException ise) + { + // Clipboard is currently unavaible. + } + } + } + } + + /** + * This FocusListener triggers repaints on focus shift. + */ + private static FocusListener focusListener; + + /** + * Receives notifications when properties of the text component change. + */ + private class Handler + implements PropertyChangeListener, DocumentListener + { + /** + * Notifies when a property of the text component changes. + * + * @param event the PropertyChangeEvent describing the change + */ + public void propertyChange(PropertyChangeEvent event) + { + if (event.getPropertyName().equals("document")) + { + // Document changed. + Object oldValue = event.getOldValue(); + if (oldValue != null) + { + Document oldDoc = (Document) oldValue; + oldDoc.removeDocumentListener(handler); + } + Object newValue = event.getNewValue(); + if (newValue != null) + { + Document newDoc = (Document) newValue; + newDoc.addDocumentListener(handler); + } + modelChanged(); + } + + BasicTextUI.this.propertyChange(event); + } + + /** + * Notification about a document change event. + * + * @param ev the DocumentEvent describing the change + */ + public void changedUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.changedUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + /** + * Notification about a document insert event. + * + * @param ev the DocumentEvent describing the insertion + */ + public void insertUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.insertUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + /** + * Notification about a document removal event. + * + * @param ev the DocumentEvent describing the removal + */ + public void removeUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.removeUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + } + /** * This view forms the root of the View hierarchy. However, it delegates * most calls to another View which is the real root of the hierarchy. @@ -225,20 +362,10 @@ public abstract class BasicTextUI extends TextUI return textComponent; } - /** - * Returns the preferred span along the specified <code>axis</code>. - * This is delegated to the real root view. - * - * @param axis the axis for which the preferred span is queried - * - * @return the preferred span along the axis - */ - public float getPreferredSpan(int axis) + public void setSize(float w, float h) { if (view != null) - return view.getPreferredSpan(axis); - - return Integer.MAX_VALUE; + view.setSize(w, h); } /** @@ -312,7 +439,8 @@ public abstract class BasicTextUI extends TextUI */ public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - view.insertUpdate(ev, shape, vf); + if (view != null) + view.insertUpdate(ev, shape, vf); } /** @@ -325,7 +453,8 @@ public abstract class BasicTextUI extends TextUI */ public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - view.removeUpdate(ev, shape, vf); + if (view != null) + view.removeUpdate(ev, shape, vf); } /** @@ -338,7 +467,8 @@ public abstract class BasicTextUI extends TextUI */ public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - view.changedUpdate(ev, shape, vf); + if (view != null) + view.changedUpdate(ev, shape, vf); } /** @@ -400,116 +530,73 @@ public abstract class BasicTextUI extends TextUI { return textComponent.getDocument(); } - } - /** - * Receives notifications when properties of the text component change. - */ - private class PropertyChangeHandler implements PropertyChangeListener - { /** - * Notifies when a property of the text component changes. - * - * @param event the PropertyChangeEvent describing the change + * Returns the attributes, which is null for the RootView. */ - public void propertyChange(PropertyChangeEvent event) + public AttributeSet getAttributes() { - if (event.getPropertyName().equals("document")) - { - // Document changed. - Object oldValue = event.getOldValue(); - if (oldValue != null) - { - Document oldDoc = (Document) oldValue; - oldDoc.removeDocumentListener(documentHandler); - } - Object newValue = event.getNewValue(); - if (newValue != null) - { - Document newDoc = (Document) newValue; - newDoc.addDocumentListener(documentHandler); - } - modelChanged(); - } - - BasicTextUI.this.propertyChange(event); + return null; } - } - /** - * Listens for changes on the underlying model and forwards notifications - * to the View. This also updates the caret position of the text component. - * - * TODO: Maybe this should somehow be handled through EditorKits - */ - class DocumentHandler implements DocumentListener - { /** - * Notification about a document change event. - * - * @param ev the DocumentEvent describing the change + * Overridden to forward to the view. */ - public void changedUpdate(DocumentEvent ev) + public float getPreferredSpan(int axis) { - // Updates are forwarded to the View even if 'getVisibleEditorRect' - // method returns null. This means the View classes have to be - // aware of that possibility. - rootView.changedUpdate(ev, getVisibleEditorRect(), - rootView.getViewFactory()); + // The RI returns 10 in the degenerate case. + float span = 10; + if (view != null) + span = view.getPreferredSpan(axis); + return span; } /** - * Notification about a document insert event. - * - * @param ev the DocumentEvent describing the insertion + * Overridden to forward to the real view. */ - public void insertUpdate(DocumentEvent ev) + public float getMinimumSpan(int axis) { - // Updates are forwarded to the View even if 'getVisibleEditorRect' - // method returns null. This means the View classes have to be - // aware of that possibility. - rootView.insertUpdate(ev, getVisibleEditorRect(), - rootView.getViewFactory()); + // The RI returns 10 in the degenerate case. + float span = 10; + if (view != null) + span = view.getMinimumSpan(axis); + return span; } /** - * Notification about a document removal event. - * - * @param ev the DocumentEvent describing the removal + * Overridden to return Integer.MAX_VALUE. */ - public void removeUpdate(DocumentEvent ev) + public float getMaximumSpan(int axis) { - // Updates are forwarded to the View even if 'getVisibleEditorRect' - // method returns null. This means the View classes have to be - // aware of that possibility. - rootView.removeUpdate(ev, getVisibleEditorRect(), - rootView.getViewFactory()); + // The RI returns Integer.MAX_VALUE here, regardless of the real view's + // maximum size. + return Integer.MAX_VALUE; } } /** * The EditorKit used by this TextUI. */ - // FIXME: should probably be non-static. - static EditorKit kit = new DefaultEditorKit(); + private static EditorKit kit; /** - * The root view. + * The combined event handler for text components. + * + * This is package private to avoid accessor methods. */ - RootView rootView = new RootView(); + Handler handler; /** - * The text component that we handle. + * The root view. + * + * This is package private to avoid accessor methods. */ - JTextComponent textComponent; + RootView rootView; /** - * Receives notification when the model changes. + * The text component that we handle. */ - private PropertyChangeHandler updateHandler = new PropertyChangeHandler(); - - /** The DocumentEvent handler. */ - DocumentHandler documentHandler = new DocumentHandler(); + JTextComponent textComponent; /** * Creates a new <code>BasicTextUI</code> instance. @@ -558,17 +645,31 @@ public abstract class BasicTextUI extends TextUI public void installUI(final JComponent c) { textComponent = (JTextComponent) c; + + if (rootView == null) + rootView = new RootView(); + installDefaults(); - textComponent.addPropertyChangeListener(updateHandler); + installFixedDefaults(); + + // These listeners must be installed outside of installListeners(), + // because overriding installListeners() doesn't prevent installing + // these in the RI, but overriding isntallUI() does. + if (handler == null) + handler = new Handler(); + textComponent.addPropertyChangeListener(handler); Document doc = textComponent.getDocument(); if (doc == null) { + // The Handler takes care of installing the necessary listeners + // on the document here. doc = getEditorKit(textComponent).createDefaultDocument(); textComponent.setDocument(doc); } else { - doc.addDocumentListener(documentHandler); + // Must install the document listener. + doc.addDocumentListener(handler); modelChanged(); } @@ -586,7 +687,6 @@ public abstract class BasicTextUI extends TextUI LookAndFeel.installColorsAndFont(textComponent, prefix + ".background", prefix + ".foreground", prefix + ".font"); LookAndFeel.installBorder(textComponent, prefix + ".border"); - textComponent.setMargin(UIManager.getInsets(prefix + ".margin")); // Some additional text component only properties. Color color = textComponent.getCaretColor(); @@ -600,7 +700,7 @@ public abstract class BasicTextUI extends TextUI color = textComponent.getDisabledTextColor(); if (color == null || color instanceof UIResource) { - color = UIManager.getColor(prefix + ".inactiveBackground"); + color = UIManager.getColor(prefix + ".inactiveForeground"); textComponent.setDisabledTextColor(color); } color = textComponent.getSelectedTextColor(); @@ -623,6 +723,15 @@ public abstract class BasicTextUI extends TextUI textComponent.setMargin(margin); } + } + + /** + * Installs defaults that can't be overridden by overriding + * installDefaults(). + */ + private void installFixedDefaults() + { + String prefix = getPropertyPrefix(); Caret caret = textComponent.getCaret(); if (caret == null || caret instanceof UIResource) { @@ -638,64 +747,18 @@ public abstract class BasicTextUI extends TextUI } /** - * This FocusListener triggers repaints on focus shift. - */ - private FocusListener focuslistener = new FocusListener() { - public void focusGained(FocusEvent e) - { - textComponent.repaint(); - } - public void focusLost(FocusEvent e) - { - textComponent.repaint(); - - // Integrates Swing text components with the system clipboard: - // The idea is that if one wants to copy text around X11-style - // (select text and middle-click in the target component) the focus - // will move to the new component which gives the old focus owner the - // possibility to paste its selection into the clipboard. - if (!e.isTemporary() - && textComponent.getSelectionStart() - != textComponent.getSelectionEnd()) - { - SecurityManager sm = System.getSecurityManager(); - try - { - if (sm != null) - sm.checkSystemClipboardAccess(); - - Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection(); - if (cb != null) - { - StringSelection selection = new StringSelection( - textComponent.getSelectedText()); - cb.setContents(selection, selection); - } - } - catch (SecurityException se) - { - // Not allowed to access the clipboard: Ignore and - // do not access it. - } - catch (HeadlessException he) - { - // There is no AWT: Ignore and do not access the - // clipboard. - } - catch (IllegalStateException ise) - { - // Clipboard is currently unavaible. - } - } - } - }; - - /** * Install all listeners on the text component. */ protected void installListeners() { - textComponent.addFocusListener(focuslistener); + // + if (SystemProperties.getProperty("gnu.swing.text.no-xlike-clipboard") + == null) + { + if (focusListener == null) + focusListener = new FocusHandler(); + textComponent.addFocusListener(focusListener); + } } /** @@ -834,10 +897,12 @@ public abstract class BasicTextUI extends TextUI */ public void uninstallUI(final JComponent component) { - super.uninstallUI(component); + textComponent.removePropertyChangeListener(handler); + textComponent.getDocument().removeDocumentListener(handler); rootView.setView(null); uninstallDefaults(); + uninstallFixedDefaults(); uninstallListeners(); uninstallKeyboardActions(); @@ -850,7 +915,29 @@ public abstract class BasicTextUI extends TextUI */ protected void uninstallDefaults() { - // Do nothing here. + if (textComponent.getCaretColor() instanceof UIResource) + textComponent.setCaretColor(null); + if (textComponent.getSelectionColor() instanceof UIResource) + textComponent.setSelectionColor(null); + if (textComponent.getDisabledTextColor() instanceof UIResource) + textComponent.setDisabledTextColor(null); + if (textComponent.getSelectedTextColor() instanceof UIResource) + textComponent.setSelectedTextColor(null); + LookAndFeel.uninstallBorder(textComponent); + if (textComponent.getMargin() instanceof UIResource) + textComponent.setMargin(null); + } + + /** + * Uninstalls additional fixed defaults that were installed + * by installFixedDefaults(). + */ + private void uninstallFixedDefaults() + { + if (textComponent.getCaret() instanceof UIResource) + textComponent.setCaret(null); + if (textComponent.getHighlighter() instanceof UIResource) + textComponent.setHighlighter(null); } /** @@ -859,7 +946,10 @@ public abstract class BasicTextUI extends TextUI */ protected void uninstallListeners() { - textComponent.removeFocusListener(focuslistener); + // Don't nullify the focusListener field, as it is static and shared + // between components. + if (focusListener != null) + textComponent.removeFocusListener(focusListener); } /** @@ -891,14 +981,33 @@ public abstract class BasicTextUI extends TextUI */ public Dimension getPreferredSize(JComponent c) { - View v = getRootView(textComponent); - - float w = v.getPreferredSpan(View.X_AXIS); - float h = v.getPreferredSpan(View.Y_AXIS); - + Dimension d = c.getSize(); Insets i = c.getInsets(); - return new Dimension((int) w + i.left + i.right, + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + float w; + float h; + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + if (d.width > (i.left + i.right) && d.height > (i.top + i.bottom)) + { + rootView.setSize(d.width - i.left - i.right, + d.height - i.top - i.bottom); + } + w = rootView.getPreferredSpan(View.X_AXIS); + h = rootView.getPreferredSpan(View.Y_AXIS); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + Dimension size = new Dimension((int) w + i.left + i.right, (int) h + i.top + i.bottom); + return size; } /** @@ -912,8 +1021,27 @@ public abstract class BasicTextUI extends TextUI */ public Dimension getMaximumSize(JComponent c) { - // Sun's implementation returns Integer.MAX_VALUE here, so do we. - return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + Dimension d = new Dimension(); + Insets i = c.getInsets(); + Document doc = textComponent.getDocument(); + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + // Check for overflow here. + d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) + + i.left + i.right, Integer.MAX_VALUE); + d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) + + i.top + i.bottom, Integer.MAX_VALUE); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return d; } /** @@ -924,8 +1052,26 @@ public abstract class BasicTextUI extends TextUI */ public Dimension getMinimumSize(JComponent c) { + Dimension d = new Dimension(); + Document doc = textComponent.getDocument(); + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + d.width = (int) rootView.getMinimumSpan(View.X_AXIS); + d.height = (int) rootView.getMinimumSpan(View.Y_AXIS); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } Insets i = c.getInsets(); - return new Dimension(i.left + i.right, i.top + i.bottom); + d.width += i.left + i.right; + d.height += i.top + i.bottom; + return d; } /** @@ -996,7 +1142,6 @@ public abstract class BasicTextUI extends TextUI g.setColor(oldColor); } - rootView.paint(g, getVisibleEditorRect()); if (caret != null && textComponent.hasFocus()) @@ -1104,6 +1249,8 @@ public abstract class BasicTextUI extends TextUI */ public EditorKit getEditorKit(JTextComponent t) { + if (kit == null) + kit = new DefaultEditorKit(); return kit; } @@ -1224,7 +1371,7 @@ public abstract class BasicTextUI extends TextUI */ public int viewToModel(JTextComponent t, Point pt) { - return viewToModel(t, pt, null); + return viewToModel(t, pt, new Position.Bias[1]); } /** diff --git a/javax/swing/plaf/basic/BasicToolTipUI.java b/javax/swing/plaf/basic/BasicToolTipUI.java index 5cec2e333..94e7bc322 100644 --- a/javax/swing/plaf/basic/BasicToolTipUI.java +++ b/javax/swing/plaf/basic/BasicToolTipUI.java @@ -40,19 +40,20 @@ package javax.swing.plaf.basic; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; -import java.awt.Toolkit; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JComponent; import javax.swing.JToolTip; import javax.swing.LookAndFeel; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ToolTipUI; +import javax.swing.text.View; /** * This is the Basic Look and Feel UI class for JToolTip. @@ -60,6 +61,28 @@ import javax.swing.plaf.ToolTipUI; public class BasicToolTipUI extends ToolTipUI { + /** + * Receives notification when a property of the JToolTip changes. + * This updates the HTML renderer if appropriate. + */ + private class PropertyChangeHandler + implements PropertyChangeListener + { + + public void propertyChange(PropertyChangeEvent e) + { + String prop = e.getPropertyName(); + if (prop.equals("tiptext") || prop.equals("font") + || prop.equals("foreground")) + { + JToolTip tip = (JToolTip) e.getSource(); + String text = tip.getTipText(); + BasicHTML.updateRenderer(tip, text); + } + } + + } + /** The shared instance of BasicToolTipUI used for all ToolTips. */ private static BasicToolTipUI shared; @@ -67,6 +90,11 @@ public class BasicToolTipUI extends ToolTipUI private String text; /** + * Handles property changes. + */ + private PropertyChangeListener propertyChangeHandler; + + /** * Creates a new BasicToolTipUI object. */ public BasicToolTipUI() @@ -98,7 +126,12 @@ public class BasicToolTipUI extends ToolTipUI */ public Dimension getMaximumSize(JComponent c) { - return getPreferredSize(c); + Dimension d = getPreferredSize(c); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + d.width += view.getMaximumSpan(View.X_AXIS) + - view.getPreferredSpan(View.X_AXIS); + return d; } /** @@ -110,7 +143,12 @@ public class BasicToolTipUI extends ToolTipUI */ public Dimension getMinimumSize(JComponent c) { - return getPreferredSize(c); + Dimension d = getPreferredSize(c); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + d.width -= view.getPreferredSpan(View.X_AXIS) + - view.getMinimumSpan(View.X_AXIS); + return d; } /** @@ -123,22 +161,25 @@ public class BasicToolTipUI extends ToolTipUI public Dimension getPreferredSize(JComponent c) { JToolTip tip = (JToolTip) c; - FontMetrics fm; - Toolkit g = tip.getToolkit(); - text = tip.getTipText(); - - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - Insets insets = tip.getInsets(); - fm = g.getFontMetrics(tip.getFont()); - SwingUtilities.layoutCompoundLabel(tip, fm, text, null, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, vr, ir, tr, 0); - return new Dimension(insets.left + tr.width + insets.right, - insets.top + tr.height + insets.bottom); + String str = tip.getTipText(); + FontMetrics fm = c.getFontMetrics(c.getFont()); + Insets i = c.getInsets(); + Dimension d = new Dimension(i.left + i.right, i.top + i.bottom); + if (str != null && ! str.equals("")) + { + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + { + d.width += (int) view.getPreferredSpan(View.X_AXIS); + d.height += (int) view.getPreferredSpan(View.Y_AXIS); + } + else + { + d.width += fm.stringWidth(str) + 6; + d.height += fm.getHeight(); + } + } + return d; } /** @@ -160,7 +201,8 @@ public class BasicToolTipUI extends ToolTipUI */ protected void installListeners(JComponent c) { - // TODO: Implement this properly. + propertyChangeHandler = new PropertyChangeHandler(); + c.addPropertyChangeListener(propertyChangeHandler); } /** @@ -172,6 +214,7 @@ public class BasicToolTipUI extends ToolTipUI { c.setOpaque(true); installDefaults(c); + BasicHTML.updateRenderer(c, ((JToolTip) c).getTipText()); installListeners(c); } @@ -186,26 +229,25 @@ public class BasicToolTipUI extends ToolTipUI JToolTip tip = (JToolTip) c; String text = tip.getTipText(); - Toolkit t = tip.getToolkit(); - if (text == null) - return; - - Rectangle vr = new Rectangle(); - vr = SwingUtilities.calculateInnerArea(tip, vr); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - FontMetrics fm = t.getFontMetrics(tip.getFont()); + Font font = c.getFont(); + FontMetrics fm = c.getFontMetrics(font); int ascent = fm.getAscent(); - SwingUtilities.layoutCompoundLabel(tip, fm, text, null, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, vr, ir, tr, 0); + Insets i = c.getInsets(); + Dimension size = c.getSize(); + Rectangle paintR = new Rectangle(i.left, i.top, + size.width - i.left - i.right, + size.height - i.top - i.bottom); Color saved = g.getColor(); + Font oldFont = g.getFont(); g.setColor(Color.BLACK); - g.drawString(text, vr.x, vr.y + ascent); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + view.paint(g, paintR); + else + g.drawString(text, paintR.x + 3, paintR.y + ascent); + g.setFont(oldFont); g.setColor(saved); } @@ -229,7 +271,11 @@ public class BasicToolTipUI extends ToolTipUI */ protected void uninstallListeners(JComponent c) { - // TODO: Implement this properly. + if (propertyChangeHandler != null) + { + c.removePropertyChangeListener(propertyChangeHandler); + propertyChangeHandler = null; + } } /** @@ -240,6 +286,7 @@ public class BasicToolTipUI extends ToolTipUI public void uninstallUI(JComponent c) { uninstallDefaults(c); + BasicHTML.updateRenderer(c, ""); uninstallListeners(c); } } diff --git a/javax/swing/plaf/basic/BasicTreeUI.java b/javax/swing/plaf/basic/BasicTreeUI.java index f61824bb7..ef46efdd2 100644 --- a/javax/swing/plaf/basic/BasicTreeUI.java +++ b/javax/swing/plaf/basic/BasicTreeUI.java @@ -38,17 +38,16 @@ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; import gnu.javax.swing.tree.GnuPath; import java.awt.Color; import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import java.awt.Label; +import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -275,13 +274,6 @@ public class BasicTreeUI TreeModelListener treeModelListener; /** - * This timer fires the editing action after about 1200 ms if not reset during - * that time. It handles the editing start with the single mouse click (and - * not the double mouse click) on the selected tree node. - */ - Timer startEditTimer; - - /** * The zero size icon, used for expand controls, if they are not visible. */ static Icon nullIcon; @@ -428,6 +420,7 @@ public class BasicTreeUI { if (largeModel != this.largeModel) { + completeEditing(); tree.removeComponentListener(componentListener); this.largeModel = largeModel; tree.addComponentListener(componentListener); @@ -451,6 +444,7 @@ public class BasicTreeUI */ protected void setRowHeight(int rowHeight) { + completeEditing(); if (rowHeight == 0) rowHeight = getMaxHeight(tree); treeState.setRowHeight(rowHeight); @@ -544,6 +538,7 @@ public class BasicTreeUI */ protected void setRootVisible(boolean newValue) { + completeEditing(); tree.setRootVisible(newValue); } @@ -590,8 +585,7 @@ public class BasicTreeUI */ protected void setCellEditor(TreeCellEditor editor) { - cellEditor = editor; - createdCellEditor = true; + updateCellEditor(); } /** @@ -611,7 +605,7 @@ public class BasicTreeUI */ protected void setEditable(boolean newValue) { - tree.setEditable(newValue); + updateCellEditor(); } /** @@ -632,6 +626,7 @@ public class BasicTreeUI */ protected void setSelectionModel(TreeSelectionModel newLSM) { + completeEditing(); if (newLSM != null) { treeSelectionModel = newLSM; @@ -787,12 +782,13 @@ public class BasicTreeUI */ public boolean stopEditing(JTree tree) { - if (isEditing(tree)) + boolean ret = false; + if (editingComponent != null && cellEditor.stopCellEditing()) { completeEditing(false, false, true); - finish(); + ret = true; } - return ! isEditing(tree); + return ret; } /** @@ -805,8 +801,8 @@ public class BasicTreeUI // There is no need to send the cancel message to the editor, // as the cancellation event itself arrives from it. This would // only be necessary when cancelling the editing programatically. - completeEditing(false, false, false); - finish(); + if (editingComponent != null) + completeEditing(false, true, false); } /** @@ -818,7 +814,9 @@ public class BasicTreeUI */ public void startEditingAtPath(JTree tree, TreePath path) { - startEditing(path, null); + tree.scrollPathToVisible(path); + if (path != null && tree.isVisible(path)) + startEditing(path, null); } /** @@ -842,6 +840,7 @@ public class BasicTreeUI preferredSize = new Dimension(); largeModel = tree.isLargeModel(); preferredSize = new Dimension(); + stopEditingInCompleteEditing = true; setModel(tree.getModel()); } @@ -1136,6 +1135,7 @@ public class BasicTreeUI */ protected void updateExpandedDescendants(TreePath path) { + completeEditing(); Enumeration expanded = tree.getExpandedDescendants(path); while (expanded.hasMoreElements()) treeState.setExpandedState((TreePath) expanded.nextElement(), true); @@ -1167,9 +1167,33 @@ public class BasicTreeUI */ protected void updateCellEditor() { - if (tree.isEditable() && cellEditor == null) - setCellEditor(createDefaultCellEditor()); - createdCellEditor = true; + completeEditing(); + TreeCellEditor newEd = null; + if (tree != null && tree.isEditable()) + { + newEd = tree.getCellEditor(); + if (newEd == null) + { + newEd = createDefaultCellEditor(); + if (newEd != null) + { + tree.setCellEditor(newEd); + createdCellEditor = true; + } + } + } + // Update listeners. + if (newEd != cellEditor) + { + if (cellEditor != null && cellEditorListener != null) + cellEditor.removeCellEditorListener(cellEditorListener); + cellEditor = newEd; + if (cellEditorListener == null) + cellEditorListener = createCellEditorListener(); + if (cellEditor != null && cellEditorListener != null) + cellEditor.addCellEditorListener(cellEditorListener); + createdCellEditor = false; + } } /** @@ -1719,6 +1743,10 @@ public class BasicTreeUI */ protected void completeEditing() { + if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing + && editingComponent != null) + cellEditor.stopCellEditing(); + completeEditing(false, true, false); } @@ -1736,28 +1764,35 @@ public class BasicTreeUI boolean messageTree) { // Make no attempt to complete the non existing editing session. - if (!isEditing(tree)) - return; - - if (messageStop) - { - getCellEditor().stopCellEditing(); - stopEditingInCompleteEditing = true; - } - - if (messageCancel) + if (stopEditingInCompleteEditing && editingComponent != null) { - getCellEditor().cancelCellEditing(); - stopEditingInCompleteEditing = true; - } + Component comp = editingComponent; + TreePath p = editingPath; + editingComponent = null; + editingPath = null; + if (messageStop) + cellEditor.stopCellEditing(); + else if (messageCancel) + cellEditor.cancelCellEditing(); + + tree.remove(comp); + + if (editorHasDifferentSize) + { + treeState.invalidatePathBounds(p); + updateSize(); + } + else + { + // Need to refresh the tree. + Rectangle b = getPathBounds(tree, p); + tree.repaint(0, b.y, tree.getWidth(), b.height); + } - if (messageTree) - { - TreeCellEditor editor = getCellEditor(); - if (editor != null) + if (messageTree) { - Object value = editor.getCellEditorValue(); - treeModel.valueForPathChanged(tree.getLeadSelectionPath(), value); + Object value = cellEditor.getCellEditorValue(); + treeModel.valueForPathChanged(p, value); } } } @@ -1772,47 +1807,105 @@ public class BasicTreeUI */ protected boolean startEditing(TreePath path, MouseEvent event) { - updateCellEditor(); - TreeCellEditor ed = getCellEditor(); + // Maybe cancel editing. + if (isEditing(tree) && tree.getInvokesStopCellEditing() + && ! stopEditing(tree)) + return false; - if (ed != null && (event == EDIT || ed.shouldSelectCell(event)) - && ed.isCellEditable(event)) + completeEditing(); + TreeCellEditor ed = cellEditor; + if (ed != null && tree.isPathEditable(path)) { - Rectangle bounds = getPathBounds(tree, path); - - // Extend the right boundary till the tree width. - bounds.width = tree.getWidth() - bounds.x; - - editingPath = path; - editingRow = tree.getRowForPath(editingPath); - - Object value = editingPath.getLastPathComponent(); - - stopEditingInCompleteEditing = false; - boolean expanded = tree.isExpanded(editingPath); - isEditing = true; - editingComponent = ed.getTreeCellEditorComponent(tree, value, true, - expanded, - isLeaf(editingRow), - editingRow); - - // Remove all previous components (if still present). Only one - // container with the editing component inside is allowed in the tree. - tree.removeAll(); - - // The editing component must be added to its container. We add the - // container, not the editing component itself. - Component container = editingComponent.getParent(); - container.setBounds(bounds); - tree.add(container); - editingComponent.requestFocus(); + if (ed.isCellEditable(event)) + { + editingRow = getRowForPath(tree, path); + Object value = path.getLastPathComponent(); + boolean isSelected = tree.isPathSelected(path); + boolean isExpanded = tree.isExpanded(editingPath); + boolean isLeaf = treeModel.isLeaf(value); + editingComponent = ed.getTreeCellEditorComponent(tree, value, + isSelected, + isExpanded, + isLeaf, + editingRow); + + Rectangle bounds = getPathBounds(tree, path); + + Dimension size = editingComponent.getPreferredSize(); + int rowHeight = getRowHeight(); + if (size.height != bounds.height && rowHeight > 0) + size.height = rowHeight; + + if (size.width != bounds.width || size.height != bounds.height) + { + editorHasDifferentSize = true; + treeState.invalidatePathBounds(path); + updateSize(); + } + else + editorHasDifferentSize = false; + + // The editing component must be added to its container. We add the + // container, not the editing component itself. + tree.add(editingComponent); + editingComponent.setBounds(bounds.x, bounds.y, size.width, + size.height); + editingComponent.validate(); + editingPath = path; + + if (ed.shouldSelectCell(event)) + { + stopEditingInCompleteEditing = false; + tree.setSelectionRow(editingRow); + stopEditingInCompleteEditing = true; + } + + editorRequestFocus(editingComponent); + // Register MouseInputHandler to redispatch initial mouse events + // correctly. + if (event instanceof MouseEvent) + { + Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(), + editingComponent); + Component active = + SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y); + if (active != null) + { + MouseInputHandler ih = new MouseInputHandler(tree, active, event); + + } + } - return true; + return true; + } + else + editingComponent = null; } return false; } /** + * Requests focus on the editor. The method is necessary since the + * DefaultTreeCellEditor returns a container that contains the + * actual editor, and we want to request focus on the editor, not the + * container. + */ + private void editorRequestFocus(Component c) + { + if (c instanceof Container) + { + // TODO: Maybe do something more reasonable here, like queriying the + // FocusTraversalPolicy. + Container cont = (Container) c; + if (cont.getComponentCount() > 0) + cont.getComponent(0).requestFocus(); + } + else if (c.isFocusable()) + c.requestFocus(); + + } + + /** * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or * collapse region of the row, this will toggle the row. * @@ -2180,7 +2273,7 @@ public class BasicTreeUI */ public void editingStopped(ChangeEvent e) { - stopEditing(tree); + completeEditing(false, false, true); } /** @@ -2191,7 +2284,7 @@ public class BasicTreeUI */ public void editingCanceled(ChangeEvent e) { - cancelEditing(tree); + completeEditing(false, false, false); } } // CellEditorHandler @@ -2347,9 +2440,15 @@ public class BasicTreeUI * events. */ public class MouseHandler - extends MouseAdapter - implements MouseMotionListener + extends MouseAdapter + implements MouseMotionListener { + + /** + * If the cell has been selected on mouse press. + */ + private boolean selectedOnPress; + /** * Constructor */ @@ -2365,76 +2464,15 @@ public class BasicTreeUI */ public void mousePressed(MouseEvent e) { - // Any mouse click cancels the previous waiting edit action, initiated - // by the single click on the selected node. - if (startEditTimer != null) + if (! e.isConsumed()) { - startEditTimer.stop(); - startEditTimer = null; + handleEvent(e); + selectedOnPress = true; } - - if (tree != null && tree.isEnabled()) + else { - // Always end the current editing session if clicked on the - // tree and outside the bounds of the editing component. - if (isEditing(tree)) - if (!stopEditing(tree)) - // Return if we have failed to cancel the editing session. - return; - - int x = e.getX(); - int y = e.getY(); - TreePath path = getClosestPathForLocation(tree, x, y); - - if (path != null) - { - Rectangle bounds = getPathBounds(tree, path); - if (SwingUtilities.isLeftMouseButton(e)) - checkForClickInExpandControl(path, x, y); - - if (x > bounds.x && x <= (bounds.x + bounds.width)) - { - TreePath currentLead = tree.getLeadSelectionPath(); - if (currentLead != null && currentLead.equals(path) - && e.getClickCount() == 1 && tree.isEditable()) - { - // Schedule the editing session. - final TreePath editPath = path; - - // The code below handles the required click-pause-click - // functionality which must be present in the tree UI. - // If the next click comes after the - // time longer than the double click interval AND - // the same node stays focused for the WAIT_TILL_EDITING - // duration, the timer starts the editing session. - if (startEditTimer != null) - startEditTimer.stop(); - - startEditTimer = new Timer(WAIT_TILL_EDITING, - new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - startEditing(editPath, EDIT); - } - }); - - startEditTimer.setRepeats(false); - startEditTimer.start(); - } - else - { - if (e.getClickCount() == 2) - toggleExpandState(path); - else - selectPathForEvent(path, e); - } - } - } + selectedOnPress = false; } - - // We need to request the focus. - tree.requestFocusInWindow(); } /** @@ -2446,9 +2484,8 @@ public class BasicTreeUI * @param e is the mouse event that occured */ public void mouseDragged(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** @@ -2458,9 +2495,8 @@ public class BasicTreeUI * @param e the mouse event that occured */ public void mouseMoved(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** @@ -2469,9 +2505,46 @@ public class BasicTreeUI * @param e is the mouse event that occured */ public void mouseReleased(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + if (! e.isConsumed() && ! selectedOnPress) + handleEvent(e); + } + + /** + * Handles press and release events. + * + * @param e the mouse event + */ + private void handleEvent(MouseEvent e) + { + if (tree != null && tree.isEnabled()) + { + // Maybe stop editing. + if (isEditing(tree) && tree.getInvokesStopCellEditing() + && ! stopEditing(tree)) + return; + + // Explicitly request focus. + tree.requestFocusInWindow(); + + int x = e.getX(); + int y = e.getY(); + TreePath path = getClosestPathForLocation(tree, x, y); + if (path != null) + { + Rectangle b = getPathBounds(tree, path); + if (y <= b.y + b.height) + { + if (SwingUtilities.isLeftMouseButton(e)) + checkForClickInExpandControl(path, x, y); + if (x > b.x && x <= b.x + b.width) + { + if (! startEditing(path, e)) + selectPathForEvent(path, e); + } + } + } + } } } @@ -2501,6 +2574,9 @@ public class BasicTreeUI { this.source = source; this.destination = destination; + source.addMouseListener(this); + source.addMouseMotionListener(this); + dispatch(e); } /** @@ -2510,9 +2586,8 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseClicked(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + dispatch(e); } /** @@ -2521,9 +2596,8 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mousePressed(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** @@ -2532,9 +2606,9 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseReleased(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + dispatch(e); + removeFromSource(); } /** @@ -2543,9 +2617,9 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseEntered(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + if (! SwingUtilities.isLeftMouseButton(e)) + removeFromSource(); } /** @@ -2554,9 +2628,9 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseExited(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + if (! SwingUtilities.isLeftMouseButton(e)) + removeFromSource(); } /** @@ -2568,9 +2642,8 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseDragged(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + dispatch(e); } /** @@ -2580,18 +2653,37 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseMoved(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + removeFromSource(); } /** * Removes event from the source */ protected void removeFromSource() - throws NotImplementedException { - // TODO: Implement this properly. + if (source != null) + { + source.removeMouseListener(this); + source.removeMouseMotionListener(this); + } + source = null; + destination = null; + } + + /** + * Redispatches mouse events to the destination. + * + * @param e the mouse event to redispatch + */ + private void dispatch(MouseEvent e) + { + if (destination != null) + { + MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e, + destination); + destination.dispatchEvent(e2); + } } } @@ -2627,24 +2719,42 @@ public class BasicTreeUI public Rectangle getNodeDimensions(Object cell, int row, int depth, boolean expanded, Rectangle size) { - if (size == null || cell == null) - return null; - - String s = cell.toString(); - Font f = tree.getFont(); - FontMetrics fm = tree.getToolkit().getFontMetrics(f); - - if (s != null) + Dimension prefSize; + if (editingComponent != null && editingRow == row) + { + // Editing, ask editor for preferred size. + prefSize = editingComponent.getPreferredSize(); + int rowHeight = getRowHeight(); + if (rowHeight > 0 && rowHeight != prefSize.height) + prefSize.height = rowHeight; + } + else + { + // Not editing, ask renderer for preferred size. + Component rend = + currentCellRenderer.getTreeCellRendererComponent(tree, cell, + tree.isRowSelected(row), + expanded, + treeModel.isLeaf(cell), + row, false); + // Make sure the layout is valid. + rendererPane.add(rend); + rend.validate(); + prefSize = rend.getPreferredSize(); + } + if (size != null) { - TreePath path = treeState.getPathForRow(row); size.x = getRowX(row, depth); - size.width = SwingUtilities.computeStringWidth(fm, s); - size.width = size.width + getCurrentControlIcon(path).getIconWidth() - + gap + getNodeIcon(path).getIconWidth(); - size.height = getMaxHeight(tree); - size.y = size.height * row; + // FIXME: This should be handled by the layout cache. + size.y = prefSize.height * row; + size.width = prefSize.width; + size.height = prefSize.height; } - + else + // FIXME: The y should be handled by the layout cache. + size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width, + prefSize.height); + return size; } @@ -2706,6 +2816,9 @@ public class BasicTreeUI if (treeState != null) treeState.invalidateSizes(); } + else if (property.equals(JTree.EDITABLE_PROPERTY)) + setEditable(((Boolean) event.getNewValue()).booleanValue()); + } } @@ -2714,7 +2827,7 @@ public class BasicTreeUI * properties of the model change. */ public class SelectionModelPropertyChangeHandler - implements PropertyChangeListener + implements PropertyChangeListener { /** @@ -2732,9 +2845,8 @@ public class BasicTreeUI * the property that has changed. */ public void propertyChange(PropertyChangeEvent event) - throws NotImplementedException { - // TODO: What should be done here, if anything? + treeSelectionModel.resetRowSelection(); } } @@ -2804,6 +2916,7 @@ public class BasicTreeUI */ public void treeCollapsed(TreeExpansionEvent event) { + completeEditing(); validCachedPreferredSize = false; treeState.setExpandedState(event.getPath(), false); // The maximal cell height may change @@ -3269,8 +3382,7 @@ public class BasicTreeUI */ public void valueChanged(TreeSelectionEvent event) { - if (tree.isEditing()) - tree.cancelEditing(); + completeEditing(); TreePath op = event.getOldLeadSelectionPath(); TreePath np = event.getNewLeadSelectionPath(); @@ -3808,25 +3920,6 @@ public class BasicTreeUI } /** - * Finish the editing session. - */ - void finish() - { - treeState.invalidatePathBounds(treeState.getPathForRow(editingRow)); - editingPath = null; - editingRow = - 1; - stopEditingInCompleteEditing = false; - isEditing = false; - Rectangle bounds = editingComponent.getParent().getBounds(); - tree.removeAll(); - validCachedPreferredSize = false; - // Repaint the region, where was the editing component. - tree.repaint(bounds); - editingComponent = null; - tree.requestFocus(); - } - - /** * Returns the amount to indent the given row * * @return amount to indent the given row. diff --git a/javax/swing/plaf/metal/MetalButtonUI.java b/javax/swing/plaf/metal/MetalButtonUI.java index 8addfc66c..be9607927 100644 --- a/javax/swing/plaf/metal/MetalButtonUI.java +++ b/javax/swing/plaf/metal/MetalButtonUI.java @@ -54,7 +54,6 @@ import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; -import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonUI; /** @@ -66,24 +65,46 @@ public class MetalButtonUI extends BasicButtonUI { - /** The color used to draw the focus rectangle around the text and/or icon. */ + /** + * The shared button UI. + */ + private static MetalButtonUI sharedUI; + + /** + * The color used to draw the focus rectangle around the text and/or icon. + */ protected Color focusColor; - /** The background color for the button when it is pressed. */ + /** + * The background color for the button when it is pressed. + */ protected Color selectColor; - /** The color for disabled button labels. */ + /** + * The color for disabled button labels. + */ protected Color disabledTextColor; /** + * Returns a UI delegate for the specified component. + * + * @param c the component (should be a subclass of {@link AbstractButton}). + * + * @return A new instance of <code>MetalButtonUI</code>. + */ + public static ComponentUI createUI(JComponent c) + { + if (sharedUI == null) + sharedUI = new MetalButtonUI(); + return sharedUI; + } + + /** * Creates a new instance. */ public MetalButtonUI() { super(); - focusColor = UIManager.getColor(getPropertyPrefix() + "focus"); - selectColor = UIManager.getColor(getPropertyPrefix() + "select"); - disabledTextColor = UIManager.getColor(getPropertyPrefix() + "disabledText"); } /** @@ -93,6 +114,7 @@ public class MetalButtonUI */ protected Color getFocusColor() { + focusColor = UIManager.getColor(getPropertyPrefix() + "focus"); return focusColor; } @@ -103,6 +125,7 @@ public class MetalButtonUI */ protected Color getSelectColor() { + selectColor = UIManager.getColor(getPropertyPrefix() + "select"); return selectColor; } @@ -113,22 +136,12 @@ public class MetalButtonUI */ protected Color getDisabledTextColor() { + disabledTextColor = UIManager.getColor(getPropertyPrefix() + + "disabledText"); return disabledTextColor; } /** - * Returns a UI delegate for the specified component. - * - * @param c the component (should be a subclass of {@link AbstractButton}). - * - * @return A new instance of <code>MetalButtonUI</code>. - */ - public static ComponentUI createUI(JComponent c) - { - return new MetalButtonUI(); - } - - /** * Installs the default settings for the specified button. * * @param button the button. @@ -137,33 +150,20 @@ public class MetalButtonUI */ public void installDefaults(AbstractButton button) { + // This is overridden to be public, for whatever reason. super.installDefaults(button); - button.setRolloverEnabled(UIManager.getBoolean( - getPropertyPrefix() + "rollover")); } - + /** * Removes the defaults added by {@link #installDefaults(AbstractButton)}. */ public void uninstallDefaults(AbstractButton button) { + // This is overridden to be public, for whatever reason. super.uninstallDefaults(button); - button.setRolloverEnabled(false); } /** - * Returns a button listener for the specified button. - * - * @param button the button. - * - * @return A button listener. - */ - protected BasicButtonListener createButtonListener(AbstractButton button) - { - return new MetalButtonListener(button); - } - - /** * Paints the background of the button to indicate that it is in the * "pressed" state. * @@ -175,7 +175,7 @@ public class MetalButtonUI if (b.isContentAreaFilled()) { Rectangle area = b.getVisibleRect(); - g.setColor(selectColor); + g.setColor(getSelectColor()); g.fillRect(area.x, area.y, area.width, area.height); } } diff --git a/javax/swing/plaf/metal/MetalCheckBoxIcon.java b/javax/swing/plaf/metal/MetalCheckBoxIcon.java index fb8280e44..30ee93162 100644 --- a/javax/swing/plaf/metal/MetalCheckBoxIcon.java +++ b/javax/swing/plaf/metal/MetalCheckBoxIcon.java @@ -1,5 +1,5 @@ /* MetalCheckBoxIcon.java -- An icon for JCheckBoxes in the Metal L&F - Copyright (C) 2005 Free Software Foundation, Inc. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -42,8 +42,8 @@ import java.awt.Component; import java.awt.Graphics; import java.io.Serializable; +import javax.swing.AbstractButton; import javax.swing.Icon; -import javax.swing.JCheckBox; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.plaf.UIResource; @@ -134,8 +134,9 @@ public class MetalCheckBoxIcon MetalUtils.paintGradient(g, x, y, getIconWidth(), getIconHeight(), SwingConstants.VERTICAL, "CheckBox.gradient"); border.paintBorder(c, g, x, y, getIconWidth(), getIconHeight()); - JCheckBox cb = (JCheckBox) c; - if (cb.isSelected()) - drawCheck(c, g, x, y); + + AbstractButton b = (AbstractButton) c; + if (b.isSelected()) + drawCheck(b, g, x, y); } } diff --git a/javax/swing/plaf/metal/MetalLookAndFeel.java b/javax/swing/plaf/metal/MetalLookAndFeel.java index da16159c9..ff26aa232 100644 --- a/javax/swing/plaf/metal/MetalLookAndFeel.java +++ b/javax/swing/plaf/metal/MetalLookAndFeel.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.plaf.metal; +import gnu.classpath.SystemProperties; + import java.awt.Color; import java.awt.Font; @@ -81,16 +83,15 @@ public class MetalLookAndFeel extends BasicLookAndFeel */ public MetalLookAndFeel() { - createDefaultTheme(); + // Nothing to do here. } /** - * Sets the current theme to a new instance of {@link OceanTheme}. + * Sets the current theme to a new instance of {@link DefaultMetalTheme}. */ protected void createDefaultTheme() { - if (theme == null) - setCurrentTheme(new OceanTheme()); + getCurrentTheme(); } /** @@ -149,6 +150,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel public UIDefaults getDefaults() { + createDefaultTheme(); if (LAF_defaults == null) { LAF_defaults = super.getDefaults(); @@ -1354,7 +1356,14 @@ public class MetalLookAndFeel extends BasicLookAndFeel public static MetalTheme getCurrentTheme() { if (theme == null) - theme = new OceanTheme(); + { + // swing.metalTheme property documented here: + // http://java.sun.com/j2se/1.5.0/docs/guide/swing/1.5/index.html + if ("steel".equals(SystemProperties.getProperty("swing.metalTheme"))) + theme = new DefaultMetalTheme(); + else + theme = new OceanTheme(); + } return theme; } diff --git a/javax/swing/plaf/metal/MetalSplitPaneDivider.java b/javax/swing/plaf/metal/MetalSplitPaneDivider.java index 6081c355c..a3069daa9 100644 --- a/javax/swing/plaf/metal/MetalSplitPaneDivider.java +++ b/javax/swing/plaf/metal/MetalSplitPaneDivider.java @@ -38,18 +38,14 @@ exception statement from your version. */ package javax.swing.plaf.metal; import java.awt.Color; -import java.awt.Component; -import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; -import java.awt.LayoutManager; -import java.awt.Point; +import java.awt.Insets; +import javax.swing.JButton; import javax.swing.JSplitPane; -import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.border.Border; -import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.plaf.basic.BasicSplitPaneDivider; /** @@ -59,6 +55,143 @@ import javax.swing.plaf.basic.BasicSplitPaneDivider; */ class MetalSplitPaneDivider extends BasicSplitPaneDivider { + /** + * The button pixel data, as indices into the colors array below. + * This is the version for 'left' buttons. + * + * This is slightly different from the icon in Sun's version, it is + * one pixel smaller and is more consistent with BUTTON_SPRITE_R. + */ + static final byte[][] BUTTON_SPRITE_L = {{ 0, 0, 0, 2, 0, 0, 0, 0 }, + { 0, 0, 2, 1, 1, 0, 0, 0 }, + { 0, 2, 1, 1, 1, 1, 0, 0 }, + { 2, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 3, 3, 3, 3, 3, 3, 3 }}; + + /** + * The button pixel data, as indices into the colors array below. + * This is the version for 'right' buttons. + */ + static final byte[][] BUTTON_SPRITE_R = {{ 2, 2, 2, 2, 2, 2, 2, 2 }, + { 0, 1, 1, 1, 1, 1, 1, 3 }, + { 0, 0, 1, 1, 1, 1, 3, 0 }, + { 0, 0, 0, 1, 1, 3, 0, 0 }, + { 0, 0, 0, 0, 3, 0, 0, 0 }}; + + private class MetalOneTouchButton + extends JButton + { + /** + * Denotes a left button. + */ + static final int LEFT = 0; + + /** + * Denotes a right button. + */ + static final int RIGHT = 1; + + /** + * The colors for the button sprite. + */ + private Color[] colors; + + /** + * Either LEFT or RIGHT. + */ + private int direction; + + /** + * Creates a new instance. + * + * @param dir either LEFT or RIGHT + */ + MetalOneTouchButton(int dir) + { + direction = dir; + colors = new Color[4]; + } + + /** + * Never allow borders. + */ + public void setBorder(Border b) + { + } + + /** + * Never allow focus traversal. + */ + public boolean isFocusTraversable() + { + return false; + } + + /** + * Paints the one touch button. + */ + public void paint(Graphics g) + { + if (splitPane != null) + { + // Update colors here to reflect dynamic changes to the theme. + colors[0] = getBackground(); + colors[1] = MetalLookAndFeel.getPrimaryControlDarkShadow(); + colors[2] = MetalLookAndFeel.getPrimaryControlInfo(); + colors[3] = MetalLookAndFeel.getPrimaryControlHighlight(); + + // Fill background. + g.setColor(getBackground()); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Pressed buttons have slightly different color mapping. + if (getModel().isPressed()) + colors[1] = colors[2]; + + byte[][] sprite; + if (direction == LEFT) + sprite = BUTTON_SPRITE_L; + else + sprite = BUTTON_SPRITE_R; + + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + // Draw the sprite as it is. + for (int y = 0; y < sprite.length; y++) + { + byte[] line = sprite[y]; + for (int x = 0; x < line.length; x++) + { + int c = line[x]; + if (c != 0) + { + g.setColor(colors[c]); + g.fillRect(x + 1, y + 1, 1, 1); + } + } + } + } + else + { + // Draw the sprite with swapped X and Y axis. + for (int y = 0; y < sprite.length; y++) + { + byte[] line = sprite[y]; + for (int x = 0; x < line.length; x++) + { + int c = line[x]; + if (c != 0) + { + g.setColor(colors[c]); + g.fillRect(y + 1, x + 1, 1, 1); + } + } + } + } + } + } + } + /** The dark color in the pattern. */ Color dark; @@ -79,7 +212,6 @@ class MetalSplitPaneDivider extends BasicSplitPaneDivider public MetalSplitPaneDivider(MetalSplitPaneUI ui, Color light, Color dark) { super(ui); - setLayout(new MetalDividerLayout()); this.splitPane = super.splitPane; this.orientation = super.orientation; this.light = light; @@ -106,126 +238,27 @@ class MetalSplitPaneDivider extends BasicSplitPaneDivider if (border != null) border.paintBorder(this, g, 0, 0, s.width, s.height); - MetalUtils.fillMetalPattern(splitPane, g, 2, 2, s.width - 4, s.height - 4, + Insets i = getInsets(); + MetalUtils.fillMetalPattern(splitPane, g, i.left + 2, i.top + 2, + s.width - i.left - i.right - 4, + s.height - i.top - i.bottom - 4, light, dark); - if (splitPane.isOneTouchExpandable()) - { - ((BasicArrowButton) rightButton).paint(g); - ((BasicArrowButton) leftButton).paint(g); - } + super.paint(g); } - - /** - * This helper class acts as the Layout Manager for the divider. - */ - public class MetalDividerLayout implements LayoutManager - { - /** The right button. */ - BasicArrowButton rb; - - /** The left button. */ - BasicArrowButton lb; - - /** - * Creates a new DividerLayout object. - */ - public MetalDividerLayout() - { - // Nothing to do here - } - - /** - * This method is called when a Component is added. - * - * @param string The constraints string. - * @param c The Component to add. - */ - public void addLayoutComponent(String string, Component c) - { - // Nothing to do here, constraints are set depending on - // orientation in layoutContainer - } - - /** - * This method is called to lay out the container. - * - * @param c The container to lay out. - */ - public void layoutContainer(Container c) - { - // The only components we care about setting up are the - // one touch buttons. - if (splitPane.isOneTouchExpandable()) - { - if (c.getComponentCount() == 2) - { - Component c1 = c.getComponent(0); - Component c2 = c.getComponent(1); - if ((c1 instanceof BasicArrowButton) - && (c2 instanceof BasicArrowButton)) - { - lb = (BasicArrowButton) c1; - rb = (BasicArrowButton) c2; - } - } - if (rb != null && lb != null) - { - Point p = getLocation(); - lb.setSize(lb.getPreferredSize()); - rb.setSize(rb.getPreferredSize()); - lb.setLocation(p.x, p.y); - - if (orientation == JSplitPane.HORIZONTAL_SPLIT) - { - rb.setDirection(SwingConstants.EAST); - lb.setDirection(SwingConstants.WEST); - rb.setLocation(p.x, p.y + lb.getHeight()); - } - else - { - rb.setDirection(SwingConstants.SOUTH); - lb.setDirection(SwingConstants.NORTH); - rb.setLocation(p.x + lb.getWidth(), p.y); - } - } - } - } - - /** - * This method returns the minimum layout size. - * - * @param c The container to calculate for. - * - * @return The minimum layout size. - */ - public Dimension minimumLayoutSize(Container c) - { - return preferredLayoutSize(c); - } - /** - * This method returns the preferred layout size. - * - * @param c The container to calculate for. - * - * @return The preferred layout size. - */ - public Dimension preferredLayoutSize(Container c) - { - int dividerSize = getDividerSize(); - return new Dimension(dividerSize, dividerSize); - } + protected JButton createLeftOneTouchButton() + { + JButton b = new MetalOneTouchButton(MetalOneTouchButton.LEFT); + b.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + b.setRequestFocusEnabled(false); + return b; + } - /** - * This method is called when a component is removed. - * - * @param c The component to remove. - */ - public void removeLayoutComponent(Component c) - { - // Nothing to do here. If buttons are removed - // they will not be layed out when layoutContainer is - // called. - } + protected JButton createRightOneTouchButton() + { + JButton b = new MetalOneTouchButton(MetalOneTouchButton.RIGHT); + b.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + b.setRequestFocusEnabled(false); + return b; } } diff --git a/javax/swing/plaf/metal/MetalTabbedPaneUI.java b/javax/swing/plaf/metal/MetalTabbedPaneUI.java index 20135fc85..53eaa3cac 100644 --- a/javax/swing/plaf/metal/MetalTabbedPaneUI.java +++ b/javax/swing/plaf/metal/MetalTabbedPaneUI.java @@ -1159,7 +1159,7 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI g.drawLine(x + 1, y + 1, x + 1, rect.y + 1); if (rect.y + rect.height < y + h - 2) { - g.drawLine(x + y, rect.y + rect.height + 1, x + 1, y + h + 2); + g.drawLine(x + 1, rect.y + rect.height + 1, x + 1, y + h + 2); } } } diff --git a/javax/swing/plaf/metal/MetalToolTipUI.java b/javax/swing/plaf/metal/MetalToolTipUI.java index d1040347f..6647cc02d 100644 --- a/javax/swing/plaf/metal/MetalToolTipUI.java +++ b/javax/swing/plaf/metal/MetalToolTipUI.java @@ -43,9 +43,6 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; -import java.awt.Insets; -import java.awt.Rectangle; -import java.awt.Toolkit; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; @@ -54,8 +51,6 @@ import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JToolTip; import javax.swing.KeyStroke; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; @@ -192,32 +187,14 @@ public class MetalToolTipUI */ public Dimension getPreferredSize(JComponent c) { - if (isAcceleratorHidden()) - return super.getPreferredSize(c); - else + Dimension d = super.getPreferredSize(c); + String acc = getAcceleratorString(); + if (acc != null && ! acc.equals("")) { - Insets insets = c.getInsets(); - JToolTip tt = (JToolTip) c; - String tipText = tt.getTipText(); - if (tipText != null) - { - FontMetrics fm = c.getFontMetrics(c.getFont()); - int prefH = fm.getHeight() + insets.top + insets.bottom; - int prefW = fm.stringWidth(tipText) + insets.left + insets.right; - - // this seems to be the first opportunity we have to get the - // accelerator string from the component (if it has one) - acceleratorString = fetchAcceleratorString(c); - if (acceleratorString != null) - { - prefW += padSpaceBetweenStrings; - fm = c.getFontMetrics(acceleratorFont); - prefW += fm.stringWidth(acceleratorString); - } - return new Dimension(prefW, prefH); - } - else return new Dimension(0, 0); + FontMetrics fm = c.getFontMetrics(c.getFont()); + d.width += fm.stringWidth(acc); } + return d; } /** @@ -228,39 +205,8 @@ public class MetalToolTipUI */ public void paint(Graphics g, JComponent c) { - JToolTip tip = (JToolTip) c; - - String text = tip.getTipText(); - Toolkit t = tip.getToolkit(); - if (text == null) - return; - - Rectangle vr = new Rectangle(); - vr = SwingUtilities.calculateInnerArea(tip, vr); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - FontMetrics fm = t.getFontMetrics(tip.getFont()); - int ascent = fm.getAscent(); - SwingUtilities.layoutCompoundLabel(tip, fm, text, null, - SwingConstants.CENTER, SwingConstants.LEFT, - SwingConstants.CENTER, SwingConstants.CENTER, vr, ir, tr, 0); - Color saved = g.getColor(); - g.setColor(Color.BLACK); - - g.drawString(text, vr.x, vr.y + ascent); - - // paint accelerator - if (acceleratorString != null) - { - g.setFont(acceleratorFont); - g.setColor(acceleratorForeground); - fm = t.getFontMetrics(acceleratorFont); - int width = fm.stringWidth(acceleratorString); - g.drawString(acceleratorString, vr.x + vr.width - width - - padSpaceBetweenStrings / 2, vr.y + vr.height - fm.getDescent()); - } - - g.setColor(saved); + super.paint(g, c); + // Somehow paint accelerator. Keep care for possible HTML rendering. } /** diff --git a/javax/swing/plaf/metal/MetalTreeUI.java b/javax/swing/plaf/metal/MetalTreeUI.java index 3ea37c82f..ed1e5b4d8 100644 --- a/javax/swing/plaf/metal/MetalTreeUI.java +++ b/javax/swing/plaf/metal/MetalTreeUI.java @@ -38,14 +38,15 @@ exception statement from your version. */ package javax.swing.plaf.metal; -import gnu.classpath.NotImplementedException; - import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JComponent; import javax.swing.JTree; +import javax.swing.UIManager; import javax.swing.tree.TreePath; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTreeUI; @@ -56,6 +57,68 @@ import javax.swing.plaf.basic.BasicTreeUI; public class MetalTreeUI extends BasicTreeUI { /** + * Listens for property changes of the line style and updates the + * internal setting. + */ + private class LineStyleListener + implements PropertyChangeListener + { + + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals(LINE_STYLE_PROPERTY)) + decodeLineStyle(e.getNewValue()); + } + + } + + /** + * The key to the lineStyle client property. + */ + private static final String LINE_STYLE_PROPERTY = "JTree.lineStyle"; + + /** + * The property value indicating no line style. + */ + private static final String LINE_STYLE_VALUE_NONE = "None"; + + /** + * The property value indicating angled line style. + */ + private static final String LINE_STYLE_VALUE_ANGLED = "Angled"; + + /** + * The property value indicating horizontal line style. + */ + private static final String LINE_STYLE_VALUE_HORIZONTAL = "Horizontal"; + + /** + * The line style for None. + */ + private static final int LINE_STYLE_NONE = 0; + + /** + * The line style for Angled. + */ + private static final int LINE_STYLE_ANGLED = 1; + + /** + * The line style for Horizontal. + */ + private static final int LINE_STYLE_HORIZONTAL = 2; + + /** + * The current line style. + */ + private int lineStyle; + + /** + * Listens for changes on the line style property and updates the + * internal settings. + */ + private PropertyChangeListener lineStyleListener; + + /** * Constructs a new instance of <code>MetalTreeUI</code>. */ public MetalTreeUI() @@ -103,8 +166,13 @@ public class MetalTreeUI extends BasicTreeUI */ public void installUI(JComponent c) { - // TODO: What to do here, if anything? super.installUI(c); + + Object lineStyleProp = c.getClientProperty(LINE_STYLE_PROPERTY); + decodeLineStyle(lineStyleProp); + if (lineStyleListener == null) + lineStyleListener = new LineStyleListener(); + c.addPropertyChangeListener(lineStyleListener); } /** @@ -124,8 +192,10 @@ public class MetalTreeUI extends BasicTreeUI */ public void uninstallUI(JComponent c) { - // TODO: What to do here? super.uninstallUI(c); + if (lineStyleListener != null) + c.removePropertyChangeListener(lineStyleListener); + lineStyleListener = null; } /** @@ -135,9 +205,15 @@ public class MetalTreeUI extends BasicTreeUI * @param lineStyleFlag - String representation */ protected void decodeLineStyle(Object lineStyleFlag) - throws NotImplementedException { - // FIXME: not implemented + if (lineStyleFlag == null || lineStyleFlag.equals(LINE_STYLE_VALUE_ANGLED)) + lineStyle = LINE_STYLE_ANGLED; + else if (lineStyleFlag.equals(LINE_STYLE_VALUE_HORIZONTAL)) + lineStyle = LINE_STYLE_HORIZONTAL; + else if (lineStyleFlag.equals(LINE_STYLE_VALUE_NONE)) + lineStyle = LINE_STYLE_NONE; + else + lineStyle = LINE_STYLE_ANGLED; } /** @@ -170,6 +246,9 @@ public class MetalTreeUI extends BasicTreeUI // Calls BasicTreeUI's paint since it takes care of painting all // types of icons. super.paint(g, c); + + if (lineStyle == LINE_STYLE_HORIZONTAL) + paintHorizontalSeparators(g, c); } /** @@ -179,9 +258,28 @@ public class MetalTreeUI extends BasicTreeUI * @param c - the current component to draw */ protected void paintHorizontalSeparators(Graphics g, JComponent c) - throws NotImplementedException { - // FIXME: not implemented + g.setColor(UIManager.getColor("Tree.line")); + Rectangle clip = g.getClipBounds(); + int row0 = getRowForPath(tree, getClosestPathForLocation(tree, 0, clip.y)); + int row1 = + getRowForPath(tree, getClosestPathForLocation(tree, 0, + clip.y + clip.height - 1)); + if (row0 >= 0 && row1 >= 0) + { + for (int i = row0; i <= row1; i++) + { + TreePath p = getPathForRow(tree, i); + if (p != null && p.getPathCount() == 2) + { + Rectangle r = getPathBounds(tree, getPathForRow(tree, i)); + if (r != null) + { + g.drawLine(clip.x, r.y, clip.x + clip.width, r.y); + } + } + } + } } @@ -197,7 +295,8 @@ public class MetalTreeUI extends BasicTreeUI protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) { - super.paintVerticalPartOfLeg(g, clipBounds, insets, path); + if (lineStyle == LINE_STYLE_ANGLED) + super.paintVerticalPartOfLeg(g, clipBounds, insets, path); } /** @@ -211,7 +310,8 @@ public class MetalTreeUI extends BasicTreeUI boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { - super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds, path, row, - isExpanded, hasBeenExpanded, isLeaf); + if (lineStyle == LINE_STYLE_ANGLED) + super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds, path, row, + isExpanded, hasBeenExpanded, isLeaf); } } diff --git a/javax/swing/table/DefaultTableModel.java b/javax/swing/table/DefaultTableModel.java index 79285903c..1b68ce2be 100644 --- a/javax/swing/table/DefaultTableModel.java +++ b/javax/swing/table/DefaultTableModel.java @@ -625,7 +625,7 @@ public class DefaultTableModel extends AbstractTableModel if (columnCount > columnIdentifiers.size()) columnIdentifiers.setSize(columnCount); - if (rowCount > dataVector.size()) + if (dataVector != null && rowCount > dataVector.size()) { int rowsToAdd = rowCount - dataVector.size(); addExtraRows(rowsToAdd, columnCount); diff --git a/javax/swing/text/BoxView.java b/javax/swing/text/BoxView.java index 7e8f19f74..962d06219 100644 --- a/javax/swing/text/BoxView.java +++ b/javax/swing/text/BoxView.java @@ -92,11 +92,6 @@ public class BoxView private int[] span = new int[2]; /** - * The SizeRequirements of the child views along the X_AXIS and Y_AXIS. - */ - private SizeRequirements[][] childReqs = new SizeRequirements[2][]; - - /** * Creates a new <code>BoxView</code> for the given * <code>Element</code> and axis. Valid values for the axis are * {@link View#X_AXIS} and {@link View#Y_AXIS}. @@ -227,48 +222,30 @@ public class BoxView */ public void replace(int offset, int length, View[] views) { - int numViews = 0; - if (views != null) - numViews = views.length; - - // Resize and copy data for cache arrays. - // The spansX cache. - int oldSize = getViewCount(); - - int[] newSpansX = new int[oldSize - length + numViews]; - System.arraycopy(spans[X_AXIS], 0, newSpansX, 0, offset); - System.arraycopy(spans[X_AXIS], offset + length, newSpansX, - offset + numViews, - oldSize - (offset + length)); - spans[X_AXIS] = newSpansX; - - // The spansY cache. - int[] newSpansY = new int[oldSize - length + numViews]; - System.arraycopy(spans[Y_AXIS], 0, newSpansY, 0, offset); - System.arraycopy(spans[Y_AXIS], offset + length, newSpansY, - offset + numViews, - oldSize - (offset + length)); - spans[Y_AXIS] = newSpansY; - - // The offsetsX cache. - int[] newOffsetsX = new int[oldSize - length + numViews]; - System.arraycopy(offsets[X_AXIS], 0, newOffsetsX, 0, offset); - System.arraycopy(offsets[X_AXIS], offset + length, newOffsetsX, - offset + numViews, - oldSize - (offset + length)); - offsets[X_AXIS] = newOffsetsX; - - // The offsetsY cache. - int[] newOffsetsY = new int[oldSize - length + numViews]; - System.arraycopy(offsets[Y_AXIS], 0, newOffsetsY, 0, offset); - System.arraycopy(offsets[Y_AXIS], offset + length, newOffsetsY, - offset + numViews, - oldSize - (offset + length)); - offsets[Y_AXIS] = newOffsetsY; + int oldNumChildren = getViewCount(); // Actually perform the replace. super.replace(offset, length, views); + // Resize and copy data for cache arrays. + int newItems = views != null ? views.length : 0; + int delta = newItems - length; + int src = offset + length; + int numMove = oldNumChildren - src; + int dst = src + delta; + offsets[X_AXIS] = replaceLayoutArray(offsets[X_AXIS], offset, + oldNumChildren, delta, src, dst, + numMove); + spans[X_AXIS] = replaceLayoutArray(spans[X_AXIS], offset, + oldNumChildren, delta, src, dst, + numMove); + offsets[Y_AXIS] = replaceLayoutArray(offsets[Y_AXIS], offset, + oldNumChildren, delta, src, dst, + numMove); + spans[Y_AXIS] = replaceLayoutArray(spans[Y_AXIS], offset, + oldNumChildren, delta, src, dst, + numMove); + // Invalidate layout information. layoutValid[X_AXIS] = false; requirementsValid[X_AXIS] = false; @@ -277,6 +254,34 @@ public class BoxView } /** + * Helper method. This updates the layout cache arrays in response + * to a call to {@link #replace(int, int, View[])}. + * + * @param oldArray the old array + * + * @return the replaced array + */ + private int[] replaceLayoutArray(int[] oldArray, int offset, int numChildren, + int delta, int src, int dst, int numMove) + + { + int[] newArray; + if (numChildren + delta > oldArray.length) + { + int newLength = Math.max(2 * oldArray.length, numChildren + delta); + newArray = new int[newLength]; + System.arraycopy(oldArray, 0, newArray, 0, offset); + System.arraycopy(oldArray, src, newArray, dst, numMove); + } + else + { + newArray = oldArray; + System.arraycopy(newArray, src, newArray, dst, numMove); + } + return newArray; + } + + /** * Renders the <code>Element</code> that is associated with this * <code>View</code>. * @@ -373,9 +378,9 @@ public class BoxView } /** - * This method is obsolete and no longer in use. It is replaced by - * {@link #calculateMajorAxisRequirements(int, SizeRequirements)} and - * {@link #calculateMinorAxisRequirements(int, SizeRequirements)}. + * Calculates size requirements for a baseline layout. This is not + * used by the BoxView itself, but by subclasses that wish to perform + * a baseline layout, like the FlowView's rows. * * @param axis the axis that is examined * @param sr the <code>SizeRequirements</code> object to hold the result, @@ -387,50 +392,94 @@ public class BoxView protected SizeRequirements baselineRequirements(int axis, SizeRequirements sr) { - updateChildRequirements(axis); + // Create new instance if sr == null. + if (sr == null) + sr = new SizeRequirements(); + sr.alignment = 0.5F; + + // Calculate overall ascent and descent. + int totalAscentMin = 0; + int totalAscentPref = 0; + int totalAscentMax = 0; + int totalDescentMin = 0; + int totalDescentPref = 0; + int totalDescentMax = 0; + + int count = getViewCount(); + for (int i = 0; i < count; i++) + { + View v = getView(i); + float align = v.getAlignment(axis); + int span = (int) v.getPreferredSpan(axis); + int ascent = (int) (align * span); + int descent = span - ascent; + + totalAscentPref = Math.max(ascent, totalAscentPref); + totalDescentPref = Math.max(descent, totalDescentPref); + if (v.getResizeWeight(axis) > 0) + { + // If the view is resizable, then use the min and max size + // of the view. + span = (int) v.getMinimumSpan(axis); + ascent = (int) (align * span); + descent = span - ascent; + totalAscentMin = Math.max(ascent, totalAscentMin); + totalDescentMin = Math.max(descent, totalDescentMin); + + span = (int) v.getMaximumSpan(axis); + ascent = (int) (align * span); + descent = span - ascent; + totalAscentMax = Math.max(ascent, totalAscentMax); + totalDescentMax = Math.max(descent, totalDescentMax); + } + else + { + // If the view is not resizable, use the preferred span. + totalAscentMin = Math.max(ascent, totalAscentMin); + totalDescentMin = Math.max(descent, totalDescentMin); + totalAscentMax = Math.max(ascent, totalAscentMax); + totalDescentMax = Math.max(descent, totalDescentMax); + } + } - SizeRequirements res = sr; - if (res == null) - res = new SizeRequirements(); + // Preferred overall span is the sum of the preferred ascent and descent. + // With overflow check. + sr.preferred = (int) Math.min((long) totalAscentPref + + (long) totalDescentPref, + Integer.MAX_VALUE); - float minLeft = 0; - float minRight = 0; - float prefLeft = 0; - float prefRight = 0; - float maxLeft = 0; - float maxRight = 0; - for (int i = 0; i < childReqs[axis].length; i++) + // Align along the baseline. + if (sr.preferred > 0) + sr.alignment = (float) totalAscentPref / sr.preferred; + + if (sr.alignment == 0) { - float myMinLeft = childReqs[axis][i].minimum * childReqs[axis][i].alignment; - float myMinRight = childReqs[axis][i].minimum - myMinLeft; - minLeft = Math.max(myMinLeft, minLeft); - minRight = Math.max(myMinRight, minRight); - float myPrefLeft = childReqs[axis][i].preferred * childReqs[axis][i].alignment; - float myPrefRight = childReqs[axis][i].preferred - myPrefLeft; - prefLeft = Math.max(myPrefLeft, prefLeft); - prefRight = Math.max(myPrefRight, prefRight); - float myMaxLeft = childReqs[axis][i].maximum * childReqs[axis][i].alignment; - float myMaxRight = childReqs[axis][i].maximum - myMaxLeft; - maxLeft = Math.max(myMaxLeft, maxLeft); - maxRight = Math.max(myMaxRight, maxRight); + // Nothing above the baseline, use the descent. + sr.minimum = totalDescentMin; + sr.maximum = totalDescentMax; } - int minSize = (int) (minLeft + minRight); - int prefSize = (int) (prefLeft + prefRight); - int maxSize = (int) (maxLeft + maxRight); - float align = prefLeft / (prefRight + prefLeft); - if (Float.isNaN(align)) - align = 0; - - res.alignment = align; - res.maximum = maxSize; - res.preferred = prefSize; - res.minimum = minSize; - return res; + else if (sr.alignment == 1.0F) + { + // Nothing below the baseline, use the descent. + sr.minimum = totalAscentMin; + sr.maximum = totalAscentMax; + } + else + { + sr.minimum = Math.max((int) (totalAscentMin / sr.alignment), + (int) (totalDescentMin / (1.0F - sr.alignment))); + sr.maximum = Math.min((int) (totalAscentMax / sr.alignment), + (int) (totalDescentMax / (1.0F - sr.alignment))); + } + return sr; } /** - * Calculates the layout of the children of this <code>BoxView</code> along - * the specified axis. + * Calculates the baseline layout of the children of this + * <code>BoxView</code> along the specified axis. + * + * This is not used by the BoxView itself, but by subclasses that wish to + * perform a baseline layout, like the FlowView's rows. * * @param span the target span * @param axis the axis that is examined @@ -440,13 +489,36 @@ public class BoxView protected void baselineLayout(int span, int axis, int[] offsets, int[] spans) { - updateChildRequirements(axis); - updateRequirements(axis); + int totalAscent = (int) (span * getAlignment(axis)); + int totalDescent = span - totalAscent; - // Calculate the spans and offsets using the SizeRequirements uility - // methods. - SizeRequirements.calculateAlignedPositions(span, requirements[axis], - childReqs[axis], offsets, spans); + int count = getViewCount(); + for (int i = 0; i < count; i++) + { + View v = getView(i); + float align = v.getAlignment(axis); + int viewSpan; + if (v.getResizeWeight(axis) > 0) + { + // If possible, then resize for best fit. + int min = (int) v.getMinimumSpan(axis); + int max = (int) v.getMaximumSpan(axis); + if (align == 0.0F) + viewSpan = Math.max(Math.min(max, totalDescent), min); + else if (align == 1.0F) + viewSpan = Math.max(Math.min(max, totalAscent), min); + else + { + int fit = (int) Math.min(totalAscent / align, + totalDescent / (1.0F - align)); + viewSpan = Math.max(Math.min(max, fit), min); + } + } + else + viewSpan = (int) v.getPreferredSpan(axis); + offsets[i] = totalAscent - (int) (viewSpan * align); + spans[i] = viewSpan; + } } /** @@ -509,7 +581,7 @@ public class BoxView res.minimum = 0; res.preferred = 0; - res.maximum = 0; + res.maximum = Integer.MAX_VALUE; res.alignment = 0.5F; int n = getViewCount(); for (int i = 0; i < n; i++) @@ -589,24 +661,54 @@ public class BoxView { View result = null; int count = getViewCount(); - Rectangle copy = new Rectangle(r); - - for (int i = 0; i < count; ++i) + if (myAxis == X_AXIS) { - copy.setBounds(r); - // The next call modifies copy. - childAllocation(i, copy); - if (copy.contains(x, y)) + // Border case. Requested point is left from the box. + if (x < r.x + offsets[X_AXIS][0]) { - // Modify r on success. - r.setBounds(copy); - result = getView(i); - break; + childAllocation(0, r); + result = getView(0); + } + else + { + // Search views inside box. + for (int i = 0; i < count && result == null; i++) + { + if (x < r.x + offsets[X_AXIS][i]) + { + childAllocation(i - 1, r); + result = getView(i - 1); + } + } } } - - if (result == null && count > 0) - return getView(count - 1); + else // Same algorithm for Y_AXIS. + { + // Border case. Requested point is above the box. + if (y < r.y + offsets[Y_AXIS][0]) + { + childAllocation(0, r); + result = getView(0); + } + else + { + // Search views inside box. + for (int i = 0; i < count && result == null; i++) + { + if (y < r.y + offsets[Y_AXIS][i]) + { + childAllocation(i - 1, r); + result = getView(i - 1); + } + } + } + } + // Not found, other border case: point is right from or below the box. + if (result == null) + { + childAllocation(count - 1, r); + result = getView(count - 1); + } return result; } @@ -808,7 +910,9 @@ public class BoxView */ public int getWidth() { - return span[X_AXIS] + getLeftInset() - getRightInset(); + // The RI returns the following here, however, I'd think that is a bug. + // return span[X_AXIS] + getLeftInset() - getRightInset(); + return span[X_AXIS] + getLeftInset() + getRightInset(); } /** @@ -818,7 +922,9 @@ public class BoxView */ public int getHeight() { - return span[Y_AXIS] + getTopInset() - getBottomInset(); + // The RI returns the following here, however, I'd think that is a bug. + // return span[Y_AXIS] + getTopInset() - getBottomInset(); + return span[Y_AXIS] + getTopInset() + getBottomInset(); } /** @@ -977,7 +1083,11 @@ public class BoxView public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { - // FIXME: What to do here? + if (! isAllocationValid()) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + setSize(r.width, r.height); + } return super.viewToModel(x, y, a, bias); } @@ -988,32 +1098,6 @@ public class BoxView } /** - * Updates the child requirements along the specified axis. The requirements - * are only updated if the layout for the specified axis is marked as - * invalid. - * - * @param axis the axis to be updated - */ - private void updateChildRequirements(int axis) - { - if (! isLayoutValid(axis)) - { - int numChildren = getViewCount(); - if (childReqs[axis] == null || childReqs[axis].length != numChildren) - childReqs[axis] = new SizeRequirements[numChildren]; - for (int i = 0; i < numChildren; ++i) - { - View child = getView(i); - childReqs[axis][i] = - new SizeRequirements((int) child.getMinimumSpan(axis), - (int) child.getPreferredSpan(axis), - (int) child.getMaximumSpan(axis), - child.getAlignment(axis)); - } - } - } - - /** * Updates the view's cached requirements along the specified axis if * necessary. The requirements are only updated if the layout for the * specified axis is marked as invalid. diff --git a/javax/swing/text/ComponentView.java b/javax/swing/text/ComponentView.java index a7d237ab7..555120396 100644 --- a/javax/swing/text/ComponentView.java +++ b/javax/swing/text/ComponentView.java @@ -39,11 +39,11 @@ package javax.swing.text; import java.awt.Component; import java.awt.Container; +import java.awt.Dimension; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; -import javax.swing.SwingConstants; import javax.swing.SwingUtilities; /** @@ -62,11 +62,161 @@ public class ComponentView extends View { /** + * A special container that sits between the component and the hosting + * container. This is used to propagate invalidate requests and cache + * the component's layout sizes. + */ + private class Interceptor + extends Container + { + Dimension min; + Dimension pref; + Dimension max; + float alignX; + float alignY; + + /** + * Creates a new instance that hosts the specified component. + */ + Interceptor(Component c) + { + setLayout(null); + add(c); + cacheComponentSizes(); + } + + /** + * Intercepts the normal invalidate call and propagates the invalidate + * request up using the View's preferenceChanged(). + */ + public void invalidate() + { + super.invalidate(); + if (getParent() != null) + preferenceChanged(null, true, true); + } + + /** + * This is overridden to simply cache the layout sizes. + */ + public void doLayout() + { + cacheComponentSizes(); + } + + /** + * Overridden to also reshape the component itself. + */ + public void reshape(int x, int y, int w, int h) + { + super.reshape(x, y, w, h); + if (getComponentCount() > 0) + getComponent(0).setSize(w, h); + cacheComponentSizes(); + } + + /** + * Overridden to also show the component. + */ + public void show() + { + super.show(); + if (getComponentCount() > 0) + getComponent(0).setVisible(true); + } + + /** + * Overridden to also hide the component. + */ + public void hide() + { + super.hide(); + if (getComponentCount() > 0) + getComponent(0).setVisible(false); + } + + /** + * Overridden to return the cached value. + */ + public Dimension getMinimumSize() + { + maybeValidate(); + return min; + } + + /** + * Overridden to return the cached value. + */ + public Dimension getPreferredSize() + { + maybeValidate(); + return pref; + } + + /** + * Overridden to return the cached value. + */ + public Dimension getMaximumSize() + { + maybeValidate(); + return max; + } + + /** + * Overridden to return the cached value. + */ + public float getAlignmentX() + { + maybeValidate(); + return alignX; + } + + /** + * Overridden to return the cached value. + */ + public float getAlignmentY() + { + maybeValidate(); + return alignY; + } + + /** + * Validates the container only when necessary. + */ + private void maybeValidate() + { + if (! isValid()) + validate(); + } + + /** + * Fetches the component layout sizes into the cache. + */ + private void cacheComponentSizes() + { + if (getComponentCount() > 0) + { + Component c = getComponent(0); + min = c.getMinimumSize(); + pref = c.getPreferredSize(); + max = c.getMaximumSize(); + alignX = c.getAlignmentX(); + alignY = c.getAlignmentY(); + } + } + } + + /** * The component that is displayed by this view. */ private Component comp; /** + * The intercepting container. + */ + private Interceptor interceptor; + + /** * Creates a new instance of <code>ComponentView</code> for the specified * <code>Element</code>. * @@ -99,13 +249,20 @@ public class ComponentView extends View */ public float getAlignment(int axis) { - float align; - if (axis == X_AXIS) - align = getComponent().getAlignmentX(); - else if (axis == Y_AXIS) - align = getComponent().getAlignmentY(); + float align = 0.0F; + // I'd rather throw an IllegalArgumentException for illegal axis, + // but the Harmony testsuite indicates fallback to super behaviour. + if (interceptor != null && (axis == X_AXIS || axis == Y_AXIS)) + { + if (axis == X_AXIS) + align = interceptor.getAlignmentX(); + else if (axis == Y_AXIS) + align = interceptor.getAlignmentY(); + else + assert false : "Must not reach here"; + } else - throw new IllegalArgumentException(); + align = super.getAlignment(axis); return align; } @@ -118,8 +275,6 @@ public class ComponentView extends View */ public final Component getComponent() { - if (comp == null) - comp = createComponent(); return comp; } @@ -135,49 +290,70 @@ public class ComponentView extends View */ public float getMaximumSpan(int axis) { - float span; - if (axis == X_AXIS) - span = getComponent().getMaximumSize().width; - else if (axis == Y_AXIS) - span = getComponent().getMaximumSize().height; - else - throw new IllegalArgumentException(); + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis"); + float span = 0; + if (interceptor != null) + { + if (axis == X_AXIS) + span = interceptor.getMaximumSize().width; + else if (axis == Y_AXIS) + span = interceptor.getMaximumSize().height; + else + assert false : "Must not reach here"; + } return span; } public float getMinimumSpan(int axis) { - float span; - if (axis == X_AXIS) - span = getComponent().getMinimumSize().width; - else if (axis == Y_AXIS) - span = getComponent().getMinimumSize().height; - else - throw new IllegalArgumentException(); + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis"); + float span = 0; + if (interceptor != null) + { + if (axis == X_AXIS) + span = interceptor.getMinimumSize().width; + else if (axis == Y_AXIS) + span = interceptor.getMinimumSize().height; + else + assert false : "Must not reach here"; + } return span; } public float getPreferredSpan(int axis) { - float span; - if (axis == X_AXIS) - span = getComponent().getPreferredSize().width; - else if (axis == Y_AXIS) - span = getComponent().getPreferredSize().height; - else - throw new IllegalArgumentException(); + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis"); + float span = 0; + if (interceptor != null) + { + if (axis == X_AXIS) + span = interceptor.getPreferredSize().width; + else if (axis == Y_AXIS) + span = interceptor.getPreferredSize().height; + else + assert false : "Must not reach here"; + } return span; } public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { - Element el = getElement(); - if (pos < el.getStartOffset() || pos >= el.getEndOffset()) - throw new BadLocationException("Illegal offset for this view", pos); - Rectangle r = a.getBounds(); - Component c = getComponent(); - return new Rectangle(r.x, r.y, c.getWidth(), c.getHeight()); + int p0 = getStartOffset(); + int p1 = getEndOffset(); + if (pos >= p0 && pos <= p1) + { + Rectangle viewRect = a.getBounds(); + if (pos == p1) + viewRect.x += viewRect.width; + viewRect.width = 0; + return viewRect; + } + else + throw new BadLocationException("Illegal position", pos); } /** @@ -191,8 +367,11 @@ public class ComponentView extends View */ public void paint(Graphics g, Shape a) { - Rectangle r = a.getBounds(); - getComponent().setBounds(r.x, r.y, r.width, r.height); + if (interceptor != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + interceptor.setBounds(r.x, r.y, r.width, r.height); + } } /** @@ -209,15 +388,16 @@ public class ComponentView extends View */ public void setParent(final View p) { + super.setParent(p); if (SwingUtilities.isEventDispatchThread()) - setParentImpl(p); + setParentImpl(); else SwingUtilities.invokeLater (new Runnable() { public void run() { - setParentImpl(p); + setParentImpl(); } }); } @@ -225,23 +405,41 @@ public class ComponentView extends View /** * The implementation of {@link #setParent}. This is package private to * avoid a synthetic accessor method. - * - * @param p the parent view to set */ - private void setParentImpl(View p) + void setParentImpl() { - super.setParent(p); + View p = getParent(); if (p != null) { - Component c = getComponent(); - p.getContainer().add(c); + Container c = getContainer(); + if (c != null) + { + if (interceptor == null) + { + // Create component and put it inside the interceptor. + Component created = createComponent(); + if (created != null) + { + comp = created; + interceptor = new Interceptor(comp); + } + } + if (interceptor != null) + { + // Add the interceptor to the hosting container. + if (interceptor.getParent() == null) + c.add(interceptor, this); + } + } } else { - Component c = getComponent(); - Container parent = c.getParent(); - parent.remove(c); - comp = null; + if (interceptor != null) + { + Container parent = interceptor.getParent(); + if (parent != null) + parent.remove(interceptor); + } } } @@ -259,10 +457,21 @@ public class ComponentView extends View */ public int viewToModel(float x, float y, Shape a, Position.Bias[] b) { - // The element should only have one character position and it is clear - // that this position is the position that best matches the given screen - // coordinates, simply because this view has only this one position. - Element el = getElement(); - return el.getStartOffset(); + int pos; + // I'd rather do the following. The harmony testsuite indicates + // that a simple cast is performed. + //Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + Rectangle r = (Rectangle) a; + if (x < r.x + r.width / 2) + { + b[0] = Position.Bias.Forward; + pos = getStartOffset(); + } + else + { + b[0] = Position.Bias.Backward; + pos = getEndOffset(); + } + return pos; } } diff --git a/javax/swing/text/CompositeView.java b/javax/swing/text/CompositeView.java index 6f487b898..ab587a9e1 100644 --- a/javax/swing/text/CompositeView.java +++ b/javax/swing/text/CompositeView.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing.text; -import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; @@ -57,7 +56,12 @@ public abstract class CompositeView /** * The child views of this <code>CompositeView</code>. */ - View[] children; + private View[] children; + + /** + * The number of child views. + */ + private int numChildren; /** * The allocation of this <code>View</code> minus its insets. This is @@ -70,7 +74,10 @@ public abstract class CompositeView * The insets of this <code>CompositeView</code>. This is initialized * in {@link #setInsets}. */ - Insets insets; + private short top; + private short bottom; + private short left; + private short right; /** * Creates a new <code>CompositeView</code> for the given @@ -82,7 +89,10 @@ public abstract class CompositeView { super(element); children = new View[0]; - insets = new Insets(0, 0, 0, 0); + top = 0; + bottom = 0; + left = 0; + right = 0; } /** @@ -96,16 +106,22 @@ public abstract class CompositeView */ protected void loadChildren(ViewFactory f) { - Element el = getElement(); - int count = el.getElementCount(); - View[] newChildren = new View[count]; - for (int i = 0; i < count; ++i) + if (f != null) { - Element child = el.getElement(i); - View view = f.create(child); - newChildren[i] = view; + Element el = getElement(); + int count = el.getElementCount(); + View[] newChildren = new View[count]; + for (int i = 0; i < count; ++i) + { + Element child = el.getElement(i); + View view = f.create(child); + newChildren[i] = view; + } + // I'd have called replace(0, getViewCount(), newChildren) here + // in order to replace all existing views. However according to + // Harmony's tests this is not what the RI does. + replace(0, 0, newChildren); } - replace(0, getViewCount(), newChildren); } /** @@ -129,7 +145,7 @@ public abstract class CompositeView */ public int getViewCount() { - return children.length; + return numChildren; } /** @@ -156,24 +172,42 @@ public abstract class CompositeView */ public void replace(int offset, int length, View[] views) { - // Check for null views to add. - for (int i = 0; i < views.length; ++i) - if (views[i] == null) - throw new NullPointerException("Added views must not be null"); - - int endOffset = offset + length; + // Make sure we have an array. The Harmony testsuite indicates that we + // have to do something like this. + if (views == null) + views = new View[0]; // First we set the parent of the removed children to null. + int endOffset = offset + length; for (int i = offset; i < endOffset; ++i) - children[i].setParent(null); + { + if (children[i].getParent() == this) + children[i].setParent(null); + children[i] = null; + } - View[] newChildren = new View[children.length - length + views.length]; - System.arraycopy(children, 0, newChildren, 0, offset); - System.arraycopy(views, 0, newChildren, offset, views.length); - System.arraycopy(children, offset + length, newChildren, - offset + views.length, - children.length - (offset + length)); - children = newChildren; + // Update the children array. + int delta = views.length - length; + int src = offset + length; + int numMove = numChildren - src; + int dst = src + delta; + if (numChildren + delta > children.length) + { + // Grow array. + int newLength = Math.max(2 * children.length, numChildren + delta); + View[] newChildren = new View[newLength]; + System.arraycopy(children, 0, newChildren, 0, offset); + System.arraycopy(views, 0, newChildren, offset, views.length); + System.arraycopy(children, src, newChildren, dst, numMove); + children = newChildren; + } + else + { + // Patch existing array. + System.arraycopy(children, src, children, dst, numMove); + System.arraycopy(views, 0, children, offset, views.length); + } + numChildren += delta; // Finally we set the parent of the added children to this. for (int i = 0; i < views.length; ++i) @@ -258,27 +292,6 @@ public abstract class CompositeView } /** - * A helper method for {@link #modelToView(int, Position.Bias, int, - * Position.Bias, Shape)}. This creates a default location when there is - * no child view that can take responsibility for mapping the position to - * view coordinates. Depending on the specified bias this will be the - * left or right edge of this view's allocation. - * - * @param a the allocation for this view - * @param bias the bias - * - * @return a default location - */ - private Shape createDefaultLocation(Shape a, Position.Bias bias) - { - Rectangle alloc = a.getBounds(); - Rectangle location = new Rectangle(alloc.x, alloc.y, 1, alloc.height); - if (bias == Position.Bias.Forward) - location.x = alloc.x + alloc.width; - return location; - } - - /** * Maps a region in the document into the coordinate space of the View. * * @param p1 the beginning position inside the document @@ -394,7 +407,7 @@ public abstract class CompositeView */ public int getViewIndex(int pos, Position.Bias b) { - if (b == Position.Bias.Backward && pos != 0) + if (b == Position.Bias.Backward) pos -= 1; int i = -1; if (pos >= getStartOffset() && pos < getEndOffset()) @@ -528,10 +541,10 @@ public abstract class CompositeView insideAllocation = inside; } } - inside.x = alloc.x + insets.left; - inside.y = alloc.y + insets.top; - inside.width = alloc.width - insets.left - insets.right; - inside.height = alloc.height - insets.top - insets.bottom; + inside.x = alloc.x + left; + inside.y = alloc.y + top; + inside.width = alloc.width - left - right; + inside.height = alloc.height - top - bottom; return inside; } @@ -546,39 +559,26 @@ public abstract class CompositeView */ protected void setParagraphInsets(AttributeSet attributes) { - Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent); - short left = 0; - if (l != null) - left = l.shortValue(); - Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent); - short right = 0; - if (r != null) - right = r.shortValue(); - Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove); - short top = 0; - if (t != null) - top = t.shortValue(); - Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow); - short bottom = 0; - if (b != null) - bottom = b.shortValue(); - setInsets(top, left, bottom, right); + top = (short) StyleConstants.getSpaceAbove(attributes); + bottom = (short) StyleConstants.getSpaceBelow(attributes); + left = (short) StyleConstants.getLeftIndent(attributes); + right = (short) StyleConstants.getRightIndent(attributes); } /** * Sets the insets of this <code>CompositeView</code>. * - * @param top the top inset - * @param left the left inset - * @param bottom the bottom inset - * @param right the right inset + * @param t the top inset + * @param l the left inset + * @param b the bottom inset + * @param r the right inset */ - protected void setInsets(short top, short left, short bottom, short right) + protected void setInsets(short t, short l, short b, short r) { - insets.top = top; - insets.left = left; - insets.bottom = bottom; - insets.right = right; + top = t; + left = l; + bottom = b; + right = r; } /** @@ -588,7 +588,7 @@ public abstract class CompositeView */ protected short getLeftInset() { - return (short) insets.left; + return left; } /** @@ -598,7 +598,7 @@ public abstract class CompositeView */ protected short getRightInset() { - return (short) insets.right; + return right; } /** @@ -608,7 +608,7 @@ public abstract class CompositeView */ protected short getTopInset() { - return (short) insets.top; + return top; } /** @@ -618,7 +618,7 @@ public abstract class CompositeView */ protected short getBottomInset() { - return (short) insets.bottom; + return bottom; } /** diff --git a/javax/swing/text/DefaultEditorKit.java b/javax/swing/text/DefaultEditorKit.java index 8602e69f8..aa69deca5 100644 --- a/javax/swing/text/DefaultEditorKit.java +++ b/javax/swing/text/DefaultEditorKit.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing.text; -import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; @@ -312,19 +311,21 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - int offs = t.getDocument().getLength(); - Caret c = t.getCaret(); - c.setDot(0); - c.moveDot(offs); - - try - { - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) - { - // Can't happen. - } + if (t != null) + { + int offs = t.getDocument().getLength(); + Caret c = t.getCaret(); + c.setDot(0); + c.moveDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } } } @@ -339,15 +340,18 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - c.moveDot(0); - try - { - c.setMagicCaretPosition(t.modelToView(0).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + Caret c = t.getCaret(); + c.moveDot(0); + try + { + c.setMagicCaretPosition(t.modelToView(0).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -363,16 +367,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - int offs = t.getDocument().getLength(); - Caret c = t.getCaret(); - c.moveDot(offs); - try - { - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + int offs = t.getDocument().getLength(); + Caret c = t.getCaret(); + c.moveDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -389,17 +396,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - try + if (t != null) { - int offs = Utilities.getRowStart(t, c.getDot()); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + Caret c = t.getCaret(); + try + { + int offs = Utilities.getRowStart(t, c.getDot()); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } - catch(BadLocationException ble) - { - // Can't happen. - } - } } @@ -414,17 +423,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - try - { - int offs = Utilities.getRowEnd(t, c.getDot()); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + Caret c = t.getCaret(); + try + { + int offs = Utilities.getRowEnd(t, c.getDot()); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } - } } @@ -438,20 +449,21 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - try - { - int offs1 = Utilities.getRowStart(t, c.getDot()); - int offs2 = Utilities.getRowEnd(t, c.getDot()); - - c.setDot(offs2); - c.moveDot(offs1); - - c.setMagicCaretPosition(t.modelToView(offs2).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + Caret c = t.getCaret(); + try + { + int offs1 = Utilities.getRowStart(t, c.getDot()); + int offs2 = Utilities.getRowEnd(t, c.getDot()); + c.setDot(offs2); + c.moveDot(offs1); + c.setMagicCaretPosition(t.modelToView(offs2).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -466,51 +478,52 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - int dot = c.getDot(); - - try + if (t != null) { - int wordStart = Utilities.getWordStart(t, dot); - - if (dot == wordStart) - { - // Current cursor position is on the first character in a word. - c.setDot(wordStart); - c.moveDot(Utilities.getWordEnd(t, wordStart)); - } - else + Caret c = t.getCaret(); + int dot = c.getDot(); + try { - // Current cursor position is not on the first character - // in a word. - int nextWord = Utilities.getNextWord(t, dot); - int previousWord = Utilities.getPreviousWord(t, dot); - int previousWordEnd = Utilities.getWordEnd(t, previousWord); - - // Cursor position is in the space between two words. In such a - // situation just select the space. - if (dot >= previousWordEnd && dot <= nextWord) + int wordStart = Utilities.getWordStart(t, dot); + + if (dot == wordStart) { - c.setDot(previousWordEnd); - c.moveDot(nextWord); + // Current cursor position is on the first character in a word. + c.setDot(wordStart); + c.moveDot(Utilities.getWordEnd(t, wordStart)); } else { - // Cursor position is inside a word. Just select it then. - c.setDot(previousWord); - c.moveDot(previousWordEnd); + // Current cursor position is not on the first character + // in a word. + int nextWord = Utilities.getNextWord(t, dot); + int previousWord = Utilities.getPreviousWord(t, dot); + int previousWordEnd = Utilities.getWordEnd(t, previousWord); + + // Cursor position is in the space between two words. In such a + // situation just select the space. + if (dot >= previousWordEnd && dot <= nextWord) + { + c.setDot(previousWordEnd); + c.moveDot(nextWord); + } + else + { + // Cursor position is inside a word. Just select it then. + c.setDot(previousWord); + c.moveDot(previousWordEnd); + } } - } - // If the position was updated change the magic caret position - // as well. - if (c.getDot() != dot) - c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation()); - - } - catch(BadLocationException ble) - { - // Can't happen. + // If the position was updated change the magic caret position + // as well. + if (c.getDot() != dot) + c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -715,21 +728,23 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - try - { - int offs = Utilities.getRowEnd(t, t.getCaretPosition()); - - if (offs > -1) - { - Caret c = t.getCaret(); - c.setDot(offs); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - } - catch (BadLocationException ble) - { - // Nothing to do here - } + if (t != null) + { + try + { + int offs = Utilities.getRowEnd(t, t.getCaretPosition()); + if (offs > -1) + { + Caret c = t.getCaret(); + c.setDot(offs); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + } + catch (BadLocationException ble) + { + // Nothing to do here + } + } } } @@ -744,21 +759,23 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - try - { - int offs = Utilities.getRowStart(t, t.getCaretPosition()); - - if (offs > -1) - { - Caret c = t.getCaret(); - c.setDot(offs); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - } - catch (BadLocationException ble) - { - // Do nothing here. - } + if (t != null) + { + try + { + int offs = Utilities.getRowStart(t, t.getCaretPosition()); + if (offs > -1) + { + Caret c = t.getCaret(); + c.setDot(offs); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + } + catch (BadLocationException ble) + { + // Do nothing here. + } + } } } @@ -773,16 +790,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - c.setDot(0); - try - { - c.setMagicCaretPosition(t.modelToView(0).getLocation()); - } - catch(BadLocationException ble) - { - // Can't happen. - } + if (t != null) + { + Caret c = t.getCaret(); + c.setDot(0); + try + { + c.setMagicCaretPosition(t.modelToView(0).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } } } @@ -797,16 +817,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - int offs = t.getDocument().getLength(); - Caret c = t.getCaret(); - c.setDot(offs); - try - { - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + int offs = t.getDocument().getLength(); + Caret c = t.getCaret(); + c.setDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -862,7 +885,9 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - getTextComponent(event).copy(); + JTextComponent target = getTextComponent(event); + if (target != null) + target.copy(); } } @@ -893,7 +918,9 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - getTextComponent(event).cut(); + JTextComponent target = getTextComponent(event); + if (target != null) + target.cut(); } } @@ -922,7 +949,9 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - getTextComponent(event).paste(); + JTextComponent target = getTextComponent(event); + if (target != null) + target.paste(); } } @@ -957,14 +986,26 @@ public class DefaultEditorKit extends EditorKit { // first we filter the following events: // - control characters - // - key events with the ALT modifier (FIXME: filter that too!) - int cp = event.getActionCommand().codePointAt(0); - if (Character.isISOControl(cp)) - return; - - JTextComponent t = getTextComponent(event); - if (t != null && t.isEnabled() && t.isEditable()) - t.replaceSelection(event.getActionCommand()); + // - key events with the ALT modifier + JTextComponent target = getTextComponent(event); + if ((target != null) && (event != null)) + { + if ((target.isEditable()) && (target.isEnabled())) + { + String content = event.getActionCommand(); + int mod = event.getModifiers(); + if ((content != null) && (content.length() > 0) + && (mod & ActionEvent.ALT_MASK) == 0 + && (mod & ActionEvent.CTRL_MASK) == 0) + { + char c = content.charAt(0); + if ((c >= 0x20) && (c != 0x7F)) + { + target.replaceSelection(content); + } + } + } + } } } @@ -992,7 +1033,8 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - t.replaceSelection("\n"); + if (t != null) + t.replaceSelection("\n"); } } @@ -1047,7 +1089,8 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - t.replaceSelection("\t"); + if (t != null) + t.replaceSelection("\t"); } } diff --git a/javax/swing/text/DefaultFormatter.java b/javax/swing/text/DefaultFormatter.java index 19994e21b..bf7c02a00 100644 --- a/javax/swing/text/DefaultFormatter.java +++ b/javax/swing/text/DefaultFormatter.java @@ -216,7 +216,7 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter */ public DefaultFormatter() { - commitsOnValidEdit = true; + commitsOnValidEdit = false; overwriteMode = true; allowsInvalid = true; } diff --git a/javax/swing/text/DefaultStyledDocument.java b/javax/swing/text/DefaultStyledDocument.java index 5705bde17..367666053 100644 --- a/javax/swing/text/DefaultStyledDocument.java +++ b/javax/swing/text/DefaultStyledDocument.java @@ -497,11 +497,6 @@ public class DefaultStyledDocument extends AbstractDocument implements private int pos; /** - * The ElementChange that describes the latest changes. - */ - private DefaultDocumentEvent documentEvent; - - /** * The parent of the fracture. */ private Element fracturedParent; @@ -835,13 +830,9 @@ public class DefaultStyledDocument extends AbstractDocument implements */ public void change(int offset, int length, DefaultDocumentEvent ev) { - if (length == 0) - return; - this.offset = offset; - this.pos = offset; - this.length = length; - documentEvent = ev; + prepareEdit(offset, length); changeUpdate(); + finishEdit(ev); } /** @@ -1258,8 +1249,6 @@ public class DefaultStyledDocument extends AbstractDocument implements { int len = tag.getLength(); int dir = tag.getDirection(); - AttributeSet tagAtts = tag.getAttributes(); - if (dir == ElementSpec.JoinNextDirection) { if (! edit.isFracture) @@ -1603,56 +1592,6 @@ public class DefaultStyledDocument extends AbstractDocument implements } - /** - * Recreates all the elements from the parent to the element on the top of - * the stack, starting from startFrom with the starting offset of - * startOffset. - * - * @param recreate - - * the elements to recreate - * @param parent - - * the element to add the new elements to - * @param startFrom - - * where to start recreating from - * @param startOffset - - * the offset of the first element - * @return the array of added elements - */ - private Element[] recreateAfterFracture(Element[] recreate, - BranchElement parent, int startFrom, - int startOffset) - { - Element[] added = new Element[recreate.length - startFrom]; - int j = 0; - for (int i = startFrom; i < recreate.length; i++) - { - Element curr = recreate[i]; - int len = curr.getEndOffset() - curr.getStartOffset(); - if (curr instanceof LeafElement) - added[j] = createLeafElement(parent, curr.getAttributes(), - startOffset, startOffset + len); - else - { - BranchElement br = - (BranchElement) createBranchElement(parent, - curr.getAttributes()); - int bSize = curr.getElementCount(); - for (int k = 0; k < bSize; k++) - { - Element bCurr = curr.getElement(k); - Element[] add = recreateAfterFracture(new Element[] { bCurr }, br, 0, - startOffset); - br.replace(0, 0, add); - - } - added[j] = br; - } - startOffset += len; - j++; - } - - return added; - } } @@ -1985,7 +1924,6 @@ public class DefaultStyledDocument extends AbstractDocument implements // start and ends at an element end. buffer.change(offset, length, ev); - Element root = getDefaultRootElement(); // Visit all paragraph elements within the specified interval int end = offset + length; Element curr; diff --git a/javax/swing/text/FieldView.java b/javax/swing/text/FieldView.java index f41f90130..0a078e53d 100644 --- a/javax/swing/text/FieldView.java +++ b/javax/swing/text/FieldView.java @@ -45,8 +45,6 @@ import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import javax.swing.BoundedRangeModel; import javax.swing.JTextField; @@ -225,7 +223,7 @@ public class FieldView extends PlainView public int getResizeWeight(int axis) { - return axis = axis == X_AXIS ? 1 : 0; + return axis == X_AXIS ? 1 : 0; } public Shape modelToView(int pos, Shape a, Position.Bias bias) diff --git a/javax/swing/text/FlowView.java b/javax/swing/text/FlowView.java index 3de95ed7f..085b0ac45 100644 --- a/javax/swing/text/FlowView.java +++ b/javax/swing/text/FlowView.java @@ -329,6 +329,16 @@ public abstract class FlowView extends BoxView { super(el, axis); } + + /** + * Overridden to return the attributes of the parent + * (== the FlowView instance). + */ + public AttributeSet getAttributes() + { + View p = getParent(); + return p != null ? p.getAttributes() : null; + } } /** diff --git a/javax/swing/text/GapContent.java b/javax/swing/text/GapContent.java index 7b1502777..990e9d464 100644 --- a/javax/swing/text/GapContent.java +++ b/javax/swing/text/GapContent.java @@ -165,7 +165,7 @@ public class GapContent */ int getOffset() { - assert mark == 0 || mark < gapStart || mark >= gapEnd : + assert mark == 0 || mark <= gapStart || mark >= gapEnd : "Invalid mark: " + mark + ", gapStart: " + gapStart + ", gapEnd: " + gapEnd; @@ -1013,7 +1013,7 @@ public class GapContent * * @return the index of the first occurance of o in l, or -i + 1 if not found */ - private int search(List l, Object o) + int search(List l, Object o) { int i = Collections.binarySearch(l, o); while (i > 0) diff --git a/javax/swing/text/GlyphView.java b/javax/swing/text/GlyphView.java index 65025dd08..385f50bf6 100644 --- a/javax/swing/text/GlyphView.java +++ b/javax/swing/text/GlyphView.java @@ -46,7 +46,6 @@ import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Toolkit; -import java.text.BreakIterator; import javax.swing.SwingConstants; import javax.swing.event.DocumentEvent; @@ -300,28 +299,19 @@ public class GlyphView extends View implements TabableView, Cloneable g.setColor(view.getForeground()); g.setFont(view.getFont()); int ascent = g.getFontMetrics().getAscent(); - if (view.isSuperscript()) - // TODO: Adjust font for superscripting. - Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent - height / 2, - g, tabEx, txt.offset); - else if (view.isSubscript()) - // TODO: Adjust font for subscripting. - Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent + height / 2, - g, tabEx, txt.offset); - else - Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx, - txt.offset); + Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx, + txt.offset); if (view.isStrikeThrough()) { int strikeHeight = (int) (getAscent(view) / 2); - g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width, + g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.x + width, bounds.y + strikeHeight); } if (view.isUnderline()) { int lineHeight = (int) getAscent(view); - g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width, + g.drawLine(bounds.x, bounds.y + lineHeight, bounds.x + width, bounds.y + lineHeight); } g.setColor(oldColor); @@ -385,7 +375,6 @@ public class GlyphView extends View implements TabableView, Cloneable public float getSpan(GlyphView view, int p0, int p1, TabExpander te, float x) { - Element el = view.getElement(); Font font = view.getFont(); FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font); Segment txt = view.getText(p0, p1); @@ -466,7 +455,7 @@ public class GlyphView extends View implements TabableView, Cloneable { Rectangle b = a.getBounds(); int pos = getBoundedPosition(v, v.getStartOffset(), b.x, x - b.x); - return pos; + return pos + v.getStartOffset(); } } @@ -574,19 +563,24 @@ public class GlyphView extends View implements TabableView, Cloneable float span = 0; checkPainter(); GlyphPainter painter = getGlyphPainter(); - if (axis == X_AXIS) + switch (axis) { - Element el = getElement(); + case X_AXIS: TabExpander tabEx = null; View parent = getParent(); if (parent instanceof TabExpander) tabEx = (TabExpander) parent; span = painter.getSpan(this, getStartOffset(), getEndOffset(), tabEx, 0.F); + break; + case Y_AXIS: + span = painter.getHeight(this); + if (isSuperscript()) + span += span / 3; + break; + default: + throw new IllegalArgumentException("Illegal axis"); } - else - span = painter.getHeight(this); - return span; } @@ -762,16 +756,19 @@ public class GlyphView extends View implements TabableView, Cloneable */ public Font getFont() { - Element el = getElement(); - AttributeSet atts = el.getAttributes(); - String family = StyleConstants.getFontFamily(atts); - int size = StyleConstants.getFontSize(atts); - int style = Font.PLAIN; - if (StyleConstants.isBold(atts)) - style |= Font.BOLD; - if (StyleConstants.isItalic(atts)) - style |= Font.ITALIC; - Font font = new Font(family, style, size); + Document doc = getDocument(); + Font font = null; + if (doc instanceof StyledDocument) + { + StyledDocument styledDoc = (StyledDocument) doc; + font = styledDoc.getFont(getAttributes()); + } + else + { + Container c = getContainer(); + if (c != null) + font = c.getFont(); + } return font; } @@ -908,10 +905,8 @@ public class GlyphView extends View implements TabableView, Cloneable return this; checkPainter(); - GlyphPainter painter = getGlyphPainter(); // Try to find a suitable line break. - BreakIterator lineBreaker = BreakIterator.getLineInstance(); Segment txt = new Segment(); try { @@ -1050,14 +1045,21 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getAlignment(int axis) { + checkPainter(); float align; if (axis == Y_AXIS) { - checkPainter(); GlyphPainter painter = getGlyphPainter(); float height = painter.getHeight(this); float descent = painter.getDescent(this); - align = (height - descent) / height; + float ascent = painter.getAscent(this); + if (isSuperscript()) + align = 1.0F; + else if (isSubscript()) + align = height > 0 ? (height - (descent + (ascent / 2))) / height + : 0; + else + align = height > 0 ? (height - descent) / height : 0; } else align = super.getAlignment(axis); diff --git a/javax/swing/text/InternationalFormatter.java b/javax/swing/text/InternationalFormatter.java index 8db435c18..d6f2359e6 100644 --- a/javax/swing/text/InternationalFormatter.java +++ b/javax/swing/text/InternationalFormatter.java @@ -285,7 +285,7 @@ public class InternationalFormatter if (minimum != null && minimum.compareTo(o) > 0) throw new ParseException("The value may not be less than the" + " specified minimum", 0); - if (maximum != null && minimum.compareTo(o) < 0) + if (maximum != null && maximum.compareTo(o) < 0) throw new ParseException("The value may not be greater than the" + " specified maximum", 0); return o; diff --git a/javax/swing/text/LabelView.java b/javax/swing/text/LabelView.java index a00a49c24..7cfeae862 100644 --- a/javax/swing/text/LabelView.java +++ b/javax/swing/text/LabelView.java @@ -114,28 +114,24 @@ public class LabelView extends GlyphView */ protected void setPropertiesFromAttributes() { - Element el = getElement(); - AttributeSet atts = el.getAttributes(); - // We cannot use StyleConstants.getBackground() here, because that returns - // BLACK as default (when background == null). What we need is the - // background setting of the text component instead, which is what we get - // when background == null anyway. - background = (Color) atts.getAttribute(StyleConstants.Background); - foreground = StyleConstants.getForeground(atts); + AttributeSet atts = getAttributes(); setStrikeThrough(StyleConstants.isStrikeThrough(atts)); setSubscript(StyleConstants.isSubscript(atts)); setSuperscript(StyleConstants.isSuperscript(atts)); setUnderline(StyleConstants.isUnderline(atts)); - // Determine the font. - String family = StyleConstants.getFontFamily(atts); - int size = StyleConstants.getFontSize(atts); - int style = Font.PLAIN; - if (StyleConstants.isBold(atts)) - style |= Font.BOLD; - if (StyleConstants.isItalic(atts)) - style |= Font.ITALIC; - font = new Font(family, style, size); + // Determine the font and colors. + Document d = getDocument(); + if (d instanceof StyledDocument) + { + StyledDocument doc = (StyledDocument) d; + font = doc.getFont(atts); + if (atts.isDefined(StyleConstants.Background)) + background = doc.getBackground(atts); + else + background = null; + foreground = doc.getForeground(atts); + } valid = true; } @@ -258,6 +254,8 @@ public class LabelView extends GlyphView */ public boolean isSubscript() { + if (! valid) + setPropertiesFromAttributes(); return subscript; } diff --git a/javax/swing/text/MaskFormatter.java b/javax/swing/text/MaskFormatter.java index d12b9ea29..581cceb61 100644 --- a/javax/swing/text/MaskFormatter.java +++ b/javax/swing/text/MaskFormatter.java @@ -110,9 +110,7 @@ public class MaskFormatter extends DefaultFormatter */ public MaskFormatter (String mask) throws java.text.ParseException { - // Override super's default behaviour, in MaskFormatter the default - // is not to allow invalid values - setAllowsInvalid(false); + this(); setMask (mask); } @@ -307,60 +305,124 @@ public class MaskFormatter extends DefaultFormatter */ public Object stringToValue (String value) throws ParseException { - int vLength = value.length(); - - // For value to be a valid it must be the same length as the mask - // note this doesn't take into account symbols that occupy more than - // one character, this is something we may possibly need to fix. - if (maskLength != vLength) - throw new ParseException ("stringToValue passed invalid value", vLength); - - // Check if the value is valid according to the mask and valid/invalid - // sets. - try - { - convertValue(value, false); - } - catch (ParseException pe) - { - throw new ParseException("stringToValue passed invalid value", - pe.getErrorOffset()); - } - - if (!getValueContainsLiteralCharacters()) - value = stripLiterals(value); - return super.stringToValue(value); + return super.stringToValue(convertStringToValue(value)); } - /** - * Strips the literal characters from the given String. - * @param value the String to strip - * @return the stripped String - */ - String stripLiterals(String value) + private String convertStringToValue(String value) + throws ParseException { StringBuffer result = new StringBuffer(); - for (int i = 0; i < value.length(); i++) + char valueChar; + boolean isPlaceHolder; + + int length = mask.length(); + for (int i = 0, j = 0; j < length; j++) { - // Only append the characters that don't correspond to literal - // characters in the mask. - switch (mask.charAt(i)) + char maskChar = mask.charAt(j); + + if (i < value.length()) + { + isPlaceHolder = false; + valueChar = value.charAt(i); + if (maskChar != ESCAPE_CHAR && maskChar != valueChar) + { + if (invalidChars != null + && invalidChars.indexOf(valueChar) != -1) + throw new ParseException("Invalid character: " + valueChar, i); + if (validChars != null + && validChars.indexOf(valueChar) == -1) + throw new ParseException("Invalid character: " + valueChar, i); + } + } + else if (placeHolder != null && i < placeHolder.length()) + { + isPlaceHolder = true; + valueChar = placeHolder.charAt(i); + } + else + { + isPlaceHolder = true; + valueChar = placeHolderChar; + } + + // This switch block on the mask character checks that the character + // within <code>value</code> at that point is valid according to the + // mask and also converts to upper/lowercase as needed. + switch (maskChar) { case NUM_CHAR: + if (! Character.isDigit(valueChar)) + throw new ParseException("Number expected: " + valueChar, i); + result.append(valueChar); + i++; + break; case UPPERCASE_CHAR: + if (! Character.isLetter(valueChar)) + throw new ParseException("Letter expected", i); + result.append(Character.toUpperCase(valueChar)); + i++; + break; case LOWERCASE_CHAR: + if (! Character.isLetter(valueChar)) + throw new ParseException("Letter expected", i); + result.append(Character.toLowerCase(valueChar)); + i++; + break; case ALPHANUM_CHAR: + if (! Character.isLetterOrDigit(valueChar)) + throw new ParseException("Letter or number expected", i); + result.append(valueChar); + i++; + break; case LETTER_CHAR: + if (! Character.isLetter(valueChar)) + throw new ParseException("Letter expected", i); + result.append(valueChar); + i++; + break; case HEX_CHAR: + if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) + throw new ParseException("Hexadecimal character expected", i); + result.append(valueChar); + i++; + break; case ANYTHING_CHAR: - result.append(value.charAt(i)); + result.append(valueChar); + i++; + break; + case ESCAPE_CHAR: + // Escape character, check the next character to make sure that + // the literals match + j++; + if (j < length) + { + maskChar = mask.charAt(j); + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + { + result.append(maskChar); + } + i++; + } + else if (! isPlaceHolder) + throw new ParseException("Bad match at trailing escape: ", i); break; default: + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + { + result.append(maskChar); + } + i++; } } return result.toString(); } - + /** * Returns a String representation of the Object value based on the mask. * @@ -368,21 +430,10 @@ public class MaskFormatter extends DefaultFormatter * @throws ParseException if value is invalid for this mask and valid/invalid * character sets */ - public String valueToString (Object value) throws ParseException + public String valueToString(Object value) throws ParseException { - String result = super.valueToString(value); - int rLength = result.length(); - - // If value is longer than the mask, truncate it. Note we may need to - // account for symbols that are more than one character long. - if (rLength > maskLength) - result = result.substring(0, maskLength); - - // Verify the validity and convert to upper/lowercase as needed. - result = convertValue(result, true); - if (rLength < maskLength) - return pad(result, rLength); - return result; + String string = value != null ? value.toString() : ""; + return convertValueToString(string); } /** @@ -390,194 +441,116 @@ public class MaskFormatter extends DefaultFormatter * sure that it is valid. If <code>convert</code> is true, it also * converts letters to upper/lowercase as required by the mask. * @param value the String to convert - * @param convert true if we should convert letters to upper/lowercase * @return the converted String * @throws ParseException if the given String isn't valid for the mask */ - String convertValue(String value, boolean convert) throws ParseException + private String convertValueToString(String value) + throws ParseException { - StringBuffer result = new StringBuffer(value); - char markChar; - char resultChar; - boolean literal; - - // this boolean is specifically to avoid calling the isCharValid method - // when neither invalidChars or validChars has been set - boolean checkCharSets = (invalidChars != null || validChars != null); + StringBuffer result = new StringBuffer(); + char valueChar; + boolean isPlaceHolder; - for (int i = 0, j = 0; i < value.length(); i++, j++) + int length = mask.length(); + for (int i = 0, j = 0; j < length; j++) { - literal = false; - resultChar = result.charAt(i); + char maskChar = mask.charAt(j); + if (i < value.length()) + { + isPlaceHolder = false; + valueChar = value.charAt(i); + if (maskChar != ESCAPE_CHAR && valueChar != maskChar) + { + if (invalidChars != null + && invalidChars.indexOf(valueChar) != -1) + throw new ParseException("Invalid character: " + valueChar, + i); + if (validChars != null && validChars.indexOf(valueChar) == -1) + throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar, + i); + } + } + else if (placeHolder != null && i < placeHolder.length()) + { + isPlaceHolder = true; + valueChar = placeHolder.charAt(i); + } + else + { + isPlaceHolder = true; + valueChar = placeHolderChar; + } + // This switch block on the mask character checks that the character // within <code>value</code> at that point is valid according to the // mask and also converts to upper/lowercase as needed. - switch (mask.charAt(j)) + switch (maskChar) { case NUM_CHAR: - if (!Character.isDigit(resultChar)) - throw new ParseException("Number expected", i); + if ( ! isPlaceHolder && ! Character.isDigit(valueChar)) + throw new ParseException("Number expected: " + valueChar, i); + result.append(valueChar); + i++; break; case UPPERCASE_CHAR: - if (!Character.isLetter(resultChar)) + if (! Character.isLetter(valueChar)) throw new ParseException("Letter expected", i); - if (convert) - result.setCharAt(i, Character.toUpperCase(resultChar)); + result.append(Character.toUpperCase(valueChar)); + i++; break; case LOWERCASE_CHAR: - if (!Character.isLetter(resultChar)) + if (! Character.isLetter(valueChar)) throw new ParseException("Letter expected", i); - if (convert) - result.setCharAt(i, Character.toLowerCase(resultChar)); + result.append(Character.toLowerCase(valueChar)); + i++; break; case ALPHANUM_CHAR: - if (!Character.isLetterOrDigit(resultChar)) + if (! Character.isLetterOrDigit(valueChar)) throw new ParseException("Letter or number expected", i); + result.append(valueChar); + i++; break; case LETTER_CHAR: - if (!Character.isLetter(resultChar)) + if (! Character.isLetter(valueChar)) throw new ParseException("Letter expected", i); + result.append(valueChar); + i++; break; case HEX_CHAR: - if (hexString.indexOf(resultChar) == -1) + if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) throw new ParseException("Hexadecimal character expected", i); + result.append(valueChar); + i++; break; case ANYTHING_CHAR: + result.append(valueChar); + i++; break; case ESCAPE_CHAR: // Escape character, check the next character to make sure that // the literals match j++; - literal = true; - if (resultChar != mask.charAt(j)) - throw new ParseException ("Invalid character: "+resultChar, i); + if (j < length) + { + maskChar = mask.charAt(j); + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + i++; + result.append(maskChar); + } break; default: - literal = true; - if (!getValueContainsLiteralCharacters() && convert) - throw new ParseException ("Invalid character: "+resultChar, i); - else if (resultChar != mask.charAt(j)) - throw new ParseException ("Invalid character: "+resultChar, i); + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + i++; + result.append(maskChar); } - // If necessary, check if the character is valid. - if (!literal && checkCharSets && !isCharValid(resultChar)) - throw new ParseException("invalid character: "+resultChar, i); - - } - return result.toString(); - } - - /** - * Convenience method used by many other methods to check if a character is - * valid according to the mask, the validChars, and the invalidChars. To - * be valid a character must: - * 1. be allowed by the mask - * 2. be present in any non-null validChars String - * 3. not be present in any non-null invalidChars String - * @param testChar the character to test - * @return true if the character is valid - */ - boolean isCharValid(char testChar) - { - char lower = Character.toLowerCase(testChar); - char upper = Character.toUpperCase(testChar); - // If validChars isn't null, the character must appear in it. - if (validChars != null) - if (validChars.indexOf(lower) == -1 && validChars.indexOf(upper) == -1) - return false; - // If invalidChars isn't null, the character must not appear in it. - if (invalidChars != null) - if (invalidChars.indexOf(lower) != -1 - || invalidChars.indexOf(upper) != -1) - return false; - return true; - } - - /** - * Pads the value with literals, the placeholder String and/or placeholder - * character as appropriate. - * @param value the value to pad - * @param currLength the current length of the value - * @return the padded String - */ - String pad (String value, int currLength) - { - StringBuffer result = new StringBuffer(value); - int index = currLength; - while (result.length() < maskLength) - { - // The character used to pad may be a literal, a character from the - // place holder string, or the place holder character. getPadCharAt - // will find the proper one for us. - result.append (getPadCharAt(index)); - index++; } return result.toString(); } - /** - * Returns the character with which to pad the value at the given index - * position. If the mask has a literal at this position, this is returned - * otherwise if the place holder string is initialized and is longer than - * <code>i</code> characters then the character at position <code>i</code> - * from this String is returned. Else, the place holder character is - * returned. - * @param i the index at which we want to pad the value - * @return the character with which we should pad the value - */ - char getPadCharAt(int i) - { - boolean escaped = false; - int target = i; - char maskChar; - int holderLength = placeHolder == null ? -1 : placeHolder.length(); - // We must iterate through the mask from the beginning, because the given - // index doesn't account for escaped characters. For example, with the - // mask "1A'A''A1" index 2 refers to the literalized A, not to the - // single quotation. - for (int n = 0; n < mask.length(); n++) - { - maskChar = mask.charAt(n); - if (maskChar == ESCAPE_CHAR && !escaped) - { - target++; - escaped = true; - } - else if (escaped == true) - { - // Check if target == n which means we've come to the character - // we want to return and since it is a literal (because escaped - // is true), we return it. - if (target == n) - return maskChar; - escaped = false; - } - if (target == n) - { - // We've come to the character we want to return. It wasn't - // escaped so if it isn't a literal we should return either - // the character from place holder string or the place holder - // character, depending on whether or not the place holder - // string is long enough. - switch (maskChar) - { - case NUM_CHAR: - case UPPERCASE_CHAR: - case LOWERCASE_CHAR: - case ALPHANUM_CHAR: - case LETTER_CHAR: - case HEX_CHAR: - case ANYTHING_CHAR: - if (holderLength > i) - return placeHolder.charAt(i); - else - return placeHolderChar; - default: - return maskChar; - } - } - } - // This shouldn't happen - throw new AssertionError("MaskFormatter.getMaskCharAt failed"); - } } diff --git a/javax/swing/text/ParagraphView.java b/javax/swing/text/ParagraphView.java index c4857863d..b0b4b246e 100644 --- a/javax/swing/text/ParagraphView.java +++ b/javax/swing/text/ParagraphView.java @@ -40,6 +40,7 @@ package javax.swing.text; import java.awt.Shape; +import javax.swing.SizeRequirements; import javax.swing.event.DocumentEvent; /** @@ -64,11 +65,39 @@ public class ParagraphView extends FlowView implements TabExpander super(el, X_AXIS); } + /** + * Overridden to adjust when we are the first line, and firstLineIndent + * is not 0. + */ + public short getLeftInset() + { + short leftInset = super.getLeftInset(); + View parent = getParent(); + if (parent != null) + { + if (parent.getView(0) == this) + leftInset += firstLineIndent; + } + return leftInset; + } + public float getAlignment(int axis) { float align; if (axis == X_AXIS) - align = 0.0F; // TODO: Implement according to justification + switch (justification) + { + case StyleConstants.ALIGN_RIGHT: + align = 1.0F; + break; + case StyleConstants.ALIGN_CENTER: + case StyleConstants.ALIGN_JUSTIFIED: + align = 0.5F; + break; + case StyleConstants.ALIGN_LEFT: + default: + align = 0.0F; + } else align = super.getAlignment(axis); return align; @@ -107,6 +136,27 @@ public class ParagraphView extends FlowView implements TabExpander return index; } + + /** + * Overridden to perform a baseline layout. The normal BoxView layout + * isn't completely suitable for rows. + */ + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + baselineLayout(targetSpan, axis, offsets, spans); + } + + /** + * Overridden to perform a baseline layout. The normal BoxView layout + * isn't completely suitable for rows. + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements r) + { + return baselineRequirements(axis, r); + } + protected void loadChildren(ViewFactory vf) { // Do nothing here. The children are added while layouting. @@ -192,11 +242,14 @@ public class ParagraphView extends FlowView implements TabExpander * * @param ev the document event * @param a the allocation of this view - * @param fv the view factory to use for creating new child views + * @param vf the view factory to use for creating new child views */ - public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory fv) + public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf) { setPropertiesFromAttributes(); + layoutChanged(X_AXIS); + layoutChanged(Y_AXIS); + super.changedUpdate(ev, a, vf); } /** diff --git a/javax/swing/text/PlainView.java b/javax/swing/text/PlainView.java index 5d0ce4a37..e048d5f71 100644 --- a/javax/swing/text/PlainView.java +++ b/javax/swing/text/PlainView.java @@ -280,7 +280,6 @@ public class PlainView extends View implements TabExpander // FIXME: Text may be scrolled. Document document = textComponent.getDocument(); Element root = getElement(); - int y = rect.y + metrics.getAscent(); int height = metrics.getHeight(); // For layered highlighters we need to paint the layered highlights @@ -292,7 +291,16 @@ public class PlainView extends View implements TabExpander int count = root.getElementCount(); - for (int i = 0; i < count; i++) + // Determine first and last line inside the clip. + Rectangle clip = g.getClipBounds(); + SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height, + clip); + int line0 = (clip.y - rect.y) / height; + line0 = Math.max(0, Math.min(line0, count - 1)); + int line1 = (clip.y + clip.height - rect.y) / height; + line1 = Math.max(0, Math.min(line1, count - 1)); + int y = rect.y + metrics.getAscent() + height * line0; + for (int i = line0; i <= line1; i++) { if (hl != null) { diff --git a/javax/swing/text/Position.java b/javax/swing/text/Position.java index bb1449e18..d02eb834d 100644 --- a/javax/swing/text/Position.java +++ b/javax/swing/text/Position.java @@ -42,8 +42,8 @@ public interface Position { static final class Bias { - public static final Bias Backward = new Bias("backward"); - public static final Bias Forward = new Bias("forward"); + public static final Bias Backward = new Bias("Backward"); + public static final Bias Forward = new Bias("Forward"); private String name; diff --git a/javax/swing/text/SimpleAttributeSet.java b/javax/swing/text/SimpleAttributeSet.java index 85556b5da..701fa8a7c 100644 --- a/javax/swing/text/SimpleAttributeSet.java +++ b/javax/swing/text/SimpleAttributeSet.java @@ -123,9 +123,17 @@ public class SimpleAttributeSet */ public Object clone() { - SimpleAttributeSet s = new SimpleAttributeSet(); - s.tab = (Hashtable) tab.clone(); - return s; + SimpleAttributeSet attr = null; + try + { + attr = (SimpleAttributeSet) super.clone(); + attr.tab = (Hashtable) tab.clone(); + } + catch (CloneNotSupportedException ex) + { + assert false; + } + return attr; } /** diff --git a/javax/swing/text/StringContent.java b/javax/swing/text/StringContent.java index 8014dc3bc..4a3f9d752 100644 --- a/javax/swing/text/StringContent.java +++ b/javax/swing/text/StringContent.java @@ -39,6 +39,9 @@ exception statement from your version. */ package javax.swing.text; import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.Vector; @@ -57,6 +60,76 @@ import javax.swing.undo.UndoableEdit; public final class StringContent implements AbstractDocument.Content, Serializable { + /** + * Stores a reference to a mark that can be resetted to the original value + * after a mark has been moved. This is used for undoing actions. + */ + private class UndoPosRef + { + /** + * The mark that might need to be reset. + */ + private Mark mark; + + /** + * The original offset to reset the mark to. + */ + private int undoOffset; + + /** + * Creates a new UndoPosRef. + * + * @param m the mark + */ + UndoPosRef(Mark m) + { + mark = m; + undoOffset = mark.mark; + } + + /** + * Resets the position of the mark to the value that it had when + * creating this UndoPosRef. + */ + void reset() + { + mark.mark = undoOffset; + } + } + + /** + * Holds a mark into the buffer that is used by StickyPosition to find + * the actual offset of the position. This is pulled out of the + * GapContentPosition object so that the mark and position can be handled + * independently, and most important, so that the StickyPosition can + * be garbage collected while we still hold a reference to the Mark object. + */ + private class Mark + { + /** + * The actual mark into the buffer. + */ + int mark; + + + /** + * The number of GapContentPosition object that reference this mark. If + * it reaches zero, it get's deleted by + * {@link StringContent#garbageCollect()}. + */ + int refCount; + + /** + * Creates a new Mark object for the specified offset. + * + * @param offset the offset + */ + Mark(int offset) + { + mark = offset; + } + } + /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 4755994433709540381L; @@ -65,7 +138,12 @@ public final class StringContent private int count; - private Vector positions = new Vector(); + /** + * Holds the marks for the positions. + * + * This is package private to avoid accessor methods. + */ + Vector marks; private class InsertUndo extends AbstractUndoableEdit { @@ -75,6 +153,8 @@ public final class StringContent private String redoContent; + private Vector positions; + public InsertUndo(int start, int length) { super(); @@ -87,10 +167,10 @@ public final class StringContent super.undo(); try { - StringContent.this.checkLocation(this.start, this.length); - this.redoContent = new String(StringContent.this.content, this.start, - this.length); - StringContent.this.remove(this.start, this.length); + if (marks != null) + positions = getPositionsInRange(null, start, length); + redoContent = getString(start, length); + remove(start, length); } catch (BadLocationException b) { @@ -103,7 +183,13 @@ public final class StringContent super.redo(); try { - StringContent.this.insertString(this.start, this.redoContent); + insertString(start, redoContent); + redoContent = null; + if (positions != null) + { + updateUndoPositions(positions); + positions = null; + } } catch (BadLocationException b) { @@ -115,14 +201,19 @@ public final class StringContent private class RemoveUndo extends AbstractUndoableEdit { private int start; - + private int len; private String undoString; + Vector positions; + public RemoveUndo(int start, String str) { super(); this.start = start; + len = str.length(); this.undoString = str; + if (marks != null) + positions = getPositionsInRange(null, start, str.length()); } public void undo() @@ -131,6 +222,12 @@ public final class StringContent try { StringContent.this.insertString(this.start, this.undoString); + if (positions != null) + { + updateUndoPositions(positions); + positions = null; + } + undoString = null; } catch (BadLocationException bad) { @@ -143,8 +240,10 @@ public final class StringContent super.redo(); try { - int end = this.undoString.length(); - StringContent.this.remove(this.start, end); + undoString = getString(start, len); + if (marks != null) + positions = getPositionsInRange(null, start, len); + remove(this.start, len); } catch (BadLocationException bad) { @@ -155,17 +254,18 @@ public final class StringContent private class StickyPosition implements Position { - private int offset = -1; + Mark mark; public StickyPosition(int offset) { - this.offset = offset; - } + // Try to make space. + garbageCollect(); - // This is package-private to avoid an accessor method. - void setOffset(int offset) - { - this.offset = this.offset >= 0 ? offset : -1; + mark = new Mark(offset); + mark.refCount++; + marks.add(mark); + + new WeakReference(this, queueOfDeath); } /** @@ -173,11 +273,25 @@ public final class StringContent */ public int getOffset() { - return offset < 0 ? 0 : offset; + return mark.mark; } } /** + * Used in {@link #remove(int,int)}. + */ + private static final char[] EMPTY = new char[0]; + + /** + * Queues all references to GapContentPositions that are about to be + * GC'ed. This is used to remove the corresponding marks from the + * positionMarks array if the number of references to that mark reaches zero. + * + * This is package private to avoid accessor synthetic methods. + */ + ReferenceQueue queueOfDeath; + + /** * Creates a new instance containing the string "\n". This is equivalent * to calling {@link #StringContent(int)} with an <code>initialLength</code> * of 10. @@ -196,6 +310,7 @@ public final class StringContent public StringContent(int initialLength) { super(); + queueOfDeath = new ReferenceQueue(); if (initialLength < 1) initialLength = 1; this.content = new char[initialLength]; @@ -207,14 +322,13 @@ public final class StringContent int offset, int length) { - Vector refPos = new Vector(); - Iterator iter = this.positions.iterator(); + Vector refPos = v == null ? new Vector() : v; + Iterator iter = marks.iterator(); while(iter.hasNext()) { - Position p = (Position) iter.next(); - if ((offset <= p.getOffset()) - && (p.getOffset() <= (offset + length))) - refPos.add(p); + Mark m = (Mark) iter.next(); + if (offset <= m.mark && m.mark <= offset + length) + refPos.add(new UndoPosRef(m)); } return refPos; } @@ -231,10 +345,10 @@ public final class StringContent */ public Position createPosition(int offset) throws BadLocationException { - if (offset < this.count || offset > this.count) - checkLocation(offset, 0); + // Lazily create marks vector. + if (marks == null) + marks = new Vector(); StickyPosition sp = new StickyPosition(offset); - this.positions.add(sp); return sp; } @@ -246,7 +360,7 @@ public final class StringContent */ public int length() { - return this.count; + return count; } /** @@ -268,27 +382,23 @@ public final class StringContent if (str == null) throw new NullPointerException(); char[] insert = str.toCharArray(); - char[] temp = new char[this.content.length + insert.length]; - this.count += insert.length; - // Copy array and insert the string. - if (where > 0) - System.arraycopy(this.content, 0, temp, 0, where); - System.arraycopy(insert, 0, temp, where, insert.length); - System.arraycopy(this.content, where, temp, (where + insert.length), - (temp.length - where - insert.length)); - if (this.content.length < temp.length) - this.content = new char[temp.length]; - // Copy the result in the original char array. - System.arraycopy(temp, 0, this.content, 0, temp.length); + replace(where, 0, insert); + // Move all the positions. - Vector refPos = getPositionsInRange(this.positions, where, - temp.length - where); - Iterator iter = refPos.iterator(); - while (iter.hasNext()) + if (marks != null) { - StickyPosition p = (StickyPosition)iter.next(); - p.setOffset(p.getOffset() + str.length()); + Iterator iter = marks.iterator(); + int start = where; + if (start == 0) + start = 1; + while (iter.hasNext()) + { + Mark m = (Mark) iter.next(); + if (m.mark >= start) + m.mark += str.length(); + } } + InsertUndo iundo = new InsertUndo(where, insert.length); return iundo; } @@ -308,32 +418,51 @@ public final class StringContent public UndoableEdit remove(int where, int nitems) throws BadLocationException { checkLocation(where, nitems + 1); - char[] temp = new char[(this.content.length - nitems)]; - this.count = this.count - nitems; RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where, nitems)); - // Copy array. - System.arraycopy(this.content, 0, temp, 0, where); - System.arraycopy(this.content, where + nitems, temp, where, - this.content.length - where - nitems); - this.content = new char[temp.length]; - // Then copy the result in the original char array. - System.arraycopy(temp, 0, this.content, 0, this.content.length); + + replace(where, nitems, EMPTY); // Move all the positions. - Vector refPos = getPositionsInRange(this.positions, where, - this.content.length + nitems - where); - Iterator iter = refPos.iterator(); - while (iter.hasNext()) + if (marks != null) { - StickyPosition p = (StickyPosition)iter.next(); - int result = p.getOffset() - nitems; - p.setOffset(result); - if (result < 0) - this.positions.remove(p); + Iterator iter = marks.iterator(); + while (iter.hasNext()) + { + Mark m = (Mark) iter.next(); + if (m.mark >= where + nitems) + m.mark -= nitems; + else if (m.mark >= where) + m.mark = where; + } } return rundo; } - + + private void replace(int offs, int numRemove, char[] insert) + { + int insertLength = insert.length; + int delta = insertLength - numRemove; + int src = offs + numRemove; + int numMove = count - src; + int dest = src + delta; + if (count + delta >= content.length) + { + // Grow data array. + int newLength = Math.max(2 * content.length, count + delta); + char[] newContent = new char[newLength]; + System.arraycopy(content, 0, newContent, 0, offs); + System.arraycopy(insert, 0, newContent, offs, insertLength); + System.arraycopy(content, src, newContent, dest, numMove); + content = newContent; + } + else + { + System.arraycopy(content, src, content, dest, numMove); + System.arraycopy(insert, 0, content, offs, insertLength); + } + count += delta; + } + /** * Returns a new <code>String</code> containing the characters in the * specified range. @@ -348,6 +477,8 @@ public final class StringContent */ public String getString(int where, int len) throws BadLocationException { + // The RI throws a StringIndexOutOfBoundsException here, which + // smells like a bug. We throw a BadLocationException instead. checkLocation(where, len); return new String(this.content, where, len); } @@ -368,22 +499,28 @@ public final class StringContent public void getChars(int where, int len, Segment txt) throws BadLocationException { - checkLocation(where, len); - txt.array = this.content; + if (where + len > count) + throw new BadLocationException("Invalid location", where + len); + txt.array = content; txt.offset = where; txt.count = len; } /** - * @specnote This method is not very well specified and the positions vector - * is implementation specific. The undo positions are managed - * differently in this implementation, this method is only here - * for binary compatibility. + * Resets the positions in the specified vector to their original offset + * after a undo operation is performed. For example, after removing some + * content, the positions in the removed range will all be set to one + * offset. This method restores the positions to their original offsets + * after an undo. */ protected void updateUndoPositions(Vector positions) { - // We do nothing here. + for (Iterator i = positions.iterator(); i.hasNext();) + { + UndoPosRef pos = (UndoPosRef) i.next(); + pos.reset(); + } } /** @@ -405,6 +542,29 @@ public final class StringContent else if ((where + len) > this.count) throw new BadLocationException("Invalid range", this.count); } - + + /** + * Polls the queue of death for GapContentPositions, updates the + * corresponding reference count and removes the corresponding mark + * if the refcount reaches zero. + * + * This is package private to avoid accessor synthetic methods. + */ + void garbageCollect() + { + Reference ref = queueOfDeath.poll(); + while (ref != null) + { + if (ref != null) + { + StickyPosition pos = (StickyPosition) ref.get(); + Mark m = pos.mark; + m.refCount--; + if (m.refCount == 0) + marks.remove(m); + } + ref = queueOfDeath.poll(); + } + } } diff --git a/javax/swing/text/StyleConstants.java b/javax/swing/text/StyleConstants.java index c7906b8ad..4e5005c6b 100644 --- a/javax/swing/text/StyleConstants.java +++ b/javax/swing/text/StyleConstants.java @@ -40,6 +40,7 @@ package javax.swing.text; import java.awt.Color; import java.awt.Component; +import java.util.ArrayList; import javax.swing.Icon; @@ -163,6 +164,12 @@ public class StyleConstants public static final Object ResolveAttribute = new StyleConstants("resolver"); + /** + * All StyleConstants keys. This is used in StyleContext to register + * all known keys as static attribute keys for serialization. + */ + static ArrayList keys; + String keyname; // Package-private to avoid accessor constructor for use by @@ -170,6 +177,9 @@ public class StyleConstants StyleConstants(String k) { keyname = k; + if (keys == null) + keys = new ArrayList(); + keys.add(this); } /** @@ -729,6 +739,7 @@ public class StyleConstants */ public static void setIcon(MutableAttributeSet a, Icon c) { + a.addAttribute(AbstractDocument.ElementNameAttribute, IconElementName); a.addAttribute(IconAttribute, c); } diff --git a/javax/swing/text/StyleContext.java b/javax/swing/text/StyleContext.java index 1e869485c..b01d1060f 100644 --- a/javax/swing/text/StyleContext.java +++ b/javax/swing/text/StyleContext.java @@ -43,19 +43,23 @@ import java.awt.Font; import java.awt.FontMetrics; import java.awt.Toolkit; import java.io.IOException; +import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.ref.WeakReference; import java.util.Enumeration; import java.util.EventListener; import java.util.Hashtable; +import java.util.Iterator; +import java.util.WeakHashMap; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; public class StyleContext - implements Serializable, AbstractDocument.AttributeContext + implements Serializable, AbstractDocument.AttributeContext { /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 8042858831190784241L; @@ -66,11 +70,10 @@ public class StyleContext /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = -6690628971806226374L; - protected ChangeEvent changeEvent; + protected transient ChangeEvent changeEvent; protected EventListenerList listenerList; - AttributeSet attributes; - String name; + private transient AttributeSet attributes; public NamedStyle() { @@ -84,22 +87,26 @@ public class StyleContext public NamedStyle(String name, Style parent) { - this.name = name; - this.attributes = getEmptySet(); - this.changeEvent = new ChangeEvent(this); - this.listenerList = new EventListenerList(); - setResolveParent(parent); + attributes = getEmptySet(); + listenerList = new EventListenerList(); + if (name != null) + setName(name); + if (parent != null) + setResolveParent(parent); } public String getName() { + String name = null; + if (isDefined(StyleConstants.NameAttribute)) + name = getAttribute(StyleConstants.NameAttribute).toString(); return name; } public void setName(String n) { - name = n; - fireStateChanged(); + if (n != null) + addAttribute(StyleConstants.NameAttribute, n); } public void addChangeListener(ChangeListener l) @@ -127,6 +134,9 @@ public class StyleContext ChangeListener[] listeners = getChangeListeners(); for (int i = 0; i < listeners.length; ++i) { + // Lazily create event. + if (changeEvent == null) + changeEvent = new ChangeEvent(this); listeners[i].stateChanged(changeEvent); } } @@ -155,7 +165,10 @@ public class StyleContext public AttributeSet copyAttributes() { - return attributes.copyAttributes(); + // The RI returns a NamedStyle as copy, so do we. + NamedStyle copy = new NamedStyle(); + copy.attributes = attributes.copyAttributes(); + return copy; } public Object getAttribute(Object attrName) @@ -210,112 +223,125 @@ public class StyleContext public void setResolveParent(AttributeSet parent) { if (parent != null) - { - attributes = StyleContext.this.addAttribute - (attributes, ResolveAttribute, parent); - } - fireStateChanged(); + addAttribute(StyleConstants.ResolveAttribute, parent); + else + removeAttribute(StyleConstants.ResolveAttribute); } public String toString() { - return ("[NamedStyle: name=" + name + ", attrs=" + attributes.toString() + "]"); - } + return "NamedStyle:" + getName() + " " + attributes; + } + + private void writeObject(ObjectOutputStream s) + throws IOException + { + s.defaultWriteObject(); + writeAttributeSet(s, attributes); + } + + private void readObject(ObjectInputStream s) + throws ClassNotFoundException, IOException + { + s.defaultReadObject(); + attributes = SimpleAttributeSet.EMPTY; + readAttributeSet(s, this); + } } public class SmallAttributeSet implements AttributeSet { final Object [] attrs; + private AttributeSet resolveParent; public SmallAttributeSet(AttributeSet a) { - if (a == null) - attrs = new Object[0]; - else + int n = a.getAttributeCount(); + int i = 0; + attrs = new Object[n * 2]; + Enumeration e = a.getAttributeNames(); + while (e.hasMoreElements()) { - int n = a.getAttributeCount(); - int i = 0; - attrs = new Object[n * 2]; - Enumeration e = a.getAttributeNames(); - while (e.hasMoreElements()) - { - Object name = e.nextElement(); - attrs[i++] = name; - attrs[i++] = a.getAttribute(name); - } + Object name = e.nextElement(); + Object value = a.getAttribute(name); + if (name == ResolveAttribute) + resolveParent = (AttributeSet) value; + attrs[i++] = name; + attrs[i++] = value; } } public SmallAttributeSet(Object [] a) { - if (a == null) - attrs = new Object[0]; - else + attrs = a; + for (int i = 0; i < attrs.length; i += 2) { - attrs = new Object[a.length]; - System.arraycopy(a, 0, attrs, 0, a.length); + if (attrs[i] == ResolveAttribute) + resolveParent = (AttributeSet) attrs[i + 1]; } } public Object clone() { - return new SmallAttributeSet(this.attrs); + return this; } public boolean containsAttribute(Object name, Object value) { - for (int i = 0; i < attrs.length; i += 2) - { - if (attrs[i].equals(name) && - attrs[i+1].equals(value)) - return true; - } - return false; + return value.equals(getAttribute(name)); } public boolean containsAttributes(AttributeSet a) { + boolean res = true; Enumeration e = a.getAttributeNames(); - while (e.hasMoreElements()) + while (e.hasMoreElements() && res) { Object name = e.nextElement(); - Object val = a.getAttribute(name); - if (!containsAttribute(name, val)) - return false; + res = a.getAttribute(name).equals(getAttribute(name)); } - return true; + return res; } public AttributeSet copyAttributes() { - return (AttributeSet) clone(); + return this; } public boolean equals(Object obj) { - return - (obj instanceof AttributeSet) - && this.isEqual((AttributeSet)obj); + boolean eq = false; + if (obj instanceof AttributeSet) + { + AttributeSet atts = (AttributeSet) obj; + eq = getAttributeCount() == atts.getAttributeCount() + && containsAttributes(atts); + } + return eq; } public Object getAttribute(Object key) { - for (int i = 0; i < attrs.length; i += 2) + Object att = null; + if (key == StyleConstants.ResolveAttribute) + att = resolveParent; + + for (int i = 0; i < attrs.length && att == null; i += 2) { if (attrs[i].equals(key)) - return attrs[i+1]; + att = attrs[i + 1]; } - + // Check the resolve parent, unless we're looking for the - // ResolveAttribute, which would cause an infinite loop - if (!(key.equals(ResolveAttribute))) + // ResolveAttribute, which must not be looked up + if (att == null) { - Object p = getResolveParent(); - if (p != null && p instanceof AttributeSet) - return (((AttributeSet)p).getAttribute(key)); + AttributeSet parent = getResolveParent(); + if (parent != null) + att = parent.getAttribute(key); } - return null; + return att; } public int getAttributeCount() @@ -342,7 +368,7 @@ public class StyleContext public AttributeSet getResolveParent() { - return (AttributeSet) getAttribute(ResolveAttribute); + return resolveParent; } public int hashCode() @@ -362,68 +388,95 @@ public class StyleContext public boolean isEqual(AttributeSet attr) { - return getAttributeCount() == attr.getAttributeCount() + boolean eq; + // If the other one is also a SmallAttributeSet, it is only considered + // equal if it's the same instance. + if (attr instanceof SmallAttributeSet) + eq = attr == this; + else + eq = getAttributeCount() == attr.getAttributeCount() && this.containsAttributes(attr); + return eq; } public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("[StyleContext.SmallattributeSet:"); - for (int i = 0; i < attrs.length - 1; ++i) + StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (int i = 0; i < attrs.length; i += 2) { - sb.append(" ("); - sb.append(attrs[i].toString()); - sb.append("="); - sb.append(attrs[i+1].toString()); - sb.append(")"); + if (attrs[i + 1] instanceof AttributeSet) + { + sb.append(attrs[i]); + sb.append("=AttributeSet,"); + } + else + { + sb.append(attrs[i]); + sb.append('='); + sb.append(attrs[i + 1]); + sb.append(','); + } } - sb.append("]"); + sb.append("}"); return sb.toString(); } } - // FIXME: official javadocs suggest that these might be more usefully - // implemented using a WeakHashMap, but not sure if that works most - // places or whether it really matters anyways. - // - // FIXME: also not sure if these tables ought to be static (singletons), - // shared across all StyleContexts. I think so, but it's not clear in - // docs. revert to non-shared if you think it matters. - /** - * The name of the default style. + * Register StyleConstant keys as static attribute keys for serialization. */ - public static final String DEFAULT_STYLE = "default"; - + static + { + // Don't let problems while doing this prevent class loading. + try + { + for (Iterator i = StyleConstants.keys.iterator(); i.hasNext();) + registerStaticAttributeKey(i.next()); + } + catch (Throwable t) + { + t.printStackTrace(); + } + } + /** - * The default style for this style context. + * The name of the default style. */ - NamedStyle defaultStyle = new NamedStyle(DEFAULT_STYLE, null); + public static final String DEFAULT_STYLE = "default"; static Hashtable sharedAttributeSets = new Hashtable(); static Hashtable sharedFonts = new Hashtable(); - static StyleContext defaultStyleContext = new StyleContext(); + static StyleContext defaultStyleContext; static final int compressionThreshold = 9; /** * These attribute keys are handled specially in serialization. */ - private static Hashtable staticAttributeKeys = new Hashtable(); + private static Hashtable writeAttributeKeys; + private static Hashtable readAttributeKeys; + + private NamedStyle styles; + + /** + * Used for searching attributes in the pool. + */ + private transient MutableAttributeSet search = new SimpleAttributeSet(); + + /** + * A pool of immutable AttributeSets. + */ + private transient WeakHashMap attributeSetPool = new WeakHashMap(); - EventListenerList listenerList; - Hashtable styleTable; - /** * Creates a new instance of the style context. Add the default style * to the style table. */ public StyleContext() { - listenerList = new EventListenerList(); - styleTable = new Hashtable(); - styleTable.put(DEFAULT_STYLE, defaultStyle); + styles = new NamedStyle(null); + addStyle(DEFAULT_STYLE, null); } protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) @@ -438,30 +491,30 @@ public class StyleContext public void addChangeListener(ChangeListener listener) { - listenerList.add(ChangeListener.class, listener); + styles.addChangeListener(listener); } public void removeChangeListener(ChangeListener listener) { - listenerList.remove(ChangeListener.class, listener); + styles.removeChangeListener(listener); } public ChangeListener[] getChangeListeners() { - return (ChangeListener[]) listenerList.getListeners(ChangeListener.class); + return styles.getChangeListeners(); } public Style addStyle(String name, Style parent) { Style newStyle = new NamedStyle(name, parent); if (name != null) - styleTable.put(name, newStyle); + styles.addAttribute(name, newStyle); return newStyle; } public void removeStyle(String name) { - styleTable.remove(name); + styles.removeAttribute(name); } /** @@ -476,7 +529,7 @@ public class StyleContext */ public Style getStyle(String name) { - return (Style) styleTable.get(name); + return (Style) styles.getAttribute(name); } /** @@ -485,7 +538,22 @@ public class StyleContext */ public Enumeration<?> getStyleNames() { - return styleTable.keys(); + return styles.getAttributeNames(); + } + + private void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException + { + search = new SimpleAttributeSet(); + attributeSetPool = new WeakHashMap(); + in.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + cleanupPool(); + out.defaultWriteObject(); } // @@ -577,132 +645,120 @@ public class StyleContext public static StyleContext getDefaultStyleContext() { + if (defaultStyleContext == null) + defaultStyleContext = new StyleContext(); return defaultStyleContext; } public AttributeSet addAttribute(AttributeSet old, Object name, Object value) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() + 1 < getCompressionThreshold()) { - ((MutableAttributeSet)old).addAttribute(name, value); - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.addAttribute(name, value); + reclaim(old); + ret = searchImmutableSet(); } - else + else { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.addAttribute(name, value); - if (mutable.getAttributeCount() >= getCompressionThreshold()) - return mutable; - else - { - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; - } + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.addAttribute(name, value); + ret = mas; } + return ret; } public AttributeSet addAttributes(AttributeSet old, AttributeSet attributes) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() + attributes.getAttributeCount() + < getCompressionThreshold()) { - ((MutableAttributeSet)old).addAttributes(attributes); - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.addAttributes(attributes); + reclaim(old); + ret = searchImmutableSet(); } - else + else { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.addAttributes(attributes); - if (mutable.getAttributeCount() >= getCompressionThreshold()) - return mutable; - else - { - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; - } + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.addAttributes(attributes); + ret = mas; } + return ret; } public AttributeSet getEmptySet() { - AttributeSet e = createSmallAttributeSet(null); - if (sharedAttributeSets.containsKey(e)) - e = (AttributeSet) sharedAttributeSets.get(e); - else - sharedAttributeSets.put(e, e); - return e; + return SimpleAttributeSet.EMPTY; } public void reclaim(AttributeSet attributes) { - if (sharedAttributeSets.containsKey(attributes)) - sharedAttributeSets.remove(attributes); + cleanupPool(); } public AttributeSet removeAttribute(AttributeSet old, Object name) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() - 1 <= getCompressionThreshold()) { - ((MutableAttributeSet)old).removeAttribute(name); - if (old.getAttributeCount() < getCompressionThreshold()) - { - SmallAttributeSet small = createSmallAttributeSet(old); - if (!sharedAttributeSets.containsKey(small)) - sharedAttributeSets.put(small,small); - old = (AttributeSet) sharedAttributeSets.get(small); - } - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.removeAttribute(name); + reclaim(old); + ret = searchImmutableSet(); } - else - { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.removeAttribute(name); - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; + else + { + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.removeAttribute(name); + ret = mas; } + return ret; } public AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes) { - return removeAttributes(old, attributes.getAttributeNames()); + AttributeSet ret; + if (old.getAttributeCount() <= getCompressionThreshold()) + { + search.removeAttributes(search); + search.addAttributes(old); + search.removeAttributes(attributes); + reclaim(old); + ret = searchImmutableSet(); + } + else + { + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.removeAttributes(attributes); + ret = mas; + } + return ret; } public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() <= getCompressionThreshold()) { - ((MutableAttributeSet)old).removeAttributes(names); - if (old.getAttributeCount() < getCompressionThreshold()) - { - SmallAttributeSet small = createSmallAttributeSet(old); - if (!sharedAttributeSets.containsKey(small)) - sharedAttributeSets.put(small,small); - old = (AttributeSet) sharedAttributeSets.get(small); - } - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.removeAttributes(names); + reclaim(old); + ret = searchImmutableSet(); } - else - { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.removeAttributes(names); - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; - } + else + { + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.removeAttributes(names); + ret = mas; + } + return ret; } /** @@ -715,7 +771,7 @@ public class StyleContext { if (key == null) return null; - return staticAttributeKeys.get(key); + return readAttributeKeys.get(key); } /** @@ -742,27 +798,25 @@ public class StyleContext * stream * @throws IOException - any I/O error */ - public static void readAttributeSet(ObjectInputStream in, MutableAttributeSet a) + public static void readAttributeSet(ObjectInputStream in, + MutableAttributeSet a) throws ClassNotFoundException, IOException { - if (in == null || a == null) - return; - - Object key = in.readObject(); - Object val = in.readObject(); - while (key != null && val != null) + int count = in.readInt(); + for (int i = 0; i < count; i++) { - Object staticKey = staticAttributeKeys.get(key); - Object staticVal = staticAttributeKeys.get(val); - - if (staticKey != null) - key = staticKey; - if (staticVal != null) - val = staticVal; - + Object key = in.readObject(); + Object val = in.readObject(); + if (readAttributeKeys != null) + { + Object staticKey = readAttributeKeys.get(key); + if (staticKey != null) + key = staticKey; + Object staticVal = readAttributeKeys.get(val); + if (staticVal != null) + val = staticVal; + } a.addAttribute(key, val); - key = in.readObject(); - val = in.readObject(); } } @@ -778,18 +832,35 @@ public class StyleContext public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a) throws IOException { + int count = a.getAttributeCount(); + out.writeInt(count); Enumeration e = a.getAttributeNames(); while (e.hasMoreElements()) { - Object oldKey = e.nextElement(); - Object newKey = getStaticAttribute(oldKey); - Object key = (newKey == null) ? oldKey : newKey; - - out.writeObject(key); - out.writeObject(a.getAttribute(oldKey)); + Object key = e.nextElement(); + // Write key. + if (key instanceof Serializable) + out.writeObject(key); + else + { + Object io = writeAttributeKeys.get(key); + if (io == null) + throw new NotSerializableException(key.getClass().getName() + + ", key: " + key); + out.writeObject(io); + } + // Write value. + Object val = a.getAttribute(key); + Object io = writeAttributeKeys.get(val); + if (val instanceof Serializable) + out.writeObject(io != null ? io : val); + else + { + if (io == null) + throw new NotSerializableException(val.getClass().getName()); + out.writeObject(io); + } } - out.writeObject(null); - out.writeObject(null); } /** @@ -833,8 +904,79 @@ public class StyleContext */ public static void registerStaticAttributeKey(Object key) { - if (key != null) - staticAttributeKeys.put(key.getClass().getName() + "." + key.toString(), - key); + String io = key.getClass().getName() + "." + key.toString(); + if (writeAttributeKeys == null) + writeAttributeKeys = new Hashtable(); + if (readAttributeKeys == null) + readAttributeKeys = new Hashtable(); + writeAttributeKeys.put(key, io); + readAttributeKeys.put(io, key); + } + + /** + * Returns a string representation of this StyleContext. + * + * @return a string representation of this StyleContext + */ + public String toString() + { + cleanupPool(); + StringBuilder b = new StringBuilder(); + Iterator i = attributeSetPool.keySet().iterator(); + while (i.hasNext()) + { + Object att = i.next(); + b.append(att); + b.append('\n'); + } + return b.toString(); + } + + /** + * Searches the AttributeSet pool and returns a pooled instance if available, + * or pool a new one. + * + * @return an immutable attribute set that equals the current search key + */ + private AttributeSet searchImmutableSet() + { + SmallAttributeSet k = createSmallAttributeSet(search); + WeakReference ref = (WeakReference) attributeSetPool.get(k); + SmallAttributeSet a; + if (ref == null || (a = (SmallAttributeSet) ref.get()) == null) + { + a = k; + attributeSetPool.put(a, new WeakReference(a)); + } + return a; + } + + /** + * Cleans up the attribute set pool from entries that are no longer + * referenced. + */ + private void cleanupPool() + { + // TODO: How else can we force cleaning up the WeakHashMap? + attributeSetPool.size(); + } + + /** + * Returns a MutableAttributeSet that holds a. If a itself is mutable, + * this returns a itself, otherwise it creates a new SimpleAtttributeSet + * via {@link #createLargeAttributeSet(AttributeSet)}. + * + * @param a the AttributeSet to create a mutable set for + * + * @return a mutable attribute set that corresponds to a + */ + private MutableAttributeSet getMutableAttributeSet(AttributeSet a) + { + MutableAttributeSet mas; + if (a instanceof MutableAttributeSet) + mas = (MutableAttributeSet) a; + else + mas = createLargeAttributeSet(a); + return mas; } } diff --git a/javax/swing/text/StyledEditorKit.java b/javax/swing/text/StyledEditorKit.java index c4eef4463..568694387 100644 --- a/javax/swing/text/StyledEditorKit.java +++ b/javax/swing/text/StyledEditorKit.java @@ -142,7 +142,7 @@ public class StyledEditorKit extends DefaultEditorKit Element el = doc.getCharacterElement(editor.getSelectionStart()); boolean isBold = StyleConstants.isBold(el.getAttributes()); SimpleAttributeSet atts = new SimpleAttributeSet(); - StyleConstants.setItalic(atts, ! isBold); + StyleConstants.setBold(atts, ! isBold); setCharacterAttributes(editor, atts, false); } } @@ -335,35 +335,21 @@ public class StyledEditorKit extends DefaultEditorKit AttributeSet atts, boolean replace) { - Document doc = editor.getDocument(); - if (doc instanceof StyledDocument) - { - StyledDocument styleDoc = (StyledDocument) editor.getDocument(); - EditorKit kit = editor.getEditorKit(); - if (!(kit instanceof StyledEditorKit)) - { - StyledEditorKit styleKit = (StyledEditorKit) kit; - int start = editor.getSelectionStart(); - int end = editor.getSelectionEnd(); - int dot = editor.getCaret().getDot(); - if (start == dot && end == dot) - { - // If there is no selection, then we only update the - // input attributes. - MutableAttributeSet inputAttributes = - styleKit.getInputAttributes(); - inputAttributes.addAttributes(atts); - } - else - styleDoc.setCharacterAttributes(start, end, atts, replace); - } - else - throw new AssertionError("The EditorKit for StyledTextActions " - + "is expected to be a StyledEditorKit"); - } - else - throw new AssertionError("The Document for StyledTextActions is " - + "expected to be a StyledDocument."); + int p0 = editor.getSelectionStart(); + int p1 = editor.getSelectionEnd(); + if (p0 != p1) + { + StyledDocument doc = getStyledDocument(editor); + doc.setCharacterAttributes(p0, p1 - p0, atts, replace); + } + // Update input attributes. + StyledEditorKit kit = getStyledEditorKit(editor); + MutableAttributeSet inputAtts = kit.getInputAttributes(); + if (replace) + { + inputAtts.removeAttributes(inputAtts); + } + inputAtts.addAttributes(atts); } /** diff --git a/javax/swing/text/TextAction.java b/javax/swing/text/TextAction.java index 28dbff00a..49c49cb9d 100644 --- a/javax/swing/text/TextAction.java +++ b/javax/swing/text/TextAction.java @@ -38,14 +38,15 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.Component; +import java.awt.KeyboardFocusManager; import java.awt.Point; import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; +import java.util.Iterator; import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.SwingConstants; /** * TextAction @@ -73,10 +74,16 @@ public abstract class TextAction extends AbstractAction */ protected final JTextComponent getTextComponent(ActionEvent event) { - if (event.getSource() instanceof JTextComponent) - return (JTextComponent) event.getSource(); - - return getFocusedComponent(); + JTextComponent target = null; + if (event != null) + { + Object source = event.getSource(); + if (source instanceof JTextComponent) + target = (JTextComponent) source; + } + if (target == null) + target = getFocusedComponent(); + return target; } /** @@ -89,16 +96,28 @@ public abstract class TextAction extends AbstractAction */ public static final Action[] augmentList(Action[] list1, Action[] list2) { - HashSet<Action> actionSet = new HashSet<Action>(); + HashMap<Object,Action> actions = new HashMap<Object,Action>(); for (int i = 0; i < list1.length; ++i) - actionSet.add(list1[i]); + { + Action a = list1[i]; + Object name = a.getValue(Action.NAME); + actions.put(name != null ? name : "", a); + } for (int i = 0; i < list2.length; ++i) - actionSet.add(list2[i]); + { + Action a = list2[i]; + Object name = a.getValue(Action.NAME); + actions.put(name != null ? name : "", a); + } + Action[] augmented = new Action[actions.size()]; + + int i = 0; + for (Iterator<Action> it = actions.values().iterator(); it.hasNext(); i++) + augmented[i] = it.next(); + return augmented; - ArrayList<Action> list = new ArrayList<Action>(actionSet); - return list.toArray(new Action[actionSet.size()]); } /** @@ -108,7 +127,13 @@ public abstract class TextAction extends AbstractAction */ protected final JTextComponent getFocusedComponent() { - return null; // TODO + KeyboardFocusManager kfm = + KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focused = kfm.getPermanentFocusOwner(); + JTextComponent textComp = null; + if (focused instanceof JTextComponent) + textComp = (JTextComponent) focused; + return textComp; } /** Abstract helper class which implements everything needed for an diff --git a/javax/swing/text/Utilities.java b/javax/swing/text/Utilities.java index f75906a0f..8ddf97a12 100644 --- a/javax/swing/text/Utilities.java +++ b/javax/swing/text/Utilities.java @@ -54,10 +54,6 @@ import javax.swing.text.Position.Bias; */ public class Utilities { - /** - * The length of the char buffer that holds the characters to be drawn. - */ - private static final int BUF_LENGTH = 64; /** * Creates a new <code>Utilities</code> object. @@ -125,8 +121,8 @@ public class Utilities // In case we have a tab, we just 'jump' over the tab. // When we have no tab expander we just use the width of ' '. if (e != null) - pixelX = (int) e.nextTabStop((float) pixelX, - startOffset + offset - s.offset); + pixelX = (int) e.nextTabStop(pixelX, + startOffset + offset - s.offset); else pixelX += metrics.charWidth(' '); break; @@ -176,7 +172,7 @@ public class Utilities // In case we have a tab, we just 'jump' over the tab. // When we have no tab expander we just use the width of 'm'. if (e != null) - pixelX = (int) e.nextTabStop((float) pixelX, + pixelX = (int) e.nextTabStop(pixelX, startOffset + offset - s.offset); else pixelX += metrics.charWidth(' '); @@ -269,7 +265,7 @@ public class Utilities currentX += width; } - return pos + p0; + return pos; } /** @@ -537,28 +533,39 @@ public class Utilities int x0, int x, TabExpander e, int startOffset) { - int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false); - BreakIterator breaker = BreakIterator.getWordInstance(); - breaker.setText(s); - - // If startOffset and s.offset differ then we need to use - // that difference two convert the offset between the two metrics. - int shift = startOffset - s.offset; - + int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, + false); + int breakLoc = mark; // If mark is equal to the end of the string, just use that position. - if (mark >= shift + s.count) - return mark; - - // Try to find a word boundary previous to the mark at which we - // can break the text. - int preceding = breaker.preceding(mark + 1 - shift); - - if (preceding != 0) - return preceding + shift; - - // If preceding is 0 we couldn't find a suitable word-boundary so - // just break it on the character boundary - return mark; + if (mark < s.count - 1) + { + for (int i = s.offset + mark; i >= s.offset; i--) + { + char ch = s.array[i]; + if (ch < 256) + { + // For ASCII simply scan backwards for whitespace. + if (Character.isWhitespace(ch)) + { + breakLoc = i - s.offset + 1; + break; + } + } + else + { + // Only query BreakIterator for complex chars. + BreakIterator bi = BreakIterator.getLineInstance(); + bi.setText(s); + int pos = bi.preceding(i + 1); + if (pos > s.offset) + { + breakLoc = breakLoc - s.offset; + } + break; + } + } + } + return breakLoc; } /** @@ -706,12 +713,12 @@ public class Utilities offset, Bias.Forward, direction, - null) + new Position.Bias[1]) : t.getUI().getNextVisualPositionFrom(t, offset, Bias.Forward, direction, - null); + new Position.Bias[1]); } catch (BadLocationException ble) { diff --git a/javax/swing/text/View.java b/javax/swing/text/View.java index 55a63f6b6..aafd76a4f 100644 --- a/javax/swing/text/View.java +++ b/javax/swing/text/View.java @@ -57,7 +57,6 @@ public abstract class View implements SwingConstants public static final int X_AXIS = 0; public static final int Y_AXIS = 1; - private float width, height; private Element elt; private View parent; @@ -307,15 +306,16 @@ public abstract class View implements SwingConstants { int index = getViewIndex(x, y, allocation); - if (index < -1) - return null; - - Shape childAllocation = getChildAllocation(index, allocation); - - if (childAllocation.getBounds().contains(x, y)) - return getView(index).getToolTipText(x, y, childAllocation); - - return null; + String text = null; + if (index >= 0) + { + allocation = getChildAllocation(index, allocation); + Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation + : allocation.getBounds(); + if (r.contains(x, y)) + text = getView(index).getToolTipText(x, y, allocation); + } + return text; } /** @@ -334,7 +334,10 @@ public abstract class View implements SwingConstants public int getBreakWeight(int axis, float pos, float len) { - return BadBreakWeight; + int weight = BadBreakWeight; + if (len > getPreferredSpan(axis)) + weight = GoodBreakWeight; + return weight; } public View breakView(int axis, int offset, float pos, float len) @@ -370,12 +373,18 @@ public abstract class View implements SwingConstants */ public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - Element el = getElement(); - DocumentEvent.ElementChange ec = ev.getChange(el); - if (ec != null) - updateChildren(ec, ev, vf); - forwardUpdate(ec, ev, shape, vf); - updateLayout(ec, ev, shape); + if (getViewCount() > 0) + { + Element el = getElement(); + DocumentEvent.ElementChange ec = ev.getChange(el); + if (ec != null) + { + if (! updateChildren(ec, ev, vf)) + ec = null; + } + forwardUpdate(ec, ev, shape, vf); + updateLayout(ec, ev, shape); + } } /** @@ -429,12 +438,18 @@ public abstract class View implements SwingConstants */ public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - Element el = getElement(); - DocumentEvent.ElementChange ec = ev.getChange(el); - if (ec != null) - updateChildren(ec, ev, vf); - forwardUpdate(ec, ev, shape, vf); - updateLayout(ec, ev, shape); + if (getViewCount() > 0) + { + Element el = getElement(); + DocumentEvent.ElementChange ec = ev.getChange(el); + if (ec != null) + { + if (! updateChildren(ec, ev, vf)) + ec = null; + } + forwardUpdate(ec, ev, shape, vf); + updateLayout(ec, ev, shape); + } } /** @@ -465,10 +480,15 @@ public abstract class View implements SwingConstants Element[] removed = ec.getChildrenRemoved(); int index = ec.getIndex(); - View[] newChildren = new View[added.length]; - for (int i = 0; i < added.length; ++i) - newChildren[i] = vf.create(added[i]); - replace(index, removed.length, newChildren); + View[] newChildren = null; + if (added != null) + { + newChildren = new View[added.length]; + for (int i = 0; i < added.length; ++i) + newChildren[i] = vf.create(added[i]); + } + int numRemoved = removed != null ? removed.length : 0; + replace(index, numRemoved, newChildren); return true; } @@ -750,7 +770,9 @@ public abstract class View implements SwingConstants */ public int viewToModel(float x, float y, Shape a) { - return viewToModel(x, y, a, new Position.Bias[0]); + Position.Bias[] biasRet = new Position.Bias[1]; + biasRet[0] = Position.Bias.Forward; + return viewToModel(x, y, a, biasRet); } /** diff --git a/javax/swing/text/WrappedPlainView.java b/javax/swing/text/WrappedPlainView.java index 8cb2f4fb5..00e12b112 100644 --- a/javax/swing/text/WrappedPlainView.java +++ b/javax/swing/text/WrappedPlainView.java @@ -83,7 +83,17 @@ public class WrappedPlainView extends BoxView implements TabExpander /** The height of the line (used while painting) **/ int lineHeight; - + + /** + * The base offset for tab calculations. + */ + private int tabBase; + + /** + * The tab size. + */ + private int tabSize; + /** * The instance returned by {@link #getLineBuffer()}. */ @@ -121,10 +131,13 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public float nextTabStop(float x, int tabStop) { - JTextComponent host = (JTextComponent)getContainer(); - float tabSizePixels = getTabSize() - * host.getFontMetrics(host.getFont()).charWidth('m'); - return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels; + int next = (int) x; + if (tabSize != 0) + { + int numTabs = ((int) x - tabBase) / tabSize; + next = tabBase + (numTabs + 1) * tabSize; + } + return next; } /** @@ -274,44 +287,32 @@ public class WrappedPlainView extends BoxView implements TabExpander */ protected int calculateBreakPosition(int p0, int p1) { - Container c = getContainer(); - - int li = getLeftInset(); - int ti = getTopInset(); - - Rectangle alloc = new Rectangle(li, ti, - getWidth()-getRightInset()-li, - getHeight()-getBottomInset()-ti); - - // Mimic a behavior observed in the RI. - if (alloc.isEmpty()) - return 0; - - updateMetrics(); - + Segment s = new Segment(); try { - getDocument().getText(p0, p1 - p0, getLineBuffer()); + getDocument().getText(p0, p1 - p0, s); } - catch (BadLocationException ble) + catch (BadLocationException ex) { - // this shouldn't happen - throw new InternalError("Invalid offsets p0: " + p0 + " - p1: " + p1); + assert false : "Couldn't load text"; } - + int width = getWidth(); + int pos; if (wordWrap) - return Utilities.getBreakLocation(lineBuffer, metrics, alloc.x, - alloc.x + alloc.width, this, p0); + pos = p0 + Utilities.getBreakLocation(s, metrics, tabBase, + tabBase + width, this, p0); else - return p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x, - alloc.x + alloc.width, this, 0, - true); + pos = p0 + Utilities.getTabbedTextOffset(s, metrics, tabBase, + tabBase + width, this, p0, + false); + return pos; } void updateMetrics() { Container component = getContainer(); metrics = component.getFontMetrics(component.getFont()); + tabSize = getTabSize()* metrics.charWidth('m'); } /** @@ -350,9 +351,15 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f) { - super.insertUpdate(e, a, viewFactory); + // Update children efficiently. + updateChildren(e, a); - // No repaint needed, as this is done by the WrappedLine instances. + // Notify children. + Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a) + : null; + View v = getViewAtPosition(e.getOffset(), r); + if (v != null) + v.insertUpdate(e, r, f); } /** @@ -361,9 +368,15 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f) { - super.removeUpdate(e, a, viewFactory); - - // No repaint needed, as this is done by the WrappedLine instances. + // Update children efficiently. + updateChildren(e, a); + + // Notify children. + Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a) + : null; + View v = getViewAtPosition(e.getOffset(), r); + if (v != null) + v.removeUpdate(e, r, f); } /** @@ -373,11 +386,39 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f) { - super.changedUpdate(e, a, viewFactory); - - // No repaint needed, as this is done by the WrappedLine instances. + // Update children efficiently. + updateChildren(e, a); } - + + /** + * Helper method. Updates the child views in response to + * insert/remove/change updates. This is here to be a little more efficient + * than the BoxView implementation. + * + * @param ev the document event + * @param a the shape + */ + private void updateChildren(DocumentEvent ev, Shape a) + { + Element el = getElement(); + DocumentEvent.ElementChange ec = ev.getChange(el); + if (ec != null) + { + Element[] removed = ec.getChildrenRemoved(); + Element[] added = ec.getChildrenAdded(); + View[] addedViews = new View[added.length]; + for (int i = 0; i < added.length; i++) + addedViews[i] = new WrappedLine(added[i]); + replace(ec.getIndex(), removed.length, addedViews); + if (a != null) + { + preferenceChanged(null, true, true); + getContainer().repaint(); + } + } + updateMetrics(); + } + class WrappedLineCreator implements ViewFactory { // Creates a new WrappedLine @@ -397,6 +438,9 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void paint(Graphics g, Shape a) { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + tabBase = r.x; + JTextComponent comp = (JTextComponent)getContainer(); // Ensure metrics are up-to-date. updateMetrics(); @@ -434,7 +478,6 @@ public class WrappedPlainView extends BoxView implements TabExpander public WrappedLine(Element elem) { super(elem); - determineNumLines(); } /** @@ -496,37 +539,29 @@ public class WrappedPlainView extends BoxView implements TabExpander } } - + /** * Calculates the number of logical lines that the Element * needs to be displayed and updates the variable numLines * accordingly. */ - void determineNumLines() + private int determineNumLines() { - numLines = 0; + int nLines = 0; int end = getEndOffset(); - if (end == 0) - return; - - int breakPoint; for (int i = getStartOffset(); i < end;) { - numLines ++; + nLines++; // careful: check that there's no off-by-one problem here // depending on which position calculateBreakPosition returns - breakPoint = calculateBreakPosition(i, end); + int breakPoint = calculateBreakPosition(i, end); - if (breakPoint == 0) - return; - - // If breakPoint is equal to the current index no further - // line is needed and we can end the loop. if (breakPoint == i) - break; + i = breakPoint + 1; else i = breakPoint; } + return nLines; } /** @@ -571,7 +606,7 @@ public class WrappedPlainView extends BoxView implements TabExpander // Throwing a BadLocationException is an observed behavior of the RI. if (rect.isEmpty()) throw new BadLocationException("Unable to calculate view coordinates " - + "when allocation area is empty.", 5); + + "when allocation area is empty.", pos); Segment s = getLineBuffer(); int lineHeight = metrics.getHeight(); @@ -648,7 +683,7 @@ public class WrappedPlainView extends BoxView implements TabExpander return currLineStart; if (y > rect.y + rect.height) - return end; + return end - 1; // Note: rect.x and rect.width do not represent the width of painted // text but the area where text *may* be painted. This means the width @@ -709,22 +744,14 @@ public class WrappedPlainView extends BoxView implements TabExpander */ void updateDamage (Rectangle a) { - // If the allocation area is empty we can't do anything useful. - // As determining the number of lines is impossible in that state we - // reset it to an invalid value which can then be recalculated at a - // later point. - if (a == null || a.isEmpty()) + int nLines = determineNumLines(); + if (numLines != nLines) { - numLines = 1; - return; + numLines = nLines; + preferenceChanged(this, false, true); + getContainer().repaint(); } - - int oldNumLines = numLines; - determineNumLines(); - - if (numLines != oldNumLines) - preferenceChanged(this, false, true); - else + else if (a != null) getContainer().repaint(a.x, a.y, a.width, a.height); } @@ -738,7 +765,8 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f) { - updateDamage((Rectangle)a); + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + updateDamage(r); } /** @@ -760,7 +788,8 @@ public class WrappedPlainView extends BoxView implements TabExpander // However this seems to cause no trouble and as it reduces the // number of method calls it can stay this way. - updateDamage((Rectangle)a); + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + updateDamage(r); } } } diff --git a/javax/swing/text/ZoneView.java b/javax/swing/text/ZoneView.java new file mode 100644 index 000000000..6cabc6c20 --- /dev/null +++ b/javax/swing/text/ZoneView.java @@ -0,0 +1,442 @@ +/* ZoneView.java -- An effective BoxView subclass + 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 javax.swing.text; + +import java.awt.Shape; +import java.util.ArrayList; +import java.util.LinkedList; + +import javax.swing.event.DocumentEvent; + +/** + * A View implementation that delays loading of sub views until they are + * needed for display or internal transformations. This can be used for + * editors that need to handle large documents more effectivly than the + * standard {@link BoxView}. + * + * @author Roman Kennke (kennke@aicas.com) + * + * @since 1.3 + */ +public class ZoneView + extends BoxView +{ + + /** + * The default zone view implementation. The specs suggest that this is + * a subclass of AsyncBoxView, so do we. + */ + static class Zone + extends AsyncBoxView + { + /** + * The start position for this zone. + */ + private Position p0; + + /** + * The end position for this zone. + */ + private Position p1; + + /** + * Creates a new Zone for the specified element, start and end positions. + * + * @param el the element + * @param pos0 the start position + * @param pos1 the end position + * @param axis the major axis + */ + Zone(Element el, Position pos0, Position pos1, int axis) + { + super(el, axis); + p0 = pos0; + p1 = pos1; + } + + /** + * Returns the start offset of the zone. + * + * @return the start offset of the zone + */ + public int getStartOffset() + { + return p0.getOffset(); + } + + /** + * Returns the end offset of the zone. + * + * @return the end offset of the zone + */ + public int getEndOffset() + { + return p1.getOffset(); + } + } + + /** + * The maximumZoneSize. + */ + private int maximumZoneSize; + + /** + * The maximum number of loaded zones. + */ + private int maxZonesLoaded; + + /** + * A queue of loaded zones. When the number of loaded zones exceeds the + * maximum number of zones, the oldest zone(s) get unloaded. + */ + private LinkedList loadedZones; + + /** + * Creates a new ZoneView for the specified element and axis. + * + * @param element the element for which to create a ZoneView + * @param axis the major layout axis for the box + */ + public ZoneView(Element element, int axis) + { + super(element, axis); + maximumZoneSize = 8192; + maxZonesLoaded = 3; + loadedZones = new LinkedList(); + } + + /** + * Sets the maximum zone size. Note that zones might still become larger + * then the size specified when a singe child view is larger for itself, + * because zones are formed on child view boundaries. + * + * @param size the maximum zone size to set + * + * @see #getMaximumZoneSize() + */ + public void setMaximumZoneSize(int size) + { + maximumZoneSize = size; + } + + /** + * Returns the maximum zone size. Note that zones might still become larger + * then the size specified when a singe child view is larger for itself, + * because zones are formed on child view boundaries. + * + * @return the maximum zone size + * + * @see #setMaximumZoneSize(int) + */ + public int getMaximumZoneSize() + { + return maximumZoneSize; + } + + /** + * Sets the maximum number of zones that are allowed to be loaded at the + * same time. If the new number of allowed zones is smaller then the + * previous settings, this unloads all zones the aren't allowed to be + * loaded anymore. + * + * @param num the number of zones allowed to be loaded at the same time + * + * @throws IllegalArgumentException if <code>num <= 0</code> + * + * @see #getMaxZonesLoaded() + */ + public void setMaxZonesLoaded(int num) + { + if (num < 1) + throw new IllegalArgumentException("Illegal number of zones"); + maxZonesLoaded = num; + unloadOldestZones(); + } + + /** + * Returns the number of zones that are allowed to be loaded. + * + * @return the number of zones that are allowed to be loaded + * + * @see #setMaxZonesLoaded(int) + */ + public int getMaxZonesLoaded() + { + return maxZonesLoaded; + } + + /** + * Gets called after a zone has been loaded. This unloads the oldest zone(s) + * when the maximum number of zones is reached. + * + * @param zone the zone that has been loaded + */ + protected void zoneWasLoaded(View zone) + { + loadedZones.addLast(zone); + unloadOldestZones(); + } + + /** + * This unloads the specified zone. This is implemented to simply remove + * all child views from that zone. + * + * @param zone the zone to be unloaded + */ + protected void unloadZone(View zone) + { + zone.removeAll(); + } + + /** + * Returns <code>true</code> when the specified zone is loaded, + * <code>false</code> otherwise. The default implementation checks if + * the zone view has child elements. + * + * @param zone the zone view to check + * + * @return <code>true</code> when the specified zone is loaded, + * <code>false</code> otherwise + */ + protected boolean isZoneLoaded(View zone) + { + return zone.getViewCount() > 0; + } + + /** + * Creates a zone for the specified range. Subclasses can override this + * to provide a custom implementation for the zones. + * + * @param p0 the start of the range + * @param p1 the end of the range + * + * @return the zone + */ + protected View createZone(int p0, int p1) + { + Document doc = getDocument(); + Position pos0 = null; + Position pos1 = null; + try + { + pos0 = doc.createPosition(p0); + pos1 = doc.createPosition(p1); + } + catch (BadLocationException ex) + { + assert false : "Must not happen"; + } + Zone zone = new Zone(getElement(), pos0, pos1, getAxis()); + return zone; + } + + // -------------------------------------------------------------------------- + // CompositeView methods. + // -------------------------------------------------------------------------- + + /** + * Overridden to not load all the child views. This methods creates + * initial zones without actually loading them. + * + * @param vf not used + */ + protected void loadChildren(ViewFactory vf) + { + int p0 = getStartOffset(); + int p1 = getEndOffset(); + append(createZone(p0, p1)); + checkZoneAt(p0); + } + + /** + * Returns the index of the child view at the document position + * <code>pos</code>. + * + * This overrides the CompositeView implementation because the ZoneView does + * not provide a one to one mapping from Elements to Views. + * + * @param pos the document position + * + * @return the index of the child view at the document position + * <code>pos</code> + */ + protected int getViewIndexAtPosition(int pos) + { + int index = -1; + boolean found = false; + if (pos >= getStartOffset() && pos <= getEndOffset()) + { + int upper = getViewCount() - 1; + int lower = 0; + index = (upper - lower) / 2 + lower; + int bias = 0; + do + { + View child = getView(index); + int childStart = child.getStartOffset(); + int childEnd = child.getEndOffset(); + if (pos >= childStart && pos < childEnd) + found = true; + else if (pos < childStart) + { + upper = index; + bias = -1; + } + else if (pos >= childEnd) + { + lower = index; + bias = 1; + } + if (! found) + { + int newIndex = (upper - lower) / 2 + lower; + if (newIndex == index) + index = newIndex + bias; + else + index = newIndex; + } + } while (upper != lower && ! found); + } + // If no child view actually covers the specified offset, reset index to + // -1. + if (! found) + index = -1; + return index; + } + + // -------------------------------------------------------------------------- + // View methods. + // -------------------------------------------------------------------------- + + public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + // TODO: Implement this. + } + + public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + // TODO: Implement this. + } + + protected boolean updateChildren(DocumentEvent.ElementChange ec, + DocumentEvent e, ViewFactory vf) + { + // TODO: Implement this. + return false; + } + + // -------------------------------------------------------------------------- + // Internal helper methods. + // -------------------------------------------------------------------------- + + /** + * A helper method to unload the oldest zones when there are more loaded + * zones then allowed. + */ + private void unloadOldestZones() + { + int maxZones = getMaxZonesLoaded(); + while (loadedZones.size() > maxZones) + { + View zone = (View) loadedZones.removeFirst(); + unloadZone(zone); + } + } + + /** + * Checks if the zone view at position <code>pos</code> should be split + * (its size is greater than maximumZoneSize) and tries to split it. + * + * @param pos the document position to check + */ + private void checkZoneAt(int pos) + { + int viewIndex = getViewIndexAtPosition(pos); //, Position.Bias.Forward); + View view = getView(viewIndex); + int p0 = view.getStartOffset(); + int p1 = view.getEndOffset(); + if (p1 - p0 > maximumZoneSize) + splitZone(viewIndex, p0, p1); + } + + /** + * Tries to break the view at the specified index and inside the specified + * range into pieces that are acceptable with respect to the maximum zone + * size. + * + * @param index the index of the view to split + * @param p0 the start offset + * @param p1 the end offset + */ + private void splitZone(int index, int p0, int p1) + { + ArrayList newZones = new ArrayList(); + int p = p0; + do + { + p0 = p; + p = Math.min(getPreferredZoneEnd(p0), p1); + newZones.add(createZone(p0, p)); + } while (p < p1); + View[] newViews = new View[newZones.size()]; + newViews = (View[]) newZones.toArray(newViews); + replace(index, 1, newViews); + } + + /** + * Calculates the positions at which a zone split is performed. This + * tries to create zones sized close to half the maximum zone size. + * + * @param start the start offset + * + * @return the preferred end offset + */ + private int getPreferredZoneEnd(int start) + { + Element el = getElement(); + int index = el.getElementIndex(start + (maximumZoneSize / 2)); + Element child = el.getElement(index); + int p0 = child.getStartOffset(); + int p1 = child.getEndOffset(); + int end = p1; + if (p0 - start > maximumZoneSize && p0 > start) + end = p0; + return end; + } +} diff --git a/javax/swing/text/html/CSS.java b/javax/swing/text/html/CSS.java index c248e758e..20a2debbc 100644 --- a/javax/swing/text/html/CSS.java +++ b/javax/swing/text/html/CSS.java @@ -37,6 +37,12 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.css.CSSColor; +import gnu.javax.swing.text.html.css.FontSize; +import gnu.javax.swing.text.html.css.FontStyle; +import gnu.javax.swing.text.html.css.FontWeight; +import gnu.javax.swing.text.html.css.Length; + import java.io.Serializable; import java.util.HashMap; @@ -459,4 +465,33 @@ public class CSS implements Serializable return defaultValue; } } + + /** + * Maps attribute values (String) to some converter class, based on the + * key. + * + * @param att the key + * @param v the value + * + * @return the wrapped value + */ + static Object getValue(Attribute att, String v) + { + Object o; + if (att == Attribute.FONT_SIZE) + o = new FontSize(v); + else if (att == Attribute.FONT_WEIGHT) + o = new FontWeight(v); + else if (att == Attribute.FONT_STYLE) + o = new FontStyle(v); + else if (att == Attribute.COLOR || att == Attribute.BACKGROUND_COLOR) + o = new CSSColor(v); + else if (att == Attribute.MARGIN || att == Attribute.MARGIN_BOTTOM + || att == Attribute.MARGIN_LEFT || att == Attribute.MARGIN_RIGHT + || att == Attribute.MARGIN_TOP) + o = new Length(v); + else + o = v; + return o; + } } diff --git a/javax/swing/text/html/HTMLDocument.java b/javax/swing/text/html/HTMLDocument.java index f7b081c6a..00372cd36 100644 --- a/javax/swing/text/html/HTMLDocument.java +++ b/javax/swing/text/html/HTMLDocument.java @@ -39,7 +39,6 @@ exception statement from your version. */ package javax.swing.text.html; import gnu.classpath.NotImplementedException; -import gnu.javax.swing.text.html.CharacterAttributeTranslator; import gnu.javax.swing.text.html.parser.htmlAttributeSet; import java.io.IOException; @@ -50,8 +49,6 @@ import java.util.Stack; import java.util.Vector; import javax.swing.JEditorPane; -import javax.swing.event.DocumentEvent; -import javax.swing.event.HyperlinkEvent.EventType; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; @@ -87,8 +84,6 @@ public class HTMLDocument extends DefaultStyledDocument boolean preservesUnknownTags = true; int tokenThreshold = Integer.MAX_VALUE; HTMLEditorKit.Parser parser; - StyleSheet styleSheet; - AbstractDocument.Content content; /** * Constructs an HTML document using the default buffer size and a default @@ -96,7 +91,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public HTMLDocument() { - this(null); + this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet()); } /** @@ -119,14 +114,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public HTMLDocument(AbstractDocument.Content c, StyleSheet styles) { - this.content = c; - if (styles == null) - { - styles = new StyleSheet(); - styles.importStyleSheet(getClass().getResource(HTMLEditorKit. - DEFAULT_CSS)); - } - this.styleSheet = styles; + super(c, styles); } /** @@ -137,7 +125,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public StyleSheet getStyleSheet() { - return styleSheet; + return (StyleSheet) getAttributeContext(); } /** @@ -269,7 +257,7 @@ public class HTMLDocument extends DefaultStyledDocument public void setBase(URL u) { baseURL = u; - styleSheet.setBase(u); + getStyleSheet().setBase(u); } /** @@ -633,13 +621,8 @@ public class HTMLDocument extends DefaultStyledDocument // Put the old attribute set on the stack. pushCharacterStyle(); - // Translate tag.. return if succesful. - if(CharacterAttributeTranslator.translateTag(charAttr, t, a)) - return; - // Just add the attributes in <code>a</code>. - if (a != null) - charAttr.addAttribute(t, a.copyAttributes()); + charAttr.addAttribute(t, a.copyAttributes()); } /** @@ -812,7 +795,42 @@ public class HTMLDocument extends DefaultStyledDocument print ("AreaAction.end not implemented"); } } - + + /** + * Converts HTML tags to CSS attributes. + */ + class ConvertAction + extends TagAction + { + + public void start(HTML.Tag tag, MutableAttributeSet atts) + { + pushCharacterStyle(); + charAttr.addAttribute(tag, atts.copyAttributes()); + StyleSheet styleSheet = getStyleSheet(); + // TODO: Add other tags here. + if (tag == HTML.Tag.FONT) + { + String color = (String) atts.getAttribute(HTML.Attribute.COLOR); + if (color != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color); + String face = (String) atts.getAttribute(HTML.Attribute.FACE); + if (face != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, + face); + String size = (String) atts.getAttribute(HTML.Attribute.SIZE); + if (size != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE, + size); + } + } + + public void end(HTML.Tag tag) + { + popCharacterStyle(); + } + } + class BaseAction extends TagAction { /** @@ -1028,7 +1046,7 @@ public class HTMLDocument extends DefaultStyledDocument StyleAction styleAction = new StyleAction(); TitleAction titleAction = new TitleAction(); - + ConvertAction convertAction = new ConvertAction(); tagToAction.put(HTML.Tag.A, characterAction); tagToAction.put(HTML.Tag.ADDRESS, characterAction); tagToAction.put(HTML.Tag.APPLET, hiddenAction); @@ -1051,7 +1069,7 @@ public class HTMLDocument extends DefaultStyledDocument tagToAction.put(HTML.Tag.DL, blockAction); tagToAction.put(HTML.Tag.DT, paragraphAction); tagToAction.put(HTML.Tag.EM, characterAction); - tagToAction.put(HTML.Tag.FONT, characterAction); + tagToAction.put(HTML.Tag.FONT, convertAction); tagToAction.put(HTML.Tag.FORM, blockAction); tagToAction.put(HTML.Tag.FRAME, specialAction); tagToAction.put(HTML.Tag.FRAMESET, blockAction); @@ -1163,7 +1181,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleText(char[] data, int pos) { - if (data != null && data.length > 0) + if (shouldInsert() && data != null && data.length > 0) addContent(data, 0, data.length); } @@ -1728,4 +1746,19 @@ public void setOuterHTML(Element elem, String htmlText) // TODO charset getParser().parse(new StringReader(htmlText), reader, true); } + + /** + * Overridden to tag content with the synthetic HTML.Tag.CONTENT + * tag. + */ + protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att) + { + if (att == null) + { + SimpleAttributeSet sas = new SimpleAttributeSet(); + sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); + att = sas; + } + super.insertUpdate(evt, att); + } } diff --git a/javax/swing/text/html/HTMLEditorKit.java b/javax/swing/text/html/HTMLEditorKit.java index b852d568c..3bf380c6f 100644 --- a/javax/swing/text/html/HTMLEditorKit.java +++ b/javax/swing/text/html/HTMLEditorKit.java @@ -48,6 +48,8 @@ import java.awt.event.MouseMotionListener; import java.awt.Cursor; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; @@ -64,7 +66,6 @@ import javax.swing.text.EditorKit; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.StyleConstants; -import javax.swing.text.StyleContext; import javax.swing.text.StyledEditorKit; import javax.swing.text.TextAction; import javax.swing.text.View; @@ -804,7 +805,7 @@ public class HTMLEditorKit /** * The current style sheet. */ - StyleSheet styleSheet; + private StyleSheet styleSheet; /** * The ViewFactory for HTMLFactory. @@ -831,11 +832,6 @@ public class HTMLEditorKit */ LinkController mouseListener; - /** - * Style context for this editor. - */ - StyleContext styleContext; - /** The content type */ String contentType = "text/html"; @@ -850,11 +846,7 @@ public class HTMLEditorKit */ public HTMLEditorKit() { - super(); - styleContext = new StyleContext(); - styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS)); - // FIXME: Set inputAttributes with default.css + // Nothing to do here. } /** @@ -923,8 +915,7 @@ public class HTMLEditorKit if (parser == null) throw new IOException("Parser is null."); - ParserCallback pc = ((HTMLDocument) doc).getReader - (offset, popDepth, pushDepth, insertTag); + ParserCallback pc = doc.getReader(offset, popDepth, pushDepth, insertTag); // FIXME: What should ignoreCharSet be set to? @@ -1154,8 +1145,18 @@ public class HTMLEditorKit { if (styleSheet == null) { - styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS)); + try + { + styleSheet = new StyleSheet(); + InputStream in = getClass().getResourceAsStream(DEFAULT_CSS); + InputStreamReader r = new InputStreamReader(in); + styleSheet.loadRules(r, null); + r.close(); + } + catch (IOException ex) + { + // No style available. + } } return styleSheet; } diff --git a/javax/swing/text/html/InlineView.java b/javax/swing/text/html/InlineView.java index 77ec86e82..31eaa129c 100644 --- a/javax/swing/text/html/InlineView.java +++ b/javax/swing/text/html/InlineView.java @@ -60,6 +60,11 @@ public class InlineView { /** + * The attributes used by this view. + */ + private AttributeSet attributes; + + /** * Creates a new <code>InlineView</code> that renders the specified element. * * @param element the element for this view @@ -115,7 +120,9 @@ public class InlineView public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { super.changedUpdate(e, a, f); - setPropertiesFromAttributes(); + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + preferenceChanged(null, true, true); } /** @@ -126,8 +133,12 @@ public class InlineView */ public AttributeSet getAttributes() { - // FIXME: Implement this. - return super.getAttributes(); + if (attributes == null) + { + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + } + return attributes; } @@ -143,10 +154,43 @@ public class InlineView return super.breakView(axis, offset, pos, len); } + /** + * Loads the character style properties from the stylesheet. + */ protected void setPropertiesFromAttributes() { - // FIXME: Implement this. super.setPropertiesFromAttributes(); + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.TEXT_DECORATION); + + // Check for underline. + boolean b = false; + if (o != null && o.toString().contains("underline")) + b = true; + setUnderline(b); + + // Check for line-through. + b = false; + if (o != null && o.toString().contains("line-through")) + b = true; + setStrikeThrough(b); + + // Check for vertical alignment (subscript/superscript). + o = atts.getAttribute(CSS.Attribute.VERTICAL_ALIGN); + + // Subscript. + b = false; + if (o != null && o.toString().contains("sub")) + b = true; + setSubscript(b); + + // Superscript. + b = false; + if (o != null && o.toString().contains("sup")) + b = true; + setSuperscript(b); + + // TODO: Handle white-space: nowrap property. } /** diff --git a/javax/swing/text/html/MultiAttributeSet.java b/javax/swing/text/html/MultiAttributeSet.java new file mode 100644 index 000000000..0f1145084 --- /dev/null +++ b/javax/swing/text/html/MultiAttributeSet.java @@ -0,0 +1,213 @@ +/* MultiAttributeSet.java -- Multiplexes between a set of AttributeSets + 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 javax.swing.text.html; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +import javax.swing.text.AttributeSet; +import javax.swing.text.SimpleAttributeSet; + +/** + * An AttributeSet impl that multiplexes between a set of other AttributeSets. + * + * @author Roman Kennke (kennke@aicas.com) + */ +class MultiAttributeSet + implements AttributeSet +{ + + /** + * The Enumeration for the multiplexed names. + */ + private class MultiNameEnumeration + implements Enumeration + { + /** + * The index of the current AttributeSet. + */ + private int index; + + /** + * The names Enumeration of the current AttributeSet. + */ + private Enumeration current; + + /** + * Creates a new instance. + */ + MultiNameEnumeration() + { + index = 0; + current = multi[0].getAttributeNames(); + } + + public boolean hasMoreElements() + { + return current.hasMoreElements() || index < multi.length - 1; + } + + public Object nextElement() + { + if (! current.hasMoreElements()) + { + if (index < multi.length - 1) + { + index++; + current = multi[index].getAttributeNames(); + } + else + throw new NoSuchElementException(); + } + return current.nextElement(); + } + + } + + /** + * The AttributeSets to multiplex. + */ + AttributeSet[] multi; + + /** + * Provided for subclasses that need to initialize via {@link #init}. + */ + MultiAttributeSet() + { + // Nothing to do here. + } + + /** + * Creates a new instance. + * + * @param m the AttributeSets to multiplex + */ + MultiAttributeSet(AttributeSet[] m) + { + init(m); + } + + /** + * Provided for subclasses to initialize the attribute set. + * + * @param m the attributes to multiplex + */ + void init(AttributeSet[] m) + { + multi = m; + } + + public boolean containsAttribute(Object name, Object value) + { + boolean ret = false; + for (int i = 0; i < multi.length && ret == false; i++) + { + if (multi[i].containsAttribute(name, value)) + ret = true; + } + return ret; + } + + public boolean containsAttributes(AttributeSet attributes) + { + boolean ret = true; + Enumeration e = attributes.getAttributeNames(); + while (ret && e.hasMoreElements()) + { + Object key = e.nextElement(); + ret = attributes.getAttribute(key).equals(getAttribute(key)); + } + return ret; + } + + public AttributeSet copyAttributes() + { + SimpleAttributeSet copy = new SimpleAttributeSet(); + for (int i = 0; i < multi.length; i++) + { + copy.addAttributes(multi[i]); + } + return copy; + } + + public Object getAttribute(Object key) + { + Object ret = null; + for (int i = 0; i < multi.length && ret == null; i++) + { + ret = multi[i].getAttribute(key); + } + return ret; + } + + public int getAttributeCount() + { + int n = 0; + for (int i = 0; i < multi.length; i++) + { + n += multi[i].getAttributeCount(); + } + return n; + } + + public Enumeration getAttributeNames() + { + return new MultiNameEnumeration(); + } + + public AttributeSet getResolveParent() + { + return null; + } + + public boolean isDefined(Object attrName) + { + boolean ret = false; + for (int i = 0; i < multi.length && ! ret; i++) + ret = multi[i].isDefined(attrName); + return ret; + } + + public boolean isEqual(AttributeSet attr) + { + return getAttributeCount() == attr.getAttributeCount() + && containsAttributes(attr); + } + +} diff --git a/javax/swing/text/html/MultiStyle.java b/javax/swing/text/html/MultiStyle.java new file mode 100644 index 000000000..3937bff75 --- /dev/null +++ b/javax/swing/text/html/MultiStyle.java @@ -0,0 +1,136 @@ +/* MultiStyle.java -- Multiplexes between several Styles + 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 javax.swing.text.html; + +import java.util.Enumeration; + +import javax.swing.event.ChangeListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.Style; + +/** + * A Style implementation that is able to multiplex between several other + * Styles. This is used for CSS style resolving. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class MultiStyle + extends MultiAttributeSet + implements Style +{ + + // FIXME: Fix the implementation to also return attributes that + // are added to this style, etc. However, this is not really needed + // now for CSS, but would be nice for correctness. + + /** + * The name of the style. + */ + private String name; + + /** + * The attributes added to this style. + */ + private SimpleAttributeSet attributes; + + /** + * Creates a new instance. + * + * @param n the name + * @param m the styles to multiplex + */ + public MultiStyle(String n, AttributeSet[] m) + { + super(m); + name = n; + attributes = new SimpleAttributeSet(); + } + + /** + * Returns the name of the style. + * + * @return the name of the style + */ + public String getName() + { + return name; + } + + public void addChangeListener(ChangeListener listener) + { + // TODO: Implement. + } + + public void removeChangeListener(ChangeListener listener) + { + // TODO: Implement. + } + + public void addAttribute(Object name, Object value) + { + attributes.addAttribute(name, value); + } + + public void addAttributes(AttributeSet atts) + { + attributes.addAttributes(atts); + } + + public void removeAttribute(Object name) + { + attributes.removeAttribute(name); + } + + public void removeAttributes(Enumeration names) + { + attributes.removeAttribute(names); + } + + public void removeAttributes(AttributeSet atts) + { + attributes.removeAttribute(atts); + } + + public void setResolveParent(AttributeSet parent) + { + // TODO: Implement. + } + +} diff --git a/javax/swing/text/html/ParagraphView.java b/javax/swing/text/html/ParagraphView.java index 2339f4e66..951f70b60 100644 --- a/javax/swing/text/html/ParagraphView.java +++ b/javax/swing/text/html/ParagraphView.java @@ -39,12 +39,14 @@ exception statement from your version. */ package javax.swing.text.html; import java.awt.Graphics; +import java.awt.Rectangle; import java.awt.Shape; import javax.swing.SizeRequirements; import javax.swing.text.AttributeSet; import javax.swing.text.Document; import javax.swing.text.Element; +import javax.swing.text.StyleConstants; import javax.swing.text.View; /** @@ -55,10 +57,20 @@ import javax.swing.text.View; * @author Roman Kennke (kennke@aicas.com) */ public class ParagraphView - extends javax.swing.text.ParagraphView + extends javax.swing.text.ParagraphView { /** + * The attributes used by this view. + */ + private AttributeSet attributes; + + /** + * The stylesheet's box painter. + */ + private StyleSheet.BoxPainter painter; + + /** * Creates a new ParagraphView for the specified element. * * @param element the element @@ -88,8 +100,11 @@ public class ParagraphView */ public AttributeSet getAttributes() { - // FIXME: Implement this multiplexing thing. - return super.getAttributes(); + if (attributes == null) + { + attributes = getStyleSheet().getViewAttributes(this); + } + return attributes; } /** @@ -98,7 +113,32 @@ public class ParagraphView */ protected void setPropertiesFromAttributes() { - // FIXME: Implement this. + super.setPropertiesFromAttributes(); + + // Fetch CSS attributes. + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN); + if (o != null) + { + String align = o.toString(); + if (align.equals("left")) + setJustification(StyleConstants.ALIGN_LEFT); + else if (align.equals("right")) + setJustification(StyleConstants.ALIGN_RIGHT); + else if (align.equals("center")) + setJustification(StyleConstants.ALIGN_CENTER); + else if (align.equals("justify")) + setJustification(StyleConstants.ALIGN_JUSTIFIED); + } + + // Fetch StyleSheet's box painter. + painter = getStyleSheet().getBoxPainter(atts); + setInsets((short) painter.getInset(TOP, this), + (short) painter.getInset(LEFT, this), + (short) painter.getInset(BOTTOM, this), + (short) painter.getInset(RIGHT, this)); + + // TODO: Handle CSS width and height attributes somehow. } /** @@ -147,15 +187,20 @@ public class ParagraphView } /** - * Paints this view. This delegates to the superclass after the coordinates - * have been updated for tab calculations. + * Paints this view. This paints the box using the stylesheet's + * box painter for this view and delegates to the super class paint() + * afterwards. * * @param g the graphics object * @param a the current allocation of this view */ public void paint(Graphics g, Shape a) { - // FIXME: Implement the above specified behaviour. + if (a != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + painter.paint(g, r.x, r.y, r.width, r.height, this); + } super.paint(g, a); } diff --git a/javax/swing/text/html/StyleSheet.java b/javax/swing/text/html/StyleSheet.java index 01853d163..520076652 100644 --- a/javax/swing/text/html/StyleSheet.java +++ b/javax/swing/text/html/StyleSheet.java @@ -38,28 +38,35 @@ exception statement from your version. */ package javax.swing.text.html; -import gnu.javax.swing.text.html.CharacterAttributeTranslator; +import gnu.classpath.NotImplementedException; +import gnu.javax.swing.text.html.css.CSSColor; +import gnu.javax.swing.text.html.css.CSSParser; +import gnu.javax.swing.text.html.css.CSSParserCallback; +import gnu.javax.swing.text.html.css.FontSize; +import gnu.javax.swing.text.html.css.FontStyle; +import gnu.javax.swing.text.html.css.FontWeight; +import gnu.javax.swing.text.html.css.Length; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; - import java.io.IOException; import java.io.Reader; import java.io.Serializable; -import java.io.StringReader; - -import java.net.MalformedURLException; import java.net.URL; - +import java.util.ArrayList; import java.util.Enumeration; -import java.util.Vector; +import java.util.HashMap; +import java.util.List; +import java.util.StringTokenizer; +import javax.swing.event.ChangeListener; import javax.swing.text.AttributeSet; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.Style; +import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.View; @@ -85,6 +92,91 @@ import javax.swing.text.View; public class StyleSheet extends StyleContext { + /** + * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css. + */ + private class CSSStyleSheetParserCallback + implements CSSParserCallback + { + /** + * The selector for which the rules are currently parsed. + */ + private String[] selector; + + /** + * Called at the beginning of a statement. + * + * @param sel the selector + */ + public void startStatement(String sel) + { + StringTokenizer tokens = new StringTokenizer(sel); + selector = new String[tokens.countTokens()]; + for (int index = 0; tokens.hasMoreTokens(); index++) + selector[index] = tokens.nextToken(); + } + + /** + * Called at the end of a statement. + */ + public void endStatement() + { + selector = null; + } + + /** + * Called when a declaration is parsed. + * + * @param property the property + * @param value the value + */ + public void declaration(String property, String value) + { + for (int i = 0; i < selector.length; i++) + { + CSSStyle style = (CSSStyle) css.get(selector[i]); + if (style == null) + { + style = new CSSStyle(); + css.put(selector[i], style); + } + CSS.Attribute cssAtt = CSS.getAttribute(property); + Object val = CSS.getValue(cssAtt, value); + if (cssAtt != null) + style.addAttribute(cssAtt, val); + // else // For debugging only. + // System.err.println("no mapping for: " + property); + } + } + + } + + /** + * Represents a style that is defined by a CSS rule. + */ + private class CSSStyle + extends SimpleAttributeSet + implements Style + { + + public String getName() + { + // TODO: Implement this for correctness. + return null; + } + + public void addChangeListener(ChangeListener listener) + { + // TODO: Implement this for correctness. + } + + public void removeChangeListener(ChangeListener listener) + { + // TODO: Implement this for correctness. + } + + } + /** The base URL */ URL base; @@ -93,7 +185,18 @@ public class StyleSheet extends StyleContext /** The style sheets stored. */ StyleSheet[] styleSheet; - + + /** + * Maps element names (selectors) to AttributSet (the corresponding style + * information). + */ + HashMap css = new HashMap(); + + /** + * Maps selectors to their resolved styles. + */ + private HashMap resolvedStyles; + /** * Constructs a StyleSheet. */ @@ -101,6 +204,7 @@ public class StyleSheet extends StyleContext { super(); baseFontSize = 4; // Default font size from CSS + resolvedStyles = new HashMap(); } /** @@ -114,10 +218,171 @@ public class StyleSheet extends StyleContext */ public Style getRule(HTML.Tag t, Element e) { - // FIXME: Not implemented. - return null; + // Create list of the element and all of its parents, starting + // with the bottommost element. + ArrayList path = new ArrayList(); + Element el; + AttributeSet atts; + for (el = e; el != null; el = el.getParentElement()) + path.add(el); + + // Create fully qualified selector. + StringBuilder selector = new StringBuilder(); + int count = path.size(); + // We append the actual element after this loop. + for (int i = count - 1; i > 0; i--) + { + el = (Element) path.get(i); + atts = el.getAttributes(); + Object name = atts.getAttribute(StyleConstants.NameAttribute); + selector.append(name.toString()); + if (atts.isDefined(HTML.Attribute.ID)) + { + selector.append('#'); + selector.append(atts.getAttribute(HTML.Attribute.ID)); + } + else if (atts.isDefined(HTML.Attribute.CLASS)) + { + selector.append('.'); + selector.append(atts.getAttribute(HTML.Attribute.CLASS)); + } + selector.append(' '); + } + selector.append(t.toString()); + el = (Element) path.get(0); + atts = el.getAttributes(); + // For leaf elements, we have to fetch the tag specific attributes. + if (el.isLeaf()) + { + Object o = atts.getAttribute(t); + if (o instanceof AttributeSet) + atts = (AttributeSet) o; + else + atts = null; + } + if (atts != null) + { + if (atts.isDefined(HTML.Attribute.ID)) + { + selector.append('#'); + selector.append(atts.getAttribute(HTML.Attribute.ID)); + } + else if (atts.isDefined(HTML.Attribute.CLASS)) + { + selector.append('.'); + selector.append(atts.getAttribute(HTML.Attribute.CLASS)); + } + } + return getResolvedStyle(selector.toString(), path, t); } - + + /** + * Fetches a resolved style. If there is no resolved style for the + * specified selector, the resolve the style using + * {@link #resolveStyle(String, List, HTML.Tag)}. + * + * @param selector the selector for which to resolve the style + * @param path the Element path, used in the resolving algorithm + * @param tag the tag for which to resolve + * + * @return the resolved style + */ + private Style getResolvedStyle(String selector, List path, HTML.Tag tag) + { + Style style = (Style) resolvedStyles.get(selector); + if (style == null) + style = resolveStyle(selector, path, tag); + return style; + } + + /** + * Resolves a style. This creates arrays that hold the tag names, + * class and id attributes and delegates the work to + * {@link #resolveStyle(String, String[], String[], String[])}. + * + * @param selector the selector + * @param path the Element path + * @param tag the tag + * + * @return the resolved style + */ + private Style resolveStyle(String selector, List path, HTML.Tag tag) + { + int count = path.size(); + String[] tags = new String[count]; + String[] ids = new String[count]; + String[] classes = new String[count]; + for (int i = 0; i < count; i++) + { + Element el = (Element) path.get(i); + AttributeSet atts = el.getAttributes(); + if (i == 0 && el.isLeaf()) + { + Object o = atts.getAttribute(tag); + if (o instanceof AttributeSet) + atts = (AttributeSet) o; + else + atts = null; + } + if (atts != null) + { + HTML.Tag t = + (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); + if (t != null) + tags[i] = t.toString(); + else + tags[i] = null; + if (atts.isDefined(HTML.Attribute.CLASS)) + classes[i] = atts.getAttribute(HTML.Attribute.CLASS).toString(); + else + classes[i] = null; + if (atts.isDefined(HTML.Attribute.ID)) + ids[i] = atts.getAttribute(HTML.Attribute.ID).toString(); + else + ids[i] = null; + } + else + { + tags[i] = null; + classes[i] = null; + ids[i] = null; + } + } + tags[0] = tag.toString(); + return resolveStyle(selector, tags, ids, classes); + } + + /** + * Performs style resolving. + * + * @param selector the selector + * @param tags the tags + * @param ids the corresponding ID attributes + * @param classes the corresponding CLASS attributes + * + * @return the resolved style + */ + private Style resolveStyle(String selector, String[] tags, String[] ids, + String[] classes) + { + // FIXME: This style resolver is not correct. But it works good enough for + // the default.css. + int count = tags.length; + ArrayList styles = new ArrayList(); + for (int i = 0; i < count; i++) + { + Style style = (Style) css.get(tags[i]); + if (style != null) + styles.add(style); + // FIXME: Handle ID and CLASS attributes. + } + Style[] styleArray = new Style[styles.size()]; + Style resolved = new MultiStyle(selector, + (Style[]) styles.toArray(styleArray)); + resolvedStyles.put(selector, resolved); + return resolved; + } + /** * Gets the rule that best matches the selector. selector is a space * separated String of element names. The attributes of the returned @@ -128,27 +393,21 @@ public class StyleSheet extends StyleContext */ public Style getRule(String selector) { - // FIXME: Not implemented. - return null; + // FIXME: This is a very rudimentary implementation. Should + // be extended to conform to the CSS spec. + return (Style) css.get(selector); } /** - * Adds a set if rules to the sheet. The rules are expected to be in valid + * Adds a set of rules to the sheet. The rules are expected to be in valid * CSS format. This is called as a result of parsing a <style> tag * * @param rule - the rule to add to the sheet */ public void addRule(String rule) + throws NotImplementedException { - CssParser cp = new CssParser(); - try - { - cp.parse(base, new StringReader(rule), false, false); - } - catch (IOException io) - { - // Do nothing here. - } + // FIXME: Implement. } /** @@ -176,10 +435,13 @@ public class StyleSheet extends StyleContext * parameter. * @throws IOException - For any IO error while reading */ - public void loadRules(Reader in, URL ref) throws IOException + public void loadRules(Reader in, URL ref) + throws IOException { - CssParser cp = new CssParser(); - cp.parse(ref, in, false, false); + CSSStyleSheetParserCallback cb = new CSSStyleSheetParserCallback(); + // FIXME: Handle ref. + CSSParser parser = new CSSParser(in, cb); + parser.parse(); } /** @@ -191,8 +453,7 @@ public class StyleSheet extends StyleContext */ public AttributeSet getViewAttributes(View v) { - // FIXME: Not implemented. - return null; + return new ViewAttributeSet(v, this); } /** @@ -310,7 +571,8 @@ public class StyleSheet extends StyleContext public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, String value) { - attr.addAttribute(key, value); + Object val = CSS.getValue(key, value); + attr.addAttribute(key, val); } /** @@ -340,8 +602,11 @@ public class StyleSheet extends StyleContext */ public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) { - // FIXME: Not implemented. - return null; + // FIXME: Really convert HTML to CSS here. + AttributeSet cssAttr = htmlAttrSet.copyAttributes(); + MutableAttributeSet cssStyle = addStyle(null, null); + cssStyle.addAttributes(cssAttr); + return cssStyle; } /** @@ -455,7 +720,31 @@ public class StyleSheet extends StyleContext */ public Font getFont(AttributeSet a) { - return super.getFont(a); + FontSize size = (FontSize) a.getAttribute(CSS.Attribute.FONT_SIZE); + int realSize = 12; + if (size != null) + realSize = size.getValue(); + + // Decrement size for subscript and superscript. + Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); + if (valign != null) + { + String v = valign.toString(); + if (v.contains("sup") || v.contains("sub")) + realSize -= 2; + } + + // TODO: Convert font family. + String family = "SansSerif"; + + int style = Font.PLAIN; + FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT); + if (weight != null) + style |= weight.getValue(); + FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE); + if (fStyle != null) + style |= fStyle.getValue(); + return new Font(family, style, realSize); } /** @@ -468,7 +757,11 @@ public class StyleSheet extends StyleContext */ public Color getForeground(AttributeSet a) { - return super.getForeground(a); + CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR); + Color color = null; + if (c != null) + color = c.getValue(); + return color; } /** @@ -481,7 +774,11 @@ public class StyleSheet extends StyleContext */ public Color getBackground(AttributeSet a) { - return super.getBackground(a); + CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR); + Color color = null; + if (c != null) + color = c.getValue(); + return color; } /** @@ -595,7 +892,7 @@ public class StyleSheet extends StyleContext */ public Color stringToColor(String colorName) { - return CharacterAttributeTranslator.getColor(colorName); + return CSSColor.convertValue(colorName); } /** @@ -609,12 +906,12 @@ public class StyleSheet extends StyleContext */ public static class BoxPainter extends Object implements Serializable { - - /** - * Attribute set for painter - */ - AttributeSet as; - + + private float leftInset; + private float rightInset; + private float topInset; + private float bottomInset; + /** * Package-private constructor. * @@ -622,9 +919,21 @@ public class StyleSheet extends StyleContext */ BoxPainter(AttributeSet as) { - this.as = as; + Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); + if (l != null) + leftInset = l.getValue(); + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT); + if (l != null) + rightInset = l.getValue(); + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP); + if (l != null) + topInset = l.getValue(); + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM); + if (l != null) + bottomInset = l.getValue(); } + /** * Gets the inset needed on a given side to account for the margin, border * and padding. @@ -638,8 +947,25 @@ public class StyleSheet extends StyleContext */ public float getInset(int size, View v) { - // FIXME: Not implemented. - return 0; + float inset; + switch (size) + { + case View.TOP: + inset = topInset; + break; + case View.BOTTOM: + inset = bottomInset; + break; + case View.LEFT: + inset = leftInset; + break; + case View.RIGHT: + inset = rightInset; + break; + default: + inset = 0.0F; + } + return inset; } /** @@ -701,207 +1027,5 @@ public class StyleSheet extends StyleContext // FIXME: Not implemented. } } - - /** - * The parser callback for the CSSParser. - */ - class CssParser implements CSSParser.CSSParserCallback - { - /** - * A vector of all the selectors. - * Each element is an array of all the selector tokens - * in a single rule. - */ - Vector selectors; - - /** A vector of all the selector tokens in a rule. */ - Vector selectorTokens; - /** Name of the current property. */ - String propertyName; - - /** The set of CSS declarations */ - MutableAttributeSet declaration; - - /** - * True if parsing a declaration, that is the Reader will not - * contain a selector. - */ - boolean parsingDeclaration; - - /** True if the attributes are coming from a linked/imported style. */ - boolean isLink; - - /** The base URL */ - URL base; - - /** The parser */ - CSSParser parser; - - /** - * Constructor - */ - CssParser() - { - selectors = new Vector(); - selectorTokens = new Vector(); - parser = new CSSParser(); - base = StyleSheet.this.base; - declaration = new SimpleAttributeSet(); - } - - /** - * Parses the passed in CSS declaration into an AttributeSet. - * - * @param s - the declaration - * @return the set of attributes containing the property and value. - */ - public AttributeSet parseDeclaration(String s) - { - try - { - return parseDeclaration(new StringReader(s)); - } - catch (IOException e) - { - // Do nothing here. - } - return null; - } - - /** - * Parses the passed in CSS declaration into an AttributeSet. - * - * @param r - the reader - * @return the attribute set - * @throws IOException from the reader - */ - public AttributeSet parseDeclaration(Reader r) throws IOException - { - parse(base, r, true, false); - return declaration; - } - - /** - * Parse the given CSS stream - * - * @param base - the url - * @param r - the reader - * @param parseDec - True if parsing a declaration - * @param isLink - True if parsing a link - */ - public void parse(URL base, Reader r, boolean parseDec, boolean isLink) throws IOException - { - parsingDeclaration = parseDec; - this.isLink = isLink; - this.base = base; - - // flush out all storage - propertyName = null; - selectors.clear(); - selectorTokens.clear(); - declaration.removeAttributes(declaration); - - parser.parse(r, this, parseDec); - } - - /** - * Invoked when a valid @import is encountered, - * will call importStyleSheet if a MalformedURLException - * is not thrown in creating the URL. - * - * @param s - the string after @import - */ - public void handleImport(String s) - { - if (s != null) - { - try - { - if (s.startsWith("url(") && s.endsWith(")")) - s = s.substring(4, s.length() - 1); - if (s.indexOf("\"") >= 0) - s = s.replaceAll("\"",""); - - URL url = new URL(s); - if (url == null && base != null) - url = new URL(base, s); - - importStyleSheet(url); - } - catch (MalformedURLException e) - { - // Do nothing here. - } - } - } - - /** - * A selector has been encountered. - * - * @param s - a selector (e.g. P or UL or even P,) - */ - public void handleSelector(String s) - { - if (s.endsWith(",")) - s = s.substring(0, s.length() - 1); - - selectorTokens.addElement(s); - addSelector(); - } - - /** - * Invoked when the start of a rule is encountered. - */ - public void startRule() - { - addSelector(); - } - - /** - * Invoked when a property name is encountered. - * - * @param s - the property - */ - public void handleProperty(String s) - { - propertyName = s; - } - - /** - * Invoked when a property value is encountered. - * - * @param s - the value - */ - public void handleValue(String s) - { - // call addCSSAttribute - // FIXME: Not implemented - } - - /** - * Invoked when the end of a rule is encountered. - */ - public void endRule() - { - // FIXME: Not implemented - // add rules - propertyName = null; - } - - /** - * Adds the selector to the vector. - */ - private void addSelector() - { - int length = selectorTokens.size(); - if (length > 0) - { - Object[] sel = new Object[length]; - System.arraycopy(selectorTokens.toArray(), 0, sel, 0, length); - selectors.add(sel); - selectorTokens.clear(); - } - } - } } diff --git a/javax/swing/text/html/ViewAttributeSet.java b/javax/swing/text/html/ViewAttributeSet.java new file mode 100644 index 000000000..25db89fc4 --- /dev/null +++ b/javax/swing/text/html/ViewAttributeSet.java @@ -0,0 +1,163 @@ +/* ViewAttributeSet.java -- The AttributeSet used by HTML views + 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 javax.swing.text.html; + +import java.util.ArrayList; +import java.util.Enumeration; + +import javax.swing.text.AttributeSet; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; +import javax.swing.text.View; + +/** + * An AttributeSet implemenation that is used by the HTML views. This + * AttributeSet is created by StyleSheet.getViewAttributes() and combines + * the following attributes: + * - The original attributes of the View's element. + * - Any translated (HTML->CSS) attributes, as returned by + * StyleSheet.translateHTMLToCS(). + * - CSS Styles as resolved by the CSS stylesheet. + * + * In addition to that, it resolves attributes to the parent views, if + * a CSS attribute is requested that is inheritable. + * + * @author Roman Kennke (kennke@aicas.com) + */ +class ViewAttributeSet + extends MultiAttributeSet +{ + + /** + * The view for which we are the AttributeSet. + */ + private View view; + + /** + * The stylesheet to use. + */ + private StyleSheet styleSheet; + + /** + * Creates a new instance. + * + * @param v the view for which to do the AttributeSet + */ + ViewAttributeSet(View v, StyleSheet ss) + { + styleSheet = ss; + view = v; + ArrayList atts = new ArrayList(); + + Element el = v.getElement(); + AttributeSet elAtts = el.getAttributes(); + AttributeSet htmlAtts = styleSheet.translateHTMLToCSS(elAtts); + if (htmlAtts.getAttributeCount() > 0) + atts.add(htmlAtts); + + if (el.isLeaf()) + { + Enumeration n = elAtts.getAttributeNames(); + while (n.hasMoreElements()) + { + Object key = n.nextElement(); + if (key instanceof HTML.Tag) + { + AttributeSet rule = styleSheet.getRule((HTML.Tag) key, el); + if (rule != null) + atts.add(rule); + } + } + } + else + { + HTML.Tag tag = + (HTML.Tag) elAtts.getAttribute(StyleConstants.NameAttribute); + AttributeSet rule = styleSheet.getRule(tag, el); + if (rule != null) + atts.add(rule); + } + + AttributeSet[] atts1 = new AttributeSet[atts.size()]; + atts1 = (AttributeSet[]) atts.toArray(atts1); + init(atts1); + } + + /** + * Fetches the attribute for the specific ckey. If the attribute + * can't be found and the key is a CSS.Attribute that is inherited, + * then the attribute is looked up in the resolve parent. + */ + public Object getAttribute(Object key) + { + Object val = super.getAttribute(key); + if (val == null) + { + // Didn't find value. If the key is a CSS.Attribute, and is + // inherited, then ask the resolve parent. + if (key instanceof CSS.Attribute) + { + CSS.Attribute cssKey = (CSS.Attribute) key; + if (cssKey.isInherited()) + { + AttributeSet resolveParent = getResolveParent(); + if (resolveParent != null) + val = resolveParent.getAttribute(cssKey); + } + } + } + return val; + } + + /** + * Returns the resolve parent of this AttributeSet. This is the AttributeSet + * returned by the parent view if available. + */ + public AttributeSet getResolveParent() + { + AttributeSet parent = null; + if (view != null) + { + View parentView = view.getParent(); + if (parentView != null) + parent = parentView.getAttributes(); + } + return parent; + } +} diff --git a/javax/swing/tree/DefaultTreeCellEditor.java b/javax/swing/tree/DefaultTreeCellEditor.java index b0a4d8db8..4c10bfe1a 100644 --- a/javax/swing/tree/DefaultTreeCellEditor.java +++ b/javax/swing/tree/DefaultTreeCellEditor.java @@ -43,7 +43,6 @@ import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ActionEvent; @@ -59,10 +58,10 @@ import javax.swing.Icon; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.CellEditorListener; -import javax.swing.event.ChangeEvent; import javax.swing.event.EventListenerList; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; @@ -77,12 +76,6 @@ public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor, TreeSelectionListener { /** - * The number of the fast mouse clicks, required to start the editing - * session. - */ - static int CLICK_COUNT_TO_START = 3; - - /** * This container that appears on the tree during editing session. * It contains the editing component displays various other editor - * specific parts like editing icon. @@ -99,7 +92,7 @@ public class DefaultTreeCellEditor */ public EditorContainer() { - // Do nothing here. + setLayout(null); } /** @@ -111,12 +104,6 @@ public class DefaultTreeCellEditor // Do nothing here. } - public void setBounds(Rectangle bounds) - { - super.setBounds(bounds); - doLayout(); - } - /** * Overrides Container.paint to paint the node's icon and use the selection * color for the background. @@ -126,11 +113,20 @@ public class DefaultTreeCellEditor */ public void paint(Graphics g) { + // Paint editing icon. if (editingIcon != null) { // From the previous version, the left margin is taken as half // of the icon width. - editingIcon.paintIcon(this, g, 0, 0); + int y = Math.max(0, (getHeight() - editingIcon.getIconHeight()) / 2); + editingIcon.paintIcon(this, g, 0, y); + } + // Paint border. + Color c = getBorderSelectionColor(); + if (c != null) + { + g.setColor(c); + g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); } super.paint(g); } @@ -141,27 +137,33 @@ public class DefaultTreeCellEditor */ public void doLayout() { - // The offset of the editing component. - int eOffset; + if (editingComponent != null) + { + editingComponent.getPreferredSize(); + editingComponent.setBounds(offset, 0, getWidth() - offset, + getHeight()); + } + } - // Move the component to the left, leaving room for the editing icon: - if (editingIcon != null) - eOffset = editingIcon.getIconWidth(); + public Dimension getPreferredSize() + { + Dimension dim; + if (editingComponent != null) + { + dim = editingComponent.getPreferredSize(); + dim.width += offset + 5; + if (renderer != null) + { + Dimension r = renderer.getPreferredSize(); + dim.height = Math.max(dim.height, r.height); + } + if (editingIcon != null) + dim.height = Math.max(dim.height, editingIcon.getIconHeight()); + dim.width = Math.max(100, dim.width); + } else - eOffset = 0; - - Rectangle bounds = getBounds(); - Component c = getComponent(0); - c.setLocation(eOffset, 0); - - // Span the editing component near over all window width. - c.setSize(bounds.width - eOffset, bounds.height); - /* - * @specnote the Sun sets some more narrow editing component width (it is - * not documented how does it is calculated). However as our text field is - * still not able to auto - scroll horizontally, replicating such strategy - * would prevent adding extra characters to the text being edited. - */ + dim = new Dimension(0, 0); + return dim; } } @@ -227,46 +229,15 @@ public class DefaultTreeCellEditor */ public Dimension getPreferredSize() { - String s = getText(); - - Font f = getFont(); - - if (f != null) + Dimension size = super.getPreferredSize(); + if (renderer != null && DefaultTreeCellEditor.this.getFont() == null) { - FontMetrics fm = getToolkit().getFontMetrics(f); - - return new Dimension(SwingUtilities.computeStringWidth(fm, s), - fm.getHeight()); + size.height = renderer.getPreferredSize().height; } return renderer.getPreferredSize(); } } - /** - * Listens for the events from the realEditor. - */ - class RealEditorListener implements CellEditorListener - { - /** - * The method is called when the editing has been cancelled. - * @param event unused - */ - public void editingCanceled(ChangeEvent event) - { - cancelCellEditing(); - } - - /** - * The method is called after completing the editing session. - * - * @param event unused - */ - public void editingStopped(ChangeEvent event) - { - stopCellEditing(); - } - } - private EventListenerList listenerList = new EventListenerList(); /** @@ -367,21 +338,14 @@ public class DefaultTreeCellEditor public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, TreeCellEditor editor) { - setTree(tree); this.renderer = renderer; - - if (editor == null) - editor = createTreeCellEditor(); - else - editor.addCellEditorListener(new RealEditorListener()); - realEditor = editor; - - lastPath = tree.getLeadSelectionPath(); - tree.addTreeSelectionListener(this); + if (realEditor == null) + realEditor = createTreeCellEditor(); editingContainer = createContainer(); - setFont(UIManager.getFont("Tree.font")); - setBorderSelectionColor(UIManager.getColor("Tree.selectionBorderColor")); + setTree(tree); + Color c = UIManager.getColor("Tree.editorBorderSelectionColor"); + setBorderSelectionColor(c); } /** @@ -505,19 +469,36 @@ public class DefaultTreeCellEditor * @return the component for editing */ public Component getTreeCellEditorComponent(JTree tree, Object value, - boolean isSelected, boolean expanded, + boolean isSelected, + boolean expanded, boolean leaf, int row) { - if (realEditor == null) - realEditor = createTreeCellEditor(); - - return realEditor.getTreeCellEditorComponent(tree, value, isSelected, - expanded, leaf, row); + setTree(tree); + lastRow = row; + determineOffset(tree, value, isSelected, expanded, leaf, row); + if (editingComponent != null) + editingContainer.remove(editingComponent); + + editingComponent = realEditor.getTreeCellEditorComponent(tree, value, + isSelected, + expanded, leaf, + row); + Font f = getFont(); + if (f == null) + { + if (renderer != null) + f = renderer.getFont(); + if (f == null) + f = tree.getFont(); + } + editingContainer.setFont(f); + prepareForEditing(); + return editingContainer; } /** * Returns the value currently being edited (requests it from the - * {@link realEditor}. + * {@link #realEditor}. * * @return the value currently being edited */ @@ -535,16 +516,48 @@ public class DefaultTreeCellEditor * @return true if editing can be started */ public boolean isCellEditable(EventObject event) - { - if (editingComponent == null) - configureEditingComponent(tree, renderer, realEditor); - - if (editingComponent != null && realEditor.isCellEditable(event)) + { + boolean ret = false; + boolean ed = false; + if (event != null) { - prepareForEditing(); - return true; + if (event.getSource() instanceof JTree) + { + setTree((JTree) event.getSource()); + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + TreePath path = tree.getPathForLocation(me.getX(), me.getY()); + ed = lastPath != null && path != null && lastPath.equals(path); + if (path != null) + { + lastRow = tree.getRowForPath(path); + Object val = path.getLastPathComponent(); + boolean isSelected = tree.isRowSelected(lastRow); + boolean isExpanded = tree.isExpanded(path); + TreeModel m = tree.getModel(); + boolean isLeaf = m.isLeaf(val); + determineOffset(tree, val, isSelected, isExpanded, isLeaf, + lastRow); + } + } + } } - return false; + if (! realEditor.isCellEditable(event)) + ret = false; + else + { + if (canEditImmediately(event)) + ret = true; + else if (ed && shouldStartEditingTimer(event)) + startEditingTimer(); + else if (timer != null && timer.isRunning()) + timer.stop(); + } + if (ret) + prepareForEditing(); + return ret; + } /** @@ -567,14 +580,13 @@ public class DefaultTreeCellEditor */ public boolean stopCellEditing() { - if (editingComponent != null) + boolean ret = false; + if (realEditor.stopCellEditing()) { - stopEditingTimer(); - tree.stopEditing(); - editingComponent = null; - return true; + finish(); + ret = true; } - return false; + return ret; } /** @@ -583,21 +595,15 @@ public class DefaultTreeCellEditor */ public void cancelCellEditing() { - if (editingComponent != null) - { - tree.cancelEditing(); - editingComponent = null; - } - stopEditingTimer(); + realEditor.cancelCellEditing(); + finish(); } - - /** - * Stop the editing timer, if it is installed and running. - */ - private void stopEditingTimer() + + private void finish() { - if (timer != null && timer.isRunning()) - timer.stop(); + if (editingComponent != null) + editingContainer.remove(editingComponent); + editingComponent = null; } /** @@ -640,10 +646,18 @@ public class DefaultTreeCellEditor */ public void valueChanged(TreeSelectionEvent e) { - tPath = lastPath; - lastPath = e.getNewLeadSelectionPath(); - lastRow = tree.getRowForPath(lastPath); - stopCellEditing(); + if (tree != null) + { + if (tree.getSelectionCount() == 1) + lastPath = tree.getSelectionPath(); + else + lastPath = null; + } + // TODO: We really should do the following here, but can't due + // to buggy DefaultTreeSelectionModel. This selection model + // should only fire if the selection actually changes. +// if (timer != null) +// timer.stop(); } /** @@ -653,6 +667,8 @@ public class DefaultTreeCellEditor */ public void actionPerformed(ActionEvent e) { + if (tree != null && lastPath != null) + tree.startEditingAtPath(lastPath); } /** @@ -664,7 +680,17 @@ public class DefaultTreeCellEditor */ protected void setTree(JTree newTree) { - tree = newTree; + if (tree != newTree) + { + if (tree != null) + tree.removeTreeSelectionListener(this); + tree = newTree; + if (tree != null) + tree.addTreeSelectionListener(this); + + if (timer != null) + timer.stop(); + } } /** @@ -675,10 +701,14 @@ public class DefaultTreeCellEditor */ protected boolean shouldStartEditingTimer(EventObject event) { - if ((event instanceof MouseEvent) && - ((MouseEvent) event).getClickCount() == 1) - return true; - return false; + boolean ret = false; + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1 + && inHitRegion(me.getX(), me.getY()); + } + return ret; } /** @@ -686,8 +716,12 @@ public class DefaultTreeCellEditor */ protected void startEditingTimer() { - if (timer != null) - timer.start(); + if (timer == null) + { + timer = new Timer(1200, this); + timer.setRepeats(false); + } + timer.start(); } /** @@ -723,7 +757,6 @@ public class DefaultTreeCellEditor protected boolean inHitRegion(int x, int y) { Rectangle bounds = tree.getPathBounds(lastPath); - return bounds.contains(x, y); } @@ -739,13 +772,24 @@ public class DefaultTreeCellEditor protected void determineOffset(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { - renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded, - leaf, row, true); - Icon c = renderer.getIcon(); - if (c != null) - offset = renderer.getIconTextGap() + c.getIconWidth(); + if (renderer != null) + { + if (leaf) + editingIcon = renderer.getLeafIcon(); + else if (expanded) + editingIcon = renderer.getOpenIcon(); + else + editingIcon = renderer.getClosedIcon(); + if (editingIcon != null) + offset = renderer.getIconTextGap() + editingIcon.getIconWidth(); + else + offset = renderer.getIconTextGap(); + } else - offset = 0; + { + editingIcon = null; + offset = 0; + } } /** @@ -754,8 +798,8 @@ public class DefaultTreeCellEditor */ protected void prepareForEditing() { - editingContainer.removeAll(); - editingContainer.add(editingComponent); + if (editingComponent != null) + editingContainer.add(editingComponent); } /** @@ -776,10 +820,10 @@ public class DefaultTreeCellEditor */ protected TreeCellEditor createTreeCellEditor() { - DefaultCellEditor editor = new DefaultCellEditor(new DefaultTreeCellEditor.DefaultTextField( - UIManager.getBorder("Tree.selectionBorder"))); - editor.addCellEditorListener(new RealEditorListener()); - editor.setClickCountToStart(CLICK_COUNT_TO_START); + Border border = UIManager.getBorder("Tree.editorBorder"); + JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border); + DefaultCellEditor editor = new DefaultCellEditor(tf); + editor.setClickCountToStart(1); realEditor = editor; return editor; } diff --git a/javax/swing/tree/DefaultTreeCellRenderer.java b/javax/swing/tree/DefaultTreeCellRenderer.java index e120b71c1..26a30f0cc 100644 --- a/javax/swing/tree/DefaultTreeCellRenderer.java +++ b/javax/swing/tree/DefaultTreeCellRenderer.java @@ -547,19 +547,9 @@ public class DefaultTreeCellRenderer */ public Dimension getPreferredSize() { - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - - FontMetrics fm = getToolkit().getFontMetrics(getFont()); - SwingUtilities.layoutCompoundLabel((JLabel) this, fm, getText(), - getIcon(), getVerticalAlignment(), - getHorizontalAlignment(), - getVerticalTextPosition(), - getHorizontalTextPosition(), vr, ir, tr, - getIconTextGap()); - Rectangle cr = ir.union(tr); - return new Dimension(cr.width, cr.height); + Dimension size = super.getPreferredSize(); + size.width += 3; + return size; } /** diff --git a/javax/swing/tree/VariableHeightLayoutCache.java b/javax/swing/tree/VariableHeightLayoutCache.java index 11509b1b0..03251eb1f 100644 --- a/javax/swing/tree/VariableHeightLayoutCache.java +++ b/javax/swing/tree/VariableHeightLayoutCache.java @@ -451,8 +451,8 @@ public class VariableHeightLayoutCache { if (y < r.y) return r.y - y; - else if (y > r.y + r.height) - return y - (r.y + r.height); + else if (y > r.y + r.height - 1) + return y - (r.y + r.height - 1); else return 0; } |