diff options
Diffstat (limited to 'libjava/classpath/javax/swing/text')
26 files changed, 1991 insertions, 990 deletions
diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java index 1ef81732fed..eb46a8c42f6 100644 --- a/libjava/classpath/javax/swing/text/AbstractDocument.java +++ b/libjava/classpath/javax/swing/text/AbstractDocument.java @@ -147,14 +147,19 @@ public abstract class AbstractDocument implements Document, Serializable /** * A condition variable that readers and writers wait on. */ - Object documentCV = new Object(); + private Object documentCV = new Object(); /** An instance of a DocumentFilter.FilterBypass which allows calling * the insert, remove and replace method without checking for an installed * document filter. */ - DocumentFilter.FilterBypass bypass; - + private DocumentFilter.FilterBypass bypass; + + /** + * The bidi root element. + */ + private Element bidiRoot; + /** * Creates a new <code>AbstractDocument</code> with the specified * {@link Content} model. @@ -185,6 +190,13 @@ public abstract class AbstractDocument implements Document, Serializable { content = doc; context = ctx; + + // FIXME: This is determined using a Mauve test. Make the document + // actually use this. + putProperty("i18n", Boolean.FALSE); + + // FIXME: Fully implement bidi. + bidiRoot = new BranchElement(null, null); } /** Returns the DocumentFilter.FilterBypass instance for this @@ -360,7 +372,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public Element getBidiRootElement() { - return null; + return bidiRoot; } /** @@ -477,8 +489,9 @@ public abstract class AbstractDocument implements Document, Serializable */ public Element[] getRootElements() { - Element[] elements = new Element[1]; + Element[] elements = new Element[2]; elements[0] = getDefaultRootElement(); + elements[1] = getBidiRootElement(); return elements; } @@ -739,29 +752,36 @@ public abstract class AbstractDocument implements Document, Serializable void removeImpl(int offset, int length) throws BadLocationException { - // Prevent some unneccessary method invocation (observed in the RI). - if (length <= 0) - return; - - DefaultDocumentEvent event = - new DefaultDocumentEvent(offset, length, - DocumentEvent.EventType.REMOVE); - - try + // The RI silently ignores all requests that have a negative length. + // Don't ask my why, but that's how it is. + if (length > 0) { - writeLock(); + if (offset < 0 || offset > getLength()) + throw new BadLocationException("Invalid remove position", offset); + + if (offset + length > getLength()) + throw new BadLocationException("Invalid remove length", offset); + + DefaultDocumentEvent event = + 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); + // The order of the operations below is critical! + removeUpdate(event); + UndoableEdit temp = content.remove(offset, length); - postRemoveUpdate(event); - fireRemoveUpdate(event); + postRemoveUpdate(event); + fireRemoveUpdate(event); + } + finally + { + writeUnlock(); + } } - finally - { - writeUnlock(); - } } /** @@ -1674,20 +1694,15 @@ public abstract class AbstractDocument implements Document, Serializable /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = -6037216547466333183L; - /** The child elements of this BranchElement. */ - private Element[] children = new Element[0]; - /** - * The cached startOffset value. This is used in the case when a - * BranchElement (temporarily) has no child elements. + * The child elements of this BranchElement. */ - private int startOffset; + private Element[] children;; /** - * The cached endOffset value. This is used in the case when a - * BranchElement (temporarily) has no child elements. + * The number of children in the branch element. */ - private int endOffset; + private int numChildren; /** * Creates a new <code>BranchElement</code> with the specified @@ -1700,8 +1715,8 @@ public abstract class AbstractDocument implements Document, Serializable public BranchElement(Element parent, AttributeSet attributes) { super(parent, attributes); - startOffset = -1; - endOffset = -1; + children = new Element[1]; + numChildren = 0; } /** @@ -1716,8 +1731,8 @@ public abstract class AbstractDocument implements Document, Serializable Vector tmp = new Vector(); - for (int index = 0; index < children.length; ++index) - tmp.add(children[index]); + for (int index = 0; index < numChildren; ++index) + tmp.add(children[index]); return tmp.elements(); } @@ -1743,8 +1758,8 @@ public abstract class AbstractDocument implements Document, Serializable */ public Element getElement(int index) { - if (index < 0 || index >= children.length) - return null; + if (index < 0 || index >= numChildren) + return null; return children[index]; } @@ -1756,7 +1771,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getElementCount() { - return children.length; + return numChildren; } /** @@ -1777,7 +1792,7 @@ public abstract class AbstractDocument implements Document, Serializable // XXX: There is surely a better algorithm // as beginning from first element each time. - for (int index = 0; index < children.length - 1; ++index) + for (int index = 0; index < numChildren - 1; ++index) { Element elem = children[index]; @@ -1814,15 +1829,11 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getEndOffset() { - if (children.length == 0) - { - if (endOffset == -1) - throw new NullPointerException("BranchElement has no children."); - } - else - endOffset = children[children.length - 1].getEndOffset(); - - return endOffset; + // This might accss one cached element or trigger an NPE for + // numChildren == 0. This is checked by a Mauve test. + Element child = numChildren > 0 ? children[numChildren - 1] + : children[0]; + return child.getEndOffset(); } /** @@ -1848,15 +1859,13 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getStartOffset() { - if (children.length == 0) - { - if (startOffset == -1) - throw new NullPointerException("BranchElement has no children."); - } - else - startOffset = children[0].getStartOffset(); - - return startOffset; + // Do not explicitly throw an NPE here. If the first element is null + // then the NPE gets thrown anyway. If it isn't, then it either + // holds a real value (for numChildren > 0) or a cached value + // (for numChildren == 0) as we don't fully remove elements in replace() + // when removing single elements. + // This is checked by a Mauve test. + return children[0].getStartOffset(); } /** @@ -1884,7 +1893,7 @@ public abstract class AbstractDocument implements Document, Serializable { // XXX: There is surely a better algorithm // as beginning from first element each time. - for (int index = 0; index < children.length; ++index) + for (int index = 0; index < numChildren; ++index) { Element elem = children[index]; @@ -1905,14 +1914,26 @@ public abstract class AbstractDocument implements Document, Serializable */ public void replace(int offset, int length, Element[] elements) { - Element[] target = new Element[children.length - length - + elements.length]; - System.arraycopy(children, 0, target, 0, offset); - System.arraycopy(elements, 0, target, offset, elements.length); - System.arraycopy(children, offset + length, target, - offset + elements.length, - children.length - offset - length); - children = target; + int delta = elements.length - length; + int copyFrom = offset + length; // From where to copy. + int copyTo = copyFrom + delta; // Where to copy to. + int numMove = numChildren - copyFrom; // How many elements are moved. + if (numChildren + delta > children.length) + { + // Gotta grow the array. + int newSize = Math.max(2 * children.length, numChildren + delta); + Element[] target = new Element[newSize]; + System.arraycopy(children, 0, target, 0, offset); + System.arraycopy(elements, 0, target, offset, elements.length); + System.arraycopy(children, copyFrom, target, copyTo, numMove); + children = target; + } + else + { + System.arraycopy(children, copyFrom, children, copyTo, numMove); + System.arraycopy(elements, 0, children, offset, elements.length); + } + numChildren += delta; } /** @@ -2165,18 +2186,6 @@ public abstract class AbstractDocument implements Document, Serializable private Position endPos; /** - * This gets possible added to the startOffset when a startOffset - * outside the document range is requested. - */ - private int startDelta; - - /** - * This gets possible added to the endOffset when a endOffset - * outside the document range is requested. - */ - private int endDelta; - - /** * Creates a new <code>LeafElement</code>. * * @param parent the parent of this <code>LeafElement</code> @@ -2188,28 +2197,21 @@ public abstract class AbstractDocument implements Document, Serializable int end) { super(parent, attributes); - int len = content.length(); - startDelta = 0; - if (start > len) - startDelta = start - len; - endDelta = 0; - if (end > len) - endDelta = end - len; try - { - startPos = createPosition(start - startDelta); - endPos = createPosition(end - endDelta); - } - catch (BadLocationException ex) - { - AssertionError as; - as = new AssertionError("BadLocationException thrown " - + "here. start=" + start - + ", end=" + end - + ", length=" + getLength()); - as.initCause(ex); - throw as; - } + { + startPos = createPosition(start); + endPos = createPosition(end); + } + catch (BadLocationException ex) + { + AssertionError as; + as = new AssertionError("BadLocationException thrown " + + "here. start=" + start + + ", end=" + end + + ", length=" + getLength()); + as.initCause(ex); + throw as; + } } /** @@ -2281,7 +2283,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getEndOffset() { - return endPos.getOffset() + endDelta; + return endPos.getOffset(); } /** @@ -2307,7 +2309,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getStartOffset() { - return startPos.getOffset() + startDelta; + return startPos.getOffset(); } /** diff --git a/libjava/classpath/javax/swing/text/BoxView.java b/libjava/classpath/javax/swing/text/BoxView.java index a184a813152..27e3c0f9a1b 100644 --- a/libjava/classpath/javax/swing/text/BoxView.java +++ b/libjava/classpath/javax/swing/text/BoxView.java @@ -67,6 +67,11 @@ public class BoxView private boolean[] layoutValid = new boolean[2]; /** + * Indicates if the requirements for an axis are valid. + */ + private boolean[] requirementsValid = new boolean[2]; + + /** * The spans along the X_AXIS and Y_AXIS. */ private int[][] spans = new int[2][]; @@ -265,8 +270,10 @@ public class BoxView super.replace(offset, length, views); // Invalidate layout information. - layoutChanged(X_AXIS); - layoutChanged(Y_AXIS); + layoutValid[X_AXIS] = false; + requirementsValid[X_AXIS] = false; + layoutValid[Y_AXIS] = false; + requirementsValid[Y_AXIS] = false; } /** @@ -278,19 +285,26 @@ public class BoxView */ public void paint(Graphics g, Shape a) { - Rectangle inside = getInsideAllocation(a); - // TODO: Used for debugging. - //g.drawRect(inside.x, inside.y, inside.width, inside.height); + Rectangle alloc; + if (a instanceof Rectangle) + alloc = (Rectangle) a; + else + alloc = a.getBounds(); + + int x = alloc.x + getLeftInset(); + int y = alloc.y + getTopInset(); - Rectangle copy = new Rectangle(inside); + Rectangle clip = g.getClipBounds(); + Rectangle tmp = new Rectangle(); int count = getViewCount(); for (int i = 0; i < count; ++i) { - copy.setBounds(inside); - childAllocation(i, copy); - if (!copy.isEmpty() - && g.hitClip(copy.x, copy.y, copy.width, copy.height)) - paintChild(g, copy, i); + tmp.x = x + getOffset(X_AXIS, i); + tmp.y = y + getOffset(Y_AXIS, i); + tmp.width = getSpan(X_AXIS, i); + tmp.height = getSpan(Y_AXIS, i); + if (tmp.intersects(clip)) + paintChild(g, tmp, i); } } @@ -305,7 +319,13 @@ public class BoxView public float getPreferredSpan(int axis) { updateRequirements(axis); - return requirements[axis].preferred; + // Add margin. + float margin; + if (axis == X_AXIS) + margin = getLeftInset() + getRightInset(); + else + margin = getTopInset() + getBottomInset(); + return requirements[axis].preferred + margin; } /** @@ -319,12 +339,14 @@ public class BoxView */ public float getMaximumSpan(int axis) { - float max; - if (axis == myAxis) - max = getPreferredSpan(axis); + updateRequirements(axis); + // Add margin. + float margin; + if (axis == X_AXIS) + margin = getLeftInset() + getRightInset(); else - max = Integer.MAX_VALUE; - return max; + margin = getTopInset() + getBottomInset(); + return requirements[axis].maximum + margin; } /** @@ -341,7 +363,13 @@ public class BoxView public float getMinimumSpan(int axis) { updateRequirements(axis); - return requirements[axis].minimum; + // Add margin. + float margin; + if (axis == X_AXIS) + margin = getLeftInset() + getRightInset(); + else + margin = getTopInset() + getBottomInset(); + return requirements[axis].minimum + margin; } /** @@ -435,34 +463,29 @@ public class BoxView protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements sr) { - updateChildRequirements(axis); + SizeRequirements res = sr; + if (res == null) + res = new SizeRequirements(); - SizeRequirements result = sr; - if (result == null) - result = new SizeRequirements(); + float min = 0; + float pref = 0; + float max = 0; - long minimum = 0; - long preferred = 0; - long maximum = 0; - for (int i = 0; i < children.length; i++) + int n = getViewCount(); + for (int i = 0; i < n; i++) { - minimum += childReqs[axis][i].minimum; - preferred += childReqs[axis][i].preferred; - maximum += childReqs[axis][i].maximum; + View child = getView(i); + min += child.getMinimumSpan(axis); + pref = child.getPreferredSpan(axis); + max = child.getMaximumSpan(axis); } - // Overflow check. - if (minimum > Integer.MAX_VALUE) - minimum = Integer.MAX_VALUE; - if (preferred > Integer.MAX_VALUE) - preferred = Integer.MAX_VALUE; - if (maximum > Integer.MAX_VALUE) - maximum = Integer.MAX_VALUE; - - result.minimum = (int) minimum; - result.preferred = (int) preferred; - result.maximum = (int) maximum; - result.alignment = 0.5F; - return result; + + res.minimum = (int) min; + res.preferred = (int) pref; + res.maximum = (int) max; + res.alignment = 0.5F; + + return res; } /** @@ -480,44 +503,24 @@ public class BoxView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements sr) { - updateChildRequirements(axis); - SizeRequirements res = sr; if (res == null) res = new SizeRequirements(); - float minLeft = 0; - float minRight = 0; - float prefLeft = 0; - float prefRight = 0; - float maxLeft = 0; - float maxRight = 0; - for (int i = 0; i < childReqs[axis].length; i++) + res.minimum = 0; + res.preferred = 0; + res.maximum = 0; + res.alignment = 0.5F; + int n = getViewCount(); + for (int i = 0; i < n; i++) { - float myMinLeft = childReqs[axis][i].minimum * childReqs[axis][i].alignment; - float myMinRight = childReqs[axis][i].minimum - myMinLeft; - minLeft = Math.max(myMinLeft, minLeft); - minRight = Math.max(myMinRight, minRight); - float myPrefLeft = childReqs[axis][i].preferred * childReqs[axis][i].alignment; - float myPrefRight = childReqs[axis][i].preferred - myPrefLeft; - prefLeft = Math.max(myPrefLeft, prefLeft); - prefRight = Math.max(myPrefRight, prefRight); - float myMaxLeft = childReqs[axis][i].maximum * childReqs[axis][i].alignment; - float myMaxRight = childReqs[axis][i].maximum - myMaxLeft; - maxLeft = Math.max(myMaxLeft, maxLeft); - maxRight = Math.max(myMaxRight, maxRight); + View child = getView(i); + res.minimum = Math.max((int) child.getMinimumSpan(axis), res.minimum); + res.preferred = Math.max((int) child.getPreferredSpan(axis), + res.preferred); + res.maximum = Math.max((int) child.getMaximumSpan(axis), res.maximum); } - int minSize = (int) (minLeft + minRight); - int prefSize = (int) (prefLeft + prefRight); - int maxSize = (int) (maxLeft + maxRight); - float align = prefLeft / (prefRight + prefLeft); - if (Float.isNaN(align)) - align = 0; - res.alignment = align; - res.maximum = maxSize; - res.preferred = prefSize; - res.minimum = minSize; return res; } @@ -697,15 +700,62 @@ public class BoxView protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - updateChildRequirements(axis); - updateRequirements(axis); + // Set the spans to the preferred sizes. Determine the space + // that we have to adjust the sizes afterwards. + long sumPref = 0; + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View child = getView(i); + spans[i] = (int) child.getPreferredSpan(axis); + sumPref = spans[i]; + } - // Calculate the spans and offsets using the SizeRequirements uility - // methods. - SizeRequirements.calculateTiledPositions(targetSpan, requirements[axis], - childReqs[axis], - offsets, spans); + // Try to adjust the spans so that we fill the targetSpan. + long diff = targetSpan - sumPref; + float factor = 0.0F; + int[] diffs = null; + if (diff != 0) + { + long total = 0; + diffs = new int[n]; + for (int i = 0; i < n; i++) + { + View child = getView(i); + int span; + if (diff < 0) + { + span = (int) child.getMinimumSpan(axis); + diffs[i] = spans[i] - span; + } + else + { + span = (int) child.getMaximumSpan(axis); + diffs[i] = span - spans[i]; + } + total += span; + } + float maxAdjust = Math.abs(total - sumPref); + factor = diff / maxAdjust; + factor = Math.min(factor, 1.0F); + factor = Math.max(factor, -1.0F); + } + + // Actually perform adjustments. + int totalOffs = 0; + for (int i = 0; i < n; i++) + { + offsets[i] = totalOffs; + if (diff != 0) + { + float adjust = factor * diffs[i]; + spans[i] += Math.round(adjust); + } + // Avoid overflow here. + totalOffs = (int) Math.min((long) totalOffs + (long) spans[i], + Integer.MAX_VALUE); + } } /** @@ -720,14 +770,26 @@ public class BoxView protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - updateChildRequirements(axis); - updateRequirements(axis); - - // Calculate the spans and offsets using the SizeRequirements uility - // methods. - SizeRequirements.calculateAlignedPositions(targetSpan, requirements[axis], - childReqs[axis], offsets, - spans); + int count = getViewCount(); + for (int i = 0; i < count; i++) + { + View child = getView(i); + int max = (int) child.getMaximumSpan(axis); + if (max < targetSpan) + {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); + spans[i] = max; + } + else + { + // Expand child to target width if possible. + int min = (int) child.getMinimumSpan(axis); + offsets[i] = 0; + spans[i] = Math.max(min, targetSpan); + } + } } /** @@ -822,15 +884,8 @@ public class BoxView */ public float getAlignment(int axis) { - float align; - if (axis == myAxis) - align = 0.5F; - else - { - updateRequirements(axis); - align = requirements[axis].alignment; - } - return align; + updateRequirements(axis); + return requirements[axis].alignment; } /** @@ -843,9 +898,15 @@ public class BoxView public void preferenceChanged(View child, boolean width, boolean height) { if (width) - layoutValid[X_AXIS] = false; + { + layoutValid[X_AXIS] = false; + requirementsValid[X_AXIS] = false; + } if (height) - layoutValid[Y_AXIS] = false; + { + layoutValid[Y_AXIS] = false; + requirementsValid[Y_AXIS] = false; + } super.preferenceChanged(child, width, height); } @@ -862,7 +923,7 @@ public class BoxView if (! isAllocationValid()) { Rectangle bounds = a.getBounds(); - layout(bounds.width, bounds.height); + setSize(bounds.width, bounds.height); } return super.modelToView(pos, a, bias); } @@ -963,7 +1024,7 @@ public class BoxView */ private void updateRequirements(int axis) { - if (! layoutValid[axis]) + if (! requirementsValid[axis]) { if (axis == myAxis) requirements[axis] = calculateMajorAxisRequirements(axis, @@ -971,6 +1032,7 @@ public class BoxView else requirements[axis] = calculateMinorAxisRequirements(axis, requirements[axis]); + requirementsValid[axis] = true; } } } diff --git a/libjava/classpath/javax/swing/text/CompositeView.java b/libjava/classpath/javax/swing/text/CompositeView.java index 17f13dbedd6..6f487b8981e 100644 --- a/libjava/classpath/javax/swing/text/CompositeView.java +++ b/libjava/classpath/javax/swing/text/CompositeView.java @@ -217,25 +217,43 @@ public abstract class CompositeView public Shape modelToView(int pos, Shape a, Position.Bias bias) throws BadLocationException { - int childIndex = getViewIndex(pos, bias); - if (childIndex == -1) - throw new BadLocationException("Position " + pos + " is not represented by view.", pos); - - Shape ret = null; - - View child = getView(childIndex); - Shape childAlloc = getChildAllocation(childIndex, a); - - if (childAlloc == null) - ret = createDefaultLocation(a, bias); - - Shape result = child.modelToView(pos, childAlloc, bias); - - if (result != null) - ret = result; - else - ret = createDefaultLocation(a, bias); + boolean backward = bias == Position.Bias.Backward; + int testpos = backward ? Math.max(0, pos - 1) : pos; + Shape ret = null; + if (! backward || testpos >= getStartOffset()) + { + int childIndex = getViewIndexAtPosition(testpos); + if (childIndex != -1 && childIndex < getViewCount()) + { + View child = getView(childIndex); + if (child != null && testpos >= child.getStartOffset() + && testpos < child.getEndOffset()) + { + Shape childAlloc = getChildAllocation(childIndex, a); + if (childAlloc != null) + { + ret = child.modelToView(pos, childAlloc, bias); + // Handle corner case. + if (ret == null && child.getEndOffset() == pos) + { + childIndex++; + if (childIndex < getViewCount()) + { + child = getView(childIndex); + childAlloc = getChildAllocation(childIndex, a); + ret = child.modelToView(pos, childAlloc, bias); + } + } + } + } + } + else + { + throw new BadLocationException("Position " + pos + + " is not represented by view.", pos); + } + } return ret; } @@ -378,7 +396,10 @@ public abstract class CompositeView { if (b == Position.Bias.Backward && pos != 0) pos -= 1; - return getViewIndexAtPosition(pos); + int i = -1; + if (pos >= getStartOffset() && pos < getEndOffset()) + i = getViewIndexAtPosition(pos); + return i; } /** @@ -446,9 +467,13 @@ public abstract class CompositeView */ protected View getViewAtPosition(int pos, Rectangle a) { + View view = null; int i = getViewIndexAtPosition(pos); - View view = children[i]; - childAllocation(i, a); + if (i >= 0 && i < getViewCount() && a != null) + { + view = getView(i); + childAllocation(i, a); + } return view; } @@ -464,17 +489,10 @@ public abstract class CompositeView */ protected int getViewIndexAtPosition(int pos) { - int index = -1; - for (int i = 0; i < children.length; i++) - { - if (children[i].getStartOffset() <= pos - && children[i].getEndOffset() > pos) - { - index = i; - break; - } - } - return index; + // We have a 1:1 mapping of elements to views here, so we forward + // this to the element. + Element el = getElement(); + return el.getElementIndex(pos); } /** diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java index 4ad204c00c9..84f47f120de 100644 --- a/libjava/classpath/javax/swing/text/DefaultCaret.java +++ b/libjava/classpath/javax/swing/text/DefaultCaret.java @@ -221,9 +221,12 @@ public class DefaultCaret extends Rectangle if (name.equals("document")) { Document oldDoc = (Document) e.getOldValue(); - oldDoc.removeDocumentListener(documentListener); + if (oldDoc != null) + oldDoc.removeDocumentListener(documentListener); + Document newDoc = (Document) e.getNewValue(); - newDoc.addDocumentListener(documentListener); + if (newDoc != null) + newDoc.addDocumentListener(documentListener); } else if (name.equals("editable")) { @@ -549,7 +552,6 @@ public class DefaultCaret extends Rectangle */ public void mousePressed(MouseEvent event) { - int button = event.getButton(); // The implementation assumes that consuming the event makes the AWT event // mechanism forget about this event instance and not transfer focus. @@ -562,23 +564,37 @@ public class DefaultCaret extends Rectangle // - a middle-click positions the caret and pastes the clipboard // contents. // - a middle-click when shift is held down is ignored - - if (button == MouseEvent.BUTTON1) - if (event.isShiftDown()) - moveCaret(event); - else - positionCaret(event); - else if(button == MouseEvent.BUTTON2) - if (event.isShiftDown()) - event.consume(); + + if (SwingUtilities.isLeftMouseButton(event)) + { + // Handle the caret. + if (event.isShiftDown() && getDot() != -1) + { + moveCaret(event); + } else { positionCaret(event); - + } + + // Handle the focus. + if (textComponent != null && textComponent.isEnabled() + && textComponent.isRequestFocusEnabled()) + { + textComponent.requestFocus(); + } + + // TODO: Handle double click for selecting words. + } + else if(event.getButton() == MouseEvent.BUTTON2) + { + // Special handling for X11-style pasting. + if (! event.isShiftDown()) + { + positionCaret(event); textComponent.paste(); } - else - event.consume(); + } } /** @@ -708,7 +724,11 @@ public class DefaultCaret extends Rectangle propertyChangeListener = new PropertyChangeHandler(); textComponent.addPropertyChangeListener(propertyChangeListener); documentListener = new DocumentHandler(); - textComponent.getDocument().addDocumentListener(documentListener); + + Document doc = textComponent.getDocument(); + if (doc != null) + doc.addDocumentListener(documentListener); + active = textComponent.isEditable() && textComponent.isEnabled(); repaint(); @@ -891,10 +911,10 @@ public class DefaultCaret extends Rectangle } catch (BadLocationException e) { - AssertionError ae; - ae = new AssertionError("Unexpected bad caret location: " + dot); - ae.initCause(e); - throw ae; + // Let's ignore that. This shouldn't really occur. But if it + // does (it seems that this happens when the model is mutating), + // it causes no real damage. Uncomment this for debugging. + // e.printStackTrace(); } if (rect == null) @@ -1128,10 +1148,10 @@ public class DefaultCaret extends Rectangle } catch (BadLocationException e) { - AssertionError ae; - ae = new AssertionError("Unexpected bad caret location: " + dot); - ae.initCause(e); - throw ae; + // Let's ignore that. This shouldn't really occur. But if it + // does (it seems that this happens when the model is mutating), + // it causes no real damage. Uncomment this for debugging. + // e.printStackTrace(); } if (area != null) damage(area); @@ -1140,6 +1160,24 @@ public class DefaultCaret extends Rectangle } /** + * Returns <code>true</code> if this <code>Caret</code> is blinking, + * and <code>false</code> if not. The returned value is independent of + * the visiblity of this <code>Caret</code> as returned by {@link #isVisible()}. + * + * @return <code>true</code> if this <code>Caret</code> is blinking, + * and <code>false</code> if not. + * @see #isVisible() + * @since 1.5 + */ + public boolean isActive() + { + if (blinkTimer != null) + return blinkTimer.isRunning(); + + return false; + } + + /** * Returns <code>true</code> if this <code>Caret</code> is currently visible, * and <code>false</code> if it is not. * diff --git a/libjava/classpath/javax/swing/text/EmptyAttributeSet.java b/libjava/classpath/javax/swing/text/EmptyAttributeSet.java new file mode 100644 index 00000000000..98fb8828c89 --- /dev/null +++ b/libjava/classpath/javax/swing/text/EmptyAttributeSet.java @@ -0,0 +1,153 @@ +/* EmptyAttributeSet.java -- An empty attribute set + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * An immutable, empty attribute set. + * + * @see SimpleAttributeSet#EMPTY + * + * @author Roman Kennke (kennke@aicas.com) + */ +final class EmptyAttributeSet + implements AttributeSet +{ + + /** + * Always return false as this AttributeSet doesn't contain any attributes. + */ + public boolean containsAttribute(Object name, Object value) + { + return false; + } + + /** + * Return true only if the attributes argument also contains no attributes. + */ + public boolean containsAttributes(AttributeSet attributes) + { + return attributes.getAttributeCount() == 0; + } + + /** + * Return this, as this is immutable. + */ + public AttributeSet copyAttributes() + { + return this; + } + + /** + * Always return null as this AttributeSet doesn't contain any attributes. + */ + public Object getAttribute(Object key) + { + return null; + } + + /** + * Always return 0. + */ + public int getAttributeCount() + { + return 0; + } + + /** + * Returns an empty Enumeration. + */ + public Enumeration getAttributeNames() + { + return new Enumeration() + { + public boolean hasMoreElements() + { + return false; + } + + public Object nextElement() + { + throw new NoSuchElementException("No more elements"); + } + + }; + } + + /** + * Always return null as this has no resolve parent. + */ + public AttributeSet getResolveParent() + { + return null; + } + + /** + * Always return false as this AttributeSet doesn't contain any attributes. + */ + public boolean isDefined(Object attrName) + { + return false; + } + + /** + * Other attribute sets are equal if they are empty too. + */ + public boolean isEqual(AttributeSet attr) + { + return attr.getAttributeCount() == 0; + } + + /** + * Other objects are equal if it's the same instance as this, or if + * it's another attribute set without attributes. + */ + public boolean equals(Object o) + { + boolean eq = o == this; + if (! eq) + { + eq = (o instanceof AttributeSet) + && ((AttributeSet) o).getAttributeCount() == 0; + } + return eq; + } +} diff --git a/libjava/classpath/javax/swing/text/FlowView.java b/libjava/classpath/javax/swing/text/FlowView.java index 8ca55d8347a..3de95ed7f8d 100644 --- a/libjava/classpath/javax/swing/text/FlowView.java +++ b/libjava/classpath/javax/swing/text/FlowView.java @@ -159,20 +159,18 @@ public abstract class FlowView extends BoxView } /** - * Lays out one row of the flow view. This is called by {@link #layout} - * to fill one row with child views until the available span is exhausted. - * - * The default implementation fills the row by calling - * {@link #createView(FlowView, int, int, int)} until the available space - * is exhausted, a forced break is encountered or there are no more views - * in the logical view. If the available space is exhausted, - * {@link #adjustRow(FlowView, int, int, int)} is called to fit the row - * into the available span. - * + * Lays out one row of the flow view. This is called by {@link #layout} to + * fill one row with child views until the available span is exhausted. The + * default implementation fills the row by calling + * {@link #createView(FlowView, int, int, int)} until the available space is + * exhausted, a forced break is encountered or there are no more views in + * the logical view. If the available space is exhausted, + * {@link #adjustRow(FlowView, int, int, int)} is called to fit the row into + * the available span. + * * @param fv the flow view for which we perform the layout * @param rowIndex the index of the row * @param pos the model position for the beginning of the row - * * @return the start position of the next row */ protected int layoutRow(FlowView fv, int rowIndex, int pos) @@ -188,34 +186,39 @@ public abstract class FlowView extends BoxView if (span == 0) span = Integer.MAX_VALUE; - while (span > 0) + Row: while (span > 0) { - if (logicalView.getViewIndex(offset, Position.Bias.Forward) == -1) + if (logicalView.getViewIndex(offset, Position.Bias.Forward) == - 1) break; View view = createView(fv, offset, span, rowIndex); if (view == null) break; + int viewSpan = (int) view.getPreferredSpan(axis); - row.append(view); int breakWeight = view.getBreakWeight(axis, x, span); - if (breakWeight >= View.ForcedBreakWeight) - break; + + row.append(view); + offset += (view.getEndOffset() - view.getStartOffset()); x += viewSpan; span -= viewSpan; - offset += (view.getEndOffset() - view.getStartOffset()); - } - if (span < 0) - { - 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 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 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; + } } - return offset != pos ? offset : -1; + + return offset != pos ? offset : - 1; } /** @@ -521,6 +524,7 @@ public abstract class FlowView extends BoxView */ public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf) { + layoutPool.removeUpdate(changes, a, vf); strategy.removeUpdate(this, changes, getInsideAllocation(a)); layoutDirty = true; } @@ -536,6 +540,7 @@ public abstract class FlowView extends BoxView */ public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf) { + layoutPool.changedUpdate(changes, a, vf); strategy.changedUpdate(this, changes, getInsideAllocation(a)); layoutDirty = true; } @@ -594,12 +599,14 @@ public abstract class FlowView extends BoxView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { - // We need to call super here so that the alignment is properly - // calculated. - SizeRequirements res = super.calculateMinorAxisRequirements(axis, r); + SizeRequirements res = r; + if (res == null) + res = new SizeRequirements(); res.minimum = (int) layoutPool.getMinimumSpan(axis); - res.preferred = (int) layoutPool.getPreferredSpan(axis); - res.maximum = (int) layoutPool.getMaximumSpan(axis); + res.preferred = Math.max(res.minimum, + (int) layoutPool.getMinimumSpan(axis)); + res.maximum = Integer.MAX_VALUE; + res.alignment = 0.5F; return res; } } diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java index 4f06003b458..760e396a223 100644 --- a/libjava/classpath/javax/swing/text/GapContent.java +++ b/libjava/classpath/javax/swing/text/GapContent.java @@ -39,7 +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; @@ -73,30 +79,39 @@ public class GapContent * The index to the positionMarks array entry, which in turn holds the * mark into the buffer array. */ - int index; + Mark mark; /** * Creates a new GapContentPosition object. * - * @param mark the mark of this Position + * @param offset the offset of this Position */ - GapContentPosition(int mark) + GapContentPosition(int offset) { // Try to find the mark in the positionMarks array, and store the index // to it. synchronized (GapContent.this) { - int i = binarySearch(positionMarks, mark, numMarks); + // Try to make space. + garbageCollect(); + Mark m = new Mark(offset); + int i = search(marks, m); if (i >= 0) // mark found { - index = i; + m = (Mark) marks.get(i); } else { - index = -i - 1; - insertMark(index, mark); + 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); } /** @@ -106,19 +121,77 @@ public class GapContent */ public int getOffset() { - synchronized (GapContent.this) - { - // Fetch the actual mark. - int mark = positionMarks[index]; - // Check precondition. - assert mark <= gapStart || mark >= gapEnd : "mark: " + mark - + ", gapStart: " + gapStart - + ", gapEnd: " + gapEnd; - int res = mark; - if (mark > gapStart) - res -= (gapEnd - gapStart); - return res; - } + return mark.getOffset(); + } + } + + /** + * Holds a mark into the buffer that is used by GapContentPosition 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 GapContentPosition can + * be garbage collected while we still hold a reference to the Mark object. + */ + private class Mark + implements Comparable + { + /** + * 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 GapContent#garbageCollect()}. + */ + int refCount; + + /** + * Creates a new Mark object for the specified offset. + * + * @param offset the offset + */ + Mark(int offset) + { + mark = offset; + if (mark >= gapStart && mark != 0) + mark += (gapEnd - gapStart); + } + + /** + * Returns the offset of the mark. + * + * @return the offset of the mark + */ + int getOffset() + { + assert mark == 0 || mark < gapStart || mark >= gapEnd : + "Invalid mark: " + mark + ", gapStart: " + gapStart + + ", gapEnd: " + gapEnd; + + int res = mark; + if (mark >= gapEnd) + res -= (gapEnd - gapStart); + return res; + } + + /** + * Implementation of Comparable. + */ + public int compareTo(Object o) + { + Mark other = (Mark) o; + return mark - other.mark; + } + /** + * Adjustment for equals(). + */ + public boolean equals(Object o) + { + if (o == null || !(o instanceof Mark)) + return false; + else + return ((Mark) o).mark == mark; } } @@ -230,19 +303,21 @@ public class GapContent /** * Holds the marks for positions. These marks are referenced by the * GapContentPosition instances by an index into this array. + * + * This is package private to avoid accessor synthetic methods. */ - int[] positionMarks; + ArrayList marks; - /** - * The number of elements in the positionMarks array. The positionMarks array - * might be bigger than the actual number of elements. - */ - int numMarks; + WeakHashMap positions; /** - * (Weakly) Stores the GapContentPosition instances. + * 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. */ - WeakHashMap positions; + ReferenceQueue queueOfDeath; /** * Creates a new GapContent object. @@ -265,8 +340,8 @@ public class GapContent gapEnd = size; buffer[0] = '\n'; positions = new WeakHashMap(); - positionMarks = new int[10]; - numMarks = 0; + marks = new ArrayList(); + queueOfDeath = new ReferenceQueue(); } /** @@ -417,6 +492,8 @@ public class GapContent if ((where + len) > length) throw new BadLocationException("len plus where cannot be greater" + " than the content length", len + where); + if (len < 0) + throw new BadLocationException("negative length not allowed: ", len); // check if requested segment is contiguous if ((where < gapStart) && ((gapStart - where) < len)) @@ -455,6 +532,11 @@ public class GapContent */ public Position createPosition(final int offset) throws BadLocationException { + // Implementation note: We used to perform explicit check on the offset + // here. However, this makes some Mauve and Intel/Harmony tests fail + // and luckily enough the GapContent can very well deal with offsets + // outside the buffer bounds. So I removed that check. + // We try to find a GapContentPosition at the specified offset and return // that. Otherwise we must create a new one. GapContentPosition pos = null; @@ -472,10 +554,7 @@ public class GapContent // If none was found, then create and return a new one. if (pos == null) { - int mark = offset; - if (mark >= gapStart) - mark += (gapEnd - gapStart); - pos = new GapContentPosition(mark); + pos = new GapContentPosition(offset); positions.put(pos, null); } @@ -497,7 +576,7 @@ public class GapContent int delta = newSize - gapEnd + gapStart; // Update the marks after the gapEnd. - adjustPositionsInRange(gapEnd, buffer.length - gapEnd, delta); + adjustPositionsInRange(gapEnd, -1, delta); // Copy the data around. char[] newBuf = (char[]) allocateArray(length() + newSize); @@ -523,7 +602,7 @@ public class GapContent { // Update the positions between newGapStart and (old) gapStart. The marks // must be shifted by (gapEnd - gapStart). - adjustPositionsInRange(newGapStart, gapStart - newGapStart, gapEnd - gapStart); + adjustPositionsInRange(newGapStart, gapStart, gapEnd - gapStart); System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart - newGapStart); gapStart = newGapStart; @@ -533,14 +612,13 @@ public class GapContent { // Update the positions between newGapEnd and (old) gapEnd. The marks // must be shifted by (gapEnd - gapStart). - adjustPositionsInRange(gapEnd, newGapEnd - gapEnd, -(gapEnd - gapStart)); + adjustPositionsInRange(gapEnd, newGapEnd, -(gapEnd - gapStart)); System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart - gapStart); gapStart = newGapStart; gapEnd = newGapEnd; } - if (gapStart == 0) - resetMarksAtZero(); + resetMarksAtZero(); } /** @@ -560,6 +638,7 @@ public class GapContent + "old gap start."; setPositionsInRange(newGapStart, gapStart, false); gapStart = newGapStart; + resetMarksAtZero(); } /** @@ -579,6 +658,7 @@ public class GapContent + "old gap end."; setPositionsInRange(gapEnd, newGapEnd, false); gapEnd = newGapEnd; + resetMarksAtZero(); } /** @@ -617,10 +697,6 @@ public class GapContent if (addItems != null) { System.arraycopy(addItems, 0, buffer, gapStart, addSize); - - - resetMarksAtZero(); - gapStart += addSize; } } @@ -689,102 +765,61 @@ public class GapContent */ private void setPositionsInRange(int start, int end, boolean toStart) { - // We slump together all the GapContentPositions to point to - // one mark. So this is implemented as follows: - // 1. Remove all the marks in the specified range. - // 2. Insert one new mark at the correct location. - // 3. Adjust all affected GapContentPosition instances to point to - // this new mark. - synchronized (this) { - int startIndex = binarySearch(positionMarks, start, numMarks); + // 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; - int endIndex = binarySearch(positionMarks, end, numMarks); + m.mark = end; + int endIndex = search(marks, m); if (endIndex < 0) // Translate to insertion index - 1, if not found. endIndex = - endIndex - 2; - // Update the marks. - // We have inclusive interval bounds, but let one element over for - // filling in the new value. - int removed = endIndex - startIndex; - if (removed <= 0) - return; - System.arraycopy(positionMarks, endIndex + 1, positionMarks, - startIndex + 1, positionMarks.length - endIndex - 1); - numMarks -= removed; - if (toStart) - { - positionMarks[startIndex] = start; - } - else - { - positionMarks[startIndex] = end; - } + // Actually adjust the marks. + for (int i = startIndex; i <= endIndex; i++) + ((Mark) marks.get(i)).mark = toStart ? start : end; + } - // Update all affected GapContentPositions to point to the new index - // and all GapContentPositions that come after the interval to - // have their index moved by -removed. - Set positionSet = positions.keySet(); - for (Iterator i = positionSet.iterator(); i.hasNext();) - { - GapContentPosition p = (GapContentPosition) i.next(); - if (p.index > startIndex || p.index <= endIndex) - p.index = startIndex; - else if (p.index > endIndex) - p.index -= removed; - } - } } - + /** * Adjusts the mark of all <code>Position</code>s that are in the range * specified by <code>offset</code> and </code>length</code> within * the buffer array by <code>increment</code> * - * @param offset the start offset of the range to search - * @param length the length of the range to search + * @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 offset, int length, int incr) + private void adjustPositionsInRange(int startOffs, int endOffs, int incr) { - int endMark = offset + length; - synchronized (this) { // Find the start and end indices in the positionMarks array. - int startIndex = binarySearch(positionMarks, offset, numMarks); + 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; - int endIndex = binarySearch(positionMarks, endMark, numMarks); - if (endIndex < 0) // Translate to insertion index - 1, if not found. - endIndex = - endIndex - 2; - - // We must not change the order of the marks, this would have - // unpredictable results while binary-searching the marks. - assert (startIndex <= 0 - || positionMarks[startIndex - 1] - <= positionMarks [startIndex] + incr) - && (endIndex >= numMarks - 1 - || positionMarks[endIndex + 1] - >= positionMarks[endIndex] + incr) - : "Adjusting the marks must not change their order"; - - // Some debug helper output to determine if the start or end of the - // should ever be coalesced together with adjecent marks. - if (startIndex > 0 && positionMarks[startIndex - 1] - == positionMarks[startIndex] + incr) - System.err.println("DEBUG: We could coalesce the start of the region" - + " in GapContent.adjustPositionsInRange()"); - if (endIndex < numMarks - 1 && positionMarks[endIndex + 1] - == positionMarks[endIndex] + incr) - System.err.println("DEBUG: We could coalesce the end of the region" - + " in GapContent.adjustPositionsInRange()"); + 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++) - positionMarks[i] += incr; + for (int i = startIndex; i <= endIndex; i++) { + ((Mark) marks.get(i)).mark += incr; + } } } @@ -800,7 +835,12 @@ public class GapContent if (gapStart != 0) return; - positionMarks[0] = 0; + for (int i = 0; i < marks.size(); i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark <= gapEnd) + m.mark = 0; + } } /** @@ -845,89 +885,60 @@ public class GapContent */ private void dumpMarks() { - System.err.print("positionMarks: "); - for (int i = 0; i < numMarks; i++) - System.err.print(positionMarks[i] + ", "); - System.err.println(); + System.out.print("positionMarks: "); + for (int i = 0; i < marks.size(); i++) + System.out.print(((Mark) marks.get(i)).mark + ", "); + System.out.println(); } /** - * Inserts a mark into the positionMarks array. This must update all the - * GapContentPosition instances in positions that come after insertionPoint. + * 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 synthetic accessor methods. - * - * @param insertionPoint the index at which to insert the mark - * @param mark the mark to insert + * This is package private to avoid accessor synthetic methods. */ - void insertMark(int insertionPoint, int mark) + void garbageCollect() { - synchronized (this) + Reference ref = queueOfDeath.poll(); + while (ref != null) { - // Update the positions. - Set positionSet = positions.keySet(); - for (Iterator i = positionSet.iterator(); i.hasNext();) - { - GapContentPosition p = (GapContentPosition) i.next(); - if (p.index >= insertionPoint) - p.index++; - } - - // Update the position marks. - if (positionMarks.length <= numMarks) + if (ref != null) { - int[] newMarks = new int[positionMarks.length + 10]; - System.arraycopy(positionMarks, 0, newMarks, 0, insertionPoint); - newMarks[insertionPoint] = mark; - System.arraycopy(positionMarks, insertionPoint, newMarks, - insertionPoint + 1, - numMarks - insertionPoint); - positionMarks = newMarks; + GapContentPosition pos = (GapContentPosition) ref.get(); + Mark m = pos.mark; + m.refCount--; + if (m.refCount == 0) + marks.remove(m); } - else - { - System.arraycopy(positionMarks, insertionPoint, positionMarks, - insertionPoint + 1, - numMarks - insertionPoint); - positionMarks[insertionPoint] = mark; - } - numMarks++; + ref = queueOfDeath.poll(); } } /** - * An adaption of {@link java.util.Arrays#binarySearch(int[], int)} to - * specify a maximum index up to which the array is searched. This allows - * us to have some trailing entries that we ignore. - * - * This is package private to avoid synthetic accessor methods. - * - * @param a the array - * @param key the key to search for - * @param maxIndex the maximum index up to which the search is performed + * Searches the first occurance of object <code>o</code> in list + * <code>l</code>. This performs a binary search by calling + * {@link Collections#binarySearch(List, Object)} and when an object has been + * found, it searches backwards to the first occurance of that object in the + * list. The meaning of the return value is the same as in + * <code>Collections.binarySearch()</code>. * - * @return the index of the found entry, or (-(index)-1) for the - * insertion point when not found + * @param l the list to search through + * @param o the object to be searched * - * @see java.util.Arrays#binarySearch(int[], int) + * @return the index of the first occurance of o in l, or -i + 1 if not found */ - int binarySearch(int[] a, int key, int maxIndex) + private int search(List l, Object o) { - int low = 0; - int hi = maxIndex - 1; - int mid = 0; - while (low <= hi) + int i = Collections.binarySearch(l, o); + while (i > 0) { - mid = (low + hi) >>> 1; - final int d = a[mid]; - if (d == key) - return mid; - else if (d > key) - hi = mid - 1; + Object o2 = l.get(i - 1); + if (o2.equals(o)) + i--; else - // This gets the insertion point right on the last loop. - low = ++mid; + break; } - return -mid - 1; + return i; } } diff --git a/libjava/classpath/javax/swing/text/IconView.java b/libjava/classpath/javax/swing/text/IconView.java index 699cda90eba..7bb7635b4e7 100644 --- a/libjava/classpath/javax/swing/text/IconView.java +++ b/libjava/classpath/javax/swing/text/IconView.java @@ -44,7 +44,6 @@ import java.awt.Shape; import javax.swing.Icon; import javax.swing.JTextPane; -import javax.swing.SwingConstants; /** * A View that can render an icon. This view is created by the @@ -156,4 +155,21 @@ public class IconView return el.getStartOffset(); } + /** + * Returns the alignment for this view. This will be 1.0 for the Y_AXIS, + * and the super behaviour for the X_AXIS. + * + * @param axis the axis for which to calculate the alignment + * + * @return the alignment + */ + public float getAlignment(int axis) + { + float align; + if (axis == Y_AXIS) + align = 1.0F; + else + align = super.getAlignment(axis); + return align; + } } diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java index 9de151dfbac..6da84bfe7d8 100644 --- a/libjava/classpath/javax/swing/text/JTextComponent.java +++ b/libjava/classpath/javax/swing/text/JTextComponent.java @@ -42,6 +42,7 @@ import gnu.classpath.NotImplementedException; import java.awt.AWTEvent; import java.awt.Color; +import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; @@ -90,9 +91,10 @@ public abstract class JTextComponent extends JComponent implements Scrollable, Accessible { /** - * This class implements accessibility support for the JTextComponent class. - * It provides an implementation of the Java Accessibility API appropriate - * to menu user-interface elements. + * AccessibleJTextComponent implements accessibility hooks for + * JTextComponent. It allows an accessibility driver to read and + * manipulate the text component's contents as well as update UI + * elements such as the caret. */ public class AccessibleJTextComponent extends AccessibleJComponent implements AccessibleText, CaretListener, DocumentListener, AccessibleAction, @@ -100,15 +102,18 @@ public abstract class JTextComponent extends JComponent { private static final long serialVersionUID = 7664188944091413696L; - /** The caret's offset. */ + /** + * The caret's offset. + */ int dot = 0; - - /** The current JTextComponent. */ + + /** + * The current JTextComponent. + */ JTextComponent textComp = JTextComponent.this; - + /** - * Constructs an AccessibleJTextComponent. - * Adds a listener to track caret change. + * Construct an AccessibleJTextComponent. */ public AccessibleJTextComponent() { @@ -117,11 +122,10 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the zero-based offset of the caret. Note: The character - * to the right of the caret will have the same index value as the - * offset (the caret is between two characters). - * - * @return offset of caret + * Retrieve the current caret position. The index of the first + * caret position is 0. + * + * @return caret position */ public int getCaretPosition() { @@ -130,9 +134,10 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the portion of the text that is selected. - * - * @return null if no text is selected. + * Retrieve the current text selection. If no text is selected + * this method returns null. + * + * @return the currently selected text or null */ public String getSelectedText() { @@ -140,11 +145,14 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the start offset within the selected text. If there is no - * selection, but there is a caret, the start and end offsets will be - * the same. Return 0 if the text is empty, or the caret position if no selection. - * - * @return index of the start of the text >= 0. + * Retrieve the index of the first character in the current text + * selection. If there is no text in the text component, this + * method returns 0. If there is text in the text component, but + * there is no selection, this method returns the current caret + * position. + * + * @return the index of the first character in the selection, the + * current caret position or 0 */ public int getSelectionStart() { @@ -154,12 +162,14 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the end offset within the selected text. If there is no - * selection, but there is a caret, the start and end offsets will - * be the same. Return 0 if the text is empty, or the caret position - * if no selection. - * - * @return index of the end of the text >= 0. + * Retrieve the index of the last character in the current text + * selection. If there is no text in the text component, this + * method returns 0. If there is text in the text component, but + * there is no selection, this method returns the current caret + * position. + * + * @return the index of the last character in the selection, the + * current caret position or 0 */ public int getSelectionEnd() { @@ -169,13 +179,10 @@ public abstract class JTextComponent extends JComponent } /** - * Handles caret updates (fire appropriate property change event, which are - * AccessibleContext.ACCESSIBLE_CARET_PROPERTY and - * AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY). This keeps track of - * the dot position internally. When the caret moves, the internal position - * is updated after firing the event. - * - * @param e - caret event + * Handle a change in the caret position and fire any applicable + * property change events. + * + * @param e - the caret update event */ public void caretUpdate(CaretEvent e) throws NotImplementedException @@ -185,7 +192,7 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the accessible state set of this component. + * Retreive the accessible state set of this component. * * @return the accessible state set of this component */ @@ -198,7 +205,7 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the accessible role of this component. + * Retrieve the accessible role of this component. * * @return the accessible role of this component * @@ -210,20 +217,19 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the AccessibleEditableText interface for this text component. - * + * Retrieve an AccessibleEditableText object that controls this + * text component. + * * @return this */ public AccessibleEditableText getAccessibleEditableText() { return this; } - + /** - * Get the AccessibleText associated with this object. In the implementation - * of the Java Accessibility API for this class, return this object, - * which is responsible for implementing the AccessibleText interface on - * behalf of itself. + * Retrieve an AccessibleText object that controls this text + * component. * * @return this * @@ -235,10 +241,11 @@ public abstract class JTextComponent extends JComponent } /** - * Insert update. Fire appropriate property change event which - * is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY. - * - * @param e - document event + * Handle a text insertion event and fire an + * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change + * event. + * + * @param e - the insertion event */ public void insertUpdate(DocumentEvent e) throws NotImplementedException @@ -247,10 +254,11 @@ public abstract class JTextComponent extends JComponent } /** - * Remove update. Fire appropriate property change event which - * is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY. - * - * @param e - document event + * Handle a text removal event and fire an + * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change + * event. + * + * @param e - the removal event */ public void removeUpdate(DocumentEvent e) throws NotImplementedException @@ -259,10 +267,11 @@ public abstract class JTextComponent extends JComponent } /** - * Changed update. Fire appropriate property change event which - * is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY. - * - * @param e - document event + * Handle a text change event and fire an + * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change + * event. + * + * @param e - text change event */ public void changedUpdate(DocumentEvent e) throws NotImplementedException @@ -271,11 +280,13 @@ public abstract class JTextComponent extends JComponent } /** - * Given a point in the coordinate system of this object, return the - * 0-based index of the character at that point, or -1 if there is none. + * Get the index of the character at the given point, in component + * pixel co-ordinates. If the point argument is invalid this + * method returns -1. * - * @param p the point to look at - * @return the character index, or -1 + * @param p - a point in component pixel co-ordinates + * + * @return a character index, or -1 */ public int getIndexAtPoint(Point p) throws NotImplementedException @@ -284,17 +295,14 @@ public abstract class JTextComponent extends JComponent } /** - * Determines the bounding box of the indexed character. Returns an empty - * rectangle if the index is out of bounds. The bounds are returned in local coordinates. - * If the index is invalid a null rectangle is returned. The screen coordinates returned are - * "unscrolled coordinates" if the JTextComponent is contained in a JScrollPane in which - * case the resulting rectangle should be composed with the parent coordinates. - * Note: the JTextComponent must have a valid size (e.g. have been added to a parent - * container whose ancestor container is a valid top-level window) for this method to - * be able to return a meaningful (non-null) value. + * Calculate the bounding box of the character at the given index. + * The returned x and y co-ordinates are relative to this text + * component's top-left corner. If the index is invalid this + * method returns null. + * + * @param index - the character index * - * @param index the 0-based character index - * @return the bounding box, may be empty or null. + * @return a character's bounding box, or null */ public Rectangle getCharacterBounds(int index) throws NotImplementedException @@ -303,9 +311,9 @@ public abstract class JTextComponent extends JComponent } /** - * Return the number of characters. + * Return the length of the text in this text component. * - * @return the character count + * @return a character length */ public int getCharCount() { @@ -313,10 +321,11 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the attributes of a character at an index, or null if the index - * is out of bounds. + * Gets the character attributes of the character at index. If + * the index is out of bounds, null is returned. * - * @param index the 0-based character index + * @param index - index of the character + * * @return the character's attributes */ public AttributeSet getCharacterAttribute(int index) @@ -326,26 +335,28 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the section of text at the index, or null if the index or part - * is invalid. - * - * @param part {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} - * @param index the 0-based character index - * @return the selection of text at that index, or null + * Gets the text located at index. null is returned if the index + * or part is invalid. + * + * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} + * @param index - index of the part + * + * @return the part of text at that index, or null */ public String getAtIndex(int part, int index) throws NotImplementedException { return null; // TODO } - + /** - * Returns the section of text after the index, or null if the index or part - * is invalid. - * - * @param part {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} - * @param index the 0-based character index - * @return the selection of text after that index, or null + * Gets the text located after index. null is returned if the index + * or part is invalid. + * + * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} + * @param index - index after the part + * + * @return the part of text after that index, or null */ public String getAfterIndex(int part, int index) throws NotImplementedException @@ -354,12 +365,13 @@ public abstract class JTextComponent extends JComponent } /** - * Returns the section of text before the index, or null if the index or part - * is invalid. - * - * @param part {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} - * @param index the 0-based character index - * @return the selection of text before that index, or null + * Gets the text located before index. null is returned if the index + * or part is invalid. + * + * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} + * @param index - index before the part + * + * @return the part of text before that index, or null */ public String getBeforeIndex(int part, int index) throws NotImplementedException @@ -368,10 +380,10 @@ public abstract class JTextComponent extends JComponent } /** - * Get the number possible actions for this object, with the zeroth - * representing the default action. + * Returns the number of actions for this object. The zero-th + * object represents the default action. * - * @return the 0-based number of actions + * @return the number of actions (0-based). */ public int getAccessibleActionCount() throws NotImplementedException @@ -380,11 +392,12 @@ public abstract class JTextComponent extends JComponent } /** - * Get a description for the specified action. Returns null if out of - * bounds. + * Returns the description of the i-th action. Null is returned if + * i is out of bounds. * - * @param i the action to describe, 0-based - * @return description of the action + * @param i - the action to get the description for + * + * @return description of the i-th action */ public String getAccessibleActionDescription(int i) throws NotImplementedException @@ -394,10 +407,12 @@ public abstract class JTextComponent extends JComponent } /** - * Perform the specified action. Does nothing if out of bounds. + * Performs the i-th action. Nothing happens if i is + * out of bounds. * - * @param i the action to perform, 0-based - * @return true if the action was performed + * @param i - the action to perform + * + * @return true if the action was performed successfully */ public boolean doAccessibleAction(int i) throws NotImplementedException @@ -406,9 +421,9 @@ public abstract class JTextComponent extends JComponent } /** - * Set the text contents to the given string. + * Sets the text contents. * - * @param s the new text + * @param s - the new text contents. */ public void setTextContents(String s) throws NotImplementedException @@ -417,10 +432,10 @@ public abstract class JTextComponent extends JComponent } /** - * Inserts the given string at the specified location. + * Inserts the text at the given index. * - * @param index the index for insertion - * @param s the new text + * @param index - the index to insert the new text at. + * @param s - the new text */ public void insertTextAtIndex(int index, String s) throws NotImplementedException @@ -429,10 +444,10 @@ public abstract class JTextComponent extends JComponent } /** - * Return the text between two points. + * Gets the text between two indexes. * - * @param start the start position, inclusive - * @param end the end position, exclusive + * @param start - the starting index (inclusive) + * @param end - the ending index (exclusive) */ public String getTextRange(int start, int end) { @@ -447,10 +462,10 @@ public abstract class JTextComponent extends JComponent } /** - * Delete the text between two points. + * Deletes the text between two indexes. * - * @param start the start position, inclusive - * @param end the end position, exclusive + * @param start - the starting index (inclusive) + * @param end - the ending index (exclusive) */ public void delete(int start, int end) { @@ -458,10 +473,11 @@ public abstract class JTextComponent extends JComponent } /** - * Cut the text between two points to the system clipboard. + * Cuts the text between two indexes. The text is put + * into the system clipboard. * - * @param start the start position, inclusive - * @param end the end position, exclusive + * @param start - the starting index (inclusive) + * @param end - the ending index (exclusive) */ public void cut(int start, int end) { @@ -470,9 +486,9 @@ public abstract class JTextComponent extends JComponent } /** - * Paste the text from the system clipboard at the given index. + * Pastes the text from the system clipboard to the given index. * - * @param start the start position + * @param start - the starting index */ public void paste(int start) { @@ -481,11 +497,12 @@ public abstract class JTextComponent extends JComponent } /** - * Replace the text between two points with the given string. + * Replaces the text between two indexes with the given text. * - * @param start the start position, inclusive - * @param end the end position, exclusive - * @param s the string to paste + * + * @param start - the starting index (inclusive) + * @param end - the ending index (exclusive) + * @param s - the text to paste */ public void replaceText(int start, int end, String s) { @@ -494,10 +511,10 @@ public abstract class JTextComponent extends JComponent } /** - * Select the text between two points. + * Selects the text between two indexes. * - * @param start the start position, inclusive - * @param end the end position, exclusive + * @param start - the starting index (inclusive) + * @param end - the ending index (exclusive) */ public void selectText(int start, int end) { @@ -505,11 +522,11 @@ public abstract class JTextComponent extends JComponent } /** - * Set the attributes of text between two points. + * Sets the attributes of all the text between two indexes. * - * @param start the start position, inclusive - * @param end the end position, exclusive - * @param s the new attribute set for the range + * @param start - the starting index (inclusive) + * @param end - the ending index (exclusive) + * @param s - the new attribute set for the text in the range */ public void setAttributes(int start, int end, AttributeSet s) throws NotImplementedException @@ -1163,8 +1180,19 @@ public abstract class JTextComponent extends JComponent public void setDocument(Document newDoc) { Document oldDoc = doc; - doc = newDoc; - firePropertyChange("document", oldDoc, newDoc); + try + { + if (oldDoc instanceof AbstractDocument) + ((AbstractDocument) oldDoc).readLock(); + + doc = newDoc; + firePropertyChange("document", oldDoc, newDoc); + } + finally + { + if (oldDoc instanceof AbstractDocument) + ((AbstractDocument) oldDoc).readUnlock(); + } revalidate(); repaint(); } @@ -1641,10 +1669,12 @@ public abstract class JTextComponent extends JComponent public boolean getScrollableTracksViewportWidth() { - if (getParent() instanceof JViewport) - return getParent().getWidth() > getPreferredSize().width; + boolean res = false;; + Container c = getParent(); + if (c instanceof JViewport) + res = ((JViewport) c).getExtentSize().width > getPreferredSize().width; - return false; + return res; } /** diff --git a/libjava/classpath/javax/swing/text/ParagraphView.java b/libjava/classpath/javax/swing/text/ParagraphView.java index 15bed781825..c4857863d35 100644 --- a/libjava/classpath/javax/swing/text/ParagraphView.java +++ b/libjava/classpath/javax/swing/text/ParagraphView.java @@ -74,6 +74,39 @@ public class ParagraphView extends FlowView implements TabExpander return align; } + /** + * Allows rows to span the whole parent view. + */ + public float getMaximumSpan(int axis) + { + float max; + if (axis == X_AXIS) + max = Float.MAX_VALUE; + else + max = super.getMaximumSpan(axis); + return max; + } + + /** + * Overridden because child views are not necessarily laid out in model + * order. + */ + protected int getViewIndexAtPosition(int pos) + { + int index = -1; + if (pos >= getStartOffset() && pos < getEndOffset()) + { + int nviews = getViewCount(); + for (int i = 0; i < nviews && index == -1; i++) + { + View child = getView(i); + if (pos >= child.getStartOffset() && pos < child.getEndOffset()) + index = i; + } + } + return index; + } + protected void loadChildren(ViewFactory vf) { // Do nothing here. The children are added while layouting. @@ -140,7 +173,7 @@ public class ParagraphView extends FlowView implements TabExpander { float align; if (axis == X_AXIS) - align = super.getAlignment(axis); + align = 0.5F; else if (getViewCount() > 0) { float prefHeight = getPreferredSpan(Y_AXIS); @@ -148,7 +181,7 @@ public class ParagraphView extends FlowView implements TabExpander align = (firstRowHeight / 2.F) / prefHeight; } else - align = 0.0F; + align = 0.5F; return align; } diff --git a/libjava/classpath/javax/swing/text/PlainDocument.java b/libjava/classpath/javax/swing/text/PlainDocument.java index c699dcad2aa..730a619da9f 100644 --- a/libjava/classpath/javax/swing/text/PlainDocument.java +++ b/libjava/classpath/javax/swing/text/PlainDocument.java @@ -56,8 +56,12 @@ public class PlainDocument extends AbstractDocument public static final String lineLimitAttribute = "lineLimit"; public static final String tabSizeAttribute = "tabSize"; - private BranchElement rootElement; - private int tabSize; + /** + * The default root element of this document. This is made type Element + * because the RI seems to accept other types of elements as well from + * createDefaultRoot() (when overridden by a subclass). + */ + private Element rootElement; public PlainDocument() { @@ -67,8 +71,10 @@ public class PlainDocument extends AbstractDocument public PlainDocument(AbstractDocument.Content content) { super(content); - tabSize = 8; - rootElement = (BranchElement) createDefaultRoot(); + rootElement = createDefaultRoot(); + + // This property has been determined using a Mauve test. + putProperty("tabSize", new Integer(8)); } private void reindex() @@ -105,10 +111,10 @@ public class PlainDocument extends AbstractDocument protected AbstractDocument.AbstractElement createDefaultRoot() { BranchElement root = - (BranchElement) createBranchElement(null, SimpleAttributeSet.EMPTY); + (BranchElement) createBranchElement(null, null); Element[] array = new Element[1]; - array[0] = createLeafElement(root, SimpleAttributeSet.EMPTY, 0, 1); + array[0] = createLeafElement(root, null, 0, 1); root.replace(0, 0, array); return root; @@ -117,116 +123,97 @@ public class PlainDocument extends AbstractDocument protected void insertUpdate(DefaultDocumentEvent event, AttributeSet attributes) { + + String text = null; int offset = event.getOffset(); - int eventLength = event.getLength(); - int end = offset + event.getLength(); - int oldElementIndex, elementIndex = rootElement.getElementIndex(offset); - Element firstElement = rootElement.getElement(elementIndex); - oldElementIndex = elementIndex; - - // If we're inserting immediately after a newline we have to fix the - // Element structure (but only if we are dealing with a line which - // has not existed as Element before). - if (offset > 0 && firstElement.getStartOffset() != offset) + int length = event.getLength(); + try { - try - { - String s = getText(offset - 1, 1); - if (s.equals("\n") ) - { - int newEl2EndOffset = end; - boolean replaceNext = false; - if (rootElement.getElementCount() > elementIndex + 1) - { - replaceNext = true; - newEl2EndOffset = - rootElement.getElement(elementIndex + 1).getEndOffset(); - } - Element newEl1 = - createLeafElement(rootElement, firstElement.getAttributes(), - firstElement.getStartOffset(), offset); - Element newEl2 = - createLeafElement (rootElement, firstElement.getAttributes(), - offset, newEl2EndOffset); - if (replaceNext) - rootElement.replace(elementIndex, 2, new Element[] { newEl1, newEl2 }); - else - rootElement.replace(elementIndex, 1, new Element[] { newEl1, newEl2 }); - firstElement = newEl2; - elementIndex ++; - } - } - catch (BadLocationException ble) - { - // This shouldn't happen. - AssertionError ae = new AssertionError(); - ae.initCause(ble); - throw ae; - } + text = getText(offset, length); + } + catch (BadLocationException ex) + { + AssertionError err = new AssertionError(); + err.initCause(ex); + throw err; } - // added and removed are Element arrays used to add an ElementEdit - // to the DocumentEvent if there were entire lines added or removed. - Element[] removed = new Element[1]; - Element[] added; - try + boolean hasLineBreak = text.indexOf('\n') != -1; + boolean prevCharIsLineBreak = false; + try { - String str = content.getString(offset, eventLength); - ArrayList elts = new ArrayList(); + prevCharIsLineBreak = + offset > 0 && getText(offset - 1, 1).charAt(0) == '\n'; + } + catch (BadLocationException ex) + { + AssertionError err = new AssertionError(); + err.initCause(ex); + throw err; + } + boolean lastCharIsLineBreak = text.charAt(text.length() - 1) == '\n'; + int lineIndex = -1; + int lineStart = -1; + int lineEnd = -1; + Element[] removed = null; + BranchElement root = (BranchElement) rootElement; + boolean updateStructure = true; - // Determine how many NEW lines were added by finding the newline - // characters within the newly inserted text - int j = firstElement.getStartOffset(); - int i = str.indexOf('\n', 0); - int contentLength = content.length(); - - while (i != -1 && i <= eventLength) - { - // For each new line, create a new element - elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, - j, offset + i + 1)); - - j = offset + i + 1; - if (j >= contentLength) - break; - i = str.indexOf('\n', i + 1); - } + if (prevCharIsLineBreak && ! lastCharIsLineBreak) + { + // We must fix the structure a little if the previous char + // is a linebreak and the last char isn't. + lineIndex = root.getElementIndex(offset - 1); + Element prevLine = root.getElement(lineIndex); + Element nextLine = root.getElement(lineIndex + 1); + lineStart = prevLine.getStartOffset(); + lineEnd = nextLine.getEndOffset(); + removed = new Element[]{ prevLine, nextLine }; + } + else if (hasLineBreak) + { + lineIndex = root.getElementIndex(offset); + Element line = root.getElement(lineIndex); + lineStart = line.getStartOffset(); + lineEnd = line.getEndOffset(); + removed = new Element[]{ line }; + } + else + { + updateStructure = false; + } - // If there were new lines added we have to add an ElementEdit to - // the DocumentEvent and we have to call rootElement.replace to - // insert the new lines - if (elts.size() != 0) + if (updateStructure) + { + // Break the lines between lineStart and lineEnd. + ArrayList lines = new ArrayList(); + int len = lineEnd - lineStart; + try { - // If we have created new lines test whether there are remaining - // characters in firstElement after the inserted text and if so - // create a new element for them. - if (j < firstElement.getEndOffset()) - elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, j, firstElement.getEndOffset())); - - // Set up the ElementEdit by filling the added and removed - // arrays with the proper Elements - added = new Element[elts.size()]; - elts.toArray(added); - - removed[0] = firstElement; - - // Now create and add the ElementEdit - ElementEdit e = new ElementEdit(rootElement, elementIndex, removed, - added); - event.addEdit(e); - - // And call replace to actually make the changes - ((BranchElement) rootElement).replace(elementIndex, 1, added); + text = getText(lineStart, len); } + catch (BadLocationException ex) + { + AssertionError err = new AssertionError(); + err.initCause(ex); + throw err; + } + int prevLineBreak = 0; + int lineBreak = text.indexOf('\n'); + do + { + lineBreak++; + lines.add(createLeafElement(root, null, lineStart + prevLineBreak, + lineStart + lineBreak)); + prevLineBreak = lineBreak; + lineBreak = text.indexOf('\n', prevLineBreak); + } while (prevLineBreak < len); + + // Update the element structure and prepare document event. + Element[] added = (Element[]) lines.toArray(new Element[lines.size()]); + event.addEdit(new ElementEdit(root, lineIndex, removed, added)); + root.replace(lineIndex, removed.length, added); } - catch (BadLocationException e) - { - // This shouldn't happen so we throw an AssertionError - AssertionError ae = new AssertionError(); - ae.initCause(e); - throw ae; - } - super.insertUpdate(event, attributes); } @@ -264,7 +251,7 @@ public class PlainDocument extends AbstractDocument event.addEdit(e); // collapse elements if the removal spans more than 1 line - rootElement.replace(i1, i2 - i1 + 1, added); + ((BranchElement) rootElement).replace(i1, i2 - i1 + 1, added); } } diff --git a/libjava/classpath/javax/swing/text/Segment.java b/libjava/classpath/javax/swing/text/Segment.java index d2364e05a10..63c5fa09dbc 100644 --- a/libjava/classpath/javax/swing/text/Segment.java +++ b/libjava/classpath/javax/swing/text/Segment.java @@ -165,8 +165,9 @@ public class Segment implements Cloneable, CharacterIterator /** * Sets the current index to point to the last character in the segment and - * returns that character. If the segment contains zero characters, this - * method returns {@link #DONE}. + * returns that character. If the segment contains zero characters, the + * current index is set to {@link #getEndIndex()} and this method returns + * {@link #DONE}. * * @return The last character in the segment, or {@link #DONE} if the * segment contains zero characters. @@ -174,7 +175,10 @@ public class Segment implements Cloneable, CharacterIterator public char last() { if (count == 0) - return DONE; + { + current = getEndIndex(); + return DONE; + } current = getEndIndex() - 1; return array[current]; diff --git a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java index 8dbcb0c6a14..8684ef87d34 100644 --- a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java +++ b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java @@ -51,8 +51,10 @@ public class SimpleAttributeSet /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 8267656273837665219L; - /** An empty attribute set. */ - public static final AttributeSet EMPTY = new SimpleAttributeSet(); + /** + * An empty attribute set. + */ + public static final AttributeSet EMPTY = new EmptyAttributeSet(); /** Storage for the attributes. */ Hashtable tab; diff --git a/libjava/classpath/javax/swing/text/StringContent.java b/libjava/classpath/javax/swing/text/StringContent.java index 0a31505f3a6..8014dc3bce6 100644 --- a/libjava/classpath/javax/swing/text/StringContent.java +++ b/libjava/classpath/javax/swing/text/StringContent.java @@ -178,11 +178,13 @@ public final class StringContent } /** - * Creates a new instance containing the string "\n". + * Creates a new instance containing the string "\n". This is equivalent + * to calling {@link #StringContent(int)} with an <code>initialLength</code> + * of 10. */ public StringContent() { - this(1); + this(10); } /** diff --git a/libjava/classpath/javax/swing/text/TabSet.java b/libjava/classpath/javax/swing/text/TabSet.java index ecad9444ea5..0f2c8c7c1ee 100644 --- a/libjava/classpath/javax/swing/text/TabSet.java +++ b/libjava/classpath/javax/swing/text/TabSet.java @@ -1,5 +1,5 @@ /* TabSet.java -- - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006, Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,23 +39,54 @@ package javax.swing.text; import java.io.Serializable; +/** + * A set of tab stops. Instances of this class are immutable. + */ public class TabSet implements Serializable { /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 2367703481999080593L; + /** Storage for the tab stops. */ TabStop[] tabs; + /** + * Creates a new <code>TabSet</code> containing the specified tab stops. + * + * @param t the tab stops (<code>null</code> permitted). + */ public TabSet(TabStop[] t) { - tabs = t; + if (t != null) + tabs = (TabStop[]) t.clone(); + else + tabs = new TabStop[0]; } + /** + * Returns the tab stop with the specified index. + * + * @param i the index. + * + * @return The tab stop. + * + * @throws IllegalArgumentException if <code>i</code> is not in the range + * <code>0</code> to <code>getTabCount() - 1</code>. + */ public TabStop getTab(int i) { + if (i < 0 || i >= tabs.length) + throw new IllegalArgumentException("Index out of bounds."); return tabs[i]; } + /** + * Returns the tab following the specified location. + * + * @param location the location. + * + * @return The tab following the specified location (or <code>null</code>). + */ public TabStop getTabAfter(float location) { int idx = getTabIndexAfter(location); @@ -65,11 +96,23 @@ public class TabSet implements Serializable return tabs[idx]; } + /** + * Returns the number of tab stops in this tab set. + * + * @return The number of tab stops in this tab set. + */ public int getTabCount() { return tabs.length; } + /** + * Returns the index of the specified tab, or -1 if the tab is not found. + * + * @param tab the tab (<code>null</code> permitted). + * + * @return The index of the specified tab, or -1. + */ public int getTabIndex(TabStop tab) { for (int i = 0; i < tabs.length; ++i) @@ -78,28 +121,88 @@ public class TabSet implements Serializable return -1; } + /** + * Returns the index of the tab at or after the specified location. + * + * @param location the tab location. + * + * @return The index of the tab stop, or -1. + */ public int getTabIndexAfter(float location) { - int idx = -1; - for (int i = 0; i < tabs.length; ++i) + for (int i = 0; i < tabs.length; i++) + { + if (location <= tabs[i].getPosition()) + return i; + } + return -1; + } + + /** + * Tests this <code>TabSet</code> for equality with an arbitrary object. + * + * @param obj the object (<code>null</code> permitted). + * + * @return <code>true</code> if this <code>TabSet</code> is equal to + * <code>obj</code>, and <code>false</code> otherwise. + * + * @since 1.5 + */ + public boolean equals(Object obj) + { + if (obj == this) + return true; + if (!(obj instanceof TabSet)) + return false; + TabSet that = (TabSet) obj; + int tabCount = getTabCount(); + if (tabCount != that.getTabCount()) + return false; + for (int i = 0; i < tabCount; i++) + { + if (!this.getTab(i).equals(that.getTab(i))) + return false; + } + return true; + } + + /** + * Returns a hash code for this <code>TabSet</code>. + * + * @return A hash code. + * + * @since 1.5 + */ + public int hashCode() + { + // this hash code won't match Sun's, but that shouldn't matter... + int result = 193; + int tabs = getTabCount(); + for (int i = 0; i < tabs; i++) { - if (location < tabs[i].getPosition()) - idx = i; + TabStop t = getTab(i); + if (t != null) + result = 37 * result + t.hashCode(); } - return idx; + return result; } + /** + * Returns a string representation of this <code>TabSet</code>. + * + * @return A string representation of this <code>TabSet</code>. + */ public String toString() { StringBuffer sb = new StringBuffer(); - sb.append("["); + sb.append("[ "); for (int i = 0; i < tabs.length; ++i) { if (i != 0) sb.append(" - "); sb.append(tabs[i].toString()); } - sb.append("]"); + sb.append(" ]"); return sb.toString(); } } diff --git a/libjava/classpath/javax/swing/text/TabStop.java b/libjava/classpath/javax/swing/text/TabStop.java index 56f862fdae4..f4c3f851406 100644 --- a/libjava/classpath/javax/swing/text/TabStop.java +++ b/libjava/classpath/javax/swing/text/TabStop.java @@ -1,5 +1,5 @@ -/* TabSet.java -- - Copyright (C) 2004 Free Software Foundation, Inc. +/* TabStop.java -- + Copyright (C) 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,6 +39,9 @@ package javax.swing.text; import java.io.Serializable; +/** + * Represents a tab position in some text. + */ public class TabStop implements Serializable { /** The serialization UID (compatible with JDK1.5). */ @@ -61,18 +64,42 @@ public class TabStop implements Serializable int align; int leader; + /** + * Creates a new <code>TabStop</code> for the specified tab position. + * + * @param pos the tab position. + */ public TabStop(float pos) { this(pos, ALIGN_LEFT, LEAD_NONE); } + /** + * Creates a new <code>TabStop</code> with the specified attributes. + * + * @param pos the tab position. + * @param align the alignment (one of {@link #ALIGN_LEFT}, + * {@link #ALIGN_CENTER}, {@link #ALIGN_RIGHT}, {@link #ALIGN_DECIMAL} + * or {@link #ALIGN_BAR}). + * @param leader the leader (one of {@link #LEAD_NONE}, {@link #LEAD_DOTS}, + * {@link #LEAD_EQUALS}, {@link #LEAD_HYPHENS}, {@link #LEAD_THICKLINE} + * or {@link #LEAD_UNDERLINE}). + */ public TabStop(float pos, int align, int leader) { this.pos = pos; this.align = align; this.leader = leader; } - + + /** + * Tests this <code>TabStop</code> for equality with an arbitrary object. + * + * @param other the other object (<code>null</code> permitted). + * + * @return <code>true</code> if this <code>TabStop</code> is equal to + * the specified object, and <code>false</code> otherwise. + */ public boolean equals(Object other) { return (other != null) @@ -82,34 +109,60 @@ public class TabStop implements Serializable && (((TabStop)other).getAlignment() == this.getAlignment()); } + /** + * Returns the tab alignment. This should be one of {@link #ALIGN_LEFT}, + * {@link #ALIGN_CENTER}, {@link #ALIGN_RIGHT}, {@link #ALIGN_DECIMAL} or + * {@link #ALIGN_BAR}. + * + * @return The tab alignment. + */ public int getAlignment() { return align; } + /** + * Returns the leader type. This should be one of {@link #LEAD_NONE}, + * {@link #LEAD_DOTS}, {@link #LEAD_EQUALS}, {@link #LEAD_HYPHENS}, + * {@link #LEAD_THICKLINE} or {@link #LEAD_UNDERLINE}. + * + * @return The leader type. + */ public int getLeader() { return leader; } + /** + * Returns the tab position. + * + * @return The tab position. + */ public float getPosition() { return pos; } + /** + * Returns a hash code for this <code>TabStop</code>. + * + * @return A hash code. + */ public int hashCode() { return (int) pos + (int) leader + (int) align; } + /** + * Returns a string describing this <code>TabStop</code>. + * + * @return A string describing this <code>TabStop</code>. + */ public String toString() { String prefix = ""; switch (align) { - case ALIGN_LEFT: - prefix = "left "; - break; case ALIGN_RIGHT: prefix = "right "; break; @@ -130,7 +183,8 @@ public class TabStop implements Serializable break; } - return (prefix + "tab @" + pos + ((leader == LEAD_NONE) ? "" : "(w/leaders)")); + return prefix + "tab @" + pos + + ((leader == LEAD_NONE) ? "" : " (w/leaders)"); } } diff --git a/libjava/classpath/javax/swing/text/View.java b/libjava/classpath/javax/swing/text/View.java index d8ad5f5858e..55a63f6b668 100644 --- a/libjava/classpath/javax/swing/text/View.java +++ b/libjava/classpath/javax/swing/text/View.java @@ -401,7 +401,10 @@ public abstract class View implements SwingConstants Element el = getElement(); DocumentEvent.ElementChange ec = ev.getChange(el); if (ec != null) - updateChildren(ec, ev, vf); + { + if (! updateChildren(ec, ev, vf)) + ec = null; + } forwardUpdate(ec, ev, shape, vf); updateLayout(ec, ev, shape); } @@ -493,27 +496,66 @@ public abstract class View implements SwingConstants int count = getViewCount(); if (count > 0) { + // Determine start index. int startOffset = ev.getOffset(); - int endOffset = startOffset + ev.getLength(); int startIndex = getViewIndex(startOffset, Position.Bias.Backward); - int endIndex = getViewIndex(endOffset, Position.Bias.Forward); - int index = -1; - int addLength = -1; - if (ec != null) + + // For REMOVE events we have to forward the event to the last element, + // for the case that an Element has been removed that represente + // the offset. + if (startIndex == -1 && ev.getType() == DocumentEvent.EventType.REMOVE + && startOffset >= getEndOffset()) { - index = ec.getIndex(); - addLength = ec.getChildrenAdded().length; + startIndex = getViewCount() - 1; } - if (startIndex >= 0 && endIndex >= 0) + // When startIndex is on a view boundary, forward event to the + // previous view too. + if (startIndex >= 0) { - for (int i = startIndex; i <= endIndex; i++) + View v = getView(startIndex); + if (v != null) + { + if (v.getStartOffset() == startOffset && startOffset > 0) + startIndex = Math.max(0, startIndex - 1); + } + } + startIndex = Math.max(0, startIndex); + + // Determine end index. + int endIndex = startIndex; + if (ev.getType() != DocumentEvent.EventType.REMOVE) + { + endIndex = getViewIndex(startOffset + ev.getLength(), + Position.Bias.Forward); + if (endIndex < 0) + endIndex = getViewCount() - 1; + } + + // Determine hole that comes from added elements (we don't forward + // the event to newly added views. + int startAdded = endIndex + 1; + int endAdded = startAdded; + Element[] added = (ec != null) ? ec.getChildrenAdded() : null; + if (added != null && added.length > 0) + { + startAdded = ec.getIndex(); + endAdded = startAdded + added.length - 1; + } + + // Forward event to all views between startIndex and endIndex, + // and leave out all views in the hole. + for (int i = startIndex; i <= endIndex; i++) + { + // Skip newly added child views. + if (! (i >= startAdded && i <= endAdded)) { - // Skip newly added child views. - if (index >= 0 && i >= index && i < (index+addLength)) - continue; View child = getView(i); - forwardUpdateToView(child, ev, shape, vf); + if (child != null) + { + Shape childAlloc = getChildAllocation(i, shape); + forwardUpdateToView(child, ev, childAlloc, vf); + } } } } @@ -611,9 +653,46 @@ public abstract class View implements SwingConstants if (b2 != Position.Bias.Forward && b2 != Position.Bias.Backward) throw new IllegalArgumentException ("b2 must be either Position.Bias.Forward or Position.Bias.Backward"); - Rectangle s1 = (Rectangle) modelToView(p1, a, b1); - Rectangle s2 = (Rectangle) modelToView(p2, a, b2); - return SwingUtilities.computeUnion(s1.x, s1.y, s1.width, s1.height, s2); + + Shape s1 = modelToView(p1, a, b1); + // Special case for p2 == end index. + Shape s2; + if (p2 != getEndOffset()) + { + s2 = modelToView(p2, a, b2); + } + else + { + try + { + s2 = modelToView(p2, a, b2); + } + catch (BadLocationException ex) + { + // Assume the end rectangle to be at the right edge of the + // view. + Rectangle aRect = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + s2 = new Rectangle(aRect.x + aRect.width - 1, aRect.y, 1, + aRect.height); + } + } + + // Need to modify the rectangle, so we create a copy in all cases. + Rectangle r1 = s1.getBounds(); + Rectangle r2 = s2 instanceof Rectangle ? (Rectangle) s2 + : s2.getBounds(); + + // For multiline view, let the resulting rectangle span the whole view. + if (r1.y != r2.y) + { + Rectangle aRect = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + r1.x = aRect.x; + r1.width = aRect.width; + } + + return SwingUtilities.computeUnion(r2.x, r2.y, r2.width, r2.height, r1); } /** diff --git a/libjava/classpath/javax/swing/text/html/HTMLTableView.java b/libjava/classpath/javax/swing/text/html/BRView.java index cac44d8dc27..5521fed8edf 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLTableView.java +++ b/libjava/classpath/javax/swing/text/html/BRView.java @@ -1,4 +1,4 @@ -/* HTMLTableView.java -- A table view for HTML tables +/* BRView.java -- HTML BR tag view Copyright (C) 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,44 +39,33 @@ exception statement from your version. */ package javax.swing.text.html; import javax.swing.text.Element; -import javax.swing.text.TableView; -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) + * Handled the HTML BR tag. */ -class HTMLTableView - extends TableView -{ +class BRView + extends NullView +{ /** - * Creates a new HTMLTableView for the specified element. - * - * @param el the element for the table view + * Creates the new BR view. + * + * @param elem the HTML element, representing the view. */ - public HTMLTableView(Element el) + public BRView(Element elem) { - super(el); + super(elem); } - + /** - * Loads the children of the Table. This completely bypasses the ViewFactory - * and creates instances of TableRow instead. - * - * @param vf ignored + * Always return ForcedBreakWeight for the X_AXIS, BadBreakWeight for the + * Y_AXIS. */ - protected void loadChildren(ViewFactory vf) + public int getBreakWeight(int axis, float pos, float len) { - Element el = getElement(); - int numChildren = el.getElementCount(); - View[] rows = new View[numChildren]; - for (int i = 0; i < numChildren; ++i) - { - rows[i] = createTableRow(el.getElement(i)); - } - replace(0, getViewCount(), rows); + if (axis == X_AXIS) + return ForcedBreakWeight; + else + return BadBreakWeight; } } diff --git a/libjava/classpath/javax/swing/text/html/HRuleView.java b/libjava/classpath/javax/swing/text/html/HRuleView.java new file mode 100644 index 00000000000..3bae5eb8e83 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/HRuleView.java @@ -0,0 +1,189 @@ +/* HRuleView.java -- Horizontal dash in HTML documents. + 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.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; + +import javax.swing.text.Element; +import javax.swing.text.View; + +/** + * Represents the long horizontal separating dash that can be inserted into the + * HTML documents with HR tag. + */ +class HRuleView extends InlineView +{ + /** + * The null view, indicating, that nothing should be painted ahead the + * breaking point. + */ + View nullView; + + /** + * The height of the horizontal dash area. + */ + static int HEIGHT = 4; + + /** + * The imaginary invisible view that stays after end of line after the + * breaking procedure. It occupies on character. + */ + class Beginning extends NullView + { + /** + * The break offset that becomes the views start offset. + */ + int breakOffset; + + /** + * Return the end offset that is always one char after the break offset. + */ + public int getEndOffset() + { + return breakOffset + 1; + } + + /** + * Return the start offset that has been passed in a constructor. + */ + public int getStartOffset() + { + return breakOffset; + } + + /** + * Create the new instance of this view. + * + * @param element the element (inherited from the HR view) + * @param offset the position where the HR view has been broken + */ + public Beginning(Element element, int offset) + { + super(element); + breakOffset = offset; + } + } + + /** + * Creates the new HR view. + */ + public HRuleView(Element element) + { + super(element); + } + + /** + * Returns the ForcedBreakWeight for the vertical axis, indicating, the the + * view must be broken to be displayed correctly. The horizontal dash is + * not breakeable along the Y axis. + */ + public int getBreakWeight(int axis, float pos, float len) + { + if (axis == X_AXIS && ((getEndOffset() - getStartOffset()) > 1)) + return ForcedBreakWeight; + else + return BadBreakWeight; + } + + /** + * Draws the double line, upped black and the lower light gray. + */ + public void paint(Graphics g, Shape a) + { + Rectangle bounds = a.getBounds(); + + int x = bounds.x; + int y = bounds.y; + + int w = bounds.x + bounds.width; + + // We move "half pixel up" from the actual horizontal position - + // this will be rounded to the closest actual int co-ordinate. + int h = bounds.y + (int) Math.round(bounds.height * 0.5 - 0.5); + + g.setColor(Color.black); + g.drawLine(x, y++, w, h++); + g.setColor(Color.lightGray); + g.drawLine(x, y, w, h); + } + + /** + * Break the view into this view and the invisible imaginary view that + * stays on the end of line that is broken by HR dash. The view is broken + * only if its length is longer than one (the two characters are expected + * in the initial length). + */ + public View breakView(int axis, int offset, float pos, float len) + { + if (getEndOffset() - getStartOffset() > 1) + return new Beginning(getElement(), offset); + else + return this; + } + + /** + * Returns the width of the container for the horizontal axis and the + * thickness of the dash area for the vertical axis. + */ + public float getMaximumSpan(int axis) + { + if (axis == X_AXIS) + { + Component container = getContainer(); + if (container != null) + return getContainer().getWidth(); + else + return 640; + } + else + return HEIGHT; + } + + /** + * Returns the same values as {@link #getMaximumSpan(int)} + */ + public float getPreferredSpan(int axis) + { + return getMaximumSpan(axis); + } +} diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java index e714a857b61..0bfc338df45 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java +++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java @@ -40,14 +40,18 @@ package javax.swing.text.html; import gnu.classpath.NotImplementedException; import gnu.javax.swing.text.html.CharacterAttributeTranslator; +import gnu.javax.swing.text.html.parser.htmlAttributeSet; import java.io.IOException; +import java.io.StringReader; import java.net.URL; import java.util.HashMap; import java.util.Stack; import java.util.Vector; import javax.swing.JEditorPane; +import javax.swing.event.DocumentEvent; +import javax.swing.event.HyperlinkEvent.EventType; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; @@ -515,19 +519,23 @@ public class HTMLDocument extends DefaultStyledDocument */ public class HTMLReader extends HTMLEditorKit.ParserCallback { - /** Holds the current character attribute set **/ + /** + * Holds the current character attribute set * + */ protected MutableAttributeSet charAttr = new SimpleAttributeSet(); protected Vector parseBuffer = new Vector(); - /** A stack for character attribute sets **/ + /** + * A stack for character attribute sets * + */ Stack charAttrStack = new Stack(); /** * The parse stack. This stack holds HTML.Tag objects that reflect the * current position in the parsing process. */ - private Stack parseStack = new Stack(); + Stack parseStack = new Stack(); /** A mapping between HTML.Tag objects and the actions that handle them **/ HashMap tagToAction; @@ -535,10 +543,31 @@ public class HTMLDocument extends DefaultStyledDocument /** Tells us whether we've received the '</html>' tag yet **/ boolean endHTMLEncountered = false; - /** Variables related to the constructor with explicit insertTag **/ - int popDepth, pushDepth, offset; + /** + * Related to the constructor with explicit insertTag + */ + int popDepth; + + /** + * Related to the constructor with explicit insertTag + */ + int pushDepth; + + /** + * Related to the constructor with explicit insertTag + */ + int offset; + + /** + * The tag (inclusve), after that the insertion should start. + */ HTML.Tag insertTag; - boolean insertTagEncountered = false; + + /** + * This variable becomes true after the insert tag has been encountered. + */ + boolean insertTagEncountered; + /** A temporary variable that helps with the printing out of debug information **/ boolean debug = false; @@ -1139,8 +1168,21 @@ public class HTMLDocument extends DefaultStyledDocument } /** - * This method is called by the parser and should route the call to - * the proper handler for the tag. + * Checks if the HTML tag should be inserted. The tags before insert tag (if + * specified) are not inserted. Also, the tags after the end of the html are + * not inserted. + * + * @return true if the tag should be inserted, false otherwise. + */ + private boolean shouldInsert() + { + return ! endHTMLEncountered + && (insertTagEncountered || insertTag == null); + } + + /** + * This method is called by the parser and should route the call to the + * proper handler for the tag. * * @param t the HTML.Tag * @param a the attribute set @@ -1148,13 +1190,15 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - TagAction action = (TagAction) tagToAction.get(t); - if (action != null) - action.start(t, a); + if (t == insertTag) + insertTagEncountered = true; + + if (shouldInsert()) + { + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.start(t, a); + } } /** @@ -1165,42 +1209,41 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleComment(char[] data, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); - if (action != null) + if (shouldInsert()) { - action.start(HTML.Tag.COMMENT, new SimpleAttributeSet()); - action.end (HTML.Tag.COMMENT); + TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); + if (action != null) + { + action.start(HTML.Tag.COMMENT, + htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET); + action.end(HTML.Tag.COMMENT); + } } } /** - * This method is called by the parser and should route the call to - * the proper handler for the tag. + * This method is called by the parser and should route the call to the + * proper handler for the tag. * * @param t the HTML.Tag * @param pos the position at which the tag was encountered */ public void handleEndTag(HTML.Tag t, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - // If this is the </html> tag we need to stop calling the Actions - if (t == HTML.Tag.HTML) - endHTMLEncountered = true; - - TagAction action = (TagAction) tagToAction.get(t); - if (action != null) - action.end(t); + if (shouldInsert()) + { + // If this is the </html> tag we need to stop calling the Actions + if (t == HTML.Tag.HTML) + endHTMLEncountered = true; + + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.end(t); + } } /** - * This is a callback from the parser that should be routed to the + * This is a callback from the parser that should be routed to the * appropriate handler for the tag. * * @param t the HTML.Tag that was encountered @@ -1209,15 +1252,17 @@ public class HTMLDocument extends DefaultStyledDocument */ public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) { - // Don't call the Action if we've already seen </html>. - if (endHTMLEncountered) - return; - - TagAction action = (TagAction) tagToAction.get (t); - if (action != null) + if (t == insertTag) + insertTagEncountered = true; + + if (shouldInsert()) { - action.start(t, a); - action.end(t); + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + { + action.start(t, a); + action.end(t); + } } } @@ -1230,7 +1275,6 @@ public class HTMLDocument extends DefaultStyledDocument * @since 1.3 */ public void handleEndOfLineString(String eol) - throws NotImplementedException { // FIXME: Implement. print ("HTMLReader.handleEndOfLineString not implemented yet"); @@ -1273,16 +1317,6 @@ public class HTMLDocument extends DefaultStyledDocument printBuffer(); DefaultStyledDocument.ElementSpec element; - // If the previous tag is content and the parent is p-implied, then - // we must also close the p-implied. - if (parseStack.size() > 0 && parseStack.peek() == HTML.Tag.IMPLIED) - { - element = new DefaultStyledDocument.ElementSpec(null, - DefaultStyledDocument.ElementSpec.EndTagType); - parseBuffer.addElement(element); - parseStack.pop(); - } - parseStack.push(t); AbstractDocument.AttributeContext ctx = getAttributeContext(); AttributeSet copy = attr.copyAttributes(); @@ -1320,16 +1354,6 @@ public class HTMLDocument extends DefaultStyledDocument new char[0], 0, 0); parseBuffer.add(element); } - // If the previous tag is content and the parent is p-implied, then - // we must also close the p-implied. - else if (parseStack.peek() == HTML.Tag.IMPLIED) - { - element = new DefaultStyledDocument.ElementSpec(null, - DefaultStyledDocument.ElementSpec.EndTagType); - parseBuffer.addElement(element); - if (parseStack.size() > 0) - parseStack.pop(); - } element = new DefaultStyledDocument.ElementSpec(null, DefaultStyledDocument.ElementSpec.EndTagType); @@ -1369,27 +1393,6 @@ public class HTMLDocument extends DefaultStyledDocument DefaultStyledDocument.ElementSpec element; AttributeSet attributes = null; - // Content must always be embedded inside a paragraph element, - // so we create this if the previous element is not one of - // <p>, <h1> .. <h6>. - boolean createImpliedParagraph = false; - HTML.Tag parent = (HTML.Tag) parseStack.peek(); - if (parent != HTML.Tag.P && parent != HTML.Tag.H1 - && parent != HTML.Tag.H2 - && parent != HTML.Tag.H3 && parent != HTML.Tag.H4 - && parent != HTML.Tag.H5 && parent != HTML.Tag.H6 - && parent != HTML.Tag.TD) - { - attributes = ctx.getEmptySet(); - attributes = ctx.addAttribute(attributes, - StyleConstants.NameAttribute, - HTML.Tag.IMPLIED); - element = new DefaultStyledDocument.ElementSpec(attributes, - DefaultStyledDocument.ElementSpec.StartTagType); - parseBuffer.add(element); - parseStack.push(HTML.Tag.IMPLIED); - } - // Copy the attribute set, don't use the same object because // it may change if (charAttr != null) @@ -1433,14 +1436,14 @@ public class HTMLDocument extends DefaultStyledDocument // Migrate from the rather htmlAttributeSet to the faster, lighter and // unchangeable alternative implementation. AttributeSet copy = a.copyAttributes(); - - // TODO: Figure out why we must always insert this single character - // (otherwise the element does not appear). Either fix or add explaining - // comment or at least report a normal bug. - DefaultStyledDocument.ElementSpec spec; - spec = new DefaultStyledDocument.ElementSpec(copy, - DefaultStyledDocument.ElementSpec.ContentType, - new char[] {' '}, 0, 1 ); + + // 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, + DefaultStyledDocument.ElementSpec.ContentType, + new char[] {' ', ' '}, 0, 2 ); parseBuffer.add(spec); } @@ -1481,7 +1484,61 @@ public class HTMLDocument extends DefaultStyledDocument HTML.Tag insertTag) { return new HTMLReader(pos, popDepth, pushDepth, insertTag); - } + } + + /** + * Gets the reader for the parser to use when inserting the HTML fragment into + * the document. Checks if the parser is present, sets the parent in the + * element stack and removes any actions for BODY (it can be only one body in + * a HTMLDocument). + * + * @param pos - the starting position + * @param popDepth - the number of EndTagTypes to generate before inserting + * @param pushDepth - the number of StartTagTypes with a direction of + * JoinNextDirection that should be generated before inserting, but + * after the end tags have been generated. + * @param insertTag - the first tag to start inserting into document + * @param parent the element that will be the parent in the document. HTML + * parsing includes checks for the parent, so it must be available. + * @return - the reader + * @throws IllegalStateException if the parsert is not set. + */ + public HTMLEditorKit.ParserCallback getInsertingReader(int pos, int popDepth, + int pushDepth, + HTML.Tag insertTag, + final Element parent) + throws IllegalStateException + { + if (parser == null) + throw new IllegalStateException("Parser has not been set"); + + HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth, insertTag) + { + /** + * Ignore BODY. + */ + public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + if (t != HTML.Tag.BODY) + super.handleStartTag(t, a, pos); + } + + /** + * Ignore BODY. + */ + public void handleEndTag(HTML.Tag t, int pos) + { + if (t != HTML.Tag.BODY) + super.handleEndTag(t, pos); + } + }; + + // Set the parent HTML tag. + reader.parseStack.push(parent.getAttributes().getAttribute( + StyleConstants.NameAttribute)); + + return reader; + } /** * Gets the child element that contains the attribute with the value or null. @@ -1490,8 +1547,8 @@ public class HTMLDocument extends DefaultStyledDocument * @param e - the element to begin search at * @param attribute - the desired attribute * @param value - the desired value - * @return the element found with the attribute and value specified or null - * if it is not found. + * @return the element found with the attribute and value specified or null if + * it is not found. */ public Element getElement(Element e, Object attribute, Object value) { @@ -1516,16 +1573,17 @@ public class HTMLDocument extends DefaultStyledDocument } /** - * Returns the element that has the given id Attribute. If it is not found, - * null is returned. This method works on an Attribute, not a character tag. - * This is not thread-safe. + * Returns the element that has the given id Attribute (for instance, <p id + * ='my paragraph >'). If it is not found, null is returned. The HTML tag, + * having this attribute, is not checked by this method and can be any. The + * method is not thread-safe. * - * @param attrId - the Attribute id to look for + * @param attrId - the value of the attribute id to look for * @return the element that has the given id. */ public Element getElement(String attrId) { - return getElement(getDefaultRootElement(), HTML.getAttributeKey(attrId), + return getElement(getDefaultRootElement(), HTML.Attribute.ID, attrId); } @@ -1542,22 +1600,30 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if an HTMLEditorKit.Parser has not been set */ public void setInnerHTML(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { if (elem.isLeaf()) throw new IllegalArgumentException("Element is a leaf"); - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("setInnerHTML not implemented"); + + int start = elem.getStartOffset(); + int end = elem.getEndOffset(); + + HTMLEditorKit.ParserCallback reader = getInsertingReader( + end, 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); + + // Remove the previous content + remove(start, end - start); } /** - * Replaces the given element in the parent with the string. When replacing - * a leaf, this will attempt to make sure there is a newline present if one is - * needed. This may result in an additional element being inserted. - * This will be seen as at least two events, n inserts followed by a remove. - * The HTMLEditorKit.Parser must be set. + * Replaces the given element in the parent with the string. When replacing a + * leaf, this will attempt to make sure there is a newline present if one is + * needed. This may result in an additional element being inserted. This will + * be seen as at least two events, n inserts followed by a remove. The + * HTMLEditorKit.Parser must be set. * * @param elem - the branch element whose parent will be replaced * @param htmlText - the string to be parsed and assigned to elem @@ -1565,18 +1631,25 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IOException * @throws IllegalStateException - if parser is not set */ - public void setOuterHTML(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException - { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("setOuterHTML not implemented"); - } +public void setOuterHTML(Element elem, String htmlText) + throws BadLocationException, IOException + { + // Remove the current element: + int start = elem.getStartOffset(); + int end = elem.getEndOffset(); + + remove(start, end-start); + + HTMLEditorKit.ParserCallback reader = getInsertingReader( + start, 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); + } /** - * Inserts the string before the start of the given element. - * The parser must be set. + * Inserts the string before the start of the given element. The parser must + * be set. * * @param elem - the element to be the root for the new text. * @param htmlText - the string to be parsed and assigned to elem @@ -1585,18 +1658,19 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser has not been set */ public void insertBeforeStart(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertBeforeStart not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); } /** - * Inserts the string at the end of the element. If elem's children - * are leaves, and the character at elem.getEndOffset() - 1 is a newline, - * then it will be inserted before the newline. The parser must be set. + * Inserts the string at the end of the element. If elem's children are + * leaves, and the character at elem.getEndOffset() - 1 is a newline, then it + * will be inserted before the newline. The parser must be set. * * @param elem - the element to be the root for the new text * @param htmlText - the text to insert @@ -1605,12 +1679,14 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser is not set */ public void insertBeforeEnd(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertBeforeEnd not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); + } /** @@ -1624,12 +1700,13 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser is not set */ public void insertAfterEnd(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertAfterEnd not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); } /** @@ -1643,11 +1720,12 @@ public class HTMLDocument extends DefaultStyledDocument * @throws IllegalStateException - if parser is not set */ public void insertAfterStart(Element elem, String htmlText) - throws BadLocationException, IOException, NotImplementedException + throws BadLocationException, IOException { - if (parser == null) - throw new IllegalStateException("Parser has not been set"); - // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit? - System.out.println("insertAfterStart not implemented"); + HTMLEditorKit.ParserCallback reader = getInsertingReader( + elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem); + + // TODO charset + getParser().parse(new StringReader(htmlText), reader, true); } } diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java index adda4922d57..5d77be8fdd4 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java +++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java @@ -71,6 +71,11 @@ import javax.swing.text.View; import javax.swing.text.ViewFactory; import javax.swing.text.html.parser.ParserDelegator; +/* Move these imports here after javax.swing.text.html to make it compile + with jikes. */ +import gnu.javax.swing.text.html.parser.GnuParserDelegator; +import gnu.javax.swing.text.html.parser.HTML_401Swing; + /** * @author Lillian Angel (langel at redhat dot com) */ @@ -557,19 +562,18 @@ public class HTMLEditorKit else if (tag == HTML.Tag.HEAD) view = new NullView(element); else if (tag.equals(HTML.Tag.TABLE)) - view = new HTMLTableView(element); + 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)) + view = new HRuleView(element); + else if (tag.equals(HTML.Tag.BR)) + view = new BRView(element); /* else if (tag.equals(HTML.Tag.MENU) || tag.equals(HTML.Tag.DIR) || tag.equals(HTML.Tag.UL) || tag.equals(HTML.Tag.OL)) view = new ListView(element); - else if (tag.equals(HTML.Tag.HR)) - view = new HRuleView(element); - else if (tag.equals(HTML.Tag.BR)) - view = new BRView(element); else if (tag.equals(HTML.Tag.INPUT) || tag.equals(HTML.Tag.SELECT) || tag.equals(HTML.Tag.TEXTAREA)) view = new FormView(element); @@ -887,7 +891,9 @@ public class HTMLEditorKit protected Parser getParser() { if (parser == null) - parser = new ParserDelegator(); + { + parser = new GnuParserDelegator(HTML_401Swing.getInstance()); + } return parser; } diff --git a/libjava/classpath/javax/swing/text/html/StyleSheet.java b/libjava/classpath/javax/swing/text/html/StyleSheet.java index 2466a2808fe..d92abde7825 100644 --- a/libjava/classpath/javax/swing/text/html/StyleSheet.java +++ b/libjava/classpath/javax/swing/text/html/StyleSheet.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.text.html; +import gnu.javax.swing.text.html.CharacterAttributeTranslator; + import java.awt.Color; import java.awt.Font; import java.awt.Graphics; @@ -585,47 +587,15 @@ public class StyleSheet extends StyleContext } /** - * Converst a color string to a color. If it is not found, null is returned. - * - * @param color - the color string such as "RED" or "#NNNNNN" - * @return the Color, or null if not found. - */ - public Color stringToColor(String color) - { - color = color.toLowerCase(); - if (color.equals("black") || color.equals("#000000")) - return Color.BLACK; - else if (color.equals("aqua") || color.equals("#00FFFF")) - return new Color(127, 255, 212); - else if (color.equals("gray") || color.equals("#808080")) - return Color.GRAY; - else if (color.equals("navy") || color.equals("#000080")) - return new Color(0, 0, 128); - else if (color.equals("silver") || color.equals("#C0C0C0")) - return Color.LIGHT_GRAY; - else if (color.equals("green") || color.equals("#008000")) - return Color.GREEN; - else if (color.equals("olive") || color.equals("#808000")) - return new Color(128, 128, 0); - else if (color.equals("teal") || color.equals("#008080")) - return new Color(0, 128, 128); - else if (color.equals("blue") || color.equals("#0000FF")) - return Color.BLUE; - else if (color.equals("lime") || color.equals("#00FF00")) - return new Color(0, 255, 0); - else if (color.equals("purple") || color.equals("#800080")) - return new Color(128, 0, 128); - else if (color.equals("white") || color.equals("#FFFFFF")) - return Color.WHITE; - else if (color.equals("fuchsia") || color.equals("#FF00FF")) - return Color.MAGENTA; - else if (color.equals("maroon") || color.equals("#800000")) - return new Color(128, 0, 0); - else if (color.equals("Red") || color.equals("#FF0000")) - return Color.RED; - else if (color.equals("Yellow") || color.equals("#FFFF00")) - return Color.YELLOW; - return null; + * Convert the color string represenation into java.awt.Color. The valid + * values are like "aqua" , "#00FFFF" or "rgb(1,6,44)". + * + * @param colorName the color to convert. + * @return the matching java.awt.color + */ + public Color stringToColor(String colorName) + { + return CharacterAttributeTranslator.getColor(colorName); } /** diff --git a/libjava/classpath/javax/swing/text/html/TableView.java b/libjava/classpath/javax/swing/text/html/TableView.java new file mode 100644 index 00000000000..c2edc8cdd64 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/TableView.java @@ -0,0 +1,137 @@ +/* TableView.java -- A table view for HTML tables + 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.Document; +import javax.swing.text.Element; +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) + */ +class TableView + extends javax.swing.text.TableView +{ + /** + * Represents a single table row. + */ + public class RowView extends TableRow + { + /** + * Creates a new instance of the <code>RowView</code>. + * + * @param el the element for which to create a row view + */ + public RowView(Element el) + { + super(el); + } + + /** + * Get the associated style sheet from the document. + * + * @return the associated style sheet. + */ + protected StyleSheet getStyleSheet() + { + Document d = getElement().getDocument(); + if (d instanceof HTMLDocument) + return ((HTMLDocument) d).getStyleSheet(); + else + return null; + } + } + + /** + * Creates a new HTML table view for the specified element. + * + * @param el the element for the table view + */ + public TableView(Element el) + { + super(el); + } + + /** + * Get the associated style sheet from the document. + * + * @return the associated style sheet. + */ + protected StyleSheet getStyleSheet() + { + Document d = getElement().getDocument(); + if (d instanceof HTMLDocument) + return ((HTMLDocument) d).getStyleSheet(); + else + return null; + } + + /** + * 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}). + */ + protected TableRow createTableRow(Element el) + { + return new RowView(el); + } + + /** + * Loads the children of the Table. This completely bypasses the ViewFactory + * and creates instances of TableRow instead. + * + * @param vf ignored + */ + protected void loadChildren(ViewFactory vf) + { + Element el = getElement(); + int numChildren = el.getElementCount(); + View[] rows = new View[numChildren]; + for (int i = 0; i < numChildren; ++i) + { + rows[i] = createTableRow(el.getElement(i)); + } + replace(0, getViewCount(), rows); + } +} diff --git a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java index e5d2db4df7c..70636d92923 100644 --- a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java +++ b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java @@ -52,9 +52,6 @@ import javax.swing.text.html.HTMLEditorKit.ParserCallback; * This class instantiates and starts the working instance of * html parser, being responsible for providing the default DTD. * - * TODO Later this class must be derived from the totally abstract class - * HTMLEditorKit.Parser. HTMLEditorKit that does not yet exist. - * * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) */ public class ParserDelegator diff --git a/libjava/classpath/javax/swing/text/rtf/RTFParser.java b/libjava/classpath/javax/swing/text/rtf/RTFParser.java index 4f0f967c117..de1b1c6ff15 100644 --- a/libjava/classpath/javax/swing/text/rtf/RTFParser.java +++ b/libjava/classpath/javax/swing/text/rtf/RTFParser.java @@ -140,9 +140,17 @@ class RTFParser parseHeader(); parseDocument(); - Token t2 = scanner.readToken(); - if (t2.type != Token.RCURLY) - throw new RTFParseException("expected right curly braces"); + Token t2 = scanner.peekToken(); + if (t2.type == Token.RCURLY) + { + // Eat the token. + scanner.readToken(); + } + else + { + // Ignore this for maximum robustness when file is broken. + System.err.println("RTF warning: expected right curly braces"); + } } diff --git a/libjava/classpath/javax/swing/text/rtf/RTFScanner.java b/libjava/classpath/javax/swing/text/rtf/RTFScanner.java index 3cdd6e8e0b9..060e087eb67 100644 --- a/libjava/classpath/javax/swing/text/rtf/RTFScanner.java +++ b/libjava/classpath/javax/swing/text/rtf/RTFScanner.java @@ -71,6 +71,11 @@ class RTFScanner private StringBuffer buffer; /** + * Lookahead token. + */ + private Token lastToken; + + /** * Constructs a new RTFScanner without initializing the {@link Reader}. */ private RTFScanner() @@ -120,7 +125,7 @@ class RTFScanner * * @throws IOException if the underlying stream has problems */ - public Token readToken() + private Token readTokenImpl() throws IOException { Token token = null; @@ -156,6 +161,27 @@ class RTFScanner return token; } + Token peekToken() + throws IOException + { + lastToken = readTokenImpl(); + return lastToken; + } + + Token readToken() + throws IOException + { + Token token; + if (lastToken != null) + { + token = lastToken; + lastToken = null; + } + else + token = readTokenImpl(); + return token; + } + /** * Reads in a control word and optional parameter. * |