diff options
author | Guilhem Lavaux <guilhem@kaffe.org> | 2006-08-14 09:40:47 +0000 |
---|---|---|
committer | Guilhem Lavaux <guilhem@kaffe.org> | 2006-08-14 09:40:47 +0000 |
commit | 310be467f8f83b189b4a40faece32478fad67bc9 (patch) | |
tree | 777d0c539432a12c86f450e8d3bf90e8e5677f57 /javax/swing | |
parent | 8f2887fc8c74aae0d541cbd59ea36c37d420267d (diff) | |
download | classpath-310be467f8f83b189b4a40faece32478fad67bc9.tar.gz |
2006-08-14 Guilhem Lavaux <guilhem@kaffe.org>
* Merged HEAD as of 2006-08-14 0:00.
Diffstat (limited to 'javax/swing')
83 files changed, 7933 insertions, 3582 deletions
diff --git a/javax/swing/AbstractButton.java b/javax/swing/AbstractButton.java index a9ec0c037..63f827a1a 100644 --- a/javax/swing/AbstractButton.java +++ b/javax/swing/AbstractButton.java @@ -199,7 +199,7 @@ public abstract class AbstractButton extends JComponent Icon pressed_icon; /** The icon displayed when the button is disabled. */ - Icon disabeldIcon; + Icon disabledIcon; /** The icon displayed when the button is selected. */ Icon selectedIcon; @@ -1364,6 +1364,9 @@ public abstract class AbstractButton extends JComponent { if (horizontalTextPosition == t) return; + if (t != LEFT && t != CENTER && t != RIGHT && t != LEADING + && t != TRAILING) + throw new IllegalArgumentException("Invalid alignment."); int old = horizontalTextPosition; horizontalTextPosition = t; @@ -1442,6 +1445,8 @@ public abstract class AbstractButton extends JComponent { if (verticalTextPosition == t) return; + if (t != TOP && t != CENTER && t != BOTTOM) + throw new IllegalArgumentException("Invalid alignment."); int old = verticalTextPosition; verticalTextPosition = t; @@ -1720,14 +1725,14 @@ public abstract class AbstractButton extends JComponent */ public Icon getDisabledIcon() { - if (disabeldIcon == null && default_icon instanceof ImageIcon) + if (disabledIcon == null && default_icon instanceof ImageIcon) { Image iconImage = ((ImageIcon) default_icon).getImage(); Image grayImage = GrayFilter.createDisabledImage(iconImage); - disabeldIcon = new ImageIcon(grayImage); + disabledIcon = new ImageIcon(grayImage); } - return disabeldIcon; + return disabledIcon; } /** @@ -1741,7 +1746,11 @@ public abstract class AbstractButton extends JComponent */ public void setDisabledIcon(Icon d) { - disabeldIcon = d; + if (disabledIcon == d) + return; + Icon old = disabledIcon; + disabledIcon = d; + firePropertyChange(DISABLED_ICON_CHANGED_PROPERTY, old, d); revalidate(); repaint(); } diff --git a/javax/swing/ButtonGroup.java b/javax/swing/ButtonGroup.java index 2f8d19831..efa36b5f6 100644 --- a/javax/swing/ButtonGroup.java +++ b/javax/swing/ButtonGroup.java @@ -1,5 +1,5 @@ /* ButtonGroup.java -- - Copyright (C) 2002 Free Software Foundation, Inc. + Copyright (C) 2002, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -65,10 +65,9 @@ import java.util.Vector; */ public class ButtonGroup implements Serializable { - /** DOCUMENT ME! */ private static final long serialVersionUID = 4259076101881721375L; - /** The buttons added to this button group. */ + /** Stores references to the buttons added to this button group. */ protected Vector buttons = new Vector(); /** The currently selected button model. */ @@ -83,12 +82,20 @@ public class ButtonGroup implements Serializable } /** - * Adds a button to this group. + * Adds a button to this group. If the button is in the selected state, then: + * <ul> + * <li>if the group has no current selection, the new button becomes the + * selected button for the group;</li> + * <li>if the group already has a selected button, the new button is set to + * "not selected".</li> + * </ul> * - * @param b the button to add + * @param b the button to add (<code>null</code> is ignored). */ public void add(AbstractButton b) { + if (b == null) + return; b.getModel().setGroup(this); if (b.isSelected()) { @@ -96,17 +103,24 @@ public class ButtonGroup implements Serializable sel = b.getModel(); else b.setSelected(false); - } buttons.addElement(b); + } + buttons.addElement(b); } /** - * Removed a given button from this group. + * Removes the specified button from this group. If the button is the + * selected button, the current selection is set to <code>null</code>. + * The group for the removed button's model is set to <code>null</code>. * - * @param b the button to remove + * @param b the button to remove (<code>null</code> is ignored). */ public void remove(AbstractButton b) { + if (b == null) + return; b.getModel().setGroup(null); + if (b.getModel() == sel) + sel = null; buttons.removeElement(b); } @@ -132,19 +146,20 @@ public class ButtonGroup implements Serializable } /** - * DOCUMENT ME! + * Returns the button that has the specified model, or <code>null</code> if + * there is no such button in the group. * - * @param m DOCUMENT ME! + * @param m the button model. * - * @return DOCUMENT ME! + * @return The button that has the specified model, or <code>null</code>. */ - AbstractButton FindButton(ButtonModel m) + AbstractButton findButton(ButtonModel m) { for (int i = 0; i < buttons.size(); i++) { - AbstractButton a = (AbstractButton) buttons.get(i); - if (a.getModel() == m) - return a; + AbstractButton a = (AbstractButton) buttons.get(i); + if (a.getModel() == m) + return a; } return null; } @@ -168,7 +183,7 @@ public class ButtonGroup implements Serializable if (old != null) old.setSelected(false); - AbstractButton button = FindButton(old); + AbstractButton button = findButton(old); if (button != null) button.repaint(); } @@ -180,10 +195,10 @@ public class ButtonGroup implements Serializable * Checks if the given <code>ButtonModel</code> is selected in this button * group. * - * @param m DOCUMENT ME! + * @param m the button model (<code>null</code> permitted). * - * @return true of given <code>ButtonModel</code> is selected, false - * otherwise + * @return <code>true</code> if <code>m</code> is the selected button model + * in this group, and <code>false</code> otherwise. */ public boolean isSelected(ButtonModel m) { diff --git a/javax/swing/DefaultBoundedRangeModel.java b/javax/swing/DefaultBoundedRangeModel.java index 10de4b948..efca148f4 100644 --- a/javax/swing/DefaultBoundedRangeModel.java +++ b/javax/swing/DefaultBoundedRangeModel.java @@ -1,6 +1,6 @@ /* DefaultBoundedRangeModel.java -- Default implementation of BoundedRangeModel. - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,6 +39,9 @@ exception statement from your version. */ package javax.swing; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.EventListener; @@ -440,4 +443,33 @@ public class DefaultBoundedRangeModel { return (ChangeListener[]) getListeners(ChangeListener.class); } + + /** + * Provides serialization support. + * + * @param stream the output stream (<code>null</code> not permitted). + * + * @throws IOException if there is an I/O error. + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + stream.defaultWriteObject(); + } + + /** + * Provides serialization support. + * + * @param stream the input stream (<code>null</code> not permitted). + * + * @throws IOException if there is an I/O error. + * @throws ClassNotFoundException if there is a classpath problem. + */ + private void readObject(ObjectInputStream stream) + throws ClassNotFoundException, IOException + { + stream.defaultReadObject(); + listenerList = new EventListenerList(); + } + } diff --git a/javax/swing/JComboBox.java b/javax/swing/JComboBox.java index efb04592b..c75a94bdc 100644 --- a/javax/swing/JComboBox.java +++ b/javax/swing/JComboBox.java @@ -471,6 +471,7 @@ public class JComboBox extends JComponent implements ItemSelectable, public void setSelectedItem(Object item) { dataModel.setSelectedItem(item); + fireActionEvent(); } /** @@ -1028,7 +1029,8 @@ public class JComboBox extends JComponent implements ItemSelectable, } /** - * This method hides combo box's popup whenever TAB key is pressed. + * This method is fired whenever a key is pressed with the combo box + * in focus * * @param e The KeyEvent indicating which key was pressed. */ @@ -1036,15 +1038,6 @@ public class JComboBox extends JComponent implements ItemSelectable, { if (e.getKeyCode() == KeyEvent.VK_TAB) setPopupVisible(false); - else if (keySelectionManager != null) - { - int i = keySelectionManager.selectionForKey(e.getKeyChar(), - getModel()); - if (i >= 0) - setSelectedIndex(i); - else - super.processKeyEvent(e); - } else super.processKeyEvent(e); } @@ -1066,7 +1059,7 @@ public class JComboBox extends JComponent implements ItemSelectable, */ public KeySelectionManager getKeySelectionManager() { - return null; + return keySelectionManager; } /** @@ -1098,7 +1091,7 @@ public class JComboBox extends JComponent implements ItemSelectable, */ protected KeySelectionManager createDefaultKeySelectionManager() { - return null; + return new DefaultKeySelectionManager(); } /** @@ -1471,4 +1464,34 @@ public class JComboBox extends JComponent implements ItemSelectable, // Nothing to do here. } } + + private class DefaultKeySelectionManager + implements KeySelectionManager + { + + public int selectionForKey(char aKey, ComboBoxModel aModel) + { + int selectedIndex = getSelectedIndex(); + + // Start at currently selected item and iterate to end of list + for (int i = selectedIndex + 1; i < aModel.getSize(); i++) + { + String nextItem = aModel.getElementAt(i).toString(); + + if (nextItem.charAt(0) == aKey) + return i; + } + + // Wrap to start of list if no match yet + for (int i = 0; i <= selectedIndex; i++) + { + String nextItem = aModel.getElementAt(i).toString(); + + if (nextItem.charAt(0) == aKey) + return i; + } + + return - 1; + } + } } diff --git a/javax/swing/JComponent.java b/javax/swing/JComponent.java index e9856d12b..fa8350294 100644 --- a/javax/swing/JComponent.java +++ b/javax/swing/JComponent.java @@ -48,12 +48,10 @@ import java.awt.EventQueue; import java.awt.FocusTraversalPolicy; import java.awt.Font; import java.awt.Graphics; -import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; -import java.awt.Shape; import java.awt.Window; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; @@ -71,7 +69,6 @@ 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; @@ -684,7 +681,7 @@ public abstract class JComponent extends Container implements Serializable * Indicates whether the current paint call is already double buffered or * not. */ - static boolean isPaintingDoubleBuffered = false; + static boolean paintingDoubleBuffered = false; /** * Indicates whether we are calling paintDoubleBuffered() from @@ -1849,7 +1846,7 @@ public abstract class JComponent extends Container implements Serializable // buffer. When this method completes, the call stack unwinds back to // paintDoubleBuffered, where the buffer contents is finally drawn to the // screen. - if (!isPaintingDoubleBuffered && isDoubleBuffered() + if (!paintingDoubleBuffered && isDoubleBuffered() && rm.isDoubleBufferingEnabled()) { Rectangle clip = g.getClipBounds(); @@ -1940,227 +1937,77 @@ public abstract class JComponent extends Container implements Serializable { if (getComponentCount() > 0) { - if (isOptimizedDrawingEnabled()) - paintChildrenOptimized(g); - else - paintChildrenWithOverlap(g); - } - } - - /** - * Paints the children of this JComponent in the case when the component - * is not marked as optimizedDrawingEnabled, that means the container cannot - * guarantee that it's children are tiled. For this case we must - * perform a more complex optimization to determine the minimal rectangle - * to be painted for each child component. - * - * @param g the graphics context to use - */ - private void paintChildrenWithOverlap(Graphics g) - { - Shape originalClip = g.getClip(); - Rectangle inner = SwingUtilities.calculateInnerArea(this, rectCache); - g.clipRect(inner.x, inner.y, inner.width, inner.height); - - // Find the rectangles that need to be painted for each child component. - // We push on this list arrays that have the Rectangles to be painted as - // the first elements and the component to be painted as the last one. - // Later we go through that list in reverse order and paint the rectangles. - int numChildren = getComponentCount(); - ArrayList paintRegions = new ArrayList(numChildren); - ArrayList paintRectangles = new ArrayList(); - ArrayList newPaintRects = new ArrayList(); - paintRectangles.add(g.getClipBounds()); - ArrayList componentRectangles = new ArrayList(); - - // Go through children from top to bottom and find out their paint - // rectangles. - for (int index = 0; paintRectangles.size() > 0 && index < numChildren; index++) - { - Component comp = getComponent(index); - if (! comp.isVisible() || ! comp.isLightweight()) - continue; - - Rectangle compBounds = comp.getBounds(); - boolean isOpaque = comp.isOpaque(); - - // Add all the current paint rectangles that intersect with the - // component to the component's paint rectangle array. - for (int i = paintRectangles.size() - 1; i >= 0; i--) + // Need to lock the tree to avoid problems with AWT and concurrency. + synchronized (getTreeLock()) { - Rectangle r = (Rectangle) paintRectangles.get(i); - if (r.intersects(compBounds)) + for (int i = getComponentCount() - 1; i >= 0; i--) { - Rectangle compRect = r.intersection(compBounds); - componentRectangles.add(compRect); - // If the component is opaque, split up each paint rect and - // add paintRect - compBounds to the newPaintRects array. - if (isOpaque) + Component child = getComponent(i); + if (child != null && child.isLightweight() + && child.isVisible()) { - int x, y, w, h; - Rectangle rect = new Rectangle(); - - // The north rectangle. - x = Math.max(compBounds.x, r.x); - y = r.y; - w = Math.min(compBounds.width, r.width + r.x - x); - h = compBounds.y - r.y; - rect.setBounds(x, y, w, h); - if (! rect.isEmpty()) - { - newPaintRects.add(rect); - rect = new Rectangle(); - } - - // The south rectangle. - x = Math.max(compBounds.x, r.x); - y = compBounds.y + compBounds.height; - w = Math.min(compBounds.width, r.width + r.x - x); - h = r.height - (compBounds.y - r.y) - compBounds.height; - rect.setBounds(x, y, w, h); - if (! rect.isEmpty()) + int cx = child.getX(); + int cy = child.getY(); + int cw = child.getWidth(); + int ch = child.getHeight(); + if (g.hitClip(cx, cy, cw, ch)) { - newPaintRects.add(rect); - rect = new Rectangle(); + if ((! isOptimizedDrawingEnabled()) && i > 0) + { + // Check if the child is completely obscured. + Rectangle clip = g.getClipBounds(); // A copy. + SwingUtilities.computeIntersection(cx, cy, cw, ch, + clip); + if (isCompletelyObscured(i, clip)) + continue; // Continues the for-loop. + } + Graphics cg = g.create(cx, cy, cw, ch); + cg.setColor(child.getForeground()); + cg.setFont(child.getFont()); + try + { + child.paint(cg); + } + finally + { + cg.dispose(); + } } - - // The west rectangle. - x = r.x; - y = r.y; - w = compBounds.x - r.x; - h = r.height; - rect.setBounds(x, y, w, h); - if (! rect.isEmpty()) - { - newPaintRects.add(rect); - rect = new Rectangle(); - } - - // The east rectangle. - x = compBounds.x + compBounds.width; - y = r.y; - w = r.width - (compBounds.x - r.x) - compBounds.width; - h = r.height; - rect.setBounds(x, y, w, h); - if (! rect.isEmpty()) - { - newPaintRects.add(rect); - } - } - else - { - // Not opaque, need to reuse the current paint rectangles - // for the next component. - newPaintRects.add(r); } - } - else - { - newPaintRects.add(r); - } - } - - // Replace the paintRectangles with the new split up - // paintRectangles. - paintRectangles.clear(); - paintRectangles.addAll(newPaintRects); - newPaintRects.clear(); - - // Store paint rectangles if there are any for the current component. - int compRectsSize = componentRectangles.size(); - if (compRectsSize > 0) - { - componentRectangles.add(comp); - paintRegions.add(componentRectangles); - componentRectangles = new ArrayList(); } } - - // paintingTile becomes true just before we start painting the component's - // children. - paintingTile = true; - - // We must go through the painting regions backwards, because the - // topmost components have been added first, followed by the components - // below. - int prEndIndex = paintRegions.size() - 1; - for (int i = prEndIndex; i >= 0; i--) - { - // paintingTile must be set to false before we begin to start painting - // the last tile. - if (i == 0) - paintingTile = false; - - ArrayList paintingRects = (ArrayList) paintRegions.get(i); - // The last element is always the component. - Component c = (Component) paintingRects.get(paintingRects.size() - 1); - int endIndex = paintingRects.size() - 2; - for (int j = 0; j <= endIndex; j++) - { - Rectangle cBounds = c.getBounds(); - Rectangle bounds = (Rectangle) paintingRects.get(j); - Rectangle oldClip = g.getClipBounds(); - if (oldClip == null) - oldClip = bounds; - - boolean translated = false; - try - { - g.setClip(bounds); - g.translate(cBounds.x, cBounds.y); - translated = true; - c.paint(g); - } - finally - { - if (translated) - g.translate(-cBounds.x, -cBounds.y); - g.setClip(oldClip); - } - } - } - g.setClip(originalClip); } /** - * Paints the children of this container when it is marked as - * optimizedDrawingEnabled. In this case the container can guarantee that - * it's children are tiled, which allows for a much more efficient - * algorithm to determine the minimum rectangles to be painted for - * each child. + * Determines if a region of a child component is completely obscured by one + * of its siblings. * - * @param g the graphics context to use + * @param index the index of the child component + * @param rect the region to check + * + * @return <code>true</code> if the region is completely obscured by a + * sibling, <code>false</code> otherwise */ - private void paintChildrenOptimized(Graphics g) + private boolean isCompletelyObscured(int index, Rectangle rect) { - Rectangle inner = SwingUtilities.calculateInnerArea(this, rectCache); - g.clipRect(inner.x, inner.y, inner.width, inner.height); - - // paintingTile becomes true just before we start painting the component's - // children. - paintingTile = true; - int numChildren = getComponentCount(); - for (int i = numChildren - 1; i >= 0; i--) //children.length; i++) + boolean obscured = false; + for (int i = index - 1; i >= 0 && obscured == false; i--) { - Component child = getComponent(i); - // paintingTile must be set to false before we begin to start painting - // the last tile. - if (i == numChildren - 1) - paintingTile = false; - - if (!child.isVisible() || ! child.isLightweight()) - continue; - - Rectangle bounds = child.getBounds(rectCache); - if (!g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height)) - continue; - - Graphics g2 = g.create(bounds.x, bounds.y, bounds.width, - bounds.height); - child.paint(g2); - g2.dispose(); + Component sib = getComponent(i); + 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)) + { + obscured = true; + } + } } + return obscured; } /** @@ -2180,12 +2027,15 @@ public abstract class JComponent extends Container implements Serializable { if (ui != null) { - Graphics g2 = g; - if (!(g instanceof Graphics2D)) - g2 = g.create(); - ui.update(g2, this); - if (!(g instanceof Graphics2D)) - g2.dispose(); + Graphics g2 = g.create(); + try + { + ui.update(g2, this); + } + finally + { + g2.dispose(); + } } } @@ -2295,7 +2145,7 @@ public abstract class JComponent extends Container implements Serializable clipAndTranslateGraphics(root, this, g2); g2.clipRect(r.x, r.y, r.width, r.height); g2 = getComponentGraphics(g2); - isPaintingDoubleBuffered = true; + paintingDoubleBuffered = true; try { if (isRepainting) // Called from paintImmediately, go through paint(). @@ -2309,7 +2159,7 @@ public abstract class JComponent extends Container implements Serializable } finally { - isPaintingDoubleBuffered = false; + paintingDoubleBuffered = false; g2.dispose(); } @@ -2329,11 +2179,16 @@ public abstract class JComponent extends Container implements Serializable private void clipAndTranslateGraphics(Component root, Component target, Graphics g) { - Component parent = target.getParent(); - if (parent != root) - clipAndTranslateGraphics(root, parent, g); - - g.translate(target.getX(), target.getY()); + Component parent = target; + int deltaX = 0; + int deltaY = 0; + while (parent != root) + { + deltaX += parent.getX(); + deltaY += parent.getY(); + parent = parent.getParent(); + } + g.translate(deltaX, deltaY); g.clipRect(0, 0, target.getWidth(), target.getHeight()); } @@ -2726,10 +2581,11 @@ public abstract class JComponent extends Container implements Serializable if (isEnabled()) { Action act = null; + Object cmd = null; InputMap map = getInputMap(condition); if (map != null) { - Object cmd = map.get(ks); + cmd = map.get(ks); if (cmd != null) { if (cmd instanceof ActionListenerProxy) @@ -2739,7 +2595,23 @@ public abstract class JComponent extends Container implements Serializable } } if (act != null && act.isEnabled()) - return SwingUtilities.notifyAction(act, ks, e, this, e.getModifiers()); + { + // Need to synchronize here so we don't get in trouble with + // our __command__ hack. + synchronized (act) + { + // We add the command as value to the action, so that + // the action can later determine the command with which it + // was called. This is undocumented, but shouldn't affect + // compatibility. It allows us to use only one Action instance + // to do the work for all components of one type, instead of + // having loads of small Actions. This effectivly saves startup + // time of Swing. + act.putValue("__command__", cmd); + return SwingUtilities.notifyAction(act, ks, e, this, + e.getModifiers()); + } + } } return false; } diff --git a/javax/swing/JEditorPane.java b/javax/swing/JEditorPane.java index 4ae3c5a1c..2dcaafc27 100644 --- a/javax/swing/JEditorPane.java +++ b/javax/swing/JEditorPane.java @@ -717,7 +717,8 @@ public class JEditorPane extends JTextComponent // and has a height > minimum UI height. Container parent = getParent(); return parent instanceof JViewport - && parent.getHeight() > getUI().getMinimumSize(this).height; + && parent.getHeight() >= getUI().getMinimumSize(this).height + && parent.getHeight() <= getUI().getMaximumSize(this).height; } /** diff --git a/javax/swing/JFileChooser.java b/javax/swing/JFileChooser.java index 64f9bd0f4..a508b8fcb 100644 --- a/javax/swing/JFileChooser.java +++ b/javax/swing/JFileChooser.java @@ -43,6 +43,8 @@ import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowAdapter; import java.beans.PropertyChangeEvent; import java.io.File; import java.util.ArrayList; @@ -351,7 +353,7 @@ public class JFileChooser extends JComponent implements Accessible * The file selection mode. * @see #setFileSelectionMode(int) */ - private int fileSelectionMode = FILES_AND_DIRECTORIES; + private int fileSelectionMode = FILES_ONLY; /** * The file view. @@ -744,10 +746,16 @@ public class JFileChooser extends JComponent implements Accessible JDialog dialog = new JDialog(toUse); setSelectedFile(null); dialog.getContentPane().add(this); + dialog.addWindowListener( new WindowAdapter() + { + public void windowClosing(WindowEvent e) + { + cancelSelection(); + } + }); dialog.setModal(true); dialog.invalidate(); dialog.repaint(); - return dialog; } diff --git a/javax/swing/JList.java b/javax/swing/JList.java index 6a98770ee..98b299044 100644 --- a/javax/swing/JList.java +++ b/javax/swing/JList.java @@ -1935,72 +1935,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; } /** @@ -2029,10 +2031,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 0840509f9..9432865e5 100644 --- a/javax/swing/JMenu.java +++ b/javax/swing/JMenu.java @@ -40,7 +40,6 @@ package javax.swing; import java.awt.Component; import java.awt.Point; -import java.awt.PopupMenu; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -54,6 +53,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; @@ -72,6 +73,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 */ @@ -93,13 +124,19 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement private Point menuLocation; /** + * The ChangeListener for the ButtonModel. + * + * @see MenuChangeListener + */ + private ChangeListener menuChangeListener; + + /** * Creates a new JMenu object. */ public JMenu() { super(); setOpaque(false); - setDelay(200); } /** @@ -113,7 +150,6 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement popupMenu = new JPopupMenu(); popupMenu.setInvoker(this); setOpaque(false); - setDelay(200); } /** @@ -129,7 +165,6 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement popupMenu = new JPopupMenu(); popupMenu.setInvoker(this); setOpaque(false); - setDelay(200); } /** @@ -143,7 +178,6 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement { // FIXME: tearoff not implemented this(text); - setDelay(200); } /** @@ -193,7 +227,7 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public JMenuItem add(String text) { - return getPopupMenu().add(text); + return add(new JMenuItem(text)); } /** @@ -205,7 +239,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; } /** @@ -328,7 +365,18 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public void setModel(ButtonModel model) { + ButtonModel oldModel = getModel(); + if (oldModel != null && changeListener != null) + oldModel.removeChangeListener(changeListener); + super.setModel(model); + + if (model != null) + { + if (changeListener == null) + changeListener = new MenuChangeListener(); + model.addChangeListener(changeListener); + } } /** @@ -342,63 +390,6 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement } /** - * A helper method to handle setSelected calls from both mouse events and - * direct calls to setSelected. Direct calls shouldn't expand the popup - * menu and should select the JMenu even if it is disabled. Mouse events - * only select the JMenu if it is enabled and should expand the popup menu - * associated with this JMenu. - * @param selected whether or not the JMenu was selected - * @param menuEnabled whether or not selecting the menu is "enabled". This - * is always true for direct calls, and is set to isEnabled() for mouse - * based calls. - * @param showMenu whether or not to show the popup menu - */ - private void setSelectedHelper(boolean selected, boolean menuEnabled, boolean showMenu) - { - // If menu is selected and enabled, activates the menu and - // displays associated popup. - if (selected && menuEnabled) - { - super.setArmed(true); - super.setSelected(true); - - // FIXME: The popup menu should be shown on the screen after certain - // number of seconds pass. The 'delay' property of this menu indicates - // this amount of seconds. 'delay' property is 0 by default. - if (isShowing()) - { - fireMenuSelected(); - - int x = 0; - int y = 0; - if (showMenu) - if (menuLocation == null) - { - // Calculate correct position of the popup. Note that location of the popup - // passed to show() should be relative to the popup's invoker - if (isTopLevelMenu()) - y = this.getHeight(); - else - x = this.getWidth(); - getPopupMenu().show(this, x, y); - } - else - { - getPopupMenu().show(this, menuLocation.x, menuLocation.y); - } - } - } - - else - { - super.setSelected(false); - super.setArmed(false); - fireMenuDeselected(); - getPopupMenu().setVisible(false); - } - } - - /** * Changes this menu selected state if selected is true and false otherwise * This method fires menuEvents to menu's registered listeners. * @@ -406,7 +397,9 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public void setSelected(boolean selected) { - setSelectedHelper(selected, true, false); + ButtonModel m = getModel(); + if (selected != m.isSelected()) + m.setSelected(selected); } /** @@ -427,8 +420,17 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public void setPopupMenuVisible(boolean popup) { - if (getModel().isEnabled()) - getPopupMenu().setVisible(popup); + if (popup != isPopupMenuVisible() && (isEnabled() || ! popup)) + { + if (popup && isShowing()) + { + // Set location as determined by getPopupLocation(). + Point loc = getPopupMenuOrigin(); + getPopupMenu().show(this, loc.x, loc.y); + } + else + getPopupMenu().setVisible(false); + } } /** @@ -438,12 +440,22 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ protected Point getPopupMenuOrigin() { + Point point; + // if menu in the menu bar if (isTopLevelMenu()) - return new Point(0, this.getHeight()); + point = new Point(0, this.getHeight()); - // if submenu - return new Point(this.getWidth(), 0); + // if submenu + else + { + int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX"); + int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY"); + int x = getWidth() + xOffset; + int y = yOffset; + point = new Point(x, y); + } + return point; } /** @@ -748,7 +760,7 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement { // if this menu selection is true, then activate this menu and // display popup associated with this menu - setSelectedHelper(changed, isEnabled(), true); + setSelected(changed); } /** diff --git a/javax/swing/JOptionPane.java b/javax/swing/JOptionPane.java index f94905538..43caecd1a 100644 --- a/javax/swing/JOptionPane.java +++ b/javax/swing/JOptionPane.java @@ -48,6 +48,8 @@ import java.awt.MenuComponent; import java.awt.Toolkit; import java.awt.event.MouseAdapter; import java.awt.event.MouseMotionAdapter; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.accessibility.Accessible; import javax.accessibility.AccessibleContext; @@ -378,11 +380,49 @@ public class JOptionPane extends JComponent implements Accessible dialog.setResizable(false); dialog.pack(); dialog.setLocationRelativeTo(parentComponent); - + + addPropertyChangeListener(new ValuePropertyHandler(dialog)); return dialog; } /** + * Handles changes of the value property. Whenever this property changes, + * the JOptionPane dialog should be closed. + */ + private static class ValuePropertyHandler + implements PropertyChangeListener + { + /** + * The dialog to close. + */ + JDialog dialog; + + /** + * Creates a new instance. + * + * @param d the dialog to be closed + */ + ValuePropertyHandler(JDialog d) + { + dialog = d; + } + + /** + * Receives notification when any of the properties change. + */ + public void propertyChange(PropertyChangeEvent p) + { + String prop = p.getPropertyName(); + Object val = p.getNewValue(); + if (prop.equals(VALUE_PROPERTY) && val != null + && val != UNINITIALIZED_VALUE) + { + dialog.setVisible(false); + } + } + } + + /** * This method creates a new JInternalFrame that is in the JLayeredPane * which contains the parentComponent given. If no suitable JLayeredPane * can be found from the parentComponent given, a RuntimeException will be diff --git a/javax/swing/JPopupMenu.java b/javax/swing/JPopupMenu.java index c7890ea0e..2e59d4767 100644 --- a/javax/swing/JPopupMenu.java +++ b/javax/swing/JPopupMenu.java @@ -120,7 +120,7 @@ public class JPopupMenu extends JComponent implements Accessible, MenuElement private boolean lightWeightPopupEnabled; /** SelectionModel that keeps track of menu selection. */ - private SingleSelectionModel selectionModel; + protected SingleSelectionModel selectionModel; /* Popup that is used to display JPopupMenu */ private transient Popup popup; @@ -820,7 +820,14 @@ public class JPopupMenu extends JComponent implements Accessible, MenuElement */ public void menuSelectionChanged(boolean changed) { - if (! changed) + if (invoker instanceof JMenu) + { + // We need to special case this since the JMenu calculates the + // position etc of the popup. + JMenu menu = (JMenu) invoker; + menu.setPopupMenuVisible(changed); + } + else if (! changed) setVisible(false); } diff --git a/javax/swing/JTabbedPane.java b/javax/swing/JTabbedPane.java index ee6af857e..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); } } @@ -990,6 +994,8 @@ public class JTabbedPane extends JComponent implements Serializable, checkIndex(index, -1, tabs.size()); if (index != getSelectedIndex()) { + // Hiding and showing the involved components + // is done by the JTabbedPane's UI. model.setSelectedIndex(index); } } @@ -1247,7 +1253,32 @@ public class JTabbedPane extends JComponent implements Serializable, */ public void remove(Component component) { - super.remove(component); + // Since components implementing UIResource + // are not added as regular tabs by the add() + // methods we have to take special care when + // removing these object. Especially + // Container.remove(Component) cannot be used + // because it will call JTabbedPane.remove(int) + // later which is overridden and can only + // handle tab components. + // This implementation can even cope with a + // situation that someone called insertTab() + // with a component that implements UIResource. + int index = indexOfComponent(component); + + // If the component is not a tab component + // find out its Container-given index + // and call that class' implementation + // directly. + if (index == -1) + { + Component[] cs = getComponents(); + for (int i = 0; i< cs.length; i++) + if (cs[i] == component) + super.remove(i); + } + else + removeTabAt(index); } /** @@ -1257,7 +1288,6 @@ public class JTabbedPane extends JComponent implements Serializable, */ public void remove(int index) { - super.remove(index); removeTabAt(index); } diff --git a/javax/swing/JTable.java b/javax/swing/JTable.java index 855530881..0b9467f33 100644 --- a/javax/swing/JTable.java +++ b/javax/swing/JTable.java @@ -2922,56 +2922,189 @@ public class JTable { // update the column model from the table model if the structure has // changed and the flag autoCreateColumnsFromModel is set - if ((event == null || (event.getFirstRow() == TableModelEvent.HEADER_ROW)) - && autoCreateColumnsFromModel) + if (event == null || (event.getFirstRow() == TableModelEvent.HEADER_ROW)) + handleCompleteChange(event); + else if (event.getType() == TableModelEvent.INSERT) + handleInsert(event); + else if (event.getType() == TableModelEvent.DELETE) + handleDelete(event); + else + handleUpdate(event); + } + + /** + * Handles a request for complete relayout. This is the case when + * event.getFirstRow() == TableModelEvent.HEADER_ROW. + * + * @param ev the table model event + */ + private void handleCompleteChange(TableModelEvent ev) + { + clearSelection(); + checkSelection(); + rowHeights = null; + if (getAutoCreateColumnsFromModel()) + createDefaultColumnsFromModel(); + else + resizeAndRepaint(); + } + + /** + * Handles table model insertions. + * + * @param ev the table model event + */ + private void handleInsert(TableModelEvent ev) + { + // Sync selection model with data model. + int first = ev.getFirstRow(); + if (first < 0) + first = 0; + int last = ev.getLastRow(); + if (last < 0) + last = getRowCount() - 1; + selectionModel.insertIndexInterval(first, last - first + 1, true); + checkSelection(); + + // For variable height rows we must update the SizeSequence thing. + if (rowHeights != null) { - rowHeights = null; - if (getAutoCreateColumnsFromModel()) - createDefaultColumnsFromModel(); - resizeAndRepaint(); - return; + rowHeights.insertEntries(first, last - first + 1, rowHeight); + // TODO: We repaint the whole thing when the rows have variable + // heights. We might want to handle this better though. + repaint(); + } + else + { + // Repaint the dirty region and revalidate. + int rowHeight = getRowHeight(); + Rectangle dirty = new Rectangle(0, first * rowHeight, + getColumnModel().getTotalColumnWidth(), + (getRowCount() - first) * rowHeight); + repaint(dirty); } + revalidate(); + } + + /** + * Handles table model deletions. + * + * @param ev the table model event + */ + private void handleDelete(TableModelEvent ev) + { + // Sync selection model with data model. + int first = ev.getFirstRow(); + if (first < 0) + first = 0; + int last = ev.getLastRow(); + if (last < 0) + last = getRowCount() - 1; + + selectionModel.removeIndexInterval(first, last); + + checkSelection(); + + if (dataModel.getRowCount() == 0) + clearSelection(); - // If the structure changes, we need to revalidate, since that might - // affect the size parameters of the JTable. Otherwise we only need - // to perform a repaint to update the view. - if (event == null || event.getType() == TableModelEvent.INSERT) + // For variable height rows we must update the SizeSequence thing. + if (rowHeights != null) + { + rowHeights.removeEntries(first, last - first + 1); + // TODO: We repaint the whole thing when the rows have variable + // heights. We might want to handle this better though. + repaint(); + } + else { - // Sync selection model with data model. - if (event != null) + // Repaint the dirty region and revalidate. + int rowHeight = getRowHeight(); + int oldRowCount = getRowCount() + last - first + 1; + Rectangle dirty = new Rectangle(0, first * rowHeight, + getColumnModel().getTotalColumnWidth(), + (oldRowCount - first) * rowHeight); + repaint(dirty); + } + revalidate(); + } + + /** + * Handles table model updates without structural changes. + * + * @param ev the table model event + */ + private void handleUpdate(TableModelEvent ev) + { + if (rowHeights == null) + { + // Some cells have been changed without changing the structure. + // Figure out the dirty rectangle and repaint. + int firstRow = ev.getFirstRow(); + int lastRow = ev.getLastRow(); + int col = ev.getColumn(); + Rectangle dirty; + if (col == TableModelEvent.ALL_COLUMNS) { - int first = event.getFirstRow(); - if (first < 0) - first = 0; - int last = event.getLastRow(); - if (last < 0) - last = getRowCount() - 1; - selectionModel.insertIndexInterval(first, last - first + 1, true); - if (rowHeights != null) - rowHeights.insertEntries(first, last - first + 1, rowHeight); + // All columns changed. + dirty = new Rectangle(0, firstRow * getRowHeight(), + getColumnModel().getTotalColumnWidth(), 0); } - revalidate(); + else + { + // Only one cell or column of cells changed. + // We need to convert to view column first. + int column = convertColumnIndexToModel(col); + dirty = getCellRect(firstRow, column, false); + } + + // Now adjust the height of the dirty region. + dirty.height = (lastRow + 1) * getRowHeight(); + // .. and repaint. + repaint(dirty); + } + else + { + // TODO: We repaint the whole thing when the rows have variable + // heights. We might want to handle this better though. + repaint(); } - if (event == null || event.getType() == TableModelEvent.DELETE) + } + + /** + * Helper method for adjusting the lead and anchor indices when the + * table structure changed. This sets the lead and anchor to -1 if there's + * no more rows, or set them to 0 when they were at -1 and there are actually + * some rows now. + */ + private void checkSelection() + { + TableModel m = getModel(); + ListSelectionModel sm = selectionModel; + if (m != null) { - // Sync selection model with data model. - if (event != null) + int lead = sm.getLeadSelectionIndex(); + int c = m.getRowCount(); + if (c == 0 && lead != -1) { - int first = event.getFirstRow(); - if (first < 0) - first = 0; - int last = event.getLastRow(); - if (last < 0) - last = getRowCount() - 1; - selectionModel.removeIndexInterval(first, last); - if (rowHeights != null) - rowHeights.removeEntries(first, last - first + 1); + // No rows in the model, reset lead and anchor to -1. + sm.setValueIsAdjusting(true); + sm.setAnchorSelectionIndex(-1); + sm.setLeadSelectionIndex(-1); + sm.setValueIsAdjusting(false); } - if (dataModel.getRowCount() == 0) - clearSelection(); - revalidate(); + else if (c != 0 && lead == -1) + { + // We have rows, but no lead/anchor. Set them to 0. We + // do a little trick here so that the actual selection is not + // touched. + if (sm.isSelectedIndex(0)) + sm.addSelectionInterval(0, 0); + else + sm.removeSelectionInterval(0, 0); + } + // Nothing to do in the other cases. } - repaint(); } /** @@ -3170,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; } /** @@ -3217,19 +3361,12 @@ public class JTable 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) - { - // Completely expose the top row - int near = ((visibleRect.y + delta + h / 2) / h) * h; - int diff = visibleRect.y + delta - near; - delta -= diff; - } - return delta; - // TODO when scrollng horizontally, scroll into the column boundary. + int unit; + if (orientation == SwingConstants.HORIZONTAL) + unit = 100; + else + unit = getRowHeight(); + return unit; } /** @@ -3468,6 +3605,8 @@ public class JTable * Get the value of the {@link #rowSelectionAllowed} property. * * @return The current value of the property + * + * @see #setRowSelectionAllowed(boolean) */ public boolean getRowSelectionAllowed() { @@ -3621,6 +3760,8 @@ public class JTable * Get the value of the <code>columnSelectionAllowed</code> property. * * @return The current value of the columnSelectionAllowed property + * + * @see #setColumnSelectionAllowed(boolean) */ public boolean getColumnSelectionAllowed() { @@ -3874,11 +4015,17 @@ public class JTable * Set the value of the {@link #rowSelectionAllowed} property. * * @param r The new value of the rowSelectionAllowed property + * + * @see #getRowSelectionAllowed() */ public void setRowSelectionAllowed(boolean r) { - rowSelectionAllowed = r; - repaint(); + if (rowSelectionAllowed != r) + { + rowSelectionAllowed = r; + firePropertyChange("rowSelectionAllowed", !r, r); + repaint(); + } } /** @@ -3988,11 +4135,17 @@ public class JTable * Set the value of the <code>columnSelectionAllowed</code> property. * * @param c The new value of the property + * + * @see #getColumnSelectionAllowed() */ public void setColumnSelectionAllowed(boolean c) { - getColumnModel().setColumnSelectionAllowed(c); - repaint(); + if (columnModel.getColumnSelectionAllowed() != c) + { + columnModel.setColumnSelectionAllowed(c); + firePropertyChange("columnSelectionAllowed", !c, c); + repaint(); + } } /** @@ -4014,6 +4167,7 @@ public class JTable if (s != null) s.addListSelectionListener(this); selectionModel = s; + checkSelection(); } /** 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/JTree.java b/javax/swing/JTree.java index 6530265b9..7d9a22fb9 100644 --- a/javax/swing/JTree.java +++ b/javax/swing/JTree.java @@ -1509,12 +1509,14 @@ public class JTree extends JComponent implements Scrollable, Accessible public JTree(TreeModel model) { setRootVisible(true); - setSelectionModel(new EmptySelectionModel()); - selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + setSelectionModel( new DefaultTreeSelectionModel() ); // The root node appears expanded by default. nodeStates = new Hashtable(); + // The cell renderer gets set by the UI. + cellRenderer = null; + // Install the UI before installing the model. This way we avoid double // initialization of lots of UI and model stuff inside the UI and related // classes. The necessary UI updates are performed via property change @@ -1682,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() @@ -2047,14 +2072,16 @@ public class JTree extends JComponent implements Scrollable, Accessible if (selectionModel == model) return; + if( model == null ) + model = EmptySelectionModel.sharedInstance(); + if (selectionModel != null) selectionModel.removeTreeSelectionListener(selectionRedirector); TreeSelectionModel oldValue = selectionModel; selectionModel = model; - if (selectionModel != null) - selectionModel.addTreeSelectionListener(selectionRedirector); + selectionModel.addTreeSelectionListener(selectionRedirector); firePropertyChange(SELECTION_MODEL_PROPERTY, oldValue, model); revalidate(); diff --git a/javax/swing/Popup.java b/javax/swing/Popup.java index 308cd662d..5074d6418 100644 --- a/javax/swing/Popup.java +++ b/javax/swing/Popup.java @@ -284,7 +284,7 @@ public class Popup panel.setSize(contents.getSize()); Point layeredPaneLoc = layeredPane.getLocationOnScreen(); panel.setLocation(x - layeredPaneLoc.x, y - layeredPaneLoc.y); - layeredPane.add(panel, JLayeredPane.POPUP_LAYER); + layeredPane.add(panel, JLayeredPane.POPUP_LAYER, 0); panel.repaint(); } diff --git a/javax/swing/RepaintManager.java b/javax/swing/RepaintManager.java index 7755ab5e8..80f0a3481 100644 --- a/javax/swing/RepaintManager.java +++ b/javax/swing/RepaintManager.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing; -import java.applet.Applet; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; @@ -463,8 +462,7 @@ public class RepaintManager */ public void markCompletelyDirty(JComponent component) { - Rectangle r = component.getBounds(); - addDirtyRegion(component, 0, 0, r.width, r.height); + addDirtyRegion(component, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE); } /** @@ -502,13 +500,11 @@ public class RepaintManager */ public boolean isCompletelyDirty(JComponent component) { - boolean retVal = false; - if (dirtyComponents.containsKey(component)) - { - Rectangle dirtyRegion = (Rectangle) dirtyComponents.get(component); - retVal = dirtyRegion.equals(SwingUtilities.getLocalBounds(component)); - } - return retVal; + boolean dirty = false; + Rectangle r = getDirtyRegion(component); + if(r.width == Integer.MAX_VALUE && r.height == Integer.MAX_VALUE) + dirty = true; + return dirty; } /** @@ -647,7 +643,7 @@ public class RepaintManager width = Math.min(doubleBufferMaximumSize.width, width); int height = Math.max(proposedHeight, root.getHeight()); height = Math.min(doubleBufferMaximumSize.height, height); - buffer = component.createImage(width, height); + buffer = root.createImage(width, height); offscreenBuffers.put(root, buffer); } return buffer; diff --git a/javax/swing/SwingUtilities.java b/javax/swing/SwingUtilities.java index d7808ba60..ba4c708fb 100644 --- a/javax/swing/SwingUtilities.java +++ b/javax/swing/SwingUtilities.java @@ -61,6 +61,8 @@ import javax.accessibility.Accessible; import javax.accessibility.AccessibleStateSet; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.InputMapUIResource; +import javax.swing.plaf.basic.BasicHTML; +import javax.swing.text.View; /** * A number of static utility functions which are @@ -751,12 +753,12 @@ public class SwingUtilities horizontalAlignment = RIGHT; } - return layoutCompoundLabel(fm, text, icon, - verticalAlignment, - horizontalAlignment, - verticalTextPosition, - horizontalTextPosition, - viewR, iconR, textR, textIconGap); + return layoutCompoundLabelImpl(c, fm, text, icon, + verticalAlignment, + horizontalAlignment, + verticalTextPosition, + horizontalTextPosition, + viewR, iconR, textR, textIconGap); } /** @@ -829,6 +831,82 @@ public class SwingUtilities Rectangle textR, int textIconGap) { + return layoutCompoundLabelImpl(null, fm, text, icon, verticalAlignment, + horizontalAlignment, verticalTextPosition, + horizontalTextPosition, viewR, iconR, textR, + textIconGap); + } + + /** + * <p>Layout a "compound label" consisting of a text string and an icon + * which is to be placed near the rendered text. Once the text and icon + * are laid out, the text rectangle and icon rectangle parameters are + * altered to store the calculated positions.</p> + * + * <p>The size of the text is calculated from the provided font metrics + * object. This object should be the metrics of the font you intend to + * paint the label with.</p> + * + * <p>The position values control where the text is placed relative to + * the icon. The horizontal position value should be one of the constants + * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>. The + * vertical position value should be one fo the constants + * <code>TOP</code>, <code>BOTTOM</code> or <code>CENTER</code>.</p> + * + * <p>The text-icon gap value controls the number of pixels between the + * icon and the text.</p> + * + * <p>The alignment values control where the text and icon are placed, as + * a combined unit, within the view rectangle. The horizontal alignment + * value should be one of the constants <code>LEFT</code>, <code>RIGHT</code> or + * <code>CENTER</code>. The vertical alignment valus should be one of the + * constants <code>TOP</code>, <code>BOTTOM</code> or + * <code>CENTER</code>.</p> + * + * <p>If the text and icon are equal to or larger than the view + * rectangle, the horizontal and vertical alignment values have no + * affect.</p> + * + * <p>Note that this method does <em>not</em> know how to deal with + * horizontal alignments or positions given as <code>LEADING</code> or + * <code>TRAILING</code> values. Use the other overloaded variant of this + * method if you wish to use such values. + * + * @param fm The font metrics used to measure the text + * @param text The text to place in the compound label + * @param icon The icon to place next to the text + * @param verticalAlignment The vertical alignment of the label relative + * to its component + * @param horizontalAlignment The horizontal alignment of the label + * relative to its component + * @param verticalTextPosition The vertical position of the label's text + * relative to its icon + * @param horizontalTextPosition The horizontal position of the label's + * text relative to its icon + * @param viewR The view rectangle, specifying the area which layout is + * constrained to + * @param iconR A rectangle which is modified to hold the laid-out + * position of the icon + * @param textR A rectangle which is modified to hold the laid-out + * position of the text + * @param textIconGap The distance between text and icon + * + * @return The string of characters, possibly truncated with an elipsis, + * which is laid out in this label + */ + private static String layoutCompoundLabelImpl(JComponent c, + FontMetrics fm, + String text, + Icon icon, + int verticalAlignment, + int horizontalAlignment, + int verticalTextPosition, + int horizontalTextPosition, + Rectangle viewR, + Rectangle iconR, + Rectangle textR, + int textIconGap) + { // Work out basic height and width. @@ -851,13 +929,23 @@ public class SwingUtilities } else { - int fromIndex = 0; - textR.width = fm.stringWidth(text); - textR.height = fm.getHeight(); - while (text.indexOf('\n', fromIndex) != -1) + View html = c == null ? null + : (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + textR.width = (int) html.getPreferredSpan(View.X_AXIS); + textR.height = (int) html.getPreferredSpan(View.Y_AXIS); + } + else { - textR.height += fm.getHeight(); - fromIndex = text.indexOf('\n', fromIndex) + 1; + int fromIndex = 0; + textR.width = fm.stringWidth(text); + textR.height = fm.getHeight(); + while (text.indexOf('\n', fromIndex) != -1) + { + textR.height += fm.getHeight(); + fromIndex = text.indexOf('\n', fromIndex) + 1; + } } } @@ -1045,8 +1133,7 @@ public class SwingUtilities */ public static boolean isLeftMouseButton(MouseEvent event) { - return ((event.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) - == InputEvent.BUTTON1_DOWN_MASK); + return ((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0); } /** diff --git a/javax/swing/Timer.java b/javax/swing/Timer.java index 231b71d73..acd226249 100644 --- a/javax/swing/Timer.java +++ b/javax/swing/Timer.java @@ -1,5 +1,5 @@ /* Timer.java -- - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -264,9 +264,13 @@ public class Timer * firing the first event. * * @param d The time gap between the subsequent events, in milliseconds + * + * @throws IllegalArgumentException if <code>d</code> is less than zero. */ public void setDelay(int d) { + if (d < 0) + throw new IllegalArgumentException("Invalid delay: " + d); delay = d; } @@ -287,9 +291,13 @@ public class Timer * subsequent events. * * @param i the initial delay, in milliseconds + * + * @throws IllegalArgumentException if <code>i</code> is less than zero. */ public void setInitialDelay(int i) { + if (i < 0) + throw new IllegalArgumentException("Invalid initial delay: " + i); initialDelay = i; } diff --git a/javax/swing/ToolTipManager.java b/javax/swing/ToolTipManager.java index c7de4db83..963ccf881 100644 --- a/javax/swing/ToolTipManager.java +++ b/javax/swing/ToolTipManager.java @@ -1,5 +1,5 @@ /* ToolTipManager.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -267,10 +267,12 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener } /** - * This method sets the initial delay before the ToolTip is shown when the + * Sets the initial delay before the ToolTip is shown when the * mouse enters a Component. * * @param delay The initial delay before the ToolTip is shown. + * + * @throws IllegalArgumentException if <code>delay</code> is less than zero. */ public void setInitialDelay(int delay) { @@ -289,9 +291,11 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener } /** - * This method sets the time the ToolTip will be shown before being hidden. + * Sets the time the ToolTip will be shown before being hidden. * - * @param delay The time the ToolTip will be shown before being hidden. + * @param delay the delay (in milliseconds) before tool tips are hidden. + * + * @throws IllegalArgumentException if <code>delay</code> is less than zero. */ public void setDismissDelay(int delay) { @@ -310,10 +314,12 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener } /** - * This method sets the amount of delay where if the mouse re-enters a + * Sets the amount of delay where if the mouse re-enters a * Component, the tooltip will be shown immediately. * - * @param delay The reshow delay. + * @param delay The reshow delay (in milliseconds). + * + * @throws IllegalArgumentException if <code>delay</code> is less than zero. */ public void setReshowDelay(int delay) { diff --git a/javax/swing/UIManager.java b/javax/swing/UIManager.java index b70d13952..77be44afc 100644 --- a/javax/swing/UIManager.java +++ b/javax/swing/UIManager.java @@ -616,7 +616,7 @@ public class UIManager implements Serializable */ public static UIDefaults getLookAndFeelDefaults() { - return currentUIDefaults; + return lookAndFeelDefaults; } /** @@ -714,7 +714,8 @@ public class UIManager implements Serializable throws UnsupportedLookAndFeelException { if (newLookAndFeel != null && ! newLookAndFeel.isSupportedLookAndFeel()) - throw new UnsupportedLookAndFeelException(newLookAndFeel.getName()); + throw new UnsupportedLookAndFeelException(newLookAndFeel.getName() + + " not supported on this platform"); LookAndFeel oldLookAndFeel = currentLookAndFeel; if (oldLookAndFeel != null) oldLookAndFeel.uninitialize(); diff --git a/javax/swing/filechooser/FileSystemView.java b/javax/swing/filechooser/FileSystemView.java index f51b745c8..84b80dd40 100644 --- a/javax/swing/filechooser/FileSystemView.java +++ b/javax/swing/filechooser/FileSystemView.java @@ -76,7 +76,10 @@ public abstract class FileSystemView */ public File createFileObject(String path) { - return new File(path); + File f = new File(path); + if (isFileSystemRoot(f)) + f = this.createFileSystemRoot(f); + return f; } /** @@ -223,16 +226,24 @@ public abstract class FileSystemView /** * Returns the name of a file as it would be displayed by the underlying - * system. This implementation returns <code>null</code>, subclasses must - * override. + * system. * * @param f the file. * - * @return <code>null</code>. + * @return the name of a file as it would be displayed by the underlying + * system + * + * @specnote The specification suggests that the information here is + * fetched from a ShellFolder class. This seems to be a non public + * private file handling class. We simply return File.getName() + * here and leave special handling to subclasses. */ public String getSystemDisplayName(File f) { - return null; + String name = null; + if (f != null) + name = f.getName(); + return name; } /** diff --git a/javax/swing/filechooser/UnixFileSystemView.java b/javax/swing/filechooser/UnixFileSystemView.java index 96dfd2e1b..f8d71e1df 100644 --- a/javax/swing/filechooser/UnixFileSystemView.java +++ b/javax/swing/filechooser/UnixFileSystemView.java @@ -106,17 +106,34 @@ class UnixFileSystemView extends FileSystemView /** * Returns the name of a file as it would be displayed by the underlying - * system. This method is NOT YET IMPLEMENTED. + * system. * * @param f the file. * - * @return <code>null</code>. + * @return the name of a file as it would be displayed by the underlying + * system */ public String getSystemDisplayName(File f) - throws NotImplementedException { - // FIXME: Implement; - return null; + String name = null; + if (f != null) + { + if (isRoot(f)) + name = f.getAbsolutePath(); + else + { + try + { + String path = f.getCanonicalPath(); + name = path.substring(path.lastIndexOf(File.separator) + 1); + } + catch (IOException e) + { + name = f.getName(); + } + } + } + return name; } /** diff --git a/javax/swing/plaf/basic/BasicButtonListener.java b/javax/swing/plaf/basic/BasicButtonListener.java index fe0365a50..042192b62 100644 --- a/javax/swing/plaf/basic/BasicButtonListener.java +++ b/javax/swing/plaf/basic/BasicButtonListener.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.plaf.basic; +import gnu.classpath.SystemProperties; + import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; @@ -71,10 +73,12 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, // Store the TextLayout for this in a client property for speed-up // painting of the label. String property = e.getPropertyName(); - if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) - || property.equals("font")) + AbstractButton b = (AbstractButton) e.getSource(); + if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) + || property.equals("font")) + && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") + == null) { - AbstractButton b = (AbstractButton) e.getSource(); String text = b.getText(); if (text == null) text = ""; @@ -83,6 +87,10 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, TextLayout layout = new TextLayout(text, b.getFont(), frc); b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout); } + if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)) + { + BasicHTML.updateRenderer(b, b.getText()); + } } protected void checkOpacity(AbstractButton b) @@ -176,11 +184,14 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, { AbstractButton button = (AbstractButton) e.getSource(); ButtonModel model = button.getModel(); - if (e.getButton() == MouseEvent.BUTTON1) + if (SwingUtilities.isLeftMouseButton(e)) { // It is important that these transitions happen in this order. model.setArmed(true); model.setPressed(true); + + if (! button.isFocusOwner() && button.isRequestFocusEnabled()) + button.requestFocus(); } } } diff --git a/javax/swing/plaf/basic/BasicButtonUI.java b/javax/swing/plaf/basic/BasicButtonUI.java index d531133ba..cdaec2543 100644 --- a/javax/swing/plaf/basic/BasicButtonUI.java +++ b/javax/swing/plaf/basic/BasicButtonUI.java @@ -56,6 +56,7 @@ import javax.swing.UIManager; import javax.swing.plaf.ButtonUI; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; +import javax.swing.text.View; /** * A UI delegate for the {@link JButton} component. @@ -255,10 +256,72 @@ public class BasicButtonUI extends ButtonUI installDefaults(b); installListeners(b); installKeyboardActions(b); + BasicHTML.updateRenderer(b, b.getText()); } } /** + * Uninstalls the UI from the component. + * + * @param c the component from which to uninstall the UI + */ + public void uninstallUI(JComponent c) + { + if (c instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) c; + uninstallKeyboardActions(b); + uninstallListeners(b); + uninstallDefaults(b); + BasicHTML.updateRenderer(b, ""); + } + } + + /** + * Calculates the minimum size for the specified component. + * + * @param c the component for which to compute the minimum size + * + * @return the minimum size for the specified component + */ + public Dimension getMinimumSize(JComponent c) + { + Dimension size = getPreferredSize(c); + // When the HTML view has a minimum width different from the preferred + // width, then substract this here accordingly. The height is not + // affected by that. + View html = (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + size.width -= html.getPreferredSpan(View.X_AXIS) + - html.getPreferredSpan(View.X_AXIS); + } + return size; + } + + /** + * Calculates the maximum size for the specified component. + * + * @param c the component for which to compute the maximum size + * + * @return the maximum size for the specified component + */ + public Dimension getMaximumSize(JComponent c) + { + Dimension size = getPreferredSize(c); + // When the HTML view has a maximum width different from the preferred + // width, then add this here accordingly. The height is not + // affected by that. + View html = (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + size.width += html.getMaximumSpan(View.X_AXIS) + - html.getPreferredSpan(View.X_AXIS); + } + return size; + } + + /** * Calculate the preferred size of this component, by delegating to * {@link BasicGraphicsUtils#getPreferredButtonSize}. * @@ -269,8 +332,8 @@ public class BasicButtonUI extends ButtonUI public Dimension getPreferredSize(JComponent c) { AbstractButton b = (AbstractButton) c; - Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, - defaultTextIconGap + defaultTextShiftOffset); + Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, + b.getIconTextGap()); return d; } @@ -344,7 +407,13 @@ public class BasicButtonUI extends ButtonUI paintIcon(g, c, ir); if (text != null) - paintText(g, b, tr, text); + { + View html = (View) b.getClientProperty(BasicHTML.propertyKey); + if (html != null) + html.paint(g, tr); + else + paintText(g, b, tr, text); + } if (b.isFocusOwner() && b.isFocusPainted()) paintFocus(g, b, vr, tr, ir); } diff --git a/javax/swing/plaf/basic/BasicComboBoxUI.java b/javax/swing/plaf/basic/BasicComboBoxUI.java index ea97782c3..d98fd2afe 100644 --- a/javax/swing/plaf/basic/BasicComboBoxUI.java +++ b/javax/swing/plaf/basic/BasicComboBoxUI.java @@ -289,7 +289,7 @@ public class BasicComboBoxUI extends ComboBoxUI comboBox.addPropertyChangeListener(propertyChangeListener); focusListener = createFocusListener(); - editor.addFocusListener(focusListener); + comboBox.addFocusListener(focusListener); itemListener = createItemListener(); comboBox.addItemListener(itemListener); @@ -543,7 +543,9 @@ public class BasicComboBoxUI extends ComboBoxUI { editor.setFont(comboBox.getFont()); if (popupKeyListener != null) - editor.addKeyListener(popupKeyListener); + editor.addKeyListener(popupKeyListener); + if (keyListener != null) + editor.addKeyListener(keyListener); comboBox.configureEditor(comboBox.getEditor(), comboBox.getSelectedItem()); } @@ -555,6 +557,8 @@ public class BasicComboBoxUI extends ComboBoxUI { if (popupKeyListener != null) editor.removeKeyListener(popupKeyListener); + if (keyListener != null) + editor.removeKeyListener(keyListener); } /** @@ -775,7 +779,9 @@ public class BasicComboBoxUI extends ComboBoxUI protected boolean isNavigationKey(int keyCode) { return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN - || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT; + || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT + || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE + || keyCode == KeyEvent.VK_TAB; } /** @@ -796,7 +802,7 @@ public class BasicComboBoxUI extends ComboBoxUI protected void selectPreviousPossibleValue() { int index = comboBox.getSelectedIndex(); - if (index != 0) + if (index > 0) comboBox.setSelectedIndex(index - 1); } @@ -1201,11 +1207,29 @@ public class BasicComboBoxUI extends ComboBoxUI */ public void keyPressed(KeyEvent e) { - if (! isNavigationKey(e.getKeyCode()) && comboBox.isEnabled() - && comboBox.getModel().getSize() != 0) + if (comboBox.getModel().getSize() != 0 && comboBox.isEnabled()) { - if (comboBox.selectWithKeyChar(e.getKeyChar())) - e.consume(); + if (! isNavigationKey(e.getKeyCode())) + { + if (! comboBox.isEditable()) + if (comboBox.selectWithKeyChar(e.getKeyChar())) + e.consume(); + } + else + { + if (e.getKeyCode() == KeyEvent.VK_UP && comboBox.isPopupVisible()) + selectPreviousPossibleValue(); + else if (e.getKeyCode() == KeyEvent.VK_DOWN) + { + if (comboBox.isPopupVisible()) + selectNextPossibleValue(); + else + comboBox.showPopup(); + } + else if (e.getKeyCode() == KeyEvent.VK_ENTER + || e.getKeyCode() == KeyEvent.VK_ESCAPE) + popup.hide(); + } } } } @@ -1370,5 +1394,4 @@ public class BasicComboBoxUI extends ComboBoxUI // FIXME: Need to handle changes in other bound properties. } } - } diff --git a/javax/swing/plaf/basic/BasicDirectoryModel.java b/javax/swing/plaf/basic/BasicDirectoryModel.java index ef7a880c2..ed916cb5f 100644 --- a/javax/swing/plaf/basic/BasicDirectoryModel.java +++ b/javax/swing/plaf/basic/BasicDirectoryModel.java @@ -1,5 +1,5 @@ /* BasicDirectoryModel.java -- - Copyright (C) 2005 Free Software Foundation, Inc. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -40,35 +40,296 @@ package javax.swing.plaf.basic; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; import java.util.Vector; import javax.swing.AbstractListModel; import javax.swing.JFileChooser; +import javax.swing.SwingUtilities; import javax.swing.event.ListDataEvent; import javax.swing.filechooser.FileSystemView; /** - * DOCUMENT ME! + * Implements an AbstractListModel for directories where the source + * of the files is a JFileChooser object. + * + * This class is used for sorting and ordering the file list in + * a JFileChooser L&F object. */ public class BasicDirectoryModel extends AbstractListModel implements PropertyChangeListener { - /** DOCUMENT ME! */ + /** The list of files itself */ private Vector contents; - /** DOCUMENT ME! */ - private int directories; + /** + * The directories in the list. + */ + private Vector directories; + + /** + * The files in the list. + */ + private Vector files; - /** DOCUMENT ME! */ + /** The listing mode of the associated JFileChooser, + either FILES_ONLY, DIRECTORIES_ONLY or FILES_AND_DIRECTORIES */ private int listingMode; - /** DOCUMENT ME! */ + /** The JFileCooser associated with this model */ private JFileChooser filechooser; - /** DOCUMENT ME! */ + /** + * The thread that loads the file view. + */ + private DirectoryLoadThread loadThread; + + /** + * This thread is responsible for loading file lists from the + * current directory and updating the model. + */ + private class DirectoryLoadThread extends Thread + { + + /** + * Updates the Swing list model. + */ + private class UpdateSwingRequest + implements Runnable + { + + private List added; + private int addIndex; + private List removed; + private int removeIndex; + private boolean cancel; + + UpdateSwingRequest(List add, int ai, List rem, int ri) + { + added = add; + addIndex = ai; + removed = rem; + removeIndex = ri; + cancel = false; + } + + public void run() + { + if (! cancel) + { + int numRemoved = removed == null ? 0 : removed.size(); + int numAdded = added == null ? 0 : added.size(); + synchronized (contents) + { + if (numRemoved > 0) + contents.removeAll(removed); + if (numAdded > 0) + contents.addAll(added); + + files = null; + directories = null; + } + if (numRemoved > 0 && numAdded == 0) + fireIntervalRemoved(BasicDirectoryModel.this, removeIndex, + removeIndex + numRemoved - 1); + else if (numRemoved == 0 && numAdded > 0) + fireIntervalAdded(BasicDirectoryModel.this, addIndex, + addIndex + numAdded - 1); + else + fireContentsChanged(); + } + } + + void cancel() + { + cancel = true; + } + } + + /** + * The directory beeing loaded. + */ + File directory; + + /** + * Stores all UpdateSwingRequests that are sent to the event queue. + */ + private UpdateSwingRequest pending; + + /** + * Creates a new DirectoryLoadThread that loads the specified + * directory. + * + * @param dir the directory to load + */ + DirectoryLoadThread(File dir) + { + super("Basic L&F directory loader"); + directory = dir; + } + + public void run() + { + FileSystemView fsv = filechooser.getFileSystemView(); + File[] files = fsv.getFiles(directory, + filechooser.isFileHidingEnabled()); + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + // Check list for accepted files. + Vector accepted = new Vector(); + for (int i = 0; i < files.length; i++) + { + if (filechooser.accept(files[i])) + accepted.add(files[i]); + } + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + // Sort list. + sort(accepted); + + // Now split up directories from files so that we get the directories + // listed before the files. + Vector newFiles = new Vector(); + Vector newDirectories = new Vector(); + for (Iterator i = accepted.iterator(); i.hasNext();) + { + File f = (File) i.next(); + boolean traversable = filechooser.isTraversable(f); + if (traversable) + newDirectories.add(f); + else if (! traversable && filechooser.isFileSelectionEnabled()) + newFiles.add(f); + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + } + + // Build up new file cache. Try to update only the changed elements. + // This will be important for actions like adding new files or + // directories inside a large file list. + Vector newCache = new Vector(newDirectories); + newCache.addAll(newFiles); + + int newSize = newCache.size(); + int oldSize = contents.size(); + if (newSize < oldSize) + { + // Check for removed interval. + int start = -1; + int end = -1; + boolean found = false; + for (int i = 0; i < newSize && !found; i++) + { + if (! newCache.get(i).equals(contents.get(i))) + { + start = i; + end = i + oldSize - newSize; + found = true; + } + } + if (start >= 0 && end > start + && contents.subList(end, oldSize) + .equals(newCache.subList(start, newSize))) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + Vector removed = new Vector(contents.subList(start, end)); + UpdateSwingRequest r = new UpdateSwingRequest(null, 0, + removed, start); + invokeLater(r); + newCache = null; + } + } + else if (newSize > oldSize) + { + // Check for inserted interval. + int start = oldSize; + int end = newSize; + boolean found = false; + for (int i = 0; i < oldSize && ! found; i++) + { + if (! newCache.get(i).equals(contents.get(i))) + { + start = i; + boolean foundEnd = false; + for (int j = i; j < newSize && ! foundEnd; j++) + { + if (newCache.get(j).equals(contents.get(i))) + { + end = j; + foundEnd = true; + } + } + end = i + oldSize - newSize; + } + } + if (start >= 0 && end > start + && newCache.subList(end, newSize) + .equals(contents.subList(start, oldSize))) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + List added = newCache.subList(start, end); + UpdateSwingRequest r = new UpdateSwingRequest(added, start, + null, 0); + invokeLater(r); + newCache = null; + } + } + + // Handle complete list changes (newCache != null). + if (newCache != null && ! contents.equals(newCache)) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + UpdateSwingRequest r = new UpdateSwingRequest(newCache, 0, + contents, 0); + invokeLater(r); + } + } + + /** + * Wraps SwingUtilities.invokeLater() and stores the request in + * a Vector so that we can still cancel it later. + * + * @param update the request to invoke + */ + private void invokeLater(UpdateSwingRequest update) + { + pending = update; + SwingUtilities.invokeLater(update); + } + + /** + * Cancels all pending update requests that might be in the AWT + * event queue. + */ + void cancelPending() + { + if (pending != null) + pending.cancel(); + } + } + + /** A Comparator class/object for sorting the file list. */ private Comparator comparator = new Comparator() { public int compare(Object o1, Object o2) @@ -91,14 +352,15 @@ public class BasicDirectoryModel extends AbstractListModel filechooser.addPropertyChangeListener(this); listingMode = filechooser.getFileSelectionMode(); contents = new Vector(); + validateFileCache(); } /** - * DOCUMENT ME! + * Returns whether a given (File) object is included in the list. * - * @param o DOCUMENT ME! + * @param o - The file object to test. * - * @return DOCUMENT ME! + * @return <code>true</code> if the list contains the given object. */ public boolean contains(Object o) { @@ -106,7 +368,7 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Fires a content change event. */ public void fireContentsChanged() { @@ -114,80 +376,99 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Returns a Vector of (java.io.File) objects containing + * the directories in this list. * - * @return DOCUMENT ME! + * @return a Vector */ public Vector getDirectories() { - Vector tmp = new Vector(); - for (int i = 0; i < directories; i++) - tmp.add(contents.get(i)); - return tmp; + // Synchronize this with the UpdateSwingRequest for the case when + // contents is modified. + synchronized (contents) + { + Vector dirs = directories; + if (dirs == null) + { + // Initializes this in getFiles(). + getFiles(); + dirs = directories; + } + return dirs; + } } /** - * DOCUMENT ME! + * Returns the (java.io.File) object at + * an index in the list. * - * @param index DOCUMENT ME! - * - * @return DOCUMENT ME! + * @param index The list index + * @return a File object */ public Object getElementAt(int index) { if (index > getSize() - 1) return null; - if (listingMode == JFileChooser.FILES_ONLY) - return contents.get(directories + index); - else - return contents.elementAt(index); + return contents.elementAt(index); } /** - * DOCUMENT ME! + * Returns a Vector of (java.io.File) objects containing + * the files in this list. * - * @return DOCUMENT ME! + * @return a Vector */ public Vector getFiles() { - Vector tmp = new Vector(); - for (int i = directories; i < getSize(); i++) - tmp.add(contents.get(i)); - return tmp; + synchronized (contents) + { + Vector f = files; + if (f == null) + { + f = new Vector(); + Vector d = new Vector(); // Directories; + for (Iterator i = contents.iterator(); i.hasNext();) + { + File file = (File) i.next(); + if (filechooser.isTraversable(file)) + d.add(file); + else + f.add(file); + } + files = f; + directories = d; + } + return f; + } } /** - * DOCUMENT ME! + * Returns the size of the list, which only includes directories + * if the JFileChooser is set to DIRECTORIES_ONLY. + * + * Otherwise, both directories and files are included in the count. * - * @return DOCUMENT ME! + * @return The size of the list. */ public int getSize() { - if (listingMode == JFileChooser.DIRECTORIES_ONLY) - return directories; - else if (listingMode == JFileChooser.FILES_ONLY) - return contents.size() - directories; return contents.size(); } /** - * DOCUMENT ME! + * Returns the index of an (java.io.File) object in the list. * - * @param o DOCUMENT ME! + * @param o The object - normally a File. * - * @return DOCUMENT ME! + * @return the index of that object, or -1 if it is not in the list. */ public int indexOf(Object o) { - if (listingMode == JFileChooser.FILES_ONLY) - return contents.indexOf(o) - directories; return contents.indexOf(o); } /** - * DOCUMENT ME! - * - * @param e DOCUMENT ME! + * Obsoleted method which does nothing. */ public void intervalAdded(ListDataEvent e) { @@ -195,9 +476,7 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! - * - * @param e DOCUMENT ME! + * Obsoleted method which does nothing. */ public void intervalRemoved(ListDataEvent e) { @@ -205,7 +484,7 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Obsoleted method which does nothing. */ public void invalidateFileCache() { @@ -213,12 +492,16 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Less than, determine the relative order in the list of two files + * for sorting purposes. + * + * The order is: directories < files, and thereafter alphabetically, + * using the default locale collation. * - * @param a DOCUMENT ME! - * @param b DOCUMENT ME! + * @param a the first file + * @param b the second file * - * @return DOCUMENT ME! + * @return <code>true</code> if a > b, <code>false</code> if a < b. */ protected boolean lt(File a, File b) { @@ -241,73 +524,66 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Listens for a property change; the change in file selection mode of the + * associated JFileChooser. Reloads the file cache on that event. * - * @param e DOCUMENT ME! + * @param e - A PropertyChangeEvent. */ public void propertyChange(PropertyChangeEvent e) { - if (e.getPropertyName().equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) - listingMode = filechooser.getFileSelectionMode(); + String property = e.getPropertyName(); + if (property.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_VIEW_CHANGED_PROPERTY) + ) + { + validateFileCache(); + } } /** - * DOCUMENT ME! + * Renames a file - However, does <I>not</I> re-sort the list + * or replace the old file with the new one in the list. * - * @param oldFile DOCUMENT ME! - * @param newFile DOCUMENT ME! + * @param oldFile The old file + * @param newFile The new file name * - * @return DOCUMENT ME! + * @return <code>true</code> if the rename succeeded */ public boolean renameFile(File oldFile, File newFile) { - // FIXME: implement - return false; + return oldFile.renameTo( newFile ); } /** - * DOCUMENT ME! + * Sorts a Vector of File objects. * - * @param v DOCUMENT ME! + * @param v The Vector to sort. */ protected void sort(Vector v) { Collections.sort(v, comparator); - Enumeration e = Collections.enumeration(v); - Vector tmp = new Vector(); - for (; e.hasMoreElements();) - tmp.add(e.nextElement()); - - contents = tmp; } /** - * DOCUMENT ME! + * Re-loads the list of files */ public void validateFileCache() { - contents.clear(); - directories = 0; - FileSystemView fsv = filechooser.getFileSystemView(); - File[] list = fsv.getFiles(filechooser.getCurrentDirectory(), - filechooser.isFileHidingEnabled()); - - if (list == null) - return; - - for (int i = 0; i < list.length; i++) + File dir = filechooser.getCurrentDirectory(); + if (dir != null) { - if (list[i] == null) - continue; - if (filechooser.accept(list[i])) - { - contents.add(list[i]); - if (filechooser.isTraversable(list[i])) - directories++; - } + // Cancel all pending requests. + if (loadThread != null) + { + loadThread.interrupt(); + loadThread.cancelPending(); + } + loadThread = new DirectoryLoadThread(dir); + loadThread.start(); } - sort(contents); - filechooser.revalidate(); - filechooser.repaint(); } } + diff --git a/javax/swing/plaf/basic/BasicFileChooserUI.java b/javax/swing/plaf/basic/BasicFileChooserUI.java index 1356db4ae..dc1c05122 100644 --- a/javax/swing/plaf/basic/BasicFileChooserUI.java +++ b/javax/swing/plaf/basic/BasicFileChooserUI.java @@ -160,6 +160,8 @@ public class BasicFileChooserUI extends FileChooserUI else { File f = new File(filechooser.getCurrentDirectory(), getFileName()); + if ( selectedDir != null ) + f = selectedDir; if (filechooser.isTraversable(f)) { filechooser.setCurrentDirectory(f); @@ -266,7 +268,14 @@ public class BasicFileChooserUI extends FileChooserUI */ public String getName(File f) { - return f.getName(); + String name = null; + if (f != null) + { + JFileChooser c = getFileChooser(); + FileSystemView v = c.getFileSystemView(); + name = v.getSystemDisplayName(f); + } + return name; } /** @@ -409,7 +418,7 @@ public class BasicFileChooserUI extends FileChooserUI closeDialog(); } } - else + else // single click { String path = p.toString(); File f = fsv.createFileObject(path); @@ -436,10 +445,11 @@ public class BasicFileChooserUI extends FileChooserUI } lastSelected = path; parentPath = path.substring(0, path.lastIndexOf("/") + 1); + if (f.isFile()) setFileName(path.substring(path.lastIndexOf("/") + 1)); - else if (filechooser.getFileSelectionMode() == - JFileChooser.DIRECTORIES_ONLY) + else if (filechooser.getFileSelectionMode() != + JFileChooser.FILES_ONLY) setFileName(path); } } @@ -538,7 +548,7 @@ public class BasicFileChooserUI extends FileChooserUI } /** - * DOCUMENT ME! + * Sets the JFileChooser to the selected file on an update * * @param e DOCUMENT ME! */ @@ -550,9 +560,15 @@ public class BasicFileChooserUI extends FileChooserUI return; File file = filechooser.getFileSystemView().createFileObject(f.toString()); if (! filechooser.isTraversable(file)) - filechooser.setSelectedFile(file); + { + selectedDir = null; + filechooser.setSelectedFile(file); + } else - filechooser.setSelectedFile(null); + { + selectedDir = file; + filechooser.setSelectedFile(null); + } } } @@ -752,6 +768,13 @@ public class BasicFileChooserUI extends FileChooserUI * @see #getUpdateAction() */ private UpdateAction updateAction; + + /** + * When in FILES_ONLY, mode a directory cannot be selected, so + * we save a reference to any it here. This is used to enter + * the directory on "Open" when in that mode. + */ + private File selectedDir; // -- end private -- @@ -874,7 +897,9 @@ public class BasicFileChooserUI extends FileChooserUI protected void installListeners(JFileChooser fc) { propertyChangeListener = createPropertyChangeListener(filechooser); - filechooser.addPropertyChangeListener(propertyChangeListener); + if (propertyChangeListener != null) + filechooser.addPropertyChangeListener(propertyChangeListener); + fc.addPropertyChangeListener(getModel()); } /** @@ -884,8 +909,12 @@ public class BasicFileChooserUI extends FileChooserUI */ protected void uninstallListeners(JFileChooser fc) { - filechooser.removePropertyChangeListener(propertyChangeListener); - propertyChangeListener = null; + if (propertyChangeListener != null) + { + filechooser.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; + } + fc.removePropertyChangeListener(getModel()); } /** @@ -1046,12 +1075,8 @@ public class BasicFileChooserUI extends FileChooserUI */ public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) { - return new PropertyChangeListener() - { - public void propertyChange(PropertyChangeEvent e) - { - } - }; + // The RI returns null here, so do we. + return null; } /** diff --git a/javax/swing/plaf/basic/BasicHTML.java b/javax/swing/plaf/basic/BasicHTML.java index 98c9cb277..67f9c20dc 100644 --- a/javax/swing/plaf/basic/BasicHTML.java +++ b/javax/swing/plaf/basic/BasicHTML.java @@ -107,6 +107,7 @@ public class BasicHTML editorKit = kit; document = doc; setView(view); + setSize(view.getPreferredSpan(X_AXIS), view.getPreferredSpan(Y_AXIS)); } /** @@ -151,6 +152,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 diff --git a/javax/swing/plaf/basic/BasicInternalFrameUI.java b/javax/swing/plaf/basic/BasicInternalFrameUI.java index bfd97b913..8161df29f 100644 --- a/javax/swing/plaf/basic/BasicInternalFrameUI.java +++ b/javax/swing/plaf/basic/BasicInternalFrameUI.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Container; @@ -460,18 +459,12 @@ public class BasicInternalFrameUI extends InternalFrameUI { if (frame.isMaximum()) { - JDesktopPane pane = (JDesktopPane) e.getSource(); - Insets insets = pane.getInsets(); - Rectangle bounds = pane.getBounds(); - - frame.setBounds(bounds.x + insets.left, bounds.y + insets.top, - bounds.width - insets.left - insets.right, - bounds.height - insets.top - insets.bottom); - frame.revalidate(); - frame.repaint(); + Container parent = frame.getParent(); + Insets i = parent.getInsets(); + int width = parent.getWidth() - i.left - i.right; + int height = parent.getHeight() - i.top - i.bottom; + frame.setBounds(0, 0, width, height); } - - // Sun also resizes the icons. but it doesn't seem to do anything. } /** @@ -692,17 +685,12 @@ public class BasicInternalFrameUI extends InternalFrameUI /** The MouseEvent target. */ private transient Component mouseEventTarget; - /** The component pressed. */ - private transient Component pressedComponent; - - /** The last component entered. */ - private transient Component lastComponentEntered; - - /** Used to store/reset lastComponentEntered. */ - private transient Component tempComponent; + private Component dragTarget; - /** The number of presses. */ - private transient int pressCount; + /** + * Indicates if we are currently in a dragging operation or not. + */ + private boolean isDragging; /** * This method is called when the mouse enters the glass pane. @@ -767,7 +755,10 @@ public class BasicInternalFrameUI extends InternalFrameUI */ public void mousePressed(MouseEvent e) { - activateFrame(frame); + // Experiments show that this seems to call the + // borderListener.mousePressed() method to activate the frame. + if (borderListener != null) + borderListener.mousePressed(e); handleEvent(e); } @@ -783,149 +774,104 @@ public class BasicInternalFrameUI extends InternalFrameUI } /** - * This method acquires a candidate component to dispatch the MouseEvent to. + * This is a helper method that dispatches the GlassPane MouseEvents to the + * proper component. * - * @param me - * The MouseEvent to acquire a component for. + * @param e the mouse event to be dispatched */ - private void acquireComponentForMouseEvent(MouseEvent me) + private void handleEvent(MouseEvent e) { - int x = me.getX(); - int y = me.getY(); - - // Find the candidate which should receive this event. - Component parent = frame.getLayeredPane(); - if (parent == null) - return; - Component candidate = null; - Point p = me.getPoint(); - while (candidate == null && parent != null) - { - candidate = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y); - if (candidate == null) - { - p = SwingUtilities.convertPoint(parent, p.x, p.y, - parent.getParent()); - parent = parent.getParent(); - } - } - - // If the only candidate we found was the native container itself, - // don't dispatch any event at all. We only care about the lightweight - // children here. - if (candidate == frame.getContentPane()) - candidate = null; - - // If our candidate is new, inform the old target we're leaving. - if (lastComponentEntered != null && lastComponentEntered.isShowing() - && lastComponentEntered != candidate) + // Find candidate component inside the JInternalFrame. + Component target = frame.getLayeredPane().findComponentAt(e.getX(), + e.getY()); + + // Now search upwards to find a component that actually has + // a MouseListener attached. + while (target != null + && target.getMouseListeners().length == 0 + && target.getMouseMotionListeners().length == 0 + && target.getMouseWheelListeners().length == 0) { - Point tp = SwingUtilities.convertPoint(frame.getContentPane(), x, y, - lastComponentEntered); - MouseEvent exited = new MouseEvent(lastComponentEntered, - MouseEvent.MOUSE_EXITED, - me.getWhen(), me.getModifiersEx(), - tp.x, tp.y, me.getClickCount(), - me.isPopupTrigger(), - me.getButton()); - tempComponent = lastComponentEntered; - lastComponentEntered = null; - tempComponent.dispatchEvent(exited); + target = target.getParent(); } - // If we have a candidate, maybe enter it. - if (candidate != null) + if (target != null) { - mouseEventTarget = candidate; - if (candidate.isLightweight() && candidate.isShowing() - && candidate != frame.getContentPane() - && candidate != lastComponentEntered) - { - lastComponentEntered = mouseEventTarget; - Point cp = SwingUtilities.convertPoint(frame.getContentPane(), x, - y, lastComponentEntered); - MouseEvent entered = new MouseEvent(lastComponentEntered, - MouseEvent.MOUSE_ENTERED, - me.getWhen(), - me.getModifiersEx(), cp.x, - cp.y, me.getClickCount(), - me.isPopupTrigger(), - me.getButton()); - lastComponentEntered.dispatchEvent(entered); - } - } - - if (me.getID() == MouseEvent.MOUSE_RELEASED - || me.getID() == MouseEvent.MOUSE_PRESSED && pressCount > 0 - || me.getID() == MouseEvent.MOUSE_DRAGGED) - // If any of the following events occur while a button is held down, - // they should be dispatched to the same component to which the - // original MOUSE_PRESSED event was dispatched: - // - MOUSE_RELEASED - // - MOUSE_PRESSED: another button pressed while the first is held down - // - MOUSE_DRAGGED - mouseEventTarget = pressedComponent; - else if (me.getID() == MouseEvent.MOUSE_CLICKED) - { - // Don't dispatch CLICKED events whose target is not the same as the - // target for the original PRESSED event. - if (candidate != pressedComponent) - mouseEventTarget = null; - else if (pressCount == 0) - pressedComponent = null; + int id = e.getID(); + switch (id) + { + case MouseEvent.MOUSE_ENTERED: + // Now redispatch the thing. + if (! isDragging || frame.isSelected()) + { + mouseEventTarget = target; + redispatch(id, e, mouseEventTarget); + } + break; + case MouseEvent.MOUSE_EXITED: + if (! isDragging || frame.isSelected()) + { + redispatch(id, e, mouseEventTarget); + } + break; + case MouseEvent.MOUSE_PRESSED: + mouseEventTarget = target; + redispatch(id, e, mouseEventTarget); + // Start dragging. + dragTarget = target; + break; + case MouseEvent.MOUSE_RELEASED: + if (isDragging) + { + redispatch(id, e, dragTarget); + isDragging = false; + } + else + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_CLICKED: + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_MOVED: + if (target != mouseEventTarget) + { + // Create additional MOUSE_EXITED/MOUSE_ENTERED pairs. + redispatch(MouseEvent.MOUSE_EXITED, e, mouseEventTarget); + mouseEventTarget = target; + redispatch(MouseEvent.MOUSE_ENTERED, e, mouseEventTarget); + } + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_DRAGGED: + if (! isDragging) + isDragging = true; + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_WHEEL: + redispatch(id, e, mouseEventTarget); + break; + default: + assert false : "Must not reach here"; + } } } /** - * This is a helper method that dispatches the GlassPane MouseEvents to the - * proper component. - * - * @param e - * The AWTEvent to be dispatched. Usually an instance of - * MouseEvent. + * Redispatches the event to the real target with the specified id. + * + * @param id the new event ID + * @param e the original event + * @param target the real event target */ - private void handleEvent(AWTEvent e) + private void redispatch(int id, MouseEvent e, Component target) { - if (e instanceof MouseEvent) - { - MouseEvent me = (MouseEvent) e; - acquireComponentForMouseEvent(me); - - //If there is no target, return - if (mouseEventTarget == null) - return; - - //Avoid re-dispatching to ourselves and causing an infinite loop - if (mouseEventTarget.equals(frame.getGlassPane())) - return; - - // Avoid dispatching ENTERED and EXITED events twice. - if (mouseEventTarget.isShowing() - && e.getID() != MouseEvent.MOUSE_ENTERED - && e.getID() != MouseEvent.MOUSE_EXITED) - { - MouseEvent newEvt = SwingUtilities.convertMouseEvent( - frame.getGlassPane(), - me, - mouseEventTarget); - mouseEventTarget.dispatchEvent(newEvt); - - switch (e.getID()) - { - case MouseEvent.MOUSE_PRESSED: - if (pressCount++ == 0) - pressedComponent = mouseEventTarget; - break; - case MouseEvent.MOUSE_RELEASED: - // Clear our memory of the original PRESSED event, only if - // we're not expecting a CLICKED event after this. If - // there is a CLICKED event after this, it will do clean up. - if (--pressCount == 0 && mouseEventTarget != pressedComponent) - pressedComponent = null; - break; - } - } - } + Point p = SwingUtilities.convertPoint(frame.getLayeredPane(), e.getX(), + e.getY(), target); + MouseEvent ev = new MouseEvent(target, id, e.getWhen(), + e.getModifiers() | e.getModifiersEx(), + p.x, p.y, e.getClickCount(), + e.isPopupTrigger()); + target.dispatchEvent(ev); } } @@ -962,10 +908,17 @@ public class BasicInternalFrameUI extends InternalFrameUI } else if (property.equals(JInternalFrame.IS_SELECTED_PROPERTY)) { + Component glassPane = frame.getGlassPane(); if (frame.isSelected()) - activateFrame(frame); + { + activateFrame(frame); + glassPane.setVisible(false); + } else - deactivateFrame(frame); + { + deactivateFrame(frame); + glassPane.setVisible(true); + } } else if (property.equals(JInternalFrame.ROOT_PANE_PROPERTY) || property.equals(JInternalFrame.GLASS_PANE_PROPERTY)) @@ -990,17 +943,25 @@ public class BasicInternalFrameUI extends InternalFrameUI { if (evt.getNewValue() == Boolean.TRUE) { + Container parent = frame.getParent(); + if (parent != null) + parent.removeComponentListener(componentListener); closeFrame(frame); } } - /* - * FIXME: need to add ancestor properties to JComponents. else if - * (evt.getPropertyName().equals(JComponent.ANCESTOR_PROPERTY)) { if - * (desktopPane != null) - * desktopPane.removeComponentListener(componentListener); desktopPane = - * frame.getDesktopPane(); if (desktopPane != null) - * desktopPane.addComponentListener(componentListener); } - */ + else if (property.equals("ancestor")) + { + Container newParent = (Container) evt.getNewValue(); + Container oldParent = (Container) evt.getOldValue(); + if (newParent != null) + { + newParent.addComponentListener(componentListener); + } + else if (oldParent != null) + { + oldParent.removeComponentListener(componentListener); + } + } } } @@ -1299,6 +1260,12 @@ public class BasicInternalFrameUI extends InternalFrameUI frame.addPropertyChangeListener(propertyChangeListener); frame.getRootPane().getGlassPane().addMouseListener(glassPaneDispatcher); frame.getRootPane().getGlassPane().addMouseMotionListener(glassPaneDispatcher); + + Container parent = frame.getParent(); + if (parent != null) + { + parent.addComponentListener(componentListener); + } } /** @@ -1327,8 +1294,13 @@ public class BasicInternalFrameUI extends InternalFrameUI */ protected void uninstallListeners() { - if (desktopPane != null) - desktopPane.removeComponentListener(componentListener); + + Container parent = frame.getParent(); + if (parent != null) + { + parent.removeComponentListener(componentListener); + } + componentListener = null; frame.getRootPane().getGlassPane().removeMouseMotionListener(glassPaneDispatcher); frame.getRootPane().getGlassPane().removeMouseListener(glassPaneDispatcher); @@ -1339,7 +1311,7 @@ public class BasicInternalFrameUI extends InternalFrameUI frame.removeMouseListener(borderListener); propertyChangeListener = null; - componentListener = null; + borderListener = null; internalFrameListener = null; glassPaneDispatcher = null; diff --git a/javax/swing/plaf/basic/BasicListUI.java b/javax/swing/plaf/basic/BasicListUI.java index 10f86ebda..493fc0578 100644 --- a/javax/swing/plaf/basic/BasicListUI.java +++ b/javax/swing/plaf/basic/BasicListUI.java @@ -479,7 +479,8 @@ public class BasicListUI extends ListUI */ public void mousePressed(MouseEvent event) { - // TODO: What should be done here, if anything? + // We need to explicitly request focus. + list.requestFocusInWindow(); } /** @@ -1210,7 +1211,7 @@ public class BasicListUI extends ListUI boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus(); Component comp = rend.getListCellRendererComponent(list, data.getElementAt(row), - 0, isSel, hasFocus); + row, isSel, hasFocus); rendererPane.paintComponent(g, comp, list, bounds); } diff --git a/javax/swing/plaf/basic/BasicLookAndFeel.java b/javax/swing/plaf/basic/BasicLookAndFeel.java index 1fffcddca..c056a2403 100644 --- a/javax/swing/plaf/basic/BasicLookAndFeel.java +++ b/javax/swing/plaf/basic/BasicLookAndFeel.java @@ -1031,6 +1031,25 @@ public abstract class BasicLookAndFeel extends LookAndFeel "PopupMenu.border", new BorderUIResource.BevelBorderUIResource(0), "PopupMenu.font", new FontUIResource("Dialog", Font.PLAIN, 12), "PopupMenu.foreground", new ColorUIResource(darkShadow), + "PopupMenu.selectedWindowInputMapBindings", + new Object[] {"ESCAPE", "cancel", + "DOWN", "selectNext", + "KP_DOWN", "selectNext", + "UP", "selectPrevious", + "KP_UP", "selectPrevious", + "LEFT", "selectParent", + "KP_LEFT", "selectParent", + "RIGHT", "selectChild", + "KP_RIGHT", "selectChild", + "ENTER", "return", + "SPACE", "return" + }, + "PopupMenu.selectedWindowInputMapBindings.RightToLeft", + new Object[] {"LEFT", "selectChild", + "KP_LEFT", "selectChild", + "RIGHT", "selectParent", + "KP_RIGHT", "selectParent", + }, "ProgressBar.background", new ColorUIResource(Color.LIGHT_GRAY), "ProgressBar.border", new BorderUIResource.LineBorderUIResource(Color.GREEN, 2), diff --git a/javax/swing/plaf/basic/BasicMenuBarUI.java b/javax/swing/plaf/basic/BasicMenuBarUI.java index f258ebe30..cd25a3baf 100644 --- a/javax/swing/plaf/basic/BasicMenuBarUI.java +++ b/javax/swing/plaf/basic/BasicMenuBarUI.java @@ -38,24 +38,31 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Dimension; +import java.awt.event.ActionEvent; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.BoxLayout; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.LookAndFeel; import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.MenuBarUI; @@ -64,6 +71,47 @@ import javax.swing.plaf.MenuBarUI; */ public class BasicMenuBarUI extends MenuBarUI { + + /** + * This action is performed for the action command 'takeFocus'. + */ + private static class FocusAction + extends AbstractAction + { + + /** + * Creates a new FocusAction. + */ + FocusAction() + { + super("takeFocus"); + } + + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + // In the JDK this action seems to pop up the first menu of the + // menu bar. + JMenuBar menuBar = (JMenuBar) event.getSource(); + MenuSelectionManager defaultManager = + MenuSelectionManager.defaultManager(); + MenuElement me[]; + MenuElement subElements[]; + JMenu menu = menuBar.getMenu(0); + if (menu != null) + { + me = new MenuElement[3]; + me[0] = (MenuElement) menuBar; + me[1] = (MenuElement) menu; + me[2] = (MenuElement) menu.getPopupMenu(); + defaultManager.setSelectedPath(me); + } + } + + } + protected ChangeListener changeListener; /*ContainerListener that listens to the ContainerEvents fired from menu bar*/ @@ -178,9 +226,46 @@ public class BasicMenuBarUI extends MenuBarUI * This method installs the keyboard actions for the JMenuBar. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: implement + // Install InputMap. + Object[] bindings = + (Object[]) SharedUIDefaults.get("MenuBar.windowBindings"); + InputMap inputMap = LookAndFeel.makeComponentInputMap(menuBar, bindings); + SwingUtilities.replaceUIInputMap(menuBar, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // Install ActionMap. + SwingUtilities.replaceUIActionMap(menuBar, getActionMap()); + } + + /** + * Creates and returns the shared action map for JTrees. + * + * @return the shared action map for JTrees + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("MenuBar.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("MenuBar.actionMap", am); + } + return am; + } + + /** + * Creates the default actions when there are none specified by the L&F. + * + * @return the default actions + */ + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new FocusAction(); + am.put(action.getValue(Action.NAME), action); + return am; } /** @@ -226,9 +311,10 @@ public class BasicMenuBarUI extends MenuBarUI * This method reverses the work done in installKeyboardActions. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + SwingUtilities.replaceUIInputMap(menuBar, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); + SwingUtilities.replaceUIActionMap(menuBar, null); } /** diff --git a/javax/swing/plaf/basic/BasicMenuItemUI.java b/javax/swing/plaf/basic/BasicMenuItemUI.java index c5ed2ff7e..6110aca66 100644 --- a/javax/swing/plaf/basic/BasicMenuItemUI.java +++ b/javax/swing/plaf/basic/BasicMenuItemUI.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.Component; import java.awt.Container; @@ -256,8 +258,11 @@ public class BasicMenuItemUI extends MenuItemUI map.put(accelerator, "doClick"); } // TextLayout caching for speed-up drawing of text. - else if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) - || property.equals("font")) + else if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) + || property.equals("font")) + && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") + == null) + { AbstractButton b = (AbstractButton) e.getSource(); String text = b.getText(); @@ -406,10 +411,6 @@ public class BasicMenuItemUI extends MenuItemUI { ArrayList path = new ArrayList(); - // Path to menu should also include its popup menu. - if (menuItem instanceof JMenu) - path.add(((JMenu) menuItem).getPopupMenu()); - Component c = menuItem; while (c instanceof MenuElement) { diff --git a/javax/swing/plaf/basic/BasicMenuUI.java b/javax/swing/plaf/basic/BasicMenuUI.java index 7d8784fd1..355e0435e 100644 --- a/javax/swing/plaf/basic/BasicMenuUI.java +++ b/javax/swing/plaf/basic/BasicMenuUI.java @@ -41,16 +41,22 @@ package javax.swing.plaf.basic; import gnu.classpath.NotImplementedException; import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeListener; +import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JPopupMenu; import javax.swing.LookAndFeel; +import javax.swing.MenuElement; import javax.swing.MenuSelectionManager; +import javax.swing.Timer; import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; @@ -69,6 +75,32 @@ import javax.swing.plaf.ComponentUI; */ public class BasicMenuUI extends BasicMenuItemUI { + /** + * Selects a menu. This is used to delay menu selection. + */ + class SelectMenuAction + extends AbstractAction + { + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + JMenu menu = (JMenu) menuItem; + MenuSelectionManager defaultManager = + MenuSelectionManager.defaultManager(); + MenuElement path[] = defaultManager.getSelectedPath(); + if(path.length > 0 && path[path.length - 1] == menu) + { + MenuElement newPath[] = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + defaultManager.setSelectedPath(newPath); + } + } + + } + protected ChangeListener changeListener; /* MenuListener listens to MenuEvents fired by JMenu */ @@ -201,6 +233,7 @@ public class BasicMenuUI extends BasicMenuItemUI */ protected void installDefaults() { + LookAndFeel.installBorder(menuItem, "Menu.border"); LookAndFeel.installColorsAndFont(menuItem, "Menu.background", "Menu.foreground", "Menu.font"); @@ -212,6 +245,7 @@ public class BasicMenuUI extends BasicMenuItemUI selectionForeground = UIManager.getColor("Menu.selectionForeground"); arrowIcon = UIManager.getIcon("Menu.arrowIcon"); oldBorderPainted = UIManager.getBoolean("Menu.borderPainted"); + ((JMenu) menuItem).setDelay(200); } /** @@ -234,9 +268,10 @@ public class BasicMenuUI extends BasicMenuItemUI } protected void setupPostTimer(JMenu menu) - throws NotImplementedException { - // TODO: Implement this properly. + Timer timer = new Timer(menu.getDelay(), new SelectMenuAction()); + timer.setRepeats(false); + timer.start(); } /** @@ -285,8 +320,7 @@ public class BasicMenuUI extends BasicMenuItemUI { public void mouseClicked(MouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.processMouseEvent(e); + // Nothing to do here. } public void mouseDragged(MouseEvent e) @@ -313,29 +347,46 @@ public class BasicMenuUI extends BasicMenuItemUI public void mouseEntered(MouseEvent e) { - /* When mouse enters menu item, it should be considered selected - - if (i) if this menu is a submenu in some other menu - (ii) or if this menu is in a menu bar and some other menu in a - menu bar was just selected and has its popup menu visible. - (If nothing was selected, menu should be pressed before - it will be selected) - */ JMenu menu = (JMenu) menuItem; - - // NOTE: the following if used to require !menu.isArmed but I could find - // no reason for this and it was preventing some JDK-compatible behaviour. - // Specifically, if a menu is selected but its popup menu not visible, - // and then another menu is selected whose popup menu IS visible, when - // the mouse is moved over the first menu, its popup menu should become - // visible. - - if (! menu.isTopLevelMenu() || popupVisible()) + if (menu.isEnabled()) { - // set new selection and forward this event to MenuSelectionManager - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.setSelectedPath(getPath()); - manager.processMouseEvent(e); + MenuSelectionManager manager = + MenuSelectionManager.defaultManager(); + MenuElement[] selectedPath = manager.getSelectedPath(); + if (! menu.isTopLevelMenu()) + { + // Open the menu immediately or delayed, depending on the + // delay value. + if(! (selectedPath.length > 0 + && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) + { + if(menu.getDelay() == 0) + { + MenuElement[] path = getPath(); + MenuElement[] newPath = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + manager.setSelectedPath(getPath()); + setupPostTimer(menu); + } + } + } + else + { + if(selectedPath.length > 0 + && selectedPath[0] == menu.getParent()) + { + MenuElement[] newPath = new MenuElement[3]; + newPath[0] = (MenuElement) menu.getParent(); + newPath[1] = menu; + newPath[2] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + } } } @@ -354,29 +405,48 @@ public class BasicMenuUI extends BasicMenuItemUI { MenuSelectionManager manager = MenuSelectionManager.defaultManager(); JMenu menu = (JMenu) menuItem; - manager.processMouseEvent(e); - - // Menu should be displayed when the menu is pressed only if - // it is top-level menu - if (menu.isTopLevelMenu()) + if (menu.isEnabled()) { - if (menu.getPopupMenu().isVisible()) - // If menu is visible and menu button was pressed.. - // then need to cancel the menu - manager.clearSelectedPath(); - else - { - // Display the menu - int x = 0; - int y = menu.getHeight(); - - manager.setSelectedPath(getPath()); - - JMenuBar mb = (JMenuBar) menu.getParent(); - - // set selectedIndex of the selectionModel of a menuBar - mb.getSelectionModel().setSelectedIndex(mb.getComponentIndex(menu)); - } + // Open up the menu immediately if it's a toplevel menu. + // But not yet the popup, which might be opened delayed, see below. + if (menu.isTopLevelMenu()) + { + if (menu.isSelected()) + manager.clearSelectedPath(); + else + { + Container cnt = menu.getParent(); + if (cnt != null && cnt instanceof JMenuBar) + { + MenuElement[] me = new MenuElement[2]; + me[0] = (MenuElement) cnt; + me[1] = menu; + manager.setSelectedPath(me); + } + } + } + + // Open the menu's popup. Either do that immediately if delay == 0, + // or delayed when delay > 0. + MenuElement[] selectedPath = manager.getSelectedPath(); + if (selectedPath.length > 0 + && selectedPath[selectedPath.length - 1] != menu.getPopupMenu()) + { + if(menu.isTopLevelMenu() || menu.getDelay() == 0) + { + MenuElement[] newPath = + new MenuElement[selectedPath.length + 1]; + System.arraycopy(selectedPath, 0, newPath, 0, + selectedPath.length); + newPath[selectedPath.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + setupPostTimer(menu); + } + } + } } @@ -493,8 +563,44 @@ public class BasicMenuUI extends BasicMenuItemUI */ public void menuDragMouseDragged(MenuDragMouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.setSelectedPath(e.getPath()); + if (menuItem.isEnabled()) + { + MenuSelectionManager manager = e.getMenuSelectionManager(); + MenuElement path[] = e.getPath(); + + Point p = e.getPoint(); + if(p.x >= 0 && p.x < menuItem.getWidth() + && p.y >= 0 && p.y < menuItem.getHeight()) + { + JMenu menu = (JMenu) menuItem; + MenuElement[] selectedPath = manager.getSelectedPath(); + if(! (selectedPath.length > 0 + && selectedPath[selectedPath.length-1] + == menu.getPopupMenu())) + { + if(menu.isTopLevelMenu() || menu.getDelay() == 0 + || e.getID() == MouseEvent.MOUSE_DRAGGED) + { + MenuElement[] newPath = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + manager.setSelectedPath(path); + setupPostTimer(menu); + } + } + } + else if (e.getID() == MouseEvent.MOUSE_RELEASED) + { + Component comp = manager.componentForPoint(e.getComponent(), + e.getPoint()); + if (comp == null) + manager.clearSelectedPath(); + } + } } /** @@ -505,8 +611,7 @@ public class BasicMenuUI extends BasicMenuItemUI */ public void menuDragMouseEntered(MenuDragMouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.setSelectedPath(e.getPath()); + // Nothing to do here. } /** diff --git a/javax/swing/plaf/basic/BasicOptionPaneUI.java b/javax/swing/plaf/basic/BasicOptionPaneUI.java index 27bcb8c46..e23808580 100644 --- a/javax/swing/plaf/basic/BasicOptionPaneUI.java +++ b/javax/swing/plaf/basic/BasicOptionPaneUI.java @@ -38,13 +38,12 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; +import java.awt.Font; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -58,10 +57,14 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; +import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; @@ -76,6 +79,7 @@ import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.OptionPaneUI; @@ -85,6 +89,21 @@ import javax.swing.plaf.OptionPaneUI; public class BasicOptionPaneUI extends OptionPaneUI { /** + * Implements the "close" keyboard action. + */ + static class OptionPaneCloseAction + extends AbstractAction + { + + public void actionPerformed(ActionEvent event) + { + JOptionPane op = (JOptionPane) event.getSource(); + op.setValue(new Integer(JOptionPane.CLOSED_OPTION)); + } + + } + + /** * This is a helper class that listens to the buttons located at the bottom * of the JOptionPane. * @@ -389,36 +408,20 @@ public class BasicOptionPaneUI extends OptionPaneUI */ public void propertyChange(PropertyChangeEvent e) { - if (e.getPropertyName().equals(JOptionPane.ICON_PROPERTY) - || e.getPropertyName().equals(JOptionPane.MESSAGE_TYPE_PROPERTY)) - addIcon(messageAreaContainer); - else if (e.getPropertyName().equals(JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY)) - resetSelectedValue(); - else if (e.getPropertyName().equals(JOptionPane.INITIAL_VALUE_PROPERTY) - || e.getPropertyName().equals(JOptionPane.OPTIONS_PROPERTY) - || e.getPropertyName().equals(JOptionPane.OPTION_TYPE_PROPERTY)) - { - Container newButtons = createButtonArea(); - optionPane.remove(buttonContainer); - optionPane.add(newButtons); - buttonContainer = newButtons; - } - - else if (e.getPropertyName().equals(JOptionPane.MESSAGE_PROPERTY) - || e.getPropertyName().equals(JOptionPane.WANTS_INPUT_PROPERTY) - || e.getPropertyName().equals(JOptionPane.SELECTION_VALUES_PROPERTY)) + String property = e.getPropertyName(); + if (property.equals(JOptionPane.ICON_PROPERTY) + || property.equals(JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY) + || property.equals(JOptionPane.INITIAL_VALUE_PROPERTY) + || property.equals(JOptionPane.MESSAGE_PROPERTY) + || property.equals(JOptionPane.MESSAGE_TYPE_PROPERTY) + || property.equals(JOptionPane.OPTION_TYPE_PROPERTY) + || property.equals(JOptionPane.OPTIONS_PROPERTY) + || property.equals(JOptionPane.WANTS_INPUT_PROPERTY)) { - optionPane.remove(messageAreaContainer); - messageAreaContainer = createMessageArea(); - optionPane.add(messageAreaContainer); - Container newButtons = createButtonArea(); - optionPane.remove(buttonContainer); - optionPane.add(newButtons); - buttonContainer = newButtons; - optionPane.add(buttonContainer); + uninstallComponents(); + installComponents(); + optionPane.validate(); } - optionPane.invalidate(); - optionPane.repaint(); } } @@ -462,15 +465,6 @@ public class BasicOptionPaneUI extends OptionPaneUI /** The size of the icons. */ private static final int ICON_SIZE = 36; - /** The foreground color for the message area. */ - private transient Color messageForeground; - - /** The border around the message area. */ - private transient Border messageBorder; - - /** The border around the button area. */ - private transient Border buttonBorder; - /** The string used to describe OK buttons. */ private static final String OK_STRING = "OK"; @@ -700,6 +694,7 @@ public class BasicOptionPaneUI extends OptionPaneUI if (icon != null) { iconLabel = new JLabel(icon); + configureLabel(iconLabel); top.add(iconLabel, BorderLayout.WEST); } } @@ -761,7 +756,9 @@ public class BasicOptionPaneUI extends OptionPaneUI } else if (msg instanceof Icon) { - container.add(new JLabel((Icon) msg), cons); + JLabel label = new JLabel((Icon) msg); + configureLabel(label); + container.add(label, cons); cons.gridy++; } else @@ -778,8 +775,11 @@ public class BasicOptionPaneUI extends OptionPaneUI addMessageComponents(container, cons, tmp, maxll, true); } else - addMessageComponents(container, cons, new JLabel(msg.toString()), - maxll, true); + { + JLabel label = new JLabel(msg.toString()); + configureLabel(label); + addMessageComponents(container, cons, label, maxll, true); + } } } @@ -810,6 +810,7 @@ public class BasicOptionPaneUI extends OptionPaneUI remainder = d.substring(maxll); } JLabel label = new JLabel(line); + configureLabel(label); c.add(label); // If there is nothing left to burst, then we can stop. @@ -820,8 +821,12 @@ public class BasicOptionPaneUI extends OptionPaneUI if (remainder.length() > maxll || remainder.contains("\n")) burstStringInto(c, remainder, maxll); else - // Add the remainder to the container and be done. - c.add(new JLabel(remainder)); + { + // Add the remainder to the container and be done. + JLabel l = new JLabel(remainder); + configureLabel(l); + c.add(l); + } } /** @@ -857,6 +862,9 @@ public class BasicOptionPaneUI extends OptionPaneUI protected Container createButtonArea() { JPanel buttonPanel = new JPanel(); + Border b = UIManager.getBorder("OptionPane.buttonAreaBorder"); + if (b != null) + buttonPanel.setBorder(b); buttonPanel.setLayout(createLayoutManager()); addButtonComponents(buttonPanel, getButtons(), getInitialValueIndex()); @@ -882,6 +890,10 @@ public class BasicOptionPaneUI extends OptionPaneUI protected Container createMessageArea() { JPanel messageArea = new JPanel(); + Border messageBorder = UIManager.getBorder("OptionPane.messageAreaBorder"); + if (messageBorder != null) + messageArea.setBorder(messageBorder); + messageArea.setLayout(new BorderLayout()); addIcon(messageArea); @@ -935,10 +947,10 @@ public class BasicOptionPaneUI extends OptionPaneUI * @return A Container that will separate the message and button areas. */ protected Container createSeparator() - throws NotImplementedException { - // FIXME: Figure out what this method is supposed to return and where - // this should be added to the OptionPane. + // The reference implementation returns null here. When overriding + // to return something non-null, the component gets added between + // the message area and the button area. See installComponents(). return null; } @@ -1139,35 +1151,17 @@ public class BasicOptionPaneUI extends OptionPaneUI */ protected void installComponents() { - // reset it. - hasCustomComponents = false; - Container msg = createMessageArea(); - if (msg != null) - { - ((JComponent) msg).setBorder(messageBorder); - msg.setForeground(messageForeground); - messageAreaContainer = msg; - optionPane.add(msg); - } + // First thing is the message area. + optionPane.add(createMessageArea()); - // FIXME: Figure out if the separator should be inserted here or what - // this thing is supposed to do. Note: The JDK does NOT insert another - // component at this place. The JOptionPane only has two panels in it - // and there actually are applications that depend on this beeing so. + // Add separator when createSeparator() is overridden to return + // something other than null. Container sep = createSeparator(); if (sep != null) optionPane.add(sep); - Container button = createButtonArea(); - if (button != null) - { - ((JComponent) button).setBorder(buttonBorder); - buttonContainer = button; - optionPane.add(button); - } - - optionPane.setBorder(BorderFactory.createEmptyBorder(12, 12, 11, 11)); - optionPane.invalidate(); + // Last thing is the button area. + optionPane.add(createButtonArea()); } /** @@ -1181,10 +1175,6 @@ public class BasicOptionPaneUI extends OptionPaneUI LookAndFeel.installBorder(optionPane, "OptionPane.border"); optionPane.setOpaque(true); - messageBorder = UIManager.getBorder("OptionPane.messageAreaBorder"); - messageForeground = UIManager.getColor("OptionPane.messageForeground"); - buttonBorder = UIManager.getBorder("OptionPane.buttonAreaBorder"); - minimumSize = UIManager.getDimension("OptionPane.minimumSize"); // FIXME: Image icons don't seem to work properly right now. @@ -1202,9 +1192,44 @@ public class BasicOptionPaneUI extends OptionPaneUI * This method installs keyboard actions for the JOptionpane. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + // Install the input map. + Object[] bindings = + (Object[]) SharedUIDefaults.get("OptionPane.windowBindings"); + InputMap inputMap = LookAndFeel.makeComponentInputMap(optionPane, + bindings); + SwingUtilities.replaceUIInputMap(optionPane, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(optionPane, getActionMap()); + } + + /** + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("OptionPane.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("OptionPane.actionMap", am); + } + return am; + } + + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new OptionPaneCloseAction(); + + am.put("close", action); + return am; } /** @@ -1317,10 +1342,6 @@ public class BasicOptionPaneUI extends OptionPaneUI minimumSize = null; - messageBorder = null; - buttonBorder = null; - messageForeground = null; - // FIXME: ImageIcons don't seem to work properly /* @@ -1335,9 +1356,10 @@ public class BasicOptionPaneUI extends OptionPaneUI * This method uninstalls keyboard actions for the JOptionPane. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + SwingUtilities.replaceUIInputMap(optionPane, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(optionPane, null); } /** @@ -1363,4 +1385,20 @@ public class BasicOptionPaneUI extends OptionPaneUI optionPane = null; } + + /** + * Applies the proper UI configuration to labels that are added to + * the OptionPane. + * + * @param l the label to configure + */ + private void configureLabel(JLabel l) + { + Color c = UIManager.getColor("OptionPane.messageForeground"); + if (c != null) + l.setForeground(c); + Font f = UIManager.getFont("OptionPane.messageFont"); + if (f != null) + l.setFont(f); + } } diff --git a/javax/swing/plaf/basic/BasicPopupMenuUI.java b/javax/swing/plaf/basic/BasicPopupMenuUI.java index 5ceaecb91..8c0fe6757 100644 --- a/javax/swing/plaf/basic/BasicPopupMenuUI.java +++ b/javax/swing/plaf/basic/BasicPopupMenuUI.java @@ -37,33 +37,574 @@ 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.KeyboardFocusManager; +import java.awt.event.ActionEvent; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; +import java.util.EventListener; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JApplet; import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; +import javax.swing.JRootPane; import javax.swing.LookAndFeel; import javax.swing.MenuElement; import javax.swing.MenuSelectionManager; import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.PopupMenuUI; - /** * UI Delegate for JPopupMenu */ public class BasicPopupMenuUI extends PopupMenuUI { + /** + * Handles keyboard navigation through menus. + */ + private static class NavigateAction + extends AbstractAction + { + + /** + * Creates a new NavigateAction instance. + * + * @param name the name of the action + */ + NavigateAction(String name) + { + super(name); + } + + /** + * Actually performs the action. + */ + public void actionPerformed(ActionEvent event) + { + String name = (String) getValue(Action.NAME); + if (name.equals("selectNext")) + navigateNextPrevious(true); + else if (name.equals("selectPrevious")) + navigateNextPrevious(false); + else if (name.equals("selectChild")) + navigateParentChild(true); + else if (name.equals("selectParent")) + navigateParentChild(false); + else if (name.equals("cancel")) + cancel(); + else if (name.equals("return")) + doReturn(); + else + assert false : "Must not reach here"; + } + + /** + * Navigates to the next or previous menu item. + * + * @param dir <code>true</code>: navigate to next, <code>false</code>: + * navigate to previous + */ + private void navigateNextPrevious(boolean dir) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + int len = path.length; + if (len >= 2) + { + + if (path[0] instanceof JMenuBar && + path[1] instanceof JMenu && len == 2) + { + + // A toplevel menu is selected, but its popup not yet shown. + // Show the popup and select the first item + JPopupMenu popup = ((JMenu)path[1]).getPopupMenu(); + MenuElement next = + findEnabledChild(popup.getSubElements(), -1, true); + MenuElement[] newPath; + + if (next != null) + { + newPath = new MenuElement[4]; + newPath[3] = next; + } + else + { + // Menu has no enabled items, show the popup anyway. + newPath = new MenuElement[3]; + } + System.arraycopy(path, 0, newPath, 0, 2); + newPath[2] = popup; + msm.setSelectedPath(newPath); + } + else if (path[len - 1] instanceof JPopupMenu && + path[len - 2] instanceof JMenu) + { + // Select next item in already shown popup menu. + JMenu menu = (JMenu) path[len - 2]; + JPopupMenu popup = menu.getPopupMenu(); + MenuElement next = + findEnabledChild(popup.getSubElements(), -1, dir); + + if (next != null) + { + MenuElement[] newPath = new MenuElement[len + 1]; + System.arraycopy(path, 0, newPath, 0, len); + newPath[len] = next; + msm.setSelectedPath(newPath); + } + else + { + // All items in the popup are disabled. + // Find the parent popup menu and select + // its next item. If there's no parent popup menu , do nothing. + if (len > 2 && path[len - 3] instanceof JPopupMenu) + { + popup = ((JPopupMenu) path[len - 3]); + next = findEnabledChild(popup.getSubElements(), + menu, dir); + if (next != null && next != menu) + { + MenuElement[] newPath = new MenuElement[len - 1]; + System.arraycopy(path, 0, newPath, 0, len - 2); + newPath[len - 2] = next; + msm.setSelectedPath(newPath); + } + } + } + } + else + { + // Only select the next item. + MenuElement subs[] = path[len - 2].getSubElements(); + MenuElement nextChild = + findEnabledChild(subs, path[len - 1], dir); + if (nextChild == null) + { + nextChild = findEnabledChild(subs, -1, dir); + } + if (nextChild != null) + { + path[len-1] = nextChild; + msm.setSelectedPath(path); + } + } + } + } + + private MenuElement findEnabledChild(MenuElement[] children, + MenuElement start, boolean dir) + { + MenuElement found = null; + for (int i = 0; i < children.length && found == null; i++) + { + if (children[i] == start) + { + found = findEnabledChild(children, i, dir); + } + } + return found; + } + + /** + * Searches the next or previous enabled child menu element. + * + * @param children the children to search through + * @param start the index at which to start + * @param dir the direction (true == forward, false == backward) + * + * @return the found element or null + */ + private MenuElement findEnabledChild(MenuElement[] children, + int start, boolean dir) + { + MenuElement result = null; + if (dir) + { + result = findNextEnabledChild(children, start + 1, children.length-1); + if (result == null) + result = findNextEnabledChild(children, 0, start - 1); + } + else + { + result = findPreviousEnabledChild(children, start - 1, 0); + if (result == null) + result = findPreviousEnabledChild(children, children.length-1, + start + 1); + } + return result; + } + + /** + * Finds the next child element that is enabled and visible. + * + * @param children the children to search through + * @param start the start index + * @param end the end index + * + * @return the found child, or null + */ + private MenuElement findNextEnabledChild(MenuElement[] children, int start, + int end) + { + MenuElement found = null; + for (int i = start; i <= end && found == null; i++) + { + if (children[i] != null) + { + Component comp = children[i].getComponent(); + if (comp != null && comp.isEnabled() && comp.isVisible()) + { + found = children[i]; + } + } + } + return found; + } + + /** + * Finds the previous child element that is enabled and visible. + * + * @param children the children to search through + * @param start the start index + * @param end the end index + * + * @return the found child, or null + */ + private MenuElement findPreviousEnabledChild(MenuElement[] children, + int start, int end) + { + MenuElement found = null; + for (int i = start; i >= end && found == null; i--) + { + if (children[i] != null) + { + Component comp = children[i].getComponent(); + if (comp != null && comp.isEnabled() && comp.isVisible()) + { + found = children[i]; + } + } + } + return found; + } + + /** + * Navigates to the parent or child menu item. + * + * @param selectChild <code>true</code>: navigate to child, + * <code>false</code>: navigate to parent + */ + private void navigateParentChild(boolean selectChild) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + int len = path.length; + + if (selectChild) + { + if (len > 0 && path[len - 1] instanceof JMenu + && ! ((JMenu) path[len-1]).isTopLevelMenu()) + { + // We have a submenu, open it. + JMenu menu = (JMenu) path[len - 1]; + JPopupMenu popup = menu.getPopupMenu(); + MenuElement[] subs = popup.getSubElements(); + MenuElement item = findEnabledChild(subs, -1, true); + MenuElement[] newPath; + + if (item == null) + { + newPath = new MenuElement[len + 1]; + } + else + { + newPath = new MenuElement[len + 2]; + newPath[len + 1] = item; + } + System.arraycopy(path, 0, newPath, 0, len); + newPath[len] = popup; + msm.setSelectedPath(newPath); + return; + } + } + else + { + int popupIndex = len-1; + if (len > 2 + && (path[popupIndex] instanceof JPopupMenu + || path[--popupIndex] instanceof JPopupMenu) + && ! ((JMenu) path[popupIndex - 1]).isTopLevelMenu()) + { + // We have a submenu, close it. + MenuElement newPath[] = new MenuElement[popupIndex]; + System.arraycopy(path, 0, newPath, 0, popupIndex); + msm.setSelectedPath(newPath); + return; + } + } + + // If we got here, we have not selected a child or parent. + // Check if we have a toplevel menu selected. If so, then select + // another one. + if (len > 1 && path[0] instanceof JMenuBar) + { + MenuElement currentMenu = path[1]; + MenuElement nextMenu = findEnabledChild(path[0].getSubElements(), + currentMenu, selectChild); + + if (nextMenu != null && nextMenu != currentMenu) + { + MenuElement newSelection[]; + if (len == 2) + { + // Menu is selected but its popup not shown. + newSelection = new MenuElement[2]; + newSelection[0] = path[0]; + newSelection[1] = nextMenu; + } + else + { + // Menu is selected and its popup is shown. + newSelection = new MenuElement[3]; + newSelection[0] = path[0]; + newSelection[1] = nextMenu; + newSelection[2] = ((JMenu) nextMenu).getPopupMenu(); + } + msm.setSelectedPath(newSelection); + } + } + } + + /** + * Handles cancel requests (ESC key). + */ + private void cancel() + { + // Fire popup menu cancelled event. Unfortunately the + // firePopupMenuCancelled() is protected in JPopupMenu so we work + // around this limitation by fetching the listeners and notifying them + // directly. + JPopupMenu lastPopup = (JPopupMenu) getLastPopup(); + EventListener[] ll = lastPopup.getListeners(PopupMenuListener.class); + for (int i = 0; i < ll.length; i++) + { + PopupMenuEvent ev = new PopupMenuEvent(lastPopup); + ((PopupMenuListener) ll[i]).popupMenuCanceled(ev); + } + + // Close the last popup or the whole selection if there's only one + // popup left. + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + if(path.length > 4) + { + MenuElement newPath[] = new MenuElement[path.length - 2]; + System.arraycopy(path,0,newPath,0,path.length-2); + MenuSelectionManager.defaultManager().setSelectedPath(newPath); + } + else + msm.clearSelectedPath(); + } + + /** + * Returns the last popup menu in the current selection or null. + * + * @return the last popup menu in the current selection or null + */ + private JPopupMenu getLastPopup() + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement[] p = msm.getSelectedPath(); + JPopupMenu popup = null; + for(int i = p.length - 1; popup == null && i >= 0; i--) + { + if (p[i] instanceof JPopupMenu) + popup = (JPopupMenu) p[i]; + } + return popup; + } + + /** + * Handles ENTER key requests. This normally opens submenus on JMenu + * items, or activates the menu item as if it's been clicked on it. + */ + private void doReturn() + { + KeyboardFocusManager fmgr = + KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusOwner = fmgr.getFocusOwner(); + if((focusOwner == null || (focusOwner instanceof JRootPane))) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + MenuElement lastElement; + if(path.length > 0) + { + lastElement = path[path.length - 1]; + if(lastElement instanceof JMenu) + { + MenuElement newPath[] = new MenuElement[path.length + 1]; + System.arraycopy(path,0,newPath,0,path.length); + newPath[path.length] = ((JMenu) lastElement).getPopupMenu(); + msm.setSelectedPath(newPath); + } + else if(lastElement instanceof JMenuItem) + { + JMenuItem mi = (JMenuItem)lastElement; + if (mi.getUI() instanceof BasicMenuItemUI) + { + ((BasicMenuItemUI)mi.getUI()).doClick(msm); + } + else + { + msm.clearSelectedPath(); + mi.doClick(0); + } + } + } + } + } + } + + /** + * Installs keyboard actions when a popup is opened, and uninstalls the + * keyboard actions when closed. This listens on the default + * MenuSelectionManager. + */ + private class KeyboardHelper + implements ChangeListener + { + private MenuElement[] lastSelectedPath = new MenuElement[0]; + private Component lastFocused; + private JRootPane invokerRootPane; + + public void stateChanged(ChangeEvent event) + { + MenuSelectionManager msm = (MenuSelectionManager) event.getSource(); + MenuElement[] p = msm.getSelectedPath(); + JPopupMenu popup = getActivePopup(p); + if (popup == null || popup.isFocusable()) + { + if (lastSelectedPath.length != 0 && p.length != 0 ) + { + if (! invokerEquals(p[0], lastSelectedPath[0])) + { + uninstallKeyboardActionsImpl(); + lastSelectedPath = new MenuElement[0]; + } + } + + if (lastSelectedPath.length == 0 && p.length > 0) + { + JComponent invoker; + if (popup == null) + { + if (p.length == 2 && p[0] instanceof JMenuBar + && p[1] instanceof JMenu) + { + // A menu has been selected but not opened. + invoker = (JComponent)p[1]; + popup = ((JMenu)invoker).getPopupMenu(); + } + else + { + return; + } + } + else + { + Component c = popup.getInvoker(); + if(c instanceof JFrame) + { + invoker = ((JFrame) c).getRootPane(); + } + else if(c instanceof JApplet) + { + invoker = ((JApplet) c).getRootPane(); + } + else + { + while (!(c instanceof JComponent)) + { + if (c == null) + { + return; + } + c = c.getParent(); + } + invoker = (JComponent)c; + } + } + + // Remember current focus owner. + lastFocused = KeyboardFocusManager. + getCurrentKeyboardFocusManager().getFocusOwner(); + + // Install keybindings used for menu navigation. + invokerRootPane = SwingUtilities.getRootPane(invoker); + if (invokerRootPane != null) + { + invokerRootPane.requestFocus(true); + installKeyboardActionsImpl(); + } + } + else if (lastSelectedPath.length != 0 && p.length == 0) + { + // menu hidden -- return focus to where it had been before + // and uninstall menu keybindings + uninstallKeyboardActionsImpl(); + } + } + + // Remember the last path selected + lastSelectedPath = p; + } + + private JPopupMenu getActivePopup(MenuElement[] path) + { + JPopupMenu active = null; + for (int i = path.length - 1; i >= 0 && active == null; i--) + { + MenuElement elem = path[i]; + if (elem instanceof JPopupMenu) + { + active = (JPopupMenu) elem; + } + } + return active; + } + + private boolean invokerEquals(MenuElement el1, MenuElement el2) + { + Component invoker1 = el1.getComponent(); + Component invoker2 = el2.getComponent(); + if (invoker1 instanceof JPopupMenu) + invoker1 = ((JPopupMenu) invoker1).getInvoker(); + if (invoker2 instanceof JPopupMenu) + invoker2 = ((JPopupMenu) invoker2).getInvoker(); + return invoker1 == invoker2; + } + } + /* popupMenu for which this UI delegate is for*/ protected JPopupMenu popupMenu; @@ -75,6 +616,19 @@ public class BasicPopupMenuUI extends PopupMenuUI TopWindowListener topWindowListener; /** + * Counts how many popup menus are handled by this UI or a subclass. + * This is used to install a KeyboardHelper on the MenuSelectionManager + * for the first popup, and uninstall this same KeyboardHelper when the + * last popup is uninstalled. + */ + private static int numPopups; + + /** + * This is the KeyboardHelper that listens on the MenuSelectionManager. + */ + private static KeyboardHelper keyboardHelper; + + /** * Creates a new BasicPopupMenuUI object. */ public BasicPopupMenuUI() @@ -106,6 +660,16 @@ public class BasicPopupMenuUI extends PopupMenuUI public void installUI(JComponent c) { super.installUI(c); + + // Install KeyboardHelper when the first popup is initialized. + if (numPopups == 0) + { + keyboardHelper = new KeyboardHelper(); + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + msm.addChangeListener(keyboardHelper); + } + numPopups++; + popupMenu = (JPopupMenu) c; popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS)); popupMenu.setBorderPainted(true); @@ -113,6 +677,7 @@ public class BasicPopupMenuUI extends PopupMenuUI installDefaults(); installListeners(); + installKeyboardActions(); } /** @@ -139,9 +704,77 @@ public class BasicPopupMenuUI extends PopupMenuUI * This method installs the keyboard actions for this {@link JPopupMenu}. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: Need to implement + // We can't install the keyboard actions here, because then all + // popup menus would have their actions registered in the KeyboardManager. + // So we install it when the popup menu is opened, and uninstall it + // when it's closed. This is done in the KeyboardHelper class. + // Install InputMap. + } + + /** + * Called by the KeyboardHandler when a popup is made visible. + */ + void installKeyboardActionsImpl() + { + Object[] bindings; + if (popupMenu.getComponentOrientation().isLeftToRight()) + { + bindings = (Object[]) + SharedUIDefaults.get("PopupMenu.selectedWindowInputMapBindings"); + } + else + { + bindings = (Object[]) SharedUIDefaults.get + ("PopupMenu.selectedWindowInputMapBindings.RightToLeft"); + } + InputMap inputMap = LookAndFeel.makeComponentInputMap(popupMenu, bindings); + SwingUtilities.replaceUIInputMap(popupMenu, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // Install ActionMap. + SwingUtilities.replaceUIActionMap(popupMenu, getActionMap()); + } + + /** + * Creates and returns the shared action map for JTrees. + * + * @return the shared action map for JTrees + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("PopupMenu.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("PopupMenu.actionMap", am); + } + return am; + } + + /** + * Creates the default actions when there are none specified by the L&F. + * + * @return the default actions + */ + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new NavigateAction("selectNext"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectPrevious"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectParent"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectChild"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("return"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("cancel"); + am.put(action.getValue(Action.NAME), action); + + return am; } /** @@ -155,7 +788,17 @@ public class BasicPopupMenuUI extends PopupMenuUI { uninstallListeners(); uninstallDefaults(); + uninstallKeyboardActions(); popupMenu = null; + + // Install KeyboardHelper when the first popup is initialized. + numPopups--; + if (numPopups == 0) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + msm.removeChangeListener(keyboardHelper); + } + } /** @@ -182,9 +825,22 @@ public class BasicPopupMenuUI extends PopupMenuUI * Uninstalls any keyboard actions. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: Need to implement + // We can't install the keyboard actions here, because then all + // popup menus would have their actions registered in the KeyboardManager. + // So we install it when the popup menu is opened, and uninstall it + // when it's closed. This is done in the KeyboardHelper class. + // Install InputMap. + } + + /** + * Called by the KeyboardHandler when a popup is made invisible. + */ + void uninstallKeyboardActionsImpl() + { + SwingUtilities.replaceUIInputMap(popupMenu, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); + SwingUtilities.replaceUIActionMap(popupMenu, null); } /** 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/BasicTabbedPaneUI.java b/javax/swing/plaf/basic/BasicTabbedPaneUI.java index 17fa6075b..11f25167d 100644 --- a/javax/swing/plaf/basic/BasicTabbedPaneUI.java +++ b/javax/swing/plaf/basic/BasicTabbedPaneUI.java @@ -1,5 +1,5 @@ /* BasicTabbedPaneUI.java -- - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Color; import java.awt.Component; import java.awt.Container; @@ -51,6 +49,7 @@ import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Point; import java.awt.Rectangle; +import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; @@ -60,7 +59,10 @@ import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; import javax.swing.Icon; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JTabbedPane; @@ -72,6 +74,7 @@ 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.ComponentUI; import javax.swing.plaf.PanelUI; import javax.swing.plaf.TabbedPaneUI; @@ -80,11 +83,127 @@ import javax.swing.text.View; /** * This is the Basic Look and Feel's UI delegate for JTabbedPane. + * + * @author Lillian Angel (langel@redhat.com) + * @author Kim Ho (kho@redhat.com) + * @author Roman Kennke (kennke@aicas.com) + * @author Robert Schuster (robertschuster@fsfe.org) */ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { + + static class NavigateAction extends AbstractAction + { + int direction; + + NavigateAction(String name, int dir) + { + super(name); + direction = dir; + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + ui.navigateSelectedTab(direction); + } + + } + + static class NavigatePageDownAction extends AbstractAction + { + + public NavigatePageDownAction() + { + super("navigatePageDown"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + int i = tp.getSelectedIndex(); + + if (i < 0) + i = 0; + + ui.selectNextTabInRun(i); + } + + } + + static class NavigatePageUpAction extends AbstractAction + { + + public NavigatePageUpAction() + { + super("navigatePageUp"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + int i = tp.getSelectedIndex(); + + if (i < 0) + i = 0; + + ui.selectPreviousTabInRun(i); + + } + } + + static class RequestFocusAction extends AbstractAction + { + + public RequestFocusAction() + { + super("requestFocus"); + } + + public void actionPerformed(ActionEvent event) + { + ((JTabbedPane) event.getSource()).requestFocus(); + } + + } + + static class RequestFocusForVisibleComponentAction extends AbstractAction + { + + public RequestFocusForVisibleComponentAction() + { + super("requestFocusForVisibleComponent"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + + // FIXME: This should select a suitable component within + // the tab content. However I dont know whether we have + // to search for this component or wether the called is + // supposed to do that. + tp.getSelectedComponent().requestFocus(); + } + + } + /** - * A helper class that handles focus. + * A helper class that handles focus. + * <p>The purpose of this class is to implement a more flexible focus + * handling for the tabbed pane, which is used to determine whether the + * focus indicator should be painted or not. When in scrolling layout + * mode the area containing the tabs is a scrollpane, so simply testing + * whether the tabbed pane has the focus does not work.</p> + * <p>The <code>FocusHandler</code> is installed on the scrollpane and + * the tabbed pane and sets the variable <code>hasFocus</code> to + * <code>false</code> only when both components do not hold the focus.</p> * * @specnote Apparently this class was intended to be protected, * but was made public by a compiler bug and is now @@ -98,9 +217,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * @param e The FocusEvent. */ public void focusGained(FocusEvent e) - throws NotImplementedException { - // FIXME: Implement. + Object source = e.getSource(); + if (source == panel ) + tabPane.requestFocus(); + else if (source == tabPane) + tabPane.repaint(); } /** @@ -109,9 +231,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * @param e The FocusEvent. */ public void focusLost(FocusEvent e) - throws NotImplementedException { - // FIXME: Implement. + if (e.getOppositeComponent() == tabPane.getSelectedComponent()) + tabPane.requestFocus(); + else if (e.getSource() == tabPane) + tabPane.repaint(); } } @@ -126,6 +250,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public class MouseHandler extends MouseAdapter { + public void mouseReleased(MouseEvent e) + { + // Nothing to do here. + } + /** * This method is called when the mouse is pressed. The index cannot * change to a tab that is not enabled. @@ -134,14 +263,84 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void mousePressed(MouseEvent e) { - if (tabPane.isEnabled()) + Object s = e.getSource(); + int placement = tabPane.getTabPlacement(); + + if (s == incrButton) { - int index = tabForCoordinate(tabPane, e.getX(), e.getY()); - if (index >= 0 && tabPane.isEnabledAt(index)) + if(!incrButton.isEnabled()) + return; + + currentScrollLocation++; + + switch (placement) { - tabPane.setSelectedIndex(index); + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + currentScrollOffset = getTabAreaInsets(placement).left; + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].width; + break; + default: + currentScrollOffset = getTabAreaInsets(placement).top; + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].height; + break; } + + updateViewPosition(); + updateButtons(); + + tabPane.repaint(); + } + else if (s == decrButton) + { + 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--; + + // 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; + } + + updateViewPosition(); + updateButtons(); + + tabPane.repaint(); + } else if (tabPane.isEnabled()) + { + int index = tabForCoordinate(tabPane, e.getX(), e.getY()); + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT + && s == panel) + scrollTab(index, placement); + + tabPane.setSelectedIndex(index); } + } /** @@ -199,6 +398,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { if (e.getPropertyName().equals("tabLayoutPolicy")) { + currentScrollLocation = currentScrollOffset = 0; + layoutManager = createLayoutManager(); tabPane.setLayout(layoutManager); @@ -267,7 +468,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // Find out the minimum/preferred size to display the largest child // of the tabbed pane. - for (int i = 0; i < tabPane.getTabCount(); i++) + int count = tabPane.getTabCount(); + for (int i = 0; i < count; i++) { c = tabPane.getComponentAt(i); if (c == null) @@ -284,21 +486,19 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) { - int min = calculateMaxTabWidth(tabPlacement); - width = Math.max(min, width); - int tabAreaHeight = preferredTabAreaHeight(tabPlacement, - width - tabAreaInsets.left - - tabAreaInsets.right); - height += tabAreaHeight; + width = Math.max(calculateMaxTabWidth(tabPlacement), width); + + height += preferredTabAreaHeight(tabPlacement, + width - tabAreaInsets.left + - tabAreaInsets.right); } else { - int min = calculateMaxTabHeight(tabPlacement); - height = Math.max(min, height); - int tabAreaWidth = preferredTabAreaWidth(tabPlacement, - height - tabAreaInsets.top - - tabAreaInsets.bottom); - width += tabAreaWidth; + height = Math.max(calculateMaxTabHeight(tabPlacement), height); + + width += preferredTabAreaWidth(tabPlacement, + height - tabAreaInsets.top + - tabAreaInsets.bottom); } Insets tabPaneInsets = tabPane.getInsets(); @@ -308,7 +508,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // if tab placement is LEFT OR RIGHT, they share width. // if tab placement is TOP OR BOTTOM, they share height - // PRE STEP: finds the default sizes for the labels as well as their locations. + // PRE STEP: finds the default sizes for the labels as well as their + // locations. // AND where they will be placed within the run system. // 1. calls normalizeTab Runs. // 2. calls rotate tab runs. @@ -347,7 +548,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants break; case RIGHT: maxTabWidth = calculateMaxTabWidth(tabPlacement); - x = size.width - (insets.right + tabAreaInsets.right) - maxTabWidth; + x = size.width - (insets.right + tabAreaInsets.right) + - maxTabWidth - 1; y = insets.top + tabAreaInsets.top; breakAt = size.height - (insets.bottom + tabAreaInsets.bottom); break; @@ -355,7 +557,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants maxTabHeight = calculateMaxTabHeight(tabPlacement); x = insets.left + tabAreaInsets.left; y = size.height - (insets.bottom + tabAreaInsets.bottom) - - maxTabHeight; + - maxTabHeight - 1; breakAt = size.width - (insets.right + tabAreaInsets.right); break; case TOP: @@ -374,6 +576,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants runCount = 0; selectedRun = -1; int selectedIndex = tabPane.getSelectedIndex(); + if (selectedIndex < 0) + selectedIndex = 0; Rectangle rect; @@ -411,7 +615,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants rect.height = maxTabHeight; if (i == selectedIndex) selectedRun = runCount - 1; - } } else @@ -456,9 +659,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int start; if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) - start = y; - else start = x; + else + start = y; normalizeTabRuns(tabPlacement, tabCount, start, breakAt); selectedRun = getRunForTab(tabCount, selectedIndex); if (shouldRotateTabRuns(tabPlacement)) @@ -466,7 +669,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants rotateTabRuns(tabPlacement, selectedRun); } } - + + // Suppress padding if we have only one tab run. + if (runCount == 1) + return; + // Pad the runs. int tabRunOverlay = getTabRunOverlay(tabPlacement); for (int i = runCount - 1; i >= 0; --i) @@ -606,16 +813,18 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public Dimension minimumLayoutSize(Container parent) { - return calculateSize(false); + return calculateSize(true); } - // If there is more free space in an adjacent run AND the tab in the run can fit in the - // adjacent run, move it. This method is not perfect, it is merely an approximation. + // If there is more free space in an adjacent run AND the tab + // in the run can fit in the adjacent run, move it. This method + // is not perfect, it is merely an approximation. // If you play around with Sun's JTabbedPane, you'll see that // it does do some pretty strange things with regards to not moving tabs // that should be moved. // start = the x position where the tabs will begin - // max = the maximum position of where the tabs can go to (tabAreaInsets.left + the width of the tab area) + // max = the maximum position of where the tabs can go to + // (tabAreaInsets.left + the width of the tab area) /** * This method tries to "even out" the number of tabs in each run based on @@ -633,18 +842,20 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabPlacement == SwingUtilities.TOP || tabPlacement == SwingUtilities.BOTTOM) { - // We should only do this for runCount - 1, cause we can only shift that many times between - // runs. + // We should only do this for runCount - 1, cause we can + // only shift that many times between runs. for (int i = 1; i < runCount; i++) { Rectangle currRun = rects[lastTabInRun(tabCount, i)]; - Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))]; + Rectangle nextRun = rects[lastTabInRun(tabCount, + getNextTabRun(i))]; int spaceInCurr = currRun.x + currRun.width; int spaceInNext = nextRun.x + nextRun.width; int diffNow = spaceInCurr - spaceInNext; int diffLater = (spaceInCurr - currRun.width) - (spaceInNext + currRun.width); + while (Math.abs(diffLater) < Math.abs(diffNow) && spaceInNext + currRun.width < max) { @@ -656,11 +867,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants diffLater = (spaceInCurr - currRun.width) - (spaceInNext + currRun.width); } - - // Fix the bounds. - int first = lastTabInRun(tabCount, i) + 1; - int last = lastTabInRun(tabCount, getNextTabRun(i)); - int currX = tabAreaInsets.left; + + // Fixes the bounds of all tabs in the current + // run. + int first = tabRuns[i]; + int last = lastTabInRun(tabCount, i); + int currX = start; for (int j = first; j <= last; j++) { rects[j].x = currX; @@ -673,7 +885,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants for (int i = 1; i < runCount; i++) { Rectangle currRun = rects[lastTabInRun(tabCount, i)]; - Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))]; + Rectangle nextRun = rects[lastTabInRun(tabCount, + getNextTabRun(i))]; int spaceInCurr = currRun.y + currRun.height; int spaceInNext = nextRun.y + nextRun.height; @@ -692,9 +905,10 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants - (spaceInNext + currRun.height); } - int first = lastTabInRun(tabCount, i) + 1; - int last = lastTabInRun(tabCount, getNextTabRun(i)); - int currY = tabAreaInsets.top; + // Fixes the bounds of tabs in the current run. + int first = tabRuns[i]; + int last = lastTabInRun(tabCount, i); + int currY = start; for (int j = first; j <= last; j++) { rects[j].y = currY; @@ -720,7 +934,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants rects[selectedIndex].height += insets.top + insets.bottom; } - // If the tabs on the run don't fill the width of the window, make it fit now. + // If the tabs on the run don't fill the width of the window, make it + // fit now. // start = starting index of the run // end = last index of the run // max = tabAreaInsets.left + width (or equivalent) @@ -744,7 +959,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int runWidth = rects[end].x + rects[end].width; int spaceRemaining = max - runWidth; int numTabs = end - start + 1; - + // now divvy up the space. int spaceAllocated = spaceRemaining / numTabs; int currX = rects[start].x; @@ -752,11 +967,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { rects[i].x = currX; rects[i].width += spaceAllocated; + currX += rects[i].width; // This is used because since the spaceAllocated // variable is an int, it rounds down. Sometimes, // we don't fill an entire row, so we make it do // so now. + if (i == end && rects[i].x + rects[i].width != max) rects[i].width = max - rects[i].x; } @@ -821,7 +1038,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // The reason why we can't use runCount: // This method is only called to calculate the size request - // for the tabbedPane. However, this size request is dependent on + // for the tabbedPane. However, this size request is dependent on // our desired width. We need to find out what the height would // be IF we got our desired width. for (int i = 0; i < tabPane.getTabCount(); i++) @@ -884,7 +1101,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants runs++; int maxTabWidth = calculateMaxTabWidth(tabPlacement); - int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, maxTabWidth); + int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, + maxTabWidth); return tabAreaWidth; } @@ -898,11 +1116,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void rotateTabRuns(int tabPlacement, int selectedRun) { - if (runCount == 1 || selectedRun == 1 || selectedRun == -1) + if (runCount == 1 || selectedRun == 0 || selectedRun == -1) return; int[] newTabRuns = new int[tabRuns.length]; int currentRun = selectedRun; - int i = 1; + int i = 0; do { newTabRuns[i] = tabRuns[currentRun]; @@ -910,8 +1128,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants i++; } while (i < runCount); - if (runCount > 1) - newTabRuns[0] = tabRuns[currentRun]; tabRuns = newTabRuns; BasicTabbedPaneUI.this.selectedRun = 1; @@ -944,7 +1160,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public Dimension preferredLayoutSize(Container parent) { - return super.calculateSize(true); + return super.calculateSize(false); } /** @@ -1018,29 +1234,27 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants SwingUtilities.calculateInnerArea(tabPane, calcRect); Insets tabAreaInsets = getTabAreaInsets(tabPlacement); Insets insets = tabPane.getInsets(); - int runs = 1; - int start = 0; - int top = 0; if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) { int maxHeight = calculateMaxTabHeight(tabPlacement); calcRect.width -= tabAreaInsets.left + tabAreaInsets.right; - start = tabAreaInsets.left + insets.left; int width = 0; - int runWidth = start; - top = insets.top + tabAreaInsets.top; + int runWidth = tabAreaInsets.left + insets.left; + int top = insets.top + tabAreaInsets.top; for (int i = 0; i < tabCount; i++) { width = calculateTabWidth(tabPlacement, i, fm); - - rects[i] = new Rectangle(runWidth, top, width, maxHeight); + + // The proper instances should exists because + // assureRectsCreated() was being run already. + rects[i].setBounds(runWidth, top, width, maxHeight); + runWidth += width; } tabAreaRect.width = tabPane.getWidth() - insets.left - insets.right; - tabAreaRect.height = runs * maxTabHeight - - (runs - 1) * tabRunOverlay - + tabAreaInsets.top + tabAreaInsets.bottom; + tabAreaRect.height = maxTabHeight + tabAreaInsets.top + + tabAreaInsets.bottom; contentRect.width = tabAreaRect.width; contentRect.height = tabPane.getHeight() - insets.top - insets.bottom - tabAreaRect.height; @@ -1063,23 +1277,25 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants calcRect.height -= tabAreaInsets.top + tabAreaInsets.bottom; int height = 0; - start = tabAreaInsets.top + insets.top; - int runHeight = start; + int runHeight = tabAreaInsets.top + insets.top;; int fontHeight = fm.getHeight(); - top = insets.left + tabAreaInsets.left; + int left = insets.left + tabAreaInsets.left; for (int i = 0; i < tabCount; i++) { height = calculateTabHeight(tabPlacement, i, fontHeight); - rects[i] = new Rectangle(top, runHeight, maxWidth, height); + + // The proper instances should exists because + // assureRectsCreated() was being run already. + rects[i].setBounds(left, runHeight, maxWidth, height); runHeight += height; } - tabAreaRect.width = runs * maxTabWidth - (runs - 1) * tabRunOverlay - + tabAreaInsets.left + tabAreaInsets.right; + tabAreaRect.width = maxTabWidth + tabAreaInsets.left + + tabAreaInsets.right; tabAreaRect.height = tabPane.getHeight() - insets.top - - insets.bottom; + - insets.bottom; tabAreaRect.y = insets.top; contentRect.width = tabPane.getWidth() - insets.left - insets.right - - tabAreaRect.width; + - tabAreaRect.width; contentRect.height = tabAreaRect.height; contentRect.y = insets.top; if (tabPlacement == SwingConstants.LEFT) @@ -1093,11 +1309,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabAreaRect.x = contentRect.x + contentRect.width; } } - runCount = runs; - if (runCount > tabRuns.length) - expandTabRunsArray(); - - padSelectedTab(tabPlacement, tabPane.getSelectedIndex()); + + // Unlike the behavior in the WRAP_TAB_LAYOUT the selected + // tab is not padded specially. } /** @@ -1115,8 +1329,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabCount == 0) return; int tabPlacement = tabPane.getTabPlacement(); - incrButton.setVisible(false); - decrButton.setVisible(false); + if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) { @@ -1126,18 +1339,49 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Dimension incrDims = incrButton.getPreferredSize(); Dimension decrDims = decrButton.getPreferredSize(); - decrButton.setBounds(tabAreaRect.x + tabAreaRect.width - - incrDims.width - decrDims.width, - tabAreaRect.y, decrDims.width, - tabAreaRect.height); - incrButton.setBounds(tabAreaRect.x + tabAreaRect.width - - incrDims.width, tabAreaRect.y, - decrDims.width, tabAreaRect.height); - + if (tabPlacement == SwingConstants.BOTTOM) + { + // Align scroll buttons with the bottom border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width - decrDims.width, + tabAreaRect.y, decrDims.width, + decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, tabAreaRect.y, + incrDims.width, incrDims.height); + } + else + { + // Align scroll buttons with the top border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width - decrDims.width, + tabAreaRect.y + tabAreaRect.height + - decrDims.height, decrDims.width, + decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, + incrDims.width, incrDims.height); + } + tabAreaRect.width -= decrDims.width + incrDims.width; + + updateButtons(); + incrButton.setVisible(true); decrButton.setVisible(true); } + else + { + incrButton.setVisible(false); + decrButton.setVisible(false); + + currentScrollOffset = 0; + currentScrollLocation = 0; + } } if (tabPlacement == SwingConstants.LEFT @@ -1149,34 +1393,54 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Dimension incrDims = incrButton.getPreferredSize(); Dimension decrDims = decrButton.getPreferredSize(); - decrButton.setBounds(tabAreaRect.x, - tabAreaRect.y + tabAreaRect.height - - incrDims.height - decrDims.height, - tabAreaRect.width, decrDims.height); - incrButton.setBounds(tabAreaRect.x, - tabAreaRect.y + tabAreaRect.height - - incrDims.height, tabAreaRect.width, - incrDims.height); + if (tabPlacement == SwingConstants.RIGHT) + { + // Align scroll buttons with the right border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x, + tabAreaRect.y + tabAreaRect.height + - incrDims.height - decrDims.height, + decrDims.width, decrDims.height); + incrButton.setBounds(tabAreaRect.x, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, incrDims.width, + incrDims.height); + } + else + { + // Align scroll buttons with the left border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - decrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height - decrDims.height, + decrDims.width, decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, incrDims.width, + incrDims.height); + } tabAreaRect.height -= decrDims.height + incrDims.height; + incrButton.setVisible(true); decrButton.setVisible(true); } + else + { + incrButton.setVisible(false); + decrButton.setVisible(false); + + currentScrollOffset = 0; + currentScrollLocation = 0; + } } viewport.setBounds(tabAreaRect.x, tabAreaRect.y, tabAreaRect.width, tabAreaRect.height); - int tabC = tabPane.getTabCount() - 1; - if (tabCount > 0) - { - int w = Math.max(rects[tabC].width + rects[tabC].x, tabAreaRect.width); - int h = Math.max(rects[tabC].height, tabAreaRect.height); - p = findPointForIndex(currentScrollLocation); - - // we want to cover that entire space so that borders that run under - // the tab area don't show up when we move the viewport around. - panel.setSize(w + p.x, h + p.y); - } - viewport.setViewPosition(p); + + updateViewPosition(); + viewport.repaint(); } } @@ -1200,7 +1464,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { selectedRun = getRunForTab(tabPane.getTabCount(), tabPane.getSelectedIndex()); - tabPane.revalidate(); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) + tabPane.revalidate(); tabPane.repaint(); } } @@ -1226,7 +1492,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void paint(Graphics g, JComponent c) { - paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); + int placement = tabPane.getTabPlacement(); + g.setColor(highlight); + if (placement == SwingUtilities.TOP + || placement == SwingUtilities.BOTTOM) + g.fillRect(currentScrollOffset, 0, + tabAreaRect.width, tabAreaRect.height); + else + g.fillRect(0, currentScrollOffset, + tabAreaRect.width, tabAreaRect.height); + + paintTabArea(g, placement, tabPane.getSelectedIndex()); } } @@ -1287,6 +1563,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants /** The starting visible tab in the run in SCROLL_TAB_MODE. * This is package-private to avoid an accessor method. */ transient int currentScrollLocation; + + transient int currentScrollOffset; /** A reusable rectangle. */ protected Rectangle calcRect; @@ -1342,16 +1620,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants /** The gap between text and label */ protected int textIconGap; - // Keeps track of tab runs. - // The organization of this array is as follows (lots of experimentation to - // figure this out) - // index 0 = furthest away from the component area (aka outer run) - // index 1 = closest to component area (aka selected run) - // index > 1 = listed in order leading from selected run to outer run. - // each int in the array is the tab index + 1 (counting starts at 1) - // for the last tab in the run. (same as the rects array) - - /** This array keeps track of which tabs are in which run. See above. */ + /** This array keeps track of which tabs are in which run. + * <p>The value at index i denotes the index of the first tab in run i.</p> + * <p>If the value for any index (i > 0) is 0 then (i - 1) is the last + * run.</p> + */ protected int[] tabRuns; /** @@ -1430,7 +1703,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * The currently visible component. */ private Component visibleComponent; - + + private Color selectedColor; + + private Rectangle tempTextRect = new Rectangle(); + + private Rectangle tempIconRect = new Rectangle(); + /** * Creates a new BasicTabbedPaneUI object. */ @@ -1519,8 +1798,115 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Point p = new Point(w, h); return p; } + + /** TabbedPanes in scrolling mode should use this method to + * scroll properly to the tab given by the index argument. + * + * @param index The tab to scroll to. + * @param placement The tab's placement. + */ + final void scrollTab(int index, int placement) + { + int diff; + if (index >= 0 && tabPane.isEnabledAt(index)) + { + // If the user clicked on the last tab and that one was + // only partially visible shift the scroll offset to make + // it completely visible. + switch (placement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + if ((diff = rects[index].x + + rects[index].width + - decrButton.getX() - currentScrollOffset) > 0) + currentScrollOffset += diff; + else if ((diff = rects[index].x - currentScrollOffset) < 0) + { + if (index == 0) + currentScrollOffset = 0; + else + currentScrollOffset += diff; + } + + currentScrollLocation = tabForCoordinate(tabPane, + currentScrollOffset, + rects[index].y); + break; + default: + if ((diff = rects[index].y + rects[index].height + - decrButton.getY() - currentScrollOffset) > 0) + currentScrollOffset += diff; + else if ((diff = rects[index].y - currentScrollOffset) < 0) + { + if (index == 0) + currentScrollOffset = 0; + else + currentScrollOffset += diff; + } + + currentScrollLocation = tabForCoordinate(tabPane, + rects[index].x, + currentScrollOffset); + } + + updateViewPosition(); + updateButtons(); + } + } + + /** Sets the enabled state of the increase and decrease button + * according to the current scrolling offset and tab pane width + * (or height in TOP/BOTTOM placement). + */ + final void updateButtons() + { + int tc = tabPane.getTabCount(); + + // The increase button should be enabled as long as the + // right/bottom border of the last tab is under the left/top + // border of the decrease button. + switch (tabPane.getTabPlacement()) + { + case JTabbedPane.BOTTOM: + case JTabbedPane.TOP: + incrButton.setEnabled(currentScrollLocation + 1 < tc + && rects[tc-1].x + rects[tc-1].width + - currentScrollOffset > decrButton.getX()); + break; + default: + incrButton.setEnabled(currentScrollLocation + 1 < tc + && rects[tc-1].y + rects[tc-1].height + - currentScrollOffset > decrButton.getY()); + } + + // The decrease button is enabled when the tab pane is scrolled in any way. + decrButton.setEnabled(currentScrollOffset > 0); + + } /** + * Updates the position of the scrolling viewport's view + * according to the current scroll offset. + */ + final void updateViewPosition() + { + Point p = viewport.getViewPosition(); + + switch (tabPane.getTabPlacement()) + { + case JTabbedPane.LEFT: + case JTabbedPane.RIGHT: + p.y = currentScrollOffset; + break; + default: + p.x = currentScrollOffset; + } + + viewport.setViewPosition(p); + } + + /** * This method creates a new BasicTabbedPaneUI. * * @param c The JComponent to create a UI for. @@ -1585,22 +1971,30 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants return new TabbedPaneLayout(); else { + runCount = 1; + tabRuns[0] = 0; + incrButton = createIncreaseButton(); + incrButton.addMouseListener(mouseListener); + decrButton = createDecreaseButton(); - viewport = new ScrollingViewport(); - viewport.setLayout(null); + decrButton.addMouseListener(mouseListener); + decrButton.setEnabled(false); + panel = new ScrollingPanel(); + panel.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + panel.addMouseListener(mouseListener); + panel.addFocusListener(focusListener); + + viewport = new ScrollingViewport(); + viewport.setBackground(Color.LIGHT_GRAY); viewport.setView(panel); + viewport.setLayout(null); + tabPane.add(incrButton); tabPane.add(decrButton); tabPane.add(viewport); - currentScrollLocation = 0; - decrButton.setEnabled(false); - panel.addMouseListener(mouseListener); - incrButton.addMouseListener(mouseListener); - decrButton.addMouseListener(mouseListener); - viewport.setBackground(Color.LIGHT_GRAY); - + return new TabbedPaneScrollLayout(); } } @@ -1618,7 +2012,14 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void uninstallComponents() { - // Nothing to be done. + if (incrButton != null) + tabPane.remove(incrButton); + + if (decrButton != null) + tabPane.remove(decrButton); + + if (viewport != null) + tabPane.remove(viewport); } /** @@ -1631,8 +2032,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants "TabbedPane.font"); tabPane.setOpaque(false); - highlight = UIManager.getColor("TabbedPane.highlight"); - lightHighlight = UIManager.getColor("TabbedPane.lightHighlight"); + lightHighlight = UIManager.getColor("TabbedPane.highlight"); + highlight = UIManager.getColor("TabbedPane.light"); shadow = UIManager.getColor("TabbedPane.shadow"); darkShadow = UIManager.getColor("TabbedPane.darkShadow"); @@ -1643,10 +2044,18 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay"); tabInsets = UIManager.getInsets("TabbedPane.tabInsets"); - selectedTabPadInsets = UIManager.getInsets("TabbedPane.tabbedPaneTabPadInsets"); + selectedTabPadInsets + = UIManager.getInsets("TabbedPane.selectedTabPadInsets"); tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets"); - contentBorderInsets = UIManager.getInsets("TabbedPane.tabbedPaneContentBorderInsets"); + contentBorderInsets + = UIManager.getInsets("TabbedPane.contentBorderInsets"); tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque"); + + // Although 'TabbedPane.contentAreaColor' is not defined in the defaults + // of BasicLookAndFeel it is used by this class. + selectedColor = UIManager.getColor("TabbedPane.contentAreaColor"); + if (selectedColor == null) + selectedColor = UIManager.getColor("control"); calcRect = new Rectangle(); tabRuns = new int[10]; @@ -1663,6 +2072,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabAreaRect = null; contentRect = null; tabRuns = null; + + tempIconRect = null; + tempTextRect = null; contentBorderInsets = null; tabAreaInsets = null; @@ -1674,11 +2086,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants shadow = null; lightHighlight = null; highlight = null; - - // Install UI colors and fonts. - LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background", - "TabbedPane.foreground", - "TabbedPane.font"); + + selectedColor = null; } /** @@ -1706,6 +2115,18 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabPane.removePropertyChangeListener(propertyChangeListener); tabPane.removeChangeListener(tabChangeListener); tabPane.removeMouseListener(mouseListener); + + if (incrButton != null) + incrButton.removeMouseListener(mouseListener); + + if (decrButton != null) + decrButton.removeMouseListener(mouseListener); + + if (panel != null) + { + panel.removeMouseListener(mouseListener); + panel.removeFocusListener(focusListener); + } focusListener = null; propertyChangeListener = null; @@ -1757,18 +2178,31 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * This method installs keyboard actions for the JTabbedPane. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: Implement. + InputMap keyMap = (InputMap) UIManager.get("TabbedPane.focusInputMap"); + SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, keyMap); + + keyMap = (InputMap) UIManager.get("TabbedPane.ancestorInputMap"); + SwingUtilities + .replaceUIInputMap(tabPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + keyMap); + + ActionMap map = getActionMap(); + SwingUtilities.replaceUIActionMap(tabPane, map); } /** * This method uninstalls keyboard actions for the JTabbedPane. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: Implement. + SwingUtilities.replaceUIActionMap(tabPane, null); + SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, null); + SwingUtilities + .replaceUIInputMap(tabPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + null); } /** @@ -1808,9 +2242,25 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabPane.getTabCount() == 0) return; + + int index = tabPane.getSelectedIndex(); + if (index < 0) + index = 0; + + int tabPlacement = tabPane.getTabPlacement(); + + // Paint the tab area only in WRAP_TAB_LAYOUT Mode from this method + // because it is done through the ScrollingViewport.paint() method + // for the SCROLL_TAB_LAYOUT mode. if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) - paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); - paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); + { + g.setColor(highlight); + g.fillRect(tabAreaRect.x, tabAreaRect.y, + tabAreaRect.width, tabAreaRect.height); + paintTabArea(g, tabPlacement, index); + } + + paintContentBorder(g, tabPlacement, index); } /** @@ -1823,14 +2273,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) { - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - - boolean isScroll = tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; - // Please note: the ordering of the painting is important. // we WANT to paint the outermost run first and then work our way in. + + // The following drawing code works for both tab layouts. int tabCount = tabPane.getTabCount(); + for (int i = runCount - 1; i >= 0; --i) { int start = tabRuns[i]; @@ -1844,14 +2292,16 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { if (j != selectedIndex) { - paintTab(g, tabPlacement, rects, j, ir, tr); + paintTab(g, tabPlacement, rects, j, + tempIconRect, tempTextRect); } } } - + // Paint selected tab in front of every other tab. if (selectedIndex >= 0) - paintTab(g, tabPlacement, rects, selectedIndex, ir, tr); + paintTab(g, tabPlacement, rects, selectedIndex, + tempIconRect, tempTextRect); } /** @@ -1891,8 +2341,10 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // Paint the text. paintText(g, tabPlacement, tabPane.getFont(), fm, tabIndex, title, textRect, isSelected); + // Paint icon if necessary. paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); + // Paint focus indicator. paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect, textRect, isSelected); @@ -2032,8 +2484,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) { - // No reason to shift. - return 0; + switch (tabPlacement) + { + default: + case SwingUtilities.TOP: + case SwingUtilities.BOTTOM: + return 1; + case SwingUtilities.LEFT: + return (isSelected) ? -1 : 1; + case SwingUtilities.RIGHT: + return (isSelected) ? 1 : -1; + } } /** @@ -2049,8 +2510,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) { - // No reason to shift. - return 0; + switch (tabPlacement) + { + default: + case SwingUtilities.TOP: + return (isSelected) ? -1 : 1; + case SwingUtilities.BOTTOM: + return (isSelected) ? 1 : -1; + case SwingUtilities.LEFT: + case SwingUtilities.RIGHT: + return 0; + } } /** @@ -2080,32 +2550,33 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants g.setColor(focus); switch (tabPlacement) - { - case LEFT: - x = rect.x + 3; - y = rect.y + 3; - w = rect.width - 5; - h = rect.height - 6; - break; - case RIGHT: - x = rect.x + 2; - y = rect.y + 3; - w = rect.width - 6; - h = rect.height - 5; - break; - case BOTTOM: - x = rect.x + 3; - y = rect.y + 2; - w = rect.width - 6; - h = rect.height - 5; - break; - case TOP: - default: - x = rect.x + 3; - y = rect.y + 3; - w = rect.width - 6; - h = rect.height - 5; - } + { + case LEFT: + x = rect.x + 3; + y = rect.y + 3; + w = rect.width - 5; + h = rect.height - 6; + break; + case RIGHT: + x = rect.x + 2; + y = rect.y + 3; + w = rect.width - 6; + h = rect.height - 5; + break; + case BOTTOM: + x = rect.x + 3; + y = rect.y + 2; + w = rect.width - 6; + h = rect.height - 5; + break; + case TOP: + default: + x = rect.x + 3; + y = rect.y + 3; + w = rect.width - 6; + h = rect.height - 5; + } + BasicGraphicsUtils.drawDashedRect(g, x, y, w, h); } } @@ -2127,34 +2598,109 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Color saved = g.getColor(); - if (! isSelected || tabPlacement != SwingConstants.TOP) - { + switch (tabPlacement) + { + case SwingConstants.TOP: g.setColor(shadow); - g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1); - g.setColor(darkShadow); - g.drawLine(x, y + h, x + w, y + h); - } + // Inner right line. + g.drawLine(x + w - 2, y + 2, x + w - 2, y + h); - if (! isSelected || tabPlacement != SwingConstants.LEFT) - { g.setColor(darkShadow); - g.drawLine(x + w, y, x + w, y + h); + // Outer right line. + g.drawLine(x + w - 1, y + 2, x + w - 1, y + h); + + // Upper right corner. + g.drawLine(x + w - 2, y + 1, x + w - 1, y + 2); + + g.setColor(lightHighlight); + + // Left line. + g.drawLine(x, y + 3, x, y + h); + + // Upper line. + g.drawLine(x + 3, y, x + w - 3, y); + + // Upper left corner. + g.drawLine(x, y + 2, x + 2, y); + + break; + case SwingConstants.LEFT: + g.setColor(lightHighlight); + // Top line. + g.drawLine(x + 3, y, x + w - 1, y); + + // Top left border. + g.drawLine(x + 2, y, x, y + 2); + + // Left line. + g.drawLine(x, y + 3, x, y + h - 4); + + // Bottom left corner. + g.drawLine(x, y + h - 3, x + 1, y + h - 2); + + g.setColor(darkShadow); + // Outer bottom line. + g.drawLine(x + 2, y + h - 1, x + w - 1, y + h - 1); + g.setColor(shadow); - g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1); - } + // Inner bottom line. + g.drawLine(x + 2, y + h - 2, x + w - 1, y + h - 2); + + break; + case SwingConstants.BOTTOM: + g.setColor(shadow); + // Inner right line. + g.drawLine(x + w - 2, y, x + w - 2, y + h - 2); - if (! isSelected || tabPlacement != SwingConstants.RIGHT) - { - g.setColor(lightHighlight); - g.drawLine(x, y, x, y + h); - } + // Inner bottom line. + g.drawLine(x + 2, y + h - 1, x + w - 3, y + h - 1); - if (! isSelected || tabPlacement != SwingConstants.BOTTOM) - { + g.setColor(darkShadow); + // Outer right line. + g.drawLine(x + w - 1, y, x + w - 1, y + h - 3); + + // Bottom right corner. + g.drawLine(x + w - 1, y + h - 2, x + w - 3, y + h); + + // Bottom line. + g.drawLine(x + 2, y + h, x + w - 4, y + h); + g.setColor(lightHighlight); - g.drawLine(x, y, x + w, y); - } - + // Left line. + g.drawLine(x, y, x, y + h - 3); + + // Bottom left corner. + g.drawLine(x, y + h - 2, x + 1, y + h - 1); + break; + case SwingConstants.RIGHT: + g.setColor(lightHighlight); + // Top line. + g.drawLine(x, y, x + w - 3, y); + + g.setColor(darkShadow); + // Top right corner. + g.drawLine(x + w - 2, y + 1, x + w - 1, y + 2); + + // Outer right line. + g.drawLine(x + w - 1, y + 3, x + w - 1, y + h - 3); + + // Bottom right corner. + g.drawLine(x + w - 2, y + h - 2, x + w - 3, y + h - 1); + + // Bottom line. + g.drawLine(x, y + h - 1, x + w - 4, y + h - 1); + + g.setColor(shadow); + + // Inner right line. + g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 3); + + // Inner bottom line. + g.drawLine(x, y + h - 2, x + w - 3, y + h - 2); + + break; + } + g.setColor(saved); } @@ -2175,17 +2721,32 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants boolean isSelected) { Color saved = g.getColor(); + if (isSelected) - g.setColor(Color.LIGHT_GRAY); + g.setColor(selectedColor); else { Color bg = tabPane.getBackgroundAt(tabIndex); if (bg == null) - bg = Color.GRAY; + bg = Color.LIGHT_GRAY; g.setColor(bg); } - g.fillRect(x, y, w, h); + switch (tabPlacement) + { + case SwingConstants.TOP: + g.fillRect(x + 1, y + 1, w - 1, h - 1); + break; + case SwingConstants.BOTTOM: + g.fillRect(x, y, w - 1, h - 1); + break; + case SwingConstants.LEFT: + g.fillRect(x + 1, y + 1, w - 1, h - 2); + break; + case SwingConstants.RIGHT: + g.fillRect(x, y + 1, w - 1, h - 2); + break; + } g.setColor(saved); } @@ -2262,25 +2823,27 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Color saved = g.getColor(); g.setColor(lightHighlight); - int startgap = rects[selectedIndex].x; - int endgap = rects[selectedIndex].x + rects[selectedIndex].width; - - int diff = 0; + int startgap = rects[selectedIndex].x - currentScrollOffset; + int endgap = rects[selectedIndex].x + rects[selectedIndex].width + - currentScrollOffset; - if (tabPlacement == SwingConstants.TOP) + // Paint the highlight line with a gap if the tabs are at the top + // and the selected tab is inside the visible area. + if (tabPlacement == SwingConstants.TOP && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.x; - } - - g.drawLine(x, y, startgap - diff, y); - g.drawLine(endgap - diff, y, x + w, y); + g.drawLine(x, y, startgap, y); + g.drawLine(endgap, y, x + w - 1, y); + + g.setColor(selectedColor); + g.drawLine(startgap, y, endgap - 1, y); } else g.drawLine(x, y, x + w, y); - + + g.setColor(selectedColor); + g.drawLine(x, y + 1, x + w - 1, y + 1); + g.drawLine(x, y + 2, x + w - 1, y + 2); + g.setColor(saved); } @@ -2302,24 +2865,25 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Color saved = g.getColor(); g.setColor(lightHighlight); - int startgap = rects[selectedIndex].y; - int endgap = rects[selectedIndex].y + rects[selectedIndex].height; + int startgap = rects[selectedIndex].y - currentScrollOffset; + int endgap = rects[selectedIndex].y + rects[selectedIndex].height + - currentScrollOffset; int diff = 0; - if (tabPlacement == SwingConstants.LEFT) + if (tabPlacement == SwingConstants.LEFT && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.y; - } - - g.drawLine(x, y, x, startgap - diff); - g.drawLine(x, endgap - diff, x, y + h); + g.drawLine(x, y, x, startgap); + g.drawLine(x, endgap, x, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(x, startgap, x, endgap - 1); } else - g.drawLine(x, y, x, y + h); + g.drawLine(x, y, x, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(x + 1, y + 1, x + 1, y + h - 4); g.setColor(saved); } @@ -2341,34 +2905,34 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Color saved = g.getColor(); - int startgap = rects[selectedIndex].x; - int endgap = rects[selectedIndex].x + rects[selectedIndex].width; - - int diff = 0; + int startgap = rects[selectedIndex].x - currentScrollOffset; + int endgap = rects[selectedIndex].x + rects[selectedIndex].width + - currentScrollOffset; - if (tabPlacement == SwingConstants.BOTTOM) + if (tabPlacement == SwingConstants.BOTTOM && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.x; - } - g.setColor(shadow); - g.drawLine(x + 1, y + h - 1, startgap - diff, y + h - 1); - g.drawLine(endgap - diff, y + h - 1, x + w - 1, y + h - 1); + g.drawLine(x + 1, y + h - 2, startgap, y + h - 2); + g.drawLine(endgap, y + h - 2, x + w - 2, y + h - 2); g.setColor(darkShadow); - g.drawLine(x, y + h, startgap - diff, y + h); - g.drawLine(endgap - diff, y + h, x + w, y + h); + g.drawLine(x, y + h - 1, startgap , y + h - 1); + g.drawLine(endgap, y + h - 1, x + w - 1, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(startgap, y + h - 1, endgap - 1, y + h - 1); + g.drawLine(startgap, y + h - 2, endgap - 1, y + h - 2); } else { g.setColor(shadow); - g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1); + g.drawLine(x + 1, y + h - 2, x + w - 1, y + h - 2); g.setColor(darkShadow); - g.drawLine(x, y + h, x + w, y + h); + g.drawLine(x, y + h - 1, x + w - 1, y + h - 1); } + + g.setColor(selectedColor); + g.drawLine(x + 1, y + h - 3, x + w - 2, y + h - 3); g.setColor(saved); } @@ -2389,34 +2953,36 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int w, int h) { Color saved = g.getColor(); - int startgap = rects[selectedIndex].y; - int endgap = rects[selectedIndex].y + rects[selectedIndex].height; + int startgap = rects[selectedIndex].y - currentScrollOffset; + int endgap = rects[selectedIndex].y + rects[selectedIndex].height + - currentScrollOffset; int diff = 0; - if (tabPlacement == SwingConstants.RIGHT) + if (tabPlacement == SwingConstants.RIGHT && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.y; - } - g.setColor(shadow); - g.drawLine(x + w - 1, y + 1, x + w - 1, startgap - diff); - g.drawLine(x + w - 1, endgap - diff, x + w - 1, y + h - 1); + g.drawLine(x + w - 2, y + 1, x + w - 2, startgap); + g.drawLine(x + w - 2, endgap, x + w - 2, y + h - 2); g.setColor(darkShadow); - g.drawLine(x + w, y, x + w, startgap - diff); - g.drawLine(x + w, endgap - diff, x + w, y + h); + g.drawLine(x + w - 1, y, x + w - 1, startgap); + g.drawLine(x + w - 1, endgap, x + w - 1, y + h - 2); + + g.setColor(selectedColor); + g.drawLine(x + w - 2, startgap, x + w - 2, endgap - 1); + g.drawLine(x + w - 1, startgap, x + w - 1, endgap - 1); } else { g.setColor(shadow); - g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1); + g.drawLine(x + w - 2, y + 1, x + w - 2, y + h - 2); g.setColor(darkShadow); - g.drawLine(x + w, y, x + w, y + h); + g.drawLine(x + w - 1, y, x + w - 1, y + h - 2); } + + g.setColor(selectedColor); + g.drawLine(x + w - 3, y + 1, x + w - 3, y + h - 4); g.setColor(saved); } @@ -2460,11 +3026,15 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public int tabForCoordinate(JTabbedPane pane, int x, int y) { + // Note: This code is tab layout mode agnostic. if (! tabPane.isValid()) tabPane.validate(); - + int tabCount = tabPane.getTabCount(); - int index = -1; + + // If the user clicked outside of any tab rect the + // selection should not change. + int index = tabPane.getSelectedIndex(); for (int i = 0; i < tabCount; ++i) { if (rects[i].contains(x, y)) @@ -2474,8 +3044,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants } } - // FIXME: Handle scrollable tab layout. - return index; } @@ -2571,7 +3139,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int getRunForTab(int tabCount, int tabIndex) { if (runCount == 1 && tabIndex < tabCount && tabIndex >= 0) - return 1; + return 0; for (int i = 0; i < runCount; i++) { int first = lastTabInRun(tabCount, getPreviousTabRun(i)) + 1; @@ -2690,6 +3258,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected View getTextViewForTab(int tabIndex) { + // FIXME: When the label contains HTML this should return something + // non-null. return null; } @@ -2706,7 +3276,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) { - // FIXME: Handle HTML somehow. + // FIXME: Handle HTML by using the view (see getTextViewForTab). int height = fontHeight; Icon icon = getIconForTab(tabIndex); @@ -2923,8 +3493,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(), tabPane.getSelectedIndex(), - (tabPlacement == SwingConstants.RIGHT) - ? true : false); + (tabPlacement == SwingConstants.TOP) + ? direction == SwingConstants.NORTH + : direction == SwingConstants.SOUTH); selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(), offset); } @@ -2940,8 +3511,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(), tabPane.getSelectedIndex(), - (tabPlacement == SwingConstants.RIGHT) - ? true : false); + (tabPlacement == SwingConstants.LEFT) + ? direction == SwingConstants.WEST + : direction == SwingConstants.EAST); selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(), offset); } @@ -2955,8 +3527,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectNextTabInRun(int current) { - tabPane.setSelectedIndex(getNextTabIndexInRun(tabPane.getTabCount(), - current)); + current = getNextTabIndexInRun(tabPane.getTabCount(), + current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -2966,8 +3543,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectPreviousTabInRun(int current) { - tabPane.setSelectedIndex(getPreviousTabIndexInRun(tabPane.getTabCount(), - current)); + current = getPreviousTabIndexInRun(tabPane.getTabCount(), + current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -2977,7 +3559,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectNextTab(int current) { - tabPane.setSelectedIndex(getNextTabIndex(current)); + current = getNextTabIndex(current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -2987,7 +3574,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectPreviousTab(int current) { - tabPane.setSelectedIndex(getPreviousTabIndex(current)); + current = getPreviousTabIndex(current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -3021,7 +3613,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int index = tabForCoordinate(tabPane, x, y); if (index != -1) - tabPane.setSelectedIndex(index); + { + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(index, tabPlacement); + tabPane.setSelectedIndex(index); + } } // This method is called when you press up/down to cycle through tab runs. @@ -3058,6 +3654,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants else offset = rects[lastTabInRun(tabCount, nextRun)].x - rects[lastTabInRun(tabCount, currRun)].x; + return offset; } @@ -3104,9 +3701,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { int index = getNextTabIndex(base); int run = getRunForTab(tabCount, base); - if (index == lastTabInRun(tabCount, run) + 1) - index = lastTabInRun(tabCount, getPreviousTabRun(run)) + 1; - return getNextTabIndex(base); + if (base == lastTabInRun(tabCount, run)) + index = (run > 0) + ? lastTabInRun(tabCount, getPreviousTabRun(run)) + 1 + : 0; + + return index; } /** @@ -3124,7 +3724,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int run = getRunForTab(tabCount, base); if (index == lastTabInRun(tabCount, getPreviousTabRun(run))) index = lastTabInRun(tabCount, run); - return getPreviousTabIndex(base); + + return index; } /** @@ -3182,6 +3783,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // so I won't check it either. switch (targetPlacement) { + default: case SwingConstants.TOP: targetInsets.top = topInsets.top; targetInsets.left = topInsets.left; @@ -3208,6 +3810,44 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants break; } } + + ActionMap getActionMap() + { + ActionMap map = (ActionMap) UIManager.get("TabbedPane.actionMap"); + + if (map == null) // first time here + { + map = createActionMap(); + if (map != null) + UIManager.put("TabbedPane.actionMap", map); + } + return map; + } + + ActionMap createActionMap() + { + ActionMap map = new ActionMapUIResource(); + + map.put("navigatePageDown", new NavigatePageDownAction()); + map.put("navigatePageUp", new NavigatePageUpAction()); + map.put("navigateDown", + new NavigateAction("navigateDown", SwingConstants.SOUTH)); + + map.put("navigateUp", + new NavigateAction("navigateUp", SwingConstants.NORTH)); + + map.put("navigateLeft", + new NavigateAction("navigateLeft", SwingConstants.WEST)); + + map.put("navigateRight", + new NavigateAction("navigateRight", SwingConstants.EAST)); + + map.put("requestFocusForVisibleComponent", + new RequestFocusForVisibleComponentAction()); + map.put("requestFocus", new RequestFocusAction()); + + return map; + } /** * Sets the tab which should be highlighted when in rollover mode. And diff --git a/javax/swing/plaf/basic/BasicTableUI.java b/javax/swing/plaf/basic/BasicTableUI.java index 85c6b574d..15be4d57e 100644 --- a/javax/swing/plaf/basic/BasicTableUI.java +++ b/javax/swing/plaf/basic/BasicTableUI.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Color; import java.awt.Component; import java.awt.ComponentOrientation; @@ -48,7 +46,6 @@ import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; @@ -58,6 +55,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.CellRendererPane; import javax.swing.DefaultCellEditor; @@ -65,16 +63,16 @@ import javax.swing.DefaultListSelectionModel; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JTable; -import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.ChangeEvent; import javax.swing.event.MouseInputListener; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; -import javax.swing.plaf.InputMapUIResource; import javax.swing.plaf.TableUI; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; @@ -164,14 +162,37 @@ public class BasicTableUI extends TableUI public class FocusHandler implements FocusListener { - public void focusGained(FocusEvent e) throws NotImplementedException + public void focusGained(FocusEvent e) { - // TODO: Implement this properly. + // The only thing that is affected by a focus change seems to be + // how the lead cell is painted. So we repaint this cell. + repaintLeadCell(); } - public void focusLost(FocusEvent e) throws NotImplementedException + public void focusLost(FocusEvent e) + { + // The only thing that is affected by a focus change seems to be + // how the lead cell is painted. So we repaint this cell. + repaintLeadCell(); + } + + /** + * Repaints the lead cell in response to a focus change, to refresh + * the display of the focus indicator. + */ + private void repaintLeadCell() { - // TODO: Implement this properly. + int rowCount = table.getRowCount(); + int columnCount = table.getColumnCount(); + int rowLead = table.getSelectionModel().getLeadSelectionIndex(); + int columnLead = table.getColumnModel().getSelectionModel(). + getLeadSelectionIndex(); + if (rowLead >= 0 && rowLead < rowCount && columnLead >= 0 + && columnLead < columnCount) + { + Rectangle dirtyRect = table.getCellRect(rowLead, columnLead, false); + table.repaint(dirtyRect); + } } } @@ -242,20 +263,19 @@ public class BasicTableUI extends TableUI } } - public void mouseEntered(MouseEvent e) - throws NotImplementedException + public void mouseEntered(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } - public void mouseExited(MouseEvent e) throws NotImplementedException + public void mouseExited(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } - public void mouseMoved(MouseEvent e) throws NotImplementedException + public void mouseMoved(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } public void mousePressed(MouseEvent e) @@ -288,6 +308,9 @@ public class BasicTableUI extends TableUI colLead != colModel.getLeadSelectionIndex()) if (table.isEditing()) table.editingStopped(new ChangeEvent(e)); + + // Must request focus explicitly. + table.requestFocusInWindow(); } } @@ -457,66 +480,100 @@ public class BasicTableUI extends TableUI table.setOpaque(true); } + /** + * Installs keyboard actions on the table. + */ protected void installKeyboardActions() { - InputMap ancestorMap = (InputMap) UIManager.get("Table.ancestorInputMap"); - InputMapUIResource parentInputMap = new InputMapUIResource(); - // FIXME: The JDK uses a LazyActionMap for parentActionMap - ActionMap parentActionMap = new ActionMapUIResource(); - action = new TableAction(); - Object keys[] = ancestorMap.allKeys(); - // Register key bindings in the UI InputMap-ActionMap pair - for (int i = 0; i < keys.length; i++) - { - KeyStroke stroke = (KeyStroke) keys[i]; - String actionString = (String) ancestorMap.get(stroke); + // Install the input map. + InputMap inputMap = + (InputMap) SharedUIDefaults.get("Table.ancestorInputMap"); + SwingUtilities.replaceUIInputMap(table, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + inputMap); - parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(), - stroke.getModifiers()), - actionString); - - parentActionMap.put(actionString, - new ActionListenerProxy(action, actionString)); + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(table, getActionMap()); - } - // Set the UI InputMap-ActionMap pair to be the parents of the - // JTable's InputMap-ActionMap pair - parentInputMap.setParent(table.getInputMap( - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent()); - parentActionMap.setParent(table.getActionMap().getParent()); - table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT). - setParent(parentInputMap); - table.getActionMap().setParent(parentActionMap); } /** - * This class is used to mimmic the behaviour of the JDK when registering - * keyboard actions. It is the same as the private class used in JComponent - * for the same reason. This class receives an action event and dispatches - * it to the true receiver after altering the actionCommand property of the - * event. + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map */ - private static class ActionListenerProxy - extends AbstractAction + private ActionMap getActionMap() { - ActionListener target; - String bindingCommandName; - - public ActionListenerProxy(ActionListener li, - String cmd) - { - target = li; - bindingCommandName = cmd; - } + ActionMap am = (ActionMap) UIManager.get("Table.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("Table.actionMap", am); + } + return am; + } - public void actionPerformed(ActionEvent e) - { - ActionEvent derivedEvent = new ActionEvent(e.getSource(), - e.getID(), - bindingCommandName, - e.getModifiers()); - target.actionPerformed(derivedEvent); - } + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new TableAction(); + + am.put("cut", TransferHandler.getCutAction()); + am.put("copy", TransferHandler.getCopyAction()); + am.put("paste", TransferHandler.getPasteAction()); + + am.put("cancel", action); + am.put("selectAll", action); + am.put("clearSelection", action); + am.put("startEditing", action); + + am.put("selectNextRow", action); + am.put("selectNextRowCell", action); + am.put("selectNextRowExtendSelection", action); + am.put("selectNextRowChangeLead", action); + + am.put("selectPreviousRow", action); + am.put("selectPreviousRowCell", action); + am.put("selectPreviousRowExtendSelection", action); + am.put("selectPreviousRowChangeLead", action); + + am.put("selectNextColumn", action); + am.put("selectNextColumnCell", action); + am.put("selectNextColumnExtendSelection", action); + am.put("selectNextColumnChangeLead", action); + + am.put("selectPreviousColumn", action); + am.put("selectPreviousColumnCell", action); + am.put("selectPreviousColumnExtendSelection", action); + am.put("selectPreviousColumnChangeLead", action); + + am.put("scrollLeftChangeSelection", action); + am.put("scrollLeftExtendSelection", action); + am.put("scrollRightChangeSelection", action); + am.put("scrollRightExtendSelection", action); + + am.put("scrollUpChangeSelection", action); + am.put("scrollUpExtendSelection", action); + am.put("scrollDownChangeSelection", action); + am.put("scrolldownExtendSelection", action); + + am.put("selectFirstColumn", action); + am.put("selectFirstColumnExtendSelection", action); + am.put("selectLastColumn", action); + am.put("selectLastColumnExtendSelection", action); + + am.put("selectFirstRow", action); + am.put("selectFirstRowExtendSelection", action); + am.put("selectLastRow", action); + am.put("selectLastRowExtendSelection", action); + + am.put("addToSelection", action); + am.put("toggleAndAnchor", action); + am.put("extendTo", action); + am.put("moveSelectionTo", action); + + return am; } /** @@ -525,7 +582,8 @@ public class BasicTableUI extends TableUI * method is called when a key that has been registered for the JTable * is received. */ - class TableAction extends AbstractAction + private static class TableAction + extends AbstractAction { /** * What to do when this action is called. @@ -534,6 +592,8 @@ public class BasicTableUI extends TableUI */ public void actionPerformed(ActionEvent e) { + JTable table = (JTable) e.getSource(); + DefaultListSelectionModel rowModel = (DefaultListSelectionModel) table.getSelectionModel(); DefaultListSelectionModel colModel @@ -544,9 +604,11 @@ public class BasicTableUI extends TableUI int colLead = colModel.getLeadSelectionIndex(); int colMax = table.getModel().getColumnCount() - 1; - - String command = e.getActionCommand(); - + + // The command with which the action has been called is stored + // in this undocumented action value. This allows us to have only + // one Action instance to serve all keyboard input for JTable. + String command = (String) getValue("__command__"); if (command.equals("selectPreviousRowExtendSelection")) { rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0)); @@ -604,11 +666,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollUpExtendSelection")) { int target; - if (rowLead == getFirstVisibleRowIndex()) - target = Math.max(0, rowLead - (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getFirstVisibleRowIndex(table)) + target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getFirstVisibleRowIndex(); + target = getFirstVisibleRowIndex(table); rowModel.setLeadSelectionIndex(target); colModel.setLeadSelectionIndex(colLead); @@ -621,11 +683,12 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollRightChangeSelection")) { int target; - if (colLead == getLastVisibleColumnIndex()) - target = Math.min(colMax, colLead + (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getLastVisibleColumnIndex(table)) + target = Math.min(colMax, colLead + + (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getLastVisibleColumnIndex(); + target = getLastVisibleColumnIndex(table); colModel.setSelectionInterval(target, target); rowModel.setSelectionInterval(rowLead, rowLead); @@ -638,11 +701,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollLeftChangeSelection")) { int target; - if (colLead == getFirstVisibleColumnIndex()) - target = Math.max(0, colLead - (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getFirstVisibleColumnIndex(table)) + target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getFirstVisibleColumnIndex(); + target = getFirstVisibleColumnIndex(table); colModel.setSelectionInterval(target, target); rowModel.setSelectionInterval(rowLead, rowLead); @@ -724,14 +787,18 @@ public class BasicTableUI extends TableUI // If there are multiple rows and columns selected, select the next // cell and wrap at the edges of the selection. if (command.indexOf("Column") != -1) - advanceMultipleSelection(colModel, colMinSelected, colMaxSelected, - rowModel, rowMinSelected, rowMaxSelected, - command.equals("selectPreviousColumnCell"), true); + advanceMultipleSelection(table, colModel, colMinSelected, + colMaxSelected, rowModel, rowMinSelected, + rowMaxSelected, + command.equals("selectPreviousColumnCell"), + true); else - advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected, - colModel, colMinSelected, colMaxSelected, - command.equals("selectPreviousRowCell"), false); + advanceMultipleSelection(table, rowModel, rowMinSelected, + rowMaxSelected, colModel, colMinSelected, + colMaxSelected, + command.equals("selectPreviousRowCell"), + false); } else if (command.equals("selectNextColumn")) { @@ -741,11 +808,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollLeftExtendSelection")) { int target; - if (colLead == getFirstVisibleColumnIndex()) - target = Math.max(0, colLead - (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getFirstVisibleColumnIndex(table)) + target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getFirstVisibleColumnIndex(); + target = getFirstVisibleColumnIndex(table); colModel.setLeadSelectionIndex(target); rowModel.setLeadSelectionIndex(rowLead); @@ -753,11 +820,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollDownChangeSelection")) { int target; - if (rowLead == getLastVisibleRowIndex()) - target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getLastVisibleRowIndex(table)) + target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getLastVisibleRowIndex(); + target = getLastVisibleRowIndex(table); rowModel.setSelectionInterval(target, target); colModel.setSelectionInterval(colLead, colLead); @@ -765,11 +832,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollRightExtendSelection")) { int target; - if (colLead == getLastVisibleColumnIndex()) - target = Math.min(colMax, colLead + (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getLastVisibleColumnIndex(table)) + target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getLastVisibleColumnIndex(); + target = getLastVisibleColumnIndex(table); colModel.setLeadSelectionIndex(target); rowModel.setLeadSelectionIndex(rowLead); @@ -786,11 +853,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollDownExtendSelection")) { int target; - if (rowLead == getLastVisibleRowIndex()) - target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getLastVisibleRowIndex(table)) + target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getLastVisibleRowIndex(); + target = getLastVisibleRowIndex(table); rowModel.setLeadSelectionIndex(target); colModel.setLeadSelectionIndex(colLead); @@ -798,11 +865,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollUpChangeSelection")) { int target; - if (rowLead == getFirstVisibleRowIndex()) - target = Math.max(0, rowLead - (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getFirstVisibleRowIndex(table)) + target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getFirstVisibleRowIndex(); + target = getFirstVisibleRowIndex(table); rowModel.setSelectionInterval(target, target); colModel.setSelectionInterval(colLead, colLead); @@ -927,7 +994,7 @@ public class BasicTableUI extends TableUI * Returns the column index of the first visible column. * @return the column index of the first visible column. */ - int getFirstVisibleColumnIndex() + int getFirstVisibleColumnIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -940,7 +1007,7 @@ public class BasicTableUI extends TableUI * Returns the column index of the last visible column. * */ - int getLastVisibleColumnIndex() + int getLastVisibleColumnIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -953,7 +1020,7 @@ public class BasicTableUI extends TableUI * Returns the row index of the first visible row. * */ - int getFirstVisibleRowIndex() + int getFirstVisibleRowIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -966,7 +1033,7 @@ public class BasicTableUI extends TableUI * Returns the row index of the last visible row. * */ - int getLastVisibleRowIndex() + int getLastVisibleRowIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -978,7 +1045,7 @@ public class BasicTableUI extends TableUI // area is larger than the table) if (table.rowAtPoint(r.getLocation()) == -1) { - if (getFirstVisibleRowIndex() == -1) + if (getFirstVisibleRowIndex(table) == -1) return -1; else return table.getModel().getRowCount() - 1; @@ -1004,7 +1071,8 @@ public class BasicTableUI extends TableUI * @param reverse true if shift was held for the event * @param eventIsTab true if TAB was pressed, false if ENTER pressed */ - void advanceMultipleSelection(ListSelectionModel firstModel, int firstMin, + void advanceMultipleSelection(JTable table, ListSelectionModel firstModel, + int firstMin, int firstMax, ListSelectionModel secondModel, int secondMin, int secondMax, boolean reverse, boolean eventIsTab) @@ -1168,29 +1236,24 @@ public class BasicTableUI extends TableUI table.addPropertyChangeListener(propertyChangeListener); } - protected void uninstallDefaults() throws NotImplementedException + /** + * Uninstalls UI defaults that have been installed by + * {@link #installDefaults()}. + */ + protected void uninstallDefaults() { - // TODO: this method used to do the following which is not - // quite right (at least it breaks apps that run fine with the - // JDK): - // - // table.setFont(null); - // table.setGridColor(null); - // table.setForeground(null); - // table.setBackground(null); - // table.setSelectionForeground(null); - // table.setSelectionBackground(null); - // - // This would leave the component in a corrupt state, which is - // not acceptable. A possible solution would be to have component - // level defaults installed, that get overridden by the UI defaults - // and get restored in this method. I am not quite sure about this - // though. / Roman Kennke + // Nothing to do here for now. } - protected void uninstallKeyboardActions() throws NotImplementedException + /** + * Uninstalls the keyboard actions that have been installed by + * {@link #installKeyboardActions()}. + */ + protected void uninstallKeyboardActions() { - // TODO: Implement this properly. + SwingUtilities.replaceUIInputMap(table, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(table, null); } protected void uninstallListeners() diff --git a/javax/swing/plaf/basic/BasicTextUI.java b/javax/swing/plaf/basic/BasicTextUI.java index 5febe141d..f201984db 100644 --- a/javax/swing/plaf/basic/BasicTextUI.java +++ b/javax/swing/plaf/basic/BasicTextUI.java @@ -83,7 +83,6 @@ import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; import javax.swing.text.Position; -import javax.swing.text.Utilities; import javax.swing.text.View; import javax.swing.text.ViewFactory; @@ -242,6 +241,12 @@ public abstract class BasicTextUI extends TextUI return Integer.MAX_VALUE; } + public void setSize(float w, float h) + { + if (view != null) + view.setSize(w, h); + } + /** * Paints the view. This is delegated to the real root view. * @@ -861,6 +866,7 @@ public abstract class BasicTextUI extends TextUI protected void uninstallListeners() { textComponent.removeFocusListener(focuslistener); + textComponent.getDocument().removeDocumentListener(documentHandler); } /** @@ -892,14 +898,19 @@ 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, + 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); + } + float w = rootView.getPreferredSpan(View.X_AXIS); + float h = rootView.getPreferredSpan(View.Y_AXIS); + + Dimension size = new Dimension((int) w + i.left + i.right, (int) h + i.top + i.bottom); + return size; } /** @@ -1042,7 +1053,7 @@ public abstract class BasicTextUI extends TextUI */ public void damageRange(JTextComponent t, int p0, int p1) { - damageRange(t, p0, p1, null, null); + damageRange(t, p0, p1, Position.Bias.Forward, Position.Bias.Backward); } /** @@ -1062,106 +1073,36 @@ public abstract class BasicTextUI extends TextUI public void damageRange(JTextComponent t, int p0, int p1, Position.Bias firstBias, Position.Bias secondBias) { - // Do nothing if the component cannot be properly displayed. - if (t.getWidth() == 0 || t.getHeight() == 0) - return; - - try + Rectangle alloc = getVisibleEditorRect(); + if (alloc != null) { - // Limit p0 and p1 to sane values to prevent unfriendly - // BadLocationExceptions. This makes it possible for the highlighter - // to send us illegal values which can happen when a large number - // of selected characters are removed (eg. by pressing delete - // or backspace). - // The reference implementation does not throw an exception, too. - p0 = Math.min(p0, t.getDocument().getLength()); - p1 = Math.min(p1, t.getDocument().getLength()); - - Rectangle l1 = modelToView(t, p0, firstBias); - Rectangle l2 = modelToView(t, p1, secondBias); - if (l1 == null || l2 == null) + Document doc = t.getDocument(); + + // Acquire lock here to avoid structural changes in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try { - // Unable to determine the start or end of the selection. - t.repaint(); + rootView.setSize(alloc.width, alloc.height); + Shape damage = rootView.modelToView(p0, firstBias, p1, secondBias, + alloc); + Rectangle r = damage instanceof Rectangle ? (Rectangle) damage + : damage.getBounds(); + textComponent.repaint(r.x, r.y, r.width, r.height); } - else if (l1.y == l2.y) + catch (BadLocationException ex) { - SwingUtilities.computeUnion(l2.x, l2.y, l2.width, l2.height, l1); - t.repaint(l1); + // Lets ignore this as it causes no serious problems. + // For debugging, comment this out. + // ex.printStackTrace(); } - else + finally { - // The two rectangles lie on different lines and we need a - // different algorithm to calculate the damaged area: - // 1. The line of p0 is damaged from the position of p0 - // to the right border. - // 2. All lines between the ones where p0 and p1 lie on - // are completely damaged. Use the allocation area to find - // out the bounds. - // 3. The final line is damaged from the left bound to the - // position of p1. - Insets insets = t.getInsets(); - - // Damage first line until the end. - l1.width = insets.right + t.getWidth() - l1.x; - t.repaint(l1); - - // Note: Utilities.getPositionBelow() may return the offset - // that was put in. In that case there is no next line and - // we should stop searching for one. - - int posBelow = Utilities.getPositionBelow(t, p0, l1.x); - int p1RowStart = Utilities.getRowStart(t, p1); - - if (posBelow != -1 - && posBelow != p0 - && Utilities.getRowStart(t, posBelow) != p1RowStart) - { - // Take the rectangle of the offset we just found and grow it - // to the maximum width. Retain y because this is our start - // height. - Rectangle grow = modelToView(t, posBelow); - grow.x = insets.left; - grow.width = t.getWidth() + insets.right; - - // Find further lines which have to be damaged completely. - int nextPosBelow = posBelow; - while (nextPosBelow != -1 - && posBelow != nextPosBelow - && Utilities.getRowStart(t, nextPosBelow) != p1RowStart) - { - posBelow = nextPosBelow; - nextPosBelow = Utilities.getPositionBelow(t, posBelow, - l1.x); - - if (posBelow == nextPosBelow) - break; - } - // Now posBelow is an offset on the last line which has to be - // damaged completely. (newPosBelow is on the same line as p1) - - // Retrieve the rectangle of posBelow and use its y and height - // value to calculate the final height of the multiple line - // spanning rectangle. - Rectangle end = modelToView(t, posBelow); - grow.height = end.y + end.height - grow.y; - - // Mark that area as damage. - t.repaint(grow); - } - - // Damage last line from its beginning to the position of p1. - l2.width += l2.x; - l2.x = insets.left; - t.repaint(l2); + // Release lock. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); } } - catch (BadLocationException ex) - { - AssertionError err = new AssertionError("Unexpected bad location"); - err.initCause(ex); - throw err; - } } /** @@ -1258,10 +1199,29 @@ public abstract class BasicTextUI extends TextUI public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias) throws BadLocationException { - Rectangle r = getVisibleEditorRect(); - - return (r != null) ? rootView.modelToView(pos, r, bias).getBounds() - : null; + // We need to read-lock here because we depend on the document + // structure not beeing changed in between. + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + Rectangle rect = null; + try + { + Rectangle r = getVisibleEditorRect(); + if (r != null) + { + rootView.setSize(r.width, r.height); + Shape s = rootView.modelToView(pos, r, bias); + if (s != null) + rect = s.getBounds(); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return rect; } /** @@ -1276,7 +1236,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/BasicToolBarUI.java b/javax/swing/plaf/basic/BasicToolBarUI.java index a4ed0c87d..1c36b408d 100644 --- a/javax/swing/plaf/basic/BasicToolBarUI.java +++ b/javax/swing/plaf/basic/BasicToolBarUI.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -50,6 +48,7 @@ import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; +import java.awt.event.ActionEvent; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.FocusEvent; @@ -62,7 +61,11 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Hashtable; +import javax.swing.AbstractAction; import javax.swing.AbstractButton; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; @@ -77,6 +80,7 @@ import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ToolBarUI; import javax.swing.plaf.UIResource; @@ -87,6 +91,35 @@ import javax.swing.plaf.basic.BasicBorders.ButtonBorder; */ public class BasicToolBarUI extends ToolBarUI implements SwingConstants { + + /** + * Implements the keyboard actions for JToolBar. + */ + static class ToolBarAction + extends AbstractAction + { + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + Object cmd = getValue("__command__"); + JToolBar toolBar = (JToolBar) event.getSource(); + BasicToolBarUI ui = (BasicToolBarUI) toolBar.getUI(); + + if (cmd.equals("navigateRight")) + ui.navigateFocusedComp(EAST); + else if (cmd.equals("navigateLeft")) + ui.navigateFocusedComp(WEST); + else if (cmd.equals("navigateUp")) + ui.navigateFocusedComp(NORTH); + else if (cmd.equals("navigateDown")) + ui.navigateFocusedComp(SOUTH); + else + assert false : "Shouldn't reach here"; + } + } + /** Static owner of all DragWindows. * This is package-private to avoid an accessor method. */ static JFrame owner = new JFrame(); @@ -619,9 +652,46 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants * by the look and feel. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + // Install the input map. + InputMap inputMap = + (InputMap) SharedUIDefaults.get("ToolBar.ancestorInputMap"); + SwingUtilities.replaceUIInputMap(toolBar, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + inputMap); + + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(toolBar, getActionMap()); + } + + /** + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("ToolBar.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("ToolBar.actionMap", am); + } + return am; + } + + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new ToolBarAction(); + + am.put("navigateLeft", action); + am.put("navigateRight", action); + am.put("navigateUp", action); + am.put("navigateDown", action); + + return am; } /** @@ -643,7 +713,12 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants floatFrame.addWindowListener(windowListener); toolBarFocusListener = createToolBarFocusListener(); - toolBar.addFocusListener(toolBarFocusListener); + if (toolBarFocusListener != null) + { + int count = toolBar.getComponentCount(); + for (int i = 0; i < count; i++) + toolBar.getComponent(i).addFocusListener(toolBarFocusListener); + } } /** @@ -758,9 +833,55 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants * @param direction The direction to give focus to. */ protected void navigateFocusedComp(int direction) - throws NotImplementedException { - // FIXME: Implement. + int count = toolBar.getComponentCount(); + switch (direction) + { + case EAST: + case SOUTH: + if (focusedCompIndex >= 0 && focusedCompIndex < count) + { + int i = focusedCompIndex + 1; + boolean focusRequested = false; + // Find component to focus and request focus on it. + while (i != focusedCompIndex && ! focusRequested) + { + if (i >= count) + i = 0; + Component comp = toolBar.getComponentAtIndex(i++); + if (comp != null && comp.isFocusable() + && comp.isEnabled()) + { + comp.requestFocus(); + focusRequested = true; + } + } + } + break; + case WEST: + case NORTH: + if (focusedCompIndex >= 0 && focusedCompIndex < count) + { + int i = focusedCompIndex - 1; + boolean focusRequested = false; + // Find component to focus and request focus on it. + while (i != focusedCompIndex && ! focusRequested) + { + if (i < 0) + i = count - 1; + Component comp = toolBar.getComponentAtIndex(i--); + if (comp != null && comp.isFocusable() + && comp.isEnabled()) + { + comp.requestFocus(); + focusRequested = true; + } + } + } + break; + default: + break; + } } /** @@ -925,9 +1046,10 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants * This method uninstalls keyboard actions installed by the UI. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + SwingUtilities.replaceUIInputMap(toolBar, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(toolBar, null); } /** @@ -935,8 +1057,13 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants */ protected void uninstallListeners() { - toolBar.removeFocusListener(toolBarFocusListener); - toolBarFocusListener = null; + if (toolBarFocusListener != null) + { + int count = toolBar.getComponentCount(); + for (int i = 0; i < count; i++) + toolBar.getComponent(i).removeFocusListener(toolBarFocusListener); + toolBarFocusListener = null; + } floatFrame.removeWindowListener(windowListener); windowListener = null; @@ -1294,6 +1421,10 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants cachedBounds = toolBar.getPreferredSize(); cachedOrientation = toolBar.getOrientation(); + + Component c = e.getChild(); + if (toolBarFocusListener != null) + c.addFocusListener(toolBarFocusListener); } /** @@ -1307,6 +1438,10 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants setBorderToNormal(e.getChild()); cachedBounds = toolBar.getPreferredSize(); cachedOrientation = toolBar.getOrientation(); + + Component c = e.getChild(); + if (toolBarFocusListener != null) + c.removeFocusListener(toolBarFocusListener); } } @@ -1334,29 +1469,32 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants /** * Creates a new ToolBarFocusListener object. */ - protected ToolBarFocusListener() throws NotImplementedException + protected ToolBarFocusListener() { - // FIXME: implement. + // Nothing to do here. } /** - * DOCUMENT ME! + * Receives notification when the toolbar or one of it's component + * receives the keyboard input focus. * - * @param e DOCUMENT ME! + * @param e the focus event */ - public void focusGained(FocusEvent e) throws NotImplementedException + public void focusGained(FocusEvent e) { - // FIXME: implement. + Component c = e.getComponent(); + focusedCompIndex = toolBar.getComponentIndex(c); } /** - * DOCUMENT ME! + * Receives notification when the toolbar or one of it's component + * looses the keyboard input focus. * - * @param e DOCUMENT ME! + * @param e the focus event */ - public void focusLost(FocusEvent e) throws NotImplementedException + public void focusLost(FocusEvent e) { - // FIXME: implement. + // Do nothing here. } } 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 0ca30b7ad..9a193986a 100644 --- a/javax/swing/plaf/basic/BasicTreeUI.java +++ b/javax/swing/plaf/basic/BasicTreeUI.java @@ -238,9 +238,6 @@ public class BasicTreeUI /** Set to true if the editor has a different size than the renderer. */ protected boolean editorHasDifferentSize; - /** The action bound to KeyStrokes. */ - TreeAction action; - /** Boolean to keep track of editing. */ boolean isEditing; @@ -477,8 +474,18 @@ public class BasicTreeUI */ protected void setCellRenderer(TreeCellRenderer tcr) { - currentCellRenderer = tcr; + // Finish editing before changing the renderer. + completeEditing(); + + // The renderer is set in updateRenderer. updateRenderer(); + + // Refresh the layout if necessary. + if (treeState != null) + { + treeState.invalidateSizes(); + updateSize(); + } } /** @@ -1071,7 +1078,6 @@ public class BasicTreeUI */ protected void uninstallKeyboardActions() { - action = null; tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent( null); tree.getActionMap().setParent(null); @@ -1172,10 +1178,26 @@ public class BasicTreeUI protected void updateRenderer() { if (tree != null) - currentCellRenderer = tree.getCellRenderer(); - - if (currentCellRenderer == null) - currentCellRenderer = createDefaultCellRenderer(); + { + TreeCellRenderer rend = tree.getCellRenderer(); + if (rend != null) + { + createdRenderer = false; + currentCellRenderer = rend; + if (createdCellEditor) + tree.setCellEditor(null); + } + else + { + tree.setCellRenderer(createDefaultCellRenderer()); + createdRenderer = true; + } + } + else + { + currentCellRenderer = null; + createdRenderer = false; + } updateCellEditor(); } @@ -1272,8 +1294,6 @@ public class BasicTreeUI JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, ancestorInputMap); - action = new TreeAction(); - SwingUtilities.replaceUIActionMap(tree, getActionMap()); } @@ -1303,9 +1323,6 @@ public class BasicTreeUI ActionMapUIResource am = new ActionMapUIResource(); Action action; - action = new TreeAction(); - am.put(action.getValue(Action.NAME), action); - // TreeHomeAction. action = new TreeHomeAction(-1, "selectFirst"); am.put(action.getValue(Action.NAME), action); @@ -2034,94 +2051,6 @@ public class BasicTreeUI } /** - * This class implements the actions that we want to happen when specific keys - * are pressed for the JTree. The actionPerformed method is called when a key - * that has been registered for the JTree is received. - */ - class TreeAction - extends AbstractAction - { - - /** - * What to do when this action is called. - * - * @param e the ActionEvent that caused this action. - */ - public void actionPerformed(ActionEvent e) - { - String command = e.getActionCommand(); - TreePath lead = tree.getLeadSelectionPath(); - - if (command.equals("selectPreviousChangeLead") - || command.equals("selectPreviousExtendSelection") - || command.equals("selectPrevious") || command.equals("selectNext") - || command.equals("selectNextExtendSelection") - || command.equals("selectNextChangeLead")) - (new TreeIncrementAction(0, "")).actionPerformed(e); - else if (command.equals("selectParent") || command.equals("selectChild")) - (new TreeTraverseAction(0, "")).actionPerformed(e); - else if (command.equals("selectAll")) - { - TreePath[] paths = new TreePath[treeState.getRowCount()]; - for (int i = 0; i < paths.length; i++) - paths[i] = treeState.getPathForRow(i); - tree.addSelectionPaths(paths); - } - else if (command.equals("startEditing")) - tree.startEditingAtPath(lead); - else if (command.equals("toggle")) - { - if (tree.isEditing()) - tree.stopEditing(); - else - { - Object last = lead.getLastPathComponent(); - TreePath path = new TreePath(getPathToRoot(last, 0)); - if (! treeModel.isLeaf(last)) - toggleExpandState(path); - } - } - else if (command.equals("clearSelection")) - tree.clearSelection(); - - if (tree.isEditing() && ! command.equals("startEditing")) - tree.stopEditing(); - - tree.scrollPathToVisible(tree.getLeadSelectionPath()); - } - } - - /** - * This class is used to mimic the behaviour of the JDK when registering - * keyboard actions. It is the same as the private class used in JComponent - * for the same reason. This class receives an action event and dispatches it - * to the true receiver after altering the actionCommand property of the - * event. - */ - private static class ActionListenerProxy - extends AbstractAction - { - ActionListener target; - - String bindingCommandName; - - public ActionListenerProxy(ActionListener li, String cmd) - { - target = li; - bindingCommandName = cmd; - } - - public void actionPerformed(ActionEvent e) - { - ActionEvent derivedEvent = new ActionEvent(e.getSource(), e.getID(), - bindingCommandName, - e.getModifiers()); - - target.actionPerformed(derivedEvent); - } - } - - /** * Updates the preferred size when scrolling, if necessary. */ public class ComponentHandler @@ -2503,6 +2432,9 @@ public class BasicTreeUI } } } + + // We need to request the focus. + tree.requestFocusInWindow(); } /** diff --git a/javax/swing/plaf/metal/MetalBorders.java b/javax/swing/plaf/metal/MetalBorders.java index 7c41180ae..d4e3a8497 100644 --- a/javax/swing/plaf/metal/MetalBorders.java +++ b/javax/swing/plaf/metal/MetalBorders.java @@ -926,15 +926,11 @@ public class MetalBorders /** The border insets. */ protected static Insets borderInsets = new Insets(1, 0, 1, 0); - // TODO: find where this color really comes from - private static Color borderColor = new Color(153, 153, 153); - /** * Creates a new border instance. */ public MenuBarBorder() { - // Nothing to do here. } /** @@ -951,7 +947,17 @@ public class MetalBorders public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) { - g.setColor(borderColor); + // Although it is not correct to decide on the static property + // currentTheme which color to use the RI does it like that. + // The trouble is that by simply changing the current theme to + // e.g. DefaultMetalLookAndFeel this method will use another color + // although a change in painting behavior should be expected only + // after setting a new look and feel and updating all components. + if(MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme) + g.setColor(UIManager.getColor("MenuBar.borderColor")); + else + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawLine(x, y + h - 1, x + w, y + h - 1); } diff --git a/javax/swing/plaf/metal/MetalComboBoxButton.java b/javax/swing/plaf/metal/MetalComboBoxButton.java index 6a528de2b..265ea7ef6 100644 --- a/javax/swing/plaf/metal/MetalComboBoxButton.java +++ b/javax/swing/plaf/metal/MetalComboBoxButton.java @@ -259,7 +259,9 @@ public class MetalComboBoxButton Component comp = renderer.getListCellRendererComponent(listBox, comboBox.getSelectedItem(), -1, false, false); comp.setFont(rendererPane.getFont()); - if (model.isArmed() && model.isPressed()) + + if ((model.isArmed() && model.isPressed()) + || (comboBox.isFocusOwner() && !comboBox.isPopupVisible())) { if (isOpaque()) { diff --git a/javax/swing/plaf/metal/MetalFileChooserUI.java b/javax/swing/plaf/metal/MetalFileChooserUI.java index 1219ad9fd..824f1d802 100644 --- a/javax/swing/plaf/metal/MetalFileChooserUI.java +++ b/javax/swing/plaf/metal/MetalFileChooserUI.java @@ -42,6 +42,7 @@ import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; +import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Insets; import java.awt.LayoutManager; @@ -303,8 +304,9 @@ public class MetalFileChooserUI if (file == null) setFileName(null); - else - setFileName(file.getName()); + else if (file.isFile() || filechooser.getFileSelectionMode() + != JFileChooser.FILES_ONLY) + setFileName(file.getName()); int index = -1; index = getModel().indexOf(file); if (index >= 0) @@ -567,10 +569,17 @@ public class MetalFileChooserUI extends DefaultListCellRenderer { /** + * This is the icon that is displayed in the combobox. This wraps + * the standard icon and adds indendation. + */ + private IndentIcon indentIcon; + + /** * Creates a new renderer. */ public DirectoryComboBoxRenderer(JFileChooser fc) - { + { + indentIcon = new IndentIcon(); } /** @@ -586,31 +595,86 @@ public class MetalFileChooserUI * @return The list cell renderer. */ public Component getListCellRendererComponent(JList list, Object value, - int index, boolean isSelected, boolean cellHasFocus) + int index, + boolean isSelected, + boolean cellHasFocus) { - FileView fileView = getFileView(getFileChooser()); + super.getListCellRendererComponent(list, value, index, isSelected, + cellHasFocus); File file = (File) value; - setIcon(fileView.getIcon(file)); - setText(fileView.getName(file)); - - if (isSelected) - { - setBackground(list.getSelectionBackground()); - setForeground(list.getSelectionForeground()); - } - else - { - setBackground(list.getBackground()); - setForeground(list.getForeground()); - } + setText(getFileChooser().getName(file)); + + // Install indented icon. + Icon icon = getFileChooser().getIcon(file); + indentIcon.setIcon(icon); + int depth = directoryModel.getDepth(index); + indentIcon.setDepth(depth); + setIcon(indentIcon); - setEnabled(list.isEnabled()); - setFont(list.getFont()); return this; } } /** + * An icon that wraps another icon and adds indentation. + */ + class IndentIcon + implements Icon + { + + /** + * The indentation level. + */ + private static final int INDENT = 10; + + /** + * The wrapped icon. + */ + private Icon icon; + + /** + * The current depth. + */ + private int depth; + + /** + * Sets the icon to be wrapped. + * + * @param i the icon + */ + void setIcon(Icon i) + { + icon = i; + } + + /** + * Sets the indentation depth. + * + * @param d the depth to set + */ + void setDepth(int d) + { + depth = d; + } + + public int getIconHeight() + { + return icon.getIconHeight(); + } + + public int getIconWidth() + { + return icon.getIconWidth() + depth * INDENT; + } + + public void paintIcon(Component c, Graphics g, int x, int y) + { + icon.paintIcon(c, g, x + depth * INDENT, y); + } + + } + + /** * A renderer for the files and directories in the file chooser. */ protected class FileRenderer @@ -956,9 +1020,12 @@ public class MetalFileChooserUI { String text = editField.getText(); if (text != null && text != "" && !text.equals(fc.getName(editFile))) - if (editFile.renameTo(fc.getFileSystemView().createFileObject( - fc.getCurrentDirectory(), text))) + { + File f = fc.getFileSystemView(). + createFileObject(fc.getCurrentDirectory(), text); + if ( editFile.renameTo(f) ) rescanCurrentDirectory(fc); + } list.remove(editField); } startEditing = false; @@ -982,17 +1049,8 @@ public class MetalFileChooserUI */ public void actionPerformed(ActionEvent e) { - if (e.getActionCommand().equals("notify-field-accept")) + if (editField != null) completeEditing(); - else if (editField != null) - { - list.remove(editField); - startEditing = false; - editFile = null; - lastSelected = null; - editField = null; - list.repaint(); - } } } } @@ -1101,7 +1159,7 @@ public class MetalFileChooserUI lastSelected = selVal; if (f.isFile()) setFileName(path.substring(path.lastIndexOf("/") + 1)); - else if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) + else if (fc.getFileSelectionMode() != JFileChooser.FILES_ONLY) setFileName(path); } fileTable.repaint(); @@ -1171,16 +1229,8 @@ public class MetalFileChooserUI */ public void actionPerformed(ActionEvent e) { - if (e.getActionCommand().equals("notify-field-accept")) + if (editField != null) completeEditing(); - else if (editField != null) - { - table.remove(editField); - startEditing = false; - editFile = null; - editField = null; - table.repaint(); - } } } diff --git a/javax/swing/plaf/metal/MetalLookAndFeel.java b/javax/swing/plaf/metal/MetalLookAndFeel.java index 9bc674db5..8a5a61107 100644 --- a/javax/swing/plaf/metal/MetalLookAndFeel.java +++ b/javax/swing/plaf/metal/MetalLookAndFeel.java @@ -1185,20 +1185,26 @@ public class MetalLookAndFeel extends BasicLookAndFeel "SplitPaneDivider.draggingColor", Color.DARK_GRAY, "TabbedPane.background", getControlShadow(), + "TabbedPane.contentBorderInsets", new InsetsUIResource(2, 2, 3, 3), + "TabbedPane.contentOpaque", Boolean.TRUE, "TabbedPane.darkShadow", getControlDarkShadow(), "TabbedPane.focus", getPrimaryControlDarkShadow(), "TabbedPane.font", new FontUIResource("Dialog", Font.BOLD, 12), "TabbedPane.foreground", getControlTextColor(), "TabbedPane.highlight", getControlHighlight(), "TabbedPane.light", getControl(), - "TabbedPane.selected", getControl(), + "TabbedPane.selected", getControl(), // overridden in OceanTheme "TabbedPane.selectHighlight", getControlHighlight(), "TabbedPane.selectedTabPadInsets", new InsetsUIResource(2, 2, 2, 1), "TabbedPane.shadow", getControlShadow(), - "TabbedPane.tabAreaBackground", getControl(), - "TabbedPane.tabAreaInsets", new InsetsUIResource(4, 2, 0, 6), + "TabbedPane.tabAreaBackground", getControl(), // overridden in OceanTheme + "TabbedPane.tabAreaInsets", new InsetsUIResource(4, 2, 0, 6), // dito "TabbedPane.tabInsets", new InsetsUIResource(0, 9, 1, 9), + // new properties in OceanTheme: + // TabbedPane.contentAreaColor + // TabbedPane.unselectedBackground + "Table.background", getWindowBackground(), "Table.focusCellBackground", getWindowBackground(), "Table.focusCellForeground", getControlTextColor(), diff --git a/javax/swing/plaf/metal/MetalMenuBarUI.java b/javax/swing/plaf/metal/MetalMenuBarUI.java index ff763ea9d..40661946b 100644 --- a/javax/swing/plaf/metal/MetalMenuBarUI.java +++ b/javax/swing/plaf/metal/MetalMenuBarUI.java @@ -1,5 +1,5 @@ /* MetalMenuBarUI.java -- MenuBar UI for 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. @@ -76,12 +76,15 @@ public class MetalMenuBarUI extends BasicMenuBarUI */ public void update(Graphics g, JComponent c) { + int height = c.getHeight(); if (c.isOpaque() && UIManager.get("MenuBar.gradient") != null - && c.getBackground() instanceof UIResource) + && c.getBackground() instanceof UIResource + && height > 2) { - MetalUtils.paintGradient(g, 0, 0, c.getWidth(), c.getHeight(), + MetalUtils.paintGradient(g, 0, 0, c.getWidth(), height - 2, SwingConstants.VERTICAL, "MenuBar.gradient"); + paint(g, c); } else diff --git a/javax/swing/plaf/metal/MetalTabbedPaneUI.java b/javax/swing/plaf/metal/MetalTabbedPaneUI.java index c49abe832..20135fc85 100644 --- a/javax/swing/plaf/metal/MetalTabbedPaneUI.java +++ b/javax/swing/plaf/metal/MetalTabbedPaneUI.java @@ -1,5 +1,5 @@ /* MetalTabbedPaneUI.java - Copyright (C) 2005 Free Software Foundation, Inc. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -170,7 +170,9 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI */ protected LayoutManager createLayoutManager() { - return new TabbedPaneLayout(); + return (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) + ? new MetalTabbedPaneUI.TabbedPaneLayout() + : super.createLayoutManager(); } /** @@ -326,7 +328,6 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI int bottom = h - 1; int right = w - 1; - int tabCount = tabPane.getTabCount(); int currentRun = getRunForTab(tabCount, tabIndex); int firstIndex = tabRuns[currentRun]; @@ -396,14 +397,17 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI { if (tabPane.getSelectedIndex() == tabIndex - 1) { - g.drawLine(0, 5, 0, bottom); - g.setColor(oceanSelectedBorder); - g.drawLine(0, 0, 0, 5); + g.drawLine(0, 6, 0, bottom); + if (tabIndex != firstIndex) + { + g.setColor(oceanSelectedBorder); + g.drawLine(0, 0, 0, 5); + } } else if (isSelected) { g.drawLine(0, 5, 0, bottom); - if (tabIndex != 0) + if (tabIndex != firstIndex) { g.setColor(darkShadow); g.drawLine(0, 0, 0, 5); @@ -463,9 +467,10 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI { Color c; if (tabPane.getSelectedIndex() == tabIndex - 1) - c = UIManager.getColor("TabbedPane.tabAreaBackground"); + c = selectColor; else c = getUnselectedBackground(tabIndex - 1); + g.setColor(c); g.fillRect(right - 5, 0, 5, 3); g.fillRect(right - 2, 3, 2, 2); } @@ -522,10 +527,13 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI } else if (isOcean && tabPane.getSelectedIndex() == tabIndex - 1) { - g.setColor(oceanSelectedBorder); - g.drawLine(right, 0, right, 6); + if (tabIndex != firstIndex) + { + g.setColor(oceanSelectedBorder); + g.drawLine(right, 0, right, 6); + } g.setColor(darkShadow); - g.drawLine(right, 6, right, bottom); + g.drawLine(right, 7, right, bottom); } else if (tabIndex != firstIndex) { @@ -598,8 +606,10 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI if (isOcean && isSelected) { g.drawLine(0, 0, 0, bottom - 5); - if ((currentRun == 0 && tabIndex != 0) - || (currentRun > 0 && tabIndex != tabRuns[currentRun - 1])) + + // Paint a connecting line to the tab below for all + // but the first tab in the last run. + if (tabIndex != tabRuns[runCount-1]) { g.setColor(darkShadow); g.drawLine(0, bottom - 5, 0, bottom); @@ -688,6 +698,103 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI } /** + * This method paints the focus rectangle around the selected tab. + * + * @param g The Graphics object to paint with. + * @param tabPlacement The JTabbedPane's tab placement. + * @param rects The array of rectangles keeping track of size and position. + * @param tabIndex The tab index. + * @param iconRect The icon bounds. + * @param textRect The text bounds. + * @param isSelected Whether this tab is selected. + */ + protected void paintFocusIndicator(Graphics g, int tabPlacement, + Rectangle[] rects, int tabIndex, + Rectangle iconRect, Rectangle textRect, + boolean isSelected) + { + if (tabPane.hasFocus() && isSelected) + { + Rectangle rect = rects[tabIndex]; + + g.setColor(focus); + g.translate(rect.x, rect.y); + + switch (tabPlacement) + { + case LEFT: + // Top line + g.drawLine(7, 2, rect.width-2, 2); + + // Right line + g.drawLine(rect.width-1, 2, rect.width-1, rect.height-3); + + // Bottom line + g.drawLine(rect.width-2, rect.height-2, 3, rect.height-2); + + // Left line + g.drawLine(2, rect.height-3, 2, 7); + + // Slant + g.drawLine(2, 6, 6, 2); + break; + case RIGHT: + // Top line + g.drawLine(1, 2, rect.width-8, 2); + + // Slant + g.drawLine(rect.width-7, 2, rect.width-3, 6); + + // Right line + g.drawLine(rect.width-3, 7, rect.width-3, rect.height-3); + + // Bottom line + g.drawLine(rect.width-3, rect.height-2, 2, rect.height-2); + + // Left line + g.drawLine(1, rect.height-2, 1, 2); + break; + case BOTTOM: + // Top line + g.drawLine(2, 1, rect.width-2, 1); + + // Right line + g.drawLine(rect.width-1, 2, rect.width-1, rect.height-3); + + // Bottom line + g.drawLine(7, rect.height-3, rect.width-2, rect.height-3); + + // Slant + g.drawLine(6, rect.height-3, 2, rect.height-7); + + // Left line + g.drawLine(2, rect.height-8, 2, 2); + + break; + case TOP: + default: + // Top line + g.drawLine(6, 2, rect.width-2, 2); + + // Right line + g.drawLine(rect.width-1, 2, rect.width-1, rect.height-3); + + // Bottom line + g.drawLine(3, rect.height-3, rect.width-2, rect.height-3); + + // Left line + g.drawLine(2, rect.height-3, 2, 7); + + // Slant + g.drawLine(2, 6, 6, 2); + + } + + g.translate(-rect.x, -rect.y); + } + } + + /** * Returns <code>true</code> if the tabs in the specified run should be * padded to make the run fill the width/height of the {@link JTabbedPane}. * @@ -1144,4 +1251,19 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI bg = unselectedBackground; return bg; } + + protected int getTabLabelShiftX(int tabPlacement, + int index, + boolean isSelected) + { + return 0; + } + + protected int getTabLabelShiftY(int tabPlacement, + int index, + boolean isSelected) + { + return 0; + } + } 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/MetalUtils.java b/javax/swing/plaf/metal/MetalUtils.java index a61b8ded2..0c3a38d5c 100644 --- a/javax/swing/plaf/metal/MetalUtils.java +++ b/javax/swing/plaf/metal/MetalUtils.java @@ -107,7 +107,7 @@ class MetalUtils for (int mX = x + xOff; mX < (x + w); mX += 4) { - g.drawLine(mX, mY, mX, mY); + g.fillRect(mX, mY, 1, 1); } // increase x offset diff --git a/javax/swing/text/AbstractDocument.java b/javax/swing/text/AbstractDocument.java index b936768b2..089785aa8 100644 --- a/javax/swing/text/AbstractDocument.java +++ b/javax/swing/text/AbstractDocument.java @@ -38,8 +38,11 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.font.TextAttribute; import java.io.PrintStream; import java.io.Serializable; +import java.text.Bidi; +import java.util.ArrayList; import java.util.Dictionary; import java.util.Enumeration; import java.util.EventListener; @@ -105,6 +108,21 @@ public abstract class AbstractDocument implements Document, Serializable public static final String ElementNameAttribute = "$ename"; /** + * Standard name for the bidi root element. + */ + private static final String BidiRootName = "bidi root"; + + /** + * Key for storing the asynchronous load priority. + */ + private static final String AsyncLoadPriority = "load priority"; + + /** + * Key for storing the I18N state. + */ + private static final String I18N = "i18n"; + + /** * The actual content model of this <code>Document</code>. */ Content content; @@ -147,14 +165,19 @@ public abstract class AbstractDocument implements Document, Serializable /** * A condition variable that readers and writers wait on. */ - Object documentCV = new Object(); + private Object documentCV = new Object(); /** An instance of a DocumentFilter.FilterBypass which allows calling * the insert, remove and replace method without checking for an installed * document filter. */ - DocumentFilter.FilterBypass bypass; - + private DocumentFilter.FilterBypass bypass; + + /** + * The bidi root element. + */ + private BidiRootElement bidiRoot; + /** * Creates a new <code>AbstractDocument</code> with the specified * {@link Content} model. @@ -186,9 +209,25 @@ public abstract class AbstractDocument implements Document, Serializable content = doc; context = ctx; + // FIXME: Fully implement bidi. + bidiRoot = new BidiRootElement(); + // FIXME: This is determined using a Mauve test. Make the document // actually use this. - putProperty("i18n", Boolean.FALSE); + putProperty(I18N, Boolean.FALSE); + + // Add one child to the bidi root. + writeLock(); + try + { + Element[] children = new Element[1]; + children[0] = new BidiElement(bidiRoot, 0, 1, 0); + bidiRoot.replace(0, 0, children); + } + finally + { + writeUnlock(); + } } /** Returns the DocumentFilter.FilterBypass instance for this @@ -344,7 +383,11 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getAsynchronousLoadPriority() { - return 0; + Object val = getProperty(AsyncLoadPriority); + int prio = -1; + if (val != null) + prio = ((Integer) val).intValue(); + return prio; } /** @@ -364,7 +407,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public Element getBidiRootElement() { - return null; + return bidiRoot; } /** @@ -417,14 +460,17 @@ public abstract class AbstractDocument implements Document, Serializable */ public final Position getEndPosition() { - // FIXME: Properly implement this by calling Content.createPosition(). - return new Position() - { - public int getOffset() - { - return getLength(); - } - }; + Position p; + try + { + p = createPosition(content.length()); + } + catch (BadLocationException ex) + { + // Shouldn't really happen. + p = null; + } + return p; } /** @@ -481,8 +527,9 @@ public abstract class AbstractDocument implements Document, Serializable */ public Element[] getRootElements() { - Element[] elements = new Element[1]; + Element[] elements = new Element[2]; elements[0] = getDefaultRootElement(); + elements[1] = getBidiRootElement(); return elements; } @@ -495,14 +542,17 @@ public abstract class AbstractDocument implements Document, Serializable */ public final Position getStartPosition() { - // FIXME: Properly implement this using Content.createPosition(). - return new Position() - { - public int getOffset() - { - return 0; - } - }; + Position p; + try + { + p = createPosition(0); + } + catch (BadLocationException ex) + { + // Shouldn't really happen. + p = null; + } + return p; } /** @@ -565,11 +615,19 @@ public abstract class AbstractDocument implements Document, Serializable // Bail out if we have a bogus insertion (Behavior observed in RI). if (text == null || text.length() == 0) return; - - if (documentFilter == null) - insertStringImpl(offset, text, attributes); - else - documentFilter.insertString(getBypass(), offset, text, attributes); + + writeLock(); + try + { + if (documentFilter == null) + insertStringImpl(offset, text, attributes); + else + documentFilter.insertString(getBypass(), offset, text, attributes); + } + finally + { + writeUnlock(); + } } void insertStringImpl(int offset, String text, AttributeSet attributes) @@ -582,23 +640,30 @@ public abstract class AbstractDocument implements Document, Serializable new DefaultDocumentEvent(offset, text.length(), DocumentEvent.EventType.INSERT); - try - { - writeLock(); - UndoableEdit undo = content.insertString(offset, text); - if (undo != null) - event.addEdit(undo); - - insertUpdate(event, attributes); + UndoableEdit undo = content.insertString(offset, text); + if (undo != null) + event.addEdit(undo); - fireInsertUpdate(event); - if (undo != null) - fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); - } - finally + // Check if we need bidi layout. + if (getProperty(I18N).equals(Boolean.FALSE)) { - writeUnlock(); + Object dir = getProperty(TextAttribute.RUN_DIRECTION); + if (TextAttribute.RUN_DIRECTION_RTL.equals(dir)) + putProperty(I18N, Boolean.TRUE); + else + { + char[] chars = text.toCharArray(); + if (Bidi.requiresBidi(chars, 0, chars.length)) + putProperty(I18N, Boolean.TRUE); + } } + + insertUpdate(event, attributes); + + fireInsertUpdate(event); + + if (undo != null) + fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); } /** @@ -611,7 +676,8 @@ public abstract class AbstractDocument implements Document, Serializable */ protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { - // Do nothing here. Subclasses may want to override this. + if (Boolean.TRUE.equals(getProperty(I18N))) + updateBidi(chng); } /** @@ -623,7 +689,8 @@ public abstract class AbstractDocument implements Document, Serializable */ protected void postRemoveUpdate(DefaultDocumentEvent chng) { - // Do nothing here. Subclasses may want to override this. + if (Boolean.TRUE.equals(getProperty(I18N))) + updateBidi(chng); } /** @@ -638,7 +705,317 @@ public abstract class AbstractDocument implements Document, Serializable if (properties == null) properties = new Hashtable(); - properties.put(key, value); + if (value == null) + properties.remove(key); + else + properties.put(key, value); + + // Update bidi structure if the RUN_DIRECTION is set. + if (TextAttribute.RUN_DIRECTION.equals(key)) + { + if (TextAttribute.RUN_DIRECTION_RTL.equals(value) + && Boolean.FALSE.equals(getProperty(I18N))) + putProperty(I18N, Boolean.TRUE); + + if (Boolean.TRUE.equals(getProperty(I18N))) + { + writeLock(); + try + { + DefaultDocumentEvent ev = + new DefaultDocumentEvent(0, getLength(), + DocumentEvent.EventType.INSERT); + updateBidi(ev); + } + finally + { + writeUnlock(); + } + } + } + } + + /** + * Updates the bidi element structure. + * + * @param ev the document event for the change + */ + private void updateBidi(DefaultDocumentEvent ev) + { + // Determine start and end offset of the paragraphs to be scanned. + int start = 0; + int end = 0; + DocumentEvent.EventType type = ev.getType(); + if (type == DocumentEvent.EventType.INSERT + || type == DocumentEvent.EventType.CHANGE) + { + int offs = ev.getOffset(); + int endOffs = offs + ev.getLength(); + start = getParagraphElement(offs).getStartOffset(); + end = getParagraphElement(endOffs).getEndOffset(); + } + else if (type == DocumentEvent.EventType.REMOVE) + { + Element par = getParagraphElement(ev.getOffset()); + start = par.getStartOffset(); + end = par.getEndOffset(); + } + else + assert false : "Unknown event type"; + + // Determine the bidi levels for the affected range. + Bidi[] bidis = getBidis(start, end); + + int removeFrom = 0; + int removeTo = 0; + + int offs = 0; + int lastRunStart = 0; + int lastRunEnd = 0; + int lastRunLevel = 0; + ArrayList newEls = new ArrayList(); + for (int i = 0; i < bidis.length; i++) + { + Bidi bidi = bidis[i]; + int numRuns = bidi.getRunCount(); + for (int r = 0; r < numRuns; r++) + { + if (r == 0 && i == 0) + { + if (start > 0) + { + // Try to merge with the previous element if it has the + // same bidi level as the first run. + int prevElIndex = bidiRoot.getElementIndex(start - 1); + removeFrom = prevElIndex; + Element prevEl = bidiRoot.getElement(prevElIndex); + AttributeSet atts = prevEl.getAttributes(); + int prevElLevel = StyleConstants.getBidiLevel(atts); + if (prevElLevel == bidi.getRunLevel(r)) + { + // Merge previous element with current run. + lastRunStart = prevEl.getStartOffset() - start; + lastRunEnd = bidi.getRunLimit(r); + lastRunLevel = bidi.getRunLevel(r); + } + else if (prevEl.getEndOffset() > start) + { + // Split previous element and replace by 2 new elements. + lastRunStart = 0; + lastRunEnd = bidi.getRunLimit(r); + lastRunLevel = bidi.getRunLevel(r); + newEls.add(new BidiElement(bidiRoot, + prevEl.getStartOffset(), + start, prevElLevel)); + } + else + { + // Simply start new run at start location. + lastRunStart = 0; + lastRunEnd = bidi.getRunLimit(r); + lastRunLevel = bidi.getRunLevel(r); + removeFrom++; + } + } + else + { + // Simply start new run at start location. + lastRunStart = 0; + lastRunEnd = bidi.getRunLimit(r); + lastRunLevel = bidi.getRunLevel(r); + removeFrom = 0; + } + } + if (i == bidis.length - 1 && r == numRuns - 1) + { + if (end <= getLength()) + { + // Try to merge last element with next element. + int nextIndex = bidiRoot.getElementIndex(end); + Element nextEl = bidiRoot.getElement(nextIndex); + AttributeSet atts = nextEl.getAttributes(); + int nextLevel = StyleConstants.getBidiLevel(atts); + int level = bidi.getRunLevel(r); + if (lastRunLevel == level && level == nextLevel) + { + // Merge runs together. + if (lastRunStart + start == nextEl.getStartOffset()) + removeTo = nextIndex - 1; + else + { + newEls.add(new BidiElement(bidiRoot, start + lastRunStart, + nextEl.getEndOffset(), level)); + removeTo = nextIndex; + } + } + else if (lastRunLevel == level) + { + // Merge current and last run. + int endOffs = offs + bidi.getRunLimit(r); + newEls.add(new BidiElement(bidiRoot, start + lastRunStart, + start + endOffs, level)); + if (start + endOffs == nextEl.getStartOffset()) + removeTo = nextIndex - 1; + else + { + newEls.add(new BidiElement(bidiRoot, start + endOffs, + nextEl.getEndOffset(), + nextLevel)); + removeTo = nextIndex; + } + } + else if (level == nextLevel) + { + // Merge current and next run. + newEls.add(new BidiElement(bidiRoot, start + lastRunStart, + start + lastRunEnd, + lastRunLevel)); + newEls.add(new BidiElement(bidiRoot, start + lastRunEnd, + nextEl.getEndOffset(), level)); + removeTo = nextIndex; + } + else + { + // Split next element. + int endOffs = offs + bidi.getRunLimit(r); + newEls.add(new BidiElement(bidiRoot, start + lastRunStart, + start + lastRunEnd, + lastRunLevel)); + newEls.add(new BidiElement(bidiRoot, start + lastRunEnd, + start + endOffs, level)); + newEls.add(new BidiElement(bidiRoot, start + endOffs, + nextEl.getEndOffset(), + nextLevel)); + removeTo = nextIndex; + } + } + else + { + removeTo = bidiRoot.getElementIndex(end); + int level = bidi.getRunLevel(r); + int runEnd = offs + bidi.getRunLimit(r); + + if (level == lastRunLevel) + { + // Merge with previous. + lastRunEnd = offs + runEnd; + newEls.add(new BidiElement(bidiRoot, + start + lastRunStart, + start + runEnd, level)); + } + else + { + // Create element for last run and current run. + newEls.add(new BidiElement(bidiRoot, start + lastRunStart, + start + lastRunEnd, + lastRunLevel)); + newEls.add(new BidiElement(bidiRoot, + start + lastRunEnd, + start + runEnd, + level)); + } + } + } + else + { + int level = bidi.getRunLevel(r); + int runEnd = bidi.getRunLimit(r); + + if (level == lastRunLevel) + { + // Merge with previous. + lastRunEnd = offs + runEnd; + } + else + { + // Create element for last run and update values for + // current run. + newEls.add(new BidiElement(bidiRoot, start + lastRunStart, + start + lastRunEnd, + lastRunLevel)); + lastRunStart = lastRunEnd; + lastRunEnd = offs + runEnd; + lastRunLevel = level; + } + } + } + offs += bidi.getLength(); + } + + // Determine the bidi elements which are to be removed. + int numRemoved = 0; + if (bidiRoot.getElementCount() > 0) + numRemoved = removeTo - removeFrom + 1; + Element[] removed = new Element[numRemoved]; + for (int i = 0; i < numRemoved; i++) + removed[i] = bidiRoot.getElement(removeFrom + i); + + Element[] added = new Element[newEls.size()]; + added = (Element[]) newEls.toArray(added); + + // Update the event. + ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added); + ev.addEdit(edit); + + // Update the structure. + bidiRoot.replace(removeFrom, numRemoved, added); + } + + /** + * Determines the Bidi objects for the paragraphs in the specified range. + * + * @param start the start of the range + * @param end the end of the range + * + * @return the Bidi analysers for the paragraphs in the range + */ + private Bidi[] getBidis(int start, int end) + { + // Determine the default run direction from the document property. + Boolean defaultDir = null; + Object o = getProperty(TextAttribute.RUN_DIRECTION); + if (o instanceof Boolean) + defaultDir = (Boolean) o; + + // Scan paragraphs and add their level arrays to the overall levels array. + ArrayList bidis = new ArrayList(); + Segment s = new Segment(); + for (int i = start; i < end;) + { + Element par = getParagraphElement(i); + int pStart = par.getStartOffset(); + int pEnd = par.getEndOffset(); + + // Determine the default run direction of the paragraph. + Boolean dir = defaultDir; + o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION); + if (o instanceof Boolean) + dir = (Boolean) o; + + // Bidi over the paragraph. + try + { + getText(pStart, pEnd - pStart, s); + } + catch (BadLocationException ex) + { + assert false : "Must not happen"; + } + int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; + if (dir != null) + { + if (TextAttribute.RUN_DIRECTION_LTR.equals(dir)) + flag = Bidi.DIRECTION_LEFT_TO_RIGHT; + else + flag = Bidi.DIRECTION_RIGHT_TO_LEFT; + } + Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag); + bidis.add(bidi); + i = pEnd; + } + Bidi[] ret = new Bidi[bidis.size()]; + ret = (Bidi[]) bidis.toArray(ret); + return ret; } /** @@ -653,6 +1030,7 @@ public abstract class AbstractDocument implements Document, Serializable { while (currentWriter != null || numWritersWaiting > 0) { + try { documentCV.wait(); @@ -743,35 +1121,36 @@ public abstract class AbstractDocument implements Document, Serializable void removeImpl(int offset, int length) throws BadLocationException { - if (offset < 0 || offset > getLength()) - throw new BadLocationException("Invalid remove position", offset); - - if (offset + length > getLength()) - throw new BadLocationException("Invalid remove length", offset); + // The RI silently ignores all requests that have a negative length. + // Don't ask my why, but that's how it is. + if (length > 0) + { + if (offset < 0 || offset > getLength()) + throw new BadLocationException("Invalid remove position", offset); - // Prevent some unneccessary method invocation (observed in the RI). - if (length == 0) - return; + if (offset + length > getLength()) + throw new BadLocationException("Invalid remove length", offset); - DefaultDocumentEvent event = - new DefaultDocumentEvent(offset, length, - DocumentEvent.EventType.REMOVE); + DefaultDocumentEvent event = + new DefaultDocumentEvent(offset, length, + DocumentEvent.EventType.REMOVE); - try - { - writeLock(); + try + { + writeLock(); - // The order of the operations below is critical! - removeUpdate(event); - UndoableEdit temp = content.remove(offset, length); + // The order of the operations below is critical! + removeUpdate(event); + UndoableEdit temp = content.remove(offset, length); - postRemoveUpdate(event); - fireRemoveUpdate(event); + postRemoveUpdate(event); + fireRemoveUpdate(event); + } + finally + { + writeUnlock(); + } } - finally - { - writeUnlock(); - } } /** @@ -804,21 +1183,28 @@ public abstract class AbstractDocument implements Document, Serializable if (length == 0 && (text == null || text.length() == 0)) return; - - if (documentFilter == null) + + writeLock(); + try { - // It is important to call the methods which again do the checks - // of the arguments and the DocumentFilter because subclasses may - // have overridden these methods and provide crucial behavior - // which would be skipped if we call the non-checking variants. - // An example for this is PlainDocument where insertString can - // provide a filtering of newlines. - remove(offset, length); - insertString(offset, text, attributes); + if (documentFilter == null) + { + // It is important to call the methods which again do the checks + // of the arguments and the DocumentFilter because subclasses may + // have overridden these methods and provide crucial behavior + // which would be skipped if we call the non-checking variants. + // An example for this is PlainDocument where insertString can + // provide a filtering of newlines. + remove(offset, length); + insertString(offset, text, attributes); + } + else + documentFilter.replace(getBypass(), offset, length, text, attributes); + } + finally + { + writeUnlock(); } - else - documentFilter.replace(getBypass(), offset, length, text, attributes); - } void replaceImpl(int offset, int length, String text, @@ -938,7 +1324,8 @@ public abstract class AbstractDocument implements Document, Serializable */ public void setAsynchronousLoadPriority(int p) { - // TODO: Implement this properly. + Integer val = p >= 0 ? new Integer(p) : null; + putProperty(AsyncLoadPriority, val); } /** @@ -1029,6 +1416,7 @@ public abstract class AbstractDocument implements Document, Serializable public void dump(PrintStream out) { ((AbstractElement) getDefaultRootElement()).dump(out, 0); + ((AbstractElement) getBidiRootElement()).dump(out, 0); } /** @@ -1245,7 +1633,7 @@ public abstract class AbstractDocument implements Document, Serializable AttributeContext ctx = getAttributeContext(); attributes = ctx.getEmptySet(); if (s != null) - attributes = ctx.addAttributes(attributes, s); + addAttributes(s); } /** @@ -1557,7 +1945,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public String getName() { - return (String) getAttribute(NameAttribute); + return (String) attributes.getAttribute(ElementNameAttribute); } /** @@ -1634,6 +2022,11 @@ public abstract class AbstractDocument implements Document, Serializable b.append('\n'); } } + if (getAttributeCount() > 0) + { + for (int i = 0; i < indent; ++i) + b.append(' '); + } b.append(">\n"); // Dump element content for leaf elements. @@ -1695,16 +2088,9 @@ public abstract class AbstractDocument implements Document, Serializable private int numChildren; /** - * The cached startOffset value. This is used in the case when a - * BranchElement (temporarily) has no child elements. + * The last found index in getElementIndex(). Used for faster searching. */ - private int startOffset; - - /** - * The cached endOffset value. This is used in the case when a - * BranchElement (temporarily) has no child elements. - */ - private int endOffset; + private int lastIndex; /** * Creates a new <code>BranchElement</code> with the specified @@ -1719,8 +2105,7 @@ public abstract class AbstractDocument implements Document, Serializable super(parent, attributes); children = new Element[1]; numChildren = 0; - startOffset = -1; - endOffset = -1; + lastIndex = -1; } /** @@ -1730,7 +2115,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public Enumeration children() { - if (children.length == 0) + if (numChildren == 0) return null; Vector tmp = new Vector(); @@ -1789,35 +2174,73 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getElementIndex(int offset) { - // If offset is less than the start offset of our first child, - // return 0 - if (offset < getStartOffset()) - return 0; + // Implemented using an improved linear search. + // This makes use of the fact that searches are not random but often + // close to the previous search. So we try to start the binary + // search at the last found index. - // XXX: There is surely a better algorithm - // as beginning from first element each time. - for (int index = 0; index < numChildren - 1; ++index) + int i0 = 0; // The lower bounds. + int i1 = numChildren - 1; // The upper bounds. + int index = -1; // The found index. + + int p0 = getStartOffset(); + int p1; // Start and end offset local variables. + + if (numChildren == 0) + index = 0; + else if (offset >= getEndOffset()) + index = numChildren - 1; + else { - Element elem = children[index]; - - if ((elem.getStartOffset() <= offset) - && (offset < elem.getEndOffset())) - return index; - // If the next element's start offset is greater than offset - // then we have to return the closest Element, since no Elements - // will contain the offset - if (children[index + 1].getStartOffset() > offset) + // Try lastIndex. + if (lastIndex >= i0 && lastIndex <= i1) { - if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset)) - return index + 1; + Element last = getElement(lastIndex); + p0 = last.getStartOffset(); + p1 = last.getEndOffset(); + if (offset >= p0 && offset < p1) + index = lastIndex; else - return index; + { + // Narrow the search bounds using the lastIndex, even + // if it hasn't been a hit. + if (offset < p0) + i1 = lastIndex; + else + i0 = lastIndex; + } + } + // The actual search. + int i = 0; + while (i0 <= i1 && index == -1) + { + i = i0 + (i1 - i0) / 2; + Element el = getElement(i); + p0 = el.getStartOffset(); + p1 = el.getEndOffset(); + if (offset >= p0 && offset < p1) + { + // Found it! + index = i; + } + else if (offset < p0) + i1 = i - 1; + else + i0 = i + 1; } - } - // If offset is greater than the index of the last element, return - // the index of the last element. - return getElementCount() - 1; + if (index == -1) + { + // Didn't find it. Return the boundary index. + if (offset < p0) + index = i; + else + index = i + 1; + } + + lastIndex = index; + } + return index; } /** @@ -1833,15 +2256,11 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getEndOffset() { - if (numChildren == 0) - { - if (endOffset == -1) - throw new NullPointerException("BranchElement has no children."); - } - else - endOffset = children[numChildren - 1].getEndOffset(); - - return endOffset; + // This might accss one cached element or trigger an NPE for + // numChildren == 0. This is checked by a Mauve test. + Element child = numChildren > 0 ? children[numChildren - 1] + : children[0]; + return child.getEndOffset(); } /** @@ -1867,15 +2286,13 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getStartOffset() { - if (numChildren == 0) - { - if (startOffset == -1) - throw new NullPointerException("BranchElement has no children."); - } - else - startOffset = children[0].getStartOffset(); - - return startOffset; + // Do not explicitly throw an NPE here. If the first element is null + // then the NPE gets thrown anyway. If it isn't, then it either + // holds a real value (for numChildren > 0) or a cached value + // (for numChildren == 0) as we don't fully remove elements in replace() + // when removing single elements. + // This is checked by a Mauve test. + return children[0].getStartOffset(); } /** @@ -1924,27 +2341,26 @@ public abstract class AbstractDocument implements Document, Serializable */ public void replace(int offset, int length, Element[] elements) { - if (numChildren + elements.length - length > children.length) + int delta = elements.length - length; + int copyFrom = offset + length; // From where to copy. + int copyTo = copyFrom + delta; // Where to copy to. + int numMove = numChildren - copyFrom; // How many elements are moved. + if (numChildren + delta > children.length) { // Gotta grow the array. - int newSize = Math.max(2 * children.length, - numChildren + elements.length - length); + int newSize = Math.max(2 * children.length, numChildren + delta); Element[] target = new Element[newSize]; System.arraycopy(children, 0, target, 0, offset); System.arraycopy(elements, 0, target, offset, elements.length); - System.arraycopy(children, offset + length, target, - offset + elements.length, - numChildren - offset - length); + System.arraycopy(children, copyFrom, target, copyTo, numMove); children = target; } else { - System.arraycopy(children, offset + length, children, - offset + elements.length, - numChildren - offset - length); + System.arraycopy(children, copyFrom, children, copyTo, numMove); System.arraycopy(elements, 0, children, offset, elements.length); } - numChildren += elements.length - length; + numChildren += delta; } /** @@ -2344,7 +2760,63 @@ public abstract class AbstractDocument implements Document, Serializable + getStartOffset() + "," + getEndOffset() + "\n"); } } - + + /** + * The root element for bidirectional text. + */ + private class BidiRootElement + extends BranchElement + { + /** + * Creates a new bidi root element. + */ + BidiRootElement() + { + super(null, null); + } + + /** + * Returns the name of the element. + * + * @return the name of the element + */ + public String getName() + { + return BidiRootName; + } + } + + /** + * A leaf element for the bidi structure. + */ + private class BidiElement + extends LeafElement + { + /** + * Creates a new BidiElement. + * + * @param parent the parent element + * @param start the start offset + * @param end the end offset + * @param level the bidi level + */ + BidiElement(Element parent, int start, int end, int level) + { + super(parent, new SimpleAttributeSet(), start, end); + addAttribute(StyleConstants.BidiLevel, new Integer(level)); + } + + /** + * Returns the name of the element. + * + * @return the name of the element + */ + public String getName() + { + return BidiElementName; + } + } + /** A class whose methods delegate to the insert, remove and replace methods * of this document which do not check for an installed DocumentFilter. */ diff --git a/javax/swing/text/BoxView.java b/javax/swing/text/BoxView.java index a184a8131..7e8f19f74 100644 --- a/javax/swing/text/BoxView.java +++ b/javax/swing/text/BoxView.java @@ -67,6 +67,11 @@ public class BoxView private boolean[] layoutValid = new boolean[2]; /** + * Indicates if the requirements for an axis are valid. + */ + private boolean[] requirementsValid = new boolean[2]; + + /** * The spans along the X_AXIS and Y_AXIS. */ private int[][] spans = new int[2][]; @@ -265,8 +270,10 @@ public class BoxView super.replace(offset, length, views); // Invalidate layout information. - layoutChanged(X_AXIS); - layoutChanged(Y_AXIS); + layoutValid[X_AXIS] = false; + requirementsValid[X_AXIS] = false; + layoutValid[Y_AXIS] = false; + requirementsValid[Y_AXIS] = false; } /** @@ -278,19 +285,26 @@ public class BoxView */ public void paint(Graphics g, Shape a) { - Rectangle inside = getInsideAllocation(a); - // TODO: Used for debugging. - //g.drawRect(inside.x, inside.y, inside.width, inside.height); + Rectangle alloc; + if (a instanceof Rectangle) + alloc = (Rectangle) a; + else + alloc = a.getBounds(); + + int x = alloc.x + getLeftInset(); + int y = alloc.y + getTopInset(); - Rectangle copy = new Rectangle(inside); + Rectangle clip = g.getClipBounds(); + Rectangle tmp = new Rectangle(); int count = getViewCount(); for (int i = 0; i < count; ++i) { - copy.setBounds(inside); - childAllocation(i, copy); - if (!copy.isEmpty() - && g.hitClip(copy.x, copy.y, copy.width, copy.height)) - paintChild(g, copy, i); + tmp.x = x + getOffset(X_AXIS, i); + tmp.y = y + getOffset(Y_AXIS, i); + tmp.width = getSpan(X_AXIS, i); + tmp.height = getSpan(Y_AXIS, i); + if (tmp.intersects(clip)) + paintChild(g, tmp, i); } } @@ -305,7 +319,13 @@ public class BoxView public float getPreferredSpan(int axis) { updateRequirements(axis); - return requirements[axis].preferred; + // Add margin. + float margin; + if (axis == X_AXIS) + margin = getLeftInset() + getRightInset(); + else + margin = getTopInset() + getBottomInset(); + return requirements[axis].preferred + margin; } /** @@ -319,12 +339,14 @@ public class BoxView */ public float getMaximumSpan(int axis) { - float max; - if (axis == myAxis) - max = getPreferredSpan(axis); + updateRequirements(axis); + // Add margin. + float margin; + if (axis == X_AXIS) + margin = getLeftInset() + getRightInset(); else - max = Integer.MAX_VALUE; - return max; + margin = getTopInset() + getBottomInset(); + return requirements[axis].maximum + margin; } /** @@ -341,7 +363,13 @@ public class BoxView public float getMinimumSpan(int axis) { updateRequirements(axis); - return requirements[axis].minimum; + // Add margin. + float margin; + if (axis == X_AXIS) + margin = getLeftInset() + getRightInset(); + else + margin = getTopInset() + getBottomInset(); + return requirements[axis].minimum + margin; } /** @@ -435,34 +463,29 @@ public class BoxView protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements sr) { - updateChildRequirements(axis); + SizeRequirements res = sr; + if (res == null) + res = new SizeRequirements(); - SizeRequirements result = sr; - if (result == null) - result = new SizeRequirements(); + float min = 0; + float pref = 0; + float max = 0; - long minimum = 0; - long preferred = 0; - long maximum = 0; - for (int i = 0; i < children.length; i++) + int n = getViewCount(); + for (int i = 0; i < n; i++) { - minimum += childReqs[axis][i].minimum; - preferred += childReqs[axis][i].preferred; - maximum += childReqs[axis][i].maximum; + View child = getView(i); + min += child.getMinimumSpan(axis); + pref += child.getPreferredSpan(axis); + max += child.getMaximumSpan(axis); } - // Overflow check. - if (minimum > Integer.MAX_VALUE) - minimum = Integer.MAX_VALUE; - if (preferred > Integer.MAX_VALUE) - preferred = Integer.MAX_VALUE; - if (maximum > Integer.MAX_VALUE) - maximum = Integer.MAX_VALUE; - - result.minimum = (int) minimum; - result.preferred = (int) preferred; - result.maximum = (int) maximum; - result.alignment = 0.5F; - return result; + + res.minimum = (int) min; + res.preferred = (int) pref; + res.maximum = (int) max; + res.alignment = 0.5F; + + return res; } /** @@ -480,44 +503,24 @@ public class BoxView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements sr) { - updateChildRequirements(axis); - SizeRequirements res = sr; if (res == null) res = new SizeRequirements(); - 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++) + res.minimum = 0; + res.preferred = 0; + res.maximum = 0; + res.alignment = 0.5F; + int n = getViewCount(); + for (int i = 0; i < n; i++) { - 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); + View child = getView(i); + res.minimum = Math.max((int) child.getMinimumSpan(axis), res.minimum); + res.preferred = Math.max((int) child.getPreferredSpan(axis), + res.preferred); + res.maximum = Math.max((int) child.getMaximumSpan(axis), res.maximum); } - 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; } @@ -565,9 +568,9 @@ public class BoxView boolean result = false; if (myAxis == X_AXIS) - result = x > r.x; + result = x > r.x + r.width; else - result = y > r.y; + result = y > r.y + r.height; return result; } @@ -620,9 +623,6 @@ public class BoxView */ protected void childAllocation(int index, Rectangle a) { - if (! isAllocationValid()) - layout(a.width, a.height); - a.x += offsets[X_AXIS][index]; a.y += offsets[Y_AXIS][index]; a.width = spans[X_AXIS][index]; @@ -697,15 +697,62 @@ public class BoxView protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - updateChildRequirements(axis); - updateRequirements(axis); + // Set the spans to the preferred sizes. Determine the space + // that we have to adjust the sizes afterwards. + long sumPref = 0; + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View child = getView(i); + spans[i] = (int) child.getPreferredSpan(axis); + sumPref = spans[i]; + } - // Calculate the spans and offsets using the SizeRequirements uility - // methods. - SizeRequirements.calculateTiledPositions(targetSpan, requirements[axis], - childReqs[axis], - offsets, spans); + // Try to adjust the spans so that we fill the targetSpan. + long diff = targetSpan - sumPref; + float factor = 0.0F; + int[] diffs = null; + if (diff != 0) + { + long total = 0; + diffs = new int[n]; + for (int i = 0; i < n; i++) + { + View child = getView(i); + int span; + if (diff < 0) + { + span = (int) child.getMinimumSpan(axis); + diffs[i] = spans[i] - span; + } + else + { + span = (int) child.getMaximumSpan(axis); + diffs[i] = span - spans[i]; + } + total += span; + } + float maxAdjust = Math.abs(total - sumPref); + factor = diff / maxAdjust; + factor = Math.min(factor, 1.0F); + factor = Math.max(factor, -1.0F); + } + + // Actually perform adjustments. + int totalOffs = 0; + for (int i = 0; i < n; i++) + { + offsets[i] = totalOffs; + if (diff != 0) + { + float adjust = factor * diffs[i]; + spans[i] += Math.round(adjust); + } + // Avoid overflow here. + totalOffs = (int) Math.min((long) totalOffs + (long) spans[i], + Integer.MAX_VALUE); + } } /** @@ -720,14 +767,26 @@ public class BoxView protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - updateChildRequirements(axis); - updateRequirements(axis); - - // Calculate the spans and offsets using the SizeRequirements uility - // methods. - SizeRequirements.calculateAlignedPositions(targetSpan, requirements[axis], - childReqs[axis], offsets, - spans); + int count = getViewCount(); + for (int i = 0; i < count; i++) + { + View child = getView(i); + int max = (int) child.getMaximumSpan(axis); + if (max < targetSpan) + { + // Align child when it can't be made as wide as the target span. + float align = child.getAlignment(axis); + offsets[i] = (int) ((targetSpan - max) * align); + spans[i] = max; + } + else + { + // Expand child to target width if possible. + int min = (int) child.getMinimumSpan(axis); + offsets[i] = 0; + spans[i] = Math.max(min, targetSpan); + } + } } /** @@ -749,7 +808,7 @@ public class BoxView */ public int getWidth() { - return span[X_AXIS]; + return span[X_AXIS] + getLeftInset() - getRightInset(); } /** @@ -759,7 +818,7 @@ public class BoxView */ public int getHeight() { - return span[Y_AXIS]; + return span[Y_AXIS] + getTopInset() - getBottomInset(); } /** @@ -771,7 +830,8 @@ public class BoxView */ public void setSize(float width, float height) { - layout((int) width, (int) height); + layout((int) (width - getLeftInset() - getRightInset()), + (int) (height - getTopInset() - getBottomInset())); } /** @@ -822,15 +882,8 @@ public class BoxView */ public float getAlignment(int axis) { - float align; - if (axis == myAxis) - align = 0.5F; - else - { - updateRequirements(axis); - align = requirements[axis].alignment; - } - return align; + updateRequirements(axis); + return requirements[axis].alignment; } /** @@ -843,9 +896,15 @@ public class BoxView public void preferenceChanged(View child, boolean width, boolean height) { if (width) - layoutValid[X_AXIS] = false; + { + layoutValid[X_AXIS] = false; + requirementsValid[X_AXIS] = false; + } if (height) - layoutValid[Y_AXIS] = false; + { + layoutValid[Y_AXIS] = false; + requirementsValid[Y_AXIS] = false; + } super.preferenceChanged(child, width, height); } @@ -862,7 +921,7 @@ public class BoxView if (! isAllocationValid()) { Rectangle bounds = a.getBounds(); - layout(bounds.width, bounds.height); + setSize(bounds.width, bounds.height); } return super.modelToView(pos, a, bias); } @@ -963,7 +1022,9 @@ public class BoxView */ private void updateRequirements(int axis) { - if (! layoutValid[axis]) + if (axis != Y_AXIS && axis != X_AXIS) + throw new IllegalArgumentException("Illegal axis: " + axis); + if (! requirementsValid[axis]) { if (axis == myAxis) requirements[axis] = calculateMajorAxisRequirements(axis, @@ -971,6 +1032,7 @@ public class BoxView else requirements[axis] = calculateMinorAxisRequirements(axis, requirements[axis]); + requirementsValid[axis] = true; } } } diff --git a/javax/swing/text/CompositeView.java b/javax/swing/text/CompositeView.java index 17f13dbed..6f487b898 100644 --- a/javax/swing/text/CompositeView.java +++ b/javax/swing/text/CompositeView.java @@ -217,25 +217,43 @@ public abstract class CompositeView public Shape modelToView(int pos, Shape a, Position.Bias bias) throws BadLocationException { - int childIndex = getViewIndex(pos, bias); - if (childIndex == -1) - throw new BadLocationException("Position " + pos + " is not represented by view.", pos); - - Shape ret = null; - - View child = getView(childIndex); - Shape childAlloc = getChildAllocation(childIndex, a); - - if (childAlloc == null) - ret = createDefaultLocation(a, bias); - - Shape result = child.modelToView(pos, childAlloc, bias); - - if (result != null) - ret = result; - else - ret = createDefaultLocation(a, bias); + boolean backward = bias == Position.Bias.Backward; + int testpos = backward ? Math.max(0, pos - 1) : pos; + Shape ret = null; + if (! backward || testpos >= getStartOffset()) + { + int childIndex = getViewIndexAtPosition(testpos); + if (childIndex != -1 && childIndex < getViewCount()) + { + View child = getView(childIndex); + if (child != null && testpos >= child.getStartOffset() + && testpos < child.getEndOffset()) + { + Shape childAlloc = getChildAllocation(childIndex, a); + if (childAlloc != null) + { + ret = child.modelToView(pos, childAlloc, bias); + // Handle corner case. + if (ret == null && child.getEndOffset() == pos) + { + childIndex++; + if (childIndex < getViewCount()) + { + child = getView(childIndex); + childAlloc = getChildAllocation(childIndex, a); + ret = child.modelToView(pos, childAlloc, bias); + } + } + } + } + } + else + { + throw new BadLocationException("Position " + pos + + " is not represented by view.", pos); + } + } return ret; } @@ -378,7 +396,10 @@ public abstract class CompositeView { if (b == Position.Bias.Backward && pos != 0) pos -= 1; - return getViewIndexAtPosition(pos); + int i = -1; + if (pos >= getStartOffset() && pos < getEndOffset()) + i = getViewIndexAtPosition(pos); + return i; } /** @@ -446,9 +467,13 @@ public abstract class CompositeView */ protected View getViewAtPosition(int pos, Rectangle a) { + View view = null; int i = getViewIndexAtPosition(pos); - View view = children[i]; - childAllocation(i, a); + if (i >= 0 && i < getViewCount() && a != null) + { + view = getView(i); + childAllocation(i, a); + } return view; } @@ -464,17 +489,10 @@ public abstract class CompositeView */ protected int getViewIndexAtPosition(int pos) { - int index = -1; - for (int i = 0; i < children.length; i++) - { - if (children[i].getStartOffset() <= pos - && children[i].getEndOffset() > pos) - { - index = i; - break; - } - } - return index; + // We have a 1:1 mapping of elements to views here, so we forward + // this to the element. + Element el = getElement(); + return el.getElementIndex(pos); } /** diff --git a/javax/swing/text/DefaultCaret.java b/javax/swing/text/DefaultCaret.java index 7b4335e40..84f47f120 100644 --- a/javax/swing/text/DefaultCaret.java +++ b/javax/swing/text/DefaultCaret.java @@ -552,7 +552,6 @@ public class DefaultCaret extends Rectangle */ public void mousePressed(MouseEvent event) { - int button = event.getButton(); // The implementation assumes that consuming the event makes the AWT event // mechanism forget about this event instance and not transfer focus. @@ -565,23 +564,37 @@ public class DefaultCaret extends Rectangle // - a middle-click positions the caret and pastes the clipboard // contents. // - a middle-click when shift is held down is ignored - - if (button == MouseEvent.BUTTON1) - if (event.isShiftDown()) - moveCaret(event); - else - positionCaret(event); - else if(button == MouseEvent.BUTTON2) - if (event.isShiftDown()) - event.consume(); + + if (SwingUtilities.isLeftMouseButton(event)) + { + // Handle the caret. + if (event.isShiftDown() && getDot() != -1) + { + moveCaret(event); + } else { positionCaret(event); - + } + + // Handle the focus. + if (textComponent != null && textComponent.isEnabled() + && textComponent.isRequestFocusEnabled()) + { + textComponent.requestFocus(); + } + + // TODO: Handle double click for selecting words. + } + else if(event.getButton() == MouseEvent.BUTTON2) + { + // Special handling for X11-style pasting. + if (! event.isShiftDown()) + { + positionCaret(event); textComponent.paste(); } - else - event.consume(); + } } /** @@ -898,10 +911,10 @@ public class DefaultCaret extends Rectangle } catch (BadLocationException e) { - AssertionError ae; - ae = new AssertionError("Unexpected bad caret location: " + dot); - ae.initCause(e); - throw ae; + // Let's ignore that. This shouldn't really occur. But if it + // does (it seems that this happens when the model is mutating), + // it causes no real damage. Uncomment this for debugging. + // e.printStackTrace(); } if (rect == null) @@ -1135,10 +1148,10 @@ public class DefaultCaret extends Rectangle } catch (BadLocationException e) { - AssertionError ae; - ae = new AssertionError("Unexpected bad caret location: " + dot); - ae.initCause(e); - throw ae; + // Let's ignore that. This shouldn't really occur. But if it + // does (it seems that this happens when the model is mutating), + // it causes no real damage. Uncomment this for debugging. + // e.printStackTrace(); } if (area != null) damage(area); diff --git a/javax/swing/text/DefaultHighlighter.java b/javax/swing/text/DefaultHighlighter.java index 59f77316e..69563e473 100644 --- a/javax/swing/text/DefaultHighlighter.java +++ b/javax/swing/text/DefaultHighlighter.java @@ -1,4 +1,4 @@ -/* DefaultHighlighter.java -- +/* DefaultHighlighter.java -- The default highlight for Swing Copyright (C) 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,18 +38,21 @@ exception statement from your version. */ package javax.swing.text; -import gnu.classpath.NotImplementedException; - import java.awt.Color; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; import java.util.ArrayList; +import java.util.Iterator; import javax.swing.SwingUtilities; import javax.swing.plaf.TextUI; +/** + * The default highlight for Swing text components. It highlights text + * by filling the background with a rectangle. + */ public class DefaultHighlighter extends LayeredHighlighter { public static class DefaultHighlightPainter @@ -68,11 +71,6 @@ public class DefaultHighlighter extends LayeredHighlighter return color; } - private void paintHighlight(Graphics g, Rectangle rect) - { - g.fillRect(rect.x, rect.y, rect.width, rect.height); - } - public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent t) { @@ -81,30 +79,31 @@ public class DefaultHighlighter extends LayeredHighlighter Rectangle rect = bounds.getBounds(); - if (color == null) - g.setColor(t.getSelectionColor()); - else - g.setColor(color); + Color col = getColor(); + if (col == null) + col = t.getSelectionColor(); + g.setColor(col); TextUI ui = t.getUI(); try - { - - Rectangle l0 = ui.modelToView(t, p0, null); - Rectangle l1 = ui.modelToView(t, p1, null); - - // Note: The computed locations may lie outside of the allocation - // area if the text is scrolled. + { + + Rectangle l0 = ui.modelToView(t, p0, null); + Rectangle l1 = ui.modelToView(t, p1, null); - if (l0.y == l1.y) + // Note: The computed locations may lie outside of the allocation + // area if the text is scrolled. + + if (l0.y == l1.y) { SwingUtilities.computeUnion(l0.x, l0.y, l0.width, l0.height, l1); // Paint only inside the allocation area. - SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height, l1); + SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, + rect.height, l1); - paintHighlight(g, l1); + g.fillRect(l1.x, l1.y, l1.width, l1.height); } else { @@ -115,77 +114,71 @@ public class DefaultHighlighter extends LayeredHighlighter // out the bounds. // 3. The final line is painted from the left border to the // position of p1. - - // Highlight first line until the end. - // If rect.x is non-zero the calculation will properly adjust the - // area to be painted. - l0.x -= rect.x; - l0.width = rect.width - l0.x - rect.x; - - paintHighlight(g, l0); - - int posBelow = Utilities.getPositionBelow(t, p0, l0.x); - int p1RowStart = Utilities.getRowStart(t, p1); - if (posBelow != -1 - && posBelow != p0 - && Utilities.getRowStart(t, posBelow) - != p1RowStart) - { - Rectangle grow = ui.modelToView(t, posBelow); - grow.x = rect.x; - grow.width = rect.width; - - // Find further lines which have to be highlighted completely. - int nextPosBelow = posBelow; - while (nextPosBelow != -1 - && Utilities.getRowStart(t, nextPosBelow) != p1RowStart) - { - posBelow = nextPosBelow; - nextPosBelow = Utilities.getPositionBelow(t, posBelow, l0.x); - - if (nextPosBelow == posBelow) - break; - } - // Now posBelow is an offset on the last line which has to be painted - // completely. (newPosBelow is on the same line as p1) - - // Retrieve the rectangle of posBelow and use its y and height - // value to calculate the final height of the multiple line - // spanning rectangle. - Rectangle end = ui.modelToView(t, posBelow); - grow.height = end.y + end.height - grow.y; - - paintHighlight(g, grow); - } - - // Paint last line from its beginning to the position of p1. - l1.width = l1.x + l1.width - rect.x; - l1.x = rect.x; - paintHighlight(g, l1); - } + + int firstLineWidth = rect.x + rect.width - l0.x; + g.fillRect(l0.x, l0.y, firstLineWidth, l0.height); + if (l0.y + l0.height != l1.y) + { + g.fillRect(rect.x, l0.y + l0.height, rect.width, + l1.y - l0.y - l0.height); + } + g.fillRect(rect.x, l1.y, l1.x - rect.x, l1.height); + } } catch (BadLocationException ex) { - AssertionError err = new AssertionError("Unexpected bad location exception"); - err.initCause(ex); - throw err; + // Can't render. Comment out for debugging. + // ex.printStackTrace(); } } public Shape paintLayer(Graphics g, int p0, int p1, Shape bounds, JTextComponent c, View view) { - throw new InternalError(); + Color col = getColor(); + if (col == null) + col = c.getSelectionColor(); + g.setColor(col); + + Rectangle rect = null; + if (p0 == view.getStartOffset() && p1 == view.getEndOffset()) + { + // Paint complete bounds region. + rect = bounds instanceof Rectangle ? (Rectangle) bounds + : bounds.getBounds(); + } + else + { + // Only partly inside the view. + try + { + Shape s = view.modelToView(p0, Position.Bias.Forward, + p1, Position.Bias.Backward, + bounds); + rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds(); + } + catch (BadLocationException ex) + { + // Can't render the highlight. + } + } + + if (rect != null) + { + g.fillRect(rect.x, rect.y, rect.width, rect.height); + } + return rect; } } private class HighlightEntry implements Highlighter.Highlight { - int p0; - int p1; + Position p0; + Position p1; Highlighter.HighlightPainter painter; - public HighlightEntry(int p0, int p1, Highlighter.HighlightPainter painter) + public HighlightEntry(Position p0, Position p1, + Highlighter.HighlightPainter painter) { this.p0 = p0; this.p1 = p1; @@ -194,12 +187,12 @@ public class DefaultHighlighter extends LayeredHighlighter public int getStartOffset() { - return p0; + return p0.getOffset(); } public int getEndOffset() { - return p1; + return p1.getOffset(); } public Highlighter.HighlightPainter getPainter() @@ -209,6 +202,58 @@ public class DefaultHighlighter extends LayeredHighlighter } /** + * A HighlightEntry that is used for LayerPainter painters. In addition + * to the info maintained by the HighlightEntry, this class maintains + * a painting rectangle. This is used as repaint region when the + * highlight changes and the text component needs repainting. + */ + private class LayerHighlightEntry + extends HighlightEntry + { + + /** + * The paint rectangle. + */ + Rectangle paintRect = new Rectangle(); + + LayerHighlightEntry(Position p0, Position p1, + Highlighter.HighlightPainter p) + { + super(p0, p1, p); + } + + /** + * Paints the highlight by calling the LayerPainter. This + * restricts the area to be painted by startOffset and endOffset + * and manages the paint rectangle. + */ + void paintLayeredHighlight(Graphics g, int p0, int p1, Shape bounds, + JTextComponent tc, View view) + { + p0 = Math.max(getStartOffset(), p0); + p1 = Math.min(getEndOffset(), p1); + + Highlighter.HighlightPainter painter = getPainter(); + if (painter instanceof LayerPainter) + { + LayerPainter layerPainter = (LayerPainter) painter; + Shape area = layerPainter.paintLayer(g, p0, p1, bounds, tc, view); + Rectangle rect; + if (area instanceof Rectangle && paintRect != null) + rect = (Rectangle) area; + else + rect = area.getBounds(); + + if (paintRect.width == 0 || paintRect.height == 0) + paintRect = rect.getBounds(); + else + paintRect = SwingUtilities.computeUnion(rect.x, rect.y, rect.width, + rect.height, paintRect); + } + } + } + + /** * @specnote final as of 1.4 */ public static final LayeredHighlighter.LayerPainter DefaultPainter = @@ -254,11 +299,19 @@ public class DefaultHighlighter extends LayeredHighlighter textComponent = null; } - public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter painter) + public Object addHighlight(int p0, int p1, + Highlighter.HighlightPainter painter) throws BadLocationException { checkPositions(p0, p1); - HighlightEntry entry = new HighlightEntry(p0, p1, painter); + HighlightEntry entry; + Document doc = textComponent.getDocument(); + Position pos0 = doc.createPosition(p0); + Position pos1 = doc.createPosition(p1); + if (getDrawsLayeredHighlights() && painter instanceof LayerPainter) + entry = new LayerHighlightEntry(pos0, pos1, painter); + else + entry = new HighlightEntry(pos0, pos1, painter); highlights.add(entry); textComponent.getUI().damageRange(textComponent, p0, p1); @@ -268,16 +321,67 @@ public class DefaultHighlighter extends LayeredHighlighter public void removeHighlight(Object tag) { + HighlightEntry entry = (HighlightEntry) tag; + if (entry instanceof LayerHighlightEntry) + { + LayerHighlightEntry lEntry = (LayerHighlightEntry) entry; + Rectangle paintRect = lEntry.paintRect; + textComponent.repaint(paintRect.x, paintRect.y, paintRect.width, + paintRect.height); + } + else + { + textComponent.getUI().damageRange(textComponent, + entry.getStartOffset(), + entry.getEndOffset()); + } highlights.remove(tag); - HighlightEntry entry = (HighlightEntry) tag; - textComponent.getUI().damageRange(textComponent, - entry.p0, - entry.p1); } public void removeAllHighlights() { + // Repaint damaged region. + int minX = 0; + int maxX = 0; + int minY = 0; + int maxY = 0; + int p0 = -1; + int p1 = -1; + for (Iterator i = highlights.iterator(); i.hasNext();) + { + HighlightEntry e = (HighlightEntry) i.next(); + if (e instanceof LayerHighlightEntry) + { + LayerHighlightEntry le = (LayerHighlightEntry) e; + Rectangle r = le.paintRect; + minX = Math.min(r.x, minX); + maxX = Math.max(r.x + r.width, maxX); + minY = Math.min(r.y, minY); + maxY = Math.max(r.y + r.height, maxY); + } + else + { + if (p0 == -1 || p1 == -1) + { + p0 = e.getStartOffset(); + p1 = e.getEndOffset(); + } + else + { + p0 = Math.min(p0, e.getStartOffset()); + p1 = Math.max(p1, e.getEndOffset()); + } + } + if (minX != maxX && minY != maxY) + textComponent.repaint(minX, minY, maxX - minX, maxY - minY); + if (p0 != -1 && p1 != -1) + { + TextUI ui = textComponent.getUI(); + ui.damageRange(textComponent, p0, p1); + } + + } highlights.clear(); } @@ -290,94 +394,61 @@ public class DefaultHighlighter extends LayeredHighlighter public void changeHighlight(Object tag, int n0, int n1) throws BadLocationException { - int o0, o1; - - checkPositions(n0, n1); - HighlightEntry entry = (HighlightEntry) tag; - o0 = entry.p0; - o1 = entry.p1; - - // Prevent useless write & repaint operations. - if (o0 == n0 && o1 == n1) - return; - - entry.p0 = n0; - entry.p1 = n1; - + Document doc = textComponent.getDocument(); TextUI ui = textComponent.getUI(); - - // Special situation where the old area has to be cleared simply. - if (n0 == n1) - ui.damageRange(textComponent, o0, o1); - // Calculates the areas where a change is really neccessary - else if ((o1 > n0 && o1 <= n1) - || (n1 > o0 && n1 <= o1)) + if (tag instanceof LayerHighlightEntry) { - // [fds, fde) - the first damage region - // [sds, sde] - the second damage region - int fds, sds; - int fde, sde; - - // Calculate first damaged region. - if(o0 < n0) - { - // Damaged region will be cleared as - // the old highlight region starts first. - fds = o0; - fde = n0; - } - else - { - // Damaged region will be painted as - // the new highlight region starts first. - fds = n0; - fde = o0; - } - - if (o1 < n1) + LayerHighlightEntry le = (LayerHighlightEntry) tag; + Rectangle r = le.paintRect; + if (r.width > 0 && r.height > 0) + textComponent.repaint(r.x, r.y, r.width, r.height); + r.width = 0; + r.height = 0; + le.p0 = doc.createPosition(n0); + le.p1 = doc.createPosition(n1); + ui.damageRange(textComponent, Math.min(n0, n1), Math.max(n0, n1)); + } + else if (tag instanceof HighlightEntry) + { + HighlightEntry e = (HighlightEntry) tag; + int p0 = e.getStartOffset(); + int p1 = e.getEndOffset(); + if (p0 == n0) { - // Final region will be painted as the - // old highlight region finishes first - sds = o1; - sde = n1; + ui.damageRange(textComponent, Math.min(p1, n1), + Math.max(p1, n1)); } - else + else if (n1 == p1) { - // Final region will be cleared as the - // new highlight region finishes first. - sds = n1; - sde = o1; + ui.damageRange(textComponent, Math.min(p0, n0), + Math.max(p0, n0)); } - - // If there is no undamaged region in between - // call damageRange only once. - if (fde == sds) - ui.damageRange(textComponent, fds, sde); else { - if (fds != fde) - ui.damageRange(textComponent, fds, fde); - - if (sds != sde) - ui.damageRange(textComponent, sds, sde); + ui.damageRange(textComponent, p0, p1); + ui.damageRange(textComponent, n0, n1); } + e.p0 = doc.createPosition(n0); + e.p1 = doc.createPosition(n1); } - else - { - // The two regions do not overlap. So mark - // both areas as damaged. - ui.damageRange(textComponent, o0, o1); - ui.damageRange(textComponent, n0, n1); - } - } public void paintLayeredHighlights(Graphics g, int p0, int p1, Shape viewBounds, JTextComponent editor, View view) - throws NotImplementedException { - // TODO: Implement this properly. + for (Iterator i = highlights.iterator(); i.hasNext();) + { + Object o = i.next(); + if (o instanceof LayerHighlightEntry) + { + LayerHighlightEntry entry = (LayerHighlightEntry) o; + int start = entry.getStartOffset(); + int end = entry.getEndOffset(); + if ((p0 < start && p1 > start) || (p0 >= start && p0 < end)) + entry.paintLayeredHighlight(g, p0, p1, viewBounds, editor, view); + } + } } public void paint(Graphics g) @@ -399,7 +470,9 @@ public class DefaultHighlighter extends LayeredHighlighter for (int index = 0; index < size; ++index) { HighlightEntry entry = (HighlightEntry) highlights.get(index); - entry.painter.paint(g, entry.p0, entry.p1, bounds, textComponent); + if (! (entry instanceof LayerHighlightEntry)) + entry.painter.paint(g, entry.getStartOffset(), entry.getEndOffset(), + bounds, textComponent); } } } diff --git a/javax/swing/text/DefaultStyledDocument.java b/javax/swing/text/DefaultStyledDocument.java index bd21e55c6..acc8fb6c8 100644 --- a/javax/swing/text/DefaultStyledDocument.java +++ b/javax/swing/text/DefaultStyledDocument.java @@ -41,7 +41,9 @@ package javax.swing.text; import java.awt.Color; import java.awt.Font; import java.io.Serializable; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.Iterator; import java.util.Stack; import java.util.Vector; @@ -424,6 +426,58 @@ public class DefaultStyledDocument extends AbstractDocument implements */ public class ElementBuffer implements Serializable { + /** + * Instance of all editing information for an object in the Vector. This class + * is used to add information to the DocumentEvent associated with an + * insertion/removal/change as well as to store the changes that need to be + * made so they can be made all at the same (appropriate) time. + */ + class Edit + { + /** The element to edit . */ + Element e; + + /** The index of the change. */ + int index; + + /** The removed elements. */ + ArrayList removed = new ArrayList(); + + /** The added elements. */ + ArrayList added = new ArrayList(); + + /** + * Indicates if this edit contains a fracture. + */ + boolean isFracture; + + /** + * Creates a new Edit for the specified element at index i. + * + * @param el the element + * @param i the index + */ + Edit(Element el, int i) + { + this(el, i, false); + } + + /** + * Creates a new Edit for the specified element at index i. + * + * @param el the element + * @param i the index + * @param frac if this is a fracture edit or not + */ + Edit(Element el, int i, boolean frac) + { + e = el; + index = i; + isFracture = frac; + } + + } + /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 1688745877691146623L; @@ -442,11 +496,25 @@ public class DefaultStyledDocument extends AbstractDocument implements /** Holds the position of the change. */ private int pos; - /** Holds the element that was last fractured. */ - private Element lastFractured; - - /** True if a fracture was not created during a insertFracture call. */ - private boolean fracNotCreated; + /** + * The ElementChange that describes the latest changes. + */ + private DefaultDocumentEvent documentEvent; + + /** + * The parent of the fracture. + */ + private Element fracturedParent; + + /** + * The fractured child. + */ + private Element fracturedChild; + + /** + * Indicates if a fracture has been created. + */ + private boolean createdFracture; /** * The current position in the element tree. This is used for bulk inserts @@ -454,10 +522,17 @@ public class DefaultStyledDocument extends AbstractDocument implements */ private Stack elementStack; + private Edit[] insertPath; + + private boolean recreateLeafs; + /** - * The ElementChange that describes the latest changes. + * Vector that contains all the edits. Maybe replace by a HashMap. */ - DefaultDocumentEvent documentEvent; + private ArrayList edits; + + private boolean offsetLastIndex; + private boolean offsetLastIndexReplace; /** * Creates a new <code>ElementBuffer</code> for the specified @@ -469,7 +544,6 @@ public class DefaultStyledDocument extends AbstractDocument implements public ElementBuffer(Element root) { this.root = root; - elementStack = new Stack(); } /** @@ -495,13 +569,9 @@ public class DefaultStyledDocument extends AbstractDocument implements */ public void remove(int offs, int len, DefaultDocumentEvent ev) { - if (len == 0) - return; - offset = offs; - length = len; - pos = offset; - documentEvent = ev; + prepareEdit(offs, len); removeUpdate(); + finishEdit(ev); } /** @@ -511,109 +581,241 @@ public class DefaultStyledDocument extends AbstractDocument implements */ protected void removeUpdate() { - int startParagraph = root.getElementIndex(offset); - int endParagraph = root.getElementIndex(offset + length); - Element[] empty = new Element[0]; - int removeStart = -1; - int removeEnd = -1; - for (int i = startParagraph; i < endParagraph; i++) + removeElements(root, offset, endOffset); + } + + private boolean removeElements(Element elem, int rmOffs0, int rmOffs1) + { + boolean ret = false; + if (! elem.isLeaf()) { - BranchElement paragraph = (BranchElement) root.getElement(i); - int contentStart = paragraph.getElementIndex(offset); - int contentEnd = paragraph.getElementIndex(offset + length); - if (contentStart == paragraph.getStartOffset() - && contentEnd == paragraph.getEndOffset()) + // Update stack for changes. + int index0 = elem.getElementIndex(rmOffs0); + int index1 = elem.getElementIndex(rmOffs1); + elementStack.push(new Edit(elem, index0)); + Edit ec = (Edit) elementStack.peek(); + + // If the range is contained by one element, + // we just forward the request + if (index0 == index1) { - // In this case we only need to remove the whole paragraph. We - // do this in one go after this loop and only record the indices - // here. - if (removeStart == -1) + Element child0 = elem.getElement(index0); + if(rmOffs0 <= child0.getStartOffset() + && rmOffs1 >= child0.getEndOffset()) { - removeStart = i; - removeEnd = i; + // Element totally removed. + ec.removed.add(child0); + } + else if (removeElements(child0, rmOffs0, rmOffs1)) + { + ec.removed.add(child0); } - else - removeEnd = i; } else { - // In this case we remove a couple of child elements from this - // paragraph. - int removeLen = contentEnd - contentStart; - Element[] removed = new Element[removeLen]; - for (int j = contentStart; j < contentEnd; j++) - removed[j] = paragraph.getElement(j); - Edit edit = getEditForParagraphAndIndex(paragraph, contentStart); - edit.addRemovedElements(removed); + // The removal range spans elements. If we can join + // the two endpoints, do it. Otherwise we remove the + // interior and forward to the endpoints. + Element child0 = elem.getElement(index0); + Element child1 = elem.getElement(index1); + boolean containsOffs1 = (rmOffs1 < elem.getEndOffset()); + if (containsOffs1 && canJoin(child0, child1)) + { + // Remove and join. + for (int i = index0; i <= index1; i++) + { + ec.removed.add(elem.getElement(i)); + } + Element e = join(elem, child0, child1, rmOffs0, rmOffs1); + ec.added.add(e); + } + else + { + // Remove interior and forward. + int rmIndex0 = index0 + 1; + int rmIndex1 = index1 - 1; + if (child0.getStartOffset() == rmOffs0 + || (index0 == 0 && child0.getStartOffset() > rmOffs0 + && child0.getEndOffset() <= rmOffs1)) + { + // Start element completely consumed. + child0 = null; + rmIndex0 = index0; + } + if (! containsOffs1) + { + child1 = null; + rmIndex1++; + } + else if (child1.getStartOffset() == rmOffs1) + { + // End element not touched. + child1 = null; + } + if (rmIndex0 <= rmIndex1) + { + ec.index = rmIndex0; + } + for (int i = rmIndex0; i <= rmIndex1; i++) + { + ec.removed.add(elem.getElement(i)); + } + if (child0 != null) + { + if(removeElements(child0, rmOffs0, rmOffs1)) + { + ec.removed.add(0, child0); + ec.index = index0; + } + } + if (child1 != null) + { + if(removeElements(child1, rmOffs0, rmOffs1)) + { + ec.removed.add(child1); + } + } } + } + + // Perform changes. + pop(); + + // Return true if we no longer have any children. + if(elem.getElementCount() == (ec.removed.size() - ec.added.size())) + ret = true; } - // Now we remove paragraphs from the root that have been tagged for - // removal. - if (removeStart != -1) + return ret; + } + + private boolean canJoin(Element e0, Element e1) + { + boolean ret = false; + if ((e0 != null) && (e1 != null)) { - int removeLen = removeEnd - removeStart; - Element[] removed = new Element[removeLen]; - for (int i = removeStart; i < removeEnd; i++) - removed[i] = root.getElement(i); - Edit edit = getEditForParagraphAndIndex((BranchElement) root, - removeStart); - edit.addRemovedElements(removed); + // Don't join a leaf to a branch. + boolean isLeaf0 = e0.isLeaf(); + boolean isLeaf1 = e1.isLeaf(); + if(isLeaf0 == isLeaf1) + { + if (isLeaf0) + { + // Only join leaves if the attributes match, otherwise + // style information will be lost. + ret = e0.getAttributes().isEqual(e1.getAttributes()); + } + else + { + // Only join non-leafs if the names are equal. This may result + // in loss of style information, but this is typically + // acceptable for non-leafs. + String name0 = e0.getName(); + String name1 = e1.getName(); + if (name0 != null) + ret = name0.equals(name1); + else if (name1 != null) + ret = name1.equals(name0); + else // Both names null. + ret = true; + } + } } + return ret; } - /** - * Performs the actual work for {@link #change}. The elements at the - * interval boundaries are split up (if necessary) so that the interval - * boundaries are located at element boundaries. - */ - protected void changeUpdate() + private Element join(Element p, Element left, Element right, int rmOffs0, + int rmOffs1) { - // Split up the element at the start offset if necessary. - Element el = getCharacterElement(offset); - Element[] res = split(el, offset, 0, el.getElementIndex(offset)); - BranchElement par = (BranchElement) el.getParentElement(); - int index = par.getElementIndex(offset); - Edit edit = getEditForParagraphAndIndex(par, index); - if (res[1] != null) + Element joined = null; + if (left.isLeaf() && right.isLeaf()) + { + joined = createLeafElement(p, left.getAttributes(), + left.getStartOffset(), + right.getEndOffset()); + } + else if ((! left.isLeaf()) && (! right.isLeaf())) { - Element[] removed; - Element[] added; - if (res[0] == null) + // Join two branch elements. This copies the children before + // the removal range on the left element, and after the removal + // range on the right element. The two elements on the edge + // are joined if possible and needed. + joined = createBranchElement(p, left.getAttributes()); + int ljIndex = left.getElementIndex(rmOffs0); + int rjIndex = right.getElementIndex(rmOffs1); + Element lj = left.getElement(ljIndex); + if (lj.getStartOffset() >= rmOffs0) + { + lj = null; + } + Element rj = right.getElement(rjIndex); + if (rj.getStartOffset() == rmOffs1) + { + rj = null; + } + ArrayList children = new ArrayList(); + // Transfer the left. + for (int i = 0; i < ljIndex; i++) + { + children.add(clone(joined, left.getElement(i))); + } + + // Transfer the join/middle. + if (canJoin(lj, rj)) { - removed = new Element[0]; - added = new Element[] { res[1] }; - index++; + Element e = join(joined, lj, rj, rmOffs0, rmOffs1); + children.add(e); } else { - removed = new Element[] { el }; - added = new Element[] { res[0], res[1] }; + if (lj != null) + { + children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1)); + } + if (rj != null) + { + children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1)); + } + } + + // Transfer the right. + int n = right.getElementCount(); + for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) + { + children.add(clone(joined, right.getElement(i))); } - edit.addRemovedElements(removed); - edit.addAddedElements(added); + // Install the children. + Element[] c = new Element[children.size()]; + c = (Element[]) children.toArray(c); + ((BranchElement) joined).replace(0, 0, c); + } + else + { + assert false : "Must not happen"; } + return joined; + } - int endOffset = offset + length; - el = getCharacterElement(endOffset); - res = split(el, endOffset, 0, el.getElementIndex(endOffset)); - par = (BranchElement) el.getParentElement(); - if (res[0] != null) + /** + * Performs the actual work for {@link #change}. The elements at the + * interval boundaries are split up (if necessary) so that the interval + * boundaries are located at element boundaries. + */ + protected void changeUpdate() + { + boolean didEnd = split(offset, length); + if (! didEnd) { - Element[] removed; - Element[] added; - if (res[1] == null) - { - removed = new Element[0]; - added = new Element[] { res[1] }; - } - else + // need to do the other end + while (elementStack.size() != 0) { - removed = new Element[] { el }; - added = new Element[] { res[0], res[1] }; + pop(); } - edit.addRemovedElements(removed); - edit.addAddedElements(added); + split(offset + length, 0); + } + while (elementStack.size() != 0) + { + pop(); } } @@ -683,6 +885,39 @@ public class DefaultStyledDocument extends AbstractDocument implements return clone; } + private Element cloneAsNecessary(Element parent, Element clonee, + int rmOffs0, int rmOffs1) + { + Element cloned; + if (clonee.isLeaf()) + { + cloned = createLeafElement(parent, clonee.getAttributes(), + clonee.getStartOffset(), + clonee.getEndOffset()); + } + else + { + Element e = createBranchElement(parent, clonee.getAttributes()); + int n = clonee.getElementCount(); + ArrayList childrenList = new ArrayList(n); + for (int i = 0; i < n; i++) + { + Element elem = clonee.getElement(i); + if (elem.getStartOffset() < rmOffs0 + || elem.getEndOffset() > rmOffs1) + { + childrenList.add(cloneAsNecessary(e, elem, rmOffs0, + rmOffs1)); + } + } + Element[] children = new Element[childrenList.size()]; + children = (Element[]) childrenList.toArray(children); + ((BranchElement) e).replace(0, 0, children); + cloned = e; + } + return cloned; + } + /** * Inserts new <code>Element</code> in the document at the specified * position. Most of the work is done by {@link #insertUpdate}, after some @@ -701,70 +936,98 @@ public class DefaultStyledDocument extends AbstractDocument implements public void insert(int offset, int length, ElementSpec[] data, DefaultDocumentEvent ev) { - if (length == 0) - return; - + if (length > 0) + { + prepareEdit(offset, length); + insertUpdate(data); + finishEdit(ev); + } + } + + /** + * Prepares the state of this object for performing an insert. + * + * @param offset the offset at which is inserted + * @param length the length of the inserted region + */ + private void prepareEdit(int offset, int length) + { this.offset = offset; this.pos = offset; this.endOffset = offset + length; this.length = length; - documentEvent = ev; - - edits.removeAllElements(); - elementStack.removeAllElements(); - lastFractured = null; - fracNotCreated = false; - insertUpdate(data); + + if (edits == null) + edits = new ArrayList(); + else + edits.clear(); + + if (elementStack == null) + elementStack = new Stack(); + else + elementStack.clear(); + + fracturedParent = null; + fracturedChild = null; + offsetLastIndex = false; + offsetLastIndexReplace = false; + } + + /** + * Finishes an insert. This applies all changes and updates + * the DocumentEvent. + * + * @param ev the document event + */ + private void finishEdit(DefaultDocumentEvent ev) + { // This for loop applies all the changes that were made and updates the // DocumentEvent. - int size = edits.size(); - for (int i = 0; i < size; i++) - { - Edit curr = (Edit) edits.get(i); - BranchElement e = (BranchElement) curr.e; - Element[] removed = curr.getRemovedElements(); - Element[] added = curr.getAddedElements(); - // FIXME: We probably shouldn't create the empty Element[] in the - // first place. - if (removed.length > 0 || added.length > 0) - { - if (curr.index + removed.length <= e.getElementCount()) - { - e.replace(curr.index, removed.length, added); - ElementEdit ee = new ElementEdit(e, curr.index, removed, added); - ev.addEdit(ee); - } - else - { - System.err.println("WARNING: Tried to replace elements "); - System.err.print("beyond boundaries: elementCount: "); - System.err.println(e.getElementCount()); - System.err.print("index: " + curr.index); - System.err.println(", removed.length: " + removed.length); - } - } - } + for (Iterator i = edits.iterator(); i.hasNext();) + { + Edit edits = (Edit) i.next(); + Element[] removed = new Element[edits.removed.size()]; + removed = (Element[]) edits.removed.toArray(removed); + Element[] added = new Element[edits.added.size()]; + added = (Element[]) edits.added.toArray(added); + int index = edits.index; + BranchElement parent = (BranchElement) edits.e; + parent.replace(index, removed.length, added); + ElementEdit ee = new ElementEdit(parent, index, removed, added); + ev.addEdit(ee); + } } /** - * Inserts new content + * Inserts new content. * - * @param data - * the element specifications for the elements to be inserted + * @param data the element specifications for the elements to be inserted */ protected void insertUpdate(ElementSpec[] data) { - // Push the root and the paragraph at offset onto the element stack. + // Push the current path to the stack. Element current = root; - int index; - while (!current.isLeaf()) + int index = current.getElementIndex(offset); + while (! current.isLeaf()) { + Element child = current.getElement(index); + int editIndex = child.isLeaf() ? index : index + 1; + Edit edit = new Edit(current, editIndex); + elementStack.push(edit); + current = child; index = current.getElementIndex(offset); - elementStack.push(current); - current = current.getElement(index); } - + + // Create a copy of the original path. + insertPath = new Edit[elementStack.size()]; + insertPath = (Edit[]) elementStack.toArray(insertPath); + + // No fracture yet. + createdFracture = false; + + // Insert first content tag. int i = 0; + recreateLeafs = false; int type = data[0].getType(); if (type == ElementSpec.ContentType) { @@ -784,123 +1047,129 @@ public class DefaultStyledDocument extends AbstractDocument implements // Handle each ElementSpec individually. for (; i < data.length; i++) { - BranchElement paragraph = (BranchElement) elementStack.peek(); - switch (data[i].getType()) + insertElement(data[i]); + } + + // Fracture if we haven't done yet. + if (! createdFracture) + fracture(-1); + + // Pop the remaining stack. + while (elementStack.size() != 0) + pop(); + + // Offset last index if necessary. + if (offsetLastIndex && offsetLastIndexReplace) + insertPath[insertPath.length - 1].index++; + + // Make sure we havea an Edit for each path item that has a change. + for (int p = insertPath.length - 1; p >= 0; p--) + { + Edit edit = insertPath[p]; + if (edit.e == fracturedParent) + edit.added.add(fracturedChild); + if ((edit.added.size() > 0 || edit.removed.size() > 0) + && ! edits.contains(edit)) + edits.add(edit); + } + + // Remove element that would be created by an insert at 0 with + // an initial end tag. + if (offset == 0 && fracturedParent != null + && data[0].getType() == ElementSpec.EndTagType) + { + for (int p = 0; + p < data.length && data[p].getType() == ElementSpec.EndTagType; + p++) { - case ElementSpec.StartTagType: - switch (data[i].getDirection()) - { - case ElementSpec.JoinFractureDirection: - // Fracture the tree and ensure the appropriate element - // is on top of the stack. - fracNotCreated = false; - insertFracture(data[i]); - if (fracNotCreated) - { - if (lastFractured != null) - elementStack.push(lastFractured.getParentElement()); - else - elementStack.push(paragraph.getElement(0)); - } - break; - case ElementSpec.JoinNextDirection: - // Push the next paragraph element onto the stack so - // future insertions are added to it. - int ix = paragraph.getElementIndex(pos) + 1; - elementStack.push(paragraph.getElement(ix)); - break; - default: - Element br = null; - if (data.length > i + 1) - { - // leaves will be added to paragraph later - int x = 0; - if (paragraph.getElementCount() > 0) - x = paragraph.getElementIndex(pos) + 1; - Edit e = getEditForParagraphAndIndex(paragraph, x); - br = (BranchElement) createBranchElement(paragraph, - data[i].getAttributes()); - e.added.add(br); - elementStack.push(br); - } - else - // need to add leaves to paragraph now - br = insertParagraph(paragraph, pos); - break; - } - break; - case ElementSpec.EndTagType: - elementStack.pop(); - break; - case ElementSpec.ContentType: - insertContentTag(data[i]); - offset = pos; - break; + Edit edit = insertPath[insertPath.length - p - 1]; + edit.index--; + edit.removed.add(0, edit.e.getElement(edit.index)); } } } - - /** - * Inserts a new paragraph. - * - * @param par - - * the parent - * @param offset - - * the offset - * @return the new paragraph - */ - private Element insertParagraph(BranchElement par, int offset) + + private void pop() + { + Edit edit = (Edit) elementStack.peek(); + elementStack.pop(); + if ((edit.added.size() > 0) || (edit.removed.size() > 0)) + { + edits.add(edit); + } + else if (! elementStack.isEmpty()) + { + Element e = edit.e; + if (e.getElementCount() == 0) + { + // If we pushed a branch element that didn't get + // used, make sure its not marked as having been added. + edit = (Edit) elementStack.peek(); + edit.added.remove(e); + } + } + } + + private void insertElement(ElementSpec spec) { - int index = par.getElementIndex(offset); - Element current = par.getElement(index); - Element[] res = split(current, offset, 0, 0); - Edit e = getEditForParagraphAndIndex(par, index + 1); - Element ret; - if (res[1] != null) + Edit edit = (Edit) elementStack.peek(); + switch (spec.getType()) { - Element[] removed; - Element[] added; - if (res[0] == null) + case ElementSpec.StartTagType: + switch (spec.getDirection()) { - removed = new Element[0]; - if (res[1] instanceof BranchElement) + case ElementSpec.JoinFractureDirection: + // Fracture the tree and ensure the appropriate element + // is on top of the stack. + if (! createdFracture) { - added = new Element[] { res[1] }; - ret = res[1]; + fracture(elementStack.size() - 1); } - else + if (! edit.isFracture) { - ret = createBranchElement(par, null); - added = new Element[] { ret, res[1] }; + // If the parent isn't a fracture, then the fracture is + // in fracturedChild. + Edit newEdit = new Edit(fracturedChild, 0, true); + elementStack.push(newEdit); } - index++; - } - else - { - removed = new Element[] { current }; - if (res[1] instanceof BranchElement) + else { - ret = res[1]; - added = new Element[] { res[0], res[1] }; + // Otherwise use the parent's first child. + Element el = edit.e.getElement(0); + Edit newEdit = new Edit(el, 0, true); + elementStack.push(newEdit); } - else + break; + case ElementSpec.JoinNextDirection: + // Push the next paragraph element onto the stack so + // future insertions are added to it. + Element parent = edit.e.getElement(edit.index); + if (parent.isLeaf()) { - ret = createBranchElement(par, null); - added = new Element[] { res[0], ret, res[1] }; + if (edit.index + 1 < edit.e.getElementCount()) + parent = edit.e.getElement(edit.index + 1); + else + assert false; // Must not happen. } + elementStack.push(new Edit(parent, 0, true)); + break; + default: + Element branch = createBranchElement(edit.e, + spec.getAttributes()); + edit.added.add(branch); + elementStack.push(new Edit(branch, 0)); + break; } - - e.addAddedElements(added); - e.addRemovedElements(removed); - } - else - { - ret = createBranchElement(par, null); - e.addAddedElement(ret); + break; + case ElementSpec.EndTagType: + pop(); + break; + case ElementSpec.ContentType: + insertContentTag(spec, edit); + break; } - return ret; } - + /** * Inserts the first tag into the document. * @@ -910,67 +1179,71 @@ public class DefaultStyledDocument extends AbstractDocument implements private void insertFirstContentTag(ElementSpec[] data) { ElementSpec first = data[0]; - BranchElement paragraph = (BranchElement) elementStack.peek(); - int index = paragraph.getElementIndex(pos); - Element current = paragraph.getElement(index); - int newEndOffset = pos + first.length; + Edit edit = (Edit) elementStack.peek(); + Element current = edit.e.getElement(edit.index); + int firstEndOffset = offset + first.length; boolean onlyContent = data.length == 1; - Edit edit = getEditForParagraphAndIndex(paragraph, index); switch (first.getDirection()) { case ElementSpec.JoinPreviousDirection: - if (current.getEndOffset() != newEndOffset && !onlyContent) + if (current.getEndOffset() != firstEndOffset && ! onlyContent) { - Element newEl1 = createLeafElement(paragraph, + Element newEl1 = createLeafElement(edit.e, current.getAttributes(), current.getStartOffset(), - newEndOffset); - edit.addAddedElement(newEl1); - edit.addRemovedElement(current); - offset = newEndOffset; + firstEndOffset); + edit.added.add(newEl1); + edit.removed.add(current); + if (current.getEndOffset() != endOffset) + recreateLeafs = true; + else + offsetLastIndex = true; + } + else + { + offsetLastIndex = true; + offsetLastIndexReplace = true; } break; case ElementSpec.JoinNextDirection: - if (pos != 0) + if (offset != 0) { - Element newEl1 = createLeafElement(paragraph, + Element newEl1 = createLeafElement(edit.e, current.getAttributes(), current.getStartOffset(), - pos); - edit.addAddedElement(newEl1); - Element next = paragraph.getElement(index + 1); - + offset); + edit.added.add(newEl1); + Element next = edit.e.getElement(edit.index + 1); if (onlyContent) - newEl1 = createLeafElement(paragraph, next.getAttributes(), - pos, next.getEndOffset()); + newEl1 = createLeafElement(edit.e, next.getAttributes(), + offset, next.getEndOffset()); else { - newEl1 = createLeafElement(paragraph, next.getAttributes(), - pos, newEndOffset); - pos = newEndOffset; + newEl1 = createLeafElement(edit.e, next.getAttributes(), + offset, firstEndOffset); } - edit.addAddedElement(newEl1); - edit.addRemovedElement(current); - edit.addRemovedElement(next); + edit.added.add(newEl1); + edit.removed.add(current); + edit.removed.add(next); } break; - default: - if (current.getStartOffset() != pos) + default: // OriginateDirection. + if (current.getStartOffset() != offset) { - Element newEl = createLeafElement(paragraph, + Element newEl = createLeafElement(edit.e, current.getAttributes(), current.getStartOffset(), - pos); - edit.addAddedElement(newEl); + offset); + edit.added.add(newEl); } - edit.addRemovedElement(current); - Element newEl1 = createLeafElement(paragraph, first.getAttributes(), - pos, newEndOffset); - edit.addAddedElement(newEl1); + edit.removed.add(current); + Element newEl1 = createLeafElement(edit.e, first.getAttributes(), + offset, firstEndOffset); + edit.added.add(newEl1); if (current.getEndOffset() != endOffset) - recreateLeaves(newEndOffset, paragraph, onlyContent); + recreateLeafs = true; else - offset = newEndOffset; + offsetLastIndex = true; break; } } @@ -981,391 +1254,353 @@ public class DefaultStyledDocument extends AbstractDocument implements * @param tag - * the element spec */ - private void insertContentTag(ElementSpec tag) + private void insertContentTag(ElementSpec tag, Edit edit) { - BranchElement paragraph = (BranchElement) elementStack.peek(); int len = tag.getLength(); int dir = tag.getDirection(); AttributeSet tagAtts = tag.getAttributes(); if (dir == ElementSpec.JoinNextDirection) { - int index = paragraph.getElementIndex(pos); - Element target = paragraph.getElement(index); - Edit edit = getEditForParagraphAndIndex(paragraph, index); - - if (paragraph.getStartOffset() > pos) - { - Element first = paragraph.getElement(0); - Element newEl = createLeafElement(paragraph, - first.getAttributes(), pos, - first.getEndOffset()); - edit.addAddedElement(newEl); - edit.addRemovedElement(first); - } - else if (paragraph.getElementCount() > (index + 1) - && (pos == target.getStartOffset() && !target.equals(lastFractured))) + if (! edit.isFracture) { - Element next = paragraph.getElement(index + 1); - Element newEl = createLeafElement(paragraph, - next.getAttributes(), pos, - next.getEndOffset()); - edit.addAddedElement(newEl); - edit.addRemovedElement(next); - edit.addRemovedElement(target); + Element first = null; + if (insertPath != null) + { + for (int p = insertPath.length - 1; p >= 0; p--) + { + if (insertPath[p] == edit) + { + if (p != insertPath.length - 1) + first = edit.e.getElement(edit.index); + break; + } + } + } + if (first == null) + first = edit.e.getElement(edit.index + 1); + Element leaf = createLeafElement(edit.e, first.getAttributes(), + pos, first.getEndOffset()); + edit.added.add(leaf); + edit.removed.add(first); } else { - BranchElement parent = (BranchElement) paragraph.getParentElement(); - int i = parent.getElementIndex(pos); - BranchElement next = (BranchElement) parent.getElement(i + 1); - AttributeSet atts = tag.getAttributes(); - - if (next != null) - { - Element nextLeaf = next.getElement(0); - Edit e = getEditForParagraphAndIndex(next, 0); - Element newEl2 = createLeafElement(next, atts, pos, nextLeaf.getEndOffset()); - e.addAddedElement(newEl2); - e.addRemovedElement(nextLeaf); - } + Element first = edit.e.getElement(0); + Element leaf = createLeafElement(edit.e, first.getAttributes(), + pos, first.getEndOffset()); + edit.added.add(leaf); + edit.removed.add(first); } } else { - int end = pos + len; - Element leaf = createLeafElement(paragraph, tag.getAttributes(), pos, end); - - // Check for overlap with other leaves/branches - if (paragraph.getElementCount() > 0) - { - int index = paragraph.getElementIndex(pos); - Element target = paragraph.getElement(index); - boolean onlyContent = target.isLeaf(); - - BranchElement toRec = paragraph; - if (!onlyContent) - toRec = (BranchElement) target; - - // Check if we should place the leaf before or after target - if (pos > target.getStartOffset()) - index++; - - Edit edit = getEditForParagraphAndIndex(paragraph, index); - edit.addAddedElement(leaf); - } - else - paragraph.replace(0, 0, new Element[] { leaf }); + Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos, + pos + len); + edit.added.add(leaf); } - + pos += len; + } /** - * This method fractures the child at offset. + * This method fractures bottomost leaf in the elementStack. This + * happens when the first inserted tag is not content. * * @param data * the ElementSpecs used for the entire insertion */ private void createFracture(ElementSpec[] data) { - BranchElement paragraph = (BranchElement) elementStack.peek(); - int index = paragraph.getElementIndex(offset); - Element child = paragraph.getElement(index); - Edit edit = getEditForParagraphAndIndex(paragraph, index); - AttributeSet atts = child.getAttributes(); - + Edit edit = (Edit) elementStack.peek(); + Element child = edit.e.getElement(edit.index); if (offset != 0) { - Element newEl1 = createLeafElement(paragraph, atts, - child.getStartOffset(), offset); - edit.addAddedElement(newEl1); - edit.addRemovedElement(child); + Element newChild = createLeafElement(edit.e, child.getAttributes(), + child.getStartOffset(), offset); + edit.added.add(newChild); } + edit.removed.add(child); + if (child.getEndOffset() != endOffset) + recreateLeafs = true; + else + offsetLastIndex = true; } - /** - * Recreates a specified part of a the tree after a new leaf - * has been inserted. - * - * @param start - where to start recreating from - * @param paragraph - the paragraph to recreate - * @param onlyContent - true if this is the only content - */ - private void recreateLeaves(int start, BranchElement paragraph, boolean onlyContent) + private void fracture(int depth) { - int index = paragraph.getElementIndex(start); - Element child = paragraph.getElement(index); - AttributeSet atts = child.getAttributes(); - - if (!onlyContent) + int len = insertPath.length; + int lastIndex = -1; + boolean recreate = recreateLeafs; + Edit lastEdit = insertPath[len - 1]; + boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount(); + int deepestChangedIndex = recreate ? len : - 1; + int lastChangedIndex = len - 1; + createdFracture = true; + for (int i = len - 2; i >= 0; i--) { - BranchElement newBranch = (BranchElement) createBranchElement(paragraph, - atts); - Element newLeaf = createLeafElement(newBranch, atts, start, - child.getEndOffset()); - newBranch.replace(0, 0, new Element[] { newLeaf }); - - BranchElement parent = (BranchElement) paragraph.getParentElement(); - int parSize = parent.getElementCount(); - Edit edit = getEditForParagraphAndIndex(parent, parSize); - edit.addAddedElement(newBranch); - - int paragraphSize = paragraph.getElementCount(); - Element[] removed = new Element[paragraphSize - (index + 1)]; - int s = 0; - for (int j = index + 1; j < paragraphSize; j++) - removed[s++] = paragraph.getElement(j); - - edit = getEditForParagraphAndIndex(paragraph, index); - edit.addRemovedElements(removed); - Element[] added = recreateAfterFracture(removed, newBranch, 0, child.getEndOffset()); - newBranch.replace(1, 0, added); - - lastFractured = newLeaf; - pos = newBranch.getEndOffset(); + Edit edit = insertPath[i]; + if (edit.added.size() > 0 || i == depth) + { + lastIndex = i; + if (! recreate && childChanged) + { + recreate = true; + if (deepestChangedIndex == -1) + deepestChangedIndex = lastChangedIndex + 1; + } + } + if (! childChanged && edit.index < edit.e.getElementCount()) + { + childChanged = true; + lastChangedIndex = i; + } } - else + if (recreate) { - Element newLeaf = createLeafElement(paragraph, atts, start, - child.getEndOffset()); - Edit edit = getEditForParagraphAndIndex(paragraph, index); - edit.addAddedElement(newLeaf); + if (lastIndex == -1) + lastIndex = len - 1; + recreate(lastIndex, deepestChangedIndex); } } - - /** - * Splits an element if <code>offset</code> is not already at its - * boundary. - * - * @param el - * the Element to possibly split - * @param offset - * the offset at which to possibly split - * @param space - * the amount of space to create between the splitted parts - * @param editIndex - * the index of the edit to use - * @return An array of elements which represent the split result. This array - * has two elements, the two parts of the split. The first element - * might be null, which means that the element which should be - * splitted can remain in place. The second element might also be - * null, which means that the offset is already at an element - * boundary and the element doesn't need to be splitted. - */ - private Element[] split(Element el, int offset, int space, int editIndex) + + private void recreate(int startIndex, int endIndex) { - // If we are at an element boundary, then return an empty array. - if ((offset == el.getStartOffset() || offset == el.getEndOffset()) - && space == 0 && el.isLeaf()) - return new Element[2]; - - // If the element is an instance of BranchElement, then we - // recursivly - // call this method to perform the split. - Element[] res = new Element[2]; - if (el instanceof BranchElement) + // Recreate the element representing the inserted index. + Edit edit = insertPath[startIndex]; + Element child; + Element newChild; + int changeLength = insertPath.length; + + if (startIndex + 1 == changeLength) + child = edit.e.getElement(edit.index); + else + child = edit.e.getElement(edit.index - 1); + + if(child.isLeaf()) { - int index = el.getElementIndex(offset); - Element child = el.getElement(index); - Element[] result = split(child, offset, space, editIndex); - Element[] removed; - Element[] added; - Element[] newAdded; - - int count = el.getElementCount(); - if (result[1] != null) - { - // This is the case when we can keep the first element. - if (result[0] == null) - { - removed = new Element[count - index - 1]; - newAdded = new Element[count - index - 1]; - added = new Element[] {}; + newChild = createLeafElement(edit.e, child.getAttributes(), + Math.max(endOffset, child.getStartOffset()), + child.getEndOffset()); + } + else + { + newChild = createBranchElement(edit.e, child.getAttributes()); + } + fracturedParent = edit.e; + fracturedChild = newChild; - } - // This is the case when we may not keep the first - // element. + // Recreate all the elements to the right of the insertion point. + Element parent = newChild; + while (++startIndex < endIndex) + { + boolean isEnd = (startIndex + 1) == endIndex; + boolean isEndLeaf = (startIndex + 1) == changeLength; + + // Create the newChild, a duplicate of the elment at + // index. This isn't done if isEnd and offsetLastIndex are true + // indicating a join previous was done. + edit = insertPath[startIndex]; + + // Determine the child to duplicate, won't have to duplicate + // if at end of fracture, or offseting index. + if(isEnd) + { + if(offsetLastIndex || ! isEndLeaf) + child = null; else + child = edit.e.getElement(edit.index); + } + else + { + child = edit.e.getElement(edit.index - 1); + } + + // Duplicate it. + if(child != null) + { + if(child.isLeaf()) { - removed = new Element[count - index]; - newAdded = new Element[count - index]; - added = new Element[] { result[0] }; + newChild = createLeafElement(parent, child.getAttributes(), + Math.max(endOffset, child.getStartOffset()), + child.getEndOffset()); } - newAdded[0] = result[1]; - for (int i = index; i < count; i++) + else { - Element el2 = el.getElement(i); - int ind = i - count + removed.length; - removed[ind] = el2; - if (ind != 0) - newAdded[ind] = el2; + newChild = createBranchElement(parent, + child.getAttributes()); } - - Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex); - edit.addRemovedElements(removed); - edit.addAddedElements(added); - - BranchElement newPar = - (BranchElement) createBranchElement(el.getParentElement(), - el.getAttributes()); - newPar.replace(0, 0, newAdded); - res = new Element[] { null, newPar }; } else - { - removed = new Element[count - index]; - for (int i = index; i < count; ++i) - removed[i - index] = el.getElement(i); - - Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex); - edit.addRemovedElements(removed); - - BranchElement newPar = (BranchElement) createBranchElement(el.getParentElement(), - el.getAttributes()); - newPar.replace(0, 0, removed); - res = new Element[] { null, newPar }; + newChild = null; + + // Recreate the remaining children (there may be none). + int childrenToMove = edit.e.getElementCount() - edit.index; + Element[] children; + int moveStartIndex; + int childStartIndex = 1; + + if (newChild == null) + { + // Last part of fracture. + if (isEndLeaf) + { + childrenToMove--; + moveStartIndex = edit.index + 1; + } + else + { + moveStartIndex = edit.index; + } + childStartIndex = 0; + children = new Element[childrenToMove]; + } + else + { + if (! isEnd) + { + // Branch. + childrenToMove++; + moveStartIndex = edit.index; } + else + { + // Last leaf, need to recreate part of it. + moveStartIndex = edit.index + 1; + } + children = new Element[childrenToMove]; + children[0] = newChild; } - else if (el instanceof LeafElement) - { - BranchElement par = (BranchElement) el.getParentElement(); - Element el1 = createLeafElement(par, el.getAttributes(), - el.getStartOffset(), offset); - - Element el2 = createLeafElement(par, el.getAttributes(), - offset + space, - el.getEndOffset()); - res = new Element[] { el1, el2 }; - } - return res; + + for (int c = childStartIndex; c < childrenToMove; c++) + { + Element toMove = edit.e.getElement(moveStartIndex++); + children[c] = recreateFracturedElement(parent, toMove); + edit.removed.add(toMove); + } + ((BranchElement) parent).replace(0, 0, children); + parent = newChild; + } + } - /** - * Inserts a fracture into the document structure. - * - * @param tag - - * the element spec. - */ - private void insertFracture(ElementSpec tag) + private Element recreateFracturedElement(Element parent, Element toCopy) { - // insert the fracture at offset. - BranchElement parent = (BranchElement) elementStack.peek(); - int parentIndex = parent.getElementIndex(pos); - AttributeSet parentAtts = parent.getAttributes(); - Element toFracture = parent.getElement(parentIndex); - int parSize = parent.getElementCount(); - Edit edit = getEditForParagraphAndIndex(parent, parentIndex); - Element frac = toFracture; - int leftIns = 0; - int indexOfFrac = toFracture.getElementIndex(pos); - int size = toFracture.getElementCount(); - - // gets the leaf that falls along the fracture - frac = toFracture.getElement(indexOfFrac); - while (!frac.isLeaf()) - frac = frac.getElement(frac.getElementIndex(pos)); - - AttributeSet atts = frac.getAttributes(); - int fracStart = frac.getStartOffset(); - int fracEnd = frac.getEndOffset(); - if (pos >= fracStart && pos < fracEnd) + Element recreated; + if(toCopy.isLeaf()) { - // recreate left-side of branch and all its children before offset - // add the fractured leaves to the right branch - BranchElement rightBranch = - (BranchElement) createBranchElement(parent, parentAtts); - - // Check if left branch has already been edited. If so, we only - // need to create the right branch. - BranchElement leftBranch = null; - Element[] added = null; - if (edit.added.size() > 0 || edit.removed.size() > 0) + recreated = createLeafElement(parent, toCopy.getAttributes(), + Math.max(toCopy.getStartOffset(), endOffset), + toCopy.getEndOffset()); + } + else + { + Element newParent = createBranchElement(parent, + toCopy.getAttributes()); + int childCount = toCopy.getElementCount(); + Element[] newChildren = new Element[childCount]; + for (int i = 0; i < childCount; i++) { - added = new Element[] { rightBranch }; - - // don't try to remove left part of tree - parentIndex++; + newChildren[i] = recreateFracturedElement(newParent, + toCopy.getElement(i)); } - else - { - leftBranch = - (BranchElement) createBranchElement(parent, parentAtts); - added = new Element[] { leftBranch, rightBranch }; + ((BranchElement) newParent).replace(0, 0, newChildren); + recreated = newParent; + } + return recreated; + } - // add fracture to leftBranch - if (fracStart != pos) - { - Element leftFracturedLeaf = - createLeafElement(leftBranch, atts, fracStart, pos); - leftBranch.replace(leftIns, 0, - new Element[] { leftFracturedLeaf }); - } - } + private boolean split(int offs, int len) + { + boolean splitEnd = false; + // Push the path to the stack. + Element e = root; + int index = e.getElementIndex(offs); + while (! e.isLeaf()) + { + elementStack.push(new Edit(e, index)); + e = e.getElement(index); + index = e.getElementIndex(offs); + } - if (!toFracture.isLeaf()) + Edit ec = (Edit) elementStack.peek(); + Element child = ec.e.getElement(ec.index); + // Make sure there is something to do. If the + // offset is already at a boundary then there is + // nothing to do. + if (child.getStartOffset() < offs && offs < child.getEndOffset()) + { + // We need to split, now see if the other end is within + // the same parent. + int index0 = ec.index; + int index1 = index0; + if (((offs + len) < ec.e.getEndOffset()) && (len != 0)) { - // add all non-fracture elements to the branches - if (indexOfFrac > 0 && leftBranch != null) + // It's a range split in the same parent. + index1 = ec.e.getElementIndex(offs+len); + if (index1 == index0) { - Element[] add = new Element[indexOfFrac]; - for (int i = 0; i < indexOfFrac; i++) - add[i] = toFracture.getElement(i); - leftIns = add.length; - leftBranch.replace(0, 0, add); + // It's a three-way split. + ec.removed.add(child); + e = createLeafElement(ec.e, child.getAttributes(), + child.getStartOffset(), offs); + ec.added.add(e); + e = createLeafElement(ec.e, child.getAttributes(), + offs, offs + len); + ec.added.add(e); + e = createLeafElement(ec.e, child.getAttributes(), + offs + len, child.getEndOffset()); + ec.added.add(e); + return true; } - - int count = size - indexOfFrac - 1; - if (count > 0) + else { - Element[] add = new Element[count]; - int j = 0; - int i = indexOfFrac + 1; - while (j < count) - add[j++] = toFracture.getElement(i++); - rightBranch.replace(0, 0, add); + child = ec.e.getElement(index1); + if ((offs + len) == child.getStartOffset()) + { + // End is already on a boundary. + index1 = index0; + } } + splitEnd = true; } - - // add to fracture to rightBranch - // Check if we can join the right frac leaf with the next leaf - int rm = 0; - int end = fracEnd; - Element next = rightBranch.getElement(0); - if (next != null && next.isLeaf() - && next.getAttributes().isEqual(atts)) + + // Split the first location. + pos = offs; + child = ec.e.getElement(index0); + ec.removed.add(child); + e = createLeafElement(ec.e, child.getAttributes(), + child.getStartOffset(), pos); + ec.added.add(e); + e = createLeafElement(ec.e, child.getAttributes(), + pos, child.getEndOffset()); + ec.added.add(e); + + // Pick up things in the middle. + for (int i = index0 + 1; i < index1; i++) { - end = next.getEndOffset(); - rm = 1; + child = ec.e.getElement(i); + ec.removed.add(child); + ec.added.add(child); } - Element rightFracturedLeaf = createLeafElement(rightBranch, atts, - pos, end); - rightBranch.replace(0, rm, new Element[] { rightFracturedLeaf }); - - // recreate those elements after parentIndex and add/remove all - // new/old elements to parent - int remove = parSize - parentIndex; - Element[] removed = new Element[0]; - Element[] added2 = new Element[0]; - if (remove > 0) + if (index1 != index0) { - removed = new Element[remove]; - int s = 0; - for (int j = parentIndex; j < parSize; j++) - removed[s++] = parent.getElement(j); - edit.addRemovedElements(removed); - added2 = recreateAfterFracture(removed, parent, 1, - rightBranch.getEndOffset()); + child = ec.e.getElement(index1); + pos = offs + len; + ec.removed.add(child); + e = createLeafElement(ec.e, child.getAttributes(), + child.getStartOffset(), pos); + ec.added.add(e); + e = createLeafElement(ec.e, child.getAttributes(), + pos, child.getEndOffset()); + + ec.added.add(e); } - - edit.addAddedElements(added); - edit.addAddedElements(added2); - elementStack.push(rightBranch); - lastFractured = rightFracturedLeaf; } - else - fracNotCreated = true; + return splitEnd; + } /** @@ -1420,190 +1655,6 @@ public class DefaultStyledDocument extends AbstractDocument implements } } - /** - * This method looks through the Vector of Edits to see if there is already an - * Edit object associated with the given paragraph. If there is, then we - * return it. Otherwise we create a new Edit object, add it to the vector, and - * return it. Note: this method is package private to avoid accessors. - * - * @param index - * the index associated with the Edit we want to create - * @param para - * the paragraph associated with the Edit we want - * @return the found or created Edit object - */ - Edit getEditForParagraphAndIndex(BranchElement para, int index) - { - Edit curr; - int size = edits.size(); - for (int i = 0; i < size; i++) - { - curr = (Edit) edits.elementAt(i); - if (curr.e.equals(para)) - return curr; - } - curr = new Edit(para, index, null, null); - edits.add(curr); - - return curr; - } - /** - * Instance of all editing information for an object in the Vector. This class - * is used to add information to the DocumentEvent associated with an - * insertion/removal/change as well as to store the changes that need to be - * made so they can be made all at the same (appropriate) time. - */ - class Edit - { - /** The element to edit . */ - Element e; - - /** The index of the change. */ - int index; - - /** The removed elements. */ - Vector removed = new Vector(); - - /** The added elements. */ - Vector added = new Vector(); - - /** - * Return an array containing the Elements that have been removed from the - * paragraph associated with this Edit. - * - * @return an array of removed Elements - */ - public Element[] getRemovedElements() - { - int size = removed.size(); - Element[] removedElements = new Element[size]; - for (int i = 0; i < size; i++) - removedElements[i] = (Element) removed.elementAt(i); - return removedElements; - } - - /** - * Return an array containing the Elements that have been added to the - * paragraph associated with this Edit. - * - * @return an array of added Elements - */ - public Element[] getAddedElements() - { - int size = added.size(); - Element[] addedElements = new Element[size]; - for (int i = 0; i < size; i++) - addedElements[i] = (Element) added.elementAt(i); - return addedElements; - } - - /** - * Checks if e is already in the vector. - * - * @param e - the Element to look for - * @param v - the vector to search - * @return true if e is in v. - */ - private boolean contains(Vector v, Element e) - { - if (e == null) - return false; - - int i = v.size(); - for (int j = 0; j < i; j++) - { - Element e1 = (Element) v.get(j); - if ((e1 != null) && (e1.getAttributes().isEqual(e.getAttributes())) - && (e1.getName().equals(e.getName())) - && (e1.getStartOffset() == e.getStartOffset()) - && (e1.getEndOffset() == e.getEndOffset()) - && (e1.getParentElement().equals(e.getParentElement())) - && (e1.getElementCount() == e.getElementCount())) - return true; - } - return false; - } - - /** - * Adds one Element to the vector of removed Elements. - * - * @param e - * the Element to add - */ - public void addRemovedElement(Element e) - { - if (!contains(removed, e)) - removed.add(e); - } - - /** - * Adds each Element in the given array to the vector of removed Elements - * - * @param e - * the array containing the Elements to be added - */ - public void addRemovedElements(Element[] e) - { - if (e == null || e.length == 0) - return; - for (int i = 0; i < e.length; i++) - { - if (!contains(removed, e[i])) - removed.add(e[i]); - } - } - - /** - * Adds one Element to the vector of added Elements. - * - * @param e - * the Element to add - */ - public void addAddedElement(Element e) - { - if (!contains(added, e)) - added.add(e); - } - - /** - * Adds each Element in the given array to the vector of added Elements. - * - * @param e - * the array containing the Elements to be added - */ - public void addAddedElements(Element[] e) - { - if (e == null || e.length == 0) - return; - for (int i = 0; i < e.length; i++) - { - if (!contains(added, e[i])) - added.add(e[i]); - } - } - - /** - * Creates a new Edit object with the given parameters - * - * @param e - * the paragraph Element associated with this Edit - * @param i - * the index within the paragraph where changes are started - * @param removed - * an array containing Elements that should be removed from the - * paragraph Element - * @param added - * an array containing Elements that should be added to the - * paragraph Element - */ - public Edit(Element e, int i, Element[] removed, Element[] added) - { - this.e = e; - this.index = i; - addRemovedElements(removed); - addAddedElements(added); - } - } /** * An element type for sections. This is a simple BranchElement with a unique @@ -1674,11 +1725,6 @@ public class DefaultStyledDocument extends AbstractDocument implements private StyleChangeListener styleChangeListener; /** - * Vector that contains all the edits. Maybe replace by a HashMap. - */ - Vector edits = new Vector(); - - /** * Creates a new <code>DefaultStyledDocument</code>. */ public DefaultStyledDocument() @@ -2079,147 +2125,220 @@ public class DefaultStyledDocument extends AbstractDocument implements */ protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr) { - super.insertUpdate(ev, attr); - // If the attribute set is null, use an empty attribute set. + int offs = ev.getOffset(); + int len = ev.getLength(); + int endOffs = offs + len; if (attr == null) attr = SimpleAttributeSet.EMPTY; - int offset = ev.getOffset(); - int length = ev.getLength(); - int endOffset = offset + length; - AttributeSet paragraphAttributes = getParagraphElement(endOffset).getAttributes(); - Segment txt = new Segment(); + + // Paragraph attributes are fetched from the point _after_ the insertion. + Element paragraph = getParagraphElement(endOffs); + AttributeSet pAttr = paragraph.getAttributes(); + // Character attributes are fetched from the actual insertion point. + Element paragraph2 = getParagraphElement(offs); + int contIndex = paragraph2.getElementIndex(offs); + Element content = paragraph2.getElement(contIndex); + AttributeSet cAttr = content.getAttributes(); + + boolean insertAtBoundary = content.getEndOffset() == endOffs; try { - getText(offset, length, txt); - } - catch (BadLocationException ex) - { - AssertionError ae = new AssertionError("Unexpected bad location"); - ae.initCause(ex); - throw ae; - } + Segment s = new Segment(); + ArrayList buf = new ArrayList(); + ElementSpec lastStartTag = null; + boolean insertAfterNewline = false; + short lastStartDir = ElementSpec.OriginateDirection; + + // Special handle if we are inserting after a newline. + if (offs > 0) + { + getText(offs - 1, 1, s); + if (s.array[s.offset] == '\n') + { + insertAfterNewline = true; + lastStartDir = insertAfterNewline(paragraph, paragraph2, + pAttr, buf, offs, + endOffs); + // Search last start tag. + for (int i = buf.size() - 1; i >= 0 && lastStartTag == null; + i--) + { + ElementSpec tag = (ElementSpec) buf.get(i); + if (tag.getType() == ElementSpec.StartTagType) + { + lastStartTag = tag; + } + } + } - int len = 0; - Vector specs = new Vector(); - ElementSpec finalStartTag = null; - short finalStartDirection = ElementSpec.OriginateDirection; - boolean prevCharWasNewline = false; - Element prev = getCharacterElement(offset); - Element next = getCharacterElement(endOffset); - Element prevParagraph = getParagraphElement(offset); - Element paragraph = getParagraphElement(endOffset); + } - int segmentEnd = txt.offset + txt.count; + // If we are not inserting after a newline, the paragraph attributes + // come from the paragraph under the insertion point. + if (! insertAfterNewline) + pAttr = paragraph2.getAttributes(); - // Check to see if we're inserting immediately after a newline. - if (offset > 0) - { - try + // Scan text and build up the specs. + getText(offs, len, s); + int end = s.offset + s.count; + int last = s.offset; + for (int i = s.offset; i < end; i++) { - String s = getText(offset - 1, 1); - if (s.equals("\n")) + if (s.array[i] == '\n') { - finalStartDirection = handleInsertAfterNewline(specs, offset, - endOffset, - prevParagraph, - paragraph, - paragraphAttributes); - - prevCharWasNewline = true; - // Find the final start tag from the ones just created. - for (int i = 0; i < specs.size(); i++) - if (((ElementSpec) specs.get(i)).getType() == ElementSpec.StartTagType) - finalStartTag = (ElementSpec) specs.get(i); + int breakOffs = i + 1; + buf.add(new ElementSpec(attr, ElementSpec.ContentType, + breakOffs - last)); + buf.add(new ElementSpec(null, ElementSpec.EndTagType)); + lastStartTag = new ElementSpec(pAttr, + ElementSpec.StartTagType); + buf.add(lastStartTag); + last = breakOffs; } } - catch (BadLocationException ble) + + // Need to add a tailing content tag if we didn't finish at a boundary. + if (last < end) { - // This shouldn't happen. - AssertionError ae = new AssertionError(); - ae.initCause(ble); - throw ae; + buf.add(new ElementSpec(attr, ElementSpec.ContentType, + end - last)); } - } - for (int i = txt.offset; i < segmentEnd; ++i) - { - len++; - if (txt.array[i] == '\n') + // Now we need to fix up the directions of the specs. + ElementSpec first = (ElementSpec) buf.get(0); + int doclen = getLength(); + + // Maybe join-previous the first tag if it is content and has + // the same attributes as the previous character run. + if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr)) + first.setDirection(ElementSpec.JoinPreviousDirection); + + // Join-fracture or join-next the last start tag if necessary. + if (lastStartTag != null) + { + if (insertAfterNewline) + lastStartTag.setDirection(lastStartDir); + else if (paragraph2.getEndOffset() != endOffs) + lastStartTag.setDirection(ElementSpec.JoinFractureDirection); + else + { + Element par = paragraph2.getParentElement(); + int par2Index = par.getElementIndex(offs); + if (par2Index + 1 < par.getElementCount() + && ! par.getElement(par2Index + 1).isLeaf()) + lastStartTag.setDirection(ElementSpec.JoinNextDirection); + } + } + + // Join-next last tag if possible. + if (insertAtBoundary && endOffs < doclen) { - // Add the ElementSpec for the content. - specs.add(new ElementSpec(attr, ElementSpec.ContentType, len)); - - // Add ElementSpecs for the newline. - specs.add(new ElementSpec(null, ElementSpec.EndTagType)); - finalStartTag = new ElementSpec(paragraphAttributes, - ElementSpec.StartTagType); - specs.add(finalStartTag); - len = 0; + ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1); + if (lastTag.getType() == ElementSpec.ContentType + && ((lastStartTag == null + && (paragraph == paragraph2 || insertAfterNewline)) + || (lastStartTag != null + && lastStartTag.getDirection() != ElementSpec.OriginateDirection))) + { + int nextIndex = paragraph.getElementIndex(endOffs); + Element nextRun = paragraph.getElement(nextIndex); + if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes())) + lastTag.setDirection(ElementSpec.JoinNextDirection); + } + } + + else if (! insertAtBoundary && lastStartTag != null + && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection) + { + ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1); + if (lastTag.getType() == ElementSpec.ContentType + && lastTag.getDirection() != ElementSpec.JoinPreviousDirection + && attr.isEqual(cAttr)) + { + lastTag.setDirection(ElementSpec.JoinNextDirection); + } } - } - // Create last element if last character hasn't been a newline. - if (len > 0) - specs.add(new ElementSpec(attr, ElementSpec.ContentType, len)); + ElementSpec[] specs = new ElementSpec[buf.size()]; + specs = (ElementSpec[]) buf.toArray(specs); + buffer.insert(offs, len, specs, ev); + } + catch (BadLocationException ex) + { + // Ignore this. Comment out for debugging. + ex.printStackTrace(); + } + super.insertUpdate(ev, attr); + } - // Set the direction of the last spec of type StartTagType. - // If we are inserting after a newline then this value comes from - // handleInsertAfterNewline. - if (finalStartTag != null) + private short insertAfterNewline(Element par1, Element par2, + AttributeSet attr, ArrayList buf, + int offs, int endOffs) + { + short dir = 0; + if (par1.getParentElement() == par2.getParentElement()) { - if (prevCharWasNewline) - finalStartTag.setDirection(finalStartDirection); - else if (prevParagraph.getEndOffset() != endOffset) - finalStartTag.setDirection(ElementSpec.JoinFractureDirection); + ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType); + buf.add(tag); + tag = new ElementSpec(attr, ElementSpec.StartTagType); + buf.add(tag); + if (par2.getEndOffset() != endOffs) + dir = ElementSpec.JoinFractureDirection; else { - // If there is an element AFTER this one, then set the - // direction to JoinNextDirection. - Element parent = prevParagraph.getParentElement(); - int index = parent.getElementIndex(offset); - if (index + 1 < parent.getElementCount() - && !parent.getElement(index + 1).isLeaf()) - finalStartTag.setDirection(ElementSpec.JoinNextDirection); + Element par = par2.getParentElement(); + if (par.getElementIndex(offs) + 1 < par.getElementCount()) + dir = ElementSpec.JoinNextDirection; } } - - // If we are at the last index, then check if we could probably be - // joined with the next element. - // This means: - // - we must be a ContentTag - // - if there is a next Element, we must have the same attributes - // - if there is no next Element, but one will be created, - // we must have the same attributes as the higher-level run. - ElementSpec last = (ElementSpec) specs.lastElement(); - if (last.getType() == ElementSpec.ContentType) + else { - Element currentRun = prevParagraph.getElement(prevParagraph.getElementIndex(offset)); - if (currentRun.getEndOffset() == endOffset) + // For text with more than 2 levels, find the common parent of + // par1 and par2. + ArrayList parentsLeft = new ArrayList(); + ArrayList parentsRight = new ArrayList(); + Element e = par2; + while (e != null) { - if (endOffset < getLength() && next.getAttributes().isEqual(attr) - && last.getType() == ElementSpec.ContentType) - last.setDirection(ElementSpec.JoinNextDirection); + parentsLeft.add(e); + e = e.getParentElement(); } - else + e = par1; + int leftIndex = -1; + while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1) + { + parentsRight.add(e); + e = e.getParentElement(); + } + + if (e != null) + { - if (finalStartTag != null - && finalStartTag.getDirection() == ElementSpec.JoinFractureDirection - && currentRun.getAttributes().isEqual(attr)) + // e is now the common parent. + // Insert the end tags. + for (int c = 0; c < leftIndex; c++) + { + buf.add(new ElementSpec(null, ElementSpec.EndTagType)); + } + // Insert the start tags. + for (int c = parentsRight.size() - 1; c >= 0; c--) { - last.setDirection(ElementSpec.JoinNextDirection); + Element el = (Element) parentsRight.get(c); + ElementSpec tag = new ElementSpec(el.getAttributes(), + ElementSpec.StartTagType); + if (c > 0) + tag.setDirection(ElementSpec.JoinNextDirection); + buf.add(tag); } + if (parentsRight.size() > 0) + dir = ElementSpec.JoinNextDirection; + else + dir = ElementSpec.JoinFractureDirection; } + else + assert false; } - - // If we are at the first new element, then check if it could be - // joined with the previous element. - ElementSpec first = (ElementSpec) specs.firstElement(); - if (prev.getAttributes().isEqual(attr) - && first.getType() == ElementSpec.ContentType) - first.setDirection(ElementSpec.JoinPreviousDirection); - - ElementSpec[] elSpecs = (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]); - buffer.insert(offset, length, elSpecs, ev); + return dir; } /** diff --git a/javax/swing/text/EmptyAttributeSet.java b/javax/swing/text/EmptyAttributeSet.java new file mode 100644 index 000000000..98fb8828c --- /dev/null +++ b/javax/swing/text/EmptyAttributeSet.java @@ -0,0 +1,153 @@ +/* EmptyAttributeSet.java -- An empty attribute set + 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.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * An immutable, empty attribute set. + * + * @see SimpleAttributeSet#EMPTY + * + * @author Roman Kennke (kennke@aicas.com) + */ +final class EmptyAttributeSet + implements AttributeSet +{ + + /** + * Always return false as this AttributeSet doesn't contain any attributes. + */ + public boolean containsAttribute(Object name, Object value) + { + return false; + } + + /** + * Return true only if the attributes argument also contains no attributes. + */ + public boolean containsAttributes(AttributeSet attributes) + { + return attributes.getAttributeCount() == 0; + } + + /** + * Return this, as this is immutable. + */ + public AttributeSet copyAttributes() + { + return this; + } + + /** + * Always return null as this AttributeSet doesn't contain any attributes. + */ + public Object getAttribute(Object key) + { + return null; + } + + /** + * Always return 0. + */ + public int getAttributeCount() + { + return 0; + } + + /** + * Returns an empty Enumeration. + */ + public Enumeration getAttributeNames() + { + return new Enumeration() + { + public boolean hasMoreElements() + { + return false; + } + + public Object nextElement() + { + throw new NoSuchElementException("No more elements"); + } + + }; + } + + /** + * Always return null as this has no resolve parent. + */ + public AttributeSet getResolveParent() + { + return null; + } + + /** + * Always return false as this AttributeSet doesn't contain any attributes. + */ + public boolean isDefined(Object attrName) + { + return false; + } + + /** + * Other attribute sets are equal if they are empty too. + */ + public boolean isEqual(AttributeSet attr) + { + return attr.getAttributeCount() == 0; + } + + /** + * Other objects are equal if it's the same instance as this, or if + * it's another attribute set without attributes. + */ + public boolean equals(Object o) + { + boolean eq = o == this; + if (! eq) + { + eq = (o instanceof AttributeSet) + && ((AttributeSet) o).getAttributeCount() == 0; + } + return eq; + } +} diff --git a/javax/swing/text/FlowView.java b/javax/swing/text/FlowView.java index 89fcc6fcd..3de95ed7f 100644 --- a/javax/swing/text/FlowView.java +++ b/javax/swing/text/FlowView.java @@ -524,6 +524,7 @@ public abstract class FlowView extends BoxView */ public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf) { + layoutPool.removeUpdate(changes, a, vf); strategy.removeUpdate(this, changes, getInsideAllocation(a)); layoutDirty = true; } @@ -539,6 +540,7 @@ public abstract class FlowView extends BoxView */ public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf) { + layoutPool.changedUpdate(changes, a, vf); strategy.changedUpdate(this, changes, getInsideAllocation(a)); layoutDirty = true; } @@ -597,12 +599,14 @@ public abstract class FlowView extends BoxView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { - // We need to call super here so that the alignment is properly - // calculated. - SizeRequirements res = super.calculateMinorAxisRequirements(axis, r); + SizeRequirements res = r; + if (res == null) + res = new SizeRequirements(); res.minimum = (int) layoutPool.getMinimumSpan(axis); - res.preferred = (int) layoutPool.getPreferredSpan(axis); - res.maximum = (int) layoutPool.getMaximumSpan(axis); + res.preferred = Math.max(res.minimum, + (int) layoutPool.getMinimumSpan(axis)); + res.maximum = Integer.MAX_VALUE; + res.alignment = 0.5F; return res; } } diff --git a/javax/swing/text/GapContent.java b/javax/swing/text/GapContent.java index eacd29c7b..7b1502777 100644 --- a/javax/swing/text/GapContent.java +++ b/javax/swing/text/GapContent.java @@ -195,10 +195,52 @@ public class GapContent } } + /** + * 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.getOffset(); + } + + /** + * Resets the position of the mark to the value that it had when + * creating this UndoPosRef. + */ + void reset() + { + if (undoOffset <= gapStart) + mark.mark = undoOffset; + else + mark.mark = (gapEnd - gapStart) + undoOffset; + } + } + private class InsertUndo extends AbstractUndoableEdit { public int where, length; String text; + private Vector positions; + public InsertUndo(int start, int len) { where = start; @@ -209,27 +251,33 @@ public class GapContent { super.undo(); try - { - text = getString(where, length); - remove(where, length); - } + { + positions = getPositionsInRange(null, where, length); + text = getString(where, length); + remove(where, length); + } catch (BadLocationException ble) - { - throw new CannotUndoException(); - } + { + throw new CannotUndoException(); + } } public void redo () throws CannotUndoException { super.redo(); try - { - insertString(where, text); - } + { + insertString(where, text); + if (positions != null) + { + updateUndoPositions(positions, where, length); + positions = null; + } + } catch (BadLocationException ble) - { - throw new CannotRedoException(); - } + { + throw new CannotRedoException(); + } } } @@ -238,10 +286,17 @@ public class GapContent { public int where; String text; + + /** + * The positions in the removed range. + */ + private Vector positions; + public UndoRemove(int start, String removedText) { where = start; text = removedText; + positions = getPositionsInRange(null, start, removedText.length()); } public void undo () throws CannotUndoException @@ -250,6 +305,8 @@ public class GapContent try { insertString(where, text); + if (positions != null) + updateUndoPositions(positions, where, text.length()); } catch (BadLocationException ble) { @@ -261,13 +318,15 @@ public class GapContent { super.redo(); try - { - remove(where, text.length()); - } + { + text = getString(where, text.length()); + positions = getPositionsInRange(null, where, text.length()); + remove(where, text.length()); + } catch (BadLocationException ble) - { - throw new CannotRedoException(); - } + { + throw new CannotRedoException(); + } } } @@ -403,9 +462,10 @@ public class GapContent throw new BadLocationException("The where argument cannot be greater" + " than the content length", where); + InsertUndo undo = new InsertUndo(where, strLen); replace(where, 0, str.toCharArray(), strLen); - return new InsertUndo(where, strLen); + return undo; } /** @@ -429,9 +489,10 @@ public class GapContent + " than the content length", where + nitems); String removedText = getString(where, nitems); + UndoRemove undoRemove = new UndoRemove(where, removedText); replace(where, nitems, null, 0); - return new UndoRemove(where, removedText); + return undoRemove; } /** @@ -492,30 +553,46 @@ public class GapContent if ((where + len) > length) throw new BadLocationException("len plus where cannot be greater" + " than the content length", len + where); + if (len < 0) + throw new BadLocationException("negative length not allowed: ", len); - // check if requested segment is contiguous - if ((where < gapStart) && ((gapStart - where) < len)) - { - // requested segment is not contiguous -> copy the pieces together - char[] copy = new char[len]; - int lenFirst = gapStart - where; // the length of the first segment - System.arraycopy(buffer, where, copy, 0, lenFirst); - System.arraycopy(buffer, gapEnd, copy, lenFirst, len - lenFirst); - txt.array = copy; - txt.offset = 0; - txt.count = len; - } - else - { - // requested segment is contiguous -> we can simply return the - // actual content - txt.array = buffer; - if (where < gapStart) + // Optimized to copy only when really needed. + if (where + len <= gapStart) + { + // Simple case: completely before gap. + txt.array = buffer; txt.offset = where; - else - txt.offset = where + (gapEnd - gapStart); - txt.count = len; - } + txt.count = len; + } + else if (where > gapStart) + { + // Completely after gap, adjust offset. + txt.array = buffer; + txt.offset = gapEnd + where - gapStart; + txt.count = len; + } + else + { + // Spans the gap. + int beforeGap = gapStart - where; + if (txt.isPartialReturn()) + { + // Return the part before the gap when partial return is allowed. + txt.array = buffer; + txt.offset = where; + txt.count = beforeGap; + } + else + { + // Copy pieces together otherwise. + txt.array = new char[len]; + txt.offset = 0; + System.arraycopy(buffer, where, txt.array, 0, beforeGap); + System.arraycopy(buffer, gapEnd, txt.array, beforeGap, + len - beforeGap); + txt.count = len; + } + } } /** @@ -530,8 +607,10 @@ public class GapContent */ public Position createPosition(final int offset) throws BadLocationException { - if (offset < 0 || offset > length()) - throw new BadLocationException("Position offset out of bounds", offset); + // Implementation note: We used to perform explicit check on the offset + // here. However, this makes some Mauve and Intel/Harmony tests fail + // and luckily enough the GapContent can very well deal with offsets + // outside the buffer bounds. So I removed that check. // We try to find a GapContentPosition at the specified offset and return // that. Otherwise we must create a new one. @@ -572,7 +651,7 @@ public class GapContent int delta = newSize - gapEnd + gapStart; // Update the marks after the gapEnd. - adjustPositionsInRange(gapEnd, buffer.length, delta); + adjustPositionsInRange(gapEnd, -1, delta); // Copy the data around. char[] newBuf = (char[]) allocateArray(length() + newSize); @@ -732,8 +811,6 @@ public class GapContent Vector res = v; if (res == null) res = new Vector(); - else - res.clear(); int endOffs = offset + length; @@ -742,8 +819,8 @@ public class GapContent { GapContentPosition p = (GapContentPosition) i.next(); int offs = p.getOffset(); - if (offs >= offset && offs < endOffs) - res.add(p); + if (offs >= offset && offs <= endOffs) + res.add(new UndoPosRef(p.mark)); } return res; @@ -787,7 +864,7 @@ public class GapContent * the buffer array by <code>increment</code> * * @param startOffs the start offset of the range to search - * @param endOffs the length of the range to search + * @param endOffs the length of the range to search, -1 means all to the end * @param incr the increment */ private void adjustPositionsInRange(int startOffs, int endOffs, int incr) @@ -803,9 +880,15 @@ public class GapContent startIndex = - startIndex - 1; m.mark = endOffs; - int endIndex = search(marks, m); - if (endIndex < 0) // Translate to insertion index - 1, if not found. - endIndex = - endIndex - 2; + int endIndex; + if (endOffs == -1) + endIndex = marks.size() - 1; + else + { + endIndex = search(marks, m); + if (endIndex < 0) // Translate to insertion index - 1, if not found. + endIndex = - endIndex - 2; + } // Actually adjust the marks. for (int i = startIndex; i <= endIndex; i++) { ((Mark) marks.get(i)).mark += incr; @@ -834,14 +917,26 @@ public class GapContent } /** - * @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 range 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. + * + * @param positions the positions to update + * @param offset + * @param length */ protected void updateUndoPositions(Vector positions, int offset, int length) { - // We do nothing here. + for (Iterator i = positions.iterator(); i.hasNext();) + { + UndoPosRef undoPosRef = (UndoPosRef) i.next(); + undoPosRef.reset(); + } + + // Resort marks. + Collections.sort(marks); } /** diff --git a/javax/swing/text/GlyphView.java b/javax/swing/text/GlyphView.java index d505274c9..65025dd08 100644 --- a/javax/swing/text/GlyphView.java +++ b/javax/swing/text/GlyphView.java @@ -39,6 +39,7 @@ exception statement from your version. */ package javax.swing.text; import java.awt.Color; +import java.awt.Container; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; @@ -354,11 +355,14 @@ public class GlyphView extends View implements TabableView, Cloneable Font font = view.getFont(); FontMetrics fm = view.getContainer().getFontMetrics(font); Segment txt = view.getText(el.getStartOffset(), pos); - int width = fm.charsWidth(txt.array, txt.offset, txt.count); + Rectangle bounds = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + TabExpander expander = view.getTabExpander(); + int width = Utilities.getTabbedTextWidth(txt, fm, bounds.x, expander, + view.getStartOffset()); int height = fm.getHeight(); - Rectangle bounds = a.getBounds(); Rectangle result = new Rectangle(bounds.x + width, bounds.y, - bounds.x + width, height); + 0, height); return result; } @@ -536,9 +540,24 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void paint(Graphics g, Shape a) { - Element el = getElement(); checkPainter(); - getGlyphPainter().paint(this, g, a, getStartOffset(), getEndOffset()); + int p0 = getStartOffset(); + int p1 = getEndOffset(); + + Container c = getContainer(); + // Paint layered highlights if there are any. + if (c instanceof JTextComponent) + { + JTextComponent tc = (JTextComponent) c; + Highlighter h = tc.getHighlighter(); + if (h instanceof LayeredHighlighter) + { + LayeredHighlighter lh = (LayeredHighlighter) h; + lh.paintLayeredHighlights(g, p0, p1, a, tc, this); + } + } + + getGlyphPainter().paint(this, g, a, p0, p1); } diff --git a/javax/swing/text/IconView.java b/javax/swing/text/IconView.java index 699cda90e..7bb7635b4 100644 --- a/javax/swing/text/IconView.java +++ b/javax/swing/text/IconView.java @@ -44,7 +44,6 @@ import java.awt.Shape; import javax.swing.Icon; import javax.swing.JTextPane; -import javax.swing.SwingConstants; /** * A View that can render an icon. This view is created by the @@ -156,4 +155,21 @@ public class IconView return el.getStartOffset(); } + /** + * Returns the alignment for this view. This will be 1.0 for the Y_AXIS, + * and the super behaviour for the X_AXIS. + * + * @param axis the axis for which to calculate the alignment + * + * @return the alignment + */ + public float getAlignment(int axis) + { + float align; + if (axis == Y_AXIS) + align = 1.0F; + else + align = super.getAlignment(axis); + return align; + } } diff --git a/javax/swing/text/JTextComponent.java b/javax/swing/text/JTextComponent.java index 6da84bfe7..68ba1f428 100644 --- a/javax/swing/text/JTextComponent.java +++ b/javax/swing/text/JTextComponent.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.text; -import gnu.classpath.NotImplementedException; - import java.awt.AWTEvent; import java.awt.Color; import java.awt.Container; @@ -47,6 +45,7 @@ import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; +import java.awt.Shape; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; @@ -59,6 +58,7 @@ import java.awt.event.MouseEvent; import java.io.IOException; import java.io.Reader; import java.io.Writer; +import java.text.BreakIterator; import java.util.Enumeration; import java.util.Hashtable; @@ -67,6 +67,7 @@ import javax.accessibility.AccessibleAction; import javax.accessibility.AccessibleContext; import javax.accessibility.AccessibleEditableText; import javax.accessibility.AccessibleRole; +import javax.accessibility.AccessibleState; import javax.accessibility.AccessibleStateSet; import javax.accessibility.AccessibleText; import javax.swing.Action; @@ -105,12 +106,7 @@ public abstract class JTextComponent extends JComponent /** * The caret's offset. */ - int dot = 0; - - /** - * The current JTextComponent. - */ - JTextComponent textComp = JTextComponent.this; + private int caretDot; /** * Construct an AccessibleJTextComponent. @@ -118,7 +114,8 @@ public abstract class JTextComponent extends JComponent public AccessibleJTextComponent() { super(); - textComp.addCaretListener(this); + JTextComponent.this.addCaretListener(this); + caretDot = getCaretPosition(); } /** @@ -129,8 +126,7 @@ public abstract class JTextComponent extends JComponent */ public int getCaretPosition() { - dot = textComp.getCaretPosition(); - return dot; + return JTextComponent.this.getCaretPosition(); } /** @@ -141,7 +137,7 @@ public abstract class JTextComponent extends JComponent */ public String getSelectedText() { - return textComp.getSelectedText(); + return JTextComponent.this.getSelectedText(); } /** @@ -156,9 +152,10 @@ public abstract class JTextComponent extends JComponent */ public int getSelectionStart() { - if (getSelectedText() == null || (textComp.getText().equals(""))) + if (getSelectedText() == null + || (JTextComponent.this.getText().equals(""))) return 0; - return textComp.getSelectionStart(); + return JTextComponent.this.getSelectionStart(); } /** @@ -173,9 +170,7 @@ public abstract class JTextComponent extends JComponent */ public int getSelectionEnd() { - if (getSelectedText() == null || (textComp.getText().equals(""))) - return 0; - return textComp.getSelectionEnd(); + return JTextComponent.this.getSelectionEnd(); } /** @@ -185,10 +180,20 @@ public abstract class JTextComponent extends JComponent * @param e - the caret update event */ public void caretUpdate(CaretEvent e) - throws NotImplementedException { - // TODO: fire appropriate event. - dot = e.getDot(); + int dot = e.getDot(); + int mark = e.getMark(); + if (caretDot != dot) + { + firePropertyChange(ACCESSIBLE_CARET_PROPERTY, new Integer(caretDot), + new Integer(dot)); + caretDot = dot; + } + if (mark != dot) + { + firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null, + getSelectedText()); + } } /** @@ -197,10 +202,10 @@ public abstract class JTextComponent extends JComponent * @return the accessible state set of this component */ public AccessibleStateSet getAccessibleStateSet() - throws NotImplementedException { AccessibleStateSet state = super.getAccessibleStateSet(); - // TODO: Figure out what state must be added here to the super's state. + if (isEditable()) + state.add(AccessibleState.EDITABLE); return state; } @@ -248,9 +253,9 @@ public abstract class JTextComponent extends JComponent * @param e - the insertion event */ public void insertUpdate(DocumentEvent e) - throws NotImplementedException { - // TODO + firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, + new Integer(e.getOffset())); } /** @@ -261,9 +266,9 @@ public abstract class JTextComponent extends JComponent * @param e - the removal event */ public void removeUpdate(DocumentEvent e) - throws NotImplementedException { - // TODO + firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, + new Integer(e.getOffset())); } /** @@ -274,9 +279,9 @@ public abstract class JTextComponent extends JComponent * @param e - text change event */ public void changedUpdate(DocumentEvent e) - throws NotImplementedException { - // TODO + firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, + new Integer(e.getOffset())); } /** @@ -289,9 +294,8 @@ public abstract class JTextComponent extends JComponent * @return a character index, or -1 */ public int getIndexAtPoint(Point p) - throws NotImplementedException { - return 0; // TODO + return viewToModel(p); } /** @@ -305,9 +309,51 @@ public abstract class JTextComponent extends JComponent * @return a character's bounding box, or null */ public Rectangle getCharacterBounds(int index) - throws NotImplementedException { - return null; // TODO + // This is basically the same as BasicTextUI.modelToView(). + + Rectangle bounds = null; + if (index >= 0 && index < doc.getLength() - 1) + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + TextUI ui = getUI(); + if (ui != null) + { + // Get editor rectangle. + Rectangle rect = new Rectangle(); + Insets insets = getInsets(); + rect.x = insets.left; + rect.y = insets.top; + rect.width = getWidth() - insets.left - insets.right; + rect.height = getHeight() - insets.top - insets.bottom; + View rootView = ui.getRootView(JTextComponent.this); + if (rootView != null) + { + rootView.setSize(rect.width, rect.height); + Shape s = rootView.modelToView(index, + Position.Bias.Forward, + index + 1, + Position.Bias.Backward, + rect); + if (s != null) + bounds = s.getBounds(); + } + } + } + catch (BadLocationException ex) + { + // Ignore (return null). + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + } + return bounds; } /** @@ -317,7 +363,7 @@ public abstract class JTextComponent extends JComponent */ public int getCharCount() { - return textComp.getText().length(); + return JTextComponent.this.getText().length(); } /** @@ -329,9 +375,26 @@ public abstract class JTextComponent extends JComponent * @return the character's attributes */ public AttributeSet getCharacterAttribute(int index) - throws NotImplementedException { - return null; // TODO + AttributeSet atts; + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + Element el = doc.getDefaultRootElement(); + while (! el.isLeaf()) + { + int i = el.getElementIndex(index); + el = el.getElement(i); + } + atts = el.getAttributes(); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return atts; } /** @@ -344,9 +407,8 @@ public abstract class JTextComponent extends JComponent * @return the part of text at that index, or null */ public String getAtIndex(int part, int index) - throws NotImplementedException { - return null; // TODO + return getAtIndexImpl(part, index, 0); } /** @@ -359,9 +421,8 @@ public abstract class JTextComponent extends JComponent * @return the part of text after that index, or null */ public String getAfterIndex(int part, int index) - throws NotImplementedException { - return null; // TODO + return getAtIndexImpl(part, index, 1); } /** @@ -374,11 +435,84 @@ public abstract class JTextComponent extends JComponent * @return the part of text before that index, or null */ public String getBeforeIndex(int part, int index) - throws NotImplementedException { - return null; // TODO + return getAtIndexImpl(part, index, -1); } - + + /** + * Implements getAtIndex(), getBeforeIndex() and getAfterIndex(). + * + * @param part the part to return, either CHARACTER, WORD or SENTENCE + * @param index the index + * @param dir the direction, -1 for backwards, 0 for here, +1 for forwards + * + * @return the resulting string + */ + private String getAtIndexImpl(int part, int index, int dir) + { + String ret = null; + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + BreakIterator iter = null; + switch (part) + { + case CHARACTER: + iter = BreakIterator.getCharacterInstance(getLocale()); + break; + case WORD: + iter = BreakIterator.getWordInstance(getLocale()); + break; + case SENTENCE: + iter = BreakIterator.getSentenceInstance(getLocale()); + break; + default: + break; + } + String text = doc.getText(0, doc.getLength() - 1); + iter.setText(text); + int start = index; + int end = index; + switch (dir) + { + case 0: + if (iter.isBoundary(index)) + { + start = index; + end = iter.following(index); + } + else + { + start = iter.preceding(index); + end = iter.next(); + } + break; + case 1: + start = iter.following(index); + end = iter.next(); + break; + case -1: + end = iter.preceding(index); + start = iter.previous(); + break; + default: + assert false; + } + ret = text.substring(start, end); + } + catch (BadLocationException ex) + { + // Ignore (return null). + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return ret; + } + /** * Returns the number of actions for this object. The zero-th * object represents the default action. @@ -386,9 +520,8 @@ public abstract class JTextComponent extends JComponent * @return the number of actions (0-based). */ public int getAccessibleActionCount() - throws NotImplementedException { - return 0; // TODO + return getActions().length; } /** @@ -400,10 +533,12 @@ public abstract class JTextComponent extends JComponent * @return description of the i-th action */ public String getAccessibleActionDescription(int i) - throws NotImplementedException { - // TODO: Not implemented fully - return super.getAccessibleDescription(); + String desc = null; + Action[] actions = getActions(); + if (i >= 0 && i < actions.length) + desc = (String) actions[i].getValue(Action.NAME); + return desc; } /** @@ -415,9 +550,17 @@ public abstract class JTextComponent extends JComponent * @return true if the action was performed successfully */ public boolean doAccessibleAction(int i) - throws NotImplementedException { - return false; // TODO + boolean ret = false; + Action[] actions = getActions(); + if (i >= 0 && i < actions.length) + { + ActionEvent ev = new ActionEvent(JTextComponent.this, + ActionEvent.ACTION_PERFORMED, null); + actions[i].actionPerformed(ev); + ret = true; + } + return ret; } /** @@ -426,9 +569,8 @@ public abstract class JTextComponent extends JComponent * @param s - the new text contents. */ public void setTextContents(String s) - throws NotImplementedException { - // TODO + setText(s); } /** @@ -438,9 +580,16 @@ public abstract class JTextComponent extends JComponent * @param s - the new text */ public void insertTextAtIndex(int index, String s) - throws NotImplementedException { - replaceText(index, index, s); + try + { + doc.insertString(index, s, null); + } + catch (BadLocationException ex) + { + // What should we do with this? + ex.printStackTrace(); + } } /** @@ -453,7 +602,7 @@ public abstract class JTextComponent extends JComponent { try { - return textComp.getText(start, end - start); + return JTextComponent.this.getText(start, end - start); } catch (BadLocationException ble) { @@ -481,8 +630,8 @@ public abstract class JTextComponent extends JComponent */ public void cut(int start, int end) { - textComp.select(start, end); - textComp.cut(); + JTextComponent.this.select(start, end); + JTextComponent.this.cut(); } /** @@ -492,8 +641,8 @@ public abstract class JTextComponent extends JComponent */ public void paste(int start) { - textComp.setCaretPosition(start); - textComp.paste(); + JTextComponent.this.setCaretPosition(start); + JTextComponent.this.paste(); } /** @@ -506,8 +655,8 @@ public abstract class JTextComponent extends JComponent */ public void replaceText(int start, int end, String s) { - textComp.select(start, end); - textComp.replaceSelection(s); + JTextComponent.this.select(start, end); + JTextComponent.this.replaceSelection(s); } /** @@ -518,7 +667,7 @@ public abstract class JTextComponent extends JComponent */ public void selectText(int start, int end) { - textComp.select(start, end); + JTextComponent.this.select(start, end); } /** @@ -529,9 +678,12 @@ public abstract class JTextComponent extends JComponent * @param s - the new attribute set for the text in the range */ public void setAttributes(int start, int end, AttributeSet s) - throws NotImplementedException { - // TODO + if (doc instanceof StyledDocument) + { + StyledDocument sdoc = (StyledDocument) doc; + sdoc.setCharacterAttributes(start, end - start, s, true); + } } } diff --git a/javax/swing/text/LabelView.java b/javax/swing/text/LabelView.java index 03279c4b2..a00a49c24 100644 --- a/javax/swing/text/LabelView.java +++ b/javax/swing/text/LabelView.java @@ -39,9 +39,11 @@ exception statement from your version. */ package javax.swing.text; import java.awt.Color; +import java.awt.Container; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Shape; +import java.awt.Toolkit; import javax.swing.event.DocumentEvent; @@ -90,6 +92,11 @@ public class LabelView extends GlyphView boolean superscript; /** + * Indicates if the attributes must be refetched. + */ + private boolean valid; + + /** * Creates a new <code>GlyphView</code> for the given <code>Element</code>. * * @param element the element that is rendered by this GlyphView @@ -97,7 +104,7 @@ public class LabelView extends GlyphView public LabelView(Element element) { super(element); - setPropertiesFromAttributes(); + valid = false; } /** @@ -115,10 +122,10 @@ public class LabelView extends GlyphView // when background == null anyway. background = (Color) atts.getAttribute(StyleConstants.Background); foreground = StyleConstants.getForeground(atts); - strikeThrough = StyleConstants.isStrikeThrough(atts); - subscript = StyleConstants.isSubscript(atts); - superscript = StyleConstants.isSuperscript(atts); - underline = StyleConstants.isUnderline(atts); + setStrikeThrough(StyleConstants.isStrikeThrough(atts)); + setSubscript(StyleConstants.isSubscript(atts)); + setSuperscript(StyleConstants.isSuperscript(atts)); + setUnderline(StyleConstants.isUnderline(atts)); // Determine the font. String family = StyleConstants.getFontFamily(atts); @@ -129,6 +136,7 @@ public class LabelView extends GlyphView if (StyleConstants.isItalic(atts)) style |= Font.ITALIC; font = new Font(family, style, size); + valid = true; } /** @@ -142,7 +150,8 @@ public class LabelView extends GlyphView */ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - setPropertiesFromAttributes(); + valid = false; + super.changedUpdate(e, a, vf); } /** @@ -152,6 +161,8 @@ public class LabelView extends GlyphView */ public Color getBackground() { + if (! valid) + setPropertiesFromAttributes(); return background; } @@ -175,6 +186,8 @@ public class LabelView extends GlyphView */ public Color getForeground() { + if (! valid) + setPropertiesFromAttributes(); return foreground; } @@ -185,6 +198,8 @@ public class LabelView extends GlyphView */ public Font getFont() { + if (! valid) + setPropertiesFromAttributes(); return font; } @@ -197,7 +212,16 @@ public class LabelView extends GlyphView */ protected FontMetrics getFontMetrics() { - return getContainer().getGraphics().getFontMetrics(font); + if (! valid) + setPropertiesFromAttributes(); + + Container c = getContainer(); + FontMetrics fm; + if (c != null) + fm = c.getFontMetrics(font); + else + fm = Toolkit.getDefaultToolkit().getFontMetrics(font); + return fm; } /** @@ -209,6 +233,8 @@ public class LabelView extends GlyphView */ public boolean isUnderline() { + if (! valid) + setPropertiesFromAttributes(); return underline; } @@ -255,6 +281,8 @@ public class LabelView extends GlyphView */ public boolean isSuperscript() { + if (! valid) + setPropertiesFromAttributes(); return superscript; } @@ -278,6 +306,8 @@ public class LabelView extends GlyphView */ public boolean isStrikeThrough() { + if (! valid) + setPropertiesFromAttributes(); return strikeThrough; } diff --git a/javax/swing/text/ParagraphView.java b/javax/swing/text/ParagraphView.java index 15bed7818..8a35ff62f 100644 --- a/javax/swing/text/ParagraphView.java +++ b/javax/swing/text/ParagraphView.java @@ -74,6 +74,39 @@ public class ParagraphView extends FlowView implements TabExpander return align; } + /** + * Allows rows to span the whole parent view. + */ + public float getMaximumSpan(int axis) + { + float max; + if (axis == X_AXIS) + max = Float.MAX_VALUE; + else + max = super.getMaximumSpan(axis); + return max; + } + + /** + * Overridden because child views are not necessarily laid out in model + * order. + */ + protected int getViewIndexAtPosition(int pos) + { + int index = -1; + if (pos >= getStartOffset() && pos < getEndOffset()) + { + int nviews = getViewCount(); + for (int i = 0; i < nviews && index == -1; i++) + { + View child = getView(i); + if (pos >= child.getStartOffset() && pos < child.getEndOffset()) + index = i; + } + } + return index; + } + protected void loadChildren(ViewFactory vf) { // Do nothing here. The children are added while layouting. @@ -140,7 +173,7 @@ public class ParagraphView extends FlowView implements TabExpander { float align; if (axis == X_AXIS) - align = super.getAlignment(axis); + align = 0.5F; else if (getViewCount() > 0) { float prefHeight = getPreferredSpan(Y_AXIS); @@ -148,7 +181,7 @@ public class ParagraphView extends FlowView implements TabExpander align = (firstRowHeight / 2.F) / prefHeight; } else - align = 0.0F; + align = 0.5F; return align; } @@ -159,11 +192,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 48fe37ce8..e048d5f71 100644 --- a/javax/swing/text/PlainView.java +++ b/javax/swing/text/PlainView.java @@ -87,6 +87,16 @@ public class PlainView extends View implements TabExpander */ private transient Segment lineBuffer; + /** + * The base offset for tab calculations. + */ + private int tabBase; + + /** + * The tab size. + */ + private int tabSize; + public PlainView(Element elem) { super(elem); @@ -104,6 +114,7 @@ public class PlainView extends View implements TabExpander { this.font = font; metrics = component.getFontMetrics(font); + tabSize = getTabSize() * metrics.charWidth('m'); } } @@ -115,7 +126,7 @@ public class PlainView extends View implements TabExpander // Ensure metrics are up-to-date. updateMetrics(); - Rectangle rect = a.getBounds(); + Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); int fontHeight = metrics.getHeight(); return new Rectangle(rect.x, rect.y + (line * fontHeight), rect.width, fontHeight); @@ -132,13 +143,14 @@ public class PlainView extends View implements TabExpander // Get rectangle of the line containing position. int lineIndex = getElement().getElementIndex(position); Rectangle rect = lineToRect(a, lineIndex); + tabBase = rect.x; // Get the rectangle for position. Element line = getElement().getElement(lineIndex); int lineStart = line.getStartOffset(); Segment segment = getLineBuffer(); document.getText(lineStart, position - lineStart, segment); - int xoffset = Utilities.getTabbedTextWidth(segment, metrics, rect.x, + int xoffset = Utilities.getTabbedTextWidth(segment, metrics, tabBase, this, lineStart); // Calc the real rectangle. @@ -262,17 +274,47 @@ public class PlainView extends View implements TabExpander selectionStart = textComponent.getSelectionStart(); selectionEnd = textComponent.getSelectionEnd(); - Rectangle rect = s.getBounds(); + Rectangle rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds(); + tabBase = rect.x; // FIXME: Text may be scrolled. Document document = textComponent.getDocument(); - Element root = document.getDefaultRootElement(); - int y = rect.y + metrics.getAscent(); + Element root = getElement(); int height = metrics.getHeight(); - + + // For layered highlighters we need to paint the layered highlights + // before painting any text. + LayeredHighlighter hl = null; + Highlighter h = textComponent.getHighlighter(); + if (h instanceof LayeredHighlighter) + hl = (LayeredHighlighter) h; + 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) + { + Element lineEl = root.getElement(i); + // Exclude the trailing newline from beeing highlighted. + if (i == count) + hl.paintLayeredHighlights(g, lineEl.getStartOffset(), + lineEl.getEndOffset(), s, textComponent, + this); + else + hl.paintLayeredHighlights(g, lineEl.getStartOffset(), + lineEl.getEndOffset() - 1, s, + textComponent, this); + } drawLine(i, g, rect.x, y); y += height; } @@ -303,8 +345,13 @@ public class PlainView extends View implements TabExpander */ public float nextTabStop(float x, int tabStop) { - float tabSizePixels = getTabSize() * metrics.charWidth('m'); - return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels; + float next = x; + if (tabSize != 0) + { + int numTabs = (((int) x) - tabBase) / tabSize; + next = tabBase + (numTabs + 1) * tabSize; + } + return next; } /** @@ -390,41 +437,58 @@ public class PlainView extends View implements TabExpander */ public int viewToModel(float x, float y, Shape a, Position.Bias[] b) { - Rectangle rec = a.getBounds(); - Document doc = getDocument(); - Element root = doc.getDefaultRootElement(); - - // PlainView doesn't support line-wrapping so we can find out which - // Element was clicked on just by the y-position. - // Since the coordinates may be outside of the coordinate space - // of the allocation area (e.g. user dragged mouse outside - // the component) we have to limit the values. - // This has the nice effect that the user can drag the - // mouse above or below the component and it will still - // react to the x values (e.g. when selecting). - int lineClicked - = Math.min(Math.max((int) (y - rec.y) / metrics.getHeight(), 0), - root.getElementCount() - 1); - - Element line = root.getElement(lineClicked); - - Segment s = getLineBuffer(); - int start = line.getStartOffset(); - // We don't want the \n at the end of the line. - int end = line.getEndOffset() - 1; - try - { - doc.getText(start, end - start, s); - } - catch (BadLocationException ble) + Rectangle rec = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + tabBase = rec.x; + + int pos; + if ((int) y < rec.y) + // Above our area vertically. Return start offset. + pos = getStartOffset(); + else if ((int) y > rec.y + rec.height) + // Below our area vertically. Return end offset. + pos = getEndOffset() - 1; + else { - AssertionError ae = new AssertionError("Unexpected bad location"); - ae.initCause(ble); - throw ae; + // Inside the allocation vertically. Determine line and X offset. + Document doc = getDocument(); + Element root = doc.getDefaultRootElement(); + int line = Math.abs(((int) y - rec.y) / metrics.getHeight()); + if (line >= root.getElementCount()) + pos = getEndOffset() - 1; + else + { + Element lineEl = root.getElement(line); + if (x < rec.x) + // To the left of the allocation area. + pos = lineEl.getStartOffset(); + else if (x > rec.x + rec.width) + // To the right of the allocation area. + pos = lineEl.getEndOffset() - 1; + else + { + try + { + int p0 = lineEl.getStartOffset(); + int p1 = lineEl.getEndOffset(); + Segment s = new Segment(); + doc.getText(p0, p1 - p0, s); + tabBase = rec.x; + pos = p0 + Utilities.getTabbedTextOffset(s, metrics, + tabBase, (int) x, + this, p0); + } + catch (BadLocationException ex) + { + // Should not happen. + pos = -1; + } + } + + } } - - int pos = Utilities.getTabbedTextOffset(s, metrics, rec.x, (int)x, this, start); - return Math.max (0, pos); + // Bias is always forward. + b[0] = Position.Bias.Forward; + return pos; } /** @@ -654,7 +718,7 @@ public class PlainView extends View implements TabExpander throw err; } - return Utilities.getTabbedTextWidth(buffer, metrics, 0, this, + return Utilities.getTabbedTextWidth(buffer, metrics, tabBase, this, lineEl.getStartOffset()); } } diff --git a/javax/swing/text/Segment.java b/javax/swing/text/Segment.java index d2364e05a..63c5fa09d 100644 --- a/javax/swing/text/Segment.java +++ b/javax/swing/text/Segment.java @@ -165,8 +165,9 @@ public class Segment implements Cloneable, CharacterIterator /** * Sets the current index to point to the last character in the segment and - * returns that character. If the segment contains zero characters, this - * method returns {@link #DONE}. + * returns that character. If the segment contains zero characters, the + * current index is set to {@link #getEndIndex()} and this method returns + * {@link #DONE}. * * @return The last character in the segment, or {@link #DONE} if the * segment contains zero characters. @@ -174,7 +175,10 @@ public class Segment implements Cloneable, CharacterIterator public char last() { if (count == 0) - return DONE; + { + current = getEndIndex(); + return DONE; + } current = getEndIndex() - 1; return array[current]; diff --git a/javax/swing/text/SimpleAttributeSet.java b/javax/swing/text/SimpleAttributeSet.java index 8dbcb0c6a..cdd3a7ee7 100644 --- a/javax/swing/text/SimpleAttributeSet.java +++ b/javax/swing/text/SimpleAttributeSet.java @@ -51,8 +51,10 @@ public class SimpleAttributeSet /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 8267656273837665219L; - /** An empty attribute set. */ - public static final AttributeSet EMPTY = new SimpleAttributeSet(); + /** + * An empty attribute set. + */ + public static final AttributeSet EMPTY = new EmptyAttributeSet(); /** Storage for the attributes. */ Hashtable tab; @@ -121,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 0a31505f3..8014dc3bc 100644 --- a/javax/swing/text/StringContent.java +++ b/javax/swing/text/StringContent.java @@ -178,11 +178,13 @@ public final class StringContent } /** - * Creates a new instance containing the string "\n". + * Creates a new instance containing the string "\n". This is equivalent + * to calling {@link #StringContent(int)} with an <code>initialLength</code> + * of 10. */ public StringContent() { - this(1); + this(10); } /** diff --git a/javax/swing/text/StyleConstants.java b/javax/swing/text/StyleConstants.java index c7906b8ad..677c479e5 100644 --- a/javax/swing/text/StyleConstants.java +++ b/javax/swing/text/StyleConstants.java @@ -729,6 +729,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/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/TabSet.java b/javax/swing/text/TabSet.java index ecad9444e..0f2c8c7c1 100644 --- a/javax/swing/text/TabSet.java +++ b/javax/swing/text/TabSet.java @@ -1,5 +1,5 @@ /* TabSet.java -- - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,23 +39,54 @@ package javax.swing.text; import java.io.Serializable; +/** + * A set of tab stops. Instances of this class are immutable. + */ public class TabSet implements Serializable { /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 2367703481999080593L; + /** Storage for the tab stops. */ TabStop[] tabs; + /** + * Creates a new <code>TabSet</code> containing the specified tab stops. + * + * @param t the tab stops (<code>null</code> permitted). + */ public TabSet(TabStop[] t) { - tabs = t; + if (t != null) + tabs = (TabStop[]) t.clone(); + else + tabs = new TabStop[0]; } + /** + * Returns the tab stop with the specified index. + * + * @param i the index. + * + * @return The tab stop. + * + * @throws IllegalArgumentException if <code>i</code> is not in the range + * <code>0</code> to <code>getTabCount() - 1</code>. + */ public TabStop getTab(int i) { + if (i < 0 || i >= tabs.length) + throw new IllegalArgumentException("Index out of bounds."); return tabs[i]; } + /** + * Returns the tab following the specified location. + * + * @param location the location. + * + * @return The tab following the specified location (or <code>null</code>). + */ public TabStop getTabAfter(float location) { int idx = getTabIndexAfter(location); @@ -65,11 +96,23 @@ public class TabSet implements Serializable return tabs[idx]; } + /** + * Returns the number of tab stops in this tab set. + * + * @return The number of tab stops in this tab set. + */ public int getTabCount() { return tabs.length; } + /** + * Returns the index of the specified tab, or -1 if the tab is not found. + * + * @param tab the tab (<code>null</code> permitted). + * + * @return The index of the specified tab, or -1. + */ public int getTabIndex(TabStop tab) { for (int i = 0; i < tabs.length; ++i) @@ -78,28 +121,88 @@ public class TabSet implements Serializable return -1; } + /** + * Returns the index of the tab at or after the specified location. + * + * @param location the tab location. + * + * @return The index of the tab stop, or -1. + */ public int getTabIndexAfter(float location) { - int idx = -1; - for (int i = 0; i < tabs.length; ++i) + for (int i = 0; i < tabs.length; i++) + { + if (location <= tabs[i].getPosition()) + return i; + } + return -1; + } + + /** + * Tests this <code>TabSet</code> for equality with an arbitrary object. + * + * @param obj the object (<code>null</code> permitted). + * + * @return <code>true</code> if this <code>TabSet</code> is equal to + * <code>obj</code>, and <code>false</code> otherwise. + * + * @since 1.5 + */ + public boolean equals(Object obj) + { + if (obj == this) + return true; + if (!(obj instanceof TabSet)) + return false; + TabSet that = (TabSet) obj; + int tabCount = getTabCount(); + if (tabCount != that.getTabCount()) + return false; + for (int i = 0; i < tabCount; i++) + { + if (!this.getTab(i).equals(that.getTab(i))) + return false; + } + return true; + } + + /** + * Returns a hash code for this <code>TabSet</code>. + * + * @return A hash code. + * + * @since 1.5 + */ + public int hashCode() + { + // this hash code won't match Sun's, but that shouldn't matter... + int result = 193; + int tabs = getTabCount(); + for (int i = 0; i < tabs; i++) { - if (location < tabs[i].getPosition()) - idx = i; + TabStop t = getTab(i); + if (t != null) + result = 37 * result + t.hashCode(); } - return idx; + return result; } + /** + * Returns a string representation of this <code>TabSet</code>. + * + * @return A string representation of this <code>TabSet</code>. + */ public String toString() { StringBuffer sb = new StringBuffer(); - sb.append("["); + sb.append("[ "); for (int i = 0; i < tabs.length; ++i) { if (i != 0) sb.append(" - "); sb.append(tabs[i].toString()); } - sb.append("]"); + sb.append(" ]"); return sb.toString(); } } diff --git a/javax/swing/text/TabStop.java b/javax/swing/text/TabStop.java index 56f862fda..f4c3f8514 100644 --- a/javax/swing/text/TabStop.java +++ b/javax/swing/text/TabStop.java @@ -1,5 +1,5 @@ -/* TabSet.java -- - Copyright (C) 2004 Free Software Foundation, Inc. +/* TabStop.java -- + Copyright (C) 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,6 +39,9 @@ package javax.swing.text; import java.io.Serializable; +/** + * Represents a tab position in some text. + */ public class TabStop implements Serializable { /** The serialization UID (compatible with JDK1.5). */ @@ -61,18 +64,42 @@ public class TabStop implements Serializable int align; int leader; + /** + * Creates a new <code>TabStop</code> for the specified tab position. + * + * @param pos the tab position. + */ public TabStop(float pos) { this(pos, ALIGN_LEFT, LEAD_NONE); } + /** + * Creates a new <code>TabStop</code> with the specified attributes. + * + * @param pos the tab position. + * @param align the alignment (one of {@link #ALIGN_LEFT}, + * {@link #ALIGN_CENTER}, {@link #ALIGN_RIGHT}, {@link #ALIGN_DECIMAL} + * or {@link #ALIGN_BAR}). + * @param leader the leader (one of {@link #LEAD_NONE}, {@link #LEAD_DOTS}, + * {@link #LEAD_EQUALS}, {@link #LEAD_HYPHENS}, {@link #LEAD_THICKLINE} + * or {@link #LEAD_UNDERLINE}). + */ public TabStop(float pos, int align, int leader) { this.pos = pos; this.align = align; this.leader = leader; } - + + /** + * Tests this <code>TabStop</code> for equality with an arbitrary object. + * + * @param other the other object (<code>null</code> permitted). + * + * @return <code>true</code> if this <code>TabStop</code> is equal to + * the specified object, and <code>false</code> otherwise. + */ public boolean equals(Object other) { return (other != null) @@ -82,34 +109,60 @@ public class TabStop implements Serializable && (((TabStop)other).getAlignment() == this.getAlignment()); } + /** + * Returns the tab alignment. This should be one of {@link #ALIGN_LEFT}, + * {@link #ALIGN_CENTER}, {@link #ALIGN_RIGHT}, {@link #ALIGN_DECIMAL} or + * {@link #ALIGN_BAR}. + * + * @return The tab alignment. + */ public int getAlignment() { return align; } + /** + * Returns the leader type. This should be one of {@link #LEAD_NONE}, + * {@link #LEAD_DOTS}, {@link #LEAD_EQUALS}, {@link #LEAD_HYPHENS}, + * {@link #LEAD_THICKLINE} or {@link #LEAD_UNDERLINE}. + * + * @return The leader type. + */ public int getLeader() { return leader; } + /** + * Returns the tab position. + * + * @return The tab position. + */ public float getPosition() { return pos; } + /** + * Returns a hash code for this <code>TabStop</code>. + * + * @return A hash code. + */ public int hashCode() { return (int) pos + (int) leader + (int) align; } + /** + * Returns a string describing this <code>TabStop</code>. + * + * @return A string describing this <code>TabStop</code>. + */ public String toString() { String prefix = ""; switch (align) { - case ALIGN_LEFT: - prefix = "left "; - break; case ALIGN_RIGHT: prefix = "right "; break; @@ -130,7 +183,8 @@ public class TabStop implements Serializable break; } - return (prefix + "tab @" + pos + ((leader == LEAD_NONE) ? "" : "(w/leaders)")); + return prefix + "tab @" + pos + + ((leader == LEAD_NONE) ? "" : " (w/leaders)"); } } diff --git a/javax/swing/text/TextAction.java b/javax/swing/text/TextAction.java index 144166e9c..e18670610 100644 --- a/javax/swing/text/TextAction.java +++ b/javax/swing/text/TextAction.java @@ -38,6 +38,8 @@ 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; @@ -45,7 +47,6 @@ import java.util.HashSet; import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.SwingConstants; /** * TextAction @@ -108,7 +109,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 36361f497..f0d3019ff 100644 --- a/javax/swing/text/Utilities.java +++ b/javax/swing/text/Utilities.java @@ -43,7 +43,6 @@ import java.awt.Graphics; import java.awt.Point; import java.text.BreakIterator; -import javax.swing.SwingConstants; import javax.swing.text.Position.Bias; /** @@ -109,7 +108,7 @@ public class Utilities for (int offset = s.offset; offset < end; ++offset) { char c = buffer[offset]; - if (c == '\t' || c == '\n') + if (c == '\t') { if (len > 0) { g.drawChars(buffer, pos, len, pixelX, pixelY + ascent); @@ -131,11 +130,6 @@ public class Utilities else pixelX += metrics.charWidth(' '); break; - case '\n': - // In case we have a newline, we must jump to the next line. - pixelY += metrics.getHeight(); - pixelX = x; - break; default: ++len; pixelWidth += metrics.charWidth(buffer[offset]); @@ -712,12 +706,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 d8ad5f585..068f969d2 100644 --- a/javax/swing/text/View.java +++ b/javax/swing/text/View.java @@ -401,7 +401,10 @@ public abstract class View implements SwingConstants Element el = getElement(); DocumentEvent.ElementChange ec = ev.getChange(el); if (ec != null) - updateChildren(ec, ev, vf); + { + if (! updateChildren(ec, ev, vf)) + ec = null; + } forwardUpdate(ec, ev, shape, vf); updateLayout(ec, ev, shape); } @@ -426,12 +429,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); + } } /** @@ -493,27 +502,66 @@ public abstract class View implements SwingConstants int count = getViewCount(); if (count > 0) { + // Determine start index. int startOffset = ev.getOffset(); - int endOffset = startOffset + ev.getLength(); int startIndex = getViewIndex(startOffset, Position.Bias.Backward); - int endIndex = getViewIndex(endOffset, Position.Bias.Forward); - int index = -1; - int addLength = -1; - if (ec != null) + + // For REMOVE events we have to forward the event to the last element, + // for the case that an Element has been removed that represente + // the offset. + if (startIndex == -1 && ev.getType() == DocumentEvent.EventType.REMOVE + && startOffset >= getEndOffset()) { - index = ec.getIndex(); - addLength = ec.getChildrenAdded().length; + startIndex = getViewCount() - 1; } - if (startIndex >= 0 && endIndex >= 0) + // When startIndex is on a view boundary, forward event to the + // previous view too. + if (startIndex >= 0) { - for (int i = startIndex; i <= endIndex; i++) + View v = getView(startIndex); + if (v != null) + { + if (v.getStartOffset() == startOffset && startOffset > 0) + startIndex = Math.max(0, startIndex - 1); + } + } + startIndex = Math.max(0, startIndex); + + // Determine end index. + int endIndex = startIndex; + if (ev.getType() != DocumentEvent.EventType.REMOVE) + { + endIndex = getViewIndex(startOffset + ev.getLength(), + Position.Bias.Forward); + if (endIndex < 0) + endIndex = getViewCount() - 1; + } + + // Determine hole that comes from added elements (we don't forward + // the event to newly added views. + int startAdded = endIndex + 1; + int endAdded = startAdded; + Element[] added = (ec != null) ? ec.getChildrenAdded() : null; + if (added != null && added.length > 0) + { + startAdded = ec.getIndex(); + endAdded = startAdded + added.length - 1; + } + + // Forward event to all views between startIndex and endIndex, + // and leave out all views in the hole. + for (int i = startIndex; i <= endIndex; i++) + { + // Skip newly added child views. + if (! (i >= startAdded && i <= endAdded)) { - // Skip newly added child views. - if (index >= 0 && i >= index && i < (index+addLength)) - continue; View child = getView(i); - forwardUpdateToView(child, ev, shape, vf); + if (child != null) + { + Shape childAlloc = getChildAllocation(i, shape); + forwardUpdateToView(child, ev, childAlloc, vf); + } } } } @@ -611,9 +659,46 @@ public abstract class View implements SwingConstants if (b2 != Position.Bias.Forward && b2 != Position.Bias.Backward) throw new IllegalArgumentException ("b2 must be either Position.Bias.Forward or Position.Bias.Backward"); - Rectangle s1 = (Rectangle) modelToView(p1, a, b1); - Rectangle s2 = (Rectangle) modelToView(p2, a, b2); - return SwingUtilities.computeUnion(s1.x, s1.y, s1.width, s1.height, s2); + + Shape s1 = modelToView(p1, a, b1); + // Special case for p2 == end index. + Shape s2; + if (p2 != getEndOffset()) + { + s2 = modelToView(p2, a, b2); + } + else + { + try + { + s2 = modelToView(p2, a, b2); + } + catch (BadLocationException ex) + { + // Assume the end rectangle to be at the right edge of the + // view. + Rectangle aRect = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + s2 = new Rectangle(aRect.x + aRect.width - 1, aRect.y, 1, + aRect.height); + } + } + + // Need to modify the rectangle, so we create a copy in all cases. + Rectangle r1 = s1.getBounds(); + Rectangle r2 = s2 instanceof Rectangle ? (Rectangle) s2 + : s2.getBounds(); + + // For multiline view, let the resulting rectangle span the whole view. + if (r1.y != r2.y) + { + Rectangle aRect = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + r1.x = aRect.x; + r1.width = aRect.width; + } + + return SwingUtilities.computeUnion(r2.x, r2.y, r2.width, r2.height, r1); } /** diff --git a/javax/swing/text/WrappedPlainView.java b/javax/swing/text/WrappedPlainView.java index a6c369a4c..8cb2f4fb5 100644 --- a/javax/swing/text/WrappedPlainView.java +++ b/javax/swing/text/WrappedPlainView.java @@ -449,10 +449,34 @@ public class WrappedPlainView extends BoxView implements TabExpander int currStart = getStartOffset(); int currEnd; int count = 0; + + // Determine layered highlights. + Container c = getContainer(); + LayeredHighlighter lh = null; + JTextComponent tc = null; + if (c instanceof JTextComponent) + { + tc = (JTextComponent) c; + Highlighter h = tc.getHighlighter(); + if (h instanceof LayeredHighlighter) + lh = (LayeredHighlighter) h; + } + while (currStart < end) { currEnd = calculateBreakPosition(currStart, end); + // Paint layered highlights, if any. + if (lh != null) + { + // Exclude trailing newline in last line. + if (currEnd == end) + lh.paintLayeredHighlights(g, currStart, currEnd - 1, s, tc, + this); + else + lh.paintLayeredHighlights(g, currStart, currEnd, s, tc, this); + + } drawLine(currStart, currEnd, g, rect.x, rect.y + metrics.getAscent()); rect.y += lineHeight; diff --git a/javax/swing/text/html/HTMLDocument.java b/javax/swing/text/html/HTMLDocument.java index 0eaf66fd8..0bfc338df 100644 --- a/javax/swing/text/html/HTMLDocument.java +++ b/javax/swing/text/html/HTMLDocument.java @@ -40,14 +40,18 @@ 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; +import java.io.StringReader; import java.net.URL; import java.util.HashMap; 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; @@ -515,19 +519,23 @@ public class HTMLDocument extends DefaultStyledDocument */ public class HTMLReader extends HTMLEditorKit.ParserCallback { - /** Holds the current character attribute set **/ + /** + * Holds the current character attribute set * + */ protected MutableAttributeSet charAttr = new SimpleAttributeSet(); protected Vector parseBuffer = new Vector(); - /** A stack for character attribute sets **/ + /** + * A stack for character attribute sets * + */ Stack charAttrStack = new Stack(); /** * The parse stack. This stack holds HTML.Tag objects that reflect the * current position in the parsing process. */ - private Stack parseStack = new Stack(); + Stack parseStack = new Stack(); /** A mapping between HTML.Tag objects and the actions that handle them **/ HashMap tagToAction; @@ -535,10 +543,31 @@ public class HTMLDocument extends DefaultStyledDocument /** Tells us whether we've received the '</html>' tag yet **/ boolean endHTMLEncountered = false; - /** Variables related to the constructor with explicit insertTag **/ - int popDepth, pushDepth, offset; + /** + * Related to the constructor with explicit insertTag + */ + int popDepth; + + /** + * Related to the constructor with explicit insertTag + */ + int pushDepth; + + /** + * Related to the constructor with explicit insertTag + */ + int offset; + + /** + * The tag (inclusve), after that the insertion should start. + */ HTML.Tag insertTag; - boolean insertTagEncountered = false; + + /** + * This variable becomes true after the insert tag has been encountered. + */ + boolean insertTagEncountered; + /** A temporary variable that helps with the printing out of debug information **/ boolean debug = false; @@ -1139,8 +1168,21 @@ public class HTMLDocument extends DefaultStyledDocument } /** - * This method is called by the parser and should route the call to - * the proper handler for the tag. + * Checks if the HTML tag should be inserted. The tags before insert tag (if + * specified) are not inserted. Also, the tags after the end of the html are + * not inserted. + * + * @return true if the tag should be inserted, false otherwise. + */ + private boolean shouldInsert() + { + return ! endHTMLEncountered + && (insertTagEncountered || insertTag == null); + } + + /** + * This method is called by the parser and should route the call to the + * proper handler for the tag. * * @param t the HTML.Tag * @param a the attribute set @@ -1148,13 +1190,15 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - TagAction action = (TagAction) tagToAction.get(t); - if (action != null) - action.start(t, a); + if (t == insertTag) + insertTagEncountered = true; + + if (shouldInsert()) + { + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.start(t, a); + } } /** @@ -1165,42 +1209,41 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleComment(char[] data, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); - if (action != null) + if (shouldInsert()) { - action.start(HTML.Tag.COMMENT, new SimpleAttributeSet()); - action.end (HTML.Tag.COMMENT); + TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); + if (action != null) + { + action.start(HTML.Tag.COMMENT, + htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET); + action.end(HTML.Tag.COMMENT); + } } } /** - * This method is called by the parser and should route the call to - * the proper handler for the tag. + * This method is called by the parser and should route the call to the + * proper handler for the tag. * * @param t the HTML.Tag * @param pos the position at which the tag was encountered */ public void handleEndTag(HTML.Tag t, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - // If this is the </html> tag we need to stop calling the Actions - if (t == HTML.Tag.HTML) - endHTMLEncountered = true; - - TagAction action = (TagAction) tagToAction.get(t); - if (action != null) - action.end(t); + if (shouldInsert()) + { + // If this is the </html> tag we need to stop calling the Actions + if (t == HTML.Tag.HTML) + endHTMLEncountered = true; + + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.end(t); + } } /** - * This is a callback from the parser that should be routed to the + * This is a callback from the parser that should be routed to the * appropriate handler for the tag. * * @param t the HTML.Tag that was encountered @@ -1209,15 +1252,17 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - TagAction action = (TagAction) tagToAction.get (t); - if (action != null) + if (t == insertTag) + insertTagEncountered = true; + + if (shouldInsert()) { - action.start(t, a); - action.end(t); + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + { + action.start(t, a); + action.end(t); + } } } @@ -1230,7 +1275,6 @@ public class HTMLDocument extends DefaultStyledDocument * @since 1.3 */ public void handleEndOfLineString(String eol) - throws NotImplementedException { // FIXME: Implement. print ("HTMLReader.handleEndOfLineString not implemented yet"); @@ -1273,16 +1317,6 @@ public class HTMLDocument extends DefaultStyledDocument printBuffer(); DefaultStyledDocument.ElementSpec element; - // If the previous tag is content and the parent is p-implied, then - // we must also close the p-implied. - if (parseStack.size() > 0 && parseStack.peek() == HTML.Tag.IMPLIED) - { - element = new DefaultStyledDocument.ElementSpec(null, - DefaultStyledDocument.ElementSpec.EndTagType); - parseBuffer.addElement(element); - parseStack.pop(); - } - parseStack.push(t); AbstractDocument.AttributeContext ctx = getAttributeContext(); AttributeSet copy = attr.copyAttributes(); @@ -1320,16 +1354,6 @@ public class HTMLDocument extends DefaultStyledDocument new char[0], 0, 0); parseBuffer.add(element); } - // If the previous tag is content and the parent is p-implied, then - // we must also close the p-implied. - else if (parseStack.peek() == HTML.Tag.IMPLIED) - { - element = new DefaultStyledDocument.ElementSpec(null, - DefaultStyledDocument.ElementSpec.EndTagType); - parseBuffer.addElement(element); - if (parseStack.size() > 0) - parseStack.pop(); - } element = new DefaultStyledDocument.ElementSpec(null, DefaultStyledDocument.ElementSpec.EndTagType); @@ -1369,27 +1393,6 @@ public class HTMLDocument extends DefaultStyledDocument DefaultStyledDocument.ElementSpec element; AttributeSet attributes = null; - // Content must always be embedded inside a paragraph element, - // so we create this if the previous element is not one of - // <p>, <h1> .. <h6>. - boolean createImpliedParagraph = false; - HTML.Tag parent = (HTML.Tag) parseStack.peek(); - if (parent != HTML.Tag.P && parent != HTML.Tag.H1 - && parent != HTML.Tag.H2 - && parent != HTML.Tag.H3 && parent != HTML.Tag.H4 - && parent != HTML.Tag.H5 && parent != HTML.Tag.H6 - && parent != HTML.Tag.TD) - { - attributes = ctx.getEmptySet(); - attributes = ctx.addAttribute(attributes, - StyleConstants.NameAttribute, - HTML.Tag.IMPLIED); - element = new DefaultStyledDocument.ElementSpec(attributes, - DefaultStyledDocument.ElementSpec.StartTagType); - parseBuffer.add(element); - parseStack.push(HTML.Tag.IMPLIED); - } - // Copy the attribute set, don't use the same object because // it may change if (charAttr != null) @@ -1481,7 +1484,61 @@ public class HTMLDocument extends DefaultStyledDocument HTML.Tag insertTag) { return new HTMLReader(pos, popDepth, pushDepth, insertTag); - } + } + + /** + * Gets the reader for the parser to use when inserting the HTML fragment into + * the document. Checks if the parser is present, sets the parent in the + * element stack and removes any actions for BODY (it can be only one body in + * a HTMLDocument). + * + * @param pos - the starting position + * @param popDepth - the number of EndTagTypes to generate before inserting + * @param pushDepth - the number of StartTagTypes with a direction of + * JoinNextDirection that should be generated before inserting, but + * after the end tags have been generated. + * @param insertTag - the first tag to start inserting into document + * @param parent the element that will be the parent in the document. HTML + * parsing includes checks for the parent, so it must be available. + * @return - the reader + * @throws IllegalStateException if the parsert is not set. + */ + public HTMLEditorKit.ParserCallback getInsertingReader(int pos, int popDepth, + int pushDepth, + HTML.Tag insertTag, + final Element parent) + throws IllegalStateException + { + if (parser == null) + throw new IllegalStateException("Parser has not been set"); + + HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth, insertTag) + { + /** + * Ignore BODY. + */ + public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + if (t != HTML.Tag.BODY) + super.handleStartTag(t, a, pos); + } + + /** + * Ignore BODY. + */ + public void handleEndTag(HTML.Tag t, int pos) + { + if (t != HTML.Tag.BODY) + super.handleEndTag(t, pos); + } + }; + + // Set the parent HTML tag. + reader.parseStack.push(parent.getAttributes().getAttribute( + StyleConstants.NameAttribute)); + + return reader; + } /** * Gets the child element that contains the attribute with the value or null. @@ -1490,8 +1547,8 @@ public class HTMLDocument extends DefaultStyledDocument * @param e - the element to begin search at * @param attribute - the desired attribute * @param value - the desired value - * @return the element found with the attribute and value specified or null - * if it is not found. + * @return the element found with the attribute and value specified or null if + * it is not found. */ public Element getElement(Element e, Object attribute, Object value) { @@ -1516,16 +1573,17 @@ public class HTMLDocument extends DefaultStyledDocument } /** - * Returns the element that has the given id Attribute. If it is not found, - * null is returned. This method works on an Attribute, not a character tag. - * This is not thread-safe. + * Returns the element that has the given id Attribute (for instance, <p id + * ='my paragraph >'). If it is not found, null is returned. The HTML tag, + * having this attribute, is not checked by this method and can be any. The + * method is not thread-safe. * - * @param attrId - the Attribute id to look for + * @param attrId - the value of the attribute id to look for * @return the element that has the given id. */ public Element getElement(String attrId) { - return getElement(getDefaultRootElement(), HTML.getAttributeKey(attrId), + return getElement(getDefaultRootElement(), HTML.Attribute.ID, attrId); } @@ -1542,22 +1600,30 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if an HTMLEditorKit.Parser has not been set */ public void setInnerHTML(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { if (elem.isLeaf()) throw new IllegalArgumentException("Element is a leaf"); - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("setInnerHTML not implemented"); + + int start = elem.getStartOffset(); + int end = elem.getEndOffset(); + + HTMLEditorKit.ParserCallback reader = getInsertingReader( + end, 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); + + // Remove the previous content + remove(start, end - start); } /** - * Replaces the given element in the parent with the string. When replacing - * a leaf, this will attempt to make sure there is a newline present if one is - * needed. This may result in an additional element being inserted. - * This will be seen as at least two events, n inserts followed by a remove. - * The HTMLEditorKit.Parser must be set. + * Replaces the given element in the parent with the string. When replacing a + * leaf, this will attempt to make sure there is a newline present if one is + * needed. This may result in an additional element being inserted. This will + * be seen as at least two events, n inserts followed by a remove. The + * HTMLEditorKit.Parser must be set. * * @param elem - the branch element whose parent will be replaced * @param htmlText - the string to be parsed and assigned to elem @@ -1565,18 +1631,25 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IOException * @throws IllegalStateException - if parser is not set */ - public void setOuterHTML(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException - { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("setOuterHTML not implemented"); - } +public void setOuterHTML(Element elem, String htmlText) + throws BadLocationException, IOException + { + // Remove the current element: + int start = elem.getStartOffset(); + int end = elem.getEndOffset(); + + remove(start, end-start); + + HTMLEditorKit.ParserCallback reader = getInsertingReader( + start, 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); + } /** - * Inserts the string before the start of the given element. - * The parser must be set. + * Inserts the string before the start of the given element. The parser must + * be set. * * @param elem - the element to be the root for the new text. * @param htmlText - the string to be parsed and assigned to elem @@ -1585,18 +1658,19 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser has not been set */ public void insertBeforeStart(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertBeforeStart not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); } /** - * Inserts the string at the end of the element. If elem's children - * are leaves, and the character at elem.getEndOffset() - 1 is a newline, - * then it will be inserted before the newline. The parser must be set. + * Inserts the string at the end of the element. If elem's children are + * leaves, and the character at elem.getEndOffset() - 1 is a newline, then it + * will be inserted before the newline. The parser must be set. * * @param elem - the element to be the root for the new text * @param htmlText - the text to insert @@ -1605,12 +1679,14 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser is not set */ public void insertBeforeEnd(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertBeforeEnd not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); + } /** @@ -1624,12 +1700,13 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser is not set */ public void insertAfterEnd(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertAfterEnd not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); } /** @@ -1643,11 +1720,12 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser is not set */ public void insertAfterStart(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertAfterStart not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); } } diff --git a/javax/swing/text/html/HTMLEditorKit.java b/javax/swing/text/html/HTMLEditorKit.java index 7addfb365..5d77be8fd 100644 --- a/javax/swing/text/html/HTMLEditorKit.java +++ b/javax/swing/text/html/HTMLEditorKit.java @@ -71,6 +71,11 @@ import javax.swing.text.View; import javax.swing.text.ViewFactory; import javax.swing.text.html.parser.ParserDelegator; +/* Move these imports here after javax.swing.text.html to make it compile + with jikes. */ +import gnu.javax.swing.text.html.parser.GnuParserDelegator; +import gnu.javax.swing.text.html.parser.HTML_401Swing; + /** * @author Lillian Angel (langel at redhat dot com) */ @@ -886,7 +891,9 @@ public class HTMLEditorKit protected Parser getParser() { if (parser == null) - parser = new ParserDelegator(); + { + parser = new GnuParserDelegator(HTML_401Swing.getInstance()); + } return parser; } diff --git a/javax/swing/text/html/parser/ParserDelegator.java b/javax/swing/text/html/parser/ParserDelegator.java index e5d2db4df..70636d929 100644 --- a/javax/swing/text/html/parser/ParserDelegator.java +++ b/javax/swing/text/html/parser/ParserDelegator.java @@ -52,9 +52,6 @@ import javax.swing.text.html.HTMLEditorKit.ParserCallback; * This class instantiates and starts the working instance of * html parser, being responsible for providing the default DTD. * - * TODO Later this class must be derived from the totally abstract class - * HTMLEditorKit.Parser. HTMLEditorKit that does not yet exist. - * * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) */ public class ParserDelegator diff --git a/javax/swing/tree/TreePath.java b/javax/swing/tree/TreePath.java index 93b59b07e..1c4d78787 100644 --- a/javax/swing/tree/TreePath.java +++ b/javax/swing/tree/TreePath.java @@ -174,7 +174,7 @@ public class TreePath implements Serializable return false; for (index = 0; index < path.length; index++) { - if (!treepath[index].equals(path[index])) + if (!path[index].equals(treepath[index])) return false; } diff --git a/javax/swing/tree/VariableHeightLayoutCache.java b/javax/swing/tree/VariableHeightLayoutCache.java index 0a787f7ca..1b85fb13d 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; } |