diff options
author | tromey <tromey@138bc75d-0d04-0410-961f-82ee72b054a4> | 2007-01-09 19:58:05 +0000 |
---|---|---|
committer | tromey <tromey@138bc75d-0d04-0410-961f-82ee72b054a4> | 2007-01-09 19:58:05 +0000 |
commit | 65bf3316cf384588453604be6b4f0ed3751a8b0f (patch) | |
tree | 996a5f57d4a68c53473382e45cb22f574cb3e4db /libjava/classpath/javax/swing | |
parent | 8fc56618a84446beccd45b80381cdfe0e94050df (diff) | |
download | gcc-65bf3316cf384588453604be6b4f0ed3751a8b0f.tar.gz |
Merged gcj-eclipse branch to trunk.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@120621 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libjava/classpath/javax/swing')
177 files changed, 21503 insertions, 8376 deletions
diff --git a/libjava/classpath/javax/swing/AbstractButton.java b/libjava/classpath/javax/swing/AbstractButton.java index 63f827a1ae0..cb0f458b89f 100644 --- a/libjava/classpath/javax/swing/AbstractButton.java +++ b/libjava/classpath/javax/swing/AbstractButton.java @@ -37,8 +37,6 @@ exception statement from your version. */ package javax.swing; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Graphics; import java.awt.Image; @@ -74,7 +72,10 @@ import javax.swing.plaf.ButtonUI; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; import javax.swing.text.Position; +import javax.swing.text.StyledDocument; import javax.swing.text.View; @@ -187,9 +188,32 @@ public abstract class AbstractButton extends JComponent */ public void stateChanged(ChangeEvent ev) { - AbstractButton.this.fireStateChanged(); + getEventHandler().stateChanged(ev); + } + } + + /** + * The combined event handler for ActionEvent, ChangeEvent and + * ItemEvent. This combines ButtonChangeListener, ActionListener + */ + private class EventHandler + implements ActionListener, ChangeListener, ItemListener + { + public void actionPerformed(ActionEvent ev) + { + fireActionPerformed(ev); + } + + public void stateChanged(ChangeEvent ev) + { + fireStateChanged(); repaint(); } + + public void itemStateChanged(ItemEvent ev) + { + fireItemStateChanged(ev); + } } /** The icon displayed by default. */ @@ -264,16 +288,29 @@ public abstract class AbstractButton extends JComponent */ int mnemonicIndex; - /** Listener the button uses to receive ActionEvents from its model. */ + /** + * Listener the button uses to receive ActionEvents from its model. + */ protected ActionListener actionListener; - /** Listener the button uses to receive ItemEvents from its model. */ + /** + * Listener the button uses to receive ItemEvents from its model. + */ protected ItemListener itemListener; - /** Listener the button uses to receive ChangeEvents from its model. */ + /** + * Listener the button uses to receive ChangeEvents from its model. + */ protected ChangeListener changeListener; /** + * The event handler for ActionEvent, ItemEvent and ChangeEvent. + * This replaces the above three handlers and combines them + * into one for efficiency. + */ + private EventHandler eventHandler; + + /** * The time in milliseconds in which clicks get coalesced into a single * <code>ActionEvent</code>. */ @@ -768,22 +805,127 @@ public abstract class AbstractButton extends JComponent return -1; } - public String getAtIndex(int value0, int value1) - throws NotImplementedException + /** + * Returns the character, word or sentence at the specified index. The + * <code>part</code> parameter determines what is returned, the character, + * word or sentence after the index. + * + * @param part one of {@link AccessibleText#CHARACTER}, + * {@link AccessibleText#WORD} or + * {@link AccessibleText#SENTENCE}, specifying what is returned + * @param index the index + * + * @return the character, word or sentence after <code>index</code> + */ + public String getAtIndex(int part, int index) { - return null; // TODO + String result = ""; + int startIndex = -1; + int endIndex = -1; + switch(part) + { + case AccessibleText.CHARACTER: + result = String.valueOf(text.charAt(index)); + break; + case AccessibleText.WORD: + startIndex = text.lastIndexOf(' ', index); + endIndex = text.indexOf(' ', startIndex + 1); + if (endIndex == -1) + endIndex = startIndex + 1; + result = text.substring(startIndex + 1, endIndex); + break; + case AccessibleText.SENTENCE: + default: + startIndex = text.lastIndexOf('.', index); + endIndex = text.indexOf('.', startIndex + 1); + if (endIndex == -1) + endIndex = startIndex + 1; + result = text.substring(startIndex + 1, endIndex); + break; + } + return result; } - public String getAfterIndex(int value0, int value1) - throws NotImplementedException + /** + * Returns the character, word or sentence after the specified index. The + * <code>part</code> parameter determines what is returned, the character, + * word or sentence after the index. + * + * @param part one of {@link AccessibleText#CHARACTER}, + * {@link AccessibleText#WORD} or + * {@link AccessibleText#SENTENCE}, specifying what is returned + * @param index the index + * + * @return the character, word or sentence after <code>index</code> + */ + public String getAfterIndex(int part, int index) { - return null; // TODO + String result = ""; + int startIndex = -1; + int endIndex = -1; + switch(part) + { + case AccessibleText.CHARACTER: + result = String.valueOf(text.charAt(index + 1)); + break; + case AccessibleText.WORD: + startIndex = text.indexOf(' ', index); + endIndex = text.indexOf(' ', startIndex + 1); + if (endIndex == -1) + endIndex = startIndex + 1; + result = text.substring(startIndex + 1, endIndex); + break; + case AccessibleText.SENTENCE: + default: + startIndex = text.indexOf('.', index); + endIndex = text.indexOf('.', startIndex + 1); + if (endIndex == -1) + endIndex = startIndex + 1; + result = text.substring(startIndex + 1, endIndex); + break; + } + return result; } - public String getBeforeIndex(int value0, int value1) - throws NotImplementedException + /** + * Returns the character, word or sentence before the specified index. The + * <code>part</code> parameter determines what is returned, the character, + * word or sentence before the index. + * + * @param part one of {@link AccessibleText#CHARACTER}, + * {@link AccessibleText#WORD} or + * {@link AccessibleText#SENTENCE}, specifying what is returned + * @param index the index + * + * @return the character, word or sentence before <code>index</code> + */ + public String getBeforeIndex(int part, int index) { - return null; // TODO + String result = ""; + int startIndex = -1; + int endIndex = -1; + switch(part) + { + case AccessibleText.CHARACTER: + result = String.valueOf(text.charAt(index - 1)); + break; + case AccessibleText.WORD: + endIndex = text.lastIndexOf(' ', index); + if (endIndex == -1) + endIndex = 0; + startIndex = text.lastIndexOf(' ', endIndex - 1); + result = text.substring(startIndex + 1, endIndex); + break; + case AccessibleText.SENTENCE: + default: + endIndex = text.lastIndexOf('.', index); + if (endIndex == -1) + endIndex = 0; + startIndex = text.lastIndexOf('.', endIndex - 1); + result = text.substring(startIndex + 1, endIndex); + break; + } + return result; } /** @@ -801,7 +943,14 @@ public abstract class AbstractButton extends JComponent View view = (View) getClientProperty(BasicHTML.propertyKey); if (view != null) { - + Document doc = view.getDocument(); + if (doc instanceof StyledDocument) + { + StyledDocument sDoc = (StyledDocument) doc; + Element charEl = sDoc.getCharacterElement(i); + if (charEl != null) + atts = charEl.getAttributes(); + } } return atts; } @@ -855,10 +1004,6 @@ public abstract class AbstractButton extends JComponent */ public AbstractButton() { - actionListener = createActionListener(); - changeListener = createChangeListener(); - itemListener = createItemListener(); - horizontalAlignment = CENTER; horizontalTextPosition = TRAILING; verticalAlignment = CENTER; @@ -872,7 +1017,10 @@ public abstract class AbstractButton extends JComponent setDisplayedMnemonicIndex(-1); setOpaque(true); text = ""; - updateUI(); + // testing on JRE1.5 shows that the iconTextGap default value is + // hard-coded here and the 'Button.iconTextGap' setting in the + // UI defaults is ignored, at least by the MetalLookAndFeel + iconTextGap = 4; } /** @@ -900,15 +1048,21 @@ public abstract class AbstractButton extends JComponent if (model != null) { model.removeActionListener(actionListener); + actionListener = null; model.removeChangeListener(changeListener); + changeListener = null; model.removeItemListener(itemListener); + itemListener = null; } ButtonModel old = model; model = newModel; if (model != null) { + actionListener = createActionListener(); model.addActionListener(actionListener); + changeListener = createChangeListener(); model.addChangeListener(changeListener); + itemListener = createItemListener(); model.addItemListener(itemListener); } firePropertyChange(MODEL_CHANGED_PROPERTY, old, model); @@ -927,6 +1081,8 @@ public abstract class AbstractButton extends JComponent if (icon != null) default_icon = icon; + + updateUI(); } /** @@ -1923,13 +2079,7 @@ public abstract class AbstractButton extends JComponent */ protected ActionListener createActionListener() { - return new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - AbstractButton.this.fireActionPerformed(e); - } - }; + return getEventHandler(); } /** @@ -1995,7 +2145,7 @@ public abstract class AbstractButton extends JComponent */ protected ChangeListener createChangeListener() { - return new ButtonChangeListener(); + return getEventHandler(); } /** @@ -2021,13 +2171,7 @@ public abstract class AbstractButton extends JComponent */ protected ItemListener createItemListener() { - return new ItemListener() - { - public void itemStateChanged(ItemEvent e) - { - AbstractButton.this.fireItemStateChanged(e); - } - }; + return getEventHandler(); } /** @@ -2490,4 +2634,17 @@ public abstract class AbstractButton extends JComponent super.setUIProperty(propertyName, value); } } + + /** + * Returns the combined event handler. The instance is created if + * necessary. + * + * @return the combined event handler + */ + EventHandler getEventHandler() + { + if (eventHandler == null) + eventHandler = new EventHandler(); + return eventHandler; + } } diff --git a/libjava/classpath/javax/swing/AbstractListModel.java b/libjava/classpath/javax/swing/AbstractListModel.java index 4b89689ddda..7d4b2bb2a87 100644 --- a/libjava/classpath/javax/swing/AbstractListModel.java +++ b/libjava/classpath/javax/swing/AbstractListModel.java @@ -164,7 +164,7 @@ public abstract class AbstractListModel implements ListModel, Serializable * * @return The set of listeners of the specified type */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/AbstractSpinnerModel.java b/libjava/classpath/javax/swing/AbstractSpinnerModel.java index 089e2048da2..d247a33136a 100644 --- a/libjava/classpath/javax/swing/AbstractSpinnerModel.java +++ b/libjava/classpath/javax/swing/AbstractSpinnerModel.java @@ -1,5 +1,5 @@ /* AbstractSpinnerModel.java -- - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -47,6 +47,8 @@ import javax.swing.event.EventListenerList; /** * Provides standard implementations for some of the methods in * {@link SpinnerModel}. + * + * @since 1.4 * * @author Ka-Hing Cheung */ @@ -54,6 +56,7 @@ public abstract class AbstractSpinnerModel implements SpinnerModel { private ChangeEvent changeEvent = new ChangeEvent(this); + /** Stores the listeners registered with the model. */ protected EventListenerList listenerList = new EventListenerList(); /** @@ -65,9 +68,10 @@ public abstract class AbstractSpinnerModel implements SpinnerModel } /** - * Adds a <code>ChangeListener</code>. + * Registers a <code>ChangeListener</code> with the model so that it will + * receive {@link ChangeEvent} notifications when the model changes. * - * @param listener the listener to add + * @param listener the listener to add (<code>null</code> is ignored). */ public void addChangeListener(ChangeListener listener) { @@ -80,7 +84,7 @@ public abstract class AbstractSpinnerModel implements SpinnerModel * @param c the type of listener * @return the listeners that are of the specific type */ - public EventListener[] getListeners(Class c) + public <T extends EventListener> T[] getListeners(Class<T> c) { return listenerList.getListeners(c); } diff --git a/libjava/classpath/javax/swing/ButtonGroup.java b/libjava/classpath/javax/swing/ButtonGroup.java index efa36b5f641..6a474f98d28 100644 --- a/libjava/classpath/javax/swing/ButtonGroup.java +++ b/libjava/classpath/javax/swing/ButtonGroup.java @@ -68,7 +68,7 @@ public class ButtonGroup implements Serializable private static final long serialVersionUID = 4259076101881721375L; /** Stores references to the buttons added to this button group. */ - protected Vector buttons = new Vector(); + protected Vector<AbstractButton> buttons = new Vector<AbstractButton>(); /** The currently selected button model. */ ButtonModel sel; @@ -129,7 +129,7 @@ public class ButtonGroup implements Serializable * * @return <code>Enumeration</code> over all added buttons */ - public Enumeration getElements() + public Enumeration<AbstractButton> getElements() { return buttons.elements(); } @@ -183,6 +183,10 @@ public class ButtonGroup implements Serializable if (old != null) old.setSelected(false); + + if (m != null) + sel.setSelected(true); + AbstractButton button = findButton(old); if (button != null) button.repaint(); diff --git a/libjava/classpath/javax/swing/DefaultBoundedRangeModel.java b/libjava/classpath/javax/swing/DefaultBoundedRangeModel.java index efca148f449..786e4ee9204 100644 --- a/libjava/classpath/javax/swing/DefaultBoundedRangeModel.java +++ b/libjava/classpath/javax/swing/DefaultBoundedRangeModel.java @@ -424,7 +424,7 @@ public class DefaultBoundedRangeModel * * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/DefaultButtonModel.java b/libjava/classpath/javax/swing/DefaultButtonModel.java index 020c904a4e9..c0eaea239b5 100644 --- a/libjava/classpath/javax/swing/DefaultButtonModel.java +++ b/libjava/classpath/javax/swing/DefaultButtonModel.java @@ -166,7 +166,7 @@ public class DefaultButtonModel implements ButtonModel, Serializable * * @return array of listeners */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } @@ -425,7 +425,7 @@ public class DefaultButtonModel implements ButtonModel, Serializable public void setRollover(boolean r) { // if this call does not represent a CHANGE in state, then return - if ((r && isRollover()) || (!r && !isRollover())) + if (r == isRollover()) return; // cannot set ROLLOVER property unless button is enabled diff --git a/libjava/classpath/javax/swing/DefaultComboBoxModel.java b/libjava/classpath/javax/swing/DefaultComboBoxModel.java index ef785f34dec..9b5bdb60d18 100644 --- a/libjava/classpath/javax/swing/DefaultComboBoxModel.java +++ b/libjava/classpath/javax/swing/DefaultComboBoxModel.java @@ -104,7 +104,7 @@ public class DefaultComboBoxModel extends AbstractListModel * * @throws NullPointerException if <code>vector</code> is <code>null</code>. */ - public DefaultComboBoxModel(Vector vector) + public DefaultComboBoxModel(Vector<?> vector) { this.list = vector; if (getSize() > 0) @@ -224,18 +224,26 @@ public class DefaultComboBoxModel extends AbstractListModel */ public void setSelectedItem(Object object) { - if (selectedItem == null) - { - if (object == null) - return; - } - else - { - if (selectedItem.equals(object)) - return; - } + // No item is selected and object is null, so no change required. + if (selectedItem == null && object == null) + return; + + // object is already selected so no change required. + if (selectedItem != null && selectedItem.equals(object)) + return; + + // Simply return if object is not in the list. + if (object != null && getIndexOf(object) == -1) + return; + + // Here we know that object is either an item in the list or null. + + // Handle the three change cases: selectedItem is null, object is + // non-null; selectedItem is non-null, object is null; + // selectedItem is non-null, object is non-null and they're not + // equal. selectedItem = object; - fireContentsChanged(this, -1, -1); + fireContentsChanged(this, -1, -1); } /** diff --git a/libjava/classpath/javax/swing/DefaultListModel.java b/libjava/classpath/javax/swing/DefaultListModel.java index 2d02874a7e4..674864cce3a 100644 --- a/libjava/classpath/javax/swing/DefaultListModel.java +++ b/libjava/classpath/javax/swing/DefaultListModel.java @@ -309,7 +309,7 @@ public class DefaultListModel extends AbstractListModel * * @return A new enumeration which iterates over the list */ - public Enumeration elements() + public Enumeration<?> elements() { return elements.elements(); } diff --git a/libjava/classpath/javax/swing/DefaultListSelectionModel.java b/libjava/classpath/javax/swing/DefaultListSelectionModel.java index 482ce2cc224..d1e2da85fe8 100644 --- a/libjava/classpath/javax/swing/DefaultListSelectionModel.java +++ b/libjava/classpath/javax/swing/DefaultListSelectionModel.java @@ -815,7 +815,7 @@ public class DefaultListSelectionModel implements Cloneable, * @see #getListSelectionListeners * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/DefaultSingleSelectionModel.java b/libjava/classpath/javax/swing/DefaultSingleSelectionModel.java index 1c6f473fdd3..3f79f0deed5 100644 --- a/libjava/classpath/javax/swing/DefaultSingleSelectionModel.java +++ b/libjava/classpath/javax/swing/DefaultSingleSelectionModel.java @@ -174,7 +174,7 @@ public class DefaultSingleSelectionModel * * @since 1.3 */ - public EventListener[] getListeners(Class listenerClass) + public <T extends EventListener> T[] getListeners(Class<T> listenerClass) { return listenerList.getListeners(listenerClass); } diff --git a/libjava/classpath/javax/swing/JButton.java b/libjava/classpath/javax/swing/JButton.java index 787adb87cf1..878cfa68397 100644 --- a/libjava/classpath/javax/swing/JButton.java +++ b/libjava/classpath/javax/swing/JButton.java @@ -132,8 +132,8 @@ public class JButton extends AbstractButton public JButton(String text, Icon icon) { super(); - init(text, icon); setModel(new DefaultButtonModel()); + init(text, icon); defaultCapable = true; } diff --git a/libjava/classpath/javax/swing/JComboBox.java b/libjava/classpath/javax/swing/JComboBox.java index c75a94bdc36..fa6941cf977 100644 --- a/libjava/classpath/javax/swing/JComboBox.java +++ b/libjava/classpath/javax/swing/JComboBox.java @@ -196,7 +196,7 @@ public class JComboBox extends JComponent implements ItemSelectable, * * @param itemVector vector containing list of items for this JComboBox. */ - public JComboBox(Vector itemVector) + public JComboBox(Vector<?> itemVector) { this(new DefaultComboBoxModel(itemVector)); diff --git a/libjava/classpath/javax/swing/JComponent.java b/libjava/classpath/javax/swing/JComponent.java index fa83502946d..5ec5079223e 100644 --- a/libjava/classpath/javax/swing/JComponent.java +++ b/libjava/classpath/javax/swing/JComponent.java @@ -69,6 +69,7 @@ import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.beans.VetoableChangeSupport; import java.io.Serializable; +import java.util.ArrayList; import java.util.EventListener; import java.util.Hashtable; import java.util.Locale; @@ -503,27 +504,6 @@ public abstract class JComponent extends Container implements Serializable } } - /** - * An explicit value for the component's preferred size; if not set by a - * user, this is calculated on the fly by delegating to the {@link - * ComponentUI#getPreferredSize} method on the {@link #ui} property. - */ - Dimension preferredSize; - - /** - * An explicit value for the component's minimum size; if not set by a - * user, this is calculated on the fly by delegating to the {@link - * ComponentUI#getMinimumSize} method on the {@link #ui} property. - */ - Dimension minimumSize; - - /** - * An explicit value for the component's maximum size; if not set by a - * user, this is calculated on the fly by delegating to the {@link - * ComponentUI#getMaximumSize} method on the {@link #ui} property. - */ - Dimension maximumSize; - /** * A value between 0.0 and 1.0 indicating the preferred horizontal * alignment of the component, relative to its siblings. The values @@ -561,14 +541,6 @@ public abstract class JComponent extends Container implements Serializable */ Border border; - /** - * The text to show in the tooltip associated with this component. - * - * @see #setToolTipText - * @see #getToolTipText() - */ - String toolTipText; - /** * The popup menu for the component. * @@ -687,7 +659,7 @@ public abstract class JComponent extends Container implements Serializable * Indicates whether we are calling paintDoubleBuffered() from * paintImmadiately (RepaintManager) or from paint() (AWT refresh). */ - static private boolean isRepainting = false; + static boolean isRepainting = false; /** * Listeners for events other than {@link PropertyChangeEvent} are @@ -783,6 +755,13 @@ public abstract class JComponent extends Container implements Serializable */ public static final int WHEN_IN_FOCUSED_WINDOW = 2; + + /** + * Used to optimize painting. This is set in paintImmediately2() to specify + * the exact component path to be painted by paintChildren. + */ + Component paintChild; + /** * Indicates if the opaque property has been set by a client program or by * the UI. @@ -868,7 +847,12 @@ public abstract class JComponent extends Container implements Serializable t.put(key, value); else t.remove(key); - firePropertyChange(key.toString(), old, value); + + // When both old and new value are null, no event is fired. This is + // different from what firePropertyChange() normally does, so we add this + // check here. + if (old != null || value != null) + firePropertyChange(key.toString(), old, value); } /** @@ -943,12 +927,12 @@ public abstract class JComponent extends Container implements Serializable * * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == PropertyChangeListener.class) - return getPropertyChangeListeners(); + return (T[]) getPropertyChangeListeners(); else if (listenerType == VetoableChangeListener.class) - return getVetoableChangeListeners(); + return (T[]) getVetoableChangeListeners(); else return listenerList.getListeners(listenerType); } @@ -1270,37 +1254,38 @@ public abstract class JComponent extends Container implements Serializable } /** - * Get the component's maximum size. If the {@link #maximumSize} property - * has been explicitly set, it is returned. If the {@link #maximumSize} + * Get the component's maximum size. If the <code>maximumSize</code> property + * has been explicitly set, it is returned. If the <code>maximumSize</code> * property has not been set but the {@link #ui} property has been, the * result of {@link ComponentUI#getMaximumSize} is returned. If neither * property has been set, the result of {@link Container#getMaximumSize} * is returned. * - * @return The maximum size of the component + * @return the maximum size of the component * - * @see #maximumSize - * @see #setMaximumSize + * @see Component#setMaximumSize + * @see Component#getMaximumSize() + * @see Component#isMaximumSizeSet() + * @see ComponentUI#getMaximumSize(JComponent) */ public Dimension getMaximumSize() { - if (maximumSize != null) - return maximumSize; - - if (ui != null) + Dimension size = null; + if (isMaximumSizeSet()) + size = super.getMaximumSize(); + else { - Dimension s = ui.getMaximumSize(this); - if (s != null) - return s; + if (ui != null) + size = ui.getMaximumSize(this); + if (size == null) + size = super.getMaximumSize(); } - - Dimension p = super.getMaximumSize(); - return p; + return size; } /** - * Get the component's minimum size. If the {@link #minimumSize} property - * has been explicitly set, it is returned. If the {@link #minimumSize} + * Get the component's minimum size. If the <code>minimumSize</code> property + * has been explicitly set, it is returned. If the <code>minimumSize</code> * property has not been set but the {@link #ui} property has been, the * result of {@link ComponentUI#getMinimumSize} is returned. If neither * property has been set, the result of {@link Container#getMinimumSize} @@ -1308,97 +1293,57 @@ public abstract class JComponent extends Container implements Serializable * * @return The minimum size of the component * - * @see #minimumSize - * @see #setMinimumSize + * @see Component#setMinimumSize + * @see Component#getMinimumSize() + * @see Component#isMinimumSizeSet() + * @see ComponentUI#getMinimumSize(JComponent) */ public Dimension getMinimumSize() { - if (minimumSize != null) - return minimumSize; - - if (ui != null) + Dimension size = null; + if (isMinimumSizeSet()) + size = super.getMinimumSize(); + else { - Dimension s = ui.getMinimumSize(this); - if (s != null) - return s; + if (ui != null) + size = ui.getMinimumSize(this); + if (size == null) + size = super.getMinimumSize(); } - - Dimension p = super.getMinimumSize(); - return p; + return size; } /** - * Get the component's preferred size. If the {@link #preferredSize} - * property has been explicitly set, it is returned. If the {@link - * #preferredSize} property has not been set but the {@link #ui} property - * has been, the result of {@link ComponentUI#getPreferredSize} is + * Get the component's preferred size. If the <code>preferredSize</code> + * property has been explicitly set, it is returned. If the + * <code>preferredSize</code> property has not been set but the {@link #ui} + * property has been, the result of {@link ComponentUI#getPreferredSize} is * returned. If neither property has been set, the result of {@link * Container#getPreferredSize} is returned. * * @return The preferred size of the component * - * @see #preferredSize - * @see #setPreferredSize + * @see Component#setPreferredSize + * @see Component#getPreferredSize() + * @see Component#isPreferredSizeSet() + * @see ComponentUI#getPreferredSize(JComponent) */ public Dimension getPreferredSize() { - Dimension prefSize = null; - if (preferredSize != null) - prefSize = new Dimension(preferredSize); - - else if (ui != null) + Dimension size = null; + if (isPreferredSizeSet()) + size = super.getPreferredSize(); + else { - Dimension s = ui.getPreferredSize(this); - if (s != null) - prefSize = s; + if (ui != null) + size = ui.getPreferredSize(this); + if (size == null) + size = super.getPreferredSize(); } - - if (prefSize == null) - prefSize = super.getPreferredSize(); - - return prefSize; + return size; } /** - * Checks if a maximum size was explicitely set on the component. - * - * @return <code>true</code> if a maximum size was set, - * <code>false</code> otherwise - * - * @since 1.3 - */ - public boolean isMaximumSizeSet() - { - return maximumSize != null; - } - - /** - * Checks if a minimum size was explicitely set on the component. - * - * @return <code>true</code> if a minimum size was set, - * <code>false</code> otherwise - * - * @since 1.3 - */ - public boolean isMinimumSizeSet() - { - return minimumSize != null; - } - - /** - * Checks if a preferred size was explicitely set on the component. - * - * @return <code>true</code> if a preferred size was set, - * <code>false</code> otherwise - * - * @since 1.3 - */ - public boolean isPreferredSizeSet() - { - return preferredSize != null; - } - - /** * Return the value of the <code>nextFocusableComponent</code> property. * * @return The current value of the property, or <code>null</code> @@ -1491,14 +1436,12 @@ public abstract class JComponent extends Container implements Serializable { JToolTip toolTip = new JToolTip(); toolTip.setComponent(this); - toolTip.setTipText(toolTipText); - return toolTip; } /** - * Return the location at which the {@link #toolTipText} property should be - * displayed, when triggered by a particular mouse event. + * Return the location at which the <code>toolTipText</code> property should + * be displayed, when triggered by a particular mouse event. * * @param event The event the tooltip is being presented in response to * @@ -1511,53 +1454,56 @@ public abstract class JComponent extends Container implements Serializable } /** - * Set the value of the {@link #toolTipText} property. + * Set the tooltip text for this component. If a non-<code>null</code> + * value is set, this component is registered in the + * <code>ToolTipManager</code> in order to turn on tooltips for this + * component. If a <code>null</code> value is set, tooltips are turne off + * for this component. * - * @param text The new property value + * @param text the tooltip text for this component * * @see #getToolTipText() + * @see #getToolTipText(MouseEvent) */ public void setToolTipText(String text) { + String old = getToolTipText(); + putClientProperty(TOOL_TIP_TEXT_KEY, text); + ToolTipManager ttm = ToolTipManager.sharedInstance(); if (text == null) - { - ToolTipManager.sharedInstance().unregisterComponent(this); - toolTipText = null; - return; - } - - // XXX: The tip text doesn't get updated unless you set it to null - // and then to something not-null. This is consistent with the behaviour - // of Sun's ToolTipManager. - - String oldText = toolTipText; - toolTipText = text; - - if (oldText == null) - ToolTipManager.sharedInstance().registerComponent(this); + ttm.unregisterComponent(this); + else if (old == null) + ttm.registerComponent(this); } /** - * Get the value of the {@link #toolTipText} property. + * Returns the current tooltip text for this component, or <code>null</code> + * if none has been set. * - * @return The current property value + * @return the current tooltip text for this component, or <code>null</code> + * if none has been set * * @see #setToolTipText + * @see #getToolTipText(MouseEvent) */ public String getToolTipText() { - return toolTipText; + return (String) getClientProperty(TOOL_TIP_TEXT_KEY); } /** - * Get the value of the {@link #toolTipText} property, in response to a - * particular mouse event. + * Returns the tooltip text for this component for a particular mouse + * event. This can be used to support context sensitive tooltips that can + * change with the mouse location. By default this returns the static + * tooltip text returned by {@link #getToolTipText()}. * - * @param event The mouse event which triggered the tooltip + * @param event the mouse event which triggered the tooltip * - * @return The current property value + * @return the tooltip text for this component for a particular mouse + * event * * @see #setToolTipText + * @see #getToolTipText() */ public String getToolTipText(MouseEvent event) { @@ -1850,7 +1796,7 @@ public abstract class JComponent extends Container implements Serializable && rm.isDoubleBufferingEnabled()) { Rectangle clip = g.getClipBounds(); - paintDoubleBuffered(clip); + paintDoubleBuffered(clip.x, clip.y, clip.width, clip.height); } else { @@ -1865,8 +1811,22 @@ public abstract class JComponent extends Container implements Serializable dragBuffer = null; } - if (g.getClip() == null) - g.setClip(0, 0, getWidth(), getHeight()); + Rectangle clip = g.getClipBounds(); + int clipX, clipY, clipW, clipH; + if (clip == null) + { + clipX = 0; + clipY = 0; + clipW = getWidth(); + clipH = getHeight(); + } + else + { + clipX = clip.x; + clipY = clip.y; + clipW = clip.width; + clipH = clip.height; + } if (dragBuffer != null && dragBufferInitialized) { g.drawImage(dragBuffer, 0, 0, this); @@ -1874,14 +1834,51 @@ public abstract class JComponent extends Container implements Serializable else { Graphics g2 = getComponentGraphics(g); - paintComponent(g2); - paintBorder(g2); + if (! isOccupiedByChild(clipX, clipY, clipW, clipH)) + { + paintComponent(g2); + paintBorder(g2); + } paintChildren(g2); } } } /** + * Determines if a region of this component is completely occupied by + * an opaque child component, in which case we don't need to bother + * painting this component at all. + * + * @param x the area, x coordinate + * @param y the area, y coordinate + * @param w the area, width + * @param h the area, height + * + * @return <code>true</code> if the specified area is completely covered + * by a child component, <code>false</code> otherwise + */ + private boolean isOccupiedByChild(int x, int y, int w, int h) + { + boolean occupied = false; + int count = getComponentCount(); + for (int i = 0; i < count; i++) + { + Component child = getComponent(i); + int cx = child.getX(); + int cy = child.getY(); + int cw = child.getWidth(); + int ch = child.getHeight(); + if (child.isVisible() && x >= cx && x + w <= cx + cw && y >= cy + && y + h <= cy + ch) + { + occupied = child.isOpaque(); + break; + } + } + return occupied; + } + + /** * Initializes the drag buffer by creating a new image and painting this * component into it. */ @@ -1940,7 +1937,14 @@ public abstract class JComponent extends Container implements Serializable // Need to lock the tree to avoid problems with AWT and concurrency. synchronized (getTreeLock()) { - for (int i = getComponentCount() - 1; i >= 0; i--) + // Fast forward to the child to paint, if set by + // paintImmediately2() + int i = getComponentCount() - 1; + if (paintChild != null && paintChild.isOpaque()) + { + for (; i >= 0 && getComponent(i) != paintChild; i--); + } + for (; i >= 0; i--) { Component child = getComponent(i); if (child != null && child.isLightweight() @@ -1958,7 +1962,8 @@ public abstract class JComponent extends Container implements Serializable Rectangle clip = g.getClipBounds(); // A copy. SwingUtilities.computeIntersection(cx, cy, cw, ch, clip); - if (isCompletelyObscured(i, clip)) + if (isCompletelyObscured(i, clip.x, clip.y, + clip.width, clip.height)) continue; // Continues the for-loop. } Graphics cg = g.create(cx, cy, cw, ch); @@ -1984,12 +1989,15 @@ public abstract class JComponent extends Container implements Serializable * of its siblings. * * @param index the index of the child component - * @param rect the region to check + * @param x the region to check, x coordinate + * @param y the region to check, y coordinate + * @param w the region to check, width + * @param h the region to check, height * * @return <code>true</code> if the region is completely obscured by a * sibling, <code>false</code> otherwise */ - private boolean isCompletelyObscured(int index, Rectangle rect) + private boolean isCompletelyObscured(int index, int x, int y, int w, int h) { boolean obscured = false; for (int i = index - 1; i >= 0 && obscured == false; i--) @@ -1998,10 +2006,10 @@ public abstract class JComponent extends Container implements Serializable if (sib.isVisible()) { Rectangle sibRect = sib.getBounds(rectCache); - if (sib.isOpaque() && rect.x >= sibRect.x - && (rect.x + rect.width) <= (sibRect.x + sibRect.width) - && rect.y >= sibRect.y - && (rect.y + rect.height) <= (sibRect.y + sibRect.height)) + if (sib.isOpaque() && x >= sibRect.x + && (x + w) <= (sibRect.x + sibRect.width) + && y >= sibRect.y + && (y + h) <= (sibRect.y + sibRect.height)) { obscured = true; } @@ -2011,6 +2019,39 @@ public abstract class JComponent extends Container implements Serializable } /** + * Checks if a component/rectangle is partially obscured by one of its + * siblings. + * Note that this doesn't check for completely obscured, this is + * done by isCompletelyObscured() and should probably also be checked. + * + * @param i the component index from which to start searching + * @param x the x coordinate of the rectangle to check + * @param y the y coordinate of the rectangle to check + * @param w the width of the rectangle to check + * @param h the height of the rectangle to check + * + * @return <code>true</code> if the rectangle is partially obscured + */ + private boolean isPartiallyObscured(int i, int x, int y, int w, int h) + { + boolean obscured = false; + for (int j = i - 1; j >= 0 && ! obscured; j--) + { + Component sibl = getComponent(j); + if (sibl.isVisible()) + { + Rectangle rect = sibl.getBounds(rectCache); + if (!(x + w <= rect.x) + || (y + h <= rect.y) + || (x >= rect.x + rect.width) + || (y >= rect.y + rect.height)) + obscured = true; + } + } + return obscured; + } + + /** * Paint the component's body. This usually means calling {@link * ComponentUI#update} on the {@link #ui} property of the component, if * it is non-<code>null</code>. You may override this if you wish to @@ -2050,7 +2091,26 @@ public abstract class JComponent extends Container implements Serializable */ public void paintImmediately(int x, int y, int w, int h) { - paintImmediately(new Rectangle(x, y, w, h)); + // Find opaque parent and call paintImmediately2() on it. + if (isShowing()) + { + Component c = this; + Component p; + while (c != null && ! c.isOpaque()) + { + p = c.getParent(); + if (p != null) + { + x += c.getX(); + y += c.getY(); + c = p; + } + } + if (c instanceof JComponent) + ((JComponent) c).paintImmediately2(x, y, w, h); + else + c.repaint(x, y, w, h); + } } /** @@ -2073,60 +2133,202 @@ public abstract class JComponent extends Container implements Serializable */ public void paintImmediately(Rectangle r) { - // Try to find a root pane for this component. - //Component root = findPaintRoot(r); - Component root = findPaintRoot(r); - // If no paint root is found, then this component is completely overlapped - // by another component and we don't need repainting. - if (root == null|| !root.isShowing()) - return; - SwingUtilities.convertRectangleToAncestor(this, r, root); - if (root instanceof JComponent) - ((JComponent) root).paintImmediately2(r); - else - root.repaint(r.x, r.y, r.width, r.height); + paintImmediately(r.x, r.y, r.width, r.height); } /** * Performs the actual work of paintImmediatly on the repaint root. * - * @param r the area to be repainted + * @param x the area to be repainted, X coordinate + * @param y the area to be repainted, Y coordinate */ - void paintImmediately2(Rectangle r) + void paintImmediately2(int x, int y, int w, int h) { - isRepainting = true; + // Optimization for components that are always painted on top. + boolean onTop = onTop() && isOpaque(); + + // Fetch the RepaintManager. RepaintManager rm = RepaintManager.currentManager(this); - if (rm.isDoubleBufferingEnabled() && isPaintingDoubleBuffered()) - paintDoubleBuffered(r); - else - paintSimple(r); - isRepainting = false; + + // The painting clip; + int paintX = x; + int paintY = y; + int paintW = w; + int paintH = h; + + // If we should paint buffered or not. + boolean haveBuffer = false; + + // The component that is finally triggered for painting. + JComponent paintRoot = this; + + // Stores the component and all its parents. This will be used to limit + // the actually painted components in paintChildren by setting + // the field paintChild. + int pIndex = -1; + int pCount = 0; + ArrayList components = new ArrayList(); + + // Offset to subtract from the paintRoot rectangle when painting. + int offsX = 0; + int offsY = 0; + + // The current component and its child. + Component child; + Container c; + + // Find appropriate paint root. + for (c = this, child = null; + c != null && ! (c instanceof Window) && ! (c instanceof Applet); + child = c, c = c.getParent()) + { + JComponent jc = c instanceof JComponent ? (JComponent) c : null; + components.add(c); + if (! onTop && jc != null && ! jc.isOptimizedDrawingEnabled()) + { + // Indicates whether we reset the paint root to be the current + // component. + boolean updatePaintRoot = false; + + // Check obscured state of the child. + // Generally, we have 3 cases here: + // 1. Not obscured. No need to paint from the parent. + // 2. Partially obscured. Paint from the parent. + // 3. Completely obscured. No need to paint anything. + if (c != this) + { + if (jc.isPaintRoot()) + updatePaintRoot = true; + else + { + int count = c.getComponentCount(); + int i = 0; + for (; i < count && c.getComponent(i) != child; i++); + + if (jc.isCompletelyObscured(i, paintX, paintY, paintW, + paintH)) + return; // No need to paint anything. + else if (jc.isPartiallyObscured(i, paintX, paintY, paintW, + paintH)) + updatePaintRoot = true; + + } + } + if (updatePaintRoot) + { + // Paint from parent. + paintRoot = jc; + pIndex = pCount; + offsX = 0; + offsY = 0; + haveBuffer = false; + } + } + pCount++; + // Check if component is double buffered. + if (rm.isDoubleBufferingEnabled() && jc != null + && jc.isDoubleBuffered()) + { + haveBuffer = true; + } + + // Clip the paint region with the parent. + if (! onTop) + { + paintX = Math.max(0, paintX); + paintY = Math.max(0, paintY); + paintW = Math.min(c.getWidth(), paintW + paintX) - paintX; + paintH = Math.min(c.getHeight(), paintH + paintY) - paintY; + int dx = c.getX(); + int dy = c.getY(); + paintX += dx; + paintY += dy; + offsX += dx; + offsY += dy; + } + } + if (c != null && c.getPeer() != null && paintW > 0 && paintH > 0) + { + isRepainting = true; + paintX -= offsX; + paintY -= offsY; + + // Set the painting path so that paintChildren paints only what we + // want. + if (paintRoot != this) + { + for (int i = pIndex; i > 0; i--) + { + Component paintParent = (Component) components.get(i); + if (paintParent instanceof JComponent) + ((JComponent) paintParent).paintChild = + (Component) components.get(i - 1); + } + } + + // Actually trigger painting. + if (haveBuffer) + paintRoot.paintDoubleBuffered(paintX, paintY, paintW, paintH); + else + { + Graphics g = paintRoot.getGraphics(); + try + { + g.setClip(paintX, paintY, paintW, paintH); + paintRoot.paint(g); + } + finally + { + g.dispose(); + } + } + + // Reset the painting path. + if (paintRoot != this) + { + for (int i = pIndex; i > 0; i--) + { + Component paintParent = (Component) components.get(i); + if (paintParent instanceof JComponent) + ((JComponent) paintParent).paintChild = null; + } + } + + isRepainting = false; + } } /** - * Returns true if we must paint double buffered, that is, when this - * component or any of it's ancestors are double buffered. + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. * - * @return true if we must paint double buffered, that is, when this - * component or any of it's ancestors are double buffered + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others */ - private boolean isPaintingDoubleBuffered() + boolean onTop() { - boolean doubleBuffered = isDoubleBuffered(); - Component parent = getParent(); - while (! doubleBuffered && parent != null) - { - doubleBuffered = parent instanceof JComponent - && ((JComponent) parent).isDoubleBuffered(); - parent = parent.getParent(); - } - return doubleBuffered; + return false; + } + + /** + * This returns true when a component needs to force itself as a paint + * origin. This is used for example in JViewport to make sure that it + * gets to update its backbuffer. + * + * @return true when a component needs to force itself as a paint + * origin + */ + boolean isPaintRoot() + { + return false; } /** * Performs double buffered repainting. */ - private void paintDoubleBuffered(Rectangle r) + private void paintDoubleBuffered(int x, int y, int w, int h) { RepaintManager rm = RepaintManager.currentManager(this); @@ -2143,7 +2345,7 @@ public abstract class JComponent extends Container implements Serializable //Rectangle targetClip = SwingUtilities.convertRectangle(this, r, root); Graphics g2 = buffer.getGraphics(); clipAndTranslateGraphics(root, this, g2); - g2.clipRect(r.x, r.y, r.width, r.height); + g2.clipRect(x, y, w, h); g2 = getComponentGraphics(g2); paintingDoubleBuffered = true; try @@ -2164,7 +2366,7 @@ public abstract class JComponent extends Container implements Serializable } // Paint the buffer contents on screen. - rm.commitBuffer(this, r); + rm.commitBuffer(this, x, y, w, h); } /** @@ -2577,7 +2779,7 @@ public abstract class JComponent extends Container implements Serializable KeyEvent e, int condition, boolean pressed) - { + { if (isEnabled()) { Action act = null; @@ -2591,7 +2793,7 @@ public abstract class JComponent extends Container implements Serializable if (cmd instanceof ActionListenerProxy) act = (Action) cmd; else - act = (Action) getActionMap().get(cmd); + act = getActionMap().get(cmd); } } if (act != null && act.isEnabled()) @@ -2741,9 +2943,25 @@ public abstract class JComponent extends Container implements Serializable */ public void scrollRectToVisible(Rectangle r) { - Component p = getParent(); - if (p instanceof JComponent) - ((JComponent) p).scrollRectToVisible(r); + // Search nearest JComponent. + int xOffs = getX(); + int yOffs = getY(); + Component p; + for (p = getParent(); p != null && ! (p instanceof JComponent); + p = p.getParent()) + { + xOffs += p.getX(); + yOffs += p.getY(); + } + if (p != null) + { + r.x += xOffs; + r.y += yOffs; + JComponent jParent = (JComponent) p; + jParent.scrollRectToVisible(r); + r.x -= xOffs; + r.y -= yOffs; + } } /** @@ -2862,57 +3080,6 @@ public abstract class JComponent extends Container implements Serializable } /** - * Set the value of the {@link #maximumSize} property. The passed value is - * copied, the later direct changes on the argument have no effect on the - * property value. - * - * @param max The new value of the property - */ - public void setMaximumSize(Dimension max) - { - Dimension oldMaximumSize = maximumSize; - if (max != null) - maximumSize = new Dimension(max); - else - maximumSize = null; - firePropertyChange("maximumSize", oldMaximumSize, maximumSize); - } - - /** - * Set the value of the {@link #minimumSize} property. The passed value is - * copied, the later direct changes on the argument have no effect on the - * property value. - * - * @param min The new value of the property - */ - public void setMinimumSize(Dimension min) - { - Dimension oldMinimumSize = minimumSize; - if (min != null) - minimumSize = new Dimension(min); - else - minimumSize = null; - firePropertyChange("minimumSize", oldMinimumSize, minimumSize); - } - - /** - * Set the value of the {@link #preferredSize} property. The passed value is - * copied, the later direct changes on the argument have no effect on the - * property value. - * - * @param pref The new value of the property - */ - public void setPreferredSize(Dimension pref) - { - Dimension oldPreferredSize = preferredSize; - if (pref != null) - preferredSize = new Dimension(pref); - else - preferredSize = null; - firePropertyChange("preferredSize", oldPreferredSize, preferredSize); - } - - /** * Set the specified component to be the next component in the * focus cycle, overriding the {@link FocusTraversalPolicy} for * this component. @@ -3571,130 +3738,6 @@ public abstract class JComponent extends Container implements Serializable jc.fireAncestorEvent(ancestor, id); } } - - /** - * Finds a suitable paint root for painting this component. This method first - * checks if this component is overlapped using - * {@link #findOverlapFreeParent(Rectangle)}. The returned paint root is then - * feeded to {@link #findOpaqueParent(Component)} to find the nearest opaque - * component for this paint root. If no paint is necessary, then we return - * <code>null</code>. - * - * @param c the clip of this component - * - * @return the paint root or <code>null</code> if no painting is necessary - */ - private Component findPaintRoot(Rectangle c) - { - Component p = findOverlapFreeParent(c); - if (p == null) - return null; - Component root = findOpaqueParent(p); - return root; - } - - /** - * Scans the containment hierarchy upwards for components that overlap the - * this component in the specified clip. This method returns - * <code>this</code>, if no component overlaps this component. It returns - * <code>null</code> if another component completely covers this component - * in the specified clip (no repaint necessary). If another component partly - * overlaps this component in the specified clip, then the parent of this - * component is returned (this is the component that must be used as repaint - * root). For efficient lookup, the method - * {@link #isOptimizedDrawingEnabled()} is used. - * - * @param clip the clip of this component - * - * @return the paint root, or <code>null</code> if no paint is necessary - */ - private Component findOverlapFreeParent(Rectangle clip) - { - Rectangle currentClip = clip; - Component found = this; - Container parent = this; - - while (parent != null && !(parent instanceof Window)) - { - Container newParent = parent.getParent(); - if (newParent == null || newParent instanceof Window) - break; - // If the parent is optimizedDrawingEnabled, then its children are - // tiled and cannot have an overlapping child. Go directly to next - // parent. - if ((newParent instanceof JComponent - && ((JComponent) newParent).isOptimizedDrawingEnabled())) - - { - parent = newParent; - continue; - } - - // If the parent is not optimizedDrawingEnabled, we must check if the - // parent or some neighbor overlaps the current clip. - - // This is the current clip converted to the parent's coordinate - // system. TODO: We can do this more efficiently by succesively - // cumulating the parent-child translations. - Rectangle target = SwingUtilities.convertRectangle(found, - currentClip, - newParent); - - // We have an overlap if either: - // - The new parent itself doesn't completely cover the clip - // (this can be the case with viewports). - // - If some higher-level (than the current) children of the new parent - // intersect the target rectangle. - Rectangle parentRect = SwingUtilities.getLocalBounds(newParent); - boolean haveOverlap = - ! SwingUtilities.isRectangleContainingRectangle(parentRect, target); - if (! haveOverlap) - { - Component child; - for (int i = 0; (child = newParent.getComponent(i)) != parent && !haveOverlap; i++) - { - Rectangle childRect = child.getBounds(); - haveOverlap = target.intersects(childRect); - } - } - if (haveOverlap) - { - found = newParent; - currentClip = target; - } - parent = newParent; - } - //System.err.println("overlapfree parent: " + found); - return found; - } - - /** - * Finds the nearest component to <code>c</code> (upwards in the containment - * hierarchy), that is opaque. If <code>c</code> itself is opaque, - * this returns <code>c</code> itself. - * - * @param c the start component for the search - * @return the nearest component to <code>c</code> (upwards in the containment - * hierarchy), that is opaque; If <code>c</code> itself is opaque, - * this returns <code>c</code> itself - */ - private Component findOpaqueParent(Component c) - { - Component found = c; - while (true) - { - if ((found instanceof JComponent) && ((JComponent) found).isOpaque()) - break; - else if (!(found instanceof JComponent) && !found.isLightweight()) - break; - Container p = found.getParent(); - if (p == null) - break; - else - found = p; - } - return found; - } /** * This is the method that gets called when the WHEN_IN_FOCUSED_WINDOW map diff --git a/libjava/classpath/javax/swing/JDialog.java b/libjava/classpath/javax/swing/JDialog.java index 08dada2fd81..495c9c791d7 100644 --- a/libjava/classpath/javax/swing/JDialog.java +++ b/libjava/classpath/javax/swing/JDialog.java @@ -38,6 +38,7 @@ exception statement from your version. */ package javax.swing; +import java.awt.AWTEvent; import java.awt.Component; import java.awt.Container; import java.awt.Dialog; @@ -97,7 +98,7 @@ public class JDialog extends Dialog implements Accessible, WindowConstants, protected boolean rootPaneCheckingEnabled = false; /** The default action taken when closed. */ - private int close_action = HIDE_ON_CLOSE; + private int closeAction = HIDE_ON_CLOSE; /** Whether JDialogs are decorated by the Look and Feel. */ private static boolean decorated; @@ -245,6 +246,10 @@ public class JDialog extends Dialog implements Accessible, WindowConstants, */ protected void dialogInit() { + // We need to explicitly enable events here so that our processKeyEvent() + // and processWindowEvent() gets called. + enableEvents(AWTEvent.WINDOW_EVENT_MASK); + // FIXME: Do a check on GraphicsEnvironment.isHeadless() setLocale(JComponent.getDefaultLocale()); getRootPane(); // Will do set/create. @@ -507,37 +512,23 @@ public class JDialog extends Dialog implements Accessible, WindowConstants, */ protected void processWindowEvent(WindowEvent e) { - // System.out.println("PROCESS_WIN_EV-1: " + e); super.processWindowEvent(e); - // System.out.println("PROCESS_WIN_EV-2: " + e); - switch (e.getID()) + if (e.getID() == WindowEvent.WINDOW_CLOSING) { - case WindowEvent.WINDOW_CLOSING: - { - switch (getDefaultCloseOperation()) - { - case DISPOSE_ON_CLOSE: - { - dispose(); - break; - } - case HIDE_ON_CLOSE: - { - setVisible(false); - break; - } - case DO_NOTHING_ON_CLOSE: - break; - } - break; - } - case WindowEvent.WINDOW_CLOSED: - case WindowEvent.WINDOW_OPENED: - case WindowEvent.WINDOW_ICONIFIED: - case WindowEvent.WINDOW_DEICONIFIED: - case WindowEvent.WINDOW_ACTIVATED: - case WindowEvent.WINDOW_DEACTIVATED: - break; + switch (closeAction) + { + case EXIT_ON_CLOSE: + System.exit(0); + break; + case DISPOSE_ON_CLOSE: + dispose(); + break; + case HIDE_ON_CLOSE: + setVisible(false); + break; + case DO_NOTHING_ON_CLOSE: + break; + } } } @@ -554,7 +545,7 @@ public class JDialog extends Dialog implements Accessible, WindowConstants, must return the invalid code, and the behaviour defaults to DO_NOTHING_ON_CLOSE. processWindowEvent above handles this */ - close_action = operation; + closeAction = operation; } /** @@ -565,7 +556,7 @@ public class JDialog extends Dialog implements Accessible, WindowConstants, */ public int getDefaultCloseOperation() { - return close_action; + return closeAction; } /** diff --git a/libjava/classpath/javax/swing/JEditorPane.java b/libjava/classpath/javax/swing/JEditorPane.java index 4ae3c5a1c6b..38b0761f00f 100644 --- a/libjava/classpath/javax/swing/JEditorPane.java +++ b/libjava/classpath/javax/swing/JEditorPane.java @@ -40,6 +40,8 @@ package javax.swing; import java.awt.Container; import java.awt.Dimension; +import java.io.BufferedInputStream; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -47,6 +49,7 @@ import java.io.Reader; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; import java.util.HashMap; import javax.accessibility.AccessibleContext; @@ -56,6 +59,8 @@ import javax.accessibility.AccessibleStateSet; import javax.accessibility.AccessibleText; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; +import javax.swing.plaf.TextUI; +import javax.swing.text.AbstractDocument; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultEditorKit; import javax.swing.text.Document; @@ -482,6 +487,34 @@ public class JEditorPane extends JTextComponent } /** + * Used to store a mapping for content-type to editor kit class. + */ + private static class EditorKitMapping + { + /** + * The classname of the editor kit. + */ + String className; + + /** + * The classloader with which the kit is to be loaded. + */ + ClassLoader classLoader; + + /** + * Creates a new EditorKitMapping object. + * + * @param cn the classname + * @param cl the classloader + */ + EditorKitMapping(String cn, ClassLoader cl) + { + className = cn; + classLoader = cl; + } + } + + /** * An EditorKit used for plain text. This is the default editor kit for * JEditorPanes. * @@ -505,19 +538,159 @@ public class JEditorPane extends JTextComponent } } + /** + * A special stream that can be cancelled. + */ + private class PageStream + extends FilterInputStream + { + /** + * True when the stream has been cancelled, false otherwise. + */ + private boolean cancelled; + + protected PageStream(InputStream in) + { + super(in); + cancelled = false; + } + + private void checkCancelled() + throws IOException + { + if (cancelled) + throw new IOException("Stream has been cancelled"); + } + + void cancel() + { + cancelled = true; + } + + public int read() + throws IOException + { + checkCancelled(); + return super.read(); + } + + public int read(byte[] b, int off, int len) + throws IOException + { + checkCancelled(); + return super.read(b, off, len); + } + + public long skip(long n) + throws IOException + { + checkCancelled(); + return super.skip(n); + } + + public int available() + throws IOException + { + checkCancelled(); + return super.available(); + } + + public void reset() + throws IOException + { + checkCancelled(); + super.reset(); + } + } + + /** + * The thread that loads documents asynchronously. + */ + private class PageLoader + implements Runnable + { + private Document doc; + private PageStream in; + private URL old; + URL page; + PageLoader(Document doc, InputStream in, URL old, URL page) + { + this.doc = doc; + this.in = new PageStream(in); + this.old = old; + this.page = page; + } + + public void run() + { + try + { + read(in, doc); + } + catch (IOException ex) + { + UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this); + } + finally + { + if (SwingUtilities.isEventDispatchThread()) + firePropertyChange("page", old, page); + else + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + firePropertyChange("page", old, page); + } + }); + } + } + } + + void cancel() + { + in.cancel(); + } + } + private static final long serialVersionUID = 3140472492599046285L; - private URL page; private EditorKit editorKit; boolean focus_root; + /** + * Maps content-types to editor kit instances. + */ + static HashMap editorKits; + // A mapping between content types and registered EditorKit types static HashMap registerMap; - + + static + { + registerMap = new HashMap(); + editorKits = new HashMap(); + registerEditorKitForContentType("application/rtf", + "javax.swing.text.rtf.RTFEditorKit"); + registerEditorKitForContentType("text/plain", + "javax.swing.JEditorPane$PlainEditorKit"); + registerEditorKitForContentType("text/html", + "javax.swing.text.html.HTMLEditorKit"); + registerEditorKitForContentType("text/rtf", + "javax.swing.text.rtf.RTFEditorKit"); + + } + // A mapping between content types and used EditorKits HashMap editorMap; + /** + * The currently loading stream, if any. + */ + private PageLoader loader; + public JEditorPane() { init(); @@ -550,15 +723,6 @@ public class JEditorPane extends JTextComponent void init() { editorMap = new HashMap(); - registerMap = new HashMap(); - registerEditorKitForContentType("application/rtf", - "javax.swing.text.rtf.RTFEditorKit"); - registerEditorKitForContentType("text/plain", - "javax.swing.JEditorPane$PlainEditorKit"); - registerEditorKitForContentType("text/html", - "javax.swing.text.html.HTMLEditorKit"); - registerEditorKitForContentType("text/rtf", - "javax.swing.text.rtf.RTFEditorKit"); } protected EditorKit createDefaultEditorKit() @@ -578,20 +742,28 @@ public class JEditorPane extends JTextComponent */ public static EditorKit createEditorKitForContentType(String type) { - // TODO: Have to handle the case where a ClassLoader was specified - // when the EditorKit was registered - EditorKit e = null; - String className = (String) registerMap.get(type); - if (className != null) + // Try cached instance. + EditorKit e = (EditorKit) editorKits.get(type); + if (e == null) { - try - { - e = (EditorKit) Class.forName(className).newInstance(); - } - catch (Exception e2) - { - // TODO: Not sure what to do here. - } + EditorKitMapping m = (EditorKitMapping) registerMap.get(type); + if (m != null) + { + String className = m.className; + ClassLoader loader = m.classLoader; + try + { + e = (EditorKit) loader.loadClass(className).newInstance(); + } + catch (Exception e2) + { + // The reference implementation returns null when class is not + // loadable or instantiatable. + } + } + // Cache this for later retrieval. + if (e != null) + editorKits.put(type, e); } return e; } @@ -652,7 +824,9 @@ public class JEditorPane extends JTextComponent */ public static String getEditorKitClassNameForContentType(String type) { - return (String) registerMap.get(type); + EditorKitMapping m = (EditorKitMapping) registerMap.get(type); + String kitName = m != null ? m.className : null; + return kitName; } /** @@ -675,10 +849,14 @@ public class JEditorPane extends JTextComponent EditorKit e = (EditorKit) editorMap.get(type); // Then check to see if we can create one. if (e == null) - e = createEditorKitForContentType(type); + { + e = createEditorKitForContentType(type); + if (e != null) + setEditorKitForContentType(type, e); + } // Otherwise default to PlainEditorKit. if (e == null) - e = new PlainEditorKit(); + e = createDefaultEditorKit(); return e; } @@ -695,10 +873,28 @@ public class JEditorPane extends JTextComponent public Dimension getPreferredSize() { Dimension pref = super.getPreferredSize(); - if (getScrollableTracksViewportWidth()) - pref.width = getUI().getMinimumSize(this).width; - if (getScrollableTracksViewportHeight()) - pref.height = getUI().getMinimumSize(this).height; + Container parent = getParent(); + if (parent instanceof JViewport) + { + JViewport vp = (JViewport) getParent(); + TextUI ui = getUI(); + Dimension min = null; + if (! getScrollableTracksViewportWidth()) + { + min = ui.getMinimumSize(this); + int vpWidth = vp.getWidth(); + if (vpWidth != 0 && vpWidth < min.width) + pref.width = min.width; + } + if (! getScrollableTracksViewportHeight()) + { + if (min == null) + min = ui.getMinimumSize(this); + int vpHeight = vp.getHeight(); + if (vpHeight != 0 && vpHeight < min.height) + pref.height = min.height; + } + } return pref; } @@ -716,8 +912,11 @@ public class JEditorPane extends JTextComponent // Tests show that this returns true when the parent is a JViewport // and has a height > minimum UI height. Container parent = getParent(); + int height = parent.getHeight(); + TextUI ui = getUI(); return parent instanceof JViewport - && parent.getHeight() > getUI().getMinimumSize(this).height; + && height >= ui.getMinimumSize(this).height + && height <= ui.getMaximumSize(this).height; } /** @@ -740,13 +939,19 @@ public class JEditorPane extends JTextComponent public URL getPage() { - return page; + return loader != null ? loader.page : null; } protected InputStream getStream(URL page) throws IOException { - return page.openStream(); + URLConnection conn = page.openConnection(); + // Try to detect the content type of the stream data. + String type = conn.getContentType(); + if (type != null) + setContentType(type); + InputStream stream = conn.getInputStream(); + return new BufferedInputStream(stream); } public String getText() @@ -777,10 +982,12 @@ public class JEditorPane extends JTextComponent EditorKit kit = getEditorKit(); if (kit instanceof HTMLEditorKit && desc instanceof HTMLDocument) { - Document doc = (Document) desc; + HTMLDocument doc = (HTMLDocument) desc; + setDocument(doc); try { - kit.read(in, doc, 0); + InputStreamReader reader = new InputStreamReader(in); + kit.read(reader, doc, 0); } catch (BadLocationException ex) { @@ -805,7 +1012,8 @@ public class JEditorPane extends JTextComponent public static void registerEditorKitForContentType(String type, String classname) { - registerMap.put(type, classname); + registerEditorKitForContentType(type, classname, + Thread.currentThread().getContextClassLoader()); } /** @@ -815,7 +1023,7 @@ public class JEditorPane extends JTextComponent String classname, ClassLoader loader) { - // TODO: Implement this properly. + registerMap.put(type, new EditorKitMapping(classname, loader)); } /** @@ -839,6 +1047,13 @@ public class JEditorPane extends JTextComponent public final void setContentType(String type) { + // Strip off content type parameters. + int paramIndex = type.indexOf(';'); + if (paramIndex > -1) + { + // TODO: Handle character encoding. + type = type.substring(0, paramIndex).trim(); + } if (editorKit != null && editorKit.getContentType().equals(type)) return; @@ -899,14 +1114,45 @@ public class JEditorPane extends JTextComponent if (page == null) throw new IOException("invalid url"); - try + URL old = getPage(); + // Only reload if the URL doesn't point to the same file. + // This is not the same as equals because there might be different + // URLs on the same file with different anchors. + if (old == null || ! old.sameFile(page)) { - this.page = page; - getEditorKit().read(page.openStream(), getDocument(), 0); - } - catch (BadLocationException e) - { - // Ignored. '0' is always a valid offset. + InputStream in = getStream(page); + if (editorKit != null) + { + Document doc = editorKit.createDefaultDocument(); + doc.putProperty(Document.StreamDescriptionProperty, page); + + if (loader != null) + loader.cancel(); + loader = new PageLoader(doc, in, old, page); + + int prio = -1; + if (doc instanceof AbstractDocument) + { + AbstractDocument aDoc = (AbstractDocument) doc; + prio = aDoc.getAsynchronousLoadPriority(); + } + if (prio >= 0) + { + // Load asynchronously. + setDocument(doc); + Thread loadThread = new Thread(loader, + "JEditorPane.PageLoader"); + loadThread.setDaemon(true); + loadThread.setPriority(prio); + loadThread.start(); + } + else + { + // Load synchronously. + loader.run(); + setDocument(doc); + } + } } } diff --git a/libjava/classpath/javax/swing/JFrame.java b/libjava/classpath/javax/swing/JFrame.java index 1371525ddf2..0ae23f101fa 100644 --- a/libjava/classpath/javax/swing/JFrame.java +++ b/libjava/classpath/javax/swing/JFrame.java @@ -157,6 +157,10 @@ public class JFrame extends Frame protected void frameInit() { + // We need to explicitly enable events here so that our processKeyEvent() + // and processWindowEvent() gets called. + enableEvents(AWTEvent.WINDOW_EVENT_MASK | AWTEvent.KEY_EVENT_MASK); + super.setLayout(new BorderLayout()); setBackground(UIManager.getDefaults().getColor("control")); enableEvents(AWTEvent.WINDOW_EVENT_MASK); @@ -351,39 +355,22 @@ public class JFrame extends Frame protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); - switch (e.getID()) + if (e.getID() == WindowEvent.WINDOW_CLOSING) { - case WindowEvent.WINDOW_CLOSING: - { - switch (closeAction) - { - case EXIT_ON_CLOSE: - { - System.exit(0); - break; - } - case DISPOSE_ON_CLOSE: - { - dispose(); - break; - } - case HIDE_ON_CLOSE: - { - setVisible(false); - break; - } - case DO_NOTHING_ON_CLOSE: - break; - } - break; - } - case WindowEvent.WINDOW_CLOSED: - case WindowEvent.WINDOW_OPENED: - case WindowEvent.WINDOW_ICONIFIED: - case WindowEvent.WINDOW_DEICONIFIED: - case WindowEvent.WINDOW_ACTIVATED: - case WindowEvent.WINDOW_DEACTIVATED: - break; + switch (closeAction) + { + case EXIT_ON_CLOSE: + System.exit(0); + break; + case DISPOSE_ON_CLOSE: + dispose(); + break; + case HIDE_ON_CLOSE: + setVisible(false); + break; + case DO_NOTHING_ON_CLOSE: + break; + } } } diff --git a/libjava/classpath/javax/swing/JLabel.java b/libjava/classpath/javax/swing/JLabel.java index fcf0fd7cb13..721287b21df 100644 --- a/libjava/classpath/javax/swing/JLabel.java +++ b/libjava/classpath/javax/swing/JLabel.java @@ -38,13 +38,14 @@ exception statement from your version. */ package javax.swing; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Font; +import java.awt.FontMetrics; import java.awt.Image; +import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; +import java.awt.Shape; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; @@ -54,8 +55,12 @@ import javax.accessibility.AccessibleExtendedComponent; import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleText; import javax.swing.plaf.LabelUI; +import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Position; import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.View; /** * A component that displays a static text message and/or an icon. @@ -303,10 +308,52 @@ public class JLabel extends JComponent implements Accessible, SwingConstants * @return the bounding box of the character at the specified index */ public Rectangle getCharacterBounds(int index) - throws NotImplementedException { - // FIXME: Implement this correctly. - return new Rectangle(); + Rectangle bounds = null; + View view = (View) getClientProperty(BasicHTML.propertyKey); + if (view != null) + { + Rectangle textR = getTextRectangle(); + try + { + Shape s = view.modelToView(index, textR, Position.Bias.Forward); + bounds = s.getBounds(); + } + catch (BadLocationException ex) + { + // Can't return something reasonable in this case. + } + } + return bounds; + } + + /** + * Returns the rectangle inside the JLabel, in which the actual text is + * rendered. This method has been adopted from the Mauve testcase + * gnu.testlet.javax.swing.JLabel.AccessibleJLabel.getCharacterBounds. + * + * @return the rectangle inside the JLabel, in which the actual text is + * rendered + */ + private Rectangle getTextRectangle() + { + JLabel l = JLabel.this; + Rectangle textR = new Rectangle(); + Rectangle iconR = new Rectangle(); + Insets i = l.getInsets(); + int w = l.getWidth(); + int h = l.getHeight(); + Rectangle viewR = new Rectangle(i.left, i.top, w - i.left - i.right, + h - i.top - i.bottom); + FontMetrics fm = l.getFontMetrics(l.getFont()); + SwingUtilities.layoutCompoundLabel(l, fm, l.getText(), l.getIcon(), + l.getVerticalAlignment(), + l.getHorizontalAlignment(), + l.getVerticalTextPosition(), + l.getHorizontalTextPosition(), + viewR, iconR, textR, + l.getIconTextGap()); + return textR; } /** @@ -319,10 +366,15 @@ public class JLabel extends JComponent implements Accessible, SwingConstants * point */ public int getIndexAtPoint(Point point) - throws NotImplementedException { - // FIXME: Implement this correctly. - return 0; + int index = -1; + View view = (View) getClientProperty(BasicHTML.propertyKey); + if (view != null) + { + Rectangle r = getTextRectangle(); + index = view.viewToModel(point.x, point.y, r, new Position.Bias[0]); + } + return index; } } @@ -379,11 +431,11 @@ public class JLabel extends JComponent implements Accessible, SwingConstants * Creates a new vertically and horizontally centered * JLabel object with no text and the given icon. * - * @param image The icon to use with the label. + * @param image The icon to use with the label, <code>null</code> permitted. */ public JLabel(Icon image) { - this("", image, CENTER); + this(null, image, CENTER); } /** @@ -391,19 +443,21 @@ public class JLabel extends JComponent implements Accessible, SwingConstants * given icon and horizontal alignment. By default, the text is TRAILING * the image. * - * @param image The icon to use with the label. - * @param horizontalAlignment The horizontal alignment of the label. + * @param image The icon to use with the label, <code>null</code> premitted. + * @param horizontalAlignment The horizontal alignment of the label, must be + * either <code>CENTER</code>, <code>LEFT</code>, <code>RIGHT</code>, + * <code>LEADING</code> or <code>TRAILING</code>. */ public JLabel(Icon image, int horizontalAlignment) { - this("", image, horizontalAlignment); + this(null, image, horizontalAlignment); } /** * Creates a new horizontally leading and vertically centered JLabel * object with no icon and the given text. * - * @param text The text to use with the label. + * @param text The text to use with the label, <code>null</code> permitted. */ public JLabel(String text) { @@ -414,8 +468,10 @@ public class JLabel extends JComponent implements Accessible, SwingConstants * Creates a new vertically centered JLabel object with no icon and the * given text and horizontal alignment. * - * @param text The text to use with the label. - * @param horizontalAlignment The horizontal alignment of the label. + * @param text The text to use with the label, <code>null</code> permitted. + * @param horizontalAlignment The horizontal alignment of the label, must be + * either <code>CENTER</code>, <code>LEFT</code>, <code>RIGHT</code>, + * <code>LEADING</code> or <code>TRAILING</code>. */ public JLabel(String text, int horizontalAlignment) { @@ -426,12 +482,21 @@ public class JLabel extends JComponent implements Accessible, SwingConstants * Creates a new vertically centered JLabel object with the given text, * icon, and horizontal alignment. * - * @param text The text to use with the label. - * @param icon The icon to use with the label. - * @param horizontalAlignment The horizontal alignment of the label. + * @param text The text to use with the label, <code>null</code> permitted. + * @param icon The icon to use with the label, <code>null</code> premitted. + * @param horizontalAlignment The horizontal alignment of the label, must be + * either <code>CENTER</code>, <code>LEFT</code>, <code>RIGHT</code>, + * <code>LEADING</code> or <code>TRAILING</code>. */ public JLabel(String text, Icon icon, int horizontalAlignment) { + if (horizontalAlignment != SwingConstants.LEFT + && horizontalAlignment != SwingConstants.RIGHT + && horizontalAlignment != SwingConstants.CENTER + && horizontalAlignment != SwingConstants.LEADING + && horizontalAlignment != SwingConstants.TRAILING) + throw new IllegalArgumentException(); + this.text = text; this.icon = icon; this.horizontalAlignment = horizontalAlignment; diff --git a/libjava/classpath/javax/swing/JLayeredPane.java b/libjava/classpath/javax/swing/JLayeredPane.java index ca913e97fed..d981788ec7d 100644 --- a/libjava/classpath/javax/swing/JLayeredPane.java +++ b/libjava/classpath/javax/swing/JLayeredPane.java @@ -406,7 +406,7 @@ public class JLayeredPane extends JComponent implements Accessible * Return a hashtable mapping child components of this container to * Integer objects representing the component's layer assignments. */ - protected Hashtable getComponentToLayer() + protected Hashtable<Component, Integer> getComponentToLayer() { return componentToLayer; } diff --git a/libjava/classpath/javax/swing/JList.java b/libjava/classpath/javax/swing/JList.java index 6a98770eeda..ff1b239217a 100644 --- a/libjava/classpath/javax/swing/JList.java +++ b/libjava/classpath/javax/swing/JList.java @@ -1041,7 +1041,7 @@ public class JList extends JComponent implements Accessible, Scrollable * * @param items the initial list items. */ - public JList(Vector items) + public JList(Vector<?> items) { init(createListModel(items)); } @@ -1643,9 +1643,20 @@ public class JList extends JComponent implements Accessible, Scrollable * @param listData The object array to build a new list model on * @see #setModel */ - public void setListData(Vector listData) + public void setListData(final Vector<?> listData) { - setModel(createListModel(listData)); + setModel(new AbstractListModel() + { + public int getSize() + { + return listData.size(); + } + + public Object getElementAt(int i) + { + return listData.elementAt(i); + } + }); } /** @@ -1935,72 +1946,74 @@ public class JList extends JComponent implements Accessible, Scrollable public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - ListUI lui = this.getUI(); + int unit = -1; if (orientation == SwingConstants.VERTICAL) { - if (direction > 0) + int row = getFirstVisibleIndex(); + if (row == -1) + unit = 0; + else if (direction > 0) + { + // Scrolling down. + Rectangle bounds = getCellBounds(row, row); + if (bounds != null) + unit = bounds.height - (visibleRect.y - bounds.y); + else + unit = 0; + } + else { - // 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 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 +2042,120 @@ public class JList extends JComponent implements Accessible, Scrollable public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - if (orientation == VERTICAL) - return visibleRect.height * direction; - else - return visibleRect.width * direction; + int block = -1; + if (orientation == SwingConstants.VERTICAL) + { + // Default block scroll. Special cases are handled below for + // better usability. + block = visibleRect.height; + if (direction > 0) + { + // Scroll down. + // Scroll so that after scrolling the last line aligns with + // the lower boundary of the visible area. + Point p = new Point(visibleRect.x, + visibleRect.y + visibleRect.height - 1); + int last = locationToIndex(p); + if (last != -1) + { + Rectangle lastR = getCellBounds(last, last); + if (lastR != null) + { + block = lastR.y - visibleRect.y; + if (block == 0&& last < getModel().getSize() - 1) + block = lastR.height; + } + } + } + else + { + // Scroll up. + // Scroll so that after scrolling the first line aligns with + // the upper boundary of the visible area. + Point p = new Point(visibleRect.x, + visibleRect.y - visibleRect.height); + int newFirst = locationToIndex(p); + if (newFirst != -1) + { + int first = getFirstVisibleIndex(); + if (first == -1) + first = locationToIndex(visibleRect.getLocation()); + Rectangle newFirstR = getCellBounds(newFirst, newFirst); + Rectangle firstR = getCellBounds(first, first); + if (newFirstR != null && firstR != null) + { + // Search first item that would left the current first + // item visible when scrolled to. + while (newFirstR.y + visibleRect.height + < firstR.y + firstR.height + && newFirstR.y < firstR.y) + { + newFirst++; + newFirstR = getCellBounds(newFirst, newFirst); + } + block = visibleRect.y - newFirstR.y; + if (block <= 0 && newFirstR.y > 0) + { + newFirst--; + newFirstR = getCellBounds(newFirst, newFirst); + if (newFirstR != null) + block = visibleRect.y - newFirstR.y; + } + } + } + } + } + else if (orientation == SwingConstants.HORIZONTAL + && getLayoutOrientation() != VERTICAL) + { + // Default block increment. Special cases are handled below for + // better usability. + block = visibleRect.width; + if (direction > 0) + { + // Scroll right. + Point p = new Point(visibleRect.x + visibleRect.width + 1, + visibleRect.y); + int last = locationToIndex(p); + if (last != -1) + { + Rectangle lastR = getCellBounds(last, last); + if (lastR != null) + { + block = lastR.x - visibleRect.x; + if (block < 0) + block += lastR.width; + else if (block == 0 && last < getModel().getSize() - 1) + block = lastR.width; + } + } + } + else + { + // Scroll left. + Point p = new Point(visibleRect.x - visibleRect.width, + visibleRect.y); + int first = locationToIndex(p); + if (first != -1) + { + Rectangle firstR = getCellBounds(first, first); + if (firstR != null) + { + if (firstR.x < visibleRect.x - visibleRect.width) + { + if (firstR.x + firstR.width > visibleRect.x) + block = visibleRect.x - firstR.x; + else + block = visibleRect.x - firstR.x - firstR.width; + } + else + block = visibleRect.x - firstR.x; + } + } + } + } + + return block; } /** diff --git a/libjava/classpath/javax/swing/JMenu.java b/libjava/classpath/javax/swing/JMenu.java index 0840509f906..7e627f1181d 100644 --- a/libjava/classpath/javax/swing/JMenu.java +++ b/libjava/classpath/javax/swing/JMenu.java @@ -39,8 +39,14 @@ exception statement from your version. */ package javax.swing; import java.awt.Component; +import java.awt.Dimension; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Insets; import java.awt.Point; -import java.awt.PopupMenu; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -54,6 +60,8 @@ import javax.accessibility.Accessible; import javax.accessibility.AccessibleContext; import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleSelection; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.plaf.MenuItemUI; @@ -72,6 +80,36 @@ import javax.swing.plaf.MenuItemUI; */ public class JMenu extends JMenuItem implements Accessible, MenuElement { + /** + * Receives notifications when the JMenu's ButtonModel is changed and + * fires menuSelected or menuDeselected events when appropriate. + */ + private class MenuChangeListener + implements ChangeListener + { + /** + * Indicates the last selected state. + */ + private boolean selected; + + /** + * Receives notification when the JMenu's ButtonModel changes. + */ + public void stateChanged(ChangeEvent ev) + { + ButtonModel m = (ButtonModel) ev.getSource(); + boolean s = m.isSelected(); + if (s != selected) + { + if (s) + fireMenuSelected(); + else + fireMenuDeselected(); + selected = s; + } + } + } + private static final long serialVersionUID = 4227225638931828014L; /** A Popup menu associated with this menu, which pops up when menu is selected */ @@ -88,18 +126,26 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement /* PopupListener */ protected WinListener popupListener; - /** Location at which popup menu associated with this menu will be - displayed */ + /** + * Location at which popup menu associated with this menu will be + * displayed + */ private Point menuLocation; /** + * The ChangeListener for the ButtonModel. + * + * @see MenuChangeListener + */ + private ChangeListener menuChangeListener; + + /** * Creates a new JMenu object. */ public JMenu() { super(); setOpaque(false); - setDelay(200); } /** @@ -113,7 +159,6 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement popupMenu = new JPopupMenu(); popupMenu.setInvoker(this); setOpaque(false); - setDelay(200); } /** @@ -129,7 +174,6 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement popupMenu = new JPopupMenu(); popupMenu.setInvoker(this); setOpaque(false); - setDelay(200); } /** @@ -143,7 +187,6 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement { // FIXME: tearoff not implemented this(text); - setDelay(200); } /** @@ -193,7 +236,7 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public JMenuItem add(String text) { - return getPopupMenu().add(text); + return add(new JMenuItem(text)); } /** @@ -205,7 +248,10 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public JMenuItem add(Action action) { - return getPopupMenu().add(action); + JMenuItem i = createActionComponent(action); + i.setAction(action); + add(i); + return i; } /** @@ -328,7 +374,18 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement */ public void setModel(ButtonModel model) { + ButtonModel oldModel = getModel(); + if (oldModel != null && menuChangeListener != null) + oldModel.removeChangeListener(menuChangeListener); + super.setModel(model); + + if (model != null) + { + if (menuChangeListener == null) + menuChangeListener = new MenuChangeListener(); + model.addChangeListener(menuChangeListener); + } } /** @@ -342,63 +399,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 +406,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,23 +429,126 @@ 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 = menuLocation == null ? getPopupMenuOrigin() + : menuLocation; + getPopupMenu().show(this, loc.x, loc.y); + } + else + getPopupMenu().setVisible(false); + } } /** - * Returns origin point of the popup menu + * Returns origin point of the popup menu. This takes the screen bounds + * into account and places the popup where it fits best. * - * @return Point containing + * @return the origin of the popup menu */ protected Point getPopupMenuOrigin() { - // if menu in the menu bar + // The menu's screen location and size. + Point screenLoc = getLocationOnScreen(); + Dimension size = getSize(); + + // Determine the popup's size. + JPopupMenu popup = getPopupMenu(); + Dimension popupSize = popup.getSize(); + if (popupSize.width == 0 || popupSize.height == 0) + popupSize = popup.getPreferredSize(); + + // Determine screen bounds. + Toolkit tk = Toolkit.getDefaultToolkit(); + Rectangle screenBounds = new Rectangle(tk.getScreenSize()); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice gd = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + Insets screenInsets = tk.getScreenInsets(gc); + screenBounds.x -= screenInsets.left; + screenBounds.width -= screenInsets.left + screenInsets.right; + screenBounds.y -= screenInsets.top; + screenBounds.height -= screenInsets.top + screenInsets.bottom; + screenLoc.x -= screenInsets.left; + screenLoc.y -= screenInsets.top; + + Point point = new Point(); if (isTopLevelMenu()) - return new Point(0, this.getHeight()); - - // if submenu - return new Point(this.getWidth(), 0); + { + // If menu in the menu bar. + int xOffset = UIManager.getInt("Menu.menuPopupOffsetX"); + int yOffset = UIManager.getInt("Menu.menuPopupOffsetY"); + // Determine X location. + if (getComponentOrientation().isLeftToRight()) + { + // Prefer popup to the right. + point.x = xOffset; + // Check if it fits, otherwise place popup wherever it fits. + if (screenLoc.x + point.x + popupSize.width + > screenBounds.width + screenBounds.width + && screenBounds.width - size.width + < 2 * (screenLoc.x - screenBounds.x)) + // Popup to the right if there's not enough room. + point.x = size.width - xOffset - popupSize.width; + } + else + { + // Prefer popup to the left. + point.x = size.width - xOffset - popupSize.width; + if (screenLoc.x + point.x < screenBounds.x + && screenBounds.width - size.width + > 2 * (screenLoc.x - screenBounds.x)) + // Popup to the left if there's not enough room. + point.x = xOffset; + } + // Determine Y location. Prefer popping down. + point.y = size.height + yOffset; + if (screenLoc.y + point.y + popupSize.height >= screenBounds.height + && screenBounds.height - size.height + < 2 * (screenLoc.y - screenBounds.y)) + // Position above if there's not enough room below. + point.y = - yOffset - popupSize.height; + } + else + { + // If submenu. + int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX"); + int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY"); + // Determine X location. + if (getComponentOrientation().isLeftToRight()) + { + // Prefer popup to the right. + point.x = size.width + xOffset; + if (screenLoc.x + point.x + popupSize.width + >= screenBounds.x + screenBounds.width + && screenBounds.width - size.width + < 2 * (screenLoc.x - screenBounds.x)) + // Position to the left if there's not enough room on the right. + point.x = - xOffset - popupSize.width; + } + else + { + // Prefer popup on the left side. + point.x = - xOffset - popupSize.width; + if (screenLoc.x + point.x < screenBounds.x + && screenBounds.width - size.width + > 2 * (screenLoc.x - screenBounds.x)) + // Popup to the right if there's not enough room. + point.x = size.width + xOffset; + } + // Determine Y location. Prefer popping down. + point.y = yOffset; + if (screenLoc.y + point.y + popupSize.height + >= screenBounds.y + screenBounds.height + && screenBounds.height - size.height + < 2 * (screenLoc.y - screenBounds.y)) + // Pop up if there's not enough room below. + point.y = size.height - yOffset - popupSize.height; + } + return point; } /** @@ -483,6 +588,8 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement public void setMenuLocation(int x, int y) { menuLocation = new Point(x, y); + if (popupMenu != null) + popupMenu.setLocation(x, y); } /** @@ -600,7 +707,7 @@ public class JMenu extends JMenuItem implements Accessible, MenuElement if (getPopupMenu() == null || getMenuComponentCount() == 0) return null; - return (Component) popupMenu.getComponentAtIndex(index); + return popupMenu.getComponentAtIndex(index); } /** @@ -748,7 +855,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/libjava/classpath/javax/swing/JMenuBar.java b/libjava/classpath/javax/swing/JMenuBar.java index 35f129377f1..73b409e42db 100644 --- a/libjava/classpath/javax/swing/JMenuBar.java +++ b/libjava/classpath/javax/swing/JMenuBar.java @@ -298,19 +298,25 @@ public class JMenuBar extends JComponent implements Accessible, MenuElement } /** - * DOCUMENT ME! + * This method is not implemented and will throw an {@link Error} if called. * - * @return DOCUMENT ME! + * @return This method never returns anything, it throws an exception. */ public JMenu getHelpMenu() { - return null; + // the following error matches the behaviour of the reference + // implementation... + throw new Error("getHelpMenu() is not implemented"); } /** - * Returns margin betweeen menu bar's border and its menues + * Returns the margin between the menu bar's border and its menus. If the + * margin is <code>null</code>, this method returns + * <code>new Insets(0, 0, 0, 0)</code>. * - * @return margin between menu bar's border and its menues + * @return The margin (never <code>null</code>). + * + * @see #setMargin(Insets) */ public Insets getMargin() { @@ -617,21 +623,20 @@ public class JMenuBar extends JComponent implements Accessible, MenuElement } /** - * Sets the menu bar's "margin" bound property, which represents - * distance between the menubar's border and its menus. - * icon. When marging property is modified, PropertyChangeEvent will - * be fired to menuBar's PropertyChangeListener's. - * - * @param m distance between the menubar's border and its menus. + * Sets the margin between the menu bar's border and its menus (this is a + * bound property with the name 'margin'). * + * @param m the margin (<code>null</code> permitted). + * + * @see #getMargin() */ public void setMargin(Insets m) { if (m != margin) { - Insets oldMargin = margin; - margin = m; - firePropertyChange("margin", oldMargin, margin); + Insets oldMargin = margin; + margin = m; + firePropertyChange("margin", oldMargin, margin); } } diff --git a/libjava/classpath/javax/swing/JMenuItem.java b/libjava/classpath/javax/swing/JMenuItem.java index f7f93bf00d7..ffdccdcef1a 100644 --- a/libjava/classpath/javax/swing/JMenuItem.java +++ b/libjava/classpath/javax/swing/JMenuItem.java @@ -39,7 +39,6 @@ exception statement from your version. */ package javax.swing; import java.awt.Component; -import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; @@ -77,12 +76,16 @@ public class JMenuItem extends AbstractButton implements Accessible, private KeyStroke accelerator; /** + * Indicates if we are currently dragging the mouse. + */ + private boolean isDragging; + + /** * Creates a new JMenuItem object. */ public JMenuItem() { - super(); - init(null, null); + this(null, null); } /** @@ -94,8 +97,7 @@ public class JMenuItem extends AbstractButton implements Accessible, { // FIXME: The requestedFocusEnabled property should // be set to false, when only icon is set for menu item. - super(); - init(null, icon); + this(null, icon); } /** @@ -117,6 +119,7 @@ public class JMenuItem extends AbstractButton implements Accessible, { super(); super.setAction(action); + setModel(new DefaultButtonModel()); init(null, null); if (action != null) { @@ -148,6 +151,7 @@ public class JMenuItem extends AbstractButton implements Accessible, public JMenuItem(String text, Icon icon) { super(); + setModel(new DefaultButtonModel()); init(text, icon); } @@ -174,7 +178,6 @@ public class JMenuItem extends AbstractButton implements Accessible, protected void init(String text, Icon icon) { super.init(text, icon); - setModel(new DefaultButtonModel()); // Initializes properties for this menu item, that are different // from Abstract button properties. @@ -320,71 +323,21 @@ public class JMenuItem extends AbstractButton implements Accessible, /** * Process mouse events forwarded from MenuSelectionManager. * - * @param event event forwarded from MenuSelectionManager + * @param ev event forwarded from MenuSelectionManager * @param path path to the menu element from which event was generated * @param manager MenuSelectionManager for the current menu hierarchy */ - public void processMouseEvent(MouseEvent event, MenuElement[] path, + public void processMouseEvent(MouseEvent ev, MenuElement[] path, MenuSelectionManager manager) { - // Fire MenuDragMouseEvents if mouse is being dragged. - boolean dragged - = (event.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0; - if (dragged) - processMenuDragMouseEvent(createMenuDragMouseEvent(event, path, manager)); - - switch (event.getID()) - { - case MouseEvent.MOUSE_CLICKED: - break; - case MouseEvent.MOUSE_ENTERED: - if (isRolloverEnabled()) - model.setRollover(true); - break; - case MouseEvent.MOUSE_EXITED: - if (isRolloverEnabled()) - model.setRollover(false); - - // for JMenu last element on the path is its popupMenu. - // JMenu shouldn't me disarmed. - if (! (path[path.length - 1] instanceof JPopupMenu) && ! dragged) - setArmed(false); - break; - case MouseEvent.MOUSE_PRESSED: - if ((event.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0) - { - model.setArmed(true); - model.setPressed(true); - } - break; - case MouseEvent.MOUSE_RELEASED: - break; - case MouseEvent.MOUSE_MOVED: - break; - case MouseEvent.MOUSE_DRAGGED: - break; - } - } - - /** - * Creates MenuDragMouseEvent. - * - * @param event MouseEvent that occured while mouse was pressed. - * @param path Path the the menu element where the dragging event was - * originated - * @param manager MenuSelectionManager for the current menu hierarchy. - * - * @return new MenuDragMouseEvent - */ - private MenuDragMouseEvent createMenuDragMouseEvent(MouseEvent event, - MenuElement[] path, - MenuSelectionManager manager) - { - return new MenuDragMouseEvent((Component) event.getSource(), - event.getID(), event.getWhen(), - event.getModifiers(), event.getX(), - event.getY(), event.getClickCount(), - event.isPopupTrigger(), path, manager); + MenuDragMouseEvent e = new MenuDragMouseEvent(ev.getComponent(), + ev.getID(), ev.getWhen(), + ev.getModifiers(), ev.getX(), + ev.getY(), + ev.getClickCount(), + ev.isPopupTrigger(), path, + manager); + processMenuDragMouseEvent(e); } /** @@ -420,16 +373,20 @@ public class JMenuItem extends AbstractButton implements Accessible, switch (event.getID()) { case MouseEvent.MOUSE_ENTERED: + isDragging = false; fireMenuDragMouseEntered(event); break; case MouseEvent.MOUSE_EXITED: + isDragging = false; fireMenuDragMouseExited(event); break; case MouseEvent.MOUSE_DRAGGED: + isDragging = true; fireMenuDragMouseDragged(event); break; case MouseEvent.MOUSE_RELEASED: - fireMenuDragMouseReleased(event); + if (isDragging) + fireMenuDragMouseReleased(event); break; } } @@ -833,4 +790,20 @@ public class JMenuItem extends AbstractButton implements Accessible, return AccessibleRole.MENU_ITEM; } } + + /** + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. + * + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others + */ + boolean onTop() + { + return SwingUtilities.getAncestorOfClass(JInternalFrame.class, this) + == null; + } + } diff --git a/libjava/classpath/javax/swing/JPopupMenu.java b/libjava/classpath/javax/swing/JPopupMenu.java index d46015afdf3..1ae8adad02a 100644 --- a/libjava/classpath/javax/swing/JPopupMenu.java +++ b/libjava/classpath/javax/swing/JPopupMenu.java @@ -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); } @@ -895,6 +902,20 @@ public class JPopupMenu extends JComponent implements Accessible, MenuElement } } + /** + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. + * + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others + */ + boolean onTop() + { + return true; + } + protected class AccessibleJPopupMenu extends AccessibleJComponent { private static final long serialVersionUID = 7423261328879849768L; diff --git a/libjava/classpath/javax/swing/JRootPane.java b/libjava/classpath/javax/swing/JRootPane.java index a2cd9c7a000..10fdf10c0ec 100644 --- a/libjava/classpath/javax/swing/JRootPane.java +++ b/libjava/classpath/javax/swing/JRootPane.java @@ -505,15 +505,21 @@ public class JRootPane extends JComponent implements Accessible } /** - * DOCUMENT ME! + * Set the layered pane for the root pane. * - * @param f DOCUMENT ME! + * @param f The JLayeredPane to be used. + * + * @throws IllegalComponentStateException if JLayeredPane + * parameter is null. */ public void setLayeredPane(JLayeredPane f) { + if (f == null) + throw new IllegalComponentStateException(); + if (layeredPane != null) remove(layeredPane); - + layeredPane = f; add(f, -1); } diff --git a/libjava/classpath/javax/swing/JScrollBar.java b/libjava/classpath/javax/swing/JScrollBar.java index bf0803ab5a1..1f21aa13ff5 100644 --- a/libjava/classpath/javax/swing/JScrollBar.java +++ b/libjava/classpath/javax/swing/JScrollBar.java @@ -50,6 +50,8 @@ import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleState; import javax.accessibility.AccessibleStateSet; import javax.accessibility.AccessibleValue; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.plaf.ScrollBarUI; /** @@ -172,6 +174,28 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible } } + /** + * Listens for changes on the model and fires them to interested + * listeners on the JScrollBar, after re-sourcing them. + */ + private class ScrollBarChangeListener + implements ChangeListener + { + + public void stateChanged(ChangeEvent event) + { + Object o = event.getSource(); + if (o instanceof BoundedRangeModel) + { + BoundedRangeModel m = (BoundedRangeModel) o; + fireAdjustmentValueChanged(AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, + AdjustmentEvent.TRACK, m.getValue(), + m.getValueIsAdjusting()); + } + } + + } + private static final long serialVersionUID = -8195169869225066566L; /** How much the thumb moves when moving in a block. */ @@ -186,6 +210,12 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible /** How much the thumb moves when moving in a unit. */ protected int unitIncrement = 1; + /** + * This ChangeListener forwards events fired from the model and re-sources + * them to originate from this JScrollBar. + */ + private ChangeListener sbChangeListener; + /** * Creates a new horizontal JScrollBar object with a minimum * of 0, a maxmium of 100, a value of 0 and an extent of 10. @@ -220,6 +250,8 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible public JScrollBar(int orientation, int value, int extent, int min, int max) { model = new DefaultBoundedRangeModel(value, extent, min, max); + sbChangeListener = new ScrollBarChangeListener(); + model.addChangeListener(sbChangeListener); if (orientation != SwingConstants.HORIZONTAL && orientation != SwingConstants.VERTICAL) throw new IllegalArgumentException(orientation @@ -319,12 +351,13 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible */ public void setModel(BoundedRangeModel newModel) { - if (model != newModel) - { - BoundedRangeModel oldModel = model; - model = newModel; - firePropertyChange("model", oldModel, model); - } + BoundedRangeModel oldModel = model; + if (oldModel != null) + oldModel.removeChangeListener(sbChangeListener); + model = newModel; + if (model != null) + model.addChangeListener(sbChangeListener); + firePropertyChange("model", oldModel, model); } /** @@ -424,12 +457,7 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible */ public void setValue(int value) { - if (isEnabled() && value != getValue()) - { - model.setValue(value); - fireAdjustmentValueChanged(AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, - AdjustmentEvent.TRACK, value); - } + model.setValue(value); } /** @@ -451,12 +479,7 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible */ public void setVisibleAmount(int extent) { - if (extent != getVisibleAmount()) - { - model.setExtent(extent); - fireAdjustmentValueChanged(AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, - AdjustmentEvent.TRACK, extent); - } + model.setExtent(extent); } /** @@ -476,12 +499,7 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible */ public void setMinimum(int minimum) { - if (minimum != getMinimum()) - { - model.setMinimum(minimum); - fireAdjustmentValueChanged(AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, - AdjustmentEvent.TRACK, minimum); - } + model.setMinimum(minimum); } /** @@ -501,12 +519,7 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible */ public void setMaximum(int maximum) { - if (maximum != getMaximum()) - { - model.setMaximum(maximum); - fireAdjustmentValueChanged(AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, - AdjustmentEvent.TRACK, maximum); - } + model.setMaximum(maximum); } /** @@ -540,17 +553,8 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible */ public void setValues(int newValue, int newExtent, int newMin, int newMax) { - if (!isEnabled()) - newValue = model.getValue(); - // It seems to be that on any change the value is fired. - if (newValue != getValue() || newExtent != getVisibleAmount() || - newMin != getMinimum() || newMax != getMaximum()) - { - model.setRangeProperties(newValue, newExtent, newMin, newMax, - model.getValueIsAdjusting()); - fireAdjustmentValueChanged(AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, - AdjustmentEvent.TRACK, newValue); - } + model.setRangeProperties(newValue, newExtent, newMin, newMax, + model.getValueIsAdjusting()); } /** @@ -596,15 +600,30 @@ public class JScrollBar extends JComponent implements Adjustable, Accessible */ protected void fireAdjustmentValueChanged(int id, int type, int value) { + fireAdjustmentValueChanged(id, type, value, getValueIsAdjusting()); + } + + /** + * Helper method for firing adjustment events that can have their + * isAdjusting field modified. + * + * This is package private to avoid an accessor method. + * + * @param id the ID of the event + * @param type the type of the event + * @param value the value + * @param isAdjusting if the scrollbar is adjusting or not + */ + void fireAdjustmentValueChanged(int id, int type, int value, + boolean isAdjusting) + { Object[] adjustmentListeners = listenerList.getListenerList(); - AdjustmentEvent adjustmentEvent = new AdjustmentEvent(this, - AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, - AdjustmentEvent.TRACK, - value); + AdjustmentEvent adjustmentEvent = new AdjustmentEvent(this, id, type, + value, isAdjusting); for (int i = adjustmentListeners.length - 2; i >= 0; i -= 2) { - if (adjustmentListeners[i] == AdjustmentListener.class) - ((AdjustmentListener) adjustmentListeners[i + 1]).adjustmentValueChanged(adjustmentEvent); + if (adjustmentListeners[i] == AdjustmentListener.class) + ((AdjustmentListener) adjustmentListeners[i + 1]).adjustmentValueChanged(adjustmentEvent); } } diff --git a/libjava/classpath/javax/swing/JScrollPane.java b/libjava/classpath/javax/swing/JScrollPane.java index 45df1d9190e..f6d37c7b47d 100644 --- a/libjava/classpath/javax/swing/JScrollPane.java +++ b/libjava/classpath/javax/swing/JScrollPane.java @@ -161,9 +161,10 @@ public class JScrollPane extends JComponent protected int verticalScrollBarPolicy; protected JViewport viewport; - - Border viewportBorder; - boolean wheelScrollingEnabled; + + private Border viewportBorder; + + private boolean wheelScrollingEnabled; public JViewport getColumnHeader() { @@ -595,6 +596,7 @@ public class JScrollPane extends JComponent */ public JScrollPane(Component view, int vsbPolicy, int hsbPolicy) { + wheelScrollingEnabled = true; setVerticalScrollBarPolicy(vsbPolicy); setVerticalScrollBar(createVerticalScrollBar()); setHorizontalScrollBarPolicy(hsbPolicy); diff --git a/libjava/classpath/javax/swing/JSlider.java b/libjava/classpath/javax/swing/JSlider.java index 8a06d4f0116..948a9629b4c 100644 --- a/libjava/classpath/javax/swing/JSlider.java +++ b/libjava/classpath/javax/swing/JSlider.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing; -import java.awt.Dimension; import java.awt.MenuContainer; import java.awt.image.ImageObserver; import java.beans.PropertyChangeEvent; @@ -56,6 +55,7 @@ import javax.accessibility.AccessibleValue; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.SliderUI; +import javax.swing.plaf.UIResource; /** * A visual component that allows selection of a value within a @@ -112,6 +112,22 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, ImageObserver, MenuContainer, Serializable { + + /** + * A little testing shows that the reference implementation creates + * labels from a class named LabelUIResource. + */ + private class LabelUIResource + extends JLabel + implements UIResource + { + LabelUIResource(String text, int align) + { + super(text, align); + setName("Slider.label"); + } + } + private static final long serialVersionUID = -1441275936141218479L; /** @@ -425,6 +441,7 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, */ public void updateUI() { + updateLabelUIs(); setUI((SliderUI) UIManager.getUI(this)); } @@ -721,6 +738,7 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, int oldOrientation = this.orientation; this.orientation = orientation; firePropertyChange("orientation", oldOrientation, this.orientation); + revalidate(); } } @@ -751,7 +769,10 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, { Dictionary oldTable = labelTable; labelTable = table; + updateLabelUIs(); firePropertyChange("labelTable", oldTable, labelTable); + revalidate(); + repaint(); } } @@ -761,12 +782,18 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, */ protected void updateLabelUIs() { - if (labelTable == null) - return; - for (Enumeration list = labelTable.elements(); list.hasMoreElements();) + if (labelTable != null) { - JLabel label = (JLabel) list.nextElement(); - label.updateUI(); + for (Enumeration list = labelTable.elements(); list.hasMoreElements();) + { + Object o = list.nextElement(); + if (o instanceof JComponent) + { + JComponent jc = (JComponent) o; + jc.updateUI(); + jc.setSize(jc.getPreferredSize()); + } + } } } @@ -810,23 +837,11 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, if (start < getMinimum() || start > getMaximum()) throw new IllegalArgumentException("The 'start' value is out of range."); Hashtable table = new Hashtable(); - JLabel label; - Dimension dim; - - int max = sliderModel.getMaximum(); - + int max = getMaximum(); for (int i = start; i <= max; i += increment) { - label = new JLabel(String.valueOf(i)); - label.setVerticalAlignment(CENTER); - label.setHorizontalAlignment(CENTER); - - // Make sure these labels have the width and height - // they want. - dim = label.getPreferredSize(); - label.setBounds(label.getX(), label.getY(), - (int) dim.getWidth(), - (int) dim.getHeight()); + LabelUIResource label = new LabelUIResource(String.valueOf(i), + JLabel.CENTER); table.put(new Integer(i), label); } return table; @@ -867,6 +882,7 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, boolean oldInverted = isInverted; isInverted = inverted; firePropertyChange("inverted", oldInverted, isInverted); + repaint(); } } @@ -898,7 +914,11 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, { int oldSpacing = majorTickSpacing; majorTickSpacing = spacing; + if (labelTable == null && majorTickSpacing > 0 && getPaintLabels()) + setLabelTable(createStandardLabels(majorTickSpacing)); firePropertyChange("majorTickSpacing", oldSpacing, majorTickSpacing); + if (getPaintTicks()) + repaint(); } } @@ -932,6 +952,8 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, int oldSpacing = minorTickSpacing; minorTickSpacing = spacing; firePropertyChange("minorTickSpacing", oldSpacing, minorTickSpacing); + if (getPaintTicks()) + repaint(); } } @@ -1001,6 +1023,8 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, boolean oldPaintTicks = paintTicks; paintTicks = paint; firePropertyChange("paintTicks", oldPaintTicks, paintTicks); + revalidate(); + repaint(); } } @@ -1031,6 +1055,7 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, { paintTrack = paint; firePropertyChange("paintTrack", !paint, paint); + repaint(); } } @@ -1062,8 +1087,10 @@ public class JSlider extends JComponent implements SwingConstants, Accessible, { paintLabels = paint; if (paint && majorTickSpacing > 0 && labelTable == null) - labelTable = createStandardLabels(majorTickSpacing); + setLabelTable(createStandardLabels(majorTickSpacing)); firePropertyChange("paintLabels", !paint, paint); + revalidate(); + repaint(); } } diff --git a/libjava/classpath/javax/swing/JSplitPane.java b/libjava/classpath/javax/swing/JSplitPane.java index 5b77f5176ae..fcdc1c04128 100644 --- a/libjava/classpath/javax/swing/JSplitPane.java +++ b/libjava/classpath/javax/swing/JSplitPane.java @@ -247,6 +247,11 @@ public class JSplitPane extends JComponent implements Accessible /** The component on the right or bottom. */ protected Component rightComponent; + /** + * The divider location. + */ + private int dividerLocation; + /** Determines how extra space should be allocated. */ private transient double resizeWeight; @@ -288,7 +293,7 @@ public class JSplitPane extends JComponent implements Accessible continuousLayout = newContinuousLayout; setLeftComponent(newLeftComponent); setRightComponent(newRightComponent); - + dividerLocation = -1; updateUI(); } @@ -355,10 +360,6 @@ public class JSplitPane extends JComponent implements Accessible */ protected void addImpl(Component comp, Object constraints, int index) { - int left = 0; - int right = 1; - int div = 2; - int place; if (constraints == null) { if (leftComponent == null) @@ -431,10 +432,7 @@ public class JSplitPane extends JComponent implements Accessible */ public int getDividerLocation() { - if (ui != null) - return ((SplitPaneUI) ui).getDividerLocation(this); - else - return -1; + return dividerLocation; } /** @@ -722,17 +720,13 @@ public class JSplitPane extends JComponent implements Accessible */ public void setDividerLocation(int location) { - if (ui != null && location != getDividerLocation()) - { - int oldLocation = getDividerLocation(); - if (location < 0) - ((SplitPaneUI) ui).resetToPreferredSizes(this); - else - ((SplitPaneUI) ui).setDividerLocation(this, location); - - firePropertyChange(DIVIDER_LOCATION_PROPERTY, oldLocation, - getDividerLocation()); - } + int oldLocation = dividerLocation; + dividerLocation = location; + SplitPaneUI ui = getUI(); + if (ui != null) + ui.setDividerLocation(this, location); + firePropertyChange(DIVIDER_LOCATION_PROPERTY, oldLocation, + location); } /** diff --git a/libjava/classpath/javax/swing/JTabbedPane.java b/libjava/classpath/javax/swing/JTabbedPane.java index 5c8d0474852..c7244bf2b71 100644 --- a/libjava/classpath/javax/swing/JTabbedPane.java +++ b/libjava/classpath/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); } } @@ -1050,7 +1054,10 @@ public class JTabbedPane extends JComponent implements Serializable, } if (getSelectedIndex() == -1) - setSelectedIndex(0); + { + setSelectedIndex(0); + fireStateChanged(); + } revalidate(); repaint(); diff --git a/libjava/classpath/javax/swing/JTable.java b/libjava/classpath/javax/swing/JTable.java index 5c7bff5d019..42563e6a2ca 100644 --- a/libjava/classpath/javax/swing/JTable.java +++ b/libjava/classpath/javax/swing/JTable.java @@ -2635,6 +2635,7 @@ public class JTable setModel(dm == null ? createDefaultDataModel() : dm); setAutoCreateColumnsFromModel(autoCreate); initializeLocalVars(); + // The following four lines properly set the lead selection indices. // After this, the UI will handle the lead selection indices. // FIXME: this should probably not be necessary, if the UI is installed @@ -2642,11 +2643,13 @@ public class JTable // own, but certain variables need to be set before the UI can be installed // so we must get the correct order for all the method calls in this // constructor. - selectionModel.setAnchorSelectionIndex(0); - selectionModel.setLeadSelectionIndex(0); - columnModel.getSelectionModel().setAnchorSelectionIndex(0); - columnModel.getSelectionModel().setLeadSelectionIndex(0); - updateUI(); + // These four lines are not needed. A Mauve test that shows this is + // gnu.testlet.javax.swing.JTable.constructors(linesNotNeeded). + // selectionModel.setAnchorSelectionIndex(-1); + // selectionModel.setLeadSelectionIndex(-1); + // columnModel.getSelectionModel().setAnchorSelectionIndex(-1); + // columnModel.getSelectionModel().setLeadSelectionIndex(-1); + updateUI(); } /** @@ -2675,10 +2678,12 @@ public class JTable setRowHeight(16); this.rowMargin = 1; this.rowSelectionAllowed = true; + // this.accessibleContext = new AccessibleJTable(); this.cellEditor = null; + // COMPAT: Both Sun and IBM have drag enabled - this.dragEnabled = true; + this.dragEnabled = false; this.preferredViewportSize = new Dimension(450,400); this.showHorizontalLines = true; this.showVerticalLines = true; @@ -3267,7 +3272,7 @@ public class JTable cellRect.x += cMargin / 2; cellRect.width -= cMargin; } - } + } return cellRect; } @@ -3303,10 +3308,21 @@ public class JTable public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - if (orientation == SwingConstants.VERTICAL) - return visibleRect.height * direction; + int block; + if (orientation == SwingConstants.HORIZONTAL) + { + block = visibleRect.width; + } else - return visibleRect.width * direction; + { + int rowHeight = getRowHeight(); + if (rowHeight > 0) + block = Math.max(rowHeight, // Little hack for useful rounding. + (visibleRect.height / rowHeight) * rowHeight); + else + block = visibleRect.height; + } + return block; } /** @@ -3345,24 +3361,40 @@ public class JTable * The values greater than one means that more mouse wheel or similar * events were generated, and hence it is better to scroll the longer * distance. - * @author Audrius Meskauskas (audriusa@bioinformatics.org) + * + * @author Roman Kennke (kennke@aicas.com) */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - int h = (rowHeight + rowMargin); - int delta = h * direction; - - // Round so that the top would start from the row boundary - if (orientation == SwingConstants.VERTICAL) + int unit; + if (orientation == SwingConstants.HORIZONTAL) + unit = 100; + else { - // Completely expose the top row - int near = ((visibleRect.y + delta + h / 2) / h) * h; - int diff = visibleRect.y + delta - near; - delta -= diff; + unit = getRowHeight(); + // The following adjustment doesn't work for variable height rows. + // It fully exposes partially visible rows in the scrolling direction. + if (rowHeights == null) + { + if (direction > 0) + { + // Scroll down. + // How much pixles are exposed from the last item? + int exposed = (visibleRect.y + visibleRect.height) % unit; + if (exposed > 0 && exposed < unit - 1) + unit = unit - exposed - 1; + } + else + { + // Scroll up. + int exposed = visibleRect.y % unit; + if (exposed > 0 && exposed < unit) + unit = exposed; + } + } } - return delta; - // TODO when scrollng horizontally, scroll into the column boundary. + return unit; } /** @@ -3397,7 +3429,7 @@ public class JTable * * @return the editor, suitable for editing this data type */ - public TableCellEditor getDefaultEditor(Class columnClass) + public TableCellEditor getDefaultEditor(Class<?> columnClass) { if (defaultEditorsByColumnClass.containsKey(columnClass)) return (TableCellEditor) defaultEditorsByColumnClass.get(columnClass); @@ -3409,7 +3441,7 @@ public class JTable return r; } } - + /** * Get the cell renderer for rendering the given cell. * @@ -3419,7 +3451,9 @@ public class JTable */ public TableCellRenderer getCellRenderer(int row, int column) { - TableCellRenderer renderer = columnModel.getColumn(column).getCellRenderer(); + TableCellRenderer renderer = null; + if (columnModel.getColumnCount() > 0) + renderer = columnModel.getColumn(column).getCellRenderer(); if (renderer == null) { int mcolumn = convertColumnIndexToModel(column); @@ -3427,7 +3461,7 @@ public class JTable } return renderer; } - + /** * Set default renderer for rendering the given data type. * @@ -3435,11 +3469,11 @@ public class JTable * rendered. * @param rend the renderer that will rend this data type */ - public void setDefaultRenderer(Class columnClass, TableCellRenderer rend) + public void setDefaultRenderer(Class<?> columnClass, TableCellRenderer rend) { defaultRenderersByColumnClass.put(columnClass, rend); } - + /** * Get the default renderer for rendering the given data type. * @@ -3447,7 +3481,7 @@ public class JTable * * @return the appropriate defauld renderer for rendering that data type. */ - public TableCellRenderer getDefaultRenderer(Class columnClass) + public TableCellRenderer getDefaultRenderer(Class<?> columnClass) { if (defaultRenderersByColumnClass.containsKey(columnClass)) return (TableCellRenderer) defaultRenderersByColumnClass.get(columnClass); @@ -3536,7 +3570,7 @@ public class JTable return renderer.getTableCellRendererComponent(this, dataModel.getValueAt(row, - convertColumnIndexToModel(column)), + convertColumnIndexToModel(column)), isSel, hasFocus, row, column); @@ -4414,7 +4448,7 @@ public class JTable { TableColumn resizingColumn = null; - int ncols = getColumnCount(); + int ncols = columnModel.getColumnCount(); if (ncols < 1) return; @@ -4423,7 +4457,7 @@ public class JTable if (tableHeader != null) resizingColumn = tableHeader.getResizingColumn(); - + for (int i = 0; i < ncols; ++i) { TableColumn col = columnModel.getColumn(i); @@ -4432,7 +4466,7 @@ public class JTable if (resizingColumn == col) rCol = i; } - + int spill = getWidth() - prefSum; if (resizingColumn != null) @@ -4498,9 +4532,11 @@ public class JTable } else { - TableColumn [] cols = new TableColumn[ncols]; + TableColumn[] cols = new TableColumn[ncols]; + for (int i = 0; i < ncols; ++i) cols[i] = columnModel.getColumn(i); + distributeSpill(cols, spill); } @@ -4588,7 +4624,7 @@ public class JTable { setUI((TableUI) UIManager.getUI(this)); } - + /** * Get the class (datatype) of the column. The cells are rendered and edited * differently, depending from they data type. @@ -4598,7 +4634,7 @@ public class JTable * @return the class, defining data type of that column (String.class for * String, Boolean.class for boolean and so on). */ - public Class getColumnClass(int column) + public Class<?> getColumnClass(int column) { return getModel().getColumnClass(convertColumnIndexToModel(column)); } @@ -4619,7 +4655,7 @@ public class JTable int modelColumn = columnModel.getColumn(column).getModelIndex(); return dataModel.getColumnName(modelColumn); } - + /** * Get the column, currently being edited * @@ -4629,7 +4665,7 @@ public class JTable { return editingColumn; } - + /** * Set the column, currently being edited * @@ -4649,7 +4685,7 @@ public class JTable { return editingRow; } - + /** * Set the row currently being edited. * @@ -4680,7 +4716,7 @@ public class JTable { return editorComp != null; } - + /** * Set the default editor for the given column class (column data type). * By default, String is handled by text field and Boolean is handled by @@ -4691,7 +4727,7 @@ public class JTable * * @see TableModel#getColumnClass(int) */ - public void setDefaultEditor(Class columnClass, TableCellEditor editor) + public void setDefaultEditor(Class<?> columnClass, TableCellEditor editor) { if (editor != null) defaultEditorsByColumnClass.put(columnClass, editor); @@ -4713,7 +4749,7 @@ public class JTable if ((index0 < 0 || index0 > (getRowCount()-1) || index1 < 0 || index1 > (getRowCount()-1))) throw new IllegalArgumentException("Row index out of range."); - + getSelectionModel().addSelectionInterval(index0, index1); } diff --git a/libjava/classpath/javax/swing/JTextField.java b/libjava/classpath/javax/swing/JTextField.java index 367503b739f..ace358f8922 100644 --- a/libjava/classpath/javax/swing/JTextField.java +++ b/libjava/classpath/javax/swing/JTextField.java @@ -270,7 +270,8 @@ public class JTextField extends JTextComponent */ protected void fireActionPerformed() { - ActionEvent event = new ActionEvent(this, 0, getText()); + ActionEvent event = new ActionEvent(this, 0, + actionCommand == null ? getText() : actionCommand); ActionListener[] listeners = getActionListeners(); for (int index = 0; index < listeners.length; ++index) diff --git a/libjava/classpath/javax/swing/JTextPane.java b/libjava/classpath/javax/swing/JTextPane.java index c0a5f80cfc8..05968fc8c20 100644 --- a/libjava/classpath/javax/swing/JTextPane.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/JToggleButton.java b/libjava/classpath/javax/swing/JToggleButton.java index 1769c5ee08a..60c44896019 100644 --- a/libjava/classpath/javax/swing/JToggleButton.java +++ b/libjava/classpath/javax/swing/JToggleButton.java @@ -1,5 +1,5 @@ /* JToggleButton.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. @@ -291,9 +291,8 @@ public class JToggleButton extends AbstractButton implements Accessible public JToggleButton (String text, Icon icon, boolean selected) { super(); + setModel(new ToggleButtonModel()); init(text, icon); - - setModel(new ToggleButtonModel()); model.setSelected(selected); setAlignmentX(LEFT_ALIGNMENT); } diff --git a/libjava/classpath/javax/swing/JToolTip.java b/libjava/classpath/javax/swing/JToolTip.java index 836c122c6bf..3153894da81 100644 --- a/libjava/classpath/javax/swing/JToolTip.java +++ b/libjava/classpath/javax/swing/JToolTip.java @@ -225,4 +225,18 @@ public class JToolTip extends JComponent implements Accessible { setUI((ToolTipUI) UIManager.getUI(this)); } + + /** + * Returns <code>true</code> if the component is guaranteed to be painted + * on top of others. This returns false by default and is overridden by + * components like JMenuItem, JPopupMenu and JToolTip to return true for + * added efficiency. + * + * @return <code>true</code> if the component is guaranteed to be painted + * on top of others + */ + boolean onTop() + { + return true; + } } diff --git a/libjava/classpath/javax/swing/JTree.java b/libjava/classpath/javax/swing/JTree.java index fa898c5a940..332ec74247d 100644 --- a/libjava/classpath/javax/swing/JTree.java +++ b/libjava/classpath/javax/swing/JTree.java @@ -1230,8 +1230,32 @@ public class JTree extends JComponent implements Scrollable, Accessible */ public void treeNodesRemoved(TreeModelEvent ev) { - // TODO: The API docs suggest that this method should do something - // but I cannot really see what has to be done here ... + if (ev != null) + { + TreePath parent = ev.getTreePath(); + Object[] children = ev.getChildren(); + TreeSelectionModel sm = getSelectionModel(); + if (children != null) + { + TreePath path; + Vector toRemove = new Vector(); + // Collect items that we must remove. + for (int i = children.length - 1; i >= 0; i--) + { + path = parent.pathByAddingChild(children[i]); + if (nodeStates.containsKey(path)) + toRemove.add(path); + // Clear selection while we are at it. + if (sm != null) + removeDescendantSelectedPaths(path, true); + } + if (toRemove.size() > 0) + removeDescendantToggledPaths(toRemove.elements()); + TreeModel model = getModel(); + if (model == null || model.isLeaf(parent.getLastPathComponent())) + nodeStates.remove(parent); + } + } } /** @@ -1243,9 +1267,38 @@ public class JTree extends JComponent implements Scrollable, Accessible */ public void treeStructureChanged(TreeModelEvent ev) { - // Set state of new path. - TreePath path = ev.getTreePath(); - setExpandedState(path, isExpanded(path)); + if (ev != null) + { + TreePath parent = ev.getTreePath(); + if (parent != null) + { + if (parent.getPathCount() == 1) + { + // We have a new root, clear everything. + clearToggledPaths(); + Object root = treeModel.getRoot(); + if (root != null && treeModel.isLeaf(root)) + nodeStates.put(parent, Boolean.TRUE); + } + else if (nodeStates.containsKey(parent)) + { + Vector toRemove = new Vector(); + boolean expanded = isExpanded(parent); + toRemove.add(parent); + removeDescendantToggledPaths(toRemove.elements()); + if (expanded) + { + TreeModel model = getModel(); + if (model != null + || model.isLeaf(parent.getLastPathComponent())) + collapsePath(parent); + else + nodeStates.put(parent, Boolean.TRUE); + } + } + removeDescendantSelectedPaths(parent, false); + } + } } } @@ -1279,13 +1332,6 @@ public class JTree extends JComponent implements Scrollable, Accessible TreeSelectionEvent rewritten = (TreeSelectionEvent) ev.cloneWithSource(JTree.this); fireValueChanged(rewritten); - - // Only repaint the changed nodes. - TreePath[] changed = ev.getPaths(); - for (int i = 0; i < changed.length; i++) - { - repaint(getPathBounds(changed[i])); - } } } @@ -1406,8 +1452,10 @@ public class JTree extends JComponent implements Scrollable, Accessible * This contains the state of all nodes in the tree. Al/ entries map the * TreePath of a note to to its state. Valid states are EXPANDED and * COLLAPSED. Nodes not in this Hashtable are assumed state COLLAPSED. + * + * This is package private to avoid accessor methods. */ - private Hashtable nodeStates = new Hashtable(); + Hashtable nodeStates = new Hashtable(); protected transient TreeCellEditor cellEditor; @@ -1486,7 +1534,7 @@ public class JTree extends JComponent implements Scrollable, Accessible * * @param value the initial nodes in the tree */ - public JTree(Hashtable value) + public JTree(Hashtable<?, ?> value) { this(createTreeModel(value)); } @@ -1509,8 +1557,7 @@ 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(); @@ -1554,7 +1601,7 @@ public class JTree extends JComponent implements Scrollable, Accessible * * @param value the initial nodes in the tree */ - public JTree(Vector value) + public JTree(Vector<?> value) { this(createTreeModel(value)); } @@ -1685,29 +1732,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.y; + } + 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() @@ -2050,14 +2120,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(); @@ -2184,20 +2256,35 @@ public class JTree extends JComponent implements Scrollable, Accessible public void setSelectionPath(TreePath path) { + clearSelectionPathStates(); selectionModel.setSelectionPath(path); } public void setSelectionPaths(TreePath[] paths) { + clearSelectionPathStates(); selectionModel.setSelectionPaths(paths); } + + /** + * This method, and all calls to it, should be removed once the + * DefaultTreeModel fires events properly. Maintenance of the nodeStates + * table should really be done in the TreeModelHandler. + */ + private void clearSelectionPathStates() + { + TreePath[] oldPaths = selectionModel.getSelectionPaths(); + if (oldPaths != null) + for (int i = 0; i < oldPaths.length; i++) + nodeStates.remove(oldPaths[i]); + } public void setSelectionRow(int row) { TreePath path = getPathForRow(row); if (path != null) - selectionModel.setSelectionPath(path); + setSelectionPath(path); } public void setSelectionRows(int[] rows) @@ -2272,11 +2359,13 @@ public class JTree extends JComponent implements Scrollable, Accessible public void removeSelectionPath(TreePath path) { + clearSelectionPathStates(); selectionModel.removeSelectionPath(path); } public void removeSelectionPaths(TreePath[] paths) { + clearSelectionPathStates(); selectionModel.removeSelectionPaths(paths); } @@ -2285,7 +2374,7 @@ public class JTree extends JComponent implements Scrollable, Accessible TreePath path = getPathForRow(row); if (path != null) - selectionModel.removeSelectionPath(path); + removeSelectionPath(path); } public void removeSelectionRows(int[] rows) @@ -2331,7 +2420,7 @@ public class JTree extends JComponent implements Scrollable, Accessible if (selectionModel != null) { TreePath oldValue = selectionModel.getLeadSelectionPath(); - if (path.equals(oldValue)) + if (path == oldValue || path != null && path.equals(oldValue)) return; // Repaint the previous and current rows with the lead selection path. @@ -2409,9 +2498,19 @@ public class JTree extends JComponent implements Scrollable, Accessible return selectionModel.isPathSelected(path); } + /** + * Returns <code>true</code> when the specified row is selected, + * <code>false</code> otherwise. This call is delegated to the + * {@link TreeSelectionModel#isRowSelected(int)} method. + * + * @param row the row to check + * + * @return <code>true</code> when the specified row is selected, + * <code>false</code> otherwise + */ public boolean isRowSelected(int row) { - return selectionModel.isPathSelected(getPathForRow(row)); + return selectionModel.isRowSelected(row); } public boolean isSelectionEmpty() @@ -2725,7 +2824,7 @@ public class JTree extends JComponent implements Scrollable, Accessible nodeStates.clear(); } - protected Enumeration getDescendantToggledPaths(TreePath parent) + protected Enumeration<TreePath> getDescendantToggledPaths(TreePath parent) { if (parent == null) return null; @@ -2874,7 +2973,7 @@ public class JTree extends JComponent implements Scrollable, Accessible * * @return An Enumeration containing TreePath objects */ - public Enumeration getExpandedDescendants(TreePath path) + public Enumeration<TreePath> getExpandedDescendants(TreePath path) { Enumeration paths = nodeStates.keys(); Vector relevantPaths = new Vector(); @@ -3002,7 +3101,7 @@ public class JTree extends JComponent implements Scrollable, Accessible * @param toRemove - Enumeration of TreePaths that need to be removed from * cache of toggled tree paths. */ - protected void removeDescendantToggledPaths(Enumeration toRemove) + protected void removeDescendantToggledPaths(Enumeration<TreePath> toRemove) { while (toRemove.hasMoreElements()) { diff --git a/libjava/classpath/javax/swing/JViewport.java b/libjava/classpath/javax/swing/JViewport.java index 7cf393996c3..d90da1d157b 100644 --- a/libjava/classpath/javax/swing/JViewport.java +++ b/libjava/classpath/javax/swing/JViewport.java @@ -157,6 +157,9 @@ public class JViewport extends JComponent implements Accessible */ public void componentResized(ComponentEvent ev) { + // Fire state change, because resizing the view means changing the + // extentSize. + fireStateChanged(); revalidate(); } } @@ -198,22 +201,6 @@ public class JViewport extends JComponent implements Accessible int scrollMode; - /** - * The width and height of the Viewport's area in terms of view - * coordinates. Typically this will be the same as the width and height - * of the viewport's bounds, unless the viewport transforms units of - * width and height, which it may do, for example if it magnifies or - * rotates its view. - * - * @see #toViewCoordinates(Dimension) - */ - Dimension extentSize; - - /** - * The width and height of the view in its own coordinate space. - */ - Dimension viewSize; - /** * The ViewListener instance. */ @@ -265,8 +252,7 @@ public class JViewport extends JComponent implements Accessible static { String scrollModeProp = - SystemProperties.getProperty("gnu.javax.swing.JViewport.scrollMode", - "BLIT"); + SystemProperties.getProperty("gnu.swing.scrollmode", "BACKINGSTORE"); if (scrollModeProp.equalsIgnoreCase("simple")) defaultScrollMode = SIMPLE_SCROLL_MODE; else if (scrollModeProp.equalsIgnoreCase("backingstore")) @@ -290,10 +276,7 @@ public class JViewport extends JComponent implements Accessible public Dimension getExtentSize() { - if (extentSize == null) - return toViewCoordinates(getSize()); - else - return extentSize; + return getSize(); } public Dimension toViewCoordinates(Dimension size) @@ -310,8 +293,12 @@ public class JViewport extends JComponent implements Accessible public void setExtentSize(Dimension newSize) { - extentSize = newSize; - fireStateChanged(); + Dimension oldExtent = getExtentSize(); + if (! newSize.equals(oldExtent)) + { + setSize(newSize); + fireStateChanged(); + } } /** @@ -321,32 +308,34 @@ public class JViewport extends JComponent implements Accessible */ public Dimension getViewSize() { - if (isViewSizeSet) - return viewSize; - else + Dimension size; + Component view = getView(); + if (view != null) { - Component view = getView(); - if (view != null) - return view.getPreferredSize(); - else - return new Dimension(); + if (isViewSizeSet) + size = view.getSize(); + else + size = view.getPreferredSize(); } + else + size = new Dimension(0, 0); + return size; } public void setViewSize(Dimension newSize) { - viewSize = newSize; Component view = getView(); if (view != null) { - if (newSize != view.getSize()) + if (! newSize.equals(view.getSize())) { - view.setSize(viewSize); + scrollUnderway = false; + view.setSize(newSize); + isViewSizeSet = true; fireStateChanged(); } } - isViewSizeSet = true; } /** @@ -371,23 +360,18 @@ public class JViewport extends JComponent implements Accessible public void setViewPosition(Point p) { - if (getViewPosition().equals(p)) - return; Component view = getView(); - if (view != null) + if (view != null && ! p.equals(getViewPosition())) { - Point q = new Point(-p.x, -p.y); - view.setLocation(q); - isViewSizeSet = false; + scrollUnderway = true; + view.setLocation(-p.x, -p.y); fireStateChanged(); } - repaint(); } public Rectangle getViewRect() { - return new Rectangle(getViewPosition(), - getExtentSize()); + return new Rectangle(getViewPosition(), getExtentSize()); } /** @@ -495,7 +479,6 @@ public class JViewport extends JComponent implements Accessible if (view == null) return; - Point pos = getViewPosition(); Rectangle viewBounds = view.getBounds(); Rectangle portBounds = getBounds(); @@ -643,19 +626,11 @@ public class JViewport extends JComponent implements Accessible */ public void repaint(long tm, int x, int y, int w, int h) { -// Component parent = getParent(); -// if (parent != null) -// parent.repaint(tm, x + getX(), y + getY(), w, h); -// else -// super.repaint(tm, x, y, w, h); - - // The specs suggest to implement something like the above. This however - // breaks blit painting, because the parent (most likely a JScrollPane) - // clears the background of the offscreen area of the JViewport, thus - // destroying the pieces that we want to clip. So we simply call super here - // instead. - super.repaint(tm, x, y, w, h); - + Component parent = getParent(); + if (parent != null) + parent.repaint(tm, x + getX(), y + getY(), w, h); + else + super.repaint(tm, x, y, w, h); } protected void addImpl(Component comp, Object constraints, int index) @@ -862,10 +837,13 @@ public class JViewport extends JComponent implements Accessible if (canBlit) { // Copy the part that remains visible during scrolling. - g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y, - cachedBlitSize.width, cachedBlitSize.height, - cachedBlitTo.x - cachedBlitFrom.x, - cachedBlitTo.y - cachedBlitFrom.y); + if (cachedBlitSize.width > 0 && cachedBlitSize.height > 0) + { + g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y, + cachedBlitSize.width, cachedBlitSize.height, + cachedBlitTo.x - cachedBlitFrom.x, + cachedBlitTo.y - cachedBlitFrom.y); + } // Now paint the part that becomes newly visible. g2.setClip(cachedBlitPaint.x, cachedBlitPaint.y, cachedBlitPaint.width, cachedBlitPaint.height); @@ -913,10 +891,13 @@ public class JViewport extends JComponent implements Accessible if (canBlit && isPaintRoot) { // Copy the part that remains visible during scrolling. - g.copyArea(cachedBlitFrom.x, cachedBlitFrom.y, - cachedBlitSize.width, cachedBlitSize.height, - cachedBlitTo.x - cachedBlitFrom.x, - cachedBlitTo.y - cachedBlitFrom.y); + if (cachedBlitSize.width > 0 && cachedBlitSize.width > 0) + { + g.copyArea(cachedBlitFrom.x, cachedBlitFrom.y, + cachedBlitSize.width, cachedBlitSize.height, + cachedBlitTo.x - cachedBlitFrom.x, + cachedBlitTo.y - cachedBlitFrom.y); + } // Now paint the part that becomes newly visible. Shape oldClip = g.getClip(); g.clipRect(cachedBlitPaint.x, cachedBlitPaint.y, @@ -940,12 +921,24 @@ public class JViewport extends JComponent implements Accessible /** * Overridden from JComponent to set the {@link #isPaintRoot} flag. * - * @param r the rectangle to paint + * @param x the rectangle to paint, X coordinate + * @param y the rectangle to paint, Y coordinate + * @param w the rectangle to paint, width + * @param h the rectangle to paint, height */ - void paintImmediately2(Rectangle r) + void paintImmediately2(int x, int y, int w, int h) { isPaintRoot = true; - super.paintImmediately2(r); + super.paintImmediately2(x, y, w, h); isPaintRoot = false; } + + /** + * Returns true when the JViewport is using a backbuffer, so that we + * can update our backbuffer correctly. + */ + boolean isPaintRoot() + { + return scrollMode == BACKINGSTORE_SCROLL_MODE; + } } diff --git a/libjava/classpath/javax/swing/JWindow.java b/libjava/classpath/javax/swing/JWindow.java index 19d830ed1f7..b36b8cf2a60 100644 --- a/libjava/classpath/javax/swing/JWindow.java +++ b/libjava/classpath/javax/swing/JWindow.java @@ -38,6 +38,7 @@ exception statement from your version. */ package javax.swing; +import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; @@ -158,6 +159,10 @@ public class JWindow extends Window implements Accessible, RootPaneContainer protected void windowInit() { + // We need to explicitly enable events here so that our processKeyEvent() + // and processWindowEvent() gets called. + enableEvents(AWTEvent.KEY_EVENT_MASK); + super.setLayout(new BorderLayout(1, 1)); getRootPane(); // will do set/create // Now we're done init stage, adds and layouts go to content pane. diff --git a/libjava/classpath/javax/swing/LookAndFeel.java b/libjava/classpath/javax/swing/LookAndFeel.java index be543439636..d995bc9e981 100644 --- a/libjava/classpath/javax/swing/LookAndFeel.java +++ b/libjava/classpath/javax/swing/LookAndFeel.java @@ -284,7 +284,7 @@ public abstract class LookAndFeel * @return A {@link UIDefaults.LazyValue} that serves up an * {@link IconUIResource}. */ - public static Object makeIcon(Class baseClass, String gifFile) + public static Object makeIcon(Class<?> baseClass, String gifFile) { final URL file = baseClass.getResource(gifFile); return new UIDefaults.LazyValue() diff --git a/libjava/classpath/javax/swing/Popup.java b/libjava/classpath/javax/swing/Popup.java index 308cd662d8d..5074d64186c 100644 --- a/libjava/classpath/javax/swing/Popup.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/RepaintManager.java b/libjava/classpath/javax/swing/RepaintManager.java index 80f0a3481cd..773371489d9 100644 --- a/libjava/classpath/javax/swing/RepaintManager.java +++ b/libjava/classpath/javax/swing/RepaintManager.java @@ -38,21 +38,29 @@ exception statement from your version. */ package javax.swing; +import gnu.classpath.SystemProperties; +import gnu.java.awt.LowPriorityEvent; + +import java.applet.Applet; import java.awt.Component; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.Window; +import java.awt.event.InvocationEvent; import java.awt.image.VolatileImage; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.Map; import java.util.Set; import java.util.WeakHashMap; +import javax.swing.text.JTextComponent; + /** * <p>The repaint manager holds a set of dirty regions, invalid components, * and a double buffer surface. The dirty regions and invalid components @@ -64,6 +72,7 @@ import java.util.WeakHashMap; * <p>See <a * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this * document</a> for more details.</p> + * document</a> for more details.</p> * * @author Roman Kennke (kennke@aicas.com) * @author Graydon Hoare (graydon@redhat.com) @@ -72,6 +81,44 @@ import java.util.WeakHashMap; public class RepaintManager { /** + * An InvocationEvent subclass that implements LowPriorityEvent. This is used + * to defer the execution of RepaintManager requests as long as possible on + * the event queue. This way we make sure that all available input is + * processed before getting active with the RepaintManager. This allows + * for better optimization (more validate and repaint requests can be + * coalesced) and thus has a positive effect on performance for GUI + * applications under heavy load. + */ + private static class RepaintWorkerEvent + extends InvocationEvent + implements LowPriorityEvent + { + + /** + * Creates a new RepaintManager event. + * + * @param source the source + * @param runnable the runnable to execute + */ + public RepaintWorkerEvent(Object source, Runnable runnable, + Object notifier, boolean catchEx) + { + super(source, runnable, notifier, catchEx); + } + + /** + * An application that I met implements its own event dispatching and + * calls dispatch() via reflection, and only checks declared methods, + * that is, it expects this method to be in the event's class, not + * in a superclass. So I put this in here... sigh. + */ + public void dispatch() + { + super.dispatch(); + } + } + + /** * The current repaint managers, indexed by their ThreadGroups. */ static WeakHashMap currentRepaintManagers; @@ -197,19 +244,6 @@ public class RepaintManager private WeakHashMap offscreenBuffers; /** - * Indicates if the RepaintManager is currently repainting an area. - */ - private boolean repaintUnderway; - - /** - * This holds buffer commit requests when the RepaintManager is working. - * This maps Component objects (the top level components) to Rectangle - * objects (the area of the corresponding buffer that must be blitted on - * the component). - */ - private HashMap commitRequests; - - /** * The maximum width and height to allocate as a double buffer. Requests * beyond this size are ignored. * @@ -230,10 +264,10 @@ public class RepaintManager invalidComponents = new ArrayList(); repaintWorker = new RepaintWorker(); doubleBufferMaximumSize = new Dimension(2000,2000); - doubleBufferingEnabled = true; + doubleBufferingEnabled = + SystemProperties.getProperty("gnu.swing.doublebuffering", "true") + .equals("true"); offscreenBuffers = new WeakHashMap(); - repaintUnderway = false; - commitRequests = new HashMap(); } /** @@ -355,7 +389,7 @@ public class RepaintManager if (! repaintWorker.isLive()) { repaintWorker.setLive(true); - SwingUtilities.invokeLater(repaintWorker); + invokeLater(repaintWorker); } } @@ -397,23 +431,21 @@ public class RepaintManager { if (w <= 0 || h <= 0 || !component.isShowing()) return; - - Component parent = component.getParent(); - component.computeVisibleRect(rectCache); SwingUtilities.computeIntersection(x, y, w, h, rectCache); if (! rectCache.isEmpty()) { - if (dirtyComponents.containsKey(component)) + synchronized (dirtyComponents) { - SwingUtilities.computeUnion(rectCache.x, rectCache.y, - rectCache.width, rectCache.height, - (Rectangle) dirtyComponents.get(component)); - } - else - { - synchronized (dirtyComponents) + Rectangle dirtyRect = (Rectangle)dirtyComponents.get(component); + if (dirtyRect != null) + { + SwingUtilities.computeUnion(rectCache.x, rectCache.y, + rectCache.width, rectCache.height, + dirtyRect); + } + else { dirtyComponents.put(component, rectCache.getBounds()); } @@ -422,7 +454,7 @@ public class RepaintManager if (! repaintWorker.isLive()) { repaintWorker.setLive(true); - SwingUtilities.invokeLater(repaintWorker); + invokeLater(repaintWorker); } } } @@ -536,7 +568,7 @@ public class RepaintManager */ public void paintDirtyRegions() { - // Short cicuit if there is nothing to paint. + // Short circuit if there is nothing to paint. if (dirtyComponents.size() == 0) return; @@ -557,7 +589,6 @@ public class RepaintManager compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots); } - repaintUnderway = true; for (Iterator i = repaintRoots.iterator(); i.hasNext();) { JComponent comp = (JComponent) i.next(); @@ -567,13 +598,11 @@ public class RepaintManager comp.paintImmediately(damaged); } dirtyComponentsWork.clear(); - repaintUnderway = false; - commitRemainingBuffers(); } - + /** * Compiles a list of components that really get repainted. This is called - * once for each component in the dirtyComponents HashMap, each time with + * once for each component in the dirtyRegions HashMap, each time with * another <code>dirty</code> parameter. This searches up the component * hierarchy of <code>dirty</code> to find the highest parent that is also * marked dirty and merges the dirty regions. @@ -588,6 +617,29 @@ public class RepaintManager Component current = dirty; Component root = dirty; + // This will contain the dirty region in the root coordinate system, + // possibly clipped by ancestor's bounds. + Rectangle originalDirtyRect = (Rectangle) dirtyRegions.get(dirty); + rectCache.setBounds(originalDirtyRect); + + // The bounds of the current component. + int x = dirty.getX(); + int y = dirty.getY(); + int w = dirty.getWidth(); + int h = dirty.getHeight(); + + // Do nothing if dirty region is clipped away by the component's bounds. + rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache); + if (rectCache.isEmpty()) + return; + + // The cumulated offsets. + int dx = 0; + int dy = 0; + // The actual offset for the found root. + int rootDx = 0; + int rootDy = 0; + // Search the highest component that is also marked dirty. Component parent; while (true) @@ -597,10 +649,29 @@ public class RepaintManager break; current = parent; + // Update the offset. + dx += x; + dy += y; + rectCache.x += x; + rectCache.y += y; + + x = current.getX(); + y = current.getY(); + w = current.getWidth(); + h = current.getHeight(); + rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache); + + // Don't paint if the dirty regions is clipped away by any of + // its ancestors. + if (rectCache.isEmpty()) + return; + // We can skip to the next up when this parent is not dirty. if (dirtyRegions.containsKey(parent)) { root = current; + rootDx = dx; + rootDy = dy; } } @@ -608,15 +679,16 @@ public class RepaintManager // the are different. if (root != dirty) { - Rectangle dirtyRect = (Rectangle) dirtyRegions.get(dirty); - dirtyRect = SwingUtilities.convertRectangle(dirty, dirtyRect, root); - Rectangle rootRect = (Rectangle) dirtyRegions.get(root); - SwingUtilities.computeUnion(dirtyRect.x, dirtyRect.y, dirtyRect.width, - dirtyRect.height, rootRect); + rectCache.x += rootDx - dx; + rectCache.y += rootDy - dy; + Rectangle dirtyRect = (Rectangle) dirtyRegions.get(root); + SwingUtilities.computeUnion(rectCache.x, rectCache.y, rectCache.width, + rectCache.height, dirtyRect); } // Adds the root to the roots set. - roots.add(root); + if (! roots.contains(root)) + roots.add(root); } /** @@ -643,128 +715,43 @@ public class RepaintManager width = Math.min(doubleBufferMaximumSize.width, width); int height = Math.max(proposedHeight, root.getHeight()); height = Math.min(doubleBufferMaximumSize.height, height); - buffer = root.createImage(width, height); + buffer = component.createImage(width, height); offscreenBuffers.put(root, buffer); } return buffer; } /** - * Blits the back buffer of the specified root component to the screen. If - * the RepaintManager is currently working on a paint request, the commit - * requests are queued up and committed at once when the paint request is - * done (by {@link #commitRemainingBuffers}). This is package private because - * it must get called by JComponent. + * Blits the back buffer of the specified root component to the screen. + * This is package private because it must get called by JComponent. * * @param comp the component to be painted - * @param area the area to paint on screen, in comp coordinates - */ - void commitBuffer(Component comp, Rectangle area) - { - // Determine the component that we finally paint the buffer upon. - // We need to paint on the nearest heavyweight component, so that Swing - // hierarchies inside (non-window) heavyweights get painted correctly. - // Otherwise we would end up blitting the backbuffer behind the heavyweight - // which is wrong. - Component root = getHeavyweightParent(comp); - // FIXME: Optimize this. - Rectangle rootRect = SwingUtilities.convertRectangle(comp, area, root); - - // We synchronize on dirtyComponents here because that is what - // paintDirtyRegions also synchronizes on while painting. - synchronized (dirtyComponents) - { - // If the RepaintManager is not currently painting, then directly - // blit the requested buffer on the screen. - if (true || ! repaintUnderway) - { - blitBuffer(root, rootRect); - } - - // Otherwise queue this request up, until all the RepaintManager work - // is done. - else - { - if (commitRequests.containsKey(root)) - SwingUtilities.computeUnion(rootRect.x, rootRect.y, - rootRect.width, rootRect.height, - (Rectangle) commitRequests.get(root)); - else - commitRequests.put(root, rootRect); - } - } - } - - /** - * Copies the buffer to the screen. Note that the root component here is - * not necessarily the component with which the offscreen buffer is - * associated. The offscreen buffers are always allocated for the toplevel - * windows. However, painted is performed on lower-level heavyweight - * components too, if they contain Swing components. - * - * @param root the heavyweight component to blit upon - * @param rootRect the rectangle in the root component's coordinate space + * @param x the area to paint on screen, in comp coordinates + * @param y the area to paint on screen, in comp coordinates + * @param w the area to paint on screen, in comp coordinates + * @param h the area to paint on screen, in comp coordinates */ - private void blitBuffer(Component root, Rectangle rootRect) + void commitBuffer(Component comp, int x, int y, int w, int h) { - if (! root.isShowing()) - return; - - // Find the Window from which we use the backbuffer. - Component bufferRoot = root; - Rectangle bufferRect = rootRect.getBounds(); - if (!(bufferRoot instanceof Window)) + Component root = comp; + while (root != null + && ! (root instanceof Window || root instanceof Applet)) { - bufferRoot = SwingUtilities.getWindowAncestor(bufferRoot); - SwingUtilities.convertRectangleToAncestor(root, rootRect, bufferRoot); + x += root.getX(); + y += root.getY(); + root = root.getParent(); } - Graphics g = root.getGraphics(); - Image buffer = (Image) offscreenBuffers.get(bufferRoot); - - // Make sure we have a sane clip at this point. - g.clipRect(rootRect.x, rootRect.y, rootRect.width, rootRect.height); - g.drawImage(buffer, rootRect.x - bufferRect.x, rootRect.y - bufferRect.y, - root); - g.dispose(); - - } - - /** - * Finds and returns the nearest heavyweight parent for the specified - * component. If the component isn't contained inside a heavyweight parent, - * this returns null. - * - * @param comp the component - * - * @return the nearest heavyweight parent for the specified component or - * null if the component has no heavyweight ancestor - */ - private Component getHeavyweightParent(Component comp) - { - while (comp != null && comp.isLightweight()) - comp = comp.getParent(); - return comp; - } - - /** - * Commits the queued up back buffers to screen all at once. - */ - private void commitRemainingBuffers() - { - // We synchronize on dirtyComponents here because that is what - // paintDirtyRegions also synchronizes on while painting. - synchronized (dirtyComponents) + if (root != null) { - Set entrySet = commitRequests.entrySet(); - Iterator i = entrySet.iterator(); - while (i.hasNext()) + Graphics g = root.getGraphics(); + Image buffer = (Image) offscreenBuffers.get(root); + if (buffer != null) { - Map.Entry entry = (Map.Entry) i.next(); - Component root = (Component) entry.getKey(); - Rectangle area = (Rectangle) entry.getValue(); - blitBuffer(root, area); - i.remove(); + // Make sure we have a sane clip at this point. + g.clipRect(x, y, w, h); + g.drawImage(buffer, 0, 0, root); + g.dispose(); } } } @@ -858,4 +845,18 @@ public class RepaintManager { return "RepaintManager"; } + + /** + * Sends an RepaintManagerEvent to the event queue with the specified + * runnable. This is similar to SwingUtilities.invokeLater(), only that the + * event is a low priority event in order to defer the execution a little + * more. + */ + private void invokeLater(Runnable runnable) + { + Toolkit tk = Toolkit.getDefaultToolkit(); + EventQueue evQueue = tk.getSystemEventQueue(); + InvocationEvent ev = new RepaintWorkerEvent(evQueue, runnable, null, false); + evQueue.postEvent(ev); + } } diff --git a/libjava/classpath/javax/swing/ScrollPaneLayout.java b/libjava/classpath/javax/swing/ScrollPaneLayout.java index 8ce8fd86f7a..2a16f26eae9 100644 --- a/libjava/classpath/javax/swing/ScrollPaneLayout.java +++ b/libjava/classpath/javax/swing/ScrollPaneLayout.java @@ -46,6 +46,8 @@ import java.awt.LayoutManager; import java.awt.Rectangle; import java.io.Serializable; +import javax.swing.border.Border; + /** * ScrollPaneLayout * @author Andrew Selkirk @@ -277,6 +279,16 @@ public class ScrollPaneLayout width += rowHead.getPreferredSize().width; if (colHead != null && colHead.isVisible()) height += colHead.getPreferredSize().height; + + // Add insets of viewportBorder if present. + Border vpBorder = sc.getViewportBorder(); + if (vpBorder != null) + { + Insets i = vpBorder.getBorderInsets(sc); + width += i.left + i.right; + height += i.top + i.bottom; + } + Insets i = sc.getInsets(); return new Dimension(width + i.left + i.right, height + i.left + i.right); @@ -300,6 +312,15 @@ public class ScrollPaneLayout != JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) height += sc.getHorizontalScrollBar().getMinimumSize().height; + // Add insets of viewportBorder if present. + Border vpBorder = sc.getViewportBorder(); + if (vpBorder != null) + { + i = vpBorder.getBorderInsets(sc); + width += i.left + i.right; + height += i.top + i.bottom; + } + return new Dimension(width, height); } @@ -342,6 +363,15 @@ public class ScrollPaneLayout int y1 = 0, y2 = 0, y3 = 0, y4 = 0; Rectangle scrollPaneBounds = SwingUtilities.calculateInnerArea(sc, null); + // If there is a viewportBorder, remove its insets from the available + // space. + Border vpBorder = sc.getViewportBorder(); + Insets vpi; + if (vpBorder != null) + vpi = vpBorder.getBorderInsets(sc); + else + vpi = new Insets(0, 0, 0, 0); + x1 = scrollPaneBounds.x; y1 = scrollPaneBounds.y; x4 = scrollPaneBounds.x + scrollPaneBounds.width; @@ -404,7 +434,9 @@ public class ScrollPaneLayout // now set the layout if (viewport != null) - viewport.setBounds(new Rectangle(x2, y2, x3 - x2, y3 - y2)); + viewport.setBounds(new Rectangle(x2 + vpi.left, y2 + vpi.top, + x3 - x2 - vpi.left - vpi.right, + y3 - y2 - vpi.top - vpi.bottom)); if (colHead != null) colHead.setBounds(new Rectangle(x2, y1, x3 - x2, y2 - y1)); @@ -415,7 +447,7 @@ public class ScrollPaneLayout if (showVsb) { vsb.setVisible(true); - vsb.setBounds(new Rectangle(x3, y2, x4 - x3, y3 - y2)); + vsb.setBounds(new Rectangle(x3, y2, x4 - x3, y3 - y2 )); } else if (vsb != null) vsb.setVisible(false); @@ -423,7 +455,7 @@ public class ScrollPaneLayout if (showHsb) { hsb.setVisible(true); - hsb.setBounds(new Rectangle(x2, y3, x3 - x2, y4 - y3)); + hsb.setBounds(new Rectangle(x2 , y3, x3 - x2, y4 - y3)); } else if (hsb != null) hsb.setVisible(false); diff --git a/libjava/classpath/javax/swing/SizeSequence.java b/libjava/classpath/javax/swing/SizeSequence.java index a5f34710c76..cb6c8bc25df 100644 --- a/libjava/classpath/javax/swing/SizeSequence.java +++ b/libjava/classpath/javax/swing/SizeSequence.java @@ -129,14 +129,18 @@ public class SizeSequence } /** - * Returns the size of the specified element. + * Returns the size of the specified element, or 0 if the element index is + * outside the defined range. * * @param index the element index. * - * @return The size of the specified element. + * @return The size of the specified element, or 0 if the element index is + * outside the defined range. */ public int getSize(int index) { + if (index < 0 || index >= sizes.length) + return 0; return sizes[index]; } diff --git a/libjava/classpath/javax/swing/SortingFocusTraversalPolicy.java b/libjava/classpath/javax/swing/SortingFocusTraversalPolicy.java index 96ef3832955..d14ee1d30a4 100644 --- a/libjava/classpath/javax/swing/SortingFocusTraversalPolicy.java +++ b/libjava/classpath/javax/swing/SortingFocusTraversalPolicy.java @@ -91,7 +91,7 @@ public class SortingFocusTraversalPolicy * * @param comparator the comparator to set */ - public SortingFocusTraversalPolicy(Comparator comparator) + public SortingFocusTraversalPolicy(Comparator<? super Component> comparator) { this.comparator = comparator; } @@ -119,7 +119,7 @@ public class SortingFocusTraversalPolicy * * @see #setComparator */ - protected Comparator getComparator() + protected Comparator<? super Component> getComparator() { return comparator; } @@ -131,7 +131,7 @@ public class SortingFocusTraversalPolicy * * @see #getComparator */ - protected void setComparator(Comparator comparator) + protected void setComparator(Comparator<? super Component> comparator) { this.comparator = comparator; } diff --git a/libjava/classpath/javax/swing/SpinnerListModel.java b/libjava/classpath/javax/swing/SpinnerListModel.java index d8e2f22d585..52ac360e924 100644 --- a/libjava/classpath/javax/swing/SpinnerListModel.java +++ b/libjava/classpath/javax/swing/SpinnerListModel.java @@ -118,7 +118,7 @@ public class SpinnerListModel extends AbstractSpinnerModel * @see SpinnerListModel#getNextValue() * @see SpinnerListModel#getValue() */ - public SpinnerListModel(List list) + public SpinnerListModel(List<?> list) { // Retain a reference to the valid list. setList(list); @@ -163,7 +163,7 @@ public class SpinnerListModel extends AbstractSpinnerModel * * @return The backing list. */ - public List getList() + public List<?> getList() { return list; } @@ -239,7 +239,7 @@ public class SpinnerListModel extends AbstractSpinnerModel * * @see ChangeEvent */ - public void setList(List list) + public void setList(List<?> list) { // Check for null or zero size list. if (list == null || list.size() == 0) diff --git a/libjava/classpath/javax/swing/SwingUtilities.java b/libjava/classpath/javax/swing/SwingUtilities.java index ccd37d03a55..6ff0b3346df 100644 --- a/libjava/classpath/javax/swing/SwingUtilities.java +++ b/libjava/classpath/javax/swing/SwingUtilities.java @@ -40,7 +40,6 @@ package javax.swing; import java.applet.Applet; import java.awt.Component; -import java.awt.ComponentOrientation; import java.awt.Container; import java.awt.FontMetrics; import java.awt.Frame; @@ -61,6 +60,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 @@ -324,7 +325,7 @@ public class SwingUtilities * @see #getAncestorOfClass * @see #windowForComponent */ - public static Container getAncestorOfClass(Class c, Component comp) + public static Container getAncestorOfClass(Class<?> c, Component comp) { while (comp != null && (! c.isInstance(comp))) comp = comp.getParent(); @@ -719,44 +720,41 @@ public class SwingUtilities // Fix up the orientation-based horizontal positions. - if (horizontalTextPosition == LEADING) - { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalTextPosition = RIGHT; - else - horizontalTextPosition = LEFT; - } - else if (horizontalTextPosition == TRAILING) + boolean ltr = true; + if (c != null && ! c.getComponentOrientation().isLeftToRight()) + ltr = false; + + switch (horizontalTextPosition) { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalTextPosition = LEFT; - else - horizontalTextPosition = RIGHT; + case LEADING: + horizontalTextPosition = ltr ? LEFT : RIGHT; + break; + case TRAILING: + horizontalTextPosition = ltr ? RIGHT : LEFT; + break; + default: + // Nothing to do in the other cases. } // Fix up the orientation-based alignments. - - if (horizontalAlignment == LEADING) - { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalAlignment = RIGHT; - else - horizontalAlignment = LEFT; - } - else if (horizontalAlignment == TRAILING) + switch (horizontalAlignment) { - if (c.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) - horizontalAlignment = LEFT; - else - horizontalAlignment = RIGHT; + case LEADING: + horizontalAlignment = ltr ? LEFT : RIGHT; + break; + case TRAILING: + horizontalAlignment = ltr ? RIGHT : LEFT; + break; + default: + // Nothing to do in the other cases. } - - return 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 +827,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. @@ -843,94 +917,108 @@ public class SwingUtilities iconR.width = icon.getIconWidth(); iconR.height = icon.getIconHeight(); } + if (text == null || text.equals("")) { textIconGap = 0; textR.width = 0; textR.height = 0; + text = ""; } else { - int fromIndex = 0; - textR.width = fm.stringWidth(text); - textR.height = fm.getHeight(); - while (text.indexOf('\n', fromIndex) != -1) + int availableWidth = viewR.width; + if (horizontalTextPosition != CENTER) + availableWidth -= iconR.width + textIconGap; + View html = c == null ? null + : (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + textR.width = (int) html.getPreferredSpan(View.X_AXIS); + textR.width = Math.min(availableWidth, textR.width); + textR.height = (int) html.getPreferredSpan(View.Y_AXIS); + } + else { - textR.height += fm.getHeight(); - fromIndex = text.indexOf('\n', fromIndex) + 1; + int fromIndex = 0; + textR.width = fm.stringWidth(text); + textR.height = fm.getHeight(); + if (textR.width > availableWidth) + { + text = clipString(c, fm, text, availableWidth); + textR.width = fm.stringWidth(text); + } } } - // Work out the position of text and icon, assuming the top-left coord + // Work out the position of text, assuming the top-left coord // starts at (0,0). We will fix that up momentarily, after these // "position" decisions are made and we look at alignment. - switch (horizontalTextPosition) + switch (verticalTextPosition) { - case LEFT: - textR.x = 0; - iconR.x = textR.width + textIconGap; + case TOP: + textR.y = horizontalTextPosition == CENTER ? + - textR.height - textIconGap : 0; break; - case RIGHT: - iconR.x = 0; - textR.x = iconR.width + textIconGap; + case BOTTOM: + textR.y = horizontalTextPosition == CENTER ? + iconR.height + textIconGap : iconR.height - textR.height; break; case CENTER: - int centerLine = Math.max(textR.width, iconR.width) / 2; - textR.x = centerLine - textR.width/2; - iconR.x = centerLine - iconR.width/2; + textR.y = iconR.height / 2 - textR.height / 2; break; } - switch (verticalTextPosition) + switch (horizontalTextPosition) { - case TOP: - textR.y = 0; - iconR.y = (horizontalTextPosition == CENTER - ? textR.height + textIconGap : 0); + case LEFT: + textR.x = -(textR.width + textIconGap); break; - case BOTTOM: - iconR.y = 0; - textR.y = (horizontalTextPosition == CENTER - ? iconR.height + textIconGap - : Math.max(iconR.height - textR.height, 0)); + case RIGHT: + textR.x = iconR.width + textIconGap; break; case CENTER: - int centerLine = Math.max(textR.height, iconR.height) / 2; - textR.y = centerLine - textR.height/2; - iconR.y = centerLine - iconR.height/2; + textR.x = iconR.width / 2 - textR.width / 2; break; } + // The two rectangles are laid out correctly now, but only assuming // that their upper left corner is at (0,0). If we have any alignment other // than TOP and LEFT, we need to adjust them. - Rectangle u = textR.union(iconR); - int horizontalAdjustment = viewR.x; - int verticalAdjustment = viewR.y; + // These coordinates specify the rectangle that contains both the + // icon and text. Move it so that it fullfills the alignment properties. + int lx = Math.min(iconR.x, textR.x); + int lw = Math.max(iconR.x + iconR.width, textR.x + textR.width) - lx; + int ly = Math.min(iconR.y, textR.y); + int lh = Math.max(iconR.y + iconR.height, textR.y + textR.height) - ly; + int horizontalAdjustment = 0; + int verticalAdjustment = 0; switch (verticalAlignment) { case TOP: + verticalAdjustment = viewR.y - ly; break; case BOTTOM: - verticalAdjustment += (viewR.height - u.height); + verticalAdjustment = viewR.y + viewR.height - ly - lh; break; case CENTER: - verticalAdjustment += ((viewR.height/2) - (u.height/2)); + verticalAdjustment = viewR.y + viewR.height / 2 - ly - lh / 2; break; } switch (horizontalAlignment) { case LEFT: + horizontalAdjustment = viewR.x - lx; break; case RIGHT: - horizontalAdjustment += (viewR.width - u.width); + horizontalAdjustment = viewR.x + viewR.width - lx - lw; break; case CENTER: - horizontalAdjustment += ((viewR.width/2) - (u.width/2)); + horizontalAdjustment = (viewR.x + (viewR.width / 2)) - (lx + (lw / 2)); break; } - iconR.x += horizontalAdjustment; iconR.y += verticalAdjustment; @@ -940,6 +1028,48 @@ public class SwingUtilities return text; } + /** + * The method clips the specified string so that it fits into the + * available width. It is only called when the text really doesn't fit, + * so we don't need to check that again. + * + * @param c the component + * @param fm the font metrics + * @param text the text + * @param availableWidth the available width + * + * @return the clipped string + */ + private static String clipString(JComponent c, FontMetrics fm, String text, + int availableWidth) + { + String dots = "..."; + int dotsWidth = fm.stringWidth(dots); + char[] string = text.toCharArray(); + int endIndex = string.length; + while (fm.charsWidth(string, 0, endIndex) + dotsWidth > availableWidth + && endIndex > 0) + endIndex--; + String clipped; + if (string.length >= endIndex + 3) + { + string[endIndex] = '.'; + string[endIndex + 1] = '.'; + string[endIndex + 2] = '.'; + clipped = new String(string, 0, endIndex + 3); + } + else + { + char[] clippedChars = new char[string.length + 3]; + System.arraycopy(string, 0, clippedChars, 0, string.length); + clippedChars[endIndex] = '.'; + clippedChars[endIndex + 1] = '.'; + clippedChars[endIndex + 2] = '.'; + clipped = new String(clippedChars, 0, endIndex + 3); + } + return clipped; + } + /** * Calls {@link java.awt.EventQueue#invokeLater} with the * specified {@link Runnable}. diff --git a/libjava/classpath/javax/swing/Timer.java b/libjava/classpath/javax/swing/Timer.java index acd22624947..acd1eb49359 100644 --- a/libjava/classpath/javax/swing/Timer.java +++ b/libjava/classpath/javax/swing/Timer.java @@ -228,7 +228,7 @@ public class Timer * fired by this timer * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/ToolTipManager.java b/libjava/classpath/javax/swing/ToolTipManager.java index 963ccf88117..152fc03430f 100644 --- a/libjava/classpath/javax/swing/ToolTipManager.java +++ b/libjava/classpath/javax/swing/ToolTipManager.java @@ -163,16 +163,21 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener private static ToolTipManager shared; /** The current component the tooltip is being displayed for. */ - private static Component currentComponent; + private JComponent currentComponent; /** The current tooltip. */ - private static JToolTip currentTip; + private JToolTip currentTip; + + /** + * The tooltip text. + */ + private String toolTipText; /** The last known position of the mouse cursor. */ - private static Point currentPoint; - + private Point currentPoint; + /** */ - private static Popup popup; + private Popup popup; /** * Creates a new ToolTipManager and sets up the timers. @@ -364,8 +369,8 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener return; currentPoint = event.getPoint(); - currentComponent = (Component) event.getSource(); - + currentComponent = (JComponent) event.getSource(); + toolTipText = currentComponent.getToolTipText(event); if (exitTimer.isRunning()) { exitTimer.stop(); @@ -443,8 +448,52 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener public void mouseMoved(MouseEvent event) { currentPoint = event.getPoint(); - if (enterTimer.isRunning()) - enterTimer.restart(); + if (currentTip != null && currentTip.isShowing()) + checkTipUpdate(event); + else + { + if (enterTimer.isRunning()) + enterTimer.restart(); + } + } + + /** + * Checks if the tooltip's text or location changes when the mouse is moved + * over the component. + */ + private void checkTipUpdate(MouseEvent ev) + { + JComponent comp = (JComponent) ev.getSource(); + String newText = comp.getToolTipText(ev); + String oldText = toolTipText; + if (newText != null) + { + if (((newText != null && newText.equals(oldText)) || newText == null)) + { + // No change at all. Restart timers. + if (popup == null) + enterTimer.restart(); + else + insideTimer.restart(); + } + else + { + // Update the tooltip. + toolTipText = newText; + hideTip(); + showTip(); + exitTimer.stop(); + } + } + else + { + // Hide tooltip. + currentTip = null; + currentPoint = null; + hideTip(); + enterTimer.stop(); + exitTimer.stop(); + } } /** @@ -461,9 +510,9 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener return; } - if (currentTip == null || currentTip.getComponent() != currentComponent - && currentComponent instanceof JComponent) - currentTip = ((JComponent) currentComponent).createToolTip(); + if (currentTip == null || currentTip.getComponent() != currentComponent) + currentTip = currentComponent.createToolTip(); + currentTip.setTipText(toolTipText); Point p = currentPoint; Point cP = currentComponent.getLocationOnScreen(); @@ -531,8 +580,8 @@ public class ToolTipManager extends MouseAdapter implements MouseMotionListener private Component getContentPaneDeepestComponent(MouseEvent e) { Component source = (Component) e.getSource(); - Container parent = (Container) SwingUtilities.getAncestorOfClass(JRootPane.class, - currentComponent); + Container parent = SwingUtilities.getAncestorOfClass(JRootPane.class, + currentComponent); if (parent == null) return null; parent = ((JRootPane) parent).getContentPane(); diff --git a/libjava/classpath/javax/swing/TransferHandler.java b/libjava/classpath/javax/swing/TransferHandler.java index 40a36b27d24..d594a8244bb 100644 --- a/libjava/classpath/javax/swing/TransferHandler.java +++ b/libjava/classpath/javax/swing/TransferHandler.java @@ -44,12 +44,117 @@ import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.IOException; import java.io.Serializable; +import java.lang.reflect.Method; public class TransferHandler implements Serializable { + + /** + * An implementation of {@link Transferable} that can be used to export + * data from a component's property. + */ + private static class PropertyTransferable + implements Transferable + { + /** + * The component from which we export. + */ + private JComponent component; + + /** + * The property descriptor of the property that we handle. + */ + private PropertyDescriptor property; + + /** + * Creates a new PropertyTransferable. + * + * @param c the component from which we export + * @param prop the property from which we export + */ + PropertyTransferable(JComponent c, PropertyDescriptor prop) + { + component = c; + property = prop; + } + + /** + * Returns the data flavors supported by the Transferable. + * + * @return the data flavors supported by the Transferable + */ + public DataFlavor[] getTransferDataFlavors() + { + DataFlavor[] flavors; + Class propClass = property.getPropertyType(); + String mime = DataFlavor.javaJVMLocalObjectMimeType + "; class=" + + propClass.getName(); + try + { + DataFlavor flavor = new DataFlavor(mime); + flavors = new DataFlavor[]{ flavor }; + } + catch (ClassNotFoundException ex) + { + flavors = new DataFlavor[0]; + } + return flavors; + } + + /** + * Returns <code>true</code> when the specified data flavor is supported, + * <code>false</code> otherwise. + * + * @return <code>true</code> when the specified data flavor is supported, + * <code>false</code> otherwise + */ + public boolean isDataFlavorSupported(DataFlavor flavor) + { + Class propClass = property.getPropertyType(); + return flavor.getPrimaryType().equals("application") + && flavor.getSubType().equals("x-java-jvm-local-objectref") + && propClass.isAssignableFrom(flavor.getRepresentationClass()); + } + + /** + * Returns the actual transfer data. + * + * @param flavor the data flavor + * + * @return the actual transfer data + */ + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException + { + if (isDataFlavorSupported(flavor)) + { + Method getter = property.getReadMethod(); + Object o; + try + { + o = getter.invoke(component, null); + return o; + } + catch (Exception ex) + { + throw new IOException("Property read failed: " + + property.getName()); + } + } + else + throw new UnsupportedFlavorException(flavor); + } + } + static class TransferAction extends AbstractAction { private String command; @@ -123,7 +228,13 @@ public class TransferHandler implements Serializable private int sourceActions; private Icon visualRepresentation; - + + /** + * The name of the property into/from which this TransferHandler + * imports/exports. + */ + private String propertyName; + public static Action getCopyAction() { return copyAction; @@ -146,19 +257,78 @@ public class TransferHandler implements Serializable public TransferHandler(String property) { + propertyName = property; this.sourceActions = property != null ? COPY : NONE; } + /** + * Returns <code>true</code> if the data in this TransferHandler can be + * imported into the specified component. This will be the case when: + * <ul> + * <li>The component has a readable and writable property with the property + * name specified in the TransferHandler constructor.</li> + * <li>There is a dataflavor with a mime type of + * <code>application/x-java-jvm-local-object-ref</code>.</li> + * <li>The dataflavor's representation class matches the class of the + * property in the component.</li> + * </li> + * + * @param c the component to check + * @param flavors the possible data flavors + * + * @return <code>true</code> if the data in this TransferHandler can be + * imported into the specified component, <code>false</code> + * otherwise + */ public boolean canImport(JComponent c, DataFlavor[] flavors) - throws NotImplementedException { - return false; + PropertyDescriptor propDesc = getPropertyDescriptor(c); + boolean canImport = false; + if (propDesc != null) + { + // Check if the property is writable. The readable check is already + // done in getPropertyDescriptor(). + Method writer = propDesc.getWriteMethod(); + if (writer != null) + { + Class[] params = writer.getParameterTypes(); + if (params.length == 1) + { + // Number of parameters ok, now check mime type and + // representation class. + DataFlavor flavor = getPropertyDataFlavor(params[0], flavors); + if (flavor != null) + canImport = true; + } + } + } + return canImport; } + /** + * Creates a {@link Transferable} that can be used to export data + * from the specified component. + * + * This method returns <code>null</code> when the specified component + * doesn't have a readable property that matches the property name + * specified in the <code>TransferHandler</code> constructor. + * + * @param c the component to create a transferable for + * + * @return a {@link Transferable} that can be used to export data + * from the specified component, or null if the component doesn't + * have a readable property like the transfer handler + */ protected Transferable createTransferable(JComponent c) - throws NotImplementedException { - return null; + Transferable transferable = null; + if (propertyName != null) + { + PropertyDescriptor prop = getPropertyDescriptor(c); + if (prop != null) + transferable = new PropertyTransferable(c, prop); + } + return transferable; } public void exportAsDrag(JComponent c, InputEvent e, int action) @@ -167,16 +337,64 @@ public class TransferHandler implements Serializable // TODO: Implement this properly } - protected void exportDone(JComponent c, Transferable data, int action) - throws NotImplementedException + /** + * This method is invoked after data has been exported. + * Subclasses should implement this method to remove the data that has been + * transferred when the action was <code>MOVE</code>. + * + * The default implementation does nothing because MOVE is not supported. + * + * @param c the source component + * @param data the data that has been transferred or <code>null</code> + * when the action is NONE + * @param action the action that has been performed + */ + protected void exportDone(JComponent c, Transferable data, int action) { - // TODO: Implement this properly + // Nothing to do in the default implementation. } + /** + * Exports the property of the component <code>c</code> that was + * specified for this TransferHandler to the clipboard, performing + * the specified action. + * + * This will check if the action is allowed by calling + * {@link #getSourceActions(JComponent)}. If the action is not allowed, + * then no export is performed. + * + * In either case the method {@link #exportDone} will be called with + * the action that has been performed, or {@link #NONE} if the action + * was not allowed or could otherwise not be completed. + * Any IllegalStateException that is thrown by the Clipboard due to + * beeing unavailable will be propagated through this method. + * + * @param c the component from which to export + * @param clip the clipboard to which the data will be exported + * @param action the action to perform + * + * @throws IllegalStateException when the clipboard is not available + */ public void exportToClipboard(JComponent c, Clipboard clip, int action) - throws NotImplementedException + throws IllegalStateException { - // TODO: Implement this properly + action &= getSourceActions(c); + Transferable transferable = createTransferable(c); + if (transferable != null && action != NONE) + { + try + { + clip.setContents(transferable, null); + exportDone(c, transferable, action); + } + catch (IllegalStateException ex) + { + exportDone(c, transferable, NONE); + throw ex; + } + } + else + exportDone(c, null, NONE); } public int getSourceActions(JComponent c) @@ -189,9 +407,124 @@ public class TransferHandler implements Serializable return visualRepresentation; } + /** + * Imports the transfer data represented by <code>t</code> into the specified + * component <code>c</code> by setting the property of this TransferHandler + * on that component. If this succeeds, this method returns + * <code>true</code>, otherwise <code>false</code>. + * + * + * @param c the component to import into + * @param t the transfer data to import + * + * @return <code>true</code> if the transfer succeeds, <code>false</code> + * otherwise + */ public boolean importData(JComponent c, Transferable t) - throws NotImplementedException { - return false; + boolean ok = false; + PropertyDescriptor prop = getPropertyDescriptor(c); + if (prop != null) + { + Method writer = prop.getWriteMethod(); + if (writer != null) + { + Class[] params = writer.getParameterTypes(); + if (params.length == 1) + { + DataFlavor flavor = getPropertyDataFlavor(params[0], + t.getTransferDataFlavors()); + if (flavor != null) + { + try + { + Object value = t.getTransferData(flavor); + writer.invoke(c, new Object[]{ value }); + ok = true; + } + catch (Exception ex) + { + // If anything goes wrong here, do nothing and return + // false; + } + } + } + } + } + return ok; + } + + /** + * Returns the property descriptor for the property of this TransferHandler + * in the specified component, or <code>null</code> if no such property + * exists in the component. This method only returns properties that are + * at least readable (that is, it has a public no-arg getter method). + * + * @param c the component to check + * + * @return the property descriptor for the property of this TransferHandler + * in the specified component, or <code>null</code> if no such + * property exists in the component + */ + private PropertyDescriptor getPropertyDescriptor(JComponent c) + { + PropertyDescriptor prop = null; + if (propertyName != null) + { + Class clazz = c.getClass(); + BeanInfo beanInfo; + try + { + beanInfo = Introspector.getBeanInfo(clazz); + } + catch (IntrospectionException ex) + { + beanInfo = null; + } + if (beanInfo != null) + { + PropertyDescriptor[] props = beanInfo.getPropertyDescriptors(); + for (int i = 0; i < props.length && prop == null; i++) + { + PropertyDescriptor desc = props[i]; + if (desc.getName().equals(propertyName)) + { + Method reader = desc.getReadMethod(); + if (reader != null) + { + Class[] params = reader.getParameterTypes(); + if (params == null || params.length == 0) + prop = desc; + } + } + } + } + } + return prop; + } + + /** + * Searches <code>flavors</code> to find a suitable data flavor that + * has the mime type application/x-java-jvm-local-objectref and a + * representation class that is the same as the specified <code>clazz</code>. + * When no such data flavor is found, this returns <code>null</code>. + * + * @param clazz the representation class required for the data flavor + * @param flavors the possible data flavors + * + * @return the suitable data flavor or null if none is found + */ + private DataFlavor getPropertyDataFlavor(Class clazz, DataFlavor[] flavors) + { + DataFlavor found = null; + for (int i = 0; i < flavors.length && found == null; i++) + { + DataFlavor flavor = flavors[i]; + if (flavor.getPrimaryType().equals("application") + && flavor.getSubType().equals("x-java-jvm-local-objectref") + && clazz.isAssignableFrom(flavor.getRepresentationClass())) + found = flavor; + } + return found; } } diff --git a/libjava/classpath/javax/swing/UIDefaults.java b/libjava/classpath/javax/swing/UIDefaults.java index bf5242f6552..9766cb05cec 100644 --- a/libjava/classpath/javax/swing/UIDefaults.java +++ b/libjava/classpath/javax/swing/UIDefaults.java @@ -63,7 +63,7 @@ import javax.swing.plaf.InputMapUIResource; * * @author Ronald Veldema (rveldema@cs.vu.nl) */ -public class UIDefaults extends Hashtable +public class UIDefaults extends Hashtable<Object, Object> { /** Our ResourceBundles. */ @@ -672,7 +672,7 @@ public class UIDefaults extends Hashtable * * @return the UI class for <code>id</code> */ - public Class getUIClass(String id, ClassLoader loader) + public Class<? extends ComponentUI> getUIClass(String id, ClassLoader loader) { String className = (String) get(id); if (className == null) @@ -681,7 +681,7 @@ public class UIDefaults extends Hashtable { if (loader == null) loader = ClassLoader.getSystemClassLoader(); - return loader.loadClass (className); + return (Class<? extends ComponentUI>) loader.loadClass (className); } catch (Exception e) { @@ -698,7 +698,7 @@ public class UIDefaults extends Hashtable * * @return the UI class for <code>id</code> */ - public Class getUIClass(String id) + public Class<? extends ComponentUI> getUIClass(String id) { return getUIClass (id, null); } diff --git a/libjava/classpath/javax/swing/UIManager.java b/libjava/classpath/javax/swing/UIManager.java index 77be44afcbb..3b1b3f72b38 100644 --- a/libjava/classpath/javax/swing/UIManager.java +++ b/libjava/classpath/javax/swing/UIManager.java @@ -154,8 +154,16 @@ public class UIManager implements Serializable UIDefaults fallback; + /** + * Creates a new <code>MultiplexUIDefaults</code> instance with + * <code>d</code> as the fallback defaults. + * + * @param d the fallback defaults (<code>null</code> not permitted). + */ MultiplexUIDefaults(UIDefaults d) { + if (d == null) + throw new NullPointerException(); fallback = d; } @@ -400,6 +408,8 @@ public class UIManager implements Serializable * @param key the key. * * @return The object. + * + * @since 1.4 */ public static Object get(Object key, Locale locale) { @@ -407,61 +417,120 @@ public class UIManager implements Serializable } /** - * Returns a boolean value from the defaults table, - * <code>false</code> if key is not present. + * Returns a boolean value from the defaults table. If there is no value + * for the specified key, or the value is not an instance of {@link Boolean}, + * this method returns <code>false</code>. + * + * @param key the key (<code>null</code> not permitted). * + * @return The boolean value associated with the specified key. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. + * * @since 1.4 */ public static boolean getBoolean(Object key) { - Boolean value = (Boolean) get(key); - return value != null ? value.booleanValue() : false; + Object value = get(key); + if (value instanceof Boolean) + return ((Boolean) value).booleanValue(); + return false; } /** - * Returns a boolean value from the defaults table, - * <code>false</code> if key is not present. + * Returns a boolean value from the defaults table. If there is no value + * for the specified key, or the value is not an instance of {@link Boolean}, + * this method returns <code>false</code>. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. * + * @return The boolean value associated with the specified key. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. + * * @since 1.4 */ public static boolean getBoolean(Object key, Locale locale) { - Boolean value = (Boolean) get(key, locale); - return value != null ? value.booleanValue() : false; + Object value = get(key, locale); + if (value instanceof Boolean) + return ((Boolean) value).booleanValue(); + return false; } /** * Returns a border from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * + * @return The border associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. */ public static Border getBorder(Object key) { - return (Border) get(key); + Object value = get(key); + if (value instanceof Border) + return (Border) value; + return null; } /** - * Returns a border from the defaults table. + * Returns a border from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. + * + * @return The border associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. * * @since 1.4 */ public static Border getBorder(Object key, Locale locale) { - return (Border) get(key, locale); + Object value = get(key, locale); + if (value instanceof Border) + return (Border) value; + return null; } /** * Returns a drawing color from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * + * @return The color associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. */ public static Color getColor(Object key) { - return (Color) get(key); + Object value = get(key); + if (value instanceof Color) + return (Color) value; + return null; } /** * Returns a drawing color from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. + * + * @return The color associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. + * + * @since 1.4 */ public static Color getColor(Object key, Locale locale) { - return (Color) get(key); + Object value = get(key, locale); + if (value instanceof Color) + return (Color) value; + return null; } /** @@ -483,24 +552,44 @@ public class UIManager implements Serializable public static UIDefaults getDefaults() { if (currentUIDefaults == null) - currentUIDefaults = new MultiplexUIDefaults(null); + currentUIDefaults = new MultiplexUIDefaults(new UIDefaults()); return currentUIDefaults; } /** * Returns a dimension from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * + * @return The color associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. */ public static Dimension getDimension(Object key) { - return (Dimension) get(key); + Object value = get(key); + if (value instanceof Dimension) + return (Dimension) value; + return null; } /** * Returns a dimension from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. + * + * @return The color associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. + * @since 1.4 */ public static Dimension getDimension(Object key, Locale locale) { - return (Dimension) get(key, locale); + Object value = get(key, locale); + if (value instanceof Dimension) + return (Dimension) value; + return null; } /** @@ -510,10 +599,17 @@ public class UIManager implements Serializable * @param key an Object that specifies the font. Typically, * this is a String such as * <code>TitledBorder.font</code>. + * + * @return The font associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. */ public static Font getFont(Object key) { - return (Font) get(key); + Object value = get(key); + if (value instanceof Font) + return (Font) value; + return null; } /** @@ -523,30 +619,66 @@ public class UIManager implements Serializable * @param key an Object that specifies the font. Typically, * this is a String such as * <code>TitledBorder.font</code>. + * @param locale the locale. + * + * @return The font associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. + * + * @since 1.4 */ public static Font getFont(Object key, Locale locale) { - return (Font) get(key, locale); + Object value = get(key, locale); + if (value instanceof Font) + return (Font) value; + return null; } /** - * Returns an Icon from the defaults table. + * Returns an icon from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * + * @return The icon associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. */ public static Icon getIcon(Object key) { - return (Icon) get(key); + Object value = get(key); + if (value instanceof Icon) + return (Icon) value; + return null; } /** - * Returns an Icon from the defaults table. + * Returns an icon from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. + * + * @return The icon associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. + * @since 1.4 */ public static Icon getIcon(Object key, Locale locale) { - return (Icon) get(key, locale); + Object value = get(key, locale); + if (value instanceof Icon) + return (Icon) value; + return null; } /** * Returns an Insets object from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * + * @return The insets associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. */ public static Insets getInsets(Object key) { @@ -559,6 +691,14 @@ public class UIManager implements Serializable /** * Returns an Insets object from the defaults table. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. + * + * @return The insets associated with the given key, or <code>null</code>. + * + * @throws NullPointerException if <code>key</code> is <code>null</code>. + * @since 1.4 */ public static Insets getInsets(Object key, Locale locale) { @@ -580,20 +720,41 @@ public class UIManager implements Serializable return installed; } + /** + * Returns the integer value of the {@link Integer} associated with the + * given key. If there is no value, or the value is not an instance of + * {@link Integer}, this method returns 0. + * + * @param key the key (<code>null</code> not permitted). + * + * @return The integer value associated with the given key, or 0. + */ public static int getInt(Object key) { - Integer x = (Integer) get(key); - if (x == null) - return 0; - return x.intValue(); + Object x = get(key); + if (x instanceof Integer) + return ((Integer) x).intValue(); + return 0; } + /** + * Returns the integer value of the {@link Integer} associated with the + * given key. If there is no value, or the value is not an instance of + * {@link Integer}, this method returns 0. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. + * + * @return The integer value associated with the given key, or 0. + * + * @since 1.4 + */ public static int getInt(Object key, Locale locale) { - Integer x = (Integer) get(key, locale); - if (x == null) - return 0; - return x.intValue(); + Object x = get(key, locale); + if (x instanceof Integer) + return ((Integer) x).intValue(); + return 0; } /** @@ -620,19 +781,38 @@ public class UIManager implements Serializable } /** - * Returns a string from the defaults table. + * Returns the {@link String} associated with the given key. If the value + * is not a {@link String}, this method returns <code>null</code>. + * + * @param key the key (<code>null</code> not permitted). + * + * @return The string associated with the given key, or <code>null</code>. */ public static String getString(Object key) { - return (String) get(key); + Object s = get(key); + if (s instanceof String) + return (String) s; + return null; } /** - * Returns a string from the defaults table. + * Returns the {@link String} associated with the given key. If the value + * is not a {@link String}, this method returns <code>null</code>. + * + * @param key the key (<code>null</code> not permitted). + * @param locale the locale. + * + * @return The string associated with the given key, or <code>null</code>. + * + * @since 1.4 */ public static String getString(Object key, Locale locale) { - return (String) get(key, locale); + Object s = get(key, locale); + if (s instanceof String) + return (String) s; + return null; } /** @@ -686,6 +866,9 @@ public class UIManager implements Serializable /** * Stores an object in the defaults table. + * + * @param key the key. + * @param value the value. */ public static Object put(Object key, Object value) { diff --git a/libjava/classpath/javax/swing/border/CompoundBorder.java b/libjava/classpath/javax/swing/border/CompoundBorder.java index 2ee639cf9a3..ba2e745aab5 100644 --- a/libjava/classpath/javax/swing/border/CompoundBorder.java +++ b/libjava/classpath/javax/swing/border/CompoundBorder.java @@ -115,15 +115,24 @@ public class CompoundBorder extends AbstractBorder */ public boolean isBorderOpaque() { - // While it would be safe to assume true for the opacity of - // a null border, this behavior would not be according to - // the API specification. Also, it is pathological to have - // null borders anyway. - if ((insideBorder == null) || (outsideBorder == null)) - return false; - - return insideBorder.isBorderOpaque() - && outsideBorder.isBorderOpaque(); + // Although the API specification states that this method + // returns true if both the inside and outside borders are non-null + // and opaque, and false otherwise, a mauve test shows that if both + // the inside or outside borders are null, then true is returned. + if ((insideBorder == null) && (outsideBorder == null)) + return true; + + // A mauve test shows that if the inside border has a null value, + // then true is returned if the outside border is opaque; if the + // outside border has a null value, then true is returned if the + // inside border is opaque; else, true is returned if both the + // inside and outside borders are opaque. + if (insideBorder == null) + return outsideBorder.isBorderOpaque(); + else if (outsideBorder == null) + return insideBorder.isBorderOpaque(); + else + return insideBorder.isBorderOpaque() && outsideBorder.isBorderOpaque(); } /** diff --git a/libjava/classpath/javax/swing/event/EventListenerList.java b/libjava/classpath/javax/swing/event/EventListenerList.java index bde8b3c7e4f..1568039f0ff 100644 --- a/libjava/classpath/javax/swing/event/EventListenerList.java +++ b/libjava/classpath/javax/swing/event/EventListenerList.java @@ -37,6 +37,9 @@ exception statement from your version. */ package javax.swing.event; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.util.EventListener; @@ -136,7 +139,7 @@ public class EventListenerList * * @throws NullPointerException if <code>t</code> is <code>null</code>. */ - public void add(Class t, EventListener listener) + public <T extends EventListener> void add(Class<T> t, T listener) { int oldLength; Object[] newList; @@ -175,7 +178,7 @@ public class EventListenerList * <code>t</code>. Thus, subclasses of <code>t</code> will not be * counted. */ - public int getListenerCount(Class t) + public int getListenerCount(Class<?> t) { int result = 0; for (int i = 0; i < listenerList.length; i += 2) @@ -224,7 +227,7 @@ public class EventListenerList * * @since 1.3 */ - public EventListener[] getListeners(Class c) + public <T extends EventListener> T[] getListeners(Class<T> c) { int count, f; EventListener[] result; @@ -236,7 +239,7 @@ public class EventListenerList if (listenerList[i] == c) result[f++] = (EventListener) listenerList[i + 1]; - return result; + return (T[]) result; } @@ -253,7 +256,7 @@ public class EventListenerList * * @throws NullPointerException if <code>t</code> is <code>null</code>. */ - public void remove(Class t, EventListener listener) + public <T extends EventListener> void remove(Class<T> t, T listener) { Object[] oldList, newList; int oldLength; @@ -304,4 +307,51 @@ public class EventListenerList } return buf.toString(); } + + /** + * Serializes an instance to an ObjectOutputStream. + * + * @param out the stream to serialize to + * + * @throws IOException if something goes wrong + */ + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + for (int i = 0; i < listenerList.length; i += 2) + { + Class cl = (Class) listenerList[i]; + EventListener l = (EventListener) listenerList[i + 1]; + if (l != null && l instanceof Serializable) + { + out.writeObject(cl.getName()); + out.writeObject(l); + } + } + // Write end marker. + out.writeObject(null); + } + + /** + * Deserializes an instance from an ObjectInputStream. + * + * @param in the input stream + * + * @throws ClassNotFoundException if a serialized class can't be found + * @throws IOException if something goes wrong + */ + private <T extends EventListener> void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException + { + listenerList = NO_LISTENERS; + in.defaultReadObject(); + Object type; + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + while ((type = in.readObject()) != null) + { + EventListener l = (EventListener) in.readObject(); + add(((Class<T>) Class.forName((String) type, true, cl)), (T) l); + } + } } diff --git a/libjava/classpath/javax/swing/filechooser/FileSystemView.java b/libjava/classpath/javax/swing/filechooser/FileSystemView.java index 84b80dd402c..41d865a964b 100644 --- a/libjava/classpath/javax/swing/filechooser/FileSystemView.java +++ b/libjava/classpath/javax/swing/filechooser/FileSystemView.java @@ -172,13 +172,8 @@ public abstract class FileSystemView { if (defaultFileSystemView == null) { - if (File.separator.equals("/")) - defaultFileSystemView = new UnixFileSystemView(); - // FIXME: need to implement additional views - // else if (File.Separator.equals("\")) - // return new Win32FileSystemView(); - // else - // return new GenericFileSystemView(); + // FIXME: We need to support other file systems too. + defaultFileSystemView = new UnixFileSystemView(); } return defaultFileSystemView; } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicButtonListener.java b/libjava/classpath/javax/swing/plaf/basic/BasicButtonListener.java index 84895821518..c99de2c708c 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicButtonListener.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicButtonListener.java @@ -54,15 +54,79 @@ import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.AbstractButton; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.ButtonModel; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ButtonUI; -public class BasicButtonListener implements MouseListener, MouseMotionListener, - FocusListener, ChangeListener, PropertyChangeListener +public class BasicButtonListener + implements MouseListener, MouseMotionListener, FocusListener, ChangeListener, + PropertyChangeListener { + /** + * Implements the keyboard action for Swing buttons. + */ + private class ButtonAction + extends AbstractAction + { + /** + * The key for pressed action. + */ + static final String PRESSED = "pressed"; + + /** + * The key for released action. + */ + static final String RELEASED = "released"; + + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + Object cmd = getValue("__command__"); + AbstractButton b = (AbstractButton) event.getSource(); + ButtonModel m = b.getModel(); + if (PRESSED.equals(cmd)) + { + m.setArmed(true); + m.setPressed(true); + if (! b.isFocusOwner()) + b.requestFocus(); + } + else if (RELEASED.equals(cmd)) + { + m.setPressed(false); + m.setArmed(false); + } + } + + /** + * Indicates if this action is enabled. + * + * @param source the source of the action + * + * @return <code>true</code> when enabled, <code>false</code> otherwise + */ + public boolean isEnabled(Object source) + { + boolean enabled = true; + if (source instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) source; + enabled = b.isEnabled(); + } + return enabled; + } + } + public BasicButtonListener(AbstractButton b) { // Do nothing here. @@ -73,12 +137,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(); + 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 = ""; @@ -86,12 +150,25 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, false, false); TextLayout layout = new TextLayout(text, b.getFont(), frc); b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout); + + // Update HTML renderer. + BasicHTML.updateRenderer(b, b.getText()); + } + else if (property.equals(AbstractButton.CONTENT_AREA_FILLED_CHANGED_PROPERTY)) + { + checkOpacity(b); } } - + + /** + * Checks the <code>contentAreaFilled</code> property and updates the + * opaque property of the button. + * + * @param b the button to check + */ protected void checkOpacity(AbstractButton b) { - // TODO: What should be done here? + b.setOpaque(b.isContentAreaFilled()); } public void focusGained(FocusEvent e) @@ -116,6 +193,26 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, public void installKeyboardActions(JComponent c) { + ButtonUI ui = ((AbstractButton) c).getUI(); + if (ui instanceof BasicButtonUI) + { + // Install InputMap. + BasicButtonUI basicUI = (BasicButtonUI) ui; + String prefix = basicUI.getPropertyPrefix(); + InputMap focusInputMap = + (InputMap) UIManager.get(prefix + "focusInputMap"); + SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, + focusInputMap); + + ActionMap am = (ActionMap) UIManager.get(prefix + "actionMap"); + if (am == null) + { + am = createDefaultActionMap(); + UIManager.put(prefix + "actionMap", am); + } + SwingUtilities.replaceUIActionMap(c, am); + } + c.getActionMap().put("pressed", new AbstractAction() { @@ -142,31 +239,46 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, } }); } - + + /** + * Creates and returns the default action map for Swing buttons. + * + * @return the default action map for Swing buttons + */ + private ActionMap createDefaultActionMap() + { + Action action = new ButtonAction(); + ActionMapUIResource am = new ActionMapUIResource(); + am.put(ButtonAction.PRESSED, action); + am.put(ButtonAction.RELEASED, action); + return am; + } + public void uninstallKeyboardActions(JComponent c) { - c.getActionMap().put("pressed", null); - c.getActionMap().put("released", null); + SwingUtilities.replaceUIActionMap(c, null); + SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null); } public void stateChanged(ChangeEvent e) { - // TODO: What should be done here, if anything? + // Need to repaint when the button state changes. + ((AbstractButton) e.getSource()).repaint(); } public void mouseMoved(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } public void mouseDragged(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } public void mouseClicked(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicButtonUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicButtonUI.java index d531133ba26..9f685bb7bfd 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicButtonUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicButtonUI.java @@ -42,12 +42,14 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Insets; import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.AbstractButton; import javax.swing.ButtonModel; import javax.swing.Icon; -import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.LookAndFeel; @@ -56,6 +58,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. @@ -63,6 +66,39 @@ import javax.swing.plaf.UIResource; public class BasicButtonUI extends ButtonUI { /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle viewR = new Rectangle(); + + /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle iconR = new Rectangle(); + + /** + * Cached rectangle for layouting the label. Used in paint() and + * BasicGraphicsUtils.getPreferredButtonSize(). + */ + static Rectangle textR = new Rectangle(); + + /** + * Cached Insets instance, used in paint(). + */ + static Insets cachedInsets; + + /** + * The shared button UI. + */ + private static BasicButtonUI sharedUI; + + /** + * The shared BasicButtonListener. + */ + private static BasicButtonListener sharedListener; + + /** * A constant used to pad out elements in the button's layout and * preferred size calculations. */ @@ -86,7 +122,9 @@ public class BasicButtonUI extends ButtonUI */ public static ComponentUI createUI(final JComponent c) { - return new BasicButtonUI(); + if (sharedUI == null) + sharedUI = new BasicButtonUI(); + return sharedUI; } /** @@ -153,14 +191,29 @@ public class BasicButtonUI extends ButtonUI protected void installDefaults(AbstractButton b) { String prefix = getPropertyPrefix(); + // Install colors and font. LookAndFeel.installColorsAndFont(b, prefix + "background", prefix + "foreground", prefix + "font"); + // Install border. LookAndFeel.installBorder(b, prefix + "border"); + + // Install margin property. if (b.getMargin() == null || b.getMargin() instanceof UIResource) b.setMargin(UIManager.getInsets(prefix + "margin")); - b.setIconTextGap(UIManager.getInt(prefix + "textIconGap")); - b.setInputMap(JComponent.WHEN_FOCUSED, - (InputMap) UIManager.get(prefix + "focusInputMap")); + + // Install rollover property. + Object rollover = UIManager.get(prefix + "rollover"); + if (rollover != null) + LookAndFeel.installProperty(b, "rolloverEnabled", rollover); + + // Fetch default textShiftOffset. + defaultTextShiftOffset = UIManager.getInt(prefix + "textShiftOffset"); + + // Make button opaque if needed. + if (b.isContentAreaFilled()) + LookAndFeel.installProperty(b, "opaque", Boolean.TRUE); + else + LookAndFeel.installProperty(b, "opaque", Boolean.FALSE); } /** @@ -170,21 +223,10 @@ public class BasicButtonUI extends ButtonUI */ protected void uninstallDefaults(AbstractButton b) { - if (b.getFont() instanceof UIResource) - b.setFont(null); - if (b.getForeground() instanceof UIResource) - b.setForeground(null); - if (b.getBackground() instanceof UIResource) - b.setBackground(null); - if (b.getBorder() instanceof UIResource) - b.setBorder(null); - b.setIconTextGap(defaultTextIconGap); - if (b.getMargin() instanceof UIResource) - b.setMargin(null); + // The other properties aren't uninstallable. + LookAndFeel.uninstallBorder(b); } - protected BasicButtonListener listener; - /** * Creates and returns a new instance of {@link BasicButtonListener}. This * method provides a hook to make it easy for subclasses to install a @@ -196,7 +238,13 @@ public class BasicButtonUI extends ButtonUI */ protected BasicButtonListener createButtonListener(AbstractButton b) { - return new BasicButtonListener(b); + // Note: The RI always returns a new instance here. However, + // the BasicButtonListener class is perfectly suitable to be shared + // between multiple buttons, so we return a shared instance here + // for efficiency. + if (sharedListener == null) + sharedListener = new BasicButtonListener(b); + return sharedListener; } /** @@ -206,12 +254,19 @@ public class BasicButtonUI extends ButtonUI */ protected void installListeners(AbstractButton b) { - listener = createButtonListener(b); - b.addChangeListener(listener); - b.addPropertyChangeListener(listener); - b.addFocusListener(listener); - b.addMouseListener(listener); - b.addMouseMotionListener(listener); + BasicButtonListener listener = createButtonListener(b); + if (listener != null) + { + b.addChangeListener(listener); + b.addPropertyChangeListener(listener); + b.addFocusListener(listener); + b.addMouseListener(listener); + b.addMouseMotionListener(listener); + } + // Fire synthetic property change event to let the listener update + // the TextLayout cache. + listener.propertyChange(new PropertyChangeEvent(b, "font", null, + b.getFont())); } /** @@ -221,21 +276,29 @@ public class BasicButtonUI extends ButtonUI */ protected void uninstallListeners(AbstractButton b) { - b.removeChangeListener(listener); - b.removePropertyChangeListener(listener); - b.removeFocusListener(listener); - b.removeMouseListener(listener); - b.removeMouseMotionListener(listener); + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + { + b.removeChangeListener(listener); + b.removePropertyChangeListener(listener); + b.removeFocusListener(listener); + b.removeMouseListener(listener); + b.removeMouseMotionListener(listener); + } } protected void installKeyboardActions(AbstractButton b) { - listener.installKeyboardActions(b); + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + listener.installKeyboardActions(b); } protected void uninstallKeyboardActions(AbstractButton b) { - listener.uninstallKeyboardActions(b); + BasicButtonListener listener = getButtonListener(b); + if (listener != null) + listener.uninstallKeyboardActions(b); } /** @@ -253,9 +316,75 @@ public class BasicButtonUI extends ButtonUI { AbstractButton b = (AbstractButton) c; installDefaults(b); + // It is important to install the listeners before installing + // the keyboard actions, because the keyboard actions + // are actually installed on the listener instance. installListeners(b); installKeyboardActions(b); + BasicHTML.updateRenderer(b, b.getText()); + } + } + + /** + * 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, ""); + b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, null); + } + } + + /** + * 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; } /** @@ -269,8 +398,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; } @@ -315,38 +444,50 @@ public class BasicButtonUI extends ButtonUI { AbstractButton b = (AbstractButton) c; - Rectangle tr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle vr = new Rectangle(); + Insets i = c.getInsets(cachedInsets); + viewR.x = i.left; + viewR.y = i.top; + viewR.width = c.getWidth() - i.left - i.right; + viewR.height = c.getHeight() - i.top - i.bottom; + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; Font f = c.getFont(); - g.setFont(f); + Icon icon = b.getIcon(); + String text = b.getText(); + text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), + text, icon, + b.getVerticalAlignment(), + b.getHorizontalAlignment(), + b.getVerticalTextPosition(), + b.getHorizontalTextPosition(), + viewR, iconR, textR, + text == null ? 0 + : b.getIconTextGap()); - if (b.isBorderPainted()) - SwingUtilities.calculateInnerArea(b, vr); - else - vr = SwingUtilities.getLocalBounds(b); - String text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), - b.getText(), - currentIcon(b), - b.getVerticalAlignment(), - b.getHorizontalAlignment(), - b.getVerticalTextPosition(), - b.getHorizontalTextPosition(), - vr, ir, tr, - b.getIconTextGap() - + defaultTextShiftOffset); - - if ((b.getModel().isArmed() && b.getModel().isPressed()) - || b.isSelected()) + ButtonModel model = b.getModel(); + if (model.isArmed() && model.isPressed()) paintButtonPressed(g, b); - - paintIcon(g, c, ir); + + if (icon != null) + paintIcon(g, c, iconR); if (text != null) - paintText(g, b, tr, text); + { + View html = (View) b.getClientProperty(BasicHTML.propertyKey); + if (html != null) + html.paint(g, textR); + else + paintText(g, b, textR, text); + } if (b.isFocusOwner() && b.isFocusPainted()) - paintFocus(g, b, vr, tr, ir); + paintFocus(g, b, viewR, textR, iconR); } /** @@ -386,7 +527,16 @@ public class BasicButtonUI extends ButtonUI Icon i = currentIcon(b); if (i != null) - i.paintIcon(c, g, iconRect.x, iconRect.y); + { + ButtonModel m = b.getModel(); + if (m.isPressed() && m.isArmed()) + { + int offs = getTextShiftOffset(); + i.paintIcon(c, g, iconRect.x + offs, iconRect.y + offs); + } + else + i.paintIcon(c, g, iconRect.x, iconRect.y); + } } /** @@ -419,22 +569,7 @@ public class BasicButtonUI extends ButtonUI protected void paintText(Graphics g, JComponent c, Rectangle textRect, String text) { - paintText(g, (AbstractButton) c, textRect, text); - } - - /** - * Paints the "text" property of an {@link AbstractButton}. - * - * @param g The graphics context to paint with - * @param b The button to paint the state of - * @param textRect The area in which to paint the text - * @param text The text to paint - * - * @since 1.4 - */ - protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, - String text) - { + AbstractButton b = (AbstractButton) c; Font f = b.getFont(); g.setFont(f); FontMetrics fm = g.getFontMetrics(f); @@ -454,5 +589,48 @@ public class BasicButtonUI extends ButtonUI BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x, textRect.y + fm.getAscent()); } + } + + /** + * Paints the "text" property of an {@link AbstractButton}. + * + * @param g The graphics context to paint with + * @param b The button to paint the state of + * @param textRect The area in which to paint the text + * @param text The text to paint + * + * @since 1.4 + */ + protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, + String text) + { + paintText(g, (JComponent) b, textRect, text); } + + /** + * A helper method that finds the BasicButtonListener for the specified + * button. This is there because this UI class is stateless and + * shared for all buttons, and thus can't store the listener + * as instance field. (We store our shared instance in sharedListener, + * however, subclasses may override createButtonListener() and we would + * be lost in this case). + * + * @param b the button + * + * @return the UI event listener + */ + private BasicButtonListener getButtonListener(AbstractButton b) + { + // The listener gets installed as PropertyChangeListener, + // so look for it in the list of property change listeners. + PropertyChangeListener[] listeners = b.getPropertyChangeListeners(); + BasicButtonListener l = null; + for (int i = 0; listeners != null && l == null && i < listeners.length; + i++) + { + if (listeners[i] instanceof BasicButtonListener) + l = (BasicButtonListener) listeners[i]; + } + return l; + } } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicDirectoryModel.java b/libjava/classpath/javax/swing/plaf/basic/BasicDirectoryModel.java index ed916cb5f1a..de82bd47bb6 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicDirectoryModel.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicDirectoryModel.java @@ -381,7 +381,7 @@ public class BasicDirectoryModel extends AbstractListModel * * @return a Vector */ - public Vector getDirectories() + public Vector<File> getDirectories() { // Synchronize this with the UpdateSwingRequest for the case when // contents is modified. @@ -418,7 +418,7 @@ public class BasicDirectoryModel extends AbstractListModel * * @return a Vector */ - public Vector getFiles() + public Vector<File> getFiles() { synchronized (contents) { @@ -562,7 +562,7 @@ public class BasicDirectoryModel extends AbstractListModel * * @param v The Vector to sort. */ - protected void sort(Vector v) + protected void sort(Vector<? extends File> v) { Collections.sort(v, comparator); } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicFileChooserUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicFileChooserUI.java index dc1c051225c..e1f8e4b28ba 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicFileChooserUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicFileChooserUI.java @@ -183,7 +183,7 @@ public class BasicFileChooserUI extends FileChooserUI protected class BasicFileView extends FileView { /** Storage for cached icons. */ - protected Hashtable iconCache = new Hashtable(); + protected Hashtable<File, Icon> iconCache = new Hashtable<File, Icon>(); /** * Creates a new instance. @@ -444,10 +444,10 @@ public class BasicFileChooserUI extends FileChooserUI setDirectory(null); } lastSelected = path; - parentPath = path.substring(0, path.lastIndexOf("/") + 1); + parentPath = f.getParent(); if (f.isFile()) - setFileName(path.substring(path.lastIndexOf("/") + 1)); + setFileName(f.getName()); else if (filechooser.getFileSelectionMode() != JFileChooser.FILES_ONLY) setFileName(path); @@ -827,9 +827,9 @@ public class BasicFileChooserUI extends FileChooserUI installComponents(fc); installListeners(fc); - Object path = filechooser.getCurrentDirectory(); + File path = filechooser.getCurrentDirectory(); if (path != null) - parentPath = path.toString().substring(path.toString().lastIndexOf("/")); + parentPath = path.getParent(); } } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java b/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java index 1e84be93282..4c270682d88 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicGraphicsUtils.java @@ -748,7 +748,6 @@ public class BasicGraphicsUtils } } - /** * Determines the preferred width and height of an AbstractButton, * given the gap between the button’s text and icon. @@ -769,24 +768,31 @@ public class BasicGraphicsUtils public static Dimension getPreferredButtonSize(AbstractButton b, int textIconGap) { - Rectangle contentRect; - Rectangle viewRect; - Rectangle iconRect = new Rectangle(); - Rectangle textRect = new Rectangle(); - Insets insets = b.getInsets(); - - viewRect = new Rectangle(); - - /* java.awt.Toolkit.getFontMetrics is deprecated. However, it - * seems not obvious how to get to the correct FontMetrics object - * otherwise. The real problem probably is that the method - * javax.swing.SwingUtilities.layoutCompundLabel should take a - * LineMetrics, not a FontMetrics argument. But fixing this that - * would change the public API. - */ + // These cached rectangles are use here and in BasicButtonUI.paint(), + // so these two methods must never be executed concurrently. Maybe + // we must use other Rectangle instances here. OTOH, Swing is + // designed to be not thread safe, and every layout and paint operation + // should be performed from the EventDispatchThread, so it _should_ be + // OK to do this optimization. + Rectangle viewRect = BasicButtonUI.viewR; + viewRect.x = 0; + viewRect.y = 0; + viewRect.width = Short.MAX_VALUE; + viewRect.height = Short.MAX_VALUE; + Rectangle iconRect = BasicButtonUI.iconR; + iconRect.x = 0; + iconRect.y = 0; + iconRect.width = 0; + iconRect.height = 0; + Rectangle textRect = BasicButtonUI.textR; + textRect.x = 0; + textRect.y = 0; + textRect.width = 0; + textRect.height = 0; + SwingUtilities.layoutCompoundLabel( b, // for the component orientation - b.getToolkit().getFontMetrics(b.getFont()), // see comment above + b.getFontMetrics(b.getFont()), // see comment above b.getText(), b.getIcon(), b.getVerticalAlignment(), @@ -804,13 +810,12 @@ public class BasicGraphicsUtils * +------------------------+ +------------------------+ */ - contentRect = textRect.union(iconRect); - - return new Dimension(insets.left - + contentRect.width - + insets.right + b.getHorizontalAlignment(), - insets.top - + contentRect.height - + insets.bottom); + Rectangle contentRect = + SwingUtilities.computeUnion(textRect.x, textRect.y, textRect.width, + textRect.height, iconRect); + + Insets insets = b.getInsets(); + return new Dimension(insets.left + contentRect.width + insets.right, + insets.top + contentRect.height + insets.bottom); } } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicHTML.java b/libjava/classpath/javax/swing/plaf/basic/BasicHTML.java index 98c9cb277f4..6e26d5355a2 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicHTML.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicHTML.java @@ -48,6 +48,7 @@ import java.io.StringReader; import javax.swing.JComponent; import javax.swing.SwingConstants; import javax.swing.event.DocumentEvent; +import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.EditorKit; @@ -107,6 +108,7 @@ public class BasicHTML editorKit = kit; document = doc; setView(view); + setSize(view.getPreferredSpan(X_AXIS), view.getPreferredSpan(Y_AXIS)); } /** @@ -151,6 +153,14 @@ public class BasicHTML } /** + * Overridden to forward to real view. + */ + public void setSize(float w, float h) + { + view.setSize(w, h); + } + + /** * Returns the real root view, regardless of the index. * * @param index not used here @@ -346,6 +356,22 @@ public class BasicHTML { return document; } + + /** + * Overridden to return null, as a RootView has no attributes on its own. + */ + public AttributeSet getAttributes() + { + return null; + } + + /** + * Overridden to provide an element for the view. + */ + public Element getElement() + { + return view.getElement(); + } } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameTitlePane.java b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameTitlePane.java index 11980f6ca2e..ea8b4603691 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameTitlePane.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameTitlePane.java @@ -177,9 +177,15 @@ public class BasicInternalFrameTitlePane extends JComponent try { if (frame.isMaximizable() && ! frame.isMaximum()) - frame.setMaximum(true); + { + frame.setMaximum(true); + maxButton.setIcon(minIcon); + } else if (frame.isMaximum()) - frame.setMaximum(false); + { + frame.setMaximum(false); + maxButton.setIcon(maxIcon); + } } catch (PropertyVetoException pve) { diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameUI.java index 8f2181336cb..87c5268c8c7 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicInternalFrameUI.java @@ -459,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. } /** @@ -949,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); + } + } } } @@ -1258,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); + } } /** @@ -1286,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); @@ -1298,7 +1311,7 @@ public class BasicInternalFrameUI extends InternalFrameUI frame.removeMouseListener(borderListener); propertyChangeListener = null; - componentListener = null; + borderListener = null; internalFrameListener = null; glassPaneDispatcher = null; @@ -1581,6 +1594,13 @@ public class BasicInternalFrameUI extends InternalFrameUI { replacePane(northPane, c); northPane = c; + // the following is needed to make internal frames draggable when using + // the JGoodies PlasticLookAndFeel, because it overrides the + // createNorthPane() method and doesn't assign anything to the titlePane + // field. It is possible there is another way to make this work, but + // I didn't find it... + if (c instanceof BasicInternalFrameTitlePane) + titlePane = (BasicInternalFrameTitlePane) c; } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicLabelUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicLabelUI.java index 304e13ad735..1ec020b1c0b 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicLabelUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicLabelUI.java @@ -119,13 +119,37 @@ public class BasicLabelUI extends LabelUI implements PropertyChangeListener { JLabel lab = (JLabel) c; Insets insets = lab.getInsets(); - FontMetrics fm = lab.getFontMetrics(lab.getFont()); - layoutCL(lab, fm, lab.getText(), lab.getIcon(), vr, ir, tr); - Rectangle cr = SwingUtilities.computeUnion(tr.x, tr.y, tr.width, tr.height, - ir); - return new Dimension(insets.left + cr.width + insets.right, insets.top - + cr.height + insets.bottom); - + int insetsX = insets.left + insets.right; + int insetsY = insets.top + insets.bottom; + Icon icon = lab.getIcon(); + String text = lab.getText(); + Dimension ret; + if (icon == null && text == null) + ret = new Dimension(insetsX, insetsY); + else if (icon != null && text == null) + ret = new Dimension(icon.getIconWidth() + insetsX, + icon.getIconHeight() + insetsY); + else + { + FontMetrics fm = lab.getFontMetrics(lab.getFont()); + ir.x = 0; + ir.y = 0; + ir.width = 0; + ir.height = 0; + tr.x = 0; + tr.y = 0; + tr.width = 0; + tr.height = 0; + vr.x = 0; + vr.y = 0; + vr.width = Short.MAX_VALUE; + vr.height = Short.MAX_VALUE; + layoutCL(lab, fm, text, icon, vr, ir, tr); + Rectangle cr = SwingUtilities.computeUnion(tr.x, tr.y, tr.width, + tr.height, ir); + ret = new Dimension(cr.width + insetsX, cr.height + insetsY); + } + return ret; } /** @@ -166,13 +190,20 @@ public class BasicLabelUI extends LabelUI implements PropertyChangeListener { JLabel b = (JLabel) c; FontMetrics fm = g.getFontMetrics(); - vr = SwingUtilities.calculateInnerArea(c, vr); - - if (vr.width < 0) - vr.width = 0; - if (vr.height < 0) - vr.height = 0; + Insets i = c.getInsets(); + vr.x = i.left; + vr.y = i.right; + vr.width = c.getWidth() - i.left + i.right; + vr.height = c.getHeight() - i.top + i.bottom; + ir.x = 0; + ir.y = 0; + ir.width = 0; + ir.height = 0; + tr.x = 0; + tr.y = 0; + tr.width = 0; + tr.height = 0; Icon icon = (b.isEnabled()) ? b.getIcon() : b.getDisabledIcon(); String text = layoutCL(b, fm, b.getText(), icon, vr, ir, tr); diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java index 493fc0578e3..befc227364a 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java @@ -921,7 +921,7 @@ public class BasicListUI extends ListUI */ protected void maybeUpdateLayoutState() { - if (updateLayoutStateNeeded != 0) + if (updateLayoutStateNeeded != 0 || !list.isValid()) { updateLayoutState(); updateLayoutStateNeeded = 0; diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java b/libjava/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java index c056a2403f9..15430945468 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java @@ -1062,8 +1062,7 @@ public abstract class BasicLookAndFeel extends LookAndFeel "ProgressBar.repaintInterval", new Integer(50), "ProgressBar.cycleTime", new Integer(3000), "RadioButton.background", new ColorUIResource(light), - "RadioButton.border", new BorderUIResource.CompoundBorderUIResource(null, - null), + "RadioButton.border", BasicBorders.getRadioButtonBorder(), "RadioButton.darkShadow", new ColorUIResource(shadow), "RadioButton.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { KeyStroke.getKeyStroke("SPACE"), "pressed", @@ -1183,6 +1182,10 @@ public abstract class BasicLookAndFeel extends LookAndFeel "Slider.thumbHeight", new Integer(20), "Slider.thumbWidth", new Integer(11), "Slider.tickHeight", new Integer(12), + "Slider.horizontalSize", new Dimension(200, 21), + "Slider.verticalSize", new Dimension(21, 200), + "Slider.minimumHorizontalSize", new Dimension(36, 21), + "Slider.minimumVerticalSize", new Dimension(21, 36), "Spinner.background", new ColorUIResource(light), "Spinner.foreground", new ColorUIResource(light), "Spinner.arrowButtonSize", new DimensionUIResource(16, 5), @@ -1218,10 +1221,10 @@ public abstract class BasicLookAndFeel extends LookAndFeel "ctrl UP", "requestFocus", "ctrl KP_UP", "requestFocus" }), - "TabbedPane.background", new ColorUIResource(light), + "TabbedPane.background", new ColorUIResource(192, 192, 192), "TabbedPane.contentBorderInsets", new InsetsUIResource(2, 2, 3, 3), - "TabbedPane.darkShadow", new ColorUIResource(shadow), - "TabbedPane.focus", new ColorUIResource(darkShadow), + "TabbedPane.darkShadow", new ColorUIResource(Color.black), + "TabbedPane.focus", new ColorUIResource(Color.black), "TabbedPane.focusInputMap", new UIDefaults.LazyInputMap(new Object[] { KeyStroke.getKeyStroke("ctrl DOWN"), "requestFocusForVisibleComponent", KeyStroke.getKeyStroke("KP_UP"), "navigateUp", @@ -1235,17 +1238,16 @@ public abstract class BasicLookAndFeel extends LookAndFeel KeyStroke.getKeyStroke("DOWN"), "navigateDown" }), "TabbedPane.font", new FontUIResource("Dialog", Font.PLAIN, 12), - "TabbedPane.foreground", new ColorUIResource(darkShadow), - "TabbedPane.highlight", new ColorUIResource(highLight), - "TabbedPane.light", new ColorUIResource(highLight), + "TabbedPane.foreground", new ColorUIResource(Color.black), + "TabbedPane.highlight", new ColorUIResource(Color.white), + "TabbedPane.light", new ColorUIResource(192, 192, 192), "TabbedPane.selectedTabPadInsets", new InsetsUIResource(2, 2, 2, 1), - "TabbedPane.shadow", new ColorUIResource(shadow), - "TabbedPane.tabbedPaneContentBorderInsets", new InsetsUIResource(3, 2, 1, 2), - "TabbedPane.tabbedPaneTabPadInsets", new InsetsUIResource(1, 1, 1, 1), + "TabbedPane.shadow", new ColorUIResource(128, 128, 128), "TabbedPane.tabsOpaque", Boolean.TRUE, "TabbedPane.tabAreaInsets", new InsetsUIResource(3, 2, 0, 2), "TabbedPane.tabInsets", new InsetsUIResource(0, 4, 1, 4), "TabbedPane.tabRunOverlay", new Integer(2), + "TabbedPane.tabsOverlapBorder", Boolean.FALSE, "TabbedPane.textIconGap", new Integer(4), "Table.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { "ctrl DOWN", "selectNextRowChangeLead", diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java index bbc08535cdc..5fafb4108b2 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java @@ -180,17 +180,9 @@ public class BasicMenuItemUI extends MenuItemUI private ItemListener itemListener; /** - * Number of spaces between accelerator and menu item's label. + * A PropertyChangeListener to make UI updates after property changes. */ - private int defaultAcceleratorLabelGap = 10; - - /** - * The gap between different menus on the MenuBar. - */ - private int MenuGap = 10; - - /** A PropertyChangeListener to make UI updates after property changes **/ - PropertyChangeHandler propertyChangeListener; + private PropertyChangeHandler propertyChangeListener; /** * The view rectangle used for layout of the menu item. @@ -262,7 +254,6 @@ public class BasicMenuItemUI extends MenuItemUI || property.equals("font")) && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") == null) - { AbstractButton b = (AbstractButton) e.getSource(); String text = b.getText(); @@ -373,7 +364,7 @@ public class BasicMenuItemUI extends MenuItemUI */ protected void doClick(MenuSelectionManager msm) { - menuItem.doClick(); + menuItem.doClick(0); msm.clearSelectedPath(); } @@ -411,14 +402,10 @@ 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) { - path.add(0, (MenuElement) c); + path.add(0, c); if (c instanceof JPopupMenu) c = ((JPopupMenu) c).getInvoker(); @@ -453,6 +440,7 @@ public class BasicMenuItemUI extends MenuItemUI // Layout the menu item. The result gets stored in the rectangle // fields of this class. + resetRectangles(null); layoutMenuItem(m, accelText); // The union of the text and icon areas is the label area. @@ -606,6 +594,11 @@ public class BasicMenuItemUI extends MenuItemUI menuItem.addMenuKeyListener(menuKeyListener); menuItem.addItemListener(itemListener); menuItem.addPropertyChangeListener(propertyChangeListener); + // Fire synthetic property change event to let the listener update + // the TextLayout cache. + propertyChangeListener.propertyChange(new PropertyChangeEvent(menuItem, + "font", null, + menuItem.getFont())); } /** @@ -704,6 +697,8 @@ public class BasicMenuItemUI extends MenuItemUI // Layout menu item. The result gets stored in the rectangle fields // of this class. + resetRectangles(m); + layoutMenuItem(m, accelText); // Paint the background. @@ -936,6 +931,7 @@ public class BasicMenuItemUI extends MenuItemUI uninstallListeners(); uninstallDefaults(); uninstallComponents(menuItem); + c.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, null); menuItem = null; } @@ -953,47 +949,6 @@ public class BasicMenuItemUI extends MenuItemUI } /** - * Return text representation of the specified accelerator - * - * @param accelerator - * Accelerator for which to return string representation - * @return $String$ Text representation of the given accelerator - */ - private String getAcceleratorText(KeyStroke accelerator) - { - // convert keystroke into string format - String modifiersText = ""; - int modifiers = accelerator.getModifiers(); - char keyChar = accelerator.getKeyChar(); - int keyCode = accelerator.getKeyCode(); - - if (modifiers != 0) - modifiersText = KeyEvent.getKeyModifiersText(modifiers) - + acceleratorDelimiter; - - if (keyCode == KeyEvent.VK_UNDEFINED) - return modifiersText + keyChar; - else - return modifiersText + KeyEvent.getKeyText(keyCode); - } - - /** - * Calculates and return rectange in which accelerator should be displayed - * - * @param accelerator - * accelerator for which to return the display rectangle - * @param fm - * The font metrics used to measure the text - * @return $Rectangle$ reactangle which will be used to display accelerator - */ - private Rectangle getAcceleratorRect(KeyStroke accelerator, FontMetrics fm) - { - int width = fm.stringWidth(getAcceleratorText(accelerator)); - int height = fm.getHeight(); - return new Rectangle(0, 0, width, height); - } - - /** * This class handles mouse events occuring inside the menu item. Most of the * events are forwarded for processing to MenuSelectionManager of the current * menu hierarchy. @@ -1103,15 +1058,14 @@ public class BasicMenuItemUI extends MenuItemUI */ public void mouseReleased(MouseEvent e) { - Rectangle size = menuItem.getBounds(); MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - if (e.getX() > 0 && e.getX() < size.width && e.getY() > 0 - && e.getY() < size.height) + int x = e.getX(); + int y = e.getY(); + if (x > 0 && x < menuItem.getWidth() && y > 0 + && y < menuItem.getHeight()) { - manager.clearSelectedPath(); - menuItem.doClick(); + doClick(manager); } - else manager.processMouseEvent(e); } @@ -1130,7 +1084,7 @@ public class BasicMenuItemUI extends MenuItemUI */ public void menuDragMouseDragged(MenuDragMouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + MenuSelectionManager manager = e.getMenuSelectionManager(); manager.setSelectedPath(e.getPath()); } @@ -1143,7 +1097,7 @@ public class BasicMenuItemUI extends MenuItemUI */ public void menuDragMouseEntered(MenuDragMouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + MenuSelectionManager manager = e.getMenuSelectionManager(); manager.setSelectedPath(e.getPath()); } @@ -1155,7 +1109,7 @@ public class BasicMenuItemUI extends MenuItemUI */ public void menuDragMouseExited(MenuDragMouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here yet. } /** @@ -1167,13 +1121,14 @@ public class BasicMenuItemUI extends MenuItemUI */ public void menuDragMouseReleased(MenuDragMouseEvent e) { - MenuElement[] path = e.getPath(); - - if (path[path.length - 1] instanceof JMenuItem) - ((JMenuItem) path[path.length - 1]).doClick(); - - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.clearSelectedPath(); + MenuSelectionManager manager = e.getMenuSelectionManager(); + int x = e.getX(); + int y = e.getY(); + if (x >= 0 && x < menuItem.getWidth() && y >= 0 + && y < menuItem.getHeight()) + doClick(manager); + else + manager.clearSelectedPath(); } } @@ -1275,32 +1230,41 @@ public class BasicMenuItemUI extends MenuItemUI } /** - * A helper method that lays out the menu item. The layout is stored - * in the fields of this class. + * Resets the cached layout rectangles. If <code>i</code> is not null, then + * the view rectangle is set to the inner area of the component, otherwise + * it is set to (0, 0, Short.MAX_VALUE, Short.MAX_VALUE), this is needed + * for layouting. * - * @param m the menu item to layout - * @param accelText the accelerator text + * @param i the component for which to initialize the rectangles */ - private void layoutMenuItem(JMenuItem m, String accelText) + private void resetRectangles(JMenuItem i) { - int width = m.getWidth(); - int height = m.getHeight(); - // Reset rectangles. iconRect.setBounds(0, 0, 0, 0); textRect.setBounds(0, 0, 0, 0); accelRect.setBounds(0, 0, 0, 0); checkIconRect.setBounds(0, 0, 0, 0); arrowIconRect.setBounds(0, 0, 0, 0); - viewRect.setBounds(0, 0, width, height); - - // Substract insets to the view rect. - Insets insets = m.getInsets(); - viewRect.x += insets.left; - viewRect.y += insets.top; - viewRect.width -= insets.left + insets.right; - viewRect.height -= insets.top + insets.bottom; + if (i == null) + viewRect.setBounds(0, 0, Short.MAX_VALUE, Short.MAX_VALUE); + else + { + Insets insets = i.getInsets(); + viewRect.setBounds(insets.left, insets.top, + i.getWidth() - insets.left - insets.right, + i.getHeight() - insets.top - insets.bottom); + } + } + /** + * A helper method that lays out the menu item. The layout is stored + * in the fields of this class. + * + * @param m the menu item to layout + * @param accelText the accelerator text + */ + private void layoutMenuItem(JMenuItem m, String accelText) + { // Fetch the fonts. Font font = m.getFont(); FontMetrics fm = m.getFontMetrics(font); diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicMenuUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicMenuUI.java index 7d8784fd15a..355e0435ec8 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicMenuUI.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonUI.java index aed4d69d6d5..bfb9e98dbc9 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicRadioButtonUI.java @@ -52,6 +52,7 @@ import javax.swing.JComponent; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; +import javax.swing.text.View; /** * The BasicLookAndFeel UI implementation for @@ -81,7 +82,7 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI */ public BasicRadioButtonUI() { - icon = getDefaultIcon(); + // nothing to do } /** @@ -93,6 +94,7 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI protected void installDefaults(AbstractButton b) { super.installDefaults(b); + icon = UIManager.getIcon(getPropertyPrefix() + "icon"); } /** @@ -116,7 +118,7 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI */ public Icon getDefaultIcon() { - return UIManager.getIcon(getPropertyPrefix() + "icon"); + return icon; } /** @@ -128,40 +130,92 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI public void paint(Graphics g, JComponent c) { AbstractButton b = (AbstractButton) c; - - Rectangle tr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle vr = new Rectangle(); + Dimension size = c.getSize(); + Insets i = b.getInsets(); + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; + viewR.x = i.left; + viewR.y = i.right; + viewR.width = size.width - i.left - i.right; + viewR.height = size.height - i.top - i.bottom; Font f = c.getFont(); g.setFont(f); ButtonModel m = b.getModel(); - // FIXME: Do a filtering on any customized icon if the following property - // is set. - boolean enabled = b.isEnabled(); - - Icon currentIcon = b.getIcon(); - if (currentIcon == null) - { - currentIcon = getDefaultIcon(); - } - - SwingUtilities.calculateInnerArea(b, vr); + // This is the icon that we use for layout. + Icon icon = b.getIcon(); + if (icon == null) + icon = getDefaultIcon(); + + // Do the layout. String text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), - b.getText(), currentIcon, + b.getText(), icon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), - vr, ir, tr, b.getIconTextGap() + defaultTextShiftOffset); - - currentIcon.paintIcon(c, g, ir.x, ir.y); - + viewR, iconR, textR, b.getIconTextGap()); + + // Figure out the correct icon. + icon = b.getIcon(); + if (icon == null) + icon = getDefaultIcon(); + else + { + if (! m.isEnabled()) + { + if (m.isSelected()) + icon = b.getDisabledSelectedIcon(); + else + icon = b.getDisabledIcon(); + } + else if (m.isArmed() && m.isPressed()) + { + icon = b.getPressedIcon(); + if (icon == null) + icon = b.getSelectedIcon(); + } + else if (m.isSelected()) + { + if (b.isRolloverEnabled() && m.isRollover()) + { + icon = b.getRolloverSelectedIcon(); + if (icon == null) + icon = b.getSelectedIcon(); + } + else + icon = b.getSelectedIcon(); + } + else if (b.isRolloverEnabled() && m.isRollover()) + icon = b.getRolloverIcon(); + if (icon == null) + icon = b.getIcon(); + } + // .. and paint it. + icon.paintIcon(c, g, iconR.x, iconR.y); + + // Paint text and focus indicator. if (text != null) - paintText(g, b, tr, text); - if (b.hasFocus() && b.isFocusPainted() && m.isEnabled()) - paintFocus(g, tr, c.getSize()); + { + // Maybe render HTML in the radio button. + View v = (View) c.getClientProperty(BasicHTML.propertyKey); + if (v != null) + v.paint(g, textR); + else + paintText(g, b, textR, text); + + // Paint focus indicator if necessary. + if (b.hasFocus() && b.isFocusPainted() + && textR.width > 0 && textR.height > 0) + paintFocus(g, textR, size); + } } public Dimension getPreferredSize(JComponent c) @@ -174,38 +228,40 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI // The other icon properties are ignored. AbstractButton b = (AbstractButton) c; - Rectangle contentRect; - Rectangle viewRect; - Rectangle iconRect = new Rectangle(); - Rectangle textRect = new Rectangle(); Insets insets = b.getInsets(); - + + String text = b.getText(); Icon i = b.getIcon(); if (i == null) i = getDefaultIcon(); - viewRect = new Rectangle(); - - SwingUtilities.layoutCompoundLabel( - b, // for the component orientation - b.getFontMetrics(b.getFont()), - b.getText(), - i, - b.getVerticalAlignment(), - b.getHorizontalAlignment(), - b.getVerticalTextPosition(), - b.getHorizontalTextPosition(), - viewRect, iconRect, textRect, - defaultTextIconGap + defaultTextShiftOffset); - - contentRect = textRect.union(iconRect); - - return new Dimension(insets.left - + contentRect.width - + insets.right + b.getHorizontalAlignment(), - insets.top - + contentRect.height - + insets.bottom); + textR.x = 0; + textR.y = 0; + textR.width = 0; + textR.height = 0; + iconR.x = 0; + iconR.y = 0; + iconR.width = 0; + iconR.height = 0; + viewR.x = 0; + viewR.y = 0; + viewR.width = Short.MAX_VALUE; + viewR.height = Short.MAX_VALUE; + + SwingUtilities.layoutCompoundLabel(b, // for the component orientation + b.getFontMetrics(b.getFont()), + text, i, b.getVerticalAlignment(), + b.getHorizontalAlignment(), + b.getVerticalTextPosition(), + b.getHorizontalTextPosition(), + viewR, iconR, textR, + text == null ? 0 : b.getIconTextGap()); + + Rectangle r = SwingUtilities.computeUnion(textR.x, textR.y, textR.width, + textR.height, iconR); + + return new Dimension(insets.left + r.width + insets.right, + insets.top + r.height + insets.bottom); } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java index 78e5168fc80..400ede03ce9 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicScrollBarUI.java @@ -760,10 +760,7 @@ public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, scrollbar.setOpaque(true); scrollbar.setLayout(this); - thumbColor = UIManager.getColor("ScrollBar.thumb"); - thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow"); - thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight"); - thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow"); + configureScrollBarColors(); maximumThumbSize = UIManager.getDimension("ScrollBar.maximumThumbSize"); minimumThumbSize = UIManager.getDimension("ScrollBar.minimumThumbSize"); @@ -1228,8 +1225,36 @@ public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, */ protected void scrollByBlock(int direction) { - scrollbar.setValue(scrollbar.getValue() - + scrollbar.getBlockIncrement(direction)); + scrollByBlock(scrollbar, direction); + } + + /** + * Scrolls the specified <code>scrollBar</code> by one block (according + * to the scrollable protocol) in the specified <code>direction</code>. + * + * This method is here statically to support wheel scrolling from the + * BasicScrollPaneUI without code duplication. + * + * @param scrollBar the scrollbar to scroll + * @param direction the scroll direction + */ + static final void scrollByBlock(JScrollBar scrollBar, int direction) + { + int delta; + if (direction > 0) + delta = scrollBar.getBlockIncrement(direction); + else + delta = - scrollBar.getBlockIncrement(direction); + int oldValue = scrollBar.getValue(); + int newValue = oldValue + delta; + + // Overflow check. + if (delta > 0 && newValue < oldValue) + newValue = scrollBar.getMaximum(); + else if (delta < 0 && newValue > oldValue) + newValue = scrollBar.getMinimum(); + + scrollBar.setValue(newValue); } /** @@ -1239,8 +1264,46 @@ public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, */ protected void scrollByUnit(int direction) { - scrollbar.setValue(scrollbar.getValue() - + scrollbar.getUnitIncrement(direction)); + scrollByUnits(scrollbar, direction, 1); + } + + /** + * Scrolls the specified <code>scrollbac/code> by <code>units</code> units + * in the specified <code>direction</code>. + * + * This method is here statically to support wheel scrolling from the + * BasicScrollPaneUI without code duplication. + * + * @param scrollBar the scrollbar to scroll + * @param direction the direction + * @param units the number of units to scroll + */ + static final void scrollByUnits(JScrollBar scrollBar, int direction, + int units) + { + // Do this inside a loop so that we don't clash with the scrollable + // interface, which can return different units at times. For instance, + // a Scrollable could return a unit of 2 pixels only to adjust the + // visibility of an item. If we would simply multiply this by units, + // then we would only get 6 pixels, which is complete crap. + for (int i = 0; i < units; i++) + { + int delta; + if (direction > 0) + delta = scrollBar.getUnitIncrement(direction); + else + delta = - scrollBar.getUnitIncrement(direction); + int oldValue = scrollBar.getValue(); + int newValue = oldValue + delta; + + // Overflow check. + if (delta > 0 && newValue < oldValue) + newValue = scrollBar.getMaximum(); + else if (delta < 0 && newValue > oldValue) + newValue = scrollBar.getMinimum(); + + scrollBar.setValue(newValue); + } } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicScrollPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicScrollPaneUI.java index a0616a8c1cf..a7194284050 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicScrollPaneUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicScrollPaneUI.java @@ -38,9 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - -import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Point; @@ -54,7 +51,6 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; -import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JComponent; @@ -65,15 +61,15 @@ import javax.swing.JViewport; import javax.swing.LookAndFeel; import javax.swing.ScrollPaneConstants; import javax.swing.ScrollPaneLayout; -import javax.swing.Scrollable; -import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.border.Border; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ScrollPaneUI; +import javax.swing.plaf.UIResource; /** * A UI delegate for the {@link JScrollPane} component. @@ -102,19 +98,8 @@ public class BasicScrollPaneUI extends ScrollPaneUI JScrollBar hsb = scrollpane.getHorizontalScrollBar(); JViewport vp = scrollpane.getViewport(); Point viewPosition = vp.getViewPosition(); - int xpos = hsb.getValue(); - - if (xpos != viewPosition.x) - { - viewPosition.x = xpos; - vp.setViewPosition(viewPosition); - } - - viewPosition.y = 0; - JViewport columnHeader = scrollpane.getColumnHeader(); - if (columnHeader != null - && !columnHeader.getViewPosition().equals(viewPosition)) - columnHeader.setViewPosition(viewPosition); + viewPosition.x = hsb.getValue(); + vp.setViewPosition(viewPosition); } } @@ -139,18 +124,8 @@ public class BasicScrollPaneUI extends ScrollPaneUI JScrollBar vsb = scrollpane.getVerticalScrollBar(); JViewport vp = scrollpane.getViewport(); Point viewPosition = vp.getViewPosition(); - int ypos = vsb.getValue(); - if (ypos != viewPosition.y) - { - viewPosition.y = ypos; - vp.setViewPosition(viewPosition); - } - - viewPosition.x = 0; - JViewport rowHeader = scrollpane.getRowHeader(); - if (rowHeader != null - && !rowHeader.getViewPosition().equals(viewPosition)) - rowHeader.setViewPosition(viewPosition); + viewPosition.y = vsb.getValue(); + vp.setViewPosition(viewPosition); } } @@ -174,9 +149,6 @@ public class BasicScrollPaneUI extends ScrollPaneUI */ public void stateChanged(ChangeEvent event) { - JViewport vp = scrollpane.getViewport(); - JScrollBar hsb = scrollpane.getHorizontalScrollBar(); - JScrollBar vsb = scrollpane.getVerticalScrollBar(); syncScrollPaneWithViewport(); } @@ -254,103 +226,24 @@ public class BasicScrollPaneUI extends ScrollPaneUI */ public void mouseWheelMoved(MouseWheelEvent e) { - if (scrollpane.getViewport().getComponentCount() == 0) - return; - - Component target = scrollpane.getViewport().getComponent(0); - JScrollBar bar = scrollpane.getVerticalScrollBar(); - Scrollable scrollable = (target instanceof Scrollable) ? (Scrollable) target - : null; - - boolean tracksHeight = scrollable != null - && scrollable.getScrollableTracksViewportHeight(); - int wheel = e.getWheelRotation() * ROWS_PER_WHEEL_CLICK; - int delta; - - // If possible, scroll vertically. - if (bar != null && ! tracksHeight) + if (scrollpane.isWheelScrollingEnabled() && e.getScrollAmount() != 0) { - if (scrollable != null) + // Try to scroll vertically first. + JScrollBar scrollBar = scrollpane.getVerticalScrollBar(); + if (scrollBar == null || ! scrollBar.isVisible()) + scrollBar = scrollpane.getHorizontalScrollBar(); + if (scrollBar != null && scrollBar.isVisible()) { - bounds(target); - delta = scrollable.getScrollableUnitIncrement( - rect, SwingConstants.VERTICAL, wheel); - } - else - { - // Scroll non scrollables. - delta = wheel * SCROLL_NON_SCROLLABLES; - } - scroll(bar, delta); - } - // If not, try to scroll horizontally - else - { - bar = scrollpane.getHorizontalScrollBar(); - boolean tracksWidth = scrollable != null - && scrollable.getScrollableTracksViewportWidth(); - - if (bar != null && ! tracksWidth) - { - if (scrollable != null) - { - bounds(target); - delta = scrollable.getScrollableUnitIncrement( - rect, SwingConstants.HORIZONTAL, wheel); - } - else - { - // Scroll non scrollables. - delta = wheel * SCROLL_NON_SCROLLABLES; - } - scroll(bar, delta); + int direction = e.getWheelRotation() < 0 ? -1 : 1; + int scrollType = e.getScrollType(); + if (scrollType == MouseWheelEvent.WHEEL_UNIT_SCROLL) + BasicScrollBarUI.scrollByUnits(scrollBar, direction, + e.getScrollAmount()); + else if (scrollType == MouseWheelEvent.WHEEL_BLOCK_SCROLL) + BasicScrollBarUI.scrollByBlock(scrollBar, direction); } } } - - /** - * Place the component bounds into rect. The x and y values - * need to be reversed. - * - * @param target the target being scrolled - */ - final void bounds(Component target) - { - // Viewport bounds, translated by the scroll bar positions. - target.getParent().getBounds(rect); - rect.x = getValue(scrollpane.getHorizontalScrollBar()); - rect.y = getValue(scrollpane.getVerticalScrollBar()); - } - - /** - * Get the scroll bar value or 0 if there is no such scroll bar. - * - * @param bar the scroll bar (<code>null</code> permitted). - * - * @return The scroll bar value, or 0. - */ - final int getValue(JScrollBar bar) - { - return bar != null ? bar.getValue() : 0; - } - - /** - * Scroll the given distance. - * - * @param bar the scrollbar to scroll - * @param delta the distance - */ - final void scroll(JScrollBar bar, int delta) - { - int y = bar.getValue() + delta; - - if (y < bar.getMinimum()) - y = bar.getMinimum(); - if (y > bar.getMaximum()) - y = bar.getMaximum(); - - bar.setValue(y); - } } /** @@ -436,16 +329,24 @@ public class BasicScrollPaneUI extends ScrollPaneUI "ScrollPane.foreground", "ScrollPane.font"); LookAndFeel.installBorder(p, "ScrollPane.border"); + + // Install Viewport border. + Border vpBorder = p.getViewportBorder(); + if (vpBorder == null || vpBorder instanceof UIResource) + { + vpBorder = UIManager.getBorder("ScrollPane.viewportBorder"); + p.setViewportBorder(vpBorder); + } + p.setOpaque(true); } protected void uninstallDefaults(JScrollPane p) { - p.setForeground(null); - p.setBackground(null); - p.setFont(null); - p.setBorder(null); - scrollpane = null; + LookAndFeel.uninstallBorder(p); + Border vpBorder = p.getViewportBorder(); + if (vpBorder != null && vpBorder instanceof UIResource) + p.setViewportBorder(null); } public void installUI(final JComponent c) @@ -770,9 +671,8 @@ public class BasicScrollPaneUI extends ScrollPaneUI public void uninstallUI(final JComponent c) { - super.uninstallUI(c); - this.uninstallDefaults((JScrollPane) c); - uninstallListeners((JScrollPane) c); + uninstallDefaults((JScrollPane) c); + uninstallListeners(c); installKeyboardActions((JScrollPane) c); } @@ -808,29 +708,65 @@ public class BasicScrollPaneUI extends ScrollPaneUI } public void paint(Graphics g, JComponent c) - { - // do nothing; the normal painting-of-children algorithm, along with - // ScrollPaneLayout, does all the relevant work. + { + Border vpBorder = scrollpane.getViewportBorder(); + if (vpBorder != null) + { + Rectangle r = scrollpane.getViewportBorderBounds(); + vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height); + } } /** - * Synchronizes the scrollbars with the viewport's extents. + * Synchronizes the scrollbar and header settings positions and extent + * with the viewport's view position and extent. */ protected void syncScrollPaneWithViewport() { JViewport vp = scrollpane.getViewport(); - // Update the horizontal scrollbar. - JScrollBar hsb = scrollpane.getHorizontalScrollBar(); - hsb.setMaximum(vp.getViewSize().width); - hsb.setValue(vp.getViewPosition().x); - hsb.setVisibleAmount(vp.getExtentSize().width); - - // Update the vertical scrollbar. - JScrollBar vsb = scrollpane.getVerticalScrollBar(); - vsb.setMaximum(vp.getViewSize().height); - vsb.setValue(vp.getViewPosition().y); - vsb.setVisibleAmount(vp.getExtentSize().height); + if (vp != null) + { + Dimension extentSize = vp.getExtentSize(); + Point viewPos = vp.getViewPosition(); + Dimension viewSize = vp.getViewSize(); + + // Update the vertical scrollbar. + JScrollBar vsb = scrollpane.getVerticalScrollBar(); + if (vsb != null) + { + int extent = extentSize.height; + int max = viewSize.height; + int val = Math.max(0, Math.min(viewPos.y, max - extent)); + vsb.setValues(val, extent, 0, max); + } + + // Update the horizontal scrollbar. + JScrollBar hsb = scrollpane.getHorizontalScrollBar(); + if (hsb != null) + { + int extent = extentSize.width; + int max = viewSize.width; + int val = Math.max(0, Math.min(viewPos.x, max - extent)); + hsb.setValues(val, extent, 0, max); + } + + // Update the row header. + JViewport rowHeader = scrollpane.getRowHeader(); + if (rowHeader != null) + { + Point p = new Point(0, viewPos.y); + rowHeader.setViewPosition(p); + } + + // Update the column header. + JViewport colHeader = scrollpane.getColumnHeader(); + if (colHeader != null) + { + Point p = new Point(viewPos.x, 0); + colHeader.setViewPosition(p); + } + } } /** @@ -863,7 +799,8 @@ public class BasicScrollPaneUI extends ScrollPaneUI */ protected void updateScrollBarDisplayPolicy(PropertyChangeEvent ev) { - // TODO: Find out what should be done here. Or is this only a hook? + scrollpane.revalidate(); + scrollpane.repaint(); } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSliderUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicSliderUI.java index 3811eebdfd6..474a4225640 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicSliderUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSliderUI.java @@ -40,7 +40,6 @@ package javax.swing.plaf.basic; import java.awt.Color; import java.awt.Component; -import java.awt.ComponentOrientation; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; @@ -65,7 +64,6 @@ import javax.swing.ActionMap; import javax.swing.BoundedRangeModel; import javax.swing.InputMap; import javax.swing.JComponent; -import javax.swing.JLabel; import javax.swing.JSlider; import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; @@ -185,8 +183,6 @@ public class BasicSliderUI extends SliderUI public void componentResized(ComponentEvent e) { calculateGeometry(); - - slider.revalidate(); slider.repaint(); } } @@ -209,7 +205,6 @@ public class BasicSliderUI extends SliderUI public void focusGained(FocusEvent e) { slider.repaint(); - hasFocus = true; } /** @@ -221,7 +216,6 @@ public class BasicSliderUI extends SliderUI public void focusLost(FocusEvent e) { slider.repaint(); - hasFocus = false; } } @@ -240,25 +234,27 @@ public class BasicSliderUI extends SliderUI public void propertyChange(PropertyChangeEvent e) { // Check for orientation changes. - if (e.getPropertyName().equals("orientation")) - recalculateIfOrientationChanged(); + String prop = e.getPropertyName(); + if (prop.equals("orientation") + || prop.equals("inverted") + || prop.equals("labelTable") + || prop.equals("majorTickSpacing") + || prop.equals("minorTickSpacing") + || prop.equals("paintTicks") + || prop.equals("paintTrack") + || prop.equals("paintLabels")) + { + calculateGeometry(); + slider.repaint(); + } else if (e.getPropertyName().equals("model")) { BoundedRangeModel oldModel = (BoundedRangeModel) e.getOldValue(); oldModel.removeChangeListener(changeListener); slider.getModel().addChangeListener(changeListener); calculateThumbLocation(); + slider.repaint(); } - else if (e.getPropertyName().equals("paintTicks")) - calculateGeometry(); - - // elif the componentOrientation changes (this is a bound property, - // just undocumented) we change leftToRightCache. In Sun's - // implementation, the LTR cache changes on a repaint. This is strange - // since there is no need to do so. We could events here and - // update the cache. - // elif the border/insets change, we recalculateInsets. - slider.repaint(); } } @@ -466,6 +462,7 @@ public class BasicSliderUI extends SliderUI if (scrollTimer != null) scrollTimer.stop(); } + slider.repaint(); } /** @@ -592,10 +589,7 @@ public class BasicSliderUI extends SliderUI /** The focus color. */ private transient Color focusColor; - - /** True if the slider has focus. */ - private transient boolean hasFocus; - + /** True if the user is dragging the slider. */ boolean dragging; @@ -935,36 +929,10 @@ public class BasicSliderUI extends SliderUI */ public Dimension getPreferredHorizontalSize() { - Insets insets = slider.getInsets(); - - // The width should cover all the labels (which are usually the - // deciding factor of the width) - int width = getWidthOfWidestLabel() * (slider.getLabelTable() == null ? 0 - : slider.getLabelTable().size()); - - // If there are not enough labels. - // This number is pretty much arbitrary, but it looks nice. - if (width < 200) - width = 200; - - // We can only draw inside of the focusRectangle, so we have to - // pad it with insets. - width += insets.left + insets.right + focusInsets.left + focusInsets.right; - - // Height is determined by the thumb, the ticks and the labels. - int height = getThumbSize().height; - - if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0) - height += getTickLength(); - - if (slider.getPaintLabels()) - height += getHeightOfTallestLabel(); - - height += insets.top + insets.bottom + focusInsets.top - + focusInsets.bottom; - - return new Dimension(width, height); + Dimension dim = UIManager.getDimension("Slider.horizontalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(200, 21); + return dim; } /** @@ -975,30 +943,10 @@ public class BasicSliderUI extends SliderUI */ public Dimension getPreferredVerticalSize() { - Insets insets = slider.getInsets(); - - int height = getHeightOfTallestLabel() * (slider.getLabelTable() == null - ? 0 : slider.getLabelTable() - .size()); - - if (height < 200) - height = 200; - - height += insets.top + insets.bottom + focusInsets.top - + focusInsets.bottom; - - int width = getThumbSize().width; - - if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0) - width += getTickLength(); - - if (slider.getPaintLabels()) - width += getWidthOfWidestLabel(); - - width += insets.left + insets.right + focusInsets.left + focusInsets.right; - - return new Dimension(width, height); + Dimension dim = UIManager.getDimension("Slider.verticalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(21, 200); + return dim; } /** @@ -1009,21 +957,10 @@ public class BasicSliderUI extends SliderUI */ public Dimension getMinimumHorizontalSize() { - Insets insets = slider.getInsets(); - // Height is determined by the thumb, the ticks and the labels. - int height = getThumbSize().height; - - if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0) - height += getTickLength(); - - if (slider.getPaintLabels()) - height += getHeightOfTallestLabel(); - - height += insets.top + insets.bottom + focusInsets.top - + focusInsets.bottom; - - return new Dimension(36, height); + Dimension dim = UIManager.getDimension("Slider.minimumHorizontalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(36, 21); + return dim; } /** @@ -1034,19 +971,10 @@ public class BasicSliderUI extends SliderUI */ public Dimension getMinimumVerticalSize() { - Insets insets = slider.getInsets(); - int width = getThumbSize().width; - - if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0) - width += getTickLength(); - - if (slider.getPaintLabels()) - width += getWidthOfWidestLabel(); - - width += insets.left + insets.right + focusInsets.left + focusInsets.right; - - return new Dimension(width, 36); + Dimension dim = UIManager.getDimension("Slider.minimumVerticalSize"); + if (dim == null) // Just to be sure we mirror the default. + dim = new Dimension(21, 36); + return dim; } /** @@ -1060,10 +988,25 @@ public class BasicSliderUI extends SliderUI */ public Dimension getPreferredSize(JComponent c) { + recalculateIfInsetsChanged(); + Dimension dim; if (slider.getOrientation() == JSlider.HORIZONTAL) - return getPreferredHorizontalSize(); + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getPreferredHorizontalSize()); + dim.height = insetCache.top + insetCache.bottom; + dim.height += focusInsets.top + focusInsets.bottom; + dim.height += trackRect.height + tickRect.height + labelRect.height; + } else - return getPreferredVerticalSize(); + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getPreferredVerticalSize()); + dim.width = insetCache.left + insetCache.right; + dim.width += focusInsets.left + focusInsets.right; + dim.width += trackRect.width + tickRect.width + labelRect.width; + } + return dim; } /** @@ -1077,10 +1020,25 @@ public class BasicSliderUI extends SliderUI */ public Dimension getMinimumSize(JComponent c) { + recalculateIfInsetsChanged(); + Dimension dim; if (slider.getOrientation() == JSlider.HORIZONTAL) - return getMinimumHorizontalSize(); + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getMinimumHorizontalSize()); + dim.height = insetCache.top + insetCache.bottom; + dim.height += focusInsets.top + focusInsets.bottom; + dim.height += trackRect.height + tickRect.height + labelRect.height; + } else - return getMinimumVerticalSize(); + { + // Create copy here to protect the UIManager value. + dim = new Dimension(getMinimumVerticalSize()); + dim.width = insetCache.left + insetCache.right; + dim.width += focusInsets.left + focusInsets.right; + dim.width += trackRect.width + tickRect.width + labelRect.width; + } + return dim; } /** @@ -1093,40 +1051,12 @@ public class BasicSliderUI extends SliderUI */ public Dimension getMaximumSize(JComponent c) { - Insets insets = slider.getInsets(); + Dimension dim = getPreferredSize(c); if (slider.getOrientation() == JSlider.HORIZONTAL) - { - // Height is determined by the thumb, the ticks and the labels. - int height = getThumbSize().height; - - if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0) - height += getTickLength(); - - if (slider.getPaintLabels()) - height += getHeightOfTallestLabel(); - - height += insets.top + insets.bottom + focusInsets.top - + focusInsets.bottom; - - return new Dimension(32767, height); - } + dim.width = Short.MAX_VALUE; else - { - int width = getThumbSize().width; - - if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0) - width += getTickLength(); - - if (slider.getPaintLabels()) - width += getWidthOfWidestLabel(); - - width += insets.left + insets.right + focusInsets.left - + focusInsets.right; - - return new Dimension(width, 32767); - } + dim.height = Short.MAX_VALUE; + return dim; } /** @@ -1151,12 +1081,10 @@ public class BasicSliderUI extends SliderUI */ protected void calculateFocusRect() { - insetCache = slider.getInsets(); - focusRect = SwingUtilities.calculateInnerArea(slider, focusRect); - if (focusRect.width < 0) - focusRect.width = 0; - if (focusRect.height < 0) - focusRect.height = 0; + focusRect.x = insetCache.left; + focusRect.y = insetCache.top; + focusRect.width = slider.getWidth() - insetCache.left - insetCache.right; + focusRect.height = slider.getHeight() - insetCache.top - insetCache.bottom; } /** @@ -1181,13 +1109,8 @@ public class BasicSliderUI extends SliderUI contentRect.y = focusRect.y + focusInsets.top; contentRect.width = focusRect.width - focusInsets.left - focusInsets.right; - contentRect.height = focusRect.height - focusInsets.top - - focusInsets.bottom; - - if (contentRect.width < 0) - contentRect.width = 0; - if (contentRect.height < 0) - contentRect.height = 0; + contentRect.height = focusRect.height - focusInsets.top + - focusInsets.bottom; } /** @@ -1258,26 +1181,24 @@ public class BasicSliderUI extends SliderUI { if (slider.getOrientation() == JSlider.HORIZONTAL) { - trackRect.x = contentRect.x + trackBuffer; - int h = getThumbSize().height; - if (slider.getPaintTicks() && (slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0)) - h += getTickLength(); + int center = thumbRect.height; + if (slider.getPaintTicks()) + center += getTickLength(); if (slider.getPaintLabels()) - h += getHeightOfTallestLabel(); - trackRect.y = contentRect.y + (contentRect.height - h) / 2 - 1; + center += getHeightOfTallestLabel(); + trackRect.x = contentRect.x + trackBuffer; + trackRect.y = contentRect.y + (contentRect.height - center - 1) / 2; trackRect.width = contentRect.width - 2 * trackBuffer; trackRect.height = thumbRect.height; } else { - int w = getThumbSize().width; - if (slider.getPaintTicks() && (slider.getMajorTickSpacing() > 0 - || slider.getMinorTickSpacing() > 0)) - w += getTickLength(); + int center = thumbRect.width; + if (slider.getPaintTicks()) + center += getTickLength(); if (slider.getPaintLabels()) - w += getWidthOfWidestLabel(); - trackRect.x = contentRect.x + (contentRect.width - w) / 2 - 1; + center += getWidthOfWidestLabel(); + trackRect.x = contentRect.x + (contentRect.width - center - 1) / 2; trackRect.y = contentRect.y + trackBuffer; trackRect.width = thumbRect.width; trackRect.height = contentRect.height - 2 * trackBuffer; @@ -1310,28 +1231,28 @@ public class BasicSliderUI extends SliderUI tickRect.x = trackRect.x; tickRect.y = trackRect.y + trackRect.height; tickRect.width = trackRect.width; - tickRect.height = slider.getPaintTicks() ? getTickLength() : 0; + tickRect.height = getTickLength(); // this makes our Mauve tests pass...can't explain it! if (!slider.getPaintTicks()) - tickRect.y--; - - if (tickRect.y + tickRect.height > contentRect.y + contentRect.height) - tickRect.height = contentRect.y + contentRect.height - tickRect.y; + { + tickRect.y--; + tickRect.height = 0; + } } else { tickRect.x = trackRect.x + trackRect.width; tickRect.y = trackRect.y; - tickRect.width = slider.getPaintTicks() ? getTickLength() : 0; + tickRect.width = getTickLength(); tickRect.height = trackRect.height; // this makes our Mauve tests pass...can't explain it! if (!slider.getPaintTicks()) - tickRect.x--; - - if (tickRect.x + tickRect.width > contentRect.x + contentRect.width) - tickRect.width = contentRect.x + contentRect.width - tickRect.x; + { + tickRect.x--; + tickRect.width = 0; + } } } @@ -1345,33 +1266,35 @@ public class BasicSliderUI extends SliderUI { if (slider.getPaintLabels()) { - labelRect.x = contentRect.x; - labelRect.y = tickRect.y + tickRect.height - 1; - labelRect.width = contentRect.width; + labelRect.x = tickRect.x - trackBuffer; + labelRect.y = tickRect.y + tickRect.height; + labelRect.width = tickRect.width + trackBuffer * 2; + labelRect.height = getHeightOfTallestLabel(); } else { - labelRect.x = trackRect.x; + labelRect.x = tickRect.x; labelRect.y = tickRect.y + tickRect.height; - labelRect.width = trackRect.width; + labelRect.width = tickRect.width; + labelRect.height = 0; } - labelRect.height = getHeightOfTallestLabel(); } else { if (slider.getPaintLabels()) { - labelRect.x = tickRect.x + tickRect.width - 1; - labelRect.y = contentRect.y; - labelRect.height = contentRect.height; + labelRect.x = tickRect.x + tickRect.width; + labelRect.y = tickRect.y - trackBuffer; + labelRect.width = getWidthOfWidestLabel(); + labelRect.height = tickRect.height + trackBuffer * 2; } else { labelRect.x = tickRect.x + tickRect.width; - labelRect.y = trackRect.y; - labelRect.height = trackRect.height; + labelRect.y = tickRect.y; + labelRect.width = 0; + labelRect.height = tickRect.height; } - labelRect.width = getWidthOfWidestLabel(); } } @@ -1384,22 +1307,15 @@ public class BasicSliderUI extends SliderUI protected int getWidthOfWidestLabel() { int widest = 0; - Component label; - - if (slider.getLabelTable() == null) - return 0; - - Dimension pref; - for (Enumeration list = slider.getLabelTable().elements(); - list.hasMoreElements();) + Dictionary table = slider.getLabelTable(); + if (table != null) { - Object comp = list.nextElement(); - if (! (comp instanceof Component)) - continue; - label = (Component) comp; - pref = label.getPreferredSize(); - if (pref != null && pref.width > widest) - widest = pref.width; + for (Enumeration list = slider.getLabelTable().elements(); + list.hasMoreElements();) + { + Component label = (Component) list.nextElement(); + widest = Math.max(label.getPreferredSize().width, widest); + } } return widest; } @@ -1576,23 +1492,18 @@ public class BasicSliderUI extends SliderUI */ public void paint(Graphics g, JComponent c) { - // FIXME: Move this to propertyChangeEvent handler, when we get those. - leftToRightCache = slider.getComponentOrientation() - != ComponentOrientation.RIGHT_TO_LEFT; - // FIXME: This next line is only here because the above line is here. - calculateGeometry(); - - if (slider.getPaintTrack()) + recalculateIfInsetsChanged(); + recalculateIfOrientationChanged(); + if (slider.getPaintTrack() && hitClip(g, trackRect)) paintTrack(g); - if (slider.getPaintTicks()) + if (slider.getPaintTicks() && hitClip(g, tickRect)) paintTicks(g); - if (slider.getPaintLabels()) + if (slider.getPaintLabels() && hitClip(g, labelRect)) paintLabels(g); - - paintThumb(g); - - if (hasFocus) + if (slider.hasFocus() && hitClip(g, focusRect)) paintFocus(g); + if (hitClip(g, thumbRect)) + paintThumb(g); } /** @@ -1601,18 +1512,12 @@ public class BasicSliderUI extends SliderUI */ protected void recalculateIfInsetsChanged() { - // Examining a test program shows that either Sun calls private - // methods that we don't know about, or these don't do anything. - calculateFocusRect(); - - calculateContentRect(); - calculateThumbSize(); - calculateTrackBuffer(); - calculateTrackRect(); - calculateThumbLocation(); - - calculateTickRect(); - calculateLabelRect(); + Insets insets = slider.getInsets(); + if (! insets.equals(insetCache)) + { + insetCache = insets; + calculateGeometry(); + } } /** @@ -1863,45 +1768,30 @@ public class BasicSliderUI extends SliderUI */ public void paintLabels(Graphics g) { - if (slider.getLabelTable() != null) + Dictionary table = slider.getLabelTable(); + if (table != null) { - Dictionary table = slider.getLabelTable(); - Integer tmpKey; - Object key; - Object element; - Component label; - if (slider.getOrientation() == JSlider.HORIZONTAL) - { - for (Enumeration list = table.keys(); list.hasMoreElements();) - { - key = list.nextElement(); - if (! (key instanceof Integer)) - continue; - tmpKey = (Integer) key; - element = table.get(tmpKey); - // We won't paint them if they're not - // JLabels so continue anyway - if (! (element instanceof JLabel)) - continue; - label = (Component) element; - paintHorizontalLabel(g, tmpKey.intValue(), label); - } - } - else + int min = slider.getMinimum(); + int max = slider.getMaximum(); + for (Enumeration list = table.keys(); list.hasMoreElements();) { - for (Enumeration list = table.keys(); list.hasMoreElements();) + Integer key = (Integer) list.nextElement(); + int value = key.intValue(); + if (value >= min && value <= max) { - key = list.nextElement(); - if (! (key instanceof Integer)) - continue; - tmpKey = (Integer) key; - element = table.get(tmpKey); - // We won't paint them if they're not - // JLabels so continue anyway - if (! (element instanceof JLabel)) - continue; - label = (Component) element; - paintVerticalLabel(g, tmpKey.intValue(), label); + Component label = (Component) table.get(key); + if (slider.getOrientation() == JSlider.HORIZONTAL) + { + g.translate(0, labelRect.y); + paintHorizontalLabel(g, value, label); + g.translate(0, -labelRect.y); + } + else + { + g.translate(labelRect.x, 0); + paintVerticalLabel(g, value, label); + g.translate(-labelRect.x, 0); + } } } } @@ -1920,51 +1810,11 @@ public class BasicSliderUI extends SliderUI */ protected void paintHorizontalLabel(Graphics g, int value, Component label) { - // This relies on clipping working properly or we'll end up - // painting all over the place. If our preferred size is ignored, then - // the labels may not fit inside the slider's bounds. Rather than mucking - // with font sizes and possible icon sizes, we'll set the bounds for - // the label and let it get clipped. - Dimension dim = label.getPreferredSize(); - int w = (int) dim.getWidth(); - int h = (int) dim.getHeight(); - - int max = slider.getMaximum(); - int min = slider.getMinimum(); - - if (value > max || value < min) - return; - - // value - // | - // ------------ - // | | - // | | - // | | - // The label must move w/2 to the right to fit directly under the value. - int xpos = xPositionForValue(value) - w / 2; - int ypos = labelRect.y; - - // We want to center the label around the xPositionForValue - // So we use xpos - w / 2. However, if value is min and the label - // is large, we run the risk of going out of bounds. So we bring it back - // to 0 if it becomes negative. - if (xpos < 0) - xpos = 0; - - // If the label + starting x position is greater than - // the x space in the label rectangle, we reset it to the largest - // amount possible in the rectangle. This means ugliness. - if (xpos + w > labelRect.x + labelRect.width) - w = labelRect.x + labelRect.width - xpos; - - // If the label is too tall. We reset it to the height of the label - // rectangle. - if (h > labelRect.height) - h = labelRect.height; - - label.setBounds(xpos, ypos, w, h); - SwingUtilities.paintComponent(g, label, null, label.getBounds()); + int center = xPositionForValue(value); + int left = center - label.getPreferredSize().width / 2; + g.translate(left, 0); + label.paint(g); + g.translate(-left, 0); } /** @@ -1980,30 +1830,11 @@ public class BasicSliderUI extends SliderUI */ protected void paintVerticalLabel(Graphics g, int value, Component label) { - Dimension dim = label.getPreferredSize(); - int w = (int) dim.getWidth(); - int h = (int) dim.getHeight(); - - int max = slider.getMaximum(); - int min = slider.getMinimum(); - - if (value > max || value < min) - return; - - int xpos = labelRect.x; - int ypos = yPositionForValue(value) - h / 2; - - if (ypos < 0) - ypos = 0; - - if (ypos + h > labelRect.y + labelRect.height) - h = labelRect.y + labelRect.height - ypos; - - if (w > labelRect.width) - w = labelRect.width; - - label.setBounds(xpos, ypos, w, h); - SwingUtilities.paintComponent(g, label, null, label.getBounds()); + int center = yPositionForValue(value); + int top = center - label.getPreferredSize().height / 2; + g.translate(0, top); + label.paint(g); + g.translate(0, -top); } /** @@ -2118,8 +1949,11 @@ public class BasicSliderUI extends SliderUI */ public void setThumbLocation(int x, int y) { - thumbRect.x = x; - thumbRect.y = y; + Rectangle union = new Rectangle(thumbRect); + thumbRect.setLocation(x, y); + SwingUtilities.computeUnion(thumbRect.x, thumbRect.y, thumbRect.width, + thumbRect.height, union); + slider.repaint(union); } /** @@ -2197,21 +2031,21 @@ public class BasicSliderUI extends SliderUI */ protected int xPositionForValue(int value) { - double min = slider.getMinimum(); - if (value < min) - value = (int) min; - double max = slider.getMaximum(); - if (value > max) - value = (int) max; - double len = trackRect.width; - if ((max - min) <= 0.0) - return 0; - int xPos = (int) ((value - min) / (max - min) * len + 0.5); - - if (drawInverted()) - return trackRect.x + Math.max(trackRect.width - xPos - 1, 0); + int min = slider.getMinimum(); + int max = slider.getMaximum(); + int len = trackRect.width; + double range = max - min; + double pixPerVal = len / range; + int left = trackRect.x; + int right = left + trackRect.width - 1; + int xpos; + if (! drawInverted()) + xpos = left + (int) Math.round(pixPerVal * ((double) value - min)); else - return trackRect.x + Math.min(xPos, trackRect.width - 1); + xpos = right - (int) Math.round(pixPerVal * ((double) value - min)); + xpos = Math.max(left, xpos); + xpos = Math.min(right, xpos); + return xpos; } /** @@ -2225,22 +2059,21 @@ public class BasicSliderUI extends SliderUI */ protected int yPositionForValue(int value) { - double min = slider.getMinimum(); - if (value < min) - value = (int) min; - double max = slider.getMaximum(); - if (value > max) - value = (int) max; + int min = slider.getMinimum(); + int max = slider.getMaximum(); int len = trackRect.height; - if ((max - min) <= 0.0) - return 0; - - int yPos = (int) ((value - min) / (max - min) * len + 0.5); - + double range = max - min; + double pixPerVal = len / range; + int top = trackRect.y; + int bottom = top + trackRect.height - 1; + int ypos; if (! drawInverted()) - return trackRect.y + trackRect.height - Math.max(yPos, 1); + ypos = top + (int) Math.round(pixPerVal * ((double) max - value)); else - return trackRect.y + Math.min(yPos, trackRect.height - 1); + ypos = top + (int) Math.round(pixPerVal * ((double) value - min)); + ypos = Math.max(top, ypos); + ypos = Math.min(bottom, ypos); + return ypos; } /** @@ -2494,4 +2327,13 @@ public class BasicSliderUI extends SliderUI ); return map; } + + /** + * Small utility method to save me from typing the hell out of myself in + * paint(). + */ + private boolean hitClip(Graphics g, Rectangle r) + { + return g.hitClip(r.x, r.y, r.width, r.height); + } } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneDivider.java b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneDivider.java index 06d32984efb..95468caa972 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneDivider.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneDivider.java @@ -38,12 +38,15 @@ exception statement from your version. */ package javax.swing.plaf.basic; +import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; @@ -52,7 +55,7 @@ import java.beans.PropertyChangeListener; import javax.swing.JButton; import javax.swing.JSplitPane; -import javax.swing.SwingConstants; +import javax.swing.UIManager; import javax.swing.border.Border; /** @@ -72,6 +75,207 @@ public class BasicSplitPaneDivider extends Container implements PropertyChangeListener { /** + * The buttons used as one touch buttons. + */ + private class BasicOneTouchButton + extends JButton + { + /** + * Denotes a left button. + */ + static final int LEFT = 0; + + /** + * Denotes a right button. + */ + static final int RIGHT = 1; + + /** + * The x points for the arrow. + */ + private int[] xpoints; + + /** + * The y points for the arrow. + */ + private int[] ypoints; + + /** + * Either LEFT or RIGHT. + */ + private int direction; + + /** + * Creates a new instance. + * + * @param dir either LEFT or RIGHT + */ + BasicOneTouchButton(int dir) + { + direction = dir; + xpoints = new int[3]; + ypoints = new int[3]; + } + + /** + * Never allow borders. + */ + public void setBorder(Border b) + { + } + + /** + * Never allow focus traversal. + */ + public boolean isFocusTraversable() + { + return false; + } + + /** + * Paints the one touch button. + */ + public void paint(Graphics g) + { + if (splitPane != null) + { + // Fill background. + g.setColor(splitPane.getBackground()); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Draw arrow. + int size; + if (direction == LEFT) + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + size = Math.min(getHeight(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size / 2; + xpoints[2] = size; + ypoints[0] = size; + ypoints[1] = 0; + ypoints[2] = size; + } + else + { + size = Math.min(getWidth(), ONE_TOUCH_SIZE); + xpoints[0] = size; + xpoints[1] = 0; + xpoints[2] = size; + ypoints[0] = 0; + ypoints[1] = size / 2; + ypoints[2] = size; + } + } + else + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + size = Math.min(getHeight(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size / 2; + xpoints[2] = size; + ypoints[0] = 0; + ypoints[1] = size; + ypoints[2] = 0; + } + else + { + size = Math.min(getWidth(), ONE_TOUCH_SIZE); + xpoints[0] = 0; + xpoints[1] = size; + xpoints[2] = 0; + ypoints[0] = 0; + ypoints[1] = size / 2; + ypoints[2] = size; + } + } + g.setColor(Color.BLACK); + g.fillPolygon(xpoints, ypoints, 3); + } + } + } + + /** + * Listens for actions on the one touch buttons. + */ + private class OneTouchAction + implements ActionListener + { + + public void actionPerformed(ActionEvent ev) + { + Insets insets = splitPane.getInsets(); + int lastLoc = splitPane.getLastDividerLocation(); + int currentLoc = splitPaneUI.getDividerLocation(splitPane); + int newLoc; + + if (ev.getSource() == leftButton) + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + if (currentLoc + >= splitPane.getHeight() - insets.bottom - getHeight()) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = insets.top; + } + } + else + { + if (currentLoc + >= splitPane.getWidth() - insets.right - getWidth()) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = insets.left; + } + } + } + else + { + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + if (currentLoc == insets.top) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = splitPane.getHeight() - insets.top - getHeight(); + } + } + else + { + if (currentLoc == insets.left) + { + newLoc = Math.min(splitPane.getMaximumDividerLocation(), + lastLoc); + } + else + { + newLoc = splitPane.getWidth() - insets.left - getWidth(); + } + } + } + if (currentLoc != newLoc) + { + splitPane.setDividerLocation(newLoc); + splitPane.setLastDividerLocation(currentLoc); + } + } + } + + /** * Determined using the <code>serialver</code> tool of Apple/Sun JDK 1.3.1 * on MacOS X 10.1.5. */ @@ -161,6 +365,14 @@ public class BasicSplitPaneDivider extends Container transient int currentDividerLocation = 1; /** + * Indicates if the ont touch buttons are laid out centered or at the + * top/left. + * + * Package private to avoid accessor method. + */ + boolean centerOneTouchButtons; + + /** * Constructs a new divider. * * @param ui the UI delegate of the enclosing <code>JSplitPane</code>. @@ -170,6 +382,8 @@ public class BasicSplitPaneDivider extends Container setLayout(new DividerLayout()); setBasicSplitPaneUI(ui); setDividerSize(splitPane.getDividerSize()); + centerOneTouchButtons = + UIManager.getBoolean("SplitPane.centerOneTouchButtons"); } /** @@ -202,7 +416,8 @@ public class BasicSplitPaneDivider extends Container addMouseMotionListener(mouseHandler); hiddenDivider = splitPaneUI.getNonContinuousLayoutDivider(); orientation = splitPane.getOrientation(); - oneTouchExpandableChanged(); + if (splitPane.isOneTouchExpandable()) + oneTouchExpandableChanged(); } } @@ -293,7 +508,12 @@ public class BasicSplitPaneDivider extends Container */ public Dimension getPreferredSize() { - return getLayout().preferredLayoutSize(this); + Dimension d; + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + d = new Dimension(getDividerSize(), 1); + else + d = new Dimension(1, getDividerSize()); + return d; } /** @@ -320,11 +540,9 @@ public class BasicSplitPaneDivider extends Container else if (e.getPropertyName().equals(JSplitPane.ORIENTATION_PROPERTY)) { orientation = splitPane.getOrientation(); - if (splitPane.isOneTouchExpandable()) - { - layout(); - repaint(); - } + invalidate(); + if (splitPane != null) + splitPane.revalidate(); } else if (e.getPropertyName().equals(JSplitPane.DIVIDER_SIZE_PROPERTY)) dividerSize = splitPane.getDividerSize(); @@ -345,11 +563,6 @@ public class BasicSplitPaneDivider extends Container dividerSize = getSize(); border.paintBorder(this, g, 0, 0, dividerSize.width, dividerSize.height); } - if (splitPane.isOneTouchExpandable()) - { - ((BasicArrowButton) rightButton).paint(g); - ((BasicArrowButton) leftButton).paint(g); - } } /** @@ -361,31 +574,23 @@ public class BasicSplitPaneDivider extends Container if (splitPane.isOneTouchExpandable()) { leftButton = createLeftOneTouchButton(); - rightButton = createRightOneTouchButton(); - add(leftButton); - add(rightButton); + if (leftButton != null) + leftButton.addActionListener(new OneTouchAction()); - leftButton.addMouseListener(mouseHandler); - rightButton.addMouseListener(mouseHandler); + rightButton = createRightOneTouchButton(); + if (rightButton != null) + rightButton.addActionListener(new OneTouchAction()); - // Set it to 1. - currentDividerLocation = 1; - } - else - { + // Only add them when both are non-null. if (leftButton != null && rightButton != null) - { - leftButton.removeMouseListener(mouseHandler); - rightButton.removeMouseListener(mouseHandler); - - remove(leftButton); - remove(rightButton); - leftButton = null; - rightButton = null; + { + add(leftButton); + add(rightButton); } } - layout(); - repaint(); + invalidate(); + if (splitPane != null) + splitPane.revalidate(); } /** @@ -396,12 +601,9 @@ public class BasicSplitPaneDivider extends Container */ protected JButton createLeftOneTouchButton() { - int dir = SwingConstants.WEST; - if (orientation == JSplitPane.VERTICAL_SPLIT) - dir = SwingConstants.NORTH; - JButton button = new BasicArrowButton(dir); - button.setBorder(null); - + JButton button = new BasicOneTouchButton(BasicOneTouchButton.LEFT); + button.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + button.setRequestFocusEnabled(false); return button; } @@ -413,11 +615,9 @@ public class BasicSplitPaneDivider extends Container */ protected JButton createRightOneTouchButton() { - int dir = SwingConstants.EAST; - if (orientation == JSplitPane.VERTICAL_SPLIT) - dir = SwingConstants.SOUTH; - JButton button = new BasicArrowButton(dir); - button.setBorder(null); + JButton button = new BasicOneTouchButton(BasicOneTouchButton.RIGHT); + button.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + button.setRequestFocusEnabled(false); return button; } @@ -521,25 +721,6 @@ public class BasicSplitPaneDivider extends Container */ public void mousePressed(MouseEvent e) { - if (splitPane.isOneTouchExpandable()) - { - if (e.getSource() == leftButton) - { - currentDividerLocation--; - if (currentDividerLocation < 0) - currentDividerLocation = 0; - moveDividerTo(currentDividerLocation); - return; - } - else if (e.getSource() == rightButton) - { - currentDividerLocation++; - if (currentDividerLocation > 2) - currentDividerLocation = 2; - moveDividerTo(currentDividerLocation); - return; - } - } isDragging = true; currentDividerLocation = 1; if (orientation == JSplitPane.HORIZONTAL_SPLIT) @@ -797,10 +978,64 @@ public class BasicSplitPaneDivider extends Container */ public void layoutContainer(Container c) { - if (splitPane.isOneTouchExpandable()) + if (leftButton != null && rightButton != null + && c == BasicSplitPaneDivider.this) { - changeButtonOrientation(); - positionButtons(); + if (splitPane.isOneTouchExpandable()) + { + Insets insets = getInsets(); + if (orientation == JSplitPane.HORIZONTAL_SPLIT) + { + int size = getWidth() - insets.left - insets.right; + size = Math.max(size, 0); + size = Math.min(size, ONE_TOUCH_SIZE); + int x, y; + if (centerOneTouchButtons) + { + y = insets.top; + x = (getWidth() - size) / 2; + } + else + { + x = insets.left; + y = 0; + } + + leftButton.setBounds(x, y + ONE_TOUCH_OFFSET, size, + size * 2); + rightButton.setBounds(x, y + ONE_TOUCH_OFFSET + + ONE_TOUCH_SIZE * 2, size, size * 2); + } + else + { + int size = getHeight() - insets.top - insets.bottom; + size = Math.max(size, 0); + size = Math.min(size, ONE_TOUCH_SIZE); + int x, y; + if (centerOneTouchButtons) + { + x = insets.left; + y = (getHeight() - size) / 2; + } + else + { + x = 0; + y = insets.top; + } + leftButton.setBounds(x + ONE_TOUCH_OFFSET, y, size * 2, + size); + rightButton.setBounds(x + ONE_TOUCH_OFFSET + + ONE_TOUCH_SIZE * 2, y, size * 2, + size); + } + } + else + { + // The JDK sets this bounds for disabled one touch buttons, so + // do we. + leftButton.setBounds(-5, -5, 1, 1); + rightButton.setBounds(-5, -5, 1, 1); + } } } @@ -838,50 +1073,5 @@ public class BasicSplitPaneDivider extends Container // Do nothing. } - /** - * This method changes the button orientation when the orientation of the - * SplitPane changes. - */ - private void changeButtonOrientation() - { - if (orientation == JSplitPane.HORIZONTAL_SPLIT) - { - ((BasicArrowButton) rightButton).setDirection(SwingConstants.EAST); - ((BasicArrowButton) leftButton).setDirection(SwingConstants.WEST); - } - else - { - ((BasicArrowButton) rightButton).setDirection(SwingConstants.SOUTH); - ((BasicArrowButton) leftButton).setDirection(SwingConstants.NORTH); - } - } - - /** - * This method sizes and positions the buttons. - */ - private void positionButtons() - { - int w = 0; - int h = 0; - if (orientation == JSplitPane.HORIZONTAL_SPLIT) - { - rightButton.setLocation(ONE_TOUCH_OFFSET, ONE_TOUCH_OFFSET); - leftButton.setLocation(ONE_TOUCH_OFFSET, - ONE_TOUCH_OFFSET + 2 * ONE_TOUCH_SIZE); - w = dividerSize - 2 * ONE_TOUCH_OFFSET; - h = 2 * ONE_TOUCH_SIZE; - } - else - { - leftButton.setLocation(ONE_TOUCH_OFFSET, ONE_TOUCH_OFFSET); - rightButton.setLocation(ONE_TOUCH_OFFSET + 2 * ONE_TOUCH_SIZE, - ONE_TOUCH_OFFSET); - h = dividerSize - 2 * ONE_TOUCH_OFFSET; - w = 2 * ONE_TOUCH_SIZE; - } - Dimension dims = new Dimension(w, h); - leftButton.setSize(dims); - rightButton.setSize(dims); - } } } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneUI.java index 2d595597424..b7cc425482d 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicSplitPaneUI.java @@ -63,6 +63,7 @@ import javax.swing.JSlider; import javax.swing.JSplitPane; import javax.swing.KeyStroke; import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ActionMapUIResource; @@ -107,13 +108,34 @@ public class BasicSplitPaneUI extends SplitPaneUI protected int[] sizes = new int[3]; /** + * This is used to determine if we are vertical or horizontal layout. + * In the JDK, the BasicVerticalLayoutManager seems to have no more + * methods implemented (as of JDK5), so we keep this state here. + */ + private int axis; + + /** * Creates a new instance. This is package private because the reference * implementation has no public constructor either. Still, we need to * call it from BasicVerticalLayoutManager. */ BasicHorizontalLayoutManager() { - // Nothing to do here. + this(SwingConstants.HORIZONTAL); + } + + /** + * Creates a new instance for a specified axis. This is provided for + * compatibility, since the BasicVerticalLayoutManager seems to have + * no more implementation in the RI, according to the specs. So + * we handle all the axis specific stuff here. + * + * @param a the axis, either SwingConstants#HORIZONTAL, + * or SwingConstants#VERTICAL + */ + BasicHorizontalLayoutManager(int a) + { + axis = a; } /** @@ -167,7 +189,12 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getAvailableSize(Dimension containerSize, Insets insets) { - return containerSize.width - insets.left - insets.right; + int size; + if (axis == SwingConstants.HORIZONTAL) + size = containerSize.width - insets.left - insets.right; + else + size = containerSize.height - insets.top - insets.bottom; + return size; } /** @@ -180,9 +207,15 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getInitialLocation(Insets insets) { + int loc = 0; if (insets != null) - return insets.left; - return 0; + { + if (axis == SwingConstants.HORIZONTAL) + loc = insets.left; + else + loc = insets.top; + } + return loc; } /** @@ -195,7 +228,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public float getLayoutAlignmentX(Container target) { - return target.getAlignmentX(); + return 0.0f; } /** @@ -208,7 +241,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public float getLayoutAlignmentY(Container target) { - return target.getAlignmentY(); + return 0.0f; } /** @@ -220,10 +253,19 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getPreferredSizeOfComponent(Component c) { + int size = 0; Dimension dims = c.getPreferredSize(); - if (dims != null) - return dims.width; - return 0; + if (axis == SwingConstants.HORIZONTAL) + { + if (dims != null) + size = dims.width; + } + else + { + if (dims != null) + size = dims.height; + } + return size; } /** @@ -235,7 +277,12 @@ public class BasicSplitPaneUI extends SplitPaneUI */ protected int getSizeOfComponent(Component c) { - return c.getWidth(); + int size; + if (axis == SwingConstants.HORIZONTAL) + size = c.getHeight(); + else + size = c.getWidth(); + return size; } /** @@ -273,8 +320,17 @@ public class BasicSplitPaneUI extends SplitPaneUI Dimension dims = split.getSize(); int loc = getInitialLocation(insets); int available = getAvailableSize(dims, insets); - sizes[0] = getDividerLocation(split) - loc; + sizes[0] = split.getDividerLocation(); sizes[1] = available - sizes[0] - sizes[2]; + + // According to a Mauve test we only honour the minimum + // size of the components, when the dividerLocation hasn't + // been excplicitly set. + if (! dividerLocationSet) + { + sizes[0] = Math.max(sizes[0], minimumSizeOfComponent(0)); + sizes[1] = Math.max(sizes[1], minimumSizeOfComponent(1)); + } // The size of the divider won't change. // Layout component#1. @@ -313,27 +369,30 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public Dimension minimumLayoutSize(Container target) { + Dimension dim = new Dimension(); if (target instanceof JSplitPane) { - JSplitPane split = (JSplitPane) target; - Insets insets = target.getInsets(); - - int height = 0; - int width = 0; + int primary = 0; + int secondary = 0; for (int i = 0; i < components.length; i++) { - if (components[i] == null) - continue; - Dimension dims = components[i].getMinimumSize(); - if (dims != null) + if (components[i] != null) { - width += dims.width; - height = Math.max(height, dims.height); + Dimension dims = components[i].getMinimumSize(); + primary += axis == SwingConstants.HORIZONTAL ? dims.width + : dims.height; + int sec = axis == SwingConstants.HORIZONTAL ? dims.height + : dims.width; + secondary = Math.max(sec, secondary); } } - return new Dimension(width, height); + int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; + int height = axis == SwingConstants.VERTICAL ? secondary : primary; + + Insets i = splitPane.getInsets(); + dim.setSize(width + i.left + i.right, height + i.top + i.bottom); } - return null; + return dim; } /** @@ -347,28 +406,30 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public Dimension preferredLayoutSize(Container target) { + Dimension dim = new Dimension(); if (target instanceof JSplitPane) { - JSplitPane split = (JSplitPane) target; - Insets insets = target.getInsets(); - - int height = 0; - int width = 0; + int primary = 0; + int secondary = 0; for (int i = 0; i < components.length; i++) { - if (components[i] == null) - continue; - Dimension dims = components[i].getPreferredSize(); - if (dims != null) + if (components[i] != null) { - width += dims.width; - if (!(components[i] instanceof BasicSplitPaneDivider)) - height = Math.max(height, dims.height); + Dimension dims = components[i].getPreferredSize(); + primary += axis == SwingConstants.HORIZONTAL ? dims.width + : dims.height; + int sec = axis == SwingConstants.HORIZONTAL ? dims.height + : dims.width; + secondary = Math.max(sec, secondary); } } - return new Dimension(width, height); + int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; + int height = axis == SwingConstants.VERTICAL ? secondary : primary; + + Insets i = splitPane.getInsets(); + dim.setSize(width + i.left + i.right, height + i.top + i.bottom); } - return null; + return dim; } /** @@ -406,8 +467,6 @@ public class BasicSplitPaneUI extends SplitPaneUI { for (int i = 0; i < components.length; i++) resetSizeAt(i); - setDividerLocation(splitPane, - getInitialLocation(splitPane.getInsets()) + sizes[0]); } /** @@ -425,11 +484,23 @@ public class BasicSplitPaneUI extends SplitPaneUI protected void setComponentToSize(Component c, int size, int location, Insets insets, Dimension containerSize) { - int w = size; - int h = containerSize.height - insets.top - insets.bottom; - int x = location; - int y = insets.top; - c.setBounds(x, y, w, h); + if (insets != null) + { + if (axis == SwingConstants.HORIZONTAL) + c.setBounds(location, insets.top, size, + containerSize.height - insets.top - insets.bottom); + else + c.setBounds(insets.left, location, + containerSize.width - insets.left - insets.right, + size); + } + else + { + if (axis == SwingConstants.HORIZONTAL) + c.setBounds(location, 0, size, containerSize.height); + else + c.setBounds(0, location, containerSize.width, size); + } } /** @@ -462,7 +533,6 @@ public class BasicSplitPaneUI extends SplitPaneUI resetSizeAt(1); } components[2] = divider; - resetSizeAt(2); } /** @@ -485,10 +555,13 @@ public class BasicSplitPaneUI extends SplitPaneUI int minimumSizeOfComponent(int index) { Dimension dims = components[index].getMinimumSize(); + int size = 0; if (dims != null) - return dims.width; - else - return 0; + if (axis == SwingConstants.HORIZONTAL) + size = dims.width; + else + size = dims.height; + return size; } } //end BasicHorizontalLayoutManager @@ -504,163 +577,11 @@ public class BasicSplitPaneUI extends SplitPaneUI extends BasicHorizontalLayoutManager { /** - * This method returns the height of the container minus the top and - * bottom inset. - * - * @param containerSize The size of the container. - * @param insets The insets of the container. - * - * @return The height minus top and bottom inset. - */ - protected int getAvailableSize(Dimension containerSize, Insets insets) - { - return containerSize.height - insets.top - insets.bottom; - } - - /** - * This method returns the top inset. - * - * @param insets The Insets to use. - * - * @return The top inset. - */ - protected int getInitialLocation(Insets insets) - { - return insets.top; - } - - /** - * This method returns the preferred height of the component. - * - * @param c The component to measure. - * - * @return The preferred height of the component. - */ - protected int getPreferredSizeOfComponent(Component c) - { - Dimension dims = c.getPreferredSize(); - if (dims != null) - return dims.height; - return 0; - } - - /** - * This method returns the current height of the component. - * - * @param c The component to measure. - * - * @return The current height of the component. - */ - protected int getSizeOfComponent(Component c) - { - return c.getHeight(); - } - - /** - * This method returns the minimum layout size. The minimum height is the - * sum of all the components' minimum heights. The minimum width is the - * maximum of all the components' minimum widths. - * - * @param container The container to measure. - * - * @return The minimum size. + * Creates a new instance. */ - public Dimension minimumLayoutSize(Container container) + public BasicVerticalLayoutManager() { - if (container instanceof JSplitPane) - { - JSplitPane split = (JSplitPane) container; - Insets insets = container.getInsets(); - - int height = 0; - int width = 0; - for (int i = 0; i < components.length; i++) - { - if (components[i] == null) - continue; - Dimension dims = components[i].getMinimumSize(); - if (dims != null) - { - height += dims.height; - width = Math.max(width, dims.width); - } - } - return new Dimension(width, height); - } - return null; - } - - /** - * This method returns the preferred layout size. The preferred height is - * the sum of all the components' preferred heights. The preferred width - * is the maximum of all the components' preferred widths. - * - * @param container The container to measure. - * - * @return The preferred size. - */ - public Dimension preferredLayoutSize(Container container) - { - if (container instanceof JSplitPane) - { - JSplitPane split = (JSplitPane) container; - Insets insets = container.getInsets(); - - int height = 0; - int width = 0; - for (int i = 0; i < components.length; i++) - { - if (components[i] == null) - continue; - Dimension dims = components[i].getPreferredSize(); - if (dims != null) - { - height += dims.height; - width = Math.max(width, dims.width); - } - } - return new Dimension(width, height); - } - return null; - } - - /** - * This method sets the bounds of the given component. The y coordinate is - * the location given. The x coordinate is the left inset. The height is - * the size given. The width is the container size minus the left and - * right inset. - * - * @param c The component to set bounds for. - * @param size The height. - * @param location The y coordinate. - * @param insets The insets to use. - * @param containerSize The container's size. - */ - protected void setComponentToSize(Component c, int size, int location, - Insets insets, Dimension containerSize) - { - int y = location; - int x = insets.left; - int h = size; - int w = containerSize.width - insets.left - insets.right; - c.setBounds(x, y, w, h); - } - - /** - * This method returns the minimum height of the component at the given - * index. - * - * @param index The index of the component to check. - * - * @return The minimum height of the given component. - */ - int minimumSizeOfComponent(int index) - { - Dimension dims = components[index].getMinimumSize(); - if (dims != null) - return dims.height; - else - return 0; + super(SwingConstants.VERTICAL); } } @@ -941,7 +862,13 @@ public class BasicSplitPaneUI extends SplitPaneUI /** The JSplitPane that this UI draws. */ protected JSplitPane splitPane; - private int dividerLocation; + /** + * True, when setDividerLocation() has been called at least + * once on the JSplitPane, false otherwise. + * + * This is package private to avoid a synthetic accessor method. + */ + boolean dividerLocationSet; /** * Creates a new BasicSplitPaneUI object. @@ -973,6 +900,7 @@ public class BasicSplitPaneUI extends SplitPaneUI if (c instanceof JSplitPane) { splitPane = (JSplitPane) c; + dividerLocationSet = false; installDefaults(); installListeners(); installKeyboardActions(); @@ -990,6 +918,7 @@ public class BasicSplitPaneUI extends SplitPaneUI uninstallListeners(); uninstallDefaults(); + dividerLocationSet = false; splitPane = null; } @@ -1007,8 +936,10 @@ public class BasicSplitPaneUI extends SplitPaneUI nonContinuousLayoutDivider = createDefaultNonContinuousLayoutDivider(); splitPane.add(divider, JSplitPane.DIVIDER); - // There is no need to add the nonContinuousLayoutDivider - splitPane.setDividerSize(UIManager.getInt("SplitPane.dividerSize")); + // There is no need to add the nonContinuousLayoutDivider. + dividerSize = UIManager.getInt("SplitPane.dividerSize"); + splitPane.setDividerSize(dividerSize); + divider.setDividerSize(dividerSize); splitPane.setOpaque(true); } @@ -1136,8 +1067,10 @@ public class BasicSplitPaneUI extends SplitPaneUI new AbstractAction("negativeIncrement") { public void actionPerformed(ActionEvent event) { - setDividerLocation(splitPane, Math.max(dividerLocation - - KEYBOARD_DIVIDER_MOVE_OFFSET, 0)); + int oldLoc = splitPane.getDividerLocation(); + int newLoc = + Math.max(oldLoc - KEYBOARD_DIVIDER_MOVE_OFFSET, 0); + splitPane.setDividerLocation(newLoc); } } ); @@ -1145,8 +1078,10 @@ public class BasicSplitPaneUI extends SplitPaneUI new AbstractAction("positiveIncrement") { public void actionPerformed(ActionEvent event) { - setDividerLocation(splitPane, dividerLocation - + KEYBOARD_DIVIDER_MOVE_OFFSET); + int oldLoc = splitPane.getDividerLocation(); + int newLoc = + Math.max(oldLoc + KEYBOARD_DIVIDER_MOVE_OFFSET, 0); + splitPane.setDividerLocation(newLoc); } } ); @@ -1436,7 +1371,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public void setDividerLocation(JSplitPane jc, int location) { - dividerLocation = location; + dividerLocationSet = true; splitPane.revalidate(); splitPane.repaint(); } @@ -1450,7 +1385,12 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public int getDividerLocation(JSplitPane jc) { - return dividerLocation; + int loc; + if (jc.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) + loc = divider.getX(); + else + loc = divider.getY(); + return loc; } /** @@ -1523,7 +1463,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public Dimension getPreferredSize(JComponent jc) { - return layoutManager.preferredLayoutSize((Container) jc); + return layoutManager.preferredLayoutSize(jc); } /** @@ -1535,7 +1475,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public Dimension getMinimumSize(JComponent jc) { - return layoutManager.minimumLayoutSize((Container) jc); + return layoutManager.minimumLayoutSize(jc); } /** @@ -1547,7 +1487,7 @@ public class BasicSplitPaneUI extends SplitPaneUI */ public Dimension getMaximumSize(JComponent jc) { - return layoutManager.maximumLayoutSize((Container) jc); + return layoutManager.maximumLayoutSize(jc); } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTabbedPaneUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTabbedPaneUI.java index 11f25167d21..0d1fa1eed9b 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicTabbedPaneUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTabbedPaneUI.java @@ -76,7 +76,6 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; -import javax.swing.plaf.PanelUI; import javax.swing.plaf.TabbedPaneUI; import javax.swing.plaf.UIResource; import javax.swing.text.View; @@ -252,7 +251,16 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { public void mouseReleased(MouseEvent e) { - // Nothing to do here. + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } } /** @@ -264,6 +272,16 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants public void mousePressed(MouseEvent e) { Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + int placement = tabPane.getTabPlacement(); if (s == incrButton) @@ -298,47 +316,61 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if(!decrButton.isEnabled()) return; - // The scroll location may be zero but the offset - // greater than zero because of an adjustement to - // make a partially visible tab completely visible. - if (currentScrollLocation > 0) - currentScrollLocation--; - - // 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; + // 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--; - for (int i = 0; i < currentScrollLocation; i++) - currentScrollOffset += rects[i].height; - } + // 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(); + updateViewPosition(); + updateButtons(); - tabPane.repaint(); - } else if (tabPane.isEnabled()) + tabPane.repaint(); + } + else if (tabPane.isEnabled()) { int index = tabForCoordinate(tabPane, e.getX(), e.getY()); + if (!tabPane.isEnabledAt(index)) + return; + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT && s == panel) + { scrollTab(index, placement); + + tabPane.setSelectedIndex(index); + tabPane.repaint(); + } + else + { + tabPane.setSelectedIndex(index); + tabPane.revalidate(); + tabPane.repaint(); + } - tabPane.setSelectedIndex(index); } } @@ -347,11 +379,22 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * Receives notification when the mouse pointer has entered the tabbed * pane. * - * @param ev the mouse event + * @param e the mouse event */ - public void mouseEntered(MouseEvent ev) + public void mouseEntered(MouseEvent e) { - int tabIndex = tabForCoordinate(tabPane, ev.getX(), ev.getY()); + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + + int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); setRolloverTab(tabIndex); } @@ -359,10 +402,21 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * Receives notification when the mouse pointer has exited the tabbed * pane. * - * @param ev the mouse event + * @param e the mouse event */ - public void mouseExited(MouseEvent ev) + public void mouseExited(MouseEvent e) { + Object s = e.getSource(); + + // Event may originate from the viewport in + // SCROLL_TAB_LAYOUT mode. It is redisptached + // through the tabbed pane then. + if (tabPane != e.getSource()) + { + redispatchEvent(e); + e.setSource(s); + } + setRolloverTab(-1); } @@ -374,9 +428,37 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void mouseMoved(MouseEvent ev) { + Object s = ev.getSource(); + + if (tabPane != ev.getSource()) + { + ev.setSource(tabPane); + tabPane.dispatchEvent(ev); + + ev.setSource(s); + } + int tabIndex = tabForCoordinate(tabPane, ev.getX(), ev.getY()); setRolloverTab(tabIndex); } + + /** Modifies the mouse event to originate from + * the tabbed pane and redispatches it. + * + * @param me + */ + void redispatchEvent(MouseEvent me) + { + me.setSource(tabPane); + Point viewPos = viewport.getViewPosition(); + viewPos.x -= viewport.getX(); + viewPos.y -= viewport.getY(); + me.translatePoint(-viewPos.x, -viewPos.y); + tabPane.dispatchEvent(me); + + me.translatePoint(viewPos.x, viewPos.y); + } + } /** @@ -396,20 +478,56 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void propertyChange(PropertyChangeEvent e) { - if (e.getPropertyName().equals("tabLayoutPolicy")) + out: { - currentScrollLocation = currentScrollOffset = 0; - - layoutManager = createLayoutManager(); - - tabPane.setLayout(layoutManager); - } - else if (e.getPropertyName().equals("tabPlacement") - && tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - incrButton = createIncreaseButton(); - decrButton = createDecreaseButton(); + if (e.getPropertyName().equals("tabLayoutPolicy")) + { + currentScrollLocation = currentScrollOffset = 0; + + layoutManager = createLayoutManager(); + + tabPane.setLayout(layoutManager); + } + else if (e.getPropertyName().equals("tabPlacement") + && tabPane.getTabLayoutPolicy() + == JTabbedPane.SCROLL_TAB_LAYOUT) + { + incrButton = createIncreaseButton(); + decrButton = createDecreaseButton(); + + // If the tab placement value was changed of a tabbed pane + // in SCROLL_TAB_LAYOUT mode we investigate the change to + // implement the following behavior which was observed in + // the RI: + // The scrolling offset will be reset if we change to + // a direction which is orthogonal to the current + // direction and stays the same if it is parallel. + + int oldPlacement = ((Integer) e.getOldValue()).intValue(); + int newPlacement = ((Integer) e.getNewValue()).intValue(); + switch (newPlacement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + if (oldPlacement == JTabbedPane.TOP + || oldPlacement == JTabbedPane.BOTTOM) + break out; + + currentScrollOffset = getTabAreaInsets(newPlacement).left; + break; + default: + if (oldPlacement == JTabbedPane.LEFT + || oldPlacement == JTabbedPane.RIGHT) + break out; + + currentScrollOffset = getTabAreaInsets(newPlacement).top; + } + + updateViewPosition(); + updateButtons(); + } } + tabPane.revalidate(); tabPane.repaint(); } @@ -784,6 +902,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants default: tabAreaHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); + compX = insets.left + contentBorderInsets.left; compY = tabAreaHeight + insets.top + contentBorderInsets.top; } @@ -838,83 +957,51 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected void normalizeTabRuns(int tabPlacement, int tabCount, int start, int max) { - Insets tabAreaInsets = getTabAreaInsets(tabPlacement); - if (tabPlacement == SwingUtilities.TOP - || tabPlacement == SwingUtilities.BOTTOM) + boolean horizontal = tabPlacement == TOP || tabPlacement == BOTTOM; + int currentRun = runCount - 1; + double weight = 1.25; + for (boolean adjust = true; adjust == true;) { - // 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++) + int last = lastTabInRun(tabCount, currentRun); + int prevLast = lastTabInRun(tabCount, currentRun - 1); + int end; + int prevLength; + if (horizontal) { - Rectangle currRun = rects[lastTabInRun(tabCount, 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) - { - tabRuns[i]--; - spaceInNext += currRun.width; - spaceInCurr -= currRun.width; - currRun = rects[lastTabInRun(tabCount, i)]; - diffNow = spaceInCurr - spaceInNext; - diffLater = (spaceInCurr - currRun.width) - - (spaceInNext + currRun.width); - } - - // 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; - currX += rects[j].width; - } + end = rects[last].x + rects[last].width; + prevLength = (int) (maxTabWidth * weight); } - } - else - { - for (int i = 1; i < runCount; i++) + else { - Rectangle currRun = rects[lastTabInRun(tabCount, i)]; - Rectangle nextRun = rects[lastTabInRun(tabCount, - getNextTabRun(i))]; - int spaceInCurr = currRun.y + currRun.height; - int spaceInNext = nextRun.y + nextRun.height; - - int diffNow = spaceInCurr - spaceInNext; - int diffLater = (spaceInCurr - currRun.height) - - (spaceInNext + currRun.height); - while (Math.abs(diffLater) < Math.abs(diffNow) - && spaceInNext + currRun.height < max) - { - tabRuns[i]--; - spaceInNext += currRun.height; - spaceInCurr -= currRun.height; - currRun = rects[lastTabInRun(tabCount, i)]; - diffNow = spaceInCurr - spaceInNext; - diffLater = (spaceInCurr - currRun.height) - - (spaceInNext + currRun.height); - } - - // 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++) + end = rects[last].y + rects[last].height; + prevLength = (int) (maxTabWidth * weight * 2); + } + if (max - end > prevLength) + { + tabRuns[currentRun] = prevLast; + if (horizontal) + rects[prevLast].x = start; + else + rects[prevLast].y = start; + for (int i = prevLast + 1; i <= last; i++) { - rects[j].y = currY; - currY += rects[j].height; + if (horizontal) + rects[i].x = rects[i - 1].x + rects[i - 1].width; + else + rects[i].y = rects[i - 1].y + rects[i - 1].height; } } + else if (currentRun == runCount - 1) + adjust = false; + if (currentRun - 1 > 0) + currentRun -= 1; + else + { + // Check again, but with higher ratio to avoid + // clogging up the last run. + currentRun = runCount - 1; + weight += 0.25; + } } } @@ -1325,7 +1412,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { super.layoutContainer(pane); int tabCount = tabPane.getTabCount(); - Point p = null; if (tabCount == 0) return; int tabPlacement = tabPane.getTabPlacement(); @@ -1512,7 +1598,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void updateUI() { - setUI((PanelUI) new ScrollingPanelUI()); + setUI(new ScrollingPanelUI()); } } @@ -1892,15 +1978,19 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants final void updateViewPosition() { Point p = viewport.getViewPosition(); - + + // The unneeded coordinate must be set to zero + // in order to correctly handle placement changes. switch (tabPane.getTabPlacement()) { case JTabbedPane.LEFT: case JTabbedPane.RIGHT: + p.x = 0; p.y = currentScrollOffset; break; default: p.x = currentScrollOffset; + p.y = 0; } viewport.setViewPosition(p); @@ -2331,7 +2421,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants paintTabBorder(g, tabPlacement, tabIndex, rect.x, rect.y, rect.width, rect.height, isSelected); - // Layout label. FontMetrics fm = getFontMetrics(); Icon icon = getIconForTab(tabIndex); @@ -2369,7 +2458,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected) { - SwingUtilities.layoutCompoundLabel(metrics, title, icon, + // Reset the icon and text rectangles, as the result is not specified + // when the locations are not (0,0). + textRect.x = 0; + textRect.y = 0; + textRect.width = 0; + textRect.height = 0; + iconRect.x = 0; + iconRect.y = 0; + iconRect.width = 0; + iconRect.height = 0; + SwingUtilities.layoutCompoundLabel(tabPane, metrics, title, icon, SwingConstants.CENTER, SwingConstants.CENTER, SwingConstants.CENTER, @@ -2764,7 +2863,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int width = tabPane.getWidth(); int height = tabPane.getHeight(); Insets insets = tabPane.getInsets(); - Insets tabAreaInsets = getTabAreaInsets(tabPlacement); // Calculate coordinates of content area. int x = insets.left; @@ -2869,8 +2967,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int endgap = rects[selectedIndex].y + rects[selectedIndex].height - currentScrollOffset; - int diff = 0; - if (tabPlacement == SwingConstants.LEFT && startgap >= 0) { g.drawLine(x, y, x, startgap); @@ -2957,8 +3053,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int endgap = rects[selectedIndex].y + rects[selectedIndex].height - currentScrollOffset; - int diff = 0; - if (tabPlacement == SwingConstants.RIGHT && startgap >= 0) { g.setColor(shadow); @@ -2988,8 +3082,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants } /** - * This method returns the tab bounds for the given index. - * + * <p>This method returns the bounds of a tab for the given index + * and shifts it by the current scrolling offset if the tabbed + * pane is in scrolling tab layout mode.</p> + * + * <p>Subclassses should retrievs a tab's bounds by this method + * if they want to find out whether the tab is currently visible.</p> + * * @param pane The JTabbedPane. * @param i The index to look for. * @@ -3000,6 +3099,26 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // Need to re-layout container if tab does not exist. if (i >= rects.length) layoutManager.layoutContainer(pane); + + // Properly shift coordinates if scrolling has taken + // place. + if (pane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + { + Rectangle r = new Rectangle(rects[i]); + + switch(pane.getTabPlacement()) + { + case SwingConstants.TOP: + case SwingConstants.BOTTOM: + r.x -= currentScrollOffset; + break; + default: + r.y -= currentScrollOffset; + } + + return r; + } + return rects[i]; } @@ -3048,7 +3167,10 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants } /** - * This method returns the tab bounds in the given rectangle. + * <p>This method returns the tab bounds in the given rectangle.</p> + * + * <p>The returned rectangle will be shifted by the current scroll + * offset if the tabbed pane is in scrolling tab layout mode.</p>. * * @param tabIndex The index to get bounds for. * @param dest The rectangle to store bounds in. @@ -3324,21 +3446,20 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Icon icon = getIconForTab(tabIndex); Insets insets = getTabInsets(tabPlacement, tabIndex); - int width = 0; + int width = insets.bottom + insets.right + 3; if (icon != null) { - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - layoutLabel(tabPlacement, getFontMetrics(), tabIndex, - tabPane.getTitleAt(tabIndex), icon, vr, ir, tr, - tabIndex == tabPane.getSelectedIndex()); - width = tr.union(ir).width; + width += icon.getIconWidth() + textIconGap; } - else - width = metrics.stringWidth(tabPane.getTitleAt(tabIndex)); - width += insets.left + insets.right; + View v = getTextViewForTab(tabIndex); + if (v != null) + width += v.getPreferredSpan(View.X_AXIS); + else + { + String label = tabPane.getTitleAt(tabIndex); + width += metrics.stringWidth(label); + } return width; } @@ -3377,7 +3498,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Insets insets = getTabAreaInsets(tabPlacement); int tabAreaHeight = horizRunCount * maxTabHeight - - (horizRunCount - 1) * tabRunOverlay; + - (horizRunCount - 1) + * getTabRunOverlay(tabPlacement); tabAreaHeight += insets.top + insets.bottom; @@ -3399,7 +3521,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Insets insets = getTabAreaInsets(tabPlacement); int tabAreaWidth = vertRunCount * maxTabWidth - - (vertRunCount - 1) * tabRunOverlay; + - (vertRunCount - 1) + * getTabRunOverlay(tabPlacement); tabAreaWidth += insets.left + insets.right; diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTableHeaderUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTableHeaderUI.java index abe7cab43b3..8a8eeb837fe 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicTableHeaderUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTableHeaderUI.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; @@ -415,9 +413,8 @@ public class BasicTableHeaderUI extends TableHeaderUI } protected void installKeyboardActions() - throws NotImplementedException { - // TODO: Implement this properly. + // AFAICS, the RI does nothing here. } /** @@ -448,9 +445,8 @@ public class BasicTableHeaderUI extends TableHeaderUI } protected void uninstallKeyboardActions() - throws NotImplementedException { - // TODO: Implement this properly. + // AFAICS, the RI does nothing here. } /** diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java index 15be4d57e62..a672173c725 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java @@ -443,11 +443,14 @@ public class BasicTableUI extends TableUI public Dimension getPreferredSize(JComponent comp) { int prefTotalColumnWidth = 0; - for (int i = 0; i < table.getColumnCount(); i++) + TableColumnModel tcm = table.getColumnModel(); + + for (int i = 0; i < tcm.getColumnCount(); i++) { - TableColumn col = table.getColumnModel().getColumn(i); + TableColumn col = tcm.getColumn(i); prefTotalColumnWidth += col.getPreferredWidth(); } + return new Dimension(prefTotalColumnWidth, getHeight()); } @@ -455,7 +458,7 @@ public class BasicTableUI extends TableUI * Returns the table height. This helper method is used by * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)} * and {@link #getMaximumSize(JComponent)} to determine the table height. - * + * * @return the table height */ private int getHeight() diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTextUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTextUI.java index 8e9c8c949f3..e152a3034d5 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicTextUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTextUI.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.plaf.basic; +import gnu.classpath.SystemProperties; + import java.awt.Color; import java.awt.Container; import java.awt.Dimension; @@ -71,6 +73,7 @@ import javax.swing.plaf.InputMapUIResource; import javax.swing.plaf.TextUI; import javax.swing.plaf.UIResource; import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import javax.swing.text.DefaultCaret; @@ -120,6 +123,140 @@ public abstract class BasicTextUI extends TextUI } } + private static class FocusHandler + implements FocusListener + { + public void focusGained(FocusEvent e) + { + // Nothing to do here. + } + public void focusLost(FocusEvent e) + { + JTextComponent textComponent = (JTextComponent) e.getComponent(); + // Integrates Swing text components with the system clipboard: + // The idea is that if one wants to copy text around X11-style + // (select text and middle-click in the target component) the focus + // will move to the new component which gives the old focus owner the + // possibility to paste its selection into the clipboard. + if (!e.isTemporary() + && textComponent.getSelectionStart() + != textComponent.getSelectionEnd()) + { + SecurityManager sm = System.getSecurityManager(); + try + { + if (sm != null) + sm.checkSystemClipboardAccess(); + + Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection(); + if (cb != null) + { + StringSelection selection = new StringSelection( + textComponent.getSelectedText()); + cb.setContents(selection, selection); + } + } + catch (SecurityException se) + { + // Not allowed to access the clipboard: Ignore and + // do not access it. + } + catch (HeadlessException he) + { + // There is no AWT: Ignore and do not access the + // clipboard. + } + catch (IllegalStateException ise) + { + // Clipboard is currently unavaible. + } + } + } + } + + /** + * This FocusListener triggers repaints on focus shift. + */ + private static FocusListener focusListener; + + /** + * Receives notifications when properties of the text component change. + */ + private class Handler + implements PropertyChangeListener, DocumentListener + { + /** + * Notifies when a property of the text component changes. + * + * @param event the PropertyChangeEvent describing the change + */ + public void propertyChange(PropertyChangeEvent event) + { + if (event.getPropertyName().equals("document")) + { + // Document changed. + Object oldValue = event.getOldValue(); + if (oldValue != null) + { + Document oldDoc = (Document) oldValue; + oldDoc.removeDocumentListener(handler); + } + Object newValue = event.getNewValue(); + if (newValue != null) + { + Document newDoc = (Document) newValue; + newDoc.addDocumentListener(handler); + } + modelChanged(); + } + + BasicTextUI.this.propertyChange(event); + } + + /** + * Notification about a document change event. + * + * @param ev the DocumentEvent describing the change + */ + public void changedUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.changedUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + /** + * Notification about a document insert event. + * + * @param ev the DocumentEvent describing the insertion + */ + public void insertUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.insertUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + /** + * Notification about a document removal event. + * + * @param ev the DocumentEvent describing the removal + */ + public void removeUpdate(DocumentEvent ev) + { + // Updates are forwarded to the View even if 'getVisibleEditorRect' + // method returns null. This means the View classes have to be + // aware of that possibility. + rootView.removeUpdate(ev, getVisibleEditorRect(), + rootView.getViewFactory()); + } + + } + /** * This view forms the root of the View hierarchy. However, it delegates * most calls to another View which is the real root of the hierarchy. @@ -226,19 +363,14 @@ public abstract class BasicTextUI extends TextUI } /** - * Returns the preferred span along the specified <code>axis</code>. - * This is delegated to the real root view. - * - * @param axis the axis for which the preferred span is queried - * - * @return the preferred span along the axis + * Sets the size of the renderer. This is synchronized because that + * potentially triggers layout and we don't want more than one thread + * playing with the layout information. */ - public float getPreferredSpan(int axis) + public synchronized void setSize(float w, float h) { if (view != null) - return view.getPreferredSpan(axis); - - return Integer.MAX_VALUE; + view.setSize(w, h); } /** @@ -251,8 +383,8 @@ public abstract class BasicTextUI extends TextUI { if (view != null) { - Rectangle b = s.getBounds(); - view.setSize(b.width, b.height); + Rectangle b = s instanceof Rectangle ? (Rectangle) s : s.getBounds(); + setSize(b.width, b.height); view.paint(g, s); } } @@ -312,7 +444,8 @@ public abstract class BasicTextUI extends TextUI */ public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - view.insertUpdate(ev, shape, vf); + if (view != null) + view.insertUpdate(ev, shape, vf); } /** @@ -325,7 +458,8 @@ public abstract class BasicTextUI extends TextUI */ public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - view.removeUpdate(ev, shape, vf); + if (view != null) + view.removeUpdate(ev, shape, vf); } /** @@ -338,7 +472,8 @@ public abstract class BasicTextUI extends TextUI */ public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - view.changedUpdate(ev, shape, vf); + if (view != null) + view.changedUpdate(ev, shape, vf); } /** @@ -400,116 +535,73 @@ public abstract class BasicTextUI extends TextUI { return textComponent.getDocument(); } - } - /** - * Receives notifications when properties of the text component change. - */ - private class PropertyChangeHandler implements PropertyChangeListener - { /** - * Notifies when a property of the text component changes. - * - * @param event the PropertyChangeEvent describing the change + * Returns the attributes, which is null for the RootView. */ - public void propertyChange(PropertyChangeEvent event) + public AttributeSet getAttributes() { - if (event.getPropertyName().equals("document")) - { - // Document changed. - Object oldValue = event.getOldValue(); - if (oldValue != null) - { - Document oldDoc = (Document) oldValue; - oldDoc.removeDocumentListener(documentHandler); - } - Object newValue = event.getNewValue(); - if (newValue != null) - { - Document newDoc = (Document) newValue; - newDoc.addDocumentListener(documentHandler); - } - modelChanged(); - } - - BasicTextUI.this.propertyChange(event); + return null; } - } - /** - * Listens for changes on the underlying model and forwards notifications - * to the View. This also updates the caret position of the text component. - * - * TODO: Maybe this should somehow be handled through EditorKits - */ - class DocumentHandler implements DocumentListener - { /** - * Notification about a document change event. - * - * @param ev the DocumentEvent describing the change + * Overridden to forward to the view. */ - public void changedUpdate(DocumentEvent ev) + public float getPreferredSpan(int axis) { - // Updates are forwarded to the View even if 'getVisibleEditorRect' - // method returns null. This means the View classes have to be - // aware of that possibility. - rootView.changedUpdate(ev, getVisibleEditorRect(), - rootView.getViewFactory()); + // The RI returns 10 in the degenerate case. + float span = 10; + if (view != null) + span = view.getPreferredSpan(axis); + return span; } /** - * Notification about a document insert event. - * - * @param ev the DocumentEvent describing the insertion + * Overridden to forward to the real view. */ - public void insertUpdate(DocumentEvent ev) + public float getMinimumSpan(int axis) { - // Updates are forwarded to the View even if 'getVisibleEditorRect' - // method returns null. This means the View classes have to be - // aware of that possibility. - rootView.insertUpdate(ev, getVisibleEditorRect(), - rootView.getViewFactory()); + // The RI returns 10 in the degenerate case. + float span = 10; + if (view != null) + span = view.getMinimumSpan(axis); + return span; } /** - * Notification about a document removal event. - * - * @param ev the DocumentEvent describing the removal + * Overridden to return Integer.MAX_VALUE. */ - public void removeUpdate(DocumentEvent ev) + public float getMaximumSpan(int axis) { - // Updates are forwarded to the View even if 'getVisibleEditorRect' - // method returns null. This means the View classes have to be - // aware of that possibility. - rootView.removeUpdate(ev, getVisibleEditorRect(), - rootView.getViewFactory()); + // The RI returns Integer.MAX_VALUE here, regardless of the real view's + // maximum size. + return Integer.MAX_VALUE; } } /** * The EditorKit used by this TextUI. */ - // FIXME: should probably be non-static. - static EditorKit kit = new DefaultEditorKit(); + private static EditorKit kit; /** - * The root view. + * The combined event handler for text components. + * + * This is package private to avoid accessor methods. */ - RootView rootView = new RootView(); + Handler handler; /** - * The text component that we handle. + * The root view. + * + * This is package private to avoid accessor methods. */ - JTextComponent textComponent; + RootView rootView; /** - * Receives notification when the model changes. + * The text component that we handle. */ - private PropertyChangeHandler updateHandler = new PropertyChangeHandler(); - - /** The DocumentEvent handler. */ - DocumentHandler documentHandler = new DocumentHandler(); + JTextComponent textComponent; /** * Creates a new <code>BasicTextUI</code> instance. @@ -558,17 +650,31 @@ public abstract class BasicTextUI extends TextUI public void installUI(final JComponent c) { textComponent = (JTextComponent) c; + + if (rootView == null) + rootView = new RootView(); + installDefaults(); - textComponent.addPropertyChangeListener(updateHandler); + installFixedDefaults(); + + // These listeners must be installed outside of installListeners(), + // because overriding installListeners() doesn't prevent installing + // these in the RI, but overriding isntallUI() does. + if (handler == null) + handler = new Handler(); + textComponent.addPropertyChangeListener(handler); Document doc = textComponent.getDocument(); if (doc == null) { + // The Handler takes care of installing the necessary listeners + // on the document here. doc = getEditorKit(textComponent).createDefaultDocument(); textComponent.setDocument(doc); } else { - doc.addDocumentListener(documentHandler); + // Must install the document listener. + doc.addDocumentListener(handler); modelChanged(); } @@ -586,7 +692,6 @@ public abstract class BasicTextUI extends TextUI LookAndFeel.installColorsAndFont(textComponent, prefix + ".background", prefix + ".foreground", prefix + ".font"); LookAndFeel.installBorder(textComponent, prefix + ".border"); - textComponent.setMargin(UIManager.getInsets(prefix + ".margin")); // Some additional text component only properties. Color color = textComponent.getCaretColor(); @@ -600,7 +705,7 @@ public abstract class BasicTextUI extends TextUI color = textComponent.getDisabledTextColor(); if (color == null || color instanceof UIResource) { - color = UIManager.getColor(prefix + ".inactiveBackground"); + color = UIManager.getColor(prefix + ".inactiveForeground"); textComponent.setDisabledTextColor(color); } color = textComponent.getSelectedTextColor(); @@ -623,6 +728,15 @@ public abstract class BasicTextUI extends TextUI textComponent.setMargin(margin); } + } + + /** + * Installs defaults that can't be overridden by overriding + * installDefaults(). + */ + private void installFixedDefaults() + { + String prefix = getPropertyPrefix(); Caret caret = textComponent.getCaret(); if (caret == null || caret instanceof UIResource) { @@ -638,64 +752,18 @@ public abstract class BasicTextUI extends TextUI } /** - * This FocusListener triggers repaints on focus shift. - */ - private FocusListener focuslistener = new FocusListener() { - public void focusGained(FocusEvent e) - { - textComponent.repaint(); - } - public void focusLost(FocusEvent e) - { - textComponent.repaint(); - - // Integrates Swing text components with the system clipboard: - // The idea is that if one wants to copy text around X11-style - // (select text and middle-click in the target component) the focus - // will move to the new component which gives the old focus owner the - // possibility to paste its selection into the clipboard. - if (!e.isTemporary() - && textComponent.getSelectionStart() - != textComponent.getSelectionEnd()) - { - SecurityManager sm = System.getSecurityManager(); - try - { - if (sm != null) - sm.checkSystemClipboardAccess(); - - Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection(); - if (cb != null) - { - StringSelection selection = new StringSelection( - textComponent.getSelectedText()); - cb.setContents(selection, selection); - } - } - catch (SecurityException se) - { - // Not allowed to access the clipboard: Ignore and - // do not access it. - } - catch (HeadlessException he) - { - // There is no AWT: Ignore and do not access the - // clipboard. - } - catch (IllegalStateException ise) - { - // Clipboard is currently unavaible. - } - } - } - }; - - /** * Install all listeners on the text component. */ protected void installListeners() { - textComponent.addFocusListener(focuslistener); + // + if (SystemProperties.getProperty("gnu.swing.text.no-xlike-clipboard") + == null) + { + if (focusListener == null) + focusListener = new FocusHandler(); + textComponent.addFocusListener(focusListener); + } } /** @@ -834,10 +902,12 @@ public abstract class BasicTextUI extends TextUI */ public void uninstallUI(final JComponent component) { - super.uninstallUI(component); + textComponent.removePropertyChangeListener(handler); + textComponent.getDocument().removeDocumentListener(handler); rootView.setView(null); uninstallDefaults(); + uninstallFixedDefaults(); uninstallListeners(); uninstallKeyboardActions(); @@ -850,7 +920,29 @@ public abstract class BasicTextUI extends TextUI */ protected void uninstallDefaults() { - // Do nothing here. + if (textComponent.getCaretColor() instanceof UIResource) + textComponent.setCaretColor(null); + if (textComponent.getSelectionColor() instanceof UIResource) + textComponent.setSelectionColor(null); + if (textComponent.getDisabledTextColor() instanceof UIResource) + textComponent.setDisabledTextColor(null); + if (textComponent.getSelectedTextColor() instanceof UIResource) + textComponent.setSelectedTextColor(null); + LookAndFeel.uninstallBorder(textComponent); + if (textComponent.getMargin() instanceof UIResource) + textComponent.setMargin(null); + } + + /** + * Uninstalls additional fixed defaults that were installed + * by installFixedDefaults(). + */ + private void uninstallFixedDefaults() + { + if (textComponent.getCaret() instanceof UIResource) + textComponent.setCaret(null); + if (textComponent.getHighlighter() instanceof UIResource) + textComponent.setHighlighter(null); } /** @@ -859,7 +951,10 @@ public abstract class BasicTextUI extends TextUI */ protected void uninstallListeners() { - textComponent.removeFocusListener(focuslistener); + // Don't nullify the focusListener field, as it is static and shared + // between components. + if (focusListener != null) + textComponent.removeFocusListener(focusListener); } /** @@ -891,14 +986,38 @@ public abstract class BasicTextUI extends TextUI */ public Dimension getPreferredSize(JComponent c) { - View v = getRootView(textComponent); - - float w = v.getPreferredSpan(View.X_AXIS); - float h = v.getPreferredSpan(View.Y_AXIS); - + Dimension d = c.getSize(); Insets i = c.getInsets(); - return new Dimension((int) w + i.left + i.right, + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + float w; + float h; + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + if (d.width > (i.left + i.right) && d.height > (i.top + i.bottom)) + { + rootView.setSize(d.width - i.left - i.right, + d.height - i.top - i.bottom); + } + else + { + // Not laid out yet. Force some pseudo size. + rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + w = rootView.getPreferredSpan(View.X_AXIS); + h = rootView.getPreferredSpan(View.Y_AXIS); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + Dimension size = new Dimension((int) w + i.left + i.right, (int) h + i.top + i.bottom); + return size; } /** @@ -912,8 +1031,27 @@ public abstract class BasicTextUI extends TextUI */ public Dimension getMaximumSize(JComponent c) { - // Sun's implementation returns Integer.MAX_VALUE here, so do we. - return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + Dimension d = new Dimension(); + Insets i = c.getInsets(); + Document doc = textComponent.getDocument(); + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + // Check for overflow here. + d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) + + i.left + i.right, Integer.MAX_VALUE); + d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) + + i.top + i.bottom, Integer.MAX_VALUE); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return d; } /** @@ -924,8 +1062,26 @@ public abstract class BasicTextUI extends TextUI */ public Dimension getMinimumSize(JComponent c) { + Dimension d = new Dimension(); + Document doc = textComponent.getDocument(); + // We need to lock here, since we require the view hierarchy to _not_ + // change in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + d.width = (int) rootView.getMinimumSpan(View.X_AXIS); + d.height = (int) rootView.getMinimumSpan(View.Y_AXIS); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } Insets i = c.getInsets(); - return new Dimension(i.left + i.right, i.top + i.bottom); + d.width += i.left + i.right; + d.height += i.top + i.bottom; + return d; } /** @@ -946,7 +1102,6 @@ public abstract class BasicTextUI extends TextUI AbstractDocument aDoc = (AbstractDocument) doc; aDoc.readLock(); } - paintSafely(g); } finally @@ -996,7 +1151,6 @@ public abstract class BasicTextUI extends TextUI g.setColor(oldColor); } - rootView.paint(g, getVisibleEditorRect()); if (caret != null && textComponent.hasFocus()) @@ -1104,6 +1258,8 @@ public abstract class BasicTextUI extends TextUI */ public EditorKit getEditorKit(JTextComponent t) { + if (kit == null) + kit = new DefaultEditorKit(); return kit; } @@ -1126,12 +1282,26 @@ public abstract class BasicTextUI extends TextUI Position.Bias[] biasRet) throws BadLocationException { - // A comment in the spec of NavigationFilter.getNextVisualPositionFrom() - // suggests that this method should be implemented by forwarding the call - // the root view. - return rootView.getNextVisualPositionFrom(pos, b, - getVisibleEditorRect(), - direction, biasRet); + int offset = -1; + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + Rectangle alloc = getVisibleEditorRect(); + if (alloc != null) + { + rootView.setSize(alloc.width, alloc.height); + offset = rootView.getNextVisualPositionFrom(pos, b, alloc, + direction, biasRet); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return offset; } /** @@ -1224,7 +1394,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]); } /** @@ -1241,7 +1411,25 @@ public abstract class BasicTextUI extends TextUI */ public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn) { - return rootView.viewToModel(pt.x, pt.y, getVisibleEditorRect(), biasReturn); + int offset = -1; + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + Rectangle alloc = getVisibleEditorRect(); + if (alloc != null) + { + rootView.setSize(alloc.width, alloc.height); + offset = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return offset; } /** @@ -1273,6 +1461,11 @@ public abstract class BasicTextUI extends TextUI } /** + * A cached Insets instance to be reused below. + */ + private Insets cachedInsets; + + /** * Returns the allocation to give the root view. * * @return the allocation to give the root view @@ -1290,7 +1483,7 @@ public abstract class BasicTextUI extends TextUI if (width <= 0 || height <= 0) return null; - Insets insets = textComponent.getInsets(); + Insets insets = textComponent.getInsets(cachedInsets); return new Rectangle(insets.left, insets.top, width - insets.left - insets.right, height - insets.top - insets.bottom); @@ -1341,4 +1534,5 @@ public abstract class BasicTextUI extends TextUI { // The default implementation does nothing. } + } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicToolBarUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicToolBarUI.java index 1c36b408d5a..7be69ec2576 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicToolBarUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicToolBarUI.java @@ -898,7 +898,8 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants b.setRolloverEnabled(false); // Save old border in hashtable. - borders.put(b, b.getBorder()); + if (b.getBorder() != null) + borders.put(b, b.getBorder()); b.setBorder(nonRolloverBorder); } @@ -932,7 +933,8 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants b.setRolloverEnabled(false); // Save old border in hashtable. - borders.put(b, b.getBorder()); + if (b.getBorder() != null) + borders.put(b, b.getBorder()); b.setBorder(rolloverBorder); } diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicToolTipUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicToolTipUI.java index 5cec2e33365..94e7bc322f6 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicToolTipUI.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/plaf/basic/BasicTreeUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTreeUI.java index 9a193986ac5..5b0ffce09b6 100644 --- a/libjava/classpath/javax/swing/plaf/basic/BasicTreeUI.java +++ b/libjava/classpath/javax/swing/plaf/basic/BasicTreeUI.java @@ -38,17 +38,16 @@ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; import gnu.javax.swing.tree.GnuPath; import java.awt.Color; import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import java.awt.Label; +import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -196,7 +195,7 @@ public class BasicTreeUI protected AbstractLayoutCache treeState; /** Used for minimizing the drawing of vertical lines. */ - protected Hashtable drawingCache; + protected Hashtable<TreePath, Boolean> drawingCache; /** * True if doing optimizations for a largeModel. Subclasses that don't support @@ -275,13 +274,6 @@ public class BasicTreeUI TreeModelListener treeModelListener; /** - * This timer fires the editing action after about 1200 ms if not reset during - * that time. It handles the editing start with the single mouse click (and - * not the double mouse click) on the selected tree node. - */ - Timer startEditTimer; - - /** * The zero size icon, used for expand controls, if they are not visible. */ static Icon nullIcon; @@ -428,6 +420,7 @@ public class BasicTreeUI { if (largeModel != this.largeModel) { + completeEditing(); tree.removeComponentListener(componentListener); this.largeModel = largeModel; tree.addComponentListener(componentListener); @@ -451,6 +444,7 @@ public class BasicTreeUI */ protected void setRowHeight(int rowHeight) { + completeEditing(); if (rowHeight == 0) rowHeight = getMaxHeight(tree); treeState.setRowHeight(rowHeight); @@ -544,6 +538,7 @@ public class BasicTreeUI */ protected void setRootVisible(boolean newValue) { + completeEditing(); tree.setRootVisible(newValue); } @@ -590,8 +585,7 @@ public class BasicTreeUI */ protected void setCellEditor(TreeCellEditor editor) { - cellEditor = editor; - createdCellEditor = true; + updateCellEditor(); } /** @@ -611,7 +605,7 @@ public class BasicTreeUI */ protected void setEditable(boolean newValue) { - tree.setEditable(newValue); + updateCellEditor(); } /** @@ -632,6 +626,7 @@ public class BasicTreeUI */ protected void setSelectionModel(TreeSelectionModel newLSM) { + completeEditing(); if (newLSM != null) { treeSelectionModel = newLSM; @@ -787,12 +782,13 @@ public class BasicTreeUI */ public boolean stopEditing(JTree tree) { - if (isEditing(tree)) + boolean ret = false; + if (editingComponent != null && cellEditor.stopCellEditing()) { completeEditing(false, false, true); - finish(); + ret = true; } - return ! isEditing(tree); + return ret; } /** @@ -805,8 +801,8 @@ public class BasicTreeUI // There is no need to send the cancel message to the editor, // as the cancellation event itself arrives from it. This would // only be necessary when cancelling the editing programatically. - completeEditing(false, false, false); - finish(); + if (editingComponent != null) + completeEditing(false, true, false); } /** @@ -818,7 +814,9 @@ public class BasicTreeUI */ public void startEditingAtPath(JTree tree, TreePath path) { - startEditing(path, null); + tree.scrollPathToVisible(path); + if (path != null && tree.isVisible(path)) + startEditing(path, null); } /** @@ -842,6 +840,7 @@ public class BasicTreeUI preferredSize = new Dimension(); largeModel = tree.isLargeModel(); preferredSize = new Dimension(); + stopEditingInCompleteEditing = true; setModel(tree.getModel()); } @@ -1136,6 +1135,7 @@ public class BasicTreeUI */ protected void updateExpandedDescendants(TreePath path) { + completeEditing(); Enumeration expanded = tree.getExpandedDescendants(path); while (expanded.hasMoreElements()) treeState.setExpandedState((TreePath) expanded.nextElement(), true); @@ -1167,9 +1167,33 @@ public class BasicTreeUI */ protected void updateCellEditor() { - if (tree.isEditable() && cellEditor == null) - setCellEditor(createDefaultCellEditor()); - createdCellEditor = true; + completeEditing(); + TreeCellEditor newEd = null; + if (tree != null && tree.isEditable()) + { + newEd = tree.getCellEditor(); + if (newEd == null) + { + newEd = createDefaultCellEditor(); + if (newEd != null) + { + tree.setCellEditor(newEd); + createdCellEditor = true; + } + } + } + // Update listeners. + if (newEd != cellEditor) + { + if (cellEditor != null && cellEditorListener != null) + cellEditor.removeCellEditorListener(cellEditorListener); + cellEditor = newEd; + if (cellEditorListener == null) + cellEditorListener = createCellEditorListener(); + if (cellEditor != null && cellEditorListener != null) + cellEditor.addCellEditorListener(cellEditorListener); + createdCellEditor = false; + } } /** @@ -1563,12 +1587,15 @@ public class BasicTreeUI for (int i = startIndex; i <= endIndex; i++, k++) { path[k] = treeState.getPathForRow(i); - isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent()); - isExpanded[k] = tree.isExpanded(path[k]); - bounds[k] = getPathBounds(tree, path[k]); - - paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k], i, - isExpanded[k], false, isLeaf[k]); + if (path[k] != null) + { + isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent()); + isExpanded[k] = tree.isExpanded(path[k]); + bounds[k] = getPathBounds(tree, path[k]); + + paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k], + i, isExpanded[k], false, isLeaf[k]); + } if (isLastChild(path[k])) paintVerticalPartOfLeg(g, clip, insets, path[k]); } @@ -1576,8 +1603,9 @@ public class BasicTreeUI k = 0; for (int i = startIndex; i <= endIndex; i++, k++) { - paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k], - false, isLeaf[k]); + if (path[k] != null) + paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k], + false, isLeaf[k]); } } } @@ -1587,7 +1615,9 @@ public class BasicTreeUI */ private boolean isLastChild(TreePath path) { - if (path instanceof GnuPath) + if (path == null) + return false; + else if (path instanceof GnuPath) { // Except the seldom case when the layout cache is changed, this // optimized code will be executed. @@ -1719,6 +1749,10 @@ public class BasicTreeUI */ protected void completeEditing() { + if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing + && editingComponent != null) + cellEditor.stopCellEditing(); + completeEditing(false, true, false); } @@ -1736,28 +1770,35 @@ public class BasicTreeUI boolean messageTree) { // Make no attempt to complete the non existing editing session. - if (!isEditing(tree)) - return; - - if (messageStop) + if (stopEditingInCompleteEditing && editingComponent != null) { - getCellEditor().stopCellEditing(); - stopEditingInCompleteEditing = true; - } - - if (messageCancel) - { - getCellEditor().cancelCellEditing(); - stopEditingInCompleteEditing = true; - } + Component comp = editingComponent; + TreePath p = editingPath; + editingComponent = null; + editingPath = null; + if (messageStop) + cellEditor.stopCellEditing(); + else if (messageCancel) + cellEditor.cancelCellEditing(); + + tree.remove(comp); + + if (editorHasDifferentSize) + { + treeState.invalidatePathBounds(p); + updateSize(); + } + else + { + // Need to refresh the tree. + Rectangle b = getPathBounds(tree, p); + tree.repaint(0, b.y, tree.getWidth(), b.height); + } - if (messageTree) - { - TreeCellEditor editor = getCellEditor(); - if (editor != null) + if (messageTree) { - Object value = editor.getCellEditorValue(); - treeModel.valueForPathChanged(tree.getLeadSelectionPath(), value); + Object value = cellEditor.getCellEditorValue(); + treeModel.valueForPathChanged(p, value); } } } @@ -1772,47 +1813,105 @@ public class BasicTreeUI */ protected boolean startEditing(TreePath path, MouseEvent event) { - updateCellEditor(); - TreeCellEditor ed = getCellEditor(); + // Maybe cancel editing. + if (isEditing(tree) && tree.getInvokesStopCellEditing() + && ! stopEditing(tree)) + return false; - if (ed != null && (event == EDIT || ed.shouldSelectCell(event)) - && ed.isCellEditable(event)) + completeEditing(); + TreeCellEditor ed = cellEditor; + if (ed != null && tree.isPathEditable(path)) { - Rectangle bounds = getPathBounds(tree, path); - - // Extend the right boundary till the tree width. - bounds.width = tree.getWidth() - bounds.x; - - editingPath = path; - editingRow = tree.getRowForPath(editingPath); - - Object value = editingPath.getLastPathComponent(); - - stopEditingInCompleteEditing = false; - boolean expanded = tree.isExpanded(editingPath); - isEditing = true; - editingComponent = ed.getTreeCellEditorComponent(tree, value, true, - expanded, - isLeaf(editingRow), - editingRow); - - // Remove all previous components (if still present). Only one - // container with the editing component inside is allowed in the tree. - tree.removeAll(); - - // The editing component must be added to its container. We add the - // container, not the editing component itself. - Component container = editingComponent.getParent(); - container.setBounds(bounds); - tree.add(container); - editingComponent.requestFocus(); + if (ed.isCellEditable(event)) + { + editingRow = getRowForPath(tree, path); + Object value = path.getLastPathComponent(); + boolean isSelected = tree.isPathSelected(path); + boolean isExpanded = tree.isExpanded(editingPath); + boolean isLeaf = treeModel.isLeaf(value); + editingComponent = ed.getTreeCellEditorComponent(tree, value, + isSelected, + isExpanded, + isLeaf, + editingRow); + + Rectangle bounds = getPathBounds(tree, path); + + Dimension size = editingComponent.getPreferredSize(); + int rowHeight = getRowHeight(); + if (size.height != bounds.height && rowHeight > 0) + size.height = rowHeight; + + if (size.width != bounds.width || size.height != bounds.height) + { + editorHasDifferentSize = true; + treeState.invalidatePathBounds(path); + updateSize(); + } + else + editorHasDifferentSize = false; + + // The editing component must be added to its container. We add the + // container, not the editing component itself. + tree.add(editingComponent); + editingComponent.setBounds(bounds.x, bounds.y, size.width, + size.height); + editingComponent.validate(); + editingPath = path; + + if (ed.shouldSelectCell(event)) + { + stopEditingInCompleteEditing = false; + tree.setSelectionRow(editingRow); + stopEditingInCompleteEditing = true; + } + + editorRequestFocus(editingComponent); + // Register MouseInputHandler to redispatch initial mouse events + // correctly. + if (event instanceof MouseEvent) + { + Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(), + editingComponent); + Component active = + SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y); + if (active != null) + { + MouseInputHandler ih = new MouseInputHandler(tree, active, event); + + } + } - return true; + return true; + } + else + editingComponent = null; } return false; } /** + * Requests focus on the editor. The method is necessary since the + * DefaultTreeCellEditor returns a container that contains the + * actual editor, and we want to request focus on the editor, not the + * container. + */ + private void editorRequestFocus(Component c) + { + if (c instanceof Container) + { + // TODO: Maybe do something more reasonable here, like queriying the + // FocusTraversalPolicy. + Container cont = (Container) c; + if (cont.getComponentCount() > 0) + cont.getComponent(0).requestFocus(); + } + else if (c.isFocusable()) + c.requestFocus(); + + } + + /** * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or * collapse region of the row, this will toggle the row. * @@ -2180,7 +2279,7 @@ public class BasicTreeUI */ public void editingStopped(ChangeEvent e) { - stopEditing(tree); + completeEditing(false, false, true); } /** @@ -2191,7 +2290,7 @@ public class BasicTreeUI */ public void editingCanceled(ChangeEvent e) { - cancelEditing(tree); + completeEditing(false, false, false); } } // CellEditorHandler @@ -2347,9 +2446,15 @@ public class BasicTreeUI * events. */ public class MouseHandler - extends MouseAdapter - implements MouseMotionListener + extends MouseAdapter + implements MouseMotionListener { + + /** + * If the cell has been selected on mouse press. + */ + private boolean selectedOnPress; + /** * Constructor */ @@ -2365,76 +2470,15 @@ public class BasicTreeUI */ public void mousePressed(MouseEvent e) { - // Any mouse click cancels the previous waiting edit action, initiated - // by the single click on the selected node. - if (startEditTimer != null) + if (! e.isConsumed()) { - startEditTimer.stop(); - startEditTimer = null; + handleEvent(e); + selectedOnPress = true; } - - if (tree != null && tree.isEnabled()) + else { - // Always end the current editing session if clicked on the - // tree and outside the bounds of the editing component. - if (isEditing(tree)) - if (!stopEditing(tree)) - // Return if we have failed to cancel the editing session. - return; - - int x = e.getX(); - int y = e.getY(); - TreePath path = getClosestPathForLocation(tree, x, y); - - if (path != null) - { - Rectangle bounds = getPathBounds(tree, path); - if (SwingUtilities.isLeftMouseButton(e)) - checkForClickInExpandControl(path, x, y); - - if (x > bounds.x && x <= (bounds.x + bounds.width)) - { - TreePath currentLead = tree.getLeadSelectionPath(); - if (currentLead != null && currentLead.equals(path) - && e.getClickCount() == 1 && tree.isEditable()) - { - // Schedule the editing session. - final TreePath editPath = path; - - // The code below handles the required click-pause-click - // functionality which must be present in the tree UI. - // If the next click comes after the - // time longer than the double click interval AND - // the same node stays focused for the WAIT_TILL_EDITING - // duration, the timer starts the editing session. - if (startEditTimer != null) - startEditTimer.stop(); - - startEditTimer = new Timer(WAIT_TILL_EDITING, - new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - startEditing(editPath, EDIT); - } - }); - - startEditTimer.setRepeats(false); - startEditTimer.start(); - } - else - { - if (e.getClickCount() == 2) - toggleExpandState(path); - else - selectPathForEvent(path, e); - } - } - } + selectedOnPress = false; } - - // We need to request the focus. - tree.requestFocusInWindow(); } /** @@ -2446,9 +2490,8 @@ public class BasicTreeUI * @param e is the mouse event that occured */ public void mouseDragged(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** @@ -2458,9 +2501,8 @@ public class BasicTreeUI * @param e the mouse event that occured */ public void mouseMoved(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** @@ -2469,9 +2511,46 @@ public class BasicTreeUI * @param e is the mouse event that occured */ public void mouseReleased(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + if (! e.isConsumed() && ! selectedOnPress) + handleEvent(e); + } + + /** + * Handles press and release events. + * + * @param e the mouse event + */ + private void handleEvent(MouseEvent e) + { + if (tree != null && tree.isEnabled()) + { + // Maybe stop editing. + if (isEditing(tree) && tree.getInvokesStopCellEditing() + && ! stopEditing(tree)) + return; + + // Explicitly request focus. + tree.requestFocusInWindow(); + + int x = e.getX(); + int y = e.getY(); + TreePath path = getClosestPathForLocation(tree, x, y); + if (path != null) + { + Rectangle b = getPathBounds(tree, path); + if (y <= b.y + b.height) + { + if (SwingUtilities.isLeftMouseButton(e)) + checkForClickInExpandControl(path, x, y); + if (x > b.x && x <= b.x + b.width) + { + if (! startEditing(path, e)) + selectPathForEvent(path, e); + } + } + } + } } } @@ -2501,6 +2580,9 @@ public class BasicTreeUI { this.source = source; this.destination = destination; + source.addMouseListener(this); + source.addMouseMotionListener(this); + dispatch(e); } /** @@ -2510,9 +2592,8 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseClicked(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + dispatch(e); } /** @@ -2521,9 +2602,8 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mousePressed(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + // Nothing to do here. } /** @@ -2532,9 +2612,9 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseReleased(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + dispatch(e); + removeFromSource(); } /** @@ -2543,9 +2623,9 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseEntered(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + if (! SwingUtilities.isLeftMouseButton(e)) + removeFromSource(); } /** @@ -2554,9 +2634,9 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseExited(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + if (! SwingUtilities.isLeftMouseButton(e)) + removeFromSource(); } /** @@ -2568,9 +2648,8 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseDragged(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + dispatch(e); } /** @@ -2580,18 +2659,37 @@ public class BasicTreeUI * @param e mouse event that occured */ public void mouseMoved(MouseEvent e) - throws NotImplementedException { - // TODO: What should be done here, if anything? + removeFromSource(); } /** * Removes event from the source */ protected void removeFromSource() - throws NotImplementedException { - // TODO: Implement this properly. + if (source != null) + { + source.removeMouseListener(this); + source.removeMouseMotionListener(this); + } + source = null; + destination = null; + } + + /** + * Redispatches mouse events to the destination. + * + * @param e the mouse event to redispatch + */ + private void dispatch(MouseEvent e) + { + if (destination != null) + { + MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e, + destination); + destination.dispatchEvent(e2); + } } } @@ -2627,24 +2725,42 @@ public class BasicTreeUI public Rectangle getNodeDimensions(Object cell, int row, int depth, boolean expanded, Rectangle size) { - if (size == null || cell == null) - return null; - - String s = cell.toString(); - Font f = tree.getFont(); - FontMetrics fm = tree.getToolkit().getFontMetrics(f); - - if (s != null) + Dimension prefSize; + if (editingComponent != null && editingRow == row) + { + // Editing, ask editor for preferred size. + prefSize = editingComponent.getPreferredSize(); + int rowHeight = getRowHeight(); + if (rowHeight > 0 && rowHeight != prefSize.height) + prefSize.height = rowHeight; + } + else + { + // Not editing, ask renderer for preferred size. + Component rend = + currentCellRenderer.getTreeCellRendererComponent(tree, cell, + tree.isRowSelected(row), + expanded, + treeModel.isLeaf(cell), + row, false); + // Make sure the layout is valid. + rendererPane.add(rend); + rend.validate(); + prefSize = rend.getPreferredSize(); + } + if (size != null) { - TreePath path = treeState.getPathForRow(row); size.x = getRowX(row, depth); - size.width = SwingUtilities.computeStringWidth(fm, s); - size.width = size.width + getCurrentControlIcon(path).getIconWidth() - + gap + getNodeIcon(path).getIconWidth(); - size.height = getMaxHeight(tree); - size.y = size.height * row; + // FIXME: This should be handled by the layout cache. + size.y = prefSize.height * row; + size.width = prefSize.width; + size.height = prefSize.height; } - + else + // FIXME: The y should be handled by the layout cache. + size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width, + prefSize.height); + return size; } @@ -2706,6 +2822,9 @@ public class BasicTreeUI if (treeState != null) treeState.invalidateSizes(); } + else if (property.equals(JTree.EDITABLE_PROPERTY)) + setEditable(((Boolean) event.getNewValue()).booleanValue()); + } } @@ -2714,7 +2833,7 @@ public class BasicTreeUI * properties of the model change. */ public class SelectionModelPropertyChangeHandler - implements PropertyChangeListener + implements PropertyChangeListener { /** @@ -2732,9 +2851,8 @@ public class BasicTreeUI * the property that has changed. */ public void propertyChange(PropertyChangeEvent event) - throws NotImplementedException { - // TODO: What should be done here, if anything? + treeSelectionModel.resetRowSelection(); } } @@ -2804,6 +2922,7 @@ public class BasicTreeUI */ public void treeCollapsed(TreeExpansionEvent event) { + completeEditing(); validCachedPreferredSize = false; treeState.setExpandedState(event.getPath(), false); // The maximal cell height may change @@ -3269,8 +3388,7 @@ public class BasicTreeUI */ public void valueChanged(TreeSelectionEvent event) { - if (tree.isEditing()) - tree.cancelEditing(); + completeEditing(); TreePath op = event.getOldLeadSelectionPath(); TreePath np = event.getNewLeadSelectionPath(); @@ -3808,25 +3926,6 @@ public class BasicTreeUI } /** - * Finish the editing session. - */ - void finish() - { - treeState.invalidatePathBounds(treeState.getPathForRow(editingRow)); - editingPath = null; - editingRow = - 1; - stopEditingInCompleteEditing = false; - isEditing = false; - Rectangle bounds = editingComponent.getParent().getBounds(); - tree.removeAll(); - validCachedPreferredSize = false; - // Repaint the region, where was the editing component. - tree.repaint(bounds); - editingComponent = null; - tree.requestFocus(); - } - - /** * Returns the amount to indent the given row * * @return amount to indent the given row. diff --git a/libjava/classpath/javax/swing/plaf/metal/DefaultMetalTheme.java b/libjava/classpath/javax/swing/plaf/metal/DefaultMetalTheme.java index 673aec1e418..672676fa081 100644 --- a/libjava/classpath/javax/swing/plaf/metal/DefaultMetalTheme.java +++ b/libjava/classpath/javax/swing/plaf/metal/DefaultMetalTheme.java @@ -38,8 +38,11 @@ exception statement from your version. */ package javax.swing.plaf.metal; +import gnu.classpath.SystemProperties; + import java.awt.Font; +import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.FontUIResource; @@ -63,10 +66,6 @@ public class DefaultMetalTheme extends MetalTheme private static final ColorUIResource SECONDARY3 = new ColorUIResource(204, 204, 204); - private static final FontUIResource CONTROL_TEXT_FONT = - new FontUIResource("Dialog", Font.BOLD, 12); - private static final FontUIResource MENU_TEXT_FONT = - new FontUIResource("Dialog", Font.BOLD, 12); private static final FontUIResource SUB_TEXT_FONT = new FontUIResource("Dialog", Font.PLAIN, 10); private static final FontUIResource SYSTEM_TEXT_FONT = @@ -77,6 +76,40 @@ public class DefaultMetalTheme extends MetalTheme new FontUIResource("Dialog", Font.BOLD, 12); /** + * The control text font for swing.boldMetal=false. + */ + private static final FontUIResource PLAIN_CONTROL_TEXT_FONT = + new FontUIResource("Dialog", Font.PLAIN, 12); + + /** + * The standard control text font. + */ + private static final FontUIResource BOLD_CONTROL_TEXT_FONT = + new FontUIResource("Dialog", Font.BOLD, 12); + + /** + * The menu text font for swing.boldMetal=false. + */ + private static final FontUIResource PLAIN_MENU_TEXT_FONT = + new FontUIResource("Dialog", Font.PLAIN, 12); + + /** + * The menu control text font. + */ + private static final FontUIResource BOLD_MENU_TEXT_FONT = + new FontUIResource("Dialog", Font.BOLD, 12); + + /** + * Indicates the control text font. + */ + static final int CONTROL_TEXT_FONT = 1; + + /** + * Indicates the menu text font. + */ + static final int MENU_TEXT_FONT = 2; + + /** * Creates a new instance of this theme. */ public DefaultMetalTheme() @@ -156,23 +189,28 @@ public class DefaultMetalTheme extends MetalTheme /** * Returns the font used for text on controls. In this case, the font is - * <code>FontUIResource("Dialog", Font.BOLD, 12)</code>. + * <code>FontUIResource("Dialog", Font.BOLD, 12)</code>, unless the + * <code>swing.boldMetal</code> UI default is set to {@link Boolean#FALSE} + * in which case it is <code>FontUIResource("Dialog", Font.PLAIN, 12)</code>. * * @return The font. */ public FontUIResource getControlTextFont() { - return CONTROL_TEXT_FONT; + return getFont(CONTROL_TEXT_FONT); } + /** * Returns the font used for text in menus. In this case, the font is - * <code>FontUIResource("Dialog", Font.BOLD, 12)</code>. + * <code>FontUIResource("Dialog", Font.BOLD, 12)</code>, unless the + * <code>swing.boldMetal</code> UI default is set to {@link Boolean#FALSE} + * in which case it is <code>FontUIResource("Dialog", Font.PLAIN, 12)</code>. * * @return The font used for text in menus. */ public FontUIResource getMenuTextFont() { - return MENU_TEXT_FONT; + return getFont(MENU_TEXT_FONT); } /** @@ -218,4 +256,50 @@ public class DefaultMetalTheme extends MetalTheme { return WINDOW_TITLE_FONT; } + + /** + * Returns the appropriate font. The font type to return is identified + * by the specified id. + * + * @param id the font type to return + * + * @return the correct font + */ + private FontUIResource getFont(int id) + { + FontUIResource font = null; + switch (id) + { + case CONTROL_TEXT_FONT: + if (isBoldMetal()) + font = BOLD_CONTROL_TEXT_FONT; + else + font = PLAIN_CONTROL_TEXT_FONT; + break; + case MENU_TEXT_FONT: + if (isBoldMetal()) + font = BOLD_MENU_TEXT_FONT; + else + font = PLAIN_MENU_TEXT_FONT; + break; + // TODO: Add other font types and their mapping here. + } + return font; + } + + /** + * Determines if the theme should be bold or not. The theme is bold by + * default, this can be turned off by setting the system property + * swing.boldMetal to true, or by putting the property with the same name + * into the current UIManager's defaults. + * + * @return <code>true</code>, when the theme is bold, <code>false</code> + * otherwise + */ + private boolean isBoldMetal() + { + Object boldMetal = UIManager.get("swing.boldMetal"); + return (boldMetal == null || ! Boolean.FALSE.equals(boldMetal)) + && ! ("false".equals(SystemProperties.getProperty("swing.boldMetal"))); + } } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalBorders.java b/libjava/classpath/javax/swing/plaf/metal/MetalBorders.java index 7c41180aeae..d4e3a849781 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalBorders.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/plaf/metal/MetalButtonUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalButtonUI.java index 8addfc66c72..be9607927ba 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalButtonUI.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalButtonUI.java @@ -54,7 +54,6 @@ import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; -import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonUI; /** @@ -66,24 +65,46 @@ public class MetalButtonUI extends BasicButtonUI { - /** The color used to draw the focus rectangle around the text and/or icon. */ + /** + * The shared button UI. + */ + private static MetalButtonUI sharedUI; + + /** + * The color used to draw the focus rectangle around the text and/or icon. + */ protected Color focusColor; - /** The background color for the button when it is pressed. */ + /** + * The background color for the button when it is pressed. + */ protected Color selectColor; - /** The color for disabled button labels. */ + /** + * The color for disabled button labels. + */ protected Color disabledTextColor; /** + * Returns a UI delegate for the specified component. + * + * @param c the component (should be a subclass of {@link AbstractButton}). + * + * @return A new instance of <code>MetalButtonUI</code>. + */ + public static ComponentUI createUI(JComponent c) + { + if (sharedUI == null) + sharedUI = new MetalButtonUI(); + return sharedUI; + } + + /** * Creates a new instance. */ public MetalButtonUI() { super(); - focusColor = UIManager.getColor(getPropertyPrefix() + "focus"); - selectColor = UIManager.getColor(getPropertyPrefix() + "select"); - disabledTextColor = UIManager.getColor(getPropertyPrefix() + "disabledText"); } /** @@ -93,6 +114,7 @@ public class MetalButtonUI */ protected Color getFocusColor() { + focusColor = UIManager.getColor(getPropertyPrefix() + "focus"); return focusColor; } @@ -103,6 +125,7 @@ public class MetalButtonUI */ protected Color getSelectColor() { + selectColor = UIManager.getColor(getPropertyPrefix() + "select"); return selectColor; } @@ -113,22 +136,12 @@ public class MetalButtonUI */ protected Color getDisabledTextColor() { + disabledTextColor = UIManager.getColor(getPropertyPrefix() + + "disabledText"); return disabledTextColor; } /** - * Returns a UI delegate for the specified component. - * - * @param c the component (should be a subclass of {@link AbstractButton}). - * - * @return A new instance of <code>MetalButtonUI</code>. - */ - public static ComponentUI createUI(JComponent c) - { - return new MetalButtonUI(); - } - - /** * Installs the default settings for the specified button. * * @param button the button. @@ -137,33 +150,20 @@ public class MetalButtonUI */ public void installDefaults(AbstractButton button) { + // This is overridden to be public, for whatever reason. super.installDefaults(button); - button.setRolloverEnabled(UIManager.getBoolean( - getPropertyPrefix() + "rollover")); } - + /** * Removes the defaults added by {@link #installDefaults(AbstractButton)}. */ public void uninstallDefaults(AbstractButton button) { + // This is overridden to be public, for whatever reason. super.uninstallDefaults(button); - button.setRolloverEnabled(false); } /** - * Returns a button listener for the specified button. - * - * @param button the button. - * - * @return A button listener. - */ - protected BasicButtonListener createButtonListener(AbstractButton button) - { - return new MetalButtonListener(button); - } - - /** * Paints the background of the button to indicate that it is in the * "pressed" state. * @@ -175,7 +175,7 @@ public class MetalButtonUI if (b.isContentAreaFilled()) { Rectangle area = b.getVisibleRect(); - g.setColor(selectColor); + g.setColor(getSelectColor()); g.fillRect(area.x, area.y, area.width, area.height); } } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalCheckBoxIcon.java b/libjava/classpath/javax/swing/plaf/metal/MetalCheckBoxIcon.java index fb8280e44da..30ee93162a9 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalCheckBoxIcon.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalCheckBoxIcon.java @@ -1,5 +1,5 @@ /* MetalCheckBoxIcon.java -- An icon for JCheckBoxes in the Metal L&F - Copyright (C) 2005 Free Software Foundation, Inc. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -42,8 +42,8 @@ import java.awt.Component; import java.awt.Graphics; import java.io.Serializable; +import javax.swing.AbstractButton; import javax.swing.Icon; -import javax.swing.JCheckBox; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.plaf.UIResource; @@ -134,8 +134,9 @@ public class MetalCheckBoxIcon MetalUtils.paintGradient(g, x, y, getIconWidth(), getIconHeight(), SwingConstants.VERTICAL, "CheckBox.gradient"); border.paintBorder(c, g, x, y, getIconWidth(), getIconHeight()); - JCheckBox cb = (JCheckBox) c; - if (cb.isSelected()) - drawCheck(c, g, x, y); + + AbstractButton b = (AbstractButton) c; + if (b.isSelected()) + drawCheck(b, g, x, y); } } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalIconFactory.java b/libjava/classpath/javax/swing/plaf/metal/MetalIconFactory.java index 30ec7e72b28..2817336a8f1 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalIconFactory.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalIconFactory.java @@ -54,7 +54,6 @@ import javax.swing.JRadioButtonMenuItem; import javax.swing.JSlider; import javax.swing.SwingConstants; import javax.swing.UIManager; -import javax.swing.plaf.IconUIResource; import javax.swing.plaf.UIResource; @@ -569,8 +568,8 @@ public class MetalIconFactory implements Serializable */ public void paintIcon(Component c, Graphics g, int x, int y) { - // TODO: pick up appropriate UI colors - g.setColor(Color.black); + y = y + getShift(); + g.setColor(MetalLookAndFeel.getBlack()); g.drawLine(x, y, x + 9, y); g.drawLine(x, y + 1, x, y + 15); g.drawLine(x, y + 15, x + 12, y + 15); @@ -580,7 +579,7 @@ public class MetalIconFactory implements Serializable g.drawLine(x + 7, y + 2, x + 11, y + 6); g.drawLine(x + 8, y + 1, x + 9, y + 1); - g.setColor(new Color(204, 204, 255)); + g.setColor(MetalLookAndFeel.getPrimaryControl()); g.drawLine(x + 1, y + 1, x + 7, y + 1); g.drawLine(x + 1, y + 1, x + 1, y + 14); g.drawLine(x + 1, y + 14, x + 11, y + 14); @@ -601,7 +600,9 @@ public class MetalIconFactory implements Serializable } /** - * Returns the shift (???). + * Returns the vertical shift, in pixels, applied when painting the icon. + * The default value is zero, but subclasses may override this (for + * example, see {@link TreeLeafIcon}). * * @return The shift. */ @@ -649,21 +650,21 @@ public class MetalIconFactory implements Serializable */ public void paintIcon(Component c, Graphics g, int x, int y) { - // TODO: pick up appropriate UI colors - g.setColor(Color.black); - g.drawLine(x, y + 3, x, y + 12); - g.drawLine(x, y + 12, x + 15, y + 12); - g.drawLine(x + 15, y + 12, x + 15, y + 2); - g.drawLine(x + 14, y + 3, x + 9, y + 3); - g.drawLine(x + 8, y + 2, x + 1, y + 2); - g.setColor(new Color(204, 204, 255)); - g.fillRect(x + 2, y + 4, 7, 8); - g.fillRect(x + 9, y + 5, 6, 7); - g.setColor(new Color(102, 102, 153)); - g.drawLine(x + 9, y + 2, x + 14, y + 2); - g.setColor(new Color(50, 50, 120)); - g.drawLine(x + 9, y + 1, x + 15, y + 1); - g.drawLine(x + 10, y, x + 15, y); + y = y + getShift(); + g.setColor(MetalLookAndFeel.getBlack()); + g.drawLine(x, y + 6, x, y + 15); + g.drawLine(x, y + 15, x + 15, y + 15); + g.drawLine(x + 15, y + 15, x + 15, y + 5); + g.drawLine(x + 14, y + 6, x + 9, y + 6); + g.drawLine(x + 8, y + 5, x + 1, y + 5); + g.setColor(MetalLookAndFeel.getPrimaryControl()); + g.fillRect(x + 2, y + 7, 7, 8); + g.fillRect(x + 9, y + 8, 6, 7); + g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); + g.drawLine(x + 9, y + 5, x + 14, y + 5); + g.setColor(MetalLookAndFeel.getPrimaryControlDarkShadow()); + g.drawLine(x + 9, y + 4, x + 15, y + 4); + g.drawLine(x + 10, y + 3, x + 15, y + 3); } /** @@ -679,7 +680,9 @@ public class MetalIconFactory implements Serializable } /** - * Returns the shift (???). + * Returns the vertical shift, in pixels, applied when painting the icon. + * The default value is zero, but subclasses may override this (for + * example, see {@link TreeFolderIcon}). * * @return The shift. */ @@ -1036,20 +1039,22 @@ public class MetalIconFactory implements Serializable g.drawLine(x + 6, y + 14, x, y + 8); g.drawLine(x, y + 7, x, y + 1); - // Fill the icon. - if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme - && enabled) - { - String gradient; - if (focus) - gradient = "Slider.focusGradient"; - else - gradient = "Slider.gradient"; - MetalUtils.paintGradient(g, x + 1, y + 2, 12, 13, - SwingConstants.VERTICAL, gradient, - gradientMask); - } - else +// The following is commented out until the masking for the gradient painting +// is working correctly +// // Fill the icon. +// if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme +// && enabled) +// { +// String gradient; +// if (focus) +// gradient = "Slider.focusGradient"; +// else +// gradient = "Slider.gradient"; +// MetalUtils.paintGradient(g, x + 1, y + 2, 12, 13, +// SwingConstants.VERTICAL, gradient, +// gradientMask); +// } +// else { if (focus) g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); @@ -1268,23 +1273,23 @@ public class MetalIconFactory implements Serializable */ public void paintIcon(Component c, Graphics g, int x, int y) { - g.setColor(new Color(102, 102, 153)); + g.setColor(MetalLookAndFeel.getPrimaryControlDarkShadow()); g.fillRect(x + 1, y, 14, 2); g.fillRect(x, y + 1, 2, 14); g.fillRect(x + 1, y + 14, 14, 2); g.fillRect(x + 14, y + 1, 2, 14); g.drawLine(x + 2, y + 5, x + 14, y + 5); - g.setColor(new Color(204, 204, 255)); + g.setColor(MetalLookAndFeel.getPrimaryControl()); g.fillRect(x + 2, y + 2, 12, 3); - g.setColor(new Color(102, 102, 153)); + g.setColor(MetalLookAndFeel.getPrimaryControlDarkShadow()); g.drawLine(x + 3, y + 3, x + 3, y + 3); g.drawLine(x + 6, y + 3, x + 6, y + 3); g.drawLine(x + 9, y + 3, x + 9, y + 3); g.drawLine(x + 12, y + 3, x + 12, y + 3); - g.setColor(Color.white); + g.setColor(MetalLookAndFeel.getWhite()); g.fillRect(x + 2, y + 6, 12, 8); g.drawLine(x + 2, y + 2, x + 2, y + 2); g.drawLine(x + 5, y + 2, x + 5, y + 2); @@ -1697,20 +1702,22 @@ public class MetalIconFactory implements Serializable g.drawLine(x + 8, y + 14, x + 1, y + 14); g.drawLine(x, y + 13, x, y + 1); - // Fill the icon. - if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme - && enabled) - { - String gradient; - if (focus) - gradient = "Slider.focusGradient"; - else - gradient = "Slider.gradient"; - MetalUtils.paintGradient(g, x + 2, y + 1, 13, 12, - SwingConstants.HORIZONTAL, gradient, - gradientMask); - } - else +// The following is commented out until the masking for the gradient painting +// is working correctly +// // Fill the icon. +// if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme +// && enabled) +// { +// String gradient; +// if (focus) +// gradient = "Slider.focusGradient"; +// else +// gradient = "Slider.gradient"; +// MetalUtils.paintGradient(g, x + 2, y + 1, 13, 12, +// SwingConstants.HORIZONTAL, gradient, +// gradientMask); +// } +// else { if (focus) g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); @@ -1883,7 +1890,8 @@ public class MetalIconFactory implements Serializable } /** - * Returns the shift (???). + * Returns the vertical shift, in pixels, applied when painting the icon. + * This overridden method returns <code>-1</code>. * * @return The shift. */ @@ -1918,7 +1926,8 @@ public class MetalIconFactory implements Serializable } /** - * Returns the shift (???). + * Returns the vertical shift, in pixels, applied when painting the icon. + * This overridden method returns <code>2</code>. * * @return The shift. */ diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalLookAndFeel.java b/libjava/classpath/javax/swing/plaf/metal/MetalLookAndFeel.java index 8a5a61107c1..a9a6790931f 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalLookAndFeel.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalLookAndFeel.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.plaf.metal; +import gnu.classpath.SystemProperties; + import java.awt.Color; import java.awt.Font; @@ -81,16 +83,15 @@ public class MetalLookAndFeel extends BasicLookAndFeel */ public MetalLookAndFeel() { - createDefaultTheme(); + // Nothing to do here. } /** - * Sets the current theme to a new instance of {@link OceanTheme}. + * Sets the current theme to a new instance of {@link DefaultMetalTheme}. */ protected void createDefaultTheme() { - if (theme == null) - setCurrentTheme(new OceanTheme()); + getCurrentTheme(); } /** @@ -149,6 +150,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel public UIDefaults getDefaults() { + createDefaultTheme(); if (LAF_defaults == null) { LAF_defaults = super.getDefaults(); @@ -887,7 +889,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "CheckBox.border", MetalBorders.getButtonBorder(), "CheckBox.disabledText", getInactiveControlTextColor(), "CheckBox.focus", getFocusColor(), - "CheckBox.font", new FontUIResource("Dialog", Font.BOLD, 12), + "CheckBox.font", getControlTextFont(), "CheckBox.foreground", getControlTextColor(), "CheckBox.icon", new UIDefaults.ProxyLazyValue("javax.swing.plaf.metal.MetalCheckBoxIcon"), @@ -903,7 +905,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "CheckBoxMenuItem.commandSound", "sounds/MenuItemCommand.wav", "CheckBoxMenuItem.checkIcon", MetalIconFactory.getCheckBoxMenuItemIcon(), "CheckBoxMenuItem.disabledForeground", getMenuDisabledForeground(), - "CheckBoxMenuItem.font", new FontUIResource("Dialog", Font.BOLD, 12), + "CheckBoxMenuItem.font", getMenuTextFont(), "CheckBoxMenuItem.foreground", getMenuForeground(), "CheckBoxMenuItem.selectionBackground", getMenuSelectedBackground(), "CheckBoxMenuItem.selectionForeground", getMenuSelectedForeground(), @@ -922,7 +924,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "ComboBox.buttonShadow", getControlShadow(), "ComboBox.disabledBackground", getControl(), "ComboBox.disabledForeground", getInactiveSystemTextColor(), - "ComboBox.font", new FontUIResource("Dialog", Font.BOLD, 12), + "ComboBox.font", getControlTextFont(), "ComboBox.foreground", getControlTextColor(), "ComboBox.selectionBackground", getPrimaryControlShadow(), "ComboBox.selectionForeground", getControlTextColor(), @@ -933,10 +935,11 @@ public class MetalLookAndFeel extends BasicLookAndFeel "DesktopIcon.foreground", getControlTextColor(), "DesktopIcon.width", new Integer(160), "DesktopIcon.border", MetalBorders.getDesktopIconBorder(), + "DesktopIcon.font", getControlTextFont(), "EditorPane.background", getWindowBackground(), "EditorPane.caretForeground", getUserTextColor(), - "EditorPane.font", new FontUIResource("Dialog", Font.BOLD, 12), + "EditorPane.font", getControlTextFont(), "EditorPane.foreground", getUserTextColor(), "EditorPane.inactiveForeground", getInactiveSystemTextColor(), "EditorPane.selectionBackground", getTextHighlightColor(), @@ -1021,7 +1024,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "Menu.borderPainted", Boolean.TRUE, "MenuItem.commandSound", "sounds/MenuItemCommand.wav", "Menu.disabledForeground", getMenuDisabledForeground(), - "Menu.font", getControlTextFont(), + "Menu.font", getMenuTextFont(), "Menu.foreground", getMenuForeground(), "Menu.selectionBackground", getMenuSelectedBackground(), "Menu.selectionForeground", getMenuSelectedForeground(), @@ -1030,7 +1033,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "MenuBar.background", getMenuBackground(), "MenuBar.border", new MetalBorders.MenuBarBorder(), - "MenuBar.font", getControlTextFont(), + "MenuBar.font", getMenuTextFont(), "MenuBar.foreground", getMenuForeground(), "MenuBar.highlight", getControlHighlight(), "MenuBar.shadow", getControlShadow(), @@ -1044,7 +1047,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "MenuItem.border", new MetalBorders.MenuItemBorder(), "MenuItem.borderPainted", Boolean.TRUE, "MenuItem.disabledForeground", getMenuDisabledForeground(), - "MenuItem.font", getControlTextFont(), + "MenuItem.font", getMenuTextFont(), "MenuItem.foreground", getMenuForeground(), "MenuItem.selectionBackground", getMenuSelectedBackground(), "MenuItem.selectionForeground", getMenuSelectedForeground(), @@ -1085,13 +1088,13 @@ public class MetalLookAndFeel extends BasicLookAndFeel "PopupMenu.background", getMenuBackground(), "PopupMenu.border", new MetalBorders.PopupMenuBorder(), - "PopupMenu.font", new FontUIResource("Dialog", Font.BOLD, 12), + "PopupMenu.font", getMenuTextFont(), "PopupMenu.foreground", getMenuForeground(), "PopupMenu.popupSound", "sounds/PopupMenuPopup.wav", "ProgressBar.background", getControl(), "ProgressBar.border", new BorderUIResource.LineBorderUIResource(getControlDarkShadow(), 1), - "ProgressBar.font", new FontUIResource("Dialog", Font.BOLD, 12), + "ProgressBar.font", getControlTextFont(), "ProgressBar.foreground", getPrimaryControlShadow(), "ProgressBar.selectionBackground", getPrimaryControlDarkShadow(), "ProgressBar.selectionForeground", getControl(), @@ -1125,7 +1128,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel MetalIconFactory.getRadioButtonMenuItemIcon(), "RadioButtonMenuItem.commandSound", "sounds/MenuItemCommand.wav", "RadioButtonMenuItem.disabledForeground", getMenuDisabledForeground(), - "RadioButtonMenuItem.font", MetalLookAndFeel.getControlTextFont(), + "RadioButtonMenuItem.font", getMenuTextFont(), "RadioButtonMenuItem.foreground", getMenuForeground(), "RadioButtonMenuItem.margin", new InsetsUIResource(2, 2, 2, 2), "RadioButtonMenuItem.selectionBackground", @@ -1172,7 +1175,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "Spinner.arrowButtonInsets", new InsetsUIResource(0, 0, 0, 0), "Spinner.background", getControl(), "Spinner.border", MetalBorders.getTextFieldBorder(), - "Spinner.font", new FontUIResource("Dialog", Font.BOLD, 12), + "Spinner.font", getControlTextFont(), "Spinner.foreground", getControl(), "SplitPane.background", getControl(), @@ -1189,7 +1192,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "TabbedPane.contentOpaque", Boolean.TRUE, "TabbedPane.darkShadow", getControlDarkShadow(), "TabbedPane.focus", getPrimaryControlDarkShadow(), - "TabbedPane.font", new FontUIResource("Dialog", Font.BOLD, 12), + "TabbedPane.font", getControlTextFont(), "TabbedPane.foreground", getControlTextColor(), "TabbedPane.highlight", getControlHighlight(), "TabbedPane.light", getControl(), @@ -1200,7 +1203,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "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 @@ -1252,7 +1255,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "TextPane.selectionForeground", getHighlightedTextColor(), "TitledBorder.border", new LineBorderUIResource(getPrimaryControl(), 1), - "TitledBorder.font", new FontUIResource("Dialog", Font.BOLD, 12), + "TitledBorder.font", getControlTextFont(), "TitledBorder.titleColor", getSystemTextColor(), "ToggleButton.background", getControl(), @@ -1274,7 +1277,7 @@ public class MetalLookAndFeel extends BasicLookAndFeel "ToolBar.dockingForeground", getPrimaryControlDarkShadow(), "ToolBar.floatingBackground", getMenuBackground(), "ToolBar.floatingForeground", getPrimaryControl(), - "ToolBar.font", new FontUIResource("Dialog", Font.BOLD, 12), + "ToolBar.font", getMenuTextFont(), "ToolBar.foreground", getMenuForeground(), "ToolBar.highlight", getControlHighlight(), "ToolBar.light", getControlHighlight(), @@ -1354,7 +1357,14 @@ public class MetalLookAndFeel extends BasicLookAndFeel public static MetalTheme getCurrentTheme() { if (theme == null) - theme = new OceanTheme(); + { + // swing.metalTheme property documented here: + // http://java.sun.com/j2se/1.5.0/docs/guide/swing/1.5/index.html + if ("steel".equals(SystemProperties.getProperty("swing.metalTheme"))) + theme = new DefaultMetalTheme(); + else + theme = new OceanTheme(); + } return theme; } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalMenuBarUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalMenuBarUI.java index ff763ea9da9..40661946b1a 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalMenuBarUI.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/plaf/metal/MetalRadioButtonUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalRadioButtonUI.java index 046e4942ee1..57f5bbe3e0a 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalRadioButtonUI.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalRadioButtonUI.java @@ -177,7 +177,7 @@ public class MetalRadioButtonUI protected void paintFocus(Graphics g, Rectangle t, Dimension d) { g.setColor(focusColor); - g.drawRect(t.x - 1, t.y - 1, t.width + 2, t.height + 2); + g.drawRect(t.x - 1, t.y - 1, t.width + 1, t.height + 1); } } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalScrollBarUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalScrollBarUI.java index 75f2750ae9c..4c75fcb4f14 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalScrollBarUI.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalScrollBarUI.java @@ -1,5 +1,5 @@ /* MetalScrollBarUI.java - Copyright (C) 2005 Free Software Foundation, Inc. + Copyright (C) 2005, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -169,6 +169,7 @@ public class MetalScrollBarUI extends BasicScrollBarUI Boolean prop = (Boolean) scrollbar.getClientProperty(FREE_STANDING_PROP); isFreeStanding = prop == null ? true : prop.booleanValue(); scrollBarShadowColor = UIManager.getColor("ScrollBar.shadow"); + scrollBarWidth = UIManager.getInt("ScrollBar.width"); super.installDefaults(); } @@ -187,7 +188,10 @@ public class MetalScrollBarUI extends BasicScrollBarUI /** * Creates a new button to use as the control at the lower end of the - * {@link JScrollBar}. + * {@link JScrollBar}. This method assigns the new button (an instance of + * {@link MetalScrollButton} to the {@link #decreaseButton} field, and also + * returns the button. The button width is determined by the + * <code>ScrollBar.width</code> setting in the UI defaults. * * @param orientation the orientation of the button ({@link #NORTH}, * {@link #SOUTH}, {@link #EAST} or {@link #WEST}). @@ -196,7 +200,6 @@ public class MetalScrollBarUI extends BasicScrollBarUI */ protected JButton createDecreaseButton(int orientation) { - scrollBarWidth = UIManager.getInt("ScrollBar.width"); decreaseButton = new MetalScrollButton(orientation, scrollBarWidth, isFreeStanding); return decreaseButton; @@ -204,7 +207,10 @@ public class MetalScrollBarUI extends BasicScrollBarUI /** * Creates a new button to use as the control at the upper end of the - * {@link JScrollBar}. + * {@link JScrollBar}. This method assigns the new button (an instance of + * {@link MetalScrollButton} to the {@link #increaseButton} field, and also + * returns the button. The button width is determined by the + * <code>ScrollBar.width</code> setting in the UI defaults. * * @param orientation the orientation of the button ({@link #NORTH}, * {@link #SOUTH}, {@link #EAST} or {@link #WEST}). @@ -213,7 +219,6 @@ public class MetalScrollBarUI extends BasicScrollBarUI */ protected JButton createIncreaseButton(int orientation) { - scrollBarWidth = UIManager.getInt("ScrollBar.width"); increaseButton = new MetalScrollButton(orientation, scrollBarWidth, isFreeStanding); return increaseButton; diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalSliderUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalSliderUI.java index 0f824418c5d..b3e8707c94d 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalSliderUI.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalSliderUI.java @@ -352,7 +352,10 @@ public class MetalSliderUI extends BasicSliderUI */ public int getTickLength() { - return tickLength + TICK_BUFFER; + int len = tickLength + TICK_BUFFER + 1; + if (slider.getOrientation() == JSlider.VERTICAL) + len += 2; + return len; } /** @@ -406,9 +409,9 @@ public class MetalSliderUI extends BasicSliderUI // Note the incoming 'g' has a translation in place to get us to the // start of the tick rect already... if (slider.isEnabled()) - g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); + g.setColor(slider.getForeground()); else - g.setColor(MetalLookAndFeel.getControlDisabled()); + g.setColor(MetalLookAndFeel.getControlShadow()); g.drawLine(x, TICK_BUFFER, x, TICK_BUFFER + tickLength / 2); } @@ -425,10 +428,10 @@ public class MetalSliderUI extends BasicSliderUI // Note the incoming 'g' has a translation in place to get us to the // start of the tick rect already... if (slider.isEnabled()) - g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); + g.setColor(slider.getForeground()); else - g.setColor(MetalLookAndFeel.getControlDisabled()); - g.drawLine(x, TICK_BUFFER, x, TICK_BUFFER + tickLength); + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawLine(x, TICK_BUFFER, x, TICK_BUFFER + tickLength - 1); } /** @@ -444,10 +447,10 @@ public class MetalSliderUI extends BasicSliderUI // Note the incoming 'g' has a translation in place to get us to the // start of the tick rect already... if (slider.isEnabled()) - g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); + g.setColor(slider.getForeground()); else - g.setColor(MetalLookAndFeel.getControlDisabled()); - g.drawLine(TICK_BUFFER - 1, y, TICK_BUFFER - 1 + tickLength / 2, y); + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawLine(TICK_BUFFER, y, TICK_BUFFER + tickLength / 2, y); } /** @@ -463,10 +466,10 @@ public class MetalSliderUI extends BasicSliderUI // Note the incoming 'g' has a translation in place to get us to the // start of the tick rect already... if (slider.isEnabled()) - g.setColor(MetalLookAndFeel.getPrimaryControlShadow()); + g.setColor(slider.getForeground()); else - g.setColor(MetalLookAndFeel.getControlDisabled()); - g.drawLine(TICK_BUFFER - 1, y, TICK_BUFFER - 1 + tickLength, y); + g.setColor(MetalLookAndFeel.getControlShadow()); + g.drawLine(TICK_BUFFER, y, TICK_BUFFER + tickLength, y); } } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalSplitPaneDivider.java b/libjava/classpath/javax/swing/plaf/metal/MetalSplitPaneDivider.java index 6081c355c37..a3069daa9c5 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalSplitPaneDivider.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalSplitPaneDivider.java @@ -38,18 +38,14 @@ exception statement from your version. */ package javax.swing.plaf.metal; import java.awt.Color; -import java.awt.Component; -import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; -import java.awt.LayoutManager; -import java.awt.Point; +import java.awt.Insets; +import javax.swing.JButton; import javax.swing.JSplitPane; -import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.border.Border; -import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.plaf.basic.BasicSplitPaneDivider; /** @@ -59,6 +55,143 @@ import javax.swing.plaf.basic.BasicSplitPaneDivider; */ class MetalSplitPaneDivider extends BasicSplitPaneDivider { + /** + * The button pixel data, as indices into the colors array below. + * This is the version for 'left' buttons. + * + * This is slightly different from the icon in Sun's version, it is + * one pixel smaller and is more consistent with BUTTON_SPRITE_R. + */ + static final byte[][] BUTTON_SPRITE_L = {{ 0, 0, 0, 2, 0, 0, 0, 0 }, + { 0, 0, 2, 1, 1, 0, 0, 0 }, + { 0, 2, 1, 1, 1, 1, 0, 0 }, + { 2, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 3, 3, 3, 3, 3, 3, 3 }}; + + /** + * The button pixel data, as indices into the colors array below. + * This is the version for 'right' buttons. + */ + static final byte[][] BUTTON_SPRITE_R = {{ 2, 2, 2, 2, 2, 2, 2, 2 }, + { 0, 1, 1, 1, 1, 1, 1, 3 }, + { 0, 0, 1, 1, 1, 1, 3, 0 }, + { 0, 0, 0, 1, 1, 3, 0, 0 }, + { 0, 0, 0, 0, 3, 0, 0, 0 }}; + + private class MetalOneTouchButton + extends JButton + { + /** + * Denotes a left button. + */ + static final int LEFT = 0; + + /** + * Denotes a right button. + */ + static final int RIGHT = 1; + + /** + * The colors for the button sprite. + */ + private Color[] colors; + + /** + * Either LEFT or RIGHT. + */ + private int direction; + + /** + * Creates a new instance. + * + * @param dir either LEFT or RIGHT + */ + MetalOneTouchButton(int dir) + { + direction = dir; + colors = new Color[4]; + } + + /** + * Never allow borders. + */ + public void setBorder(Border b) + { + } + + /** + * Never allow focus traversal. + */ + public boolean isFocusTraversable() + { + return false; + } + + /** + * Paints the one touch button. + */ + public void paint(Graphics g) + { + if (splitPane != null) + { + // Update colors here to reflect dynamic changes to the theme. + colors[0] = getBackground(); + colors[1] = MetalLookAndFeel.getPrimaryControlDarkShadow(); + colors[2] = MetalLookAndFeel.getPrimaryControlInfo(); + colors[3] = MetalLookAndFeel.getPrimaryControlHighlight(); + + // Fill background. + g.setColor(getBackground()); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Pressed buttons have slightly different color mapping. + if (getModel().isPressed()) + colors[1] = colors[2]; + + byte[][] sprite; + if (direction == LEFT) + sprite = BUTTON_SPRITE_L; + else + sprite = BUTTON_SPRITE_R; + + if (orientation == JSplitPane.VERTICAL_SPLIT) + { + // Draw the sprite as it is. + for (int y = 0; y < sprite.length; y++) + { + byte[] line = sprite[y]; + for (int x = 0; x < line.length; x++) + { + int c = line[x]; + if (c != 0) + { + g.setColor(colors[c]); + g.fillRect(x + 1, y + 1, 1, 1); + } + } + } + } + else + { + // Draw the sprite with swapped X and Y axis. + for (int y = 0; y < sprite.length; y++) + { + byte[] line = sprite[y]; + for (int x = 0; x < line.length; x++) + { + int c = line[x]; + if (c != 0) + { + g.setColor(colors[c]); + g.fillRect(y + 1, x + 1, 1, 1); + } + } + } + } + } + } + } + /** The dark color in the pattern. */ Color dark; @@ -79,7 +212,6 @@ class MetalSplitPaneDivider extends BasicSplitPaneDivider public MetalSplitPaneDivider(MetalSplitPaneUI ui, Color light, Color dark) { super(ui); - setLayout(new MetalDividerLayout()); this.splitPane = super.splitPane; this.orientation = super.orientation; this.light = light; @@ -106,126 +238,27 @@ class MetalSplitPaneDivider extends BasicSplitPaneDivider if (border != null) border.paintBorder(this, g, 0, 0, s.width, s.height); - MetalUtils.fillMetalPattern(splitPane, g, 2, 2, s.width - 4, s.height - 4, + Insets i = getInsets(); + MetalUtils.fillMetalPattern(splitPane, g, i.left + 2, i.top + 2, + s.width - i.left - i.right - 4, + s.height - i.top - i.bottom - 4, light, dark); - if (splitPane.isOneTouchExpandable()) - { - ((BasicArrowButton) rightButton).paint(g); - ((BasicArrowButton) leftButton).paint(g); - } + super.paint(g); } - - /** - * This helper class acts as the Layout Manager for the divider. - */ - public class MetalDividerLayout implements LayoutManager - { - /** The right button. */ - BasicArrowButton rb; - - /** The left button. */ - BasicArrowButton lb; - - /** - * Creates a new DividerLayout object. - */ - public MetalDividerLayout() - { - // Nothing to do here - } - - /** - * This method is called when a Component is added. - * - * @param string The constraints string. - * @param c The Component to add. - */ - public void addLayoutComponent(String string, Component c) - { - // Nothing to do here, constraints are set depending on - // orientation in layoutContainer - } - - /** - * This method is called to lay out the container. - * - * @param c The container to lay out. - */ - public void layoutContainer(Container c) - { - // The only components we care about setting up are the - // one touch buttons. - if (splitPane.isOneTouchExpandable()) - { - if (c.getComponentCount() == 2) - { - Component c1 = c.getComponent(0); - Component c2 = c.getComponent(1); - if ((c1 instanceof BasicArrowButton) - && (c2 instanceof BasicArrowButton)) - { - lb = (BasicArrowButton) c1; - rb = (BasicArrowButton) c2; - } - } - if (rb != null && lb != null) - { - Point p = getLocation(); - lb.setSize(lb.getPreferredSize()); - rb.setSize(rb.getPreferredSize()); - lb.setLocation(p.x, p.y); - - if (orientation == JSplitPane.HORIZONTAL_SPLIT) - { - rb.setDirection(SwingConstants.EAST); - lb.setDirection(SwingConstants.WEST); - rb.setLocation(p.x, p.y + lb.getHeight()); - } - else - { - rb.setDirection(SwingConstants.SOUTH); - lb.setDirection(SwingConstants.NORTH); - rb.setLocation(p.x + lb.getWidth(), p.y); - } - } - } - } - - /** - * This method returns the minimum layout size. - * - * @param c The container to calculate for. - * - * @return The minimum layout size. - */ - public Dimension minimumLayoutSize(Container c) - { - return preferredLayoutSize(c); - } - /** - * This method returns the preferred layout size. - * - * @param c The container to calculate for. - * - * @return The preferred layout size. - */ - public Dimension preferredLayoutSize(Container c) - { - int dividerSize = getDividerSize(); - return new Dimension(dividerSize, dividerSize); - } + protected JButton createLeftOneTouchButton() + { + JButton b = new MetalOneTouchButton(MetalOneTouchButton.LEFT); + b.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + b.setRequestFocusEnabled(false); + return b; + } - /** - * This method is called when a component is removed. - * - * @param c The component to remove. - */ - public void removeLayoutComponent(Component c) - { - // Nothing to do here. If buttons are removed - // they will not be layed out when layoutContainer is - // called. - } + protected JButton createRightOneTouchButton() + { + JButton b = new MetalOneTouchButton(MetalOneTouchButton.RIGHT); + b.setMinimumSize(new Dimension(ONE_TOUCH_SIZE, ONE_TOUCH_SIZE)); + b.setRequestFocusEnabled(false); + return b; } } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalTabbedPaneUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalTabbedPaneUI.java index 20135fc857e..53eaa3cac5a 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalTabbedPaneUI.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalTabbedPaneUI.java @@ -1159,7 +1159,7 @@ public class MetalTabbedPaneUI extends BasicTabbedPaneUI g.drawLine(x + 1, y + 1, x + 1, rect.y + 1); if (rect.y + rect.height < y + h - 2) { - g.drawLine(x + y, rect.y + rect.height + 1, x + 1, y + h + 2); + g.drawLine(x + 1, rect.y + rect.height + 1, x + 1, y + h + 2); } } } diff --git a/libjava/classpath/javax/swing/plaf/metal/MetalToolTipUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalToolTipUI.java index d1040347fc6..6647cc02d16 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalToolTipUI.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/plaf/metal/MetalTreeUI.java b/libjava/classpath/javax/swing/plaf/metal/MetalTreeUI.java index 3ea37c82f18..ed1e5b4d825 100644 --- a/libjava/classpath/javax/swing/plaf/metal/MetalTreeUI.java +++ b/libjava/classpath/javax/swing/plaf/metal/MetalTreeUI.java @@ -38,14 +38,15 @@ exception statement from your version. */ package javax.swing.plaf.metal; -import gnu.classpath.NotImplementedException; - import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JComponent; import javax.swing.JTree; +import javax.swing.UIManager; import javax.swing.tree.TreePath; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTreeUI; @@ -56,6 +57,68 @@ import javax.swing.plaf.basic.BasicTreeUI; public class MetalTreeUI extends BasicTreeUI { /** + * Listens for property changes of the line style and updates the + * internal setting. + */ + private class LineStyleListener + implements PropertyChangeListener + { + + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals(LINE_STYLE_PROPERTY)) + decodeLineStyle(e.getNewValue()); + } + + } + + /** + * The key to the lineStyle client property. + */ + private static final String LINE_STYLE_PROPERTY = "JTree.lineStyle"; + + /** + * The property value indicating no line style. + */ + private static final String LINE_STYLE_VALUE_NONE = "None"; + + /** + * The property value indicating angled line style. + */ + private static final String LINE_STYLE_VALUE_ANGLED = "Angled"; + + /** + * The property value indicating horizontal line style. + */ + private static final String LINE_STYLE_VALUE_HORIZONTAL = "Horizontal"; + + /** + * The line style for None. + */ + private static final int LINE_STYLE_NONE = 0; + + /** + * The line style for Angled. + */ + private static final int LINE_STYLE_ANGLED = 1; + + /** + * The line style for Horizontal. + */ + private static final int LINE_STYLE_HORIZONTAL = 2; + + /** + * The current line style. + */ + private int lineStyle; + + /** + * Listens for changes on the line style property and updates the + * internal settings. + */ + private PropertyChangeListener lineStyleListener; + + /** * Constructs a new instance of <code>MetalTreeUI</code>. */ public MetalTreeUI() @@ -103,8 +166,13 @@ public class MetalTreeUI extends BasicTreeUI */ public void installUI(JComponent c) { - // TODO: What to do here, if anything? super.installUI(c); + + Object lineStyleProp = c.getClientProperty(LINE_STYLE_PROPERTY); + decodeLineStyle(lineStyleProp); + if (lineStyleListener == null) + lineStyleListener = new LineStyleListener(); + c.addPropertyChangeListener(lineStyleListener); } /** @@ -124,8 +192,10 @@ public class MetalTreeUI extends BasicTreeUI */ public void uninstallUI(JComponent c) { - // TODO: What to do here? super.uninstallUI(c); + if (lineStyleListener != null) + c.removePropertyChangeListener(lineStyleListener); + lineStyleListener = null; } /** @@ -135,9 +205,15 @@ public class MetalTreeUI extends BasicTreeUI * @param lineStyleFlag - String representation */ protected void decodeLineStyle(Object lineStyleFlag) - throws NotImplementedException { - // FIXME: not implemented + if (lineStyleFlag == null || lineStyleFlag.equals(LINE_STYLE_VALUE_ANGLED)) + lineStyle = LINE_STYLE_ANGLED; + else if (lineStyleFlag.equals(LINE_STYLE_VALUE_HORIZONTAL)) + lineStyle = LINE_STYLE_HORIZONTAL; + else if (lineStyleFlag.equals(LINE_STYLE_VALUE_NONE)) + lineStyle = LINE_STYLE_NONE; + else + lineStyle = LINE_STYLE_ANGLED; } /** @@ -170,6 +246,9 @@ public class MetalTreeUI extends BasicTreeUI // Calls BasicTreeUI's paint since it takes care of painting all // types of icons. super.paint(g, c); + + if (lineStyle == LINE_STYLE_HORIZONTAL) + paintHorizontalSeparators(g, c); } /** @@ -179,9 +258,28 @@ public class MetalTreeUI extends BasicTreeUI * @param c - the current component to draw */ protected void paintHorizontalSeparators(Graphics g, JComponent c) - throws NotImplementedException { - // FIXME: not implemented + g.setColor(UIManager.getColor("Tree.line")); + Rectangle clip = g.getClipBounds(); + int row0 = getRowForPath(tree, getClosestPathForLocation(tree, 0, clip.y)); + int row1 = + getRowForPath(tree, getClosestPathForLocation(tree, 0, + clip.y + clip.height - 1)); + if (row0 >= 0 && row1 >= 0) + { + for (int i = row0; i <= row1; i++) + { + TreePath p = getPathForRow(tree, i); + if (p != null && p.getPathCount() == 2) + { + Rectangle r = getPathBounds(tree, getPathForRow(tree, i)); + if (r != null) + { + g.drawLine(clip.x, r.y, clip.x + clip.width, r.y); + } + } + } + } } @@ -197,7 +295,8 @@ public class MetalTreeUI extends BasicTreeUI protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) { - super.paintVerticalPartOfLeg(g, clipBounds, insets, path); + if (lineStyle == LINE_STYLE_ANGLED) + super.paintVerticalPartOfLeg(g, clipBounds, insets, path); } /** @@ -211,7 +310,8 @@ public class MetalTreeUI extends BasicTreeUI boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { - super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds, path, row, - isExpanded, hasBeenExpanded, isLeaf); + if (lineStyle == LINE_STYLE_ANGLED) + super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds, path, row, + isExpanded, hasBeenExpanded, isLeaf); } } diff --git a/libjava/classpath/javax/swing/plaf/metal/OceanTheme.java b/libjava/classpath/javax/swing/plaf/metal/OceanTheme.java index 9d76ff7e808..1ea0bc24385 100644 --- a/libjava/classpath/javax/swing/plaf/metal/OceanTheme.java +++ b/libjava/classpath/javax/swing/plaf/metal/OceanTheme.java @@ -266,6 +266,8 @@ public class OceanTheme extends DefaultMetalTheme defaults.put("Tree.selectionBorderColor", PRIMARY1); // Borders. + defaults.put("List.focusCellHighlightBorder", + new LineBorderUIResource(getPrimary1())); defaults.put("Table.focusCellHighlightBorder", new LineBorderUIResource(getPrimary1())); diff --git a/libjava/classpath/javax/swing/plaf/synth/SynthLookAndFeel.java b/libjava/classpath/javax/swing/plaf/synth/SynthLookAndFeel.java index 1a2489e7ea6..46a95f09860 100644 --- a/libjava/classpath/javax/swing/plaf/synth/SynthLookAndFeel.java +++ b/libjava/classpath/javax/swing/plaf/synth/SynthLookAndFeel.java @@ -216,9 +216,7 @@ public class SynthLookAndFeel * @throws IllegalArgumentException if one of the parameters is * <code>null</code> */ - // FIXME: The signature in the JDK has a Class<?> here. Should be fixed as - // soon as we switch to the generics branch. - public void load(InputStream in, Class resourceBase) + public void load(InputStream in, Class<?> resourceBase) throws ParseException, IllegalArgumentException, NotImplementedException { // FIXME: Implement this correctly. diff --git a/libjava/classpath/javax/swing/table/AbstractTableModel.java b/libjava/classpath/javax/swing/table/AbstractTableModel.java index 7914e0b3f46..66b6a0743b6 100644 --- a/libjava/classpath/javax/swing/table/AbstractTableModel.java +++ b/libjava/classpath/javax/swing/table/AbstractTableModel.java @@ -125,7 +125,7 @@ public abstract class AbstractTableModel implements TableModel, Serializable * * @return The class. */ - public Class getColumnClass(int columnIndex) + public Class<?> getColumnClass(int columnIndex) { return Object.class; } @@ -294,7 +294,7 @@ public abstract class AbstractTableModel implements TableModel, Serializable * * @return An array of listeners (possibly empty). */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/table/DefaultTableColumnModel.java b/libjava/classpath/javax/swing/table/DefaultTableColumnModel.java index 33e68ea9fcd..e4e7201b184 100644 --- a/libjava/classpath/javax/swing/table/DefaultTableColumnModel.java +++ b/libjava/classpath/javax/swing/table/DefaultTableColumnModel.java @@ -71,7 +71,7 @@ public class DefaultTableColumnModel /** * Storage for the table columns. */ - protected Vector tableColumns; + protected Vector<TableColumn> tableColumns; /** * A selection model that keeps track of column selections. @@ -187,7 +187,7 @@ public class DefaultTableColumnModel throw new IllegalArgumentException("Index 'i' out of range."); if (j < 0 || j >= columnCount) throw new IllegalArgumentException("Index 'j' out of range."); - Object column = tableColumns.remove(i); + TableColumn column = tableColumns.remove(i); tableColumns.add(j, column); fireColumnMoved(new TableColumnModelEvent(this, i, j)); } @@ -221,7 +221,7 @@ public class DefaultTableColumnModel * * @return An enumeration of the columns in the model. */ - public Enumeration getColumns() + public Enumeration<TableColumn> getColumns() { return tableColumns.elements(); } @@ -597,7 +597,7 @@ public class DefaultTableColumnModel * @return An array containing the listeners (of the specified type) that * are registered with this model. */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/table/DefaultTableModel.java b/libjava/classpath/javax/swing/table/DefaultTableModel.java index 79285903c00..1b68ce2be81 100644 --- a/libjava/classpath/javax/swing/table/DefaultTableModel.java +++ b/libjava/classpath/javax/swing/table/DefaultTableModel.java @@ -625,7 +625,7 @@ public class DefaultTableModel extends AbstractTableModel if (columnCount > columnIdentifiers.size()) columnIdentifiers.setSize(columnCount); - if (rowCount > dataVector.size()) + if (dataVector != null && rowCount > dataVector.size()) { int rowsToAdd = rowCount - dataVector.size(); addExtraRows(rowsToAdd, columnCount); diff --git a/libjava/classpath/javax/swing/table/TableColumnModel.java b/libjava/classpath/javax/swing/table/TableColumnModel.java index 986c0253323..7e8a70c3a5b 100644 --- a/libjava/classpath/javax/swing/table/TableColumnModel.java +++ b/libjava/classpath/javax/swing/table/TableColumnModel.java @@ -102,7 +102,7 @@ public interface TableColumnModel * * @return An enumeration of the columns in the model. */ - Enumeration getColumns(); + Enumeration<TableColumn> getColumns(); /** * Returns the index of the {@link TableColumn} with the given identifier. diff --git a/libjava/classpath/javax/swing/table/TableModel.java b/libjava/classpath/javax/swing/table/TableModel.java index 016ae171dd4..7629fa4e404 100644 --- a/libjava/classpath/javax/swing/table/TableModel.java +++ b/libjava/classpath/javax/swing/table/TableModel.java @@ -84,7 +84,7 @@ public interface TableModel * * @return The class. */ - Class getColumnClass(int columnIndex); + Class<?> getColumnClass(int columnIndex); /** * Returns <code>true</code> if the cell is editable, and <code>false</code> diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java index eb46a8c42f6..eead8de5261 100644 --- a/libjava/classpath/javax/swing/text/AbstractDocument.java +++ b/libjava/classpath/javax/swing/text/AbstractDocument.java @@ -38,11 +38,15 @@ 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; +import java.util.HashMap; import java.util.Hashtable; import java.util.Vector; @@ -105,6 +109,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; @@ -140,14 +159,10 @@ public abstract class AbstractDocument implements Document, Serializable private int numReaders = 0; /** - * Tells if there are one or more writers waiting. + * The number of current writers. If this is > 1 then the same thread entered + * the write lock more than once. */ - private int numWritersWaiting = 0; - - /** - * A condition variable that readers and writers wait on. - */ - private Object documentCV = new Object(); + private int numWriters = 0; /** An instance of a DocumentFilter.FilterBypass which allows calling * the insert, remove and replace method without checking for an installed @@ -158,7 +173,13 @@ public abstract class AbstractDocument implements Document, Serializable /** * The bidi root element. */ - private Element bidiRoot; + private BidiRootElement bidiRoot; + + /** + * True when we are currently notifying any listeners. This is used + * to detect illegal situations in writeLock(). + */ + private transient boolean notifyListeners; /** * Creates a new <code>AbstractDocument</code> with the specified @@ -191,12 +212,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); - // FIXME: Fully implement bidi. - bidiRoot = new BranchElement(null, null); + // 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 @@ -284,7 +318,8 @@ public abstract class AbstractDocument implements Document, Serializable * @throws BadLocationException if <code>offset</code> is not a valid * location in the documents content model */ - public Position createPosition(final int offset) throws BadLocationException + public synchronized Position createPosition(final int offset) + throws BadLocationException { return content.createPosition(offset); } @@ -296,10 +331,17 @@ public abstract class AbstractDocument implements Document, Serializable */ protected void fireChangedUpdate(DocumentEvent event) { - DocumentListener[] listeners = getDocumentListeners(); - - for (int index = 0; index < listeners.length; ++index) - listeners[index].changedUpdate(event); + notifyListeners = true; + try + { + DocumentListener[] listeners = getDocumentListeners(); + for (int index = 0; index < listeners.length; ++index) + listeners[index].changedUpdate(event); + } + finally + { + notifyListeners = false; + } } /** @@ -310,10 +352,17 @@ public abstract class AbstractDocument implements Document, Serializable */ protected void fireInsertUpdate(DocumentEvent event) { - DocumentListener[] listeners = getDocumentListeners(); - - for (int index = 0; index < listeners.length; ++index) - listeners[index].insertUpdate(event); + notifyListeners = true; + try + { + DocumentListener[] listeners = getDocumentListeners(); + for (int index = 0; index < listeners.length; ++index) + listeners[index].insertUpdate(event); + } + finally + { + notifyListeners = false; + } } /** @@ -324,10 +373,17 @@ public abstract class AbstractDocument implements Document, Serializable */ protected void fireRemoveUpdate(DocumentEvent event) { - DocumentListener[] listeners = getDocumentListeners(); - - for (int index = 0; index < listeners.length; ++index) - listeners[index].removeUpdate(event); + notifyListeners = true; + try + { + DocumentListener[] listeners = getDocumentListeners(); + for (int index = 0; index < listeners.length; ++index) + listeners[index].removeUpdate(event); + } + finally + { + notifyListeners = false; + } } /** @@ -352,7 +408,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; } /** @@ -397,7 +457,7 @@ public abstract class AbstractDocument implements Document, Serializable * @return the thread that currently modifies this <code>Document</code> * if there is one, otherwise <code>null</code> */ - protected final Thread getCurrentWriter() + protected final synchronized Thread getCurrentWriter() { return currentWriter; } @@ -407,7 +467,7 @@ public abstract class AbstractDocument implements Document, Serializable * * @return the properties of this <code>Document</code> */ - public Dictionary getDocumentProperties() + public Dictionary<Object, Object> getDocumentProperties() { // FIXME: make me thread-safe if (properties == null) @@ -425,14 +485,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; } /** @@ -455,7 +518,7 @@ public abstract class AbstractDocument implements Document, Serializable * * @return all registered listeners of the specified type */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } @@ -504,14 +567,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; } /** @@ -574,11 +640,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) @@ -591,23 +665,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)); } /** @@ -620,7 +701,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); } /** @@ -632,7 +714,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); } /** @@ -647,31 +730,338 @@ 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(); + } + } + } } /** - * Blocks until a read lock can be obtained. Must block if there is - * currently a writer modifying the <code>Document</code>. + * Updates the bidi element structure. + * + * @param ev the document event for the change */ - public final void readLock() + private void updateBidi(DefaultDocumentEvent ev) { - if (currentWriter != null && currentWriter.equals(Thread.currentThread())) - return; - synchronized (documentCV) + // 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) { - while (currentWriter != null || numWritersWaiting > 0) + 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++) { - try + 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) { - documentCV.wait(); + 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)); + } + } } - catch (InterruptedException ie) + else { - throw new Error("interrupted trying to get a readLock"); + 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; + } } } - numReaders++; + 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; + } + + /** + * Blocks until a read lock can be obtained. Must block if there is + * currently a writer modifying the <code>Document</code>. + */ + public final synchronized void readLock() + { + try + { + while (currentWriter != null) + { + if (currentWriter == Thread.currentThread()) + return; + wait(); + } + numReaders++; + } + catch (InterruptedException ex) + { + throw new Error("Interrupted during grab read lock"); } } @@ -679,7 +1069,7 @@ public abstract class AbstractDocument implements Document, Serializable * Releases the read lock. If this was the only reader on this * <code>Document</code>, writing may begin now. */ - public final void readUnlock() + public final synchronized void readUnlock() { // Note we could have a problem here if readUnlock was called without a // prior call to readLock but the specs simply warn users to ensure that @@ -706,21 +1096,14 @@ public abstract class AbstractDocument implements Document, Serializable // FIXME: the reference implementation throws a // javax.swing.text.StateInvariantError here - if (numReaders == 0) + if (numReaders <= 0) throw new IllegalStateException("document lock failure"); - synchronized (documentCV) - { - // If currentWriter is not null, the application code probably had a - // writeLock and then tried to obtain a readLock, in which case - // numReaders wasn't incremented - if (currentWriter == null) - { - numReaders --; - if (numReaders == 0 && numWritersWaiting != 0) - documentCV.notify(); - } - } + // If currentWriter is not null, the application code probably had a + // writeLock and then tried to obtain a readLock, in which case + // numReaders wasn't incremented + numReaders--; + notify(); } /** @@ -744,12 +1127,21 @@ public abstract class AbstractDocument implements Document, Serializable */ public void remove(int offset, int length) throws BadLocationException { - if (documentFilter == null) - removeImpl(offset, length); - else - documentFilter.remove(getBypass(), offset, length); + writeLock(); + try + { + DocumentFilter f = getDocumentFilter(); + if (f == null) + removeImpl(offset, length); + else + f.remove(getBypass(), offset, length); + } + finally + { + writeUnlock(); + } } - + void removeImpl(int offset, int length) throws BadLocationException { // The RI silently ignores all requests that have a negative length. @@ -766,21 +1158,12 @@ public abstract class AbstractDocument implements Document, Serializable new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.REMOVE); - try - { - writeLock(); - - // The order of the operations below is critical! - removeUpdate(event); - UndoableEdit temp = content.remove(offset, length); - - postRemoveUpdate(event); - fireRemoveUpdate(event); - } - finally - { - writeUnlock(); - } + // The order of the operations below is critical! + removeUpdate(event); + UndoableEdit temp = content.remove(offset, length); + + postRemoveUpdate(event); + fireRemoveUpdate(event); } } @@ -814,21 +1197,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, @@ -948,7 +1338,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); } /** @@ -956,7 +1347,7 @@ public abstract class AbstractDocument implements Document, Serializable * * @param p the document properties to set */ - public void setDocumentProperties(Dictionary p) + public void setDocumentProperties(Dictionary<Object, Object> p) { // FIXME: make me thread-safe properties = p; @@ -966,26 +1357,27 @@ public abstract class AbstractDocument implements Document, Serializable * Blocks until a write lock can be obtained. Must wait if there are * readers currently reading or another thread is currently writing. */ - protected final void writeLock() + protected synchronized final void writeLock() { - if (currentWriter != null && currentWriter.equals(Thread.currentThread())) - return; - synchronized (documentCV) + try { - numWritersWaiting++; - while (numReaders > 0) + while (numReaders > 0 || currentWriter != null) { - try - { - documentCV.wait(); - } - catch (InterruptedException ie) + if (Thread.currentThread() == currentWriter) { - throw new Error("interruped while trying to obtain write lock"); + if (notifyListeners) + throw new IllegalStateException("Mutation during notify"); + numWriters++; + return; } + wait(); } - numWritersWaiting --; currentWriter = Thread.currentThread(); + numWriters = 1; + } + catch (InterruptedException ex) + { + throw new Error("Interupted during grab write lock"); } } @@ -993,16 +1385,14 @@ public abstract class AbstractDocument implements Document, Serializable * Releases the write lock. This allows waiting readers or writers to * obtain the lock. */ - protected final void writeUnlock() + protected final synchronized void writeUnlock() { - synchronized (documentCV) - { - if (Thread.currentThread().equals(currentWriter)) - { - currentWriter = null; - documentCV.notifyAll(); - } - } + if (--numWriters <= 0) + { + numWriters = 0; + currentWriter = null; + notifyAll(); + } } /** @@ -1039,6 +1429,7 @@ public abstract class AbstractDocument implements Document, Serializable public void dump(PrintStream out) { ((AbstractElement) getDefaultRootElement()).dump(out, 0); + ((AbstractElement) getBidiRootElement()).dump(out, 0); } /** @@ -1130,7 +1521,7 @@ public abstract class AbstractDocument implements Document, Serializable * @return the attributes of <code>old</code> minus the attributes in * <code>attributes</code> */ - AttributeSet removeAttributes(AttributeSet old, Enumeration names); + AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names); } /** @@ -1255,7 +1646,7 @@ public abstract class AbstractDocument implements Document, Serializable AttributeContext ctx = getAttributeContext(); attributes = ctx.getEmptySet(); if (s != null) - attributes = ctx.addAttributes(attributes, s); + addAttributes(s); } /** @@ -1386,7 +1777,7 @@ public abstract class AbstractDocument implements Document, Serializable * * @param names the names of the attributes to be removed */ - public void removeAttributes(Enumeration names) + public void removeAttributes(Enumeration<?> names) { attributes = getAttributeContext().removeAttributes(attributes, names); } @@ -1481,7 +1872,7 @@ public abstract class AbstractDocument implements Document, Serializable * * @return the names of the attributes of this element */ - public Enumeration getAttributeNames() + public Enumeration<?> getAttributeNames() { return attributes.getAttributeNames(); } @@ -1567,7 +1958,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public String getName() { - return (String) getAttribute(NameAttribute); + return (String) attributes.getAttribute(ElementNameAttribute); } /** @@ -1644,6 +2035,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. @@ -1705,6 +2101,11 @@ public abstract class AbstractDocument implements Document, Serializable private int numChildren; /** + * The last found index in getElementIndex(). Used for faster searching. + */ + private int lastIndex; + + /** * Creates a new <code>BranchElement</code> with the specified * parent and attributes. * @@ -1717,6 +2118,7 @@ public abstract class AbstractDocument implements Document, Serializable super(parent, attributes); children = new Element[1]; numChildren = 0; + lastIndex = -1; } /** @@ -1726,7 +2128,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public Enumeration children() { - if (children.length == 0) + if (numChildren == 0) return null; Vector tmp = new Vector(); @@ -1785,35 +2187,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; } /** @@ -1957,6 +2397,11 @@ public abstract class AbstractDocument implements Document, Serializable /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 5230037221564563284L; + /** + * The threshold that indicates when we switch to using a Hashtable. + */ + private static final int THRESHOLD = 10; + /** The starting offset of the change. */ private int offset; @@ -1967,15 +2412,18 @@ public abstract class AbstractDocument implements Document, Serializable private DocumentEvent.EventType type; /** - * Maps <code>Element</code> to their change records. + * Maps <code>Element</code> to their change records. This is only + * used when the changes array gets too big. We can use an + * (unsync'ed) HashMap here, since changes to this are (should) always + * be performed inside a write lock. */ - Hashtable changes; + private HashMap changes; /** * Indicates if this event has been modified or not. This is used to * determine if this event is thrown. */ - boolean modified; + private boolean modified; /** * Creates a new <code>DefaultDocumentEvent</code>. @@ -1990,7 +2438,6 @@ public abstract class AbstractDocument implements Document, Serializable this.offset = offset; this.length = length; this.type = type; - changes = new Hashtable(); modified = false; } @@ -2004,9 +2451,27 @@ public abstract class AbstractDocument implements Document, Serializable public boolean addEdit(UndoableEdit edit) { // XXX - Fully qualify ElementChange to work around gcj bug #2499. - if (edit instanceof DocumentEvent.ElementChange) + + // Start using Hashtable when we pass a certain threshold. This + // gives a good memory/performance compromise. + if (changes == null && edits.size() > THRESHOLD) + { + changes = new HashMap(); + int count = edits.size(); + for (int i = 0; i < count; i++) + { + Object o = edits.elementAt(i); + if (o instanceof DocumentEvent.ElementChange) + { + DocumentEvent.ElementChange ec = + (DocumentEvent.ElementChange) o; + changes.put(ec.getElement(), ec); + } + } + } + + if (changes != null && edit instanceof DocumentEvent.ElementChange) { - modified = true; DocumentEvent.ElementChange elEdit = (DocumentEvent.ElementChange) edit; changes.put(elEdit.getElement(), elEdit); @@ -2065,7 +2530,27 @@ public abstract class AbstractDocument implements Document, Serializable public DocumentEvent.ElementChange getChange(Element elem) { // XXX - Fully qualify ElementChange to work around gcj bug #2499. - return (DocumentEvent.ElementChange) changes.get(elem); + DocumentEvent.ElementChange change = null; + if (changes != null) + { + change = (DocumentEvent.ElementChange) changes.get(elem); + } + else + { + int count = edits.size(); + for (int i = 0; i < count && change == null; i++) + { + Object o = edits.get(i); + if (o instanceof DocumentEvent.ElementChange) + { + DocumentEvent.ElementChange ec = + (DocumentEvent.ElementChange) o; + if (elem.equals(ec.getElement())) + change = ec; + } + } + } + return change; } /** @@ -2333,7 +2818,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/libjava/classpath/javax/swing/text/AttributeSet.java b/libjava/classpath/javax/swing/text/AttributeSet.java index 01d148c067b..2d39881c28b 100644 --- a/libjava/classpath/javax/swing/text/AttributeSet.java +++ b/libjava/classpath/javax/swing/text/AttributeSet.java @@ -158,7 +158,7 @@ public interface AttributeSet * @return the names of the attributes that are stored in this * <code>AttributeSet</code> */ - Enumeration getAttributeNames(); + Enumeration<?> getAttributeNames(); /** * Returns the resolving parent of this <code>AttributeSet</code>. diff --git a/libjava/classpath/javax/swing/text/BoxView.java b/libjava/classpath/javax/swing/text/BoxView.java index 27e3c0f9a1b..0754d9b9b8b 100644 --- a/libjava/classpath/javax/swing/text/BoxView.java +++ b/libjava/classpath/javax/swing/text/BoxView.java @@ -38,6 +38,7 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.Container; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; @@ -92,11 +93,6 @@ public class BoxView private int[] span = new int[2]; /** - * The SizeRequirements of the child views along the X_AXIS and Y_AXIS. - */ - private SizeRequirements[][] childReqs = new SizeRequirements[2][]; - - /** * Creates a new <code>BoxView</code> for the given * <code>Element</code> and axis. Valid values for the axis are * {@link View#X_AXIS} and {@link View#Y_AXIS}. @@ -110,6 +106,8 @@ public class BoxView myAxis = axis; layoutValid[0] = false; layoutValid[1] = false; + requirementsValid[X_AXIS] = false; + requirementsValid[Y_AXIS] = false; span[0] = 0; span[1] = 0; requirements[0] = new SizeRequirements(); @@ -146,7 +144,10 @@ public class BoxView */ public void setAxis(int axis) { + boolean changed = axis != myAxis; myAxis = axis; + if (changed) + preferenceChanged(null, true, true); } /** @@ -227,56 +228,49 @@ public class BoxView */ public void replace(int offset, int length, View[] views) { - int numViews = 0; - if (views != null) - numViews = views.length; + // Actually perform the replace. + super.replace(offset, length, views); // Resize and copy data for cache arrays. - // The spansX cache. - int oldSize = getViewCount(); - - int[] newSpansX = new int[oldSize - length + numViews]; - System.arraycopy(spans[X_AXIS], 0, newSpansX, 0, offset); - System.arraycopy(spans[X_AXIS], offset + length, newSpansX, - offset + numViews, - oldSize - (offset + length)); - spans[X_AXIS] = newSpansX; - - // The spansY cache. - int[] newSpansY = new int[oldSize - length + numViews]; - System.arraycopy(spans[Y_AXIS], 0, newSpansY, 0, offset); - System.arraycopy(spans[Y_AXIS], offset + length, newSpansY, - offset + numViews, - oldSize - (offset + length)); - spans[Y_AXIS] = newSpansY; - - // The offsetsX cache. - int[] newOffsetsX = new int[oldSize - length + numViews]; - System.arraycopy(offsets[X_AXIS], 0, newOffsetsX, 0, offset); - System.arraycopy(offsets[X_AXIS], offset + length, newOffsetsX, - offset + numViews, - oldSize - (offset + length)); - offsets[X_AXIS] = newOffsetsX; - - // The offsetsY cache. - int[] newOffsetsY = new int[oldSize - length + numViews]; - System.arraycopy(offsets[Y_AXIS], 0, newOffsetsY, 0, offset); - System.arraycopy(offsets[Y_AXIS], offset + length, newOffsetsY, - offset + numViews, - oldSize - (offset + length)); - offsets[Y_AXIS] = newOffsetsY; + int newItems = views != null ? views.length : 0; + int minor = 1 - myAxis; + offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems); + spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems); + layoutValid[myAxis] = false; + requirementsValid[myAxis] = false; + offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems); + spans[minor] = replaceLayoutArray(spans[minor], offset, newItems); + layoutValid[minor] = false; + requirementsValid[minor] = false; + } - // Actually perform the replace. - super.replace(offset, length, views); + /** + * Helper method. This updates the layout cache arrays in response + * to a call to {@link #replace(int, int, View[])}. + * + * @param oldArray the old array + * + * @return the replaced array + */ + private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems) - // Invalidate layout information. - layoutValid[X_AXIS] = false; - requirementsValid[X_AXIS] = false; - layoutValid[Y_AXIS] = false; - requirementsValid[Y_AXIS] = false; + { + int num = getViewCount(); + int[] newArray = new int[num]; + System.arraycopy(oldArray, 0, newArray, 0, offset); + System.arraycopy(oldArray, offset, newArray, offset + newItems, + num - newItems - offset); + return newArray; } /** + * A Rectangle instance to be reused in the paint() method below. + */ + private final Rectangle tmpRect = new Rectangle(); + + private Rectangle clipRect = new Rectangle(); + + /** * Renders the <code>Element</code> that is associated with this * <code>View</code>. * @@ -285,26 +279,20 @@ public class BoxView */ public void paint(Graphics g, Shape a) { - Rectangle alloc; - if (a instanceof Rectangle) - alloc = (Rectangle) a; - else - alloc = a.getBounds(); + // Try to avoid allocation if possible (almost all cases). + Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); - int x = alloc.x + getLeftInset(); - int y = alloc.y + getTopInset(); + // This returns a cached instance. + alloc = getInsideAllocation(alloc); - Rectangle clip = g.getClipBounds(); - Rectangle tmp = new Rectangle(); int count = getViewCount(); - for (int i = 0; i < count; ++i) + for (int i = 0; i < count; 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); + View child = getView(i); + tmpRect.setBounds(alloc); + childAllocation(i, tmpRect); + if (g.hitClip(tmpRect.x, tmpRect.y, tmpRect.width, tmpRect.height)) + paintChild(g, tmpRect, i); } } @@ -373,9 +361,9 @@ public class BoxView } /** - * This method is obsolete and no longer in use. It is replaced by - * {@link #calculateMajorAxisRequirements(int, SizeRequirements)} and - * {@link #calculateMinorAxisRequirements(int, SizeRequirements)}. + * Calculates size requirements for a baseline layout. This is not + * used by the BoxView itself, but by subclasses that wish to perform + * a baseline layout, like the FlowView's rows. * * @param axis the axis that is examined * @param sr the <code>SizeRequirements</code> object to hold the result, @@ -387,50 +375,94 @@ public class BoxView protected SizeRequirements baselineRequirements(int axis, SizeRequirements sr) { - updateChildRequirements(axis); + // Create new instance if sr == null. + if (sr == null) + sr = new SizeRequirements(); + sr.alignment = 0.5F; + + // Calculate overall ascent and descent. + int totalAscentMin = 0; + int totalAscentPref = 0; + int totalAscentMax = 0; + int totalDescentMin = 0; + int totalDescentPref = 0; + int totalDescentMax = 0; + + int count = getViewCount(); + for (int i = 0; i < count; i++) + { + View v = getView(i); + float align = v.getAlignment(axis); + int span = (int) v.getPreferredSpan(axis); + int ascent = (int) (align * span); + int descent = span - ascent; + + totalAscentPref = Math.max(ascent, totalAscentPref); + totalDescentPref = Math.max(descent, totalDescentPref); + if (v.getResizeWeight(axis) > 0) + { + // If the view is resizable, then use the min and max size + // of the view. + span = (int) v.getMinimumSpan(axis); + ascent = (int) (align * span); + descent = span - ascent; + totalAscentMin = Math.max(ascent, totalAscentMin); + totalDescentMin = Math.max(descent, totalDescentMin); + + span = (int) v.getMaximumSpan(axis); + ascent = (int) (align * span); + descent = span - ascent; + totalAscentMax = Math.max(ascent, totalAscentMax); + totalDescentMax = Math.max(descent, totalDescentMax); + } + else + { + // If the view is not resizable, use the preferred span. + totalAscentMin = Math.max(ascent, totalAscentMin); + totalDescentMin = Math.max(descent, totalDescentMin); + totalAscentMax = Math.max(ascent, totalAscentMax); + totalDescentMax = Math.max(descent, totalDescentMax); + } + } - SizeRequirements res = sr; - if (res == null) - res = new SizeRequirements(); + // Preferred overall span is the sum of the preferred ascent and descent. + // With overflow check. + sr.preferred = (int) Math.min((long) totalAscentPref + + (long) totalDescentPref, + Integer.MAX_VALUE); + + // Align along the baseline. + if (sr.preferred > 0) + sr.alignment = (float) totalAscentPref / sr.preferred; - 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++) + if (sr.alignment == 0) { - float myMinLeft = childReqs[axis][i].minimum * childReqs[axis][i].alignment; - float myMinRight = childReqs[axis][i].minimum - myMinLeft; - minLeft = Math.max(myMinLeft, minLeft); - minRight = Math.max(myMinRight, minRight); - float myPrefLeft = childReqs[axis][i].preferred * childReqs[axis][i].alignment; - float myPrefRight = childReqs[axis][i].preferred - myPrefLeft; - prefLeft = Math.max(myPrefLeft, prefLeft); - prefRight = Math.max(myPrefRight, prefRight); - float myMaxLeft = childReqs[axis][i].maximum * childReqs[axis][i].alignment; - float myMaxRight = childReqs[axis][i].maximum - myMaxLeft; - maxLeft = Math.max(myMaxLeft, maxLeft); - maxRight = Math.max(myMaxRight, maxRight); + // Nothing above the baseline, use the descent. + sr.minimum = totalDescentMin; + sr.maximum = totalDescentMax; } - int minSize = (int) (minLeft + minRight); - int prefSize = (int) (prefLeft + prefRight); - int maxSize = (int) (maxLeft + maxRight); - float align = prefLeft / (prefRight + prefLeft); - if (Float.isNaN(align)) - align = 0; - - res.alignment = align; - res.maximum = maxSize; - res.preferred = prefSize; - res.minimum = minSize; - return res; + else if (sr.alignment == 1.0F) + { + // Nothing below the baseline, use the descent. + sr.minimum = totalAscentMin; + sr.maximum = totalAscentMax; + } + else + { + sr.minimum = Math.max((int) (totalAscentMin / sr.alignment), + (int) (totalDescentMin / (1.0F - sr.alignment))); + sr.maximum = Math.min((int) (totalAscentMax / sr.alignment), + (int) (totalDescentMax / (1.0F - sr.alignment))); + } + return sr; } /** - * Calculates the layout of the children of this <code>BoxView</code> along - * the specified axis. + * Calculates the baseline layout of the children of this + * <code>BoxView</code> along the specified axis. + * + * This is not used by the BoxView itself, but by subclasses that wish to + * perform a baseline layout, like the FlowView's rows. * * @param span the target span * @param axis the axis that is examined @@ -440,13 +472,36 @@ public class BoxView protected void baselineLayout(int span, int axis, int[] offsets, int[] spans) { - updateChildRequirements(axis); - updateRequirements(axis); + int totalAscent = (int) (span * getAlignment(axis)); + int totalDescent = span - totalAscent; - // Calculate the spans and offsets using the SizeRequirements uility - // methods. - SizeRequirements.calculateAlignedPositions(span, requirements[axis], - childReqs[axis], offsets, spans); + int count = getViewCount(); + for (int i = 0; i < count; i++) + { + View v = getView(i); + float align = v.getAlignment(axis); + int viewSpan; + if (v.getResizeWeight(axis) > 0) + { + // If possible, then resize for best fit. + int min = (int) v.getMinimumSpan(axis); + int max = (int) v.getMaximumSpan(axis); + if (align == 0.0F) + viewSpan = Math.max(Math.min(max, totalDescent), min); + else if (align == 1.0F) + viewSpan = Math.max(Math.min(max, totalAscent), min); + else + { + int fit = (int) Math.min(totalAscent / align, + totalDescent / (1.0F - align)); + viewSpan = Math.max(Math.min(max, fit), min); + } + } + else + viewSpan = (int) v.getPreferredSpan(axis); + offsets[i] = totalAscent - (int) (viewSpan * align); + spans[i] = viewSpan; + } } /** @@ -476,8 +531,8 @@ public class BoxView { View child = getView(i); min += child.getMinimumSpan(axis); - pref = child.getPreferredSpan(axis); - max = child.getMaximumSpan(axis); + pref += child.getPreferredSpan(axis); + max += child.getMaximumSpan(axis); } res.minimum = (int) min; @@ -509,7 +564,7 @@ public class BoxView res.minimum = 0; res.preferred = 0; - res.maximum = 0; + res.maximum = Integer.MAX_VALUE; res.alignment = 0.5F; int n = getViewCount(); for (int i = 0; i < n; i++) @@ -568,9 +623,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; } @@ -589,24 +644,54 @@ public class BoxView { View result = null; int count = getViewCount(); - Rectangle copy = new Rectangle(r); - - for (int i = 0; i < count; ++i) + if (myAxis == X_AXIS) { - copy.setBounds(r); - // The next call modifies copy. - childAllocation(i, copy); - if (copy.contains(x, y)) + // Border case. Requested point is left from the box. + if (x < r.x + offsets[X_AXIS][0]) { - // Modify r on success. - r.setBounds(copy); - result = getView(i); - break; + childAllocation(0, r); + result = getView(0); + } + else + { + // Search views inside box. + for (int i = 0; i < count && result == null; i++) + { + if (x < r.x + offsets[X_AXIS][i]) + { + childAllocation(i - 1, r); + result = getView(i - 1); + } + } } } - - if (result == null && count > 0) - return getView(count - 1); + else // Same algorithm for Y_AXIS. + { + // Border case. Requested point is above the box. + if (y < r.y + offsets[Y_AXIS][0]) + { + childAllocation(0, r); + result = getView(0); + } + else + { + // Search views inside box. + for (int i = 0; i < count && result == null; i++) + { + if (y < r.y + offsets[Y_AXIS][i]) + { + childAllocation(i - 1, r); + result = getView(i - 1); + } + } + } + } + // Not found, other border case: point is right from or below the box. + if (result == null) + { + childAllocation(count - 1, r); + result = getView(count - 1); + } return result; } @@ -623,9 +708,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]; @@ -643,49 +725,32 @@ public class BoxView */ protected void layout(int width, int height) { - int[] newSpan = new int[]{ width, height }; - int count = getViewCount(); - - // Update minor axis as appropriate. We need to first update the minor - // axis layout because that might affect the children's preferences along - // the major axis. - int minorAxis = myAxis == X_AXIS ? Y_AXIS : X_AXIS; - if ((! isLayoutValid(minorAxis)) || newSpan[minorAxis] != span[minorAxis]) - { - layoutValid[minorAxis] = false; - span[minorAxis] = newSpan[minorAxis]; - layoutMinorAxis(span[minorAxis], minorAxis, offsets[minorAxis], - spans[minorAxis]); - - // Update the child view's sizes. - for (int i = 0; i < count; ++i) - { - getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); - } - layoutValid[minorAxis] = true; - } - + layoutAxis(X_AXIS, width); + layoutAxis(Y_AXIS, height); + } - // Update major axis as appropriate. - if ((! isLayoutValid(myAxis)) || newSpan[myAxis] != span[myAxis]) + private void layoutAxis(int axis, int s) + { + if (span[axis] != s) + layoutValid[axis] = false; + if (! layoutValid[axis]) { - layoutValid[myAxis] = false; - span[myAxis] = newSpan[myAxis]; - layoutMajorAxis(span[myAxis], myAxis, offsets[myAxis], - spans[myAxis]); + span[axis] = s; + updateRequirements(axis); + if (axis == myAxis) + layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]); + else + layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]); + layoutValid[axis] = true; - // Update the child view's sizes. - for (int i = 0; i < count; ++i) + // Push out child layout. + int viewCount = getViewCount(); + for (int i = 0; i < viewCount; i++) { - getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); + View v = getView(i); + v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); } - layoutValid[myAxis] = true; } - - if (layoutValid[myAxis] == false) - System.err.println("WARNING: Major axis layout must be valid after layout"); - if (layoutValid[minorAxis] == false) - System.err.println("Minor axis layout must be valid after layout"); } /** @@ -708,7 +773,7 @@ public class BoxView { View child = getView(i); spans[i] = (int) child.getPreferredSpan(axis); - sumPref = spans[i]; + sumPref += spans[i]; } // Try to adjust the spans so that we fill the targetSpan. @@ -776,7 +841,7 @@ public class BoxView View child = getView(i); int max = (int) child.getMaximumSpan(axis); if (max < targetSpan) - {System.err.println("align: " + child); + { // 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); @@ -811,7 +876,9 @@ public class BoxView */ public int getWidth() { - return span[X_AXIS]; + // The RI returns the following here, however, I'd think that is a bug. + // return span[X_AXIS] + getLeftInset() - getRightInset(); + return span[X_AXIS] + getLeftInset() + getRightInset(); } /** @@ -821,7 +888,9 @@ public class BoxView */ public int getHeight() { - return span[Y_AXIS]; + // The RI returns the following here, however, I'd think that is a bug. + // return span[Y_AXIS] + getTopInset() - getBottomInset(); + return span[Y_AXIS] + getTopInset() + getBottomInset(); } /** @@ -833,7 +902,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())); } /** @@ -944,9 +1014,11 @@ public class BoxView { if (axis != X_AXIS && axis != Y_AXIS) throw new IllegalArgumentException("Illegal axis argument"); - int weight = 1; - if (axis == myAxis) - weight = 0; + updateRequirements(axis); + int weight = 0; + if ((requirements[axis].preferred != requirements[axis].minimum) + || (requirements[axis].preferred != requirements[axis].maximum)) + weight = 1; return weight; } @@ -973,13 +1045,39 @@ public class BoxView protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e, Shape a, ViewFactory vf) { - // FIXME: What to do here? + boolean wasValid = isLayoutValid(myAxis); super.forwardUpdate(ec, e, a, vf); + // Trigger repaint when one of the children changed the major axis. + if (wasValid && ! isLayoutValid(myAxis)) + { + Container c = getContainer(); + if (a != null && c != null) + { + int pos = e.getOffset(); + int index = getViewIndexAtPosition(pos); + Rectangle r = getInsideAllocation(a); + if (myAxis == X_AXIS) + { + r.x += offsets[myAxis][index]; + r.width -= offsets[myAxis][index]; + } + else + { + r.y += offsets[myAxis][index]; + r.height -= offsets[myAxis][index]; + } + c.repaint(r.x, r.y, r.width, r.height); + } + } } public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { - // FIXME: What to do here? + if (! isAllocationValid()) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + setSize(r.width, r.height); + } return super.viewToModel(x, y, a, bias); } @@ -990,32 +1088,6 @@ public class BoxView } /** - * Updates the child requirements along the specified axis. The requirements - * are only updated if the layout for the specified axis is marked as - * invalid. - * - * @param axis the axis to be updated - */ - private void updateChildRequirements(int axis) - { - if (! isLayoutValid(axis)) - { - int numChildren = getViewCount(); - if (childReqs[axis] == null || childReqs[axis].length != numChildren) - childReqs[axis] = new SizeRequirements[numChildren]; - for (int i = 0; i < numChildren; ++i) - { - View child = getView(i); - childReqs[axis][i] = - new SizeRequirements((int) child.getMinimumSpan(axis), - (int) child.getPreferredSpan(axis), - (int) child.getMaximumSpan(axis), - child.getAlignment(axis)); - } - } - } - - /** * Updates the view's cached requirements along the specified axis if * necessary. The requirements are only updated if the layout for the * specified axis is marked as invalid. @@ -1024,6 +1096,8 @@ public class BoxView */ private void updateRequirements(int axis) { + if (axis != Y_AXIS && axis != X_AXIS) + throw new IllegalArgumentException("Illegal axis: " + axis); if (! requirementsValid[axis]) { if (axis == myAxis) diff --git a/libjava/classpath/javax/swing/text/ComponentView.java b/libjava/classpath/javax/swing/text/ComponentView.java index a7d237ab73a..8de4de60fa3 100644 --- a/libjava/classpath/javax/swing/text/ComponentView.java +++ b/libjava/classpath/javax/swing/text/ComponentView.java @@ -39,11 +39,11 @@ package javax.swing.text; import java.awt.Component; import java.awt.Container; +import java.awt.Dimension; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; -import javax.swing.SwingConstants; import javax.swing.SwingUtilities; /** @@ -62,11 +62,161 @@ public class ComponentView extends View { /** + * A special container that sits between the component and the hosting + * container. This is used to propagate invalidate requests and cache + * the component's layout sizes. + */ + private class Interceptor + extends Container + { + Dimension min; + Dimension pref; + Dimension max; + float alignX; + float alignY; + + /** + * Creates a new instance that hosts the specified component. + */ + Interceptor(Component c) + { + setLayout(null); + add(c); + cacheComponentSizes(); + } + + /** + * Intercepts the normal invalidate call and propagates the invalidate + * request up using the View's preferenceChanged(). + */ + public void invalidate() + { + super.invalidate(); + if (getParent() != null) + preferenceChanged(null, true, true); + } + + /** + * This is overridden to simply cache the layout sizes. + */ + public void doLayout() + { + cacheComponentSizes(); + } + + /** + * Overridden to also reshape the component itself. + */ + public void reshape(int x, int y, int w, int h) + { + super.reshape(x, y, w, h); + if (getComponentCount() > 0) + getComponent(0).setSize(w, h); + cacheComponentSizes(); + } + + /** + * Overridden to also show the component. + */ + public void show() + { + super.show(); + if (getComponentCount() > 0) + getComponent(0).setVisible(true); + } + + /** + * Overridden to also hide the component. + */ + public void hide() + { + super.hide(); + if (getComponentCount() > 0) + getComponent(0).setVisible(false); + } + + /** + * Overridden to return the cached value. + */ + public Dimension getMinimumSize() + { + maybeValidate(); + return min; + } + + /** + * Overridden to return the cached value. + */ + public Dimension getPreferredSize() + { + maybeValidate(); + return pref; + } + + /** + * Overridden to return the cached value. + */ + public Dimension getMaximumSize() + { + maybeValidate(); + return max; + } + + /** + * Overridden to return the cached value. + */ + public float getAlignmentX() + { + maybeValidate(); + return alignX; + } + + /** + * Overridden to return the cached value. + */ + public float getAlignmentY() + { + maybeValidate(); + return alignY; + } + + /** + * Validates the container only when necessary. + */ + private void maybeValidate() + { + if (! isValid()) + validate(); + } + + /** + * Fetches the component layout sizes into the cache. + */ + private void cacheComponentSizes() + { + if (getComponentCount() > 0) + { + Component c = getComponent(0); + min = c.getMinimumSize(); + pref = c.getPreferredSize(); + max = c.getMaximumSize(); + alignX = c.getAlignmentX(); + alignY = c.getAlignmentY(); + } + } + } + + /** * The component that is displayed by this view. */ private Component comp; /** + * The intercepting container. + */ + private Interceptor interceptor; + + /** * Creates a new instance of <code>ComponentView</code> for the specified * <code>Element</code>. * @@ -99,13 +249,20 @@ public class ComponentView extends View */ public float getAlignment(int axis) { - float align; - if (axis == X_AXIS) - align = getComponent().getAlignmentX(); - else if (axis == Y_AXIS) - align = getComponent().getAlignmentY(); + float align = 0.0F; + // I'd rather throw an IllegalArgumentException for illegal axis, + // but the Harmony testsuite indicates fallback to super behaviour. + if (interceptor != null && (axis == X_AXIS || axis == Y_AXIS)) + { + if (axis == X_AXIS) + align = interceptor.getAlignmentX(); + else if (axis == Y_AXIS) + align = interceptor.getAlignmentY(); + else + assert false : "Must not reach here"; + } else - throw new IllegalArgumentException(); + align = super.getAlignment(axis); return align; } @@ -118,8 +275,6 @@ public class ComponentView extends View */ public final Component getComponent() { - if (comp == null) - comp = createComponent(); return comp; } @@ -135,49 +290,70 @@ public class ComponentView extends View */ public float getMaximumSpan(int axis) { - float span; - if (axis == X_AXIS) - span = getComponent().getMaximumSize().width; - else if (axis == Y_AXIS) - span = getComponent().getMaximumSize().height; - else - throw new IllegalArgumentException(); + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis"); + float span = 0; + if (interceptor != null) + { + if (axis == X_AXIS) + span = interceptor.getMaximumSize().width; + else if (axis == Y_AXIS) + span = interceptor.getMaximumSize().height; + else + assert false : "Must not reach here"; + } return span; } public float getMinimumSpan(int axis) { - float span; - if (axis == X_AXIS) - span = getComponent().getMinimumSize().width; - else if (axis == Y_AXIS) - span = getComponent().getMinimumSize().height; - else - throw new IllegalArgumentException(); + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis"); + float span = 0; + if (interceptor != null) + { + if (axis == X_AXIS) + span = interceptor.getMinimumSize().width; + else if (axis == Y_AXIS) + span = interceptor.getMinimumSize().height; + else + assert false : "Must not reach here"; + } return span; } public float getPreferredSpan(int axis) { - float span; - if (axis == X_AXIS) - span = getComponent().getPreferredSize().width; - else if (axis == Y_AXIS) - span = getComponent().getPreferredSize().height; - else - throw new IllegalArgumentException(); + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis"); + float span = 0; + if (interceptor != null) + { + if (axis == X_AXIS) + span = interceptor.getPreferredSize().width; + else if (axis == Y_AXIS) + span = interceptor.getPreferredSize().height; + else + assert false : "Must not reach here"; + } return span; } public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { - Element el = getElement(); - if (pos < el.getStartOffset() || pos >= el.getEndOffset()) - throw new BadLocationException("Illegal offset for this view", pos); - Rectangle r = a.getBounds(); - Component c = getComponent(); - return new Rectangle(r.x, r.y, c.getWidth(), c.getHeight()); + int p0 = getStartOffset(); + int p1 = getEndOffset(); + if (pos >= p0 && pos <= p1) + { + Rectangle viewRect = a.getBounds(); + if (pos == p1) + viewRect.x += viewRect.width; + viewRect.width = 0; + return viewRect; + } + else + throw new BadLocationException("Illegal position", pos); } /** @@ -191,8 +367,11 @@ public class ComponentView extends View */ public void paint(Graphics g, Shape a) { - Rectangle r = a.getBounds(); - getComponent().setBounds(r.x, r.y, r.width, r.height); + if (interceptor != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + interceptor.setBounds(r.x, r.y, r.width, r.height); + } } /** @@ -209,15 +388,33 @@ public class ComponentView extends View */ public void setParent(final View p) { + super.setParent(p); if (SwingUtilities.isEventDispatchThread()) - setParentImpl(p); + setParentImpl(); else SwingUtilities.invokeLater (new Runnable() { public void run() { - setParentImpl(p); + Document doc = getDocument(); + try + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + setParentImpl(); + Container host = getContainer(); + if (host != null) + { + preferenceChanged(null, true, true); + host.repaint(); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } } }); } @@ -225,23 +422,41 @@ public class ComponentView extends View /** * The implementation of {@link #setParent}. This is package private to * avoid a synthetic accessor method. - * - * @param p the parent view to set */ - private void setParentImpl(View p) + void setParentImpl() { - super.setParent(p); + View p = getParent(); if (p != null) { - Component c = getComponent(); - p.getContainer().add(c); + Container c = getContainer(); + if (c != null) + { + if (interceptor == null) + { + // Create component and put it inside the interceptor. + Component created = createComponent(); + if (created != null) + { + comp = created; + interceptor = new Interceptor(comp); + } + } + if (interceptor != null) + { + // Add the interceptor to the hosting container. + if (interceptor.getParent() == null) + c.add(interceptor, this); + } + } } else { - Component c = getComponent(); - Container parent = c.getParent(); - parent.remove(c); - comp = null; + if (interceptor != null) + { + Container parent = interceptor.getParent(); + if (parent != null) + parent.remove(interceptor); + } } } @@ -259,10 +474,21 @@ public class ComponentView extends View */ public int viewToModel(float x, float y, Shape a, Position.Bias[] b) { - // The element should only have one character position and it is clear - // that this position is the position that best matches the given screen - // coordinates, simply because this view has only this one position. - Element el = getElement(); - return el.getStartOffset(); + int pos; + // I'd rather do the following. The harmony testsuite indicates + // that a simple cast is performed. + //Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + Rectangle r = (Rectangle) a; + if (x < r.x + r.width / 2) + { + b[0] = Position.Bias.Forward; + pos = getStartOffset(); + } + else + { + b[0] = Position.Bias.Backward; + pos = getEndOffset(); + } + return pos; } } diff --git a/libjava/classpath/javax/swing/text/CompositeView.java b/libjava/classpath/javax/swing/text/CompositeView.java index 6f487b8981e..570fc955c88 100644 --- a/libjava/classpath/javax/swing/text/CompositeView.java +++ b/libjava/classpath/javax/swing/text/CompositeView.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing.text; -import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; @@ -57,20 +56,28 @@ public abstract class CompositeView /** * The child views of this <code>CompositeView</code>. */ - View[] children; + private View[] children; + + /** + * The number of child views. + */ + private int numChildren; /** * The allocation of this <code>View</code> minus its insets. This is * initialized in {@link #getInsideAllocation} and reused and modified in * {@link #childAllocation(int, Rectangle)}. */ - Rectangle insideAllocation; + private final Rectangle insideAllocation = new Rectangle(); /** * The insets of this <code>CompositeView</code>. This is initialized * in {@link #setInsets}. */ - Insets insets; + private short top; + private short bottom; + private short left; + private short right; /** * Creates a new <code>CompositeView</code> for the given @@ -82,7 +89,10 @@ public abstract class CompositeView { super(element); children = new View[0]; - insets = new Insets(0, 0, 0, 0); + top = 0; + bottom = 0; + left = 0; + right = 0; } /** @@ -96,16 +106,22 @@ public abstract class CompositeView */ protected void loadChildren(ViewFactory f) { - Element el = getElement(); - int count = el.getElementCount(); - View[] newChildren = new View[count]; - for (int i = 0; i < count; ++i) + if (f != null) { - Element child = el.getElement(i); - View view = f.create(child); - newChildren[i] = view; + Element el = getElement(); + int count = el.getElementCount(); + View[] newChildren = new View[count]; + for (int i = 0; i < count; ++i) + { + Element child = el.getElement(i); + View view = f.create(child); + newChildren[i] = view; + } + // I'd have called replace(0, getViewCount(), newChildren) here + // in order to replace all existing views. However according to + // Harmony's tests this is not what the RI does. + replace(0, 0, newChildren); } - replace(0, getViewCount(), newChildren); } /** @@ -118,7 +134,7 @@ public abstract class CompositeView public void setParent(View parent) { super.setParent(parent); - if (parent != null && ((children == null) || children.length == 0)) + if (parent != null && numChildren == 0) loadChildren(getViewFactory()); } @@ -129,7 +145,7 @@ public abstract class CompositeView */ public int getViewCount() { - return children.length; + return numChildren; } /** @@ -156,24 +172,42 @@ public abstract class CompositeView */ public void replace(int offset, int length, View[] views) { - // Check for null views to add. - for (int i = 0; i < views.length; ++i) - if (views[i] == null) - throw new NullPointerException("Added views must not be null"); - - int endOffset = offset + length; + // Make sure we have an array. The Harmony testsuite indicates that we + // have to do something like this. + if (views == null) + views = new View[0]; // First we set the parent of the removed children to null. + int endOffset = offset + length; for (int i = offset; i < endOffset; ++i) - children[i].setParent(null); + { + if (children[i].getParent() == this) + children[i].setParent(null); + children[i] = null; + } - View[] newChildren = new View[children.length - length + views.length]; - System.arraycopy(children, 0, newChildren, 0, offset); - System.arraycopy(views, 0, newChildren, offset, views.length); - System.arraycopy(children, offset + length, newChildren, - offset + views.length, - children.length - (offset + length)); - children = newChildren; + // Update the children array. + int delta = views.length - length; + int src = offset + length; + int numMove = numChildren - src; + int dst = src + delta; + if (numChildren + delta > children.length) + { + // Grow array. + int newLength = Math.max(2 * children.length, numChildren + delta); + View[] newChildren = new View[newLength]; + System.arraycopy(children, 0, newChildren, 0, offset); + System.arraycopy(views, 0, newChildren, offset, views.length); + System.arraycopy(children, src, newChildren, dst, numMove); + children = newChildren; + } + else + { + // Patch existing array. + System.arraycopy(children, src, children, dst, numMove); + System.arraycopy(views, 0, children, offset, views.length); + } + numChildren += delta; // Finally we set the parent of the added children to this. for (int i = 0; i < views.length; ++i) @@ -248,34 +282,13 @@ public abstract class CompositeView } } } - else - { - throw new BadLocationException("Position " + pos - + " is not represented by view.", pos); - } } - return ret; - } - /** - * A helper method for {@link #modelToView(int, Position.Bias, int, - * Position.Bias, Shape)}. This creates a default location when there is - * no child view that can take responsibility for mapping the position to - * view coordinates. Depending on the specified bias this will be the - * left or right edge of this view's allocation. - * - * @param a the allocation for this view - * @param bias the bias - * - * @return a default location - */ - private Shape createDefaultLocation(Shape a, Position.Bias bias) - { - Rectangle alloc = a.getBounds(); - Rectangle location = new Rectangle(alloc.x, alloc.y, 1, alloc.height); - if (bias == Position.Bias.Forward) - location.x = alloc.x + alloc.width; - return location; + if (ret == null) + throw new BadLocationException("Position " + pos + + " is not represented by view.", pos); + + return ret; } /** @@ -394,7 +407,7 @@ public abstract class CompositeView */ public int getViewIndex(int pos, Position.Bias b) { - if (b == Position.Bias.Backward && pos != 0) + if (b == Position.Bias.Backward) pos -= 1; int i = -1; if (pos >= getStartOffset() && pos < getEndOffset()) @@ -514,24 +527,17 @@ public abstract class CompositeView if (a == null) return null; - Rectangle alloc = a.getBounds(); + // Try to avoid allocation of Rectangle here. + Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + // Initialize the inside allocation rectangle. This is done inside // a synchronized block in order to avoid multiple threads creating // this instance simultanously. - Rectangle inside; - synchronized(this) - { - inside = insideAllocation; - if (inside == null) - { - inside = new Rectangle(); - insideAllocation = inside; - } - } - inside.x = alloc.x + insets.left; - inside.y = alloc.y + insets.top; - inside.width = alloc.width - insets.left - insets.right; - inside.height = alloc.height - insets.top - insets.bottom; + Rectangle inside = insideAllocation; + inside.x = alloc.x + getLeftInset(); + inside.y = alloc.y + getTopInset(); + inside.width = alloc.width - getLeftInset() - getRightInset(); + inside.height = alloc.height - getTopInset() - getBottomInset(); return inside; } @@ -546,39 +552,26 @@ public abstract class CompositeView */ protected void setParagraphInsets(AttributeSet attributes) { - Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent); - short left = 0; - if (l != null) - left = l.shortValue(); - Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent); - short right = 0; - if (r != null) - right = r.shortValue(); - Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove); - short top = 0; - if (t != null) - top = t.shortValue(); - Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow); - short bottom = 0; - if (b != null) - bottom = b.shortValue(); - setInsets(top, left, bottom, right); + top = (short) StyleConstants.getSpaceAbove(attributes); + bottom = (short) StyleConstants.getSpaceBelow(attributes); + left = (short) StyleConstants.getLeftIndent(attributes); + right = (short) StyleConstants.getRightIndent(attributes); } /** * Sets the insets of this <code>CompositeView</code>. * - * @param top the top inset - * @param left the left inset - * @param bottom the bottom inset - * @param right the right inset + * @param t the top inset + * @param l the left inset + * @param b the bottom inset + * @param r the right inset */ - protected void setInsets(short top, short left, short bottom, short right) + protected void setInsets(short t, short l, short b, short r) { - insets.top = top; - insets.left = left; - insets.bottom = bottom; - insets.right = right; + top = t; + left = l; + bottom = b; + right = r; } /** @@ -588,7 +581,7 @@ public abstract class CompositeView */ protected short getLeftInset() { - return (short) insets.left; + return left; } /** @@ -598,7 +591,7 @@ public abstract class CompositeView */ protected short getRightInset() { - return (short) insets.right; + return right; } /** @@ -608,7 +601,7 @@ public abstract class CompositeView */ protected short getTopInset() { - return (short) insets.top; + return top; } /** @@ -618,7 +611,7 @@ public abstract class CompositeView */ protected short getBottomInset() { - return (short) insets.bottom; + return bottom; } /** diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java index 84f47f120de..c4c2580c398 100644 --- a/libjava/classpath/javax/swing/text/DefaultCaret.java +++ b/libjava/classpath/javax/swing/text/DefaultCaret.java @@ -804,7 +804,7 @@ public class DefaultCaret extends Rectangle } } } - + private void handleHighlight() { Highlighter highlighter = textComponent.getHighlighter(); @@ -946,7 +946,7 @@ public class DefaultCaret extends Rectangle * * @return all registered event listeners of the specified type */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } @@ -1075,8 +1075,6 @@ public class DefaultCaret extends Rectangle handleHighlight(); appear(); - - adjustVisibility(this); } } @@ -1114,8 +1112,6 @@ public class DefaultCaret extends Rectangle clearHighlight(); appear(); - - adjustVisibility(this); } } @@ -1154,7 +1150,12 @@ public class DefaultCaret extends Rectangle // e.printStackTrace(); } if (area != null) - damage(area); + { + adjustVisibility(area); + if (getMagicCaretPosition() == null) + setMagicCaretPosition(new Point(area.x, area.y)); + damage(area); + } } repaint(); } diff --git a/libjava/classpath/javax/swing/text/DefaultEditorKit.java b/libjava/classpath/javax/swing/text/DefaultEditorKit.java index 8602e69f8e7..aa69deca545 100644 --- a/libjava/classpath/javax/swing/text/DefaultEditorKit.java +++ b/libjava/classpath/javax/swing/text/DefaultEditorKit.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing.text; -import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; @@ -312,19 +311,21 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - int offs = t.getDocument().getLength(); - Caret c = t.getCaret(); - c.setDot(0); - c.moveDot(offs); - - try - { - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) - { - // Can't happen. - } + if (t != null) + { + int offs = t.getDocument().getLength(); + Caret c = t.getCaret(); + c.setDot(0); + c.moveDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } } } @@ -339,15 +340,18 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - c.moveDot(0); - try - { - c.setMagicCaretPosition(t.modelToView(0).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + Caret c = t.getCaret(); + c.moveDot(0); + try + { + c.setMagicCaretPosition(t.modelToView(0).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -363,16 +367,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - int offs = t.getDocument().getLength(); - Caret c = t.getCaret(); - c.moveDot(offs); - try - { - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + int offs = t.getDocument().getLength(); + Caret c = t.getCaret(); + c.moveDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -389,17 +396,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - try + if (t != null) { - int offs = Utilities.getRowStart(t, c.getDot()); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + Caret c = t.getCaret(); + try + { + int offs = Utilities.getRowStart(t, c.getDot()); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } - catch(BadLocationException ble) - { - // Can't happen. - } - } } @@ -414,17 +423,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - try - { - int offs = Utilities.getRowEnd(t, c.getDot()); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + Caret c = t.getCaret(); + try + { + int offs = Utilities.getRowEnd(t, c.getDot()); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } - } } @@ -438,20 +449,21 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - try - { - int offs1 = Utilities.getRowStart(t, c.getDot()); - int offs2 = Utilities.getRowEnd(t, c.getDot()); - - c.setDot(offs2); - c.moveDot(offs1); - - c.setMagicCaretPosition(t.modelToView(offs2).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + Caret c = t.getCaret(); + try + { + int offs1 = Utilities.getRowStart(t, c.getDot()); + int offs2 = Utilities.getRowEnd(t, c.getDot()); + c.setDot(offs2); + c.moveDot(offs1); + c.setMagicCaretPosition(t.modelToView(offs2).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -466,51 +478,52 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - int dot = c.getDot(); - - try + if (t != null) { - int wordStart = Utilities.getWordStart(t, dot); - - if (dot == wordStart) - { - // Current cursor position is on the first character in a word. - c.setDot(wordStart); - c.moveDot(Utilities.getWordEnd(t, wordStart)); - } - else + Caret c = t.getCaret(); + int dot = c.getDot(); + try { - // Current cursor position is not on the first character - // in a word. - int nextWord = Utilities.getNextWord(t, dot); - int previousWord = Utilities.getPreviousWord(t, dot); - int previousWordEnd = Utilities.getWordEnd(t, previousWord); - - // Cursor position is in the space between two words. In such a - // situation just select the space. - if (dot >= previousWordEnd && dot <= nextWord) + int wordStart = Utilities.getWordStart(t, dot); + + if (dot == wordStart) { - c.setDot(previousWordEnd); - c.moveDot(nextWord); + // Current cursor position is on the first character in a word. + c.setDot(wordStart); + c.moveDot(Utilities.getWordEnd(t, wordStart)); } else { - // Cursor position is inside a word. Just select it then. - c.setDot(previousWord); - c.moveDot(previousWordEnd); + // Current cursor position is not on the first character + // in a word. + int nextWord = Utilities.getNextWord(t, dot); + int previousWord = Utilities.getPreviousWord(t, dot); + int previousWordEnd = Utilities.getWordEnd(t, previousWord); + + // Cursor position is in the space between two words. In such a + // situation just select the space. + if (dot >= previousWordEnd && dot <= nextWord) + { + c.setDot(previousWordEnd); + c.moveDot(nextWord); + } + else + { + // Cursor position is inside a word. Just select it then. + c.setDot(previousWord); + c.moveDot(previousWordEnd); + } } - } - // If the position was updated change the magic caret position - // as well. - if (c.getDot() != dot) - c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation()); - - } - catch(BadLocationException ble) - { - // Can't happen. + // If the position was updated change the magic caret position + // as well. + if (c.getDot() != dot) + c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -715,21 +728,23 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - try - { - int offs = Utilities.getRowEnd(t, t.getCaretPosition()); - - if (offs > -1) - { - Caret c = t.getCaret(); - c.setDot(offs); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - } - catch (BadLocationException ble) - { - // Nothing to do here - } + if (t != null) + { + try + { + int offs = Utilities.getRowEnd(t, t.getCaretPosition()); + if (offs > -1) + { + Caret c = t.getCaret(); + c.setDot(offs); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + } + catch (BadLocationException ble) + { + // Nothing to do here + } + } } } @@ -744,21 +759,23 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - try - { - int offs = Utilities.getRowStart(t, t.getCaretPosition()); - - if (offs > -1) - { - Caret c = t.getCaret(); - c.setDot(offs); - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - } - catch (BadLocationException ble) - { - // Do nothing here. - } + if (t != null) + { + try + { + int offs = Utilities.getRowStart(t, t.getCaretPosition()); + if (offs > -1) + { + Caret c = t.getCaret(); + c.setDot(offs); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + } + catch (BadLocationException ble) + { + // Do nothing here. + } + } } } @@ -773,16 +790,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - Caret c = t.getCaret(); - c.setDot(0); - try - { - c.setMagicCaretPosition(t.modelToView(0).getLocation()); - } - catch(BadLocationException ble) - { - // Can't happen. - } + if (t != null) + { + Caret c = t.getCaret(); + c.setDot(0); + try + { + c.setMagicCaretPosition(t.modelToView(0).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } } } @@ -797,16 +817,19 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - int offs = t.getDocument().getLength(); - Caret c = t.getCaret(); - c.setDot(offs); - try - { - c.setMagicCaretPosition(t.modelToView(offs).getLocation()); - } - catch(BadLocationException ble) + if (t != null) { - // Can't happen. + int offs = t.getDocument().getLength(); + Caret c = t.getCaret(); + c.setDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } } } } @@ -862,7 +885,9 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - getTextComponent(event).copy(); + JTextComponent target = getTextComponent(event); + if (target != null) + target.copy(); } } @@ -893,7 +918,9 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - getTextComponent(event).cut(); + JTextComponent target = getTextComponent(event); + if (target != null) + target.cut(); } } @@ -922,7 +949,9 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - getTextComponent(event).paste(); + JTextComponent target = getTextComponent(event); + if (target != null) + target.paste(); } } @@ -957,14 +986,26 @@ public class DefaultEditorKit extends EditorKit { // first we filter the following events: // - control characters - // - key events with the ALT modifier (FIXME: filter that too!) - int cp = event.getActionCommand().codePointAt(0); - if (Character.isISOControl(cp)) - return; - - JTextComponent t = getTextComponent(event); - if (t != null && t.isEnabled() && t.isEditable()) - t.replaceSelection(event.getActionCommand()); + // - key events with the ALT modifier + JTextComponent target = getTextComponent(event); + if ((target != null) && (event != null)) + { + if ((target.isEditable()) && (target.isEnabled())) + { + String content = event.getActionCommand(); + int mod = event.getModifiers(); + if ((content != null) && (content.length() > 0) + && (mod & ActionEvent.ALT_MASK) == 0 + && (mod & ActionEvent.CTRL_MASK) == 0) + { + char c = content.charAt(0); + if ((c >= 0x20) && (c != 0x7F)) + { + target.replaceSelection(content); + } + } + } + } } } @@ -992,7 +1033,8 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - t.replaceSelection("\n"); + if (t != null) + t.replaceSelection("\n"); } } @@ -1047,7 +1089,8 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - t.replaceSelection("\t"); + if (t != null) + t.replaceSelection("\t"); } } diff --git a/libjava/classpath/javax/swing/text/DefaultFormatter.java b/libjava/classpath/javax/swing/text/DefaultFormatter.java index e42b1698af8..bf7c02a004d 100644 --- a/libjava/classpath/javax/swing/text/DefaultFormatter.java +++ b/libjava/classpath/javax/swing/text/DefaultFormatter.java @@ -216,7 +216,7 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter */ public DefaultFormatter() { - commitsOnValidEdit = true; + commitsOnValidEdit = false; overwriteMode = true; allowsInvalid = true; } @@ -330,7 +330,7 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter * * @return the class that is used for values */ - public Class getValueClass() + public Class<?> getValueClass() { return valueClass; } @@ -342,7 +342,7 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter * * @see #getValueClass() */ - public void setValueClass(Class valueClass) + public void setValueClass(Class<?> valueClass) { this.valueClass = valueClass; } diff --git a/libjava/classpath/javax/swing/text/DefaultHighlighter.java b/libjava/classpath/javax/swing/text/DefaultHighlighter.java index 59f77316e87..69563e473ac 100644 --- a/libjava/classpath/javax/swing/text/DefaultHighlighter.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java index bd21e55c6fb..3156ca67f66 100644 --- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java +++ b/libjava/classpath/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,20 @@ 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 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 +517,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 +539,6 @@ public class DefaultStyledDocument extends AbstractDocument implements public ElementBuffer(Element root) { this.root = root; - elementStack = new Stack(); } /** @@ -495,13 +564,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 +576,293 @@ 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) - { - 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); - } + 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. + * Creates a document in response to a call to + * {@link DefaultStyledDocument#create(ElementSpec[])}. + * + * @param len the length of the inserted text + * @param data the specs for the elements + * @param ev the document event */ - protected void changeUpdate() + void create(int len, ElementSpec[] data, DefaultDocumentEvent ev) + { + prepareEdit(offset, len); + Element el = root; + int index = el.getElementIndex(0); + while (! el.isLeaf()) + { + Element child = el.getElement(index); + Edit edit = new Edit(el, index, false); + elementStack.push(edit); + el = child; + index = el.getElementIndex(0); + } + Edit ed = (Edit) elementStack.peek(); + Element child = ed.e.getElement(ed.index); + ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(), + child.getEndOffset())); + ed.removed.add(child); + while (elementStack.size() > 1) + pop(); + int n = data.length; + + // Reset root element's attributes. + AttributeSet newAtts = null; + if (n > 0 && data[0].getType() == ElementSpec.StartTagType) + newAtts = data[0].getAttributes(); + if (newAtts == null) + newAtts = SimpleAttributeSet.EMPTY; + MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes(); + ev.addEdit(new AttributeUndoableEdit(root, newAtts, true)); + mAtts.removeAttributes(mAtts); + mAtts.addAttributes(newAtts); + + // Insert the specified elements. + for (int i = 1; i < n; i++) + insertElement(data[i]); + + // Pop remaining stack. + while (elementStack.size() > 0) + pop(); + + finishEdit(ev); + } + + private boolean canJoin(Element e0, Element e1) + { + boolean ret = false; + if ((e0 != null) && (e1 != null)) + { + // 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; + } + + 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) { - removed = new Element[0]; - added = new Element[] { res[1] }; - index++; + 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)) + { + 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(); } } @@ -633,13 +882,9 @@ public class DefaultStyledDocument extends AbstractDocument implements */ public void change(int offset, int length, DefaultDocumentEvent ev) { - if (length == 0) - return; - this.offset = offset; - this.pos = offset; - this.length = length; - documentEvent = ev; + prepareEdit(offset, length); changeUpdate(); + finishEdit(ev); } /** @@ -683,6 +928,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 +979,100 @@ 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); + } + edits.clear(); + elementStack.clear(); } /** - * 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) { @@ -780,127 +1088,132 @@ public class DefaultStyledDocument extends AbstractDocument implements createFracture(data); i = 0; } - + // Handle each ElementSpec individually. for (; i < data.length; i++) { - BranchElement paragraph = (BranchElement) elementStack.peek(); - switch (data[i].getType()) - { - 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; - } + 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) + { + int p; + for (p = 0; + p < data.length && data[p].getType() == ElementSpec.EndTagType; + p++); + 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 +1223,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,630 +1298,356 @@ 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); - } - } - - /** - * 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) - { - int index = paragraph.getElementIndex(start); - Element child = paragraph.getElement(index); - AttributeSet atts = child.getAttributes(); - - if (!onlyContent) - { - 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(); + 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 - { - Element newLeaf = createLeafElement(paragraph, atts, start, - child.getEndOffset()); - Edit edit = getEditForParagraphAndIndex(paragraph, index); - edit.addAddedElement(newLeaf); - } + offsetLastIndex = true; } - - /** - * 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 fracture(int depth) { - // 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) + 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--) { - 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) + Edit edit = insertPath[i]; + if (edit.added.size() > 0 || i == depth) { - // 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[] {}; - - } - // This is the case when we may not keep the first - // element. - else - { - removed = new Element[count - index]; - newAdded = new Element[count - index]; - added = new Element[] { result[0] }; - } - newAdded[0] = result[1]; - for (int i = index; i < count; i++) + lastIndex = i; + if (! recreate && childChanged) { - Element el2 = el.getElement(i); - int ind = i - count + removed.length; - removed[ind] = el2; - if (ind != 0) - newAdded[ind] = el2; + recreate = true; + if (deepestChangedIndex == -1) + deepestChangedIndex = lastChangedIndex + 1; } - - 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 + if (! childChanged && edit.index < edit.e.getElementCount()) { - 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 }; + childChanged = true; + lastChangedIndex = i; } } - else if (el instanceof LeafElement) + if (recreate) { - 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 }; + if (lastIndex == -1) + lastIndex = len - 1; + recreate(lastIndex, deepestChangedIndex); } - return res; } - /** - * Inserts a fracture into the document structure. - * - * @param tag - - * the element spec. - */ - private void insertFracture(ElementSpec tag) + private void recreate(int startIndex, int endIndex) { - // 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) + // 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()) + { + newChild = createLeafElement(edit.e, child.getAttributes(), + Math.max(endOffset, child.getStartOffset()), + child.getEndOffset()); + } + else { - // 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) + newChild = createBranchElement(edit.e, child.getAttributes()); + } + fracturedParent = edit.e; + fracturedChild = newChild; + + // 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) { - added = new Element[] { rightBranch }; - - // don't try to remove left part of tree - parentIndex++; + if(offsetLastIndex || ! isEndLeaf) + child = null; + else + child = edit.e.getElement(edit.index); } else { - leftBranch = - (BranchElement) createBranchElement(parent, parentAtts); - added = new Element[] { leftBranch, rightBranch }; - - // add fracture to leftBranch - if (fracStart != pos) - { - Element leftFracturedLeaf = - createLeafElement(leftBranch, atts, fracStart, pos); - leftBranch.replace(leftIns, 0, - new Element[] { leftFracturedLeaf }); - } + child = edit.e.getElement(edit.index - 1); } - if (!toFracture.isLeaf()) + // Duplicate it. + if(child != null) { - // add all non-fracture elements to the branches - if (indexOfFrac > 0 && leftBranch != null) + if(child.isLeaf()) { - 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); + newChild = createLeafElement(parent, child.getAttributes(), + Math.max(endOffset, child.getStartOffset()), + child.getEndOffset()); } - - 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); + newChild = createBranchElement(parent, + child.getAttributes()); } } - - // 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)) - { - end = next.getEndOffset(); - rm = 1; - } + else + newChild = null; - Element rightFracturedLeaf = createLeafElement(rightBranch, atts, - pos, end); - rightBranch.replace(0, rm, new Element[] { rightFracturedLeaf }); + // Recreate the remaining children (there may be none). + int childrenToMove = edit.e.getElementCount() - edit.index; + Element[] children; + int moveStartIndex; + int childStartIndex = 1; - // 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) - { - 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()); + 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; } - - edit.addAddedElements(added); - edit.addAddedElements(added2); - elementStack.push(rightBranch); - lastFractured = rightFracturedLeaf; + else + { + // Last leaf, need to recreate part of it. + moveStartIndex = edit.index + 1; + } + children = new Element[childrenToMove]; + children[0] = newChild; } - else - fracNotCreated = true; + + 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; + } + } - /** - * Recreates all the elements from the parent to the element on the top of - * the stack, starting from startFrom with the starting offset of - * startOffset. - * - * @param recreate - - * the elements to recreate - * @param parent - - * the element to add the new elements to - * @param startFrom - - * where to start recreating from - * @param startOffset - - * the offset of the first element - * @return the array of added elements - */ - private Element[] recreateAfterFracture(Element[] recreate, - BranchElement parent, int startFrom, - int startOffset) + private Element recreateFracturedElement(Element parent, Element toCopy) { - Element[] added = new Element[recreate.length - startFrom]; - int j = 0; - for (int i = startFrom; i < recreate.length; i++) + Element recreated; + if(toCopy.isLeaf()) { - Element curr = recreate[i]; - int len = curr.getEndOffset() - curr.getStartOffset(); - if (curr instanceof LeafElement) - added[j] = createLeafElement(parent, curr.getAttributes(), - startOffset, startOffset + len); - else + 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++) { - BranchElement br = - (BranchElement) createBranchElement(parent, - curr.getAttributes()); - int bSize = curr.getElementCount(); - for (int k = 0; k < bSize; k++) - { - Element bCurr = curr.getElement(k); - Element[] add = recreateAfterFracture(new Element[] { bCurr }, br, 0, - startOffset); - br.replace(0, 0, add); - - } - added[j] = br; + newChildren[i] = recreateFracturedElement(newParent, + toCopy.getElement(i)); } - startOffset += len; - j++; + ((BranchElement) newParent).replace(0, 0, newChildren); + recreated = newParent; } - - return added; - } - } - - /** - * 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 recreated; } - /** - * 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() + private boolean split(int offs, int len) { - 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++) + boolean splitEnd = false; + // Push the path to the stack. + Element e = root; + int index = e.getElementIndex(offs); + while (! e.isLeaf()) { - 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; + elementStack.push(new Edit(e, index)); + e = e.getElement(index); + index = e.getElementIndex(offs); } - 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++) + 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()) { - if (!contains(removed, e[i])) - removed.add(e[i]); - } - } + // 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)) + { + // It's a range split in the same parent. + index1 = ec.e.getElementIndex(offs+len); + if (index1 == index0) + { + // 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; + } + else + { + child = ec.e.getElement(index1); + if ((offs + len) == child.getStartOffset()) + { + // End is already on a boundary. + index1 = index0; + } + } + splitEnd = true; + } - /** - * 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); - } + // 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++) + { + child = ec.e.getElement(i); + ec.removed.add(child); + ec.added.add(child); + } - /** - * 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]); + if (index1 != index0) + { + 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); + } } + return splitEnd; + } - /** - * 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 * name. @@ -1674,11 +1717,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() @@ -1939,7 +1977,6 @@ public class DefaultStyledDocument extends AbstractDocument implements // start and ends at an element end. buffer.change(offset, length, ev); - Element root = getDefaultRootElement(); // Visit all paragraph elements within the specified interval int end = offset + length; Element curr; @@ -2079,147 +2116,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) { - if (finalStartTag != null - && finalStartTag.getDirection() == ElementSpec.JoinFractureDirection - && currentRun.getAttributes().isEqual(attr)) + parentsRight.add(e); + e = e.getParentElement(); + } + + if (e != null) + + { + // 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; } /** @@ -2267,7 +2377,7 @@ public class DefaultStyledDocument extends AbstractDocument implements * * @return an enumeration of all style names */ - public Enumeration getStyleNames() + public Enumeration<?> getStyleNames() { StyleContext context = (StyleContext) getAttributeContext(); return context.getStyleNames(); @@ -2322,18 +2432,24 @@ public class DefaultStyledDocument extends AbstractDocument implements if (length == 0) return; - UndoableEdit edit = content.insertString(offset, - contentBuffer.toString()); + Content c = getContent(); + UndoableEdit edit = c.insertString(offset, + contentBuffer.toString()); // Create the DocumentEvent with the ElementEdit added DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT); + ev.addEdit(edit); // Finally we must update the document structure and fire the insert // update event. buffer.insert(offset, length, data, ev); + + super.insertUpdate(ev, null); + + ev.end(); fireInsertUpdate(ev); fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); } @@ -2353,14 +2469,16 @@ public class DefaultStyledDocument extends AbstractDocument implements */ protected void create(ElementSpec[] data) { - writeLock(); try { + // Clear content if there is some. int len = getLength(); if (len > 0) remove(0, len); + writeLock(); + // Now we insert the content. StringBuilder b = new StringBuilder(); for (int i = 0; i < data.length; ++i) @@ -2372,38 +2490,18 @@ public class DefaultStyledDocument extends AbstractDocument implements Content content = getContent(); UndoableEdit cEdit = content.insertString(0, b.toString()); + len = b.length(); DefaultDocumentEvent ev = new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT); ev.addEdit(cEdit); - // We do a little trick here to get the new structure: We instantiate - // a new ElementBuffer with a new root element, insert into that root - // and then reparent the newly created elements to the old root - // element. - BranchElement createRoot = - (BranchElement) createBranchElement(null, null); - Element dummyLeaf = createLeafElement(createRoot, null, 0, 1); - createRoot.replace(0, 0, new Element[]{ dummyLeaf }); - ElementBuffer createBuffer = new ElementBuffer(createRoot); - createBuffer.insert(0, b.length(), data, new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT)); - // Now the new root is the first child of the createRoot. - Element newRoot = createRoot.getElement(0); - BranchElement root = (BranchElement) getDefaultRootElement(); - Element[] added = new Element[newRoot.getElementCount()]; - for (int i = 0; i < added.length; ++i) - { - added[i] = newRoot.getElement(i); - ((AbstractElement) added[i]).element_parent = root; - } - Element[] removed = new Element[root.getElementCount()]; - for (int i = 0; i < removed.length; ++i) - removed[i] = root.getElement(i); + buffer.create(len, data, ev); - // Replace the old elements in root with the new and update the event. - root.replace(0, removed.length, added); - ev.addEdit(new ElementEdit(root, 0, removed, added)); + // For the bidi update. + super.insertUpdate(ev, null); + ev.end(); fireInsertUpdate(ev); fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); } diff --git a/libjava/classpath/javax/swing/text/ElementIterator.java b/libjava/classpath/javax/swing/text/ElementIterator.java index a6a5ff618bd..112d55e96d0 100644 --- a/libjava/classpath/javax/swing/text/ElementIterator.java +++ b/libjava/classpath/javax/swing/text/ElementIterator.java @@ -37,6 +37,8 @@ exception statement from your version. */ package javax.swing.text; +import java.util.Stack; + /** * This class can be used to iterate over the {@link Element} tree of * a {@link Document} or an {@link Element}. This iterator performs @@ -46,20 +48,41 @@ package javax.swing.text; */ public class ElementIterator implements Cloneable { + /** + * Uses to track the iteration on the stack. + */ + private class ElementRef + { + /** + * The element. + */ + Element element; + + /** + * The child index. -1 means the element itself. >= 0 values mean the + * n-th child of the element. + */ + int index; + + /** + * Creates a new ElementRef. + * + * @param el the element + */ + ElementRef(Element el) + { + element = el; + index = -1; + } + } + // The root element. private Element root; - // The current element. - private Element currentElement; - // The depth to which we have descended in the tree. - private int currentDepth; - - // This is at least as big as the current depth, and at index N - // contains the index of the child element we're currently - // examining. - private int[] state; - // The previous item. - private Element previousItem; + /** + * Holds ElementRefs. + */ + private Stack stack; /** * Create a new ElementIterator to iterate over the given document. @@ -67,9 +90,7 @@ public class ElementIterator implements Cloneable */ public ElementIterator(Document document) { - this.root = document.getDefaultRootElement(); - this.currentElement = root; - this.state = new int[5]; + root = document.getDefaultRootElement(); } /** @@ -79,8 +100,6 @@ public class ElementIterator implements Cloneable public ElementIterator(Element root) { this.root = root; - this.currentElement = root; - this.state = new int[5]; } /** @@ -105,7 +124,24 @@ public class ElementIterator implements Cloneable */ public Element current() { - return currentElement; + Element current; + if (stack == null) + current = first(); + else + { + current = null; + if (! stack.isEmpty()) + { + ElementRef ref = (ElementRef) stack.peek(); + Element el = ref.element; + int index = ref.index; + if (index == -1) + current = el; + else + current = el.getElement(index); + } + } + return current; } /** @@ -113,7 +149,10 @@ public class ElementIterator implements Cloneable */ public int depth() { - return currentDepth; + int depth = 0; + if (stack != null) + depth = stack.size(); + return depth; } /** @@ -121,11 +160,15 @@ public class ElementIterator implements Cloneable */ public Element first() { - // Reset the iterator. - currentElement = root; - currentDepth = 0; - previousItem = null; - return root; + Element first = null; + if (root != null) + { + stack = new Stack(); + if (root.getElementCount() > 0) + stack.push(new ElementRef(root)); + first = root; + } + return first; } /** @@ -134,48 +177,96 @@ public class ElementIterator implements Cloneable */ public Element next() { - previousItem = currentElement; - if (currentElement == null) - return null; - if (! currentElement.isLeaf()) + Element next; + if (stack == null) + next = first(); + else { - ++currentDepth; - if (currentDepth > state.length) - { - int[] newState = new int[state.length * 2]; - System.arraycopy(state, 0, newState, 0, state.length); - state = newState; - } - state[currentDepth] = 0; - currentElement = currentElement.getElement(0); - return currentElement; + next = null; + if (! stack.isEmpty()) + { + ElementRef ref = (ElementRef) stack.peek(); + Element el = ref.element; + int index = ref.index; + if (el.getElementCount() > index + 1) + { + Element child = el.getElement(index + 1); + if (child.isLeaf()) + ref.index++; + else + stack.push(new ElementRef(child)); + next = child; + next = child; + } + else + { + stack.pop(); + if (! stack.isEmpty()) + { + ElementRef top = (ElementRef) stack.peek(); + top.index++; + next = next(); + } + } + } + // else return null. } + return next; + } - while (currentDepth > 0) + /** + * Returns the previous item. Does not modify the iterator state. + */ + public Element previous() + { + Element previous = null; + int stackSize; + if (stack != null && (stackSize = stack.size()) > 0) { - // At a leaf, or done with a non-leaf's children, so go up a - // level. - --currentDepth; - currentElement = currentElement.getParentElement(); - ++state[currentDepth]; - if (state[currentDepth] < currentElement.getElementCount()) - { - currentElement = currentElement.getElement(state[currentDepth]); - return currentElement; - } + ElementRef ref = (ElementRef) stack.peek(); + Element el = ref.element; + int index = ref.index; + if (index > 0) + { + previous = deepestLeaf(el.getElement(--index)); + } + else if (index == 0) + { + previous = el; + } + else if (index == -1) + { + ElementRef top = (ElementRef) stack.pop(); + ElementRef item = (ElementRef) stack.peek(); + stack.push(top); + index = item.index; + el = item.element; + previous = index == -1 ? el : deepestLeaf(el.getElement(index)); + } } - - currentElement = null; - return currentElement; + return previous; } /** - * Returns the previous item. Does not modify the iterator state. + * Determines and returns the deepest leaf of the element <code>el</code>. + * + * @param el the base element + * + * @returnthe deepest leaf of the element <code>el</code> */ - public Element previous() + private Element deepestLeaf(Element el) { - if (currentElement == null || currentElement == root) - return null; - return previousItem; + Element leaf; + if (el.isLeaf()) + leaf = el; + else + { + int count = el.getElementCount(); + if (count == 0) + leaf = el; + else + leaf = deepestLeaf(el.getElement(count - 1)); + } + return leaf; } } diff --git a/libjava/classpath/javax/swing/text/FieldView.java b/libjava/classpath/javax/swing/text/FieldView.java index f41f9013092..0a078e53dca 100644 --- a/libjava/classpath/javax/swing/text/FieldView.java +++ b/libjava/classpath/javax/swing/text/FieldView.java @@ -45,8 +45,6 @@ import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import javax.swing.BoundedRangeModel; import javax.swing.JTextField; @@ -225,7 +223,7 @@ public class FieldView extends PlainView public int getResizeWeight(int axis) { - return axis = axis == X_AXIS ? 1 : 0; + return axis == X_AXIS ? 1 : 0; } public Shape modelToView(int pos, Shape a, Position.Bias bias) diff --git a/libjava/classpath/javax/swing/text/FlowView.java b/libjava/classpath/javax/swing/text/FlowView.java index 3de95ed7f8d..c2bed399f3a 100644 --- a/libjava/classpath/javax/swing/text/FlowView.java +++ b/libjava/classpath/javax/swing/text/FlowView.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.Component; +import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; @@ -85,7 +87,17 @@ public abstract class FlowView extends BoxView */ public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { - // The default implementation does nothing. + if (alloc == null) + { + fv.layoutChanged(X_AXIS); + fv.layoutChanged(Y_AXIS); + } + else + { + Component host = fv.getContainer(); + if (host != null) + host.repaint(alloc.x, alloc.y, alloc.width, alloc.height); + } } /** @@ -101,7 +113,17 @@ public abstract class FlowView extends BoxView */ public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { - // The default implementation does nothing. + if (alloc == null) + { + fv.layoutChanged(X_AXIS); + fv.layoutChanged(Y_AXIS); + } + else + { + Component host = fv.getContainer(); + if (host != null) + host.repaint(alloc.x, alloc.y, alloc.width, alloc.height); + } } /** @@ -117,7 +139,17 @@ public abstract class FlowView extends BoxView */ public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { - // The default implementation does nothing. + if (alloc == null) + { + fv.layoutChanged(X_AXIS); + fv.layoutChanged(Y_AXIS); + } + else + { + Component host = fv.getContainer(); + if (host != null) + host.repaint(alloc.x, alloc.y, alloc.width, alloc.height); + } } /** @@ -143,18 +175,35 @@ public abstract class FlowView extends BoxView */ public void layout(FlowView fv) { + int start = fv.getStartOffset(); + int end = fv.getEndOffset(); + + // Preserve the views from the logical view from beeing removed. + View lv = getLogicalView(fv); + int viewCount = lv.getViewCount(); + for (int i = 0; i < viewCount; i++) + { + View v = lv.getView(i); + v.setParent(lv); + } + + // Then remove all views from the flow view. fv.removeAll(); - Element el = fv.getElement(); - int rowStart = el.getStartOffset(); - int end = el.getEndOffset(); - int rowIndex = 0; - while (rowStart >= 0 && rowStart < end) + for (int rowIndex = 0; start < end; rowIndex++) { View row = fv.createRow(); fv.append(row); - rowStart = layoutRow(fv, rowIndex, rowStart); - rowIndex++; + int next = layoutRow(fv, rowIndex, start); + if (row.getViewCount() == 0) + { + row.append(createView(fv, start, Integer.MAX_VALUE, rowIndex)); + next = row.getEndOffset(); + } + if (start < next) + start = next; + else + assert false: "May not happen"; } } @@ -179,46 +228,69 @@ public abstract class FlowView extends BoxView int axis = fv.getFlowAxis(); int span = fv.getFlowSpan(rowIndex); int x = fv.getFlowStart(rowIndex); - int offset = pos; - View logicalView = getLogicalView(fv); - // Special case when span == 0. We need to layout the row as if it had - // a span of Integer.MAX_VALUE. - if (span == 0) - span = Integer.MAX_VALUE; - - Row: while (span > 0) + int end = fv.getEndOffset(); + + // Needed for adjusting indentation in adjustRow(). + int preX = x; + int availableSpan = span; + + TabExpander tabExp = fv instanceof TabExpander ? (TabExpander) fv : null; + + boolean forcedBreak = false; + while (pos < end && span >= 0) { - if (logicalView.getViewIndex(offset, Position.Bias.Forward) == - 1) - break; - View view = createView(fv, offset, span, rowIndex); - if (view == null) + View view = createView(fv, pos, span, rowIndex); + if (view == null + || (span == 0 && view.getPreferredSpan(axis) > 0)) break; - int viewSpan = (int) view.getPreferredSpan(axis); - int breakWeight = view.getBreakWeight(axis, x, span); - - row.append(view); - offset += (view.getEndOffset() - view.getStartOffset()); - x += viewSpan; - span -= viewSpan; + int viewSpan; + if (axis == X_AXIS && view instanceof TabableView) + viewSpan = (int) ((TabableView) view).getTabbedSpan(x, tabExp); + else + viewSpan = (int) view.getPreferredSpan(axis); // Break if the line if the view does not fit in this row or the // line just must be broken. - if (span < 0 || breakWeight >= View.ForcedBreakWeight) + int breakWeight = view.getBreakWeight(axis, pos, span); + if (breakWeight >= ForcedBreakWeight) { - int flowStart = fv.getFlowStart(axis); - int flowSpan = fv.getFlowSpan(axis); - adjustRow(fv, rowIndex, flowSpan, flowStart); int rowViewCount = row.getViewCount(); if (rowViewCount > 0) - offset = row.getView(rowViewCount - 1).getEndOffset(); - else - offset = - 1; - break Row; + { + view = view.breakView(axis, pos, x, span); + if (view != null) + { + if (axis == X_AXIS && view instanceof TabableView) + viewSpan = + (int) ((TabableView) view).getTabbedSpan(x, tabExp); + else + viewSpan = (int) view.getPreferredSpan(axis); + } + else + viewSpan = 0; + } + forcedBreak = true; + } + span -= viewSpan; + x += viewSpan; + if (view != null) + { + row.append(view); + pos = view.getEndOffset(); } + if (forcedBreak) + break; } - return offset != pos ? offset : - 1; + if (span < 0) + adjustRow(fv, rowIndex, availableSpan, preX); + else if (row.getViewCount() == 0) + { + View view = createView(fv, pos, Integer.MAX_VALUE, rowIndex); + row.append(view); + } + return row.getEndOffset(); } /** @@ -246,15 +318,11 @@ public abstract class FlowView extends BoxView int rowIndex) { View logicalView = getLogicalView(fv); - // FIXME: Handle the bias thing correctly. - int index = logicalView.getViewIndex(startOffset, Position.Bias.Forward); - View retVal = null; - if (index >= 0) - { - retVal = logicalView.getView(index); - if (retVal.getStartOffset() != startOffset) - retVal = retVal.createFragment(startOffset, retVal.getEndOffset()); - } + int index = logicalView.getViewIndex(startOffset, + Position.Bias.Forward); + View retVal = logicalView.getView(index); + if (retVal.getStartOffset() != startOffset) + retVal = retVal.createFragment(startOffset, retVal.getEndOffset()); return retVal; } @@ -279,37 +347,82 @@ public abstract class FlowView extends BoxView View row = fv.getView(rowIndex); int count = row.getViewCount(); int breakIndex = -1; - int maxBreakWeight = View.BadBreakWeight; - int breakX = x; - int breakSpan = desiredSpan; - int currentX = x; - int currentSpan = desiredSpan; + int breakWeight = BadBreakWeight; + int breakSpan = 0; + int currentSpan = 0; for (int i = 0; i < count; ++i) { View view = row.getView(i); - int weight = view.getBreakWeight(axis, currentX, currentSpan); - if (weight >= maxBreakWeight) + int spanLeft = desiredSpan - currentSpan; + int weight = view.getBreakWeight(axis, x + currentSpan, spanLeft); + if (weight >= breakWeight && weight > BadBreakWeight) { breakIndex = i; - breakX = currentX; breakSpan = currentSpan; - maxBreakWeight = weight; + breakWeight = weight; + if (weight >= ForcedBreakWeight) + // Don't search further. + break; } - int size = (int) view.getPreferredSpan(axis); - currentX += size; - currentSpan -= size; + currentSpan += view.getPreferredSpan(axis); } // If there is a potential break location found, break the row at // this location. - if (breakIndex > -1) + if (breakIndex >= 0) { + int spanLeft = desiredSpan - breakSpan; View toBeBroken = row.getView(breakIndex); View brokenView = toBeBroken.breakView(axis, toBeBroken.getStartOffset(), - breakX, breakSpan); + x + breakSpan, spanLeft); + View lv = getLogicalView(fv); + for (int i = breakIndex; i < count; i++) + { + View tmp = row.getView(i); + if (contains(lv, tmp)) + tmp.setParent(lv); + else if (tmp.getViewCount() > 0) + reparent(tmp, lv); + } row.replace(breakIndex, count - breakIndex, - new View[]{brokenView}); + new View[]{ brokenView }); + } + + } + + /** + * Helper method to determine if one view contains another as child. + */ + private boolean contains(View view, View child) + { + boolean ret = false; + int n = view.getViewCount(); + for (int i = 0; i < n && ret == false; i++) + { + if (view.getView(i) == child) + ret = true; + } + return ret; + } + + /** + * Helper method that reparents the <code>view</code> and all of its + * decendents to the <code>parent</code> (the logical view). + * + * @param view the view to reparent + * @param parent the new parent + */ + private void reparent(View view, View parent) + { + int n = view.getViewCount(); + for (int i = 0; i < n; i++) + { + View tmp = view.getView(i); + if (contains(parent, tmp)) + tmp.setParent(parent); + else + reparent(tmp, parent); } } } @@ -320,14 +433,135 @@ public abstract class FlowView extends BoxView * visual representation, this is handled by the physical view implemented * in the <code>FlowView</code>. */ - class LogicalView extends BoxView + class LogicalView extends CompositeView { /** * Creates a new LogicalView instance. */ - LogicalView(Element el, int axis) + LogicalView(Element el) + { + super(el); + } + + /** + * Overridden to return the attributes of the parent + * (== the FlowView instance). + */ + public AttributeSet getAttributes() + { + View p = getParent(); + return p != null ? p.getAttributes() : null; + } + + protected void childAllocation(int index, Rectangle a) + { + // Nothing to do here (not visual). + } + + protected View getViewAtPoint(int x, int y, Rectangle r) + { + // Nothing to do here (not visual). + return null; + } + + protected boolean isAfter(int x, int y, Rectangle r) + { + // Nothing to do here (not visual). + return false; + } + + protected boolean isBefore(int x, int y, Rectangle r) + { + // Nothing to do here (not visual). + return false; + } + + public float getPreferredSpan(int axis) + { + float max = 0; + float pref = 0; + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View v = getView(i); + pref += v.getPreferredSpan(axis); + if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) + >= ForcedBreakWeight) + { + max = Math.max(max, pref); + pref = 0; + } + } + max = Math.max(max, pref); + return max; + } + + public float getMinimumSpan(int axis) + { + float max = 0; + float min = 0; + boolean wrap = true; + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View v = getView(i); + if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) + == BadBreakWeight) + { + min += v.getPreferredSpan(axis); + wrap = false; + } + else if (! wrap) + { + max = Math.max(min, max); + wrap = true; + min = 0; + } + } + max = Math.max(max, min); + return max; + } + + public void paint(Graphics g, Shape s) + { + // Nothing to do here (not visual). + } + + /** + * Overridden to handle possible leaf elements. + */ + protected void loadChildren(ViewFactory f) { - super(el, axis); + Element el = getElement(); + if (el.isLeaf()) + { + View v = new LabelView(el); + append(v); + } + else + super.loadChildren(f); + } + + /** + * Overridden to reparent the children to this logical view, in case + * they have been parented by a row. + */ + protected void forwardUpdateToView(View v, DocumentEvent e, Shape a, + ViewFactory f) + { + v.setParent(this); + super.forwardUpdateToView(v, e, a, f); + } + + /** + * Overridden to handle possible leaf element. + */ + protected int getViewIndexAtPosition(int pos) + { + int index = 0; + if (! getElement().isLeaf()) + index = super.getViewIndexAtPosition(pos); + return index; } } @@ -357,11 +591,6 @@ public abstract class FlowView extends BoxView protected FlowStrategy strategy; /** - * Indicates if the flow should be rebuild during the next layout. - */ - private boolean layoutDirty; - - /** * Creates a new <code>FlowView</code> for the given * <code>Element</code> and <code>axis</code>. * @@ -374,7 +603,7 @@ public abstract class FlowView extends BoxView { super(element, axis); strategy = sharedStrategy; - layoutDirty = true; + layoutSpan = Short.MAX_VALUE; } /** @@ -423,7 +652,7 @@ public abstract class FlowView extends BoxView */ public int getFlowStart(int index) { - return getLeftInset(); // TODO: Is this correct? + return 0; } /** @@ -449,9 +678,11 @@ public abstract class FlowView extends BoxView { if (layoutPool == null) { - layoutPool = new LogicalView(getElement(), getAxis()); - layoutPool.setParent(this); + layoutPool = new LogicalView(getElement()); } + layoutPool.setParent(this); + // Initialize the flow strategy. + strategy.insertUpdate(this, null, null); } /** @@ -466,32 +697,33 @@ public abstract class FlowView extends BoxView protected void layout(int width, int height) { int flowAxis = getFlowAxis(); + int span; if (flowAxis == X_AXIS) - { - if (layoutSpan != width) - { - layoutChanged(Y_AXIS); - layoutSpan = width; - } - } + span = (int) width; else + span = (int) height; + + if (layoutSpan != span) { - if (layoutSpan != height) - { - layoutChanged(X_AXIS); - layoutSpan = height; - } + layoutChanged(flowAxis); + layoutChanged(getAxis()); + layoutSpan = span; } - if (layoutDirty) + if (! isLayoutValid(flowAxis)) { + int axis = getAxis(); + int oldSpan = axis == X_AXIS ? getWidth() : getHeight(); strategy.layout(this); - layoutDirty = false; + int newSpan = (int) getPreferredSpan(axis); + if (oldSpan != newSpan) + { + View parent = getParent(); + if (parent != null) + parent.preferenceChanged(this, axis == X_AXIS, axis == Y_AXIS); + } } - if (getPreferredSpan(getAxis()) != height) - preferenceChanged(this, false, true); - super.layout(width, height); } @@ -510,7 +742,6 @@ public abstract class FlowView extends BoxView // be updated accordingly. layoutPool.insertUpdate(changes, a, vf); strategy.insertUpdate(this, changes, getInsideAllocation(a)); - layoutDirty = true; } /** @@ -526,7 +757,6 @@ public abstract class FlowView extends BoxView { layoutPool.removeUpdate(changes, a, vf); strategy.removeUpdate(this, changes, getInsideAllocation(a)); - layoutDirty = true; } /** @@ -542,7 +772,6 @@ public abstract class FlowView extends BoxView { layoutPool.changedUpdate(changes, a, vf); strategy.changedUpdate(this, changes, getInsideAllocation(a)); - layoutDirty = true; } /** @@ -604,7 +833,7 @@ public abstract class FlowView extends BoxView res = new SizeRequirements(); res.minimum = (int) layoutPool.getMinimumSpan(axis); res.preferred = Math.max(res.minimum, - (int) layoutPool.getMinimumSpan(axis)); + (int) layoutPool.getPreferredSpan(axis)); res.maximum = Integer.MAX_VALUE; res.alignment = 0.5F; return res; diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java index 760e396a223..08a318d8bb4 100644 --- a/libjava/classpath/javax/swing/text/GapContent.java +++ b/libjava/classpath/javax/swing/text/GapContent.java @@ -39,16 +39,13 @@ exception statement from your version. */ package javax.swing.text; import java.io.Serializable; -import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Set; import java.util.Vector; -import java.util.WeakHashMap; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; @@ -71,7 +68,7 @@ public class GapContent /** * A {@link Position} implementation for <code>GapContent</code>. */ - private class GapContentPosition + class GapContentPosition implements Position { @@ -82,39 +79,6 @@ public class GapContent Mark mark; /** - * Creates a new GapContentPosition object. - * - * @param offset the offset of this Position - */ - GapContentPosition(int offset) - { - // Try to find the mark in the positionMarks array, and store the index - // to it. - synchronized (GapContent.this) - { - // Try to make space. - garbageCollect(); - Mark m = new Mark(offset); - int i = search(marks, m); - if (i >= 0) // mark found - { - m = (Mark) marks.get(i); - } - else - { - i = -i - 1; - marks.add(i, m); - } - m.refCount++; - mark = m; - } - - // Register this position in the death queue, so we can cleanup the marks - // when this position object gets GC'ed. - new WeakReference(this, queueOfDeath); - } - - /** * Returns the current offset of this Position within the content. * * @return the current offset of this Position within the content. @@ -133,7 +97,7 @@ public class GapContent * be garbage collected while we still hold a reference to the Mark object. */ private class Mark - implements Comparable + extends WeakReference { /** * The actual mark into the buffer. @@ -141,21 +105,20 @@ public class GapContent int mark; /** - * The number of GapContentPosition object that reference this mark. If - * it reaches zero, it get's deleted by {@link GapContent#garbageCollect()}. - */ - int refCount; - - /** * Creates a new Mark object for the specified offset. * * @param offset the offset */ Mark(int offset) { + super(null); + mark = offset; + } + + Mark(int offset, GapContentPosition pos, ReferenceQueue queue) + { + super(pos, queue); mark = offset; - if (mark >= gapStart && mark != 0) - mark += (gapEnd - gapStart); } /** @@ -165,33 +128,62 @@ public class GapContent */ int getOffset() { - assert mark == 0 || mark < gapStart || mark >= gapEnd : - "Invalid mark: " + mark + ", gapStart: " + gapStart - + ", gapEnd: " + gapEnd; - int res = mark; - if (mark >= gapEnd) + if (mark >= gapStart) res -= (gapEnd - gapStart); - return res; + return Math.max(0, res); + } + + /** + * Returns the GapContentPosition that is associated ith this mark. + * This fetches the weakly referenced position object. + * + * @return the GapContentPosition that is associated ith this mark + */ + GapContentPosition getPosition() + { + return (GapContentPosition) get(); } + } + + /** + * 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; + /** - * Implementation of Comparable. + * The original offset to reset the mark to. */ - public int compareTo(Object o) + private int undoOffset; + + /** + * Creates a new UndoPosRef. + * + * @param m the mark + */ + UndoPosRef(Mark m) { - Mark other = (Mark) o; - return mark - other.mark; + mark = m; + undoOffset = mark.getOffset(); } + /** - * Adjustment for equals(). + * Resets the position of the mark to the value that it had when + * creating this UndoPosRef. */ - public boolean equals(Object o) + void reset() { - if (o == null || !(o instanceof Mark)) - return false; + if (undoOffset <= gapStart) + mark.mark = undoOffset; else - return ((Mark) o).mark == mark; + mark.mark = (gapEnd - gapStart) + undoOffset; } } @@ -199,6 +191,8 @@ public class GapContent { public int where, length; String text; + private Vector positions; + public InsertUndo(int start, int len) { where = start; @@ -209,27 +203,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 +238,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 +257,8 @@ public class GapContent try { insertString(where, text); + if (positions != null) + updateUndoPositions(positions, where, text.length()); } catch (BadLocationException ble) { @@ -261,13 +270,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(); + } } } @@ -308,7 +319,15 @@ public class GapContent */ ArrayList marks; - WeakHashMap positions; + /** + * The number of unused marks. + */ + private int garbageMarks; + + /** + * A 'static' mark that is used for searching. + */ + private Mark searchMark = new Mark(0); /** * Queues all references to GapContentPositions that are about to be @@ -339,7 +358,6 @@ public class GapContent gapStart = 1; gapEnd = size; buffer[0] = '\n'; - positions = new WeakHashMap(); marks = new ArrayList(); queueOfDeath = new ReferenceQueue(); } @@ -403,9 +421,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 +448,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; } /** @@ -495,29 +515,43 @@ public class GapContent 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; + } + } } /** @@ -537,27 +571,33 @@ public class GapContent // and luckily enough the GapContent can very well deal with offsets // outside the buffer bounds. So I removed that check. + // First do some garbage collections. + while (queueOfDeath.poll() != null) + garbageMarks++; + if (garbageMarks > Math.max(5, marks.size() / 10)) + garbageCollect(); + // We try to find a GapContentPosition at the specified offset and return // that. Otherwise we must create a new one. - GapContentPosition pos = null; - Set positionSet = positions.keySet(); - for (Iterator i = positionSet.iterator(); i.hasNext();) - { - GapContentPosition p = (GapContentPosition) i.next(); - if (p.getOffset() == offset) - { - pos = p; - break; - } - } - - // If none was found, then create and return a new one. - if (pos == null) + Mark m; + GapContentPosition pos; + int index = offset; + if (offset >= gapStart) + index += (gapEnd - gapStart); + searchMark.mark = index; + int insertIndex = search(searchMark); + if (!(insertIndex < marks.size() + && (m = (Mark) marks.get(insertIndex)).mark == index + && (pos = m.getPosition()) != null)) { - pos = new GapContentPosition(offset); - positions.put(pos, null); + // Create new position if none was found. + pos = new GapContentPosition(); + m = new Mark(index, pos, queueOfDeath); + pos.mark = m; + marks.add(insertIndex, m); } - + // Otherwise use the found position. + return pos; } @@ -574,18 +614,29 @@ public class GapContent assert newSize > (gapEnd - gapStart) : "The new gap size must be greater " + "than the old gap size"; - int delta = newSize - gapEnd + gapStart; - // Update the marks after the gapEnd. - adjustPositionsInRange(gapEnd, -1, delta); + int oldEnd = getGapEnd(); + int oldSize = getArrayLength(); + int upper = oldSize - oldEnd; + int size = (newSize + 1) * 2; + int newEnd = size - upper; // Copy the data around. - char[] newBuf = (char[]) allocateArray(length() + newSize); - System.arraycopy(buffer, 0, newBuf, 0, gapStart); - System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length - - gapEnd); - gapEnd = gapStart + newSize; + char[] newBuf = (char[]) allocateArray(size); + System.arraycopy(buffer, 0, newBuf, 0, Math.min(size, oldSize)); buffer = newBuf; - + gapEnd = newEnd; + if (upper != 0) + System.arraycopy(buffer, oldEnd, buffer, newEnd, upper); + + // Adjust marks. + int delta = gapEnd - oldEnd; + int adjIndex = searchFirst(oldEnd); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + m.mark += delta; + } } /** @@ -595,28 +646,44 @@ public class GapContent */ protected void shiftGap(int newGapStart) { - if (newGapStart == gapStart) - return; - int newGapEnd = newGapStart + gapEnd - gapStart; - if (newGapStart < gapStart) + int oldStart = gapStart; + int delta = newGapStart - oldStart; + int oldEnd = gapEnd; + int newGapEnd = oldEnd + delta; + int size = oldEnd - oldStart; + + // Shift gap in array. + gapStart = newGapStart; + gapEnd = newGapEnd; + if (delta > 0) + System.arraycopy(buffer, oldEnd, buffer, oldStart, delta); + else + System.arraycopy(buffer, newGapStart, buffer, newGapEnd, -delta); + + // Adjust marks. + if (delta > 0) { - // Update the positions between newGapStart and (old) gapStart. The marks - // must be shifted by (gapEnd - gapStart). - adjustPositionsInRange(newGapStart, gapStart, gapEnd - gapStart); - System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart - - newGapStart); - gapStart = newGapStart; - gapEnd = newGapEnd; + int adjIndex = searchFirst(oldStart); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark >= newGapEnd) + break; + m.mark -= size; + } } - else + else if (delta < 0) { - // Update the positions between newGapEnd and (old) gapEnd. The marks - // must be shifted by (gapEnd - gapStart). - adjustPositionsInRange(gapEnd, newGapEnd, -(gapEnd - gapStart)); - System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart - - gapStart); - gapStart = newGapStart; - gapEnd = newGapEnd; + int adjIndex = searchFirst(newGapStart); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark >= oldEnd) + break; + m.mark += size; + } } resetMarksAtZero(); } @@ -636,7 +703,18 @@ public class GapContent assert newGapStart < gapStart : "The new gap start must be less than the " + "old gap start."; - setPositionsInRange(newGapStart, gapStart, false); + + // Adjust positions. + int adjIndex = searchFirst(newGapStart); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark > gapStart) + break; + m.mark = gapEnd; + } + gapStart = newGapStart; resetMarksAtZero(); } @@ -656,7 +734,19 @@ public class GapContent assert newGapEnd > gapEnd : "The new gap end must be greater than the " + "old gap end."; - setPositionsInRange(gapEnd, newGapEnd, false); + + // Adjust marks. + int adjIndex = searchFirst(gapEnd); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark >= newGapEnd) + break; + m.mark = newGapEnd; + } + + gapEnd = newGapEnd; resetMarksAtZero(); } @@ -682,23 +772,88 @@ public class GapContent protected void replace(int position, int rmSize, Object addItems, int addSize) { - if (gapStart != position) - shiftGap(position); - - // Remove content - if (rmSize > 0) - shiftGapEndUp(gapEnd + rmSize); + if (addSize == 0) + { + removeImpl(position, rmSize); + return; + } + else if (rmSize > addSize) + { + removeImpl(position + addSize, rmSize - addSize); + } + else + { + int endSize = addSize - rmSize; + int end = addImpl(position + rmSize, endSize); + System.arraycopy(addItems, rmSize, buffer, end, endSize); + addSize = rmSize; + } + System.arraycopy(addItems, 0, buffer, position, addSize); + } + + /** + * Adjusts the positions and gap in response to a remove operation. + * + * @param pos the position at which to remove + * @param num the number of removed items + */ + private void removeImpl(int pos, int num) + { + if (num > 0) + { + int end = pos + num; + int newGapSize = (gapEnd - gapStart) + num; + if (end <= gapStart) + { + if (gapStart != end) + { + shiftGap(end); + } + shiftGapStartDown(gapStart - num); + } + else if (pos >= gapStart) + { + if (gapStart != pos) + { + shiftGap(pos); + } + shiftGapEndUp(gapStart + newGapSize); + } + else + { + shiftGapStartDown(pos); + shiftGapEndUp(gapStart + newGapSize); + } + } + } - // If gap is too small, enlarge the gap. - if ((gapEnd - gapStart) <= addSize) - shiftEnd((addSize - gapEnd + gapStart + 1) * 2 + gapEnd + DEFAULT_BUFSIZE); + /** + * Adjusts the positions and gap in response to an add operation. + * + * @param pos the position at which to add + * @param num the number of added items + * + * @return the adjusted position + */ + private int addImpl(int pos, int num) + { + int size = gapEnd - gapStart; + if (num == 0) + { + if (pos > gapStart) + pos += size; + return pos; + } - // Add new items to the buffer. - if (addItems != null) + shiftGap(pos); + if (num >= size) { - System.arraycopy(addItems, 0, buffer, gapStart, addSize); - gapStart += addSize; + shiftEnd(getArrayLength() - size + num); + size = gapEnd - gapStart; } + + gapStart += num; + return pos; } /** @@ -733,97 +888,34 @@ public class GapContent */ protected Vector getPositionsInRange(Vector v, int offset, int length) { - Vector res = v; - if (res == null) - res = new Vector(); - else - res.clear(); - - int endOffs = offset + length; - - Set positionSet = positions.keySet(); - for (Iterator i = positionSet.iterator(); i.hasNext();) + int end = offset + length; + int startIndex; + int endIndex; + if (offset < gapStart) { - GapContentPosition p = (GapContentPosition) i.next(); - int offs = p.getOffset(); - if (offs >= offset && offs < endOffs) - res.add(p); + if (offset == 0) + startIndex = 0; + else + startIndex = searchFirst(offset); + if (end >= gapStart) + endIndex = searchFirst(end + (gapEnd - gapStart) + 1); + else + endIndex = searchFirst(end + 1); } - - return res; - } - - /** - * Crunches all positions in the specified range to either the start or - * end of that interval. The interval boundaries are meant to be inclusive - * [start, end]. - * - * @param start the start offset of the range - * @param end the end offset of the range - * @param toStart a boolean indicating if the positions should be crunched - * to the start (true) or to the end (false) - */ - private void setPositionsInRange(int start, int end, boolean toStart) - { - synchronized (this) + else { - // Find the start and end indices in the positionMarks array. - Mark m = new Mark(0); // For comparison / search only. - m.mark = start; - int startIndex = search(marks, m); - if (startIndex < 0) // Translate to insertion index, if not found. - startIndex = - startIndex - 1; - m.mark = end; - int 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 = toStart ? start : end; + startIndex = searchFirst(offset + (gapEnd - gapStart)); + endIndex = searchFirst(end + (gapEnd - gapStart) + 1); } - - } - - /** - * Adjusts the mark of all <code>Position</code>s that are in the range - * specified by <code>offset</code> and </code>length</code> within - * 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, -1 means all to the end - * @param incr the increment - */ - private void adjustPositionsInRange(int startOffs, int endOffs, int incr) - { - synchronized (this) + if (v == null) + v = new Vector(); + for (int i = startIndex; i < endIndex; i++) { - // Find the start and end indices in the positionMarks array. - Mark m = new Mark(0); // For comparison / search only. - - m.mark = startOffs; - int startIndex = search(marks, m); - if (startIndex < 0) // Translate to insertion index, if not found. - startIndex = - startIndex - 1; - - m.mark = endOffs; - 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; - } + v.add(new UndoPosRef((Mark) marks.get(i))); } - + return v; } - + /** * Resets all <code>Position</code> that have an offset of <code>0</code>, * to also have an array index of <code>0</code>. This might be necessary @@ -844,14 +936,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); } /** @@ -892,30 +996,6 @@ public class GapContent } /** - * Polls the queue of death for GapContentPositions, updates the - * corresponding reference count and removes the corresponding mark - * if the refcount reaches zero. - * - * This is package private to avoid accessor synthetic methods. - */ - void garbageCollect() - { - Reference ref = queueOfDeath.poll(); - while (ref != null) - { - if (ref != null) - { - GapContentPosition pos = (GapContentPosition) ref.get(); - Mark m = pos.mark; - m.refCount--; - if (m.refCount == 0) - marks.remove(m); - } - ref = queueOfDeath.poll(); - } - } - - /** * Searches the first occurance of object <code>o</code> in list * <code>l</code>. This performs a binary search by calling * {@link Collections#binarySearch(List, Object)} and when an object has been @@ -923,22 +1003,93 @@ public class GapContent * list. The meaning of the return value is the same as in * <code>Collections.binarySearch()</code>. * - * @param l the list to search through * @param o the object to be searched * * @return the index of the first occurance of o in l, or -i + 1 if not found */ - private int search(List l, Object o) + int search(Mark o) { - int i = Collections.binarySearch(l, o); - while (i > 0) + int foundInd = 0; + boolean found = false; + int low = 0; + int up = marks.size() - 1; + int mid = 0; + if (up > -1) { - Object o2 = l.get(i - 1); - if (o2.equals(o)) - i--; + int cmp = 0; + Mark last = (Mark) marks.get(up); + cmp = compare(o, last); + if (cmp > 0) + { + foundInd = up + 1; + found = true; + } else + { + while (low <= up && ! found) + { + mid = low + (up - low) / 2; + Mark m = (Mark) marks.get(mid); + cmp = compare(o, m); + if (cmp == 0) + { + foundInd = mid; + found = true; + } + else if (cmp < 0) + up = mid - 1; + else + low = mid + 1; + } + + if (! found) + foundInd = cmp < 0 ? mid : mid + 1; + } + } + return foundInd; + } + + private int searchFirst(int index) + { + searchMark.mark = Math.max(index, 1); + int i = search(searchMark); + for (int j = i - 1; j >= 0; j--) + { + Mark m = (Mark) marks.get(j); + if (m.mark != index) break; + i--; } return i; } + + /** + * Compares two marks. + * + * @param m1 the first mark + * @param m2 the second mark + * + * @return negative when m1 < m2, positive when m1 > m2 and 0 when equal + */ + private int compare(Mark m1, Mark m2) + { + return m1.mark - m2.mark; + } + + /** + * Collects and frees unused marks. + */ + private void garbageCollect() + { + int count = marks.size(); + ArrayList clean = new ArrayList(); + for (int i = 0; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.get() != null) + clean.add(m); + } + marks = clean; + garbageMarks = 0; + } } diff --git a/libjava/classpath/javax/swing/text/GlyphView.java b/libjava/classpath/javax/swing/text/GlyphView.java index d505274c91f..1e418d2e06a 100644 --- a/libjava/classpath/javax/swing/text/GlyphView.java +++ b/libjava/classpath/javax/swing/text/GlyphView.java @@ -38,14 +38,21 @@ exception statement from your version. */ package javax.swing.text; +import gnu.classpath.SystemProperties; + import java.awt.Color; +import java.awt.Container; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Toolkit; -import java.text.BreakIterator; +import java.awt.font.FontRenderContext; +import java.awt.font.TextHitInfo; +import java.awt.font.TextLayout; +import java.awt.geom.Rectangle2D; import javax.swing.SwingConstants; import javax.swing.event.DocumentEvent; @@ -248,10 +255,164 @@ public class GlyphView extends View implements TabableView, Cloneable } /** + * A GlyphPainter implementation based on TextLayout. This should give + * better performance in Java2D environments. + */ + private static class J2DGlyphPainter + extends GlyphPainter + { + + /** + * The text layout. + */ + TextLayout textLayout; + + /** + * Creates a new J2DGlyphPainter. + * + * @param str the string + * @param font the font + * @param frc the font render context + */ + J2DGlyphPainter(String str, Font font, FontRenderContext frc) + { + textLayout = new TextLayout(str, font, frc); + } + + /** + * Returns null so that GlyphView.checkPainter() creates a new instance. + */ + public GlyphPainter getPainter(GlyphView v, int p0, int p1) + { + return null; + } + + /** + * Delegates to the text layout. + */ + public float getAscent(GlyphView v) + { + return textLayout.getAscent(); + } + + /** + * Delegates to the text layout. + */ + public int getBoundedPosition(GlyphView v, int p0, float x, float len) + { + int pos; + TextHitInfo hit = textLayout.hitTestChar(len, 0); + if (hit.getCharIndex() == -1 && ! textLayout.isLeftToRight()) + pos = v.getEndOffset(); + else + { + pos = hit.isLeadingEdge() ? hit.getInsertionIndex() + : hit.getInsertionIndex() - 1; + pos += v.getStartOffset(); + } + return pos; + } + + /** + * Delegates to the text layout. + */ + public float getDescent(GlyphView v) + { + return textLayout.getDescent(); + } + + /** + * Delegates to the text layout. + */ + public float getHeight(GlyphView view) + { + return textLayout.getAscent() + textLayout.getDescent() + + textLayout.getLeading(); + } + + /** + * Delegates to the text layout. + */ + public float getSpan(GlyphView v, int p0, int p1, TabExpander te, float x) + { + float span; + if (p0 == v.getStartOffset() && p1 == v.getEndOffset()) + span = textLayout.getAdvance(); + else + { + int start = v.getStartOffset(); + int i0 = p0 - start; + int i1 = p1 - start; + TextHitInfo hit0 = TextHitInfo.afterOffset(i0); + TextHitInfo hit1 = TextHitInfo.afterOffset(i1); + float x0 = textLayout.getCaretInfo(hit0)[0]; + float x1 = textLayout.getCaretInfo(hit1)[0]; + span = Math.abs(x1 - x0); + } + return span; + } + + /** + * Delegates to the text layout. + */ + public Shape modelToView(GlyphView v, int pos, Bias b, Shape a) + throws BadLocationException + { + int offs = pos - v.getStartOffset(); + // Create copy here to protect original shape. + Rectangle2D bounds = a.getBounds2D(); + TextHitInfo hit = + b == Position.Bias.Forward ? TextHitInfo.afterOffset(offs) + : TextHitInfo.beforeOffset(offs); + float[] loc = textLayout.getCaretInfo(hit); + bounds.setRect(bounds.getX() + loc[0], bounds.getY(), 1, + bounds.getHeight()); + return bounds; + } + + /** + * Delegates to the text layout. + */ + public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1) + { + // Can't paint this with plain graphics. + if (g instanceof Graphics2D) + { + Graphics2D g2d = (Graphics2D) g; + Rectangle2D b = a instanceof Rectangle2D ? (Rectangle2D) a + : a.getBounds2D(); + float x = (float) b.getX(); + float y = (float) b.getY() + textLayout.getAscent() + + textLayout.getLeading(); + // TODO: Try if clipping makes things faster for narrow views. + textLayout.draw(g2d, x, y); + } + } + + /** + * Delegates to the text layout. + */ + public int viewToModel(GlyphView v, float x, float y, Shape a, + Bias[] biasRet) + { + Rectangle2D bounds = a instanceof Rectangle2D ? (Rectangle2D) a + : a.getBounds2D(); + TextHitInfo hit = textLayout.hitTestChar(x - (float) bounds.getX(), 0); + int pos = hit.getInsertionIndex(); + biasRet[0] = hit.isLeadingEdge() ? Position.Bias.Forward + : Position.Bias.Backward; + return pos + v.getStartOffset(); + } + + } + + /** * The default <code>GlyphPainter</code> used in <code>GlyphView</code>. */ static class DefaultGlyphPainter extends GlyphPainter { + FontMetrics fontMetrics; + /** * Returns the full height of the rendered text. * @@ -259,9 +420,8 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getHeight(GlyphView view) { - Font font = view.getFont(); - FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(font); - float height = metrics.getHeight(); + updateFontMetrics(view); + float height = fontMetrics.getHeight(); return height; } @@ -277,53 +437,27 @@ public class GlyphView extends View implements TabableView, Cloneable public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1) { - Color oldColor = g.getColor(); - int height = (int) getHeight(view); + updateFontMetrics(view); + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + TabExpander tabEx = view.getTabExpander(); Segment txt = view.getText(p0, p1); - Rectangle bounds = a.getBounds(); - TabExpander tabEx = null; - View parent = view.getParent(); - if (parent instanceof TabExpander) - tabEx = (TabExpander) parent; - - int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(), - bounds.x, tabEx, txt.offset); - // Fill the background of the text run. - Color background = view.getBackground(); - if (background != null) - { - g.setColor(background); - g.fillRect(bounds.x, bounds.y, width, height); - } - // Draw the actual text. - g.setColor(view.getForeground()); - g.setFont(view.getFont()); - int ascent = g.getFontMetrics().getAscent(); - if (view.isSuperscript()) - // TODO: Adjust font for superscripting. - Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent - height / 2, - g, tabEx, txt.offset); - else if (view.isSubscript()) - // TODO: Adjust font for subscripting. - Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent + height / 2, - g, tabEx, txt.offset); - else - Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx, - txt.offset); - if (view.isStrikeThrough()) - { - int strikeHeight = (int) (getAscent(view) / 2); - g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width, - bounds.y + strikeHeight); - } - if (view.isUnderline()) + // Find out the X location at which we have to paint. + int x = r.x; + int p = view.getStartOffset(); + if (p != p0) { - int lineHeight = (int) getAscent(view); - g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width, - bounds.y + lineHeight); + int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx, + p); + x += width; } - g.setColor(oldColor); + // Find out Y location. + int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent(); + + // Render the thing. + g.setFont(fontMetrics.getFont()); + Utilities.drawTabbedText(txt, x, y, g, tabEx, p0); + } /** @@ -350,15 +484,18 @@ public class GlyphView extends View implements TabableView, Cloneable Shape a) throws BadLocationException { + updateFontMetrics(view); Element el = view.getElement(); - 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); - int height = fm.getHeight(); - Rectangle bounds = a.getBounds(); + Rectangle bounds = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + TabExpander expander = view.getTabExpander(); + int width = Utilities.getTabbedTextWidth(txt, fontMetrics, bounds.x, + expander, + view.getStartOffset()); + int height = fontMetrics.getHeight(); Rectangle result = new Rectangle(bounds.x + width, bounds.y, - bounds.x + width, height); + 0, height); return result; } @@ -381,11 +518,10 @@ public class GlyphView extends View implements TabableView, Cloneable public float getSpan(GlyphView view, int p0, int p1, TabExpander te, float x) { - Element el = view.getElement(); - Font font = view.getFont(); - FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font); + updateFontMetrics(view); Segment txt = view.getText(p0, p1); - int span = Utilities.getTabbedTextWidth(txt, fm, (int) x, te, p0); + int span = Utilities.getTabbedTextWidth(txt, fontMetrics, (int) x, te, + p0); return span; } @@ -402,9 +538,8 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getAscent(GlyphView v) { - Font font = v.getFont(); - FontMetrics fm = v.getContainer().getFontMetrics(font); - return fm.getAscent(); + updateFontMetrics(v); + return fontMetrics.getAscent(); } /** @@ -420,9 +555,8 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getDescent(GlyphView v) { - Font font = v.getFont(); - FontMetrics fm = v.getContainer().getFontMetrics(font); - return fm.getDescent(); + updateFontMetrics(v); + return fontMetrics.getDescent(); } /** @@ -437,13 +571,12 @@ public class GlyphView extends View implements TabableView, Cloneable */ public int getBoundedPosition(GlyphView v, int p0, float x, float len) { + updateFontMetrics(v); TabExpander te = v.getTabExpander(); Segment txt = v.getText(p0, v.getEndOffset()); - Font font = v.getFont(); - FontMetrics fm = v.getContainer().getFontMetrics(font); - int pos = Utilities.getTabbedTextOffset(txt, fm, (int) x, + int pos = Utilities.getTabbedTextOffset(txt, fontMetrics, (int) x, (int) (x + len), te, p0, false); - return pos; + return pos + p0; } /** @@ -460,9 +593,33 @@ public class GlyphView extends View implements TabableView, Cloneable public int viewToModel(GlyphView v, float x, float y, Shape a, Bias[] biasRet) { - Rectangle b = a.getBounds(); - int pos = getBoundedPosition(v, v.getStartOffset(), b.x, x - b.x); - return pos; + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + int p0 = v.getStartOffset(); + int p1 = v.getEndOffset(); + TabExpander te = v.getTabExpander(); + Segment s = v.getText(p0, p1); + int offset = Utilities.getTabbedTextOffset(s, fontMetrics, r.x, (int) x, + te, p0); + int ret = p0 + offset; + if (ret == p1) + ret--; + biasRet[0] = Position.Bias.Forward; + return ret; + } + + private void updateFontMetrics(GlyphView v) + { + Font font = v.getFont(); + if (fontMetrics == null || ! font.equals(fontMetrics.getFont())) + { + Container c = v.getContainer(); + FontMetrics fm; + if (c != null) + fm = c.getFontMetrics(font); + else + fm = Toolkit.getDefaultToolkit().getFontMetrics(font); + fontMetrics = fm; + } } } @@ -474,12 +631,22 @@ public class GlyphView extends View implements TabableView, Cloneable /** * The start offset within the document for this view. */ - private int startOffset; + private int offset; /** * The end offset within the document for this view. */ - private int endOffset; + private int length; + + /** + * The x location against which the tab expansion is done. + */ + private float tabX; + + /** + * The tab expander that is used in this view. + */ + private TabExpander tabExpander; /** * Creates a new <code>GlyphView</code> for the given <code>Element</code>. @@ -489,8 +656,8 @@ public class GlyphView extends View implements TabableView, Cloneable public GlyphView(Element element) { super(element); - startOffset = -1; - endOffset = -1; + offset = 0; + length = 0; } /** @@ -524,7 +691,21 @@ public class GlyphView extends View implements TabableView, Cloneable protected void checkPainter() { if (glyphPainter == null) - glyphPainter = new DefaultGlyphPainter(); + { + if ("true".equals( + SystemProperties.getProperty("gnu.javax.swing.noGraphics2D"))) + { + glyphPainter = new DefaultGlyphPainter(); + } + else + { + Segment s = getText(getStartOffset(), getEndOffset()); + glyphPainter = new J2DGlyphPainter(s.toString(), getFont(), + new FontRenderContext(null, + false, + false)); + } + } } /** @@ -536,9 +717,80 @@ 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(); + + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + Container c = getContainer(); + + Color fg = getForeground(); + JTextComponent tc = null; + if (c instanceof JTextComponent) + { + tc = (JTextComponent) c; + if (! tc.isEnabled()) + fg = tc.getDisabledTextColor(); + } + Color bg = getBackground(); + if (bg != null) + { + g.setColor(bg); + System.err.println("fill background: " + bg); + g.fillRect(r.x, r.y, r.width, r.height); + } + + + // Paint layered highlights if there are any. + if (tc != null) + { + Highlighter h = tc.getHighlighter(); + if (h instanceof LayeredHighlighter) + { + LayeredHighlighter lh = (LayeredHighlighter) h; + lh.paintLayeredHighlights(g, p0, p1, a, tc, this); + } + } + + g.setColor(fg); + glyphPainter.paint(this, g, a, p0, p1); + boolean underline = isUnderline(); + boolean striked = isStrikeThrough(); + if (underline || striked) + { + View parent = getParent(); + // X coordinate. + if (parent != null && parent.getEndOffset() == p1) + { + // Strip whitespace. + Segment s = getText(p0, p1); + while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1])) + { + p1--; + s.count--; + } + } + int x0 = r.x; + int p = getStartOffset(); + TabExpander tabEx = getTabExpander(); + if (p != p0) + x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0); + int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0); + // Y coordinate. + int y = r.y + r.height - (int) glyphPainter.getDescent(this); + if (underline) + { + int yTmp = y; + yTmp += 1; + g.drawLine(x0, yTmp, x1, yTmp); + } + if (striked) + { + int yTmp = y; + yTmp -= (int) glyphPainter.getAscent(this); + g.drawLine(x0, yTmp, x1, yTmp); + } + } } @@ -555,19 +807,24 @@ public class GlyphView extends View implements TabableView, Cloneable float span = 0; checkPainter(); GlyphPainter painter = getGlyphPainter(); - if (axis == X_AXIS) + switch (axis) { - Element el = getElement(); + case X_AXIS: TabExpander tabEx = null; View parent = getParent(); if (parent instanceof TabExpander) tabEx = (TabExpander) parent; span = painter.getSpan(this, getStartOffset(), getEndOffset(), tabEx, 0.F); + break; + case Y_AXIS: + span = painter.getHeight(this); + if (isSuperscript()) + span += span / 3; + break; + default: + throw new IllegalArgumentException("Illegal axis"); } - else - span = painter.getHeight(this); - return span; } @@ -623,13 +880,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public TabExpander getTabExpander() { - TabExpander te = null; - View parent = getParent(); - - if (parent instanceof TabExpander) - te = (TabExpander) parent; - - return te; + return tabExpander; } /** @@ -642,9 +893,17 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getTabbedSpan(float x, TabExpander te) { - Element el = getElement(); - return getGlyphPainter().getSpan(this, el.getStartOffset(), - el.getEndOffset(), te, x); + checkPainter(); + TabExpander old = tabExpander; + tabExpander = te; + if (tabExpander != old) + { + // Changing the tab expander will lead to a relayout in the X_AXIS. + preferenceChanged(null, true, false); + } + tabX = x; + return getGlyphPainter().getSpan(this, getStartOffset(), + getEndOffset(), tabExpander, x); } /** @@ -658,23 +917,8 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getPartialSpan(int p0, int p1) { - Element el = getElement(); - Document doc = el.getDocument(); - Segment seg = new Segment(); - try - { - doc.getText(p0, p1 - p0, seg); - } - catch (BadLocationException ex) - { - AssertionError ae; - ae = new AssertionError("BadLocationException must not be thrown " - + "here"); - ae.initCause(ex); - throw ae; - } - FontMetrics fm = null; // Fetch font metrics somewhere. - return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0); + checkPainter(); + return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX); } /** @@ -686,10 +930,11 @@ public class GlyphView extends View implements TabableView, Cloneable */ public int getStartOffset() { - int start = startOffset; - if (start < 0) - start = super.getStartOffset(); - return start; + Element el = getElement(); + int offs = el.getStartOffset(); + if (length > 0) + offs += offset; + return offs; } /** @@ -701,12 +946,17 @@ public class GlyphView extends View implements TabableView, Cloneable */ public int getEndOffset() { - int end = endOffset; - if (end < 0) - end = super.getEndOffset(); - return end; + Element el = getElement(); + int offs; + if (length > 0) + offs = el.getStartOffset() + offset + length; + else + offs = el.getEndOffset(); + return offs; } + private Segment cached = new Segment(); + /** * Returns the text segment that this view is responsible for. * @@ -717,10 +967,9 @@ public class GlyphView extends View implements TabableView, Cloneable */ public Segment getText(int p0, int p1) { - Segment txt = new Segment(); try { - getDocument().getText(p0, p1 - p0, txt); + getDocument().getText(p0, p1 - p0, cached); } catch (BadLocationException ex) { @@ -731,7 +980,7 @@ public class GlyphView extends View implements TabableView, Cloneable throw ae; } - return txt; + return cached; } /** @@ -743,16 +992,19 @@ public class GlyphView extends View implements TabableView, Cloneable */ public Font getFont() { - Element el = getElement(); - AttributeSet atts = el.getAttributes(); - String family = StyleConstants.getFontFamily(atts); - int size = StyleConstants.getFontSize(atts); - int style = Font.PLAIN; - if (StyleConstants.isBold(atts)) - style |= Font.BOLD; - if (StyleConstants.isItalic(atts)) - style |= Font.ITALIC; - Font font = new Font(family, style, size); + Document doc = getDocument(); + Font font = null; + if (doc instanceof StyledDocument) + { + StyledDocument styledDoc = (StyledDocument) doc; + font = styledDoc.getFont(getAttributes()); + } + else + { + Container c = getContainer(); + if (c != null) + font = c.getFont(); + } return font; } @@ -885,33 +1137,21 @@ public class GlyphView extends View implements TabableView, Cloneable */ public View breakView(int axis, int p0, float pos, float len) { - if (axis == Y_AXIS) - return this; - - checkPainter(); - GlyphPainter painter = getGlyphPainter(); - - // Try to find a suitable line break. - BreakIterator lineBreaker = BreakIterator.getLineInstance(); - Segment txt = new Segment(); - try - { - int start = getStartOffset(); - int length = getEndOffset() - start; - getDocument().getText(start, length, txt); - } - catch (BadLocationException ex) + View brokenView = this; + if (axis == X_AXIS) { - AssertionError err = new AssertionError("BadLocationException must not " - + "be thrown here."); - err.initCause(ex); - throw err; + checkPainter(); + int end = glyphPainter.getBoundedPosition(this, p0, pos, len); + int breakLoc = getBreakLocation(p0, end); + if (breakLoc != -1) + end = breakLoc; + if (p0 != getStartOffset() || end != getEndOffset()) + { + brokenView = createFragment(p0, end); + if (brokenView instanceof GlyphView) + ((GlyphView) brokenView).tabX = pos; + } } - int breakLocation = - Utilities.getBreakLocation(txt, getContainer().getFontMetrics(getFont()), - (int) pos, (int) (pos + len), - getTabExpander(), p0); - View brokenView = createFragment(p0, breakLocation); return brokenView; } @@ -937,28 +1177,36 @@ public class GlyphView extends View implements TabableView, Cloneable weight = super.getBreakWeight(axis, pos, len); else { - // FIXME: Commented out because the Utilities.getBreakLocation method - // is still buggy. The GoodBreakWeight is a reasonable workaround for - // now. -// int startOffset = getStartOffset(); -// int endOffset = getEndOffset() - 1; -// Segment s = getText(startOffset, endOffset); -// Container c = getContainer(); -// FontMetrics fm = c.getFontMetrics(c.getFont()); -// int x0 = (int) pos; -// int x = (int) (pos + len); -// int breakLoc = Utilities.getBreakLocation(s, fm, x0, x, -// getTabExpander(), -// startOffset); -// if (breakLoc == startOffset || breakLoc == endOffset) -// weight = GoodBreakWeight; -// else -// weight = ExcellentBreakWeight; - weight = GoodBreakWeight; + checkPainter(); + int start = getStartOffset(); + int end = glyphPainter.getBoundedPosition(this, start, pos, len); + if (end == 0) + weight = BadBreakWeight; + else + { + if (getBreakLocation(start, end) != -1) + weight = ExcellentBreakWeight; + else + weight = GoodBreakWeight; + } } return weight; } + private int getBreakLocation(int start, int end) + { + int loc = -1; + Segment s = getText(start, end); + for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous()) + { + if (Character.isWhitespace(c)) + { + loc = s.getIndex() - s.getBeginIndex() + 1 + start; + } + } + return loc; + } + /** * Receives notification that some text attributes have changed within the * text fragment that this view is responsible for. This calls @@ -971,7 +1219,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - preferenceChanged(this, true, true); + preferenceChanged(null, true, true); } /** @@ -986,7 +1234,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - preferenceChanged(this, true, false); + preferenceChanged(null, true, false); } /** @@ -1001,7 +1249,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - preferenceChanged(this, true, false); + preferenceChanged(null, true, false); } /** @@ -1015,11 +1263,12 @@ public class GlyphView extends View implements TabableView, Cloneable */ public View createFragment(int p0, int p1) { + checkPainter(); + Element el = getElement(); GlyphView fragment = (GlyphView) clone(); - if (p0 != getStartOffset()) - fragment.startOffset = p0; - if (p1 != getEndOffset()) - fragment.endOffset = p1; + fragment.offset = p0 - el.getStartOffset(); + fragment.length = p1 - p0; + fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1); return fragment; } @@ -1031,14 +1280,21 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getAlignment(int axis) { + checkPainter(); float align; if (axis == Y_AXIS) { - checkPainter(); GlyphPainter painter = getGlyphPainter(); float height = painter.getHeight(this); float descent = painter.getDescent(this); - align = (height - descent) / height; + float ascent = painter.getAscent(this); + if (isSuperscript()) + align = 1.0F; + else if (isSubscript()) + align = height > 0 ? (height - (descent + (ascent / 2))) / height + : 0; + else + align = height > 0 ? (height - descent) / height : 0; } else align = super.getAlignment(axis); diff --git a/libjava/classpath/javax/swing/text/InternationalFormatter.java b/libjava/classpath/javax/swing/text/InternationalFormatter.java index 8db435c18f3..d6f2359e6f7 100644 --- a/libjava/classpath/javax/swing/text/InternationalFormatter.java +++ b/libjava/classpath/javax/swing/text/InternationalFormatter.java @@ -285,7 +285,7 @@ public class InternationalFormatter if (minimum != null && minimum.compareTo(o) > 0) throw new ParseException("The value may not be less than the" + " specified minimum", 0); - if (maximum != null && minimum.compareTo(o) < 0) + if (maximum != null && maximum.compareTo(o) < 0) throw new ParseException("The value may not be greater than the" + " specified maximum", 0); return o; diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java index 6da84bfe7d8..68ba1f4284c 100644 --- a/libjava/classpath/javax/swing/text/JTextComponent.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/text/LabelView.java b/libjava/classpath/javax/swing/text/LabelView.java index 03279c4b2b5..7cfeae86229 100644 --- a/libjava/classpath/javax/swing/text/LabelView.java +++ b/libjava/classpath/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; } /** @@ -107,28 +114,25 @@ public class LabelView extends GlyphView */ protected void setPropertiesFromAttributes() { - Element el = getElement(); - AttributeSet atts = el.getAttributes(); - // We cannot use StyleConstants.getBackground() here, because that returns - // BLACK as default (when background == null). What we need is the - // background setting of the text component instead, which is what we get - // when background == null anyway. - background = (Color) atts.getAttribute(StyleConstants.Background); - foreground = StyleConstants.getForeground(atts); - strikeThrough = StyleConstants.isStrikeThrough(atts); - subscript = StyleConstants.isSubscript(atts); - superscript = StyleConstants.isSuperscript(atts); - underline = StyleConstants.isUnderline(atts); - - // Determine the font. - String family = StyleConstants.getFontFamily(atts); - int size = StyleConstants.getFontSize(atts); - int style = Font.PLAIN; - if (StyleConstants.isBold(atts)) - style |= Font.BOLD; - if (StyleConstants.isItalic(atts)) - style |= Font.ITALIC; - font = new Font(family, style, size); + AttributeSet atts = getAttributes(); + setStrikeThrough(StyleConstants.isStrikeThrough(atts)); + setSubscript(StyleConstants.isSubscript(atts)); + setSuperscript(StyleConstants.isSuperscript(atts)); + setUnderline(StyleConstants.isUnderline(atts)); + + // Determine the font and colors. + Document d = getDocument(); + if (d instanceof StyledDocument) + { + StyledDocument doc = (StyledDocument) d; + font = doc.getFont(atts); + if (atts.isDefined(StyleConstants.Background)) + background = doc.getBackground(atts); + else + background = null; + foreground = doc.getForeground(atts); + } + valid = true; } /** @@ -142,7 +146,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 +157,8 @@ public class LabelView extends GlyphView */ public Color getBackground() { + if (! valid) + setPropertiesFromAttributes(); return background; } @@ -175,6 +182,8 @@ public class LabelView extends GlyphView */ public Color getForeground() { + if (! valid) + setPropertiesFromAttributes(); return foreground; } @@ -185,6 +194,8 @@ public class LabelView extends GlyphView */ public Font getFont() { + if (! valid) + setPropertiesFromAttributes(); return font; } @@ -197,7 +208,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 +229,8 @@ public class LabelView extends GlyphView */ public boolean isUnderline() { + if (! valid) + setPropertiesFromAttributes(); return underline; } @@ -232,6 +254,8 @@ public class LabelView extends GlyphView */ public boolean isSubscript() { + if (! valid) + setPropertiesFromAttributes(); return subscript; } @@ -255,6 +279,8 @@ public class LabelView extends GlyphView */ public boolean isSuperscript() { + if (! valid) + setPropertiesFromAttributes(); return superscript; } @@ -278,6 +304,8 @@ public class LabelView extends GlyphView */ public boolean isStrikeThrough() { + if (! valid) + setPropertiesFromAttributes(); return strikeThrough; } diff --git a/libjava/classpath/javax/swing/text/MaskFormatter.java b/libjava/classpath/javax/swing/text/MaskFormatter.java index d12b9ea29d7..581cceb617d 100644 --- a/libjava/classpath/javax/swing/text/MaskFormatter.java +++ b/libjava/classpath/javax/swing/text/MaskFormatter.java @@ -110,9 +110,7 @@ public class MaskFormatter extends DefaultFormatter */ public MaskFormatter (String mask) throws java.text.ParseException { - // Override super's default behaviour, in MaskFormatter the default - // is not to allow invalid values - setAllowsInvalid(false); + this(); setMask (mask); } @@ -307,60 +305,124 @@ public class MaskFormatter extends DefaultFormatter */ public Object stringToValue (String value) throws ParseException { - int vLength = value.length(); - - // For value to be a valid it must be the same length as the mask - // note this doesn't take into account symbols that occupy more than - // one character, this is something we may possibly need to fix. - if (maskLength != vLength) - throw new ParseException ("stringToValue passed invalid value", vLength); - - // Check if the value is valid according to the mask and valid/invalid - // sets. - try - { - convertValue(value, false); - } - catch (ParseException pe) - { - throw new ParseException("stringToValue passed invalid value", - pe.getErrorOffset()); - } - - if (!getValueContainsLiteralCharacters()) - value = stripLiterals(value); - return super.stringToValue(value); + return super.stringToValue(convertStringToValue(value)); } - /** - * Strips the literal characters from the given String. - * @param value the String to strip - * @return the stripped String - */ - String stripLiterals(String value) + private String convertStringToValue(String value) + throws ParseException { StringBuffer result = new StringBuffer(); - for (int i = 0; i < value.length(); i++) + char valueChar; + boolean isPlaceHolder; + + int length = mask.length(); + for (int i = 0, j = 0; j < length; j++) { - // Only append the characters that don't correspond to literal - // characters in the mask. - switch (mask.charAt(i)) + char maskChar = mask.charAt(j); + + if (i < value.length()) + { + isPlaceHolder = false; + valueChar = value.charAt(i); + if (maskChar != ESCAPE_CHAR && maskChar != valueChar) + { + if (invalidChars != null + && invalidChars.indexOf(valueChar) != -1) + throw new ParseException("Invalid character: " + valueChar, i); + if (validChars != null + && validChars.indexOf(valueChar) == -1) + throw new ParseException("Invalid character: " + valueChar, i); + } + } + else if (placeHolder != null && i < placeHolder.length()) + { + isPlaceHolder = true; + valueChar = placeHolder.charAt(i); + } + else + { + isPlaceHolder = true; + valueChar = placeHolderChar; + } + + // This switch block on the mask character checks that the character + // within <code>value</code> at that point is valid according to the + // mask and also converts to upper/lowercase as needed. + switch (maskChar) { case NUM_CHAR: + if (! Character.isDigit(valueChar)) + throw new ParseException("Number expected: " + valueChar, i); + result.append(valueChar); + i++; + break; case UPPERCASE_CHAR: + if (! Character.isLetter(valueChar)) + throw new ParseException("Letter expected", i); + result.append(Character.toUpperCase(valueChar)); + i++; + break; case LOWERCASE_CHAR: + if (! Character.isLetter(valueChar)) + throw new ParseException("Letter expected", i); + result.append(Character.toLowerCase(valueChar)); + i++; + break; case ALPHANUM_CHAR: + if (! Character.isLetterOrDigit(valueChar)) + throw new ParseException("Letter or number expected", i); + result.append(valueChar); + i++; + break; case LETTER_CHAR: + if (! Character.isLetter(valueChar)) + throw new ParseException("Letter expected", i); + result.append(valueChar); + i++; + break; case HEX_CHAR: + if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) + throw new ParseException("Hexadecimal character expected", i); + result.append(valueChar); + i++; + break; case ANYTHING_CHAR: - result.append(value.charAt(i)); + result.append(valueChar); + i++; + break; + case ESCAPE_CHAR: + // Escape character, check the next character to make sure that + // the literals match + j++; + if (j < length) + { + maskChar = mask.charAt(j); + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + { + result.append(maskChar); + } + i++; + } + else if (! isPlaceHolder) + throw new ParseException("Bad match at trailing escape: ", i); break; default: + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + { + result.append(maskChar); + } + i++; } } return result.toString(); } - + /** * Returns a String representation of the Object value based on the mask. * @@ -368,21 +430,10 @@ public class MaskFormatter extends DefaultFormatter * @throws ParseException if value is invalid for this mask and valid/invalid * character sets */ - public String valueToString (Object value) throws ParseException + public String valueToString(Object value) throws ParseException { - String result = super.valueToString(value); - int rLength = result.length(); - - // If value is longer than the mask, truncate it. Note we may need to - // account for symbols that are more than one character long. - if (rLength > maskLength) - result = result.substring(0, maskLength); - - // Verify the validity and convert to upper/lowercase as needed. - result = convertValue(result, true); - if (rLength < maskLength) - return pad(result, rLength); - return result; + String string = value != null ? value.toString() : ""; + return convertValueToString(string); } /** @@ -390,194 +441,116 @@ public class MaskFormatter extends DefaultFormatter * sure that it is valid. If <code>convert</code> is true, it also * converts letters to upper/lowercase as required by the mask. * @param value the String to convert - * @param convert true if we should convert letters to upper/lowercase * @return the converted String * @throws ParseException if the given String isn't valid for the mask */ - String convertValue(String value, boolean convert) throws ParseException + private String convertValueToString(String value) + throws ParseException { - StringBuffer result = new StringBuffer(value); - char markChar; - char resultChar; - boolean literal; - - // this boolean is specifically to avoid calling the isCharValid method - // when neither invalidChars or validChars has been set - boolean checkCharSets = (invalidChars != null || validChars != null); + StringBuffer result = new StringBuffer(); + char valueChar; + boolean isPlaceHolder; - for (int i = 0, j = 0; i < value.length(); i++, j++) + int length = mask.length(); + for (int i = 0, j = 0; j < length; j++) { - literal = false; - resultChar = result.charAt(i); + char maskChar = mask.charAt(j); + if (i < value.length()) + { + isPlaceHolder = false; + valueChar = value.charAt(i); + if (maskChar != ESCAPE_CHAR && valueChar != maskChar) + { + if (invalidChars != null + && invalidChars.indexOf(valueChar) != -1) + throw new ParseException("Invalid character: " + valueChar, + i); + if (validChars != null && validChars.indexOf(valueChar) == -1) + throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar, + i); + } + } + else if (placeHolder != null && i < placeHolder.length()) + { + isPlaceHolder = true; + valueChar = placeHolder.charAt(i); + } + else + { + isPlaceHolder = true; + valueChar = placeHolderChar; + } + // This switch block on the mask character checks that the character // within <code>value</code> at that point is valid according to the // mask and also converts to upper/lowercase as needed. - switch (mask.charAt(j)) + switch (maskChar) { case NUM_CHAR: - if (!Character.isDigit(resultChar)) - throw new ParseException("Number expected", i); + if ( ! isPlaceHolder && ! Character.isDigit(valueChar)) + throw new ParseException("Number expected: " + valueChar, i); + result.append(valueChar); + i++; break; case UPPERCASE_CHAR: - if (!Character.isLetter(resultChar)) + if (! Character.isLetter(valueChar)) throw new ParseException("Letter expected", i); - if (convert) - result.setCharAt(i, Character.toUpperCase(resultChar)); + result.append(Character.toUpperCase(valueChar)); + i++; break; case LOWERCASE_CHAR: - if (!Character.isLetter(resultChar)) + if (! Character.isLetter(valueChar)) throw new ParseException("Letter expected", i); - if (convert) - result.setCharAt(i, Character.toLowerCase(resultChar)); + result.append(Character.toLowerCase(valueChar)); + i++; break; case ALPHANUM_CHAR: - if (!Character.isLetterOrDigit(resultChar)) + if (! Character.isLetterOrDigit(valueChar)) throw new ParseException("Letter or number expected", i); + result.append(valueChar); + i++; break; case LETTER_CHAR: - if (!Character.isLetter(resultChar)) + if (! Character.isLetter(valueChar)) throw new ParseException("Letter expected", i); + result.append(valueChar); + i++; break; case HEX_CHAR: - if (hexString.indexOf(resultChar) == -1) + if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) throw new ParseException("Hexadecimal character expected", i); + result.append(valueChar); + i++; break; case ANYTHING_CHAR: + result.append(valueChar); + i++; break; case ESCAPE_CHAR: // Escape character, check the next character to make sure that // the literals match j++; - literal = true; - if (resultChar != mask.charAt(j)) - throw new ParseException ("Invalid character: "+resultChar, i); + if (j < length) + { + maskChar = mask.charAt(j); + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + i++; + result.append(maskChar); + } break; default: - literal = true; - if (!getValueContainsLiteralCharacters() && convert) - throw new ParseException ("Invalid character: "+resultChar, i); - else if (resultChar != mask.charAt(j)) - throw new ParseException ("Invalid character: "+resultChar, i); + if (! isPlaceHolder && getValueContainsLiteralCharacters() + && valueChar != maskChar) + throw new ParseException ("Invalid character: "+ valueChar, i); + if (getValueContainsLiteralCharacters()) + i++; + result.append(maskChar); } - // If necessary, check if the character is valid. - if (!literal && checkCharSets && !isCharValid(resultChar)) - throw new ParseException("invalid character: "+resultChar, i); - - } - return result.toString(); - } - - /** - * Convenience method used by many other methods to check if a character is - * valid according to the mask, the validChars, and the invalidChars. To - * be valid a character must: - * 1. be allowed by the mask - * 2. be present in any non-null validChars String - * 3. not be present in any non-null invalidChars String - * @param testChar the character to test - * @return true if the character is valid - */ - boolean isCharValid(char testChar) - { - char lower = Character.toLowerCase(testChar); - char upper = Character.toUpperCase(testChar); - // If validChars isn't null, the character must appear in it. - if (validChars != null) - if (validChars.indexOf(lower) == -1 && validChars.indexOf(upper) == -1) - return false; - // If invalidChars isn't null, the character must not appear in it. - if (invalidChars != null) - if (invalidChars.indexOf(lower) != -1 - || invalidChars.indexOf(upper) != -1) - return false; - return true; - } - - /** - * Pads the value with literals, the placeholder String and/or placeholder - * character as appropriate. - * @param value the value to pad - * @param currLength the current length of the value - * @return the padded String - */ - String pad (String value, int currLength) - { - StringBuffer result = new StringBuffer(value); - int index = currLength; - while (result.length() < maskLength) - { - // The character used to pad may be a literal, a character from the - // place holder string, or the place holder character. getPadCharAt - // will find the proper one for us. - result.append (getPadCharAt(index)); - index++; } return result.toString(); } - /** - * Returns the character with which to pad the value at the given index - * position. If the mask has a literal at this position, this is returned - * otherwise if the place holder string is initialized and is longer than - * <code>i</code> characters then the character at position <code>i</code> - * from this String is returned. Else, the place holder character is - * returned. - * @param i the index at which we want to pad the value - * @return the character with which we should pad the value - */ - char getPadCharAt(int i) - { - boolean escaped = false; - int target = i; - char maskChar; - int holderLength = placeHolder == null ? -1 : placeHolder.length(); - // We must iterate through the mask from the beginning, because the given - // index doesn't account for escaped characters. For example, with the - // mask "1A'A''A1" index 2 refers to the literalized A, not to the - // single quotation. - for (int n = 0; n < mask.length(); n++) - { - maskChar = mask.charAt(n); - if (maskChar == ESCAPE_CHAR && !escaped) - { - target++; - escaped = true; - } - else if (escaped == true) - { - // Check if target == n which means we've come to the character - // we want to return and since it is a literal (because escaped - // is true), we return it. - if (target == n) - return maskChar; - escaped = false; - } - if (target == n) - { - // We've come to the character we want to return. It wasn't - // escaped so if it isn't a literal we should return either - // the character from place holder string or the place holder - // character, depending on whether or not the place holder - // string is long enough. - switch (maskChar) - { - case NUM_CHAR: - case UPPERCASE_CHAR: - case LOWERCASE_CHAR: - case ALPHANUM_CHAR: - case LETTER_CHAR: - case HEX_CHAR: - case ANYTHING_CHAR: - if (holderLength > i) - return placeHolder.charAt(i); - else - return placeHolderChar; - default: - return maskChar; - } - } - } - // This shouldn't happen - throw new AssertionError("MaskFormatter.getMaskCharAt failed"); - } } diff --git a/libjava/classpath/javax/swing/text/MutableAttributeSet.java b/libjava/classpath/javax/swing/text/MutableAttributeSet.java index 3728b9ce126..5dd2406a3a9 100644 --- a/libjava/classpath/javax/swing/text/MutableAttributeSet.java +++ b/libjava/classpath/javax/swing/text/MutableAttributeSet.java @@ -90,7 +90,7 @@ public interface MutableAttributeSet extends AttributeSet * @throws NullPointerException if <code>names</code> is <code>null</code> * or contains any <code>null</code> values. */ - void removeAttributes(Enumeration names); + void removeAttributes(Enumeration<?> names); /** * Removes attributes from this set if they are found in the diff --git a/libjava/classpath/javax/swing/text/ParagraphView.java b/libjava/classpath/javax/swing/text/ParagraphView.java index c4857863d35..fb4ac65d835 100644 --- a/libjava/classpath/javax/swing/text/ParagraphView.java +++ b/libjava/classpath/javax/swing/text/ParagraphView.java @@ -38,8 +38,12 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; import java.awt.Shape; +import javax.swing.SizeRequirements; import javax.swing.event.DocumentEvent; /** @@ -64,30 +68,45 @@ public class ParagraphView extends FlowView implements TabExpander super(el, X_AXIS); } + /** + * Overridden to adjust when we are the first line, and firstLineIndent + * is not 0. + */ + public short getLeftInset() + { + short leftInset = super.getLeftInset(); + View parent = getParent(); + if (parent != null) + { + if (parent.getView(0) == this) + leftInset += firstLineIndent; + } + return leftInset; + } + public float getAlignment(int axis) { float align; if (axis == X_AXIS) - align = 0.0F; // TODO: Implement according to justification + switch (justification) + { + case StyleConstants.ALIGN_RIGHT: + align = 1.0F; + break; + case StyleConstants.ALIGN_CENTER: + case StyleConstants.ALIGN_JUSTIFIED: + align = 0.5F; + break; + case StyleConstants.ALIGN_LEFT: + default: + align = 0.0F; + } else align = super.getAlignment(axis); return align; } /** - * 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. */ @@ -107,10 +126,63 @@ public class ParagraphView extends FlowView implements TabExpander return index; } + + /** + * Overridden to perform a baseline layout. The normal BoxView layout + * isn't completely suitable for rows. + */ + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + baselineLayout(targetSpan, axis, offsets, spans); + } + + /** + * Overridden to perform a baseline layout. The normal BoxView layout + * isn't completely suitable for rows. + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements r) + { + return baselineRequirements(axis, r); + } + protected void loadChildren(ViewFactory vf) { // Do nothing here. The children are added while layouting. } + + /** + * Overridden to determine the minimum start offset of the row's children. + */ + public int getStartOffset() + { + // Determine minimum start offset of the children. + int offset = Integer.MAX_VALUE; + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View v = getView(i); + offset = Math.min(offset, v.getStartOffset()); + } + return offset; + } + + /** + * Overridden to determine the maximum end offset of the row's children. + */ + public int getEndOffset() + { + // Determine minimum start offset of the children. + int offset = 0; + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View v = getView(i); + offset = Math.max(offset, v.getEndOffset()); + } + return offset; + } } /** @@ -192,11 +264,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/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java index 48fe37ce880..e048d5f7168 100644 --- a/libjava/classpath/javax/swing/text/PlainView.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/text/Position.java b/libjava/classpath/javax/swing/text/Position.java index bb1449e187a..d02eb834dd9 100644 --- a/libjava/classpath/javax/swing/text/Position.java +++ b/libjava/classpath/javax/swing/text/Position.java @@ -42,8 +42,8 @@ public interface Position { static final class Bias { - public static final Bias Backward = new Bias("backward"); - public static final Bias Forward = new Bias("forward"); + public static final Bias Backward = new Bias("Backward"); + public static final Bias Forward = new Bias("Forward"); private String name; diff --git a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java index 8684ef87d34..701fa8a7c90 100644 --- a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java +++ b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java @@ -123,9 +123,17 @@ public class SimpleAttributeSet */ public Object clone() { - SimpleAttributeSet s = new SimpleAttributeSet(); - s.tab = (Hashtable) tab.clone(); - return s; + SimpleAttributeSet attr = null; + try + { + attr = (SimpleAttributeSet) super.clone(); + attr.tab = (Hashtable) tab.clone(); + } + catch (CloneNotSupportedException ex) + { + assert false; + } + return attr; } /** @@ -253,7 +261,7 @@ public class SimpleAttributeSet * * @return An enumeration of the attribute names. */ - public Enumeration getAttributeNames() + public Enumeration<?> getAttributeNames() { return tab.keys(); } @@ -367,7 +375,7 @@ public class SimpleAttributeSet * @throws NullPointerException if <code>names</code> is <code>null</code> * or contains any <code>null</code> values. */ - public void removeAttributes(Enumeration names) + public void removeAttributes(Enumeration<?> names) { while (names.hasMoreElements()) { diff --git a/libjava/classpath/javax/swing/text/StringContent.java b/libjava/classpath/javax/swing/text/StringContent.java index 8014dc3bce6..4a3f9d75222 100644 --- a/libjava/classpath/javax/swing/text/StringContent.java +++ b/libjava/classpath/javax/swing/text/StringContent.java @@ -39,6 +39,9 @@ exception statement from your version. */ package javax.swing.text; import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.Vector; @@ -57,6 +60,76 @@ import javax.swing.undo.UndoableEdit; public final class StringContent implements AbstractDocument.Content, Serializable { + /** + * Stores a reference to a mark that can be resetted to the original value + * after a mark has been moved. This is used for undoing actions. + */ + private class UndoPosRef + { + /** + * The mark that might need to be reset. + */ + private Mark mark; + + /** + * The original offset to reset the mark to. + */ + private int undoOffset; + + /** + * Creates a new UndoPosRef. + * + * @param m the mark + */ + UndoPosRef(Mark m) + { + mark = m; + undoOffset = mark.mark; + } + + /** + * Resets the position of the mark to the value that it had when + * creating this UndoPosRef. + */ + void reset() + { + mark.mark = undoOffset; + } + } + + /** + * Holds a mark into the buffer that is used by StickyPosition to find + * the actual offset of the position. This is pulled out of the + * GapContentPosition object so that the mark and position can be handled + * independently, and most important, so that the StickyPosition can + * be garbage collected while we still hold a reference to the Mark object. + */ + private class Mark + { + /** + * The actual mark into the buffer. + */ + int mark; + + + /** + * The number of GapContentPosition object that reference this mark. If + * it reaches zero, it get's deleted by + * {@link StringContent#garbageCollect()}. + */ + int refCount; + + /** + * Creates a new Mark object for the specified offset. + * + * @param offset the offset + */ + Mark(int offset) + { + mark = offset; + } + } + /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 4755994433709540381L; @@ -65,7 +138,12 @@ public final class StringContent private int count; - private Vector positions = new Vector(); + /** + * Holds the marks for the positions. + * + * This is package private to avoid accessor methods. + */ + Vector marks; private class InsertUndo extends AbstractUndoableEdit { @@ -75,6 +153,8 @@ public final class StringContent private String redoContent; + private Vector positions; + public InsertUndo(int start, int length) { super(); @@ -87,10 +167,10 @@ public final class StringContent super.undo(); try { - StringContent.this.checkLocation(this.start, this.length); - this.redoContent = new String(StringContent.this.content, this.start, - this.length); - StringContent.this.remove(this.start, this.length); + if (marks != null) + positions = getPositionsInRange(null, start, length); + redoContent = getString(start, length); + remove(start, length); } catch (BadLocationException b) { @@ -103,7 +183,13 @@ public final class StringContent super.redo(); try { - StringContent.this.insertString(this.start, this.redoContent); + insertString(start, redoContent); + redoContent = null; + if (positions != null) + { + updateUndoPositions(positions); + positions = null; + } } catch (BadLocationException b) { @@ -115,14 +201,19 @@ public final class StringContent private class RemoveUndo extends AbstractUndoableEdit { private int start; - + private int len; private String undoString; + Vector positions; + public RemoveUndo(int start, String str) { super(); this.start = start; + len = str.length(); this.undoString = str; + if (marks != null) + positions = getPositionsInRange(null, start, str.length()); } public void undo() @@ -131,6 +222,12 @@ public final class StringContent try { StringContent.this.insertString(this.start, this.undoString); + if (positions != null) + { + updateUndoPositions(positions); + positions = null; + } + undoString = null; } catch (BadLocationException bad) { @@ -143,8 +240,10 @@ public final class StringContent super.redo(); try { - int end = this.undoString.length(); - StringContent.this.remove(this.start, end); + undoString = getString(start, len); + if (marks != null) + positions = getPositionsInRange(null, start, len); + remove(this.start, len); } catch (BadLocationException bad) { @@ -155,17 +254,18 @@ public final class StringContent private class StickyPosition implements Position { - private int offset = -1; + Mark mark; public StickyPosition(int offset) { - this.offset = offset; - } + // Try to make space. + garbageCollect(); - // This is package-private to avoid an accessor method. - void setOffset(int offset) - { - this.offset = this.offset >= 0 ? offset : -1; + mark = new Mark(offset); + mark.refCount++; + marks.add(mark); + + new WeakReference(this, queueOfDeath); } /** @@ -173,11 +273,25 @@ public final class StringContent */ public int getOffset() { - return offset < 0 ? 0 : offset; + return mark.mark; } } /** + * Used in {@link #remove(int,int)}. + */ + private static final char[] EMPTY = new char[0]; + + /** + * Queues all references to GapContentPositions that are about to be + * GC'ed. This is used to remove the corresponding marks from the + * positionMarks array if the number of references to that mark reaches zero. + * + * This is package private to avoid accessor synthetic methods. + */ + ReferenceQueue queueOfDeath; + + /** * Creates a new instance containing the string "\n". This is equivalent * to calling {@link #StringContent(int)} with an <code>initialLength</code> * of 10. @@ -196,6 +310,7 @@ public final class StringContent public StringContent(int initialLength) { super(); + queueOfDeath = new ReferenceQueue(); if (initialLength < 1) initialLength = 1; this.content = new char[initialLength]; @@ -207,14 +322,13 @@ public final class StringContent int offset, int length) { - Vector refPos = new Vector(); - Iterator iter = this.positions.iterator(); + Vector refPos = v == null ? new Vector() : v; + Iterator iter = marks.iterator(); while(iter.hasNext()) { - Position p = (Position) iter.next(); - if ((offset <= p.getOffset()) - && (p.getOffset() <= (offset + length))) - refPos.add(p); + Mark m = (Mark) iter.next(); + if (offset <= m.mark && m.mark <= offset + length) + refPos.add(new UndoPosRef(m)); } return refPos; } @@ -231,10 +345,10 @@ public final class StringContent */ public Position createPosition(int offset) throws BadLocationException { - if (offset < this.count || offset > this.count) - checkLocation(offset, 0); + // Lazily create marks vector. + if (marks == null) + marks = new Vector(); StickyPosition sp = new StickyPosition(offset); - this.positions.add(sp); return sp; } @@ -246,7 +360,7 @@ public final class StringContent */ public int length() { - return this.count; + return count; } /** @@ -268,27 +382,23 @@ public final class StringContent if (str == null) throw new NullPointerException(); char[] insert = str.toCharArray(); - char[] temp = new char[this.content.length + insert.length]; - this.count += insert.length; - // Copy array and insert the string. - if (where > 0) - System.arraycopy(this.content, 0, temp, 0, where); - System.arraycopy(insert, 0, temp, where, insert.length); - System.arraycopy(this.content, where, temp, (where + insert.length), - (temp.length - where - insert.length)); - if (this.content.length < temp.length) - this.content = new char[temp.length]; - // Copy the result in the original char array. - System.arraycopy(temp, 0, this.content, 0, temp.length); + replace(where, 0, insert); + // Move all the positions. - Vector refPos = getPositionsInRange(this.positions, where, - temp.length - where); - Iterator iter = refPos.iterator(); - while (iter.hasNext()) + if (marks != null) { - StickyPosition p = (StickyPosition)iter.next(); - p.setOffset(p.getOffset() + str.length()); + Iterator iter = marks.iterator(); + int start = where; + if (start == 0) + start = 1; + while (iter.hasNext()) + { + Mark m = (Mark) iter.next(); + if (m.mark >= start) + m.mark += str.length(); + } } + InsertUndo iundo = new InsertUndo(where, insert.length); return iundo; } @@ -308,32 +418,51 @@ public final class StringContent public UndoableEdit remove(int where, int nitems) throws BadLocationException { checkLocation(where, nitems + 1); - char[] temp = new char[(this.content.length - nitems)]; - this.count = this.count - nitems; RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where, nitems)); - // Copy array. - System.arraycopy(this.content, 0, temp, 0, where); - System.arraycopy(this.content, where + nitems, temp, where, - this.content.length - where - nitems); - this.content = new char[temp.length]; - // Then copy the result in the original char array. - System.arraycopy(temp, 0, this.content, 0, this.content.length); + + replace(where, nitems, EMPTY); // Move all the positions. - Vector refPos = getPositionsInRange(this.positions, where, - this.content.length + nitems - where); - Iterator iter = refPos.iterator(); - while (iter.hasNext()) + if (marks != null) { - StickyPosition p = (StickyPosition)iter.next(); - int result = p.getOffset() - nitems; - p.setOffset(result); - if (result < 0) - this.positions.remove(p); + Iterator iter = marks.iterator(); + while (iter.hasNext()) + { + Mark m = (Mark) iter.next(); + if (m.mark >= where + nitems) + m.mark -= nitems; + else if (m.mark >= where) + m.mark = where; + } } return rundo; } - + + private void replace(int offs, int numRemove, char[] insert) + { + int insertLength = insert.length; + int delta = insertLength - numRemove; + int src = offs + numRemove; + int numMove = count - src; + int dest = src + delta; + if (count + delta >= content.length) + { + // Grow data array. + int newLength = Math.max(2 * content.length, count + delta); + char[] newContent = new char[newLength]; + System.arraycopy(content, 0, newContent, 0, offs); + System.arraycopy(insert, 0, newContent, offs, insertLength); + System.arraycopy(content, src, newContent, dest, numMove); + content = newContent; + } + else + { + System.arraycopy(content, src, content, dest, numMove); + System.arraycopy(insert, 0, content, offs, insertLength); + } + count += delta; + } + /** * Returns a new <code>String</code> containing the characters in the * specified range. @@ -348,6 +477,8 @@ public final class StringContent */ public String getString(int where, int len) throws BadLocationException { + // The RI throws a StringIndexOutOfBoundsException here, which + // smells like a bug. We throw a BadLocationException instead. checkLocation(where, len); return new String(this.content, where, len); } @@ -368,22 +499,28 @@ public final class StringContent public void getChars(int where, int len, Segment txt) throws BadLocationException { - checkLocation(where, len); - txt.array = this.content; + if (where + len > count) + throw new BadLocationException("Invalid location", where + len); + txt.array = content; txt.offset = where; txt.count = len; } /** - * @specnote This method is not very well specified and the positions vector - * is implementation specific. The undo positions are managed - * differently in this implementation, this method is only here - * for binary compatibility. + * Resets the positions in the specified vector to their original offset + * after a undo operation is performed. For example, after removing some + * content, the positions in the removed range will all be set to one + * offset. This method restores the positions to their original offsets + * after an undo. */ protected void updateUndoPositions(Vector positions) { - // We do nothing here. + for (Iterator i = positions.iterator(); i.hasNext();) + { + UndoPosRef pos = (UndoPosRef) i.next(); + pos.reset(); + } } /** @@ -405,6 +542,29 @@ public final class StringContent else if ((where + len) > this.count) throw new BadLocationException("Invalid range", this.count); } - + + /** + * Polls the queue of death for GapContentPositions, updates the + * corresponding reference count and removes the corresponding mark + * if the refcount reaches zero. + * + * This is package private to avoid accessor synthetic methods. + */ + void garbageCollect() + { + Reference ref = queueOfDeath.poll(); + while (ref != null) + { + if (ref != null) + { + StickyPosition pos = (StickyPosition) ref.get(); + Mark m = pos.mark; + m.refCount--; + if (m.refCount == 0) + marks.remove(m); + } + ref = queueOfDeath.poll(); + } + } } diff --git a/libjava/classpath/javax/swing/text/StyleConstants.java b/libjava/classpath/javax/swing/text/StyleConstants.java index c7906b8ad32..4e5005c6bb2 100644 --- a/libjava/classpath/javax/swing/text/StyleConstants.java +++ b/libjava/classpath/javax/swing/text/StyleConstants.java @@ -40,6 +40,7 @@ package javax.swing.text; import java.awt.Color; import java.awt.Component; +import java.util.ArrayList; import javax.swing.Icon; @@ -163,6 +164,12 @@ public class StyleConstants public static final Object ResolveAttribute = new StyleConstants("resolver"); + /** + * All StyleConstants keys. This is used in StyleContext to register + * all known keys as static attribute keys for serialization. + */ + static ArrayList keys; + String keyname; // Package-private to avoid accessor constructor for use by @@ -170,6 +177,9 @@ public class StyleConstants StyleConstants(String k) { keyname = k; + if (keys == null) + keys = new ArrayList(); + keys.add(this); } /** @@ -729,6 +739,7 @@ public class StyleConstants */ public static void setIcon(MutableAttributeSet a, Icon c) { + a.addAttribute(AbstractDocument.ElementNameAttribute, IconElementName); a.addAttribute(IconAttribute, c); } diff --git a/libjava/classpath/javax/swing/text/StyleContext.java b/libjava/classpath/javax/swing/text/StyleContext.java index 63df3df6a91..4dded0d0402 100644 --- a/libjava/classpath/javax/swing/text/StyleContext.java +++ b/libjava/classpath/javax/swing/text/StyleContext.java @@ -43,19 +43,25 @@ import java.awt.Font; import java.awt.FontMetrics; import java.awt.Toolkit; import java.io.IOException; +import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; public class StyleContext - implements Serializable, AbstractDocument.AttributeContext + implements Serializable, AbstractDocument.AttributeContext { /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 8042858831190784241L; @@ -66,11 +72,10 @@ public class StyleContext /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = -6690628971806226374L; - protected ChangeEvent changeEvent; + protected transient ChangeEvent changeEvent; protected EventListenerList listenerList; - AttributeSet attributes; - String name; + private transient AttributeSet attributes; public NamedStyle() { @@ -84,22 +89,26 @@ public class StyleContext public NamedStyle(String name, Style parent) { - this.name = name; - this.attributes = getEmptySet(); - this.changeEvent = new ChangeEvent(this); - this.listenerList = new EventListenerList(); - setResolveParent(parent); + attributes = getEmptySet(); + listenerList = new EventListenerList(); + if (name != null) + setName(name); + if (parent != null) + setResolveParent(parent); } public String getName() { + String name = null; + if (isDefined(StyleConstants.NameAttribute)) + name = getAttribute(StyleConstants.NameAttribute).toString(); return name; } public void setName(String n) { - name = n; - fireStateChanged(); + if (n != null) + addAttribute(StyleConstants.NameAttribute, n); } public void addChangeListener(ChangeListener l) @@ -112,7 +121,7 @@ public class StyleContext listenerList.remove(ChangeListener.class, l); } - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } @@ -127,6 +136,9 @@ public class StyleContext ChangeListener[] listeners = getChangeListeners(); for (int i = 0; i < listeners.length; ++i) { + // Lazily create event. + if (changeEvent == null) + changeEvent = new ChangeEvent(this); listeners[i].stateChanged(changeEvent); } } @@ -155,7 +167,10 @@ public class StyleContext public AttributeSet copyAttributes() { - return attributes.copyAttributes(); + // The RI returns a NamedStyle as copy, so do we. + NamedStyle copy = new NamedStyle(); + copy.attributes = attributes.copyAttributes(); + return copy; } public Object getAttribute(Object attrName) @@ -168,7 +183,7 @@ public class StyleContext return attributes.getAttributeCount(); } - public Enumeration getAttributeNames() + public Enumeration<?> getAttributeNames() { return attributes.getAttributeNames(); } @@ -195,7 +210,7 @@ public class StyleContext fireStateChanged(); } - public void removeAttributes(Enumeration names) + public void removeAttributes(Enumeration<?> names) { attributes = StyleContext.this.removeAttributes(attributes, names); fireStateChanged(); @@ -210,112 +225,125 @@ public class StyleContext public void setResolveParent(AttributeSet parent) { if (parent != null) - { - attributes = StyleContext.this.addAttribute - (attributes, ResolveAttribute, parent); - } - fireStateChanged(); + addAttribute(StyleConstants.ResolveAttribute, parent); + else + removeAttribute(StyleConstants.ResolveAttribute); } public String toString() { - return ("[NamedStyle: name=" + name + ", attrs=" + attributes.toString() + "]"); - } + return "NamedStyle:" + getName() + " " + attributes; + } + + private void writeObject(ObjectOutputStream s) + throws IOException + { + s.defaultWriteObject(); + writeAttributeSet(s, attributes); + } + + private void readObject(ObjectInputStream s) + throws ClassNotFoundException, IOException + { + s.defaultReadObject(); + attributes = SimpleAttributeSet.EMPTY; + readAttributeSet(s, this); + } } public class SmallAttributeSet implements AttributeSet { final Object [] attrs; + private AttributeSet resolveParent; public SmallAttributeSet(AttributeSet a) { - if (a == null) - attrs = new Object[0]; - else + int n = a.getAttributeCount(); + int i = 0; + attrs = new Object[n * 2]; + Enumeration e = a.getAttributeNames(); + while (e.hasMoreElements()) { - int n = a.getAttributeCount(); - int i = 0; - attrs = new Object[n * 2]; - Enumeration e = a.getAttributeNames(); - while (e.hasMoreElements()) - { - Object name = e.nextElement(); - attrs[i++] = name; - attrs[i++] = a.getAttribute(name); - } + Object name = e.nextElement(); + Object value = a.getAttribute(name); + if (name == ResolveAttribute) + resolveParent = (AttributeSet) value; + attrs[i++] = name; + attrs[i++] = value; } } public SmallAttributeSet(Object [] a) { - if (a == null) - attrs = new Object[0]; - else + attrs = a; + for (int i = 0; i < attrs.length; i += 2) { - attrs = new Object[a.length]; - System.arraycopy(a, 0, attrs, 0, a.length); + if (attrs[i] == ResolveAttribute) + resolveParent = (AttributeSet) attrs[i + 1]; } } public Object clone() { - return new SmallAttributeSet(this.attrs); + return this; } public boolean containsAttribute(Object name, Object value) { - for (int i = 0; i < attrs.length; i += 2) - { - if (attrs[i].equals(name) && - attrs[i+1].equals(value)) - return true; - } - return false; + return value.equals(getAttribute(name)); } public boolean containsAttributes(AttributeSet a) { + boolean res = true; Enumeration e = a.getAttributeNames(); - while (e.hasMoreElements()) + while (e.hasMoreElements() && res) { Object name = e.nextElement(); - Object val = a.getAttribute(name); - if (!containsAttribute(name, val)) - return false; + res = a.getAttribute(name).equals(getAttribute(name)); } - return true; + return res; } public AttributeSet copyAttributes() { - return (AttributeSet) clone(); + return this; } public boolean equals(Object obj) { - return - (obj instanceof AttributeSet) - && this.isEqual((AttributeSet)obj); + boolean eq = false; + if (obj instanceof AttributeSet) + { + AttributeSet atts = (AttributeSet) obj; + eq = getAttributeCount() == atts.getAttributeCount() + && containsAttributes(atts); + } + return eq; } public Object getAttribute(Object key) { - for (int i = 0; i < attrs.length; i += 2) + Object att = null; + if (key == StyleConstants.ResolveAttribute) + att = resolveParent; + + for (int i = 0; i < attrs.length && att == null; i += 2) { if (attrs[i].equals(key)) - return attrs[i+1]; + att = attrs[i + 1]; } - + // Check the resolve parent, unless we're looking for the - // ResolveAttribute, which would cause an infinite loop - if (!(key.equals(ResolveAttribute))) + // ResolveAttribute, which must not be looked up + if (att == null) { - Object p = getResolveParent(); - if (p != null && p instanceof AttributeSet) - return (((AttributeSet)p).getAttribute(key)); + AttributeSet parent = getResolveParent(); + if (parent != null) + att = parent.getAttribute(key); } - return null; + return att; } public int getAttributeCount() @@ -323,7 +351,7 @@ public class StyleContext return attrs.length / 2; } - public Enumeration getAttributeNames() + public Enumeration<?> getAttributeNames() { return new Enumeration() { @@ -342,7 +370,7 @@ public class StyleContext public AttributeSet getResolveParent() { - return (AttributeSet) getAttribute(ResolveAttribute); + return resolveParent; } public int hashCode() @@ -362,68 +390,96 @@ public class StyleContext public boolean isEqual(AttributeSet attr) { - return getAttributeCount() == attr.getAttributeCount() + boolean eq; + // If the other one is also a SmallAttributeSet, it is only considered + // equal if it's the same instance. + if (attr instanceof SmallAttributeSet) + eq = attr == this; + else + eq = getAttributeCount() == attr.getAttributeCount() && this.containsAttributes(attr); + return eq; } public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("[StyleContext.SmallattributeSet:"); - for (int i = 0; i < attrs.length - 1; ++i) + StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (int i = 0; i < attrs.length; i += 2) { - sb.append(" ("); - sb.append(attrs[i].toString()); - sb.append("="); - sb.append(attrs[i+1].toString()); - sb.append(")"); + if (attrs[i + 1] instanceof AttributeSet) + { + sb.append(attrs[i]); + sb.append("=AttributeSet,"); + } + else + { + sb.append(attrs[i]); + sb.append('='); + sb.append(attrs[i + 1]); + sb.append(','); + } } - sb.append("]"); + sb.append("}"); return sb.toString(); } } - // FIXME: official javadocs suggest that these might be more usefully - // implemented using a WeakHashMap, but not sure if that works most - // places or whether it really matters anyways. - // - // FIXME: also not sure if these tables ought to be static (singletons), - // shared across all StyleContexts. I think so, but it's not clear in - // docs. revert to non-shared if you think it matters. - /** - * The name of the default style. + * Register StyleConstant keys as static attribute keys for serialization. */ - public static final String DEFAULT_STYLE = "default"; - + static + { + // Don't let problems while doing this prevent class loading. + try + { + for (Iterator i = StyleConstants.keys.iterator(); i.hasNext();) + registerStaticAttributeKey(i.next()); + } + catch (Throwable t) + { + t.printStackTrace(); + } + } + /** - * The default style for this style context. + * The name of the default style. */ - NamedStyle defaultStyle = new NamedStyle(DEFAULT_STYLE, null); + public static final String DEFAULT_STYLE = "default"; static Hashtable sharedAttributeSets = new Hashtable(); static Hashtable sharedFonts = new Hashtable(); - static StyleContext defaultStyleContext = new StyleContext(); + static StyleContext defaultStyleContext; static final int compressionThreshold = 9; /** * These attribute keys are handled specially in serialization. */ - private static Hashtable staticAttributeKeys = new Hashtable(); + private static Hashtable writeAttributeKeys; + private static Hashtable readAttributeKeys; + + private NamedStyle styles; + + /** + * Used for searching attributes in the pool. + */ + private transient MutableAttributeSet search = new SimpleAttributeSet(); + + /** + * A pool of immutable AttributeSets. + */ + private transient Map attributeSetPool = + Collections.synchronizedMap(new WeakHashMap()); - EventListenerList listenerList; - Hashtable styleTable; - /** * Creates a new instance of the style context. Add the default style * to the style table. */ public StyleContext() { - listenerList = new EventListenerList(); - styleTable = new Hashtable(); - styleTable.put(DEFAULT_STYLE, defaultStyle); + styles = new NamedStyle(null); + addStyle(DEFAULT_STYLE, null); } protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) @@ -438,30 +494,30 @@ public class StyleContext public void addChangeListener(ChangeListener listener) { - listenerList.add(ChangeListener.class, listener); + styles.addChangeListener(listener); } public void removeChangeListener(ChangeListener listener) { - listenerList.remove(ChangeListener.class, listener); + styles.removeChangeListener(listener); } public ChangeListener[] getChangeListeners() { - return (ChangeListener[]) listenerList.getListeners(ChangeListener.class); + return styles.getChangeListeners(); } public Style addStyle(String name, Style parent) { Style newStyle = new NamedStyle(name, parent); if (name != null) - styleTable.put(name, newStyle); + styles.addAttribute(name, newStyle); return newStyle; } public void removeStyle(String name) { - styleTable.remove(name); + styles.removeAttribute(name); } /** @@ -476,16 +532,31 @@ public class StyleContext */ public Style getStyle(String name) { - return (Style) styleTable.get(name); + return (Style) styles.getAttribute(name); } /** * Get the names of the style. The returned enumeration always * contains at least one member, the default style. */ - public Enumeration getStyleNames() + public Enumeration<?> getStyleNames() + { + return styles.getAttributeNames(); + } + + private void readObject(ObjectInputStream in) + throws ClassNotFoundException, IOException { - return styleTable.keys(); + search = new SimpleAttributeSet(); + attributeSetPool = Collections.synchronizedMap(new WeakHashMap()); + in.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + cleanupPool(); + out.defaultWriteObject(); } // @@ -577,132 +648,125 @@ public class StyleContext public static StyleContext getDefaultStyleContext() { + if (defaultStyleContext == null) + defaultStyleContext = new StyleContext(); return defaultStyleContext; } - public AttributeSet addAttribute(AttributeSet old, Object name, Object value) + public synchronized AttributeSet addAttribute(AttributeSet old, Object name, + Object value) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() + 1 < getCompressionThreshold()) { - ((MutableAttributeSet)old).addAttribute(name, value); - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.addAttribute(name, value); + reclaim(old); + ret = searchImmutableSet(); } - else + else { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.addAttribute(name, value); - if (mutable.getAttributeCount() >= getCompressionThreshold()) - return mutable; - else - { - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; - } + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.addAttribute(name, value); + ret = mas; } + return ret; } - public AttributeSet addAttributes(AttributeSet old, AttributeSet attributes) + public synchronized AttributeSet addAttributes(AttributeSet old, + AttributeSet attributes) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() + attributes.getAttributeCount() + < getCompressionThreshold()) { - ((MutableAttributeSet)old).addAttributes(attributes); - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.addAttributes(attributes); + reclaim(old); + ret = searchImmutableSet(); } - else + else { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.addAttributes(attributes); - if (mutable.getAttributeCount() >= getCompressionThreshold()) - return mutable; - else - { - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; - } + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.addAttributes(attributes); + ret = mas; } + return ret; } public AttributeSet getEmptySet() { - AttributeSet e = createSmallAttributeSet(null); - if (sharedAttributeSets.containsKey(e)) - e = (AttributeSet) sharedAttributeSets.get(e); - else - sharedAttributeSets.put(e, e); - return e; + return SimpleAttributeSet.EMPTY; } public void reclaim(AttributeSet attributes) { - if (sharedAttributeSets.containsKey(attributes)) - sharedAttributeSets.remove(attributes); + cleanupPool(); } - public AttributeSet removeAttribute(AttributeSet old, Object name) + public synchronized AttributeSet removeAttribute(AttributeSet old, + Object name) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() - 1 <= getCompressionThreshold()) { - ((MutableAttributeSet)old).removeAttribute(name); - if (old.getAttributeCount() < getCompressionThreshold()) - { - SmallAttributeSet small = createSmallAttributeSet(old); - if (!sharedAttributeSets.containsKey(small)) - sharedAttributeSets.put(small,small); - old = (AttributeSet) sharedAttributeSets.get(small); - } - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.removeAttribute(name); + reclaim(old); + ret = searchImmutableSet(); } - else - { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.removeAttribute(name); - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; + else + { + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.removeAttribute(name); + ret = mas; } + return ret; } - public AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes) + public synchronized AttributeSet removeAttributes(AttributeSet old, + AttributeSet attributes) { - return removeAttributes(old, attributes.getAttributeNames()); + AttributeSet ret; + if (old.getAttributeCount() <= getCompressionThreshold()) + { + search.removeAttributes(search); + search.addAttributes(old); + search.removeAttributes(attributes); + reclaim(old); + ret = searchImmutableSet(); + } + else + { + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.removeAttributes(attributes); + ret = mas; + } + return ret; } - public AttributeSet removeAttributes(AttributeSet old, Enumeration names) + public synchronized AttributeSet removeAttributes(AttributeSet old, + Enumeration<?> names) { - if (old instanceof MutableAttributeSet) + AttributeSet ret; + if (old.getAttributeCount() <= getCompressionThreshold()) { - ((MutableAttributeSet)old).removeAttributes(names); - if (old.getAttributeCount() < getCompressionThreshold()) - { - SmallAttributeSet small = createSmallAttributeSet(old); - if (!sharedAttributeSets.containsKey(small)) - sharedAttributeSets.put(small,small); - old = (AttributeSet) sharedAttributeSets.get(small); - } - return old; + search.removeAttributes(search); + search.addAttributes(old); + search.removeAttributes(names); + reclaim(old); + ret = searchImmutableSet(); } - else - { - MutableAttributeSet mutable = createLargeAttributeSet(old); - mutable.removeAttributes(names); - SmallAttributeSet small = createSmallAttributeSet(mutable); - if (sharedAttributeSets.containsKey(small)) - small = (SmallAttributeSet) sharedAttributeSets.get(small); - else - sharedAttributeSets.put(small,small); - return small; - } + else + { + MutableAttributeSet mas = getMutableAttributeSet(old); + mas.removeAttributes(names); + ret = mas; + } + return ret; } /** @@ -715,7 +779,7 @@ public class StyleContext { if (key == null) return null; - return staticAttributeKeys.get(key); + return readAttributeKeys.get(key); } /** @@ -742,27 +806,25 @@ public class StyleContext * stream * @throws IOException - any I/O error */ - public static void readAttributeSet(ObjectInputStream in, MutableAttributeSet a) + public static void readAttributeSet(ObjectInputStream in, + MutableAttributeSet a) throws ClassNotFoundException, IOException { - if (in == null || a == null) - return; - - Object key = in.readObject(); - Object val = in.readObject(); - while (key != null && val != null) + int count = in.readInt(); + for (int i = 0; i < count; i++) { - Object staticKey = staticAttributeKeys.get(key); - Object staticVal = staticAttributeKeys.get(val); - - if (staticKey != null) - key = staticKey; - if (staticVal != null) - val = staticVal; - + Object key = in.readObject(); + Object val = in.readObject(); + if (readAttributeKeys != null) + { + Object staticKey = readAttributeKeys.get(key); + if (staticKey != null) + key = staticKey; + Object staticVal = readAttributeKeys.get(val); + if (staticVal != null) + val = staticVal; + } a.addAttribute(key, val); - key = in.readObject(); - val = in.readObject(); } } @@ -778,18 +840,35 @@ public class StyleContext public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a) throws IOException { + int count = a.getAttributeCount(); + out.writeInt(count); Enumeration e = a.getAttributeNames(); while (e.hasMoreElements()) { - Object oldKey = e.nextElement(); - Object newKey = getStaticAttribute(oldKey); - Object key = (newKey == null) ? oldKey : newKey; - - out.writeObject(key); - out.writeObject(a.getAttribute(oldKey)); + Object key = e.nextElement(); + // Write key. + if (key instanceof Serializable) + out.writeObject(key); + else + { + Object io = writeAttributeKeys.get(key); + if (io == null) + throw new NotSerializableException(key.getClass().getName() + + ", key: " + key); + out.writeObject(io); + } + // Write value. + Object val = a.getAttribute(key); + Object io = writeAttributeKeys.get(val); + if (val instanceof Serializable) + out.writeObject(io != null ? io : val); + else + { + if (io == null) + throw new NotSerializableException(val.getClass().getName()); + out.writeObject(io); + } } - out.writeObject(null); - out.writeObject(null); } /** @@ -833,8 +912,79 @@ public class StyleContext */ public static void registerStaticAttributeKey(Object key) { - if (key != null) - staticAttributeKeys.put(key.getClass().getName() + "." + key.toString(), - key); + String io = key.getClass().getName() + "." + key.toString(); + if (writeAttributeKeys == null) + writeAttributeKeys = new Hashtable(); + if (readAttributeKeys == null) + readAttributeKeys = new Hashtable(); + writeAttributeKeys.put(key, io); + readAttributeKeys.put(io, key); + } + + /** + * Returns a string representation of this StyleContext. + * + * @return a string representation of this StyleContext + */ + public String toString() + { + cleanupPool(); + StringBuilder b = new StringBuilder(); + Iterator i = attributeSetPool.keySet().iterator(); + while (i.hasNext()) + { + Object att = i.next(); + b.append(att); + b.append('\n'); + } + return b.toString(); + } + + /** + * Searches the AttributeSet pool and returns a pooled instance if available, + * or pool a new one. + * + * @return an immutable attribute set that equals the current search key + */ + private AttributeSet searchImmutableSet() + { + SmallAttributeSet k = createSmallAttributeSet(search); + WeakReference ref = (WeakReference) attributeSetPool.get(k); + SmallAttributeSet a; + if (ref == null || (a = (SmallAttributeSet) ref.get()) == null) + { + a = k; + attributeSetPool.put(a, new WeakReference(a)); + } + return a; + } + + /** + * Cleans up the attribute set pool from entries that are no longer + * referenced. + */ + private void cleanupPool() + { + // TODO: How else can we force cleaning up the WeakHashMap? + attributeSetPool.size(); + } + + /** + * Returns a MutableAttributeSet that holds a. If a itself is mutable, + * this returns a itself, otherwise it creates a new SimpleAtttributeSet + * via {@link #createLargeAttributeSet(AttributeSet)}. + * + * @param a the AttributeSet to create a mutable set for + * + * @return a mutable attribute set that corresponds to a + */ + private MutableAttributeSet getMutableAttributeSet(AttributeSet a) + { + MutableAttributeSet mas; + if (a instanceof MutableAttributeSet) + mas = (MutableAttributeSet) a; + else + mas = createLargeAttributeSet(a); + return mas; } } diff --git a/libjava/classpath/javax/swing/text/StyledEditorKit.java b/libjava/classpath/javax/swing/text/StyledEditorKit.java index c4eef4463fb..568694387f3 100644 --- a/libjava/classpath/javax/swing/text/StyledEditorKit.java +++ b/libjava/classpath/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/libjava/classpath/javax/swing/text/TextAction.java b/libjava/classpath/javax/swing/text/TextAction.java index 144166e9cdb..49c49cb9d7f 100644 --- a/libjava/classpath/javax/swing/text/TextAction.java +++ b/libjava/classpath/javax/swing/text/TextAction.java @@ -38,14 +38,15 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.Component; +import java.awt.KeyboardFocusManager; import java.awt.Point; import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; +import java.util.Iterator; import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.SwingConstants; /** * TextAction @@ -73,10 +74,16 @@ public abstract class TextAction extends AbstractAction */ protected final JTextComponent getTextComponent(ActionEvent event) { - if (event.getSource() instanceof JTextComponent) - return (JTextComponent) event.getSource(); - - return getFocusedComponent(); + JTextComponent target = null; + if (event != null) + { + Object source = event.getSource(); + if (source instanceof JTextComponent) + target = (JTextComponent) source; + } + if (target == null) + target = getFocusedComponent(); + return target; } /** @@ -89,16 +96,28 @@ public abstract class TextAction extends AbstractAction */ public static final Action[] augmentList(Action[] list1, Action[] list2) { - HashSet actionSet = new HashSet(); + HashMap<Object,Action> actions = new HashMap<Object,Action>(); for (int i = 0; i < list1.length; ++i) - actionSet.add(list1[i]); + { + Action a = list1[i]; + Object name = a.getValue(Action.NAME); + actions.put(name != null ? name : "", a); + } for (int i = 0; i < list2.length; ++i) - actionSet.add(list2[i]); + { + Action a = list2[i]; + Object name = a.getValue(Action.NAME); + actions.put(name != null ? name : "", a); + } + Action[] augmented = new Action[actions.size()]; + + int i = 0; + for (Iterator<Action> it = actions.values().iterator(); it.hasNext(); i++) + augmented[i] = it.next(); + return augmented; - ArrayList list = new ArrayList(actionSet); - return (Action[]) list.toArray(new Action[actionSet.size()]); } /** @@ -108,7 +127,13 @@ public abstract class TextAction extends AbstractAction */ protected final JTextComponent getFocusedComponent() { - return null; // TODO + KeyboardFocusManager kfm = + KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focused = kfm.getPermanentFocusOwner(); + JTextComponent textComp = null; + if (focused instanceof JTextComponent) + textComp = (JTextComponent) focused; + return textComp; } /** Abstract helper class which implements everything needed for an diff --git a/libjava/classpath/javax/swing/text/Utilities.java b/libjava/classpath/javax/swing/text/Utilities.java index 36361f49796..d49d806cfa3 100644 --- a/libjava/classpath/javax/swing/text/Utilities.java +++ b/libjava/classpath/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; /** @@ -55,10 +54,6 @@ import javax.swing.text.Position.Bias; */ public class Utilities { - /** - * The length of the char buffer that holds the characters to be drawn. - */ - private static final int BUF_LENGTH = 64; /** * Creates a new <code>Utilities</code> object. @@ -94,13 +89,12 @@ public class Utilities // The font metrics of the current selected font. FontMetrics metrics = g.getFontMetrics(); + int ascent = metrics.getAscent(); // The current x and y pixel coordinates. int pixelX = x; - int pixelY = y - ascent; - int pixelWidth = 0; int pos = s.offset; int len = 0; @@ -109,44 +103,43 @@ public class Utilities for (int offset = s.offset; offset < end; ++offset) { char c = buffer[offset]; - if (c == '\t' || c == '\n') + switch (c) { + case '\t': if (len > 0) { - g.drawChars(buffer, pos, len, pixelX, pixelY + ascent); - pixelX += pixelWidth; - pixelWidth = 0; + g.drawChars(buffer, pos, len, pixelX, y); + pixelX += metrics.charsWidth(buffer, pos, len); + len = 0; } pos = offset+1; - len = 0; + if (e != null) + pixelX = (int) e.nextTabStop((float) pixelX, startOffset + offset + - s.offset); + else + pixelX += metrics.charWidth(' '); + x = pixelX; + break; + case '\n': + case '\r': + if (len > 0) { + g.drawChars(buffer, pos, len, pixelX, y); + pixelX += metrics.charsWidth(buffer, pos, len); + len = 0; + } + x = pixelX; + break; + default: + len += 1; } - - switch (c) - { - case '\t': - // In case we have a tab, we just 'jump' over the tab. - // When we have no tab expander we just use the width of ' '. - if (e != null) - pixelX = (int) e.nextTabStop((float) pixelX, - startOffset + offset - s.offset); - 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]); - break; - } } if (len > 0) - g.drawChars(buffer, pos, len, pixelX, pixelY + ascent); + { + g.drawChars(buffer, pos, len, pixelX, y); + pixelX += metrics.charsWidth(buffer, pos, len); + } - return pixelX + pixelWidth; + return pixelX; } /** @@ -174,7 +167,9 @@ public class Utilities // The current maximum width. int maxWidth = 0; - for (int offset = s.offset; offset < (s.offset + s.count); ++offset) + int end = s.offset + s.count; + int count = 0; + for (int offset = s.offset; offset < end; offset++) { switch (buffer[offset]) { @@ -182,7 +177,7 @@ public class Utilities // In case we have a tab, we just 'jump' over the tab. // When we have no tab expander we just use the width of 'm'. if (e != null) - pixelX = (int) e.nextTabStop((float) pixelX, + pixelX = (int) e.nextTabStop(pixelX, startOffset + offset - s.offset); else pixelX += metrics.charWidth(' '); @@ -190,21 +185,18 @@ public class Utilities case '\n': // In case we have a newline, we must 'draw' // the buffer and jump on the next line. - pixelX += metrics.charWidth(buffer[offset]); - maxWidth = Math.max(maxWidth, pixelX - x); - pixelX = x; - break; - default: - // Here we draw the char. - pixelX += metrics.charWidth(buffer[offset]); - break; - } + pixelX += metrics.charsWidth(buffer, offset - count, count); + count = 0; + break; + default: + count++; + } } // Take the last line into account. - maxWidth = Math.max(maxWidth, pixelX - x); + pixelX += metrics.charsWidth(buffer, end - count, count); - return maxWidth; + return pixelX - x; } /** @@ -239,43 +231,41 @@ public class Utilities int x, TabExpander te, int p0, boolean round) { - // At the end of the for loop, this holds the requested model location - int pos; + int found = s.count; int currentX = x0; - int width = 0; + int nextX = currentX; - for (pos = 0; pos < s.count; pos++) + int end = s.offset + s.count; + for (int pos = s.offset; pos < end && found == s.count; pos++) { - char nextChar = s.array[s.offset+pos]; - - if (nextChar == 0) - break; + char nextChar = s.array[pos]; if (nextChar != '\t') - width = fm.charWidth(nextChar); + nextX += fm.charWidth(nextChar); else { if (te == null) - width = fm.charWidth(' '); + nextX += fm.charWidth(' '); else - width = ((int) te.nextTabStop(currentX, pos)) - currentX; + nextX += ((int) te.nextTabStop(nextX, p0 + pos - s.offset)); } - if (round) + if (x >= currentX && x < nextX) { - if (currentX + (width>>1) > x) - break; - } - else - { - if (currentX + width > x) - break; + // Found position. + if ((! round) || ((x - currentX) < (nextX - x))) + { + found = pos - s.offset; + } + else + { + found = pos + 1 - s.offset; + } } - - currentX += width; + currentX = nextX; } - return pos + p0; + return found; } /** @@ -543,28 +533,39 @@ public class Utilities int x0, int x, TabExpander e, int startOffset) { - int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false); - BreakIterator breaker = BreakIterator.getWordInstance(); - breaker.setText(s); - - // If startOffset and s.offset differ then we need to use - // that difference two convert the offset between the two metrics. - int shift = startOffset - s.offset; - + int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, + false); + int breakLoc = mark; // If mark is equal to the end of the string, just use that position. - if (mark >= shift + s.count) - return mark; - - // Try to find a word boundary previous to the mark at which we - // can break the text. - int preceding = breaker.preceding(mark + 1 - shift); - - if (preceding != 0) - return preceding + shift; - - // If preceding is 0 we couldn't find a suitable word-boundary so - // just break it on the character boundary - return mark; + if (mark < s.count - 1) + { + for (int i = s.offset + mark; i >= s.offset; i--) + { + char ch = s.array[i]; + if (ch < 256) + { + // For ASCII simply scan backwards for whitespace. + if (Character.isWhitespace(ch)) + { + breakLoc = i - s.offset + 1; + break; + } + } + else + { + // Only query BreakIterator for complex chars. + BreakIterator bi = BreakIterator.getLineInstance(); + bi.setText(s); + int pos = bi.preceding(i + 1); + if (pos > s.offset) + { + breakLoc = breakLoc - s.offset; + } + break; + } + } + } + return breakLoc; } /** @@ -712,12 +713,12 @@ public class Utilities offset, Bias.Forward, direction, - null) + new Position.Bias[1]) : t.getUI().getNextVisualPositionFrom(t, offset, Bias.Forward, direction, - null); + new Position.Bias[1]); } catch (BadLocationException ble) { diff --git a/libjava/classpath/javax/swing/text/View.java b/libjava/classpath/javax/swing/text/View.java index 55a63f6b668..c63ddbce776 100644 --- a/libjava/classpath/javax/swing/text/View.java +++ b/libjava/classpath/javax/swing/text/View.java @@ -57,7 +57,6 @@ public abstract class View implements SwingConstants public static final int X_AXIS = 0; public static final int Y_AXIS = 1; - private float width, height; private Element elt; private View parent; @@ -93,7 +92,14 @@ public abstract class View implements SwingConstants { int numChildren = getViewCount(); for (int i = 0; i < numChildren; i++) - getView(i).setParent(null); + { + View child = getView(i); + // It is important that we only reset the parent on views that + // actually belong to us. In FlowView the child may already be + // reparented. + if (child.getParent() == this) + child.setParent(null); + } } this.parent = parent; @@ -263,7 +269,7 @@ public abstract class View implements SwingConstants public void removeAll() { - replace(0, getViewCount(), new View[0]); + replace(0, getViewCount(), null); } public void remove(int index) @@ -307,15 +313,16 @@ public abstract class View implements SwingConstants { int index = getViewIndex(x, y, allocation); - if (index < -1) - return null; - - Shape childAllocation = getChildAllocation(index, allocation); - - if (childAllocation.getBounds().contains(x, y)) - return getView(index).getToolTipText(x, y, childAllocation); - - return null; + String text = null; + if (index >= 0) + { + allocation = getChildAllocation(index, allocation); + Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation + : allocation.getBounds(); + if (r.contains(x, y)) + text = getView(index).getToolTipText(x, y, allocation); + } + return text; } /** @@ -328,13 +335,17 @@ public abstract class View implements SwingConstants public void preferenceChanged(View child, boolean width, boolean height) { - if (parent != null) - parent.preferenceChanged(this, width, height); + View p = getParent(); + if (p != null) + p.preferenceChanged(this, width, height); } public int getBreakWeight(int axis, float pos, float len) { - return BadBreakWeight; + int weight = BadBreakWeight; + if (len > getPreferredSpan(axis)) + weight = GoodBreakWeight; + return weight; } public View breakView(int axis, int offset, float pos, float len) @@ -370,12 +381,18 @@ public abstract class View implements SwingConstants */ public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - Element el = getElement(); - DocumentEvent.ElementChange ec = ev.getChange(el); - if (ec != null) - updateChildren(ec, ev, vf); - forwardUpdate(ec, ev, shape, vf); - updateLayout(ec, ev, shape); + if (getViewCount() > 0) + { + Element el = getElement(); + DocumentEvent.ElementChange ec = ev.getChange(el); + if (ec != null) + { + if (! updateChildren(ec, ev, vf)) + ec = null; + } + forwardUpdate(ec, ev, shape, vf); + updateLayout(ec, ev, shape); + } } /** @@ -429,12 +446,18 @@ public abstract class View implements SwingConstants */ public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { - Element el = getElement(); - DocumentEvent.ElementChange ec = ev.getChange(el); - if (ec != null) - updateChildren(ec, ev, vf); - forwardUpdate(ec, ev, shape, vf); - updateLayout(ec, ev, shape); + if (getViewCount() > 0) + { + Element el = getElement(); + DocumentEvent.ElementChange ec = ev.getChange(el); + if (ec != null) + { + if (! updateChildren(ec, ev, vf)) + ec = null; + } + forwardUpdate(ec, ev, shape, vf); + updateLayout(ec, ev, shape); + } } /** @@ -465,10 +488,15 @@ public abstract class View implements SwingConstants Element[] removed = ec.getChildrenRemoved(); int index = ec.getIndex(); - View[] newChildren = new View[added.length]; - for (int i = 0; i < added.length; ++i) - newChildren[i] = vf.create(added[i]); - replace(index, removed.length, newChildren); + View[] newChildren = null; + if (added != null) + { + newChildren = new View[added.length]; + for (int i = 0; i < added.length; ++i) + newChildren[i] = vf.create(added[i]); + } + int numRemoved = removed != null ? removed.length : 0; + replace(index, numRemoved, newChildren); return true; } @@ -598,10 +626,12 @@ public abstract class View implements SwingConstants DocumentEvent ev, Shape shape) { if (ec != null && shape != null) - preferenceChanged(null, true, true); - Container c = getContainer(); - if (c != null) - c.repaint(); + { + preferenceChanged(null, true, true); + Container c = getContainer(); + if (c != null) + c.repaint(); + } } /** @@ -750,7 +780,9 @@ public abstract class View implements SwingConstants */ public int viewToModel(float x, float y, Shape a) { - return viewToModel(x, y, a, new Position.Bias[0]); + Position.Bias[] biasRet = new Position.Bias[1]; + biasRet[0] = Position.Bias.Forward; + return viewToModel(x, y, a, biasRet); } /** diff --git a/libjava/classpath/javax/swing/text/WrappedPlainView.java b/libjava/classpath/javax/swing/text/WrappedPlainView.java index a6c369a4c25..00e12b1129e 100644 --- a/libjava/classpath/javax/swing/text/WrappedPlainView.java +++ b/libjava/classpath/javax/swing/text/WrappedPlainView.java @@ -83,7 +83,17 @@ public class WrappedPlainView extends BoxView implements TabExpander /** The height of the line (used while painting) **/ int lineHeight; - + + /** + * The base offset for tab calculations. + */ + private int tabBase; + + /** + * The tab size. + */ + private int tabSize; + /** * The instance returned by {@link #getLineBuffer()}. */ @@ -121,10 +131,13 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public float nextTabStop(float x, int tabStop) { - JTextComponent host = (JTextComponent)getContainer(); - float tabSizePixels = getTabSize() - * host.getFontMetrics(host.getFont()).charWidth('m'); - return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels; + int next = (int) x; + if (tabSize != 0) + { + int numTabs = ((int) x - tabBase) / tabSize; + next = tabBase + (numTabs + 1) * tabSize; + } + return next; } /** @@ -274,44 +287,32 @@ public class WrappedPlainView extends BoxView implements TabExpander */ protected int calculateBreakPosition(int p0, int p1) { - Container c = getContainer(); - - int li = getLeftInset(); - int ti = getTopInset(); - - Rectangle alloc = new Rectangle(li, ti, - getWidth()-getRightInset()-li, - getHeight()-getBottomInset()-ti); - - // Mimic a behavior observed in the RI. - if (alloc.isEmpty()) - return 0; - - updateMetrics(); - + Segment s = new Segment(); try { - getDocument().getText(p0, p1 - p0, getLineBuffer()); + getDocument().getText(p0, p1 - p0, s); } - catch (BadLocationException ble) + catch (BadLocationException ex) { - // this shouldn't happen - throw new InternalError("Invalid offsets p0: " + p0 + " - p1: " + p1); + assert false : "Couldn't load text"; } - + int width = getWidth(); + int pos; if (wordWrap) - return Utilities.getBreakLocation(lineBuffer, metrics, alloc.x, - alloc.x + alloc.width, this, p0); + pos = p0 + Utilities.getBreakLocation(s, metrics, tabBase, + tabBase + width, this, p0); else - return p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x, - alloc.x + alloc.width, this, 0, - true); + pos = p0 + Utilities.getTabbedTextOffset(s, metrics, tabBase, + tabBase + width, this, p0, + false); + return pos; } void updateMetrics() { Container component = getContainer(); metrics = component.getFontMetrics(component.getFont()); + tabSize = getTabSize()* metrics.charWidth('m'); } /** @@ -350,9 +351,15 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f) { - super.insertUpdate(e, a, viewFactory); + // Update children efficiently. + updateChildren(e, a); - // No repaint needed, as this is done by the WrappedLine instances. + // Notify children. + Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a) + : null; + View v = getViewAtPosition(e.getOffset(), r); + if (v != null) + v.insertUpdate(e, r, f); } /** @@ -361,9 +368,15 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f) { - super.removeUpdate(e, a, viewFactory); - - // No repaint needed, as this is done by the WrappedLine instances. + // Update children efficiently. + updateChildren(e, a); + + // Notify children. + Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a) + : null; + View v = getViewAtPosition(e.getOffset(), r); + if (v != null) + v.removeUpdate(e, r, f); } /** @@ -373,11 +386,39 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f) { - super.changedUpdate(e, a, viewFactory); - - // No repaint needed, as this is done by the WrappedLine instances. + // Update children efficiently. + updateChildren(e, a); } - + + /** + * Helper method. Updates the child views in response to + * insert/remove/change updates. This is here to be a little more efficient + * than the BoxView implementation. + * + * @param ev the document event + * @param a the shape + */ + private void updateChildren(DocumentEvent ev, Shape a) + { + Element el = getElement(); + DocumentEvent.ElementChange ec = ev.getChange(el); + if (ec != null) + { + Element[] removed = ec.getChildrenRemoved(); + Element[] added = ec.getChildrenAdded(); + View[] addedViews = new View[added.length]; + for (int i = 0; i < added.length; i++) + addedViews[i] = new WrappedLine(added[i]); + replace(ec.getIndex(), removed.length, addedViews); + if (a != null) + { + preferenceChanged(null, true, true); + getContainer().repaint(); + } + } + updateMetrics(); + } + class WrappedLineCreator implements ViewFactory { // Creates a new WrappedLine @@ -397,6 +438,9 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void paint(Graphics g, Shape a) { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + tabBase = r.x; + JTextComponent comp = (JTextComponent)getContainer(); // Ensure metrics are up-to-date. updateMetrics(); @@ -434,7 +478,6 @@ public class WrappedPlainView extends BoxView implements TabExpander public WrappedLine(Element elem) { super(elem); - determineNumLines(); } /** @@ -449,10 +492,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; @@ -472,37 +539,29 @@ public class WrappedPlainView extends BoxView implements TabExpander } } - + /** * Calculates the number of logical lines that the Element * needs to be displayed and updates the variable numLines * accordingly. */ - void determineNumLines() + private int determineNumLines() { - numLines = 0; + int nLines = 0; int end = getEndOffset(); - if (end == 0) - return; - - int breakPoint; for (int i = getStartOffset(); i < end;) { - numLines ++; + nLines++; // careful: check that there's no off-by-one problem here // depending on which position calculateBreakPosition returns - breakPoint = calculateBreakPosition(i, end); - - if (breakPoint == 0) - return; + int breakPoint = calculateBreakPosition(i, end); - // If breakPoint is equal to the current index no further - // line is needed and we can end the loop. if (breakPoint == i) - break; + i = breakPoint + 1; else i = breakPoint; } + return nLines; } /** @@ -547,7 +606,7 @@ public class WrappedPlainView extends BoxView implements TabExpander // Throwing a BadLocationException is an observed behavior of the RI. if (rect.isEmpty()) throw new BadLocationException("Unable to calculate view coordinates " - + "when allocation area is empty.", 5); + + "when allocation area is empty.", pos); Segment s = getLineBuffer(); int lineHeight = metrics.getHeight(); @@ -624,7 +683,7 @@ public class WrappedPlainView extends BoxView implements TabExpander return currLineStart; if (y > rect.y + rect.height) - return end; + return end - 1; // Note: rect.x and rect.width do not represent the width of painted // text but the area where text *may* be painted. This means the width @@ -685,22 +744,14 @@ public class WrappedPlainView extends BoxView implements TabExpander */ void updateDamage (Rectangle a) { - // If the allocation area is empty we can't do anything useful. - // As determining the number of lines is impossible in that state we - // reset it to an invalid value which can then be recalculated at a - // later point. - if (a == null || a.isEmpty()) + int nLines = determineNumLines(); + if (numLines != nLines) { - numLines = 1; - return; + numLines = nLines; + preferenceChanged(this, false, true); + getContainer().repaint(); } - - int oldNumLines = numLines; - determineNumLines(); - - if (numLines != oldNumLines) - preferenceChanged(this, false, true); - else + else if (a != null) getContainer().repaint(a.x, a.y, a.width, a.height); } @@ -714,7 +765,8 @@ public class WrappedPlainView extends BoxView implements TabExpander */ public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f) { - updateDamage((Rectangle)a); + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + updateDamage(r); } /** @@ -736,7 +788,8 @@ public class WrappedPlainView extends BoxView implements TabExpander // However this seems to cause no trouble and as it reduces the // number of method calls it can stay this way. - updateDamage((Rectangle)a); + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + updateDamage(r); } } } diff --git a/libjava/classpath/javax/swing/text/ZoneView.java b/libjava/classpath/javax/swing/text/ZoneView.java new file mode 100644 index 00000000000..6cabc6c200c --- /dev/null +++ b/libjava/classpath/javax/swing/text/ZoneView.java @@ -0,0 +1,442 @@ +/* ZoneView.java -- An effective BoxView subclass + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.awt.Shape; +import java.util.ArrayList; +import java.util.LinkedList; + +import javax.swing.event.DocumentEvent; + +/** + * A View implementation that delays loading of sub views until they are + * needed for display or internal transformations. This can be used for + * editors that need to handle large documents more effectivly than the + * standard {@link BoxView}. + * + * @author Roman Kennke (kennke@aicas.com) + * + * @since 1.3 + */ +public class ZoneView + extends BoxView +{ + + /** + * The default zone view implementation. The specs suggest that this is + * a subclass of AsyncBoxView, so do we. + */ + static class Zone + extends AsyncBoxView + { + /** + * The start position for this zone. + */ + private Position p0; + + /** + * The end position for this zone. + */ + private Position p1; + + /** + * Creates a new Zone for the specified element, start and end positions. + * + * @param el the element + * @param pos0 the start position + * @param pos1 the end position + * @param axis the major axis + */ + Zone(Element el, Position pos0, Position pos1, int axis) + { + super(el, axis); + p0 = pos0; + p1 = pos1; + } + + /** + * Returns the start offset of the zone. + * + * @return the start offset of the zone + */ + public int getStartOffset() + { + return p0.getOffset(); + } + + /** + * Returns the end offset of the zone. + * + * @return the end offset of the zone + */ + public int getEndOffset() + { + return p1.getOffset(); + } + } + + /** + * The maximumZoneSize. + */ + private int maximumZoneSize; + + /** + * The maximum number of loaded zones. + */ + private int maxZonesLoaded; + + /** + * A queue of loaded zones. When the number of loaded zones exceeds the + * maximum number of zones, the oldest zone(s) get unloaded. + */ + private LinkedList loadedZones; + + /** + * Creates a new ZoneView for the specified element and axis. + * + * @param element the element for which to create a ZoneView + * @param axis the major layout axis for the box + */ + public ZoneView(Element element, int axis) + { + super(element, axis); + maximumZoneSize = 8192; + maxZonesLoaded = 3; + loadedZones = new LinkedList(); + } + + /** + * Sets the maximum zone size. Note that zones might still become larger + * then the size specified when a singe child view is larger for itself, + * because zones are formed on child view boundaries. + * + * @param size the maximum zone size to set + * + * @see #getMaximumZoneSize() + */ + public void setMaximumZoneSize(int size) + { + maximumZoneSize = size; + } + + /** + * Returns the maximum zone size. Note that zones might still become larger + * then the size specified when a singe child view is larger for itself, + * because zones are formed on child view boundaries. + * + * @return the maximum zone size + * + * @see #setMaximumZoneSize(int) + */ + public int getMaximumZoneSize() + { + return maximumZoneSize; + } + + /** + * Sets the maximum number of zones that are allowed to be loaded at the + * same time. If the new number of allowed zones is smaller then the + * previous settings, this unloads all zones the aren't allowed to be + * loaded anymore. + * + * @param num the number of zones allowed to be loaded at the same time + * + * @throws IllegalArgumentException if <code>num <= 0</code> + * + * @see #getMaxZonesLoaded() + */ + public void setMaxZonesLoaded(int num) + { + if (num < 1) + throw new IllegalArgumentException("Illegal number of zones"); + maxZonesLoaded = num; + unloadOldestZones(); + } + + /** + * Returns the number of zones that are allowed to be loaded. + * + * @return the number of zones that are allowed to be loaded + * + * @see #setMaxZonesLoaded(int) + */ + public int getMaxZonesLoaded() + { + return maxZonesLoaded; + } + + /** + * Gets called after a zone has been loaded. This unloads the oldest zone(s) + * when the maximum number of zones is reached. + * + * @param zone the zone that has been loaded + */ + protected void zoneWasLoaded(View zone) + { + loadedZones.addLast(zone); + unloadOldestZones(); + } + + /** + * This unloads the specified zone. This is implemented to simply remove + * all child views from that zone. + * + * @param zone the zone to be unloaded + */ + protected void unloadZone(View zone) + { + zone.removeAll(); + } + + /** + * Returns <code>true</code> when the specified zone is loaded, + * <code>false</code> otherwise. The default implementation checks if + * the zone view has child elements. + * + * @param zone the zone view to check + * + * @return <code>true</code> when the specified zone is loaded, + * <code>false</code> otherwise + */ + protected boolean isZoneLoaded(View zone) + { + return zone.getViewCount() > 0; + } + + /** + * Creates a zone for the specified range. Subclasses can override this + * to provide a custom implementation for the zones. + * + * @param p0 the start of the range + * @param p1 the end of the range + * + * @return the zone + */ + protected View createZone(int p0, int p1) + { + Document doc = getDocument(); + Position pos0 = null; + Position pos1 = null; + try + { + pos0 = doc.createPosition(p0); + pos1 = doc.createPosition(p1); + } + catch (BadLocationException ex) + { + assert false : "Must not happen"; + } + Zone zone = new Zone(getElement(), pos0, pos1, getAxis()); + return zone; + } + + // -------------------------------------------------------------------------- + // CompositeView methods. + // -------------------------------------------------------------------------- + + /** + * Overridden to not load all the child views. This methods creates + * initial zones without actually loading them. + * + * @param vf not used + */ + protected void loadChildren(ViewFactory vf) + { + int p0 = getStartOffset(); + int p1 = getEndOffset(); + append(createZone(p0, p1)); + checkZoneAt(p0); + } + + /** + * Returns the index of the child view at the document position + * <code>pos</code>. + * + * This overrides the CompositeView implementation because the ZoneView does + * not provide a one to one mapping from Elements to Views. + * + * @param pos the document position + * + * @return the index of the child view at the document position + * <code>pos</code> + */ + protected int getViewIndexAtPosition(int pos) + { + int index = -1; + boolean found = false; + if (pos >= getStartOffset() && pos <= getEndOffset()) + { + int upper = getViewCount() - 1; + int lower = 0; + index = (upper - lower) / 2 + lower; + int bias = 0; + do + { + View child = getView(index); + int childStart = child.getStartOffset(); + int childEnd = child.getEndOffset(); + if (pos >= childStart && pos < childEnd) + found = true; + else if (pos < childStart) + { + upper = index; + bias = -1; + } + else if (pos >= childEnd) + { + lower = index; + bias = 1; + } + if (! found) + { + int newIndex = (upper - lower) / 2 + lower; + if (newIndex == index) + index = newIndex + bias; + else + index = newIndex; + } + } while (upper != lower && ! found); + } + // If no child view actually covers the specified offset, reset index to + // -1. + if (! found) + index = -1; + return index; + } + + // -------------------------------------------------------------------------- + // View methods. + // -------------------------------------------------------------------------- + + public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + // TODO: Implement this. + } + + public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + // TODO: Implement this. + } + + protected boolean updateChildren(DocumentEvent.ElementChange ec, + DocumentEvent e, ViewFactory vf) + { + // TODO: Implement this. + return false; + } + + // -------------------------------------------------------------------------- + // Internal helper methods. + // -------------------------------------------------------------------------- + + /** + * A helper method to unload the oldest zones when there are more loaded + * zones then allowed. + */ + private void unloadOldestZones() + { + int maxZones = getMaxZonesLoaded(); + while (loadedZones.size() > maxZones) + { + View zone = (View) loadedZones.removeFirst(); + unloadZone(zone); + } + } + + /** + * Checks if the zone view at position <code>pos</code> should be split + * (its size is greater than maximumZoneSize) and tries to split it. + * + * @param pos the document position to check + */ + private void checkZoneAt(int pos) + { + int viewIndex = getViewIndexAtPosition(pos); //, Position.Bias.Forward); + View view = getView(viewIndex); + int p0 = view.getStartOffset(); + int p1 = view.getEndOffset(); + if (p1 - p0 > maximumZoneSize) + splitZone(viewIndex, p0, p1); + } + + /** + * Tries to break the view at the specified index and inside the specified + * range into pieces that are acceptable with respect to the maximum zone + * size. + * + * @param index the index of the view to split + * @param p0 the start offset + * @param p1 the end offset + */ + private void splitZone(int index, int p0, int p1) + { + ArrayList newZones = new ArrayList(); + int p = p0; + do + { + p0 = p; + p = Math.min(getPreferredZoneEnd(p0), p1); + newZones.add(createZone(p0, p)); + } while (p < p1); + View[] newViews = new View[newZones.size()]; + newViews = (View[]) newZones.toArray(newViews); + replace(index, 1, newViews); + } + + /** + * Calculates the positions at which a zone split is performed. This + * tries to create zones sized close to half the maximum zone size. + * + * @param start the start offset + * + * @return the preferred end offset + */ + private int getPreferredZoneEnd(int start) + { + Element el = getElement(); + int index = el.getElementIndex(start + (maximumZoneSize / 2)); + Element child = el.getElement(index); + int p0 = child.getStartOffset(); + int p1 = child.getEndOffset(); + int end = p1; + if (p0 - start > maximumZoneSize && p0 > start) + end = p0; + return end; + } +} diff --git a/libjava/classpath/javax/swing/text/html/BRView.java b/libjava/classpath/javax/swing/text/html/BRView.java index 5521fed8edf..7d0d5164d49 100644 --- a/libjava/classpath/javax/swing/text/html/BRView.java +++ b/libjava/classpath/javax/swing/text/html/BRView.java @@ -44,8 +44,7 @@ import javax.swing.text.Element; * Handled the HTML BR tag. */ class BRView - extends NullView - + extends InlineView { /** * Creates the new BR view. @@ -66,6 +65,6 @@ class BRView if (axis == X_AXIS) return ForcedBreakWeight; else - return BadBreakWeight; + return super.getBreakWeight(axis, pos, len); } } diff --git a/libjava/classpath/javax/swing/text/html/BlockView.java b/libjava/classpath/javax/swing/text/html/BlockView.java index 6274e7b1756..b05c983e922 100644 --- a/libjava/classpath/javax/swing/text/html/BlockView.java +++ b/libjava/classpath/javax/swing/text/html/BlockView.java @@ -38,9 +38,12 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.css.Length; + import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; +import java.util.HashMap; import javax.swing.SizeRequirements; import javax.swing.event.DocumentEvent; @@ -55,7 +58,106 @@ import javax.swing.text.ViewFactory; */ public class BlockView extends BoxView { - + + /** + * Stores information about child positioning according to the + * CSS attributes position, left, right, top and bottom. + */ + private static class PositionInfo + { + // TODO: Use enums when available. + + /** + * Static positioning. This is the default and is thus rarely really + * used. + */ + static final int STATIC = 0; + + /** + * Relative positioning. The box is teaked relative to its static + * computed bounds. + */ + static final int RELATIVE = 1; + + /** + * Absolute positioning. The box is moved relative to the parent's box. + */ + static final int ABSOLUTE = 2; + + /** + * Like ABSOLUTE, with some fixation against the viewport (not yet + * implemented). + */ + static final int FIXED = 3; + + /** + * The type according to the constants of this class. + */ + int type; + + /** + * The left constraint, null if not set. + */ + Length left; + + /** + * The right constraint, null if not set. + */ + Length right; + + /** + * The top constraint, null if not set. + */ + Length top; + + /** + * The bottom constraint, null if not set. + */ + Length bottom; + + /** + * Creates a new PositionInfo object. + * + * @param typ the type to set + * @param l the left constraint + * @param r the right constraint + * @param t the top constraint + * @param b the bottom constraint + */ + PositionInfo(int typ, Length l, Length r, Length t, Length b) + { + type = typ; + left = l; + right = r; + top = t; + bottom = b; + } + } + + /** + * The attributes for this view. + */ + private AttributeSet attributes; + + /** + * The box painter for this view. + * + * This is package private because the TableView needs access to it. + */ + StyleSheet.BoxPainter painter; + + /** + * The width and height as specified in the stylesheet, null if not + * specified. The first value is the X_AXIS, the second the Y_AXIS. You + * can index this directly by the X_AXIS and Y_AXIS constants. + */ + private Length[] cssSpans; + + /** + * Stores additional CSS layout information. + */ + private HashMap positionInfo; + /** * Creates a new view that represents an html box. * This can be used for a number of elements. @@ -66,8 +168,10 @@ public class BlockView extends BoxView public BlockView(Element elem, int axis) { super(elem, axis); + cssSpans = new Length[2]; + positionInfo = new HashMap(); } - + /** * Creates the parent view for this. It is called before * any other methods, if the parent view is working properly. @@ -99,12 +203,27 @@ public class BlockView extends BoxView protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) { - SizeRequirements sr = super.calculateMajorAxisRequirements(axis, r); - // FIXME: adjust it if the CSS width or height attribute is specified - // and applicable - return sr; + if (r == null) + r = new SizeRequirements(); + + if (setCSSSpan(r, axis)) + { + // If we have set the span from CSS, then we need to adjust + // the margins. + SizeRequirements parent = super.calculateMajorAxisRequirements(axis, + null); + int margin = axis == X_AXIS ? getLeftInset() + getRightInset() + : getTopInset() + getBottomInset(); + r.minimum -= margin; + r.preferred -= margin; + r.maximum -= margin; + constrainSize(axis, r, parent); + } + else + r = super.calculateMajorAxisRequirements(axis, r); + return r; } - + /** * Calculates the requirements along the minor axis. * This is implemented to call the superclass and then @@ -118,12 +237,89 @@ public class BlockView extends BoxView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { - SizeRequirements sr = super.calculateMinorAxisRequirements(axis, r); - // FIXME: adjust it if the CSS width or height attribute is specified - // and applicable. - return sr; + if (r == null) + r = new SizeRequirements(); + + if (setCSSSpan(r, axis)) + { + // If we have set the span from CSS, then we need to adjust + // the margins. + SizeRequirements parent = super.calculateMinorAxisRequirements(axis, + null); + int margin = axis == X_AXIS ? getLeftInset() + getRightInset() + : getTopInset() + getBottomInset(); + r.minimum -= margin; + r.preferred -= margin; + r.maximum -= margin; + constrainSize(axis, r, parent); + } + else + r = super.calculateMinorAxisRequirements(axis, r); + + // Apply text alignment if appropriate. + if (axis == X_AXIS) + { + Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN); + if (o != null) + { + String al = o.toString().trim(); + if (al.equals("center")) + r.alignment = 0.5f; + else if (al.equals("right")) + r.alignment = 1.0f; + else + r.alignment = 0.0f; + } + } + return r; + } + + /** + * Sets the span on the SizeRequirements object according to the + * according CSS span value, when it is set. + * + * @param r the size requirements + * @param axis the axis + * + * @return <code>true</code> when the CSS span has been set, + * <code>false</code> otherwise + */ + private boolean setCSSSpan(SizeRequirements r, int axis) + { + boolean ret = false; + Length span = cssSpans[axis]; + // We can't set relative CSS spans here because we don't know + // yet about the allocated span. Instead we use the view's + // normal requirements. + if (span != null && ! span.isPercentage()) + { + r.minimum = (int) span.getValue(); + r.preferred = (int) span.getValue(); + r.maximum = (int) span.getValue(); + ret = true; + } + return ret; } - + + /** + * Constrains the <code>r</code> requirements according to + * <code>min</code>. + * + * @param axis the axis + * @param r the requirements to constrain + * @param min the constraining requirements + */ + private void constrainSize(int axis, SizeRequirements r, + SizeRequirements min) + { + if (min.minimum > r.minimum) + { + r.minimum = min.minimum; + r.preferred = min.minimum; + r.maximum = Math.max(r.maximum, min.maximum); + } + } + /** * Lays out the box along the minor axis (the axis that is * perpendicular to the axis that it represents). The results @@ -142,10 +338,133 @@ public class BlockView extends BoxView protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - // FIXME: Not implemented. - super.layoutMinorAxis(targetSpan, axis, offsets, spans); + int viewCount = getViewCount(); + for (int i = 0; i < viewCount; i++) + { + View view = getView(i); + int min = (int) view.getMinimumSpan(axis); + int max; + // Handle CSS span value of child. + Length length = cssSpans[axis]; + if (length != null) + { + min = Math.max((int) length.getValue(targetSpan), min); + max = min; + } + else + max = (int) view.getMaximumSpan(axis); + + if (max < targetSpan) + { + // Align child. + float align = view.getAlignment(axis); + offsets[i] = (int) ((targetSpan - max) * align); + spans[i] = max; + } + else + { + offsets[i] = 0; + spans[i] = Math.max(min, targetSpan); + } + + // Adjust according to CSS position info. + positionView(targetSpan, axis, i, offsets, spans); + } } - + + /** + * Overridden to perform additional CSS layout (absolute/relative + * positioning). + */ + protected void layoutMajorAxis(int targetSpan, int axis, + int[] offsets, int[] spans) + { + super.layoutMajorAxis(targetSpan, axis, offsets, spans); + + // Adjust according to CSS position info. + int viewCount = getViewCount(); + for (int i = 0; i < viewCount; i++) + { + positionView(targetSpan, axis, i, offsets, spans); + } + } + + /** + * Positions a view according to any additional CSS constraints. + * + * @param targetSpan the target span + * @param axis the axis + * @param i the index of the view + * @param offsets the offsets get placed here + * @param spans the spans get placed here + */ + private void positionView(int targetSpan, int axis, int i, int[] offsets, + int[] spans) + { + View view = getView(i); + PositionInfo pos = (PositionInfo) positionInfo.get(view); + if (pos != null) + { + int p0 = -1; + int p1 = -1; + if (axis == X_AXIS) + { + Length l = pos.left; + if (l != null) + p0 = (int) l.getValue(targetSpan); + l = pos.right; + if (l != null) + p1 = (int) l.getValue(targetSpan); + } + else + { + Length l = pos.top; + if (l != null) + p0 = (int) l.getValue(targetSpan); + l = pos.bottom; + if (l != null) + p1 = (int) l.getValue(targetSpan); + } + if (pos.type == PositionInfo.ABSOLUTE + || pos.type == PositionInfo.FIXED) + { + if (p0 != -1) + { + offsets[i] = p0; + if (p1 != -1) + { + // Overrides computed width. (Possibly overconstrained + // when the width attribute was set too.) + spans[i] = targetSpan - p1 - offsets[i]; + } + } + else if (p1 != -1) + { + // Preserve any computed width. + offsets[i] = targetSpan - p1 - spans[i]; + } + } + else if (pos.type == PositionInfo.RELATIVE) + { + if (p0 != -1) + { + offsets[i] += p0; + if (p1 != -1) + { + // Overrides computed width. (Possibly overconstrained + // when the width attribute was set too.) + spans[i] = spans[i] - p0 - p1 - offsets[i]; + } + } + else if (p1 != -1) + { + // Preserve any computed width. + offsets[i] -= p1; + } + } + } + } + /** * Paints using the given graphics configuration and shape. * This delegates to the css box painter to paint the @@ -156,14 +475,16 @@ public class BlockView extends BoxView */ public void paint(Graphics g, Shape a) { - Rectangle rect = (Rectangle) a; - // FIXME: not fully implemented - getStyleSheet().getBoxPainter(getAttributes()).paint(g, rect.x, rect.y, - rect.width, - rect.height, this); + Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + + // Debug output. Shows blocks in green rectangles. + // g.setColor(Color.GREEN); + // g.drawRect(rect.x, rect.y, rect.width, rect.height); + + painter.paint(g, rect.x, rect.y, rect.width, rect.height, this); super.paint(g, a); } - + /** * Fetches the attributes to use when painting. * @@ -171,7 +492,9 @@ public class BlockView extends BoxView */ public AttributeSet getAttributes() { - return getStyleSheet().getViewAttributes(this); + if (attributes == null) + attributes = getStyleSheet().getViewAttributes(this); + return attributes; } /** @@ -200,14 +523,17 @@ public class BlockView extends BoxView public float getAlignment(int axis) { if (axis == X_AXIS) - return 0.0F; + return super.getAlignment(axis); if (axis == Y_AXIS) { if (getViewCount() == 0) return 0.0F; float prefHeight = getPreferredSpan(Y_AXIS); - float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS); - return (firstRowHeight / 2.F) / prefHeight; + View first = getView(0); + float firstRowHeight = first.getPreferredSpan(Y_AXIS); + return prefHeight != 0 ? (firstRowHeight * first.getAlignment(Y_AXIS)) + / prefHeight + : 0; } throw new IllegalArgumentException("Invalid Axis"); } @@ -227,7 +553,8 @@ public class BlockView extends BoxView // If more elements were added, then need to set the properties for them int currPos = ev.getOffset(); - if (currPos <= getStartOffset() && (currPos + ev.getLength()) >= getEndOffset()) + if (currPos <= getStartOffset() + && (currPos + ev.getLength()) >= getEndOffset()) setPropertiesFromAttributes(); } @@ -284,9 +611,33 @@ public class BlockView extends BoxView */ protected void setPropertiesFromAttributes() { - // FIXME: Not implemented (need to use StyleSheet). + // Fetch attributes. + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + + // Fetch painter. + painter = ss.getBoxPainter(attributes); + + // Update insets. + if (attributes != null) + { + setInsets((short) painter.getInset(TOP, this), + (short) painter.getInset(LEFT, this), + (short) painter.getInset(BOTTOM, this), + (short) painter.getInset(RIGHT, this)); + } + + // Fetch width and height. + float emBase = ss.getEMBase(attributes); + float exBase = ss.getEXBase(attributes); + cssSpans[X_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.WIDTH); + if (cssSpans[X_AXIS] != null) + cssSpans[X_AXIS].setFontBases(emBase, exBase); + cssSpans[Y_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.HEIGHT); + if (cssSpans[Y_AXIS] != null) + cssSpans[Y_AXIS].setFontBases(emBase, exBase); } - + /** * Gets the default style sheet. * @@ -294,8 +645,77 @@ public class BlockView extends BoxView */ protected StyleSheet getStyleSheet() { - StyleSheet styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(HTMLEditorKit.DEFAULT_CSS)); - return styleSheet; + HTMLDocument doc = (HTMLDocument) getDocument(); + return doc.getStyleSheet(); + } + + /** + * Overridden to fetch additional CSS layout information. + */ + public void replace(int offset, int length, View[] views) + { + // First remove unneeded stuff. + for (int i = 0; i < length; i++) + { + View child = getView(i + offset); + positionInfo.remove(child); + } + + // Call super to actually replace the views. + super.replace(offset, length, views); + + // Now fetch the position infos for the new views. + for (int i = 0; i < views.length; i++) + { + fetchLayoutInfo(views[i]); + } + } + + /** + * Fetches and stores the layout info for the specified view. + * + * @param view the view for which the layout info is stored + */ + private void fetchLayoutInfo(View view) + { + AttributeSet atts = view.getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.POSITION); + if (o != null && o instanceof String && ! o.equals("static")) + { + int type; + if (o.equals("relative")) + type = PositionInfo.RELATIVE; + else if (o.equals("absolute")) + type = PositionInfo.ABSOLUTE; + else if (o.equals("fixed")) + type = PositionInfo.FIXED; + else + type = PositionInfo.STATIC; + + if (type != PositionInfo.STATIC) + { + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + Length left = (Length) atts.getAttribute(CSS.Attribute.LEFT); + if (left != null) + left.setFontBases(emBase, exBase); + Length right = (Length) atts.getAttribute(CSS.Attribute.RIGHT); + if (right != null) + right.setFontBases(emBase, exBase); + Length top = (Length) atts.getAttribute(CSS.Attribute.TOP); + if (top != null) + top.setFontBases(emBase, exBase); + Length bottom = (Length) atts.getAttribute(CSS.Attribute.BOTTOM); + if (bottom != null) + bottom.setFontBases(emBase, exBase); + if (left != null || right != null || top != null || bottom != null) + { + PositionInfo pos = new PositionInfo(type, left, right, top, + bottom); + positionInfo.put(view, pos); + } + } + } } } diff --git a/libjava/classpath/javax/swing/text/html/CSS.java b/libjava/classpath/javax/swing/text/html/CSS.java index c248e758ec2..77f94a60878 100644 --- a/libjava/classpath/javax/swing/text/html/CSS.java +++ b/libjava/classpath/javax/swing/text/html/CSS.java @@ -37,8 +37,19 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.css.BorderStyle; +import gnu.javax.swing.text.html.css.BorderWidth; +import gnu.javax.swing.text.html.css.CSSColor; +import gnu.javax.swing.text.html.css.FontSize; +import gnu.javax.swing.text.html.css.FontStyle; +import gnu.javax.swing.text.html.css.FontWeight; +import gnu.javax.swing.text.html.css.Length; + import java.io.Serializable; import java.util.HashMap; +import java.util.StringTokenizer; + +import javax.swing.text.MutableAttributeSet; /** * Provides CSS attributes to be used by the HTML view classes. The constants @@ -388,6 +399,36 @@ public class CSS implements Serializable public static final Attribute WORD_SPACING = new Attribute("word-spacing", true, "normal"); + // Some GNU Classpath specific extensions. + static final Attribute BORDER_TOP_STYLE = + new Attribute("border-top-style", false, null); + static final Attribute BORDER_BOTTOM_STYLE = + new Attribute("border-bottom-style", false, null); + static final Attribute BORDER_LEFT_STYLE = + new Attribute("border-left-style", false, null); + static final Attribute BORDER_RIGHT_STYLE = + new Attribute("border-right-style", false, null); + static final Attribute BORDER_TOP_COLOR = + new Attribute("border-top-color", false, null); + static final Attribute BORDER_BOTTOM_COLOR = + new Attribute("border-bottom-color", false, null); + static final Attribute BORDER_LEFT_COLOR = + new Attribute("border-left-color", false, null); + static final Attribute BORDER_RIGHT_COLOR = + new Attribute("border-right-color", false, null); + static final Attribute BORDER_SPACING = + new Attribute("border-spacing", false, null); + static final Attribute POSITION = + new Attribute("position", false, null); + static final Attribute LEFT = + new Attribute("left", false, null); + static final Attribute RIGHT = + new Attribute("right", false, null); + static final Attribute TOP = + new Attribute("top", false, null); + static final Attribute BOTTOM = + new Attribute("bottom", false, null); + /** * The attribute string. */ @@ -459,4 +500,237 @@ public class CSS implements Serializable return defaultValue; } } + + /** + * Maps attribute values (String) to some converter class, based on the + * key. + * + * @param att the key + * @param v the value + * + * @return the wrapped value + */ + static Object getValue(Attribute att, String v) + { + Object o; + if (att == Attribute.FONT_SIZE) + o = new FontSize(v); + else if (att == Attribute.FONT_WEIGHT) + o = new FontWeight(v); + else if (att == Attribute.FONT_STYLE) + o = new FontStyle(v); + else if (att == Attribute.COLOR || att == Attribute.BACKGROUND_COLOR + || att == Attribute.BORDER_COLOR + || att == Attribute.BORDER_TOP_COLOR + || att == Attribute.BORDER_BOTTOM_COLOR + || att == Attribute.BORDER_LEFT_COLOR + || att == Attribute.BORDER_RIGHT_COLOR) + o = new CSSColor(v); + else if (att == Attribute.MARGIN || att == Attribute.MARGIN_BOTTOM + || att == Attribute.MARGIN_LEFT || att == Attribute.MARGIN_RIGHT + || att == Attribute.MARGIN_TOP || att == Attribute.WIDTH + || att == Attribute.HEIGHT + || att == Attribute.PADDING || att == Attribute.PADDING_BOTTOM + || att == Attribute.PADDING_LEFT || att == Attribute.PADDING_RIGHT + || att == Attribute.PADDING_TOP + || att == Attribute.LEFT || att == Attribute.RIGHT + || att == Attribute.TOP || att == Attribute.BOTTOM) + o = new Length(v); + else if (att == Attribute.BORDER_WIDTH || att == Attribute.BORDER_TOP_WIDTH + || att == Attribute.BORDER_LEFT_WIDTH + || att == Attribute.BORDER_RIGHT_WIDTH + || att == Attribute.BORDER_BOTTOM_WIDTH) + o = new BorderWidth(v); + else + o = v; + return o; + } + + static void addInternal(MutableAttributeSet atts, Attribute a, String v) + { + if (a == Attribute.BACKGROUND) + parseBackgroundShorthand(atts, v); + else if (a == Attribute.PADDING) + parsePaddingShorthand(atts, v); + else if (a == Attribute.MARGIN) + parseMarginShorthand(atts, v); + else if (a == Attribute.BORDER || a == Attribute.BORDER_LEFT + || a == Attribute.BORDER_RIGHT || a == Attribute.BORDER_TOP + || a == Attribute.BORDER_BOTTOM) + parseBorderShorthand(atts, v, a); + } + + /** + * Parses the background shorthand and translates it to more specific + * background attributes. + * + * @param atts the attributes + * @param v the value + */ + private static void parseBackgroundShorthand(MutableAttributeSet atts, + String v) + { + StringTokenizer tokens = new StringTokenizer(v, " "); + while (tokens.hasMoreElements()) + { + String token = tokens.nextToken(); + if (CSSColor.isValidColor(token)) + atts.addAttribute(Attribute.BACKGROUND_COLOR, + new CSSColor(token)); + } + } + + /** + * Parses the padding shorthand and translates to the specific padding + * values. + * + * @param atts the attributes + * @param v the actual value + */ + private static void parsePaddingShorthand(MutableAttributeSet atts, String v) + { + StringTokenizer tokens = new StringTokenizer(v, " "); + int numTokens = tokens.countTokens(); + if (numTokens == 1) + { + Length l = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_BOTTOM, l); + atts.addAttribute(Attribute.PADDING_LEFT, l); + atts.addAttribute(Attribute.PADDING_RIGHT, l); + atts.addAttribute(Attribute.PADDING_TOP, l); + } + else if (numTokens == 2) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_BOTTOM, l1); + atts.addAttribute(Attribute.PADDING_TOP, l1); + atts.addAttribute(Attribute.PADDING_LEFT, l2); + atts.addAttribute(Attribute.PADDING_RIGHT, l2); + } + else if (numTokens == 3) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_TOP, l1); + atts.addAttribute(Attribute.PADDING_LEFT, l2); + atts.addAttribute(Attribute.PADDING_RIGHT, l2); + atts.addAttribute(Attribute.PADDING_BOTTOM, l3); + } + else + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + Length l4 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.PADDING_TOP, l1); + atts.addAttribute(Attribute.PADDING_RIGHT, l2); + atts.addAttribute(Attribute.PADDING_BOTTOM, l3); + atts.addAttribute(Attribute.PADDING_LEFT, l4); + } + } + + /** + * Parses the margin shorthand and translates to the specific margin + * values. + * + * @param atts the attributes + * @param v the actual value + */ + private static void parseMarginShorthand(MutableAttributeSet atts, String v) + { + StringTokenizer tokens = new StringTokenizer(v, " "); + int numTokens = tokens.countTokens(); + if (numTokens == 1) + { + Length l = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l); + atts.addAttribute(Attribute.MARGIN_LEFT, l); + atts.addAttribute(Attribute.MARGIN_RIGHT, l); + atts.addAttribute(Attribute.MARGIN_TOP, l); + } + else if (numTokens == 2) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l1); + atts.addAttribute(Attribute.MARGIN_TOP, l1); + atts.addAttribute(Attribute.MARGIN_LEFT, l2); + atts.addAttribute(Attribute.MARGIN_RIGHT, l2); + } + else if (numTokens == 3) + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_TOP, l1); + atts.addAttribute(Attribute.MARGIN_LEFT, l2); + atts.addAttribute(Attribute.MARGIN_RIGHT, l2); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l3); + } + else + { + Length l1 = new Length(tokens.nextToken()); + Length l2 = new Length(tokens.nextToken()); + Length l3 = new Length(tokens.nextToken()); + Length l4 = new Length(tokens.nextToken()); + atts.addAttribute(Attribute.MARGIN_TOP, l1); + atts.addAttribute(Attribute.MARGIN_RIGHT, l2); + atts.addAttribute(Attribute.MARGIN_BOTTOM, l3); + atts.addAttribute(Attribute.MARGIN_LEFT, l4); + } + } + + /** + * Parses the CSS border shorthand attribute and translates it to the + * more specific border attributes. + * + * @param atts the attribute + * @param value the value + */ + private static void parseBorderShorthand(MutableAttributeSet atts, + String value, Attribute cssAtt) + { + StringTokenizer tokens = new StringTokenizer(value, " "); + while (tokens.hasMoreTokens()) + { + String token = tokens.nextToken(); + if (BorderStyle.isValidStyle(token)) + { + if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_LEFT_STYLE, token); + if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_RIGHT_STYLE, token); + if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_BOTTOM_STYLE, token); + if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_TOP_STYLE, token); + } + else if (BorderWidth.isValid(token)) + { + BorderWidth w = new BorderWidth(token); + if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_LEFT_WIDTH, w); + if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_RIGHT_WIDTH, w); + if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_BOTTOM_WIDTH, w); + if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_TOP_WIDTH, w); + } + else if (CSSColor.isValidColor(token)) + { + CSSColor c = new CSSColor(token); + if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_LEFT_COLOR, c); + if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_RIGHT_COLOR, c); + if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_BOTTOM_COLOR, c); + if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER) + atts.addAttribute(Attribute.BORDER_TOP_COLOR, c); + } + } + } } diff --git a/libjava/classpath/javax/swing/text/html/CSSBorder.java b/libjava/classpath/javax/swing/text/html/CSSBorder.java new file mode 100644 index 00000000000..fff6b01a170 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/CSSBorder.java @@ -0,0 +1,421 @@ +/* CSSBorder.java -- A border for rendering CSS border styles + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import gnu.javax.swing.text.html.css.BorderWidth; +import gnu.javax.swing.text.html.css.CSSColor; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Insets; + +import javax.swing.border.Border; +import javax.swing.text.AttributeSet; + +/** + * A border implementation to render CSS border styles. + */ +class CSSBorder + implements Border +{ + + /** + * The CSS border styles. + */ + + private static final int STYLE_NOT_SET = -1; + private static final int STYLE_NONE = 0; + private static final int STYLE_HIDDEN = 1; + private static final int STYLE_DOTTED = 2; + private static final int STYLE_DASHED = 3; + private static final int STYLE_SOLID = 4; + private static final int STYLE_DOUBLE = 5; + private static final int STYLE_GROOVE = 6; + private static final int STYLE_RIDGE = 7; + private static final int STYLE_INSET = 8; + private static final int STYLE_OUTSET = 9; + + /** + * The left insets. + */ + private int left; + + /** + * The right insets. + */ + private int right; + + /** + * The top insets. + */ + private int top; + + /** + * The bottom insets. + */ + private int bottom; + + /** + * The border style on the left. + */ + private int leftStyle; + + /** + * The border style on the right. + */ + private int rightStyle; + + /** + * The border style on the top. + */ + private int topStyle; + + /** + * The color for the top border. + */ + private Color topColor; + + /** + * The color for the bottom border. + */ + private Color bottomColor; + + /** + * The color for the left border. + */ + private Color leftColor; + + /** + * The color for the right border. + */ + private Color rightColor; + + /** + * The border style on the bottom. + */ + private int bottomStyle; + + /** + * Creates a new CSS border and fetches its attributes from the specified + * attribute set. + * + * @param atts the attribute set that contains the border spec + */ + CSSBorder(AttributeSet atts, StyleSheet ss) + { + // Determine the border styles. + int style = getBorderStyle(atts, CSS.Attribute.BORDER_STYLE); + if (style == STYLE_NOT_SET) + style = STYLE_NONE; // Default to none. + topStyle = bottomStyle = leftStyle = rightStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_TOP_STYLE); + if (style != STYLE_NOT_SET) + topStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_BOTTOM_STYLE); + if (style != STYLE_NOT_SET) + bottomStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_LEFT_STYLE); + if (style != STYLE_NOT_SET) + leftStyle = style; + style = getBorderStyle(atts, CSS.Attribute.BORDER_RIGHT_STYLE); + if (style != STYLE_NOT_SET) + rightStyle = style; + + // Determine the border colors. + Color color = getBorderColor(atts, CSS.Attribute.BORDER_COLOR); + if (color == null) + color = Color.BLACK; + topColor = bottomColor = leftColor = rightColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_TOP_COLOR); + if (color != null) + topColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_BOTTOM_COLOR); + if (color != null) + bottomColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_LEFT_COLOR); + if (color != null) + leftColor = color; + color = getBorderColor(atts, CSS.Attribute.BORDER_RIGHT_COLOR); + if (color != null) + rightColor = color; + + // Determine the border widths. + int width = getBorderWidth(atts, CSS.Attribute.BORDER_WIDTH, ss); + if (width == -1) + width = 0; + top = bottom = left = right = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_TOP_WIDTH, ss); + if (width >= 0) + top = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_BOTTOM_WIDTH, ss); + if (width >= 0) + bottom = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_LEFT_WIDTH, ss); + if (width >= 0) + left = width; + width = getBorderWidth(atts, CSS.Attribute.BORDER_RIGHT_WIDTH, ss); + if (width >= 0) + right = width; + } + + /** + * Determines the border style for a given CSS attribute. + * + * @param atts the attribute set + * @param key the CSS key + * + * @return the border style according to the constants defined in this class + */ + private int getBorderStyle(AttributeSet atts, CSS.Attribute key) + { + int style = STYLE_NOT_SET; + Object o = atts.getAttribute(key); + if (o != null) + { + String cssStyle = o.toString(); + if (cssStyle.equals("none")) + style = STYLE_NONE; + else if (cssStyle.equals("hidden")) + style = STYLE_HIDDEN; + else if (cssStyle.equals("dotted")) + style = STYLE_DOTTED; + else if (cssStyle.equals("dashed")) + style = STYLE_DASHED; + else if (cssStyle.equals("solid")) + style = STYLE_SOLID; + else if (cssStyle.equals("double")) + style = STYLE_DOUBLE; + else if (cssStyle.equals("groove")) + style = STYLE_GROOVE; + else if (cssStyle.equals("ridge")) + style = STYLE_RIDGE; + else if (cssStyle.equals("inset")) + style = STYLE_INSET; + else if (cssStyle.equals("outset")) + style = STYLE_OUTSET; + } + return style; + } + + /** + * Determines the border color for the specified key. + * + * @param atts the attribute set from which to fetch the color + * @param key the CSS key + * + * @return the border color + */ + private Color getBorderColor(AttributeSet atts, CSS.Attribute key) + { + Object o = atts.getAttribute(key); + Color color = null; + if (o instanceof CSSColor) + { + CSSColor cssColor = (CSSColor) o; + color = cssColor.getValue(); + } + return color; + } + + /** + * Returns the width for the specified key. + * + * @param atts the attributes to fetch the width from + * @param key the CSS key + * + * @return the width, or -1 of none has been set + */ + private int getBorderWidth(AttributeSet atts, CSS.Attribute key, + StyleSheet ss) + { + int width = -1; + Object o = atts.getAttribute(key); + if (o instanceof BorderWidth) + { + BorderWidth w = (BorderWidth) o; + w.setFontBases(ss.getEMBase(atts), ss.getEXBase(atts)); + width = (int) ((BorderWidth) o).getValue(); + } + return width; + } + + /** + * Returns the border insets. + */ + public Insets getBorderInsets(Component c) + { + return new Insets(top, left, bottom, right); + } + + /** + * CSS borders are generally opaque so return true here. + */ + public boolean isBorderOpaque() + { + return true; + } + + public void paintBorder(Component c, Graphics g, int x, int y, int width, + int height) + { + // Top border. + paintBorderLine(g, x, y + top / 2, x + width, y + top / 2, topStyle, top, + topColor, false); + // Left border. + paintBorderLine(g, x + left / 2, y, x + left / 2, y + height, leftStyle, + left, leftColor, true); + // Bottom border. + paintBorderLine(g, x, y + height - bottom / 2, x + width, + y + height - bottom / 2, topStyle, bottom, bottomColor, + false); + // Right border. + paintBorderLine(g, x + width - right / 2, y, x + width - right / 2, + y + height, topStyle, right, rightColor, true); + + } + + private void paintBorderLine(Graphics g, int x1, int y1, int x2, int y2, + int style, int width, Color color, + boolean vertical) + { + switch (style) + { + case STYLE_DOTTED: + paintDottedLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_DASHED: + paintDashedLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_SOLID: + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_DOUBLE: + paintDoubleLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_GROOVE: + paintGrooveLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_RIDGE: + paintRidgeLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_OUTSET: + paintOutsetLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_INSET: + paintInsetLine(g, x1, y1, x2, y2, width, color, vertical); + break; + case STYLE_NONE: + case STYLE_HIDDEN: + default: + // Nothing to do in these cases. + } + } + + private void paintDottedLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintDashedLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintSolidLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + int x = Math.min(x1, x2); + int y = Math.min(y1, y1); + int w = Math.abs(x2 - x1); + int h = Math.abs(y2 - y1); + if (vertical) + { + w = width; + x -= width / 2; + } + else + { + h = width; + y -= width / 2; + } + g.setColor(color); + g.fillRect(x, y, w, h); + } + + private void paintDoubleLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintGrooveLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintRidgeLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintOutsetLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + + private void paintInsetLine(Graphics g, int x1, int y1, int x2, int y2, + int width, Color color, boolean vertical) + { + // FIXME: Implement this. + paintSolidLine(g, x1, y1, x2, y2, width, color, vertical); + } + +} diff --git a/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java new file mode 100644 index 00000000000..bc7c36f4b27 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java @@ -0,0 +1,123 @@ +/* FormSubmitEvent.java -- Event fired on form submit + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.net.URL; + +import javax.swing.text.Element; + +/** + * The event fired on form submit. + * + * @since 1.5 + */ +public class FormSubmitEvent + extends HTMLFrameHyperlinkEvent +{ + + // FIXME: Use enums when available. + /** + * The submit method. + */ + public static class MethodType + { + /** + * Indicates a form submit with HTTP method POST. + */ + public static final MethodType POST = new MethodType(); + + /** + * Indicates a form submit with HTTP method GET. + */ + public static final MethodType GET = new MethodType(); + + private MethodType() + { + } + } + + /** + * The submit method. + */ + private MethodType method; + + /** + * The actual submit data. + */ + private String data; + + /** + * Creates a new FormSubmitEvent. + * + * @param source the source + * @param type the type of hyperlink update + * @param url the action url + * @param el the associated element + * @param target the target attribute + * @param m the submit method + * @param d the submit data + */ + FormSubmitEvent(Object source, EventType type, URL url, Element el, + String target, MethodType m, String d) + { + super(source, type, url, el, target); + method = m; + data = d; + } + + /** + * Returns the submit data. + * + * @return the submit data + */ + public String getData() + { + return data; + } + + /** + * Returns the HTTP submit method. + * + * @return the HTTP submit method + */ + public MethodType getMethod() + { + return method; + } +} diff --git a/libjava/classpath/javax/swing/text/html/FormView.java b/libjava/classpath/javax/swing/text/html/FormView.java index d54021066d0..ef362bd3d9b 100644 --- a/libjava/classpath/javax/swing/text/html/FormView.java +++ b/libjava/classpath/javax/swing/text/html/FormView.java @@ -44,16 +44,36 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import javax.swing.ButtonModel; +import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JEditorPane; +import javax.swing.JList; import javax.swing.JPasswordField; import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.event.HyperlinkEvent; import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; import javax.swing.text.ComponentView; +import javax.swing.text.Document; import javax.swing.text.Element; +import javax.swing.text.ElementIterator; import javax.swing.text.StyleConstants; /** @@ -105,6 +125,231 @@ public class FormView } /** + * Actually submits the form data. + */ + private class SubmitThread + extends Thread + { + /** + * The submit data. + */ + private String data; + + /** + * Creates a new SubmitThread. + * + * @param d the submit data + */ + SubmitThread(String d) + { + data = d; + } + + /** + * Actually performs the submit. + */ + public void run() + { + if (data.length() > 0) + { + final String method = getMethod(); + final URL actionURL = getActionURL(); + final String target = getTarget(); + URLConnection conn; + final JEditorPane editor = (JEditorPane) getContainer(); + final HTMLDocument doc = (HTMLDocument) editor.getDocument(); + HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit(); + if (kit.isAutoFormSubmission()) + { + try + { + final URL url; + if (method != null && method.equals("post")) + { + // Perform POST. + url = actionURL; + conn = url.openConnection(); + postData(conn, data); + } + else + { + // Default to GET. + url = new URL(actionURL + "?" + data); + } + Runnable loadDoc = new Runnable() + { + public void run() + { + if (doc.isFrameDocument()) + { + editor.fireHyperlinkUpdate(createSubmitEvent(method, + actionURL, + target)); + } + else + { + try + { + editor.setPage(url); + } + catch (IOException ex) + { + // Oh well. + ex.printStackTrace(); + } + } + } + }; + SwingUtilities.invokeLater(loadDoc); + } + catch (MalformedURLException ex) + { + ex.printStackTrace(); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + else + { + editor.fireHyperlinkUpdate(createSubmitEvent(method,actionURL, + target)); + } + } + } + + /** + * Determines the submit method. + * + * @return the submit method + */ + private String getMethod() + { + AttributeSet formAtts = getFormAttributes(); + String method = null; + if (formAtts != null) + { + method = (String) formAtts.getAttribute(HTML.Attribute.METHOD); + } + return method; + } + + /** + * Determines the action URL. + * + * @return the action URL + */ + private URL getActionURL() + { + AttributeSet formAtts = getFormAttributes(); + HTMLDocument doc = (HTMLDocument) getElement().getDocument(); + URL url = doc.getBase(); + if (formAtts != null) + { + String action = + (String) formAtts.getAttribute(HTML.Attribute.ACTION); + if (action != null) + { + try + { + url = new URL(url, action); + } + catch (MalformedURLException ex) + { + url = null; + } + } + } + return url; + } + + /** + * Fetches the target attribute. + * + * @return the target attribute or _self if none is present + */ + private String getTarget() + { + AttributeSet formAtts = getFormAttributes(); + String target = null; + if (formAtts != null) + { + target = (String) formAtts.getAttribute(HTML.Attribute.TARGET); + if (target != null) + target = target.toLowerCase(); + } + if (target == null) + target = "_self"; + return target; + } + + /** + * Posts the form data over the specified connection. + * + * @param conn the connection + */ + private void postData(URLConnection conn, String data) + { + conn.setDoOutput(true); + PrintWriter out = null; + try + { + out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream())); + out.print(data); + out.flush(); + } + catch (IOException ex) + { + // Deal with this! + ex.printStackTrace(); + } + finally + { + if (out != null) + out.close(); + } + } + + /** + * Determines the attributes from the relevant form tag. + * + * @return the attributes from the relevant form tag, <code>null</code> + * when there is no form tag + */ + private AttributeSet getFormAttributes() + { + AttributeSet atts = null; + Element form = getFormElement(); + if (form != null) + atts = form.getAttributes(); + return atts; + } + + /** + * Creates the submit event that should be fired. + * + * This is package private to avoid accessor methods. + * + * @param method the submit method + * @param actionURL the action URL + * @param target the target + * + * @return the submit event + */ + FormSubmitEvent createSubmitEvent(String method, URL actionURL, + String target) + { + FormSubmitEvent.MethodType m = "post".equals(method) + ? FormSubmitEvent.MethodType.POST + : FormSubmitEvent.MethodType.GET; + return new FormSubmitEvent(FormView.this, + HyperlinkEvent.EventType.ACTIVATED, + actionURL, getElement(), target, m, data); + } + } + + /** * If the value attribute of an <code><input type="submit">> * tag is not specified, then this string is used. * @@ -125,6 +370,11 @@ public class FormView UIManager.getString("FormView.resetButtonText"); /** + * If this is true, the maximum size is set to the preferred size. + */ + private boolean maxIsPreferred; + + /** * Creates a new <code>FormView</code>. * * @param el the element that is displayed by this view. @@ -141,39 +391,161 @@ public class FormView { Component comp = null; Element el = getElement(); - Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute); + AttributeSet atts = el.getAttributes(); + Object tag = atts.getAttribute(StyleConstants.NameAttribute); + Object model = atts.getAttribute(StyleConstants.ModelAttribute); if (tag.equals(HTML.Tag.INPUT)) { - AttributeSet atts = el.getAttributes(); String type = (String) atts.getAttribute(HTML.Attribute.TYPE); - String value = (String) atts.getAttribute(HTML.Attribute.VALUE); if (type.equals("button")) - comp = new JButton(value); + { + String value = (String) atts.getAttribute(HTML.Attribute.VALUE); + JButton b = new JButton(value); + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; + } else if (type.equals("checkbox")) - comp = new JCheckBox(value); + { + if (model instanceof ResetableToggleButtonModel) + { + ResetableToggleButtonModel m = + (ResetableToggleButtonModel) model; + JCheckBox c = new JCheckBox(); + c.setModel(m); + comp = c; + maxIsPreferred = true; + } + } else if (type.equals("image")) - comp = new JButton(value); // FIXME: Find out how to fetch the image. + { + String src = (String) atts.getAttribute(HTML.Attribute.SRC); + JButton b; + try + { + URL base = ((HTMLDocument) el.getDocument()).getBase(); + URL srcURL = new URL(base, src); + ImageIcon icon = new ImageIcon(srcURL); + b = new JButton(icon); + } + catch (MalformedURLException ex) + { + b = new JButton(src); + } + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; + } else if (type.equals("password")) - comp = new JPasswordField(value); + { + int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, + -1); + JTextField tf = new JPasswordField(); + if (size > 0) + tf.setColumns(size); + else + tf.setColumns(20); + if (model != null) + tf.setDocument((Document) model); + tf.addActionListener(this); + comp = tf; + maxIsPreferred = true; + } else if (type.equals("radio")) - comp = new JRadioButton(value); + { + if (model instanceof ResetableToggleButtonModel) + { + ResetableToggleButtonModel m = + (ResetableToggleButtonModel) model; + JRadioButton c = new JRadioButton(); + c.setModel(m); + comp = c; + maxIsPreferred = true; + } + } else if (type.equals("reset")) { - if (value == null || value.equals("")) - value = RESET; - comp = new JButton(value); + String value = (String) atts.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = UIManager.getString("FormView.resetButtonText"); + JButton b = new JButton(value); + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; } else if (type.equals("submit")) { - if (value == null || value.equals("")) - value = SUBMIT; - comp = new JButton(value); + String value = (String) atts.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = UIManager.getString("FormView.submitButtonText"); + JButton b = new JButton(value); + if (model != null) + { + b.setModel((ButtonModel) model); + b.addActionListener(this); + } + comp = b; + maxIsPreferred = true; } else if (type.equals("text")) - comp = new JTextField(value); - + { + int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, + -1); + JTextField tf = new JTextField(); + if (size > 0) + tf.setColumns(size); + else + tf.setColumns(20); + if (model != null) + tf.setDocument((Document) model); + tf.addActionListener(this); + comp = tf; + maxIsPreferred = true; + } + } + else if (tag == HTML.Tag.TEXTAREA) + { + JTextArea textArea = new JTextArea((Document) model); + int rows = HTML.getIntegerAttributeValue(atts, HTML.Attribute.ROWS, 1); + textArea.setRows(rows); + int cols = HTML.getIntegerAttributeValue(atts, HTML.Attribute.COLS, 20); + textArea.setColumns(cols); + maxIsPreferred = true; + comp = new JScrollPane(textArea, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + } + else if (tag == HTML.Tag.SELECT) + { + if (model instanceof SelectListModel) + { + SelectListModel slModel = (SelectListModel) model; + JList list = new JList(slModel); + int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE, + 1); + list.setVisibleRowCount(size); + list.setSelectionModel(slModel.getSelectionModel()); + comp = new JScrollPane(list); + } + else if (model instanceof SelectComboBoxModel) + { + SelectComboBoxModel scbModel = (SelectComboBoxModel) model; + comp = new JComboBox(scbModel); + } + maxIsPreferred = true; } - // FIXME: Implement the remaining components. return comp; } @@ -188,16 +560,11 @@ public class FormView */ public float getMaximumSpan(int axis) { - // FIXME: The specs say that for some components the maximum span == the - // preferred span of the component. This should be figured out and - // implemented accordingly. float span; - if (axis == X_AXIS) - span = getComponent().getMaximumSize().width; - else if (axis == Y_AXIS) - span = getComponent().getMaximumSize().height; + if (maxIsPreferred) + span = getPreferredSpan(axis); else - throw new IllegalArgumentException("Invalid axis parameter"); + span = super.getMaximumSpan(axis); return span; } @@ -222,7 +589,9 @@ public class FormView AttributeSet atts = el.getAttributes(); String type = (String) atts.getAttribute(HTML.Attribute.TYPE); if (type.equals("submit")) - submitData(""); // FIXME: How to fetch the actual form data? + submitData(getFormData()); + else if (type.equals("reset")) + resetForm(); } // FIXME: Implement the remaining actions. } @@ -235,7 +604,8 @@ public class FormView */ protected void submitData(String data) { - // FIXME: Implement this. + SubmitThread submitThread = new SubmitThread(data); + submitThread.start(); } /** @@ -272,4 +642,229 @@ public class FormView } return data; } + + /** + * Determines and returns the enclosing form element if there is any. + * + * This is package private to avoid accessor methods. + * + * @return the enclosing form element, or <code>null</code> if there is no + * enclosing form element + */ + Element getFormElement() + { + Element form = null; + Element el = getElement(); + while (el != null && form == null) + { + AttributeSet atts = el.getAttributes(); + if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FORM) + form = el; + else + el = el.getParentElement(); + } + return form; + } + + /** + * Determines the form data that is about to be submitted. + * + * @return the form data + */ + private String getFormData() + { + Element form = getFormElement(); + StringBuilder b = new StringBuilder(); + if (form != null) + { + ElementIterator i = new ElementIterator(form); + Element next; + while ((next = i.next()) != null) + { + if (next.isLeaf()) + { + AttributeSet atts = next.getAttributes(); + String type = (String) atts.getAttribute(HTML.Attribute.TYPE); + if (type != null && type.equals("submit") + && next != getElement()) + { + // Skip this. This is not the actual submit trigger. + } + else if (type == null || ! type.equals("image")) + { + getElementFormData(next, b); + } + } + } + } + return b.toString(); + } + + /** + * Fetches the form data from the specified element and appends it to + * the data string. + * + * @param el the element from which to fetch form data + * @param b the data string + */ + private void getElementFormData(Element el, StringBuilder b) + { + AttributeSet atts = el.getAttributes(); + String name = (String) atts.getAttribute(HTML.Attribute.NAME); + if (name != null) + { + String value = null; + HTML.Tag tag = (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); + if (tag == HTML.Tag.SELECT) + { + getSelectData(atts, b); + } + else + { + if (tag == HTML.Tag.INPUT) + value = getInputFormData(atts); + else if (tag == HTML.Tag.TEXTAREA) + value = getTextAreaData(atts); + if (name != null && value != null) + { + addData(b, name, value); + } + } + } + } + + /** + * Fetches form data from select boxes. + * + * @param atts the attributes of the element + * + * @param b the form data string to append to + */ + private void getSelectData(AttributeSet atts, StringBuilder b) + { + String name = (String) atts.getAttribute(HTML.Attribute.NAME); + if (name != null) + { + Object m = atts.getAttribute(StyleConstants.ModelAttribute); + if (m instanceof SelectListModel) + { + SelectListModel sl = (SelectListModel) m; + ListSelectionModel lsm = sl.getSelectionModel(); + for (int i = 0; i < sl.getSize(); i++) + { + if (lsm.isSelectedIndex(i)) + { + Option o = (Option) sl.getElementAt(i); + addData(b, name, o.getValue()); + } + } + } + else if (m instanceof SelectComboBoxModel) + { + SelectComboBoxModel scb = (SelectComboBoxModel) m; + Option o = (Option) scb.getSelectedItem(); + if (o != null) + addData(b, name, o.getValue()); + } + } + } + + /** + * Fetches form data from a textarea. + * + * @param atts the attributes + * + * @return the form data + */ + private String getTextAreaData(AttributeSet atts) + { + Document doc = (Document) atts.getAttribute(StyleConstants.ModelAttribute); + String data; + try + { + data = doc.getText(0, doc.getLength()); + } + catch (BadLocationException ex) + { + data = null; + } + return data; + } + + /** + * Fetches form data from an input tag. + * + * @param atts the attributes from which to fetch the data + * + * @return the field value + */ + private String getInputFormData(AttributeSet atts) + { + String type = (String) atts.getAttribute(HTML.Attribute.TYPE); + Object model = atts.getAttribute(StyleConstants.ModelAttribute); + String value = null; + if (type.equals("text") || type.equals("password")) + { + Document doc = (Document) model; + try + { + value = doc.getText(0, doc.getLength()); + } + catch (BadLocationException ex) + { + // Sigh. + assert false; + } + } + else if (type.equals("hidden") || type.equals("submit")) + { + value = (String) atts.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = ""; + } + // TODO: Implement the others. radio, checkbox and file. + return value; + } + + /** + * Actually adds the specified data to the string. It URL encodes + * the name and value and handles separation of the fields. + * + * @param b the string at which the form data to be added + * @param name the name of the field + * @param value the value + */ + private void addData(StringBuilder b, String name, String value) + { + if (b.length() > 0) + b.append('&'); + String encName = URLEncoder.encode(name); + b.append(encName); + b.append('='); + String encValue = URLEncoder.encode(value); + b.append(encValue); + } + + /** + * Resets the form data to their initial state. + */ + private void resetForm() + { + Element form = getFormElement(); + if (form != null) + { + ElementIterator iter = new ElementIterator(form); + Element next; + while ((next = iter.next()) != null) + { + if (next.isLeaf()) + { + AttributeSet atts = next.getAttributes(); + Object m = atts.getAttribute(StyleConstants.ModelAttribute); + if (m instanceof ResetableModel) + ((ResetableModel) m).reset(); + } + } + } + } } diff --git a/libjava/classpath/javax/swing/text/html/FrameSetView.java b/libjava/classpath/javax/swing/text/html/FrameSetView.java new file mode 100644 index 00000000000..e3252d79caf --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/FrameSetView.java @@ -0,0 +1,274 @@ +/* FrameSetView.java -- Implements HTML frameset + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.StringTokenizer; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BoxView; +import javax.swing.text.Element; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; + +/** + * Implements HTML framesets. This is implemented as a vertical box that + * holds the rows of the frameset. Each row is again a horizontal box that + * holds the actual columns. + */ +public class FrameSetView + extends BoxView +{ + + /** + * A row of a frameset. + */ + private class FrameSetRow + extends BoxView + { + private int row; + FrameSetRow(Element el, int r) + { + super(el, X_AXIS); + row = r; + } + + protected void loadChildren(ViewFactory f) + { + // Load the columns here. + Element el = getElement(); + View[] columns = new View[numViews[X_AXIS]]; + int offset = row * numViews[X_AXIS]; + for (int c = 0; c < numViews[X_AXIS]; c++) + { + Element child = el.getElement(offset + c); + columns[c] = f.create(child); + } + replace(0, 0, columns); + } + + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + int numRows = numViews[X_AXIS]; + int[] abs = absolute[X_AXIS]; + int[] rel = relative[X_AXIS]; + int[] perc = percent[X_AXIS]; + layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc); + } + } + + /** + * Holds the absolute layout information for the views along one axis. The + * indices are absolute[axis][index], where axis is either X_AXIS (columns) + * or Y_AXIS (rows). Rows or columns that don't have absolute layout have + * a -1 in this array. + */ + int[][] absolute; + + /** + * Holds the relative (*) layout information for the views along one axis. + * The indices are relative[axis][index], where axis is either X_AXIS + * (columns) or Y_AXIS (rows). Rows or columns that don't have relative + * layout have a Float.NaN in this array. + */ + int[][] relative; + + /** + * Holds the relative (%) layout information for the views along one axis. + * The indices are relative[axis][index], where axis is either X_AXIS + * (columns) or Y_AXIS (rows). Rows or columns that don't have relative + * layout have a Float.NaN in this array. + * + * The percentage is divided by 100 so that we hold the actual fraction here. + */ + int[][] percent; + + /** + * The number of children in each direction. + */ + int[] numViews; + + FrameSetView(Element el) + { + super(el, Y_AXIS); + numViews = new int[2]; + absolute = new int[2][]; + relative = new int[2][]; + percent = new int[2][]; + } + + /** + * Loads the children and places them inside the grid. + */ + protected void loadChildren(ViewFactory f) + { + parseRowsCols(); + // Set up the rows. + View[] rows = new View[numViews[Y_AXIS]]; + for (int r = 0; r < numViews[Y_AXIS]; r++) + { + rows[r] = new FrameSetRow(getElement(), r); + } + replace(0, 0, rows); + } + + /** + * Parses the rows and cols attributes and sets up the layout info. + */ + private void parseRowsCols() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + String cols = (String) atts.getAttribute(HTML.Attribute.COLS); + if (cols == null) // Defaults to '100%' when not specified. + cols = "100%"; + parseLayout(cols, X_AXIS); + String rows = (String) atts.getAttribute(HTML.Attribute.ROWS); + if (rows == null) // Defaults to '100%' when not specified. + rows = "100%"; + parseLayout(rows, Y_AXIS); + } + + /** + * Parses the cols or rows attribute and places the layout info in the + * appropriate arrays. + * + * @param att the attributes to parse + * @param axis the axis + */ + private void parseLayout(String att, int axis) + { + StringTokenizer tokens = new StringTokenizer(att, ","); + numViews[axis] = tokens.countTokens(); + absolute[axis] = new int[numViews[axis]]; + relative[axis] = new int[numViews[axis]]; + percent[axis] = new int[numViews[axis]]; + for (int index = 0; tokens.hasMoreTokens(); index++) + { + String token = tokens.nextToken(); + int p = token.indexOf('%'); + int s = token.indexOf('*'); + if (p != -1) + { + // Percent value. + String number = token.substring(0, p); + try + { + percent[axis][index] = Integer.parseInt(number); + } + catch (NumberFormatException ex) + { + // Leave value as 0 then. + } + } + else if (s != -1) + { + // Star relative value. + String number = token.substring(0, s); + try + { + relative[axis][index] = Integer.parseInt(number); + } + catch (NumberFormatException ex) + { + // Leave value as 0 then. + } + } + else + { + // Absolute value. + try + { + absolute[axis][index] = Integer.parseInt(token); + } + catch (NumberFormatException ex) + { + // Leave value as 0 then. + } + } + } + } + + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + int numRows = numViews[Y_AXIS]; + int[] abs = absolute[Y_AXIS]; + int[] rel = relative[Y_AXIS]; + int[] perc = percent[Y_AXIS]; + layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc); + } + + void layoutViews(int targetSpan, int axis, int[] offsets, int[] spans, + int numViews, int[] abs, int[] rel, int[] perc) + { + // We need two passes. In the first pass we layout the absolute and + // percent values and accumulate the needed space. In the second pass + // the relative values are distributed and the offsets are set. + int total = 0; + int relTotal = 0; + for (int i = 0; i < numViews; i++) + { + if (abs[i] > 0) + { + spans[i] = abs[i]; + total += spans[i]; + } + else if (perc[i] > 0) + { + spans[i] = (targetSpan * perc[i]) / 100; + total += spans[i]; + } + else if (rel[i] > 0) + { + relTotal += rel[i]; + } + } + int offs = 0; + for (int i = 0; i < numViews; i++) + { + if (relTotal > 0 && rel[i] > 0) + { + spans[i] = targetSpan * (rel[i] / relTotal); + } + offsets[i] = offs; + offs += spans[i]; + } + } +} diff --git a/libjava/classpath/javax/swing/text/html/FrameView.java b/libjava/classpath/javax/swing/text/html/FrameView.java new file mode 100644 index 00000000000..cd4e44a98ce --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/FrameView.java @@ -0,0 +1,233 @@ +/* FrameView.java -- Renders HTML frame tags + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Component; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.swing.JEditorPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.ComponentView; +import javax.swing.text.Element; +import javax.swing.text.View; + +/** + * A view that is responsible for rendering HTML frame tags. + * This is accomplished by a specialized {@link ComponentView} + * that embeds a JEditorPane with an own document. + */ +class FrameView + extends ComponentView + implements HyperlinkListener +{ + + /** + * Creates a new FrameView for the specified element. + * + * @param el the element for the view + */ + FrameView(Element el) + { + super(el); + } + + /** + * Creates the element that will be embedded in the view. + * This will be a JEditorPane with the appropriate content set. + * + * @return the element that will be embedded in the view + */ + protected Component createComponent() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + JEditorPane html = new JEditorPane(); + html.addHyperlinkListener(this); + URL base = ((HTMLDocument) el.getDocument()).getBase(); + String srcAtt = (String) atts.getAttribute(HTML.Attribute.SRC); + if (srcAtt != null && ! srcAtt.equals("")) + { + try + { + URL page = new URL(base, srcAtt); + html.setPage(page); + ((HTMLDocument) html.getDocument()).setFrameDocument(true); + } + catch (MalformedURLException ex) + { + // Leave page empty. + } + catch (IOException ex) + { + // Leave page empty. + } + } + return html; + } + + /** + * Catches hyperlink events on that frame's editor and forwards it to + * the outermost editorpane. + */ + public void hyperlinkUpdate(HyperlinkEvent event) + { + JEditorPane outer = getTopEditorPane(); + if (outer != null) + { + if (event instanceof HTMLFrameHyperlinkEvent) + { + HTMLFrameHyperlinkEvent hfhe = (HTMLFrameHyperlinkEvent) event; + if (hfhe.getEventType() == HyperlinkEvent.EventType.ACTIVATED) + { + String target = hfhe.getTarget(); + if (event instanceof FormSubmitEvent) + { + handleFormSubmitEvent(hfhe, outer, target); + } + else // No FormSubmitEvent. + { + handleHyperlinkEvent(hfhe, outer, target); + } + } + } + else + { + // Simply forward this event. + outer.fireHyperlinkUpdate(event); + } + } + } + + /** + * Handles normal hyperlink events. + * + * @param event the event + * @param outer the top editor + * @param target the target + */ + private void handleHyperlinkEvent(HyperlinkEvent event, + JEditorPane outer, String target) + { + if (target.equals("_top")) + { + try + { + outer.setPage(event.getURL()); + } + catch (IOException ex) + { + // Well... + ex.printStackTrace(); + } + } + if (! outer.isEditable()) + { + outer.fireHyperlinkUpdate + (new HTMLFrameHyperlinkEvent(outer, + event.getEventType(), + event.getURL(), + event.getDescription(), + getElement(), + target)); + } + } + + /** + * Handles form submit events. + * + * @param event the event + * @param outer the top editor + * @param target the target + */ + private void handleFormSubmitEvent(HTMLFrameHyperlinkEvent event, + JEditorPane outer, + String target) + { + HTMLEditorKit kit = (HTMLEditorKit) outer.getEditorKit(); + if (kit != null && kit.isAutoFormSubmission()) + { + if (target.equals("_top")) + { + try + { + outer.setPage(event.getURL()); + } + catch (IOException ex) + { + // Well... + ex.printStackTrace(); + } + } + else + { + HTMLDocument doc = + (HTMLDocument) outer.getDocument(); + doc.processHTMLFrameHyperlinkEvent(event); + } + } + else + { + outer.fireHyperlinkUpdate(event); + } + } + + /** + * Determines the topmost editor in a nested frameset. + * + * @return the topmost editor in a nested frameset + */ + private JEditorPane getTopEditorPane() + { + View parent = getParent(); + View top = null; + while (parent != null) + { + if (parent instanceof FrameSetView) + top = parent; + } + JEditorPane editor = null; + if (top != null) + editor = (JEditorPane) top.getContainer(); + return editor; + } +} diff --git a/libjava/classpath/javax/swing/text/html/HTML.java b/libjava/classpath/javax/swing/text/html/HTML.java index 2c908f6fc6e..93c05daa2f8 100644 --- a/libjava/classpath/javax/swing/text/html/HTML.java +++ b/libjava/classpath/javax/swing/text/html/HTML.java @@ -465,6 +465,16 @@ public class HTML public static final Attribute WIDTH = new Attribute("width"); /** + * This is used to reflect the pseudo class for the a tag. + */ + static final Attribute PSEUDO_CLASS = new Attribute("_pseudo"); + + /** + * This is used to reflect the dynamic class for the a tag. + */ + static final Attribute DYNAMIC_CLASS = new Attribute("_dynamic"); + + /** * The attribute name. */ private final String name; @@ -1119,8 +1129,8 @@ public class HTML static final int BLOCK = 2; static final int PREFORMATTED = 4; static final int SYNTHETIC = 8; - private static Map tagMap; - private static Map attrMap; + private static Map<String,Tag> tagMap; + private static Map<String,Attribute> attrMap; /** * The public constructor (does nothing). It it seldom required to have @@ -1159,7 +1169,7 @@ public class HTML if (attrMap == null) { // Create the map on demand. - attrMap = new TreeMap(); + attrMap = new TreeMap<String,Attribute>(); Attribute[] attrs = getAllAttributeKeys(); @@ -1169,7 +1179,7 @@ public class HTML } } - return (Attribute) attrMap.get(attName.toLowerCase()); + return attrMap.get(attName.toLowerCase()); } /** @@ -1228,7 +1238,7 @@ public class HTML if (tagMap == null) { // Create the mao on demand. - tagMap = new TreeMap(); + tagMap = new TreeMap<String,Tag>(); Tag[] tags = getAllTags(); @@ -1238,6 +1248,6 @@ public class HTML } } - return (Tag) tagMap.get(tagName.toLowerCase()); + return tagMap.get(tagName.toLowerCase()); } } diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java index 0bfc338df45..f3d3ce3faaf 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java +++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java @@ -39,19 +39,22 @@ exception statement from your version. */ package javax.swing.text.html; import gnu.classpath.NotImplementedException; -import gnu.javax.swing.text.html.CharacterAttributeTranslator; -import gnu.javax.swing.text.html.parser.htmlAttributeSet; import java.io.IOException; import java.io.StringReader; +import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.HashMap; import java.util.Stack; import java.util.Vector; +import javax.swing.ButtonGroup; +import javax.swing.DefaultButtonModel; import javax.swing.JEditorPane; +import javax.swing.ListSelectionModel; import javax.swing.event.DocumentEvent; -import javax.swing.event.HyperlinkEvent.EventType; +import javax.swing.event.UndoableEditEvent; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; @@ -60,6 +63,7 @@ import javax.swing.text.Element; import javax.swing.text.ElementIterator; import javax.swing.text.GapContent; import javax.swing.text.MutableAttributeSet; +import javax.swing.text.PlainDocument; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.html.HTML.Tag; @@ -87,16 +91,24 @@ public class HTMLDocument extends DefaultStyledDocument boolean preservesUnknownTags = true; int tokenThreshold = Integer.MAX_VALUE; HTMLEditorKit.Parser parser; - StyleSheet styleSheet; - AbstractDocument.Content content; - + + /** + * Indicates whether this document is inside a frame or not. + */ + private boolean frameDocument; + + /** + * Package private to avoid accessor methods. + */ + String baseTarget; + /** * Constructs an HTML document using the default buffer size and a default * StyleSheet. */ public HTMLDocument() { - this(null); + this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet()); } /** @@ -119,14 +131,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public HTMLDocument(AbstractDocument.Content c, StyleSheet styles) { - this.content = c; - if (styles == null) - { - styles = new StyleSheet(); - styles.importStyleSheet(getClass().getResource(HTMLEditorKit. - DEFAULT_CSS)); - } - this.styleSheet = styles; + super(c, styles); } /** @@ -137,7 +142,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public StyleSheet getStyleSheet() { - return styleSheet; + return (StyleSheet) getAttributeContext(); } /** @@ -191,8 +196,6 @@ public class HTMLDocument extends DefaultStyledDocument protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { - RunElement el = new RunElement(parent, a, p0, p1); - el.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); return new RunElement(parent, a, p0, p1); } @@ -269,7 +272,7 @@ public class HTMLDocument extends DefaultStyledDocument public void setBase(URL u) { baseURL = u; - styleSheet.setBase(u); + getStyleSheet().setBase(u); } /** @@ -374,11 +377,119 @@ public class HTMLDocument extends DefaultStyledDocument } public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event) - throws NotImplementedException { - // TODO: Implement this properly. + String target = event.getTarget(); + Element el = event.getSourceElement(); + URL url = event.getURL(); + if (target.equals("_self")) + { + updateFrame(el, url); + } + else if (target.equals("_parent")) + { + updateFrameSet(el.getParentElement(), url); + } + else + { + Element targetFrame = findFrame(target); + if (targetFrame != null) + updateFrame(targetFrame, url); + } } - + + /** + * Finds the named frame inside this document. + * + * @param target the name to look for + * + * @return the frame if there is a matching frame, <code>null</code> + * otherwise + */ + private Element findFrame(String target) + { + ElementIterator i = new ElementIterator(this); + Element next = null; + while ((next = i.next()) != null) + { + AttributeSet atts = next.getAttributes(); + if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FRAME) + { + String name = (String) atts.getAttribute(HTML.Attribute.NAME); + if (name != null && name.equals(target)) + break; + } + } + return next; + } + + /** + * Updates the frame that is represented by the specified element to + * refer to the specified URL. + * + * @param el the element + * @param url the new url + */ + private void updateFrame(Element el, URL url) + { + try + { + writeLock(); + DefaultDocumentEvent ev = + new DefaultDocumentEvent(el.getStartOffset(), 1, + DocumentEvent.EventType.CHANGE); + AttributeSet elAtts = el.getAttributes(); + AttributeSet copy = elAtts.copyAttributes(); + MutableAttributeSet matts = (MutableAttributeSet) elAtts; + ev.addEdit(new AttributeUndoableEdit(el, copy, false)); + matts.removeAttribute(HTML.Attribute.SRC); + matts.addAttribute(HTML.Attribute.SRC, url.toString()); + ev.end(); + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + finally + { + writeUnlock(); + } + } + + /** + * Updates the frameset that is represented by the specified element + * to create a frame that refers to the specified URL. + * + * @param el the element + * @param url the url + */ + private void updateFrameSet(Element el, URL url) + { + int start = el.getStartOffset(); + int end = el.getEndOffset(); + + StringBuilder html = new StringBuilder(); + html.append("<frame"); + if (url != null) + { + html.append(" src=\""); + html.append(url.toString()); + html.append("\""); + } + html.append('>'); + if (getParser() == null) + setParser(new HTMLEditorKit().getParser()); + try + { + setOuterHTML(el, html.toString()); + } + catch (BadLocationException ex) + { + ex.printStackTrace(); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + /** * Gets an iterator for the given HTML.Tag. * @param t the requested HTML.Tag @@ -461,6 +572,8 @@ public class HTMLDocument extends DefaultStyledDocument String name = null; if (tag != null) name = tag.toString(); + if (name == null) + name = super.getName(); return name; } } @@ -497,6 +610,8 @@ public class HTMLDocument extends DefaultStyledDocument String name = null; if (tag != null) name = tag.toString(); + if (name == null) + name = super.getName(); return name; } @@ -518,24 +633,33 @@ public class HTMLDocument extends DefaultStyledDocument * @author Anthony Balkissoon abalkiss at redhat dot com */ public class HTMLReader extends HTMLEditorKit.ParserCallback - { + { + /** + * The maximum token threshold. We don't grow it larger than this. + */ + private static final int MAX_THRESHOLD = 10000; + + /** + * The threshold growth factor. + */ + private static final int GROW_THRESHOLD = 5; + /** * Holds the current character attribute set * */ protected MutableAttributeSet charAttr = new SimpleAttributeSet(); - protected Vector parseBuffer = new Vector(); - + protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>(); + + /** + * The parse stack. It holds the current element tree path. + */ + private Stack<HTML.Tag> parseStack = new Stack<HTML.Tag>(); + /** * 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. - */ - Stack parseStack = new Stack(); /** A mapping between HTML.Tag objects and the actions that handle them **/ HashMap tagToAction; @@ -571,13 +695,68 @@ public class HTMLDocument extends DefaultStyledDocument /** A temporary variable that helps with the printing out of debug information **/ boolean debug = false; - - void print (String line) - { - if (debug) - System.out.println (line); - } - + + /** + * This is true when we are inside a pre tag. + */ + boolean inPreTag = false; + + /** + * This is true when we are inside a style tag. This will add text + * content inside this style tag beeing parsed as CSS. + * + * This is package private to avoid accessor methods. + */ + boolean inStyleTag = false; + + /** + * This is true when we are inside a <textarea> tag. Any text + * content will then be added to the text area. + * + * This is package private to avoid accessor methods. + */ + boolean inTextArea = false; + + /** + * This contains all stylesheets that are somehow read, either + * via embedded style tags, or via linked stylesheets. The + * elements will be String objects containing a stylesheet each. + */ + ArrayList styles; + + /** + * The document model for a textarea. + * + * This is package private to avoid accessor methods. + */ + ResetablePlainDocument textAreaDocument; + + /** + * The current model of a select tag. Can be a ComboBoxModel or a + * ListModel depending on the type of the select box. + */ + Object selectModel; + + /** + * The current option beeing read. + */ + Option option; + + /** + * The current number of options in the current select model. + */ + int numOptions; + + /** + * The current button groups mappings. + */ + HashMap buttonGroups; + + /** + * The token threshold. This gets increased while loading. + */ + private int threshold; + public class TagAction { /** @@ -633,13 +812,12 @@ public class HTMLDocument extends DefaultStyledDocument // Put the old attribute set on the stack. pushCharacterStyle(); - // Translate tag.. return if succesful. - if(CharacterAttributeTranslator.translateTag(charAttr, t, a)) - return; + // Initialize with link pseudo class. + if (t == HTML.Tag.A) + a.addAttribute(HTML.Attribute.PSEUDO_CLASS, "link"); // Just add the attributes in <code>a</code>. - if (a != null) - charAttr.addAttribute(t, a.copyAttributes()); + charAttr.addAttribute(t, a.copyAttributes()); } /** @@ -651,7 +829,11 @@ public class HTMLDocument extends DefaultStyledDocument popCharacterStyle(); } } - + + /** + * Processes elements that make up forms: <input>, <textarea>, + * <select> and <option>. + */ public class FormAction extends SpecialAction { /** @@ -659,10 +841,73 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("FormAction.start not implemented"); + if (t == HTML.Tag.INPUT) + { + String type = (String) a.getAttribute(HTML.Attribute.TYPE); + if (type == null) + { + type = "text"; // Default to 'text' when nothing was specified. + a.addAttribute(HTML.Attribute.TYPE, type); + } + setModel(type, a); + } + else if (t == HTML.Tag.TEXTAREA) + { + inTextArea = true; + textAreaDocument = new ResetablePlainDocument(); + a.addAttribute(StyleConstants.ModelAttribute, textAreaDocument); + } + else if (t == HTML.Tag.SELECT) + { + int size = HTML.getIntegerAttributeValue(a, HTML.Attribute.SIZE, + 1); + boolean multi = a.getAttribute(HTML.Attribute.MULTIPLE) != null; + if (size > 1 || multi) + { + SelectListModel m = new SelectListModel(); + if (multi) + m.getSelectionModel().setSelectionMode(ListSelectionModel + .MULTIPLE_INTERVAL_SELECTION); + selectModel = m; + } + else + { + selectModel = new SelectComboBoxModel(); + } + a.addAttribute(StyleConstants.ModelAttribute, selectModel); + } + if (t == HTML.Tag.OPTION) + { + option = new Option(a); + if (selectModel instanceof SelectListModel) + { + SelectListModel m = (SelectListModel) selectModel; + m.addElement(option); + if (option.isSelected()) + { + m.getSelectionModel().addSelectionInterval(numOptions, + numOptions); + m.addInitialSelection(numOptions); + } + } + else if (selectModel instanceof SelectComboBoxModel) + { + SelectComboBoxModel m = (SelectComboBoxModel) selectModel; + m.addElement(option); + if (option.isSelected()) + { + m.setSelectedItem(option); + m.setInitialSelection(option); + } + } + numOptions++; + } + else + { + // Build the element. + super.start(t, a); + } } /** @@ -670,13 +915,106 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("FormAction.end not implemented"); + if (t == HTML.Tag.OPTION) + { + option = null; + } + else + { + if (t == HTML.Tag.TEXTAREA) + { + inTextArea = false; + } + else if (t == HTML.Tag.SELECT) + { + selectModel = null; + numOptions = 0; + } + // Finish the element. + super.end(t); + } + } + + private void setModel(String type, MutableAttributeSet attrs) + { + if (type.equals("submit") || type.equals("reset") + || type.equals("image")) + { + // Create button. + attrs.addAttribute(StyleConstants.ModelAttribute, + new DefaultButtonModel()); + } + else if (type.equals("text") || type.equals("password")) + { + String text = (String) attrs.getAttribute(HTML.Attribute.VALUE); + ResetablePlainDocument doc = new ResetablePlainDocument(); + if (text != null) + { + doc.setInitialText(text); + try + { + doc.insertString(0, text, null); + } + catch (BadLocationException ex) + { + // Shouldn't happen. + assert false; + } + } + attrs.addAttribute(StyleConstants.ModelAttribute, doc); + } + else if (type.equals("file")) + { + attrs.addAttribute(StyleConstants.ModelAttribute, + new PlainDocument()); + } + else if (type.equals("checkbox") || type.equals("radio")) + { + ResetableToggleButtonModel model = + new ResetableToggleButtonModel(); + if (attrs.getAttribute(HTML.Attribute.SELECTED) != null) + { + model.setSelected(true); + model.setInitial(true); + } + if (type.equals("radio")) + { + String name = (String) attrs.getAttribute(HTML.Attribute.NAME); + if (name != null) + { + if (buttonGroups == null) + buttonGroups = new HashMap(); + ButtonGroup group = (ButtonGroup) buttonGroups.get(name); + if (group == null) + { + group = new ButtonGroup(); + buttonGroups.put(name, group); + } + model.setGroup(group); + } + } + attrs.addAttribute(StyleConstants.ModelAttribute, model); + } + } + } + + /** + * Called for form tags. + */ + class FormTagAction + extends BlockAction + { + /** + * Clears the button group mapping. + */ + public void end(HTML.Tag t) + { + super.end(t); + buttonGroups = null; } } - + /** * This action indicates that the content between starting and closing HTML * elements (like script - /script) should not be visible. The content is @@ -707,7 +1045,10 @@ public class HTMLDocument extends DefaultStyledDocument blockClose(t); } } - + + /** + * Handles <isindex> tags. + */ public class IsindexAction extends TagAction { /** @@ -715,10 +1056,10 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("IsindexAction.start not implemented"); + blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); + addSpecialElement(t, a); + blockClose(HTML.Tag.IMPLIED); } } @@ -730,7 +1071,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public void start(HTML.Tag t, MutableAttributeSet a) { - blockOpen(t, a); + super.start(t, a); } /** @@ -739,10 +1080,13 @@ public class HTMLDocument extends DefaultStyledDocument */ public void end(HTML.Tag t) { - blockClose(t); + super.end(t); } } - + + /** + * This action is performed when a <pre> tag is parsed. + */ public class PreAction extends BlockAction { /** @@ -750,11 +1094,11 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("PreAction.start not implemented"); - super.start(t, a); + inPreTag = true; + blockOpen(t, a); + a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre"); + blockOpen(HTML.Tag.IMPLIED, a); } /** @@ -762,11 +1106,10 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("PreAction.end not implemented"); - super.end(t); + blockClose(HTML.Tag.IMPLIED); + inPreTag = false; + blockClose(t); } } @@ -798,7 +1141,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("AreaAction.start not implemented"); } /** @@ -809,10 +1151,44 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("AreaAction.end not implemented"); } } - + + /** + * Converts HTML tags to CSS attributes. + */ + class ConvertAction + extends TagAction + { + + public void start(HTML.Tag tag, MutableAttributeSet atts) + { + pushCharacterStyle(); + charAttr.addAttribute(tag, atts.copyAttributes()); + StyleSheet styleSheet = getStyleSheet(); + // TODO: Add other tags here. + if (tag == HTML.Tag.FONT) + { + String color = (String) atts.getAttribute(HTML.Attribute.COLOR); + if (color != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color); + String face = (String) atts.getAttribute(HTML.Attribute.FACE); + if (face != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, + face); + String size = (String) atts.getAttribute(HTML.Attribute.SIZE); + if (size != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE, + size); + } + } + + public void end(HTML.Tag tag) + { + popCharacterStyle(); + } + } + class BaseAction extends TagAction { /** @@ -820,22 +1196,9 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("BaseAction.start not implemented"); + baseTarget = (String) a.getAttribute(HTML.Attribute.TARGET); } - - /** - * Called when an end tag is seen for one of the types of tags associated - * with this Action. - */ - public void end(HTML.Tag t) - throws NotImplementedException - { - // FIXME: Implement. - print ("BaseAction.end not implemented"); - } } class HeadAction extends BlockAction @@ -848,7 +1211,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("HeadAction.start not implemented: "+t); super.start(t, a); } @@ -857,37 +1219,87 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("HeadAction.end not implemented: "+t); + // We read in all the stylesheets that are embedded or referenced + // inside the header. + if (styles != null) + { + int numStyles = styles.size(); + for (int i = 0; i < numStyles; i++) + { + String style = (String) styles.get(i); + getStyleSheet().addRule(style); + } + } super.end(t); - } + } } - class LinkAction extends TagAction + class LinkAction extends HiddenAction { /** * This method is called when a start tag is seen for one of the types * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("LinkAction.start not implemented"); + super.start(t, a); + String type = (String) a.getAttribute(HTML.Attribute.TYPE); + if (type == null) + type = "text/css"; + if (type.equals("text/css")) + { + String rel = (String) a.getAttribute(HTML.Attribute.REL); + String media = (String) a.getAttribute(HTML.Attribute.MEDIA); + String title = (String) a.getAttribute(HTML.Attribute.TITLE); + if (media == null) + media = "all"; + else + media = media.toLowerCase(); + if (rel != null) + { + rel = rel.toLowerCase(); + if ((media.indexOf("all") != -1 + || media.indexOf("screen") != -1) + && (rel.equals("stylesheet"))) + { + String href = (String) a.getAttribute(HTML.Attribute.HREF); + URL url = null; + try + { + url = new URL(baseURL, href); + } + catch (MalformedURLException ex) + { + try + { + url = new URL(href); + } + catch (MalformedURLException ex2) + { + url = null; + } + } + if (url != null) + { + try + { + getStyleSheet().importStyleSheet(url); + } + catch (Exception ex) + { + // Don't let exceptions and runtime exceptions + // in CSS parsing disprupt the HTML parsing + // process. But inform the user/developer + // on the console about it. + ex.printStackTrace(); + } + } + } + } + } } - /** - * Called when an end tag is seen for one of the types of tags associated - * with this Action. - */ - public void end(HTML.Tag t) - throws NotImplementedException - { - // FIXME: Implement. - print ("LinkAction.end not implemented"); - } } class MapAction extends TagAction @@ -900,7 +1312,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MapAction.start not implemented"); } /** @@ -911,7 +1322,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MapAction.end not implemented"); } } @@ -925,7 +1335,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MetaAction.start not implemented"); } /** @@ -936,10 +1345,9 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("MetaAction.end not implemented"); } } - + class StyleAction extends TagAction { /** @@ -947,10 +1355,8 @@ public class HTMLDocument extends DefaultStyledDocument * of tags associated with this Action. */ public void start(HTML.Tag t, MutableAttributeSet a) - throws NotImplementedException { - // FIXME: Implement. - print ("StyleAction.start not implemented"); + inStyleTag = true; } /** @@ -958,10 +1364,8 @@ public class HTMLDocument extends DefaultStyledDocument * with this Action. */ public void end(HTML.Tag t) - throws NotImplementedException { - // FIXME: Implement. - print ("StyleAction.end not implemented"); + inStyleTag = false; } } @@ -975,7 +1379,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("TitleAction.start not implemented"); } /** @@ -986,7 +1389,6 @@ public class HTMLDocument extends DefaultStyledDocument throws NotImplementedException { // FIXME: Implement. - print ("TitleAction.end not implemented"); } } @@ -998,13 +1400,11 @@ public class HTMLDocument extends DefaultStyledDocument public HTMLReader(int offset, int popDepth, int pushDepth, HTML.Tag insertTag) { - print ("HTMLReader created with pop: "+popDepth - + " push: "+pushDepth + " offset: "+offset - + " tag: "+insertTag); this.insertTag = insertTag; this.offset = offset; this.popDepth = popDepth; this.pushDepth = pushDepth; + threshold = getTokenThreshold(); initTags(); } @@ -1028,7 +1428,7 @@ public class HTMLDocument extends DefaultStyledDocument StyleAction styleAction = new StyleAction(); TitleAction titleAction = new TitleAction(); - + ConvertAction convertAction = new ConvertAction(); tagToAction.put(HTML.Tag.A, characterAction); tagToAction.put(HTML.Tag.ADDRESS, characterAction); tagToAction.put(HTML.Tag.APPLET, hiddenAction); @@ -1051,8 +1451,8 @@ public class HTMLDocument extends DefaultStyledDocument tagToAction.put(HTML.Tag.DL, blockAction); tagToAction.put(HTML.Tag.DT, paragraphAction); tagToAction.put(HTML.Tag.EM, characterAction); - tagToAction.put(HTML.Tag.FONT, characterAction); - tagToAction.put(HTML.Tag.FORM, blockAction); + tagToAction.put(HTML.Tag.FONT, convertAction); + tagToAction.put(HTML.Tag.FORM, new FormTagAction()); tagToAction.put(HTML.Tag.FRAME, specialAction); tagToAction.put(HTML.Tag.FRAMESET, blockAction); tagToAction.put(HTML.Tag.H1, paragraphAction); @@ -1142,18 +1542,28 @@ public class HTMLDocument extends DefaultStyledDocument */ public void flush() throws BadLocationException { - DefaultStyledDocument.ElementSpec[] elements; - elements = new DefaultStyledDocument.ElementSpec[parseBuffer.size()]; - parseBuffer.copyInto(elements); - parseBuffer.removeAllElements(); - if (offset == 0) - create(elements); - else - insert(offset, elements); + flushImpl(); + } - offset += HTMLDocument.this.getLength() - offset; + /** + * Flushes the buffer and handle partial inserts. + * + */ + private void flushImpl() + throws BadLocationException + { + int oldLen = getLength(); + int size = parseBuffer.size(); + ElementSpec[] elems = new ElementSpec[size]; + parseBuffer.copyInto(elems); + if (oldLen == 0) + create(elems); + else + insert(offset, elems); + parseBuffer.removeAllElements(); + offset += getLength() - oldLen; } - + /** * This method is called by the parser to indicate a block of * text was encountered. Should insert the text appropriately. @@ -1163,8 +1573,24 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleText(char[] data, int pos) { - if (data != null && data.length > 0) - addContent(data, 0, data.length); + if (shouldInsert() && data != null && data.length > 0) + { + if (inTextArea) + textAreaContent(data); + else if (inPreTag) + preContent(data); + else if (option != null) + option.setLabel(new String(data)); + else if (inStyleTag) + { + if (styles == null) + styles = new ArrayList(); + styles.add(new String(data)); + } + else + addContent(data, 0, data.length); + + } } /** @@ -1214,8 +1640,7 @@ public class HTMLDocument extends DefaultStyledDocument TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); if (action != null) { - action.start(HTML.Tag.COMMENT, - htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET); + action.start(HTML.Tag.COMMENT, new SimpleAttributeSet()); action.end(HTML.Tag.COMMENT); } } @@ -1277,7 +1702,6 @@ public class HTMLDocument extends DefaultStyledDocument public void handleEndOfLineString(String eol) { // FIXME: Implement. - print ("HTMLReader.handleEndOfLineString not implemented yet"); } /** @@ -1287,22 +1711,48 @@ public class HTMLDocument extends DefaultStyledDocument * @param data the text to add to the textarea */ protected void textAreaContent(char[] data) - throws NotImplementedException { - // FIXME: Implement. - print ("HTMLReader.textAreaContent not implemented yet"); + try + { + int offset = textAreaDocument.getLength(); + String text = new String(data); + textAreaDocument.setInitialText(text); + textAreaDocument.insertString(offset, text, null); + } + catch (BadLocationException ex) + { + // Must not happen as we insert at a model location that we + // got from the document itself. + assert false; + } } /** * Adds the given text that was encountered in a <PRE> element. - * + * This adds synthesized lines to hold the text runs. + * * @param data the text */ protected void preContent(char[] data) - throws NotImplementedException { - // FIXME: Implement - print ("HTMLReader.preContent not implemented yet"); + int start = 0; + for (int i = 0; i < data.length; i++) + { + if (data[i] == '\n') + { + addContent(data, start, i - start + 1); + blockClose(HTML.Tag.IMPLIED); + MutableAttributeSet atts = new SimpleAttributeSet(); + atts.addAttribute(CSS.Attribute.WHITE_SPACE, "pre"); + blockOpen(HTML.Tag.IMPLIED, atts); + start = i + 1; + } + } + if (start < data.length) + { + // Add remaining last line. + addContent(data, start, data.length - start); + } } /** @@ -1314,17 +1764,48 @@ public class HTMLDocument extends DefaultStyledDocument */ protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) { - printBuffer(); - DefaultStyledDocument.ElementSpec element; + if (inImpliedParagraph()) + blockClose(HTML.Tag.IMPLIED); + // Push the new tag on top of the stack. parseStack.push(t); + + DefaultStyledDocument.ElementSpec element; + AbstractDocument.AttributeContext ctx = getAttributeContext(); AttributeSet copy = attr.copyAttributes(); copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t); element = new DefaultStyledDocument.ElementSpec(copy, DefaultStyledDocument.ElementSpec.StartTagType); parseBuffer.addElement(element); - printBuffer(); + } + + /** + * Returns true when we are currently inside a paragraph, either + * a real one or an implied, false otherwise. + * + * @return + */ + private boolean inParagraph() + { + boolean inParagraph = false; + if (! parseStack.isEmpty()) + { + HTML.Tag top = parseStack.peek(); + inParagraph = top == HTML.Tag.P || top == HTML.Tag.IMPLIED; + } + return inParagraph; + } + + private boolean inImpliedParagraph() + { + boolean inParagraph = false; + if (! parseStack.isEmpty()) + { + HTML.Tag top = parseStack.peek(); + inParagraph = top == HTML.Tag.IMPLIED; + } + return inParagraph; } /** @@ -1335,32 +1816,29 @@ public class HTMLDocument extends DefaultStyledDocument */ protected void blockClose(HTML.Tag t) { - printBuffer(); DefaultStyledDocument.ElementSpec element; + if (inImpliedParagraph() && t != HTML.Tag.IMPLIED) + blockClose(HTML.Tag.IMPLIED); + + // Pull the token from the stack. + if (! parseStack.isEmpty()) // Just to be sure. + parseStack.pop(); + // If the previous tag is a start tag then we insert a synthetic // content tag. DefaultStyledDocument.ElementSpec prev; - prev = (DefaultStyledDocument.ElementSpec) - parseBuffer.get(parseBuffer.size() - 1); - if (prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType) + prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec) + parseBuffer.get(parseBuffer.size() - 1) : null; + if (prev != null && + prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType) { - AbstractDocument.AttributeContext ctx = getAttributeContext(); - AttributeSet attributes = ctx.getEmptySet(); - attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute, - HTML.Tag.CONTENT); - element = new DefaultStyledDocument.ElementSpec(attributes, - DefaultStyledDocument.ElementSpec.ContentType, - new char[0], 0, 0); - parseBuffer.add(element); + addContent(new char[]{' '}, 0, 1); } element = new DefaultStyledDocument.ElementSpec(null, DefaultStyledDocument.ElementSpec.EndTagType); parseBuffer.addElement(element); - printBuffer(); - if (parseStack.size() > 0) - parseStack.pop(); } /** @@ -1389,6 +1867,11 @@ public class HTMLDocument extends DefaultStyledDocument protected void addContent(char[] data, int offs, int length, boolean generateImpliedPIfNecessary) { + if (generateImpliedPIfNecessary && ! inParagraph()) + { + blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); + } + AbstractDocument.AttributeContext ctx = getAttributeContext(); DefaultStyledDocument.ElementSpec element; AttributeSet attributes = null; @@ -1405,16 +1888,16 @@ public class HTMLDocument extends DefaultStyledDocument DefaultStyledDocument.ElementSpec.ContentType, data, offs, length); - printBuffer(); // Add the element to the buffer parseBuffer.addElement(element); - printBuffer(); - if (parseBuffer.size() > HTMLDocument.this.getTokenThreshold()) + if (parseBuffer.size() > threshold) { + if (threshold <= MAX_THRESHOLD) + threshold *= GROW_THRESHOLD; try { - flush(); + flushImpl(); } catch (BadLocationException ble) { @@ -1431,29 +1914,23 @@ public class HTMLDocument extends DefaultStyledDocument */ protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) { + if (t != HTML.Tag.FRAME && ! inParagraph()) + { + blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); + } + a.addAttribute(StyleConstants.NameAttribute, t); - // Migrate from the rather htmlAttributeSet to the faster, lighter and - // unchangeable alternative implementation. - AttributeSet copy = a.copyAttributes(); - // The two spaces are required because some special elements like HR // must be broken. At least two characters are needed to break into the // two parts. DefaultStyledDocument.ElementSpec spec = - new DefaultStyledDocument.ElementSpec(copy, + new DefaultStyledDocument.ElementSpec(a.copyAttributes(), DefaultStyledDocument.ElementSpec.ContentType, - new char[] {' ', ' '}, 0, 2 ); + new char[] {' '}, 0, 1 ); parseBuffer.add(spec); } - void printBuffer() - { - print ("\n*********BUFFER**********"); - for (int i = 0; i < parseBuffer.size(); i ++) - print (" "+parseBuffer.get(i)); - print ("***************************"); - } } /** @@ -1533,10 +2010,6 @@ public class HTMLDocument extends DefaultStyledDocument } }; - // Set the parent HTML tag. - reader.parseStack.push(parent.getAttributes().getAttribute( - StyleConstants.NameAttribute)); - return reader; } @@ -1728,4 +2201,98 @@ public void setOuterHTML(Element elem, String htmlText) // TODO charset getParser().parse(new StringReader(htmlText), reader, true); } + + /** + * Overridden to tag content with the synthetic HTML.Tag.CONTENT + * tag. + */ + protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att) + { + if (att == null) + { + SimpleAttributeSet sas = new SimpleAttributeSet(); + sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); + att = sas; + } + super.insertUpdate(evt, att); + } + + /** + * Returns <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise. + * + * @return <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise + */ + boolean isFrameDocument() + { + return frameDocument; + } + + /** + * Set <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise. + * + * @param frameDoc <code>true</code> when this document is inside a frame, + * <code>false</code> otherwise + */ + void setFrameDocument(boolean frameDoc) + { + frameDocument = frameDoc; + } + + /** + * Returns the target that is specified in the base tag, if this is the case. + * + * @return the target that is specified in the base tag, if this is the case + */ + String getBaseTarget() + { + return baseTarget; + } + + /** + * Updates the A tag's pseudo class value in response to a hyperlink + * action. + * + * @param el the corresponding element + * @param value the new value + */ + void updateSpecialClass(Element el, HTML.Attribute cl, String value) + { + try + { + writeLock(); + DefaultDocumentEvent ev = + new DefaultDocumentEvent(el.getStartOffset(), 1, + DocumentEvent.EventType.CHANGE); + AttributeSet elAtts = el.getAttributes(); + AttributeSet anchorAtts = (AttributeSet) elAtts.getAttribute(HTML.Tag.A); + if (anchorAtts != null) + { + AttributeSet copy = elAtts.copyAttributes(); + StyleSheet ss = getStyleSheet(); + if (value != null) + { + anchorAtts = ss.addAttribute(anchorAtts, cl, value); + } + else + { + anchorAtts = ss.removeAttribute(anchorAtts, cl); + } + MutableAttributeSet matts = (MutableAttributeSet) elAtts; + ev.addEdit(new AttributeUndoableEdit(el, copy, false)); + matts.removeAttribute(HTML.Tag.A); + matts.addAttribute(HTML.Tag.A, anchorAtts); + ev.end(); + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + } + finally + { + writeUnlock(); + } + } + } diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java index 5d77be8fdd4..0ede1c74ed9 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java +++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java @@ -39,32 +39,38 @@ exception statement from your version. */ package javax.swing.text.html; -import gnu.classpath.NotImplementedException; - import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.Cursor; +import java.awt.Point; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; import java.io.Writer; +import java.net.MalformedURLException; +import java.net.URL; import javax.accessibility.Accessible; import javax.accessibility.AccessibleContext; import javax.swing.Action; import javax.swing.JEditorPane; +import javax.swing.SwingUtilities; +import javax.swing.event.HyperlinkEvent; +import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.EditorKit; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.StyleConstants; -import javax.swing.text.StyleContext; +import javax.swing.text.StyledDocument; import javax.swing.text.StyledEditorKit; import javax.swing.text.TextAction; import javax.swing.text.View; @@ -74,7 +80,7 @@ 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; +import gnu.javax.swing.text.html.parser.HTML_401F; /** * @author Lillian Angel (langel at redhat dot com) @@ -92,7 +98,12 @@ public class HTMLEditorKit extends MouseAdapter implements MouseMotionListener, Serializable { - + + /** + * The element of the last anchor tag. + */ + private Element lastAnchorElement; + /** * Constructor */ @@ -110,11 +121,14 @@ public class HTMLEditorKit */ public void mouseClicked(MouseEvent e) { - /* - These MouseInputAdapter methods generate mouse appropriate events around - hyperlinks (entering, exiting, and activating). - */ - // FIXME: Not implemented. + JEditorPane editor = (JEditorPane) e.getSource(); + if (! editor.isEditable() && SwingUtilities.isLeftMouseButton(e)) + { + Point loc = e.getPoint(); + int pos = editor.viewToModel(loc); + if (pos >= 0) + activateLink(pos, editor, e.getX(), e.getY()); + } } /** @@ -124,11 +138,7 @@ public class HTMLEditorKit */ public void mouseDragged(MouseEvent e) { - /* - These MouseInputAdapter methods generate mouse appropriate events around - hyperlinks (entering, exiting, and activating). - */ - // FIXME: Not implemented. + // Nothing to do here. } /** @@ -138,29 +148,159 @@ public class HTMLEditorKit */ public void mouseMoved(MouseEvent e) { - /* - These MouseInputAdapter methods generate mouse appropriate events around - hyperlinks (entering, exiting, and activating). - */ - // FIXME: Not implemented. + JEditorPane editor = (JEditorPane) e.getSource(); + HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit(); + if (! editor.isEditable()) + { + Document doc = editor.getDocument(); + if (doc instanceof HTMLDocument) + { + Cursor newCursor = kit.getDefaultCursor(); + HTMLDocument htmlDoc = (HTMLDocument) doc; + Point loc = e.getPoint(); + int pos = editor.viewToModel(loc); + Element el = htmlDoc.getCharacterElement(pos); + if (pos < el.getStartOffset() || pos >= el.getEndOffset()) + el = null; + if (el != null) + { + AttributeSet aAtts = (AttributeSet) + el.getAttributes().getAttribute(HTML.Tag.A); + if (aAtts != null) + { + if (el != lastAnchorElement) + { + if (lastAnchorElement != null) + htmlDoc.updateSpecialClass(lastAnchorElement, + HTML.Attribute.DYNAMIC_CLASS, + null); + lastAnchorElement = el; + htmlDoc.updateSpecialClass(el, + HTML.Attribute.DYNAMIC_CLASS, + "hover"); + } + newCursor = kit.getLinkCursor(); + } + else + { + if (lastAnchorElement != null) + htmlDoc.updateSpecialClass(lastAnchorElement, + HTML.Attribute.DYNAMIC_CLASS, + null); + lastAnchorElement = null; + } + } + else + { + if (lastAnchorElement != null) + htmlDoc.updateSpecialClass(lastAnchorElement, + HTML.Attribute.DYNAMIC_CLASS, + null); + lastAnchorElement = null; + } + if (editor.getCursor() != newCursor) + { + editor.setCursor(newCursor); + } + } + } } - + /** * If the given position represents a link, then linkActivated is called - * on the JEditorPane. Implemented to forward to the method with the same - * name, but pos == editor == -1. - * - * @param pos - the position - * @param editor - the editor pane + * on the JEditorPane. + * + * @param pos the position + * @param editor the editor pane + */ + protected void activateLink(int pos, JEditorPane editor) + { + activateLink(pos, editor); + } + + private void activateLink(int pos, JEditorPane editor, int x, int y) + { + // TODO: This is here for future extension for mapped links support. + // For the time beeing we implement simple hyperlinks. + Document doc = editor.getDocument(); + if (doc instanceof HTMLDocument) + { + HTMLDocument htmlDoc = (HTMLDocument) doc; + Element el = htmlDoc.getCharacterElement(pos); + AttributeSet atts = el.getAttributes(); + AttributeSet anchorAtts = + (AttributeSet) atts.getAttribute(HTML.Tag.A); + String href = null; + if (anchorAtts != null) + { + href = (String) anchorAtts.getAttribute(HTML.Attribute.HREF); + htmlDoc.updateSpecialClass(el, HTML.Attribute.PSEUDO_CLASS, + "visited"); + } + else + { + // TODO: Implement link maps here. + } + HyperlinkEvent event = null; + if (href != null) + event = createHyperlinkEvent(editor, htmlDoc, href, + anchorAtts, el); + if (event != null) + editor.fireHyperlinkUpdate(event); + } + + } + + /** + * Creates a HyperlinkEvent for the specified href and anchor if + * possible. If for some reason this won't work, return null. + * + * @param editor the editor + * @param doc the document + * @param href the href link + * @param anchor the anchor + * @param el the element + * + * @return the hyperlink event, or <code>null</code> if we couldn't + * create one */ - protected void activateLink(int pos, - JEditorPane editor) + private HyperlinkEvent createHyperlinkEvent(JEditorPane editor, + HTMLDocument doc, + String href, + AttributeSet anchor, + Element el) { - /* - This method creates and fires a HyperlinkEvent if the document is an - instance of HTMLDocument and the href tag of the link is not null. - */ - // FIXME: Not implemented. + URL url; + try + { + URL base = doc.getBase(); + url = new URL(base, href); + + } + catch (MalformedURLException ex) + { + url = null; + } + HyperlinkEvent ev; + if (doc.isFrameDocument()) + { + String target = null; + if (anchor != null) + target = (String) anchor.getAttribute(HTML.Attribute.TARGET); + if (target == null || target.equals("")) + target = doc.getBaseTarget(); + if (target == null || target.equals("")) + target = "_self"; + ev = new HTMLFrameHyperlinkEvent(editor, + HyperlinkEvent.EventType.ACTIVATED, + url, href, el, target); + } + else + { + ev = new HyperlinkEvent(editor, HyperlinkEvent.EventType.ACTIVATED, + url, href, el); + } + return ev; } } @@ -201,7 +341,7 @@ public class HTMLEditorKit * Tag to check for in the document. */ protected HTML.Tag parentTag; - + /** * Initializes all fields. * @@ -305,20 +445,9 @@ public class HTMLEditorKit Element insertElement, String html, HTML.Tag parentTag, HTML.Tag addTag) - throws NotImplementedException { - /* - As its name implies, this protected method is used when HTML is inserted at a - boundary. (A boundary in this case is an offset in doc that exactly matches the - beginning offset of the parentTag.) It performs the extra work required to keep - the tag stack in shape and then calls insertHTML(). The editor and doc argu- - ments are the editor pane and document where the HTML should go. The offset - argument represents the cursor location or selection start in doc. The insert- - Element and parentTag arguments are used to calculate the proper number of - tag pops and pushes before inserting the HTML (via html and addTag, which are - passed directly to insertHTML()). - */ - // FIXME: not implemented + insertAtBoundry(editor, doc, offset, insertElement, + html, parentTag, addTag); } /** @@ -344,8 +473,50 @@ public class HTMLEditorKit String html, HTML.Tag parentTag, HTML.Tag addTag) { - insertAtBoundary(editor, doc, offset, insertElement, - html, parentTag, addTag); + Element parent = insertElement; + Element el; + // Find common parent element. + if (offset > 0 || insertElement == null) + { + el = doc.getDefaultRootElement(); + while (el != null && el.getStartOffset() != offset + && ! el.isLeaf()) + el = el.getElement(el.getElementIndex(offset)); + parent = el != null ? el.getParentElement() : null; + } + if (parent != null) + { + int pops = 0; + int pushes = 0; + if (offset == 0 && insertElement != null) + { + el = parent; + while (el != null && ! el.isLeaf()) + { + el = el.getElement(el.getElementIndex(offset)); + pops++; + } + } + else + { + el = parent; + offset--; + while (el != null && ! el.isLeaf()) + { + el = el.getElement(el.getElementIndex(offset)); + pops++; + } + el = parent; + offset++; + while (el != null && el != insertElement) + { + el = el.getElement(el.getElementIndex(offset)); + pushes++; + } + } + pops = Math.max(0, pops - 1); + insertHTML(editor, doc, offset, html, pops, pushes, addTag); + } } /** @@ -355,16 +526,97 @@ public class HTMLEditorKit */ public void actionPerformed(ActionEvent ae) { - Object source = ae.getSource(); - if (source instanceof JEditorPane) + JEditorPane source = getEditor(ae); + if (source != null) + { + HTMLDocument d = getHTMLDocument(source); + int offset = source.getSelectionStart(); + int length = d.getLength(); + boolean inserted = true; + if (! tryInsert(source, d, offset, parentTag, addTag)) + { + inserted = tryInsert(source, d, offset, alternateParentTag, + alternateAddTag); + } + if (inserted) + adjustSelection(source, d, offset, length); + } + } + + /** + * Tries to insert the html chunk to the specified <code>addTag</code>. + * + * @param pane the editor + * @param doc the document + * @param offset the offset at which to insert + * @param tag the tag at which to insert + * @param addTag the add tag + * + * @return <code>true</code> when the html has been inserted successfully, + * <code>false</code> otherwise + */ + private boolean tryInsert(JEditorPane pane, HTMLDocument doc, int offset, + HTML.Tag tag, HTML.Tag addTag) + { + boolean inserted = false; + Element el = findElementMatchingTag(doc, offset, tag); + if (el != null && el.getStartOffset() == offset) + { + insertAtBoundary(pane, doc, offset, el, html, tag, addTag); + inserted = true; + } + else if (offset > 0) + { + int depth = elementCountToTag(doc, offset - 1, tag); + if (depth != -1) + { + insertHTML(pane, doc, offset, html, depth, 0, addTag); + inserted = true; + } + } + return inserted; + } + + /** + * Adjusts the selection after an insertion has been performed. + * + * @param pane the editor pane + * @param doc the document + * @param offset the insert offset + * @param oldLen the old document length + */ + private void adjustSelection(JEditorPane pane, HTMLDocument doc, + int offset, int oldLen) + { + int newLen = doc.getLength(); + if (newLen != oldLen && offset < newLen) { - JEditorPane pane = ((JEditorPane) source); - Document d = pane.getDocument(); - if (d instanceof HTMLDocument) - insertHTML(pane, (HTMLDocument) d, 0, html, 0, 0, addTag); - // FIXME: is this correct parameters? + if (offset > 0) + { + String text; + try + { + text = doc.getText(offset - 1, 1); + } + catch (BadLocationException ex) + { + text = null; + } + if (text != null && text.length() > 0 + && text.charAt(0) == '\n') + { + pane.select(offset, offset); + } + else + { + pane.select(offset + 1, offset + 1); + } + } + else + { + pane.select(1, 1); + } } - // FIXME: else not implemented } } @@ -540,53 +792,56 @@ public class HTMLEditorKit { HTML.Tag tag = (HTML.Tag) attr; - if (tag.equals(HTML.Tag.IMPLIED) || tag.equals(HTML.Tag.P) - || tag.equals(HTML.Tag.H1) || tag.equals(HTML.Tag.H2) - || tag.equals(HTML.Tag.H3) || tag.equals(HTML.Tag.H4) - || tag.equals(HTML.Tag.H5) || tag.equals(HTML.Tag.H6) - || tag.equals(HTML.Tag.DT)) + if (tag == HTML.Tag.IMPLIED || tag == HTML.Tag.P + || tag == HTML.Tag.H1 || tag == HTML.Tag.H2 + || tag == HTML.Tag.H3 || tag == HTML.Tag.H4 + || tag == HTML.Tag.H5 || tag == HTML.Tag.H6 + || tag == HTML.Tag.DT) view = new ParagraphView(element); - else if (tag.equals(HTML.Tag.LI) || tag.equals(HTML.Tag.DL) - || tag.equals(HTML.Tag.DD) || tag.equals(HTML.Tag.BODY) - || tag.equals(HTML.Tag.HTML) || tag.equals(HTML.Tag.CENTER) - || tag.equals(HTML.Tag.DIV) - || tag.equals(HTML.Tag.BLOCKQUOTE) - || tag.equals(HTML.Tag.PRE)) + else if (tag == HTML.Tag.LI || tag == HTML.Tag.DL + || tag == HTML.Tag.DD || tag == HTML.Tag.BODY + || tag == HTML.Tag.HTML || tag == HTML.Tag.CENTER + || tag == HTML.Tag.DIV + || tag == HTML.Tag.BLOCKQUOTE + || tag == HTML.Tag.PRE + || tag == HTML.Tag.FORM + // Misplaced TD and TH tags get mapped as vertical block. + // Note that correctly placed tags get mapped in TableView. + || tag == HTML.Tag.TD || tag == HTML.Tag.TH) view = new BlockView(element, View.Y_AXIS); - else if (tag.equals(HTML.Tag.IMG)) + else if (tag == HTML.Tag.TR) + // Misplaced TR tags get mapped as horizontal blocks. + // Note that correctly placed tags get mapped in TableView. + view = new BlockView(element, View.X_AXIS); + else if (tag == HTML.Tag.IMG) view = new ImageView(element); - // FIXME: Uncomment when the views have been implemented - else if (tag.equals(HTML.Tag.CONTENT)) + else if (tag == HTML.Tag.CONTENT) view = new InlineView(element); else if (tag == HTML.Tag.HEAD) view = new NullView(element); - else if (tag.equals(HTML.Tag.TABLE)) + else if (tag == HTML.Tag.TABLE) view = new javax.swing.text.html.TableView(element); - else if (tag.equals(HTML.Tag.TD)) - view = new ParagraphView(element); - else if (tag.equals(HTML.Tag.HR)) + else if (tag == HTML.Tag.HR) view = new HRuleView(element); - else if (tag.equals(HTML.Tag.BR)) + else if (tag == HTML.Tag.BR) view = new BRView(element); + else if (tag == HTML.Tag.INPUT || tag == HTML.Tag.SELECT + || tag == HTML.Tag.TEXTAREA) + view = new FormView(element); - /* - else if (tag.equals(HTML.Tag.MENU) || tag.equals(HTML.Tag.DIR) - || tag.equals(HTML.Tag.UL) || tag.equals(HTML.Tag.OL)) + else if (tag == HTML.Tag.MENU || tag == HTML.Tag.DIR + || tag == HTML.Tag.UL || tag == HTML.Tag.OL) view = new ListView(element); - else if (tag.equals(HTML.Tag.INPUT) || tag.equals(HTML.Tag.SELECT) - || tag.equals(HTML.Tag.TEXTAREA)) - view = new FormView(element); - else if (tag.equals(HTML.Tag.OBJECT)) - view = new ObjectView(element); - else if (tag.equals(HTML.Tag.FRAMESET)) + else if (tag == HTML.Tag.FRAMESET) view = new FrameSetView(element); - else if (tag.equals(HTML.Tag.FRAME)) - view = new FrameView(element); */ + else if (tag == HTML.Tag.FRAME) + view = new FrameView(element); + else if (tag == HTML.Tag.OBJECT) + view = new ObjectView(element); } if (view == null) { - System.err.println("missing tag->view mapping for: " + element); view = new NullView(element); } return view; @@ -797,14 +1052,42 @@ public class HTMLEditorKit /** * Actions for HTML */ - private static final Action[] defaultActions = { - // FIXME: Add default actions for html + private static final Action[] defaultActions = + { + new InsertHTMLTextAction("InsertTable", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableRow", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.TABLE, HTML.Tag.TR, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableCell", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.TR, HTML.Tag.TD, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertUnorderedList", + "<ul><li></li></ul>", + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertUnorderedListItem", + "<ul><li></li></ul>", + HTML.Tag.UL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertOrderedList", + "<ol><li></li></ol>", + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertOrderedListItem", + "<ol><li></li></ol>", + HTML.Tag.OL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertPre", + "<pre></pre>", HTML.Tag.BODY, HTML.Tag.PRE) + // TODO: The reference impl has an InsertHRAction too. }; /** * The current style sheet. */ - StyleSheet styleSheet; + private StyleSheet styleSheet; /** * The ViewFactory for HTMLFactory. @@ -829,12 +1112,7 @@ public class HTMLEditorKit /** * The mouse listener used for links. */ - LinkController mouseListener; - - /** - * Style context for this editor. - */ - StyleContext styleContext; + private LinkController linkController; /** The content type */ String contentType = "text/html"; @@ -844,17 +1122,22 @@ public class HTMLEditorKit /** The editor pane used. */ JEditorPane editorPane; - + + /** + * Whether or not the editor kit handles form submissions. + * + * @see #isAutoFormSubmission() + * @see #setAutoFormSubmission(boolean) + */ + private boolean autoFormSubmission; + /** * Constructs an HTMLEditorKit, creates a StyleContext, and loads the style sheet. */ public HTMLEditorKit() { - super(); - styleContext = new StyleContext(); - styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS)); - // FIXME: Set inputAttributes with default.css + linkController = new LinkController(); + autoFormSubmission = true; } /** @@ -877,8 +1160,15 @@ public class HTMLEditorKit */ public Document createDefaultDocument() { - HTMLDocument document = new HTMLDocument(getStyleSheet()); + // Protect the shared stylesheet. + StyleSheet styleSheet = getStyleSheet(); + StyleSheet ss = new StyleSheet(); + ss.addStyleSheet(styleSheet); + + HTMLDocument document = new HTMLDocument(ss); document.setParser(getParser()); + document.setAsynchronousLoadPriority(4); + document.setTokenThreshold(100); return document; } @@ -892,7 +1182,7 @@ public class HTMLEditorKit { if (parser == null) { - parser = new GnuParserDelegator(HTML_401Swing.getInstance()); + parser = new GnuParserDelegator(HTML_401F.getInstance()); } return parser; } @@ -923,8 +1213,7 @@ public class HTMLEditorKit if (parser == null) throw new IOException("Parser is null."); - ParserCallback pc = ((HTMLDocument) doc).getReader - (offset, popDepth, pushDepth, insertTag); + ParserCallback pc = doc.getReader(offset, popDepth, pushDepth, insertTag); // FIXME: What should ignoreCharSet be set to? @@ -991,8 +1280,15 @@ public class HTMLEditorKit { if (doc instanceof HTMLDocument) { - // FIXME: Not implemented. Use HTMLWriter. - out.write(doc.getText(pos, len)); + HTMLWriter writer = new HTMLWriter(out, (HTMLDocument) doc, pos, len); + writer.write(); + } + else if (doc instanceof StyledDocument) + { + MinimalHTMLWriter writer = new MinimalHTMLWriter(out, + (StyledDocument) doc, + pos, len); + writer.write(); } else super.write(out, doc, pos, len); @@ -1017,7 +1313,9 @@ public class HTMLEditorKit public Object clone() { // FIXME: Need to clone all fields - return (HTMLEditorKit) super.clone(); + HTMLEditorKit copy = (HTMLEditorKit) super.clone(); + copy.linkController = new LinkController(); + return copy; } /** @@ -1044,10 +1342,9 @@ public class HTMLEditorKit public void install(JEditorPane c) { super.install(c); - mouseListener = new LinkController(); - c.addMouseListener(mouseListener); + c.addMouseListener(linkController); + c.addMouseMotionListener(linkController); editorPane = c; - // FIXME: need to set up hyperlinklistener object } /** @@ -1059,8 +1356,8 @@ public class HTMLEditorKit public void deinstall(JEditorPane c) { super.deinstall(c); - c.removeMouseListener(mouseListener); - mouseListener = null; + c.removeMouseListener(linkController); + c.removeMouseMotionListener(linkController); editorPane = null; } @@ -1154,8 +1451,19 @@ public class HTMLEditorKit { if (styleSheet == null) { - styleSheet = new StyleSheet(); - styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS)); + try + { + styleSheet = new StyleSheet(); + Class c = HTMLEditorKit.class; + InputStream in = c.getResourceAsStream(DEFAULT_CSS); + InputStreamReader r = new InputStreamReader(in); + styleSheet.loadRules(r, null); + r.close(); + } + catch (IOException ex) + { + // No style available. + } } return styleSheet; } @@ -1173,4 +1481,40 @@ public class HTMLEditorKit { styleSheet = s; } + + /** + * Returns <code>true</code> when forms should be automatically submitted + * by the editor kit. Set this to <code>false</code> when you want to + * intercept form submission. In this case you'd want to listen for + * hyperlink events on the document and handle FormSubmitEvents specially. + * + * The default is <code>true</code>. + * + * @return <code>true</code> when forms should be automatically submitted + * by the editor kit, <code>false</code> otherwise + * + * @since 1.5 + * + * @see #setAutoFormSubmission(boolean) + * @see FormSubmitEvent + */ + public boolean isAutoFormSubmission() + { + return autoFormSubmission; + } + + /** + * Sets whether or not the editor kit should automatically submit forms. + * + * @param auto <code>true</code> when the editor kit should handle form + * submission, <code>false</code> otherwise + * + * @since 1.5 + * + * @see #isAutoFormSubmission() + */ + public void setAutoFormSubmission(boolean auto) + { + autoFormSubmission = auto; + } } diff --git a/libjava/classpath/javax/swing/text/html/HTMLWriter.java b/libjava/classpath/javax/swing/text/html/HTMLWriter.java new file mode 100644 index 00000000000..44119c73286 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/HTMLWriter.java @@ -0,0 +1,1084 @@ +/* HTMLWriter.java -- + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.text.html; + +import java.io.IOException; +import java.io.Writer; + +import java.util.Enumeration; +import java.util.HashSet; + +import javax.swing.ComboBoxModel; + +import javax.swing.text.AbstractWriter; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; + +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.Option; + +/** + * HTMLWriter, + * A Writer for HTMLDocuments. + * + * @author David Fu (fchoong at netbeans.jp) + */ + +public class HTMLWriter + extends AbstractWriter +{ + /** + * We keep a reference of the writer passed by the construct. + */ + private Writer outWriter = null; + + /** + * We keep a reference of the HTMLDocument passed by the construct. + */ + private HTMLDocument htmlDoc = null; + + /** + * Used to keep track of which embeded has been written out. + */ + private HashSet openEmbededTagHashSet = null; + + private String new_line_str = "" + NEWLINE; + + private char[] html_entity_char_arr = {'<', '>', '&', '"'}; + + private String[] html_entity_escape_str_arr = {"<", ">", "&", + """}; + + // variables used to output Html Fragment + private int doc_pos = -1; + private int doc_len = -1; + private int doc_offset_remaining = -1; + private int doc_len_remaining = -1; + private HashSet htmlFragmentParentHashSet = null; + private Element startElem = null; + private Element endElem = null; + private boolean fg_pass_start_elem = false; + private boolean fg_pass_end_elem = false; + + /** + * Constructs a HTMLWriter. + * + * @param writer writer to write output to + * @param doc the HTMLDocument to output + */ + public HTMLWriter(Writer writer, HTMLDocument doc) + { + super(writer, doc); + outWriter = writer; + htmlDoc = doc; + openEmbededTagHashSet = new HashSet(); + } // public HTMLWriter(Writer writer, HTMLDocument doc) + + /** + * Constructs a HTMLWriter which outputs a Html Fragment. + * + * @param writer <code>Writer</code> to write output to + * @param doc the <code>javax.swing.text.html.HTMLDocument</code> + * to output + * @param pos position to start outputing the document + * @param len amount to output the document + */ + public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len) + { + super(writer, doc, pos, len); + outWriter = writer; + htmlDoc = doc; + openEmbededTagHashSet = new HashSet(); + + doc_pos = pos; + doc_offset_remaining = pos; + doc_len = len; + doc_len_remaining = len; + htmlFragmentParentHashSet = new HashSet(); + } // public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len) + + /** + * Call this method to start outputing HTML. + * + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + public void write() + throws IOException, BadLocationException + { + Element rootElem = htmlDoc.getDefaultRootElement(); + + if (doc_pos == -1 && doc_len == -1) + { + // Normal traversal. + traverse(rootElem); + } // if(doc_pos == -1 && doc_len == -1) + else + { + // Html fragment traversal. + if (doc_pos == -1 || doc_len == -1) + throw new BadLocationException("Bad Location(" + + doc_pos + ", " + doc_len + ")", doc_pos); + + startElem = htmlDoc.getCharacterElement(doc_pos); + + int start_offset = startElem.getStartOffset(); + + // Positions before start_offset will not be traversed, and thus + // will not be counted. + if (start_offset > 0) + doc_offset_remaining = doc_offset_remaining - start_offset; + + Element tempParentElem = startElem; + + while ((tempParentElem = tempParentElem.getParentElement()) != null) + { + if (!htmlFragmentParentHashSet.contains(tempParentElem)) + htmlFragmentParentHashSet.add(tempParentElem); + } // while((tempParentElem = tempParentElem.getParentElement()) + // != null) + + // NOTE: 20061030 - fchoong - the last index should not be included. + endElem = htmlDoc.getCharacterElement(doc_pos + doc_len - 1); + + tempParentElem = endElem; + + while ((tempParentElem = tempParentElem.getParentElement()) != null) + { + if (!htmlFragmentParentHashSet.contains(tempParentElem)) + htmlFragmentParentHashSet.add(tempParentElem); + } // while((tempParentElem = tempParentElem.getParentElement()) + // != null) + + traverseHtmlFragment(rootElem); + + } // else + + // NOTE: close out remaining open embeded tags. + Object[] tag_arr = openEmbededTagHashSet.toArray(); + + for (int i = 0; i < tag_arr.length; i++) + { + writeRaw("</" + tag_arr[i].toString() + ">"); + } // for(int i = 0; i < tag_arr.length; i++) + + } // public void write() throws IOException, BadLocationException + + /** + * Writes all the attributes in the attrSet, except for attrbutes with + * keys of <code>javax.swing.text.html.HTML.Tag</code>, + * <code>javax.swing.text.StyleConstants</code> or + * <code>javax.swing.text.html.HTML.Attribute.ENDTAG</code>. + * + * @param attrSet attrSet to write out + * + * @throws IOException on any I/O exceptions + */ + protected void writeAttributes(AttributeSet attrSet) + throws IOException + { + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + // HTML.Attribute.ENDTAG is an instance, not a class. + if (!((key instanceof HTML.Tag) || (key instanceof StyleConstants) + || (key == HTML.Attribute.ENDTAG))) + { + if (key == HTML.Attribute.SELECTED) + writeRaw(" selected"); + else if (key == HTML.Attribute.CHECKED) + writeRaw(" checked"); + else + writeRaw(" " + key + "=\"" + value + "\""); + } // if(!((key instanceof HTML.Tag) || (key instanceof + // StyleConstants) || (key == HTML.Attribute.ENDTAG))) + } // while(attrNameEnum.hasMoreElements()) + + } // protected void writeAttributes(AttributeSet attrSet) throws IOException + + /** + * Writes out an empty tag. i.e. a tag without any child elements. + * + * @param paramElem the element to output as an empty tag + * + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void emptyTag(Element paramElem) + throws IOException, BadLocationException + { + String elem_name = paramElem.getName(); + AttributeSet attrSet = paramElem.getAttributes(); + + writeRaw("<" + elem_name); + writeAttributes(attrSet); + writeRaw(">"); + + if (isBlockTag(attrSet)) + { + writeRaw("</" + elem_name + ">"); + } // if(isBlockTag(attrSet)) + + } // protected void emptyTag(Element paramElem) + // throws IOException, BadLocationException + + /** + * Determines if it is a block tag or not. + * + * @param attrSet the attrSet of the element + * + * @return <code>true</code> if it is a block tag + * <code>false</code> if it is a not block tag + */ + protected boolean isBlockTag(AttributeSet attrSet) + { + return ((HTML.Tag) + attrSet.getAttribute(StyleConstants.NameAttribute)).isBlock(); + } // protected boolean isBlockTag(AttributeSet attrSet) + + /** + * Writes out a start tag. Synthesized elements are skipped. + * + * @param paramElem the element to output as a start tag + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void startTag(Element paramElem) + throws IOException, BadLocationException + { + // NOTE: Sysnthesized elements do no call this method at all. + String elem_name = paramElem.getName(); + AttributeSet attrSet = paramElem.getAttributes(); + + indent(); + writeRaw("<" + elem_name); + writeAttributes(attrSet); + writeRaw(">"); + writeLineSeparator(); // Extra formatting to look more like the RI. + incrIndent(); + + } // protected void startTag(Element paramElem) + // throws IOException, BadLocationException + + /** + * Writes out the contents of a textarea. + * + * @param attrSet the attrSet of the element to output as a text area + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void textAreaContent(AttributeSet attrSet) + throws IOException, BadLocationException + { + writeLineSeparator(); // Extra formatting to look more like the RI. + indent(); + writeRaw("<textarea"); + writeAttributes(attrSet); + writeRaw(">"); + + Document tempDocument = + (Document) attrSet.getAttribute(StyleConstants.ModelAttribute); + + writeRaw(tempDocument.getText(0, tempDocument.getLength())); + indent(); + writeRaw("</textarea>"); + + } // protected void textAreaContent(AttributeSet attrSet) + // throws IOException, BadLocationException + + /** + * Writes out text, within the appropriate range if it is specified. + * + * @param paramElem the element to output as a text + * @throws IOException on any I/O exceptions + * @throws BadLocationException if a pos is not a valid position in the + * html doc element + */ + protected void text(Element paramElem) + throws IOException, BadLocationException + { + int offset = paramElem.getStartOffset(); + int len = paramElem.getEndOffset() - paramElem.getStartOffset(); + String txt_value = htmlDoc.getText(offset, len); + + writeContent(txt_value); + + } // protected void text(Element paramElem) + // throws IOException, BadLocationException + + /** + * Writes out the contents of a select element. + * + * @param attrSet the attrSet of the element to output as a select box + * + * @throws IOException on any I/O exceptions + */ + protected void selectContent(AttributeSet attrSet) + throws IOException + { + writeLineSeparator(); // Extra formatting to look more like the RI. + indent(); + writeRaw("<select"); + writeAttributes(attrSet); + writeRaw(">"); + incrIndent(); + writeLineSeparator(); // extra formatting to look more like the RI. + + ComboBoxModel comboBoxModel = + (ComboBoxModel) attrSet.getAttribute(StyleConstants.ModelAttribute); + + for (int i = 0; i < comboBoxModel.getSize(); i++) + { + writeOption((Option) comboBoxModel.getElementAt(i)); + } // for(int i = 0; i < comboBoxModel.getSize(); i++) + + decrIndent(); + indent(); + writeRaw("</select>"); + + } // protected void selectContent(AttributeSet attrSet) throws IOException + + /** + * Writes out the contents of an option element. + * + * @param option the option object to output as a select option + * + * @throws IOException on any I/O exceptions + */ + protected void writeOption(Option option) + throws IOException + { + indent(); + writeRaw("<option"); + writeAttributes(option.getAttributes()); + writeRaw(">"); + + writeContent(option.getLabel()); + + writeRaw("</option>"); + writeLineSeparator(); // extra formatting to look more like the RI. + + } // protected void writeOption(Option option) throws IOException + + /** + * Writes out an end tag. + * + * @param paramElem the element to output as an end tag + * + * @throws IOException on any I/O exceptions + */ + protected void endTag(Element paramElem) + throws IOException + { + String elem_name = paramElem.getName(); + + //writeLineSeparator(); // Extra formatting to look more like the RI. + decrIndent(); + indent(); + writeRaw("</" + elem_name + ">"); + writeLineSeparator(); // Extra formatting to look more like the RI. + + } // protected void endTag(Element paramElem) throws IOException + + /** + * Writes out the comment. + * + * @param paramElem the element to output as a comment + */ + protected void comment(Element paramElem) + throws IOException, BadLocationException + { + AttributeSet attrSet = paramElem.getAttributes(); + + String comment_str = (String) attrSet.getAttribute(HTML.Attribute.COMMENT); + + writeRaw("<!--" + comment_str + "-->"); + + } // protected void comment(Element paramElem) + // throws IOException, BadLocationException + + /** + * Determines if element is a synthesized + * <code>javax.swing.text.Element</code> or not. + * + * @param element the element to test + * + * @return <code>true</code> if it is a synthesized element, + * <code>false</code> if it is a not synthesized element + */ + protected boolean synthesizedElement(Element element) + { + AttributeSet attrSet = element.getAttributes(); + Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute); + + if (tagType == HTML.Tag.CONTENT || tagType == HTML.Tag.COMMENT + || tagType == HTML.Tag.IMPLIED) + return true; + else + return false; + } // protected boolean synthesizedElement(Element element) + + /** + * Determines if + * <code>javax.swing.text.StyleConstants.NameAttribute</code> + * matches tag or not. + * + * @param attrSet the <code>javax.swing.text.AttributeSet</code> of + * element to be matched + * @param tag the HTML.Tag to match + * + * @return <code>true</code> if it matches, + * <code>false</code> if it does not match + */ + protected boolean matchNameAttribute(AttributeSet attrSet, HTML.Tag tag) + { + Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute); + + if (tagType == tag) + return true; + else + return false; + } // protected boolean matchNameAttribute(AttributeSet attrSet, + // HTML.Tag tag) + + /** + * Writes out an embedded tag. The tags not already in + * openEmbededTagHashSet will written out. + * + * @param attrSet the <code>javax.swing.text.AttributeSet</code> of + * the element to write out + * + * @throws IOException on any I/O exceptions + */ + protected void writeEmbeddedTags(AttributeSet attrSet) + throws IOException + { + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + if (key instanceof HTML.Tag) + { + if (!openEmbededTagHashSet.contains(key)) + { + writeRaw("<" + key); + writeAttributes((AttributeSet) value); + writeRaw(">"); + openEmbededTagHashSet.add(key); + } // if(!openEmbededTagHashSet.contains(key)) + } // if(key instanceof HTML.Tag) + } // while(attrNameEnum.hasMoreElements()) + + } // protected void writeEmbeddedTags(AttributeSet attrSet) + // throws IOException + + /** + * Closes out an unwanted embedded tag. The tags from the + * openEmbededTagHashSet not found in attrSet will be written out. + * + * @param attrSet the AttributeSet of the element to write out + * + * @throws IOException on any I/O exceptions + */ + protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet) + throws IOException + { + Object[] tag_arr = openEmbededTagHashSet.toArray(); + + for (int i = 0; i < tag_arr.length; i++) + { + HTML.Tag key = (HTML.Tag) tag_arr[i]; + + if (!attrSet.isDefined(key)) + { + writeRaw("</" + key.toString() + ">"); + openEmbededTagHashSet.remove(key); + } // if(!attrSet.isDefined(key)) + } // for(int i = 0; i < tag_arr.length; i++) + + } // protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet) + // throws IOException + + /** + * Writes out a line separator. Overwrites the parent to write out a new + * line. + * + * @throws IOException on any I/O exceptions. + */ + protected void writeLineSeparator() + throws IOException + { + writeRaw(new_line_str); + } // protected void writeLineSeparator() throws IOException + + /** + * Write to the writer. Character entites such as <, > + * are escaped appropriately. + * + * @param chars char array to write out + * @param off offset + * @param len length + * + * @throws IOException on any I/O exceptions + */ + protected void output(char[] chars, int off, int len) + throws IOException + { + StringBuffer strBuffer = new StringBuffer(); + + for (int i = 0; i < chars.length; i++) + { + if (isCharHtmlEntity(chars[i])) + strBuffer.append(escapeCharHtmlEntity(chars[i])); + else + strBuffer.append(chars[i]); + } // for(int i = 0; i < chars.length; i++) + + writeRaw(strBuffer.toString()); + + } // protected void output(char[] chars, int off, int len) + // throws IOException + + //------------------------------------------------------------------------- + // private methods + + /** + * The main method used to traverse through the elements. + * + * @param paramElem element to traverse + * + * @throws IOException on any I/O exceptions + */ + private void traverse(Element paramElem) + throws IOException, BadLocationException + { + Element currElem = paramElem; + + AttributeSet attrSet = currElem.getAttributes(); + + closeOutUnwantedEmbeddedTags(attrSet); + + // handle the tag + if (synthesizedElement(paramElem)) + { + if (matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + { + writeEmbeddedTags(attrSet); + text(currElem); + } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + { + comment(currElem); + } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverse(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + } // if(child_elem_count > 0) + } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + } // if(synthesizedElement(paramElem)) + else + { + // NOTE: 20061030 - fchoong - title is treated specially here. + // based on RI behavior. + if (matchNameAttribute(attrSet, HTML.Tag.TITLE)) + { + boolean fg_is_end_tag = false; + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + if (key == HTML.Attribute.ENDTAG && value.equals("true")) + fg_is_end_tag = true; + } // while(attrNameEnum.hasMoreElements()) + + if (fg_is_end_tag) + writeRaw("</title>"); + else + { + indent(); + writeRaw("<title>"); + + String title_str = + (String) htmlDoc.getProperty(HTMLDocument.TitleProperty); + + if (title_str != null) + writeContent(title_str); + + } // else + } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE)) + else if (matchNameAttribute(attrSet, HTML.Tag.PRE)) + { + // We pursue more stringent formating here. + attrSet = paramElem.getAttributes(); + + indent(); + writeRaw("<pre"); + writeAttributes(attrSet); + writeRaw(">"); + + int child_elem_count = currElem.getElementCount(); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverse(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + writeRaw("</pre>"); + + } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE)) + else if (matchNameAttribute(attrSet, HTML.Tag.SELECT)) + { + selectContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT)) + else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + { + textAreaContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + else + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + startTag(currElem); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverse(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + endTag(currElem); + + } // if(child_elem_count > 0) + else + { + emptyTag(currElem); + } // else + } // else + } // else + + } // private void traverse(Element paramElem) + // throws IOException, BadLocationException + + /** + * The method used to traverse through a html fragment. + * + * @param paramElem element to traverse + * + * @throws IOException on any I/O exceptions + */ + private void traverseHtmlFragment(Element paramElem) + throws IOException, BadLocationException + { + // NOTE: This method is similar to traverse(Element paramElem) + Element currElem = paramElem; + + boolean fg_is_fragment_parent_elem = false; + boolean fg_is_start_and_end_elem = false; + + if (htmlFragmentParentHashSet.contains(paramElem)) + fg_is_fragment_parent_elem = true; + + if (paramElem == startElem) + fg_pass_start_elem = true; + + if (paramElem == startElem && paramElem == endElem) + fg_is_start_and_end_elem = true; + + AttributeSet attrSet = currElem.getAttributes(); + + closeOutUnwantedEmbeddedTags(attrSet); + + if (fg_is_fragment_parent_elem || (fg_pass_start_elem + && fg_pass_end_elem == false) || fg_is_start_and_end_elem) + { + // handle the tag + if (synthesizedElement(paramElem)) + { + if (matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + { + writeEmbeddedTags(attrSet); + + int content_offset = paramElem.getStartOffset(); + int content_length = currElem.getEndOffset() - content_offset; + + if (doc_offset_remaining > 0) + { + if (content_length > doc_offset_remaining) + { + int split_len = content_length; + + split_len = split_len - doc_offset_remaining; + + if (split_len > doc_len_remaining) + split_len = doc_len_remaining; + + // we need to split it. + String txt_value = htmlDoc.getText(content_offset + + doc_offset_remaining, split_len); + + writeContent(txt_value); + + doc_offset_remaining = 0; // the offset is used up. + doc_len_remaining = doc_len_remaining - split_len; + } // if(content_length > doc_offset_remaining) + else + { + // doc_offset_remaining is greater than the entire + // length of content + doc_offset_remaining = doc_offset_remaining + - content_length; + } // else + } // if(doc_offset_remaining > 0) + else if (content_length <= doc_len_remaining) + { + // we can fit the entire content. + text(currElem); + doc_len_remaining = doc_len_remaining - content_length; + } // else if(content_length <= doc_len_remaining) + else + { + // we need to split it. + String txt_value = htmlDoc.getText(content_offset, + doc_len_remaining); + + writeContent(txt_value); + + doc_len_remaining = 0; + } // else + + } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + { + comment(currElem); + } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT)) + else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverseHtmlFragment(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + } // if(child_elem_count > 0) + } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED)) + } // if(synthesizedElement(paramElem)) + else + { + // NOTE: 20061030 - fchoong - the isLeaf() condition seems to + // generate the closest behavior to the RI. + if (paramElem.isLeaf()) + { + if (doc_offset_remaining > 0) + { + doc_offset_remaining--; + } // if(doc_offset_remaining > 0) + else if (doc_len_remaining > 0) + { + doc_len_remaining--; + } // else if(doc_len_remaining > 0) + } // if(paramElem.isLeaf()) + + // NOTE: 20061030 - fchoong - title is treated specially here. + // based on RI behavior. + if (matchNameAttribute(attrSet, HTML.Tag.TITLE)) + { + boolean fg_is_end_tag = false; + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + if (key == HTML.Attribute.ENDTAG && value.equals("true")) + fg_is_end_tag = true; + } // while(attrNameEnum.hasMoreElements()) + + if (fg_is_end_tag) + writeRaw("</title>"); + else + { + indent(); + writeRaw("<title>"); + + String title_str = + (String) htmlDoc.getProperty(HTMLDocument.TitleProperty); + + if (title_str != null) + writeContent(title_str); + + } // else + } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE)) + else if (matchNameAttribute(attrSet, HTML.Tag.PRE)) + { + // We pursue more stringent formating here. + attrSet = paramElem.getAttributes(); + + indent(); + writeRaw("<pre"); + writeAttributes(attrSet); + writeRaw(">"); + + int child_elem_count = currElem.getElementCount(); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverseHtmlFragment(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + writeRaw("</pre>"); + + } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE)) + else if (matchNameAttribute(attrSet, HTML.Tag.SELECT)) + { + selectContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT)) + else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + { + textAreaContent(attrSet); + } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA)) + else + { + int child_elem_count = currElem.getElementCount(); + + if (child_elem_count > 0) + { + startTag(currElem); + + for (int i = 0; i < child_elem_count; i++) + { + Element childElem = paramElem.getElement(i); + + traverseHtmlFragment(childElem); + + } // for(int i = 0; i < child_elem_count; i++) + + endTag(currElem); + + } // if(child_elem_count > 0) + else + { + emptyTag(currElem); + } // else + } // else + } // else + + } // if(fg_is_fragment_parent_elem || (fg_pass_start_elem + // && fg_pass_end_elem == false) || fg_is_start_and_end_elem) + + if (paramElem == endElem) + fg_pass_end_elem = true; + + } // private void traverseHtmlFragment(Element paramElem) + // throws IOException, BadLocationException + + /** + * Write to the writer without any modifications. + * + * @param param_str the str to write out + * + * @throws IOException on any I/O exceptions + */ + private void writeRaw(String param_str) + throws IOException + { + super.output(param_str.toCharArray(), 0, param_str.length()); + } // private void writeRaw(char[] chars, int off, int len) + // throws IOException + + /** + * Write to the writer, escaping HTML character entitie where neccessary. + * + * @param param_str the str to write out + * + * @throws IOException on any I/O exceptions + */ + private void writeContent(String param_str) + throws IOException + { + char[] str_char_arr = param_str.toCharArray(); + + if (hasHtmlEntity(param_str)) + output(str_char_arr, 0, str_char_arr.length); + else + super.output(str_char_arr, 0, str_char_arr.length); + + } // private void writeContent(String param_str) throws IOException + + /** + * Use this for debugging. Writes out all attributes regardless of type. + * + * @param attrSet the <code>javax.swing.text.AttributeSet</code> to + * write out + * + * @throws IOException on any I/O exceptions + */ + private void writeAllAttributes(AttributeSet attrSet) + throws IOException + { + Enumeration attrNameEnum = attrSet.getAttributeNames(); + + while (attrNameEnum.hasMoreElements()) + { + Object key = attrNameEnum.nextElement(); + Object value = attrSet.getAttribute(key); + + writeRaw(" " + key + "=\"" + value + "\""); + writeRaw(" " + key.getClass().toString() + "=\"" + + value.getClass().toString() + "\""); + } // while(attrNameEnum.hasMoreElements()) + + } // private void writeAllAttributes(AttributeSet attrSet) + // throws IOException + + /** + * Tests if the str contains any html entities. + * + * @param param_str the str to test + * + * @return <code>true</code> if it has a html entity + * <code>false</code> if it does not have a html entity + */ + private boolean hasHtmlEntity(String param_str) + { + boolean ret_bool = false; + + for (int i = 0; i < html_entity_char_arr.length; i++) + { + if (param_str.indexOf(html_entity_char_arr[i]) != -1) + { + ret_bool = true; + break; + } // if(param_str.indexOf(html_entity_char_arr[i]) != -1) + } // for(int i = 0; i < html_entity_char_arr.length; i++) + + return ret_bool; + } // private boolean hasHtmlEntity(String param_str) + + /** + * Tests if the char is a html entities. + * + * @param param_char the char to test + * + * @return <code>true</code> if it is a html entity + * <code>false</code> if it is not a html entity. + */ + private boolean isCharHtmlEntity(char param_char) + { + boolean ret_bool = false; + + for (int i = 0; i < html_entity_char_arr.length; i++) + { + if (param_char == html_entity_char_arr[i]) + { + ret_bool = true; + break; + } // if(param_char == html_entity_char_arr[i]) + } // for(int i = 0; i < html_entity_char_arr.length; i++) + + return ret_bool; + } // private boolean hasHtmlEntity(String param_str) + + /** + * Escape html entities. + * + * @param param_char the char to escape + * + * @return escaped html entity. Original char is returned as a str if is + * is not a html entity + */ + private String escapeCharHtmlEntity(char param_char) + { + String ret_str = "" + param_char; + + for (int i = 0; i < html_entity_char_arr.length; i++) + { + if (param_char == html_entity_char_arr[i]) + { + ret_str = html_entity_escape_str_arr[i]; + break; + } // if(param_char == html_entity_char_arr[i]) + } // for(int i = 0; i < html_entity_char_arr.length; i++) + + return ret_str; + } // private String escapeCharHtmlEntity(char param_char) + +} // public class HTMLWriter extends AbstractWriter
\ No newline at end of file diff --git a/libjava/classpath/javax/swing/text/html/ImageView.java b/libjava/classpath/javax/swing/text/html/ImageView.java index 84b021070a9..bf906e4500e 100644 --- a/libjava/classpath/javax/swing/text/html/ImageView.java +++ b/libjava/classpath/javax/swing/text/html/ImageView.java @@ -1,18 +1,21 @@ package javax.swing.text.html; -import gnu.javax.swing.text.html.CombinedAttributes; import gnu.javax.swing.text.html.ImageViewIconFactory; +import gnu.javax.swing.text.html.css.Length; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.Toolkit; +import java.awt.image.ImageObserver; import java.net.MalformedURLException; import java.net.URL; import javax.swing.Icon; -import javax.swing.ImageIcon; +import javax.swing.SwingUtilities; +import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; @@ -29,15 +32,38 @@ import javax.swing.text.html.HTML.Attribute; public class ImageView extends View { /** + * Tracks image loading state and performs the necessary layout updates. + */ + class Observer + implements ImageObserver + { + + public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height) + { + boolean widthChanged = false; + if ((flags & ImageObserver.WIDTH) != 0 && spans[X_AXIS] == null) + widthChanged = true; + boolean heightChanged = false; + if ((flags & ImageObserver.HEIGHT) != 0 && spans[Y_AXIS] == null) + heightChanged = true; + if (widthChanged || heightChanged) + safePreferenceChanged(ImageView.this, widthChanged, heightChanged); + boolean ret = (flags & ALLBITS) != 0; + return ret; + } + + } + + /** * True if the image loads synchronuosly (on demand). By default, the image * loads asynchronuosly. */ boolean loadOnDemand; - + /** * The image icon, wrapping the image, */ - ImageIcon imageIcon; + Image image; /** * The image state. @@ -45,6 +71,58 @@ public class ImageView extends View byte imageState = MediaTracker.LOADING; /** + * True when the image needs re-loading, false otherwise. + */ + private boolean reloadImage; + + /** + * True when the image properties need re-loading, false otherwise. + */ + private boolean reloadProperties; + + /** + * True when the width is set as CSS/HTML attribute. + */ + private boolean haveWidth; + + /** + * True when the height is set as CSS/HTML attribute. + */ + private boolean haveHeight; + + /** + * True when the image is currently loading. + */ + private boolean loading; + + /** + * The current width of the image. + */ + private int width; + + /** + * The current height of the image. + */ + private int height; + + /** + * Our ImageObserver for tracking the loading state. + */ + private ImageObserver observer; + + /** + * The CSS width and height. + * + * Package private to avoid synthetic accessor methods. + */ + Length[] spans; + + /** + * The cached attributes. + */ + private AttributeSet attributes; + + /** * Creates the image view that represents the given element. * * @param element the element, represented by this image view. @@ -52,25 +130,36 @@ public class ImageView extends View public ImageView(Element element) { super(element); + spans = new Length[2]; + observer = new Observer(); + reloadProperties = true; + reloadImage = true; + loadOnDemand = false; } /** * Load or reload the image. This method initiates the image reloading. After * the image is ready, the repaint event will be scheduled. The current image, * if it already exists, will be discarded. - * - * @param itsTime - * also load if the "on demand" property is set */ - void reloadImage(boolean itsTime) + private void reloadImage() { - URL url = getImageURL(); - if (url == null) - imageState = (byte) MediaTracker.ERRORED; - else if (!(loadOnDemand && !itsTime)) - imageIcon = new ImageIcon(url); - else - imageState = (byte) MediaTracker.LOADING; + loading = true; + reloadImage = false; + haveWidth = false; + haveHeight = false; + image = null; + width = 0; + height = 0; + try + { + loadImage(); + updateSize(); + } + finally + { + loading = false; + } } /** @@ -146,12 +235,9 @@ public class ImageView extends View */ public AttributeSet getAttributes() { - StyleSheet styles = getStyleSheet(); - if (styles == null) - return super.getAttributes(); - else - return CombinedAttributes.combine(super.getAttributes(), - styles.getViewAttributes(this)); + if (attributes == null) + attributes = getStyleSheet().getViewAttributes(this); + return attributes; } /** @@ -159,10 +245,8 @@ public class ImageView extends View */ public Image getImage() { - if (imageIcon == null) - return null; - else - return imageIcon.getImage(); + updateState(); + return image; } /** @@ -175,19 +259,22 @@ public class ImageView extends View */ public URL getImageURL() { - Object url = getAttributes().getAttribute(Attribute.SRC); - if (url == null) - return null; - - try + Element el = getElement(); + String src = (String) el.getAttributes().getAttribute(Attribute.SRC); + URL url = null; + if (src != null) { - return new URL(url.toString()); - } - catch (MalformedURLException e) - { - // The URL is malformed - no image. - return null; + URL base = ((HTMLDocument) getDocument()).getBase(); + try + { + url = new URL(base, src); + } + catch (MalformedURLException ex) + { + // Return null. + } } + return url; } /** @@ -242,9 +329,8 @@ public class ImageView extends View if (axis == View.X_AXIS) { - Object w = attrs.getAttribute(Attribute.WIDTH); - if (w != null) - return Integer.parseInt(w.toString()); + if (spans[axis] != null) + return spans[axis].getValue(); else if (image != null) return image.getWidth(getContainer()); else @@ -252,9 +338,8 @@ public class ImageView extends View } else if (axis == View.Y_AXIS) { - Object w = attrs.getAttribute(Attribute.HEIGHT); - if (w != null) - return Integer.parseInt(w.toString()); + if (spans[axis] != null) + return spans[axis].getValue(); else if (image != null) return image.getHeight(getContainer()); else @@ -271,11 +356,8 @@ public class ImageView extends View */ protected StyleSheet getStyleSheet() { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); - else - return null; + HTMLDocument doc = (HTMLDocument) getDocument(); + return doc.getStyleSheet(); } /** @@ -288,7 +370,7 @@ public class ImageView extends View { return getAltText(); } - + /** * Paints the image or one of the two image state icons. The image is resized * to the shape bounds. If there is no image available, the alternative text @@ -302,83 +384,22 @@ public class ImageView extends View */ public void paint(Graphics g, Shape bounds) { - Rectangle r = bounds.getBounds(); - - if (imageIcon == null) - - { - // Loading image on demand, rendering the loading icon so far. - reloadImage(true); - - // The reloadImage sets the imageIcon, unless the URL is broken - // or malformed. - if (imageIcon != null) - { - if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE) - { - // Render "not ready" icon, unless the image is ready - // immediately. - renderIcon(g, r, getLoadingImageIcon()); - // Add the listener to repaint when the icon will be ready. - imageIcon.setImageObserver(getContainer()); - return; - } - } - else - { - renderIcon(g, r, getNoImageIcon()); - return; - } - } - - imageState = (byte) imageIcon.getImageLoadStatus(); - - switch (imageState) - { - case MediaTracker.ABORTED: - case MediaTracker.ERRORED: - renderIcon(g, r, getNoImageIcon()); - break; - case MediaTracker.LOADING: - // If the image is not loaded completely, we still render it, as the - // partial image may be available. - case MediaTracker.COMPLETE: + updateState(); + Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds + : bounds.getBounds(); + Image image = getImage(); + if (image != null) { - // Paint the scaled image. - Image scaled = imageIcon.getImage().getScaledInstance( - r.width, - r.height, - Image.SCALE_DEFAULT); - ImageIcon painter = new ImageIcon(scaled); - painter.paintIcon(getContainer(), g, r.x, r.y); - } - break; + g.drawImage(image, r.x, r.y, r.width, r.height, observer); } - } - - /** - * Render "no image" icon and the alternative "no image" text. The text is - * rendered right from the icon and is aligned to the icon bottom. - */ - private void renderIcon(Graphics g, Rectangle bounds, Icon icon) - { - Shape current = g.getClip(); - try + else { - g.setClip(bounds); + Icon icon = getNoImageIcon(); if (icon != null) - { - icon.paintIcon(getContainer(), g, bounds.x, bounds.y); - g.drawString(getAltText(), bounds.x + icon.getIconWidth(), - bounds.y + icon.getIconHeight()); - } - } - finally - { - g.setClip(current); + icon.paintIcon(getContainer(), g, r.x, r.y); } } - + /** * Set if the image should be loaded only when needed (synchronuosly). By * default, the image loads asynchronuosly. If the image is not yet ready, the @@ -395,9 +416,20 @@ public class ImageView extends View */ protected void setPropertiesFromAttributes() { - // In the current implementation, nothing is cached yet, unless the image - // itself. - imageIcon = null; + AttributeSet atts = getAttributes(); + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + spans[X_AXIS] = (Length) atts.getAttribute(CSS.Attribute.WIDTH); + if (spans[X_AXIS] != null) + { + spans[X_AXIS].setFontBases(emBase, exBase); + } + spans[Y_AXIS] = (Length) atts.getAttribute(CSS.Attribute.HEIGHT); + if (spans[Y_AXIS] != null) + { + spans[Y_AXIS].setFontBases(emBase, exBase); + } } /** @@ -433,9 +465,130 @@ public class ImageView extends View */ public void setSize(float width, float height) { - if (imageIcon == null) - reloadImage(false); + updateState(); + // TODO: Implement this when we have an alt view for the alt=... attribute. } - + /** + * This makes sure that the image and properties have been loaded. + */ + private void updateState() + { + if (reloadImage) + reloadImage(); + if (reloadProperties) + setPropertiesFromAttributes(); + } + + /** + * Actually loads the image. + */ + private void loadImage() + { + URL src = getImageURL(); + Image newImage = null; + if (src != null) + { + // Call getImage(URL) to allow the toolkit caching of that image URL. + Toolkit tk = Toolkit.getDefaultToolkit(); + newImage = tk.getImage(src); + tk.prepareImage(newImage, -1, -1, observer); + if (newImage != null && getLoadsSynchronously()) + { + // Load image synchronously. + MediaTracker tracker = new MediaTracker(getContainer()); + tracker.addImage(newImage, 0); + try + { + tracker.waitForID(0); + } + catch (InterruptedException ex) + { + Thread.interrupted(); + } + + } + } + image = newImage; + } + + /** + * Updates the size parameters of the image. + */ + private void updateSize() + { + int newW = 0; + int newH = 0; + Image newIm = getImage(); + if (newIm != null) + { + AttributeSet atts = getAttributes(); + // Fetch width. + Length l = spans[X_AXIS]; + if (l != null) + { + newW = (int) l.getValue(); + haveWidth = true; + } + else + { + newW = newIm.getWidth(observer); + } + // Fetch height. + l = spans[Y_AXIS]; + if (l != null) + { + newH = (int) l.getValue(); + haveHeight = true; + } + else + { + newW = newIm.getWidth(observer); + } + // Go and trigger loading. + Toolkit tk = Toolkit.getDefaultToolkit(); + if (haveWidth || haveHeight) + tk.prepareImage(newIm, width, height, observer); + else + tk.prepareImage(newIm, -1, -1, observer); + } + } + + /** + * Calls preferenceChanged from the event dispatch thread and within + * a read lock to protect us from threading issues. + * + * @param v the view + * @param width true when the width changed + * @param height true when the height changed + */ + void safePreferenceChanged(final View v, final boolean width, + final boolean height) + { + if (SwingUtilities.isEventDispatchThread()) + { + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try + { + preferenceChanged(v, width, height); + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + } + else + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + safePreferenceChanged(v, width, height); + } + }); + } + } } diff --git a/libjava/classpath/javax/swing/text/html/InlineView.java b/libjava/classpath/javax/swing/text/html/InlineView.java index 77ec86e8263..58edc738526 100644 --- a/libjava/classpath/javax/swing/text/html/InlineView.java +++ b/libjava/classpath/javax/swing/text/html/InlineView.java @@ -38,13 +38,17 @@ exception statement from your version. */ package javax.swing.text.html; +import java.awt.FontMetrics; import java.awt.Shape; +import java.text.BreakIterator; import javax.swing.event.DocumentEvent; import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.LabelView; +import javax.swing.text.Segment; import javax.swing.text.View; import javax.swing.text.ViewFactory; @@ -60,6 +64,23 @@ public class InlineView { /** + * The attributes used by this view. + */ + private AttributeSet attributes; + + /** + * The span of the longest word in this view. + * + * @see #getLongestWord() + */ + private float longestWord; + + /** + * Indicates if we may wrap or not. + */ + private boolean nowrap; + + /** * Creates a new <code>InlineView</code> that renders the specified element. * * @param element the element for this view @@ -115,6 +136,9 @@ public class InlineView public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { super.changedUpdate(e, a, f); + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + preferenceChanged(null, true, true); setPropertiesFromAttributes(); } @@ -126,15 +150,23 @@ public class InlineView */ public AttributeSet getAttributes() { - // FIXME: Implement this. - return super.getAttributes(); + if (attributes == null) + { + StyleSheet ss = getStyleSheet(); + attributes = ss.getViewAttributes(this); + } + return attributes; } public int getBreakWeight(int axis, float pos, float len) { - // FIXME: Implement this. - return super.getBreakWeight(axis, pos, len); + int weight; + if (nowrap) + weight = BadBreakWeight; + else + weight = super.getBreakWeight(axis, pos, len); + return weight; } public View breakView(int axis, int offset, float pos, float len) @@ -143,10 +175,48 @@ public class InlineView return super.breakView(axis, offset, pos, len); } + /** + * Loads the character style properties from the stylesheet. + */ protected void setPropertiesFromAttributes() { - // FIXME: Implement this. super.setPropertiesFromAttributes(); + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.TEXT_DECORATION); + + // Check for underline. + boolean b = false; + if (o != null && o.toString().contains("underline")) + b = true; + setUnderline(b); + + // Check for line-through. + b = false; + if (o != null && o.toString().contains("line-through")) + b = true; + setStrikeThrough(b); + + // Check for vertical alignment (subscript/superscript). + o = atts.getAttribute(CSS.Attribute.VERTICAL_ALIGN); + + // Subscript. + b = false; + if (o != null && o.toString().contains("sub")) + b = true; + setSubscript(b); + + // Superscript. + b = false; + if (o != null && o.toString().contains("sup")) + b = true; + setSuperscript(b); + + // Fetch nowrap setting. + o = atts.getAttribute(CSS.Attribute.WHITE_SPACE); + if (o != null && o.equals("nowrap")) + nowrap = true; + else + nowrap = false; } /** @@ -163,4 +233,75 @@ public class InlineView styleSheet = ((HTMLDocument) doc).getStyleSheet(); return styleSheet; } + + /** + * Returns the minimum span for the specified axis. This returns the + * width of the longest word for the X axis and the super behaviour for + * the Y axis. This is a slight deviation from the reference implementation. + * IMO this should improve rendering behaviour so that an InlineView never + * gets smaller than the longest word in it. + */ + public float getMinimumSpan(int axis) + { + float min = super.getMinimumSpan(axis); + if (axis == X_AXIS) + min = Math.max(getLongestWord(), min); + return min; + } + + /** + * Returns the span of the longest word in this view. + * + * @return the span of the longest word in this view + */ + private float getLongestWord() + { + if (longestWord == -1) + longestWord = calculateLongestWord(); + return longestWord; + } + + /** + * Calculates the span of the longest word in this view. + * + * @return the span of the longest word in this view + */ + private float calculateLongestWord() + { + float span = 0; + try + { + Document doc = getDocument(); + int p0 = getStartOffset(); + int p1 = getEndOffset(); + Segment s = new Segment(); + doc.getText(p0, p1 - p0, s); + BreakIterator iter = BreakIterator.getWordInstance(); + iter.setText(s); + int wordStart = p0; + int wordEnd = p0; + int start = iter.first(); + for (int end = iter.next(); end != BreakIterator.DONE; + start = end, end = iter.next()) + { + if ((end - start) > (wordEnd - wordStart)) + { + wordStart = start; + wordEnd = end; + } + } + if (wordEnd - wordStart > 0) + { + FontMetrics fm = getFontMetrics(); + int offset = s.offset + wordStart - s.getBeginIndex(); + span = fm.charsWidth(s.array, offset, wordEnd - wordStart); + } + } + catch (BadLocationException ex) + { + // Return 0. + } + return span; + } + } diff --git a/libjava/classpath/javax/swing/text/html/ListView.java b/libjava/classpath/javax/swing/text/html/ListView.java index c07d3598c92..3e809bbd209 100644 --- a/libjava/classpath/javax/swing/text/html/ListView.java +++ b/libjava/classpath/javax/swing/text/html/ListView.java @@ -94,9 +94,6 @@ public class ListView public void paint(Graphics g, Shape allocation) { super.paint(g, allocation); - // FIXME: Why is this overridden? I think that painting would be done - // by the superclass and the stylesheet... Maybe find out when this - // stuff is implemented properly. } /** diff --git a/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java new file mode 100644 index 00000000000..0f1145084e1 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java @@ -0,0 +1,213 @@ +/* MultiAttributeSet.java -- Multiplexes between a set of AttributeSets + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +import javax.swing.text.AttributeSet; +import javax.swing.text.SimpleAttributeSet; + +/** + * An AttributeSet impl that multiplexes between a set of other AttributeSets. + * + * @author Roman Kennke (kennke@aicas.com) + */ +class MultiAttributeSet + implements AttributeSet +{ + + /** + * The Enumeration for the multiplexed names. + */ + private class MultiNameEnumeration + implements Enumeration + { + /** + * The index of the current AttributeSet. + */ + private int index; + + /** + * The names Enumeration of the current AttributeSet. + */ + private Enumeration current; + + /** + * Creates a new instance. + */ + MultiNameEnumeration() + { + index = 0; + current = multi[0].getAttributeNames(); + } + + public boolean hasMoreElements() + { + return current.hasMoreElements() || index < multi.length - 1; + } + + public Object nextElement() + { + if (! current.hasMoreElements()) + { + if (index < multi.length - 1) + { + index++; + current = multi[index].getAttributeNames(); + } + else + throw new NoSuchElementException(); + } + return current.nextElement(); + } + + } + + /** + * The AttributeSets to multiplex. + */ + AttributeSet[] multi; + + /** + * Provided for subclasses that need to initialize via {@link #init}. + */ + MultiAttributeSet() + { + // Nothing to do here. + } + + /** + * Creates a new instance. + * + * @param m the AttributeSets to multiplex + */ + MultiAttributeSet(AttributeSet[] m) + { + init(m); + } + + /** + * Provided for subclasses to initialize the attribute set. + * + * @param m the attributes to multiplex + */ + void init(AttributeSet[] m) + { + multi = m; + } + + public boolean containsAttribute(Object name, Object value) + { + boolean ret = false; + for (int i = 0; i < multi.length && ret == false; i++) + { + if (multi[i].containsAttribute(name, value)) + ret = true; + } + return ret; + } + + public boolean containsAttributes(AttributeSet attributes) + { + boolean ret = true; + Enumeration e = attributes.getAttributeNames(); + while (ret && e.hasMoreElements()) + { + Object key = e.nextElement(); + ret = attributes.getAttribute(key).equals(getAttribute(key)); + } + return ret; + } + + public AttributeSet copyAttributes() + { + SimpleAttributeSet copy = new SimpleAttributeSet(); + for (int i = 0; i < multi.length; i++) + { + copy.addAttributes(multi[i]); + } + return copy; + } + + public Object getAttribute(Object key) + { + Object ret = null; + for (int i = 0; i < multi.length && ret == null; i++) + { + ret = multi[i].getAttribute(key); + } + return ret; + } + + public int getAttributeCount() + { + int n = 0; + for (int i = 0; i < multi.length; i++) + { + n += multi[i].getAttributeCount(); + } + return n; + } + + public Enumeration getAttributeNames() + { + return new MultiNameEnumeration(); + } + + public AttributeSet getResolveParent() + { + return null; + } + + public boolean isDefined(Object attrName) + { + boolean ret = false; + for (int i = 0; i < multi.length && ! ret; i++) + ret = multi[i].isDefined(attrName); + return ret; + } + + public boolean isEqual(AttributeSet attr) + { + return getAttributeCount() == attr.getAttributeCount() + && containsAttributes(attr); + } + +} diff --git a/libjava/classpath/javax/swing/text/html/MultiStyle.java b/libjava/classpath/javax/swing/text/html/MultiStyle.java new file mode 100644 index 00000000000..3937bff75a9 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/MultiStyle.java @@ -0,0 +1,136 @@ +/* MultiStyle.java -- Multiplexes between several Styles + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.Enumeration; + +import javax.swing.event.ChangeListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.Style; + +/** + * A Style implementation that is able to multiplex between several other + * Styles. This is used for CSS style resolving. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class MultiStyle + extends MultiAttributeSet + implements Style +{ + + // FIXME: Fix the implementation to also return attributes that + // are added to this style, etc. However, this is not really needed + // now for CSS, but would be nice for correctness. + + /** + * The name of the style. + */ + private String name; + + /** + * The attributes added to this style. + */ + private SimpleAttributeSet attributes; + + /** + * Creates a new instance. + * + * @param n the name + * @param m the styles to multiplex + */ + public MultiStyle(String n, AttributeSet[] m) + { + super(m); + name = n; + attributes = new SimpleAttributeSet(); + } + + /** + * Returns the name of the style. + * + * @return the name of the style + */ + public String getName() + { + return name; + } + + public void addChangeListener(ChangeListener listener) + { + // TODO: Implement. + } + + public void removeChangeListener(ChangeListener listener) + { + // TODO: Implement. + } + + public void addAttribute(Object name, Object value) + { + attributes.addAttribute(name, value); + } + + public void addAttributes(AttributeSet atts) + { + attributes.addAttributes(atts); + } + + public void removeAttribute(Object name) + { + attributes.removeAttribute(name); + } + + public void removeAttributes(Enumeration names) + { + attributes.removeAttribute(names); + } + + public void removeAttributes(AttributeSet atts) + { + attributes.removeAttribute(atts); + } + + public void setResolveParent(AttributeSet parent) + { + // TODO: Implement. + } + +} diff --git a/libjava/classpath/javax/swing/text/html/Option.java b/libjava/classpath/javax/swing/text/html/Option.java index 1def51b2f59..18d5c2bd86f 100644 --- a/libjava/classpath/javax/swing/text/html/Option.java +++ b/libjava/classpath/javax/swing/text/html/Option.java @@ -72,10 +72,10 @@ public class Option */ public Option(AttributeSet attr) { - attributes = attr; + // Protect the attribute set. + attributes = attr.copyAttributes(); label = null; - selected = false; - // FIXME: Probably initialize something using the attributes. + selected = attr.getAttribute(HTML.Attribute.SELECTED) != null; } /** @@ -151,7 +151,9 @@ public class Option */ public String getValue() { - // FIXME: Return some attribute here if specified. - return label; + String value = (String) attributes.getAttribute(HTML.Attribute.VALUE); + if (value == null) + value = label; + return value; } } diff --git a/libjava/classpath/javax/swing/text/html/ParagraphView.java b/libjava/classpath/javax/swing/text/html/ParagraphView.java index 2339f4e661d..d149627ff1c 100644 --- a/libjava/classpath/javax/swing/text/html/ParagraphView.java +++ b/libjava/classpath/javax/swing/text/html/ParagraphView.java @@ -38,13 +38,17 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.css.Length; + import java.awt.Graphics; +import java.awt.Rectangle; import java.awt.Shape; import javax.swing.SizeRequirements; import javax.swing.text.AttributeSet; import javax.swing.text.Document; import javax.swing.text.Element; +import javax.swing.text.StyleConstants; import javax.swing.text.View; /** @@ -55,10 +59,30 @@ import javax.swing.text.View; * @author Roman Kennke (kennke@aicas.com) */ public class ParagraphView - extends javax.swing.text.ParagraphView + extends javax.swing.text.ParagraphView { /** + * The attributes used by this view. + */ + private AttributeSet attributes; + + /** + * The stylesheet's box painter. + */ + private StyleSheet.BoxPainter painter; + + /** + * The width as specified in the stylesheet or null if not specified. + */ + private Length cssWidth; + + /** + * The height as specified in the stylesheet or null if not specified. + */ + private Length cssHeight; + + /** * Creates a new ParagraphView for the specified element. * * @param element the element @@ -88,8 +112,11 @@ public class ParagraphView */ public AttributeSet getAttributes() { - // FIXME: Implement this multiplexing thing. - return super.getAttributes(); + if (attributes == null) + { + attributes = getStyleSheet().getViewAttributes(this); + } + return attributes; } /** @@ -98,7 +125,44 @@ public class ParagraphView */ protected void setPropertiesFromAttributes() { - // FIXME: Implement this. + super.setPropertiesFromAttributes(); + + // Fetch CSS attributes. + attributes = getAttributes(); + if (attributes != null) + { + super.setPropertiesFromAttributes(); + Object o = attributes.getAttribute(CSS.Attribute.TEXT_ALIGN); + if (o != null) + { + String align = o.toString(); + if (align.equals("left")) + setJustification(StyleConstants.ALIGN_LEFT); + else if (align.equals("right")) + setJustification(StyleConstants.ALIGN_RIGHT); + else if (align.equals("center")) + setJustification(StyleConstants.ALIGN_CENTER); + else if (align.equals("justify")) + setJustification(StyleConstants.ALIGN_JUSTIFIED); + } + + // Fetch StyleSheet's box painter. + painter = getStyleSheet().getBoxPainter(attributes); + setInsets((short) painter.getInset(TOP, this), + (short) painter.getInset(LEFT, this), + (short) painter.getInset(BOTTOM, this), + (short) painter.getInset(RIGHT, this)); + + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(attributes); + float exBase = ss.getEXBase(attributes); + cssWidth = (Length) attributes.getAttribute(CSS.Attribute.WIDTH); + if (cssWidth != null) + cssWidth.setFontBases(emBase, exBase); + cssHeight = (Length) attributes.getAttribute(CSS.Attribute.WIDTH); + if (cssHeight != null) + cssHeight.setFontBases(emBase, exBase); + } } /** @@ -129,8 +193,52 @@ public class ParagraphView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { - // FIXME: Implement the above specified behaviour. - return super.calculateMinorAxisRequirements(axis, r); + r = super.calculateMinorAxisRequirements(axis, r); + if (! setCSSSpan(r, axis)) + { + int margin = axis == X_AXIS ? getLeftInset() + getRightInset() + : getTopInset() + getBottomInset(); + r.minimum -= margin; + r.preferred -= margin; + r.maximum -= margin; + } + return r; + } + + /** + * Sets the span on the SizeRequirements object according to the + * according CSS span value, when it is set. + * + * @param r the size requirements + * @param axis the axis + * + * @return <code>true</code> when the CSS span has been set, + * <code>false</code> otherwise + */ + private boolean setCSSSpan(SizeRequirements r, int axis) + { + boolean ret = false; + if (axis == X_AXIS) + { + if (cssWidth != null && ! cssWidth.isPercentage()) + { + r.minimum = (int) cssWidth.getValue(); + r.preferred = (int) cssWidth.getValue(); + r.maximum = (int) cssWidth.getValue(); + ret = true; + } + } + else + { + if (cssHeight != null && ! cssWidth.isPercentage()) + { + r.minimum = (int) cssHeight.getValue(); + r.preferred = (int) cssHeight.getValue(); + r.maximum = (int) cssHeight.getValue(); + ret = true; + } + } + return ret; } /** @@ -147,15 +255,20 @@ public class ParagraphView } /** - * Paints this view. This delegates to the superclass after the coordinates - * have been updated for tab calculations. + * Paints this view. This paints the box using the stylesheet's + * box painter for this view and delegates to the super class paint() + * afterwards. * * @param g the graphics object * @param a the current allocation of this view */ public void paint(Graphics g, Shape a) { - // FIXME: Implement the above specified behaviour. + if (a != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + painter.paint(g, r.x, r.y, r.width, r.height, this); + } super.paint(g, a); } diff --git a/libjava/classpath/javax/swing/text/html/ResetableModel.java b/libjava/classpath/javax/swing/text/html/ResetableModel.java new file mode 100644 index 00000000000..17f65b97d13 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ResetableModel.java @@ -0,0 +1,50 @@ +/* ResetableModel.java -- Form models that can be resetted + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +/** + * Form models that can be resetted implement this. + */ +interface ResetableModel +{ + /** + * Resets the model. + */ + void reset(); +} diff --git a/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java new file mode 100644 index 00000000000..6177f9b86cf --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java @@ -0,0 +1,82 @@ +/* ResetablePlainDocument.java -- A plain document for use in the HTML renderer + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; + +/** + * A PlainDocument that can be resetted. + */ +class ResetablePlainDocument + extends PlainDocument + implements ResetableModel +{ + /** + * The initial text. + */ + private String initial; + + /** + * Stores the initial text. + * + * @param text the initial text + */ + void setInitialText(String text) + { + initial = text; + } + + /** + * Resets the model. + */ + public void reset() + { + try + { + replace(0, getLength(), initial, null); + } + catch (BadLocationException ex) + { + // Shouldn't happen. + assert false; + } + } + +} diff --git a/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java new file mode 100644 index 00000000000..619c24e477c --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java @@ -0,0 +1,71 @@ +/* ResetableToggleButtonModel.java -- A toggle button model with reset support + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.ButtonGroup; +import javax.swing.JToggleButton.ToggleButtonModel; + +class ResetableToggleButtonModel + extends ToggleButtonModel + implements ResetableModel +{ + + /** + * The initial state. + */ + private boolean initial; + + /** + * Sets the initial selection value. + * + * @param state the initial value + */ + public void setInitial(boolean state) + { + initial = state; + } + + /** + * Resets the model. + */ + public void reset() + { + setSelected(initial); + } +} diff --git a/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java new file mode 100644 index 00000000000..999746413c8 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java @@ -0,0 +1,84 @@ +/* SelectComboBoxModel.java -- A special ComboBoxModel for use in HTML renderer + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.DefaultComboBoxModel; + +/** + * A special ComboBoxModel that supports storing the initial value so that + * the combobox can be resetted later. + */ +class SelectComboBoxModel + extends DefaultComboBoxModel + implements ResetableModel +{ + + /** + * The initial selection. + */ + private Option initial; + + /** + * Sets the initial selection. + * + * @param option the initial selection + */ + void setInitialSelection(Option option) + { + initial = option; + } + + /** + * Returns the initial selection. + * + * @return the initial selection + */ + Option getInitialSelection() + { + return initial; + } + + /** + * Resets the model. + */ + public void reset() + { + setSelectedItem(initial); + } +} diff --git a/libjava/classpath/javax/swing/text/html/SelectListModel.java b/libjava/classpath/javax/swing/text/html/SelectListModel.java new file mode 100644 index 00000000000..23bfaa11b86 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/SelectListModel.java @@ -0,0 +1,106 @@ +/* OptionListModel.java -- A special ListModel for use in the HTML renderer + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.BitSet; + +import javax.swing.DefaultListModel; +import javax.swing.DefaultListSelectionModel; +import javax.swing.ListSelectionModel; + +/** + * A special list model that encapsulates its selection model and supports + * storing of the initial value so that it can be resetted. + */ +class SelectListModel + extends DefaultListModel + implements ResetableModel +{ + /** + * The selection model. + */ + private DefaultListSelectionModel selectionModel; + + /** + * The initial selection. + */ + private BitSet initialSelection; + + /** + * Creates a new SelectListModel. + */ + SelectListModel() + { + selectionModel = new DefaultListSelectionModel(); + initialSelection = new BitSet(); + } + + /** + * Sets the initial selection. + * + * @param init the initial selection + */ + void addInitialSelection(int init) + { + initialSelection.set(init); + } + + /** + * Resets the model. + */ + public void reset() + { + selectionModel.clearSelection(); + for (int i = initialSelection.size(); i >= 0; i--) + { + if (initialSelection.get(i)) + selectionModel.addSelectionInterval(i, i); + } + } + + /** + * Returns the associated selection model. + * + * @return the associated selection model + */ + ListSelectionModel getSelectionModel() + { + return selectionModel; + } +} diff --git a/libjava/classpath/javax/swing/text/html/StyleSheet.java b/libjava/classpath/javax/swing/text/html/StyleSheet.java index d92abde7825..01f19fd7bdd 100644 --- a/libjava/classpath/javax/swing/text/html/StyleSheet.java +++ b/libjava/classpath/javax/swing/text/html/StyleSheet.java @@ -38,28 +38,47 @@ exception statement from your version. */ package javax.swing.text.html; -import gnu.javax.swing.text.html.CharacterAttributeTranslator; +import gnu.javax.swing.text.html.css.BorderWidth; +import gnu.javax.swing.text.html.css.CSSColor; +import gnu.javax.swing.text.html.css.CSSParser; +import gnu.javax.swing.text.html.css.CSSParserCallback; +import gnu.javax.swing.text.html.css.FontSize; +import gnu.javax.swing.text.html.css.FontStyle; +import gnu.javax.swing.text.html.css.FontWeight; +import gnu.javax.swing.text.html.css.Length; +import gnu.javax.swing.text.html.css.Selector; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; - +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.geom.Rectangle2D; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; - -import java.net.MalformedURLException; import java.net.URL; - +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; -import java.util.Vector; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.swing.border.Border; +import javax.swing.event.ChangeListener; import javax.swing.text.AttributeSet; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.Style; +import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.View; @@ -79,21 +98,168 @@ import javax.swing.text.View; * * The rules are stored as named styles, and other information is stored to * translate the context of an element to a rule. - * + * * @author Lillian Angel (langel@redhat.com) */ public class StyleSheet extends StyleContext { + /** + * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css. + * + * This is package private to avoid accessor methods. + */ + class CSSStyleSheetParserCallback + implements CSSParserCallback + { + /** + * The current styles. + */ + private CSSStyle[] styles; + + /** + * The precedence of the stylesheet to be parsed. + */ + private int precedence; + + /** + * Creates a new CSS parser. This parser parses a CSS stylesheet with + * the specified precedence. + * + * @param prec the precedence, according to the constants defined in + * CSSStyle + */ + CSSStyleSheetParserCallback(int prec) + { + precedence = prec; + } + + /** + * Called at the beginning of a statement. + * + * @param sel the selector + */ + public void startStatement(Selector[] sel) + { + styles = new CSSStyle[sel.length]; + for (int i = 0; i < sel.length; i++) + styles[i] = new CSSStyle(precedence, sel[i]); + } + + /** + * Called at the end of a statement. + */ + public void endStatement() + { + for (int i = 0; i < styles.length; i++) + css.add(styles[i]); + styles = null; + } + + /** + * Called when a declaration is parsed. + * + * @param property the property + * @param value the value + */ + public void declaration(String property, String value) + { + CSS.Attribute cssAtt = CSS.getAttribute(property); + Object val = CSS.getValue(cssAtt, value); + for (int i = 0; i < styles.length; i++) + { + CSSStyle style = styles[i]; + CSS.addInternal(style, cssAtt, value); + if (cssAtt != null) + style.addAttribute(cssAtt, val); + } + } + + } + + /** + * Represents a style that is defined by a CSS rule. + */ + private class CSSStyle + extends SimpleAttributeSet + implements Style, Comparable + { + + static final int PREC_UA = 0; + static final int PREC_NORM = 100000; + static final int PREC_AUTHOR_NORMAL = 200000; + static final int PREC_AUTHOR_IMPORTANT = 300000; + static final int PREC_USER_IMPORTANT = 400000; + + /** + * The priority of this style when matching CSS selectors. + */ + private int precedence; + + /** + * The selector for this rule. + * + * This is package private to avoid accessor methods. + */ + Selector selector; + + CSSStyle(int prec, Selector sel) + { + precedence = prec; + selector = sel; + } + + public String getName() + { + // TODO: Implement this for correctness. + return null; + } + + public void addChangeListener(ChangeListener listener) + { + // TODO: Implement this for correctness. + } + + public void removeChangeListener(ChangeListener listener) + { + // TODO: Implement this for correctness. + } + + /** + * Sorts the rule according to the style's precedence and the + * selectors specificity. + */ + public int compareTo(Object o) + { + CSSStyle other = (CSSStyle) o; + return other.precedence + other.selector.getSpecificity() + - precedence - selector.getSpecificity(); + } + + } + /** The base URL */ URL base; /** Base font size (int) */ int baseFontSize; - /** The style sheets stored. */ - StyleSheet[] styleSheet; - + /** + * The linked style sheets stored. + */ + private ArrayList linked; + + /** + * Maps element names (selectors) to AttributSet (the corresponding style + * information). + */ + ArrayList css = new ArrayList(); + + /** + * Maps selectors to their resolved styles. + */ + private HashMap resolvedStyles; + /** * Constructs a StyleSheet. */ @@ -101,6 +267,7 @@ public class StyleSheet extends StyleContext { super(); baseFontSize = 4; // Default font size from CSS + resolvedStyles = new HashMap(); } /** @@ -114,10 +281,198 @@ public class StyleSheet extends StyleContext */ public Style getRule(HTML.Tag t, Element e) { - // FIXME: Not implemented. - return null; + // Create list of the element and all of its parents, starting + // with the bottommost element. + ArrayList path = new ArrayList(); + Element el; + AttributeSet atts; + for (el = e; el != null; el = el.getParentElement()) + path.add(el); + + // Create fully qualified selector. + StringBuilder selector = new StringBuilder(); + int count = path.size(); + // We append the actual element after this loop. + for (int i = count - 1; i > 0; i--) + { + el = (Element) path.get(i); + atts = el.getAttributes(); + Object name = atts.getAttribute(StyleConstants.NameAttribute); + selector.append(name.toString()); + if (atts.isDefined(HTML.Attribute.ID)) + { + selector.append('#'); + selector.append(atts.getAttribute(HTML.Attribute.ID)); + } + if (atts.isDefined(HTML.Attribute.CLASS)) + { + selector.append('.'); + selector.append(atts.getAttribute(HTML.Attribute.CLASS)); + } + if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); + } + if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); + } + selector.append(' '); + } + selector.append(t.toString()); + el = (Element) path.get(0); + atts = el.getAttributes(); + // For leaf elements, we have to fetch the tag specific attributes. + if (el.isLeaf()) + { + Object o = atts.getAttribute(t); + if (o instanceof AttributeSet) + atts = (AttributeSet) o; + else + atts = null; + } + if (atts != null) + { + if (atts.isDefined(HTML.Attribute.ID)) + { + selector.append('#'); + selector.append(atts.getAttribute(HTML.Attribute.ID)); + } + if (atts.isDefined(HTML.Attribute.CLASS)) + { + selector.append('.'); + selector.append(atts.getAttribute(HTML.Attribute.CLASS)); + } + if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS)); + } + if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS)) + { + selector.append(':'); + selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS)); + } + } + return getResolvedStyle(selector.toString(), path, t); } - + + /** + * Fetches a resolved style. If there is no resolved style for the + * specified selector, the resolve the style using + * {@link #resolveStyle(String, List, HTML.Tag)}. + * + * @param selector the selector for which to resolve the style + * @param path the Element path, used in the resolving algorithm + * @param tag the tag for which to resolve + * + * @return the resolved style + */ + private Style getResolvedStyle(String selector, List path, HTML.Tag tag) + { + Style style = (Style) resolvedStyles.get(selector); + if (style == null) + style = resolveStyle(selector, path, tag); + return style; + } + + /** + * Resolves a style. This creates arrays that hold the tag names, + * class and id attributes and delegates the work to + * {@link #resolveStyle(String, String[], Map[])}. + * + * @param selector the selector + * @param path the Element path + * @param tag the tag + * + * @return the resolved style + */ + private Style resolveStyle(String selector, List path, HTML.Tag tag) + { + int count = path.size(); + String[] tags = new String[count]; + Map[] attributes = new Map[count]; + for (int i = 0; i < count; i++) + { + Element el = (Element) path.get(i); + AttributeSet atts = el.getAttributes(); + if (i == 0 && el.isLeaf()) + { + Object o = atts.getAttribute(tag); + if (o instanceof AttributeSet) + atts = (AttributeSet) o; + else + atts = null; + } + if (atts != null) + { + HTML.Tag t = + (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute); + if (t != null) + tags[i] = t.toString(); + else + tags[i] = null; + attributes[i] = attributeSetToMap(atts); + } + else + { + tags[i] = null; + attributes[i] = null; + } + } + tags[0] = tag.toString(); + return resolveStyle(selector, tags, attributes); + } + + /** + * Performs style resolving. + * + * @param selector the selector + * @param tags the tags + * @param attributes the attributes of the tags + * + * @return the resolved style + */ + private Style resolveStyle(String selector, String[] tags, Map[] attributes) + { + // FIXME: This style resolver is not correct. But it works good enough for + // the default.css. + int count = tags.length; + ArrayList styles = new ArrayList(); + for (Iterator i = css.iterator(); i.hasNext();) + { + CSSStyle style = (CSSStyle) i.next(); + if (style.selector.matches(tags, attributes)) + styles.add(style); + } + + // Add styles from linked stylesheets. + if (linked != null) + { + for (int i = linked.size() - 1; i >= 0; i--) + { + StyleSheet ss = (StyleSheet) linked.get(i); + for (int j = ss.css.size() - 1; j >= 0; j--) + { + CSSStyle style = (CSSStyle) ss.css.get(j); + if (style.selector.matches(tags, attributes)) + styles.add(style); + } + } + } + + // Sort selectors. + Collections.sort(styles); + Style[] styleArray = new Style[styles.size()]; + styleArray = (Style[]) styles.toArray(styleArray); + Style resolved = new MultiStyle(selector, + (Style[]) styles.toArray(styleArray)); + resolvedStyles.put(selector, resolved); + return resolved; + } + /** * Gets the rule that best matches the selector. selector is a space * separated String of element names. The attributes of the returned @@ -128,27 +483,40 @@ public class StyleSheet extends StyleContext */ public Style getRule(String selector) { - // FIXME: Not implemented. - return null; + CSSStyle best = null; + for (Iterator i = css.iterator(); i.hasNext();) + { + CSSStyle style = (CSSStyle) i.next(); + if (style.compareTo(best) < 0) + best = style; + } + return best; } /** - * Adds a set if rules to the sheet. The rules are expected to be in valid + * Adds a set of rules to the sheet. The rules are expected to be in valid * CSS format. This is called as a result of parsing a <style> tag * * @param rule - the rule to add to the sheet */ public void addRule(String rule) { - CssParser cp = new CssParser(); + CSSStyleSheetParserCallback cb = + new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); + // FIXME: Handle ref. + StringReader in = new StringReader(rule); + CSSParser parser = new CSSParser(in, cb); try - { - cp.parse(base, new StringReader(rule), false, false); - } - catch (IOException io) - { - // Do nothing here. - } + { + parser.parse(); + } + catch (IOException ex) + { + // Shouldn't happen. And if, then don't let it bork the outside code. + } + // Clean up resolved styles cache so that the new styles are recognized + // on next stylesheet request. + resolvedStyles.clear(); } /** @@ -176,10 +544,14 @@ public class StyleSheet extends StyleContext * parameter. * @throws IOException - For any IO error while reading */ - public void loadRules(Reader in, URL ref) throws IOException + public void loadRules(Reader in, URL ref) + throws IOException { - CssParser cp = new CssParser(); - cp.parse(ref, in, false, false); + CSSStyleSheetParserCallback cb = + new CSSStyleSheetParserCallback(CSSStyle.PREC_UA); + // FIXME: Handle ref. + CSSParser parser = new CSSParser(in, cb); + parser.parse(); } /** @@ -191,8 +563,7 @@ public class StyleSheet extends StyleContext */ public AttributeSet getViewAttributes(View v) { - // FIXME: Not implemented. - return null; + return new ViewAttributeSet(v, this); } /** @@ -215,11 +586,9 @@ public class StyleSheet extends StyleContext */ public void addStyleSheet(StyleSheet ss) { - if (styleSheet == null) - styleSheet = new StyleSheet[] {ss}; - else - System.arraycopy(new StyleSheet[] {ss}, 0, styleSheet, - styleSheet.length, 1); + if (linked == null) + linked = new ArrayList(); + linked.add(ss); } /** @@ -229,31 +598,9 @@ public class StyleSheet extends StyleContext */ public void removeStyleSheet(StyleSheet ss) { - if (styleSheet.length == 1 && styleSheet[0].equals(ss)) - styleSheet = null; - else + if (linked != null) { - for (int i = 0; i < styleSheet.length; i++) - { - StyleSheet curr = styleSheet[i]; - if (curr.equals(ss)) - { - StyleSheet[] tmp = new StyleSheet[styleSheet.length - 1]; - if (i != 0 && i != (styleSheet.length - 1)) - { - System.arraycopy(styleSheet, 0, tmp, 0, i); - System.arraycopy(styleSheet, i + 1, tmp, i, - styleSheet.length - i - 1); - } - else if (i == 0) - System.arraycopy(styleSheet, 1, tmp, 0, styleSheet.length - 1); - else - System.arraycopy(styleSheet, 0, tmp, 0, styleSheet.length - 1); - - styleSheet = tmp; - break; - } - } + linked.remove(ss); } } @@ -264,18 +611,41 @@ public class StyleSheet extends StyleContext */ public StyleSheet[] getStyleSheets() { - return styleSheet; + StyleSheet[] linkedSS; + if (linked != null) + { + linkedSS = new StyleSheet[linked.size()]; + linkedSS = (StyleSheet[]) linked.toArray(linkedSS); + } + else + { + linkedSS = null; + } + return linkedSS; } /** * Imports a style sheet from the url. The rules are directly added to the - * receiver. + * receiver. This is usually called when a <link> tag is resolved in an + * HTML document. * - * @param url - the URL to import the StyleSheet from. + * @param url the URL to import the StyleSheet from */ public void importStyleSheet(URL url) { - // FIXME: Not implemented + try + { + InputStream in = url.openStream(); + Reader r = new BufferedReader(new InputStreamReader(in)); + CSSStyleSheetParserCallback cb = + new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL); + CSSParser parser = new CSSParser(r, cb); + parser.parse(); + } + catch (IOException ex) + { + // We can't do anything about it I guess. + } } /** @@ -310,7 +680,9 @@ public class StyleSheet extends StyleContext public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key, String value) { - attr.addAttribute(key, value); + Object val = CSS.getValue(key, value); + CSS.addInternal(attr, key, value); + attr.addAttribute(key, val); } /** @@ -340,8 +712,90 @@ public class StyleSheet extends StyleContext */ public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) { - // FIXME: Not implemented. - return null; + AttributeSet cssAttr = htmlAttrSet.copyAttributes(); + + // The HTML align attribute maps directly to the CSS text-align attribute. + Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o); + + // The HTML width attribute maps directly to CSS width. + o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH, + new Length(o.toString())); + + // The HTML height attribute maps directly to CSS height. + o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT, + new Length(o.toString())); + + o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap"); + + // Map cellspacing attr of tables to CSS border-spacing. + o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING, + new Length(o.toString())); + + // For table cells and headers, fetch the cellpadding value from the + // parent table and set it as CSS padding attribute. + HTML.Tag tag = (HTML.Tag) + htmlAttrSet.getAttribute(StyleConstants.NameAttribute); + if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH) + && htmlAttrSet instanceof Element) + { + Element el = (Element) htmlAttrSet; + AttributeSet tableAttrs = el.getParentElement().getParentElement() + .getAttributes(); + o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING); + if (o != null) + { + Length l = new Length(o.toString()); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l); + } + o = tableAttrs.getAttribute(HTML.Attribute.BORDER); + cssAttr = translateBorder(cssAttr, o); + } + + // Translate border attribute. + o = cssAttr.getAttribute(HTML.Attribute.BORDER); + cssAttr = translateBorder(cssAttr, o); + + // TODO: Add more mappings. + return cssAttr; + } + + /** + * Translates a HTML border attribute to a corresponding set of CSS + * attributes. + * + * @param cssAttr the original set of CSS attributes to add to + * @param o the value of the border attribute + * + * @return the new set of CSS attributes + */ + private AttributeSet translateBorder(AttributeSet cssAttr, Object o) + { + if (o != null) + { + BorderWidth l = new BorderWidth(o.toString()); + if (l.getValue() > 0) + { + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l); + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE, + "solid"); + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR, + new CSSColor("black")); + } + } + return cssAttr; } /** @@ -416,10 +870,10 @@ public class StyleSheet extends StyleContext * @param names - the attribute names * @return the update attribute set */ - public AttributeSet removeAttributes(AttributeSet old, Enumeration names) + public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) { // FIXME: Not implemented. - return super.removeAttributes(old, names); + return super.removeAttributes(old, names); } /** @@ -455,9 +909,95 @@ public class StyleSheet extends StyleContext */ public Font getFont(AttributeSet a) { - return super.getFont(a); + int realSize = getFontSize(a); + + // Decrement size for subscript and superscript. + Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); + if (valign != null) + { + String v = valign.toString(); + if (v.contains("sup") || v.contains("sub")) + realSize -= 2; + } + + // TODO: Convert font family. + String family = "SansSerif"; + + int style = Font.PLAIN; + FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT); + if (weight != null) + style |= weight.getValue(); + FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE); + if (fStyle != null) + style |= fStyle.getValue(); + return new Font(family, style, realSize); } - + + /** + * Determines the EM base value based on the specified attributes. + * + * @param atts the attibutes + * + * @return the EM base value + */ + float getEMBase(AttributeSet atts) + { + Font font = getFont(atts); + FontRenderContext ctx = new FontRenderContext(null, false, false); + Rectangle2D bounds = font.getStringBounds("M", ctx); + return (float) bounds.getWidth(); + } + + /** + * Determines the EX base value based on the specified attributes. + * + * @param atts the attibutes + * + * @return the EX base value + */ + float getEXBase(AttributeSet atts) + { + Font font = getFont(atts); + FontRenderContext ctx = new FontRenderContext(null, false, false); + Rectangle2D bounds = font.getStringBounds("x", ctx); + return (float) bounds.getHeight(); + } + + /** + * Resolves the fontsize for a given set of attributes. + * + * @param atts the attributes + * + * @return the resolved font size + */ + private int getFontSize(AttributeSet atts) + { + int size = 12; + if (atts.isDefined(CSS.Attribute.FONT_SIZE)) + { + FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE); + if (fs.isRelative()) + { + int parSize = 12; + AttributeSet resolver = atts.getResolveParent(); + if (resolver != null) + parSize = getFontSize(resolver); + size = fs.getValue(parSize); + } + else + { + size = fs.getValue(); + } + } + else + { + AttributeSet resolver = atts.getResolveParent(); + if (resolver != null) + size = getFontSize(resolver); + } + return size; + } + /** * Takes a set of attributes and turns it into a foreground * color specification. This is used to specify things like, brigher, more hue @@ -468,7 +1008,11 @@ public class StyleSheet extends StyleContext */ public Color getForeground(AttributeSet a) { - return super.getForeground(a); + CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR); + Color color = null; + if (c != null) + color = c.getValue(); + return color; } /** @@ -481,7 +1025,11 @@ public class StyleSheet extends StyleContext */ public Color getBackground(AttributeSet a) { - return super.getBackground(a); + CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR); + Color color = null; + if (c != null) + color = c.getValue(); + return color; } /** @@ -492,7 +1040,7 @@ public class StyleSheet extends StyleContext */ public BoxPainter getBoxPainter(AttributeSet a) { - return new BoxPainter(a); + return new BoxPainter(a, this); } /** @@ -503,7 +1051,7 @@ public class StyleSheet extends StyleContext */ public ListPainter getListPainter(AttributeSet a) { - return new ListPainter(a); + return new ListPainter(a, this); } /** @@ -595,7 +1143,7 @@ public class StyleSheet extends StyleContext */ public Color stringToColor(String colorName) { - return CharacterAttributeTranslator.getColor(colorName); + return CSSColor.convertValue(colorName); } /** @@ -609,22 +1157,112 @@ public class StyleSheet extends StyleContext */ public static class BoxPainter extends Object implements Serializable { - + /** - * Attribute set for painter + * The left inset. */ - AttributeSet as; - + private float leftInset; + + /** + * The right inset. + */ + private float rightInset; + + /** + * The top inset. + */ + private float topInset; + + /** + * The bottom inset. + */ + private float bottomInset; + + /** + * The border of the box. + */ + private Border border; + + private float leftPadding; + private float rightPadding; + private float topPadding; + private float bottomPadding; + + /** + * The background color. + */ + private Color background; + /** * Package-private constructor. * * @param as - AttributeSet for painter */ - BoxPainter(AttributeSet as) + BoxPainter(AttributeSet as, StyleSheet ss) { - this.as = as; + float emBase = ss.getEMBase(as); + float exBase = ss.getEXBase(as); + // Fetch margins. + Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); + if (l != null) + { + l.setFontBases(emBase, exBase); + leftInset = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT); + if (l != null) + { + l.setFontBases(emBase, exBase); + rightInset = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP); + if (l != null) + { + l.setFontBases(emBase, exBase); + topInset = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM); + if (l != null) + { + l.setFontBases(emBase, exBase); + bottomInset = l.getValue(); + } + + // Fetch padding. + l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT); + if (l != null) + { + l.setFontBases(emBase, exBase); + leftPadding = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT); + if (l != null) + { + l.setFontBases(emBase, exBase); + rightPadding = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP); + if (l != null) + { + l.setFontBases(emBase, exBase); + topPadding = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM); + if (l != null) + { + l.setFontBases(emBase, exBase); + bottomPadding = l.getValue(); + } + + // Determine border. + border = new CSSBorder(as, ss); + + // Determine background. + background = ss.getBackground(as); + } + /** * Gets the inset needed on a given side to account for the margin, border * and padding. @@ -638,8 +1276,37 @@ public class StyleSheet extends StyleContext */ public float getInset(int size, View v) { - // FIXME: Not implemented. - return 0; + float inset; + switch (size) + { + case View.TOP: + inset = topInset; + if (border != null) + inset += border.getBorderInsets(null).top; + inset += topPadding; + break; + case View.BOTTOM: + inset = bottomInset; + if (border != null) + inset += border.getBorderInsets(null).bottom; + inset += bottomPadding; + break; + case View.LEFT: + inset = leftInset; + if (border != null) + inset += border.getBorderInsets(null).left; + inset += leftPadding; + break; + case View.RIGHT: + inset = rightInset; + if (border != null) + inset += border.getBorderInsets(null).right; + inset += rightPadding; + break; + default: + inset = 0.0F; + } + return inset; } /** @@ -655,7 +1322,19 @@ public class StyleSheet extends StyleContext */ public void paint(Graphics g, float x, float y, float w, float h, View v) { - // FIXME: Not implemented. + int inX = (int) (x + leftInset); + int inY = (int) (y + topInset); + int inW = (int) (w - leftInset - rightInset); + int inH = (int) (h - topInset - bottomInset); + if (background != null) + { + g.setColor(background); + g.fillRect(inX, inY, inW, inH); + } + if (border != null) + { + border.paintBorder(null, g, inX, inY, inW, inH); + } } } @@ -666,24 +1345,41 @@ public class StyleSheet extends StyleContext * * @author Lillian Angel (langel@redhat.com) */ - public static class ListPainter extends Object implements Serializable + public static class ListPainter implements Serializable { - + /** * Attribute set for painter */ - AttributeSet as; - + private AttributeSet attributes; + + /** + * The associated style sheet. + */ + private StyleSheet styleSheet; + + /** + * The bullet type. + */ + private String type; + /** * Package-private constructor. * * @param as - AttributeSet for painter */ - ListPainter(AttributeSet as) + ListPainter(AttributeSet as, StyleSheet ss) { - this.as = as; + attributes = as; + styleSheet = ss; + type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE); } - + + /** + * Cached rectangle re-used in the paint method below. + */ + private final Rectangle tmpRect = new Rectangle(); + /** * Paints the CSS list decoration according to the attributes given. * @@ -698,210 +1394,66 @@ public class StyleSheet extends StyleContext public void paint(Graphics g, float x, float y, float w, float h, View v, int item) { - // FIXME: Not implemented. - } - } - - /** - * The parser callback for the CSSParser. - */ - class CssParser implements CSSParser.CSSParserCallback - { - /** - * A vector of all the selectors. - * Each element is an array of all the selector tokens - * in a single rule. - */ - Vector selectors; - - /** A vector of all the selector tokens in a rule. */ - Vector selectorTokens; - - /** Name of the current property. */ - String propertyName; - - /** The set of CSS declarations */ - MutableAttributeSet declaration; - - /** - * True if parsing a declaration, that is the Reader will not - * contain a selector. - */ - boolean parsingDeclaration; - - /** True if the attributes are coming from a linked/imported style. */ - boolean isLink; - - /** The base URL */ - URL base; - - /** The parser */ - CSSParser parser; - - /** - * Constructor - */ - CssParser() - { - selectors = new Vector(); - selectorTokens = new Vector(); - parser = new CSSParser(); - base = StyleSheet.this.base; - declaration = new SimpleAttributeSet(); - } - - /** - * Parses the passed in CSS declaration into an AttributeSet. - * - * @param s - the declaration - * @return the set of attributes containing the property and value. - */ - public AttributeSet parseDeclaration(String s) - { - try - { - return parseDeclaration(new StringReader(s)); - } - catch (IOException e) - { - // Do nothing here. - } - return null; - } - - /** - * Parses the passed in CSS declaration into an AttributeSet. - * - * @param r - the reader - * @return the attribute set - * @throws IOException from the reader - */ - public AttributeSet parseDeclaration(Reader r) throws IOException - { - parse(base, r, true, false); - return declaration; - } - - /** - * Parse the given CSS stream - * - * @param base - the url - * @param r - the reader - * @param parseDec - True if parsing a declaration - * @param isLink - True if parsing a link - */ - public void parse(URL base, Reader r, boolean parseDec, boolean isLink) throws IOException - { - parsingDeclaration = parseDec; - this.isLink = isLink; - this.base = base; - - // flush out all storage - propertyName = null; - selectors.clear(); - selectorTokens.clear(); - declaration.removeAttributes(declaration); - - parser.parse(r, this, parseDec); - } - - /** - * Invoked when a valid @import is encountered, - * will call importStyleSheet if a MalformedURLException - * is not thrown in creating the URL. - * - * @param s - the string after @import - */ - public void handleImport(String s) - { - if (s != null) + // FIXME: This is a very simplistic list rendering. We still need + // to implement different bullet types (see type field) and custom + // bullets via images. + View itemView = v.getView(item); + AttributeSet viewAtts = itemView.getAttributes(); + Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute); + // Only paint something here when the child view is an LI tag + // and the calling view is some of the list tags then). + if (tag != null && tag == HTML.Tag.LI) { - try + g.setColor(Color.BLACK); + int centerX = (int) (x - 12); + int centerY = -1; + // For paragraphs (almost all cases) center bullet vertically + // in the middle of the first line. + tmpRect.setBounds((int) x, (int) y, (int) w, (int) h); + if (itemView.getViewCount() > 0) { - if (s.startsWith("url(") && s.endsWith(")")) - s = s.substring(4, s.length() - 1); - if (s.indexOf("\"") >= 0) - s = s.replaceAll("\"",""); - - URL url = new URL(s); - if (url == null && base != null) - url = new URL(base, s); - - importStyleSheet(url); + View v1 = itemView.getView(0); + if (v1 instanceof ParagraphView && v1.getViewCount() > 0) + { + Shape a1 = itemView.getChildAllocation(0, tmpRect); + Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1 + : a1.getBounds(); + ParagraphView par = (ParagraphView) v1; + Shape a = par.getChildAllocation(0, r1); + if (a != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + centerY = (int) (r.height / 2 + r.y); + } + } } - catch (MalformedURLException e) + if (centerY == -1) { - // Do nothing here. + centerY =(int) (h / 2 + y); } + g.fillOval(centerX - 3, centerY - 3, 6, 6); } } + } - /** - * A selector has been encountered. - * - * @param s - a selector (e.g. P or UL or even P,) - */ - public void handleSelector(String s) - { - if (s.endsWith(",")) - s = s.substring(0, s.length() - 1); - - selectorTokens.addElement(s); - addSelector(); - } - - /** - * Invoked when the start of a rule is encountered. - */ - public void startRule() - { - addSelector(); - } - - /** - * Invoked when a property name is encountered. - * - * @param s - the property - */ - public void handleProperty(String s) - { - propertyName = s; - } - - /** - * Invoked when a property value is encountered. + /** + * Converts an AttributeSet to a Map. This is used for CSS resolving. * - * @param s - the value - */ - public void handleValue(String s) - { - // call addCSSAttribute - // FIXME: Not implemented - } - - /** - * Invoked when the end of a rule is encountered. - */ - public void endRule() - { - // FIXME: Not implemented - // add rules - propertyName = null; - } - - /** - * Adds the selector to the vector. - */ - private void addSelector() - { - int length = selectorTokens.size(); - if (length > 0) - { - Object[] sel = new Object[length]; - System.arraycopy(selectorTokens.toArray(), 0, sel, 0, length); - selectors.add(sel); - selectorTokens.clear(); - } - } + * @param atts the attributes to convert + * + * @return the converted map + */ + private Map attributeSetToMap(AttributeSet atts) + { + HashMap map = new HashMap(); + Enumeration keys = atts.getAttributeNames(); + while (keys.hasMoreElements()) + { + Object key = keys.nextElement(); + Object value = atts.getAttribute(key); + map.put(key.toString(), value.toString()); + } + return map; } } diff --git a/libjava/classpath/javax/swing/text/html/TableView.java b/libjava/classpath/javax/swing/text/html/TableView.java index c2edc8cdd64..f87d7b35fc5 100644 --- a/libjava/classpath/javax/swing/text/html/TableView.java +++ b/libjava/classpath/javax/swing/text/html/TableView.java @@ -38,49 +38,318 @@ exception statement from your version. */ package javax.swing.text.html; -import javax.swing.text.Document; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; + +import gnu.javax.swing.text.html.css.Length; + +import javax.swing.SizeRequirements; +import javax.swing.event.DocumentEvent; +import javax.swing.text.AttributeSet; import javax.swing.text.Element; +import javax.swing.text.StyleConstants; import javax.swing.text.View; import javax.swing.text.ViewFactory; /** - * A conrete implementation of TableView that renders HTML tables. - * - * @author Roman Kennke (kennke@aicas.com) + * A view implementation that renders HTML tables. + * + * This is basically a vertical BoxView that contains the rows of the table + * and the rows are horizontal BoxViews that contain the actual columns. */ class TableView - extends javax.swing.text.TableView + extends BlockView + implements ViewFactory { + /** * Represents a single table row. */ - public class RowView extends TableRow + class RowView + extends BlockView { /** - * Creates a new instance of the <code>RowView</code>. + * Has true at column positions where an above row's cell overlaps into + * this row. + */ + boolean[] overlap; + + /** + * Stores the row index of this row. + */ + int rowIndex; + + /** + * Creates a new RowView. * - * @param el the element for which to create a row view + * @param el the element for the row view + */ + RowView(Element el) + { + super(el, X_AXIS); + } + + public void replace(int offset, int len, View[] views) + { + gridValid = false; + super.replace(offset, len, views); + } + + /** + * Overridden to make rows not resizable along the Y axis. + */ + public float getMaximumSpan(int axis) + { + float span; + if (axis == Y_AXIS) + span = super.getPreferredSpan(axis); + else + span = Integer.MAX_VALUE; + return span; + } + + public float getMinimumSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = totalColumnRequirements.minimum; + else + span = super.getMinimumSpan(axis); + return span; + } + + public float getPreferredSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = totalColumnRequirements.preferred; + else + span = super.getPreferredSpan(axis); + return span; + } + + /** + * Calculates the overall size requirements for the row along the + * major axis. This will be the sum of the column requirements. + */ + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + if (r == null) + r = new SizeRequirements(); + int adjust = (columnRequirements.length + 1) * cellSpacing; + r.minimum = totalColumnRequirements.minimum + adjust; + r.preferred = totalColumnRequirements.preferred + adjust; + r.maximum = totalColumnRequirements.maximum + adjust; + r.alignment = 0.0F; + return r; + } + + /** + * Lays out the columns in this row. */ - public RowView(Element el) + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) { - super(el); + super.layoutMinorAxis(targetSpan, axis, offsets, spans); + + // Adjust columns that have rowSpan > 1. + int numCols = getViewCount(); + for (int i = 0; i < numCols; i++) + { + View v = getView(i); + if (v instanceof CellView) + { + CellView cell = (CellView) v; + if (cell.rowSpan > 1) + { + for (int r = 1; r < cell.rowSpan; r++) + { + spans[i] += TableView.this.getSpan(axis, rowIndex + r); + spans[i] += cellSpacing; + } + } + } + } } - + + /** + * Lays out the columns in this row. + */ + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) + { + updateGrid(); + int numCols = offsets.length; + int realColumn = 0; + int colCount = getViewCount(); + for (int i = 0; i < numColumns;) + { + if (! overlap[i] && realColumn < colCount) + { + View v = getView(realColumn); + if (v instanceof CellView) + { + CellView cv = (CellView) v; + offsets[realColumn] = columnOffsets[i]; + spans[realColumn] = 0; + for (int j = 0; j < cv.colSpan; j++, i++) + { + spans[realColumn] += columnSpans[i]; + if (j < cv.colSpan - 1) + spans[realColumn] += cellSpacing; + } + } + realColumn++; + } + else + { + i++; + } + } + } + } + /** - * Get the associated style sheet from the document. - * - * @return the associated style sheet. + * A view that renders HTML table cells (TD and TH tags). */ - protected StyleSheet getStyleSheet() + class CellView + extends BlockView + { + + /** + * The number of columns that this view spans. + */ + int colSpan; + + /** + * The number of rows that this cell spans. + */ + int rowSpan; + + /** + * Creates a new CellView for the specified element. + * + * @param el the element for which to create the colspan + */ + CellView(Element el) { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); - else - return null; - } + super(el, Y_AXIS); + } + + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + r = super.calculateMajorAxisRequirements(axis, r); + r.maximum = Integer.MAX_VALUE; + return r; + } + + /** + * Overridden to fetch the columnSpan attibute. + */ + protected void setPropertiesFromAttributes() + { + super.setPropertiesFromAttributes(); + colSpan = 1; + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(HTML.Attribute.COLSPAN); + if (o != null) + { + try + { + colSpan = Integer.parseInt(o.toString()); + } + catch (NumberFormatException ex) + { + // Couldn't parse the colspan, assume 1. + colSpan = 1; + } + } + rowSpan = 1; + o = atts.getAttribute(HTML.Attribute.ROWSPAN); + if (o != null) + { + try + { + rowSpan = Integer.parseInt(o.toString()); + } + catch (NumberFormatException ex) + { + // Couldn't parse the colspan, assume 1. + rowSpan = 1; + } + } + } } + + /** + * The attributes of this view. + */ + private AttributeSet attributes; + + /** + * The column requirements. + * + * Package private to avoid accessor methods. + */ + SizeRequirements[] columnRequirements; + + /** + * The overall requirements across all columns. + * + * Package private to avoid accessor methods. + */ + SizeRequirements totalColumnRequirements; + + /** + * The column layout, offsets. + * + * Package private to avoid accessor methods. + */ + int[] columnOffsets; + + /** + * The column layout, spans. + * + * Package private to avoid accessor methods. + */ + int[] columnSpans; + + /** + * The widths of the columns that have been explicitly specified. + */ + Length[] columnWidths; + + /** + * The total number of columns. + */ + int numColumns; + + /** + * The table width. + */ + private Length width; + + /** + * Indicates if the grid setup is ok. + */ + boolean gridValid = false; + + /** + * Additional space that is added _between_ table cells. + * + * This is package private to avoid accessor methods. + */ + int cellSpacing; + + /** + * A cached Rectangle object for reuse in paint(). + */ + private Rectangle tmpRect; + /** * Creates a new HTML table view for the specified element. * @@ -88,50 +357,619 @@ class TableView */ public TableView(Element el) { - super(el); + super(el, Y_AXIS); + totalColumnRequirements = new SizeRequirements(); + tmpRect = new Rectangle(); } - + /** - * Get the associated style sheet from the document. - * - * @return the associated style sheet. + * Implementation of the ViewFactory interface for creating the + * child views correctly. */ - protected StyleSheet getStyleSheet() + public View create(Element elem) { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); + View view = null; + AttributeSet atts = elem.getAttributes(); + Object name = atts.getAttribute(StyleConstants.NameAttribute); + AttributeSet pAtts = elem.getParentElement().getAttributes(); + Object pName = pAtts.getAttribute(StyleConstants.NameAttribute); + + if (name == HTML.Tag.TR && pName == HTML.Tag.TABLE) + view = new RowView(elem); + else if ((name == HTML.Tag.TD || name == HTML.Tag.TH) + && pName == HTML.Tag.TR) + view = new CellView(elem); + else if (name == HTML.Tag.CAPTION) + view = new ParagraphView(elem); else - return null; - } - + { + // If we haven't mapped the element, then fall back to the standard + // view factory. + View parent = getParent(); + if (parent != null) + { + ViewFactory vf = parent.getViewFactory(); + if (vf != null) + view = vf.create(elem); + } + } + return view; + } + + /** + * Returns this object as view factory so that we get our TR, TD, TH + * and CAPTION subelements created correctly. + */ + public ViewFactory getViewFactory() + { + return this; + } + + /** + * Returns the attributes of this view. This is overridden to provide + * the attributes merged with the CSS stuff. + */ + public AttributeSet getAttributes() + { + if (attributes == null) + attributes = getStyleSheet().getViewAttributes(this); + return attributes; + } + + /** + * Returns the stylesheet associated with this view. + * + * @return the stylesheet associated with this view + */ + protected StyleSheet getStyleSheet() + { + HTMLDocument doc = (HTMLDocument) getDocument(); + return doc.getStyleSheet(); + } + + /** + * Overridden to calculate the size requirements according to the + * columns distribution. + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements r) + { + updateGrid(); + calculateColumnRequirements(); + + // Calculate the horizontal requirements according to the superclass. + // This will return the maximum of the row's widths. + r = super.calculateMinorAxisRequirements(axis, r); + + // Try to set the CSS width if it fits. + if (width != null) + { + int w = (int) width.getValue(); + if (r.minimum < w) + r.minimum = w; + } + + // Adjust requirements when we have cell spacing. + int adjust = (columnRequirements.length + 1) * cellSpacing; + r.minimum += adjust; + r.preferred += adjust; + + // Apply the alignment. + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN); + r.alignment = 0.0F; + if (o != null) + { + String al = o.toString(); + if (al.equals("left")) + r.alignment = 0.0F; + else if (al.equals("center")) + r.alignment = 0.5F; + else if (al.equals("right")) + r.alignment = 1.0F; + } + + // Make it not resize in the horizontal direction. + r.maximum = r.preferred; + return r; + } + + /** + * Overridden to perform the table layout before calling the super + * implementation. + */ + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + updateGrid(); + + // Mark all rows as invalid along their minor axis to force correct + // layout of multi-row cells. + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View row = getView(i); + if (row instanceof RowView) + ((RowView) row).layoutChanged(axis); + } + + layoutColumns(targetSpan); + super.layoutMinorAxis(targetSpan, axis, offsets, spans); + } + + /** + * Calculates the size requirements for the columns. + */ + private void calculateColumnRequirements() + { + int numRows = getViewCount(); + totalColumnRequirements.minimum = 0; + totalColumnRequirements.preferred = 0; + totalColumnRequirements.maximum = 0; + + // In this first pass we find out a suitable total width to fit in + // all columns of all rows. + for (int r = 0; r < numRows; r++) + { + View rowView = getView(r); + int numCols; + if (rowView instanceof RowView) + numCols = ((RowView) rowView).getViewCount(); + else + numCols = 0; + + // We collect the normal (non-relative) column requirements in the + // total variable and the relative requirements in the relTotal + // variable. In the end we create the maximum of both to get the + // real requirements. + SizeRequirements total = new SizeRequirements(); + SizeRequirements relTotal = new SizeRequirements(); + float totalPercent = 0.F; + int realCol = 0; + for (int c = 0; c < numCols; c++) + { + View v = rowView.getView(c); + if (v instanceof CellView) + { + CellView cellView = (CellView) v; + int colSpan = cellView.colSpan; + if (colSpan > 1) + { + int cellMin = (int) cellView.getMinimumSpan(X_AXIS); + int cellPref = (int) cellView.getPreferredSpan(X_AXIS); + int cellMax = (int) cellView.getMaximumSpan(X_AXIS); + int currentMin = 0; + int currentPref = 0; + long currentMax = 0; + for (int i = 0; i < colSpan; i++) + { + SizeRequirements req = columnRequirements[realCol]; + currentMin += req.minimum; + currentPref += req.preferred; + currentMax += req.maximum; + } + int deltaMin = cellMin - currentMin; + int deltaPref = cellPref - currentPref; + int deltaMax = (int) (cellMax - currentMax); + // Distribute delta. + for (int i = 0; i < colSpan; i++) + { + SizeRequirements req = columnRequirements[realCol]; + if (deltaMin > 0) + req.minimum += deltaMin / colSpan; + if (deltaPref > 0) + req.preferred += deltaPref / colSpan; + if (deltaMax > 0) + req.maximum += deltaMax / colSpan; + if (columnWidths[realCol] == null + || ! columnWidths[realCol].isPercentage()) + { + total.minimum += req.minimum; + total.preferred += req.preferred; + total.maximum += req.maximum; + } + else + { + relTotal.minimum = + Math.max(relTotal.minimum, + (int) (req.minimum + * columnWidths[realCol].getValue())); + relTotal.preferred = + Math.max(relTotal.preferred, + (int) (req.preferred + * columnWidths[realCol].getValue())); + relTotal.maximum = + Math.max(relTotal.maximum, + (int) (req.maximum + * columnWidths[realCol].getValue())); + totalPercent += columnWidths[realCol].getValue(); + } + } + realCol += colSpan; + } + else + { + // Shortcut for colSpan == 1. + SizeRequirements req = columnRequirements[realCol]; + req.minimum = Math.max(req.minimum, + (int) cellView.getMinimumSpan(X_AXIS)); + req.preferred = Math.max(req.preferred, + (int) cellView.getPreferredSpan(X_AXIS)); + req.maximum = Math.max(req.maximum, + (int) cellView.getMaximumSpan(X_AXIS)); + if (columnWidths[realCol] == null + || ! columnWidths[realCol].isPercentage()) + { + total.minimum += columnRequirements[realCol].minimum; + total.preferred += + columnRequirements[realCol].preferred; + total.maximum += columnRequirements[realCol].maximum; + } + else + { + relTotal.minimum = + Math.max(relTotal.minimum, + (int) (req.minimum + / columnWidths[c].getValue())); + relTotal.preferred = + Math.max(relTotal.preferred, + (int) (req.preferred + / columnWidths[c].getValue())); + relTotal.maximum = + Math.max(relTotal.maximum, + (int) (req.maximum + / columnWidths[c].getValue())); + totalPercent += columnWidths[c].getValue(); + } + realCol += 1; + } + } + } + + // Update the total requirements as follows: + // 1. Multiply the absolute requirements with 1 - totalPercent. This + // gives the total requirements based on the wishes of the absolute + // cells. + // 2. Take the maximum of this value and the total relative + // requirements. Now we should have enough space for whatever cell + // in this column. + // 3. Take the maximum of this value and the previous maximum value. + total.minimum *= 1.F / (1.F - totalPercent); + total.preferred *= 1.F / (1.F - totalPercent); + total.maximum *= 1.F / (1.F - totalPercent); + + int rowTotalMin = Math.max(total.minimum, relTotal.minimum); + int rowTotalPref = Math.max(total.preferred, relTotal.preferred); + int rowTotalMax = Math.max(total.maximum, relTotal.maximum); + totalColumnRequirements.minimum = + Math.max(totalColumnRequirements.minimum, rowTotalMin); + totalColumnRequirements.preferred = + Math.max(totalColumnRequirements.preferred, rowTotalPref); + totalColumnRequirements.maximum = + Math.max(totalColumnRequirements.maximum, rowTotalMax); + } + + // Now we know what we want and can fix up the actual relative + // column requirements. + int numCols = columnRequirements.length; + for (int i = 0; i < numCols; i++) + { + if (columnWidths[i] != null) + { + columnRequirements[i].minimum = (int) + columnWidths[i].getValue(totalColumnRequirements.minimum); + columnRequirements[i].preferred = (int) + columnWidths[i].getValue(totalColumnRequirements.preferred); + columnRequirements[i].maximum = (int) + columnWidths[i].getValue(totalColumnRequirements.maximum); + } + } + } + /** - * Creates a view for a table row. - * - * @param el the element that represents the table row - * @return a view for rendering the table row - * (and instance of {@link RowView}). + * Lays out the columns. + * + * @param targetSpan the target span into which the table is laid out */ - protected TableRow createTableRow(Element el) + private void layoutColumns(int targetSpan) { - return new RowView(el); - } - + // Set the spans to the preferred sizes. Determine the space + // that we have to adjust the sizes afterwards. + long sumPref = 0; + int n = columnRequirements.length; + for (int i = 0; i < n; i++) + { + SizeRequirements col = columnRequirements[i]; + if (columnWidths[i] != null) + columnSpans[i] = (int) columnWidths[i].getValue(targetSpan); + else + columnSpans[i] = col.preferred; + sumPref += columnSpans[i]; + } + + // Try to adjust the spans so that we fill the targetSpan. + // For adjustments we have to use the targetSpan minus the cumulated + // cell spacings. + long diff = targetSpan - (n + 1) * cellSpacing - 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++) + { + // Only adjust the width if we haven't set a column width here. + if (columnWidths[i] == null) + { + SizeRequirements col = columnRequirements[i]; + int span; + if (diff < 0) + { + span = col.minimum; + diffs[i] = columnSpans[i] - span; + } + else + { + span = col.maximum; + diffs[i] = span - columnSpans[i]; + } + total += span; + } + else + total += columnSpans[i]; + } + + 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 = cellSpacing; + for (int i = 0; i < n; i++) + { + columnOffsets[i] = totalOffs; + if (diff != 0) + { + float adjust = factor * diffs[i]; + columnSpans[i] += Math.round(adjust); + } + // Avoid overflow here. + totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i] + + (long) cellSpacing, Integer.MAX_VALUE); + } + } + /** - * Loads the children of the Table. This completely bypasses the ViewFactory - * and creates instances of TableRow instead. + * Updates the arrays that contain the row and column data in response + * to a change to the table structure. * - * @param vf ignored + * Package private to avoid accessor methods. + */ + void updateGrid() + { + if (! gridValid) + { + AttributeSet atts = getAttributes(); + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + int maxColumns = 0; + int numRows = getViewCount(); + for (int r = 0; r < numRows; r++) + { + View rowView = getView(r); + int numCols = 0; + if (rowView instanceof RowView) + { + int numCells = ((RowView) rowView).getViewCount(); + for (int i = 0; i < numCells; i++) + { + View v = rowView.getView(i); + if (v instanceof CellView) + numCols += ((CellView) v).colSpan; + } + } + maxColumns = Math.max(numCols, maxColumns); + } + numColumns = maxColumns; + columnWidths = new Length[maxColumns]; + int[] rowSpans = new int[maxColumns]; + for (int r = 0; r < numRows; r++) + { + View view = getView(r); + if (view instanceof RowView) + { + RowView rowView = (RowView) view; + rowView.rowIndex = r; + rowView.overlap = new boolean[maxColumns]; + int colIndex = 0; + int colCount = rowView.getViewCount(); + for (int c = 0; c < maxColumns;) + { + if (rowSpans[c] > 0) + { + rowSpans[c]--; + rowView.overlap[c] = true; + c++; + } + else if (colIndex < colCount) + { + View v = rowView.getView(colIndex); + colIndex++; + if (v instanceof CellView) + { + CellView cv = (CellView) v; + Object o = + cv.getAttributes().getAttribute(CSS.Attribute.WIDTH); + if (o != null && columnWidths[c] == null + && o instanceof Length) + { + columnWidths[c]= (Length) o; + columnWidths[c].setFontBases(emBase, exBase); + } + int rs = cv.rowSpan - 1; + for (int col = cv.colSpan - 1; col >= 0; col--) + { + rowSpans[c] = rs; + c++; + } + } + } + else + { + c++; + } + } + } + } + columnRequirements = new SizeRequirements[maxColumns]; + for (int i = 0; i < maxColumns; i++) + columnRequirements[i] = new SizeRequirements(); + columnOffsets = new int[maxColumns]; + columnSpans = new int[maxColumns]; + + gridValid = true; + } + } + + /** + * Overridden to restrict the table width to the preferred size. + */ + public float getMaximumSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = super.getPreferredSpan(axis); + else + span = super.getMaximumSpan(axis); + return span; + } + + /** + * Overridden to fetch the CSS attributes when view gets connected. + */ + public void setParent(View parent) + { + super.setParent(parent); + if (parent != null) + setPropertiesFromAttributes(); + } + + /** + * Fetches CSS and HTML layout attributes. + */ + protected void setPropertiesFromAttributes() + { + super.setPropertiesFromAttributes(); + + // Fetch and parse cell spacing. + AttributeSet atts = getAttributes(); + StyleSheet ss = getStyleSheet(); + float emBase = ss.getEMBase(atts); + float exBase = ss.getEXBase(atts); + Object o = atts.getAttribute(CSS.Attribute.BORDER_SPACING); + if (o != null && o instanceof Length) + { + Length l = (Length) o; + l.setFontBases(emBase, exBase); + cellSpacing = (int) l.getValue(); + } + o = atts.getAttribute(CSS.Attribute.WIDTH); + if (o != null && o instanceof Length) + { + width = (Length) o; + width.setFontBases(emBase, exBase); + } + } + + /** + * Overridden to adjust for cellSpacing. + */ + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + r = super.calculateMajorAxisRequirements(axis, r); + int adjust = (getViewCount() + 1) * cellSpacing; + r.minimum += adjust; + r.preferred += adjust; + r.maximum += adjust; + return r; + } + + /** + * Overridden to adjust for cellSpacing. + */ + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) + { + // Mark all rows as invalid along their minor axis to force correct + // layout of multi-row cells. + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View row = getView(i); + if (row instanceof RowView) + ((RowView) row).layoutChanged(axis); + } + + int adjust = (getViewCount() + 1) * cellSpacing; + super.layoutMajorAxis(targetSpan - adjust, axis, offsets, spans); + for (int i = 0; i < offsets.length; i++) + { + offsets[i] += (i + 1) * cellSpacing; + } + } + + /** + * Overridden to replace view factory with this one. + */ + public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.insertUpdate(e, a, this); + } + + /** + * Overridden to replace view factory with this one. + */ + public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.removeUpdate(e, a, this); + } + + /** + * Overridden to replace view factory with this one. */ - protected void loadChildren(ViewFactory vf) + public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { - Element el = getElement(); - int numChildren = el.getElementCount(); - View[] rows = new View[numChildren]; - for (int i = 0; i < numChildren; ++i) + super.changedUpdate(e, a, this); + } + + public void replace(int offset, int len, View[] views) + { + gridValid = false; + super.replace(offset, len, views); + } + + /** + * We can't use the super class's paint() method because it might cut + * off multi-row children. Instead we trigger painting for all rows + * and let the rows sort out what to paint and what not. + */ + public void paint(Graphics g, Shape a) + { + Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + painter.paint(g, rect.x, rect.y, rect.width, rect.height, this); + int nRows = getViewCount(); + Rectangle inside = getInsideAllocation(a); + for (int r = 0; r < nRows; r++) { - rows[i] = createTableRow(el.getElement(i)); + tmpRect.setBounds(inside); + childAllocation(r, tmpRect); + paintChild(g, tmpRect, r); } - replace(0, getViewCount(), rows); } + } diff --git a/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java new file mode 100644 index 00000000000..25db89fc405 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java @@ -0,0 +1,163 @@ +/* ViewAttributeSet.java -- The AttributeSet used by HTML views + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.util.ArrayList; +import java.util.Enumeration; + +import javax.swing.text.AttributeSet; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; +import javax.swing.text.View; + +/** + * An AttributeSet implemenation that is used by the HTML views. This + * AttributeSet is created by StyleSheet.getViewAttributes() and combines + * the following attributes: + * - The original attributes of the View's element. + * - Any translated (HTML->CSS) attributes, as returned by + * StyleSheet.translateHTMLToCS(). + * - CSS Styles as resolved by the CSS stylesheet. + * + * In addition to that, it resolves attributes to the parent views, if + * a CSS attribute is requested that is inheritable. + * + * @author Roman Kennke (kennke@aicas.com) + */ +class ViewAttributeSet + extends MultiAttributeSet +{ + + /** + * The view for which we are the AttributeSet. + */ + private View view; + + /** + * The stylesheet to use. + */ + private StyleSheet styleSheet; + + /** + * Creates a new instance. + * + * @param v the view for which to do the AttributeSet + */ + ViewAttributeSet(View v, StyleSheet ss) + { + styleSheet = ss; + view = v; + ArrayList atts = new ArrayList(); + + Element el = v.getElement(); + AttributeSet elAtts = el.getAttributes(); + AttributeSet htmlAtts = styleSheet.translateHTMLToCSS(elAtts); + if (htmlAtts.getAttributeCount() > 0) + atts.add(htmlAtts); + + if (el.isLeaf()) + { + Enumeration n = elAtts.getAttributeNames(); + while (n.hasMoreElements()) + { + Object key = n.nextElement(); + if (key instanceof HTML.Tag) + { + AttributeSet rule = styleSheet.getRule((HTML.Tag) key, el); + if (rule != null) + atts.add(rule); + } + } + } + else + { + HTML.Tag tag = + (HTML.Tag) elAtts.getAttribute(StyleConstants.NameAttribute); + AttributeSet rule = styleSheet.getRule(tag, el); + if (rule != null) + atts.add(rule); + } + + AttributeSet[] atts1 = new AttributeSet[atts.size()]; + atts1 = (AttributeSet[]) atts.toArray(atts1); + init(atts1); + } + + /** + * Fetches the attribute for the specific ckey. If the attribute + * can't be found and the key is a CSS.Attribute that is inherited, + * then the attribute is looked up in the resolve parent. + */ + public Object getAttribute(Object key) + { + Object val = super.getAttribute(key); + if (val == null) + { + // Didn't find value. If the key is a CSS.Attribute, and is + // inherited, then ask the resolve parent. + if (key instanceof CSS.Attribute) + { + CSS.Attribute cssKey = (CSS.Attribute) key; + if (cssKey.isInherited()) + { + AttributeSet resolveParent = getResolveParent(); + if (resolveParent != null) + val = resolveParent.getAttribute(cssKey); + } + } + } + return val; + } + + /** + * Returns the resolve parent of this AttributeSet. This is the AttributeSet + * returned by the parent view if available. + */ + public AttributeSet getResolveParent() + { + AttributeSet parent = null; + if (view != null) + { + View parentView = view.getParent(); + if (parentView != null) + parent = parentView.getAttributes(); + } + return parent; + } +} diff --git a/libjava/classpath/javax/swing/text/html/parser/AttributeList.java b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java index 5bca0bfa7db..d48266d4730 100644 --- a/libjava/classpath/javax/swing/text/html/parser/AttributeList.java +++ b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java @@ -122,7 +122,7 @@ public final class AttributeList * null, if this parameter was not specified. * Values, defined in DTD, are case insensitive. */ - public Vector values; + public Vector<?> values; /** * The modifier of this attribute. This field contains one of the @@ -176,7 +176,7 @@ public final class AttributeList * Equals to null for the last attribute definition. */ public AttributeList(String a_name, int a_type, int a_modifier, - String a_default, Vector allowed_values, + String a_default, Vector<?> allowed_values, AttributeList a_next ) { @@ -251,7 +251,7 @@ public final class AttributeList /** * Get the allowed values of this attribute. */ - public Enumeration getValues() + public Enumeration<?> getValues() { return values.elements(); } diff --git a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java index 70e9c2acbff..d5c4418de27 100644 --- a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java +++ b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java @@ -151,13 +151,15 @@ public final class ContentModel * discarded. * @param elements - a vector to add the values to. */ - public void getElements(Vector elements) + public void getElements(Vector<Element> elements) { ContentModel c = this; while (c != null) { - elements.add(c.content); + // FIXME: correct? + if (c.content instanceof Element) + elements.add((Element) c.content); c = c.next; } } diff --git a/libjava/classpath/javax/swing/text/html/parser/DTD.java b/libjava/classpath/javax/swing/text/html/parser/DTD.java index 16bc5b0d6af..ae3c184f153 100644 --- a/libjava/classpath/javax/swing/text/html/parser/DTD.java +++ b/libjava/classpath/javax/swing/text/html/parser/DTD.java @@ -88,7 +88,7 @@ public class DTD /** * The table of existing available DTDs. */ - static Hashtable dtdHash = new Hashtable(); + static Hashtable<String,DTD> dtdHash = new Hashtable<String,DTD>(); /** * The applet element for this DTD. @@ -148,12 +148,13 @@ public class DTD /** * The element for accessing all DTD elements by name. */ - public Hashtable elementHash = new Hashtable(); + public Hashtable<String,Element> elementHash = + new Hashtable<String,Element>(); /** * The entity table for accessing all DTD entities by name. */ - public Hashtable entityHash = new Hashtable(); + public Hashtable<Object, Entity> entityHash = new Hashtable<Object, Entity>(); /** * The name of this DTD. @@ -165,7 +166,7 @@ public class DTD * javax.swing.text.html.parser.Element#index field of all elements * in this vector is set to the element position in this vector. */ - public Vector elements = new Vector(); + public Vector<Element> elements = new Vector<Element>(); /** Create a new DTD with the specified name. */ protected DTD(String a_name) @@ -224,7 +225,7 @@ public class DTD String name = Entity.mapper.get(id); if (name != null) - return (Entity) entityHash.get(name); + return entityHash.get(name); else return null; } @@ -269,7 +270,7 @@ public class DTD */ public void defineAttributes(String forElement, AttributeList attributes) { - Element e = (Element) elementHash.get(forElement.toLowerCase()); + Element e = elementHash.get(forElement.toLowerCase()); if (e == null) e = newElement(forElement); @@ -420,7 +421,7 @@ public class DTD if (allowed_values != null) { StringTokenizer st = new StringTokenizer(allowed_values, " \t|"); - Vector v = new Vector(st.countTokens()); + Vector<String> v = new Vector<String>(st.countTokens()); while (st.hasMoreTokens()) v.add(st.nextToken()); @@ -571,7 +572,7 @@ public class DTD */ private Element newElement(String name) { - Element e = (Element) elementHash.get(name.toLowerCase()); + Element e = elementHash.get(name.toLowerCase()); if (e == null) { diff --git a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java index 062606d17ba..f717d69cbda 100644 --- a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java +++ b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java @@ -38,13 +38,13 @@ exception statement from your version. */ package javax.swing.text.html.parser; -import gnu.javax.swing.text.html.parser.htmlAttributeSet; import javax.swing.text.html.parser.Parser; import java.io.IOException; import java.io.Reader; import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; import javax.swing.text.html.HTMLEditorKit; /** @@ -117,7 +117,7 @@ public class DocumentParser protected final void handleStartTag(TagElement tag) { parser.handleStartTag(tag); - htmlAttributeSet attributes = gnu.getAttributes(); + SimpleAttributeSet attributes = gnu.getAttributes(); if (tag.fictional()) attributes.addAttribute(HTMLEditorKit.ParserCallback.IMPLIED, diff --git a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java index 70636d92923..cdd339b8f21 100644 --- a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java +++ b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java @@ -38,13 +38,13 @@ exception statement from your version. */ package javax.swing.text.html.parser; import gnu.javax.swing.text.html.parser.HTML_401F; -import gnu.javax.swing.text.html.parser.htmlAttributeSet; import java.io.IOException; import java.io.Reader; import java.io.Serializable; import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit.ParserCallback; @@ -93,7 +93,7 @@ public class ParserDelegator protected final void handleStartTag(TagElement tag) { - htmlAttributeSet attributes = gnu.getAttributes(); + SimpleAttributeSet attributes = gnu.getAttributes(); if (tag.fictional()) attributes.addAttribute(ParserCallback.IMPLIED, Boolean.TRUE); diff --git a/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java index 155343f5bcc..4a6899fbeae 100644 --- a/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java @@ -149,9 +149,11 @@ public abstract class AbstractLayoutCache protected Rectangle getNodeDimensions(Object value, int row, int depth, boolean expanded, Rectangle bounds) { - if (nodeDimensions == null) - throw new InternalError("The NodeDimensions are not set"); - return nodeDimensions.getNodeDimensions(value, row, depth, expanded, bounds); + Rectangle d = null; + if (nodeDimensions != null) + d = nodeDimensions.getNodeDimensions(value, row, depth, expanded, + bounds); + return d; } /** @@ -224,7 +226,12 @@ public abstract class AbstractLayoutCache */ public void setSelectionModel(TreeSelectionModel model) { + if (treeSelectionModel != null) + treeSelectionModel.setRowMapper(null); treeSelectionModel = model; + if (treeSelectionModel != null) + treeSelectionModel.setRowMapper(this); + } /** @@ -337,7 +344,7 @@ public abstract class AbstractLayoutCache * * @return Enumeration */ - public abstract Enumeration getVisiblePathsFrom(TreePath path); + public abstract Enumeration<TreePath> getVisiblePathsFrom(TreePath path); /** * getVisibleChildCount @@ -425,9 +432,13 @@ public abstract class AbstractLayoutCache */ public int[] getRowsForPaths(TreePath[] paths) { - int[] rows = new int[paths.length]; - for (int i = 0; i < rows.length; i++) - rows[i] = getRowForPath(paths[i]); + int[] rows = null; + if (paths != null) + { + rows = new int[paths.length]; + for (int i = 0; i < rows.length; i++) + rows[i] = getRowForPath(paths[i]); + } return rows; } @@ -440,6 +451,6 @@ public abstract class AbstractLayoutCache */ protected boolean isFixedRowHeight() { - return false; + return rowHeight > 0; } } diff --git a/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java index 6951b960005..9f587946fc2 100644 --- a/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java +++ b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java @@ -67,7 +67,7 @@ public class DefaultMutableTreeNode * An empty enumeration, returned by {@link #children()} if a node has no * children. */ - public static final Enumeration EMPTY_ENUMERATION = + public static final Enumeration<TreeNode> EMPTY_ENUMERATION = EmptyEnumeration.getInstance(); /** @@ -78,7 +78,7 @@ public class DefaultMutableTreeNode /** * The child nodes for this node (may be empty). */ - protected Vector children = new Vector(); + protected Vector<MutableTreeNode> children = new Vector<MutableTreeNode>(); /** * userObject @@ -480,7 +480,7 @@ public class DefaultMutableTreeNode public TreeNode getSharedAncestor(DefaultMutableTreeNode node) { TreeNode current = this; - ArrayList list = new ArrayList(); + ArrayList<TreeNode> list = new ArrayList<TreeNode>(); while (current != null) { @@ -527,7 +527,7 @@ public class DefaultMutableTreeNode || children.size() == 0) return 0; - Stack stack = new Stack(); + Stack<Integer> stack = new Stack<Integer>(); stack.push(new Integer(0)); TreeNode node = getChildAt(0); int depth = 0; @@ -765,7 +765,7 @@ public class DefaultMutableTreeNode throw new IllegalArgumentException(); TreeNode parent = this; - Vector nodes = new Vector(); + Vector<TreeNode> nodes = new Vector<TreeNode>(); nodes.add(this); while (parent != node && parent != null) @@ -1148,7 +1148,7 @@ public class DefaultMutableTreeNode static class PostorderEnumeration implements Enumeration { - Stack nodes = new Stack(); + Stack<TreeNode> nodes = new Stack<TreeNode>(); Stack childrenEnums = new Stack(); PostorderEnumeration(TreeNode node) diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java index b0a4d8db823..4c10bfe1af2 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java @@ -43,7 +43,6 @@ import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ActionEvent; @@ -59,10 +58,10 @@ import javax.swing.Icon; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.CellEditorListener; -import javax.swing.event.ChangeEvent; import javax.swing.event.EventListenerList; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; @@ -77,12 +76,6 @@ public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor, TreeSelectionListener { /** - * The number of the fast mouse clicks, required to start the editing - * session. - */ - static int CLICK_COUNT_TO_START = 3; - - /** * This container that appears on the tree during editing session. * It contains the editing component displays various other editor - * specific parts like editing icon. @@ -99,7 +92,7 @@ public class DefaultTreeCellEditor */ public EditorContainer() { - // Do nothing here. + setLayout(null); } /** @@ -111,12 +104,6 @@ public class DefaultTreeCellEditor // Do nothing here. } - public void setBounds(Rectangle bounds) - { - super.setBounds(bounds); - doLayout(); - } - /** * Overrides Container.paint to paint the node's icon and use the selection * color for the background. @@ -126,11 +113,20 @@ public class DefaultTreeCellEditor */ public void paint(Graphics g) { + // Paint editing icon. if (editingIcon != null) { // From the previous version, the left margin is taken as half // of the icon width. - editingIcon.paintIcon(this, g, 0, 0); + int y = Math.max(0, (getHeight() - editingIcon.getIconHeight()) / 2); + editingIcon.paintIcon(this, g, 0, y); + } + // Paint border. + Color c = getBorderSelectionColor(); + if (c != null) + { + g.setColor(c); + g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); } super.paint(g); } @@ -141,27 +137,33 @@ public class DefaultTreeCellEditor */ public void doLayout() { - // The offset of the editing component. - int eOffset; + if (editingComponent != null) + { + editingComponent.getPreferredSize(); + editingComponent.setBounds(offset, 0, getWidth() - offset, + getHeight()); + } + } - // Move the component to the left, leaving room for the editing icon: - if (editingIcon != null) - eOffset = editingIcon.getIconWidth(); + public Dimension getPreferredSize() + { + Dimension dim; + if (editingComponent != null) + { + dim = editingComponent.getPreferredSize(); + dim.width += offset + 5; + if (renderer != null) + { + Dimension r = renderer.getPreferredSize(); + dim.height = Math.max(dim.height, r.height); + } + if (editingIcon != null) + dim.height = Math.max(dim.height, editingIcon.getIconHeight()); + dim.width = Math.max(100, dim.width); + } else - eOffset = 0; - - Rectangle bounds = getBounds(); - Component c = getComponent(0); - c.setLocation(eOffset, 0); - - // Span the editing component near over all window width. - c.setSize(bounds.width - eOffset, bounds.height); - /* - * @specnote the Sun sets some more narrow editing component width (it is - * not documented how does it is calculated). However as our text field is - * still not able to auto - scroll horizontally, replicating such strategy - * would prevent adding extra characters to the text being edited. - */ + dim = new Dimension(0, 0); + return dim; } } @@ -227,46 +229,15 @@ public class DefaultTreeCellEditor */ public Dimension getPreferredSize() { - String s = getText(); - - Font f = getFont(); - - if (f != null) + Dimension size = super.getPreferredSize(); + if (renderer != null && DefaultTreeCellEditor.this.getFont() == null) { - FontMetrics fm = getToolkit().getFontMetrics(f); - - return new Dimension(SwingUtilities.computeStringWidth(fm, s), - fm.getHeight()); + size.height = renderer.getPreferredSize().height; } return renderer.getPreferredSize(); } } - /** - * Listens for the events from the realEditor. - */ - class RealEditorListener implements CellEditorListener - { - /** - * The method is called when the editing has been cancelled. - * @param event unused - */ - public void editingCanceled(ChangeEvent event) - { - cancelCellEditing(); - } - - /** - * The method is called after completing the editing session. - * - * @param event unused - */ - public void editingStopped(ChangeEvent event) - { - stopCellEditing(); - } - } - private EventListenerList listenerList = new EventListenerList(); /** @@ -367,21 +338,14 @@ public class DefaultTreeCellEditor public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, TreeCellEditor editor) { - setTree(tree); this.renderer = renderer; - - if (editor == null) - editor = createTreeCellEditor(); - else - editor.addCellEditorListener(new RealEditorListener()); - realEditor = editor; - - lastPath = tree.getLeadSelectionPath(); - tree.addTreeSelectionListener(this); + if (realEditor == null) + realEditor = createTreeCellEditor(); editingContainer = createContainer(); - setFont(UIManager.getFont("Tree.font")); - setBorderSelectionColor(UIManager.getColor("Tree.selectionBorderColor")); + setTree(tree); + Color c = UIManager.getColor("Tree.editorBorderSelectionColor"); + setBorderSelectionColor(c); } /** @@ -505,19 +469,36 @@ public class DefaultTreeCellEditor * @return the component for editing */ public Component getTreeCellEditorComponent(JTree tree, Object value, - boolean isSelected, boolean expanded, + boolean isSelected, + boolean expanded, boolean leaf, int row) { - if (realEditor == null) - realEditor = createTreeCellEditor(); - - return realEditor.getTreeCellEditorComponent(tree, value, isSelected, - expanded, leaf, row); + setTree(tree); + lastRow = row; + determineOffset(tree, value, isSelected, expanded, leaf, row); + if (editingComponent != null) + editingContainer.remove(editingComponent); + + editingComponent = realEditor.getTreeCellEditorComponent(tree, value, + isSelected, + expanded, leaf, + row); + Font f = getFont(); + if (f == null) + { + if (renderer != null) + f = renderer.getFont(); + if (f == null) + f = tree.getFont(); + } + editingContainer.setFont(f); + prepareForEditing(); + return editingContainer; } /** * Returns the value currently being edited (requests it from the - * {@link realEditor}. + * {@link #realEditor}. * * @return the value currently being edited */ @@ -535,16 +516,48 @@ public class DefaultTreeCellEditor * @return true if editing can be started */ public boolean isCellEditable(EventObject event) - { - if (editingComponent == null) - configureEditingComponent(tree, renderer, realEditor); - - if (editingComponent != null && realEditor.isCellEditable(event)) + { + boolean ret = false; + boolean ed = false; + if (event != null) { - prepareForEditing(); - return true; + if (event.getSource() instanceof JTree) + { + setTree((JTree) event.getSource()); + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + TreePath path = tree.getPathForLocation(me.getX(), me.getY()); + ed = lastPath != null && path != null && lastPath.equals(path); + if (path != null) + { + lastRow = tree.getRowForPath(path); + Object val = path.getLastPathComponent(); + boolean isSelected = tree.isRowSelected(lastRow); + boolean isExpanded = tree.isExpanded(path); + TreeModel m = tree.getModel(); + boolean isLeaf = m.isLeaf(val); + determineOffset(tree, val, isSelected, isExpanded, isLeaf, + lastRow); + } + } + } } - return false; + if (! realEditor.isCellEditable(event)) + ret = false; + else + { + if (canEditImmediately(event)) + ret = true; + else if (ed && shouldStartEditingTimer(event)) + startEditingTimer(); + else if (timer != null && timer.isRunning()) + timer.stop(); + } + if (ret) + prepareForEditing(); + return ret; + } /** @@ -567,14 +580,13 @@ public class DefaultTreeCellEditor */ public boolean stopCellEditing() { - if (editingComponent != null) + boolean ret = false; + if (realEditor.stopCellEditing()) { - stopEditingTimer(); - tree.stopEditing(); - editingComponent = null; - return true; + finish(); + ret = true; } - return false; + return ret; } /** @@ -583,21 +595,15 @@ public class DefaultTreeCellEditor */ public void cancelCellEditing() { - if (editingComponent != null) - { - tree.cancelEditing(); - editingComponent = null; - } - stopEditingTimer(); + realEditor.cancelCellEditing(); + finish(); } - - /** - * Stop the editing timer, if it is installed and running. - */ - private void stopEditingTimer() + + private void finish() { - if (timer != null && timer.isRunning()) - timer.stop(); + if (editingComponent != null) + editingContainer.remove(editingComponent); + editingComponent = null; } /** @@ -640,10 +646,18 @@ public class DefaultTreeCellEditor */ public void valueChanged(TreeSelectionEvent e) { - tPath = lastPath; - lastPath = e.getNewLeadSelectionPath(); - lastRow = tree.getRowForPath(lastPath); - stopCellEditing(); + if (tree != null) + { + if (tree.getSelectionCount() == 1) + lastPath = tree.getSelectionPath(); + else + lastPath = null; + } + // TODO: We really should do the following here, but can't due + // to buggy DefaultTreeSelectionModel. This selection model + // should only fire if the selection actually changes. +// if (timer != null) +// timer.stop(); } /** @@ -653,6 +667,8 @@ public class DefaultTreeCellEditor */ public void actionPerformed(ActionEvent e) { + if (tree != null && lastPath != null) + tree.startEditingAtPath(lastPath); } /** @@ -664,7 +680,17 @@ public class DefaultTreeCellEditor */ protected void setTree(JTree newTree) { - tree = newTree; + if (tree != newTree) + { + if (tree != null) + tree.removeTreeSelectionListener(this); + tree = newTree; + if (tree != null) + tree.addTreeSelectionListener(this); + + if (timer != null) + timer.stop(); + } } /** @@ -675,10 +701,14 @@ public class DefaultTreeCellEditor */ protected boolean shouldStartEditingTimer(EventObject event) { - if ((event instanceof MouseEvent) && - ((MouseEvent) event).getClickCount() == 1) - return true; - return false; + boolean ret = false; + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1 + && inHitRegion(me.getX(), me.getY()); + } + return ret; } /** @@ -686,8 +716,12 @@ public class DefaultTreeCellEditor */ protected void startEditingTimer() { - if (timer != null) - timer.start(); + if (timer == null) + { + timer = new Timer(1200, this); + timer.setRepeats(false); + } + timer.start(); } /** @@ -723,7 +757,6 @@ public class DefaultTreeCellEditor protected boolean inHitRegion(int x, int y) { Rectangle bounds = tree.getPathBounds(lastPath); - return bounds.contains(x, y); } @@ -739,13 +772,24 @@ public class DefaultTreeCellEditor protected void determineOffset(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { - renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded, - leaf, row, true); - Icon c = renderer.getIcon(); - if (c != null) - offset = renderer.getIconTextGap() + c.getIconWidth(); + if (renderer != null) + { + if (leaf) + editingIcon = renderer.getLeafIcon(); + else if (expanded) + editingIcon = renderer.getOpenIcon(); + else + editingIcon = renderer.getClosedIcon(); + if (editingIcon != null) + offset = renderer.getIconTextGap() + editingIcon.getIconWidth(); + else + offset = renderer.getIconTextGap(); + } else - offset = 0; + { + editingIcon = null; + offset = 0; + } } /** @@ -754,8 +798,8 @@ public class DefaultTreeCellEditor */ protected void prepareForEditing() { - editingContainer.removeAll(); - editingContainer.add(editingComponent); + if (editingComponent != null) + editingContainer.add(editingComponent); } /** @@ -776,10 +820,10 @@ public class DefaultTreeCellEditor */ protected TreeCellEditor createTreeCellEditor() { - DefaultCellEditor editor = new DefaultCellEditor(new DefaultTreeCellEditor.DefaultTextField( - UIManager.getBorder("Tree.selectionBorder"))); - editor.addCellEditorListener(new RealEditorListener()); - editor.setClickCountToStart(CLICK_COUNT_TO_START); + Border border = UIManager.getBorder("Tree.editorBorder"); + JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border); + DefaultCellEditor editor = new DefaultCellEditor(tf); + editor.setClickCountToStart(1); realEditor = editor; return editor; } diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java index e120b71c167..3766485abdb 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java @@ -77,7 +77,7 @@ public class DefaultTreeCellRenderer protected boolean hasFocus; /** - * drawsFocusBorderAroundIcon // FIXME: is this used? + * Indicates if the focus border is also drawn around the icon. */ private boolean drawsFocusBorderAroundIcon; @@ -152,6 +152,8 @@ public class DefaultTreeCellRenderer setBackgroundNonSelectionColor(UIManager.getColor("Tree.textBackground")); setBackgroundSelectionColor(UIManager.getColor("Tree.selectionBackground")); setBorderSelectionColor(UIManager.getColor("Tree.selectionBorderColor")); + Object val = UIManager.get("Tree.drawsFocusBorderAroundIcon"); + drawsFocusBorderAroundIcon = val != null && ((Boolean) val).booleanValue(); } /** @@ -499,67 +501,75 @@ public class DefaultTreeCellRenderer */ public void paint(Graphics g) { - // paint background - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - - Insets insets = new Insets(0, 0, 0, 0); - Border border = UIManager.getBorder("Tree.selectionBorder"); - if (border != null) - insets = border.getBorderInsets(this); - - FontMetrics fm = getToolkit().getFontMetrics(getFont()); - SwingUtilities.layoutCompoundLabel((JLabel) this, fm, getText(), - getIcon(), getVerticalAlignment(), - getHorizontalAlignment(), - getVerticalTextPosition(), - getHorizontalTextPosition(), vr, ir, tr, - getIconTextGap()); - - // Reusing one rectangle. - Rectangle bounds = getBounds(ir); - - bounds.x = tr.x - insets.left; - bounds.width = tr.width + insets.left + insets.right; - - g.setColor(super.getBackground()); - g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); + // Determine background color. + Color bgColor; + if (selected) + bgColor = getBackgroundSelectionColor(); + else + { + bgColor = getBackgroundNonSelectionColor(); + if (bgColor == null) + bgColor = getBackground(); + } + // Paint background. + int xOffset = -1; + if (bgColor != null) + { + Icon i = getIcon(); + xOffset = getXOffset(); + g.setColor(bgColor); + g.fillRect(xOffset, 0, getWidth() - xOffset, getHeight()); + } - super.paint(g); - - // Paint the border of the focused element only (lead selection) if (hasFocus) { - Color b = getBorderSelectionColor(); - if (b != null) - { - g.setColor(b); - g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height - 1); - } + if (drawsFocusBorderAroundIcon) + xOffset = 0; + else if (xOffset == -1) + xOffset = getXOffset(); + paintFocus(g, xOffset, 0, getWidth() - xOffset, getHeight()); + } + super.paint(g); + } + + /** + * Paints the focus indicator. + */ + private void paintFocus(Graphics g, int x, int y, int w, int h) + { + Color col = getBorderSelectionColor(); + if (col != null) + { + g.setColor(col); + g.drawRect(x, y, w - 1, h - 1); } } /** + * Determines the X offset of the label that is caused by + * the icon. + * + * @return the X offset of the label + */ + private int getXOffset() + { + Icon i = getIcon(); + int offs = 0; + if (i != null && getText() != null) + offs = i.getIconWidth() + Math.max(0, getIconTextGap() - 1); + return offs; + } + + /** * Returns the preferred size of the cell. * * @return The preferred size of the cell. */ public Dimension getPreferredSize() { - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - - FontMetrics fm = getToolkit().getFontMetrics(getFont()); - SwingUtilities.layoutCompoundLabel((JLabel) this, fm, getText(), - getIcon(), getVerticalAlignment(), - getHorizontalAlignment(), - getVerticalTextPosition(), - getHorizontalTextPosition(), vr, ir, tr, - getIconTextGap()); - Rectangle cr = ir.union(tr); - return new Dimension(cr.width, cr.height); + Dimension size = super.getPreferredSize(); + size.width += 3; + return size; } /** diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java index 5819d15b627..afee7ea22fa 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeModel.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java @@ -210,17 +210,32 @@ public class DefaultTreeModel } /** - * isLeaf + * Returns if the specified node is a leaf or not. When + * {@link #asksAllowsChildren} is true, then this checks if the TreeNode + * allows children, otherwise it returns the TreeNode's <code>leaf</code> + * property. * - * @param node TODO - * @return boolean + * @param node the node to check + * + * @return boolean <code>true</code> if the node is a leaf node, + * <code>false</code> otherwise + * + * @throws ClassCastException if the specified node is not a + * <code>TreeNode</code> instance + * + * @see TreeNode#getAllowsChildren() + * @see TreeNode#isLeaf() */ public boolean isLeaf(Object node) { - if (node instanceof TreeNode) - return ((TreeNode) node).isLeaf(); + // The RI throws a ClassCastException when node isn't a TreeNode, so do we. + TreeNode treeNode = (TreeNode) node; + boolean leaf; + if (asksAllowsChildren) + leaf = ! treeNode.getAllowsChildren(); else - return true; + leaf = treeNode.isLeaf(); + return leaf; } /** @@ -600,7 +615,7 @@ public class DefaultTreeModel * * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java index 0684ef76659..3d9c67728bd 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java @@ -44,6 +44,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Arrays; +import java.util.BitSet; import java.util.EventListener; import java.util.HashSet; import java.util.Iterator; @@ -67,7 +68,39 @@ import javax.swing.event.TreeSelectionListener; public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel { - + + /** + * According to the API docs, the method + * {@link DefaultTreeSelectionModel#notifyPathChange} should + * expect instances of a class PathPlaceHolder in the Vector parameter. + * This seems to be a non-public class, so I can only make guesses about the + * use of it. + */ + private static class PathPlaceHolder + { + /** + * The path that we wrap. + */ + TreePath path; + + /** + * Indicates if the path is new or already in the selection. + */ + boolean isNew; + + /** + * Creates a new instance. + * + * @param p the path to wrap + * @param n if the path is new or already in the selection + */ + PathPlaceHolder(TreePath p, boolean n) + { + path = p; + isNew = n; + } + } + /** * Use serialVersionUID for interoperability. */ @@ -124,12 +157,36 @@ public class DefaultTreeSelectionModel protected int leadRow = -1; /** + * A supporting datastructure that is used in addSelectionPaths() and + * removeSelectionPaths(). It contains currently selected paths. + * + * @see #addSelectionPaths(TreePath[]) + * @see #removeSelectionPaths(TreePath[]) + * @see #setSelectionPaths(TreePath[]) + */ + private transient HashSet selectedPaths; + + /** + * A supporting datastructure that is used in addSelectionPaths() and + * removeSelectionPaths(). It contains the paths that are added or removed. + * + * @see #addSelectionPaths(TreePath[]) + * @see #removeSelectionPaths(TreePath[]) + * @see #setSelectionPaths(TreePath[]) + */ + private transient HashSet tmpPaths; + + /** * Constructs a new DefaultTreeSelectionModel. */ public DefaultTreeSelectionModel() { setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); + listSelectionModel = new DefaultListSelectionModel(); listenerList = new EventListenerList(); + leadIndex = -1; + tmpPaths = new HashSet(); + selectedPaths = new HashSet(); } /** @@ -144,12 +201,14 @@ public class DefaultTreeSelectionModel { DefaultTreeSelectionModel cloned = (DefaultTreeSelectionModel) super.clone(); - - // Clone the selection and the list selection model. + cloned.changeSupport = null; cloned.selection = (TreePath[]) selection.clone(); - if (listSelectionModel != null) - cloned.listSelectionModel - = (DefaultListSelectionModel) listSelectionModel.clone(); + cloned.listenerList = new EventListenerList(); + cloned.listSelectionModel = + (DefaultListSelectionModel) listSelectionModel.clone(); + cloned.selectedPaths = new HashSet(); + cloned.tmpPaths = new HashSet(); + return cloned; } @@ -209,6 +268,7 @@ public class DefaultTreeSelectionModel public void setRowMapper(RowMapper mapper) { rowMapper = mapper; + resetRowSelection(); } /** @@ -236,8 +296,18 @@ public class DefaultTreeSelectionModel */ public void setSelectionMode(int mode) { + int oldMode = selectionMode; selectionMode = mode; - insureRowContinuity(); + // Make sure we have a valid selection mode. + if (selectionMode != SINGLE_TREE_SELECTION + && selectionMode != CONTIGUOUS_TREE_SELECTION + && selectionMode != DISCONTIGUOUS_TREE_SELECTION) + selectionMode = DISCONTIGUOUS_TREE_SELECTION; + + // Fire property change event. + if (oldMode != selectionMode && changeSupport != null) + changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode, + selectionMode); } /** @@ -262,32 +332,10 @@ public class DefaultTreeSelectionModel */ public void setSelectionPath(TreePath path) { - // The most frequently only one cell in the tree is selected. - TreePath[] ose = selection; - selection = new TreePath[] { path }; - TreePath oldLead = leadPath; - leadIndex = 0; - leadRow = getRow(path); - leadPath = path; - - TreeSelectionEvent event; - - if (ose != null && ose.length > 0) - { - // The first item in the path list is the selected path. - // The remaining items are unselected pathes. - TreePath[] changed = new TreePath[ose.length + 1]; - boolean[] news = new boolean[changed.length]; - news[0] = true; - changed[0] = path; - System.arraycopy(ose, 0, changed, 1, ose.length); - event = new TreeSelectionEvent(this, changed, news, oldLead, path); - } - else - { - event = new TreeSelectionEvent(this, path, true, oldLead, path); - } - fireValueChanged(event); + TreePath[] paths = null; + if (path != null) + paths = new TreePath[]{ path }; + setSelectionPaths(paths); } /** @@ -307,7 +355,7 @@ public class DefaultTreeSelectionModel AbstractLayoutCache ama = (AbstractLayoutCache) mapper; return ama.getRowForPath(path); } - else + else if (mapper != null) { // Generic non optimized implementation. int[] rows = mapper.getRowsForPaths(new TreePath[] { path }); @@ -316,6 +364,7 @@ public class DefaultTreeSelectionModel else return rows[0]; } + return -1; } /** @@ -327,10 +376,90 @@ public class DefaultTreeSelectionModel */ public void setSelectionPaths(TreePath[] paths) { - // Must be called, as defined in JDK API 1.4. - insureUniqueness(); - clearSelection(); - addSelectionPaths(paths); + int oldLength = 0; + if (selection != null) + oldLength = selection.length; + int newLength = 0; + if (paths != null) + newLength = paths.length; + if (newLength > 0 || oldLength > 0) + { + // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with + // a non-contiguous path, we only allow the first path element. + if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1) + || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0 + && ! arePathsContiguous(paths))) + { + paths = new TreePath[] { paths[0] }; + newLength = 1; + } + // Find new paths. + Vector changedPaths = null; + tmpPaths.clear(); + int validPaths = 0; + TreePath oldLeadPath = leadPath; + for (int i = 0; i < newLength; i++) + { + if (paths[i] != null && ! tmpPaths.contains(paths[i])) + { + validPaths++; + tmpPaths.add(paths[i]); + if (! selectedPaths.contains(paths[i])) + { + if (changedPaths == null) + changedPaths = new Vector(); + changedPaths.add(new PathPlaceHolder(paths[i], true)); + } + leadPath = paths[i]; + } + } + // Put together the new selection. + TreePath[] newSelection = null; + if (validPaths != 0) + { + if (validPaths != newLength) + { + // Some of the paths are already selected, put together + // the new selection carefully. + newSelection = new TreePath[validPaths]; + Iterator newPaths = tmpPaths.iterator(); + validPaths = 0; + for (int i = 0; newPaths.hasNext(); i++) + newSelection[i] = (TreePath) newPaths.next(); + } + else + { + newSelection = new TreePath[paths.length]; + System.arraycopy(paths, 0, newSelection, 0, paths.length); + } + } + + // Find paths that have been selected, but are no more. + for (int i = 0; i < oldLength; i++) + { + if (selection[i] != null && ! tmpPaths.contains(selection[i])) + { + if (changedPaths == null) + changedPaths = new Vector(); + changedPaths.add(new PathPlaceHolder(selection[i], false)); + } + } + + // Perform changes and notification. + selection = newSelection; + HashSet tmp = selectedPaths; + selectedPaths = tmpPaths; + tmpPaths = tmp; + tmpPaths.clear(); + + // Not necessary, but required according to the specs and to tests. + if (selection != null) + insureUniqueness(); + updateLeadIndex(); + resetRowSelection(); + if (changedPaths != null && changedPaths.size() > 0) + notifyPathChange(changedPaths, oldLeadPath); + } } /** @@ -345,29 +474,10 @@ public class DefaultTreeSelectionModel */ public void addSelectionPath(TreePath path) { - if (! isPathSelected(path)) + if (path != null) { - if (selectionMode == SINGLE_TREE_SELECTION || isSelectionEmpty() - || ! canPathBeAdded(path)) - setSelectionPath(path); - else - { - TreePath[] temp = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, temp, 0, selection.length); - temp[temp.length - 1] = path; - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - } - } - - if (path != leadPath) - { - TreePath oldLead = leadPath; - leadPath = path; - leadRow = getRow(path); - leadIndex = selection.length - 1; - fireValueChanged(new TreeSelectionEvent(this, path, true, oldLead, - leadPath)); + TreePath[] add = new TreePath[]{ path }; + addSelectionPaths(add); } } @@ -380,37 +490,76 @@ public class DefaultTreeSelectionModel */ public void addSelectionPaths(TreePath[] paths) { - // Must be called, as defined in JDK API 1.4. - insureUniqueness(); - - if (paths != null) + int length = paths != null ? paths.length : 0; + if (length > 0) { - TreePath v0 = null; - for (int i = 0; i < paths.length; i++) + if (selectionMode == SINGLE_TREE_SELECTION) + setSelectionPaths(paths); + else if (selectionMode == CONTIGUOUS_TREE_SELECTION + && ! canPathsBeAdded(paths)) + { + if (arePathsContiguous(paths)) + setSelectionPaths(paths); + else + setSelectionPaths(new TreePath[] { paths[0] }); + } + else { - v0 = paths[i]; - if (! isPathSelected(v0)) + Vector changedPaths = null; + tmpPaths.clear(); + int validPaths = 0; + TreePath oldLeadPath = leadPath; + int oldPaths = 0; + if (selection != null) + oldPaths = selection.length; + int i; + for (i = 0; i < length; i++) { - if (isSelectionEmpty()) - setSelectionPath(v0); - else + if (paths[i] != null) { - TreePath[] temp = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, temp, 0, selection.length); - temp[temp.length - 1] = v0; - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); + if (! selectedPaths.contains(paths[i])) + { + validPaths++; + if (changedPaths == null) + changedPaths = new Vector(); + changedPaths.add(new PathPlaceHolder(paths[i], true)); + selectedPaths.add(paths[i]); + tmpPaths.add(paths[i]); + } + leadPath = paths[i]; } - TreePath oldLead = leadPath; - leadPath = paths[paths.length - 1]; - leadRow = getRow(leadPath); - leadIndex = selection.length - 1; - - fireValueChanged(new TreeSelectionEvent(this, v0, true, - oldLead, leadPath)); } + if (validPaths > 0) + { + TreePath[] newSelection = new TreePath[oldPaths + validPaths]; + if (oldPaths > 0) + System.arraycopy(selection, 0, newSelection, 0, oldPaths); + if (validPaths != paths.length) + { + // Some of the paths are already selected, put together + // the new selection carefully. + Iterator newPaths = tmpPaths.iterator(); + i = oldPaths; + while (newPaths.hasNext()) + { + newSelection[i] = (TreePath) newPaths.next(); + i++; + } + } + else + System.arraycopy(paths, 0, newSelection, oldPaths, + validPaths); + selection = newSelection; + insureUniqueness(); + updateLeadIndex(); + resetRowSelection(); + if (changedPaths != null && changedPaths.size() > 0) + notifyPathChange(changedPaths, oldLeadPath); + } + else + leadPath = oldLeadPath; + tmpPaths.clear(); } - insureRowContinuity(); } } @@ -422,36 +571,8 @@ public class DefaultTreeSelectionModel */ public void removeSelectionPath(TreePath path) { - if (isSelectionEmpty()) - return; - - int index = - 1; - if (isPathSelected(path)) - { - for (int i = 0; i < selection.length; i++) - { - if (selection[i].equals(path)) - { - index = i; - break; - } - } - TreePath[] temp = new TreePath[selection.length - 1]; - System.arraycopy(selection, 0, temp, 0, index); - System.arraycopy(selection, index + 1, temp, index, selection.length - - index - 1); - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - - // If the removed path was the lead path, set the lead path to null. - TreePath oldLead = leadPath; - if (path != null && leadPath != null && path.equals(leadPath)) - leadPath = null; - - fireValueChanged(new TreeSelectionEvent(this, path, false, oldLead, - leadPath)); - insureRowContinuity(); - } + if (path != null) + removeSelectionPaths(new TreePath[]{ path }); } /** @@ -462,40 +583,54 @@ public class DefaultTreeSelectionModel */ public void removeSelectionPaths(TreePath[] paths) { - if (isSelectionEmpty()) - return; - if (paths != null) + if (paths != null && selection != null && paths.length > 0) { - int index = - 1; - TreePath v0 = null; - TreePath oldLead = leadPath; - for (int i = 0; i < paths.length; i++) + if (! canPathsBeRemoved(paths)) + clearSelection(); + else { - v0 = paths[i]; - if (isPathSelected(v0)) + Vector pathsToRemove = null; + for (int i = paths.length - 1; i >= 0; i--) { - for (int x = 0; x < selection.length; x++) + if (paths[i] != null && selectedPaths.contains(paths[i])) { - if (selection[i].equals(v0)) - { - index = x; - break; - } - if (leadPath != null && leadPath.equals(v0)) + if (pathsToRemove == null) + pathsToRemove = new Vector(); + selectedPaths.remove(paths[i]); + pathsToRemove.add(new PathPlaceHolder(paths[i], + false)); + } + } + if (pathsToRemove != null) + { + int numRemove = pathsToRemove.size(); + TreePath oldLead = leadPath; + if (numRemove == selection.length) + selection = null; + else + { + selection = new TreePath[selection.length - numRemove]; + Iterator keep = selectedPaths.iterator(); + for (int valid = 0; keep.hasNext(); valid++) + selection[valid] = (TreePath) keep.next(); + } + // Update lead path. + if (leadPath != null && ! selectedPaths.contains(leadPath)) + { + if (selection != null) + leadPath = selection[selection.length - 1]; + else leadPath = null; } - TreePath[] temp = new TreePath[selection.length - 1]; - System.arraycopy(selection, 0, temp, 0, index); - System.arraycopy(selection, index + 1, temp, index, - selection.length - index - 1); - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - - fireValueChanged(new TreeSelectionEvent(this, v0, false, - oldLead, leadPath)); + else if (selection != null) + leadPath = selection[selection.length - 1]; + else + leadPath = null; + updateLeadIndex(); + resetRowSelection(); + notifyPathChange(pathsToRemove, oldLead); } } - insureRowContinuity(); } } @@ -572,19 +707,22 @@ public class DefaultTreeSelectionModel */ public void clearSelection() { - if (! isSelectionEmpty()) + if (selection != null) { - TreeSelectionEvent event = new TreeSelectionEvent( - this, selection, new boolean[selection.length], leadPath, null); + int selectionLength = selection.length; + boolean[] news = new boolean[selectionLength]; + Arrays.fill(news, false); + TreeSelectionEvent event = new TreeSelectionEvent(this, selection, + news, leadPath, + null); leadPath = null; + leadIndex = 0; + leadRow = 0; + selectedPaths.clear(); selection = null; + resetRowSelection(); fireValueChanged(event); } - else - { - leadPath = null; - selection = null; - } } /** @@ -638,7 +776,7 @@ public class DefaultTreeSelectionModel * @return an array of listeners * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } @@ -650,10 +788,43 @@ public class DefaultTreeSelectionModel */ public int[] getSelectionRows() { - if (rowMapper == null) - return null; - else - return rowMapper.getRowsForPaths(selection); + int[] rows = null; + if (rowMapper != null && selection != null) + { + rows = rowMapper.getRowsForPaths(selection); + if (rows != null) + { + // Find invisible rows. + int invisible = 0; + for (int i = rows.length - 1; i >= 0; i--) + { + if (rows[i] == -1) + invisible++; + + } + // Clean up invisible rows. + if (invisible > 0) + { + if (invisible == rows.length) + rows = null; + else + { + int[] newRows = new int[rows.length - invisible]; + int visCount = 0; + for (int i = rows.length - 1; i >= 0; i--) + { + if (rows[i] != -1) + { + newRows[visCount] = rows[i]; + visCount++; + } + } + rows = newRows; + } + } + } + } + return rows; } /** @@ -663,16 +834,7 @@ public class DefaultTreeSelectionModel */ public int getMinSelectionRow() { - if ((rowMapper == null) || (selection == null) || (selection.length == 0)) - return - 1; - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - int minRow = Integer.MAX_VALUE; - for (int index = 0; index < rows.length; index++) - minRow = Math.min(minRow, rows[index]); - return minRow; - } + return listSelectionModel.getMinSelectionIndex(); } /** @@ -682,16 +844,7 @@ public class DefaultTreeSelectionModel */ public int getMaxSelectionRow() { - if ((rowMapper == null) || (selection == null) || (selection.length == 0)) - return - 1; - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - int maxRow = - 1; - for (int index = 0; index < rows.length; index++) - maxRow = Math.max(maxRow, rows[index]); - return maxRow; - } + return listSelectionModel.getMaxSelectionIndex(); } /** @@ -706,29 +859,7 @@ public class DefaultTreeSelectionModel */ public boolean isRowSelected(int row) { - // Return false if nothing is selected. - if (isSelectionEmpty()) - return false; - - RowMapper mapper = getRowMapper(); - - if (mapper instanceof AbstractLayoutCache) - { - // The absolute majority of cases, unless the TreeUI is very - // seriously rewritten - AbstractLayoutCache ama = (AbstractLayoutCache) mapper; - TreePath path = ama.getPathForRow(row); - return isPathSelected(path); - } - else - { - // Generic non optimized implementation. - int[] rows = mapper.getRowsForPaths(selection); - for (int i = 0; i < rows.length; i++) - if (rows[i] == row) - return true; - return false; - } + return listSelectionModel.isSelectedIndex(row); } /** @@ -736,7 +867,32 @@ public class DefaultTreeSelectionModel */ public void resetRowSelection() { - // Nothing to do here. + listSelectionModel.clearSelection(); + if (selection != null && rowMapper != null) + { + int[] rows = rowMapper.getRowsForPaths(selection); + // Update list selection model. + for (int i = 0; i < rows.length; i++) + { + int row = rows[i]; + if (row != -1) + listSelectionModel.addSelectionInterval(row, row); + } + // Update lead selection. + if (leadIndex != -1 && rows != null) + leadRow = rows[leadIndex]; + else if (leadPath != null) + { + TreePath[] tmp = new TreePath[]{ leadPath }; + rows = rowMapper.getRowsForPaths(tmp); + leadRow = rows != null ? rows[0] : -1; + } + else + leadRow = -1; + insureRowContinuity(); + } + else + leadRow = -1; } /** @@ -766,6 +922,8 @@ public class DefaultTreeSelectionModel */ public void addPropertyChangeListener(PropertyChangeListener listener) { + if (changeSupport == null) + changeSupport = new SwingPropertyChangeSupport(this); changeSupport.addPropertyChangeListener(listener); } @@ -776,7 +934,8 @@ public class DefaultTreeSelectionModel */ public void removePropertyChangeListener(PropertyChangeListener listener) { - changeSupport.removePropertyChangeListener(listener); + if (changeSupport != null) + changeSupport.removePropertyChangeListener(listener); } /** @@ -787,7 +946,12 @@ public class DefaultTreeSelectionModel */ public PropertyChangeListener[] getPropertyChangeListeners() { - return changeSupport.getPropertyChangeListeners(); + PropertyChangeListener[] listeners = null; + if (changeSupport != null) + listeners = changeSupport.getPropertyChangeListeners(); + else + listeners = new PropertyChangeListener[0]; + return listeners; } /** @@ -801,67 +965,41 @@ public class DefaultTreeSelectionModel */ protected void insureRowContinuity() { - if (selection == null || selection.length < 2) - return; - else if (selectionMode == CONTIGUOUS_TREE_SELECTION) + if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null + && rowMapper != null) { - if (rowMapper == null) - // This is the best we can do without the row mapper: - selectOne(); - else + int min = listSelectionModel.getMinSelectionIndex(); + if (min != -1) { - int[] rows = rowMapper.getRowsForPaths(selection); - Arrays.sort(rows); - int i; - for (i = 1; i < rows.length; i++) - { - if (rows[i - 1] != rows[i] - 1) - // Break if no longer continuous. - break; - } - - if (i < rows.length) + int max = listSelectionModel.getMaxSelectionIndex(); + for (int i = min; i <= max; i++) { - TreePath[] ns = new TreePath[i]; - for (int j = 0; j < ns.length; j++) - ns[i] = getPath(j); - setSelectionPaths(ns); + if (! listSelectionModel.isSelectedIndex(i)) + { + if (i == min) + clearSelection(); + else + { + TreePath[] newSelection = new TreePath[i - min]; + int[] rows = rowMapper.getRowsForPaths(selection); + for (int j = 0; j < rows.length; j++) + { + if (rows[j] < i) + newSelection[rows[j] - min] = selection[j]; + } + setSelectionPaths(newSelection); + break; + } + } } } } - else if (selectionMode == SINGLE_TREE_SELECTION) - selectOne(); + else if (selectionMode == SINGLE_TREE_SELECTION && selection != null + && selection.length > 1) + setSelectionPath(selection[0]); } /** - * Keep only one (normally last or leading) path in the selection. - */ - private void selectOne() - { - if (leadIndex > 0 && leadIndex < selection.length) - setSelectionPath(selection[leadIndex]); - else - setSelectionPath(selection[selection.length - 1]); - } - - /** - * Get path for the given row that must be in the current selection. - */ - private TreePath getPath(int row) - { - if (rowMapper instanceof AbstractLayoutCache) - return ((AbstractLayoutCache) rowMapper).getPathForRow(row); - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - for (int i = 0; i < rows.length; i++) - if (rows[i] == row) - return selection[i]; - } - throw new InternalError(row + " not in selection"); - } - - /** * Returns <code>true</code> if the paths are contiguous (take subsequent * rows in the diplayed tree view. The method returns <code>true</code> if * we have no RowMapper assigned. @@ -875,16 +1013,36 @@ public class DefaultTreeSelectionModel if (rowMapper == null || paths.length < 2) return true; - int[] rows = rowMapper.getRowsForPaths(paths); - - // The patches may not be sorted. - Arrays.sort(rows); - - for (int i = 1; i < rows.length; i++) + int length = paths.length; + TreePath[] tmp = new TreePath[1]; + tmp[0] = paths[0]; + int min = rowMapper.getRowsForPaths(tmp)[0]; + BitSet selected = new BitSet(); + int valid = 0; + for (int i = 0; i < length; i++) { - if (rows[i - 1] != rows[i] - 1) - return false; + if (paths[i] != null) + { + tmp[0] = paths[i]; + int[] rows = rowMapper.getRowsForPaths(tmp); + if (rows == null) + return false; // No row mapping yet, can't be selected. + int row = rows[0]; + if (row == -1 || row < (min - length) || row > (min + length)) + return false; // Not contiguous. + min = Math.min(min, row); + if (! selected.get(row)) + { + selected.set(row); + valid++; + } + + } } + int max = valid + min; + for (int i = min; i < max; i++) + if (! selected.get(i)) + return false; // Not contiguous. return true; } @@ -904,34 +1062,51 @@ public class DefaultTreeSelectionModel */ protected boolean canPathsBeAdded(TreePath[] paths) { - if (rowMapper == null || isSelectionEmpty() - || selectionMode == DISCONTIGUOUS_TREE_SELECTION) + if (paths == null || paths.length == 0 || rowMapper == null + || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION) return true; - - TreePath [] all = new TreePath[paths.length + selection.length]; - System.arraycopy(paths, 0, all, 0, paths.length); - System.arraycopy(selection, 0, all, paths.length, selection.length); - return arePathsContiguous(all); + BitSet selected = new BitSet(); + int min = listSelectionModel.getMinSelectionIndex(); + int max = listSelectionModel.getMaxSelectionIndex(); + TreePath[] tmp = new TreePath[1]; + if (min != -1) + { + // Set the bitmask of selected elements. + for (int i = min; i <= max; i++) + selected.set(i); + } + else + { + tmp[0] = paths[0]; + min = rowMapper.getRowsForPaths(tmp)[0]; + max = min; + } + // Mark new paths as selected. + for (int i = paths.length - 1; i >= 0; i--) + { + if (paths[i] != null) + { + tmp[0] = paths[i]; + int[] rows = rowMapper.getRowsForPaths(tmp); + if (rows == null) + return false; // Now row mapping yet, can't be selected. + int row = rows[0]; + if (row == -1) + return false; // Now row mapping yet, can't be selected. + min = Math.min(min, row); + max = Math.max(max, row); + selected.set(row); + } + } + // Now look if the new selection would be contiguous. + for (int i = min; i <= max; i++) + if (! selected.get(i)) + return false; + return true; } /** - * Checks if the single path can be added to selection. - */ - private boolean canPathBeAdded(TreePath path) - { - if (rowMapper == null || isSelectionEmpty() - || selectionMode == DISCONTIGUOUS_TREE_SELECTION) - return true; - - TreePath[] all = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, all, 0, selection.length); - all[all.length - 1] = path; - - return arePathsContiguous(all); - } - - /** * Checks if the paths can be removed without breaking the continuity of the * selection according to selectionMode. * @@ -966,20 +1141,23 @@ public class DefaultTreeSelectionModel * method will call listeners if invoked, but it is not called from the * implementation of this class. * - * @param vPathes the vector of the changed patches + * @param vPaths the vector of the changed patches * @param oldLeadSelection the old selection index */ - protected void notifyPathChange(Vector vPathes, TreePath oldLeadSelection) + protected void notifyPathChange(Vector vPaths, TreePath oldLeadSelection) { - TreePath[] pathes = new TreePath[vPathes.size()]; - for (int i = 0; i < pathes.length; i++) - pathes[i] = (TreePath) vPathes.get(i); - boolean[] news = new boolean[pathes.length]; - for (int i = 0; i < news.length; i++) - news[i] = isPathSelected(pathes[i]); + int numChangedPaths = vPaths.size(); + boolean[] news = new boolean[numChangedPaths]; + TreePath[] paths = new TreePath[numChangedPaths]; + for (int i = 0; i < numChangedPaths; i++) + { + PathPlaceHolder p = (PathPlaceHolder) vPaths.get(i); + news[i] = p.isNew; + paths[i] = p.path; + } - TreeSelectionEvent event = new TreeSelectionEvent(this, pathes, news, + TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news, oldLeadSelection, leadPath); fireValueChanged(event); @@ -991,22 +1169,20 @@ public class DefaultTreeSelectionModel */ protected void updateLeadIndex() { - if (isSelectionEmpty()) + leadIndex = -1; + if (leadPath != null) { - leadRow = leadIndex = - 1; - } - else - { - leadRow = getRow(leadPath); - for (int i = 0; i < selection.length; i++) + leadRow = -1; + if (selection == null) + leadPath = null; + else { - if (selection[i].equals(leadPath)) + for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--) { - leadIndex = i; - break; + if (selection[i] == leadPath) + leadIndex = i; } } - leadIndex = leadRow; } } diff --git a/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java index a699a6c9f21..dff9298e8f5 100644 --- a/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java @@ -480,7 +480,7 @@ public class FixedHeightLayoutCache * @param parentPath the parent path * @return the enumeration over pathes */ - public Enumeration getVisiblePathsFrom(TreePath parentPath) + public Enumeration<TreePath> getVisiblePathsFrom(TreePath parentPath) { if (dirty) update(); diff --git a/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java index 0a787f7ca8c..8c70c13afd2 100644 --- a/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java @@ -40,6 +40,7 @@ package javax.swing.tree; import gnu.javax.swing.tree.GnuPath; import java.awt.Rectangle; +import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; @@ -60,8 +61,11 @@ import javax.swing.event.TreeModelEvent; * @author Audrius Meskauskas */ public class VariableHeightLayoutCache - extends AbstractLayoutCache + extends AbstractLayoutCache { + + private static final Rectangle RECT_CACHE = new Rectangle(); + /** * The cached node record. */ @@ -73,8 +77,8 @@ public class VariableHeightLayoutCache depth = aDepth; parent = aParent; node = aNode; - - isExpanded = expanded.contains(aNode); + isExpanded = expanded.contains(aNode); + bounds = new Rectangle(0, -1, 0, 0); } /** @@ -102,7 +106,7 @@ public class VariableHeightLayoutCache * Using this field saves one hashtable access operation. */ final boolean isExpanded; - + /** * The cached bounds of the tree row. */ @@ -160,11 +164,6 @@ public class VariableHeightLayoutCache */ Rectangle getBounds() { - // This method may be called in the context when the tree rectangle is - // not known. To work around this, it is assumed near infinitely large. - if (bounds == null) - bounds = getNodeDimensions(node, row, depth, isExpanded, - new Rectangle()); return bounds; } } @@ -182,7 +181,7 @@ public class VariableHeightLayoutCache /** * Maps row numbers to nodes. */ - Hashtable row2node = new Hashtable(); + ArrayList row2node = new ArrayList(); /** * If true, the row map must be recomputed before using. @@ -236,45 +235,54 @@ public class VariableHeightLayoutCache return; Object root = treeModel.getRoot(); - - if (rootVisible) - { - countRows(root, null, 0); - } - else - { - int sc = treeModel.getChildCount(root); - for (int i = 0; i < sc; i++) - { - Object child = treeModel.getChild(root, i); - countRows(child, root, 0); - } - } + countRows(root, null, 0, 0); dirty = false; } /** * Recursively counts all rows in the tree. */ - private final void countRows(Object node, Object parent, int depth) + private final int countRows(Object node, Object parent, int depth, int y) { - Integer n = new Integer(row2node.size()); - row2node.put(n, node); - - NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent); + boolean visible = node != treeModel.getRoot() || rootVisible; + int row = row2node.size(); + if (visible) + { + row2node.add(node); + } + NodeRecord nr = new NodeRecord(row, depth, node, parent); + NodeDimensions d = getNodeDimensions(); + Rectangle r = RECT_CACHE; + if (d != null) + r = d.getNodeDimensions(node, row, depth, nr.isExpanded, r); + else + r.setBounds(0, 0, 0, 0); + + if (! visible) + r.y = -1; + else + r.y = Math.max(0, y); + + if (isFixedRowHeight()) + r.height = getRowHeight(); + + nr.bounds.setBounds(r); nodes.put(node, nr); - - // For expanded nodes + + if (visible) + y += r.height; + + int sc = treeModel.getChildCount(node); + int deeper = depth + 1; if (expanded.contains(node)) { - int sc = treeModel.getChildCount(node); - int deeper = depth + 1; for (int i = 0; i < sc; i++) { Object child = treeModel.getChild(node, i); - countRows(child, node, deeper); + y = countRows(child, node, deeper, y); } } + return y; } /** @@ -309,10 +317,14 @@ public class VariableHeightLayoutCache public void setExpandedState(TreePath path, boolean isExpanded) { if (isExpanded) - expanded.add(path.getLastPathComponent()); + { + int length = path.getPathCount(); + for (int i = 0; i < length; i++) + expanded.add(path.getPathComponent(i)); + } else expanded.remove(path.getLastPathComponent()); - + dirty = true; } @@ -339,25 +351,21 @@ public class VariableHeightLayoutCache return null; if (dirty) update(); + Object last = path.getLastPathComponent(); + Rectangle result = null; NodeRecord r = (NodeRecord) nodes.get(last); - if (r == null) - // This node is not visible. - { - rect.x = rect.y = rect.width = rect.height = 0; - } - else + if (r != null) { - if (r.bounds == null) - { - Rectangle dim = getNodeDimensions(last, r.row, r.depth, - r.isExpanded, rect); - r.bounds = dim; - } - - rect.setRect(r.bounds); + // The RI allows null arguments for rect, in which case a new Rectangle + // is created. + result = rect; + if (result == null) + result = new Rectangle(r.bounds); + else + result.setBounds(r.bounds); } - return rect; + return result; } /** @@ -370,14 +378,17 @@ public class VariableHeightLayoutCache { if (dirty) update(); - Object last = row2node.get(new Integer(row)); - if (last == null) - return null; - else + + TreePath path = null; + // Search row in the nodes map. TODO: This is inefficient, optimize this. + Enumeration nodesEnum = nodes.elements(); + while (nodesEnum.hasMoreElements() && path == null) { - NodeRecord r = (NodeRecord) nodes.get(last); - return r.getPath(); + NodeRecord record = (NodeRecord) nodesEnum.nextElement(); + if (record.row == row) + path = record.getPath(); } + return path; } /** @@ -390,7 +401,9 @@ public class VariableHeightLayoutCache { if (path == null) return -1; - if (dirty) update(); + + if (dirty) + update(); NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent()); if (r == null) @@ -451,8 +464,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; } @@ -468,7 +481,7 @@ public class VariableHeightLayoutCache */ public int getVisibleChildCount(TreePath path) { - if (isExpanded(path)) + if (! isExpanded(path) || treeModel == null) return 0; else return treeModel.getChildCount(path.getLastPathComponent()); @@ -481,7 +494,7 @@ public class VariableHeightLayoutCache * @param parentPath the parent path * @return the enumeration over pathes */ - public Enumeration getVisiblePathsFrom(TreePath parentPath) + public Enumeration<TreePath> getVisiblePathsFrom(TreePath parentPath) { if (dirty) update(); @@ -493,7 +506,7 @@ public class VariableHeightLayoutCache { node = parentPath.getPathComponent(i); nr = (NodeRecord) nodes.get(node); - if (nr.row >= 0) + if (nr != null && nr.row >= 0) p.add(node); } return p.elements(); @@ -558,15 +571,11 @@ public class VariableHeightLayoutCache public void setModel(TreeModel newModel) { treeModel = newModel; - // We need to clear the table and update the layout, - // so that we don't end up with wrong data in the tables. - expanded.clear(); - update(); + dirty = true; if (treeModel != null) { // The root node is expanded by default. expanded.add(treeModel.getRoot()); - dirty = true; } } @@ -590,15 +599,14 @@ public class VariableHeightLayoutCache { if (dirty) update(); - totalHeight = 0; - Enumeration en = nodes.elements(); - while (en.hasMoreElements()) + int height = 0; + int rowCount = getRowCount(); + if (rowCount > 0) { - NodeRecord nr = (NodeRecord) en.nextElement(); - Rectangle r = nr.getBounds(); - totalHeight += r.height; + NodeRecord last = (NodeRecord) nodes.get(row2node.get(rowCount - 1)); + height = last.bounds.y + last.bounds.height; } - return totalHeight; + return height; } /** @@ -614,10 +622,36 @@ public class VariableHeightLayoutCache while (en.hasMoreElements()) { NodeRecord nr = (NodeRecord) en.nextElement(); - Rectangle r = nr.getBounds(); - if (r.x + r.width > maximalWidth) - maximalWidth = r.x + r.width; + if (nr != null) + { + Rectangle r = nr.getBounds(); + int width = r.x + r.width; + if (width > maximalWidth) + maximalWidth = width; + } } return maximalWidth; } + + /** + * Sets the node dimensions and invalidates the cached layout. + * + * @param dim the dimensions to set + */ + public void setNodeDimensions(NodeDimensions dim) + { + super.setNodeDimensions(dim); + dirty = true; + } + + /** + * Sets the row height and marks the layout as invalid. + * + * @param height the row height to set + */ + public void setRowHeight(int height) + { + super.setRowHeight(height); + dirty = true; + } } diff --git a/libjava/classpath/javax/swing/undo/CompoundEdit.java b/libjava/classpath/javax/swing/undo/CompoundEdit.java index e1cfbb619b3..fbff2a26418 100644 --- a/libjava/classpath/javax/swing/undo/CompoundEdit.java +++ b/libjava/classpath/javax/swing/undo/CompoundEdit.java @@ -1,5 +1,5 @@ /* CompoundEdit.java -- Combines multiple UndoableEdits. - Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -75,7 +75,7 @@ public class CompoundEdit * The <code>UndoableEdit</code>s being combined into a compound * editing action. */ - protected Vector edits; + protected Vector<UndoableEdit> edits; /** @@ -92,7 +92,7 @@ public class CompoundEdit */ public CompoundEdit() { - edits = new Vector(); + edits = new Vector<UndoableEdit>(); inProgress = true; } @@ -118,7 +118,7 @@ public class CompoundEdit super.undo(); for (int i = edits.size() - 1; i >= 0; i--) - ((UndoableEdit) edits.elementAt(i)).undo(); + edits.elementAt(i).undo(); } @@ -143,7 +143,7 @@ public class CompoundEdit super.redo(); for (int i = 0; i < edits.size(); i++) - ((UndoableEdit) edits.elementAt(i)).redo(); + edits.elementAt(i).redo(); } @@ -156,7 +156,7 @@ public class CompoundEdit if (edits.size() == 0) return null; else - return (UndoableEdit) edits.elementAt(edits.size() - 1); + return edits.elementAt(edits.size() - 1); } @@ -172,7 +172,7 @@ public class CompoundEdit public void die() { for (int i = edits.size() - 1; i >= 0; i--) - ((UndoableEdit) edits.elementAt(i)).die(); + edits.elementAt(i).die(); super.die(); } @@ -316,7 +316,7 @@ public class CompoundEdit public boolean isSignificant() { for (int i = edits.size() - 1; i >= 0; i--) - if (((UndoableEdit) edits.elementAt(i)).isSignificant()) + if (edits.elementAt(i).isSignificant()) return true; return false; diff --git a/libjava/classpath/javax/swing/undo/StateEdit.java b/libjava/classpath/javax/swing/undo/StateEdit.java index 326abea1f4e..91fc88faa60 100644 --- a/libjava/classpath/javax/swing/undo/StateEdit.java +++ b/libjava/classpath/javax/swing/undo/StateEdit.java @@ -121,14 +121,14 @@ public class StateEdit * The state of <code>object</code> at the time of constructing * this <code>StateEdit</code>. */ - protected Hashtable preState; + protected Hashtable<Object, Object> preState; /** * The state of <code>object</code> at the time when {@link #end()} * was called. */ - protected Hashtable postState; + protected Hashtable<Object, Object> postState; /** diff --git a/libjava/classpath/javax/swing/undo/StateEditable.java b/libjava/classpath/javax/swing/undo/StateEditable.java index 459025be7da..7e6cc97856f 100644 --- a/libjava/classpath/javax/swing/undo/StateEditable.java +++ b/libjava/classpath/javax/swing/undo/StateEditable.java @@ -100,7 +100,7 @@ public interface StateEditable * @param state a hash table containing the relevant state * information. */ - void restoreState(Hashtable state); + void restoreState(Hashtable<?, ?> state); /** @@ -110,5 +110,5 @@ public interface StateEditable * @param state a hash table for storing relevant state * information. */ - void storeState(Hashtable state); + void storeState(Hashtable<Object, Object> state); } diff --git a/libjava/classpath/javax/swing/undo/UndoableEditSupport.java b/libjava/classpath/javax/swing/undo/UndoableEditSupport.java index 6d7bbea0728..b5a93341954 100644 --- a/libjava/classpath/javax/swing/undo/UndoableEditSupport.java +++ b/libjava/classpath/javax/swing/undo/UndoableEditSupport.java @@ -69,7 +69,8 @@ public class UndoableEditSupport /** * The currently registered listeners. */ - protected Vector listeners = new Vector(); + protected Vector<UndoableEditListener> listeners = + new Vector<UndoableEditListener>(); /** @@ -148,7 +149,7 @@ public class UndoableEditSupport public synchronized UndoableEditListener[] getUndoableEditListeners() { UndoableEditListener[] result = new UndoableEditListener[listeners.size()]; - return (UndoableEditListener[]) listeners.toArray(result); + return listeners.toArray(result); } |