diff options
author | mark <mark@138bc75d-0d04-0410-961f-82ee72b054a4> | 2005-11-15 23:20:01 +0000 |
---|---|---|
committer | mark <mark@138bc75d-0d04-0410-961f-82ee72b054a4> | 2005-11-15 23:20:01 +0000 |
commit | 3b3101d8b5ae4f08a16c0b7111da6cad41bbd282 (patch) | |
tree | a5eb7cf42a51869cc8aa1fad7ad6a90cca47fdd8 /libjava/classpath/javax/swing/text | |
parent | 7e55c49d7d91ef9f09e93c1100119b1ab3652446 (diff) | |
download | gcc-3b3101d8b5ae4f08a16c0b7111da6cad41bbd282.tar.gz |
Imported GNU Classpath 0.19 + gcj-import-20051115.
* sources.am: Regenerated.
* Makefile.in: Likewise.
* scripts/makemake.tcl: Use glob -nocomplain.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@107049 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libjava/classpath/javax/swing/text')
46 files changed, 5911 insertions, 815 deletions
diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java index 3c9a4d497a5..baf8608b888 100644 --- a/libjava/classpath/javax/swing/text/AbstractDocument.java +++ b/libjava/classpath/javax/swing/text/AbstractDocument.java @@ -65,11 +65,10 @@ import javax.swing.undo.UndoableEdit; * @author original author unknown * @author Roman Kennke (roman@kennke.org) */ -public abstract class AbstractDocument - implements Document, Serializable +public abstract class AbstractDocument implements Document, Serializable { - /** The serial version UID for this class as of JDK1.4. */ - private static final long serialVersionUID = -116069779446114664L; + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 6842927725919637215L; /** * Standard error message to indicate a bad location. @@ -128,7 +127,28 @@ public abstract class AbstractDocument * Manages event listeners for this <code>Document</code>. */ protected EventListenerList listenerList = new EventListenerList(); + + /** + * Stores the current writer thread. Used for locking. + */ + private Thread currentWriter = null; + + /** + * The number of readers. Used for locking. + */ + private int numReaders = 0; + + /** + * Tells if there are one or more writers waiting. + */ + private int numWritersWaiting = 0; + + /** + * A condition variable that readers and writers wait on. + */ + Object documentCV = new Object(); + /** * Creates a new <code>AbstractDocument</code> with the specified * {@link Content} model. @@ -332,7 +352,7 @@ public abstract class AbstractDocument * @see GapContent * @see StringContent */ - protected Content getContent() + protected final Content getContent() { return content; } @@ -348,8 +368,7 @@ public abstract class AbstractDocument */ protected Thread getCurrentWriter() { - // FIXME: Implement locking! - return null; + return currentWriter; } /** @@ -516,13 +535,18 @@ public abstract class AbstractDocument // Just return when no text to insert was given. if (text == null || text.length() == 0) return; - DefaultDocumentEvent event = new DefaultDocumentEvent(offset, text.length(), DocumentEvent.EventType.INSERT); - content.insertString(offset, text); + + writeLock(); + UndoableEdit undo = content.insertString(offset, text); insertUpdate(event, attributes); + writeUnlock(); + fireInsertUpdate(event); + if (undo != null) + fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); } /** @@ -566,10 +590,28 @@ public abstract class AbstractDocument } /** - * Blocks until a read lock can be obtained. + * Blocks until a read lock can be obtained. Must block if there is + * currently a writer modifying the <code>Document</code>. */ public void readLock() { + if (currentWriter != null && currentWriter.equals(Thread.currentThread())) + return; + synchronized (documentCV) + { + while (currentWriter != null || numWritersWaiting > 0) + { + try + { + documentCV.wait(); + } + catch (InterruptedException ie) + { + throw new Error("interrupted trying to get a readLock"); + } + } + numReaders++; + } } /** @@ -578,6 +620,40 @@ public abstract class AbstractDocument */ public void readUnlock() { + // Note we could have a problem here if readUnlock was called without a + // prior call to readLock but the specs simply warn users to ensure that + // balance by using a finally block: + // readLock() + // try + // { + // doSomethingHere + // } + // finally + // { + // readUnlock(); + // } + + // All that the JDK seems to check for is that you don't call unlock + // more times than you've previously called lock, but it doesn't make + // sure that the threads calling unlock were the same ones that called lock + + // FIXME: the reference implementation throws a + // javax.swing.text.StateInvariantError here + if (numReaders == 0) + throw new IllegalStateException("document lock failure"); + + synchronized (documentCV) + { + // If currentWriter is not null, the application code probably had a + // writeLock and then tried to obtain a readLock, in which case + // numReaders wasn't incremented + if (currentWriter == null) + { + numReaders --; + if (numReaders == 0 && numWritersWaiting != 0) + documentCV.notify(); + } + } } /** @@ -595,10 +671,42 @@ public abstract class AbstractDocument DefaultDocumentEvent event = new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.REMOVE); + + // Here we set up the parameters for an ElementChange, if one + // needs to be added to the DocumentEvent later + Element root = getDefaultRootElement(); + int start = root.getElementIndex(offset); + int end = root.getElementIndex(offset + length); + + Element[] removed = new Element[end - start + 1]; + for (int i = start; i <= end; i++) + removed[i - start] = root.getElement(i); + removeUpdate(event); - content.remove(offset, length); + + Element[] added = new Element[1]; + added[0] = root.getElement(start); + boolean shouldFire = content.getString(offset, length).length() != 0; + + writeLock(); + UndoableEdit temp = content.remove(offset, length); + writeUnlock(); + postRemoveUpdate(event); - fireRemoveUpdate(event); + + GapContent.UndoRemove changes = null; + if (content instanceof GapContent) + changes = (GapContent.UndoRemove) temp; + + if (changes != null && !(start == end)) + { + // We need to add an ElementChange to our DocumentEvent + ElementEdit edit = new ElementEdit (root, start, removed, added); + event.addEdit(edit); + } + + if (shouldFire) + fireRemoveUpdate(event); } /** @@ -713,7 +821,15 @@ public abstract class AbstractDocument */ public void render(Runnable runnable) { - // FIXME: Implement me! + readLock(); + try + { + runnable.run(); + } + finally + { + readUnlock(); + } } /** @@ -725,6 +841,7 @@ public abstract class AbstractDocument */ public void setAsynchronousLoadPriority(int p) { + // TODO: Implement this properly. } /** @@ -739,11 +856,30 @@ public abstract class AbstractDocument } /** - * Blocks until a write lock can be obtained. + * Blocks until a write lock can be obtained. Must wait if there are + * readers currently reading or another thread is currently writing. */ protected void writeLock() { - // FIXME: Implement me. + if (currentWriter!= null && currentWriter.equals(Thread.currentThread())) + return; + synchronized (documentCV) + { + numWritersWaiting++; + while (numReaders > 0) + { + try + { + documentCV.wait(); + } + catch (InterruptedException ie) + { + throw new Error("interruped while trying to obtain write lock"); + } + } + numWritersWaiting --; + currentWriter = Thread.currentThread(); + } } /** @@ -752,7 +888,14 @@ public abstract class AbstractDocument */ protected void writeUnlock() { - // FIXME: Implement me. + synchronized (documentCV) + { + if (Thread.currentThread().equals(currentWriter)) + { + currentWriter = null; + documentCV.notifyAll(); + } + } } /** @@ -970,8 +1113,8 @@ public abstract class AbstractDocument public abstract class AbstractElement implements Element, MutableAttributeSet, TreeNode, Serializable { - /** The serial version UID for AbstractElement. */ - private static final long serialVersionUID = 1265312733007397733L; + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 1712240033321461704L; /** The number of characters that this Element spans. */ int count; @@ -1231,6 +1374,9 @@ public abstract class AbstractDocument /** * Returns the resolve parent of this element. + * This is taken from the AttributeSet, but if this is null, + * this method instead returns the Element's parent's + * AttributeSet * * @return the resolve parent of this element * @@ -1238,7 +1384,9 @@ public abstract class AbstractDocument */ public AttributeSet getResolveParent() { - return attributes.getResolveParent(); + if (attributes.getResolveParent() != null) + return attributes.getResolveParent(); + return element_parent.getAttributes(); } /** @@ -1355,49 +1503,6 @@ public abstract class AbstractDocument public abstract int getStartOffset(); /** - * Prints diagnostic information to the specified stream. - * - * @param stream the stream to dump to - * @param indent the indentation level - * @param element the element to be dumped - */ - private void dumpElement(PrintStream stream, String indent, - Element element) - { - // FIXME: Should the method be removed? - System.out.println(indent + "<" + element.getName() +">"); - - if (element.isLeaf()) - { - int start = element.getStartOffset(); - int end = element.getEndOffset(); - String text = ""; - try - { - text = getContent().getString(start, end - start); - } - catch (BadLocationException e) - { - AssertionError error = - new AssertionError("BadLocationException should not be " - + "thrown here. start = " + start - + ", end = " + end); - error.initCause(e); - throw error; - } - System.out.println(indent + " [" - + start + "," - + end + "][" - + text + "]"); - } - else - { - for (int i = 0; i < element.getElementCount(); ++i) - dumpElement(stream, indent + " ", element.getElement(i)); - } - } - - /** * Prints diagnostic output to the specified stream. * * @param stream the stream to write to @@ -1405,10 +1510,66 @@ public abstract class AbstractDocument */ public void dump(PrintStream stream, int indent) { - String indentStr = ""; + StringBuffer b = new StringBuffer(); for (int i = 0; i < indent; ++i) - indentStr += " "; - dumpElement(stream, indentStr, this); + b.append(' '); + b.append('<'); + b.append(getName()); + // Dump attributes if there are any. + if (getAttributeCount() > 0) + { + b.append('\n'); + Enumeration attNames = getAttributeNames(); + while (attNames.hasMoreElements()) + { + for (int i = 0; i < indent + 2; ++i) + b.append(' '); + Object attName = attNames.nextElement(); + b.append(attName); + b.append('='); + Object attribute = getAttribute(attName); + b.append(attribute); + b.append('\n'); + } + } + b.append(">\n"); + + // Dump element content for leaf elements. + if (isLeaf()) + { + for (int i = 0; i < indent + 2; ++i) + b.append(' '); + int start = getStartOffset(); + int end = getEndOffset(); + b.append('['); + b.append(start); + b.append(','); + b.append(end); + b.append("]["); + try + { + b.append(getDocument().getText(start, end - start)); + } + catch (BadLocationException ex) + { + AssertionError err = new AssertionError("BadLocationException " + + "must not be thrown " + + "here."); + err.initCause(ex); + throw err; + } + b.append("]\n"); + } + stream.print(b.toString()); + + // Dump child elements if any. + int count = getElementCount(); + for (int i = 0; i < count; ++i) + { + Element el = getElement(i); + if (el instanceof AbstractElement) + ((AbstractElement) el).dump(stream, indent + 2); + } } } @@ -1418,8 +1579,8 @@ public abstract class AbstractDocument */ public class BranchElement extends AbstractElement { - /** The serial version UID for BranchElement. */ - private static final long serialVersionUID = -8595176318868717313L; + /** 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]; @@ -1503,19 +1664,30 @@ public abstract class AbstractDocument */ public int getElementIndex(int offset) { - // If we have no children, return -1. - if (getElementCount() == 0) - return - 1; - + // If offset is less than the start offset of our first child, + // return 0 + if (offset < getStartOffset()) + return 0; + // 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 < children.length - 1; ++index) { Element elem = children[index]; if ((elem.getStartOffset() <= offset) && (offset < elem.getEndOffset())) return index; + // If the next element's start offset is greater than offset + // then we have to return the closest Element, since no Elements + // will contain the offset + if (children[index + 1].getStartOffset() > offset) + { + if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset)) + return index + 1; + else + return index; + } } // If offset is greater than the index of the last element, return @@ -1642,8 +1814,8 @@ public abstract class AbstractDocument public class DefaultDocumentEvent extends CompoundEdit implements DocumentEvent { - /** The serial version UID of DefaultDocumentEvent. */ - private static final long serialVersionUID = -7406103236022413522L; + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 5230037221564563284L; /** The starting offset of the change. */ private int offset; @@ -1748,7 +1920,7 @@ public abstract class AbstractDocument return (DocumentEvent.ElementChange) changes.get(elem); } } - + /** * An implementation of {@link DocumentEvent.ElementChange} to be added * to {@link DefaultDocumentEvent}s. @@ -1843,8 +2015,8 @@ public abstract class AbstractDocument */ public class LeafElement extends AbstractElement { - /** The serial version UID of LeafElement. */ - private static final long serialVersionUID = 5115368706941283802L; + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = -8906306331347768017L; /** Manages the start offset of this element. */ Position startPos; diff --git a/libjava/classpath/javax/swing/text/AttributeSet.java b/libjava/classpath/javax/swing/text/AttributeSet.java index 2f1f1890bae..01d148c067b 100644 --- a/libjava/classpath/javax/swing/text/AttributeSet.java +++ b/libjava/classpath/javax/swing/text/AttributeSet.java @@ -58,6 +58,7 @@ public interface AttributeSet */ static interface CharacterAttribute { + // This interface is a marker interface and has no methods. } /** @@ -65,6 +66,7 @@ public interface AttributeSet */ static interface ColorAttribute { + // This interface is a marker interface and has no methods. } /** @@ -72,6 +74,7 @@ public interface AttributeSet */ static interface FontAttribute { + // This interface is a marker interface and has no methods. } /** @@ -79,6 +82,7 @@ public interface AttributeSet */ static interface ParagraphAttribute { + // This interface is a marker interface and has no methods. } /** @@ -99,7 +103,7 @@ public interface AttributeSet * <code>false</code> otherwise. * * @param name the name of the requested attribute - * @param the value of the requested attribute + * @param value the value of the requested attribute * * @return <code>true</code> if this <code>AttributeSet</code> contains * an attribute with the specified <code>name</code> and diff --git a/libjava/classpath/javax/swing/text/BoxView.java b/libjava/classpath/javax/swing/text/BoxView.java index 0f8ba1ce15e..f201045dbdb 100644 --- a/libjava/classpath/javax/swing/text/BoxView.java +++ b/libjava/classpath/javax/swing/text/BoxView.java @@ -155,8 +155,9 @@ public class BoxView * automatically when any of the child view changes its preferences * via {@link #preferenceChanged(View, boolean, boolean)}. * - * The layout will be updated the next time when {@link #setSize()} is - * called, typically from within the {@link #paint()} method. + * The layout will be updated the next time when + * {@link #setSize(float, float)} is called, typically from within the + * {@link #paint(Graphics, Shape)} method. * * Valid values for the axis are {@link View#X_AXIS} and * {@link View#Y_AXIS}. @@ -216,12 +217,11 @@ public class BoxView * @param alloc the allocated region for the child to paint into * @param index the index of the child to be painted * - * @see {@link #childAllocation} + * @see #childAllocation(int, Rectangle) */ protected void paintChild(Graphics g, Rectangle alloc, int index) { View child = getView(index); - childAllocation(index, alloc); child.paint(g, alloc); } @@ -301,18 +301,15 @@ public class BoxView setSize(bounds.width, bounds.height); Rectangle inside = getInsideAllocation(a); - Rectangle copy = new Rectangle(inside); int count = getViewCount(); for (int i = 0; i < count; ++i) { - // TODO: Figure out if the parameter to paintChild is meant to - // be the child allocation or the allocation of this BoxView. - // I assume the second option here. - // We pass this method a copy of the inside rectangle here because - // it modifies the actual values. copy.setBounds(inside); - paintChild(g, copy, i); + childAllocation(i, copy); + if (!copy.isEmpty() + && g.hitClip(copy.x, copy.y, copy.width, copy.height)) + paintChild(g, copy, i); } } @@ -362,6 +359,24 @@ public class BoxView } /** + * Calculates the layout of the children of this <code>BoxView</code> along + * the specified axis. + * + * @param span the target span + * @param axis the axis that is examined + * @param offsets an empty array, filled with the offsets of the children + * @param spans an empty array, filled with the spans of the children + */ + protected void baselineLayout(int span, int axis, int[] offsets, + int[] spans) + { + if (axis == myAxis) + layoutMajorAxis(span, axis, offsets, spans); + else + layoutMinorAxis(span, axis, offsets, spans); + } + + /** * Calculates the size requirements of this <code>BoxView</code> along * its major axis, that is the axis specified in the constructor. * @@ -375,27 +390,8 @@ public class BoxView protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements sr) { - if (sr == null) - sr = new SizeRequirements(); - else - { - sr.maximum = 0; - sr.minimum = 0; - sr.preferred = 0; - sr.alignment = 0.5F; - } - - int count = getViewCount(); - - // Sum up the sizes of the children along the specified axis. - for (int i = 0; i < count; ++i) - { - View child = getView(i); - sr.minimum += child.getMinimumSpan(axis); - sr.preferred += child.getPreferredSpan(axis); - sr.maximum += child.getMaximumSpan(axis); - } - return sr; + SizeRequirements[] childReqs = getChildRequirements(axis); + return SizeRequirements.getTiledSizeRequirements(childReqs); } /** @@ -413,48 +409,8 @@ public class BoxView protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements sr) { - if (sr == null) - sr = new SizeRequirements(); - else - { - sr.maximum = 0; - sr.minimum = 0; - sr.preferred = 0; - sr.alignment = 0.5F; - } - - int count = getViewCount(); - - int aboveBaseline = 0; - int belowBaseline = 0; - int aboveBaselineMin = 0; - int belowBaselineMin = 0; - int aboveBaselineMax = 0; - int belowBaselineMax = 0; - - for (int i = 0; i < count; ++i) - { - View child = getView(i); - float align = child.getAlignment(axis); - int pref = (int) child.getPreferredSpan(axis); - int min = (int) child.getMinimumSpan(axis); - int max = (int) child.getMaximumSpan(axis); - aboveBaseline += (int) (align * pref); - belowBaseline += (int) ((1.F - align) * pref); - aboveBaselineMin += (int) (align * min); - belowBaselineMin += (int) ((1.F - align) * min); - aboveBaselineMax += (int) (align * max); - belowBaselineMax += (int) ((1.F - align) * max); - } - sr.minimum = aboveBaselineMin + belowBaselineMin; - sr.maximum = aboveBaselineMax + belowBaselineMax; - sr.preferred = aboveBaseline + belowBaseline; - if (aboveBaseline == 0) - sr.alignment = 1.0F; - else - sr.alignment = (float) (sr.preferred / aboveBaseline); - - return sr; + SizeRequirements[] childReqs = getChildRequirements(axis); + return SizeRequirements.getAlignedSizeRequirements(childReqs); } /** @@ -569,19 +525,8 @@ public class BoxView */ protected void layout(int width, int height) { - this.width = width; - this.height = height; - - if (myAxis == X_AXIS) - { - layoutMajorAxis(width, X_AXIS, offsetsX, spansX); - layoutMinorAxis(height, Y_AXIS, offsetsY, spansY); - } - else - { - layoutMajorAxis(height, Y_AXIS, offsetsY, spansY); - layoutMinorAxis(width, X_AXIS, offsetsX, spansX); - } + baselineLayout(width, X_AXIS, offsetsX, spansX); + baselineLayout(height, Y_AXIS, offsetsY, spansY); } /** @@ -591,28 +536,16 @@ public class BoxView * to layout the children * @param axis the axis along which the layout is performed * @param offsets the array that holds the offsets of the children on exit - * @param offsets the array that holds the spans of the children on exit + * @param spans the array that holds the spans of the children on exit */ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - // Allocate SizeRequirements for each child view. - int count = getViewCount(); - SizeRequirements[] childReqs = new SizeRequirements[count]; - for (int i = 0; i < count; ++i) - { - View view = getView(i); - childReqs[i] = new SizeRequirements((int) view.getMinimumSpan(axis), - (int) view.getPreferredSpan(axis), - (int) view.getMaximumSpan(axis), - view.getAlignment(axis)); - } - + SizeRequirements[] childReqs = getChildRequirements(axis); // Calculate the spans and offsets using the SizeRequirements uility // methods. SizeRequirements.calculateTiledPositions(targetSpan, null, childReqs, offsets, spans); - validateLayout(axis); } @@ -623,26 +556,21 @@ public class BoxView * to layout the children * @param axis the axis along which the layout is performed * @param offsets the array that holds the offsets of the children on exit - * @param offsets the array that holds the spans of the children on exit + * @param spans the array that holds the spans of the children on exit */ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - // Allocate SizeRequirements for each child view. - int count = getViewCount(); - SizeRequirements[] childReqs = new SizeRequirements[count]; - for (int i = 0; i < count; ++i) - { - View view = getView(i); - childReqs[i] = new SizeRequirements((int) view.getMinimumSpan(axis), - (int) view.getPreferredSpan(axis), - (int) view.getMaximumSpan(axis), - view.getAlignment(axis)); - } - + SizeRequirements[] childReqs = getChildRequirements(axis); // Calculate the spans and offsets using the SizeRequirements uility // methods. - SizeRequirements.calculateAlignedPositions(targetSpan, null, childReqs, + // TODO: This might be an opportunity for performance optimization. Here + // we could use a cached instance of SizeRequirements instead of passing + // null to baselineRequirements. However, this would involve rewriting + // the baselineRequirements() method to not use the SizeRequirements + // utility method, since they cannot reuse a cached instance. + SizeRequirements total = baselineRequirements(axis, null); + SizeRequirements.calculateAlignedPositions(targetSpan, total, childReqs, offsets, spans); validateLayout(axis); } @@ -692,6 +620,9 @@ public class BoxView layoutChanged(X_AXIS); if (this.height != (int) height) layoutChanged(Y_AXIS); + + this.width = (int) width; + this.height = (int) height; Rectangle outside = new Rectangle(0, 0, this.width, this.height); Rectangle inside = getInsideAllocation(outside); @@ -711,4 +642,99 @@ public class BoxView if (axis == Y_AXIS) yLayoutValid = true; } + + /** + * Returns the size requirements of this view's children for the major + * axis. + * + * @return the size requirements of this view's children for the major + * axis + */ + SizeRequirements[] getChildRequirements(int axis) + { + // Allocate SizeRequirements for each child view. + int count = getViewCount(); + SizeRequirements[] childReqs = new SizeRequirements[count]; + for (int i = 0; i < count; ++i) + { + View view = getView(i); + childReqs[i] = new SizeRequirements((int) view.getMinimumSpan(axis), + (int) view.getPreferredSpan(axis), + (int) view.getMaximumSpan(axis), + view.getAlignment(axis)); + } + return childReqs; + } + + /** + * Returns the span for the child view with the given index for the specified + * axis. + * + * @param axis the axis to examine, either <code>X_AXIS</code> or + * <code>Y_AXIS</code> + * @param childIndex the index of the child for for which to return the span + * + * @return the span for the child view with the given index for the specified + * axis + */ + protected int getSpan(int axis, int childIndex) + { + if (axis == X_AXIS) + return spansX[childIndex]; + else + return spansY[childIndex]; + } + + /** + * Returns the offset for the child view with the given index for the + * specified axis. + * + * @param axis the axis to examine, either <code>X_AXIS</code> or + * <code>Y_AXIS</code> + * @param childIndex the index of the child for for which to return the span + * + * @return the offset for the child view with the given index for the + * specified axis + */ + protected int getOffset(int axis, int childIndex) + { + if (axis == X_AXIS) + return offsetsX[childIndex]; + else + return offsetsY[childIndex]; + } + + /** + * Returns the alignment for this box view for the specified axis. The + * axis that is tiled (the major axis) will be requested to be aligned + * centered (0.5F). The minor axis alignment depends on the child view's + * total alignment. + * + * @param axis the axis which is examined + * + * @return the alignment for this box view for the specified axis + */ + public float getAlignment(int axis) + { + if (axis == myAxis) + return 0.5F; + else + return baselineRequirements(axis, null).alignment; + } + + /** + * Called by a child View when its preferred span has changed. + * + * @param width indicates that the preferred width of the child changed. + * @param height indicates that the preferred height of the child changed. + * @param child the child View. + */ + public void preferenceChanged (View child, boolean width, boolean height) + { + if (width) + xLayoutValid = false; + if (height) + yLayoutValid = false; + super.preferenceChanged(child, width, height); + } } diff --git a/libjava/classpath/javax/swing/text/ComponentView.java b/libjava/classpath/javax/swing/text/ComponentView.java index f6feda21513..16112c8f4de 100644 --- a/libjava/classpath/javax/swing/text/ComponentView.java +++ b/libjava/classpath/javax/swing/text/ComponentView.java @@ -1,5 +1,5 @@ /* ComponentView.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -41,65 +41,125 @@ import java.awt.Component; import java.awt.Graphics; import java.awt.Shape; +import javax.swing.SwingConstants; + +/** + * A {@link View} implementation that is able to render arbitrary + * {@link Component}s. This uses the attribute + * {@link StyleConstants#ComponentAttribute} to determine the + * <code>Component</code> that should be rendered. This <code>Component</code> + * becomes a direct child of the <code>JTextComponent</code> that contains + * this <code>ComponentView</code>, so this view must not be shared between + * multiple <code>JTextComponent</code>s. + * + * @author original author unknown + * @author Roman Kennke (roman@kennke.org) + */ +// FIXME: This class is a complete stub and needs to be implemented properly. public class ComponentView extends View { - public ComponentView(Element elem) - { - super(elem); - } - - protected Component createComponent() - { - return null; - } - - public float getAlignment(int axis) - { - return 0; - } - - public final Component getComponent() - { - return null; - } - - public float getMaximumSpan(int axis) - { - return 0; - } - - public float getMinimumSpan(int axis) - { - return 0; - } - - public float getPreferredSpan(int axis) - { - return 0; - } - - public Shape modelToView(int pos, Shape a, Position.Bias b) - throws BadLocationException - { - return null; - } - - public void paint(Graphics g, Shape a) - { - } + /** + * Creates a new instance of <code>ComponentView</code> for the specified + * <code>Element</code>. + * + * @param elem the element that this <code>View</code> is rendering + */ + public ComponentView(Element elem) + { + super(elem); + } + + /** + * Creates the <code>Component</code> that this <code>View</code> is + * rendering. The <code>Component</code> is determined using + * the {@link StyleConstants#ComponentAttribute} of the associated + * <code>Element</code>. + * + * @return the component that is rendered + */ + protected Component createComponent() + { + return StyleConstants.getComponent(getElement().getAttributes()); + } + + /** + * Returns the alignment of this <code>View</code> along the specified axis. + * + * @param axis either {@link View#X_AXIS} or {@link View#Y_AXIS} + * + * @return the alignment of this <code>View</code> along the specified axis + */ + public float getAlignment(int axis) + { + return 0; + } + + /** + * Returns the <code>Component</code> that is rendered by this + * <code>ComponentView</code>. + * + * @return the <code>Component</code> that is rendered by this + * <code>ComponentView</code> + */ + public final Component getComponent() + { + return null; + } + + /** + * Returns the maximum span of this <code>View</code> along the specified + * axis. + * + * This will return {@link Component#getMaximumSize()} for the specified + * axis. + * + * @return the maximum span of this <code>View</code> along the specified + * axis + */ + public float getMaximumSpan(int axis) + { + return 0; + } + + public float getMinimumSpan(int axis) + { + // TODO: Implement this properly. + return 0; + } + + public float getPreferredSpan(int axis) + { + // TODO: Implement this properly. + return 0; + } + + public Shape modelToView(int pos, Shape a, Position.Bias b) + throws BadLocationException + { + // TODO: Implement this properly. + return null; + } - public void setParent(View p) - { - } + public void paint(Graphics g, Shape a) + { + // TODO: Implement this properly. + } + + public void setParent(View p) + { + // TODO: Implement this properly. + } - public void setSize(float width, float height) - { - } + public void setSize(float width, float height) + { + // TODO: Implement this properly. + } - public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) - { - return 0; - } + public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) + { + // TODO: Implement this properly. + return 0; + } /** * Maps coordinates from the <code>View</code>'s space into a position @@ -118,4 +178,34 @@ public class ComponentView extends View // FIXME: Implement this properly. return 0; } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + // TODO: Implement this properly. + throw new AssertionError("Not implemented yet."); + } } diff --git a/libjava/classpath/javax/swing/text/CompositeView.java b/libjava/classpath/javax/swing/text/CompositeView.java index 6776c95727a..bc626a40696 100644 --- a/libjava/classpath/javax/swing/text/CompositeView.java +++ b/libjava/classpath/javax/swing/text/CompositeView.java @@ -62,7 +62,7 @@ public abstract class CompositeView /** * The allocation of this <code>View</code> minus its insets. This is * initialized in {@link #getInsideAllocation} and reused and modified in - * {@link childAllocation}. + * {@link #childAllocation(int, Rectangle)}. */ Rectangle insideAllocation; @@ -221,20 +221,17 @@ public abstract class CompositeView if (childIndex != -1) { View child = getView(childIndex); - Shape result = child.modelToView(pos, a, bias); + Rectangle r = a.getBounds(); + childAllocation(childIndex, r); + Shape result = child.modelToView(pos, r, bias); if (result == null) throw new AssertionError("" + child.getClass().getName() + ".modelToView() must not return null"); return result; } else - { - // FIXME: Handle the case when we have no child view for the given - // position. - throw new AssertionError("No child views found where child views are " - + "expected. pos = " + pos + ", bias = " - + bias); - } + throw new BadLocationException("No child view for the specified location", + pos); } /** @@ -314,6 +311,7 @@ public abstract class CompositeView */ public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) + throws BadLocationException { int retVal = -1; switch (direction) @@ -433,10 +431,16 @@ public abstract class CompositeView */ protected int getViewIndexAtPosition(int pos) { - // We have one child view allocated for each child element in - // loadChildren(), so this should work. - Element el = getElement(); - int index = el.getElementIndex(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; } @@ -473,8 +477,8 @@ public abstract class CompositeView insideAllocation = inside; } } - inside.x = alloc.x - insets.left; - inside.y = alloc.y - insets.top; + inside.x = alloc.x + insets.left; + inside.y = alloc.y + insets.top; inside.width = alloc.width - insets.left - insets.right; inside.height = alloc.height - insets.top - insets.bottom; return inside; @@ -594,6 +598,7 @@ public abstract class CompositeView protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) + throws BadLocationException { // FIXME: Implement this correctly. return pos; @@ -627,6 +632,7 @@ public abstract class CompositeView protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) + throws BadLocationException { // FIXME: Implement this correctly. return pos; @@ -649,4 +655,34 @@ public abstract class CompositeView { return false; } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + // TODO: Implement this properly. + throw new AssertionError("Not implemented yet."); + } } diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java index 33c3ae3bf28..66e2f4723cf 100644 --- a/libjava/classpath/javax/swing/text/DefaultCaret.java +++ b/libjava/classpath/javax/swing/text/DefaultCaret.java @@ -40,15 +40,24 @@ package javax.swing.text; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.EventListener; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.event.EventListenerList; /** @@ -60,12 +69,164 @@ import javax.swing.event.EventListenerList; public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener, MouseMotionListener { + + /** + * Controls the blinking of the caret. + * + * @author Roman Kennke (kennke@aicas.com) + * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) + */ + private class BlinkTimerListener implements ActionListener + { + /** + * Forces the next event to be ignored. The next event should be ignored + * if we force the caret to appear. We do not know how long will it take + * to fire the comming event; this may be near immediately. Better to leave + * the caret visible one iteration longer. + */ + boolean ignoreNextEvent; + + /** + * Receives notification when the blink timer fires and updates the visible + * state of the caret. + * + * @param event the action event + */ + public void actionPerformed(ActionEvent event) + { + if (ignoreNextEvent) + ignoreNextEvent = false; + else + { + visible = !visible; + repaint(); + } + } + } + + /** + * Listens for changes in the text component's document and updates the + * caret accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class DocumentHandler implements DocumentListener + { + /** + * Receives notification that some text attributes have changed. No action + * is taken here. + * + * @param event the document event + */ + public void changedUpdate(DocumentEvent event) + { + // Nothing to do here. + } + + /** + * Receives notification that some text has been inserted from the text + * component. The caret is moved forward accordingly. + * + * @param event the document event + */ + public void insertUpdate(DocumentEvent event) + { + if (policy == ALWAYS_UPDATE || + (SwingUtilities.isEventDispatchThread() && + policy == UPDATE_WHEN_ON_EDT)) + { + int dot = getDot(); + setDot(dot + event.getLength()); + } + } + + /** + * Receives notification that some text has been removed into the text + * component. The caret is moved backwards accordingly. + * + * @param event the document event + */ + public void removeUpdate(DocumentEvent event) + { + if (policy == ALWAYS_UPDATE || + (SwingUtilities.isEventDispatchThread() && + policy == UPDATE_WHEN_ON_EDT)) + { + int dot = getDot(); + setDot(dot - event.getLength()); + } + else if (policy == NEVER_UPDATE) + { + int docLength = event.getDocument().getLength(); + if (getDot() > docLength) + setDot(docLength); + } + } + } + + /** + * Listens for property changes on the text document. This is used to add and + * remove our document listener, if the document of the text component has + * changed. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class PropertyChangeHandler implements PropertyChangeListener + { + + /** + * Receives notification when a property has changed on the text component. + * This adds/removes our document listener from the text component's + * document when the document changes. + * + * @param e the property change event + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals("document")) + { + Document oldDoc = (Document) e.getOldValue(); + oldDoc.removeDocumentListener(documentListener); + Document newDoc = (Document) e.getNewValue(); + newDoc.addDocumentListener(documentListener); + } + } + + } + + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 4325555698756477346L; + /** - * The serial version UID for DefaultCaret. + * Indicates the Caret position should always be updated after Document + * changes even if the updates are not performed on the Event Dispatching + * thread. + * + * @since 1.5 */ - private static final long serialVersionUID = 228155774675466193L; + public static final int ALWAYS_UPDATE = 2; /** + * Indicates the Caret position should not be changed unless the Document + * length becomes less than the Caret position, in which case the Caret + * is moved to the end of the Document. + * + * @since 1.5 + */ + public static final int NEVER_UPDATE = 1; + + /** + * Indicates the Caret position should be updated only if Document changes + * are made on the Event Dispatcher thread. + * + * @since 1.5 + */ + public static final int UPDATE_WHEN_ON_EDT = 0; + + /** Keeps track of the current update policy **/ + int policy = UPDATE_WHEN_ON_EDT; + + /** * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}. */ protected ChangeEvent changeEvent = new ChangeEvent(this); @@ -76,6 +237,16 @@ public class DefaultCaret extends Rectangle protected EventListenerList listenerList = new EventListenerList(); /** + * Our document listener. + */ + DocumentListener documentListener; + + /** + * Our property listener. + */ + PropertyChangeListener propertyChangeListener; + + /** * The text component in which this caret is installed. */ private JTextComponent textComponent; @@ -106,15 +277,65 @@ public class DefaultCaret extends Rectangle private Point magicCaretPosition = null; /** - * Indicates if this <code>Caret</code> is currently visible or not. + * Indicates if this <code>Caret</code> is currently visible or not. This is + * package private to avoid an accessor method. */ - private boolean visible = true; + boolean visible = false; /** * The current highlight entry. */ private Object highlightEntry; + private Timer blinkTimer; + + private BlinkTimerListener blinkListener; + + /** + * Creates a new <code>DefaultCaret</code> instance. + */ + public DefaultCaret() + { + // Nothing to do here. + } + + /** + * Sets the Caret update policy. + * + * @param policy the new policy. Valid values are: + * ALWAYS_UPDATE: always update the Caret position, even when Document + * updates don't occur on the Event Dispatcher thread. + * NEVER_UPDATE: don't update the Caret position unless the Document + * length becomes less than the Caret position (then update the + * Caret to the end of the Document). + * UPDATE_WHEN_ON_EDT: update the Caret position when the + * Document updates occur on the Event Dispatcher thread. This is the + * default. + * + * @since 1.5 + * @throws IllegalArgumentException if policy is not one of the above. + */ + public void setUpdatePolicy (int policy) + { + if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE + && policy != UPDATE_WHEN_ON_EDT) + throw new + IllegalArgumentException + ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT"); + this.policy = policy; + } + + /** + * Gets the caret update policy. + * + * @return the caret update policy. + * @since 1.5 + */ + public int getUpdatePolicy () + { + return policy; + } + /** * Moves the caret position when the mouse is dragged over the text * component, modifying the selection accordingly. @@ -123,7 +344,7 @@ public class DefaultCaret extends Rectangle */ public void mouseDragged(MouseEvent event) { - // FIXME: Implement this properly. + moveCaret(event); } /** @@ -153,7 +374,7 @@ public class DefaultCaret extends Rectangle */ public void mouseClicked(MouseEvent event) { - // FIXME: Implement this properly. + // TODO: Implement double- and triple-click behaviour here. } /** @@ -175,6 +396,7 @@ public class DefaultCaret extends Rectangle */ public void mouseExited(MouseEvent event) { + // Nothing to do here. } /** @@ -187,7 +409,7 @@ public class DefaultCaret extends Rectangle */ public void mousePressed(MouseEvent event) { - // FIXME: Implement this properly. + positionCaret(event); } /** @@ -208,6 +430,7 @@ public class DefaultCaret extends Rectangle */ public void focusGained(FocusEvent event) { + setVisible(true); } /** @@ -217,6 +440,8 @@ public class DefaultCaret extends Rectangle */ public void focusLost(FocusEvent event) { + if (event.isTemporary() == false) + setVisible(false); } /** @@ -227,7 +452,8 @@ public class DefaultCaret extends Rectangle */ protected void moveCaret(MouseEvent event) { - // FIXME: Implement this properly. + int newDot = getComponent().viewToModel(event.getPoint()); + moveDot(newDot); } /** @@ -238,7 +464,8 @@ public class DefaultCaret extends Rectangle */ protected void positionCaret(MouseEvent event) { - // FIXME: Implement this properly. + int newDot = getComponent().viewToModel(event.getPoint()); + setDot(newDot); } /** @@ -253,7 +480,16 @@ public class DefaultCaret extends Rectangle textComponent.removeFocusListener(this); textComponent.removeMouseListener(this); textComponent.removeMouseMotionListener(this); + textComponent.getDocument().removeDocumentListener(documentListener); + documentListener = null; + textComponent.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; textComponent = null; + + // Deinstall blink timer if present. + if (blinkTimer != null) + blinkTimer.stop(); + blinkTimer = null; } /** @@ -269,6 +505,11 @@ public class DefaultCaret extends Rectangle textComponent.addFocusListener(this); textComponent.addMouseListener(this); textComponent.addMouseMotionListener(this); + propertyChangeListener = new PropertyChangeHandler(); + textComponent.addPropertyChangeListener(propertyChangeListener); + documentListener = new DocumentHandler(); + textComponent.getDocument().addDocumentListener(documentListener); + repaint(); } @@ -376,10 +617,7 @@ public class DefaultCaret extends Rectangle */ protected final void repaint() { - // FIXME: Is this good? This possibly causes alot of the component - // hierarchy to be repainted on every caret blink. - if (textComponent != null) - textComponent.repaint(); + getComponent().repaint(x, y, width, height); } /** @@ -390,7 +628,8 @@ public class DefaultCaret extends Rectangle */ public void paint(Graphics g) { - if (textComponent == null) + JTextComponent comp = getComponent(); + if (comp == null) return; int dot = getDot(); @@ -398,25 +637,33 @@ public class DefaultCaret extends Rectangle try { - rect = textComponent.modelToView(dot); + rect = textComponent.modelToView(dot); } catch (BadLocationException e) { - // This should never happen as dot should be always valid. - return; + assert false : "Unexpected bad caret location: " + dot; + return; } if (rect == null) return; - - // First we need to delete the old caret. - // FIXME: Implement deleting of old caret. - + + // Check if paint has possibly been called directly, without a previous + // call to damage(). In this case we need to do some cleanup first. + if ((x != rect.x) || (y != rect.y)) + { + repaint(); // Erase previous location of caret. + x = rect.x; + y = rect.y; + width = 1; + height = rect.height; + } + // Now draw the caret on the new position if visible. if (visible) { - g.setColor(textComponent.getCaretColor()); - g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height); + g.setColor(textComponent.getCaretColor()); + g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height); } } @@ -507,6 +754,8 @@ public class DefaultCaret extends Rectangle */ public void setBlinkRate(int rate) { + if (blinkTimer != null) + blinkTimer.setDelay(rate); blinkRate = rate; } @@ -534,7 +783,8 @@ public class DefaultCaret extends Rectangle { this.dot = dot; handleHighlight(); - repaint(); + adjustVisibility(this); + appear(); } /** @@ -551,8 +801,45 @@ public class DefaultCaret extends Rectangle this.dot = dot; this.mark = dot; handleHighlight(); - repaint(); + adjustVisibility(this); + appear(); } + + /** + * Show the caret (may be hidden due blinking) and adjust the timer not to + * hide it (possibly immediately). + * + * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) + */ + void appear() + { + // All machinery is only required if the carret is blinking. + if (blinkListener != null) + { + blinkListener.ignoreNextEvent = true; + + // If the caret is visible, erase the current position by repainting + // over. + if (visible) + repaint(); + + // Draw the caret in the new position. + visible = true; + + Rectangle area = null; + try + { + area = getComponent().modelToView(getDot()); + } + catch (BadLocationException ex) + { + assert false : "Unexpected bad caret location: " + getDot(); + } + if (area != null) + damage(area); + } + repaint(); + } /** * Returns <code>true</code> if this <code>Caret</code> is currently visible, @@ -574,8 +861,33 @@ public class DefaultCaret extends Rectangle */ public void setVisible(boolean v) { - visible = v; - repaint(); + if (v != visible) + { + visible = v; + if (visible) + if (textComponent.isEnabled() && textComponent.isEditable()) + { + if (blinkTimer == null) + initBlinkTimer(); + blinkTimer.start(); + } + else + { + if (blinkTimer != null) + blinkTimer.stop(); + } + Rectangle area = null; + try + { + area = getComponent().modelToView(getDot()); + } + catch (BadLocationException ex) + { + assert false: "Unexpected bad caret location: " + getDot(); + } + if (area != null) + damage(area); + } } /** @@ -589,4 +901,47 @@ public class DefaultCaret extends Rectangle { return DefaultHighlighter.DefaultPainter; } + + /** + * Updates the carets rectangle properties to the specified rectangle and + * repaints the caret. + * + * @param r the rectangle to set as the caret rectangle + */ + protected void damage(Rectangle r) + { + if (r == null) + return; + x = r.x; + y = r.y; + width = 1; + // height is normally set in paint and we leave it untouched. However, we + // must set a valid value here, since otherwise the painting mechanism + // sets a zero clip and never calls paint. + if (height <= 0) + height = getComponent().getHeight(); + repaint(); + } + + /** + * Adjusts the text component so that the caret is visible. This default + * implementation simply calls + * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component. + * Subclasses may wish to change this. + */ + protected void adjustVisibility(Rectangle rect) + { + getComponent().scrollRectToVisible(rect); + } + + /** + * Initializes the blink timer. + */ + private void initBlinkTimer() + { + // Setup the blink timer. + blinkListener = new BlinkTimerListener(); + blinkTimer = new Timer(getBlinkRate(), blinkListener); + blinkTimer.setRepeats(true); + } } diff --git a/libjava/classpath/javax/swing/text/DefaultEditorKit.java b/libjava/classpath/javax/swing/text/DefaultEditorKit.java index a14f3ff4fe0..3b3fc1f7238 100644 --- a/libjava/classpath/javax/swing/text/DefaultEditorKit.java +++ b/libjava/classpath/javax/swing/text/DefaultEditorKit.java @@ -66,8 +66,7 @@ public class DefaultEditorKit extends EditorKit * * @see Toolkit#beep() */ - public static class BeepAction - extends TextAction + public static class BeepAction extends TextAction { /** * Creates a new <code>BeepAction</code>. @@ -95,8 +94,7 @@ public class DefaultEditorKit extends EditorKit * @see CutAction * @see PasteAction */ - public static class CopyAction - extends TextAction + public static class CopyAction extends TextAction { /** @@ -128,8 +126,7 @@ public class DefaultEditorKit extends EditorKit * @see CopyAction * @see PasteAction */ - public static class CutAction - extends TextAction + public static class CutAction extends TextAction { /** @@ -159,8 +156,7 @@ public class DefaultEditorKit extends EditorKit * @see CopyAction * @see CutAction */ - public static class PasteAction - extends TextAction + public static class PasteAction extends TextAction { /** @@ -226,9 +222,6 @@ public class DefaultEditorKit extends EditorKit { t.getDocument().insertString(t.getCaret().getDot(), event.getActionCommand(), null); - t.getCaret().setDot(Math.min(t.getCaret().getDot() + 1, - t.getDocument().getEndPosition() - .getOffset())); } catch (BadLocationException be) { @@ -243,8 +236,7 @@ public class DefaultEditorKit extends EditorKit * of the text component. This is typically triggered by hitting * ENTER on the keyboard. */ - public static class InsertBreakAction - extends TextAction + public static class InsertBreakAction extends TextAction { /** @@ -273,8 +265,7 @@ public class DefaultEditorKit extends EditorKit */ // FIXME: Figure out what this Action is supposed to do. Obviously text // that is entered by the user is inserted through DefaultKeyTypedAction. - public static class InsertContentAction - extends TextAction + public static class InsertContentAction extends TextAction { /** @@ -292,14 +283,15 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { + // FIXME: Figure out what this Action is supposed to do. Obviously text + // that is entered by the user is inserted through DefaultKeyTypedAction. } } /** * Inserts a TAB character into the text editor. */ - public static class InsertTabAction - extends TextAction + public static class InsertTabAction extends TextAction { /** @@ -699,6 +691,7 @@ public class DefaultEditorKit extends EditorKit */ public DefaultEditorKit() { + // Nothing to do here. } /** @@ -954,15 +947,23 @@ public class DefaultEditorKit extends EditorKit * @param offset the beginning offset from where to write * @param len the length of the fragment to write * - * @throws BadLocationException if <code>offset</code> or - * <code>offset + len</code>is an invalid location inside - * <code>document</code> + * @throws BadLocationException if <code>offset</code> is an + * invalid location inside <code>document</code>. * @throws IOException if something goes wrong while writing to * <code>out</code> */ public void write(Writer out, Document document, int offset, int len) - throws BadLocationException, IOException + throws BadLocationException, IOException { - // TODO: Implement this properly. + // Throw a BLE if offset is invalid + if (offset < 0 || offset > document.getLength()) + throw new BadLocationException("Tried to write to invalid location", + offset); + + // If they gave an overly large len, just adjust it + if (offset + len > document.getLength()) + len = document.getLength() - offset; + + out.write(document.getText(offset, len)); } } diff --git a/libjava/classpath/javax/swing/text/DefaultFormatter.java b/libjava/classpath/javax/swing/text/DefaultFormatter.java index c97d90703c7..f9e0f10e654 100644 --- a/libjava/classpath/javax/swing/text/DefaultFormatter.java +++ b/libjava/classpath/javax/swing/text/DefaultFormatter.java @@ -57,8 +57,7 @@ import javax.swing.JFormattedTextField; * * @author Roman Kennke (roman@kennke.org) */ -public class DefaultFormatter - extends JFormattedTextField.AbstractFormatter +public class DefaultFormatter extends JFormattedTextField.AbstractFormatter implements Cloneable, Serializable { @@ -156,9 +155,6 @@ public class DefaultFormatter * Checks if the value in the input field is valid. If the * property allowsInvalid is set to <code>false</code>, then * the string in the input field is not allowed to be entered. - * - * @param doc the document of the input field - * @param value the current (old) value of the input field */ private void checkValidInput() { @@ -179,15 +175,18 @@ public class DefaultFormatter catch (ParseException pe) { // if that happens, something serious must be wrong - throw new AssertionError("values must be parseable"); + AssertionError ae; + ae = new AssertionError("values must be parseable"); + ae.initCause(pe); + throw ae; } } } } } - /** The serialVersoinUID. */ - private static final long serialVersionUID = -7369196326612908900L; + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = -355018354457785329L; /** * Indicates if the value should be committed after every diff --git a/libjava/classpath/javax/swing/text/DefaultHighlighter.java b/libjava/classpath/javax/swing/text/DefaultHighlighter.java index c8d874caa51..40ea4f80aab 100644 --- a/libjava/classpath/javax/swing/text/DefaultHighlighter.java +++ b/libjava/classpath/javax/swing/text/DefaultHighlighter.java @@ -168,6 +168,7 @@ public class DefaultHighlighter extends LayeredHighlighter public DefaultHighlighter() { + // Nothing to do here. } public boolean getDrawsLayeredHighlights() @@ -238,6 +239,7 @@ public class DefaultHighlighter extends LayeredHighlighter Shape viewBounds, JTextComponent editor, View view) { + // TODO: Implement this properly. } public void paint(Graphics g) diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java index 3545e52c453..eb56bb0f8e5 100644 --- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java +++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java @@ -1,5 +1,5 @@ /* DefaultStyledDocument.java -- - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -41,8 +41,14 @@ package javax.swing.text; import java.awt.Color; import java.awt.Font; import java.io.Serializable; +import java.util.Enumeration; +import java.util.Vector; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.UndoableEdit; /** * The default implementation of {@link StyledDocument}. @@ -60,12 +66,359 @@ public class DefaultStyledDocument extends AbstractDocument implements StyledDocument { /** + * An {@link UndoableEdit} that can undo attribute changes to an element. + * + * @author Roman Kennke (kennke@aicas.com) + */ + public static class AttributeUndoableEdit + extends AbstractUndoableEdit + { + /** + * A copy of the old attributes. + */ + protected AttributeSet copy; + + /** + * The new attributes. + */ + protected AttributeSet newAttributes; + + /** + * If the new attributes replaced the old attributes or if they only were + * added to them. + */ + protected boolean isReplacing; + + /** + * The element that has changed. + */ + protected Element element; + + /** + * Creates a new <code>AttributeUndoableEdit</code>. + * + * @param el the element that changes attributes + * @param newAtts the new attributes + * @param replacing if the new attributes replace the old or only append to + * them + */ + public AttributeUndoableEdit(Element el, AttributeSet newAtts, + boolean replacing) + { + element = el; + newAttributes = newAtts; + isReplacing = replacing; + copy = el.getAttributes().copyAttributes(); + } + + /** + * Undos the attribute change. The <code>copy</code> field is set as + * attributes on <code>element</code>. + */ + public void undo() + { + super.undo(); + AttributeSet atts = element.getAttributes(); + if (atts instanceof MutableAttributeSet) + { + MutableAttributeSet mutable = (MutableAttributeSet) atts; + mutable.removeAttributes(atts); + mutable.addAttributes(copy); + } + } + + /** + * Redos an attribute change. This adds <code>newAttributes</code> to the + * <code>element</code>'s attribute set, possibly clearing all attributes + * if <code>isReplacing</code> is true. + */ + public void redo() + { + super.undo(); + AttributeSet atts = element.getAttributes(); + if (atts instanceof MutableAttributeSet) + { + MutableAttributeSet mutable = (MutableAttributeSet) atts; + if (isReplacing) + mutable.removeAttributes(atts); + mutable.addAttributes(newAttributes); + } + } + } + + /** + * Carries specification information for new {@link Element}s that should + * be created in {@link ElementBuffer}. This allows the parsing process + * to be decoupled from the <code>Element</code> creation process. + */ + public static class ElementSpec + { + /** + * This indicates a start tag. This is a possible value for + * {@link #getType}. + */ + public static final short StartTagType = 1; + + /** + * This indicates an end tag. This is a possible value for + * {@link #getType}. + */ + public static final short EndTagType = 2; + + /** + * This indicates a content element. This is a possible value for + * {@link #getType}. + */ + public static final short ContentType = 3; + + /** + * This indicates that the data associated with this spec should be joined + * with what precedes it. This is a possible value for + * {@link #getDirection}. + */ + public static final short JoinPreviousDirection = 4; + + /** + * This indicates that the data associated with this spec should be joined + * with what follows it. This is a possible value for + * {@link #getDirection}. + */ + public static final short JoinNextDirection = 5; + + /** + * This indicates that the data associated with this spec should be used + * to create a new element. This is a possible value for + * {@link #getDirection}. + */ + public static final short OriginateDirection = 6; + + /** + * This indicates that the data associated with this spec should be joined + * to the fractured element. This is a possible value for + * {@link #getDirection}. + */ + public static final short JoinFractureDirection = 7; + + /** + * The type of the tag. + */ + short type; + + /** + * The direction of the tag. + */ + short direction; + + /** + * The offset of the content. + */ + int offset; + + /** + * The length of the content. + */ + int length; + + /** + * The actual content. + */ + char[] content; + + /** + * The attributes for the tag. + */ + AttributeSet attributes; + + /** + * Creates a new <code>ElementSpec</code> with no content, length or + * offset. This is most useful for start and end tags. + * + * @param a the attributes for the element to be created + * @param type the type of the tag + */ + public ElementSpec(AttributeSet a, short type) + { + this(a, type, 0); + } + + /** + * Creates a new <code>ElementSpec</code> that specifies the length but + * not the offset of an element. Such <code>ElementSpec</code>s are + * processed sequentially from a known starting point. + * + * @param a the attributes for the element to be created + * @param type the type of the tag + * @param len the length of the element + */ + public ElementSpec(AttributeSet a, short type, int len) + { + this(a, type, null, 0, len); + } + + /** + * Creates a new <code>ElementSpec</code> with document content. + * + * @param a the attributes for the element to be created + * @param type the type of the tag + * @param txt the actual content + * @param offs the offset into the <code>txt</code> array + * @param len the length of the element + */ + public ElementSpec(AttributeSet a, short type, char[] txt, int offs, + int len) + { + attributes = a; + this.type = type; + offset = offs; + length = len; + content = txt; + direction = OriginateDirection; + } + + /** + * Sets the type of the element. + * + * @param type the type of the element to be set + */ + public void setType(short type) + { + this.type = type; + } + + /** + * Returns the type of the element. + * + * @return the type of the element + */ + public short getType() + { + return type; + } + + /** + * Sets the direction of the element. + * + * @param dir the direction of the element to be set + */ + public void setDirection(short dir) + { + direction = dir; + } + + /** + * Returns the direction of the element. + * + * @return the direction of the element + */ + public short getDirection() + { + return direction; + } + + /** + * Returns the attributes of the element. + * + * @return the attributes of the element + */ + public AttributeSet getAttributes() + { + return attributes; + } + + /** + * Returns the actual content of the element. + * + * @return the actual content of the element + */ + public char[] getArray() + { + return content; + } + + /** + * Returns the offset of the content. + * + * @return the offset of the content + */ + public int getOffset() + { + return offset; + } + + /** + * Returns the length of the content. + * + * @return the length of the content + */ + public int getLength() + { + return length; + } + + /** + * Returns a String representation of this <code>ElementSpec</code> + * describing the type, direction and length of this + * <code>ElementSpec</code>. + * + * @return a String representation of this <code>ElementSpec</code> + */ + public String toString() + { + StringBuilder b = new StringBuilder(); + b.append('<'); + switch (type) + { + case StartTagType: + b.append("StartTag"); + break; + case EndTagType: + b.append("EndTag"); + break; + case ContentType: + b.append("Content"); + break; + default: + b.append("??"); + break; + } + + b.append(':'); + + switch (direction) + { + case JoinPreviousDirection: + b.append("JoinPrevious"); + break; + case JoinNextDirection: + b.append("JoinNext"); + break; + case OriginateDirection: + b.append("Originate"); + break; + case JoinFractureDirection: + b.append("Fracture"); + break; + default: + b.append("??"); + break; + } + + b.append(':'); + b.append(length); + + return b.toString(); + } + } + + /** * Performs all <em>structural</code> changes to the <code>Element</code> * hierarchy. */ - public class ElementBuffer - implements Serializable + public class ElementBuffer implements Serializable { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 1688745877691146623L; + /** The root element of the hierarchy. */ private Element root; @@ -76,6 +429,19 @@ public class DefaultStyledDocument extends AbstractDocument private int length; /** + * Holds fractured elements during insertion of end and start tags. + * Inserting an end tag may lead to fracturing of the current paragraph + * element. The elements that have been cut off may be added to the + * next paragraph that is created in the next start tag. + */ + Element[] fracture; + + /** + * The ElementChange that describes the latest changes. + */ + DefaultDocumentEvent documentEvent; + + /** * Creates a new <code>ElementBuffer</code> for the specified * <code>root</code> element. * @@ -114,6 +480,7 @@ public class DefaultStyledDocument extends AbstractDocument { this.offset = offset; this.length = length; + documentEvent = ev; changeUpdate(); } @@ -142,38 +509,352 @@ public class DefaultStyledDocument extends AbstractDocument void split(Element el, int offset) { if (el instanceof AbstractElement) - { - AbstractElement ael = (AbstractElement) el; - int startOffset = ael.getStartOffset(); - int endOffset = ael.getEndOffset(); - int len = endOffset - startOffset; - if (startOffset != offset && endOffset != offset) - { - Element paragraph = ael.getParentElement(); - if (paragraph instanceof BranchElement) - { - BranchElement par = (BranchElement) paragraph; - Element child1 = createLeafElement(par, ael, startOffset, - offset); - Element child2 = createLeafElement(par, ael, offset, - endOffset); - int index = par.getElementIndex(startOffset); - par.replace(index, 1, new Element[]{ child1, child2 }); - } + { + AbstractElement ael = (AbstractElement) el; + int startOffset = ael.getStartOffset(); + int endOffset = ael.getEndOffset(); + int len = endOffset - startOffset; + if (startOffset != offset && endOffset != offset) + { + Element paragraph = ael.getParentElement(); + if (paragraph instanceof BranchElement) + { + BranchElement par = (BranchElement) paragraph; + Element child1 = createLeafElement(par, ael, startOffset, + offset); + Element child2 = createLeafElement(par, ael, offset, + endOffset); + int index = par.getElementIndex(startOffset); + Element[] add = new Element[]{ child1, child2 }; + par.replace(index, 1, add); + documentEvent.addEdit(new ElementEdit(par, index, + new Element[]{ el }, + add)); + } else throw new AssertionError("paragraph elements are expected to " + "be instances of " + "javax.swing.text.AbstractDocument.BranchElement"); - } - } + } + } else - throw new AssertionError("content elements are expected to be " - + "instances of " + throw new AssertionError("content elements are expected to be " + + "instances of " + "javax.swing.text.AbstractDocument.AbstractElement"); } + + /** + * Inserts new <code>Element</code> in the document at the specified + * position. + * + * Most of the work is done by {@link #insertUpdate}, after some fields + * have been prepared for it. + * + * @param offset the location in the document at which the content is + * inserted + * @param length the length of the inserted content + * @param data the element specifications for the content to be inserted + * @param ev the document event that is updated to reflect the structural + * changes + */ + public void insert(int offset, int length, ElementSpec[] data, + DefaultDocumentEvent ev) + { + this.offset = offset; + this.length = length; + documentEvent = ev; + insertUpdate(data); + } + + /** + * Performs the actual structural change for {@link #insert}. This + * creates a bunch of {@link Element}s as specified by <code>data</code> + * and inserts it into the document as specified in the arguments to + * {@link #insert}. + * + * @param data the element specifications for the elements to be inserte + */ + protected void insertUpdate(ElementSpec[] data) + { + for (int i = 0; i < data.length; i++) + { + switch (data[i].getType()) + { + case ElementSpec.StartTagType: + insertStartTag(data[i]); + break; + case ElementSpec.EndTagType: + insertEndTag(data[i]); + break; + default: + insertContentTag(data[i]); + break; + } + } + } + + /** + * Insert a new paragraph after the paragraph at the current position. + * + * @param tag the element spec that describes the element to be inserted + */ + void insertStartTag(ElementSpec tag) + { + BranchElement root = (BranchElement) getDefaultRootElement(); + int index = root.getElementIndex(offset); + if (index == -1) + index = 0; + + BranchElement newParagraph = + (BranchElement) createBranchElement(root, tag.getAttributes()); + newParagraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE)); + + // Add new paragraph into document structure. + Element[] added = new Element[]{newParagraph}; + root.replace(index + 1, 0, added); + ElementEdit edit = new ElementEdit(root, index + 1, new Element[0], + added); + documentEvent.addEdit(edit); + + // Maybe add fractured elements. + if (tag.getDirection() == ElementSpec.JoinFractureDirection) + { + Element[] newFracture = new Element[fracture.length]; + for (int i = 0; i < fracture.length; i++) + { + Element oldLeaf = fracture[i]; + Element newLeaf = createLeafElement(newParagraph, + oldLeaf.getAttributes(), + oldLeaf.getStartOffset(), + oldLeaf.getEndOffset()); + newFracture[i] = newLeaf; + } + newParagraph.replace(0, 0, newFracture); + edit = new ElementEdit(newParagraph, 0, new Element[0], + fracture); + documentEvent.addEdit(edit); + fracture = new Element[0]; + } + } + + /** + * Inserts an end tag into the document structure. This cuts of the + * current paragraph element, possibly fracturing it's child elements. + * The fractured elements are saved so that they can be joined later + * with a new paragraph element. + */ + void insertEndTag(ElementSpec tag) + { + BranchElement root = (BranchElement) getDefaultRootElement(); + int parIndex = root.getElementIndex(offset); + BranchElement paragraph = (BranchElement) root.getElement(parIndex); + + int index = paragraph.getElementIndex(offset); + LeafElement content = (LeafElement) paragraph.getElement(index); + // We might have to split the element at offset. + split(content, offset); + index = paragraph.getElementIndex(offset); + + int count = paragraph.getElementCount(); + // Store fractured elements. + fracture = new Element[count - index]; + for (int i = index; i < count; ++i) + fracture[i - index] = paragraph.getElement(i); + + // Delete fractured elements. + paragraph.replace(index, count - index, new Element[0]); + + // Add this action to the document event. + ElementEdit edit = new ElementEdit(paragraph, index, fracture, + new Element[0]); + documentEvent.addEdit(edit); + } + + /** + * Inserts a content element into the document structure. + * + * @param tag the element spec + */ + void insertContentTag(ElementSpec tag) + { + int len = tag.getLength(); + int dir = tag.getDirection(); + if (dir == ElementSpec.JoinPreviousDirection) + { + Element prev = getCharacterElement(offset); + BranchElement prevParent = (BranchElement) prev.getParentElement(); + Element join = createLeafElement(prevParent, tag.getAttributes(), + prev.getStartOffset(), + Math.max(prev.getEndOffset(), + offset + len)); + int ind = prevParent.getElementIndex(offset); + if (ind == -1) + ind = 0; + Element[] add = new Element[]{join}; + prevParent.replace(ind, 1, add); + + // Add this action to the document event. + ElementEdit edit = new ElementEdit(prevParent, ind, + new Element[]{prev}, add); + documentEvent.addEdit(edit); + } + else if (dir == ElementSpec.JoinNextDirection) + { + Element next = getCharacterElement(offset + len); + BranchElement nextParent = (BranchElement) next.getParentElement(); + Element join = createLeafElement(nextParent, tag.getAttributes(), + offset, + next.getEndOffset()); + int ind = nextParent.getElementIndex(offset + len); + if (ind == -1) + ind = 0; + Element[] add = new Element[]{join}; + nextParent.replace(ind, 1, add); + + // Add this action to the document event. + ElementEdit edit = new ElementEdit(nextParent, ind, + new Element[]{next}, add); + documentEvent.addEdit(edit); + } + else + { + BranchElement par = (BranchElement) getParagraphElement(offset); + + int ind = par.getElementIndex(offset); + + // Make room for the element. + // Cut previous element. + Element prev = par.getElement(ind); + if (prev != null && prev.getStartOffset() < offset) + { + Element cutPrev = createLeafElement(par, prev.getAttributes(), + prev.getStartOffset(), + offset); + Element[] remove = new Element[]{prev}; + Element[] add = new Element[]{cutPrev}; + if (prev.getEndOffset() > offset + len) + { + Element rem = createLeafElement(par, prev.getAttributes(), + offset + len, + prev.getEndOffset()); + add = new Element[]{cutPrev, rem}; + } + + par.replace(ind, 1, add); + documentEvent.addEdit(new ElementEdit(par, ind, remove, add)); + ind++; + } + // ind now points to the next element. + + // Cut next element if necessary. + Element next = par.getElement(ind); + if (next != null && next.getStartOffset() < offset + len) + { + Element cutNext = createLeafElement(par, next.getAttributes(), + offset + len, + next.getEndOffset()); + Element[] remove = new Element[]{next}; + Element[] add = new Element[]{cutNext}; + par.replace(ind, 1, add); + documentEvent.addEdit(new ElementEdit(par, ind, remove, + add)); + } + + // Insert new element. + Element newEl = createLeafElement(par, tag.getAttributes(), + offset, offset + len); + Element[] added = new Element[]{newEl}; + par.replace(ind, 0, added); + // Add this action to the document event. + ElementEdit edit = new ElementEdit(par, ind, new Element[0], + added); + documentEvent.addEdit(edit); + } + offset += len; + } + + /** + * Creates a copy of the element <code>clonee</code> that has the parent + * <code>parent</code>. + * @param parent the parent of the newly created Element + * @param clonee the Element to clone + * @return the cloned Element + */ + public Element clone (Element parent, Element clonee) + { + // If the Element we want to clone is a leaf, then simply copy it + if (clonee.isLeaf()) + return createLeafElement(parent, clonee.getAttributes(), + clonee.getStartOffset(), clonee.getEndOffset()); + + // Otherwise create a new BranchElement with the desired parent and + // the clonee's attributes + BranchElement result = (BranchElement) createBranchElement(parent, clonee.getAttributes()); + + // And clone all the of clonee's children + Element[] children = new Element[clonee.getElementCount()]; + for (int i = 0; i < children.length; i++) + children[i] = clone(result, clonee.getElement(i)); + + // Make the cloned children the children of the BranchElement + result.replace(0, 0, children); + return result; + } } /** + * An element type for sections. This is a simple BranchElement with + * a unique name. + */ + protected class SectionElement extends BranchElement + { + /** + * Creates a new SectionElement. + */ + public SectionElement() + { + super(null, null); + } + + /** + * Returns the name of the element. This method always returns + * "section". + * + * @return the name of the element + */ + public String getName() + { + return "section"; + } + } + + /** + * Receives notification when any of the document's style changes and calls + * {@link DefaultStyledDocument#styleChanged(Style)}. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class StyleChangeListener + implements ChangeListener + { + + /** + * Receives notification when any of the document's style changes and calls + * {@link DefaultStyledDocument#styleChanged(Style)}. + * + * @param event the change event + */ + public void stateChanged(ChangeEvent event) + { + Style style = (Style) event.getSource(); + styleChanged(style); + } + } + + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 940485415728614849L; + + /** * The default size to use for new content buffers. */ public static final int BUFFER_SIZE_DEFAULT = 4096; @@ -185,6 +866,11 @@ public class DefaultStyledDocument extends AbstractDocument protected DefaultStyledDocument.ElementBuffer buffer; /** + * Listens for changes on this document's styles and notifies styleChanged(). + */ + private StyleChangeListener styleChangeListener; + + /** * Creates a new <code>DefaultStyledDocument</code>. */ public DefaultStyledDocument() @@ -237,7 +923,14 @@ public class DefaultStyledDocument extends AbstractDocument public Style addStyle(String nm, Style parent) { StyleContext context = (StyleContext) getAttributeContext(); - return context.addStyle(nm, parent); + Style newStyle = context.addStyle(nm, parent); + + // Register change listener. + if (styleChangeListener == null) + styleChangeListener = new StyleChangeListener(); + newStyle.addChangeListener(styleChangeListener); + + return newStyle; } /** @@ -250,9 +943,11 @@ public class DefaultStyledDocument extends AbstractDocument Element[] tmp; // FIXME: Create a SecionElement here instead of a BranchElement. // Use createBranchElement() and createLeafElement instead. - BranchElement section = new BranchElement(null, null); - - BranchElement paragraph = new BranchElement(section, null); + SectionElement section = new SectionElement(); + + BranchElement paragraph = + (BranchElement) createBranchElement(section, null); + paragraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE)); tmp = new Element[1]; tmp[0] = paragraph; section.replace(0, 0, tmp); @@ -261,7 +956,7 @@ public class DefaultStyledDocument extends AbstractDocument tmp = new Element[1]; tmp[0] = leaf; paragraph.replace(0, 0, tmp); - + return section; } @@ -279,10 +974,10 @@ public class DefaultStyledDocument extends AbstractDocument { Element element = getDefaultRootElement(); - while (! element.isLeaf()) + while (!element.isLeaf()) { - int index = element.getElementIndex(position); - element = element.getElement(index); + int index = element.getElementIndex(position); + element = element.getElement(index); } return element; @@ -353,6 +1048,10 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the paragraph element for the specified position. + * If the position is outside the bounds of the document's root element, + * then the closest element is returned. That is the last paragraph if + * <code>position >= endIndex</code> or the first paragraph if + * <code>position < startIndex</code>. * * @param position the position for which to query the paragraph element * @@ -360,8 +1059,18 @@ public class DefaultStyledDocument extends AbstractDocument */ public Element getParagraphElement(int position) { - Element element = getCharacterElement(position); - return element.getParentElement(); + BranchElement root = (BranchElement) getDefaultRootElement(); + int start = root.getStartOffset(); + int end = root.getEndOffset(); + if (position >= end) + position = end - 1; + else if (position < start) + position = start; + + Element par = root.positionToElement(position); + + assert par != null : "The paragraph element must not be null"; + return par; } /** @@ -416,35 +1125,37 @@ public class DefaultStyledDocument extends AbstractDocument int paragraphCount = root.getElementCount(); for (int pindex = 0; pindex < paragraphCount; pindex++) { - Element paragraph = root.getElement(pindex); - // Skip paragraphs that lie outside the interval. - if ((paragraph.getStartOffset() > offset + length) - || (paragraph.getEndOffset() < offset)) - continue; - - // Visit content elements within this paragraph - int contentCount = paragraph.getElementCount(); - for (int cindex = 0; cindex < contentCount; cindex++) - { - Element content = paragraph.getElement(cindex); - // Skip content that lies outside the interval. - if ((content.getStartOffset() > offset + length) - || (content.getEndOffset() < offset)) - continue; - - if (content instanceof AbstractElement) - { - AbstractElement el = (AbstractElement) content; - if (replace) - el.removeAttributes(el); - el.addAttributes(attributes); - } - else - throw new AssertionError("content elements are expected to be" - + "instances of " + Element paragraph = root.getElement(pindex); + // Skip paragraphs that lie outside the interval. + if ((paragraph.getStartOffset() > offset + length) + || (paragraph.getEndOffset() < offset)) + continue; + + // Visit content elements within this paragraph + int contentCount = paragraph.getElementCount(); + for (int cindex = 0; cindex < contentCount; cindex++) + { + Element content = paragraph.getElement(cindex); + // Skip content that lies outside the interval. + if ((content.getStartOffset() > offset + length) + || (content.getEndOffset() < offset)) + continue; + + if (content instanceof AbstractElement) + { + AbstractElement el = (AbstractElement) content; + if (replace) + el.removeAttributes(el); + el.addAttributes(attributes); + } + else + throw new AssertionError("content elements are expected to be" + + "instances of " + "javax.swing.text.AbstractDocument.AbstractElement"); - } + } } + + fireChangedUpdate(ev); } /** @@ -476,10 +1187,199 @@ public class DefaultStyledDocument extends AbstractDocument * selection are overridden, otherwise they are merged */ public void setParagraphAttributes(int offset, int length, - AttributeSet attributes, - boolean replace) + AttributeSet attributes, + boolean replace) + { + int index = offset; + while (index < offset + length) + { + AbstractElement par = (AbstractElement) getParagraphElement(index); + AttributeContext ctx = getAttributeContext(); + if (replace) + par.removeAttributes(par); + par.addAttributes(attributes); + index = par.getElementCount(); + } + } + + /** + * Called in response to content insert actions. This is used to + * update the element structure. + * + * @param ev the <code>DocumentEvent</code> describing the change + * @param attr the attributes for the change + */ + protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr) + { + super.insertUpdate(ev, attr); + int offset = ev.getOffset(); + int length = ev.getLength(); + int endOffset = offset + length; + Segment txt = new Segment(); + try + { + getText(offset, length, txt); + } + catch (BadLocationException ex) + { + AssertionError ae = new AssertionError("Unexpected bad location"); + ae.initCause(ex); + throw ae; + } + + int len = 0; + Vector specs = new Vector(); + + Element prev = getCharacterElement(offset); + Element next = getCharacterElement(endOffset); + + for (int i = offset; i < endOffset; ++i) + { + len++; + if (txt.array[i] == '\n') + { + ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType, + len); + + // If we are at the last index, then check if we could probably be + // joined with the next element. + if (i == endOffset - 1) + { + if (next.getAttributes().isEqual(attr)) + spec.setDirection(ElementSpec.JoinNextDirection); + } + // If we are at the first new element, then check if it could be + // joined with the previous element. + else if (specs.size() == 0) + { + if (prev.getAttributes().isEqual(attr)) + spec.setDirection(ElementSpec.JoinPreviousDirection); + } + + specs.add(spec); + + // Add ElementSpecs for the newline. + ElementSpec endTag = new ElementSpec(null, ElementSpec.EndTagType); + specs.add(endTag); + ElementSpec startTag = new ElementSpec(null, + ElementSpec.StartTagType); + startTag.setDirection(ElementSpec.JoinFractureDirection); + specs.add(startTag); + + len = 0; + offset += len; + } + } + + // Create last element if last character hasn't been a newline. + if (len > 0) + { + ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType, len); + // If we are at the first new element, then check if it could be + // joined with the previous element. + if (specs.size() == 0) + { + if (prev.getAttributes().isEqual(attr)) + spec.setDirection(ElementSpec.JoinPreviousDirection); + } + // Check if we could probably be joined with the next element. + else if (next.getAttributes().isEqual(attr)) + spec.setDirection(ElementSpec.JoinNextDirection); + + specs.add(spec); + } + + ElementSpec[] elSpecs = + (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]); + + buffer.insert(offset, length, elSpecs, ev); + } + + /** + * Returns an enumeration of all style names. + * + * @return an enumeration of all style names + */ + public Enumeration getStyleNames() + { + StyleContext context = (StyleContext) getAttributeContext(); + return context.getStyleNames(); + } + + /** + * Called when any of this document's styles changes. + * + * @param style the style that changed + */ + protected void styleChanged(Style style) { - // FIXME: Implement me. - throw new Error("not implemented"); + // Nothing to do here. This is intended to be overridden by subclasses. + } + + /** + * Inserts a bulk of structured content at once. + * + * @param offset the offset at which the content should be inserted + * @param data the actual content spec to be inserted + */ + protected void insert(int offset, ElementSpec[] data) + throws BadLocationException + { + writeLock(); + // First we insert the content. + int index = offset; + for (int i = 0; i < data.length; i++) + { + ElementSpec spec = data[i]; + if (spec.getArray() != null && spec.getLength() > 0) + { + String insertString = new String(spec.getArray(), spec.getOffset(), + spec.getLength()); + content.insertString(index, insertString); + } + index += spec.getLength(); + } + // Update the view structure. + DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, index - offset, + DocumentEvent.EventType.INSERT); + for (int i = 0; i < data.length; i++) + { + ElementSpec spec = data[i]; + AttributeSet atts = spec.getAttributes(); + if (atts != null) + insertUpdate(ev, atts); + } + + // Finally we must update the document structure and fire the insert update + // event. + buffer.insert(offset, index - offset, data, ev); + fireInsertUpdate(ev); + writeUnlock(); + } + + /** + * Initializes the <code>DefaultStyledDocument</code> with the specified + * data. + * + * @param data the specification of the content with which the document is + * initialized + */ + protected void create(ElementSpec[] data) + { + try + { + // Clear content. + content.remove(0, content.length()); + // Clear buffer and root element. + buffer = new ElementBuffer(createDefaultRoot()); + // Insert the data. + insert(0, data); + } + catch (BadLocationException ex) + { + AssertionError err = new AssertionError("Unexpected bad location"); + err.initCause(ex); + throw err; + } } } diff --git a/libjava/classpath/javax/swing/text/DefaultTextUI.java b/libjava/classpath/javax/swing/text/DefaultTextUI.java new file mode 100644 index 00000000000..e7ff01845e6 --- /dev/null +++ b/libjava/classpath/javax/swing/text/DefaultTextUI.java @@ -0,0 +1,61 @@ +/* DefaultTextUI.java -- Deprecated base UI for text components + Copyright (C) 2005 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 javax.swing.plaf.basic.BasicTextUI; + +/** + * This class is deprecated and should not be used anymore. The base UI for + * all text components is now {@link BasicTextUI}. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public abstract class DefaultTextUI extends BasicTextUI +{ + /** + * This class is deprecated and should not be used anymore. The base UI for + * all text components is now {@link BasicTextUI}. + * + * @deprecated use {@link BasicTextUI} instead + */ + public DefaultTextUI() + { + // Nothing to do here. + } +} diff --git a/libjava/classpath/javax/swing/text/EditorKit.java b/libjava/classpath/javax/swing/text/EditorKit.java index bd51a866f68..8719aee595f 100644 --- a/libjava/classpath/javax/swing/text/EditorKit.java +++ b/libjava/classpath/javax/swing/text/EditorKit.java @@ -48,24 +48,24 @@ import java.io.Writer; import javax.swing.Action; import javax.swing.JEditorPane; -public abstract class EditorKit - implements Cloneable, Serializable +public abstract class EditorKit implements Cloneable, Serializable { private static final long serialVersionUID = -5044124649345887822L; public EditorKit() { + // Nothing to do here. } public Object clone() { try { - return super.clone(); + return super.clone(); } catch (CloneNotSupportedException e) { - return null; + return null; } } @@ -74,10 +74,12 @@ public abstract class EditorKit */ public void deinstall(JEditorPane c) { + // This default implementation does nothing. } public void install(JEditorPane c) { + // This default implementation does nothing. } public abstract Caret createCaret(); diff --git a/libjava/classpath/javax/swing/text/FieldView.java b/libjava/classpath/javax/swing/text/FieldView.java index e2e04d7c495..2496418444e 100644 --- a/libjava/classpath/javax/swing/text/FieldView.java +++ b/libjava/classpath/javax/swing/text/FieldView.java @@ -118,22 +118,24 @@ public class FieldView extends PlainView FontMetrics fm = getFontMetrics(); if (axis == Y_AXIS) - return fm.getHeight(); + return super.getPreferredSpan(axis); String text; Element elem = getElement(); try { - text = elem.getDocument().getText(elem.getStartOffset(), - elem.getEndOffset()); + text = elem.getDocument().getText(elem.getStartOffset(), + elem.getEndOffset()); } catch (BadLocationException e) { - // This should never happen. - text = ""; + // Should never happen + AssertionError ae = new AssertionError(); + ae.initCause(e); + throw ae; } - + return fm.stringWidth(text); } @@ -159,18 +161,21 @@ public class FieldView extends PlainView { Shape newAlloc = adjustAllocation(shape); super.insertUpdate(ev, newAlloc, vf); + getContainer().repaint(); } public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { Shape newAlloc = adjustAllocation(shape); super.removeUpdate(ev, newAlloc, vf); + getContainer().repaint(); } public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) { Shape newAlloc = adjustAllocation(shape); super.removeUpdate(ev, newAlloc, vf); + getContainer().repaint(); } public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) diff --git a/libjava/classpath/javax/swing/text/FlowView.java b/libjava/classpath/javax/swing/text/FlowView.java index a6ef89efb78..fd6785b6f18 100644 --- a/libjava/classpath/javax/swing/text/FlowView.java +++ b/libjava/classpath/javax/swing/text/FlowView.java @@ -42,8 +42,10 @@ import java.awt.Container; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; +import java.util.Iterator; import java.util.Vector; +import javax.swing.SwingConstants; import javax.swing.event.DocumentEvent; /** @@ -71,6 +73,7 @@ public abstract class FlowView extends BoxView */ public FlowStrategy() { + // Nothing to do here. } /** @@ -137,7 +140,7 @@ public abstract class FlowView extends BoxView * Performs the layout for the whole view. By default this rebuilds * all the physical views from the logical views of the managed FlowView. * - * This is called by {@link FlowLayout#layout} to update the layout of + * This is called by {@link FlowView#layout} to update the layout of * the view. * * @param fv the flow view for which we perform the layout @@ -183,11 +186,17 @@ public abstract class FlowView extends BoxView { View child = createView(fv, offset, spanLeft, rowIndex); if (child == null) - break; + { + offset = -1; + break; + } int span = (int) child.getPreferredSpan(flowAxis); if (span > spanLeft) - break; + { + offset = -1; + break; + } row.append(child); spanLeft -= span; @@ -204,7 +213,7 @@ public abstract class FlowView extends BoxView * not fit in the available span and also cannot be broken down). * * @param fv the flow view - * @param startOffset the start offset for the view to be created + * @param offset the start offset for the view to be created * @param spanLeft the available span * @param rowIndex the index of the row * @@ -218,13 +227,15 @@ public abstract class FlowView extends BoxView View logicalView = getLogicalView(fv); int viewIndex = logicalView.getViewIndex(offset, Position.Bias.Forward); + if (viewIndex == -1) + return null; + View child = logicalView.getView(viewIndex); int flowAxis = fv.getFlowAxis(); int span = (int) child.getPreferredSpan(flowAxis); if (span <= spanLeft) return child; - else if (child.getBreakWeight(flowAxis, offset, spanLeft) > BadBreakWeight) // FIXME: What to do with the pos parameter here? @@ -326,7 +337,19 @@ public abstract class FlowView extends BoxView */ public int getViewIndex(int pos, Position.Bias b) { - return getElement().getElementIndex(pos); + int index = -1; + int i = 0; + for (Iterator it = children.iterator(); it.hasNext(); i++) + { + View child = (View) it.next(); + if (child.getStartOffset() >= pos + && child.getEndOffset() < pos) + { + index = i; + break; + } + } + return index; } /** @@ -373,6 +396,37 @@ public abstract class FlowView extends BoxView throw new AssertionError("This method must not be called in " + "LogicalView."); } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + assert false : "getNextVisualPositionFrom() must not be called in " + + "LogicalView"; + return 0; + } } /** @@ -478,7 +532,7 @@ public abstract class FlowView extends BoxView * The real children are created at layout time and each represent one * row. * - * This method is called by {@link #setParent} in order to initialize + * This method is called by {@link View#setParent} in order to initialize * the view. * * @param vf the view factory to use for creating the child views @@ -502,7 +556,7 @@ public abstract class FlowView extends BoxView /** * Performs the layout of this view. If the span along the flow axis changed, - * this first calls {@link FlowStrategy.layout} in order to rebuild the + * this first calls {@link FlowStrategy#layout} in order to rebuild the * rows of this view. Then the superclass's behaviour is called to arrange * the rows within the box. * diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java index 1dd46c4b0f4..4c65de0f5f4 100644 --- a/libjava/classpath/javax/swing/text/GapContent.java +++ b/libjava/classpath/javax/swing/text/GapContent.java @@ -39,10 +39,15 @@ exception statement from your version. */ package javax.swing.text; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; +import java.util.Iterator; import java.util.ListIterator; +import java.util.Vector; +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.CannotRedoException; +import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoableEdit; /** @@ -59,7 +64,6 @@ import javax.swing.undo.UndoableEdit; public class GapContent implements AbstractDocument.Content, Serializable { - /** * A {@link Position} implementation for <code>GapContent</code>. */ @@ -114,6 +118,11 @@ public class GapContent */ public int getOffset() { + // Check precondition. + assert mark <= gapStart || mark >= gapEnd : "mark: " + mark + + ", gapStart: " + gapStart + + ", gapEnd: " + gapEnd; + if (mark <= gapStart) return mark; else @@ -121,13 +130,91 @@ public class GapContent } } - private static final long serialVersionUID = 8374645204155842629L; + class UndoInsertString extends AbstractUndoableEdit + { + public int where, length; + String text; + public UndoInsertString(int start, int len) + { + where = start; + length = len; + } + + public void undo () throws CannotUndoException + { + super.undo(); + try + { + text = getString(where, length); + remove(where, length); + } + catch (BadLocationException ble) + { + throw new CannotUndoException(); + } + } + + public void redo () throws CannotUndoException + { + super.redo(); + try + { + insertString(where, text); + } + catch (BadLocationException ble) + { + throw new CannotRedoException(); + } + } + + } + + class UndoRemove extends AbstractUndoableEdit + { + public int where; + String text; + public UndoRemove(int start, String removedText) + { + where = start; + text = removedText; + } + + public void undo () throws CannotUndoException + { + super.undo(); + try + { + insertString(where, text); + } + catch (BadLocationException ble) + { + throw new CannotUndoException(); + } + } + + public void redo () throws CannotUndoException + { + super.redo(); + try + { + remove(where, text.length()); + } + catch (BadLocationException ble) + { + throw new CannotRedoException(); + } + } + + } + + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = -6226052713477823730L; /** * This is the default buffer size and the amount of bytes that a buffer is * extended if it is full. */ - static final int DEFAULT_BUFSIZE = 64; + static final int DEFAULT_BUFSIZE = 10; /** * The text buffer. @@ -148,7 +235,7 @@ public class GapContent * The positions generated by this GapContent. They are kept in an ordered * fashion, so they can be looked up easily. */ - LinkedList positions; + ArrayList positions; /** * Creates a new GapContent object. @@ -166,10 +253,10 @@ public class GapContent public GapContent(int size) { buffer = (char[]) allocateArray(size); - gapStart = 0; - gapEnd = size - 1; - buffer[size - 1] = '\n'; - positions = new LinkedList(); + gapStart = 1; + gapEnd = size; + buffer[0] = '\n'; + positions = new ArrayList(); } /** @@ -211,8 +298,7 @@ public class GapContent * @param where the position where the string is inserted * @param str the string that is to be inserted * - * @return an UndoableEdit object (currently not supported, so - * <code>null</code> is returned) + * @return an UndoableEdit object * * @throws BadLocationException if <code>where</code> is not a valid * location in the buffer @@ -228,9 +314,9 @@ public class GapContent throw new BadLocationException("the where argument cannot be greater" + " than the content length", where); - replace(where, 0, str.toCharArray(), str.length()); + replace(where, 0, str.toCharArray(), strLen); - return null; + return new UndoInsertString(where, strLen); } /** @@ -239,8 +325,7 @@ public class GapContent * @param where the position where the content is to be removed * @param nitems number of characters to be removed * - * @return an UndoableEdit object (currently not supported, so - * <code>null</code> is returned) + * @return an UndoableEdit object * * @throws BadLocationException if <code>where</code> is not a valid * location in the buffer @@ -257,9 +342,10 @@ public class GapContent throw new BadLocationException("where + nitems cannot be greater" + " than the content length", where + nitems); + String removedText = getString(where, nitems); replace(where, nitems, null, 0); - return null; + return new UndoRemove(where, removedText); } /** @@ -372,7 +458,6 @@ public class GapContent if (index < 0) index = -(index + 1); positions.add(index, pos); - return pos; } @@ -386,7 +471,14 @@ public class GapContent */ protected void shiftEnd(int newSize) { - int delta = (gapEnd - gapStart) - newSize; + assert newSize > (gapEnd - gapStart) : "The new gap size must be greater " + + "than the old gap size"; + + int delta = newSize - gapEnd + gapStart; + // Update the marks after the gapEnd. + adjustPositionsInRange(gapEnd, buffer.length - gapEnd, delta); + + // Copy the data around. char[] newBuf = (char[]) allocateArray(length() + newSize); System.arraycopy(buffer, 0, newBuf, 0, gapStart); System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length @@ -394,18 +486,6 @@ public class GapContent gapEnd = gapStart + newSize; buffer = newBuf; - // Update the marks after the gapEnd. - int index = Collections.binarySearch(positions, new GapContentPosition( - gapEnd)); - if (index < 0) - { - index = -(index + 1); - } - for (ListIterator i = positions.listIterator(index); i.hasNext();) - { - GapContentPosition p = (GapContentPosition) i.next(); - p.mark += delta; - } } /** @@ -415,32 +495,15 @@ public class GapContent */ protected void shiftGap(int newGapStart) { - int newGapEnd = newGapStart + (gapEnd - gapStart); - - // Update the positions between newGapEnd and (old) gapEnd. The marks - // must be shifted by (gapEnd - newGapEnd). - int index1 = Collections.binarySearch(positions, - new GapContentPosition(gapEnd)); - int index2 = Collections.binarySearch(positions, - new GapContentPosition(newGapEnd)); - if (index1 > 0 && index2 > 0) - { - int i1 = Math.min(index1, index2); - int i2 = Math.max(index1, index2); - for (ListIterator i = positions.listIterator(i1); i.hasNext();) - { - if (i.nextIndex() > i2) - break; - - GapContentPosition p = (GapContentPosition) i.next(); - p.mark += gapEnd - newGapEnd; - } - } - if (newGapStart == gapStart) return; - else if (newGapStart < gapStart) + + int newGapEnd = newGapStart + gapEnd - gapStart; + if (newGapStart < gapStart) { + // Update the positions between newGapStart and (old) gapStart. The marks + // must be shifted by (gapEnd - gapStart). + adjustPositionsInRange(newGapStart, gapStart - newGapStart, gapEnd - gapStart); System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart - newGapStart); gapStart = newGapStart; @@ -448,11 +511,54 @@ public class GapContent } else { + // Update the positions between newGapEnd and (old) gapEnd. The marks + // must be shifted by (gapEnd - gapStart). + adjustPositionsInRange(gapEnd, newGapEnd - gapEnd, -(gapEnd - gapStart)); System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart - gapStart); gapStart = newGapStart; gapEnd = newGapEnd; } + if (gapStart == 0) + resetMarksAtZero(); + } + + /** + * Shifts the gap start downwards. This does not affect the content of the + * buffer. This only updates the gap start and all the marks that are between + * the old gap start and the new gap start. They all are squeezed to the start + * of the gap, because their location has been removed. + * + * @param newGapStart the new gap start + */ + protected void shiftGapStartDown(int newGapStart) + { + if (newGapStart == gapStart) + return; + + assert newGapStart < gapStart : "The new gap start must be less than the " + + "old gap start."; + setPositionsInRange(newGapStart, gapStart - newGapStart, gapStart); + gapStart = newGapStart; + } + + /** + * Shifts the gap end upwards. This does not affect the content of the + * buffer. This only updates the gap end and all the marks that are between + * the old gap end and the new end start. They all are squeezed to the end + * of the gap, because their location has been removed. + * + * @param newGapEnd the new gap start + */ + protected void shiftGapEndUp(int newGapEnd) + { + if (newGapEnd == gapEnd) + return; + + assert newGapEnd > gapEnd : "The new gap end must be greater than the " + + "old gap end."; + setPositionsInRange(gapEnd, newGapEnd - gapEnd, newGapEnd + 1); + gapEnd = newGapEnd; } /** @@ -476,13 +582,15 @@ public class GapContent protected void replace(int position, int rmSize, Object addItems, int addSize) { + if (gapStart != position) + shiftGap(position); // Remove content - shiftGap(position); - gapEnd += rmSize; + if (rmSize > 0) + shiftGapEndUp(gapEnd + rmSize); // If gap is too small, enlarge the gap. - if ((gapEnd - gapStart) < addSize) - shiftEnd(addSize); + if ((gapEnd - gapStart) <= addSize) + shiftEnd((addSize - gapEnd + gapStart + 1) * 2 + gapEnd + DEFAULT_BUFSIZE); // Add new items to the buffer. if (addItems != null) @@ -491,4 +599,164 @@ public class GapContent gapStart += addSize; } } + + /** + * Returns the start index of the gap within the buffer array. + * + * @return the start index of the gap within the buffer array + */ + protected final int getGapStart() + { + return gapStart; + } + + /** + * Returns the end index of the gap within the buffer array. + * + * @return the end index of the gap within the buffer array + */ + protected final int getGapEnd() + { + return gapEnd; + } + + /** + * Returns all <code>Position</code>s that are in the range specified by + * <code>offset</code> and </code>length</code> within the buffer array. + * + * @param v the vector to use; if <code>null</code>, a new Vector is allocated + * @param offset the start offset of the range to search + * @param length the length of the range to search + * + * @return the positions within the specified range + */ + protected Vector getPositionsInRange(Vector v, int offset, int length) + { + Vector res = v; + if (res == null) + res = new Vector(); + else + res.clear(); + + int endOffset = offset + length; + + int index1 = Collections.binarySearch(positions, + new GapContentPosition(offset)); + if (index1 < 0) + index1 = -(index1 + 1); + for (ListIterator i = positions.listIterator(index1); i.hasNext();) + { + GapContentPosition p = (GapContentPosition) i.next(); + if (p.mark > endOffset) + break; + if (p.mark >= offset && p.mark <= endOffset) + res.add(p); + } + return res; + } + + /** + * Sets 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 to <code>value</code> + * + * @param offset the start offset of the range to search + * @param length the length of the range to search + * @param value the new value for each mark + */ + void setPositionsInRange(int offset, int length, int value) + { + int endOffset = offset + length; + + int index1 = Collections.binarySearch(positions, + new GapContentPosition(offset)); + if (index1 < 0) + index1 = -(index1 + 1); + for (ListIterator i = positions.listIterator(index1); i.hasNext();) + { + GapContentPosition p = (GapContentPosition) i.next(); + if (p.mark > endOffset) + break; + + if (p.mark >= offset && p.mark <= endOffset) + p.mark = value; + } + } + + /** + * 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 incr the increment + */ + void adjustPositionsInRange(int offset, int length, int incr) + { + int endOffset = offset + length; + + int index1 = Collections.binarySearch(positions, + new GapContentPosition(offset)); + if (index1 < 0) + index1 = -(index1 + 1); + for (ListIterator i = positions.listIterator(index1); i.hasNext();) + { + GapContentPosition p = (GapContentPosition) i.next(); + if (p.mark > endOffset) + break; + + if (p.mark >= offset && p.mark <= endOffset) + p.mark += incr; + } + } + + /** + * Resets all <code>Position</code> that have an offset of <code>0</code>, + * to also have an array index of <code>0</code>. This might be necessary + * after a call to <code>shiftGap(0)</code>, since then the marks at offset + * <code>0</code> get shifted to <code>gapEnd</code>. + */ + protected void resetMarksAtZero() + { + if (gapStart != 0) + return; + + setPositionsInRange(gapEnd, 0, 0); + } + + /** + * Outputs debugging info to System.err. It prints out the buffer array, + * the gapStart is marked by a < sign, the gapEnd is marked by a > + * sign and each position is marked by a # sign. + */ + private void dump() + { + System.err.println("GapContent debug information"); + System.err.println("buffer length: " + buffer.length); + System.err.println("gap start: " + gapStart); + System.err.println("gap end: " + gapEnd); + for (int i = 0; i < buffer.length; i++) + { + if (i == gapStart) + System.err.print('<'); + if (i == gapEnd) + System.err.print('>'); + + if (!Character.isISOControl(buffer[i])) + System.err.print(buffer[i]); + else + System.err.print('.'); + } + System.err.println(); + } + + private void dumpPositions() + { + for (Iterator i = positions.iterator(); i.hasNext();) + { + GapContentPosition pos = (GapContentPosition) i.next(); + System.err.println("position at: " + pos.mark); + } + } } diff --git a/libjava/classpath/javax/swing/text/GlyphView.java b/libjava/classpath/javax/swing/text/GlyphView.java index f9e60972d84..eb1fadd4f2d 100644 --- a/libjava/classpath/javax/swing/text/GlyphView.java +++ b/libjava/classpath/javax/swing/text/GlyphView.java @@ -44,6 +44,12 @@ import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.Toolkit; +import java.text.BreakIterator; + +import javax.swing.SwingConstants; +import javax.swing.event.DocumentEvent; +import javax.swing.text.Position.Bias; /** * Renders a run of styled text. This {@link View} subclass paints the @@ -52,9 +58,7 @@ import java.awt.Shape; * * @author Roman Kennke (roman@kennke.org) */ -public class GlyphView - extends View - implements TabableView, Cloneable +public class GlyphView extends View implements TabableView, Cloneable { /** @@ -68,15 +72,47 @@ public class GlyphView */ public GlyphPainter() { + // Nothing to do here. } /** + * Returns the ascent of the font that is used by this glyph painter. + * + * @param v the glyph view + * + * @return the ascent of the font that is used by this glyph painter + */ + public abstract float getAscent(GlyphView v); + + /** + * Returns the descent of the font that is used by this glyph painter. + * + * @param v the glyph view + * + * @return the descent of the font that is used by this glyph painter + */ + public abstract float getDescent(GlyphView v); + + /** * Returns the full height of the rendered text. * * @return the full height of the rendered text */ public abstract float getHeight(GlyphView view); - + + /** + * Determines the model offset, so that the text between <code>p0</code> + * and this offset fits within the span starting at <code>x</code> with + * the length of <code>len</code>. + * + * @param v the glyph view + * @param p0 the starting offset in the model + * @param x the start location in the view + * @param len the length of the span in the view + */ + public abstract int getBoundedPosition(GlyphView v, int p0, float x, + float len); + /** * Paints the glyphs. * @@ -97,8 +133,8 @@ public class GlyphView * @param view the glyph view * @param pos the position of the character in the model * @param a the area that is occupied by the view - * @param bias either {@link Position.Bias.Forward} or - * {@link Position.Bias.Backward} depending on the preferred + * @param b either {@link Position.Bias#Forward} or + * {@link Position.Bias#Backward} depending on the preferred * direction bias. If <code>null</code> this defaults to * <code>Position.Bias.Forward</code> * @@ -114,6 +150,20 @@ public class GlyphView throws BadLocationException; /** + * Maps a visual position into a document location. + * + * @param v the glyph view + * @param x the X coordinate of the visual position + * @param y the Y coordinate of the visual position + * @param a the allocated region + * @param biasRet filled with the bias of the model location on method exit + * + * @return the model location that represents the specified view location + */ + public abstract int viewToModel(GlyphView v, float x, float y, Shape a, + Position.Bias[] biasRet); + + /** * Determine the span of the glyphs from location <code>p0</code> to * location <code>p1</code>. If <code>te</code> is not <code>null</code>, * then TABs are expanded using this <code>TabExpander</code>. @@ -122,7 +172,7 @@ public class GlyphView * * @param view the glyph view * @param p0 the starting location in the document model - * @param p0 the end location in the document model + * @param p1 the end location in the document model * @param te the tab expander to use * @param x the location at which the view is located * @@ -132,6 +182,69 @@ public class GlyphView public abstract float getSpan(GlyphView view, int p0, int p1, TabExpander te, float x); + + /** + * Returns the model location that should be used to place a caret when + * moving the caret through the document. + * + * @param v the glyph view + * @param pos the current model location + * @param b the bias for <code>p</code> + * @param a the allocated region for the glyph view + * @param direction the direction from the current position; Must be one of + * {@link SwingConstants#EAST}, {@link SwingConstants#WEST}, + * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH} + * @param biasRet filled with the bias of the resulting location when method + * returns + * + * @return the location within the document that should be used to place the + * caret when moving the caret around the document + * + * @throws BadLocationException if <code>pos</code> is an invalid model + * location + * @throws IllegalArgumentException if <code>d</code> is invalid + */ + public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, + Shape a, int direction, + Position.Bias[] biasRet) + throws BadLocationException + + { + int result = pos; + switch (direction) + { + case SwingConstants.EAST: + result = pos + 1; + break; + case SwingConstants.WEST: + result = pos - 1; + break; + case SwingConstants.NORTH: + case SwingConstants.SOUTH: + default: + // This should be handled in enclosing view, since the glyph view + // does not layout vertically. + break; + } + return result; + } + + /** + * Returns a painter that can be used to render the specified glyph view. + * If this glyph painter is stateful, then it should return a new instance. + * However, if this painter is stateless it should return itself. The + * default behaviour is to return itself. + * + * @param v the glyph view for which to create a painter + * @param p0 the start offset of the rendered area + * @param p1 the end offset of the rendered area + * + * @return a painter that can be used to render the specified glyph view + */ + public GlyphPainter getPainter(GlyphView v, int p0, int p1) + { + return this; + } } /** @@ -147,7 +260,7 @@ public class GlyphView public float getHeight(GlyphView view) { Font font = view.getFont(); - FontMetrics metrics = view.getContainer().getFontMetrics(font); + FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(font); float height = metrics.getHeight(); return height; } @@ -173,11 +286,40 @@ public class GlyphView if (parent instanceof TabExpander) tabEx = (TabExpander) parent; - // FIXME: Set character attributes like font-family, font-size, colors. - Color foreground = view.getForeground(); - g.setColor(foreground); - Utilities.drawTabbedText(txt, bounds.x, bounds.y, g, tabEx, - txt.offset); + // Fill the background of the text run. + Color background = view.getBackground(); + g.setColor(background); + int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(), + bounds.x, tabEx, txt.offset); + g.fillRect(bounds.x, bounds.y, width, height); + + // Draw the actual text. + g.setColor(view.getForeground()); + g.setFont(view.getFont()); + if (view.isSuperscript()) + // TODO: Adjust font for superscripting. + Utilities.drawTabbedText(txt, bounds.x, bounds.y - height / 2, g, tabEx, + txt.offset); + else if (view.isSubscript()) + // TODO: Adjust font for subscripting. + Utilities.drawTabbedText(txt, bounds.x, bounds.y + height / 2, g, tabEx, + txt.offset); + else + Utilities.drawTabbedText(txt, bounds.x, bounds.y, g, tabEx, + txt.offset); + + if (view.isStikeThrough()) + { + int strikeHeight = (int) (getAscent(view) / 2); + g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width, + bounds.y + strikeHeight); + } + if (view.isUnderline()) + { + int lineHeight = (int) getAscent(view); + g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width, + bounds.y + lineHeight); + } } /** @@ -188,8 +330,8 @@ public class GlyphView * @param view the glyph view * @param pos the position of the character in the model * @param a the area that is occupied by the view - * @param bias either {@link Position.Bias.Forward} or - * {@link Position.Bias.Backward} depending on the preferred + * @param b either {@link Position.Bias#Forward} or + * {@link Position.Bias#Backward} depending on the preferred * direction bias. If <code>null</code> this defaults to * <code>Position.Bias.Forward</code> * @@ -225,7 +367,7 @@ public class GlyphView * * @param view the glyph view * @param p0 the starting location in the document model - * @param p0 the end location in the document model + * @param p1 the end location in the document model * @param te the tab expander to use * @param x the location at which the view is located * @@ -237,11 +379,90 @@ public class GlyphView { Element el = view.getElement(); Font font = view.getFont(); - FontMetrics fm = view.getContainer().getFontMetrics(font); + FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font); Segment txt = view.getText(p0, p1); int span = Utilities.getTabbedTextWidth(txt, fm, (int) x, te, p0); return span; } + + /** + * Returns the ascent of the text run that is rendered by this + * <code>GlyphPainter</code>. + * + * @param v the glyph view + * + * @return the ascent of the text run that is rendered by this + * <code>GlyphPainter</code> + * + * @see FontMetrics#getAscent() + */ + public float getAscent(GlyphView v) + { + Font font = v.getFont(); + FontMetrics fm = v.getContainer().getFontMetrics(font); + return fm.getAscent(); + } + + /** + * Returns the descent of the text run that is rendered by this + * <code>GlyphPainter</code>. + * + * @param v the glyph view + * + * @return the descent of the text run that is rendered by this + * <code>GlyphPainter</code> + * + * @see FontMetrics#getDescent() + */ + public float getDescent(GlyphView v) + { + Font font = v.getFont(); + FontMetrics fm = v.getContainer().getFontMetrics(font); + return fm.getDescent(); + } + + /** + * Determines the model offset, so that the text between <code>p0</code> + * and this offset fits within the span starting at <code>x</code> with + * the length of <code>len</code>. + * + * @param v the glyph view + * @param p0 the starting offset in the model + * @param x the start location in the view + * @param len the length of the span in the view + */ + public int getBoundedPosition(GlyphView v, int p0, float x, float len) + { + TabExpander te = v.getTabExpander(); + Segment txt = v.getText(p0, v.getEndOffset()); + Font font = v.getFont(); + FontMetrics fm = v.getContainer().getFontMetrics(font); + int pos = Utilities.getTabbedTextOffset(txt, fm, (int) x, + (int) (x + len), te, p0, false); + return pos; + } + + /** + * Maps a visual position into a document location. + * + * @param v the glyph view + * @param x the X coordinate of the visual position + * @param y the Y coordinate of the visual position + * @param a the allocated region + * @param biasRet filled with the bias of the model location on method exit + * + * @return the model location that represents the specified view location + */ + public int viewToModel(GlyphView v, float x, float y, Shape a, + Bias[] biasRet) + { + Rectangle b = a.getBounds(); + assert b.contains(x, y) : "The coordinates are expected to be within the " + + "view's bounds: x=" + x + ", y=" + y + + "a=" + a; + int pos = getBoundedPosition(v, v.getStartOffset(), b.x, x - b.x); + return pos; + } } /** @@ -250,6 +471,16 @@ public class GlyphView GlyphPainter glyphPainter; /** + * The start offset within the document for this view. + */ + int startOffset; + + /** + * The end offset within the document for this view. + */ + int endOffset; + + /** * Creates a new <code>GlyphView</code> for the given <code>Element</code>. * * @param element the element that is rendered by this GlyphView @@ -257,6 +488,8 @@ public class GlyphView public GlyphView(Element element) { super(element); + startOffset = element.getStartOffset(); + endOffset = element.getEndOffset(); } /** @@ -319,16 +552,21 @@ public class GlyphView */ public float getPreferredSpan(int axis) { - Element el = getElement(); + float span = 0; checkPainter(); GlyphPainter painter = getGlyphPainter(); - TabExpander tabEx = null; - View parent = getParent(); - if (parent instanceof TabExpander) - tabEx = (TabExpander) parent; - // FIXME: Figure out how to determine the x parameter. - float span = painter.getSpan(this, el.getStartOffset(), el.getEndOffset(), - tabEx, 0.F); + if (axis == X_AXIS) + { + Element el = getElement(); + TabExpander tabEx = null; + View parent = getParent(); + if (parent instanceof TabExpander) + tabEx = (TabExpander) parent; + span = painter.getSpan(this, getStartOffset(), getEndOffset(), + tabEx, 0.F); + } + else + span = painter.getHeight(this); return span; } @@ -372,8 +610,9 @@ public class GlyphView */ public int viewToModel(float x, float y, Shape a, Position.Bias[] b) { - // FIXME: not implemented - return 0; + checkPainter(); + GlyphPainter painter = getGlyphPainter(); + return painter.viewToModel(this, x, y, a, b); } /** @@ -383,12 +622,11 @@ public class GlyphView */ public TabExpander getTabExpander() { - // TODO: Figure out if this is correct. TabExpander te = null; View parent = getParent(); - if (parent instanceof ParagraphView) - te = (ParagraphView) parent; + if (parent instanceof TabExpander) + te = (TabExpander) parent; return te; } @@ -428,23 +666,26 @@ public class GlyphView } catch (BadLocationException ex) { - throw new AssertionError("BadLocationException must not be thrown " - + "here"); + AssertionError ae; + ae = new AssertionError("BadLocationException must not be thrown " + + "here"); + ae.initCause(ex); + throw ae; } FontMetrics fm = null; // Fetch font metrics somewhere. return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0); } /** - * Returns the starting offset in the document model of the portion + * Returns the start offset in the document model of the portion * of text that this view is responsible for. * - * @return the starting offset in the document model of the portion + * @return the start offset in the document model of the portion * of text that this view is responsible for */ - public int getBeginIndex() + public int getStartOffset() { - return getElement().getStartOffset(); + return startOffset; } /** @@ -454,9 +695,9 @@ public class GlyphView * @return the end offset in the document model of the portion * of text that this view is responsible for */ - public int getEndIndex() + public int getEndOffset() { - return getElement().getEndOffset(); + return endOffset; } /** @@ -476,8 +717,11 @@ public class GlyphView } catch (BadLocationException ex) { - throw new AssertionError("BadLocationException should not be " - + "thrown here. p0 = " + p0 + ", p1 = " + p1); + AssertionError ae; + ae = new AssertionError("BadLocationException should not be " + + "thrown here. p0 = " + p0 + ", p1 = " + p1); + ae.initCause(ex); + throw ae; } return txt; @@ -518,4 +762,332 @@ public class GlyphView AttributeSet atts = el.getAttributes(); return StyleConstants.getForeground(atts); } + + /** + * Returns the background color which should be used to paint the text. + * This is fetched from the associated element's text attributes using + * {@link StyleConstants#getBackground}. + * + * @return the background color which should be used to paint the text + */ + public Color getBackground() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + return StyleConstants.getBackground(atts); + } + + /** + * Determines whether the text should be rendered strike-through or not. This + * is determined using the method + * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of + * this view. + * + * @return whether the text should be rendered strike-through or not + */ + public boolean isStikeThrough() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + return StyleConstants.isStrikeThrough(atts); + } + + /** + * Determines whether the text should be rendered as subscript or not. This + * is determined using the method + * {@link StyleConstants#isSubscript(AttributeSet)} on the element of + * this view. + * + * @return whether the text should be rendered as subscript or not + */ + public boolean isSubscript() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + return StyleConstants.isSubscript(atts); + } + + /** + * Determines whether the text should be rendered as superscript or not. This + * is determined using the method + * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of + * this view. + * + * @return whether the text should be rendered as superscript or not + */ + public boolean isSuperscript() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + return StyleConstants.isSuperscript(atts); + } + + /** + * Determines whether the text should be rendered as underlined or not. This + * is determined using the method + * {@link StyleConstants#isUnderline(AttributeSet)} on the element of + * this view. + * + * @return whether the text should be rendered as underlined or not + */ + public boolean isUnderline() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + return StyleConstants.isUnderline(atts); + } + + /** + * Creates and returns a shallow clone of this GlyphView. This is used by + * the {@link #createFragment} and {@link #breakView} methods. + * + * @return a shallow clone of this GlyphView + */ + protected final Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException ex) + { + AssertionError err = new AssertionError("CloneNotSupportedException " + + "must not be thrown here"); + err.initCause(ex); + throw err; + } + } + + /** + * Tries to break the view near the specified view span <code>len</code>. + * The glyph view can only be broken in the X direction. For Y direction it + * returns itself. + * + * @param axis the axis for breaking, may be {@link View#X_AXIS} or + * {@link View#Y_AXIS} + * @param p0 the model location where the fragment should start + * @param pos the view position along the axis where the fragment starts + * @param len the desired length of the fragment view + * + * @return the fragment view, or <code>this</code> if breaking was not + * possible + */ + public View breakView(int axis, int p0, float pos, float len) + { + if (axis == Y_AXIS) + return this; + + checkPainter(); + GlyphPainter painter = getGlyphPainter(); + int breakLocation = painter.getBoundedPosition(this, p0, pos, len); + // Try to find a suitable line break. + BreakIterator lineBreaker = BreakIterator.getLineInstance(); + Segment txt = new Segment(); + try + { + getDocument().getText(getStartOffset(), getEndOffset(), txt); + } + catch (BadLocationException ex) + { + AssertionError err = new AssertionError("BadLocationException must not " + + "be thrown here."); + err.initCause(ex); + throw err; + } + lineBreaker.setText(txt); + int goodBreakLocation = lineBreaker.previous(); + if (goodBreakLocation != BreakIterator.DONE) + breakLocation = goodBreakLocation; + + View brokenView = createFragment(p0, breakLocation); + return brokenView; + } + + /** + * Determines how well the specified view location is suitable for inserting + * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then + * this method forwards to the superclass, if <code>axis</code> is + * <code>View.X_AXIS</code> then this method returns + * {@link View#ExcellentBreakWeight} if there is a suitable break location + * (usually whitespace) within the specified view span, or + * {@link View#GoodBreakWeight} if not. + * + * @param axis the axis along which the break weight is requested + * @param pos the starting view location + * @param len the length of the span at which the view should be broken + * + * @return the break weight + */ + public int getBreakWeight(int axis, float pos, float len) + { + int weight; + if (axis == Y_AXIS) + weight = super.getBreakWeight(axis, pos, len); + else + { + // Determine the model locations at pos and pos + len. + int spanX = (int) getPreferredSpan(X_AXIS); + int spanY = (int) getPreferredSpan(Y_AXIS); + Rectangle dummyAlloc = new Rectangle(0, 0, spanX, spanY); + Position.Bias[] biasRet = new Position.Bias[1]; + int offset1 = viewToModel(pos, spanY / 2, dummyAlloc, biasRet); + int offset2 = viewToModel(pos, spanY / 2, dummyAlloc, biasRet); + Segment txt = getText(offset1, offset2); + BreakIterator lineBreaker = BreakIterator.getLineInstance(); + lineBreaker.setText(txt); + int breakLoc = lineBreaker.previous(); + if (breakLoc == offset1) + weight = View.BadBreakWeight; + else if(breakLoc == BreakIterator.DONE) + weight = View.GoodBreakWeight; + else + weight = View.ExcellentBreakWeight; + } + return weight; + } + + /** + * Receives notification that some text attributes have changed within the + * text fragment that this view is responsible for. This calls + * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for + * both width and height. + * + * @param e the document event describing the change; not used here + * @param a the view allocation on screen; not used here + * @param vf the view factory; not used here + */ + public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + getParent().preferenceChanged(this, true, true); + } + + /** + * Receives notification that some text has been inserted within the + * text fragment that this view is responsible for. This calls + * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for + * width. + * + * @param e the document event describing the change; not used here + * @param a the view allocation on screen; not used here + * @param vf the view factory; not used here + */ + public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + getParent().preferenceChanged(this, true, false); + } + + /** + * Receives notification that some text has been removed within the + * text fragment that this view is responsible for. This calls + * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for + * width. + * + * @param e the document event describing the change; not used here + * @param a the view allocation on screen; not used here + * @param vf the view factory; not used here + */ + public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + getParent().preferenceChanged(this, true, false); + } + + /** + * Creates a fragment view of this view that starts at <code>p0</code> and + * ends at <code>p1</code>. + * + * @param p0 the start location for the fragment view + * @param p1 the end location for the fragment view + * + * @return the fragment view + */ + public View createFragment(int p0, int p1) + { + GlyphView fragment = (GlyphView) clone(); + fragment.startOffset = p0; + fragment.endOffset = p1; + return fragment; + } + + /** + * Returns the alignment of this view along the specified axis. For the Y + * axis this is <code>(height - descent) / height</code> for the used font, + * so that it is aligned along the baseline. + * For the X axis the superclass is called. + */ + public float getAlignment(int axis) + { + float align; + if (axis == Y_AXIS) + { + checkPainter(); + GlyphPainter painter = getGlyphPainter(); + float height = painter.getHeight(this); + float descent = painter.getDescent(this); + align = (height - descent) / height; + } + else + align = super.getAlignment(axis); + + return align; + } + + /** + * Returns the model location that should be used to place a caret when + * moving the caret through the document. + * + * @param pos the current model location + * @param bias the bias for <code>p</code> + * @param a the allocated region for the glyph view + * @param direction the direction from the current position; Must be one of + * {@link SwingConstants#EAST}, {@link SwingConstants#WEST}, + * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH} + * @param biasRet filled with the bias of the resulting location when method + * returns + * + * @return the location within the document that should be used to place the + * caret when moving the caret around the document + * + * @throws BadLocationException if <code>pos</code> is an invalid model + * location + * @throws IllegalArgumentException if <code>d</code> is invalid + */ + public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a, + int direction, Position.Bias[] biasRet) + throws BadLocationException + { + checkPainter(); + GlyphPainter painter = getGlyphPainter(); + return painter.getNextVisualPositionFrom(this, pos, bias, a, direction, + biasRet); + } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + // TODO: Implement this properly. + throw new AssertionError("Not implemented yet."); + } } diff --git a/libjava/classpath/javax/swing/text/IconView.java b/libjava/classpath/javax/swing/text/IconView.java index c7e22b6c3eb..6dd0f7ad34a 100644 --- a/libjava/classpath/javax/swing/text/IconView.java +++ b/libjava/classpath/javax/swing/text/IconView.java @@ -41,6 +41,8 @@ package javax.swing.text; import java.awt.Graphics; import java.awt.Shape; +import javax.swing.SwingConstants; + // TODO: Implement this class. public class IconView extends View @@ -125,4 +127,34 @@ public class IconView // FIXME: not implemented return 0; } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + // TODO: Implement this properly. + throw new AssertionError("Not implemented yet."); + } } diff --git a/libjava/classpath/javax/swing/text/InternationalFormatter.java b/libjava/classpath/javax/swing/text/InternationalFormatter.java index cedaf59feeb..86300a70d8e 100644 --- a/libjava/classpath/javax/swing/text/InternationalFormatter.java +++ b/libjava/classpath/javax/swing/text/InternationalFormatter.java @@ -57,9 +57,8 @@ import javax.swing.JFormattedTextField; public class InternationalFormatter extends DefaultFormatter { - - /** The serialVersoinUID. */ - private static final long serialVersionUID = 6941977820906408656L; + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 2436068675711756856L; /** The format that handles value to string conversion. */ Format format; diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java index b3fad79124c..83966bbdf47 100644 --- a/libjava/classpath/javax/swing/text/JTextComponent.java +++ b/libjava/classpath/javax/swing/text/JTextComponent.java @@ -50,9 +50,9 @@ import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.InputMethodListener; import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; import java.io.IOException; import java.io.Reader; import java.io.Writer; @@ -72,7 +72,6 @@ import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.Scrollable; import javax.swing.SwingConstants; -import javax.swing.Timer; import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.event.CaretEvent; @@ -89,6 +88,7 @@ public abstract class JTextComponent extends JComponent /** * AccessibleJTextComponent */ + // FIXME: This inner class is a complete stub and needs to be implemented. public class AccessibleJTextComponent extends AccessibleJComponent implements AccessibleText, CaretListener, DocumentListener { @@ -99,6 +99,7 @@ public abstract class JTextComponent extends JComponent */ public AccessibleJTextComponent() { + // Nothing to do here. } /** @@ -301,50 +302,6 @@ public abstract class JTextComponent extends JComponent } /** - * The timer that lets the caret blink. - */ - private class CaretBlinkTimer - extends Timer - implements ActionListener - { - /** - * Creates a new CaretBlinkTimer object with a default delay of 1 second. - */ - public CaretBlinkTimer() - { - super(1000, null); - addActionListener(this); - } - - /** - * Lets the caret blink. - */ - public void actionPerformed(ActionEvent ev) - { - Caret c = caret; - if (c != null) - c.setVisible(!c.isVisible()); - } - - /** - * Updates the blink delay according to the current caret. - */ - public void update() - { - stop(); - Caret c = caret; - if (c != null) - { - setDelay(c.getBlinkRate()); - if (editable) - start(); - else - c.setVisible(false); - } - } - } - - /** * According to <a * href="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html">this * report</a>, a pair of private classes wraps a {@link @@ -604,8 +561,7 @@ public abstract class JTextComponent extends JComponent } } - class DefaultTransferHandler - extends TransferHandler + class DefaultTransferHandler extends TransferHandler { public boolean canImport(JComponent component, DataFlavor[] flavors) { @@ -631,23 +587,23 @@ public abstract class JTextComponent extends JComponent int end = textComponent.getSelectionEnd(); if (start == end) - return; + return; try - { - // Copy text to clipboard. - String data = textComponent.getDocument().getText(start, end); - StringSelection selection = new StringSelection(data); - clipboard.setContents(selection, null); - - // Delete selected text on cut action. - if (action == MOVE) - doc.remove(start, end - start); - } + { + // Copy text to clipboard. + String data = textComponent.getDocument().getText(start, end); + StringSelection selection = new StringSelection(data); + clipboard.setContents(selection, null); + + // Delete selected text on cut action. + if (action == MOVE) + doc.remove(start, end - start); + } catch (BadLocationException e) - { - // Ignore this and do nothing. - } + { + // Ignore this and do nothing. + } } public int getSourceActions() @@ -661,30 +617,30 @@ public abstract class JTextComponent extends JComponent DataFlavor[] flavors = transferable.getTransferDataFlavors(); if (flavors == null) - return false; + return false; for (int i = 0; i < flavors.length; ++i) - if (flavors[i].equals(DataFlavor.stringFlavor)) - flavor = flavors[i]; + if (flavors[i].equals(DataFlavor.stringFlavor)) + flavor = flavors[i]; if (flavor == null) - return false; + return false; try - { - JTextComponent textComponent = (JTextComponent) component; - String data = (String) transferable.getTransferData(flavor); - textComponent.replaceSelection(data); - return true; - } + { + JTextComponent textComponent = (JTextComponent) component; + String data = (String) transferable.getTransferData(flavor); + textComponent.replaceSelection(data); + return true; + } catch (IOException e) - { - // Ignored. - } + { + // Ignored. + } catch (UnsupportedFlavorException e) - { - // Ignored. - } + { + // Ignored. + } return false; } @@ -701,8 +657,6 @@ public abstract class JTextComponent extends JComponent private char focusAccelerator = '\0'; private NavigationFilter navigationFilter; - private CaretBlinkTimer caretBlinkTimer; - /** * Get a Keymap from the global keymap table, by name. * @@ -960,8 +914,6 @@ public abstract class JTextComponent extends JComponent creatingKeymap = true; } - caretBlinkTimer = new CaretBlinkTimer(); - setFocusable(true); setEditable(true); enableEvents(AWTEvent.KEY_EVENT_MASK); @@ -1021,12 +973,17 @@ public abstract class JTextComponent extends JComponent { try { - doc.remove(0, doc.getLength()); - doc.insertString(0, text, null); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).replace(0, doc.getLength(), text, null); + else + { + doc.remove(0, doc.getLength()); + doc.insertString(0, text, null); + } } catch (BadLocationException e) { - // This can never happen. + // This can never happen. } } @@ -1044,12 +1001,12 @@ public abstract class JTextComponent extends JComponent try { - return doc.getText(0, doc.getLength()); + return doc.getText(0, doc.getLength()); } catch (BadLocationException e) { - // This should never happen. - return ""; + // This should never happen. + return ""; } } @@ -1080,12 +1037,12 @@ public abstract class JTextComponent extends JComponent { try { - return doc.getText(getSelectionStart(), getSelectionEnd()); + return doc.getText(getSelectionStart(), getSelectionEnd()); } catch (BadLocationException e) { - // This should never happen. - return null; + // This should never happen. + return null; } } @@ -1105,7 +1062,8 @@ public abstract class JTextComponent extends JComponent */ protected String paramString() { - return "JTextComponent"; + // TODO: Do something useful here. + return super.paramString(); } /** @@ -1194,14 +1152,6 @@ public abstract class JTextComponent extends JComponent if (editable == newValue) return; - if (newValue == true) - caretBlinkTimer.start(); - else - { - caretBlinkTimer.stop(); - caret.setVisible(false); - } - boolean oldValue = editable; editable = newValue; firePropertyChange("editable", oldValue, newValue); @@ -1230,8 +1180,6 @@ public abstract class JTextComponent extends JComponent Caret oldCaret = caret; caret = newCaret; - caretBlinkTimer.update(); - if (caret != null) caret.install(this); @@ -1399,7 +1347,7 @@ public abstract class JTextComponent extends JComponent start = Math.max(start, 0); start = Math.min(start, length); - end = Math.max(end, 0); + end = Math.max(end, start); end = Math.min(end, length); setCaretPosition(start); @@ -1422,28 +1370,28 @@ public abstract class JTextComponent extends JComponent // If content is empty delete selection. if (content == null) { - caret.setDot(dot); - return; + caret.setDot(dot); + return; } try { - int start = getSelectionStart(); - int end = getSelectionEnd(); - - // Remove selected text. - if (dot != mark) - doc.remove(start, end - start); - - // Insert new text. - doc.insertString(start, content, null); - - // Set dot to new position. - setCaretPosition(start + content.length()); + int start = getSelectionStart(); + int end = getSelectionEnd(); + + // Remove selected text. + if (dot != mark) + doc.remove(start, end - start); + + // Insert new text. + doc.insertString(start, content, null); + + // Set dot to new position. + setCaretPosition(start + content.length()); } catch (BadLocationException e) { - // This should never happen. + // This should never happen. } } @@ -1577,15 +1525,15 @@ public abstract class JTextComponent extends JComponent // Install default TransferHandler if none set. if (getTransferHandler() == null) { - if (defaultTransferHandler == null) - defaultTransferHandler = new DefaultTransferHandler(); - - setTransferHandler(defaultTransferHandler); + if (defaultTransferHandler == null) + defaultTransferHandler = new DefaultTransferHandler(); + + setTransferHandler(defaultTransferHandler); } // Perform action. ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, - action.getValue(Action.NAME).toString()); + action.getValue(Action.NAME).toString()); action.actionPerformed(event); } @@ -1669,5 +1617,20 @@ public abstract class JTextComponent extends JComponent throws IOException { output.write(getText()); - } + } + + /** + * Returns the tooltip text for this text component for the given mouse + * event. This forwards the call to + * {@link TextUI#getToolTipText(JTextComponent, Point)}. + * + * @param ev the mouse event + * + * @return the tooltip text for this text component for the given mouse + * event + */ + public String getToolTipText(MouseEvent ev) + { + return getUI().getToolTipText(this, ev.getPoint()); + } } diff --git a/libjava/classpath/javax/swing/text/LabelView.java b/libjava/classpath/javax/swing/text/LabelView.java index a10391613cd..4890735b925 100644 --- a/libjava/classpath/javax/swing/text/LabelView.java +++ b/libjava/classpath/javax/swing/text/LabelView.java @@ -38,10 +38,57 @@ exception statement from your version. */ package javax.swing.text; -// TODO: Implement this class. -public class LabelView - extends GlyphView +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Shape; + +import javax.swing.event.DocumentEvent; + +/** + * A {@link GlyphView} that caches the textattributes for most effective + * rendering. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class LabelView extends GlyphView { + + /** + * The background color. + */ + Color background; + + /** + * The foreground color. + */ + Color foreground; + + /** + * The background color. + */ + Font font; + + /** + * The strikethrough flag. + */ + boolean strikeThrough; + + /** + * The underline flag. + */ + boolean underline; + + /** + * The subscript flag. + */ + boolean subscript; + + /** + * The superscript flag. + */ + boolean superscript; + /** * Creates a new <code>GlyphView</code> for the given <code>Element</code>. * @@ -50,5 +97,194 @@ public class LabelView public LabelView(Element element) { super(element); + setPropertiesFromAttributes(); + } + + /** + * Loads the properties of this label view from the element's text + * attributes. This method is called from the constructor and the + * {@link #changedUpdate} method + */ + protected void setPropertiesFromAttributes() + { + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + background = StyleConstants.getBackground(atts); + foreground = StyleConstants.getForeground(atts); + strikeThrough = StyleConstants.isStrikeThrough(atts); + subscript = StyleConstants.isSubscript(atts); + superscript = StyleConstants.isSuperscript(atts); + underline = StyleConstants.isUnderline(atts); + + // Determine the font. + String family = StyleConstants.getFontFamily(atts); + int size = StyleConstants.getFontSize(atts); + int style = Font.PLAIN; + if (StyleConstants.isBold(atts)) + style |= Font.BOLD; + if (StyleConstants.isItalic(atts)) + style |= Font.ITALIC; + font = new Font(family, style, size); + } + + /** + * Receives notification when text attributes change in the chunk of + * text that this view is responsible for. This simply calls + * {@link #setPropertiesFromAttributes()}. + * + * @param e the document event + * @param a the allocation of this view + * @param vf the view factory to use for creating new views + */ + public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) + { + setPropertiesFromAttributes(); + } + + /** + * Returns the background color for the glyphs. + * + * @return the background color for the glyphs + */ + public Color getBackground() + { + return background; + } + + /** + * Sets the background color for the glyphs. A value of <code>null</code> + * means the background of the parent view should shine through. + * + * @param bg the background to set or <code>null</code> + * + * @since 1.5 + */ + protected void setBackground(Color bg) + { + background = bg; + } + + /** + * Returns the foreground color for the glyphs. + * + * @return the foreground color for the glyphs + */ + public Color getForeground() + { + return foreground; + } + + /** + * Returns the font for the glyphs. + * + * @return the font for the glyphs + */ + public Font getFont() + { + return font; + } + + /** + * Returns the font metrics of the current font. + * + * @return the font metrics of the current font + * + * @deprecated this is not used anymore + */ + protected FontMetrics getFontMetrics() + { + return getContainer().getGraphics().getFontMetrics(font); + } + + /** + * Returns <code>true</code> if the glyphs are rendered underlined, + * <code>false</code> otherwise. + * + * @return <code>true</code> if the glyphs are rendered underlined, + * <code>false</code> otherwise + */ + public boolean isUnderline() + { + return underline; + } + + /** + * Sets the underline flag. + * + * @param flag <code>true</code> if the glyphs are rendered underlined, + * <code>false</code> otherwise + */ + protected void setUnderline(boolean flag) + { + underline = flag; + } + + /** + * Returns <code>true</code> if the glyphs are rendered as subscript, + * <code>false</code> otherwise. + * + * @return <code>true</code> if the glyphs are rendered as subscript, + * <code>false</code> otherwise + */ + public boolean isSubscript() + { + return subscript; + } + + /** + * Sets the subscript flag. + * + * @param flag <code>true</code> if the glyphs are rendered as subscript, + * <code>false</code> otherwise + */ + protected void setSubscript(boolean flag) + { + subscript = flag; + } + + /** + * Returns <code>true</code> if the glyphs are rendered as superscript, + * <code>false</code> otherwise. + * + * @return <code>true</code> if the glyphs are rendered as superscript, + * <code>false</code> otherwise + */ + public boolean isSuperscript() + { + return superscript; + } + + /** + * Sets the superscript flag. + * + * @param flag <code>true</code> if the glyphs are rendered as superscript, + * <code>false</code> otherwise + */ + protected void setSuperscript(boolean flag) + { + superscript = flag; + } + + /** + * Returns <code>true</code> if the glyphs are rendered strike-through, + * <code>false</code> otherwise. + * + * @return <code>true</code> if the glyphs are rendered strike-through, + * <code>false</code> otherwise + */ + public boolean isStrikeThrough() + { + return strikeThrough; + } + + /** + * Sets the strike-through flag. + * + * @param flag <code>true</code> if the glyphs are rendered strike-through, + * <code>false</code> otherwise + */ + protected void setStrikeThrough(boolean flag) + { + strikeThrough = flag; } } diff --git a/libjava/classpath/javax/swing/text/LayoutQueue.java b/libjava/classpath/javax/swing/text/LayoutQueue.java index 83433b6eef5..b0c84b972b2 100644 --- a/libjava/classpath/javax/swing/text/LayoutQueue.java +++ b/libjava/classpath/javax/swing/text/LayoutQueue.java @@ -57,6 +57,7 @@ public class LayoutQueue */ public LayoutQueue() { + // Nothing to do here. } /** diff --git a/libjava/classpath/javax/swing/text/ParagraphView.java b/libjava/classpath/javax/swing/text/ParagraphView.java index 6c6006a2a0f..6fb121f949e 100644 --- a/libjava/classpath/javax/swing/text/ParagraphView.java +++ b/libjava/classpath/javax/swing/text/ParagraphView.java @@ -59,6 +59,11 @@ public class ParagraphView extends FlowView implements TabExpander { super(el, X_AXIS); } + public float getAlignment(int axis) + { + // FIXME: This is very likely not 100% correct. Work this out. + return 0.0F; + } } /** @@ -86,4 +91,29 @@ public class ParagraphView extends FlowView implements TabExpander { return new Row(getElement()); } + + /** + * Returns the alignment for this paragraph view for the specified axis. + * For the X_AXIS the paragraph view will be aligned at it's left edge + * (0.0F). For the Y_AXIS the paragraph view will be aligned at the + * center of it's first row. + * + * @param axis the axis which is examined + * + * @return the alignment for this paragraph view for the specified axis + */ + public float getAlignment(int axis) + { + if (axis == X_AXIS) + return 0.0F; + else if (getViewCount() > 0) + { + + float prefHeight = getPreferredSpan(Y_AXIS); + float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS); + return (firstRowHeight / 2.F) / prefHeight; + } + else + return 0.0F; + } } diff --git a/libjava/classpath/javax/swing/text/PlainDocument.java b/libjava/classpath/javax/swing/text/PlainDocument.java index 71070e92da7..9e600c4c908 100644 --- a/libjava/classpath/javax/swing/text/PlainDocument.java +++ b/libjava/classpath/javax/swing/text/PlainDocument.java @@ -132,8 +132,8 @@ public class PlainDocument extends AbstractDocument // collapse elements if the removal spans more than 1 line Element newEl = createLeafElement(rootElement, SimpleAttributeSet.EMPTY, - start, end - len); - rootElement.replace(i1, i2 - i1, new Element[]{ newEl }); + start, end); + rootElement.replace(i1, i2 - i1 + 1, new Element[]{ newEl }); } } @@ -147,4 +147,28 @@ public class PlainDocument extends AbstractDocument Element root = getDefaultRootElement(); return root.getElement(root.getElementIndex(pos)); } + + /** + * Inserts a string into the document. If the document property + * '<code>filterNewLines</code>' is set to <code>Boolean.TRUE</code>, then + * all newlines in the inserted string are replaced by space characters, + * otherwise the superclasses behaviour is executed. + * + * Inserting content causes a write lock to be acquired during this method + * call. + * + * @param offs the offset at which to insert the string + * @param str the string to be inserted + * @param atts the text attributes of the string to be inserted + * + * @throws BadLocationException + */ + public void insertString(int offs, String str, AttributeSet atts) + throws BadLocationException + { + String string = str; + if (Boolean.TRUE.equals(getProperty("filterNewlines"))) + string = str.replaceAll("\n", " "); + super.insertString(offs, string, atts); + } } diff --git a/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java index 91d7547e77c..9f5ee8ad3c8 100644 --- a/libjava/classpath/javax/swing/text/PlainView.java +++ b/libjava/classpath/javax/swing/text/PlainView.java @@ -46,15 +46,35 @@ import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; -public class PlainView extends View - implements TabExpander +import javax.swing.SwingConstants; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentEvent.ElementChange; + +public class PlainView extends View implements TabExpander { Color selectedColor; Color unselectedColor; + + /** + * The color that is used to draw disabled text fields. + */ + Color disabledColor; + Font font; + /** The length of the longest line in the Document **/ + float maxLineLength = -1; + + /** The longest line in the Document **/ + Element longestLine = null; + protected FontMetrics metrics; + /** + * The instance returned by {@link #getLineBuffer()}. + */ + private transient Segment lineBuffer; + public PlainView(Element elem) { super(elem); @@ -104,7 +124,7 @@ public class PlainView extends View // Get the rectangle for position. Element line = getElement().getElement(lineIndex); int lineStart = line.getStartOffset(); - Segment segment = new Segment(); + Segment segment = getLineBuffer(); document.getText(lineStart, position - lineStart, segment); int xoffset = Utilities.getTabbedTextWidth(segment, metrics, rect.x, this, lineStart); @@ -129,7 +149,9 @@ public class PlainView extends View } catch (BadLocationException e) { - // This should never happen. + AssertionError ae = new AssertionError("Unexpected bad location"); + ae.initCause(e); + throw ae; } } @@ -137,7 +159,7 @@ public class PlainView extends View throws BadLocationException { g.setColor(selectedColor); - Segment segment = new Segment(); + Segment segment = getLineBuffer(); getDocument().getText(p0, p1 - p0, segment); return Utilities.drawTabbedText(segment, x, y, g, this, 0); } @@ -145,8 +167,13 @@ public class PlainView extends View protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { - g.setColor(unselectedColor); - Segment segment = new Segment(); + JTextComponent textComponent = (JTextComponent) getContainer(); + if (textComponent.isEnabled()) + g.setColor(unselectedColor); + else + g.setColor(disabledColor); + + Segment segment = getLineBuffer(); getDocument().getText(p0, p1 - p0, segment); return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset); } @@ -161,7 +188,8 @@ public class PlainView extends View g.setFont(textComponent.getFont()); selectedColor = textComponent.getSelectedTextColor(); unselectedColor = textComponent.getForeground(); - + disabledColor = textComponent.getDisabledTextColor(); + Rectangle rect = s.getBounds(); // FIXME: Text may be scrolled. @@ -176,9 +204,19 @@ public class PlainView extends View } } + /** + * Returns the tab size of a tab. Checks the Document's + * properties for PlainDocument.tabSizeAttribute and returns it if it is + * defined, otherwise returns 8. + * + * @return the tab size. + */ protected int getTabSize() { - return 8; + Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute); + if (tabSize == null) + return 8; + return ((Integer)tabSize).intValue(); } /** @@ -191,10 +229,54 @@ public class PlainView extends View */ public float nextTabStop(float x, int tabStop) { - float tabSizePixels = getTabSize() + metrics.charWidth('m'); + float tabSizePixels = getTabSize() * metrics.charWidth('m'); return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels; } + /** + * Returns the length of the longest line, used for getting the span + * @return the length of the longest line + */ + float determineMaxLineLength() + { + // if the longest line is cached, return the cached value + if (maxLineLength != -1) + return maxLineLength; + + // otherwise we have to go through all the lines and find it + Element el = getElement(); + Segment seg = getLineBuffer(); + float span = 0; + for (int i = 0; i < el.getElementCount(); i++) + { + Element child = el.getElement(i); + int start = child.getStartOffset(); + int end = child.getEndOffset(); + try + { + el.getDocument().getText(start, end - start, seg); + } + catch (BadLocationException ex) + { + AssertionError ae = new AssertionError("Unexpected bad location"); + ae.initCause(ex); + throw ae; + } + + if (seg == null || seg.array == null || seg.count == 0) + continue; + + int width = metrics.charsWidth(seg.array, seg.offset, seg.count); + if (width > span) + { + longestLine = child; + span = width; + } + } + maxLineLength = span; + return maxLineLength; + } + public float getPreferredSpan(int axis) { if (axis != X_AXIS && axis != Y_AXIS) @@ -205,36 +287,16 @@ public class PlainView extends View float span = 0; Element el = getElement(); - Document doc = el.getDocument(); - Segment seg = new Segment(); switch (axis) { case X_AXIS: - // calculate the maximum of the line's widths - for (int i = 0; i < el.getElementCount(); i++) - { - Element child = el.getElement(i); - int start = child.getStartOffset(); - int end = child.getEndOffset(); - try { - doc.getText(start, start + end, seg); - } - catch (BadLocationException ex) - { - // throw new ClasspathAssertionError - // ("no BadLocationException should be thrown here"); - } - int width = metrics.charsWidth(seg.array, seg.offset, seg.count); - span = Math.max(span, width); - } - break; + span = determineMaxLineLength(); case Y_AXIS: default: span = metrics.getHeight() * el.getElementCount(); break; } - return span; } @@ -252,8 +314,251 @@ public class PlainView extends View */ public int viewToModel(float x, float y, Shape a, Position.Bias[] b) { - // FIXME: not implemented - return 0; + Rectangle rec = a.getBounds(); + Document doc = getDocument(); + Element root = doc.getDefaultRootElement(); + + // PlainView doesn't support line-wrapping so we can find out which + // Element was clicked on just by the y-position + int lineClicked = (int) (y - rec.y) / metrics.getHeight(); + if (lineClicked >= root.getElementCount()) + return getEndOffset() - 1; + + Element line = root.getElement(lineClicked); + Segment s = getLineBuffer(); + int start = line.getStartOffset(); + // We don't want the \n at the end of the line. + int end = line.getEndOffset() - 1; + try + { + doc.getText(start, end - start, s); + } + catch (BadLocationException ble) + { + AssertionError ae = new AssertionError("Unexpected bad location"); + ae.initCause(ble); + throw ae; + } + + int pos = Utilities.getTabbedTextOffset(s, metrics, rec.x, (int)x, this, start); + return Math.max (0, pos); + } + + /** + * Since insertUpdate and removeUpdate each deal with children + * Elements being both added and removed, they both have to perform + * the same checks. So they both simply call this method. + * @param changes the DocumentEvent for the changes to the Document. + * @param a the allocation of the View. + * @param f the ViewFactory to use for rebuilding. + */ + protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) + { + Element el = getElement(); + ElementChange ec = changes.getChange(el); + + // If ec is null then no lines were added or removed, just + // repaint the changed line + if (ec == null) + { + int line = getElement().getElementIndex(changes.getOffset()); + damageLineRange(line, line, a, getContainer()); + return; + } + + Element[] removed = ec.getChildrenRemoved(); + Element[] newElements = ec.getChildrenAdded(); + + // If no Elements were added or removed, we just want to repaint + // the area containing the line that was modified + if (removed == null && newElements == null) + { + int line = getElement().getElementIndex(changes.getOffset()); + damageLineRange(line, line, a, getContainer()); + return; + } + + // Check to see if we removed the longest line, if so we have to + // search through all lines and find the longest one again + if (removed != null) + { + for (int i = 0; i < removed.length; i++) + if (removed[i].equals(longestLine)) + { + // reset maxLineLength and search through all lines for longest one + maxLineLength = -1; + determineMaxLineLength(); + ((JTextComponent)getContainer()).repaint(); + return; + } + } + + // If we've reached here, that means we haven't removed the longest line + if (newElements == null) + { + // No lines were added, just repaint the container and exit + ((JTextComponent)getContainer()).repaint(); + return; + } + + // Make sure we have the metrics + updateMetrics(); + + // If we've reached here, that means we haven't removed the longest line + // and we have added at least one line, so we have to check if added lines + // are longer than the previous longest line + Segment seg = getLineBuffer(); + float longestNewLength = 0; + Element longestNewLine = null; + + // Loop through the added lines to check their length + for (int i = 0; i < newElements.length; i++) + { + Element child = newElements[i]; + int start = child.getStartOffset(); + int end = child.getEndOffset(); + try + { + el.getDocument().getText(start, end - start, seg); + } + catch (BadLocationException ex) + { + AssertionError ae = new AssertionError("Unexpected bad location"); + ae.initCause(ex); + throw ae; + } + + if (seg == null || seg.array == null || seg.count == 0) + continue; + + int width = metrics.charsWidth(seg.array, seg.offset, seg.count); + if (width > longestNewLength) + { + longestNewLine = child; + longestNewLength = width; + } + } + + // Check if the longest of the new lines is longer than our previous + // longest line, and if so update our values + if (longestNewLength > maxLineLength) + { + maxLineLength = longestNewLength; + longestLine = longestNewLine; + } + // Repaint the container + ((JTextComponent)getContainer()).repaint(); + } + + /** + * This method is called when something is inserted into the Document + * that this View is displaying. + * + * @param changes the DocumentEvent for the changes. + * @param a the allocation of the View + * @param f the ViewFactory used to rebuild + */ + public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) + { + updateDamage(changes, a, f); + } + + /** + * This method is called when something is removed from the Document + * that this View is displaying. + * + * @param changes the DocumentEvent for the changes. + * @param a the allocation of the View + * @param f the ViewFactory used to rebuild + */ + public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) + { + updateDamage(changes, a, f); + } + + /** + * This method is called when attributes were changed in the + * Document in a location that this view is responsible for. + */ + public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f) + { + updateDamage(changes, a, f); + } + + /** + * Repaint the given line range. This is called from insertUpdate, + * changedUpdate, and removeUpdate when no new lines were added + * and no lines were removed, to repaint the line that was + * modified. + * + * @param line0 the start of the range + * @param line1 the end of the range + * @param a the rendering region of the host + * @param host the Component that uses this View (used to call repaint + * on that Component) + * + * @since 1.4 + */ + protected void damageLineRange (int line0, int line1, Shape a, Component host) + { + if (a == null) + return; + + Rectangle rec0 = lineToRect(a, line0); + Rectangle rec1 = lineToRect(a, line1); + + if (rec0 == null || rec1 == null) + // something went wrong, repaint the entire host to be safe + host.repaint(); + else + { + Rectangle repaintRec = rec0.union(rec1); + host.repaint(); + } + } + + /** + * Provides a {@link Segment} object, that can be used to fetch text from + * the document. + * + * @returna {@link Segment} object, that can be used to fetch text from + * the document + */ + protected Segment getLineBuffer() + { + if (lineBuffer == null) + lineBuffer = new Segment(); + return lineBuffer; + } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + // TODO: Implement this properly. + throw new AssertionError("Not implemented yet."); } } diff --git a/libjava/classpath/javax/swing/text/Segment.java b/libjava/classpath/javax/swing/text/Segment.java index 92d850016d9..84e0e700f2e 100644 --- a/libjava/classpath/javax/swing/text/Segment.java +++ b/libjava/classpath/javax/swing/text/Segment.java @@ -39,8 +39,7 @@ package javax.swing.text; import java.text.CharacterIterator; -public class Segment - implements Cloneable, CharacterIterator +public class Segment implements Cloneable, CharacterIterator { private boolean partialReturn; private int current; @@ -51,6 +50,7 @@ public class Segment public Segment() { + // Nothing to do here. } public Segment(char[] array, int offset, int count) diff --git a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java index 3ef5db61d43..0c9f607b196 100644 --- a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java +++ b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java @@ -45,6 +45,9 @@ import java.util.Hashtable; public class SimpleAttributeSet implements MutableAttributeSet, Serializable, Cloneable { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 8267656273837665219L; + public static final AttributeSet EMPTY = new SimpleAttributeSet(); Hashtable tab; @@ -84,12 +87,34 @@ public class SimpleAttributeSet return s; } + /** + * Returns true if the given name and value represent an attribute + * found either in this AttributeSet or in its resolve parent hierarchy. + * @param name the key for the attribute + * @param value the value for the attribute + * @return true if the attribute is found here or in this set's resolve + * parent hierarchy + */ public boolean containsAttribute(Object name, Object value) { + return (tab.containsKey(name) && tab.get(name).equals(value)) || + (getResolveParent() != null && getResolveParent(). + containsAttribute(name, value)); + } + + /** + * Returns true if the given name and value are found in this AttributeSet. + * Does not check the resolve parent. + * @param name the key for the attribute + * @param value the value for the attribute + * @return true if the attribute is found in this AttributeSet + */ + boolean containsAttributeLocally(Object name, Object value) + { return tab.containsKey(name) && tab.get(name).equals(value); } - + public boolean containsAttributes(AttributeSet attributes) { Enumeration e = attributes.getAttributeNames(); @@ -110,9 +135,9 @@ public class SimpleAttributeSet public boolean equals(Object obj) { - return (obj != null) - && (obj instanceof SimpleAttributeSet) - && ((SimpleAttributeSet)obj).tab.equals(this.tab); + return + (obj instanceof AttributeSet) + && this.isEqual((AttributeSet) obj); } public Object getAttribute(Object name) @@ -157,10 +182,16 @@ public class SimpleAttributeSet { return tab.isEmpty(); } - + + /** + * Returns true if the given set has the same number of attributes + * as this set and <code>containsAttributes(attr)</code> returns + * true. + */ public boolean isEqual(AttributeSet attr) { - return this.equals(attr); + return getAttributeCount() == attr.getAttributeCount() + && this.containsAttributes(attr); } public void removeAttribute(Object name) @@ -168,9 +199,21 @@ public class SimpleAttributeSet tab.remove(name); } + /** + * Removes attributes from this set if they are found in the + * given set. Only attributes whose key AND value are removed. + * Removes attributes only from this set, not from the resolving parent. + */ public void removeAttributes(AttributeSet attributes) { - removeAttributes(attributes.getAttributeNames()); + Enumeration e = attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + Object name = e.nextElement(); + Object val = attributes.getAttribute(name); + if (containsAttributeLocally(name, val)) + removeAttribute(name); + } } public void removeAttributes(Enumeration names) diff --git a/libjava/classpath/javax/swing/text/StringContent.java b/libjava/classpath/javax/swing/text/StringContent.java index bedf480d4ec..7db377a1c9b 100644 --- a/libjava/classpath/javax/swing/text/StringContent.java +++ b/libjava/classpath/javax/swing/text/StringContent.java @@ -56,6 +56,9 @@ import javax.swing.undo.UndoableEdit; */ public final class StringContent implements AbstractDocument.Content, Serializable { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 4755994433709540381L; + // This is package-private to avoid an accessor method. char[] content; diff --git a/libjava/classpath/javax/swing/text/StyleConstants.java b/libjava/classpath/javax/swing/text/StyleConstants.java index 3f973f22631..598eaf621bc 100644 --- a/libjava/classpath/javax/swing/text/StyleConstants.java +++ b/libjava/classpath/javax/swing/text/StyleConstants.java @@ -54,11 +54,13 @@ public class StyleConstants public static final Object BidiLevel = CharacterConstants.BidiLevel; public static final Object Bold = CharacterConstants.Bold; public static final Object ComponentAttribute = CharacterConstants.ComponentAttribute; - public static final Object FontFamily = CharacterConstants.Family; + public static final Object Family = CharacterConstants.Family; + public static final Object FontFamily = CharacterConstants.Family; public static final Object FontSize = CharacterConstants.Size; public static final Object Foreground = CharacterConstants.Foreground; public static final Object IconAttribute = CharacterConstants.IconAttribute; public static final Object Italic = CharacterConstants.Italic; + public static final Object Size = CharacterConstants.Size; public static final Object StrikeThrough = CharacterConstants.StrikeThrough; public static final Object Subscript = CharacterConstants.Subscript; public static final Object Superscript = CharacterConstants.Superscript; @@ -109,7 +111,7 @@ public class StyleConstants if (a.isDefined(Background)) return (Color) a.getAttribute(Background); else - return Color.BLACK; + return Color.WHITE; } public static int getBidiLevel(AttributeSet a) diff --git a/libjava/classpath/javax/swing/text/StyleContext.java b/libjava/classpath/javax/swing/text/StyleContext.java index ae11622ffc6..6c4e299455f 100644 --- a/libjava/classpath/javax/swing/text/StyleContext.java +++ b/libjava/classpath/javax/swing/text/StyleContext.java @@ -57,9 +57,15 @@ import javax.swing.event.EventListenerList; public class StyleContext implements Serializable, AbstractDocument.AttributeContext { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 8042858831190784241L; + public class NamedStyle implements Serializable, Style { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = -6690628971806226374L; + protected ChangeEvent changeEvent; protected EventListenerList listenerList; @@ -288,7 +294,7 @@ public class StyleContext public boolean equals(Object obj) { return - (obj instanceof SmallAttributeSet) + (obj instanceof AttributeSet) && this.isEqual((AttributeSet)obj); } @@ -300,9 +306,14 @@ public class StyleContext return attrs[i+1]; } - Object p = getResolveParent(); - if (p != null && p instanceof AttributeSet) - return (((AttributeSet)p).getAttribute(key)); + // Check the resolve parent, unless we're looking for the + // ResolveAttribute, which would cause an infinite loop + if (!(key.equals(ResolveAttribute))) + { + Object p = getResolveParent(); + if (p != null && p instanceof AttributeSet) + return (((AttributeSet)p).getAttribute(key)); + } return null; } diff --git a/libjava/classpath/javax/swing/text/StyledDocument.java b/libjava/classpath/javax/swing/text/StyledDocument.java index ea277540f23..168e1b116f3 100644 --- a/libjava/classpath/javax/swing/text/StyledDocument.java +++ b/libjava/classpath/javax/swing/text/StyledDocument.java @@ -45,101 +45,96 @@ import java.awt.Font; * @author Andrew Selkirk * @version 1.0 */ -public interface StyledDocument extends Document { - - //------------------------------------------------------------- - // Methods ---------------------------------------------------- - //------------------------------------------------------------- - - /** - * addStyle - * @param nm TODO - * @param rent TODO - * @returns Style - */ - Style addStyle(String nm, Style parent); - - /** - * removeStyle - * @param nm TODO - */ - void removeStyle(String nm); - - /** - * getStyle - * @param nm TODO - * @returns Style - */ - Style getStyle(String nm); - - /** - * setCharacterAttributes - * @param offset TODO - * @param length TODO - * @param set TODO - * @param replace TODO - */ - void setCharacterAttributes(int offset, int length, - AttributeSet set, boolean replace); - - /** - * setParagraphAttributes - * @param offset TODO - * @param length TODO - * @param set TODO - * @param replace TODO - */ - void setParagraphAttributes(int offset, int length, - AttributeSet set, boolean replace); - - /** - * getLogicalStyle - * @param position TODO - * @returns Style - */ - Style getLogicalStyle(int position); - - /** - * setLogicalStyle - * @param position TODO - * @param style TODO - */ - void setLogicalStyle(int position, Style style); - - /** - * getParagraphElement - * @param position TODO - * @returns Element - */ - Element getParagraphElement(int position); - - /** - * getCharacterElement - * @param position TODO - * @returns Element - */ - Element getCharacterElement(int position); - - /** - * getForeground - * @param set TODO - * @returns Color - */ - Color getForeground(AttributeSet set); - - /** - * getBackground - * @param set TODO - * @returns Color - */ - Color getBackground(AttributeSet set); - - /** - * getFont - * @param set TODO - * @returns Font - */ - Font getFont(AttributeSet set); - - -} // StyledDocument +public interface StyledDocument extends Document +{ + /** + * addStyle + * @param nm TODO + * @param parent TODO + * @returns Style + */ + Style addStyle(String nm, Style parent); + + /** + * removeStyle + * @param nm TODO + */ + void removeStyle(String nm); + + /** + * getStyle + * @param nm TODO + * @returns Style + */ + Style getStyle(String nm); + + /** + * setCharacterAttributes + * @param offset TODO + * @param length TODO + * @param set TODO + * @param replace TODO + */ + void setCharacterAttributes(int offset, int length, AttributeSet set, + boolean replace); + + /** + * setParagraphAttributes + * @param offset TODO + * @param length TODO + * @param set TODO + * @param replace TODO + */ + void setParagraphAttributes(int offset, int length, AttributeSet set, + boolean replace); + + /** + * getLogicalStyle + * @param position TODO + * @returns Style + */ + Style getLogicalStyle(int position); + + /** + * setLogicalStyle + * @param position TODO + * @param style TODO + */ + void setLogicalStyle(int position, Style style); + + /** + * getParagraphElement + * @param position TODO + * @returns Element + */ + Element getParagraphElement(int position); + + /** + * getCharacterElement + * @param position TODO + * @returns Element + */ + Element getCharacterElement(int position); + + /** + * getForeground + * @param set TODO + * @returns Color + */ + Color getForeground(AttributeSet set); + + /** + * getBackground + * @param set TODO + * @returns Color + */ + Color getBackground(AttributeSet set); + + /** + * getFont + * @param set TODO + * @returns Font + */ + Font getFont(AttributeSet set); + +} diff --git a/libjava/classpath/javax/swing/text/StyledEditorKit.java b/libjava/classpath/javax/swing/text/StyledEditorKit.java index 89c4cf18ee4..e71f992b534 100644 --- a/libjava/classpath/javax/swing/text/StyledEditorKit.java +++ b/libjava/classpath/javax/swing/text/StyledEditorKit.java @@ -40,13 +40,9 @@ package javax.swing.text; import java.awt.Color; import java.awt.event.ActionEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.Serializable; import javax.swing.Action; import javax.swing.JEditorPane; -import javax.swing.JTextPane; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; @@ -460,11 +456,11 @@ public class StyledEditorKit extends DefaultEditorKit * <code>StyledEditorKit</code>, namely the following types of Elements: * * <ul> - * <li>{@link AbstractDocument.ContentElementName}</li> - * <li>{@link AbstractDocument.ParagraphElementName}</li> - * <li>{@link AbstractDocument.SectionElementName}</li> - * <li>{@link StyleContext.ComponentElementName}</li> - * <li>{@link StyleContext.IconElementName}</li> + * <li>{@link AbstractDocument#ContentElementName}</li> + * <li>{@link AbstractDocument#ParagraphElementName}</li> + * <li>{@link AbstractDocument#SectionElementName}</li> + * <li>{@link StyleConstants#ComponentElementName}</li> + * <li>{@link StyleConstants#IconElementName}</li> * </ul> */ static class StyledViewFactory @@ -667,11 +663,11 @@ public class StyledEditorKit extends DefaultEditorKit * namely the following types of <code>Element</code>s: * * <ul> - * <li>{@link AbstractDocument.ContentElementName}</li> - * <li>{@link AbstractDocument.ParagraphElementName}</li> - * <li>{@link AbstractDocument.SectionElementName}</li> - * <li>{@link StyleContext.ComponentElementName}</li> - * <li>{@link StyleContext.IconElementName}</li> + * <li>{@link AbstractDocument#ContentElementName}</li> + * <li>{@link AbstractDocument#ParagraphElementName}</li> + * <li>{@link AbstractDocument#SectionElementName}</li> + * <li>{@link StyleConstants#ComponentElementName}</li> + * <li>{@link StyleConstants#IconElementName}</li> * </ul> * * @return a {@link ViewFactory} that is able to create {@link View}s diff --git a/libjava/classpath/javax/swing/text/TabSet.java b/libjava/classpath/javax/swing/text/TabSet.java index 146f545aac7..ecad9444ea5 100644 --- a/libjava/classpath/javax/swing/text/TabSet.java +++ b/libjava/classpath/javax/swing/text/TabSet.java @@ -41,6 +41,9 @@ import java.io.Serializable; public class TabSet implements Serializable { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 2367703481999080593L; + TabStop[] tabs; public TabSet(TabStop[] t) diff --git a/libjava/classpath/javax/swing/text/TabStop.java b/libjava/classpath/javax/swing/text/TabStop.java index 032da8bca46..56f862fdae4 100644 --- a/libjava/classpath/javax/swing/text/TabStop.java +++ b/libjava/classpath/javax/swing/text/TabStop.java @@ -41,6 +41,9 @@ import java.io.Serializable; public class TabStop implements Serializable { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = -5381995917363605058L; + public static final int ALIGN_LEFT = 0; public static final int ALIGN_RIGHT = 1; public static final int ALIGN_CENTER = 2; diff --git a/libjava/classpath/javax/swing/text/Utilities.java b/libjava/classpath/javax/swing/text/Utilities.java index d40408ddc3f..7830b2fca04 100644 --- a/libjava/classpath/javax/swing/text/Utilities.java +++ b/libjava/classpath/javax/swing/text/Utilities.java @@ -40,6 +40,11 @@ package javax.swing.text; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.text.BreakIterator; + +import javax.swing.SwingConstants; /** * A set of utilities to deal with text. This is used by several other classes @@ -195,4 +200,409 @@ public class Utilities return maxWidth; } + + /** + * Provides a facility to map screen coordinates into a model location. For a + * given text fragment and start location within this fragment, this method + * determines the model location so that the resulting fragment fits best + * into the span <code>[x0, x]</code>. + * + * The parameter <code>round</code> controls which model location is returned + * if the view coordinates are on a character: If <code>round</code> is + * <code>true</code>, then the result is rounded up to the next character, so + * that the resulting fragment is the smallest fragment that is larger than + * the specified span. If <code>round</code> is <code>false</code>, then the + * resulting fragment is the largest fragment that is smaller than the + * specified span. + * + * @param s the text segment + * @param fm the font metrics to use + * @param x0 the starting screen location + * @param x the target screen location at which the requested fragment should + * end + * @param te the tab expander to use; if this is <code>null</code>, TABs are + * expanded to one space character + * @param p0 the starting model location + * @param round if <code>true</code> round up to the next location, otherwise + * round down to the current location + * + * @return the model location, so that the resulting fragment fits within the + * specified span + */ + public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0, + int x, TabExpander te, int p0, + boolean round) + { + // At the end of the for loop, this holds the requested model location + int pos; + int currentX = x0; + + for (pos = p0; pos < s.count; pos++) + { + char nextChar = s.array[s.offset+pos]; + if (nextChar == 0) + { + if (! round) + pos--; + break; + } + if (nextChar != '\t') + currentX += fm.charWidth(nextChar); + else + { + if (te == null) + currentX += fm.charWidth(' '); + else + currentX = (int) te.nextTabStop(currentX, pos); + } + if (currentX > x) + { + if (! round) + pos--; + break; + } + } + return pos; + } + + /** + * Provides a facility to map screen coordinates into a model location. For a + * given text fragment and start location within this fragment, this method + * determines the model location so that the resulting fragment fits best + * into the span <code>[x0, x]</code>. + * + * This method rounds up to the next location, so that the resulting fragment + * will be the smallest fragment of the text, that is greater than the + * specified span. + * + * @param s the text segment + * @param fm the font metrics to use + * @param x0 the starting screen location + * @param x the target screen location at which the requested fragment should + * end + * @param te the tab expander to use; if this is <code>null</code>, TABs are + * expanded to one space character + * @param p0 the starting model location + * + * @return the model location, so that the resulting fragment fits within the + * specified span + */ + public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0, + int x, TabExpander te, int p0) + { + return getTabbedTextOffset(s, fm, x0, x, te, p0, true); + } + + /** + * Finds the start of the next word for the given offset. + * + * @param c + * the text component + * @param offs + * the offset in the document + * @return the location in the model of the start of the next word. + * @throws BadLocationException + * if the offset is invalid. + */ + public static final int getNextWord(JTextComponent c, int offs) + throws BadLocationException + { + if (offs < 0 || offs > (c.getText().length() - 1)) + throw new BadLocationException("invalid offset specified", offs); + String text = c.getText(); + BreakIterator wb = BreakIterator.getWordInstance(); + wb.setText(text); + int last = wb.following(offs); + int current = wb.next(); + while (current != BreakIterator.DONE) + { + for (int i = last; i < current; i++) + { + // FIXME: Should use isLetter(int) and text.codePointAt(int) + // instead, but isLetter(int) isn't implemented yet + if (Character.isLetter(text.charAt(i))) + return last; + } + last = current; + current = wb.next(); + } + return BreakIterator.DONE; + } + + /** + * Finds the start of the previous word for the given offset. + * + * @param c + * the text component + * @param offs + * the offset in the document + * @return the location in the model of the start of the previous word. + * @throws BadLocationException + * if the offset is invalid. + */ + public static final int getPreviousWord(JTextComponent c, int offs) + throws BadLocationException + { + if (offs < 0 || offs > (c.getText().length() - 1)) + throw new BadLocationException("invalid offset specified", offs); + String text = c.getText(); + BreakIterator wb = BreakIterator.getWordInstance(); + wb.setText(text); + int last = wb.preceding(offs); + int current = wb.previous(); + + while (current != BreakIterator.DONE) + { + for (int i = last; i < offs; i++) + { + // FIXME: Should use isLetter(int) and text.codePointAt(int) + // instead, but isLetter(int) isn't implemented yet + if (Character.isLetter(text.charAt(i))) + return last; + } + last = current; + current = wb.previous(); + } + return 0; + } + + /** + * Finds the start of a word for the given location. + * @param c the text component + * @param offs the offset location + * @return the location of the word beginning + * @throws BadLocationException if the offset location is invalid + */ + public static final int getWordStart(JTextComponent c, int offs) + throws BadLocationException + { + if (offs < 0 || offs >= c.getText().length()) + throw new BadLocationException("invalid offset specified", offs); + + String text = c.getText(); + BreakIterator wb = BreakIterator.getWordInstance(); + wb.setText(text); + if (wb.isBoundary(offs)) + return offs; + return wb.preceding(offs); + } + + /** + * Finds the end of a word for the given location. + * @param c the text component + * @param offs the offset location + * @return the location of the word end + * @throws BadLocationException if the offset location is invalid + */ + public static final int getWordEnd(JTextComponent c, int offs) + throws BadLocationException + { + if (offs < 0 || offs >= c.getText().length()) + throw new BadLocationException("invalid offset specified", offs); + + String text = c.getText(); + BreakIterator wb = BreakIterator.getWordInstance(); + wb.setText(text); + return wb.following(offs); + } + + /** + * Get the model position of the end of the row that contains the + * specified model position. Return null if the given JTextComponent + * does not have a size. + * @param c the JTextComponent + * @param offs the model position + * @return the model position of the end of the row containing the given + * offset + * @throws BadLocationException if the offset is invalid + */ + public static final int getRowEnd(JTextComponent c, int offs) + throws BadLocationException + { + String text = c.getText(); + if (text == null) + return -1; + + // Do a binary search for the smallest position X > offs + // such that that character at positino X is not on the same + // line as the character at position offs + int high = offs + ((text.length() - 1 - offs) / 2); + int low = offs; + int oldHigh = text.length() + 1; + while (true) + { + if (c.modelToView(high).y != c.modelToView(offs).y) + { + oldHigh = high; + high = low + ((high + 1 - low) / 2); + if (oldHigh == high) + return high - 1; + } + else + { + low = high; + high += ((oldHigh - high) / 2); + if (low == high) + return low; + } + } + } + + /** + * Get the model position of the start of the row that contains the specified + * model position. Return null if the given JTextComponent does not have a + * size. + * + * @param c the JTextComponent + * @param offs the model position + * @return the model position of the start of the row containing the given + * offset + * @throws BadLocationException if the offset is invalid + */ + public static final int getRowStart(JTextComponent c, int offs) + throws BadLocationException + { + String text = c.getText(); + if (text == null) + return -1; + + // Do a binary search for the greatest position X < offs + // such that the character at position X is not on the same + // row as the character at position offs + int high = offs; + int low = 0; + int oldLow = 0; + while (true) + { + if (c.modelToView(low).y != c.modelToView(offs).y) + { + oldLow = low; + low = high - ((high + 1 - low) / 2); + if (oldLow == low) + return low + 1; + } + else + { + high = low; + low -= ((low - oldLow) / 2); + if (low == high) + return low; + } + } + } + + /** + * Determine where to break the text in the given Segment, attempting to find + * a word boundary. + * @param s the Segment that holds the text + * @param metrics the font metrics used for calculating the break point + * @param x0 starting view location representing the start of the text + * @param x the target view location + * @param e the TabExpander used for expanding tabs (if this is null tabs + * are expanded to 1 space) + * @param startOffset the offset in the Document of the start of the text + * @return the offset at which we should break the text + */ + public static final int getBreakLocation(Segment s, FontMetrics metrics, + int x0, int x, TabExpander e, + int startOffset) + { + int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset); + BreakIterator breaker = BreakIterator.getWordInstance(); + breaker.setText(s.toString()); + + // If mark is equal to the end of the string, just use that position + if (mark == s.count) + return mark; + + // Try to find a word boundary previous to the mark at which we + // can break the text + int preceding = breaker.preceding(mark + 1); + + if (preceding != 0) + return preceding; + else + // If preceding is 0 we couldn't find a suitable word-boundary so + // just break it on the character boundary + return mark; + } + + /** + * Returns the paragraph element in the text component <code>c</code> at + * the specified location <code>offset</code>. + * + * @param c the text component + * @param offset the offset of the paragraph element to return + * + * @return the paragraph element at <code>offset</code> + */ + public static final Element getParagraphElement(JTextComponent c, int offset) + { + Document doc = c.getDocument(); + Element par = null; + if (doc instanceof StyledDocument) + { + StyledDocument styledDoc = (StyledDocument) doc; + par = styledDoc.getParagraphElement(offset); + } + else + { + Element root = c.getDocument().getDefaultRootElement(); + int parIndex = root.getElementIndex(offset); + par = root.getElement(parIndex); + } + return par; + } + + /** + * Returns the document position that is closest above to the specified x + * coordinate in the row containing <code>offset</code>. + * + * @param c the text component + * @param offset the offset + * @param x the x coordinate + * + * @return the document position that is closest above to the specified x + * coordinate in the row containing <code>offset</code> + * + * @throws BadLocationException if <code>offset</code> is not a valid offset + */ + public static final int getPositionAbove(JTextComponent c, int offset, int x) + throws BadLocationException + { + View rootView = c.getUI().getRootView(c); + Rectangle r = c.modelToView(offset); + int offs = c.viewToModel(new Point(x, r.y)); + int pos = rootView.getNextVisualPositionFrom(c, offs, + Position.Bias.Forward, + SwingConstants.NORTH, + new Position.Bias[1]); + return pos; + } + + /** + * Returns the document position that is closest below to the specified x + * coordinate in the row containing <code>offset</code>. + * + * @param c the text component + * @param offset the offset + * @param x the x coordinate + * + * @return the document position that is closest above to the specified x + * coordinate in the row containing <code>offset</code> + * + * @throws BadLocationException if <code>offset</code> is not a valid offset + */ + public static final int getPositionBelow(JTextComponent c, int offset, int x) + throws BadLocationException + { + View rootView = c.getUI().getRootView(c); + Rectangle r = c.modelToView(offset); + int offs = c.viewToModel(new Point(x, r.y)); + int pos = rootView.getNextVisualPositionFrom(c, offs, + Position.Bias.Forward, + SwingConstants.SOUTH, + new Position.Bias[1]); + return pos; + } } diff --git a/libjava/classpath/javax/swing/text/View.java b/libjava/classpath/javax/swing/text/View.java index 24efba9a1bc..daab347d731 100644 --- a/libjava/classpath/javax/swing/text/View.java +++ b/libjava/classpath/javax/swing/text/View.java @@ -43,7 +43,6 @@ import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; -import javax.swing.JComponent; import javax.swing.SwingConstants; import javax.swing.event.DocumentEvent; @@ -87,9 +86,9 @@ public abstract class View implements SwingConstants { View parent = getParent(); if (parent == null) - throw new AssertionError("The parent of a View must not be null."); - - return parent.getContainer(); + return null; + else + return parent.getContainer(); } public Document getDocument() @@ -508,6 +507,30 @@ public abstract class View implements SwingConstants } /** + * Maps a position in the document into the coordinate space of the View. + * The output rectangle usually reflects the font height but has a width + * of zero. + * + * This method is deprecated and calls + * {@link #modelToView(int, Position.Bias, int, Position.Bias, Shape)} with + * a bias of {@link Position.Bias#Forward}. + * + * @param pos the position of the character in the model + * @param a the area that is occupied by the view + * + * @return a rectangle that gives the location of the document position + * inside the view coordinate space + * + * @throws BadLocationException if <code>pos</code> is invalid + * + * @deprecated Use {@link #modelToView(int, Shape, Position.Bias)} instead. + */ + public Shape modelToView(int pos, Shape a) throws BadLocationException + { + return modelToView(pos, a, Position.Bias.Forward); + } + + /** * Maps coordinates from the <code>View</code>'s space into a position * in the document model. * @@ -521,6 +544,25 @@ public abstract class View implements SwingConstants */ public abstract int viewToModel(float x, float y, Shape a, Position.Bias[] b); + /** + * Maps coordinates from the <code>View</code>'s space into a position + * in the document model. This method is deprecated and only there for + * compatibility. + * + * @param x the x coordinate in the view space + * @param y the y coordinate in the view space + * @param a the allocation of this <code>View</code> + * + * @return the position in the document that corresponds to the screen + * coordinates <code>x, y</code> + * + * @deprecated Use {@link #viewToModel(float, float, Shape, Position.Bias[])} + * instead. + */ + public int viewToModel(float x, float y, Shape a) + { + return viewToModel(x, y, a, new Position.Bias[0]); + } /** * Dumps the complete View hierarchy. This method can be used for debugging @@ -552,4 +594,30 @@ public abstract class View implements SwingConstants for (int i = 0; i < count; ++i) getView(i).dump(indent + 1); } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset in + * the document model + */ + public abstract int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException; } diff --git a/libjava/classpath/javax/swing/text/WrappedPlainView.java b/libjava/classpath/javax/swing/text/WrappedPlainView.java new file mode 100644 index 00000000000..b90519046ae --- /dev/null +++ b/libjava/classpath/javax/swing/text/WrappedPlainView.java @@ -0,0 +1,700 @@ +/* WrappedPlainView.java -- + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.awt.Color; +import java.awt.Container; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; + +import javax.swing.SwingConstants; +import javax.swing.event.DocumentEvent; +import javax.swing.text.Position.Bias; + +/** + * @author abalkiss + * + */ +public class WrappedPlainView extends BoxView implements TabExpander +{ + /** The color for selected text **/ + Color selectedColor; + + /** The color for unselected text **/ + Color unselectedColor; + + /** The color for disabled components **/ + Color disabledColor; + + /** Stores the font metrics **/ + protected FontMetrics metrics; + + /** Whether or not to wrap on word boundaries **/ + boolean wordWrap; + + /** A ViewFactory that creates WrappedLines **/ + ViewFactory viewFactory = new WrappedLineCreator(); + + /** The start of the selected text **/ + int selectionStart; + + /** The end of the selected text **/ + int selectionEnd; + + /** + * The instance returned by {@link #getLineBuffer()}. + */ + private transient Segment lineBuffer; + + public WrappedPlainView (Element elem) + { + this (elem, false); + } + + public WrappedPlainView (Element elem, boolean wordWrap) + { + super (elem, Y_AXIS); + this.wordWrap = wordWrap; + } + + /** + * Provides access to the Segment used for retrievals from the Document. + * @return the Segment. + */ + protected final Segment getLineBuffer() + { + if (lineBuffer == null) + lineBuffer = new Segment(); + return lineBuffer; + } + + /** + * Returns the next tab stop position after a given reference position. + * + * This implementation ignores the <code>tabStop</code> argument. + * + * @param x the current x position in pixels + * @param tabStop the position within the text stream that the tab occured at + */ + public float nextTabStop(float x, int tabStop) + { + JTextComponent host = (JTextComponent)getContainer(); + float tabSizePixels = getTabSize() + * host.getFontMetrics(host.getFont()).charWidth('m'); + return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels; + } + + /** + * Returns the tab size for the Document based on + * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is + * not defined + * + * @return the tab size. + */ + protected int getTabSize() + { + Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute); + if (tabSize == null) + return 8; + return ((Integer)tabSize).intValue(); + } + + /** + * Draws a line of text, suppressing white space at the end and expanding + * tabs. Calls drawSelectedText and drawUnselectedText. + * @param p0 starting document position to use + * @param p1 ending document position to use + * @param g graphics context + * @param x starting x position + * @param y starting y position + */ + protected void drawLine(int p0, int p1, Graphics g, int x, int y) + { + try + { + // We have to draw both selected and unselected text. There are + // several cases: + // - entire range is unselected + // - entire range is selected + // - start of range is selected, end of range is unselected + // - start of range is unselected, end of range is selected + // - middle of range is selected, start and end of range is unselected + + // entire range unselected: + if ((selectionStart == selectionEnd) || + (p0 > selectionEnd || p1 < selectionStart)) + drawUnselectedText(g, x, y, p0, p1); + + // entire range selected + else if (p0 >= selectionStart && p1 <= selectionEnd) + drawSelectedText(g, x, y, p0, p1); + + // start of range selected, end of range unselected + else if (p0 >= selectionStart) + { + x = drawSelectedText(g, x, y, p0, selectionEnd); + drawUnselectedText(g, x, y, selectionEnd, p1); + } + + // start of range unselected, end of range selected + else if (selectionStart > p0 && selectionEnd > p1) + { + x = drawUnselectedText(g, x, y, p0, selectionStart); + drawSelectedText(g, x, y, selectionStart, p1); + } + + // middle of range selected + else if (selectionStart > p0) + { + x = drawUnselectedText(g, x, y, p0, selectionStart); + x = drawSelectedText(g, x, y, selectionStart, selectionEnd); + drawUnselectedText(g, x, y, selectionEnd, p1); + } + } + catch (BadLocationException ble) + { + // shouldn't happen + } + } + + /** + * Renders the range of text as selected text. Just paints the text + * in the color specified by the host component. Assumes the highlighter + * will render the selected background. + * @param g the graphics context + * @param x the starting X coordinate + * @param y the starting Y coordinate + * @param p0 the starting model location + * @param p1 the ending model location + * @return the X coordinate of the end of the text + * @throws BadLocationException if the given range is invalid + */ + protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) + throws BadLocationException + { + g.setColor(selectedColor); + Segment segment = getLineBuffer(); + getDocument().getText(p0, p1 - p0, segment); + return Utilities.drawTabbedText(segment, x, y, g, this, p0); + } + + /** + * Renders the range of text as normal unhighlighted text. + * @param g the graphics context + * @param x the starting X coordinate + * @param y the starting Y coordinate + * @param p0 the starting model location + * @param p1 the end model location + * @return the X location of the end off the range + * @throws BadLocationException if the range given is invalid + */ + protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) + throws BadLocationException + { + JTextComponent textComponent = (JTextComponent) getContainer(); + if (textComponent.isEnabled()) + g.setColor(unselectedColor); + else + g.setColor(disabledColor); + + Segment segment = getLineBuffer(); + getDocument().getText(p0, p1 - p0, segment); + return Utilities.drawTabbedText(segment, x, y, g, this, p0); + } + + /** + * Loads the children to initiate the view. Called by setParent. + * Creates a WrappedLine for each child Element. + */ + protected void loadChildren (ViewFactory f) + { + Element root = getElement(); + int numChildren = root.getElementCount(); + if (numChildren == 0) + return; + + View[] children = new View[numChildren]; + for (int i = 0; i < numChildren; i++) + children[i] = new WrappedLine(root.getElement(i)); + replace(0, 0, children); + } + + /** + * Calculates the break position for the text between model positions + * p0 and p1. Will break on word boundaries or character boundaries + * depending on the break argument given in construction of this + * WrappedPlainView. Used by the nested WrappedLine class to determine + * when to start the next logical line. + * @param p0 the start model position + * @param p1 the end model position + * @return the model position at which to break the text + */ + protected int calculateBreakPosition(int p0, int p1) + { + Container c = getContainer(); + Rectangle alloc = c.isValid() ? c.getBounds() + : new Rectangle(c.getPreferredSize()); + updateMetrics(); + try + { + getDocument().getText(p0, p1 - p0, getLineBuffer()); + } + catch (BadLocationException ble) + { + // this shouldn't happen + } + // FIXME: Should we account for the insets of the container? + if (wordWrap) + return p0 + + Utilities.getBreakLocation(lineBuffer, metrics, alloc.x, + alloc.x + alloc.width, this, 0); + else + { + return p0 + + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x, + alloc.x + alloc.width, this, 0); + } + } + + void updateMetrics() + { + Container component = getContainer(); + metrics = component.getFontMetrics(component.getFont()); + } + + /** + * Determines the preferred span along the given axis. Implemented to + * cache the font metrics and then call the super classes method. + */ + public float getPreferredSpan (int axis) + { + updateMetrics(); + return super.getPreferredSpan(axis); + } + + /** + * Determines the minimum span along the given axis. Implemented to + * cache the font metrics and then call the super classes method. + */ + public float getMinimumSpan (int axis) + { + updateMetrics(); + return super.getMinimumSpan(axis); + } + + /** + * Determines the maximum span along the given axis. Implemented to + * cache the font metrics and then call the super classes method. + */ + public float getMaximumSpan (int axis) + { + updateMetrics(); + return super.getMaximumSpan(axis); + } + + /** + * Called when something was inserted. Overridden so that + * the view factory creates WrappedLine views. + */ + public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f) + { + super.insertUpdate(e, a, viewFactory); + // FIXME: could improve performance by repainting only the necessary area + getContainer().repaint(); + } + + /** + * Called when something is removed. Overridden so that + * the view factory creates WrappedLine views. + */ + public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f) + { + super.removeUpdate(e, a, viewFactory); + // FIXME: could improve performance by repainting only the necessary area + getContainer().repaint(); + } + + /** + * Called when the portion of the Document that this View is responsible + * for changes. Overridden so that the view factory creates + * WrappedLine views. + */ + public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f) + { + super.changedUpdate(e, a, viewFactory); + // FIXME: could improve performance by repainting only the necessary area + getContainer().repaint(); + } + + class WrappedLineCreator implements ViewFactory + { + // Creates a new WrappedLine + public View create(Element elem) + { + return new WrappedLine(elem); + } + } + + /** + * Renders the <code>Element</code> that is associated with this + * <code>View</code>. Caches the metrics and then calls + * super.paint to paint all the child views. + * + * @param g the <code>Graphics</code> context to render to + * @param a the allocated region for the <code>Element</code> + */ + public void paint(Graphics g, Shape a) + { + JTextComponent comp = (JTextComponent)getContainer(); + selectionStart = comp.getSelectionStart(); + selectionEnd = comp.getSelectionEnd(); + updateMetrics(); + super.paint(g, a); + } + + /** + * Sets the size of the View. Implemented to update the metrics + * and then call super method. + */ + public void setSize (float width, float height) + { + updateMetrics(); + if (width != getWidth()) + preferenceChanged(null, true, true); + super.setSize(width, height); + } + + class WrappedLine extends View + { + /** Used to cache the number of lines for this View **/ + int numLines; + + public WrappedLine(Element elem) + { + super(elem); + determineNumLines(); + } + + /** + * Renders this (possibly wrapped) line using the given Graphics object + * and on the given rendering surface. + */ + public void paint(Graphics g, Shape s) + { + // Ensure metrics are up-to-date. + updateMetrics(); + JTextComponent textComponent = (JTextComponent) getContainer(); + + g.setFont(textComponent.getFont()); + selectedColor = textComponent.getSelectedTextColor(); + unselectedColor = textComponent.getForeground(); + disabledColor = textComponent.getDisabledTextColor(); + + // FIXME: this is a hack, for some reason textComponent.getSelectedColor + // was returning black, which is not visible against a black background + selectedColor = Color.WHITE; + + Rectangle rect = s.getBounds(); + int lineHeight = metrics.getHeight(); + + int end = getEndOffset(); + int currStart = getStartOffset(); + int currEnd; + while (currStart < end) + { + currEnd = calculateBreakPosition(currStart, end); + drawLine(currStart, currEnd, g, rect.x, rect.y); + rect.y += lineHeight; + if (currEnd == currStart) + currStart ++; + else + currStart = currEnd; + } + } + + /** + * Determines the number of logical lines that the Element + * needs to be displayed + * @return the number of lines needed to display the Element + */ + int determineNumLines() + { + numLines = 0; + int end = getEndOffset(); + if (end == 0) + return 0; + + int breakPoint; + for (int i = getStartOffset(); i < end;) + { + numLines ++; + // careful: check that there's no off-by-one problem here + // depending on which position calculateBreakPosition returns + breakPoint = calculateBreakPosition(i, end); + if (breakPoint == i) + i ++; + else + i = breakPoint; + } + return numLines; + } + + /** + * Determines the preferred span for this view along the given axis. + * + * @param axis the axis (either X_AXIS or Y_AXIS) + * + * @return the preferred span along the given axis. + * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS + */ + public float getPreferredSpan(int axis) + { + if (axis == X_AXIS) + return getWidth(); + else if (axis == Y_AXIS) + return numLines * metrics.getHeight(); + + throw new IllegalArgumentException("Invalid axis for getPreferredSpan: " + + axis); + } + + /** + * Provides a mapping from model space to view space. + * + * @param pos the position in the model + * @param a the region into which the view is rendered + * @param b the position bias (forward or backward) + * + * @return a box in view space that represents the given position + * in model space + * @throws BadLocationException if the given model position is invalid + */ + public Shape modelToView(int pos, Shape a, Bias b) + throws BadLocationException + { + Segment s = getLineBuffer(); + int lineHeight = metrics.getHeight(); + Rectangle rect = a.getBounds(); + + // Return a rectangle with width 1 and height equal to the height + // of the text + rect.height = lineHeight; + rect.width = 1; + + int currLineStart = getStartOffset(); + int end = getEndOffset(); + + if (pos < currLineStart || pos >= end) + throw new BadLocationException("invalid offset", pos); + + while (true) + { + int currLineEnd = calculateBreakPosition(currLineStart, end); + // If pos is between currLineStart and currLineEnd then just find + // the width of the text from currLineStart to pos and add that + // to rect.x + if (pos >= currLineStart && pos < currLineEnd || pos == end - 1) + { + try + { + getDocument().getText(currLineStart, pos - currLineStart, s); + } + catch (BadLocationException ble) + { + // Shouldn't happen + } + rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x, + WrappedPlainView.this, + currLineStart); + return rect; + } + // Increment rect.y so we're checking the next logical line + rect.y += lineHeight; + + // Increment currLineStart to the model position of the start + // of the next logical line + if (currLineEnd == currLineStart) + currLineStart = end; + else + currLineStart = currLineEnd; + } + + } + + /** + * Provides a mapping from view space to model space. + * + * @param x the x coordinate in view space + * @param y the y coordinate in view space + * @param a the region into which the view is rendered + * @param b the position bias (forward or backward) + * + * @return the location in the model that best represents the + * given point in view space + */ + public int viewToModel(float x, float y, Shape a, Bias[] b) + { + Segment s = getLineBuffer(); + Rectangle rect = a.getBounds(); + int currLineStart = getStartOffset(); + int end = getEndOffset(); + int lineHeight = metrics.getHeight(); + if (y < rect.y) + return currLineStart; + if (y > rect.y + rect.height) + return end - 1; + + while (true) + { + int currLineEnd = calculateBreakPosition(currLineStart, end); + // If we're at the right y-position that means we're on the right + // logical line and we should look for the character + if (y >= rect.y && y < rect.y + lineHeight) + { + // Check if the x position is to the left or right of the text + if (x < rect.x) + return currLineStart; + if (x > rect.x + rect.width) + return currLineEnd - 1; + + try + { + getDocument().getText(currLineStart, end - currLineStart, s); + } + catch (BadLocationException ble) + { + // Shouldn't happen + } + int mark = Utilities.getTabbedTextOffset(s, metrics, rect.x, + (int) x, + WrappedPlainView.this, + currLineStart); + return currLineStart + mark; + } + // Increment rect.y so we're checking the next logical line + rect.y += lineHeight; + + // Increment currLineStart to the model position of the start + // of the next logical line + if (currLineEnd == currLineStart) + currLineStart = end; + else + currLineStart = currLineEnd; + } + } + + /** + * Returns the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction <code>d</code>. + * + * @param c the text component + * @param pos the document position + * @param b the bias for <code>pos</code> + * @param d the direction, must be either {@link SwingConstants#NORTH}, + * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or + * {@link SwingConstants#EAST} + * @param biasRet an array of {@link Position.Bias} that can hold at least + * one element, which is filled with the bias of the return position + * on method exit + * + * @return the document position that is (visually) nearest to the given + * document position <code>pos</code> in the given direction + * <code>d</code> + * + * @throws BadLocationException if <code>pos</code> is not a valid offset + * in the document model + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias b, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + // TODO: Implement this properly. + throw new AssertionError("Not implemented yet."); + } + + /** + * This method is called from insertUpdate and removeUpdate. + * If the number of lines in the document has changed, just repaint + * the whole thing (note, could improve performance by not repainting + * anything above the changes). If the number of lines hasn't changed, + * just repaint the given Rectangle. + * @param a the Rectangle to repaint if the number of lines hasn't changed + */ + void updateDamage (Rectangle a) + { + int newNumLines = determineNumLines(); + if (numLines != newNumLines) + { + numLines = newNumLines; + getContainer().repaint(); + } + else + getContainer().repaint(a.x, a.y, a.width, a.height); + } + + /** + * This method is called when something is inserted into the Document + * that this View is displaying. + * + * @param changes the DocumentEvent for the changes. + * @param a the allocation of the View + * @param f the ViewFactory used to rebuild + */ + public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f) + { + updateDamage((Rectangle)a); + } + + /** + * This method is called when something is removed from the Document + * that this View is displaying. + * + * @param changes the DocumentEvent for the changes. + * @param a the allocation of the View + * @param f the ViewFactory used to rebuild + */ + public void removeUpdate (DocumentEvent changes, Shape a, ViewFactory f) + { + updateDamage((Rectangle)a); + } + } +} diff --git a/libjava/classpath/javax/swing/text/html/CSS.java b/libjava/classpath/javax/swing/text/html/CSS.java new file mode 100644 index 00000000000..029ad26f8a9 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/CSS.java @@ -0,0 +1,461 @@ +/* CSS.java -- Provides CSS attributes + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.text.html; + +import java.util.HashMap; + +/** + * Provides CSS attributes to be used by the HTML view classes. The constants + * defined here are used as keys for text attributes for use in + * {@link javax.swing.text.AttributeSet}s of {@link javax.swing.text.Element}s. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class CSS +{ + /** + * Returns an array of all CSS attributes. + * + * @return All available CSS.Attribute objects. + */ + public static CSS.Attribute[] getAllAttributeKeys() + { + Object[] src = Attribute.attributeMap.values().toArray(); + CSS.Attribute[] dst = new CSS.Attribute[ src.length ]; + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + /** + * Returns an a given CSS attribute. + * + * @param name - The name of the attribute. + * @return The CSS attribute with the given name, or <code>null</code> if + * no attribute with that name exists. + */ + public static CSS.Attribute getAttribute(String name) + { + return (CSS.Attribute)Attribute.attributeMap.get( name ); + } + + public static final class Attribute + { + /** + * The CSS attribute 'background'. + */ + public static final Attribute BACKGROUND = + new Attribute("background", false, null); + + /** + * The CSS attribute 'background-attachment'. + */ + public static final Attribute BACKGROUND_ATTACHMENT = + new Attribute("background-attachment", false, "scroll"); + + /** + * The CSS attribute 'background-color'. + */ + public static final Attribute BACKGROUND_COLOR = + new Attribute("background-color", false, "transparent"); + + /** + * The CSS attribute 'background-image'. + */ + public static final Attribute BACKGROUND_IMAGE = + new Attribute("background-image", false, "none"); + + /** + * The CSS attribute 'background-position'. + */ + public static final Attribute BACKGROUND_POSITION = + new Attribute("background-position", false, null); + + /** + * The CSS attribute 'background-repeat'. + */ + public static final Attribute BACKGROUND_REPEAT = + new Attribute("background-repeat", false, "repeat"); + + /** + * The CSS attribute 'border'. + */ + public static final Attribute BORDER = new Attribute("border", false, null); + + /** + * The CSS attribute 'border-bottom'. + */ + public static final Attribute BORDER_BOTTOM = + new Attribute("border-bottom", false, null); + + /** + * The CSS attribute 'border-bottom-width'. + */ + public static final Attribute BORDER_BOTTOM_WIDTH = + new Attribute("border-bottom-width", false, "medium"); + + /** + * The CSS attribute 'border-color'. + */ + public static final Attribute BORDER_COLOR = + new Attribute("border-color", false, "black"); + + /** + * The CSS attribute 'border-left'. + */ + public static final Attribute BORDER_LEFT = + new Attribute("border-left", false, null); + + /** + * The CSS attribute 'border-left-width'. + */ + public static final Attribute BORDER_LEFT_WIDTH = + new Attribute("border-left-width", false, "medium"); + + /** + * The CSS attribute 'border-right'. + */ + public static final Attribute BORDER_RIGHT = + new Attribute("border-right", false, null); + + /** + * The CSS attribute 'border-right-width'. + */ + public static final Attribute BORDER_RIGHT_WIDTH = + new Attribute("border-right-width", false, "medium"); + + /** + * The CSS attribute 'border-style'. + */ + public static final Attribute BORDER_STYLE = + new Attribute("border-style", false, "none"); + + /** + * The CSS attribute 'border-top'. + */ + public static final Attribute BORDER_TOP = + new Attribute("border-top", false, null); + + /** + * The CSS attribute 'border-top-width'. + */ + public static final Attribute BORDER_TOP_WIDTH = + new Attribute("border-top-width", false, "medium"); + + /** + * The CSS attribute 'border-width'. + */ + public static final Attribute BORDER_WIDTH = + new Attribute("border-width", false, "medium"); + + /** + * The CSS attribute 'clear'. + */ + public static final Attribute CLEAR = new Attribute("clear", false, "none"); + + /** + * The CSS attribute 'color'. + */ + public static final Attribute COLOR = new Attribute("color", true, "black"); + + /** + * The CSS attribute 'display'. + */ + public static final Attribute DISPLAY = + new Attribute("display", false, "block"); + + /** + * The CSS attribute 'float'. + */ + public static final Attribute FLOAT = new Attribute("float", false, "none"); + + /** + * The CSS attribute 'font'. + */ + public static final Attribute FONT = new Attribute("font", true, null); + + /** + * The CSS attribute 'font-family'. + */ + public static final Attribute FONT_FAMILY = + new Attribute("font-family", true, null); + + /** + * The CSS attribute 'font-size'. + */ + public static final Attribute FONT_SIZE = + new Attribute("font-size", true, "medium"); + + /** + * The CSS attribute 'font-style'. + */ + public static final Attribute FONT_STYLE = + new Attribute("font-style", true, "normal"); + + /** + * The CSS attribute 'font-variant'. + */ + public static final Attribute FONT_VARIANT = + new Attribute("font-variant", true, "normal"); + + /** + * The CSS attribute 'font-weight'. + */ + public static final Attribute FONT_WEIGHT = + new Attribute("font-weight", true, "normal"); + + /** + * The CSS attribute 'height'. + */ + public static final Attribute HEIGHT = + new Attribute("height", false, "auto"); + + /** + * The CSS attribute 'letter-spacing'. + */ + public static final Attribute LETTER_SPACING = + new Attribute("letter-spacing", true, "normal"); + + /** + * The CSS attribute 'line-height'. + */ + public static final Attribute LINE_HEIGHT = + new Attribute("line-height", true, "normal"); + + /** + * The CSS attribute 'list-style'. + */ + public static final Attribute LIST_STYLE = + new Attribute("list-style", true, null); + + /** + * The CSS attribute 'list-style-image'. + */ + public static final Attribute LIST_STYLE_IMAGE = + new Attribute("list-style-image", true, "none"); + + /** + * The CSS attribute 'list-style-position'. + */ + public static final Attribute LIST_STYLE_POSITION = + new Attribute("list-style-position", true, "outside"); + + /** + * The CSS attribute 'list-style-type'. + */ + public static final Attribute LIST_STYLE_TYPE = + new Attribute("list-style-type", true, "disc"); + + /** + * The CSS attribute 'margin'. + */ + public static final Attribute MARGIN = new Attribute("margin", false, null); + + /** + * The CSS attribute 'margin-bottom'. + */ + public static final Attribute MARGIN_BOTTOM = + new Attribute("margin-bottom", false, "0"); + + /** + * The CSS attribute 'margin-left'. + */ + public static final Attribute MARGIN_LEFT = + new Attribute("margin-left", false, "0"); + + /** + * The CSS attribute 'margin-right'. + */ + public static final Attribute MARGIN_RIGHT = + new Attribute("margin-right", false, "0"); + + /** + * The CSS attribute 'margin-top'. + */ + public static final Attribute MARGIN_TOP = + new Attribute("margin-top", false, "0"); + + /** + * The CSS attribute 'padding'. + */ + public static final Attribute PADDING = + new Attribute("padding", false, null); + + /** + * The CSS attribute 'padding-bottom'. + */ + public static final Attribute PADDING_BOTTOM = + new Attribute("padding-bottom", false, "0"); + + /** + * The CSS attribute 'padding-left'. + */ + public static final Attribute PADDING_LEFT = + new Attribute("padding-left", false, "0"); + + /** + * The CSS attribute 'padding-right'. + */ + public static final Attribute PADDING_RIGHT = + new Attribute("padding-right", false, "0"); + + /** + * The CSS attribute 'padding-top'. + */ + public static final Attribute PADDING_TOP = + new Attribute("padding-top", false, "0"); + + /** + * The CSS attribute 'text-align'. + */ + public static final Attribute TEXT_ALIGN = + new Attribute("text-align", true, null); + + /** + * The CSS attribute 'text-decoration'. + */ + public static final Attribute TEXT_DECORATION = + new Attribute("text-decoration", true, "none"); + + /** + * The CSS attribute 'text-indent'. + */ + public static final Attribute TEXT_INDENT = + new Attribute("text-indent", true, "0"); + + /** + * The CSS attribute 'text-transform'. + */ + public static final Attribute TEXT_TRANSFORM = + new Attribute("text-transform", true, "none"); + + /** + * The CSS attribute 'vertical-align'. + */ + public static final Attribute VERTICAL_ALIGN = + new Attribute("vertical-align", false, "baseline"); + + /** + * The CSS attribute 'white-space'. + */ + public static final Attribute WHITE_SPACE = + new Attribute("white-space", true, "normal"); + + /** + * The CSS attribute 'width'. + */ + public static final Attribute WIDTH = + new Attribute("width", false, "auto"); + + /** + * The CSS attribute 'word-spacing'. + */ + public static final Attribute WORD_SPACING = + new Attribute("word-spacing", true, "normal"); + + /** + * The attribute string. + */ + String attStr; + + /** + * Indicates if this attribute should be inherited from it's parent or + * not. + */ + boolean isInherited; + + /** + * A default value for this attribute if one exists, otherwise null. + */ + String defaultValue; + + /** + * A HashMap of all attributes. + */ + static HashMap attributeMap; + + /** + * Creates a new Attribute instance with the specified values. + * + * @param attr the attribute string + * @param inherited if the attribute should be inherited or not + * @param def a default value; may be <code>null</code> + */ + Attribute(String attr, boolean inherited, String def) + { + attStr = attr; + isInherited = inherited; + defaultValue = def; + if( attributeMap == null) + attributeMap = new HashMap(); + attributeMap.put( attr, this ); + } + + /** + * Returns the string representation of this attribute as specified + * in the CSS specification. + */ + public String toString() + { + return attStr; + } + + /** + * Returns <code>true</code> if the attribute should be inherited from + * the parent, <code>false</code> otherwise. + * + * @return <code>true</code> if the attribute should be inherited from + * the parent, <code>false</code> otherwise + */ + public boolean isInherited() + { + return isInherited; + } + + /** + * Returns the default value of this attribute if one exists, + * <code>null</code> otherwise. + * + * @return the default value of this attribute if one exists, + * <code>null</code> otherwise + */ + public String getDefaultValue() + { + return defaultValue; + } + } +} diff --git a/libjava/classpath/javax/swing/text/html/HTML.java b/libjava/classpath/javax/swing/text/html/HTML.java index 3c03a63a471..0b758d2b873 100644 --- a/libjava/classpath/javax/swing/text/html/HTML.java +++ b/libjava/classpath/javax/swing/text/html/HTML.java @@ -945,22 +945,22 @@ public class HTML * This tag is not included into the array, returned by getAllTags(). * toString() returns 'comment'. HTML reader synthesizes this tag. */ - public static final Tag COMMENT = new Tag("comment", SYNTETIC); + public static final Tag COMMENT = new Tag("comment", SYNTHETIC); /** * All text content is labeled with this tag. * This tag is not included into the array, returned by getAllTags(). * toString() returns 'content'. HTML reader synthesizes this tag. */ - public static final Tag CONTENT = new Tag("content", SYNTETIC); + public static final Tag CONTENT = new Tag("content", SYNTHETIC); /** * All text content must be in a paragraph element. * If a paragraph didn't exist when content was encountered, * a paragraph is manufactured. - * toString() returns 'implied'. HTML reader synthesizes this tag. + * toString() returns 'p-implied'. HTML reader synthesizes this tag. */ - public static final Tag IMPLIED = new Tag("implied", SYNTETIC); + public static final Tag IMPLIED = new Tag("p-implied", SYNTHETIC); final String name; final int flags; @@ -1144,7 +1144,7 @@ public class HTML */ boolean isSyntetic() { - return (flags & SYNTETIC) != 0; + return (flags & SYNTHETIC) != 0; } private static void unexpected(Exception ex) @@ -1185,7 +1185,7 @@ public class HTML static final int BREAKS = 1; static final int BLOCK = 2; static final int PREFORMATTED = 4; - static final int SYNTETIC = 8; + static final int SYNTHETIC = 8; private static Map tagMap; private static Map attrMap; @@ -1196,6 +1196,7 @@ public class HTML */ public HTML() { + // Nothing to do here. } /** diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java index a95e496ec4d..d048a04e614 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java +++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java @@ -38,7 +38,14 @@ exception statement from your version. */ package javax.swing.text.html; +import java.net.URL; + +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.Element; +import javax.swing.text.ElementIterator; +import javax.swing.text.html.HTML.Tag; /** * TODO: This class is not yet completetely implemented. @@ -47,7 +54,215 @@ import javax.swing.text.DefaultStyledDocument; */ public class HTMLDocument extends DefaultStyledDocument { + /** A key for document properies. The value for the key is + * a Vector of Strings of comments not found in the body. + */ + public static final String AdditionalComments = "AdditionalComments"; + URL baseURL = null; + boolean preservesUnknownTags = true; + + /** + * Returns the location against which to resolve relative URLs. + * This is the document's URL if the document was loaded from a URL. + * If a <code>base</code> tag is found, it will be used. + * @return the base URL + */ + public URL getBase() + { + return baseURL; + } + + /** + * Sets the location against which to resolve relative URLs. + * @param u the new base URL + */ + public void setBase(URL u) + { + baseURL = u; + //TODO: also set the base of the StyleSheet + } + + /** + * Returns whether or not the parser preserves unknown HTML tags. + * @return true if the parser preserves unknown tags + */ + public boolean getPreservesUnknownTags() + { + return preservesUnknownTags; + } + + /** + * Sets the behaviour of the parser when it encounters unknown HTML tags. + * @param preservesTags true if the parser should preserve unknown tags. + */ + public void setPreservesUnknownTags(boolean preservesTags) + { + preservesUnknownTags = preservesTags; + } + + /** + * An iterator to iterate through LeafElements in the document. + */ + class LeafIterator extends Iterator + { + HTML.Tag tag; + HTMLDocument doc; + ElementIterator it; + + public LeafIterator (HTML.Tag t, HTMLDocument d) + { + doc = d; + tag = t; + it = new ElementIterator(doc); + } + + /** + * Return the attributes for the tag associated with this iteartor + * @return the AttributeSet + */ + public AttributeSet getAttributes() + { + if (it.current() != null) + return it.current().getAttributes(); + return null; + } + + /** + * Get the end of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the end of the range + */ + public int getEndOffset() + { + if (it.current() != null) + return it.current().getEndOffset(); + return -1; + } + + /** + * Get the start of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the start of the range (-1 if it can't be found). + */ + + public int getStartOffset() + { + if (it.current() != null) + return it.current().getStartOffset(); + return -1; + } + + /** + * Advance the iterator to the next LeafElement . + */ + public void next() + { + it.next(); + while (it.current()!= null && !it.current().isLeaf()) + it.next(); + } + + /** + * Indicates whether or not the iterator currently represents an occurrence + * of the tag. + * @return true if the iterator currently represents an occurrence of the + * tag. + */ + public boolean isValid() + { + return it.current() != null; + } + + /** + * Type of tag for this iterator. + */ + public Tag getTag() + { + return tag; + } + + } + public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event) { + // TODO: Implement this properly. + } + + /** + * Gets an iterator for the given HTML.Tag. + * @param t the requested HTML.Tag + * @return the Iterator + */ + public HTMLDocument.Iterator getIterator (HTML.Tag t) + { + return new HTMLDocument.LeafIterator(t, this); + } + + /** + * An iterator over a particular type of tag. + */ + public abstract static class Iterator + { + /** + * Return the attribute set for this tag. + * @return the <code>AttributeSet</code> (null if none found). + */ + public abstract AttributeSet getAttributes(); + + /** + * Get the end of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the end of the range + */ + public abstract int getEndOffset(); + + /** + * Get the start of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the start of the range (-1 if it can't be found). + */ + public abstract int getStartOffset(); + + /** + * Move the iterator forward. + */ + public abstract void next(); + + /** + * Indicates whether or not the iterator currently represents an occurrence + * of the tag. + * @return true if the iterator currently represents an occurrence of the + * tag. + */ + public abstract boolean isValid(); + + /** + * Type of tag this iterator represents. + * @return the tag. + */ + public abstract HTML.Tag getTag(); + } + + public class BlockElement extends AbstractDocument.BranchElement + { + public BlockElement (Element parent, AttributeSet a) + { + super (parent, a); + } + + /** + * Gets the resolving parent. Since HTML attributes are not + * inherited at the model level, this returns null. + */ + public AttributeSet getResolveParent() + { + return null; + } + + public String getName() + { + //FIXME: this is supposed to do something different from the super class + return super.getName(); + } } } diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java index c0182fe6ac9..5189c777539 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java +++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java @@ -43,8 +43,10 @@ import java.io.Reader; import java.io.Serializable; import javax.swing.text.BadLocationException; +import javax.swing.text.Document; import javax.swing.text.MutableAttributeSet; import javax.swing.text.StyledEditorKit; +import javax.swing.text.html.parser.ParserDelegator; /** * This class is NOT implemented. This file currently holds only @@ -96,9 +98,9 @@ public class HTMLEditorKit /** * The parser calls this method after it finishes parsing the document. */ - public void flush() - throws BadLocationException + public void flush() throws BadLocationException { + // TODO: What to do here, if anything? } /** @@ -108,6 +110,7 @@ public class HTMLEditorKit */ public void handleComment(char[] comment, int position) { + // TODO: What to do here, if anything? } /** @@ -118,6 +121,7 @@ public class HTMLEditorKit */ public void handleEndOfLineString(String end_of_line) { + // TODO: What to do here, if anything? } /** @@ -129,6 +133,7 @@ public class HTMLEditorKit */ public void handleEndTag(HTML.Tag tag, int position) { + // TODO: What to do here, if anything? } /** @@ -139,6 +144,7 @@ public class HTMLEditorKit */ public void handleError(String message, int position) { + // TODO: What to do here, if anything? } /** @@ -149,9 +155,9 @@ public class HTMLEditorKit * @param position The tag position in the text being parsed. */ public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes, - int position - ) + int position) { + // TODO: What to do here, if anything? } /** @@ -165,6 +171,7 @@ public class HTMLEditorKit int position ) { + // TODO: What to do here, if anything? } /** @@ -174,6 +181,7 @@ public class HTMLEditorKit */ public void handleText(char[] text, int position) { + // TODO: What to do here, if anything? } } @@ -247,4 +255,26 @@ public class HTMLEditorKit * The "ident paragraph right" action. */ public static final String PARA_INDENT_RIGHT = "html-para-indent-right"; + + /** + * Create a text storage model for this type of editor. + * + * @return the model + */ + public Document createDefaultDocument() + { + HTMLDocument document = new HTMLDocument(); + return document; + } + + /** + * Get the parser that this editor kit uses for reading HTML streams. This + * method can be overridden to use the alternative parser. + * + * @return the HTML parser (by default, {@link ParserDelegator}). + */ + protected Parser getParser() + { + return new ParserDelegator(); + } }
\ No newline at end of file diff --git a/libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java b/libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java index dc0ab10a8f7..e146965d778 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java +++ b/libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java @@ -41,7 +41,6 @@ package javax.swing.text.html; import java.net.URL; import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkEvent.EventType; import javax.swing.text.Element; /** @@ -50,8 +49,7 @@ import javax.swing.text.Element; * * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org) */ -public class HTMLFrameHyperlinkEvent - extends HyperlinkEvent +public class HTMLFrameHyperlinkEvent extends HyperlinkEvent { private final String target_frame; diff --git a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java index deb7b1602bb..70e9c2acbff 100644 --- a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java +++ b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java @@ -95,9 +95,12 @@ public final class ContentModel */ public int type; - /** Create a content model initializing all fields to default values. */ + /** + * Create a content model initializing all fields to default values. + */ public ContentModel() { + // Nothing to do here. } /** diff --git a/libjava/classpath/javax/swing/text/html/parser/DTD.java b/libjava/classpath/javax/swing/text/html/parser/DTD.java index f17ca011ea0..16bc5b0d6af 100644 --- a/libjava/classpath/javax/swing/text/html/parser/DTD.java +++ b/libjava/classpath/javax/swing/text/html/parser/DTD.java @@ -81,8 +81,9 @@ public class DTD { /** * The version of the persistent data format. + * @specnote This was made <code>final</code> in 1.5. */ - public static int FILE_VERSION = 1; + public static final int FILE_VERSION = 1; /** * The table of existing available DTDs. @@ -590,8 +591,7 @@ public class DTD * @param name the name of the entity * @param type the type of the entity, a bitwise combination * of GENERAL, PARAMETER, SYSTEM and PUBLIC. - * @throws an error if the parameter is both GENERAL and PARAMETER - * of both PUBLIC and SYSTEM. + * * @return the created entity */ private Entity newEntity(String name, int type) diff --git a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java index 164297f1882..062606d17ba 100644 --- a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java +++ b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java @@ -168,6 +168,7 @@ public class DocumentParser * specific packages, write your own DTD or obtain the working instance * of parser in other way, for example, by calling * {@link javax.swing.text.html.HTMLEditorKit#getParser()}. + * * @param a_dtd a DTD to use. */ public DocumentParser(DTD a_dtd) @@ -212,6 +213,7 @@ public class DocumentParser */ protected void handleComment(char[] comment) { + // This default implementation does nothing. } /** @@ -224,6 +226,7 @@ public class DocumentParser protected void handleEmptyTag(TagElement tag) throws javax.swing.text.ChangedCharSetException { + // This default implementation does nothing. } /** @@ -234,11 +237,13 @@ public class DocumentParser */ protected void handleEndTag(TagElement tag) { + // This default implementation does nothing. } /* Handle error that has occured in the given line. */ protected void handleError(int line, String message) { + // This default implementation does nothing. } /** @@ -249,6 +254,7 @@ public class DocumentParser */ protected void handleStartTag(TagElement tag) { + // This default implementation does nothing. } /** @@ -257,5 +263,6 @@ public class DocumentParser */ protected void handleText(char[] text) { + // This default implementation does nothing. } } diff --git a/libjava/classpath/javax/swing/text/html/parser/Element.java b/libjava/classpath/javax/swing/text/html/parser/Element.java index 098983c6923..c07c07f5426 100644 --- a/libjava/classpath/javax/swing/text/html/parser/Element.java +++ b/libjava/classpath/javax/swing/text/html/parser/Element.java @@ -148,10 +148,10 @@ public final class Element /** * The default constructor must have package level access in this * class. Use DTD.defineElement(..) to create an element when required. - * @todo MAKE THIS PACKAGE in the final version. Now the Parser needs it! */ Element() { + // Nothing to do here. } /** diff --git a/libjava/classpath/javax/swing/text/html/parser/Parser.java b/libjava/classpath/javax/swing/text/html/parser/Parser.java index 7ff6853da82..a88e9ce1953 100644 --- a/libjava/classpath/javax/swing/text/html/parser/Parser.java +++ b/libjava/classpath/javax/swing/text/html/parser/Parser.java @@ -256,6 +256,7 @@ public class Parser */ protected void endTag(boolean omitted) { + // This default implementation does nothing. } /** @@ -310,6 +311,7 @@ public class Parser */ protected void handleComment(char[] comment) { + // This default implementation does nothing. } /** @@ -333,6 +335,7 @@ public class Parser protected void handleEmptyTag(TagElement tag) throws ChangedCharSetException { + // This default implementation does nothing. } /** @@ -343,11 +346,13 @@ public class Parser */ protected void handleEndTag(TagElement tag) { + // This default implementation does nothing. } /* Handle error that has occured in the given line. */ protected void handleError(int line, String message) { + // This default implementation does nothing. } /** @@ -358,6 +363,7 @@ public class Parser */ protected void handleStartTag(TagElement tag) { + // This default implementation does nothing. } /** @@ -376,6 +382,7 @@ public class Parser */ protected void handleText(char[] text) { + // This default implementation does nothing. } /** @@ -387,6 +394,7 @@ public class Parser */ protected void handleTitle(char[] title) { + // This default implementation does nothing. } /** @@ -420,6 +428,7 @@ public class Parser */ protected void markFirstTime(Element element) { + // This default implementation does nothing. } /** @@ -432,5 +441,6 @@ public class Parser protected void startTag(TagElement tag) throws ChangedCharSetException { + // This default implementation does nothing. } } |