From 97b8365cafc3a344a22d3980b8ed885f5c6d8357 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Tue, 9 Jan 2007 19:58:05 +0000 Subject: Merged gcj-eclipse branch to trunk. From-SVN: r120621 --- .../javax/swing/text/AbstractDocument.java | 913 +++++++-- .../classpath/javax/swing/text/AttributeSet.java | 2 +- libjava/classpath/javax/swing/text/BoxView.java | 484 +++-- .../classpath/javax/swing/text/ComponentView.java | 336 +++- .../classpath/javax/swing/text/CompositeView.java | 195 +- .../classpath/javax/swing/text/DefaultCaret.java | 15 +- .../javax/swing/text/DefaultEditorKit.java | 365 ++-- .../javax/swing/text/DefaultFormatter.java | 6 +- .../javax/swing/text/DefaultHighlighter.java | 389 ++-- .../javax/swing/text/DefaultStyledDocument.java | 2036 ++++++++++---------- .../javax/swing/text/ElementIterator.java | 203 +- libjava/classpath/javax/swing/text/FieldView.java | 4 +- libjava/classpath/javax/swing/text/FlowView.java | 419 +++- libjava/classpath/javax/swing/text/GapContent.java | 723 ++++--- libjava/classpath/javax/swing/text/GlyphView.java | 620 ++++-- .../javax/swing/text/InternationalFormatter.java | 2 +- .../classpath/javax/swing/text/JTextComponent.java | 278 ++- libjava/classpath/javax/swing/text/LabelView.java | 78 +- .../classpath/javax/swing/text/MaskFormatter.java | 377 ++-- .../javax/swing/text/MutableAttributeSet.java | 2 +- .../classpath/javax/swing/text/ParagraphView.java | 107 +- libjava/classpath/javax/swing/text/PlainView.java | 150 +- libjava/classpath/javax/swing/text/Position.java | 4 +- .../javax/swing/text/SimpleAttributeSet.java | 18 +- .../classpath/javax/swing/text/StringContent.java | 304 ++- .../classpath/javax/swing/text/StyleConstants.java | 11 + .../classpath/javax/swing/text/StyleContext.java | 600 +++--- .../javax/swing/text/StyledEditorKit.java | 46 +- libjava/classpath/javax/swing/text/TextAction.java | 51 +- libjava/classpath/javax/swing/text/Utilities.java | 189 +- libjava/classpath/javax/swing/text/View.java | 104 +- .../javax/swing/text/WrappedPlainView.java | 199 +- libjava/classpath/javax/swing/text/ZoneView.java | 442 +++++ .../classpath/javax/swing/text/html/BRView.java | 5 +- .../classpath/javax/swing/text/html/BlockView.java | 482 ++++- libjava/classpath/javax/swing/text/html/CSS.java | 274 +++ .../classpath/javax/swing/text/html/CSSBorder.java | 421 ++++ .../javax/swing/text/html/FormSubmitEvent.java | 123 ++ .../classpath/javax/swing/text/html/FormView.java | 649 ++++++- .../javax/swing/text/html/FrameSetView.java | 274 +++ .../classpath/javax/swing/text/html/FrameView.java | 233 +++ libjava/classpath/javax/swing/text/html/HTML.java | 22 +- .../javax/swing/text/html/HTMLDocument.java | 931 +++++++-- .../javax/swing/text/html/HTMLEditorKit.java | 578 ++++-- .../javax/swing/text/html/HTMLWriter.java | 1084 +++++++++++ .../classpath/javax/swing/text/html/ImageView.java | 403 ++-- .../javax/swing/text/html/InlineView.java | 151 +- .../classpath/javax/swing/text/html/ListView.java | 3 - .../javax/swing/text/html/MultiAttributeSet.java | 213 ++ .../javax/swing/text/html/MultiStyle.java | 136 ++ .../classpath/javax/swing/text/html/Option.java | 12 +- .../javax/swing/text/html/ParagraphView.java | 131 +- .../javax/swing/text/html/ResetableModel.java | 50 + .../swing/text/html/ResetablePlainDocument.java | 82 + .../text/html/ResetableToggleButtonModel.java | 71 + .../javax/swing/text/html/SelectComboBoxModel.java | 84 + .../javax/swing/text/html/SelectListModel.java | 106 + .../javax/swing/text/html/StyleSheet.java | 1124 ++++++++--- .../classpath/javax/swing/text/html/TableView.java | 942 ++++++++- .../javax/swing/text/html/ViewAttributeSet.java | 163 ++ .../swing/text/html/parser/AttributeList.java | 6 +- .../javax/swing/text/html/parser/ContentModel.java | 6 +- .../javax/swing/text/html/parser/DTD.java | 17 +- .../swing/text/html/parser/DocumentParser.java | 4 +- .../swing/text/html/parser/ParserDelegator.java | 4 +- 65 files changed, 14219 insertions(+), 4237 deletions(-) create mode 100644 libjava/classpath/javax/swing/text/ZoneView.java create mode 100644 libjava/classpath/javax/swing/text/html/CSSBorder.java create mode 100644 libjava/classpath/javax/swing/text/html/FormSubmitEvent.java create mode 100644 libjava/classpath/javax/swing/text/html/FrameSetView.java create mode 100644 libjava/classpath/javax/swing/text/html/FrameView.java create mode 100644 libjava/classpath/javax/swing/text/html/HTMLWriter.java create mode 100644 libjava/classpath/javax/swing/text/html/MultiAttributeSet.java create mode 100644 libjava/classpath/javax/swing/text/html/MultiStyle.java create mode 100644 libjava/classpath/javax/swing/text/html/ResetableModel.java create mode 100644 libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java create mode 100644 libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java create mode 100644 libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java create mode 100644 libjava/classpath/javax/swing/text/html/SelectListModel.java create mode 100644 libjava/classpath/javax/swing/text/html/ViewAttributeSet.java (limited to 'libjava/classpath/javax/swing/text') diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java index eb46a8c..eead8de 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 Document. */ 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 AbstractDocument 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 offset 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 Document * if there is one, otherwise null */ - 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 Document */ - public Dictionary getDocumentProperties() + public Dictionary 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[] getListeners(Class 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 Document. + * 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 Document. + */ + 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 * Document, 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 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 old minus the attributes in * attributes */ - 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 BranchElement 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 Element to their change records. + * Maps Element 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 DefaultDocumentEvent. @@ -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 01d148c..2d39881 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 * AttributeSet */ - Enumeration getAttributeNames(); + Enumeration getAttributeNames(); /** * Returns the resolving parent of this AttributeSet. diff --git a/libjava/classpath/javax/swing/text/BoxView.java b/libjava/classpath/javax/swing/text/BoxView.java index 27e3c0f..0754d9b 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 BoxView for the given * Element 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 Element that is associated with this * View. * @@ -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 SizeRequirements 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 BoxView along - * the specified axis. + * Calculates the baseline layout of the children of this + * BoxView 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 a7d237a..8de4de6 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 ComponentView for the specified * Element. * @@ -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 6f487b8..570fc95 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 CompositeView. */ - View[] children; + private View[] children; + + /** + * The number of child views. + */ + private int numChildren; /** * The allocation of this View 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 CompositeView. This is initialized * in {@link #setInsets}. */ - Insets insets; + private short top; + private short bottom; + private short left; + private short right; /** * Creates a new CompositeView 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 CompositeView. * - * @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 84f47f1..c4c2580 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[] getListeners(Class 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 8602e69..aa69dec 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 e42b169..bf7c02a 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 59f7731..69563e4 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 bd21e55..3156ca6 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 ElementBuffer 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 Element 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 offset 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 DefaultStyledDocument. */ 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 a6a5ff6..112d55e 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 el. + * + * @param el the base element + * + * @returnthe deepest leaf of the element el */ - 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 f41f901..0a078e5 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 3de95ed..c2bed39 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 view and all of its + * decendents to the parent (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 FlowView. */ - 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 FlowView for the given * Element and axis. * @@ -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 760e396..08a318d 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 GapContent. */ - 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 Positions that are in the range - * specified by offset and length within - * the buffer array by increment - * - * @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 Position that have an offset of 0, * to also have an array index of 0. 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 o in list * l. 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 * Collections.binarySearch(). * - * @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 d505274..1e418d2 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 GlyphPainter used in GlyphView. */ 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 GlyphView for the given Element. @@ -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 8db435c..d6f2359 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 6da84bf..68ba1f4 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 03279c4..7cfeae8 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 GlyphView for the given Element. * * @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 d12b9ea..581cceb 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 value 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 convert 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 value 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 - * i characters then the character at position i - * 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 3728b9c..5dd2406 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 names is null * or contains any null 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 c485786..fb4ac65 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 48fe37c..e048d5f 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 bb1449e..d02eb83 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 8684ef8..701fa8a 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 names is null * or contains any null 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 8014dc3..4a3f9d7 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 initialLength * 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 String 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 c7906b8..4e5005c 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 63df3df..4dded0d 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[] getListeners(Class 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 c4eef44..5686943 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 144166e..49c49cb 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 actions = new HashMap(); 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 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 36361f4..d49d806 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 Utilities 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 55a63f6..c63ddbc 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 a6c369a..00e12b1 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 0000000..6cabc6c --- /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 num <= 0 + * + * @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 true when the specified zone is loaded, + * false otherwise. The default implementation checks if + * the zone view has child elements. + * + * @param zone the zone view to check + * + * @return true when the specified zone is loaded, + * false 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 + * pos. + * + * 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 + * pos + */ + 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 pos 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 5521fed..7d0d516 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 6274e7b..b05c983 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 true when the CSS span has been set, + * false 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 r requirements according to + * min. + * + * @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 c248e75..77f94a6 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 0000000..fff6b01 --- /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 0000000..bc7c36f --- /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 d540210..ef362bd 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, null + * 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 <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 FormView. * * @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 null 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 0000000..e3252d7 --- /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 0000000..cd4e44a --- /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 2c908f6..93c05da 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 tagMap; + private static Map 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(); 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(); 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 0bfc338..f3d3ce3 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, null + * 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("'); + 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 parseBuffer = new Vector(); + + /** + * The parse stack. It holds the current element tree path. + */ + private Stack parseStack = new Stack(); + /** * 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 a. - 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
 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 true when this document is inside a frame,
+   * false otherwise.
+   *
+   * @return true when this document is inside a frame,
+   *         false otherwise
+   */
+  boolean isFrameDocument()
+  {
+    return frameDocument;
+  }
+
+  /**
+   * Set true when this document is inside a frame,
+   * false otherwise.
+   *
+   * @param frameDoc true when this document is inside a frame,
+   *                 false 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 5d77be8..0ede1c7 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 null 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 addTag.
+       *
+       * @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 true when the html has been inserted successfully,
+       *         false 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",
+                             "
", + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableRow", + "
", + HTML.Tag.TABLE, HTML.Tag.TR, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableCell", + "
", + HTML.Tag.TR, HTML.Tag.TD, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertUnorderedList", + "
", + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertUnorderedListItem", + "
", + HTML.Tag.UL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertOrderedList", + "
", + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertOrderedListItem", + "
", + HTML.Tag.OL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertPre", + "
", 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 true when forms should be automatically submitted
+   * by the editor kit. Set this to false 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 true.
+   *
+   * @return true when forms should be automatically submitted
+   *         by the editor kit, false 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 true when the editor kit should handle form
+   *        submission, false 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 0000000..44119c7
--- /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 Writer to write output to
+   * @param doc the javax.swing.text.html.HTMLDocument
+   *        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("");
+      } // 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 javax.swing.text.html.HTML.Tag,
+   * javax.swing.text.StyleConstants or
+   * javax.swing.text.html.HTML.Attribute.ENDTAG.
+   *
+   * @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("");
+      } // 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 true if it is a block tag
+   *         false 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("");
+
+    Document tempDocument = 
+      (Document) attrSet.getAttribute(StyleConstants.ModelAttribute);
+
+    writeRaw(tempDocument.getText(0, tempDocument.getLength()));
+    indent();
+    writeRaw("");
+
+  } // 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("");
+    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("");
+
+  } // 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("");
+
+    writeContent(option.getLabel());
+
+    writeRaw("");
+    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("");
+    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("");
+
+  } // protected void comment(Element paramElem)
+    //   throws IOException, BadLocationException
+
+  /**
+   * Determines if element is a synthesized
+   * javax.swing.text.Element or not.
+   *
+   * @param element the element to test
+   *
+   * @return true if it is a synthesized element,
+   *         false 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
+   * javax.swing.text.StyleConstants.NameAttribute
+   * matches tag or not.
+   *
+   * @param attrSet the javax.swing.text.AttributeSet of
+   *        element to be matched
+   * @param tag the HTML.Tag to match
+   *
+   * @return true if it matches,
+   *         false 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 javax.swing.text.AttributeSet 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("");
+            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("");
+            else
+              {
+                indent();
+                writeRaw("");
+
+                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("");
+              else
+                {
+                  indent();
+                  writeRaw("");
+
+                  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 84b0210..bf906e4 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 77ec86e..58edc73 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 c07d359..3e809bb 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 0000000..0f11450
--- /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 0000000..3937bff
--- /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 1def51b..18d5c2b 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 2339f4e..d149627 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 0000000..17f65b9
--- /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 0000000..6177f9b
--- /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 0000000..619c24e
--- /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 0000000..9997464
--- /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 0000000..23bfaa1
--- /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 d92abde..01f19fd 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 c2edc8c..f87d7b3 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 0000000..25db89f
--- /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 5bca0bf..d48266d 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 70e9c2a..d5c4418 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 16bc5b0..ae3c184 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 062606d..f717d69 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 70636d9..cdd339b 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);
-- 
cgit v1.1