diff options
author | Mark Wielaard <mark@gcc.gnu.org> | 2006-03-10 21:46:48 +0000 |
---|---|---|
committer | Mark Wielaard <mark@gcc.gnu.org> | 2006-03-10 21:46:48 +0000 |
commit | 8aa540d2f783474d1d2e06f16744bf67b9c1facc (patch) | |
tree | ea38c56431c5d4528fb54254c3f8e50f517bede3 /libjava/classpath/javax/swing/text | |
parent | 27079765d00123f8e53d0e1ef7f9d46559266e6d (diff) | |
download | gcc-8aa540d2f783474d1d2e06f16744bf67b9c1facc.zip gcc-8aa540d2f783474d1d2e06f16744bf67b9c1facc.tar.gz gcc-8aa540d2f783474d1d2e06f16744bf67b9c1facc.tar.bz2 |
Imported GNU Classpath 0.90
Imported GNU Classpath 0.90
* scripts/makemake.tcl: Set gnu/java/awt/peer/swing to ignore.
* gnu/classpath/jdwp/VMFrame.java (SIZE): New constant.
* java/lang/VMCompiler.java: Use gnu.java.security.hash.MD5.
* java/lang/Math.java: New override file.
* java/lang/Character.java: Merged from Classpath.
(start, end): Now 'int's.
(canonicalName): New field.
(CANONICAL_NAME, NO_SPACES_NAME, CONSTANT_NAME): New constants.
(UnicodeBlock): Added argument.
(of): New overload.
(forName): New method.
Updated unicode blocks.
(sets): Updated.
* sources.am: Regenerated.
* Makefile.in: Likewise.
From-SVN: r111942
Diffstat (limited to 'libjava/classpath/javax/swing/text')
43 files changed, 7534 insertions, 2011 deletions
diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java index c735388..a3e8c46 100644 --- a/libjava/classpath/javax/swing/text/AbstractDocument.java +++ b/libjava/classpath/javax/swing/text/AbstractDocument.java @@ -538,18 +538,24 @@ public abstract class AbstractDocument implements Document, Serializable DefaultDocumentEvent event = new DefaultDocumentEvent(offset, text.length(), DocumentEvent.EventType.INSERT); - - writeLock(); - UndoableEdit undo = content.insertString(offset, text); - if (undo != null) - event.addEdit(undo); - insertUpdate(event, attributes); - writeUnlock(); + try + { + writeLock(); + UndoableEdit undo = content.insertString(offset, text); + if (undo != null) + event.addEdit(undo); + + insertUpdate(event, attributes); - fireInsertUpdate(event); - if (undo != null) - fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); + fireInsertUpdate(event); + if (undo != null) + fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); + } + finally + { + writeUnlock(); + } } /** @@ -640,6 +646,12 @@ public abstract class AbstractDocument implements Document, Serializable // 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 + // If the current thread holds the write lock, and attempted to also obtain + // a readLock, then numReaders hasn't been incremented and we don't need + // to unlock it here. + if (currentWriter == Thread.currentThread()) + return; + // FIXME: the reference implementation throws a // javax.swing.text.StateInvariantError here if (numReaders == 0) @@ -675,18 +687,21 @@ public abstract class AbstractDocument implements Document, Serializable new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.REMOVE); - removeUpdate(event); - - boolean shouldFire = content.getString(offset, length).length() != 0; - - writeLock(); - UndoableEdit temp = content.remove(offset, length); - writeUnlock(); - - postRemoveUpdate(event); - - if (shouldFire) - fireRemoveUpdate(event); + try + { + writeLock(); + + // The order of the operations below is critical! + removeUpdate(event); + UndoableEdit temp = content.remove(offset, length); + + postRemoveUpdate(event); + fireRemoveUpdate(event); + } + finally + { + writeUnlock(); + } } /** @@ -841,7 +856,7 @@ public abstract class AbstractDocument implements Document, Serializable */ protected void writeLock() { - if (currentWriter!= null && currentWriter.equals(Thread.currentThread())) + if (currentWriter != null && currentWriter.equals(Thread.currentThread())) return; synchronized (documentCV) { @@ -1330,11 +1345,11 @@ public abstract class AbstractDocument implements Document, Serializable public Object getAttribute(Object key) { Object result = attributes.getAttribute(key); - if (result == null && element_parent != null) + if (result == null) { - AttributeSet parentSet = element_parent.getAttributes(); - if (parentSet != null) - result = parentSet.getAttribute(key); + AttributeSet resParent = getResolveParent(); + if (resParent != null) + result = resParent.getAttribute(key); } return result; } @@ -1371,9 +1386,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public AttributeSet getResolveParent() { - if (attributes.getResolveParent() != null) - return attributes.getResolveParent(); - return element_parent.getAttributes(); + return attributes.getResolveParent(); } /** @@ -1573,6 +1586,18 @@ public abstract class AbstractDocument implements Document, Serializable private Element[] children = new Element[0]; /** + * The cached startOffset value. This is used in the case when a + * BranchElement (temporarily) has no child elements. + */ + private int startOffset; + + /** + * The cached endOffset value. This is used in the case when a + * BranchElement (temporarily) has no child elements. + */ + private int endOffset; + + /** * Creates a new <code>BranchElement</code> with the specified * parent and attributes. * @@ -1583,6 +1608,8 @@ public abstract class AbstractDocument implements Document, Serializable public BranchElement(Element parent, AttributeSet attributes) { super(parent, attributes); + startOffset = -1; + endOffset = -1; } /** @@ -1655,7 +1682,7 @@ public abstract class AbstractDocument implements Document, Serializable // 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 - 1; ++index) @@ -1695,9 +1722,15 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getEndOffset() { - if (getElementCount() == 0) - throw new NullPointerException("This BranchElement has no children."); - return children[children.length - 1].getEndOffset(); + if (children.length == 0) + { + if (endOffset == -1) + throw new NullPointerException("BranchElement has no children."); + } + else + endOffset = children[children.length - 1].getEndOffset(); + + return endOffset; } /** @@ -1718,13 +1751,20 @@ public abstract class AbstractDocument implements Document, Serializable * * @return the start offset of this element inside the document model * - * @throws NullPointerException if this branch element has no children + * @throws NullPointerException if this branch element has no children and + * no startOffset value has been cached */ public int getStartOffset() { - if (getElementCount() == 0) - throw new NullPointerException("This BranchElement has no children."); - return children[0].getStartOffset(); + if (children.length == 0) + { + if (startOffset == -1) + throw new NullPointerException("BranchElement has no children."); + } + else + startOffset = children[0].getStartOffset(); + + return startOffset; } /** @@ -2022,13 +2062,29 @@ public abstract class AbstractDocument implements Document, Serializable /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = -8906306331347768017L; - /** Manages the start offset of this element. */ - Position startPos; + /** + * Manages the start offset of this element. + */ + private Position startPos; + + /** + * Manages the end offset of this element. + */ + private Position endPos; - /** Manages the end offset of this element. */ - Position endPos; + /** + * This gets possible added to the startOffset when a startOffset + * outside the document range is requested. + */ + private int startDelta; /** + * This gets possible added to the endOffset when a endOffset + * outside the document range is requested. + */ + private int endDelta; + + /** * Creates a new <code>LeafElement</code>. * * @param parent the parent of this <code>LeafElement</code> @@ -2040,20 +2096,18 @@ public abstract class AbstractDocument implements Document, Serializable int end) { super(parent, attributes); - { - try + int len = content.length(); + startDelta = 0; + if (start > len) + startDelta = start - len; + endDelta = 0; + if (end > len) + endDelta = end - len; + try { - if (parent != null) - { - startPos = parent.getDocument().createPosition(start); - endPos = parent.getDocument().createPosition(end); - } - else - { - startPos = createPosition(start); - endPos = createPosition(end); + startPos = createPosition(start - startDelta); + endPos = createPosition(end - endDelta); } - } catch (BadLocationException ex) { AssertionError as; @@ -2064,7 +2118,6 @@ public abstract class AbstractDocument implements Document, Serializable as.initCause(ex); throw as; } - } } /** @@ -2136,7 +2189,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getEndOffset() { - return endPos.getOffset(); + return endPos.getOffset() + endDelta; } /** @@ -2162,7 +2215,7 @@ public abstract class AbstractDocument implements Document, Serializable */ public int getStartOffset() { - return startPos.getOffset(); + return startPos.getOffset() + startDelta; } /** diff --git a/libjava/classpath/javax/swing/text/AsyncBoxView.java b/libjava/classpath/javax/swing/text/AsyncBoxView.java new file mode 100644 index 0000000..1988bba --- /dev/null +++ b/libjava/classpath/javax/swing/text/AsyncBoxView.java @@ -0,0 +1,1480 @@ +/* AsyncBoxView.java -- A box view that performs layout asynchronously + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; +import java.util.ArrayList; + +import javax.swing.event.DocumentEvent; +import javax.swing.text.Position.Bias; + +/** + * A {@link View} implementation that lays out its child views in a box, either + * vertically or horizontally. The difference to {@link BoxView} is that the + * layout is performed in an asynchronous manner. This helps to keep the + * eventqueue free from non-GUI related tasks. + * + * This view is currently not used in standard text components. In order to + * use it you would have to implement a special {@link EditorKit} with a + * {@link ViewFactory} that returns this view. For example: + * + * <pre> + * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory + * { + * public View create(Element el) + * { + * if (el.getName().equals(AbstractDocument.SectionElementName)) + * return new AsyncBoxView(el, View.Y_AXIS); + * return super.getViewFactory().create(el); + * } + * public ViewFactory getViewFactory() { + * return this; + * } + * } + * </pre> + * + * @author Roman Kennke (kennke@aicas.com) + * + * @since 1.3 + */ +public class AsyncBoxView + extends View +{ + + /** + * Manages the effective position of child views. That keeps the visible + * layout stable while the AsyncBoxView might be changing until the layout + * thread decides to publish the new layout. + */ + public class ChildLocator + { + + /** + * The last valid location. + */ + protected ChildState lastValidOffset; + + /** + * The last allocation. + */ + protected Rectangle lastAlloc; + + /** + * A Rectangle used for child allocation calculation to avoid creation + * of lots of garbage Rectangle objects. + */ + protected Rectangle childAlloc; + + /** + * Creates a new ChildLocator. + */ + public ChildLocator() + { + lastAlloc = new Rectangle(); + childAlloc = new Rectangle(); + } + + /** + * Receives notification that a child has changed. This is called by + * child state objects that have changed it's major span. + * + * This sets the {@link #lastValidOffset} field to <code>cs</code> if + * the new child state's view start offset is smaller than the start offset + * of the current child state's view or when <code>lastValidOffset</code> + * is <code>null</code>. + * + * @param cs the child state object that has changed + */ + public synchronized void childChanged(ChildState cs) + { + if (lastValidOffset == null + || cs.getChildView().getStartOffset() + < lastValidOffset.getChildView().getStartOffset()) + { + lastValidOffset = cs; + } + } + + /** + * Returns the view index of the view that occupies the specified area, or + * <code>-1</code> if there is no such child view. + * + * @param x the x coordinate (relative to <code>a</code>) + * @param y the y coordinate (relative to <code>a</code>) + * @param a the current allocation of this view + * + * @return the view index of the view that occupies the specified area, or + * <code>-1</code> if there is no such child view + */ + public int getViewIndexAtPoint(float x, float y, Shape a) + { + setAllocation(a); + float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x + : y - lastAlloc.y; + int index = getViewIndexAtVisualOffset(targetOffset); + return index; + } + + /** + * Returns the current allocation for a child view. This updates the + * offsets for all children <em>before</em> the requested child view. + * + * @param index the index of the child view + * @param a the current allocation of this view + * + * @return the current allocation for a child view + */ + public synchronized Shape getChildAllocation(int index, Shape a) + { + if (a == null) + return null; + setAllocation(a); + ChildState cs = getChildState(index); + if (cs.getChildView().getStartOffset() + > lastValidOffset.getChildView().getStartOffset()) + { + updateChildOffsetsToIndex(index); + } + Shape ca = getChildAllocation(index); + return ca; + } + + /** + * Paints all child views. + * + * @param g the graphics context to use + */ + public synchronized void paintChildren(Graphics g) + { + Rectangle clip = g.getClipBounds(); + float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x + : clip.y - lastAlloc.y; + int index = getViewIndexAtVisualOffset(targetOffset); + int n = getViewCount(); + float offs = getChildState(index).getMajorOffset(); + for (int i = index; i < n; i++) + { + ChildState cs = getChildState(i); + cs.setMajorOffset(offs); + Shape ca = getChildAllocation(i); + if (ca.intersects(clip)) + { + synchronized (cs) + { + View v = cs.getChildView(); + v.paint(g, ca); + } + } + else + { + // done painting intersection + break; + } + offs += cs.getMajorSpan(); + } + } + + /** + * Returns the current allocation of the child view with the specified + * index. Note that this will <em>not</em> update any location information. + * + * @param index the index of the requested child view + * + * @return the current allocation of the child view with the specified + * index + */ + protected Shape getChildAllocation(int index) + { + ChildState cs = getChildState(index); + if (! cs.isLayoutValid()) + cs.run(); + + if (getMajorAxis() == X_AXIS) + { + childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset(); + childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset(); + childAlloc.width = (int) cs.getMajorSpan(); + childAlloc.height = (int) cs.getMinorSpan(); + } + else + { + childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset(); + childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset(); + childAlloc.height = (int) cs.getMajorSpan(); + childAlloc.width = (int) cs.getMinorSpan(); + } + return childAlloc; + } + + /** + * Sets the current allocation for this view. + * + * @param a the allocation to set + */ + protected void setAllocation(Shape a) + { + if (a instanceof Rectangle) + lastAlloc.setBounds((Rectangle) a); + else + lastAlloc.setBounds(a.getBounds()); + + setSize(lastAlloc.width, lastAlloc.height); + } + + /** + * Returns the index of the view at the specified offset along the major + * layout axis. + * + * @param targetOffset the requested offset + * + * @return the index of the view at the specified offset along the major + * layout axis + */ + protected int getViewIndexAtVisualOffset(float targetOffset) + { + int n = getViewCount(); + if (n > 0) + { + if (lastValidOffset == null) + lastValidOffset = getChildState(0); + if (targetOffset > majorSpan) + return 0; + else if (targetOffset > lastValidOffset.getMajorOffset()) + return updateChildOffsets(targetOffset); + else + { + float offs = 0f; + for (int i = 0; i < n; i++) + { + ChildState cs = getChildState(i); + float nextOffs = offs + cs.getMajorSpan(); + if (targetOffset < nextOffs) + return i; + offs = nextOffs; + } + } + } + return n - 1; + } + + /** + * Updates all the child view offsets up to the specified targetOffset. + * + * @param targetOffset the offset up to which the child view offsets are + * updated + * + * @return the index of the view at the specified offset + */ + private int updateChildOffsets(float targetOffset) + { + int n = getViewCount(); + int targetIndex = n - 1;; + int pos = lastValidOffset.getChildView().getStartOffset(); + int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); + float start = lastValidOffset.getMajorOffset(); + float lastOffset = start; + for (int i = startIndex; i < n; i++) + { + ChildState cs = getChildState(i); + cs.setMajorOffset(lastOffset); + lastOffset += cs.getMajorSpan(); + if (targetOffset < lastOffset) + { + targetIndex = i; + lastValidOffset = cs; + break; + } + } + return targetIndex; + } + + /** + * Updates the offsets of the child views up to the specified index. + * + * @param index the index up to which the offsets are updated + */ + private void updateChildOffsetsToIndex(int index) + { + int pos = lastValidOffset.getChildView().getStartOffset(); + int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); + float lastOffset = lastValidOffset.getMajorOffset(); + for (int i = startIndex; i <= index; i++) + { + ChildState cs = getChildState(i); + cs.setMajorOffset(lastOffset); + lastOffset += cs.getMajorSpan(); + } + } + } + + /** + * Represents the layout state of a child view. + */ + public class ChildState + implements Runnable + { + + /** + * The child view for this state record. + */ + private View childView; + + /** + * Indicates if the minor axis requirements of this child view are valid + * or not. + */ + private boolean minorValid; + + /** + * Indicates if the major axis requirements of this child view are valid + * or not. + */ + private boolean majorValid; + + /** + * Indicates if the current child size is valid. This is package private + * to avoid synthetic accessor method. + */ + boolean childSizeValid; + + /** + * The child views minimumSpan. This is package private to avoid accessor + * method. + */ + float minimum; + + /** + * The child views preferredSpan. This is package private to avoid accessor + * method. + */ + float preferred; + + /** + * The current span of the child view along the major axis. + */ + private float majorSpan; + + /** + * The current offset of the child view along the major axis. + */ + private float majorOffset; + + /** + * The current span of the child view along the minor axis. + */ + private float minorSpan; + + /** + * The current offset of the child view along the major axis. + */ + private float minorOffset; + + /** + * The child views maximumSpan. + */ + private float maximum; + + /** + * Creates a new <code>ChildState</code> object for the specified child + * view. + * + * @param view the child view for which to create the state record + */ + public ChildState(View view) + { + childView = view; + } + + /** + * Returns the child view for which this <code>ChildState</code> represents + * the layout state. + * + * @return the child view for this child state object + */ + public View getChildView() + { + return childView; + } + + /** + * Returns <code>true</code> if the current layout information is valid, + * <code>false</code> otherwise. + * + * @return <code>true</code> if the current layout information is valid, + * <code>false</code> otherwise + */ + public boolean isLayoutValid() + { + return minorValid && majorValid && childSizeValid; + } + + /** + * Performs the layout update for the child view managed by this + * <code>ChildState</code>. + */ + public void run() + { + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readLock(); + } + + try + { + + if (!(minorValid && majorValid && childSizeValid) + && childView.getParent() == AsyncBoxView.this) + { + synchronized(AsyncBoxView.this) + { + changing = this; + } + update(); + synchronized(AsyncBoxView.this) + { + changing = null; + } + // Changing the major axis may cause the minor axis + // requirements to have changed, so we need to do this again. + update(); + } + } + finally + { + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readUnlock(); + } + } + } + + /** + * Performs the actual update after the run methods has made its checks + * and locked the document. + */ + private void update() + { + int majorAxis = getMajorAxis(); + boolean minorUpdated = false; + synchronized (this) + { + if (! minorValid) + { + int minorAxis = getMinorAxis(); + minimum = childView.getMinimumSpan(minorAxis); + preferred = childView.getPreferredSpan(minorAxis); + maximum = childView.getMaximumSpan(minorAxis); + minorValid = true; + minorUpdated = true; + } + } + if (minorUpdated) + minorRequirementChange(this); + + boolean majorUpdated = false; + float delta = 0.0F; + synchronized (this) + { + if (! majorValid) + { + float oldSpan = majorSpan; + majorSpan = childView.getPreferredSpan(majorAxis); + delta = majorSpan - oldSpan; + majorValid = true; + majorUpdated = true; + } + } + if (majorUpdated) + { + majorRequirementChange(this, delta); + locator.childChanged(this); + } + + synchronized (this) + { + if (! childSizeValid) + { + float w; + float h; + if (majorAxis == X_AXIS) + { + w = majorSpan; + h = getMinorSpan(); + } + else + { + w = getMinorSpan(); + h = majorSpan; + } + childSizeValid = true; + childView.setSize(w, h); + } + } + } + + /** + * Returns the span of the child view along the minor layout axis. + * + * @return the span of the child view along the minor layout axis + */ + public float getMinorSpan() + { + float retVal; + if (maximum < minorSpan) + retVal = maximum; + else + retVal = Math.max(minimum, minorSpan); + return retVal; + } + + /** + * Returns the offset of the child view along the minor layout axis. + * + * @return the offset of the child view along the minor layout axis + */ + public float getMinorOffset() + { + float retVal; + if (maximum < minorSpan) + { + float align = childView.getAlignment(getMinorAxis()); + retVal = ((minorSpan - maximum) * align); + } + else + retVal = 0f; + + return retVal; + } + + /** + * Returns the span of the child view along the major layout axis. + * + * @return the span of the child view along the major layout axis + */ + + public float getMajorSpan() + { + return majorSpan; + } + + /** + * Returns the offset of the child view along the major layout axis. + * + * @return the offset of the child view along the major layout axis + */ + public float getMajorOffset() + { + return majorOffset; + } + + /** + * Sets the offset of the child view along the major layout axis. This + * should only be called by the ChildLocator of that child view. + * + * @param offset the offset to set + */ + public void setMajorOffset(float offset) + { + majorOffset = offset; + } + + /** + * Mark the preferences changed for that child. This forwards to + * {@link AsyncBoxView#preferenceChanged}. + * + * @param width <code>true</code> if the width preference has changed + * @param height <code>true</code> if the height preference has changed + */ + public void preferenceChanged(boolean width, boolean height) + { + if (getMajorAxis() == X_AXIS) + { + if (width) + majorValid = false; + if (height) + minorValid = false; + } + else + { + if (width) + minorValid = false; + if (height) + majorValid = false; + } + childSizeValid = false; + } + } + + /** + * Flushes the requirements changes upwards asynchronously. + */ + private class FlushTask implements Runnable + { + /** + * Starts the flush task. This obtains a readLock on the document + * and then flushes all the updates using + * {@link AsyncBoxView#flushRequirementChanges()} after updating the + * requirements. + */ + public void run() + { + try + { + // Acquire a lock on the document. + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readLock(); + } + + int n = getViewCount(); + if (minorChanged && (n > 0)) + { + LayoutQueue q = getLayoutQueue(); + ChildState min = getChildState(0); + ChildState pref = getChildState(0); + for (int i = 1; i < n; i++) + { + ChildState cs = getChildState(i); + if (cs.minimum > min.minimum) + min = cs; + if (cs.preferred > pref.preferred) + pref = cs; + } + synchronized (AsyncBoxView.this) + { + minReq = min; + prefReq = pref; + } + } + + flushRequirementChanges(); + } + finally + { + // Release the lock on the document. + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readUnlock(); + } + } + } + + } + + /** + * The major layout axis. + */ + private int majorAxis; + + /** + * The top inset. + */ + private float topInset; + + /** + * The bottom inset. + */ + private float bottomInset; + + /** + * The left inset. + */ + private float leftInset; + + /** + * Indicates if the major span should be treated as beeing estimated or not. + */ + private boolean estimatedMajorSpan; + + /** + * The right inset. + */ + private float rightInset; + + /** + * The children and their layout statistics. + */ + private ArrayList childStates; + + /** + * The currently changing child state. May be null if there is no child state + * updating at the moment. This is package private to avoid a synthetic + * accessor method inside ChildState. + */ + ChildState changing; + + /** + * Represents the minimum requirements. This is used in + * {@link #getMinimumSpan(int)}. + */ + ChildState minReq; + + /** + * Represents the minimum requirements. This is used in + * {@link #getPreferredSpan(int)}. + */ + ChildState prefReq; + + /** + * Indicates that the major axis requirements have changed. + */ + private boolean majorChanged; + + /** + * Indicates that the minor axis requirements have changed. This is package + * private to avoid synthetic accessor method. + */ + boolean minorChanged; + + /** + * The current span along the major layout axis. This is package private to + * avoid synthetic accessor method. + */ + float majorSpan; + + /** + * The current span along the minor layout axis. This is package private to + * avoid synthetic accessor method. + */ + float minorSpan; + + /** + * This tasked is placed on the layout queue to flush updates up to the + * parent view. + */ + private Runnable flushTask; + + /** + * The child locator for this view. + */ + protected ChildLocator locator; + + /** + * Creates a new <code>AsyncBoxView</code> that represents the specified + * element and layouts its children along the specified axis. + * + * @param elem the element + * @param axis the layout axis + */ + public AsyncBoxView(Element elem, int axis) + { + super(elem); + majorAxis = axis; + childStates = new ArrayList(); + flushTask = new FlushTask(); + locator = new ChildLocator(); + minorSpan = Short.MAX_VALUE; + } + + /** + * Returns the major layout axis. + * + * @return the major layout axis + */ + public int getMajorAxis() + { + return majorAxis; + } + + /** + * Returns the minor layout axis, that is the axis orthogonal to the major + * layout axis. + * + * @return the minor layout axis + */ + public int getMinorAxis() + { + return majorAxis == X_AXIS ? Y_AXIS : X_AXIS; + } + + /** + * Returns the view at the specified <code>index</code>. + * + * @param index the index of the requested child view + * + * @return the view at the specified <code>index</code> + */ + public View getView(int index) + { + View view = null; + synchronized(childStates) + { + if ((index >= 0) && (index < childStates.size())) + { + ChildState cs = (ChildState) childStates.get(index); + view = cs.getChildView(); + } + } + return view; + } + + /** + * Returns the number of child views. + * + * @return the number of child views + */ + public int getViewCount() + { + synchronized(childStates) + { + return childStates.size(); + } + } + + /** + * Returns the view index of the child view that represents the specified + * model position. + * + * @param pos the model position for which we search the view index + * @param bias the bias + * + * @return the view index of the child view that represents the specified + * model position + */ + public int getViewIndex(int pos, Position.Bias bias) + { + int retVal = -1; + + if (bias == Position.Bias.Backward) + pos = Math.max(0, pos - 1); + + // TODO: A possible optimization would be to implement a binary search + // here. + int numChildren = childStates.size(); + if (numChildren > 0) + { + for (int i = 0; i < numChildren; ++i) + { + View child = ((ChildState) childStates.get(i)).getChildView(); + if (child.getStartOffset() <= pos && child.getEndOffset() > pos) + { + retVal = i; + break; + } + } + } + return retVal; + } + + /** + * Returns the top inset. + * + * @return the top inset + */ + public float getTopInset() + { + return topInset; + } + + /** + * Sets the top inset. + * + * @param top the top inset + */ + public void setTopInset(float top) + { + topInset = top; + } + + /** + * Returns the bottom inset. + * + * @return the bottom inset + */ + public float getBottomInset() + { + return bottomInset; + } + + /** + * Sets the bottom inset. + * + * @param bottom the bottom inset + */ + public void setBottomInset(float bottom) + { + bottomInset = bottom; + } + + /** + * Returns the left inset. + * + * @return the left inset + */ + public float getLeftInset() + { + return leftInset; + } + + /** + * Sets the left inset. + * + * @param left the left inset + */ + public void setLeftInset(float left) + { + leftInset = left; + } + + /** + * Returns the right inset. + * + * @return the right inset + */ + public float getRightInset() + { + return rightInset; + } + + /** + * Sets the right inset. + * + * @param right the right inset + */ + public void setRightInset(float right) + { + rightInset = right; + } + + /** + * Loads the child views of this view. This is triggered by + * {@link #setParent(View)}. + * + * @param f the view factory to build child views with + */ + protected void loadChildren(ViewFactory f) + { + Element e = getElement(); + int n = e.getElementCount(); + if (n > 0) + { + View[] added = new View[n]; + for (int i = 0; i < n; i++) + { + added[i] = f.create(e.getElement(i)); + } + replace(0, 0, added); + } + } + + /** + * Returns the span along an axis that is taken up by the insets. + * + * @param axis the axis + * + * @return the span along an axis that is taken up by the insets + * + * @since 1.4 + */ + protected float getInsetSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = leftInset + rightInset; + else + span = topInset + bottomInset; + return span; + } + + /** + * Sets the <code>estimatedMajorSpan</code> property that determines if + * the major span should be treated as beeing estimated. + * + * @param estimated if the major span should be treated as estimated or not + * + * @since 1.4 + */ + public void setEstimatedMajorSpan(boolean estimated) + { + estimatedMajorSpan = estimated; + } + + /** + * Determines whether the major span should be treated as estimated or as + * beeing accurate. + * + * @return <code>true</code> if the major span should be treated as + * estimated, <code>false</code> if the major span should be treated + * as accurate + * + * @since 1.4 + */ + public boolean getEstimatedMajorSpan() + { + return estimatedMajorSpan; + } + + /** + * Receives notification from the child states that the requirements along + * the minor axis have changed. + * + * @param cs the child state from which this notification is messaged + */ + protected synchronized void minorRequirementChange(ChildState cs) + { + minorChanged = true; + } + + /** + * Receives notification from the child states that the requirements along + * the major axis have changed. + * + * @param cs the child state from which this notification is messaged + */ + protected void majorRequirementChange(ChildState cs, float delta) + { + if (! estimatedMajorSpan) + majorSpan += delta; + majorChanged = true; + } + + /** + * Sets the parent for this view. This calls loadChildren if + * <code>parent</code> is not <code>null</code> and there have not been any + * child views initializes. + * + * @param parent the new parent view; <code>null</code> if this view is + * removed from the view hierarchy + * + * @see View#setParent(View) + */ + public void setParent(View parent) + { + super.setParent(parent); + if ((parent != null) && (getViewCount() == 0)) + { + ViewFactory f = getViewFactory(); + loadChildren(f); + } + } + + /** + * Sets the size of this view. This is ususally called before {@link #paint} + * is called to make sure the view has a valid layout. + * + * This implementation queues layout requests for every child view if the + * minor axis span has changed. (The major axis span is requested to never + * change for this view). + * + * @param width the width of the view + * @param height the height of the view + */ + public void setSize(float width, float height) + { + float targetSpan; + if (majorAxis == X_AXIS) + targetSpan = height - getTopInset() - getBottomInset(); + else + targetSpan = width - getLeftInset() - getRightInset(); + + if (targetSpan != minorSpan) + { + minorSpan = targetSpan; + + int n = getViewCount(); + LayoutQueue q = getLayoutQueue(); + for (int i = 0; i < n; i++) + { + ChildState cs = getChildState(i); + cs.childSizeValid = false; + q.addTask(cs); + } + q.addTask(flushTask); + } + } + + /** + * Replaces child views with new child views. + * + * This creates ChildState objects for all the new views and adds layout + * requests for them to the layout queue. + * + * @param offset the offset at which to remove/insert + * @param length the number of child views to remove + * @param views the new child views to insert + */ + public void replace(int offset, int length, View[] views) + { + synchronized(childStates) + { + LayoutQueue q = getLayoutQueue(); + for (int i = 0; i < length; i++) + childStates.remove(offset); + + for (int i = views.length - 1; i >= 0; i--) + childStates.add(offset, createChildState(views[i])); + + // We need to go through the new child states _after_ they have been + // added to the childStates list, otherwise the layout tasks may find + // an incomplete child list. That means we have to loop through + // them again, but what else can we do? + if (views.length != 0) + { + for (int i = 0; i < views.length; i++) + { + ChildState cs = (ChildState) childStates.get(i + offset); + cs.getChildView().setParent(this); + q.addTask(cs); + } + q.addTask(flushTask); + } + } + } + + /** + * Paints the view. This requests the {@link ChildLocator} to paint the views + * after setting the allocation on it. + * + * @param g the graphics context to use + * @param s the allocation for this view + */ + public void paint(Graphics g, Shape s) + { + synchronized (locator) + { + locator.setAllocation(s); + locator.paintChildren(g); + } + } + + /** + * Returns the preferred span of this view along the specified layout axis. + * + * @return the preferred span of this view along the specified layout axis + */ + public float getPreferredSpan(int axis) + { + float retVal; + if (majorAxis == axis) + retVal = majorSpan; + + else if (prefReq != null) + { + View child = prefReq.getChildView(); + retVal = child.getPreferredSpan(axis); + } + + // If we have no layout information yet, then return insets + 30 as + // an estimation. + else + { + if (axis == X_AXIS) + retVal = getLeftInset() + getRightInset() + 30; + else + retVal = getTopInset() + getBottomInset() + 30; + } + return retVal; + } + + /** + * Maps a model location to view coordinates. + * + * @param pos the model location + * @param a the current allocation of this view + * @param b the bias + * + * @return the view allocation for the specified model location + */ + public Shape modelToView(int pos, Shape a, Bias b) + throws BadLocationException + { + int index = getViewIndexAtPosition(pos, b); + Shape ca = locator.getChildAllocation(index, a); + + ChildState cs = getChildState(index); + synchronized (cs) + { + View cv = cs.getChildView(); + Shape v = cv.modelToView(pos, ca, b); + return v; + } + } + + /** + * Maps view coordinates to a model location. + * + * @param x the x coordinate (relative to <code>a</code>) + * @param y the y coordinate (relative to <code>a</code>) + * @param b holds the bias of the model location on method exit + * + * @return the model location for the specified view location + */ + public int viewToModel(float x, float y, Shape a, Bias[] b) + { + int pos; + int index; + Shape ca; + + synchronized (locator) + { + index = locator.getViewIndexAtPoint(x, y, a); + ca = locator.getChildAllocation(index, a); + } + + ChildState cs = getChildState(index); + synchronized (cs) + { + View v = cs.getChildView(); + pos = v.viewToModel(x, y, ca, b); + } + return pos; + } + + /** + * Returns the child allocation for the child view with the specified + * <code>index</code>. + * + * @param index the index of the child view + * @param a the current allocation of this view + * + * @return the allocation of the child view + */ + public Shape getChildAllocation(int index, Shape a) + { + Shape ca = locator.getChildAllocation(index, a); + return ca; + } + + /** + * Returns the maximum span of this view along the specified axis. + * This is implemented to return the <code>preferredSpan</code> for the + * major axis (that means the box can't be resized along the major axis) and + * {@link Short#MAX_VALUE} for the minor axis. + * + * @param axis the axis + * + * @return the maximum span of this view along the specified axis + */ + public float getMaximumSpan(int axis) + { + float max; + if (axis == majorAxis) + max = getPreferredSpan(axis); + else + max = Short.MAX_VALUE; + return max; + } + + /** + * Returns the minimum span along the specified axis. + */ + public float getMinimumSpan(int axis) + { + float min; + if (axis == majorAxis) + min = getPreferredSpan(axis); + else + { + if (minReq != null) + { + View child = minReq.getChildView(); + min = child.getMinimumSpan(axis); + } + else + { + // No layout information yet. Return insets + 5 as some kind of + // estimation. + if (axis == X_AXIS) + min = getLeftInset() + getRightInset() + 5; + else + min = getTopInset() + getBottomInset() + 5; + } + } + return min; + } + + /** + * Receives notification that one of the child views has changed its + * layout preferences along one or both axis. + * + * This queues a layout request for that child view if necessary. + * + * @param view the view that has changed its preferences + * @param width <code>true</code> if the width preference has changed + * @param height <code>true</code> if the height preference has changed + */ + public synchronized void preferenceChanged(View view, boolean width, + boolean height) + { + if (view == null) + getParent().preferenceChanged(this, width, height); + else + { + if (changing != null) + { + View cv = changing.getChildView(); + if (cv == view) + { + changing.preferenceChanged(width, height); + return; + } + } + int index = getViewIndexAtPosition(view.getStartOffset(), + Position.Bias.Forward); + ChildState cs = getChildState(index); + cs.preferenceChanged(width, height); + LayoutQueue q = getLayoutQueue(); + q.addTask(cs); + q.addTask(flushTask); + } + } + + /** + * Updates the layout for this view. This is implemented to trigger + * {link ChildLocator#childChanged} for the changed view, if there is + * any. + * + * @param ec the element change, may be <code>null</code> if there were + * no changes to the element of this view + * @param e the document event + * @param a the current allocation of this view + */ + protected void updateLayout(DocumentEvent.ElementChange ec, + DocumentEvent e, Shape a) + { + if (ec != null) + { + int index = Math.max(ec.getIndex() - 1, 0); + ChildState cs = getChildState(index); + locator.childChanged(cs); + } + } + + + /** + * Returns the <code>ChildState</code> object associated with the child view + * at the specified <code>index</code>. + * + * @param index the index of the child view for which to query the state + * + * @return the child state for the specified child view + */ + protected ChildState getChildState(int index) { + synchronized (childStates) + { + return (ChildState) childStates.get(index); + } + } + + /** + * Returns the <code>LayoutQueue</code> used for layouting the box view. + * This simply returns {@link LayoutQueue#getDefaultQueue()}. + * + * @return the <code>LayoutQueue</code> used for layouting the box view + */ + protected LayoutQueue getLayoutQueue() + { + return LayoutQueue.getDefaultQueue(); + } + + /** + * Returns the child view index of the view that represents the specified + * position in the document model. + * + * @param pos the position in the model + * @param b the bias + * + * @return the child view index of the view that represents the specified + * position in the document model + */ + protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b) + { + if (b == Position.Bias.Backward) + pos = Math.max(0, pos - 1); + Element elem = getElement(); + return elem.getElementIndex(pos); + } + + /** + * Creates a <code>ChildState</code> object for the specified view. + * + * @param v the view for which to create a child state object + * + * @return the created child state + */ + protected ChildState createChildState(View v) + { + return new ChildState(v); + } + + /** + * Flushes the requirements changes upwards to the parent view. This is + * called from the layout thread. + */ + protected synchronized void flushRequirementChanges() + { + if (majorChanged || minorChanged) + { + View p = getParent(); + if (p != null) + { + boolean horizontal; + boolean vertical; + if (majorAxis == X_AXIS) + { + horizontal = majorChanged; + vertical = minorChanged; + } + else + { + vertical = majorChanged; + horizontal = minorChanged; + } + + p.preferenceChanged(this, horizontal, vertical); + majorChanged = false; + minorChanged = false; + + Component c = getContainer(); + if (c != null) + c.repaint(); + } + } + } +} diff --git a/libjava/classpath/javax/swing/text/BoxView.java b/libjava/classpath/javax/swing/text/BoxView.java index 5c9587d..b5907dc 100644 --- a/libjava/classpath/javax/swing/text/BoxView.java +++ b/libjava/classpath/javax/swing/text/BoxView.java @@ -43,6 +43,7 @@ import java.awt.Rectangle; import java.awt.Shape; import javax.swing.SizeRequirements; +import javax.swing.event.DocumentEvent; /** * An implementation of {@link CompositeView} that arranges its children in @@ -58,49 +59,37 @@ public class BoxView /** * The axis along which this <code>BoxView</code> is laid out. */ - int myAxis; + private int myAxis; /** - * Indicates wether the layout in X_AXIS is valid. + * Indicates if the layout is valid along X_AXIS or Y_AXIS. */ - boolean xLayoutValid; + private boolean[] layoutValid = new boolean[2]; /** - * Indicates whether the layout in Y_AXIS is valid. + * The spans along the X_AXIS and Y_AXIS. */ - boolean yLayoutValid; + private int[][] spans = new int[2][]; /** - * The spans in X direction of the children. + * The offsets of the children along the X_AXIS and Y_AXIS. */ - int[] spansX; + private int[][] offsets = new int[2][]; /** - * The spans in Y direction of the children. + * The size requirements along the X_AXIS and Y_AXIS. */ - int[] spansY; + private SizeRequirements[] requirements = new SizeRequirements[2]; /** - * The offsets of the children in X direction relative to this BoxView's - * inner bounds. + * The current span along X_AXIS or Y_AXIS. */ - int[] offsetsX; + private int[] span = new int[2]; /** - * The offsets of the children in Y direction relative to this BoxView's - * inner bounds. + * The SizeRequirements of the child views along the X_AXIS and Y_AXIS. */ - int[] offsetsY; - - /** - * The current width. - */ - int width; - - /** - * The current height. - */ - int height; + private SizeRequirements[][] childReqs = new SizeRequirements[2][]; /** * Creates a new <code>BoxView</code> for the given @@ -114,23 +103,26 @@ public class BoxView { super(element); myAxis = axis; - xLayoutValid = false; - yLayoutValid = false; + layoutValid[0] = false; + layoutValid[1] = false; + span[0] = 0; + span[1] = 0; + requirements[0] = new SizeRequirements(); + requirements[1] = new SizeRequirements(); // Initialize the cache arrays. - spansX = new int[0]; - spansY = new int[0]; - offsetsX = new int[0]; - offsetsY = new int[0]; - - width = 0; - height = 0; + spans[0] = new int[0]; + spans[1] = new int[0]; + offsets[0] = new int[0]; + offsets[1] = new int[0]; } /** * Returns the axis along which this <code>BoxView</code> is laid out. * * @return the axis along which this <code>BoxView</code> is laid out + * + * @since 1.3 */ public int getAxis() { @@ -144,6 +136,8 @@ public class BoxView * {@link View#Y_AXIS}. * * @param axis the axis along which this <code>BoxView</code> is laid out + * + * @since 1.3 */ public void setAxis(int axis) { @@ -163,20 +157,14 @@ public class BoxView * {@link View#Y_AXIS}. * * @param axis an <code>int</code> value + * + * @since 1.3 */ public void layoutChanged(int axis) { - switch (axis) - { - case X_AXIS: - xLayoutValid = false; - break; - case Y_AXIS: - yLayoutValid = false; - break; - default: - throw new IllegalArgumentException("Invalid axis parameter."); - } + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Invalid axis parameter."); + layoutValid[axis] = false; } /** @@ -190,22 +178,14 @@ public class BoxView * * @return <code>true</code> if the layout along the specified * <code>axis</code> is valid, <code>false</code> otherwise + * + * @since 1.4 */ protected boolean isLayoutValid(int axis) { - boolean valid = false; - switch (axis) - { - case X_AXIS: - valid = xLayoutValid; - break; - case Y_AXIS: - valid = yLayoutValid; - break; - default: - throw new IllegalArgumentException("Invalid axis parameter."); - } - return valid; + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Invalid axis parameter."); + return layoutValid[axis]; } /** @@ -242,40 +222,44 @@ public class BoxView */ public void replace(int offset, int length, View[] views) { + int numViews = 0; + if (views != null) + numViews = views.length; + // Resize and copy data for cache arrays. // The spansX cache. int oldSize = getViewCount(); - int[] newSpansX = new int[oldSize - length + views.length]; - System.arraycopy(spansX, 0, newSpansX, 0, offset); - System.arraycopy(spansX, offset + length, newSpansX, - offset + views.length, + int[] newSpansX = new int[oldSize - length + numViews]; + System.arraycopy(spans[X_AXIS], 0, newSpansX, 0, offset); + System.arraycopy(spans[X_AXIS], offset + length, newSpansX, + offset + numViews, oldSize - (offset + length)); - spansX = newSpansX; + spans[X_AXIS] = newSpansX; // The spansY cache. - int[] newSpansY = new int[oldSize - length + views.length]; - System.arraycopy(spansY, 0, newSpansY, 0, offset); - System.arraycopy(spansY, offset + length, newSpansY, - offset + views.length, + int[] newSpansY = new int[oldSize - length + numViews]; + System.arraycopy(spans[Y_AXIS], 0, newSpansY, 0, offset); + System.arraycopy(spans[Y_AXIS], offset + length, newSpansY, + offset + numViews, oldSize - (offset + length)); - spansY = newSpansY; + spans[Y_AXIS] = newSpansY; // The offsetsX cache. - int[] newOffsetsX = new int[oldSize - length + views.length]; - System.arraycopy(offsetsX, 0, newOffsetsX, 0, offset); - System.arraycopy(offsetsX, offset + length, newOffsetsX, - offset + views.length, + int[] newOffsetsX = new int[oldSize - length + numViews]; + System.arraycopy(offsets[X_AXIS], 0, newOffsetsX, 0, offset); + System.arraycopy(offsets[X_AXIS], offset + length, newOffsetsX, + offset + numViews, oldSize - (offset + length)); - offsetsX = newOffsetsX; + offsets[X_AXIS] = newOffsetsX; // The offsetsY cache. - int[] newOffsetsY = new int[oldSize - length + views.length]; - System.arraycopy(offsetsY, 0, newOffsetsY, 0, offset); - System.arraycopy(offsetsY, offset + length, newOffsetsY, - offset + views.length, + int[] newOffsetsY = new int[oldSize - length + numViews]; + System.arraycopy(offsets[Y_AXIS], 0, newOffsetsY, 0, offset); + System.arraycopy(offsets[Y_AXIS], offset + length, newOffsetsY, + offset + numViews, oldSize - (offset + length)); - offsetsY = newOffsetsY; + offsets[Y_AXIS] = newOffsetsY; // Actually perform the replace. super.replace(offset, length, views); @@ -294,13 +278,10 @@ public class BoxView */ public void paint(Graphics g, Shape a) { - // Adjust size if the size is changed. - Rectangle bounds = a.getBounds(); - - if (bounds.width != getWidth() || bounds.height != getHeight()) - setSize(bounds.width, bounds.height); - Rectangle inside = getInsideAllocation(a); + // TODO: Used for debugging. + //g.drawRect(inside.x, inside.y, inside.width, inside.height); + Rectangle copy = new Rectangle(inside); int count = getViewCount(); for (int i = 0; i < count; ++i) @@ -323,22 +304,50 @@ public class BoxView */ public float getPreferredSpan(int axis) { - SizeRequirements sr = new SizeRequirements(); - int pref = baselineRequirements(axis, sr).preferred; - return (float) pref; + updateRequirements(axis); + return requirements[axis].preferred; } + /** + * Returns the maximum span of this view along the specified axis. + * This returns <code>Integer.MAX_VALUE</code> for the minor axis + * and the preferred span for the major axis. + * + * @param axis the axis + * + * @return the maximum span of this view along the specified axis + */ public float getMaximumSpan(int axis) { - if (axis == getAxis()) - return getPreferredSpan(axis); + float max; + if (axis == myAxis) + max = getPreferredSpan(axis); else - return Integer.MAX_VALUE; + max = Integer.MAX_VALUE; + return max; } /** - * Calculates the size requirements for this <code>BoxView</code> along - * the specified axis. + * Returns the minimum span of this view along the specified axis. + * This calculates the minimum span using + * {@link #calculateMajorAxisRequirements} or + * {@link #calculateMinorAxisRequirements} (depending on the axis) and + * returns the resulting minimum span. + * + * @param axis the axis + * + * @return the minimum span of this view along the specified axis + */ + public float getMinimumSpan(int axis) + { + updateRequirements(axis); + return requirements[axis].minimum; + } + + /** + * This method is obsolete and no longer in use. It is replaced by + * {@link #calculateMajorAxisRequirements(int, SizeRequirements)} and + * {@link #calculateMinorAxisRequirements(int, SizeRequirements)}. * * @param axis the axis that is examined * @param sr the <code>SizeRequirements</code> object to hold the result, @@ -350,12 +359,45 @@ public class BoxView protected SizeRequirements baselineRequirements(int axis, SizeRequirements sr) { - SizeRequirements result; - if (axis == myAxis) - result = calculateMajorAxisRequirements(axis, sr); - else - result = calculateMinorAxisRequirements(axis, sr); - return result; + updateChildRequirements(axis); + + SizeRequirements res = sr; + if (res == null) + res = new SizeRequirements(); + + float minLeft = 0; + float minRight = 0; + float prefLeft = 0; + float prefRight = 0; + float maxLeft = 0; + float maxRight = 0; + for (int i = 0; i < childReqs[axis].length; i++) + { + float myMinLeft = childReqs[axis][i].minimum * childReqs[axis][i].alignment; + float myMinRight = childReqs[axis][i].minimum - myMinLeft; + minLeft = Math.max(myMinLeft, minLeft); + minRight = Math.max(myMinRight, minRight); + float myPrefLeft = childReqs[axis][i].preferred * childReqs[axis][i].alignment; + float myPrefRight = childReqs[axis][i].preferred - myPrefLeft; + prefLeft = Math.max(myPrefLeft, prefLeft); + prefRight = Math.max(myPrefRight, prefRight); + float myMaxLeft = childReqs[axis][i].maximum * childReqs[axis][i].alignment; + float myMaxRight = childReqs[axis][i].maximum - myMaxLeft; + maxLeft = Math.max(myMaxLeft, maxLeft); + maxRight = Math.max(myMaxRight, maxRight); + } + int minSize = (int) (minLeft + minRight); + int prefSize = (int) (prefLeft + prefRight); + int maxSize = (int) (maxLeft + maxRight); + float align = prefLeft / (prefRight + prefLeft); + if (Float.isNaN(align)) + align = 0; + + res.alignment = align; + res.maximum = maxSize; + res.preferred = prefSize; + res.minimum = minSize; + return res; } /** @@ -370,10 +412,13 @@ public class BoxView 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); + updateChildRequirements(axis); + updateRequirements(axis); + + // Calculate the spans and offsets using the SizeRequirements uility + // methods. + SizeRequirements.calculateAlignedPositions(span, requirements[axis], + childReqs[axis], offsets, spans); } /** @@ -390,8 +435,34 @@ public class BoxView protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements sr) { - SizeRequirements[] childReqs = getChildRequirements(axis); - return SizeRequirements.getTiledSizeRequirements(childReqs); + updateChildRequirements(axis); + + SizeRequirements result = sr; + if (result == null) + result = new SizeRequirements(); + + long minimum = 0; + long preferred = 0; + long maximum = 0; + for (int i = 0; i < children.length; i++) + { + minimum += childReqs[axis][i].minimum; + preferred += childReqs[axis][i].preferred; + maximum += childReqs[axis][i].maximum; + } + // Overflow check. + if (minimum > Integer.MAX_VALUE) + minimum = Integer.MAX_VALUE; + if (preferred > Integer.MAX_VALUE) + preferred = Integer.MAX_VALUE; + if (maximum > Integer.MAX_VALUE) + maximum = Integer.MAX_VALUE; + + result.minimum = (int) minimum; + result.preferred = (int) preferred; + result.maximum = (int) maximum; + result.alignment = 0.5F; + return result; } /** @@ -407,11 +478,49 @@ public class BoxView * the specified axis */ protected SizeRequirements calculateMinorAxisRequirements(int axis, - SizeRequirements sr) + SizeRequirements sr) { - SizeRequirements[] childReqs = getChildRequirements(axis); - return SizeRequirements.getAlignedSizeRequirements(childReqs); + updateChildRequirements(axis); + + SizeRequirements res = sr; + if (res == null) + res = new SizeRequirements(); + + float minLeft = 0; + float minRight = 0; + float prefLeft = 0; + float prefRight = 0; + float maxLeft = 0; + float maxRight = 0; + for (int i = 0; i < childReqs[axis].length; i++) + { + float myMinLeft = childReqs[axis][i].minimum * childReqs[axis][i].alignment; + float myMinRight = childReqs[axis][i].minimum - myMinLeft; + minLeft = Math.max(myMinLeft, minLeft); + minRight = Math.max(myMinRight, minRight); + float myPrefLeft = childReqs[axis][i].preferred * childReqs[axis][i].alignment; + float myPrefRight = childReqs[axis][i].preferred - myPrefLeft; + prefLeft = Math.max(myPrefLeft, prefLeft); + prefRight = Math.max(myPrefRight, prefRight); + float myMaxLeft = childReqs[axis][i].maximum * childReqs[axis][i].alignment; + float myMaxRight = childReqs[axis][i].maximum - myMaxLeft; + maxLeft = Math.max(myMaxLeft, maxLeft); + maxRight = Math.max(myMaxRight, maxRight); + } + int minSize = (int) (minLeft + minRight); + int prefSize = (int) (prefLeft + prefRight); + int maxSize = (int) (maxLeft + maxRight); + float align = prefLeft / (prefRight + prefLeft); + if (Float.isNaN(align)) + align = 0; + + res.alignment = align; + res.maximum = maxSize; + res.preferred = prefSize; + res.minimum = minSize; + return res; } + /** * Returns <code>true</code> if the specified point lies before the @@ -511,10 +620,10 @@ public class BoxView if (! isAllocationValid()) layout(a.width, a.height); - a.x += offsetsX[index]; - a.y += offsetsY[index]; - a.width = spansX[index]; - a.height = spansY[index]; + a.x += offsets[X_AXIS][index]; + a.y += offsets[Y_AXIS][index]; + a.width = spans[X_AXIS][index]; + a.height = spans[Y_AXIS][index]; } /** @@ -528,8 +637,49 @@ public class BoxView */ protected void layout(int width, int height) { - baselineLayout(width, X_AXIS, offsetsX, spansX); - baselineLayout(height, Y_AXIS, offsetsY, spansY); + int[] newSpan = new int[]{ width, height }; + int count = getViewCount(); + + // Update minor axis as appropriate. We need to first update the minor + // axis layout because that might affect the children's preferences along + // the major axis. + int minorAxis = myAxis == X_AXIS ? Y_AXIS : X_AXIS; + if ((! isLayoutValid(minorAxis)) || newSpan[minorAxis] != span[minorAxis]) + { + layoutValid[minorAxis] = false; + span[minorAxis] = newSpan[minorAxis]; + layoutMinorAxis(span[minorAxis], minorAxis, offsets[minorAxis], + spans[minorAxis]); + + // Update the child view's sizes. + for (int i = 0; i < count; ++i) + { + getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); + } + layoutValid[minorAxis] = true; + } + + + // Update major axis as appropriate. + if ((! isLayoutValid(myAxis)) || newSpan[myAxis] != span[myAxis]) + { + layoutValid[myAxis] = false; + span[myAxis] = newSpan[myAxis]; + layoutMajorAxis(span[myAxis], myAxis, offsets[myAxis], + spans[myAxis]); + + // Update the child view's sizes. + for (int i = 0; i < count; ++i) + { + getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); + } + layoutValid[myAxis] = true; + } + + if (layoutValid[myAxis] == false) + System.err.println("WARNING: Major axis layout must be valid after layout"); + if (layoutValid[minorAxis] == false) + System.err.println("Minor axis layout must be valid after layout"); } /** @@ -544,12 +694,15 @@ public class BoxView protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - SizeRequirements[] childReqs = getChildRequirements(axis); + updateChildRequirements(axis); + updateRequirements(axis); + // Calculate the spans and offsets using the SizeRequirements uility // methods. - SizeRequirements.calculateTiledPositions(targetSpan, null, childReqs, + SizeRequirements.calculateTiledPositions(targetSpan, requirements[axis], + childReqs[axis], offsets, spans); - validateLayout(axis); + } /** @@ -564,18 +717,14 @@ public class BoxView protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - SizeRequirements[] childReqs = getChildRequirements(axis); + updateChildRequirements(axis); + updateRequirements(axis); + // Calculate the spans and offsets using the SizeRequirements uility // methods. - // 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); + SizeRequirements.calculateAlignedPositions(targetSpan, requirements[axis], + childReqs[axis], offsets, + spans); } /** @@ -597,7 +746,7 @@ public class BoxView */ public int getWidth() { - return width; + return span[X_AXIS]; } /** @@ -607,7 +756,7 @@ public class BoxView */ public int getHeight() { - return height; + return span[Y_AXIS]; } /** @@ -619,54 +768,7 @@ public class BoxView */ public void setSize(float width, float height) { - if (this.width != (int) width) - 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); - if (!isAllocationValid()) - layout(inside.width, inside.height); - } - - /** - * Sets the layout to valid for a specific axis. - * - * @param axis the axis for which to validate the layout - */ - void validateLayout(int axis) - { - if (axis == X_AXIS) - xLayoutValid = true; - 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; + layout((int) width, (int) height); } /** @@ -682,10 +784,9 @@ public class BoxView */ protected int getSpan(int axis, int childIndex) { - if (axis == X_AXIS) - return spansX[childIndex]; - else - return spansY[childIndex]; + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis argument"); + return spans[axis][childIndex]; } /** @@ -701,10 +802,9 @@ public class BoxView */ protected int getOffset(int axis, int childIndex) { - if (axis == X_AXIS) - return offsetsX[childIndex]; - else - return offsetsY[childIndex]; + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis argument"); + return offsets[axis][childIndex]; } /** @@ -719,10 +819,15 @@ public class BoxView */ public float getAlignment(int axis) { + float align; if (axis == myAxis) - return 0.5F; + align = 0.5F; else - return baselineRequirements(axis, null).alignment; + { + updateRequirements(axis); + align = requirements[axis].alignment; + } + return align; } /** @@ -732,12 +837,12 @@ public class BoxView * @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) + public void preferenceChanged(View child, boolean width, boolean height) { if (width) - xLayoutValid = false; + layoutValid[X_AXIS] = false; if (height) - yLayoutValid = false; + layoutValid[Y_AXIS] = false; super.preferenceChanged(child, width, height); } @@ -751,11 +856,118 @@ public class BoxView throws BadLocationException { // Make sure everything is allocated properly and then call super - if (!isAllocationValid()) + if (! isAllocationValid()) { Rectangle bounds = a.getBounds(); - setSize(bounds.width, bounds.height); + layout(bounds.width, bounds.height); } return super.modelToView(pos, a, bias); } + + /** + * Returns the resize weight of this view. A value of <code>0</code> or less + * means this view is not resizeable. Positive values make the view + * resizeable. This implementation returns <code>0</code> for the major + * axis and <code>1</code> for the minor axis of this box view. + * + * @param axis the axis + * + * @return the resizability of this view along the specified axis + * + * @throws IllegalArgumentException if <code>axis</code> is invalid + */ + public int getResizeWeight(int axis) + { + if (axis != X_AXIS && axis != Y_AXIS) + throw new IllegalArgumentException("Illegal axis argument"); + int weight = 1; + if (axis == myAxis) + weight = 0; + return weight; + } + + /** + * Returns the child allocation for the child view with the specified + * <code>index</code>. If the layout is invalid, this returns + * <code>null</code>. + * + * @param index the child view index + * @param a the allocation to this view + * + * @return the child allocation for the child view with the specified + * <code>index</code> or <code>null</code> if the layout is invalid + * or <code>a</code> is null + */ + public Shape getChildAllocation(int index, Shape a) + { + Shape ret = null; + if (isAllocationValid() && a != null) + ret = super.getChildAllocation(index, a); + return ret; + } + + protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e, + Shape a, ViewFactory vf) + { + // FIXME: What to do here? + super.forwardUpdate(ec, e, a, vf); + } + + public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) + { + // FIXME: What to do here? + return super.viewToModel(x, y, a, bias); + } + + protected boolean flipEastAndWestEnds(int position, Position.Bias bias) + { + // FIXME: What to do here? + return super.flipEastAndWestAtEnds(position, bias); + } + + /** + * Updates the child requirements along the specified axis. The requirements + * are only updated if the layout for the specified axis is marked as + * invalid. + * + * @param axis the axis to be updated + */ + private void updateChildRequirements(int axis) + { + if (! isLayoutValid(axis)) + { + int numChildren = getViewCount(); + if (childReqs[axis] == null || childReqs[axis].length != numChildren) + childReqs[axis] = new SizeRequirements[numChildren]; + for (int i = 0; i < numChildren; ++i) + { + View child = getView(i); + childReqs[axis][i] = + new SizeRequirements((int) child.getMinimumSpan(axis), + (int) child.getPreferredSpan(axis), + (int) child.getMaximumSpan(axis), + child.getAlignment(axis)); + } + } + } + + /** + * Updates the view's cached requirements along the specified axis if + * necessary. The requirements are only updated if the layout for the + * specified axis is marked as invalid. + * + * @param axis the axis + */ + private void updateRequirements(int axis) + { + if (! layoutValid[axis]) + { + if (axis == myAxis) + requirements[axis] = calculateMajorAxisRequirements(axis, + requirements[axis]); + else + requirements[axis] = calculateMinorAxisRequirements(axis, + requirements[axis]); + } + } } diff --git a/libjava/classpath/javax/swing/text/ComponentView.java b/libjava/classpath/javax/swing/text/ComponentView.java index 2846f8b..a7d237a 100644 --- a/libjava/classpath/javax/swing/text/ComponentView.java +++ b/libjava/classpath/javax/swing/text/ComponentView.java @@ -228,8 +228,9 @@ public class ComponentView extends View * * @param p the parent view to set */ - void setParentImpl(View p) + private void setParentImpl(View p) { + super.setParent(p); if (p != null) { Component c = getComponent(); diff --git a/libjava/classpath/javax/swing/text/CompositeView.java b/libjava/classpath/javax/swing/text/CompositeView.java index cd66452..a10aca7 100644 --- a/libjava/classpath/javax/swing/text/CompositeView.java +++ b/libjava/classpath/javax/swing/text/CompositeView.java @@ -218,20 +218,43 @@ public abstract class CompositeView throws BadLocationException { int childIndex = getViewIndex(pos, bias); + Shape ret = null; if (childIndex != -1) { View child = getView(childIndex); - 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; + Shape childAlloc = getChildAllocation(childIndex, a); + if (childAlloc == null) + ret = createDefaultLocation(a, bias); + Shape result = child.modelToView(pos, childAlloc, bias); + if (result != null) + ret = result; + else + ret = createDefaultLocation(a, bias); } else - throw new BadLocationException("No child view for the specified location", - pos); + ret = createDefaultLocation(a, bias); + return ret; + } + + /** + * A helper method for {@link #modelToView(int, Position.Bias, int, + * Position.Bias, Shape)}. This creates a default location when there is + * no child view that can take responsibility for mapping the position to + * view coordinates. Depending on the specified bias this will be the + * left or right edge of this view's allocation. + * + * @param a the allocation for this view + * @param bias the bias + * + * @return a default location + */ + private Shape createDefaultLocation(Shape a, Position.Bias bias) + { + Rectangle alloc = a.getBounds(); + Rectangle location = new Rectangle(alloc.x, alloc.y, 1, alloc.height); + if (bias == Position.Bias.Forward) + location.x = alloc.x + alloc.width; + return location; } /** @@ -350,7 +373,8 @@ public abstract class CompositeView */ public int getViewIndex(int pos, Position.Bias b) { - // FIXME: Handle bias somehow. + if (b == Position.Bias.Backward && pos != 0) + pos -= 1; return getViewIndexAtPosition(pos); } diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java index 776ef69..f2a68c0 100644 --- a/libjava/classpath/javax/swing/text/DefaultCaret.java +++ b/libjava/classpath/javax/swing/text/DefaultCaret.java @@ -148,14 +148,16 @@ public class DefaultCaret extends Rectangle */ public void removeUpdate(DocumentEvent event) { - if (policy == ALWAYS_UPDATE || - (SwingUtilities.isEventDispatchThread() && - policy == UPDATE_WHEN_ON_EDT)) + if (policy == ALWAYS_UPDATE + || (SwingUtilities.isEventDispatchThread() + && policy == UPDATE_WHEN_ON_EDT)) { int dot = getDot(); setDot(dot - event.getLength()); } - else if (policy == NEVER_UPDATE) + else if (policy == NEVER_UPDATE + || (! SwingUtilities.isEventDispatchThread() + && policy == UPDATE_WHEN_ON_EDT)) { int docLength = event.getDocument().getLength(); if (getDot() > docLength) @@ -364,7 +366,8 @@ public class DefaultCaret extends Rectangle * <ul> * <li>If we receive a double click, the caret position (dot) is set * to the position associated to the mouse click and the word at - * this location is selected.</li> + * this location is selected. If there is no word at the pointer + * the gap is selected instead.</li> * <li>If we receive a triple click, the caret position (dot) is set * to the position associated to the mouse click and the line at * this location is selected.</li> @@ -374,7 +377,50 @@ public class DefaultCaret extends Rectangle */ public void mouseClicked(MouseEvent event) { - // TODO: Implement double- and triple-click behaviour here. + int count = event.getClickCount(); + + if (count >= 2) + { + int newDot = getComponent().viewToModel(event.getPoint()); + JTextComponent t = getComponent(); + + try + { + if (count == 3) + t.select(Utilities.getRowStart(t, newDot), Utilities.getRowEnd(t, newDot)); + else + { + int nextWord = Utilities.getNextWord(t, newDot); + + // When the mouse points at the offset of the first character + // in a word Utilities().getPreviousWord will not return that + // word but we want to select that. We have to use + // Utilities.nextWord() to get it. + if (newDot == nextWord) + t.select(nextWord, Utilities.getNextWord(t, nextWord)); + else + { + int previousWord = Utilities.getPreviousWord(t, newDot); + int previousWordEnd = Utilities.getWordEnd(t, previousWord); + + // If the user clicked in the space between two words, + // then select the space. + if (newDot >= previousWordEnd && newDot <= nextWord) + t.select(previousWordEnd, nextWord); + // Otherwise select the word under the mouse pointer. + else + t.select(previousWord, previousWordEnd); + } + } + } + catch(BadLocationException ble) + { + // TODO: Swallowing ok here? + } + + dot = newDot; + } + } /** @@ -409,7 +455,10 @@ public class DefaultCaret extends Rectangle */ public void mousePressed(MouseEvent event) { - positionCaret(event); + if (event.isShiftDown()) + moveCaret(event); + else + positionCaret(event); } /** @@ -575,7 +624,39 @@ public class DefaultCaret extends Rectangle { return mark; } - + + private void clearHighlight() + { + Highlighter highlighter = textComponent.getHighlighter(); + + if (highlighter == null) + return; + + if (selectionVisible) + { + try + { + if (highlightEntry == null) + highlightEntry = highlighter.addHighlight(0, 0, getSelectionPainter()); + else + highlighter.changeHighlight(highlightEntry, 0, 0); + } + catch (BadLocationException e) + { + // This should never happen. + throw new InternalError(); + } + } + else + { + if (highlightEntry != null) + { + highlighter.removeHighlight(highlightEntry); + highlightEntry = null; + } + } + } + private void handleHighlight() { Highlighter highlighter = textComponent.getHighlighter(); @@ -586,7 +667,7 @@ public class DefaultCaret extends Rectangle int p0 = Math.min(dot, mark); int p1 = Math.max(dot, mark); - if (selectionVisible && p0 != p1) + if (selectionVisible) { try { @@ -659,7 +740,10 @@ public class DefaultCaret extends Rectangle if (comp == null) return; - int dot = getDot(); + // Make sure the dot has a sane position. + dot = Math.min(dot, textComponent.getDocument().getLength()); + dot = Math.max(dot, 0); + Rectangle rect = null; try @@ -668,10 +752,10 @@ public class DefaultCaret extends Rectangle } catch (BadLocationException e) { - AssertionError ae; - ae = new AssertionError("Unexpected bad caret location: " + dot); - ae.initCause(e); - throw ae; + AssertionError ae; + ae = new AssertionError("Unexpected bad caret location: " + dot); + ae.initCause(e); + throw ae; } if (rect == null) @@ -812,7 +896,11 @@ public class DefaultCaret extends Rectangle { if (dot >= 0) { - this.dot = dot; + Document doc = textComponent.getDocument(); + if (doc != null) + this.dot = Math.min(dot, doc.getLength()); + this.dot = Math.max(this.dot, 0); + handleHighlight(); adjustVisibility(this); appear(); @@ -836,8 +924,9 @@ public class DefaultCaret extends Rectangle if (doc != null) this.dot = Math.min(dot, doc.getLength()); this.dot = Math.max(this.dot, 0); - this.mark = dot; - handleHighlight(); + this.mark = this.dot; + + clearHighlight(); adjustVisibility(this); appear(); } diff --git a/libjava/classpath/javax/swing/text/DefaultEditorKit.java b/libjava/classpath/javax/swing/text/DefaultEditorKit.java index 88094b8..c005696 100644 --- a/libjava/classpath/javax/swing/text/DefaultEditorKit.java +++ b/libjava/classpath/javax/swing/text/DefaultEditorKit.java @@ -707,16 +707,14 @@ public class DefaultEditorKit extends EditorKit JTextComponent t = getTextComponent(event); try { - // TODO: There is a more efficent solution, but - // viewToModel doesn't work properly. - Point p = t.modelToView(t.getCaret().getDot()).getLocation(); - int cur = t.getCaretPosition(); - int y = p.y; - while (y == p.y && cur > 0) - y = t.modelToView(--cur).getLocation().y; - if (cur != 0) - cur++; - t.setCaretPosition(cur); + int offs = Utilities.getRowStart(t, t.getCaretPosition()); + + if (offs > -1) + { + Caret c = t.getCaret(); + c.setDot(offs); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } } catch (BadLocationException ble) { @@ -729,17 +727,16 @@ public class DefaultEditorKit extends EditorKit public void actionPerformed(ActionEvent event) { JTextComponent t = getTextComponent(event); - try + try { - Point p = t.modelToView(t.getCaret().getDot()).getLocation(); - int cur = t.getCaretPosition(); - int y = p.y; - int length = t.getDocument().getLength(); - while (y == p.y && cur < length) - y = t.modelToView(++cur).getLocation().y; - if (cur != length) - cur--; - t.setCaretPosition(cur); + int offs = Utilities.getRowEnd(t, t.getCaretPosition()); + + if (offs > -1) + { + Caret c = t.getCaret(); + c.setDot(offs); + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } } catch (BadLocationException ble) { @@ -756,11 +753,17 @@ public class DefaultEditorKit extends EditorKit { try { - int pos = t.getCaret().getDot(); - if (pos < t.getDocument().getEndPosition().getOffset()) - { - t.getDocument().remove(t.getCaret().getDot(), 1); - } + int pos = t.getSelectionStart(); + int len = t.getSelectionEnd() - pos; + + if (len > 0) + t.getDocument().remove(pos, len); + else if (pos < t.getDocument().getLength()) + t.getDocument().remove(pos, 1); + + Caret c = t.getCaret(); + c.setDot(pos); + c.setMagicCaretPosition(t.modelToView(pos).getLocation()); } catch (BadLocationException e) { @@ -778,11 +781,18 @@ public class DefaultEditorKit extends EditorKit { try { - int pos = t.getCaret().getDot(); - if (pos > t.getDocument().getStartPosition().getOffset()) + int pos = t.getSelectionStart(); + int len = t.getSelectionEnd() - pos; + + if (len > 0) + t.getDocument().remove(pos, len); + else if (pos > 0) { - t.getDocument().remove(pos - 1, 1); - t.getCaret().setDot(pos - 1); + pos--; + t.getDocument().remove(pos, 1); + Caret c = t.getCaret(); + c.setDot(pos); + c.setMagicCaretPosition(t.modelToView(pos).getLocation()); } } catch (BadLocationException e) @@ -799,8 +809,21 @@ public class DefaultEditorKit extends EditorKit JTextComponent t = getTextComponent(event); if (t != null) { - t.getCaret().setDot(Math.max(t.getCaret().getDot() - 1, - t.getDocument().getStartPosition().getOffset())); + int offs = t.getCaretPosition() - 1; + if (offs >= 0) + { + Caret c = t.getCaret(); + c.setDot(offs); + + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch (BadLocationException ble) + { + // Should not happen. + } + } } } }, @@ -811,8 +834,74 @@ public class DefaultEditorKit extends EditorKit JTextComponent t = getTextComponent(event); if (t != null) { - t.getCaret().setDot(Math.min(t.getCaret().getDot() + 1, - t.getDocument().getEndPosition().getOffset())); + int offs = t.getCaretPosition() + 1; + if (offs <= t.getDocument().getLength()) + { + Caret c = t.getCaret(); + c.setDot(offs); + + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch (BadLocationException ble) + { + // Should not happen. + } + } + } + + } + }, + new TextAction(upAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + try + { + if (t != null) + { + Caret c = t.getCaret(); + // The magic caret position may be null when the caret + // has not moved yet. + Point mcp = c.getMagicCaretPosition(); + int x = (mcp != null) ? mcp.x : 0; + int pos = Utilities.getPositionAbove(t, t.getCaretPosition(), x); + + if (pos > -1) + t.setCaretPosition(pos); + } + } + catch(BadLocationException ble) + { + // FIXME: Swallowing allowed? + } + } + }, + new TextAction(downAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + try + { + if (t != null) + { + Caret c = t.getCaret(); + // The magic caret position may be null when the caret + // has not moved yet. + Point mcp = c.getMagicCaretPosition(); + int x = (mcp != null) ? mcp.x : 0; + int pos = Utilities.getPositionBelow(t, t.getCaretPosition(), x); + + if (pos > -1) + t.setCaretPosition(pos); + } + } + catch(BadLocationException ble) + { + // FIXME: Swallowing allowed? } } }, @@ -823,8 +912,21 @@ public class DefaultEditorKit extends EditorKit JTextComponent t = getTextComponent(event); if (t != null) { - t.getCaret().moveDot(Math.max(t.getCaret().getDot() - 1, - t.getDocument().getStartPosition().getOffset())); + int offs = t.getCaretPosition() - 1; + + if(offs >= 0) + { + Caret c = t.getCaret(); + c.moveDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } } } }, @@ -835,11 +937,167 @@ public class DefaultEditorKit extends EditorKit JTextComponent t = getTextComponent(event); if (t != null) { - t.getCaret().moveDot(Math.min(t.getCaret().getDot() + 1, - t.getDocument().getEndPosition().getOffset())); + int offs = t.getCaretPosition() + 1; + + if(offs <= t.getDocument().getLength()) + { + Caret c = t.getCaret(); + c.moveDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } + } + } + }, + new TextAction(selectionUpAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + try + { + if (t != null) + { + Caret c = t.getCaret(); + // The magic caret position may be null when the caret + // has not moved yet. + Point mcp = c.getMagicCaretPosition(); + int x = (mcp != null) ? mcp.x : 0; + int pos = Utilities.getPositionAbove(t, t.getCaretPosition(), x); + + if (pos > -1) + t.moveCaretPosition(pos); + } + } + catch(BadLocationException ble) + { + // FIXME: Swallowing allowed? + } + } + }, + new TextAction(selectionDownAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + try + { + if (t != null) + { + Caret c = t.getCaret(); + // The magic caret position may be null when the caret + // has not moved yet. + Point mcp = c.getMagicCaretPosition(); + int x = (mcp != null) ? mcp.x : 0; + int pos = Utilities.getPositionBelow(t, t.getCaretPosition(), x); + + if (pos > -1) + t.moveCaretPosition(pos); + } + } + catch(BadLocationException ble) + { + // FIXME: Swallowing allowed? } } }, + new TextAction(selectionBeginLineAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + + try + { + // TODO: There is a more efficent solution, but + // viewToModel doesn't work properly. + Point p = t.modelToView(t.getCaret().getDot()).getLocation(); + + int cur = t.getCaretPosition(); + int y = p.y; + + while (y == p.y && cur > 0) + y = t.modelToView(--cur).getLocation().y; + if (cur != 0) + cur++; + + Caret c = t.getCaret(); + c.moveDot(cur); + c.setMagicCaretPosition(t.modelToView(cur).getLocation()); + } + catch (BadLocationException ble) + { + // Do nothing here. + } + } + }, + new TextAction(selectionEndLineAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + try + { + Point p = t.modelToView(t.getCaret().getDot()).getLocation(); + int cur = t.getCaretPosition(); + int y = p.y; + int length = t.getDocument().getLength(); + while (y == p.y && cur < length) + y = t.modelToView(++cur).getLocation().y; + if (cur != length) + cur--; + + Caret c = t.getCaret(); + c.moveDot(cur); + c.setMagicCaretPosition(t.modelToView(cur).getLocation()); + } + catch (BadLocationException ble) + { + // Nothing to do here + } + } + }, + new TextAction(selectionEndAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + int offs = t.getDocument().getLength(); + Caret c = t.getCaret(); + c.moveDot(offs); + try + { + c.setMagicCaretPosition(t.modelToView(offs).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } + }, + new TextAction(selectionBeginAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + Caret c = t.getCaret(); + c.moveDot(0); + try + { + c.setMagicCaretPosition(t.modelToView(0).getLocation()); + } + catch(BadLocationException ble) + { + // Can't happen. + } + } + } }; /** diff --git a/libjava/classpath/javax/swing/text/DefaultFormatter.java b/libjava/classpath/javax/swing/text/DefaultFormatter.java index 493699d..e42b169 100644 --- a/libjava/classpath/javax/swing/text/DefaultFormatter.java +++ b/libjava/classpath/javax/swing/text/DefaultFormatter.java @@ -219,7 +219,6 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter commitsOnValidEdit = true; overwriteMode = true; allowsInvalid = true; - valueClass = Object.class; } /** @@ -368,7 +367,11 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter Object value = string; Class valueClass = getValueClass(); if (valueClass == null) - valueClass = getFormattedTextField().getValue().getClass(); + { + JFormattedTextField jft = getFormattedTextField(); + if (jft != null) + valueClass = jft.getValue().getClass(); + } if (valueClass != null) try { diff --git a/libjava/classpath/javax/swing/text/DefaultHighlighter.java b/libjava/classpath/javax/swing/text/DefaultHighlighter.java index 40ea4f8..33b5fca 100644 --- a/libjava/classpath/javax/swing/text/DefaultHighlighter.java +++ b/libjava/classpath/javax/swing/text/DefaultHighlighter.java @@ -1,5 +1,5 @@ /* DefaultHighlighter.java -- - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -40,9 +40,12 @@ package javax.swing.text; import java.awt.Color; import java.awt.Graphics; +import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; -import java.util.Vector; +import java.util.ArrayList; + +import javax.swing.plaf.TextUI; public class DefaultHighlighter extends LayeredHighlighter { @@ -84,7 +87,7 @@ public class DefaultHighlighter extends LayeredHighlighter // This should never occur. return; } - + if (r0 == null || r1 == null) return; @@ -100,7 +103,7 @@ public class DefaultHighlighter extends LayeredHighlighter paintHighlight(g, r0); return; } - + // First line, from p0 to end-of-line. r0.width = rect.x + rect.width - r0.x; paintHighlight(g, r0); @@ -109,15 +112,19 @@ public class DefaultHighlighter extends LayeredHighlighter // have the same height -- not a good assumption with JEditorPane/JTextPane). r0.y += r0.height; r0.x = rect.x; - + r0.width = rect.width; + while (r0.y < r1.y) { paintHighlight(g, r0); r0.y += r0.height; } - // Last line, from beginnin-of-line to p1. - paintHighlight(g, r1); + // Last line, from beginning-of-line to p1. + // The "-1" is neccessary else we would paint one pixel column more + // than in the case where the selection is only on one line. + r0.width = r1.x + r1.width - 1; + paintHighlight(g, r0); } public Shape paintLayer(Graphics g, int p0, int p1, Shape bounds, @@ -127,7 +134,7 @@ public class DefaultHighlighter extends LayeredHighlighter } } - private class HighlightEntry + private class HighlightEntry implements Highlighter.Highlight { int p0; int p1; @@ -140,12 +147,12 @@ public class DefaultHighlighter extends LayeredHighlighter this.painter = painter; } - public int getStartPosition() + public int getStartOffset() { return p0; } - public int getEndPosition() + public int getEndOffset() { return p1; } @@ -163,7 +170,7 @@ public class DefaultHighlighter extends LayeredHighlighter new DefaultHighlightPainter(null); private JTextComponent textComponent; - private Vector highlights = new Vector(); + private ArrayList highlights = new ArrayList(); private boolean drawsLayeredHighlights = true; public DefaultHighlighter() @@ -208,12 +215,20 @@ public class DefaultHighlighter extends LayeredHighlighter checkPositions(p0, p1); HighlightEntry entry = new HighlightEntry(p0, p1, painter); highlights.add(entry); + + textComponent.getUI().damageRange(textComponent, p0, p1); + return entry; } public void removeHighlight(Object tag) { highlights.remove(tag); + + HighlightEntry entry = (HighlightEntry) tag; + textComponent.getUI().damageRange(textComponent, + entry.p0, + entry.p1); } public void removeAllHighlights() @@ -223,16 +238,93 @@ public class DefaultHighlighter extends LayeredHighlighter public Highlighter.Highlight[] getHighlights() { - return null; + return (Highlighter.Highlight[]) + highlights.toArray(new Highlighter.Highlight[highlights.size()]); } - public void changeHighlight(Object tag, int p0, int p1) + public void changeHighlight(Object tag, int n0, int n1) throws BadLocationException { - checkPositions(p0, p1); + int o0, o1; + + checkPositions(n0, n1); HighlightEntry entry = (HighlightEntry) tag; - entry.p0 = p0; - entry.p1 = p1; + o0 = entry.p0; + o1 = entry.p1; + + // Prevent useless write & repaint operations. + if (o0 == n0 && o1 == n1) + return; + + entry.p0 = n0; + entry.p1 = n1; + + TextUI ui = textComponent.getUI(); + + // Special situation where the old area has to be cleared simply. + if (n0 == n1) + ui.damageRange(textComponent, o0, o1); + // Calculates the areas where a change is really neccessary + else if ((o1 > n0 && o1 <= n1) + || (n1 > o0 && n1 <= o1)) + { + // [fds, fde) - the first damage region + // [sds, sde] - the second damage region + int fds, sds; + int fde, sde; + + // Calculate first damaged region. + if(o0 < n0) + { + // Damaged region will be cleared as + // the old highlight region starts first. + fds = o0; + fde = n0; + } + else + { + // Damaged region will be painted as + // the new highlight region starts first. + fds = n0; + fde = o0; + } + + if (o1 < n1) + { + // Final region will be painted as the + // old highlight region finishes first + sds = o1; + sde = n1; + } + else + { + // Final region will be cleared as the + // new highlight region finishes first. + sds = n1; + sde = o1; + } + + // If there is no undamaged region in between + // call damageRange only once. + if (fde == sds) + ui.damageRange(textComponent, fds, sde); + else + { + if (fds != fde) + ui.damageRange(textComponent, fds, fde); + + if (sds != sde) + ui.damageRange(textComponent, sds, sde); + } + } + else + { + // The two regions do not overlap. So mark + // both areas as damaged. + ui.damageRange(textComponent, o0, o1); + ui.damageRange(textComponent, n0, n1); + } + } public void paintLayeredHighlights(Graphics g, int p0, int p1, @@ -244,13 +336,21 @@ public class DefaultHighlighter extends LayeredHighlighter public void paint(Graphics g) { + int size = highlights.size(); + // Check if there are any highlights. - if (highlights.size() == 0) + if (size == 0) return; + + // Prepares the rectangle of the inner drawing area. + Insets insets = textComponent.getInsets(); + Shape bounds = + new Rectangle(insets.left, + insets.top, + textComponent.getWidth() - insets.left - insets.right, + textComponent.getHeight() - insets.top - insets.bottom); - Shape bounds = textComponent.getBounds(); - - for (int index = 0; index < highlights.size(); ++index) + for (int index = 0; index < size; ++index) { HighlightEntry entry = (HighlightEntry) highlights.get(index); entry.painter.paint(g, entry.p0, entry.p1, bounds, textComponent); diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java index 46b8225..625ba4c 100644 --- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java +++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java @@ -53,27 +53,25 @@ import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.UndoableEdit; /** - * The default implementation of {@link StyledDocument}. - * - * The document is modeled as an {@link Element} tree, which has - * a {@link SectionElement} as single root, which has one or more - * {@link AbstractDocument.BranchElement}s as paragraph nodes - * and each paragraph node having one or more + * The default implementation of {@link StyledDocument}. The document is + * modeled as an {@link Element} tree, which has a {@link SectionElement} as + * single root, which has one or more {@link AbstractDocument.BranchElement}s + * as paragraph nodes and each paragraph node having one or more * {@link AbstractDocument.LeafElement}s as content nodes. - * + * * @author Michael Koch (konqueror@gmx.de) * @author Roman Kennke (roman@kennke.org) */ -public class DefaultStyledDocument extends AbstractDocument - implements StyledDocument +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 + public static class AttributeUndoableEdit extends AbstractUndoableEdit { /** * A copy of the old attributes. @@ -98,11 +96,13 @@ public class DefaultStyledDocument extends AbstractDocument /** * 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 + * + * @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) @@ -149,21 +149,19 @@ public class DefaultStyledDocument extends AbstractDocument } /** - * 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. + * 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}. + * 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}. + * This indicates an end tag. This is a possible value for {@link #getType}. */ public static final short EndTagType = 2; @@ -175,22 +173,19 @@ public class DefaultStyledDocument extends AbstractDocument /** * This indicates that the data associated with this spec should be joined - * with what precedes it. This is a possible value for - * {@link #getDirection}. + * 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}. + * 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}. + * 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; @@ -234,9 +229,11 @@ public class DefaultStyledDocument extends AbstractDocument /** * 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 + * + * @param a + * the attributes for the element to be created + * @param type + * the type of the tag */ public ElementSpec(AttributeSet a, short type) { @@ -247,27 +244,34 @@ public class DefaultStyledDocument extends AbstractDocument * 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 + * + * @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 + * + * @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) + public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len) { attributes = a; this.type = type; @@ -279,8 +283,9 @@ public class DefaultStyledDocument extends AbstractDocument /** * Sets the type of the element. - * - * @param type the type of the element to be set + * + * @param type + * the type of the element to be set */ public void setType(short type) { @@ -289,7 +294,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the type of the element. - * + * * @return the type of the element */ public short getType() @@ -299,8 +304,9 @@ public class DefaultStyledDocument extends AbstractDocument /** * Sets the direction of the element. - * - * @param dir the direction of the element to be set + * + * @param dir + * the direction of the element to be set */ public void setDirection(short dir) { @@ -309,7 +315,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the direction of the element. - * + * * @return the direction of the element */ public short getDirection() @@ -319,7 +325,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the attributes of the element. - * + * * @return the attributes of the element */ public AttributeSet getAttributes() @@ -329,7 +335,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the actual content of the element. - * + * * @return the actual content of the element */ public char[] getArray() @@ -339,7 +345,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the offset of the content. - * + * * @return the offset of the content */ public int getOffset() @@ -349,7 +355,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the length of the content. - * + * * @return the length of the content */ public int getLength() @@ -361,7 +367,7 @@ public class DefaultStyledDocument extends AbstractDocument * 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() @@ -413,7 +419,8 @@ public class DefaultStyledDocument extends AbstractDocument /** * Performs all <em>structural</code> changes to the <code>Element</code> - * hierarchy. + * hierarchy. This class was implemented with much help from the document: + * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html. */ public class ElementBuffer implements Serializable { @@ -426,25 +433,20 @@ public class DefaultStyledDocument extends AbstractDocument /** Holds the offset for structural changes. */ private int offset; + /** Holds the end offset for structural changes. */ + private int endOffset; + /** Holds the length of structural changes. */ private int length; - - /** Holds the end offset for structural changes. **/ - private int endOffset; - /** - * The number of inserted end tags. This is a counter which always gets - * incremented when an end tag is inserted. This is evaluated before - * content insertion to go up the element stack. - */ - private int numEndTags; + /** Holds the position of the change. */ + private int pos; - /** - * The number of inserted start tags. This is a counter which always gets - * incremented when an end tag is inserted. This is evaluated before - * content insertion to go up the element stack. - */ - private int numStartTags; + /** Holds the element that was last fractured. */ + private Element lastFractured; + + /** True if a fracture was not created during a insertFracture call. */ + private boolean fracNotCreated; /** * The current position in the element tree. This is used for bulk inserts @@ -453,14 +455,6 @@ public class DefaultStyledDocument extends AbstractDocument private Stack elementStack; /** - * 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; @@ -468,8 +462,9 @@ public class DefaultStyledDocument extends AbstractDocument /** * Creates a new <code>ElementBuffer</code> for the specified * <code>root</code> element. - * - * @param root the root element for this <code>ElementBuffer</code> + * + * @param root + * the root element for this <code>ElementBuffer</code> */ public ElementBuffer(Element root) { @@ -479,7 +474,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the root element of this <code>ElementBuffer</code>. - * + * * @return the root element of this <code>ElementBuffer</code> */ public Element getRootElement() @@ -488,21 +483,23 @@ public class DefaultStyledDocument extends AbstractDocument } /** - * Updates the element structure of the document in response to removal of - * content. It removes the affected {@link Element}s from the document - * structure. - * - * This method sets some internal parameters and delegates the work - * to {@link #removeUpdate}. - * - * @param offs the offset from which content is remove - * @param len the length of the removed content - * @param ev the document event that records the changes + * Removes the content. This method sets some internal parameters and + * delegates the work to {@link #removeUpdate}. + * + * @param offs + * the offset from which content is remove + * @param len + * the length of the removed content + * @param ev + * the document event that records the changes */ public void remove(int offs, int len, DefaultDocumentEvent ev) { + if (len == 0) + return; offset = offs; length = len; + pos = offset; documentEvent = ev; removeUpdate(); } @@ -519,9 +516,9 @@ public class DefaultStyledDocument extends AbstractDocument Element[] empty = new Element[0]; int removeStart = -1; int removeEnd = -1; - for (int i = startParagraph; i < endParagraph; i++) + for (int i = startParagraph; i < endParagraph; i++) { - Element paragraph = root.getElement(i); + BranchElement paragraph = (BranchElement) root.getElement(i); int contentStart = paragraph.getElementIndex(offset); int contentEnd = paragraph.getElementIndex(offset + length); if (contentStart == paragraph.getStartOffset() @@ -546,10 +543,8 @@ public class DefaultStyledDocument extends AbstractDocument Element[] removed = new Element[removeLen]; for (int j = contentStart; j < contentEnd; j++) removed[j] = paragraph.getElement(j); - ((BranchElement) paragraph).replace(contentStart, removeLen, - empty); - documentEvent.addEdit(new ElementEdit(paragraph, contentStart, - removed, empty)); + Edit edit = getEditForParagraphAndIndex(paragraph, contentStart); + edit.addRemovedElements(removed); } } // Now we remove paragraphs from the root that have been tagged for @@ -560,265 +555,234 @@ public class DefaultStyledDocument extends AbstractDocument Element[] removed = new Element[removeLen]; for (int i = removeStart; i < removeEnd; i++) removed[i] = root.getElement(i); - ((BranchElement) root).replace(removeStart, removeLen, empty); - documentEvent.addEdit(new ElementEdit(root, removeStart, removed, - empty)); + Edit edit = getEditForParagraphAndIndex((BranchElement) root, + removeStart); + edit.addRemovedElements(removed); } } /** - * Modifies the element structure so that the specified interval starts - * and ends at an element boundary. Content and paragraph elements - * are split and created as necessary. - * - * This also updates the <code>DefaultDocumentEvent</code> to reflect the - * structural changes. - * - * The bulk work is delegated to {@link #changeUpdate()}. - * - * @param offset the start index of the interval to be changed - * @param length the length of the interval to be changed - * @param ev the <code>DefaultDocumentEvent</code> describing the change - */ - public void change(int offset, int length, DefaultDocumentEvent ev) - { - this.offset = offset; - this.length = length; - documentEvent = ev; - changeUpdate(); - } - - /** - * Performs the actual work for {@link #change}. - * The elements at the interval boundaries are split up (if necessary) - * so that the interval boundaries are located at element boundaries. + * Performs the actual work for {@link #change}. The elements at the + * interval boundaries are split up (if necessary) so that the interval + * boundaries are located at element boundaries. */ protected void changeUpdate() { // Split up the element at the start offset if necessary. Element el = getCharacterElement(offset); - Element[] res = split(el, offset, 0); + Element[] res = split(el, offset, 0, el.getElementIndex(offset)); BranchElement par = (BranchElement) el.getParentElement(); + int index = par.getElementIndex(offset); + Edit edit = getEditForParagraphAndIndex(par, index); if (res[1] != null) { - int index = par.getElementIndex(offset); Element[] removed; Element[] added; if (res[0] == null) { removed = new Element[0]; - added = new Element[]{ res[1] }; + added = new Element[] { res[1] }; index++; } else { - removed = new Element[]{ el }; - added = new Element[]{ res[0], res[1] }; + removed = new Element[] { el }; + added = new Element[] { res[0], res[1] }; } - par.replace(index, removed.length, added); - addEdit(par, index, removed, added); + edit.addRemovedElements(removed); + + edit.addAddedElements(added); } int endOffset = offset + length; el = getCharacterElement(endOffset); - res = split(el, endOffset, 0); + res = split(el, endOffset, 0, el.getElementIndex(endOffset)); par = (BranchElement) el.getParentElement(); - if (res[1] != null) + if (res[0] != null) { - int index = par.getElementIndex(offset); Element[] removed; Element[] added; if (res[1] == null) { removed = new Element[0]; - added = new Element[]{ res[1] }; + added = new Element[] { res[1] }; } else { - removed = new Element[]{ el }; - added = new Element[]{ res[0], res[1] }; + removed = new Element[] { el }; + added = new Element[] { res[0], res[1] }; } - par.replace(index, removed.length, added); - addEdit(par, index, removed, added); + edit.addRemovedElements(removed); + edit.addAddedElements(added); } } /** - * Splits an element if <code>offset</code> is not alread at its boundary. + * Modifies the element structure so that the specified interval starts and + * ends at an element boundary. Content and paragraph elements are split and + * created as necessary. This also updates the + * <code>DefaultDocumentEvent</code> to reflect the structural changes. + * The bulk work is delegated to {@link #changeUpdate()}. + * + * @param offset + * the start index of the interval to be changed + * @param length + * the length of the interval to be changed + * @param ev + * the <code>DefaultDocumentEvent</code> describing the change + */ + public void change(int offset, int length, DefaultDocumentEvent ev) + { + if (length == 0) + return; + this.offset = offset; + this.pos = offset; + this.length = length; + documentEvent = ev; + changeUpdate(); + } + + /** + * Creates and returns a deep clone of the specified <code>clonee</code> + * with the specified parent as new parent. * - * @param el the Element to possibly split - * @param offset the offset at which to possibly split - * @param space the amount of space to create between the splitted parts + * This method can only clone direct instances of {@link BranchElement} + * or {@link LeafElement}. * - * @return An array of elements which represent the split result. This - * array has two elements, the two parts of the split. The first - * element might be null, which means that the element which should - * be splitted can remain in place. The second element might also - * be null, which means that the offset is already at an element - * boundary and the element doesn't need to be splitted. - * + * @param parent the new parent + * @param clonee the element to be cloned + * + * @return the cloned element with the new parent */ - private Element[] split(Element el, int offset, int space) + public Element clone(Element parent, Element clonee) { - // If we are at an element boundary, then return an empty array. - if ((offset == el.getStartOffset() || offset == el.getEndOffset()) - && space == 0 && el.isLeaf()) - return new Element[2]; - - // If the element is an instance of BranchElement, then we recursivly - // call this method to perform the split. - Element[] res = new Element[2]; - if (el instanceof BranchElement) + Element clone = clonee; + // We can only handle AbstractElements here. + if (clonee instanceof BranchElement) { - int index = el.getElementIndex(offset); - Element child = el.getElement(index); - Element[] result = split(child, offset, space); - Element[] removed; - Element[] added; - Element[] newAdded; - - int count = el.getElementCount(); - if (!(result[1] == null)) - { - // This is the case when we can keep the first element. - if (result[0] == null) - { - removed = new Element[count - index - 1]; - newAdded = new Element[count - index - 1]; - added = new Element[]{}; - } - // This is the case when we may not keep the first element. - else - { - removed = new Element[count - index]; - newAdded = new Element[count - index]; - added = new Element[]{result[0]}; - } - newAdded[0] = result[1]; - for (int i = index; i < count; i++) - { - Element el2 = el.getElement(i); - int ind = i - count + removed.length; - removed[ind] = el2; - if (ind != 0) - newAdded[ind] = el2; - } - - ((BranchElement) el).replace(index, removed.length, added); - addEdit(el, index, removed, added); - BranchElement newPar = - (BranchElement) createBranchElement(el.getParentElement(), - el.getAttributes()); - newPar.replace(0, 0, newAdded); - res = new Element[]{ null, newPar }; - } - else + BranchElement branchEl = (BranchElement) clonee; + BranchElement branchClone = + new BranchElement(parent, branchEl.getAttributes()); + // Also clone all of the children. + int numChildren = branchClone.getElementCount(); + Element[] cloneChildren = new Element[numChildren]; + for (int i = 0; i < numChildren; ++i) { - removed = new Element[count - index]; - for (int i = index; i < count; ++i) - removed[i - index] = el.getElement(i); - added = new Element[0]; - ((BranchElement) el).replace(index, removed.length, - added); - addEdit(el, index, removed, added); - BranchElement newPar = - (BranchElement) createBranchElement(el.getParentElement(), - el.getAttributes()); - newPar.replace(0, 0, removed); - res = new Element[]{ null, newPar }; + cloneChildren[i] = clone(branchClone, + branchClone.getElement(i)); } + branchClone.replace(0, 0, cloneChildren); + clone = branchClone; } - else if (el instanceof LeafElement) + else if (clonee instanceof LeafElement) { - BranchElement par = (BranchElement) el.getParentElement(); - Element el1 = createLeafElement(par, el.getAttributes(), - el.getStartOffset(), offset); - Element el2 = createLeafElement(par, el.getAttributes(), - offset + space, el.getEndOffset()); - res = new Element[]{ el1, el2 }; + clone = new LeafElement(parent, clonee.getAttributes(), + clonee.getStartOffset(), + clonee.getEndOffset()); } - return res; + return clone; } /** * 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 + * 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) { if (length == 0) return; + this.offset = offset; - this.length = length; + this.pos = offset; this.endOffset = offset + length; + this.length = length; documentEvent = ev; - // Push the root and the paragraph at offset onto the element stack. - elementStack.clear(); - elementStack.push(root); - elementStack.push(root.getElement(root.getElementIndex(offset))); - numEndTags = 0; - numStartTags = 0; + + edits.removeAllElements(); + elementStack.removeAllElements(); + lastFractured = null; + fracNotCreated = false; insertUpdate(data); + // This for loop applies all the changes that were made and updates the + // DocumentEvent. + int size = edits.size(); + for (int i = 0; i < size; i++) + { + Edit curr = (Edit) edits.get(i); + BranchElement e = (BranchElement) curr.e; + Element[] removed = curr.getRemovedElements(); + Element[] added = curr.getAddedElements(); + // FIXME: We probably shouldn't create the empty Element[] in the + // first place. + if (removed.length > 0 || added.length > 0) + { + if (curr.index + removed.length <= e.getElementCount()) + { + e.replace(curr.index, removed.length, added); + ElementEdit ee = new ElementEdit(e, curr.index, removed, added); + ev.addEdit(ee); + } + else + { + System.err.println("WARNING: Tried to replace elements "); + System.err.print("beyond boundaries: elementCount: "); + System.err.println(e.getElementCount()); + System.err.print("index: " + curr.index); + System.err.println(", removed.length: " + removed.length); + } + } + } } /** - * 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 - */ + * Inserts new content + * + * @param data + * the element specifications for the elements to be inserted + */ protected void insertUpdate(ElementSpec[] data) { - if (data[0].getType() == ElementSpec.EndTagType) + // Push the root and the paragraph at offset onto the element stack. + Element current = root; + int index; + while (!current.isLeaf()) { - // fracture deepest child here - BranchElement paragraph = (BranchElement) elementStack.peek(); - Element curr = paragraph.getParentElement(); - int index = curr.getElementIndex(offset); - while (!curr.isLeaf()) - { - index = curr.getElementIndex(offset); - curr = curr.getElement(index); - } - Element parent = curr.getParentElement(); - Element newEl1 = createLeafElement(parent, - curr.getAttributes(), - curr.getStartOffset(), offset); - Element grandParent = parent.getParentElement(); - BranchElement nextBranch = - (BranchElement) grandParent.getElement - (grandParent.getElementIndex(parent.getEndOffset())); - Element firstLeaf = nextBranch.getElement(0); - while (!firstLeaf.isLeaf()) - { - firstLeaf = firstLeaf.getElement(0); - } - BranchElement parent2 = (BranchElement) firstLeaf.getParentElement(); - Element newEl2 = - createLeafElement(parent2, - firstLeaf.getAttributes(), - offset, firstLeaf.getEndOffset()); - parent2.replace(0, 1, new Element[] { newEl2 }); - - - ((BranchElement) parent). - replace(index, 1, new Element[] { newEl1 }); + index = current.getElementIndex(offset); + elementStack.push(current); + current = current.getElement(index); } - for (int i = 0; i < data.length; i++) + int i = 0; + int type = data[0].getType(); + if (type == ElementSpec.ContentType) + { + // If the first tag is content we must treat it separately to allow + // for joining properly to previous Elements and to ensure that + // no extra LeafElements are erroneously inserted. + insertFirstContentTag(data); + pos += data[0].length; + i = 1; + } + else + { + createFracture(data); + i = 0; + } + + // Handle each ElementSpec individually. + for (; i < data.length; i++) { BranchElement paragraph = (BranchElement) elementStack.peek(); switch (data[i].getType()) @@ -827,19 +791,41 @@ public class DefaultStyledDocument extends AbstractDocument switch (data[i].getDirection()) { case ElementSpec.JoinFractureDirection: + // Fracture the tree and ensure the appropriate element + // is on top of the stack. + fracNotCreated = false; insertFracture(data[i]); + if (fracNotCreated) + { + if (lastFractured != null) + elementStack.push(lastFractured.getParentElement()); + else + elementStack.push(paragraph.getElement(0)); + } break; case ElementSpec.JoinNextDirection: - int index = paragraph.getElementIndex(offset); - elementStack.push(paragraph.getElement(index)); - break; - case ElementSpec.OriginateDirection: - Element current = (Element) elementStack.peek(); - Element newParagraph = - insertParagraph((BranchElement) current, offset); - elementStack.push(newParagraph); + // Push the next paragraph element onto the stack so + // future insertions are added to it. + int ix = paragraph.getElementIndex(pos) + 1; + elementStack.push(paragraph.getElement(ix)); break; default: + Element br = null; + if (data.length > i + 1) + { + // leaves will be added to paragraph later + int x = 0; + if (paragraph.getElementCount() > 0) + x = paragraph.getElementIndex(pos) + 1; + Edit e = getEditForParagraphAndIndex(paragraph, x); + br = (BranchElement) createBranchElement(paragraph, + data[i].getAttributes()); + e.added.add(br); + elementStack.push(br); + } + else + // need to add leaves to paragraph now + br = insertParagraph(paragraph, pos); break; } break; @@ -848,50 +834,27 @@ public class DefaultStyledDocument extends AbstractDocument break; case ElementSpec.ContentType: insertContentTag(data[i]); + offset = pos; break; } } - endEdit(); } - - /** - * Finishes an insertion by possibly evaluating the outstanding start and - * end tags. However, this is only performed if the event has received any - * modifications. - */ - private void endEdit() - { - if (documentEvent.modified) - prepareContentInsertion(); - } - + /** - * Evaluates the number of inserted end tags and performs the corresponding - * structural changes. + * Inserts a new paragraph. + * + * @param par - + * the parent + * @param offset - + * the offset + * @return the new paragraph */ - private void prepareContentInsertion() - { - while (numEndTags > 0) - { - elementStack.pop(); - numEndTags--; - } - - while (numStartTags > 0) - { - Element current = (Element) elementStack.peek(); - Element newParagraph = - insertParagraph((BranchElement) current, offset); - elementStack.push(newParagraph); - numStartTags--; - } - } - private Element insertParagraph(BranchElement par, int offset) { - Element current = par.getElement(par.getElementIndex(offset)); - Element[] res = split(current, offset, 0); int index = par.getElementIndex(offset); + Element current = par.getElement(index); + Element[] res = split(current, offset, 0, 0); + Edit e = getEditForParagraphAndIndex(par, index + 1); Element ret; if (res[1] != null) { @@ -902,334 +865,757 @@ public class DefaultStyledDocument extends AbstractDocument removed = new Element[0]; if (res[1] instanceof BranchElement) { - added = new Element[]{ res[1] }; + added = new Element[] { res[1] }; ret = res[1]; } else { ret = createBranchElement(par, null); - added = new Element[]{ ret, res[1] }; + added = new Element[] { ret, res[1] }; } index++; } else { - removed = new Element[]{ current }; + removed = new Element[] { current }; if (res[1] instanceof BranchElement) { ret = res[1]; - added = new Element[]{ res[0], res[1] }; + added = new Element[] { res[0], res[1] }; } else { ret = createBranchElement(par, null); - added = new Element[]{ res[0], ret, res[1] }; + added = new Element[] { res[0], ret, res[1] }; } } - par.replace(index, removed.length, added); - addEdit(par, index, removed, added); + + e.addAddedElements(added); + e.addRemovedElements(removed); } else { ret = createBranchElement(par, null); - Element[] added = new Element[]{ ret }; - par.replace(index, 0, added); - addEdit(par, index, new Element[0], added); + e.addAddedElement(ret); } return ret; } /** - * Inserts a fracture into the document structure. + * Inserts the first tag into the document. * - * @param tag - the element spec. + * @param data - + * the data to be inserted. */ - private void insertFracture(ElementSpec tag) + private void insertFirstContentTag(ElementSpec[] data) { - // This is the parent of the paragraph about to be fractured. We will - // create a new child of this parent. - BranchElement parent = (BranchElement) elementStack.peek(); - int parentIndex = parent.getElementIndex(offset); - - // This is the old paragraph. We must remove all its children that - // occur after offset and move them to a new paragraph. We must - // also recreate its child that occurs at offset to have the proper - // end offset. The remainder of this child will also go in the new - // paragraph. - BranchElement previous = (BranchElement) parent.getElement(parentIndex); - - // This is the new paragraph. - BranchElement newBranch = - (BranchElement) createBranchElement(parent, previous.getAttributes()); - - - // The steps we must take to properly fracture are: - // 1. Recreate the LeafElement at offset to have the correct end offset. - // 2. Create a new LeafElement with the remainder of the LeafElement in - // #1 ==> this is whatever was in that LeafElement to the right of the - // inserted newline. - // 3. Find the paragraph at offset and remove all its children that - // occur _after_ offset. These will be moved to the newly created - // paragraph. - // 4. Move the LeafElement created in #2 and all the LeafElements removed - // in #3 to the newly created paragraph. - // 5. Add the new paragraph to the parent. - int previousIndex = previous.getElementIndex(offset); - int numReplaced = previous.getElementCount() - previousIndex; - Element previousLeaf = previous.getElement(previousIndex); - AttributeSet prevLeafAtts = previous.getAttributes(); - - // This recreates the child at offset to have the proper end offset. - // (Step 1). - Element newPreviousLeaf = - createLeafElement(previous, - prevLeafAtts, previousLeaf.getStartOffset(), - offset); - // This creates the new child, which is the remainder of the old child. - // (Step 2). - - Element firstLeafInNewBranch = - createLeafElement(newBranch, prevLeafAtts, - offset, previousLeaf.getEndOffset()); - - // Now we move the new LeafElement and all the old children that occurred - // after the offset to the new paragraph. (Step 4). - Element[] newLeaves = new Element[numReplaced]; - newLeaves[0] = firstLeafInNewBranch; - for (int i = 1; i < numReplaced; i++) - newLeaves[i] = previous.getElement(previousIndex + i); - newBranch.replace(0, 0, newLeaves); - addEdit(newBranch, 0, null, newLeaves); - - // Now we remove the children after the offset from the previous - // paragraph. (Step 3). - int removeSize = previous.getElementCount() - previousIndex; - Element[] add = new Element[] { newPreviousLeaf }; - Element[] remove = new Element[removeSize]; - for (int j = 0; j < removeSize; j++) - remove[j] = previous.getElement(previousIndex + j); - previous.replace(previousIndex, removeSize, add); - addEdit(previous, previousIndex, remove, add); - - // Finally we add the new paragraph to the parent. (Step 5). - Element[] nb = new Element[] { newBranch }; - int index = parentIndex + 1; - parent.replace(index, 0, nb); - addEdit(parent, index, null, nb); + ElementSpec first = data[0]; + BranchElement paragraph = (BranchElement) elementStack.peek(); + int index = paragraph.getElementIndex(pos); + Element current = paragraph.getElement(index); + int newEndOffset = pos + first.length; + boolean onlyContent = data.length == 1; + Edit edit = getEditForParagraphAndIndex(paragraph, index); + switch (first.getDirection()) + { + case ElementSpec.JoinPreviousDirection: + if (current.getEndOffset() != newEndOffset && !onlyContent) + { + Element newEl1 = createLeafElement(paragraph, + current.getAttributes(), + current.getStartOffset(), + newEndOffset); + edit.addAddedElement(newEl1); + edit.addRemovedElement(current); + offset = newEndOffset; + } + break; + case ElementSpec.JoinNextDirection: + if (pos != 0) + { + Element newEl1 = createLeafElement(paragraph, + current.getAttributes(), + current.getStartOffset(), + pos); + edit.addAddedElement(newEl1); + Element next = paragraph.getElement(index + 1); + + if (onlyContent) + newEl1 = createLeafElement(paragraph, next.getAttributes(), + pos, next.getEndOffset()); + else + { + newEl1 = createLeafElement(paragraph, next.getAttributes(), + pos, newEndOffset); + pos = newEndOffset; + } + edit.addAddedElement(newEl1); + edit.addRemovedElement(current); + edit.addRemovedElement(next); + } + break; + default: + if (current.getStartOffset() != pos) + { + Element newEl = createLeafElement(paragraph, + current.getAttributes(), + current.getStartOffset(), + pos); + edit.addAddedElement(newEl); + } + edit.addRemovedElement(current); + Element newEl1 = createLeafElement(paragraph, first.getAttributes(), + pos, newEndOffset); + edit.addAddedElement(newEl1); + if (current.getEndOffset() != endOffset) + recreateLeaves(newEndOffset, paragraph, onlyContent); + else + offset = newEndOffset; + break; + } } - + /** * Inserts a content element into the document structure. * - * @param tag the element spec + * @param tag - + * the element spec */ private void insertContentTag(ElementSpec tag) { - prepareContentInsertion(); + BranchElement paragraph = (BranchElement) elementStack.peek(); int len = tag.getLength(); int dir = tag.getDirection(); AttributeSet tagAtts = tag.getAttributes(); - if (dir == ElementSpec.JoinPreviousDirection) - { - // The mauve tests to this class show that a JoinPrevious insertion - // does not add any edits to the document event. To me this means - // that nothing is done here. The previous element naturally should - // expand so that it covers the new characters. - } - else if (dir == ElementSpec.JoinNextDirection) + + if (dir == ElementSpec.JoinNextDirection) { - // FIXME: - // Have to handle JoinNext differently depending on whether - // or not it comes after a fracture. If comes after a fracture, - // the insertFracture method takes care of everything and nothing - // needs to be done here. Otherwise, we need to adjust the - // Element structure. For now, I check if the elementStack's - // top Element is the immediate parent of the LeafElement at - // offset - if so, we did not come immediately after a - // fracture. This seems awkward and should probably be improved. - // We may be doing too much in insertFracture because we are - // adjusting the offsets, the correct thing to do may be to - // create a new branch element and push it on to element stack - // and then this method here can be more general. - - BranchElement paragraph = (BranchElement) elementStack.peek(); - int index = paragraph.getElementIndex(offset); + int index = paragraph.getElementIndex(pos); Element target = paragraph.getElement(index); - if (target.isLeaf() && paragraph.getElementCount() > (index + 1)) + Edit edit = getEditForParagraphAndIndex(paragraph, index); + + if (paragraph.getStartOffset() > pos) + { + Element first = paragraph.getElement(0); + Element newEl = createLeafElement(paragraph, + first.getAttributes(), pos, + first.getEndOffset()); + edit.addAddedElement(newEl); + edit.addRemovedElement(first); + } + else if (paragraph.getElementCount() > (index + 1) + && (pos == target.getStartOffset() && !target.equals(lastFractured))) { Element next = paragraph.getElement(index + 1); - Element newEl1 = createLeafElement(paragraph, - target.getAttributes(), - target.getStartOffset(), - offset); - Element newEl2 = createLeafElement(paragraph, - next.getAttributes(), offset, - next.getEndOffset()); - Element[] add = new Element[] { newEl1, newEl2 }; - paragraph.replace (index, 2, add); - addEdit(paragraph, index, new Element[] { target, next }, add); + Element newEl = createLeafElement(paragraph, + next.getAttributes(), pos, + next.getEndOffset()); + edit.addAddedElement(newEl); + edit.addRemovedElement(next); + edit.addRemovedElement(target); + } + else + { + BranchElement parent = (BranchElement) paragraph.getParentElement(); + int i = parent.getElementIndex(pos); + BranchElement next = (BranchElement) parent.getElement(i + 1); + AttributeSet atts = tag.getAttributes(); + + if (next != null) + { + Element nextLeaf = next.getElement(0); + Edit e = getEditForParagraphAndIndex(next, 0); + Element newEl2 = createLeafElement(next, atts, pos, nextLeaf.getEndOffset()); + e.addAddedElement(newEl2); + e.addRemovedElement(nextLeaf); + } } } - else if (dir == ElementSpec.OriginateDirection) + else { - BranchElement paragraph = (BranchElement) elementStack.peek(); - int index = paragraph.getElementIndex(offset); - Element current = paragraph.getElement(index); + int end = pos + len; + Element leaf = createLeafElement(paragraph, tag.getAttributes(), pos, end); - Element[] added; - Element[] removed = new Element[] {current}; - Element[] splitRes = split(current, offset, length); - if (splitRes[0] == null) + // Check for overlap with other leaves/branches + if (paragraph.getElementCount() > 0) { - added = new Element[2]; - added[0] = createLeafElement(paragraph, tagAtts, - offset, endOffset); - added[1] = splitRes[1]; - removed = new Element[0]; - index++; - } - else if (current.getStartOffset() == offset) - { - // This is if the new insertion happens immediately before - // the <code>current</code> Element. In this case there are 2 - // resulting Elements. - added = new Element[2]; - added[0] = createLeafElement(paragraph, tagAtts, offset, - endOffset); - added[1] = splitRes[1]; - } - else if (current.getEndOffset() == endOffset) - { - // This is if the new insertion happens right at the end of - // the <code>current</code> Element. In this case there are - // 2 resulting Elements. - added = new Element[2]; - added[0] = splitRes[0]; - added[1] = createLeafElement(paragraph, tagAtts, offset, - endOffset); + int index = paragraph.getElementIndex(pos); + Element target = paragraph.getElement(index); + boolean onlyContent = target.isLeaf(); + + BranchElement toRec = paragraph; + if (!onlyContent) + toRec = (BranchElement) target; + + // Check if we should place the leaf before or after target + if (pos > target.getStartOffset()) + index++; + + Edit edit = getEditForParagraphAndIndex(paragraph, index); + edit.addAddedElement(leaf); + + if (end != toRec.getEndOffset()) + { + recreateLeaves(end, toRec, onlyContent); + + if (onlyContent) + edit.addRemovedElement(target); + } } else - { - // This is if the new insertion is in the middle of the - // <code>current</code> Element. In this case - // there will be 3 resulting Elements. - added = new Element[3]; - added[0] = splitRes[0]; - added[1] = createLeafElement(paragraph, tagAtts, offset, - endOffset); - added[2] = splitRes[1]; - } - paragraph.replace(index, removed.length, added); - addEdit(paragraph, index, removed, added); + paragraph.replace(0, 0, new Element[] { leaf }); } - offset += len; + + pos += 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 + * This method fractures the child at offset. + * + * @param data + * the ElementSpecs used for the entire insertion */ - public Element clone (Element parent, Element clonee) + private void createFracture(ElementSpec[] data) { - // 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()); + BranchElement paragraph = (BranchElement) elementStack.peek(); + int index = paragraph.getElementIndex(offset); + Element child = paragraph.getElement(index); + Edit edit = getEditForParagraphAndIndex(paragraph, index); + AttributeSet atts = child.getAttributes(); - // 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; + if (offset != 0) + { + Element newEl1 = createLeafElement(paragraph, atts, + child.getStartOffset(), offset); + edit.addAddedElement(newEl1); + edit.addRemovedElement(child); + } } /** - * Adds an ElementChange for a given element modification to the document - * event. If there already is an ElementChange registered for this element, - * this method tries to merge the ElementChanges together. However, this - * is only possible if the indices of the new and old ElementChange are - * equal. - * - * @param e the element - * @param i the index of the change - * @param removed the removed elements, or <code>null</code> - * @param added the added elements, or <code>null</code> + * Recreates a specified part of a the tree after a new leaf + * has been inserted. + * + * @param start - where to start recreating from + * @param paragraph - the paragraph to recreate + * @param onlyContent - true if this is the only content + */ + private void recreateLeaves(int start, BranchElement paragraph, boolean onlyContent) + { + int index = paragraph.getElementIndex(start); + Element child = paragraph.getElement(index); + AttributeSet atts = child.getAttributes(); + + if (!onlyContent) + { + BranchElement newBranch = (BranchElement) createBranchElement(paragraph, + atts); + Element newLeaf = createLeafElement(newBranch, atts, start, + child.getEndOffset()); + newBranch.replace(0, 0, new Element[] { newLeaf }); + + BranchElement parent = (BranchElement) paragraph.getParentElement(); + int parSize = parent.getElementCount(); + Edit edit = getEditForParagraphAndIndex(parent, parSize); + edit.addAddedElement(newBranch); + + int paragraphSize = paragraph.getElementCount(); + Element[] removed = new Element[paragraphSize - (index + 1)]; + int s = 0; + for (int j = index + 1; j < paragraphSize; j++) + removed[s++] = paragraph.getElement(j); + + edit = getEditForParagraphAndIndex(paragraph, index); + edit.addRemovedElements(removed); + Element[] added = recreateAfterFracture(removed, newBranch, 0, child.getEndOffset()); + newBranch.replace(1, 0, added); + + lastFractured = newLeaf; + pos = newBranch.getEndOffset(); + } + else + { + Element newLeaf = createLeafElement(paragraph, atts, start, + child.getEndOffset()); + Edit edit = getEditForParagraphAndIndex(paragraph, index); + edit.addAddedElement(newLeaf); + } + } + + /** + * Splits an element if <code>offset</code> is not already at its + * boundary. + * + * @param el + * the Element to possibly split + * @param offset + * the offset at which to possibly split + * @param space + * the amount of space to create between the splitted parts + * @param editIndex + * the index of the edit to use + * @return An array of elements which represent the split result. This array + * has two elements, the two parts of the split. The first element + * might be null, which means that the element which should be + * splitted can remain in place. The second element might also be + * null, which means that the offset is already at an element + * boundary and the element doesn't need to be splitted. */ - private void addEdit(Element e, int i, Element[] removed, Element[] added) + private Element[] split(Element el, int offset, int space, int editIndex) { - // Perform sanity check first. - DocumentEvent.ElementChange ec = documentEvent.getChange(e); + // If we are at an element boundary, then return an empty array. + if ((offset == el.getStartOffset() || offset == el.getEndOffset()) + && space == 0 && el.isLeaf()) + return new Element[2]; - // Merge the existing stuff with the new stuff. - Element[] oldAdded = ec == null ? null: ec.getChildrenAdded(); - Element[] newAdded; - if (oldAdded != null && added != null) + // If the element is an instance of BranchElement, then we + // recursivly + // call this method to perform the split. + Element[] res = new Element[2]; + if (el instanceof BranchElement) { - if (ec.getIndex() <= i) + int index = el.getElementIndex(offset); + Element child = el.getElement(index); + Element[] result = split(child, offset, space, editIndex); + Element[] removed; + Element[] added; + Element[] newAdded; + + int count = el.getElementCount(); + if (result[1] != null) { - int index = i - ec.getIndex(); - // Merge adds together. - newAdded = new Element[oldAdded.length + added.length]; - System.arraycopy(oldAdded, 0, newAdded, 0, index); - System.arraycopy(added, 0, newAdded, index, added.length); - System.arraycopy(oldAdded, index, newAdded, index + added.length, - oldAdded.length - index); - i = ec.getIndex(); + // This is the case when we can keep the first element. + if (result[0] == null) + { + removed = new Element[count - index - 1]; + newAdded = new Element[count - index - 1]; + added = new Element[] {}; + + } + // This is the case when we may not keep the first + // element. + else + { + removed = new Element[count - index]; + newAdded = new Element[count - index]; + added = new Element[] { result[0] }; + } + newAdded[0] = result[1]; + for (int i = index; i < count; i++) + { + Element el2 = el.getElement(i); + int ind = i - count + removed.length; + removed[ind] = el2; + if (ind != 0) + newAdded[ind] = el2; + } + + Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex); + edit.addRemovedElements(removed); + edit.addAddedElements(added); + + BranchElement newPar = + (BranchElement) createBranchElement(el.getParentElement(), + el.getAttributes()); + newPar.replace(0, 0, newAdded); + res = new Element[] { null, newPar }; } else - throw new AssertionError("Not yet implemented case."); + { + removed = new Element[count - index]; + for (int i = index; i < count; ++i) + removed[i - index] = el.getElement(i); + + Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex); + edit.addRemovedElements(removed); + + BranchElement newPar = (BranchElement) createBranchElement(el.getParentElement(), + el.getAttributes()); + newPar.replace(0, 0, removed); + res = new Element[] { null, newPar }; + } } - else if (added != null) - newAdded = added; - else if (oldAdded != null) - newAdded = oldAdded; - else - newAdded = new Element[0]; + else if (el instanceof LeafElement) + { + BranchElement par = (BranchElement) el.getParentElement(); + Element el1 = createLeafElement(par, el.getAttributes(), + el.getStartOffset(), offset); + + Element el2 = createLeafElement(par, el.getAttributes(), + offset + space, + el.getEndOffset()); + res = new Element[] { el1, el2 }; + } + return res; + } - Element[] oldRemoved = ec == null ? null: ec.getChildrenRemoved(); - Element[] newRemoved; - if (oldRemoved != null && removed != null) + /** + * Inserts a fracture into the document structure. + * + * @param tag - + * the element spec. + */ + private void insertFracture(ElementSpec tag) + { + // insert the fracture at offset. + BranchElement parent = (BranchElement) elementStack.peek(); + int parentIndex = parent.getElementIndex(pos); + AttributeSet parentAtts = parent.getAttributes(); + Element toFracture = parent.getElement(parentIndex); + int parSize = parent.getElementCount(); + Edit edit = getEditForParagraphAndIndex(parent, parentIndex); + Element frac = toFracture; + int leftIns = 0; + int indexOfFrac = toFracture.getElementIndex(pos); + int size = toFracture.getElementCount(); + + // gets the leaf that falls along the fracture + frac = toFracture.getElement(indexOfFrac); + while (!frac.isLeaf()) + frac = frac.getElement(frac.getElementIndex(pos)); + + AttributeSet atts = frac.getAttributes(); + int fracStart = frac.getStartOffset(); + int fracEnd = frac.getEndOffset(); + if (pos >= fracStart && pos < fracEnd) { - if (ec.getIndex() <= i) + // recreate left-side of branch and all its children before offset + // add the fractured leaves to the right branch + BranchElement rightBranch = + (BranchElement) createBranchElement(parent, parentAtts); + + // Check if left branch has already been edited. If so, we only + // need to create the right branch. + BranchElement leftBranch = null; + Element[] added = null; + if (edit.added.size() > 0 || edit.removed.size() > 0) { - int index = i - ec.getIndex(); - // Merge removes together. - newRemoved = new Element[oldRemoved.length + removed.length]; - System.arraycopy(oldAdded, 0, newRemoved, 0, index); - System.arraycopy(removed, 0, newRemoved, index, removed.length); - System.arraycopy(oldRemoved, index, newRemoved, - index + removed.length, - oldRemoved.length - index); - i = ec.getIndex(); + added = new Element[] { rightBranch }; + + // don't try to remove left part of tree + parentIndex++; } else - throw new AssertionError("Not yet implemented case."); + { + leftBranch = + (BranchElement) createBranchElement(parent, parentAtts); + added = new Element[] { leftBranch, rightBranch }; + + // add fracture to leftBranch + if (fracStart != pos) + { + Element leftFracturedLeaf = + createLeafElement(leftBranch, atts, fracStart, pos); + leftBranch.replace(leftIns, 0, + new Element[] { leftFracturedLeaf }); + } + } + + if (!toFracture.isLeaf()) + { + // add all non-fracture elements to the branches + if (indexOfFrac > 0 && leftBranch != null) + { + Element[] add = new Element[indexOfFrac]; + for (int i = 0; i < indexOfFrac; i++) + add[i] = toFracture.getElement(i); + leftIns = add.length; + leftBranch.replace(0, 0, add); + } + + int count = size - indexOfFrac - 1; + if (count > 0) + { + Element[] add = new Element[count]; + int j = 0; + int i = indexOfFrac + 1; + while (j < count) + add[j++] = toFracture.getElement(i++); + rightBranch.replace(0, 0, add); + } + } + + // add to fracture to rightBranch + // Check if we can join the right frac leaf with the next leaf + int rm = 0; + int end = fracEnd; + Element next = rightBranch.getElement(0); + if (next != null && next.isLeaf() + && next.getAttributes().isEqual(atts)) + { + end = next.getEndOffset(); + rm = 1; + } + + Element rightFracturedLeaf = createLeafElement(rightBranch, atts, + pos, end); + rightBranch.replace(0, rm, new Element[] { rightFracturedLeaf }); + + // recreate those elements after parentIndex and add/remove all + // new/old elements to parent + int remove = parSize - parentIndex; + Element[] removed = new Element[0]; + Element[] added2 = new Element[0]; + if (remove > 0) + { + removed = new Element[remove]; + int s = 0; + for (int j = parentIndex; j < parSize; j++) + removed[s++] = parent.getElement(j); + edit.addRemovedElements(removed); + added2 = recreateAfterFracture(removed, parent, 1, + rightBranch.getEndOffset()); + } + + edit.addAddedElements(added); + edit.addAddedElements(added2); + elementStack.push(rightBranch); + lastFractured = rightFracturedLeaf; } - else if (removed != null) - newRemoved = removed; - else if (oldRemoved != null) - newRemoved = oldRemoved; else - newRemoved = new Element[0]; + fracNotCreated = true; + } + + /** + * Recreates all the elements from the parent to the element on the top of + * the stack, starting from startFrom with the starting offset of + * startOffset. + * + * @param recreate - + * the elements to recreate + * @param parent - + * the element to add the new elements to + * @param startFrom - + * where to start recreating from + * @param startOffset - + * the offset of the first element + * @return the array of added elements + */ + private Element[] recreateAfterFracture(Element[] recreate, + BranchElement parent, int startFrom, + int startOffset) + { + Element[] added = new Element[recreate.length - startFrom]; + int j = 0; + for (int i = startFrom; i < recreate.length; i++) + { + Element curr = recreate[i]; + int len = curr.getEndOffset() - curr.getStartOffset(); + if (curr instanceof LeafElement) + added[j] = createLeafElement(parent, curr.getAttributes(), + startOffset, startOffset + len); + else + { + BranchElement br = + (BranchElement) createBranchElement(parent, + curr.getAttributes()); + int bSize = curr.getElementCount(); + for (int k = 0; k < bSize; k++) + { + Element bCurr = curr.getElement(k); + Element[] add = recreateAfterFracture(new Element[] { bCurr }, br, 0, + startOffset); + br.replace(0, 0, add); + + } + added[j] = br; + } + startOffset += len; + j++; + } + + return added; + } + } + + /** + * This method looks through the Vector of Edits to see if there is already an + * Edit object associated with the given paragraph. If there is, then we + * return it. Otherwise we create a new Edit object, add it to the vector, and + * return it. Note: this method is package private to avoid accessors. + * + * @param index + * the index associated with the Edit we want to create + * @param para + * the paragraph associated with the Edit we want + * @return the found or created Edit object + */ + Edit getEditForParagraphAndIndex(BranchElement para, int index) + { + Edit curr; + int size = edits.size(); + for (int i = 0; i < size; i++) + { + curr = (Edit) edits.elementAt(i); + if (curr.e.equals(para)) + return curr; + } + curr = new Edit(para, index, null, null); + edits.add(curr); + + return curr; + } + /** + * Instance of all editing information for an object in the Vector. This class + * is used to add information to the DocumentEvent associated with an + * insertion/removal/change as well as to store the changes that need to be + * made so they can be made all at the same (appropriate) time. + */ + class Edit + { + /** The element to edit . */ + Element e; + + /** The index of the change. */ + int index; + + /** The removed elements. */ + Vector removed = new Vector(); + + /** The added elements. */ + Vector added = new Vector(); + + /** + * Return an array containing the Elements that have been removed from the + * paragraph associated with this Edit. + * + * @return an array of removed Elements + */ + public Element[] getRemovedElements() + { + int size = removed.size(); + Element[] removedElements = new Element[size]; + for (int i = 0; i < size; i++) + removedElements[i] = (Element) removed.elementAt(i); + return removedElements; + } + + /** + * Return an array containing the Elements that have been added to the + * paragraph associated with this Edit. + * + * @return an array of added Elements + */ + public Element[] getAddedElements() + { + int size = added.size(); + Element[] addedElements = new Element[size]; + for (int i = 0; i < size; i++) + addedElements[i] = (Element) added.elementAt(i); + return addedElements; + } + + /** + * Checks if e is already in the vector. + * + * @param e - the Element to look for + * @param v - the vector to search + * @return true if e is in v. + */ + private boolean contains(Vector v, Element e) + { + if (e == null) + return false; + + int i = v.size(); + for (int j = 0; j < i; j++) + { + Element e1 = (Element) v.get(j); + if ((e1 != null) && (e1.getAttributes().isEqual(e.getAttributes())) + && (e1.getName().equals(e.getName())) + && (e1.getStartOffset() == e.getStartOffset()) + && (e1.getEndOffset() == e.getEndOffset()) + && (e1.getParentElement().equals(e.getParentElement())) + && (e1.getElementCount() == e.getElementCount())) + return true; + } + return false; + } + + /** + * Adds one Element to the vector of removed Elements. + * + * @param e + * the Element to add + */ + public void addRemovedElement(Element e) + { + if (!contains(removed, e)) + removed.add(e); + } + + /** + * Adds each Element in the given array to the vector of removed Elements + * + * @param e + * the array containing the Elements to be added + */ + public void addRemovedElements(Element[] e) + { + if (e == null || e.length == 0) + return; + for (int i = 0; i < e.length; i++) + { + if (!contains(removed, e[i])) + removed.add(e[i]); + } + } - // Replace the existing edit for the element with the merged. - documentEvent.addEdit(new ElementEdit(e, i, newRemoved, newAdded)); + /** + * Adds one Element to the vector of added Elements. + * + * @param e + * the Element to add + */ + public void addAddedElement(Element e) + { + if (!contains(added, e)) + added.add(e); + } + + /** + * Adds each Element in the given array to the vector of added Elements. + * + * @param e + * the array containing the Elements to be added + */ + public void addAddedElements(Element[] e) + { + if (e == null || e.length == 0) + return; + for (int i = 0; i < e.length; i++) + { + if (!contains(added, e[i])) + added.add(e[i]); + } + } + + /** + * Creates a new Edit object with the given parameters + * + * @param e + * the paragraph Element associated with this Edit + * @param i + * the index within the paragraph where changes are started + * @param removed + * an array containing Elements that should be removed from the + * paragraph Element + * @param added + * an array containing Elements that should be added to the + * paragraph Element + */ + public Edit(Element e, int i, Element[] removed, Element[] added) + { + this.e = e; + this.index = i; + addRemovedElements(removed); + addAddedElements(added); } } /** - * An element type for sections. This is a simple BranchElement with - * a unique name. + * An element type for sections. This is a simple BranchElement with a unique + * name. */ protected class SectionElement extends BranchElement { @@ -1244,7 +1630,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the name of the element. This method always returns * "section". - * + * * @return the name of the element */ public String getName() @@ -1256,18 +1642,18 @@ public class DefaultStyledDocument extends AbstractDocument /** * 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 + 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 + * + * @param event + * the change event */ public void stateChanged(ChangeEvent event) { @@ -1296,6 +1682,11 @@ public class DefaultStyledDocument extends AbstractDocument private StyleChangeListener styleChangeListener; /** + * Vector that contains all the edits. Maybe replace by a HashMap. + */ + Vector edits = new Vector(); + + /** * Creates a new <code>DefaultStyledDocument</code>. */ public DefaultStyledDocument() @@ -1304,10 +1695,11 @@ public class DefaultStyledDocument extends AbstractDocument } /** - * Creates a new <code>DefaultStyledDocument</code> that uses the - * specified {@link StyleContext}. - * - * @param context the <code>StyleContext</code> to use + * Creates a new <code>DefaultStyledDocument</code> that uses the specified + * {@link StyleContext}. + * + * @param context + * the <code>StyleContext</code> to use */ public DefaultStyledDocument(StyleContext context) { @@ -1315,14 +1707,16 @@ public class DefaultStyledDocument extends AbstractDocument } /** - * Creates a new <code>DefaultStyledDocument</code> that uses the - * specified {@link StyleContext} and {@link Content} buffer. - * - * @param content the <code>Content</code> buffer to use - * @param context the <code>StyleContext</code> to use + * Creates a new <code>DefaultStyledDocument</code> that uses the specified + * {@link StyleContext} and {@link Content} buffer. + * + * @param content + * the <code>Content</code> buffer to use + * @param context + * the <code>StyleContext</code> to use */ public DefaultStyledDocument(AbstractDocument.Content content, - StyleContext context) + StyleContext context) { super(content, context); buffer = new ElementBuffer(createDefaultRoot()); @@ -1330,10 +1724,9 @@ public class DefaultStyledDocument extends AbstractDocument } /** - * Adds a style into the style hierarchy. Unspecified style attributes - * can be resolved in the <code>parent</code> style, if one is specified. - * - * While it is legal to add nameless styles (<code>nm == null</code), + * Adds a style into the style hierarchy. Unspecified style attributes can be + * resolved in the <code>parent</code> style, if one is specified. While it + * is legal to add nameless styles (<code>nm == null</code), * you must be aware that the client application is then responsible * for managing the style hierarchy, since unnamed styles cannot be * looked up by their name. @@ -1360,14 +1753,12 @@ public class DefaultStyledDocument extends AbstractDocument /** * Create the default root element for this kind of <code>Document</code>. - * + * * @return the default root element for this kind of <code>Document</code> */ protected AbstractDocument.AbstractElement createDefaultRoot() { Element[] tmp; - // FIXME: Create a SecionElement here instead of a BranchElement. - // Use createBranchElement() and createLeafElement instead. SectionElement section = new SectionElement(); BranchElement paragraph = new BranchElement(section, null); @@ -1375,7 +1766,7 @@ public class DefaultStyledDocument extends AbstractDocument tmp[0] = paragraph; section.replace(0, 0, tmp); - LeafElement leaf = new LeafElement(paragraph, null, 0, 1); + Element leaf = new LeafElement(paragraph, null, 0, 1); tmp = new Element[1]; tmp[0] = leaf; paragraph.replace(0, 0, tmp); @@ -1384,14 +1775,14 @@ public class DefaultStyledDocument extends AbstractDocument } /** - * Returns the <code>Element</code> that corresponds to the character - * at the specified position. - * - * @param position the position of which we query the corresponding - * <code>Element</code> - * - * @return the <code>Element</code> that corresponds to the character - * at the specified position + * Returns the <code>Element</code> that corresponds to the character at the + * specified position. + * + * @param position + * the position of which we query the corresponding + * <code>Element</code> + * @return the <code>Element</code> that corresponds to the character at the + * specified position */ public Element getCharacterElement(int position) { @@ -1402,15 +1793,15 @@ public class DefaultStyledDocument extends AbstractDocument int index = element.getElementIndex(position); element = element.getElement(index); } - + return element; } /** * Extracts a background color from a set of attributes. - * - * @param attributes the attributes from which to get a background color - * + * + * @param attributes + * the attributes from which to get a background color * @return the background color that correspond to the attributes */ public Color getBackground(AttributeSet attributes) @@ -1421,7 +1812,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the default root element. - * + * * @return the default root element */ public Element getDefaultRootElement() @@ -1431,9 +1822,9 @@ public class DefaultStyledDocument extends AbstractDocument /** * Extracts a font from a set of attributes. - * - * @param attributes the attributes from which to get a font - * + * + * @param attributes + * the attributes from which to get a font * @return the font that correspond to the attributes */ public Font getFont(AttributeSet attributes) @@ -1441,12 +1832,12 @@ public class DefaultStyledDocument extends AbstractDocument StyleContext context = (StyleContext) getAttributeContext(); return context.getFont(attributes); } - + /** * Extracts a foreground color from a set of attributes. - * - * @param attributes the attributes from which to get a foreground color - * + * + * @param attributes + * the attributes from which to get a foreground color * @return the foreground color that correspond to the attributes */ public Color getForeground(AttributeSet attributes) @@ -1457,9 +1848,9 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns the logical <code>Style</code> for the specified position. - * - * @param position the position from which to query to logical style - * + * + * @param position + * the position from which to query to logical style * @return the logical <code>Style</code> for the specified position */ public Style getLogicalStyle(int position) @@ -1474,37 +1865,32 @@ 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 + * 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 - * + * + * @param position + * the position for which to query the paragraph element * @return the paragraph element for the specified position */ public Element getParagraphElement(int position) { - 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; + Element e = getDefaultRootElement(); + while (!e.isLeaf()) + e = e.getElement(e.getElementIndex(position)); + + if (e != null) + return e.getParentElement(); + return e; } /** * Looks up and returns a named <code>Style</code>. - * - * @param nm the name of the <code>Style</code> - * + * + * @param nm + * the name of the <code>Style</code> * @return the found <code>Style</code> of <code>null</code> if no such * <code>Style</code> exists */ @@ -1516,8 +1902,9 @@ public class DefaultStyledDocument extends AbstractDocument /** * Removes a named <code>Style</code> from the style hierarchy. - * - * @param nm the name of the <code>Style</code> to be removed + * + * @param nm + * the name of the <code>Style</code> to be removed */ public void removeStyle(String nm) { @@ -1528,31 +1915,32 @@ public class DefaultStyledDocument extends AbstractDocument /** * Sets text attributes for the fragment specified by <code>offset</code> * and <code>length</code>. - * - * @param offset the start offset of the fragment - * @param length the length of the fragment - * @param attributes the text attributes to set - * @param replace if <code>true</code>, the attributes of the current - * selection are overridden, otherwise they are merged + * + * @param offset + * the start offset of the fragment + * @param length + * the length of the fragment + * @param attributes + * the text attributes to set + * @param replace + * if <code>true</code>, the attributes of the current selection + * are overridden, otherwise they are merged */ public void setCharacterAttributes(int offset, int length, - AttributeSet attributes, - boolean replace) + AttributeSet attributes, boolean replace) { // Exit early if length is 0, so no DocumentEvent is created or fired. if (length == 0) return; try { - // Must obtain a write lock for this method. writeLock() and + // Must obtain a write lock for this method. writeLock() and // writeUnlock() should always be in try/finally block to make // sure that locking happens in a balanced manner. writeLock(); - DefaultDocumentEvent ev = - new DefaultDocumentEvent( - offset, - length, - DocumentEvent.EventType.CHANGE); + DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, + length, + DocumentEvent.EventType.CHANGE); // Modify the element structure so that the interval begins at an // element @@ -1563,13 +1951,13 @@ public class DefaultStyledDocument extends AbstractDocument // Visit all paragraph elements within the specified interval int end = offset + length; Element curr; - for (int pos = offset; pos < end; ) + for (int pos = offset; pos < end;) { // Get the CharacterElement at offset pos. curr = getCharacterElement(pos); if (pos == curr.getEndOffset()) break; - + MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes(); ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace)); // If replace is true, remove all the old attributes. @@ -1588,12 +1976,14 @@ public class DefaultStyledDocument extends AbstractDocument writeUnlock(); } } - + /** * Sets the logical style for the paragraph at the specified position. - * - * @param position the position at which the logical style is added - * @param style the style to set for the current paragraph + * + * @param position + * the position at which the logical style is added + * @param style + * the style to set for the current paragraph */ public void setLogicalStyle(int position, Style style) { @@ -1603,60 +1993,59 @@ public class DefaultStyledDocument extends AbstractDocument if (el == null) return; try - { - writeLock(); - if (el instanceof AbstractElement) - { - AbstractElement ael = (AbstractElement) el; - ael.setResolveParent(style); - int start = el.getStartOffset(); - int end = el.getEndOffset(); - DefaultDocumentEvent ev = - new DefaultDocumentEvent ( - start, - end - start, - DocumentEvent.EventType.CHANGE); - // FIXME: Add an UndoableEdit to this event and fire it. - fireChangedUpdate(ev); - } - else - throw new - AssertionError("paragraph elements are expected to be" - + "instances of AbstractDocument.AbstractElement"); - } + { + writeLock(); + if (el instanceof AbstractElement) + { + AbstractElement ael = (AbstractElement) el; + ael.setResolveParent(style); + int start = el.getStartOffset(); + int end = el.getEndOffset(); + DefaultDocumentEvent ev = new DefaultDocumentEvent(start, + end - start, + DocumentEvent.EventType.CHANGE); + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + else + throw new AssertionError( + "paragraph elements are expected to be" + + "instances of AbstractDocument.AbstractElement"); + } finally - { - writeUnlock(); - } + { + writeUnlock(); + } } /** * Sets text attributes for the paragraph at the specified fragment. - * - * @param offset the beginning of the fragment - * @param length the length of the fragment - * @param attributes the text attributes to set - * @param replace if <code>true</code>, the attributes of the current - * selection are overridden, otherwise they are merged + * + * @param offset + * the beginning of the fragment + * @param length + * the length of the fragment + * @param attributes + * the text attributes to set + * @param replace + * if <code>true</code>, the attributes of the current selection + * are overridden, otherwise they are merged */ public void setParagraphAttributes(int offset, int length, - AttributeSet attributes, - boolean replace) + AttributeSet attributes, boolean replace) { try { - // Must obtain a write lock for this method. writeLock() and + // Must obtain a write lock for this method. writeLock() and // writeUnlock() should always be in try/finally blocks to make // sure that locking occurs in a balanced manner. writeLock(); - + // Create a DocumentEvent to use for changedUpdate(). - DefaultDocumentEvent ev = - new DefaultDocumentEvent ( - offset, - length, - DocumentEvent.EventType.CHANGE); - + DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, + length, + DocumentEvent.EventType.CHANGE); + // Have to iterate through all the _paragraph_ elements that are // contained or partially contained in the interval // (offset, offset + length). @@ -1665,7 +2054,7 @@ public class DefaultStyledDocument extends AbstractDocument int endElement = rootElement.getElementIndex(offset + length - 1); if (endElement < startElement) endElement = startElement; - + for (int i = startElement; i <= endElement; i++) { Element par = rootElement.getElement(i); @@ -1688,11 +2077,13 @@ public class DefaultStyledDocument extends AbstractDocument } /** - * 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 + * 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) { @@ -1703,8 +2094,7 @@ public class DefaultStyledDocument extends AbstractDocument int offset = ev.getOffset(); int length = ev.getLength(); int endOffset = offset + length; - AttributeSet paragraphAttributes = - getParagraphElement(endOffset).getAttributes(); + AttributeSet paragraphAttributes = getParagraphElement(endOffset).getAttributes(); Segment txt = new Segment(); try { @@ -1723,91 +2113,75 @@ public class DefaultStyledDocument extends AbstractDocument short finalStartDirection = ElementSpec.OriginateDirection; boolean prevCharWasNewline = false; Element prev = getCharacterElement(offset); - Element next = getCharacterElement(endOffset); + Element next = getCharacterElement(endOffset); Element prevParagraph = getParagraphElement(offset); Element paragraph = getParagraphElement(endOffset); - + int segmentEnd = txt.offset + txt.count; - + // Check to see if we're inserting immediately after a newline. if (offset > 0) { try - { - String s = getText(offset - 1, 1); - if (s.equals("\n")) - { - finalStartDirection = - handleInsertAfterNewline(specs, offset, endOffset, - prevParagraph, - paragraph, - paragraphAttributes); - - prevCharWasNewline = true; - // Find the final start tag from the ones just created. - for (int i = 0; i < specs.size(); i++) - if (((ElementSpec) specs.get(i)).getType() - == ElementSpec.StartTagType) - finalStartTag = (ElementSpec)specs.get(i); - } - } + { + String s = getText(offset - 1, 1); + if (s.equals("\n")) + { + finalStartDirection = handleInsertAfterNewline(specs, offset, + endOffset, + prevParagraph, + paragraph, + paragraphAttributes); + + prevCharWasNewline = true; + // Find the final start tag from the ones just created. + for (int i = 0; i < specs.size(); i++) + if (((ElementSpec) specs.get(i)).getType() == ElementSpec.StartTagType) + finalStartTag = (ElementSpec) specs.get(i); + } + } catch (BadLocationException ble) - { - // This shouldn't happen. - AssertionError ae = new AssertionError(); - ae.initCause(ble); - throw ae; - } + { + // This shouldn't happen. + AssertionError ae = new AssertionError(); + ae.initCause(ble); + throw ae; + } } - for (int i = txt.offset; i < segmentEnd; ++i) { len++; if (txt.array[i] == '\n') { // Add the ElementSpec for the content. - specs.add(new ElementSpec(attr, ElementSpec.ContentType, len)); + specs.add(new ElementSpec(attr, ElementSpec.ContentType, len)); // Add ElementSpecs for the newline. specs.add(new ElementSpec(null, ElementSpec.EndTagType)); finalStartTag = new ElementSpec(paragraphAttributes, - ElementSpec.StartTagType); + ElementSpec.StartTagType); specs.add(finalStartTag); len = 0; } } // Create last element if last character hasn't been a newline. - if (len > 0) + if (len > 0) specs.add(new ElementSpec(attr, ElementSpec.ContentType, len)); - // Set the direction of the last spec of type StartTagType. - // If we are inserting after a newline then this value comes from + // Set the direction of the last spec of type StartTagType. + // If we are inserting after a newline then this value comes from // handleInsertAfterNewline. if (finalStartTag != null) - { + { if (prevCharWasNewline) finalStartTag.setDirection(finalStartDirection); else if (prevParagraph.getEndOffset() != endOffset) - { - try - { - String last = getText(endOffset - 1, 1); - if (!last.equals("\n")) - finalStartTag.setDirection(ElementSpec.JoinFractureDirection); - } - catch (BadLocationException ble) - { - // This shouldn't happen. - AssertionError ae = new AssertionError(); - ae.initCause(ble); - throw ae; - } - } + finalStartTag.setDirection(ElementSpec.JoinFractureDirection); else { - // If there is an element AFTER this one, then set the + // If there is an element AFTER this one, then set the // direction to JoinNextDirection. Element parent = prevParagraph.getParentElement(); int index = parent.getElementIndex(offset); @@ -1816,19 +2190,18 @@ public class DefaultStyledDocument extends AbstractDocument finalStartTag.setDirection(ElementSpec.JoinNextDirection); } } - + // If we are at the last index, then check if we could probably be // joined with the next element. // This means: - // - we must be a ContentTag - // - if there is a next Element, we must have the same attributes - // - if there is no next Element, but one will be created, - // we must have the same attributes as the higher-level run. + // - we must be a ContentTag + // - if there is a next Element, we must have the same attributes + // - if there is no next Element, but one will be created, + // we must have the same attributes as the higher-level run. ElementSpec last = (ElementSpec) specs.lastElement(); if (last.getType() == ElementSpec.ContentType) { - Element currentRun = - prevParagraph.getElement(prevParagraph.getElementIndex(offset)); + Element currentRun = prevParagraph.getElement(prevParagraph.getElementIndex(offset)); if (currentRun.getEndOffset() == endOffset) { if (endOffset < getLength() && next.getAttributes().isEqual(attr) @@ -1838,62 +2211,58 @@ public class DefaultStyledDocument extends AbstractDocument else { if (finalStartTag != null - && finalStartTag.getDirection() == - ElementSpec.JoinFractureDirection + && finalStartTag.getDirection() == ElementSpec.JoinFractureDirection && currentRun.getAttributes().isEqual(attr)) { last.setDirection(ElementSpec.JoinNextDirection); } } } - + // If we are at the first new element, then check if it could be // joined with the previous element. ElementSpec first = (ElementSpec) specs.firstElement(); if (prev.getAttributes().isEqual(attr) && first.getType() == ElementSpec.ContentType) first.setDirection(ElementSpec.JoinPreviousDirection); - - ElementSpec[] elSpecs = - (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]); + ElementSpec[] elSpecs = (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]); buffer.insert(offset, length, elSpecs, ev); } /** - * A helper method to set up the ElementSpec buffer for the special - * case of an insertion occurring immediately after a newline. - * @param specs the ElementSpec buffer to initialize. + * A helper method to set up the ElementSpec buffer for the special case of an + * insertion occurring immediately after a newline. + * + * @param specs + * the ElementSpec buffer to initialize. */ short handleInsertAfterNewline(Vector specs, int offset, int endOffset, - Element prevParagraph, Element paragraph, - AttributeSet a) + Element prevParagraph, Element paragraph, + AttributeSet a) { if (prevParagraph.getParentElement() == paragraph.getParentElement()) { specs.add(new ElementSpec(a, ElementSpec.EndTagType)); specs.add(new ElementSpec(a, ElementSpec.StartTagType)); - if (prevParagraph.getEndOffset() != endOffset) + if (paragraph.getStartOffset() != endOffset) return ElementSpec.JoinFractureDirection; // If there is an Element after this one, use JoinNextDirection. Element parent = paragraph.getParentElement(); - if (parent.getElementCount() > parent.getElementIndex(offset) + 1) + if (parent.getElementCount() > (parent.getElementIndex(offset) + 1)) return ElementSpec.JoinNextDirection; } - else - { - // TODO: What to do here? - } return ElementSpec.OriginateDirection; } - + /** * Updates the document structure in response to text removal. This is - * forwarded to the {@link ElementBuffer} of this document. Any changes to - * the document structure are added to the specified document event and - * sent to registered listeners. - * - * @param ev the document event that records the changes to the document + * forwarded to the {@link ElementBuffer} of this document. Any changes to the + * document structure are added to the specified document event and sent to + * registered listeners. + * + * @param ev + * the document event that records the changes to the document */ protected void removeUpdate(DefaultDocumentEvent ev) { @@ -1903,7 +2272,7 @@ public class DefaultStyledDocument extends AbstractDocument /** * Returns an enumeration of all style names. - * + * * @return an enumeration of all style names */ public Enumeration getStyleNames() @@ -1914,61 +2283,35 @@ public class DefaultStyledDocument extends AbstractDocument /** * Called when any of this document's styles changes. - * - * @param style the style that changed + * + * @param style + * the style that changed */ protected void styleChanged(Style style) { // Nothing to do here. This is intended to be overridden by subclasses. } - void printElements (Element start, int pad) - { - for (int i = 0; i < pad; i++) - System.out.print(" "); - if (pad == 0) - System.out.println ("ROOT ELEMENT ("+start.getStartOffset()+", "+start.getEndOffset()+")"); - else if (start instanceof AbstractDocument.BranchElement) - System.out.println ("BranchElement ("+start.getStartOffset()+", "+start.getEndOffset()+")"); - else - { - { - try - { - System.out.println ("LeafElement ("+start.getStartOffset()+", " - + start.getEndOffset()+"): "+ - start.getDocument(). - getText(start.getStartOffset(), - start.getEndOffset() - - start.getStartOffset())); - } - catch (BadLocationException ble) - { - } - } - } - for (int i = 0; i < start.getElementCount(); i ++) - printElements (start.getElement(i), pad+3); - } - /** * 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 + * + * @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 + throws BadLocationException { if (data == null || data.length == 0) return; try { // writeLock() and writeUnlock() should always be in a try/finally - // block so that locking balance is guaranteed even if some + // block so that locking balance is guaranteed even if some // exception is thrown. writeLock(); - + // First we collect the content to be inserted. StringBuffer contentBuffer = new StringBuffer(); for (int i = 0; i < data.length; i++) @@ -1986,15 +2329,14 @@ public class DefaultStyledDocument extends AbstractDocument // If there was no content inserted then exit early. if (length == 0) return; - + UndoableEdit edit = content.insertString(offset, contentBuffer.toString()); // Create the DocumentEvent with the ElementEdit added - DefaultDocumentEvent ev = - new DefaultDocumentEvent(offset, - length, - DocumentEvent.EventType.INSERT); + DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, + length, + DocumentEvent.EventType.INSERT); ev.addEdit(edit); // Finally we must update the document structure and fire the insert @@ -2012,20 +2354,66 @@ public class DefaultStyledDocument extends AbstractDocument /** * Initializes the <code>DefaultStyledDocument</code> with the specified * data. - * - * @param data the specification of the content with which the document is - * initialized + * + * @param data + * the specification of the content with which the document is + * initialized */ protected void create(ElementSpec[] data) { + writeLock(); try { - // Clear content. - content.remove(0, content.length()); - // Clear buffer and root element. - buffer = new ElementBuffer(createDefaultRoot()); - // Insert the data. - insert(0, data); + // Clear content if there is some. + int len = getLength(); + if (len > 0) + remove(0, len); + + // Now we insert the content. + StringBuilder b = new StringBuilder(); + for (int i = 0; i < data.length; ++i) + { + ElementSpec el = data[i]; + if (el.getArray() != null && el.getLength() > 0) + b.append(el.getArray(), el.getOffset(), el.getLength()); + } + Content content = getContent(); + UndoableEdit cEdit = content.insertString(0, b.toString()); + + DefaultDocumentEvent ev = + new DefaultDocumentEvent(0, b.length(), + DocumentEvent.EventType.INSERT); + ev.addEdit(cEdit); + + // We do a little trick here to get the new structure: We instantiate + // a new ElementBuffer with a new root element, insert into that root + // and then reparent the newly created elements to the old root + // element. + BranchElement createRoot = + (BranchElement) createBranchElement(null, null); + Element dummyLeaf = createLeafElement(createRoot, null, 0, 1); + createRoot.replace(0, 0, new Element[]{ dummyLeaf }); + ElementBuffer createBuffer = new ElementBuffer(createRoot); + createBuffer.insert(0, b.length(), data, new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT)); + // Now the new root is the first child of the createRoot. + Element newRoot = createRoot.getElement(0); + BranchElement root = (BranchElement) getDefaultRootElement(); + Element[] added = new Element[newRoot.getElementCount()]; + for (int i = 0; i < added.length; ++i) + { + added[i] = newRoot.getElement(i); + ((AbstractElement) added[i]).element_parent = root; + } + Element[] removed = new Element[root.getElementCount()]; + for (int i = 0; i < removed.length; ++i) + removed[i] = root.getElement(i); + + // Replace the old elements in root with the new and update the event. + root.replace(0, removed.length, added); + ev.addEdit(new ElementEdit(root, 0, removed, added)); + + fireInsertUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); } catch (BadLocationException ex) { @@ -2033,10 +2421,9 @@ public class DefaultStyledDocument extends AbstractDocument err.initCause(ex); throw err; } - } - - static boolean attributeSetsAreSame (AttributeSet a, AttributeSet b) - { - return (a == null && b == null) || (a != null && a.isEqual(b)); + finally + { + writeUnlock(); + } } } diff --git a/libjava/classpath/javax/swing/text/DefaultTextUI.java b/libjava/classpath/javax/swing/text/DefaultTextUI.java index e7ff018..c347668 100644 --- a/libjava/classpath/javax/swing/text/DefaultTextUI.java +++ b/libjava/classpath/javax/swing/text/DefaultTextUI.java @@ -45,6 +45,7 @@ import javax.swing.plaf.basic.BasicTextUI; * all text components is now {@link BasicTextUI}. * * @author Roman Kennke (kennke@aicas.com) + * @deprecated as of 1.5 use {@link BasicTextUI} instead */ public abstract class DefaultTextUI extends BasicTextUI { diff --git a/libjava/classpath/javax/swing/text/FlowView.java b/libjava/classpath/javax/swing/text/FlowView.java index 6d4b9cd..8be8f41 100644 --- a/libjava/classpath/javax/swing/text/FlowView.java +++ b/libjava/classpath/javax/swing/text/FlowView.java @@ -38,14 +38,10 @@ exception statement from your version. */ package javax.swing.text; -import java.awt.Container; -import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; -import java.util.Iterator; -import java.util.Vector; -import javax.swing.SwingConstants; +import javax.swing.SizeRequirements; import javax.swing.event.DocumentEvent; /** @@ -89,7 +85,7 @@ public abstract class FlowView extends BoxView */ public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { - layout(fv); + // The default implementation does nothing. } /** @@ -105,7 +101,7 @@ public abstract class FlowView extends BoxView */ public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { - layout(fv); + // The default implementation does nothing. } /** @@ -121,7 +117,7 @@ public abstract class FlowView extends BoxView */ public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { - layout(fv); + // The default implementation does nothing. } /** @@ -131,7 +127,7 @@ public abstract class FlowView extends BoxView * * @return the logical view of the managed <code>FlowView</code> */ - public View getLogicalView(FlowView fv) + protected View getLogicalView(FlowView fv) { return fv.layoutPool; } @@ -166,43 +162,60 @@ public abstract class FlowView extends BoxView * Lays out one row of the flow view. This is called by {@link #layout} * to fill one row with child views until the available span is exhausted. * + * The default implementation fills the row by calling + * {@link #createView(FlowView, int, int, int)} until the available space + * is exhausted, a forced break is encountered or there are no more views + * in the logical view. If the available space is exhausted, + * {@link #adjustRow(FlowView, int, int, int)} is called to fit the row + * into the available span. + * * @param fv the flow view for which we perform the layout * @param rowIndex the index of the row - * @param pos the start position for the row + * @param pos the model position for the beginning of the row * * @return the start position of the next row */ protected int layoutRow(FlowView fv, int rowIndex, int pos) { - int spanLeft = fv.getFlowSpan(rowIndex); - if (spanLeft <= 0) - return -1; - - int offset = pos; View row = fv.getView(rowIndex); - int flowAxis = fv.getFlowAxis(); + int axis = fv.getFlowAxis(); + int span = fv.getFlowSpan(rowIndex); + int x = fv.getFlowStart(rowIndex); + int offset = pos; + View logicalView = getLogicalView(fv); + // Special case when span == 0. We need to layout the row as if it had + // a span of Integer.MAX_VALUE. + if (span == 0) + span = Integer.MAX_VALUE; - while (spanLeft > 0) + while (span > 0) { - View child = createView(fv, offset, spanLeft, rowIndex); - if (child == null) - { - offset = -1; - break; - } - - int span = (int) child.getPreferredSpan(flowAxis); - if (span > spanLeft) - { - offset = -1; - break; - } - - row.append(child); - spanLeft -= span; - offset = child.getEndOffset(); + if (logicalView.getViewIndex(offset, Position.Bias.Forward) == -1) + break; + View view = createView(fv, offset, span, rowIndex); + if (view == null) + break; + int viewSpan = (int) view.getPreferredSpan(axis); + row.append(view); + int breakWeight = view.getBreakWeight(axis, x, span); + if (breakWeight >= View.ForcedBreakWeight) + break; + x += viewSpan; + span -= viewSpan; + offset += (view.getEndOffset() - view.getStartOffset()); } - return offset; + if (span < 0) + { + int flowStart = fv.getFlowStart(axis); + int flowSpan = fv.getFlowSpan(axis); + adjustRow(fv, rowIndex, flowSpan, flowStart); + int rowViewCount = row.getViewCount(); + if (rowViewCount > 0) + offset = row.getView(rowViewCount - 1).getEndOffset(); + else + offset = -1; + } + return offset != pos ? offset : -1; } /** @@ -212,189 +225,106 @@ public abstract class FlowView extends BoxView * available span and can be broken down) or <code>null</code> (if it does * not fit in the available span and also cannot be broken down). * + * The default implementation fetches the logical view at the specified + * <code>startOffset</code>. If that view has a different startOffset than + * specified in the argument, a fragment is created using + * {@link View#createFragment(int, int)} that has the correct startOffset + * and the logical view's endOffset. + * * @param fv the flow view - * @param offset the start offset for the view to be created + * @param startOffset the start offset for the view to be created * @param spanLeft the available span * @param rowIndex the index of the row * * @return a view to fill the row with, or <code>null</code> if there * is no view or view fragment that fits in the available span */ - protected View createView(FlowView fv, int offset, int spanLeft, + protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) { - // Find the logical element for the given offset. - 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? - return child.breakView(flowAxis, offset, 0, spanLeft); - else - return null; - } - } - - /** - * This special subclass of <code>View</code> is used to represent - * the logical representation of this view. It does not support any - * visual representation, this is handled by the physical view implemented - * in the <code>FlowView</code>. - */ - class LogicalView extends View - { - /** - * The child views of this logical view. - */ - Vector children; - - /** - * Creates a new LogicalView instance. - */ - LogicalView(Element el) - { - super(el); - children = new Vector(); - } - - /** - * Returns the container that holds this view. The logical view returns - * the enclosing FlowView's container here. - * - * @return the container that holds this view - */ - public Container getContainer() - { - return FlowView.this.getContainer(); - } - - /** - * Returns the number of child views of this logical view. - * - * @return the number of child views of this logical view - */ - public int getViewCount() - { - return children.size(); - } - - /** - * Returns the child view at the specified index. - * - * @param index the index - * - * @return the child view at the specified index - */ - public View getView(int index) - { - return (View) children.get(index); - } - - /** - * Replaces some child views with other child views. - * - * @param offset the offset at which to replace child views - * @param length the number of children to remove - * @param views the views to be inserted - */ - public void replace(int offset, int length, View[] views) - { - if (length > 0) - { - for (int count = 0; count < length; ++count) - children.remove(offset); - } - - int endOffset = offset + views.length; - for (int i = offset; i < endOffset; ++i) - { - children.add(i, views[i - offset]); - // Set the parent of the child views to the flow view itself so - // it has something to resolve. - views[i - offset].setParent(FlowView.this); - } + View logicalView = getLogicalView(fv); + // FIXME: Handle the bias thing correctly. + int index = logicalView.getViewIndex(startOffset, Position.Bias.Forward); + View retVal = null; + if (index >= 0) + { + retVal = logicalView.getView(index); + if (retVal.getStartOffset() != startOffset) + retVal = retVal.createFragment(startOffset, retVal.getEndOffset()); + } + return retVal; } /** - * Returns the index of the child view that contains the specified - * position in the document model. + * Tries to adjust the specified row to fit within the desired span. The + * default implementation iterates through the children of the specified + * row to find the view that has the highest break weight and - if there + * is more than one view with such a break weight - which is nearest to + * the end of the row. If there is such a view that has a break weight > + * {@link View#BadBreakWeight}, this view is broken using the + * {@link View#breakView(int, int, float, float)} method and this view and + * all views after the now broken view are replaced by the broken view. * - * @param pos the position for which we are searching the child view - * @param b the bias - * - * @return the index of the child view that contains the specified - * position in the document model + * @param fv the flow view + * @param rowIndex the index of the row to be adjusted + * @param desiredSpan the layout span + * @param x the X location at which the row starts */ - public int getViewIndex(int pos, Position.Bias b) - { - int index = -1; - int i = 0; - for (Iterator it = children.iterator(); it.hasNext(); i++) + protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) { + // Determine the last view that has the highest break weight. + int axis = fv.getFlowAxis(); + View row = fv.getView(rowIndex); + int count = row.getViewCount(); + int breakIndex = -1; + int maxBreakWeight = View.BadBreakWeight; + int breakX = x; + int breakSpan = desiredSpan; + int currentX = x; + int currentSpan = desiredSpan; + for (int i = 0; i < count; ++i) { - View child = (View) it.next(); - if (child.getStartOffset() >= pos - && child.getEndOffset() < pos) + View view = row.getView(i); + int weight = view.getBreakWeight(axis, currentX, currentSpan); + if (weight >= maxBreakWeight) { - index = i; - break; + breakIndex = i; + breakX = currentX; + breakSpan = currentSpan; + maxBreakWeight = weight; } + int size = (int) view.getPreferredSpan(axis); + currentX += size; + currentSpan -= size; } - return index; - } - /** - * Throws an AssertionError because it must never be called. LogicalView - * only serves as a holder for child views and has no visual - * representation. - */ - public float getPreferredSpan(int axis) - { - throw new AssertionError("This method must not be called in " - + "LogicalView."); - } - - /** - * Throws an AssertionError because it must never be called. LogicalView - * only serves as a holder for child views and has no visual - * representation. - */ - public Shape modelToView(int pos, Shape a, Position.Bias b) - throws BadLocationException - { - throw new AssertionError("This method must not be called in " - + "LogicalView."); - } - - /** - * Throws an AssertionError because it must never be called. LogicalView - * only serves as a holder for child views and has no visual - * representation. - */ - public void paint(Graphics g, Shape s) - { - throw new AssertionError("This method must not be called in " - + "LogicalView."); + // If there is a potential break location found, break the row at + // this location. + if (breakIndex > -1) + { + View toBeBroken = row.getView(breakIndex); + View brokenView = toBeBroken.breakView(axis, + toBeBroken.getStartOffset(), + breakX, breakSpan); + row.replace(breakIndex, count - breakIndex, + new View[]{brokenView}); + } } + } + /** + * This special subclass of <code>View</code> is used to represent + * the logical representation of this view. It does not support any + * visual representation, this is handled by the physical view implemented + * in the <code>FlowView</code>. + */ + class LogicalView extends BoxView + { /** - * Throws an AssertionError because it must never be called. LogicalView - * only serves as a holder for child views and has no visual - * representation. + * Creates a new LogicalView instance. */ - public int viewToModel(float x, float y, Shape a, Position.Bias[] b) + LogicalView(Element el, int axis) { - throw new AssertionError("This method must not be called in " - + "LogicalView."); + super(el, axis); } } @@ -424,6 +354,11 @@ public abstract class FlowView extends BoxView protected FlowStrategy strategy; /** + * Indicates if the flow should be rebuild during the next layout. + */ + private boolean layoutDirty; + + /** * Creates a new <code>FlowView</code> for the given * <code>Element</code> and <code>axis</code>. * @@ -436,6 +371,7 @@ public abstract class FlowView extends BoxView { super(element, axis); strategy = sharedStrategy; + layoutDirty = true; } /** @@ -510,16 +446,8 @@ public abstract class FlowView extends BoxView { if (layoutPool == null) { - layoutPool = new LogicalView(getElement()); - - Element el = getElement(); - int count = el.getElementCount(); - for (int i = 0; i < count; ++i) - { - Element childEl = el.getElement(i); - View childView = vf.create(childEl); - layoutPool.append(childView); - } + layoutPool = new LogicalView(getElement(), getAxis()); + layoutPool.setParent(this); } } @@ -534,27 +462,32 @@ public abstract class FlowView extends BoxView */ protected void layout(int width, int height) { - boolean rebuild = false; - int flowAxis = getFlowAxis(); if (flowAxis == X_AXIS) { - rebuild = !(width == layoutSpan); - layoutSpan = width; + if (layoutSpan != width) + { + layoutChanged(Y_AXIS); + layoutSpan = width; + } } else { - rebuild = !(height == layoutSpan); - layoutSpan = height; + if (layoutSpan != height) + { + layoutChanged(X_AXIS); + layoutSpan = height; + } } - if (rebuild) - strategy.layout(this); + if (layoutDirty) + { + strategy.layout(this); + layoutDirty = false; + } - // TODO: If the span along the box axis has changed in the process of - // relayouting the rows (that is, if rows have been added or removed), - // call preferenceChanged in order to throw away cached layout information - // of the surrounding BoxView. + if (getPreferredSpan(getAxis()) != height) + preferenceChanged(this, false, true); super.layout(width, height); } @@ -574,6 +507,7 @@ public abstract class FlowView extends BoxView // be updated accordingly. layoutPool.insertUpdate(changes, a, vf); strategy.insertUpdate(this, changes, getInsideAllocation(a)); + layoutDirty = true; } /** @@ -588,6 +522,7 @@ public abstract class FlowView extends BoxView public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf) { strategy.removeUpdate(this, changes, getInsideAllocation(a)); + layoutDirty = true; } /** @@ -602,6 +537,7 @@ public abstract class FlowView extends BoxView public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf) { strategy.changedUpdate(this, changes, getInsideAllocation(a)); + layoutDirty = true; } /** @@ -640,4 +576,30 @@ public abstract class FlowView extends BoxView } return result; } + + /** + * Calculates the size requirements of this <code>BoxView</code> along + * its minor axis, that is the axis opposite to the axis specified in the + * constructor. + * + * This is overridden and forwards the request to the logical view. + * + * @param axis the axis that is examined + * @param r the <code>SizeRequirements</code> object to hold the result, + * if <code>null</code>, a new one is created + * + * @return the size requirements for this <code>BoxView</code> along + * the specified axis + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements r) + { + // We need to call super here so that the alignment is properly + // calculated. + SizeRequirements res = super.calculateMinorAxisRequirements(axis, r); + res.minimum = (int) layoutPool.getMinimumSpan(axis); + res.preferred = (int) layoutPool.getPreferredSpan(axis); + res.maximum = (int) layoutPool.getMaximumSpan(axis); + return res; + } } diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java index 80dcfa5..28d1d6e 100644 --- a/libjava/classpath/javax/swing/text/GapContent.java +++ b/libjava/classpath/javax/swing/text/GapContent.java @@ -1,5 +1,5 @@ /* GapContent.java -- - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,8 +39,10 @@ exception statement from your version. */ package javax.swing.text; import java.io.Serializable; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.ListIterator; import java.util.Vector; @@ -68,8 +70,8 @@ public class GapContent /** * A {@link Position} implementation for <code>GapContent</code>. */ - class GapContentPosition - implements Position, Comparable + private class GapContentPosition + implements Position, Comparable { /** The index within the buffer array. */ @@ -130,7 +132,7 @@ public class GapContent } } - class InsertUndo extends AbstractUndoableEdit + private class InsertUndo extends AbstractUndoableEdit { public int where, length; String text; @@ -169,7 +171,7 @@ public class GapContent } - class UndoRemove extends AbstractUndoableEdit + private class UndoRemove extends AbstractUndoableEdit { public int where; String text; @@ -206,7 +208,41 @@ public class GapContent } } - + + /** + * Compares WeakReference objects in a List by comparing the referenced + * objects instead. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class WeakPositionComparator + implements Comparator + { + + /** + * Compares two objects of type WeakReference. The objects are compared + * using the referenced objects compareTo() method. + */ + public int compare(Object o1, Object o2) + { + // Unwrap references. + if (o1 instanceof WeakReference) + o1 = ((WeakReference) o1).get(); + if (o2 instanceof WeakReference) + o2 = ((WeakReference) o2).get(); + + GapContentPosition p1 = (GapContentPosition) o1; + GapContentPosition p2 = (GapContentPosition) o2; + + int retVal; + if (p1 == null || p2 == null) + retVal = -1; + else + retVal = p1.compareTo(p2); + return retVal; + } + } + /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = -6226052713477823730L; @@ -233,9 +269,10 @@ public class GapContent /** * The positions generated by this GapContent. They are kept in an ordered - * fashion, so they can be looked up easily. + * fashion, so they can be looked up easily. The value objects will be + * WeakReference objects that in turn hold GapContentPosition objects. */ - ArrayList positions; + private ArrayList positions; /** * Creates a new GapContent object. @@ -310,8 +347,12 @@ public class GapContent int length = length(); int strLen = str.length(); + if (where < 0) + throw new BadLocationException("The where argument cannot be smaller" + + " than the zero", where); + if (where >= length) - throw new BadLocationException("the where argument cannot be greater" + throw new BadLocationException("The where argument cannot be greater" + " than the content length", where); replace(where, 0, str.toCharArray(), strLen); @@ -446,18 +487,22 @@ public class GapContent throw new BadLocationException("The offset was out of the bounds of this" + " buffer", offset); + clearPositionReferences(); + // We store the actual array index in the GapContentPosition. The real // offset is then calculated in the GapContentPosition. int mark = offset; if (offset >= gapStart) mark += gapEnd - gapStart; GapContentPosition pos = new GapContentPosition(mark); + WeakReference r = new WeakReference(pos); // Add this into our list in a sorted fashion. - int index = Collections.binarySearch(positions, pos); + int index = Collections.binarySearch(positions, r, + new WeakPositionComparator()); if (index < 0) index = -(index + 1); - positions.add(index, pos); + positions.add(index, r); return pos; } @@ -557,7 +602,7 @@ public class GapContent assert newGapEnd > gapEnd : "The new gap end must be greater than the " + "old gap end."; - setPositionsInRange(gapEnd, newGapEnd - gapEnd, newGapEnd + 1); + setPositionsInRange(gapEnd, newGapEnd - gapEnd, newGapEnd); gapEnd = newGapEnd; } @@ -566,7 +611,7 @@ public class GapContent * * @return the allocated buffer array */ - protected Object getArray() + protected final Object getArray() { return buffer; } @@ -642,19 +687,30 @@ public class GapContent int endOffset = offset + length; int index1 = Collections.binarySearch(positions, - new GapContentPosition(offset)); + new GapContentPosition(offset), + new WeakPositionComparator()); if (index1 < 0) index1 = -(index1 + 1); // Search the first index with the specified offset. The binarySearch does // not necessarily find the first one. - while (index1 > 0 - && ((GapContentPosition) positions.get(index1 - 1)).mark == offset) - index1--; + while (index1 > 0) + { + WeakReference r = (WeakReference) positions.get(index1 - 1); + GapContentPosition p = (GapContentPosition) r.get(); + if (p != null && p.mark == offset || p == null) + index1--; + else + break; + } for (ListIterator i = positions.listIterator(index1); i.hasNext();) { - GapContentPosition p = (GapContentPosition) i.next(); + WeakReference r = (WeakReference) i.next(); + GapContentPosition p = (GapContentPosition) r.get(); + if (p == null) + continue; + if (p.mark > endOffset) break; if (p.mark >= offset && p.mark <= endOffset) @@ -672,24 +728,35 @@ public class GapContent * @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) + private void setPositionsInRange(int offset, int length, int value) { int endOffset = offset + length; int index1 = Collections.binarySearch(positions, - new GapContentPosition(offset)); + new GapContentPosition(offset), + new WeakPositionComparator()); if (index1 < 0) index1 = -(index1 + 1); // Search the first index with the specified offset. The binarySearch does // not necessarily find the first one. - while (index1 > 0 - && ((GapContentPosition) positions.get(index1 - 1)).mark == offset) - index1--; + while (index1 > 0) + { + WeakReference r = (WeakReference) positions.get(index1 - 1); + GapContentPosition p = (GapContentPosition) r.get(); + if (p != null && p.mark == offset || p == null) + index1--; + else + break; + } for (ListIterator i = positions.listIterator(index1); i.hasNext();) { - GapContentPosition p = (GapContentPosition) i.next(); + WeakReference r = (WeakReference) i.next(); + GapContentPosition p = (GapContentPosition) r.get(); + if (p == null) + continue; + if (p.mark > endOffset) break; @@ -707,23 +774,35 @@ public class GapContent * @param length the length of the range to search * @param incr the increment */ - void adjustPositionsInRange(int offset, int length, int incr) + private void adjustPositionsInRange(int offset, int length, int incr) { int endOffset = offset + length; int index1 = Collections.binarySearch(positions, - new GapContentPosition(offset)); + new GapContentPosition(offset), + new WeakPositionComparator()); if (index1 < 0) index1 = -(index1 + 1); // Search the first index with the specified offset. The binarySearch does // not necessarily find the first one. - while (index1 > 0 - && ((GapContentPosition) positions.get(index1 - 1)).mark == offset) - index1--; + while (index1 > 0) + { + WeakReference r = (WeakReference) positions.get(index1 - 1); + GapContentPosition p = (GapContentPosition) r.get(); + if (p != null && p.mark == offset || p == null) + index1--; + else + break; + } + for (ListIterator i = positions.listIterator(index1); i.hasNext();) { - GapContentPosition p = (GapContentPosition) i.next(); + WeakReference r = (WeakReference) i.next(); + GapContentPosition p = (GapContentPosition) r.get(); + if (p == null) + continue; + if (p.mark > endOffset) break; @@ -747,6 +826,17 @@ public class GapContent } /** + * @specnote This method is not very well specified and the positions vector + * is implementation specific. The undo positions are managed + * differently in this implementation, this method is only here + * for binary compatibility. + */ + protected void updateUndoPositions(Vector positions, int offset, int length) + { + // We do nothing here. + } + + /** * 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. @@ -776,8 +866,23 @@ public class GapContent { for (Iterator i = positions.iterator(); i.hasNext();) { - GapContentPosition pos = (GapContentPosition) i.next(); + WeakReference r = (WeakReference) i.next(); + GapContentPosition pos = (GapContentPosition) r.get(); System.err.println("position at: " + pos.mark); } } + + /** + * Clears all GC'ed references in the positions array. + */ + private void clearPositionReferences() + { + Iterator i = positions.iterator(); + while (i.hasNext()) + { + WeakReference r = (WeakReference) i.next(); + if (r.get() == null) + i.remove(); + } + } } diff --git a/libjava/classpath/javax/swing/text/GlyphView.java b/libjava/classpath/javax/swing/text/GlyphView.java index 47deb50..d505274 100644 --- a/libjava/classpath/javax/swing/text/GlyphView.java +++ b/libjava/classpath/javax/swing/text/GlyphView.java @@ -277,38 +277,41 @@ public class GlyphView extends View implements TabableView, Cloneable public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1) { + Color oldColor = g.getColor(); int height = (int) getHeight(view); Segment txt = view.getText(p0, p1); Rectangle bounds = a.getBounds(); - TabExpander tabEx = null; View parent = view.getParent(); if (parent instanceof TabExpander) tabEx = (TabExpander) parent; - // 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); - + // Fill the background of the text run. + Color background = view.getBackground(); + if (background != null) + { + g.setColor(background); + g.fillRect(bounds.x, bounds.y, width, height); + } // Draw the actual text. g.setColor(view.getForeground()); g.setFont(view.getFont()); + int ascent = g.getFontMetrics().getAscent(); if (view.isSuperscript()) // TODO: Adjust font for superscripting. - Utilities.drawTabbedText(txt, bounds.x, bounds.y - height / 2, g, tabEx, - txt.offset); + Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent - height / 2, + g, tabEx, txt.offset); else if (view.isSubscript()) // TODO: Adjust font for subscripting. - Utilities.drawTabbedText(txt, bounds.x, bounds.y + height / 2, g, tabEx, - txt.offset); + Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent + height / 2, + g, tabEx, txt.offset); else - Utilities.drawTabbedText(txt, bounds.x, bounds.y, g, tabEx, + Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx, txt.offset); - if (view.isStikeThrough()) + if (view.isStrikeThrough()) { int strikeHeight = (int) (getAscent(view) / 2); g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width, @@ -320,6 +323,7 @@ public class GlyphView extends View implements TabableView, Cloneable g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width, bounds.y + lineHeight); } + g.setColor(oldColor); } /** @@ -470,12 +474,12 @@ public class GlyphView extends View implements TabableView, Cloneable /** * The start offset within the document for this view. */ - int startOffset; + private int startOffset; /** * The end offset within the document for this view. */ - int endOffset; + private int endOffset; /** * Creates a new <code>GlyphView</code> for the given <code>Element</code>. @@ -485,8 +489,8 @@ public class GlyphView extends View implements TabableView, Cloneable public GlyphView(Element element) { super(element); - startOffset = element.getStartOffset(); - endOffset = element.getEndOffset(); + startOffset = -1; + endOffset = -1; } /** @@ -534,8 +538,7 @@ public class GlyphView extends View implements TabableView, Cloneable { Element el = getElement(); checkPainter(); - getGlyphPainter().paint(this, g, a, el.getStartOffset(), - el.getEndOffset()); + getGlyphPainter().paint(this, g, a, getStartOffset(), getEndOffset()); } @@ -563,7 +566,8 @@ public class GlyphView extends View implements TabableView, Cloneable tabEx, 0.F); } else - span = painter.getHeight(this); + span = painter.getHeight(this); + return span; } @@ -682,7 +686,10 @@ public class GlyphView extends View implements TabableView, Cloneable */ public int getStartOffset() { - return startOffset; + int start = startOffset; + if (start < 0) + start = super.getStartOffset(); + return start; } /** @@ -694,7 +701,10 @@ public class GlyphView extends View implements TabableView, Cloneable */ public int getEndOffset() { - return endOffset; + int end = endOffset; + if (end < 0) + end = super.getEndOffset(); + return end; } /** @@ -771,7 +781,11 @@ public class GlyphView extends View implements TabableView, Cloneable { Element el = getElement(); AttributeSet atts = el.getAttributes(); - return StyleConstants.getBackground(atts); + // We cannot use StyleConstants.getBackground() here, because that returns + // BLACK as default (when background == null). What we need is the + // background setting of the text component instead, which is what we get + // when background == null anyway. + return (Color) atts.getAttribute(StyleConstants.Background); } /** @@ -782,7 +796,7 @@ public class GlyphView extends View implements TabableView, Cloneable * * @return whether the text should be rendered strike-through or not */ - public boolean isStikeThrough() + public boolean isStrikeThrough() { Element el = getElement(); AttributeSet atts = el.getAttributes(); @@ -876,13 +890,15 @@ public class GlyphView extends View implements TabableView, Cloneable 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); + int start = getStartOffset(); + int length = getEndOffset() - start; + getDocument().getText(start, length, txt); } catch (BadLocationException ex) { @@ -891,11 +907,10 @@ public class GlyphView extends View implements TabableView, Cloneable err.initCause(ex); throw err; } - lineBreaker.setText(txt); - int goodBreakLocation = lineBreaker.previous(); - if (goodBreakLocation != BreakIterator.DONE) - breakLocation = goodBreakLocation; - + int breakLocation = + Utilities.getBreakLocation(txt, getContainer().getFontMetrics(getFont()), + (int) pos, (int) (pos + len), + getTabExpander(), p0); View brokenView = createFragment(p0, breakLocation); return brokenView; } @@ -922,23 +937,24 @@ public class GlyphView extends View implements TabableView, Cloneable 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; + // FIXME: Commented out because the Utilities.getBreakLocation method + // is still buggy. The GoodBreakWeight is a reasonable workaround for + // now. +// int startOffset = getStartOffset(); +// int endOffset = getEndOffset() - 1; +// Segment s = getText(startOffset, endOffset); +// Container c = getContainer(); +// FontMetrics fm = c.getFontMetrics(c.getFont()); +// int x0 = (int) pos; +// int x = (int) (pos + len); +// int breakLoc = Utilities.getBreakLocation(s, fm, x0, x, +// getTabExpander(), +// startOffset); +// if (breakLoc == startOffset || breakLoc == endOffset) +// weight = GoodBreakWeight; +// else +// weight = ExcellentBreakWeight; + weight = GoodBreakWeight; } return weight; } @@ -955,14 +971,14 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - getParent().preferenceChanged(this, true, true); + 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. + * {@link View#preferenceChanged(View, boolean, boolean)} for the + * direction in which the glyphs are rendered. * * @param e the document event describing the change; not used here * @param a the view allocation on screen; not used here @@ -970,7 +986,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - getParent().preferenceChanged(this, true, false); + preferenceChanged(this, true, false); } /** @@ -985,7 +1001,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - getParent().preferenceChanged(this, true, false); + preferenceChanged(this, true, false); } /** @@ -1000,8 +1016,10 @@ public class GlyphView extends View implements TabableView, Cloneable public View createFragment(int p0, int p1) { GlyphView fragment = (GlyphView) clone(); - fragment.startOffset = p0; - fragment.endOffset = p1; + if (p0 != getStartOffset()) + fragment.startOffset = p0; + if (p1 != getEndOffset()) + fragment.endOffset = p1; return fragment; } diff --git a/libjava/classpath/javax/swing/text/IconView.java b/libjava/classpath/javax/swing/text/IconView.java index af2581a..699cda9 100644 --- a/libjava/classpath/javax/swing/text/IconView.java +++ b/libjava/classpath/javax/swing/text/IconView.java @@ -130,8 +130,6 @@ public class IconView throws BadLocationException { Element el = getElement(); - if (pos < el.getStartOffset() || pos >= el.getEndOffset()) - throw new BadLocationException("Illegal offset for this view", pos); Rectangle r = a.getBounds(); Icon icon = StyleConstants.getIcon(el.getAttributes()); return new Rectangle(r.x, r.y, icon.getIconWidth(), icon.getIconHeight()); diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java index afa1f24..6b8348c 100644 --- a/libjava/classpath/javax/swing/text/JTextComponent.java +++ b/libjava/classpath/javax/swing/text/JTextComponent.java @@ -60,7 +60,9 @@ import java.util.Enumeration; import java.util.Hashtable; import javax.accessibility.Accessible; +import javax.accessibility.AccessibleAction; import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleEditableText; import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleStateSet; import javax.accessibility.AccessibleText; @@ -86,200 +88,415 @@ public abstract class JTextComponent extends JComponent implements Scrollable, Accessible { /** - * AccessibleJTextComponent + * This class implements accessibility support for the JTextComponent class. + * It provides an implementation of the Java Accessibility API appropriate + * to menu user-interface elements. */ - // FIXME: This inner class is a complete stub and needs to be implemented. - public class AccessibleJTextComponent extends AccessibleJComponent - implements AccessibleText, CaretListener, DocumentListener + public class AccessibleJTextComponent extends AccessibleJComponent implements + AccessibleText, CaretListener, DocumentListener, AccessibleAction, + AccessibleEditableText { private static final long serialVersionUID = 7664188944091413696L; + /** The caret's offset. */ + int dot = 0; + + /** The current JTextComponent. */ + JTextComponent textComp = JTextComponent.this; + /** - * Constructor AccessibleJTextComponent + * Constructs an AccessibleJTextComponent. + * Adds a listener to track caret change. */ public AccessibleJTextComponent() { - // Nothing to do here. + super(); + textComp.addCaretListener(this); } /** - * getCaretPosition - * @return int + * Returns the zero-based offset of the caret. Note: The character + * to the right of the caret will have the same index value as the + * offset (the caret is between two characters). + * + * @return offset of caret */ public int getCaretPosition() { - return 0; // TODO + dot = textComp.getCaretPosition(); + return dot; } /** - * getSelectedText - * @return String + * Returns the portion of the text that is selected. + * + * @return null if no text is selected. */ public String getSelectedText() { - return null; // TODO + return textComp.getSelectedText(); } /** - * getSelectionStart - * @return int + * Returns the start offset within the selected text. If there is no + * selection, but there is a caret, the start and end offsets will be + * the same. Return 0 if the text is empty, or the caret position if no selection. + * + * @return index of the start of the text >= 0. */ public int getSelectionStart() { - return 0; // TODO + if (getSelectedText() == null || (textComp.getText().equals(""))) + return 0; + return textComp.getSelectionStart(); } /** - * getSelectionEnd - * @return int + * Returns the end offset within the selected text. If there is no + * selection, but there is a caret, the start and end offsets will + * be the same. Return 0 if the text is empty, or the caret position + * if no selection. + * + * @return index of the end of the text >= 0. */ public int getSelectionEnd() { - return 0; // TODO + if (getSelectedText() == null || (textComp.getText().equals(""))) + return 0; + return textComp.getSelectionEnd(); } /** - * caretUpdate - * @param value0 TODO + * Handles caret updates (fire appropriate property change event, which are + * AccessibleContext.ACCESSIBLE_CARET_PROPERTY and + * AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY). This keeps track of + * the dot position internally. When the caret moves, the internal position + * is updated after firing the event. + * + * @param e - caret event */ - public void caretUpdate(CaretEvent value0) + public void caretUpdate(CaretEvent e) { - // TODO + // TODO: fire appropriate event. + dot = e.getDot(); } /** - * getAccessibleStateSet - * @return AccessibleStateSet + * Returns the accessible state set of this component. + * + * @return the accessible state set of this component */ public AccessibleStateSet getAccessibleStateSet() { - return null; // TODO + AccessibleStateSet state = super.getAccessibleStateSet(); + // TODO: Figure out what state must be added here to the super's state. + return state; } /** - * getAccessibleRole - * @return AccessibleRole + * Returns the accessible role of this component. + * + * @return the accessible role of this component + * + * @see AccessibleRole */ public AccessibleRole getAccessibleRole() { - return null; // TODO + return AccessibleRole.TEXT; } /** - * getAccessibleText - * @return AccessibleText + * Returns the AccessibleEditableText interface for this text component. + * + * @return this + */ + public AccessibleEditableText getAccessibleEditableText() + { + return this; + } + + /** + * Get the AccessibleText associated with this object. In the implementation + * of the Java Accessibility API for this class, return this object, + * which is responsible for implementing the AccessibleText interface on + * behalf of itself. + * + * @return this + * + * @see AccessibleText */ public AccessibleText getAccessibleText() { - return null; // TODO + return this; } - + /** - * insertUpdate - * @param value0 TODO + * Insert update. Fire appropriate property change event which + * is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY. + * + * @param e - document event */ - public void insertUpdate(DocumentEvent value0) + public void insertUpdate(DocumentEvent e) { // TODO } /** - * removeUpdate - * @param value0 TODO + * Remove update. Fire appropriate property change event which + * is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY. + * + * @param e - document event */ - public void removeUpdate(DocumentEvent value0) + public void removeUpdate(DocumentEvent e) { // TODO } /** - * changedUpdate - * @param value0 TODO + * Changed update. Fire appropriate property change event which + * is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY. + * + * @param e - document event */ - public void changedUpdate(DocumentEvent value0) + public void changedUpdate(DocumentEvent e) { // TODO } /** - * getIndexAtPoint - * @param value0 TODO - * @return int + * Given a point in the coordinate system of this object, return the + * 0-based index of the character at that point, or -1 if there is none. + * + * @param p the point to look at + * @return the character index, or -1 */ - public int getIndexAtPoint(Point value0) + public int getIndexAtPoint(Point p) { return 0; // TODO } /** - * getRootEditorRect - * @return Rectangle + * Determines the bounding box of the indexed character. Returns an empty + * rectangle if the index is out of bounds. The bounds are returned in local coordinates. + * If the index is invalid a null rectangle is returned. The screen coordinates returned are + * "unscrolled coordinates" if the JTextComponent is contained in a JScrollPane in which + * case the resulting rectangle should be composed with the parent coordinates. + * Note: the JTextComponent must have a valid size (e.g. have been added to a parent + * container whose ancestor container is a valid top-level window) for this method to + * be able to return a meaningful (non-null) value. + * + * @param index the 0-based character index + * @return the bounding box, may be empty or null. */ - Rectangle getRootEditorRect() + public Rectangle getCharacterBounds(int index) { - return null; + return null; // TODO } /** - * getCharacterBounds - * @param value0 TODO - * @return Rectangle + * Return the number of characters. + * + * @return the character count */ - public Rectangle getCharacterBounds(int value0) + public int getCharCount() + { + return textComp.getText().length(); + } + + /** + * Returns the attributes of a character at an index, or null if the index + * is out of bounds. + * + * @param index the 0-based character index + * @return the character's attributes + */ + public AttributeSet getCharacterAttribute(int index) { return null; // TODO } /** - * getCharCount - * @return int + * Returns the section of text at the index, or null if the index or part + * is invalid. + * + * @param part {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} + * @param index the 0-based character index + * @return the selection of text at that index, or null */ - public int getCharCount() + public String getAtIndex(int part, int index) { - return 0; // TODO + return null; // TODO } /** - * getCharacterAttribute - * @param value0 TODO - * @return AttributeSet + * Returns the section of text after the index, or null if the index or part + * is invalid. + * + * @param part {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} + * @param index the 0-based character index + * @return the selection of text after that index, or null */ - public AttributeSet getCharacterAttribute(int value0) + public String getAfterIndex(int part, int index) { return null; // TODO } /** - * getAtIndex - * @param value0 TODO - * @param value1 TODO - * @return String + * Returns the section of text before the index, or null if the index or part + * is invalid. + * + * @param part {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE} + * @param index the 0-based character index + * @return the selection of text before that index, or null */ - public String getAtIndex(int value0, int value1) + public String getBeforeIndex(int part, int index) { return null; // TODO } + + /** + * Get the number possible actions for this object, with the zeroth + * representing the default action. + * + * @return the 0-based number of actions + */ + public int getAccessibleActionCount() + { + return 0; // TODO + } + + /** + * Get a description for the specified action. Returns null if out of + * bounds. + * + * @param i + * the action to describe, 0-based + * @return description of the action + */ + public String getAccessibleActionDescription(int i) + { + // TODO: Not implemented fully + return super.getAccessibleDescription(); + } + + /** + * Perform the specified action. Does nothing if out of bounds. + * + * @param i the action to perform, 0-based + * @return true if the action was performed + */ + public boolean doAccessibleAction(int i) + { + return false; // TODO + } + + /** + * Set the text contents to the given string. + * + * @param s the new text + */ + public void setTextContents(String s) + { + // TODO + } /** - * getAfterIndex - * @param value0 TODO - * @param value1 TODO - * @return String + * Inserts the given string at the specified location. + * + * @param index the index for insertion + * @param s the new text */ - public String getAfterIndex(int value0, int value1) + public void insertTextAtIndex(int index, String s) { - return null; // TODO + replaceText(index, index, s); } /** - * getBeforeIndex - * @param value0 TODO - * @param value1 TODO - * @return String + * Return the text between two points. + * + * @param start the start position, inclusive + * @param end the end position, exclusive */ - public String getBeforeIndex(int value0, int value1) + public String getTextRange(int start, int end) { - return null; // TODO + try + { + return textComp.getText(start, end - start); + } + catch (BadLocationException ble) + { + return ""; + } + } + + /** + * Delete the text between two points. + * + * @param start the start position, inclusive + * @param end the end position, exclusive + */ + public void delete(int start, int end) + { + replaceText(start, end, ""); + } + + /** + * Cut the text between two points to the system clipboard. + * + * @param start the start position, inclusive + * @param end the end position, exclusive + */ + public void cut(int start, int end) + { + textComp.select(start, end); + textComp.cut(); + } + + /** + * Paste the text from the system clipboard at the given index. + * + * @param start the start position + */ + public void paste(int start) + { + textComp.setCaretPosition(start); + textComp.paste(); + } + + /** + * Replace the text between two points with the given string. + * + * @param start the start position, inclusive + * @param end the end position, exclusive + * @param s the string to paste + */ + public void replaceText(int start, int end, String s) + { + textComp.select(start, end); + textComp.replaceSelection(s); + } + + /** + * Select the text between two points. + * + * @param start the start position, inclusive + * @param end the end position, exclusive + */ + public void selectText(int start, int end) + { + textComp.select(start, end); + } + + /** + * Set the attributes of text between two points. + * + * @param start the start position, inclusive + * @param end the end position, exclusive + * @param s the new attribute set for the range + */ + public void setAttributes(int start, int end, AttributeSet s) + { + // TODO } } @@ -945,7 +1162,7 @@ public abstract class JTextComponent extends JComponent */ public AccessibleContext getAccessibleContext() { - return null; + return new AccessibleJTextComponent(); } public void setMargin(Insets m) @@ -1024,9 +1241,15 @@ public abstract class JTextComponent extends JComponent */ public String getSelectedText() { + int start = getSelectionStart(); + int offset = getSelectionEnd() - start; + + if (offset <= 0) + return null; + try { - return doc.getText(getSelectionStart(), getSelectionEnd()); + return doc.getText(start, offset); } catch (BadLocationException e) { @@ -1375,8 +1598,12 @@ public abstract class JTextComponent extends JComponent // Insert new text. doc.insertString(start, content, null); - // Set dot to new position. - setCaretPosition(start + content.length()); + // Set dot to new position, + dot = start + content.length(); + setCaretPosition(dot); + + // and update it's magic position. + caret.setMagicCaretPosition(modelToView(dot).getLocation()); } catch (BadLocationException e) { @@ -1387,7 +1614,7 @@ public abstract class JTextComponent extends JComponent public boolean getScrollableTracksViewportHeight() { if (getParent() instanceof JViewport) - return ((JViewport) getParent()).getHeight() > getPreferredSize().height; + return getParent().getHeight() > getPreferredSize().height; return false; } @@ -1395,7 +1622,7 @@ public abstract class JTextComponent extends JComponent public boolean getScrollableTracksViewportWidth() { if (getParent() instanceof JViewport) - return ((JViewport) getParent()).getWidth() > getPreferredSize().width; + return getParent().getWidth() > getPreferredSize().width; return false; } diff --git a/libjava/classpath/javax/swing/text/LabelView.java b/libjava/classpath/javax/swing/text/LabelView.java index 4890735..03279c4 100644 --- a/libjava/classpath/javax/swing/text/LabelView.java +++ b/libjava/classpath/javax/swing/text/LabelView.java @@ -109,7 +109,11 @@ public class LabelView extends GlyphView { Element el = getElement(); AttributeSet atts = el.getAttributes(); - background = StyleConstants.getBackground(atts); + // We cannot use StyleConstants.getBackground() here, because that returns + // BLACK as default (when background == null). What we need is the + // background setting of the text component instead, which is what we get + // when background == null anyway. + background = (Color) atts.getAttribute(StyleConstants.Background); foreground = StyleConstants.getForeground(atts); strikeThrough = StyleConstants.isStrikeThrough(atts); subscript = StyleConstants.isSubscript(atts); diff --git a/libjava/classpath/javax/swing/text/MutableAttributeSet.java b/libjava/classpath/javax/swing/text/MutableAttributeSet.java index 2fe9ad5..3728b9c 100644 --- a/libjava/classpath/javax/swing/text/MutableAttributeSet.java +++ b/libjava/classpath/javax/swing/text/MutableAttributeSet.java @@ -1,5 +1,5 @@ /* MutableAttributeSet.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -40,46 +40,78 @@ package javax.swing.text; import java.util.Enumeration; /** - * MutableAttributeSet + * An {@link AttributeSet} that supports modification of the stored + * attributes. + * * @author Andrew Selkirk - * @version 1.0 + * @since 1.2 */ public interface MutableAttributeSet extends AttributeSet { /** - * addAttribute - * @param name TODO - * @param value TODO + * Adds an attribute with the given <code>name</code> and <code>value</code> + * to the set. If the set already contains an attribute with the given + * <code>name</code>, the attribute value is updated. + * + * @param name the attribute name (<code>null</code> not permitted). + * @param value the value (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. */ void addAttribute(Object name, Object value); /** - * addAttributes - * @param attributes TODO + * Adds all the attributes from <code>attributes</code> to this set. + * + * @param attributes the set of attributes to add (<code>null</code> not + * permitted). + * + * @throws NullPointerException if <code>attributes</code> is + * <code>null</code>. */ void addAttributes(AttributeSet attributes); /** - * removeAttribute - * @param name TODO + * Removes the attribute with the specified <code>name</code>, if this + * attribute is defined. This method will only remove an attribute from + * this set, not from the resolving parent. + * + * @param name the attribute name (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>name</code> is <code>null</code>. */ void removeAttribute(Object name); /** - * removeAttributes - * @param names TODO + * Removes the attributes listed in <code>names</code>. + * + * @param names the attribute names (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>names</code> is <code>null</code> + * or contains any <code>null</code> values. */ void removeAttributes(Enumeration names); /** - * removeAttributes - * @param attributes TODO + * 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. + * Since the resolving parent is stored as an attribute, if + * <code>attributes</code> has the same resolving parent as this set, the + * parent will be removed from this set. + * + * @param attributes the attributes (<code>null</code> not permitted). */ void removeAttributes(AttributeSet attributes); /** - * setResolveParent - * @param parent TODO + * Sets the reolving parent for this set. When looking up an attribute, if + * it is not found in this set, then the resolving parent is also used for + * the lookup. + * + * @param parent the parent attribute set (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>parent</code> is <code>null</code>. */ void setResolveParent(AttributeSet parent); } diff --git a/libjava/classpath/javax/swing/text/NavigationFilter.java b/libjava/classpath/javax/swing/text/NavigationFilter.java index 45f58f9..ea9b29d 100644 --- a/libjava/classpath/javax/swing/text/NavigationFilter.java +++ b/libjava/classpath/javax/swing/text/NavigationFilter.java @@ -68,4 +68,31 @@ public class NavigationFilter { fb.setDot(dot, bias); } + + /** + * Returns the next visual position in the specified direction at which one + * would place a caret. The default implementation forwards to the text + * component's root view. Subclasses may wish to restrict that more. + * + * @param c the text component + * @param pos the current model position + * @param bias the bias of <code>pos</code> + * @param dir the direction, one of {@link javax.swing.SwingConstants#NORTH}, + * {@link javax.swing.SwingConstants#SOUTH}, + * {@link javax.swing.SwingConstants#WEST} or + * {@link javax.swing.SwingConstants#EAST} + * @param retBias the bias of the returned position + * + * @return the next model location to place the caret + * + * @throws BadLocationException when <code>pos</code> is not a valid model + * position + */ + public int getNextVisualPositionFrom(JTextComponent c, int pos, + Position.Bias bias, int dir, + Position.Bias[] retBias) + throws BadLocationException + { + return c.getUI().getNextVisualPositionFrom(c, pos, bias, dir, retBias); + } } diff --git a/libjava/classpath/javax/swing/text/ParagraphView.java b/libjava/classpath/javax/swing/text/ParagraphView.java index c486450..15bed78 100644 --- a/libjava/classpath/javax/swing/text/ParagraphView.java +++ b/libjava/classpath/javax/swing/text/ParagraphView.java @@ -63,10 +63,20 @@ 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; + float align; + if (axis == X_AXIS) + align = 0.0F; // TODO: Implement according to justification + else + align = super.getAlignment(axis); + return align; + } + + protected void loadChildren(ViewFactory vf) + { + // Do nothing here. The children are added while layouting. } } @@ -128,17 +138,18 @@ public class ParagraphView extends FlowView implements TabExpander */ public float getAlignment(int axis) { + float align; if (axis == X_AXIS) - return 0.0F; + align = super.getAlignment(axis); else if (getViewCount() > 0) { - float prefHeight = getPreferredSpan(Y_AXIS); float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS); - return (firstRowHeight / 2.F) / prefHeight; + align = (firstRowHeight / 2.F) / prefHeight; } else - return 0.0F; + align = 0.0F; + return align; } /** @@ -229,4 +240,184 @@ public class ParagraphView extends FlowView implements TabExpander { return tabSet; } + + /** + * Finds the next offset in the document that has one of the characters + * specified in <code>string</code>. If there is no such character found, + * this returns -1. + * + * @param string the characters to search for + * @param start the start offset + * + * @return the next offset in the document that has one of the characters + * specified in <code>string</code> + */ + protected int findOffsetToCharactersInString(char[] string, int start) + { + int offset = -1; + Document doc = getDocument(); + Segment text = new Segment(); + try + { + doc.getText(start, doc.getLength() - start, text); + int index = start; + + searchLoop: + while (true) + { + char ch = text.next(); + if (ch == Segment.DONE) + break; + + for (int j = 0; j < string.length; ++j) + { + if (string[j] == ch) + { + offset = index; + break searchLoop; + } + } + index++; + } + } + catch (BadLocationException ex) + { + // Ignore this and return -1. + } + return offset; + } + + protected int getClosestPositionTo(int pos, Position.Bias bias, Shape a, + int direction, Position.Bias[] biasRet, + int rowIndex, int x) + throws BadLocationException + { + // FIXME: Implement this properly. However, this looks like it might + // have been replaced by viewToModel. + return pos; + } + + /** + * Returns the size that is used by this view (or it's child views) between + * <code>startOffset</code> and <code>endOffset</code>. If the child views + * implement the {@link TabableView} interface, then this is used to + * determine the span, otherwise we use the preferred span of the child + * views. + * + * @param startOffset the start offset + * @param endOffset the end offset + * + * @return the span used by the view between <code>startOffset</code> and + * <code>endOffset</cod> + */ + protected float getPartialSize(int startOffset, int endOffset) + { + int startIndex = getViewIndex(startOffset, Position.Bias.Backward); + int endIndex = getViewIndex(endOffset, Position.Bias.Forward); + float span; + if (startIndex == endIndex) + { + View child = getView(startIndex); + if (child instanceof TabableView) + { + TabableView tabable = (TabableView) child; + span = tabable.getPartialSpan(startOffset, endOffset); + } + else + span = child.getPreferredSpan(X_AXIS); + } + else if (endIndex - startIndex == 1) + { + View child1 = getView(startIndex); + if (child1 instanceof TabableView) + { + TabableView tabable = (TabableView) child1; + span = tabable.getPartialSpan(startOffset, child1.getEndOffset()); + } + else + span = child1.getPreferredSpan(X_AXIS); + View child2 = getView(endIndex); + if (child2 instanceof TabableView) + { + TabableView tabable = (TabableView) child2; + span += tabable.getPartialSpan(child2.getStartOffset(), endOffset); + } + else + span += child2.getPreferredSpan(X_AXIS); + } + else + { + // Start with the first view. + View child1 = getView(startIndex); + if (child1 instanceof TabableView) + { + TabableView tabable = (TabableView) child1; + span = tabable.getPartialSpan(startOffset, child1.getEndOffset()); + } + else + span = child1.getPreferredSpan(X_AXIS); + + // Add up the view spans between the start and the end view. + for (int i = startIndex + 1; i < endIndex; i++) + { + View child = getView(i); + span += child.getPreferredSpan(X_AXIS); + } + + // Add the span of the last view. + View child2 = getView(endIndex); + if (child2 instanceof TabableView) + { + TabableView tabable = (TabableView) child2; + span += tabable.getPartialSpan(child2.getStartOffset(), endOffset); + } + else + span += child2.getPreferredSpan(X_AXIS); + } + return span; + } + + /** + * Returns the location where the tabs are calculated from. This returns + * <code>0.0F</code> by default. + * + * @return the location where the tabs are calculated from + */ + protected float getTabBase() + { + return 0.0F; + } + + /** + * @specnote This method is specified to take a Row parameter, which is a + * private inner class of that class, which makes it unusable from + * application code. Also, this method seems to be replaced by + * {@link FlowStrategy#adjustRow(FlowView, int, int, int)}. + * + */ + protected void adjustRow(Row r, int desiredSpan, int x) + { + } + + /** + * @specnote This method's signature differs from the one defined in + * {@link View} and is therefore never called. It is probably there + * for historical reasons. + */ + public View breakView(int axis, float len, Shape a) + { + // This method is not used. + return null; + } + + /** + * @specnote This method's signature differs from the one defined in + * {@link View} and is therefore never called. It is probably there + * for historical reasons. + */ + public int getBreakWeight(int axis, float len) + { + // This method is not used. + return 0; + } } diff --git a/libjava/classpath/javax/swing/text/PasswordView.java b/libjava/classpath/javax/swing/text/PasswordView.java index e54331c5..9d4c86a 100644 --- a/libjava/classpath/javax/swing/text/PasswordView.java +++ b/libjava/classpath/javax/swing/text/PasswordView.java @@ -107,8 +107,6 @@ public class PasswordView protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { - // FIXME: Throw BadLocationException somehow. - // Update font metrics. updateMetrics(); @@ -119,25 +117,18 @@ public class PasswordView g.setColor(selectedColor); g.setColor(Color.BLACK); - // Initialize buffer for faster drawing of all characters. - int len = p1 - p0; - char[] buffer = new char[len]; - for (int index = 0; index < len; ++index) - buffer[index] = ch; - - // Draw echo charaters. - g.drawChars(buffer, 0, len, x, y); - - // Return new x position right of all drawn characters. - return x + len * metrics.charWidth(ch); + // Draw echo character using drawEchoCharacter() method. + for (int index = p0; index < p1; ++index) + x = drawEchoCharacter(g, x, y, ch); + return x; } /** * Draws unselected text at a given position. * * @param g the <code>Graphics</code> object to draw to - * @param x the x-position - * @param y the y-position + * @param x the x-position of the start of the baseline + * @param y the y-position of the start of the baseline * @param p0 the position of the first character to draw * @param p1 the position of the first character not to draw * @@ -146,35 +137,20 @@ public class PasswordView protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { - // FIXME: Throw BadLocationException somehow. - // Update font metrics. updateMetrics(); // Get echo character. char ch = getEchoChar(); - Segment segment = new Segment(); // Set color for unselected text. g.setColor(unselectedColor); g.setColor(Color.BLACK); - // Initialize buffer for faster drawing of all characters. - p1--; - getDocument().getText(p0, p1 - p0, segment); - int len = segment.toString().length(); - - char[] buffer = new char[len]; - for (int index = 0; index < len; ++index) - buffer[index] = ch; - - y += getPreferredSpan(Y_AXIS)/2; - - // Draw echo charaters. - g.drawChars(buffer, 0, len, x, y); - - // Return new x position right of all drawn characters. - return x + (len * metrics.charWidth(ch)); + // Draw echo character using drawEchoCharacter() method. + for (int index = p0; index < p1; ++index) + x = drawEchoCharacter(g, x, y, ch); + return x; } /** diff --git a/libjava/classpath/javax/swing/text/PlainDocument.java b/libjava/classpath/javax/swing/text/PlainDocument.java index 2200cae..c699dcad2 100644 --- a/libjava/classpath/javax/swing/text/PlainDocument.java +++ b/libjava/classpath/javax/swing/text/PlainDocument.java @@ -1,5 +1,5 @@ /* PlainDocument.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -40,6 +40,15 @@ package javax.swing.text; import java.util.ArrayList; +/** + * A simple document class which maps lines to {@link Element}s. + * + * @author Anthony Balkissoon (abalkiss@redhat.com) + * @author Graydon Hoare (graydon@redhat.com) + * @author Roman Kennke (roman@kennke.org) + * @author Michael Koch (konqueror@gmx.de) + * @author Robert Schuster (robertschuster@fsfe.org) + */ public class PlainDocument extends AbstractDocument { private static final long serialVersionUID = 4758290289196893664L; @@ -109,18 +118,21 @@ public class PlainDocument extends AbstractDocument AttributeSet attributes) { int offset = event.getOffset(); + int eventLength = event.getLength(); int end = offset + event.getLength(); - int elementIndex = rootElement.getElementIndex(offset); + int oldElementIndex, elementIndex = rootElement.getElementIndex(offset); Element firstElement = rootElement.getElement(elementIndex); - + oldElementIndex = elementIndex; + // If we're inserting immediately after a newline we have to fix the - // Element structure. - if (offset > 0) + // Element structure (but only if we are dealing with a line which + // has not existed as Element before). + if (offset > 0 && firstElement.getStartOffset() != offset) { try { String s = getText(offset - 1, 1); - if (s.equals("\n")) + if (s.equals("\n") ) { int newEl2EndOffset = end; boolean replaceNext = false; @@ -159,33 +171,43 @@ public class PlainDocument extends AbstractDocument Element[] added; try { - String str = content.getString(0, content.length()); + String str = content.getString(offset, eventLength); ArrayList elts = new ArrayList(); // Determine how many NEW lines were added by finding the newline // characters within the newly inserted text int j = firstElement.getStartOffset(); - int i = str.indexOf('\n', offset); - while (i != -1 && i <= end) + int i = str.indexOf('\n', 0); + int contentLength = content.length(); + + while (i != -1 && i <= eventLength) { // For each new line, create a new element elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, - j, i + 1)); - j = i + 1; - if (j >= str.length()) - break; - i = str.indexOf('\n', j); + j, offset + i + 1)); + + j = offset + i + 1; + if (j >= contentLength) + break; + i = str.indexOf('\n', i + 1); } + // If there were new lines added we have to add an ElementEdit to // the DocumentEvent and we have to call rootElement.replace to // insert the new lines if (elts.size() != 0) { + // If we have created new lines test whether there are remaining + // characters in firstElement after the inserted text and if so + // create a new element for them. + if (j < firstElement.getEndOffset()) + elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, j, firstElement.getEndOffset())); + // Set up the ElementEdit by filling the added and removed // arrays with the proper Elements added = new Element[elts.size()]; - for (int k = 0; k < elts.size(); ++k) - added[k] = (Element) elts.get(k); + elts.toArray(added); + removed[0] = firstElement; // Now create and add the ElementEdit @@ -204,6 +226,7 @@ public class PlainDocument extends AbstractDocument ae.initCause(e); throw ae; } + super.insertUpdate(event, attributes); } diff --git a/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java index a318ee7..4bb3a8e 100644 --- a/libjava/classpath/javax/swing/text/PlainView.java +++ b/libjava/classpath/javax/swing/text/PlainView.java @@ -1,5 +1,5 @@ /* PlainView.java -- - Copyright (C) 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -46,7 +46,7 @@ import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; -import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent.ElementChange; @@ -137,22 +137,32 @@ public class PlainView extends View implements TabExpander return rect; } + /** + * Draws a line of text. The X and Y coordinates specify the start of + * the <em>baseline</em> of the line. + * + * @param lineIndex the index of the line + * @param g the graphics to use for drawing the text + * @param x the X coordinate of the baseline + * @param y the Y coordinate of the baseline + */ protected void drawLine(int lineIndex, Graphics g, int x, int y) { try { - metrics = g.getFontMetrics(); - // FIXME: Selected text are not drawn yet. - Element line = getElement().getElement(lineIndex); - drawUnselectedText(g, x, y, line.getStartOffset(), line.getEndOffset()); - //drawSelectedText(g, , , , ); + metrics = g.getFontMetrics(); + // FIXME: Selected text are not drawn yet. + Element line = getElement().getElement(lineIndex); + drawUnselectedText(g, x, y, line.getStartOffset(), + line.getEndOffset() - 1); + //drawSelectedText(g, , , , ); } catch (BadLocationException e) - { - AssertionError ae = new AssertionError("Unexpected bad location"); - ae.initCause(e); - throw ae; - } + { + AssertionError ae = new AssertionError("Unexpected bad location"); + ae.initCause(e); + throw ae; + } } protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) @@ -164,6 +174,20 @@ public class PlainView extends View implements TabExpander return Utilities.drawTabbedText(segment, x, y, g, this, 0); } + /** + * Draws a chunk of unselected text. + * + * @param g the graphics to use for drawing the text + * @param x the X coordinate of the baseline + * @param y the Y coordinate of the baseline + * @param p0 the start position in the text model + * @param p1 the end position in the text model + * + * @return the X location of the end of the range + * + * @throws BadLocationException if <code>p0</code> or <code>p1</code> are + * invalid + */ protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { @@ -194,12 +218,12 @@ public class PlainView extends View implements TabExpander // FIXME: Text may be scrolled. Document document = textComponent.getDocument(); Element root = document.getDefaultRootElement(); - int y = rect.y; + int y = rect.y + metrics.getAscent(); for (int i = 0; i < root.getElementCount(); i++) { - drawLine(i, g, rect.x, y); - y += metrics.getHeight(); + drawLine(i, g, rect.x, y); + y += metrics.getHeight(); } } @@ -284,18 +308,20 @@ public class PlainView extends View implements TabExpander // make sure we have the metrics updateMetrics(); - float span = 0; Element el = getElement(); + float span; switch (axis) { case X_AXIS: span = determineMaxLineLength(); + break; case Y_AXIS: default: span = metrics.getHeight() * el.getElementCount(); break; } + return span; } @@ -318,12 +344,19 @@ public class PlainView extends View implements TabExpander 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 was clicked on just by the y-position. + // Since the coordinates may be outside of the coordinate space + // of the allocation area (e.g. user dragged mouse outside + // the component) we have to limit the values. + // This has the nice effect that the user can drag the + // mouse above or below the component and it will still + // react to the x values (e.g. when selecting). + int lineClicked + = Math.min(Math.max((int) (y - rec.y) / metrics.getHeight(), 0), + root.getElementCount() - 1); Element line = root.getElement(lineClicked); + Segment s = getLineBuffer(); int start = line.getStartOffset(); // We don't want the \n at the end of the line. @@ -353,6 +386,8 @@ public class PlainView extends View implements TabExpander */ protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) { + float oldMaxLineLength = maxLineLength; + Rectangle alloc = a.getBounds(); Element el = getElement(); ElementChange ec = changes.getChange(el); @@ -360,7 +395,19 @@ public class PlainView extends View implements TabExpander // repaint the changed line if (ec == null) { - int line = getElement().getElementIndex(changes.getOffset()); + int line = el.getElementIndex(changes.getOffset()); + + // If characters have been removed from the current longest line + // we have to find out which one is the longest now otherwise + // the preferred x-axis span will not shrink. + if (changes.getType() == DocumentEvent.EventType.REMOVE + && el.getElement(line) == longestLine) + { + maxLineLength = -1; + if (determineMaxLineLength() != alloc.width) + preferenceChanged(this, true, false); + } + damageLineRange(line, line, a, getContainer()); return; } @@ -373,12 +420,13 @@ public class PlainView extends View implements TabExpander 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 + // search through all lines and find the longest one again. if (removed != null) { for (int i = 0; i < removed.length; i++) @@ -386,8 +434,11 @@ public class PlainView extends View implements TabExpander { // reset maxLineLength and search through all lines for longest one maxLineLength = -1; - determineMaxLineLength(); + if (determineMaxLineLength() != alloc.width) + preferenceChanged(this, true, removed.length != newElements.length); + ((JTextComponent)getContainer()).repaint(); + return; } } @@ -397,6 +448,7 @@ public class PlainView extends View implements TabExpander { // No lines were added, just repaint the container and exit ((JTextComponent)getContainer()).repaint(); + return; } @@ -445,6 +497,14 @@ public class PlainView extends View implements TabExpander maxLineLength = longestNewLength; longestLine = longestNewLine; } + + // Report any changes to the preferred sizes of the view + // which may cause the underlying component to be revalidated. + boolean widthChanged = oldMaxLineLength != maxLineLength; + boolean heightChanged = removed.length != newElements.length; + if (widthChanged || heightChanged) + preferenceChanged(this, widthChanged, heightChanged); + // Repaint the container ((JTextComponent)getContainer()).repaint(); } @@ -511,7 +571,9 @@ public class PlainView extends View implements TabExpander host.repaint(); else { - Rectangle repaintRec = rec0.union(rec1); + Rectangle repaintRec = SwingUtilities.computeUnion(rec0.x, rec0.y, + rec0.width, + rec0.height, rec1); host.repaint(repaintRec.x, repaintRec.y, repaintRec.width, repaintRec.height); } diff --git a/libjava/classpath/javax/swing/text/Segment.java b/libjava/classpath/javax/swing/text/Segment.java index 84e0e70..875d996 100644 --- a/libjava/classpath/javax/swing/text/Segment.java +++ b/libjava/classpath/javax/swing/text/Segment.java @@ -1,5 +1,5 @@ /* Segment.java -- - Copyright (C) 2002, 2004 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -39,20 +39,40 @@ package javax.swing.text; import java.text.CharacterIterator; +/** + * A text fragment represented by a sequence of characters stored in an array. + */ public class Segment implements Cloneable, CharacterIterator { private boolean partialReturn; + + /** The current index. */ private int current; + /** Storage for the characters (may contain additional characters). */ public char[] array; + + /** The number of characters in the segment. */ public int count; + + /** The offset of the first character in the segment. */ public int offset; + /** + * Creates a new <code>Segment</code>. + */ public Segment() { // Nothing to do here. } + /** + * Creates a new <code>Segment</code>. + * + * @param array the underlying character data. + * @param offset the offset of the first character in the segment. + * @param count the number of characters in the segment. + */ public Segment(char[] array, int offset, int count) { this.array = array; @@ -60,6 +80,12 @@ public class Segment implements Cloneable, CharacterIterator this.count = count; } + /** + * Clones the segment (note that the underlying character array is not cloned, + * just the reference to it). + * + * @return A clone of the segment. + */ public Object clone() { try @@ -72,6 +98,13 @@ public class Segment implements Cloneable, CharacterIterator } } + /** + * Returns the character at the current index. If the segment consists of + * zero characters, or the current index has passed the end of the + * characters in the segment, this method returns {@link #DONE}. + * + * @return The character at the current index. + */ public char current() { if (count == 0 @@ -81,6 +114,14 @@ public class Segment implements Cloneable, CharacterIterator return array[current]; } + /** + * Sets the current index to the first character in the segment and returns + * that character. If the segment contains zero characters, this method + * returns {@link #DONE}. + * + * @return The first character in the segment, or {@link #DONE} if the + * segment contains zero characters. + */ public char first() { if (count == 0) @@ -90,21 +131,46 @@ public class Segment implements Cloneable, CharacterIterator return array[current]; } + /** + * Returns the index of the first character in the segment. + * + * @return The index of the first character. + */ public int getBeginIndex() { return offset; } + /** + * Returns the end index for the segment (one position beyond the last + * character in the segment - note that this can be outside the range of the + * underlying character array). + * + * @return The end index for the segment. + */ public int getEndIndex() { return offset + count; } + /** + * Returns the index of the current character in the segment. + * + * @return The index of the current character. + */ public int getIndex() { return current; } + /** + * Sets the current index to point to the last character in the segment and + * returns that character. If the segment contains zero characters, this + * method returns {@link #DONE}. + * + * @return The last character in the segment, or {@link #DONE} if the + * segment contains zero characters. + */ public char last() { if (count == 0) @@ -114,6 +180,17 @@ public class Segment implements Cloneable, CharacterIterator return array[current]; } + /** + * Sets the current index to point to the next character in the segment and + * returns that character. If the next character position is past the end of + * the segment, the index is set to {@link #getEndIndex()} and the method + * returns {@link #DONE}. If the segment contains zero characters, this + * method returns {@link #DONE}. + * + * @return The next character in the segment or {@link #DONE} (if the next + * character position is past the end of the segment or if the + * segment contains zero characters). + */ public char next() { if (count == 0) @@ -129,6 +206,16 @@ public class Segment implements Cloneable, CharacterIterator return array[current]; } + /** + * Sets the current index to point to the previous character in the segment + * and returns that character. If the current index is equal to + * {@link #getBeginIndex()}, or if the segment contains zero characters, this + * method returns {@link #DONE}. + * + * @return The previous character in the segment or {@link #DONE} (if the + * current character position is at the beginning of the segment or + * if the segment contains zero characters). + */ public char previous() { if (count == 0 @@ -139,11 +226,26 @@ public class Segment implements Cloneable, CharacterIterator return array[current]; } + /** + * Sets the current index and returns the character at that position (or + * {@link #DONE} if the index is equal to {@link #getEndIndex()}. + * + * @param position the current position. + * + * @return The character at the specified <code>position</code>, or + * {@link #DONE} if <code>position</code> is equal to + * {@link #getEndIndex()}. + * + * @throws IllegalArgumentException if <code>position</code> is not in the + * range {@link #getBeginIndex()} to {@link #getEndIndex()}. + */ public char setIndex(int position) { if (position < getBeginIndex() || position > getEndIndex()) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("position: " + position + + ", beginIndex: " + getBeginIndex() + + ", endIndex: " + getEndIndex()); current = position; @@ -153,12 +255,23 @@ public class Segment implements Cloneable, CharacterIterator return array[current]; } + /** + * Returns a <code>String</code> containing the same characters as this + * <code>Segment</code>. + * + * @return A <code>String</code> containing the same characters as this + * <code>Segment</code>. + */ public String toString() { return new String(array, offset, count); } /** + * Sets the partial return flag. + * + * @param p the new value of the flag. + * * @since 1.4 */ public void setPartialReturn(boolean p) @@ -167,6 +280,9 @@ public class Segment implements Cloneable, CharacterIterator } /** + * Returns the partial return flag. + * + * @return The partial return flag. * @since 1.4 */ public boolean isPartialReturn() diff --git a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java index 0c9f607..8dbcb0c 100644 --- a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java +++ b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java @@ -1,5 +1,5 @@ /* SimpleAttributeSet.java -- - Copyright (C) 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -42,33 +42,67 @@ import java.io.Serializable; import java.util.Enumeration; import java.util.Hashtable; +/** + * A set of attributes. + */ public class SimpleAttributeSet implements MutableAttributeSet, Serializable, Cloneable { /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 8267656273837665219L; + /** An empty attribute set. */ public static final AttributeSet EMPTY = new SimpleAttributeSet(); + /** Storage for the attributes. */ Hashtable tab; + /** + * Creates a new attribute set that is initially empty. + */ public SimpleAttributeSet() { - this(null); + tab = new Hashtable(); } + /** + * Creates a new <code>SimpleAttributeSet</code> with the same attributes + * and resolve parent as the specified set. + * + * @param a the attributes (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + */ public SimpleAttributeSet(AttributeSet a) { tab = new Hashtable(); - if (a != null) - addAttributes(a); + addAttributes(a); } + /** + * Adds an attribute with the given <code>name</code> and <code>value</code> + * to the set. If the set already contains an attribute with the given + * <code>name</code>, the attribute value is updated. + * + * @param name the attribute name (<code>null</code> not permitted). + * @param value the value (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. + */ public void addAttribute(Object name, Object value) { tab.put(name, value); } + /** + * Adds all the attributes from <code>attributes</code> to this set. + * + * @param attributes the set of attributes to add (<code>null</code> not + * permitted). + * + * @throws NullPointerException if <code>attributes</code> is + * <code>null</code>. + */ public void addAttributes(AttributeSet attributes) { Enumeration e = attributes.getAttributeNames(); @@ -80,6 +114,11 @@ public class SimpleAttributeSet } } + /** + * Returns a clone of the attribute set. + * + * @return A clone of the attribute set. + */ public Object clone() { SimpleAttributeSet s = new SimpleAttributeSet(); @@ -97,9 +136,18 @@ public class SimpleAttributeSet */ public boolean containsAttribute(Object name, Object value) { - return (tab.containsKey(name) && tab.get(name).equals(value)) || - (getResolveParent() != null && getResolveParent(). - containsAttribute(name, value)); + if (value == null) + throw new NullPointerException("Null 'value' argument."); + if (tab.containsKey(name)) + return tab.get(name).equals(value); + else + { + AttributeSet p = getResolveParent(); + if (p != null) + return p.containsAttribute(name, value); + else + return false; + } } /** @@ -115,6 +163,15 @@ public class SimpleAttributeSet && tab.get(name).equals(value); } + /** + * Returns <code>true</code> of this <code>AttributeSet</code> contains all + * of the specified <code>attributes</code>. + * + * @param attributes the requested attributes + * + * @return <code>true</code> of this <code>AttributeSet</code> contains all + * of the specified <code>attributes</code> + */ public boolean containsAttributes(AttributeSet attributes) { Enumeration e = attributes.getAttributeNames(); @@ -128,11 +185,24 @@ public class SimpleAttributeSet return true; } + /** + * Creates and returns a copy of this <code>AttributeSet</code>. + * + * @return a copy of this <code>AttributeSet</code> + */ public AttributeSet copyAttributes() { return (AttributeSet) clone(); } + /** + * Checks this set for equality with an arbitrary object. + * + * @param obj the object (<code>null</code> permitted). + * + * @return <code>true</code> if this set is equal to <code>obj</code>, and + * <code>false</code> otherwise. + */ public boolean equals(Object obj) { return @@ -140,44 +210,95 @@ public class SimpleAttributeSet && this.isEqual((AttributeSet) obj); } + /** + * Returns the value of the specified attribute, or <code>null</code> if + * there is no attribute with that name. If the attribute is not defined + * directly in this set, the parent hierarchy (if there is one) will be + * used. + * + * @param name the attribute (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>name</code> is <code>null</code>. + */ public Object getAttribute(Object name) { Object val = tab.get(name); if (val != null) return val; - Object p = getResolveParent(); - if (p != null && p instanceof AttributeSet) - return (((AttributeSet)p).getAttribute(name)); + AttributeSet p = getResolveParent(); + if (p != null) + return p.getAttribute(name); return null; } + /** + * Returns the number of attributes stored in this set, plus 1 if a parent + * has been specified (the reference to the parent is stored as a special + * attribute). The attributes stored in the parent do NOT contribute + * to the count. + * + * @return The attribute count. + */ public int getAttributeCount() { return tab.size(); } + /** + * Returns an enumeration of the attribute names. + * + * @return An enumeration of the attribute names. + */ public Enumeration getAttributeNames() { return tab.keys(); } + /** + * Returns the resolving parent. + * + * @return The resolving parent (possibly <code>null</code>). + * + * @see #setResolveParent(AttributeSet) + */ public AttributeSet getResolveParent() { return (AttributeSet) tab.get(ResolveAttribute); } + /** + * Returns a hash code for this instance. + * + * @return A hash code. + */ public int hashCode() { return tab.hashCode(); } + /** + * Returns <code>true</code> if the given attribute is defined in this set, + * and <code>false</code> otherwise. The parent attribute set is not + * checked. + * + * @param attrName the attribute name (<code>null</code> not permitted). + */ public boolean isDefined(Object attrName) { return tab.containsKey(attrName); } + /** + * Returns <code>true</code> if the set contains no attributes, and + * <code>false</code> otherwise. Note that the resolving parent is + * stored as an attribute, so this method will return <code>false</code> if + * a resolving parent is set. + * + * @return <code>true</code> if the set contains no attributes, and + * <code>false</code> otherwise. + */ public boolean isEmpty() { return tab.isEmpty(); @@ -186,7 +307,13 @@ public class SimpleAttributeSet /** * Returns true if the given set has the same number of attributes * as this set and <code>containsAttributes(attr)</code> returns - * true. + * <code>true</code>. + * + * @param attr the attribute set (<code>null</code> not permitted). + * + * @return A boolean. + * + * @throws NullPointerException if <code>attr</code> is <code>null</code>. */ public boolean isEqual(AttributeSet attr) { @@ -194,6 +321,15 @@ public class SimpleAttributeSet && this.containsAttributes(attr); } + /** + * Removes the attribute with the specified <code>name</code>, if this + * attribute is defined. This method will only remove an attribute from + * this set, not from the resolving parent. + * + * @param name the attribute name (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>name</code> is <code>null</code>. + */ public void removeAttribute(Object name) { tab.remove(name); @@ -202,7 +338,12 @@ public class SimpleAttributeSet /** * 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. + * Removes attributes only from this set, not from the resolving parent. + * Since the resolving parent is stored as an attribute, if + * <code>attributes</code> has the same resolving parent as this set, the + * parent will be removed from this set. + * + * @param attributes the attributes (<code>null</code> not permitted). */ public void removeAttributes(AttributeSet attributes) { @@ -216,6 +357,14 @@ public class SimpleAttributeSet } } + /** + * Removes the attributes listed in <code>names</code>. + * + * @param names the attribute names (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>names</code> is <code>null</code> + * or contains any <code>null</code> values. + */ public void removeAttributes(Enumeration names) { while (names.hasMoreElements()) @@ -224,11 +373,31 @@ public class SimpleAttributeSet } } + /** + * Sets the reolving parent for this set. When looking up an attribute, if + * it is not found in this set, then the resolving parent is also used for + * the lookup. + * <p> + * Note that the parent is stored as an attribute, and will contribute 1 to + * the count returned by {@link #getAttributeCount()}. + * + * @param parent the parent attribute set (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>parent</code> is <code>null</code>. + * + * @see #setResolveParent(AttributeSet) + */ public void setResolveParent(AttributeSet parent) { addAttribute(ResolveAttribute, parent); } - + + /** + * Returns a string representation of this instance, typically used for + * debugging purposes. + * + * @return A string representation of this instance. + */ public String toString() { return tab.toString(); diff --git a/libjava/classpath/javax/swing/text/StringContent.java b/libjava/classpath/javax/swing/text/StringContent.java index 7db377a..0a31505 100644 --- a/libjava/classpath/javax/swing/text/StringContent.java +++ b/libjava/classpath/javax/swing/text/StringContent.java @@ -1,5 +1,5 @@ /* StringContent.java -- - Copyright (C) 2005 Free Software Foundation, Inc. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -54,7 +54,8 @@ import javax.swing.undo.UndoableEdit; * * <p>Do not use this class for large size.</p> */ -public final class StringContent implements AbstractDocument.Content, Serializable +public final class StringContent + implements AbstractDocument.Content, Serializable { /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 4755994433709540381L; @@ -87,7 +88,8 @@ public final class StringContent implements AbstractDocument.Content, Serializab try { StringContent.this.checkLocation(this.start, this.length); - this.redoContent = new String(StringContent.this.content, this.start, this.length); + this.redoContent = new String(StringContent.this.content, this.start, + this.length); StringContent.this.remove(this.start, this.length); } catch (BadLocationException b) @@ -175,11 +177,20 @@ public final class StringContent implements AbstractDocument.Content, Serializab } } + /** + * Creates a new instance containing the string "\n". + */ public StringContent() { this(1); } + /** + * Creates a new instance containing the string "\n". + * + * @param initialLength the initial length of the underlying character + * array used to store the content. + */ public StringContent(int initialLength) { super(); @@ -198,7 +209,7 @@ public final class StringContent implements AbstractDocument.Content, Serializab Iterator iter = this.positions.iterator(); while(iter.hasNext()) { - Position p = (Position)iter.next(); + Position p = (Position) iter.next(); if ((offset <= p.getOffset()) && (p.getOffset() <= (offset + length))) refPos.add(p); @@ -206,6 +217,16 @@ public final class StringContent implements AbstractDocument.Content, Serializab return refPos; } + /** + * Creates a position reference for the character at the given offset. The + * position offset will be automatically updated when new characters are + * inserted into or removed from the content. + * + * @param offset the character offset. + * + * @throws BadLocationException if offset is outside the bounds of the + * content. + */ public Position createPosition(int offset) throws BadLocationException { if (offset < this.count || offset > this.count) @@ -215,11 +236,27 @@ public final class StringContent implements AbstractDocument.Content, Serializab return sp; } + /** + * Returns the length of the string content, including the '\n' character at + * the end. + * + * @return The length of the string content. + */ public int length() { return this.count; } + /** + * Inserts <code>str</code> at the given position and returns an + * {@link UndoableEdit} that enables undo/redo support. + * + * @param where the insertion point (must be less than + * <code>length()</code>). + * @param str the string to insert (<code>null</code> not permitted). + * + * @return An object that can undo the insertion. + */ public UndoableEdit insertString(int where, String str) throws BadLocationException { @@ -235,13 +272,15 @@ public final class StringContent implements AbstractDocument.Content, Serializab if (where > 0) System.arraycopy(this.content, 0, temp, 0, where); System.arraycopy(insert, 0, temp, where, insert.length); - System.arraycopy(this.content, where, temp, (where + insert.length), (temp.length - where - insert.length)); + System.arraycopy(this.content, where, temp, (where + insert.length), + (temp.length - where - insert.length)); if (this.content.length < temp.length) this.content = new char[temp.length]; // Copy the result in the original char array. System.arraycopy(temp, 0, this.content, 0, temp.length); // Move all the positions. - Vector refPos = getPositionsInRange(this.positions, where, temp.length - where); + Vector refPos = getPositionsInRange(this.positions, where, + temp.length - where); Iterator iter = refPos.iterator(); while (iter.hasNext()) { @@ -252,20 +291,35 @@ public final class StringContent implements AbstractDocument.Content, Serializab return iundo; } + /** + * Removes the specified range of characters and returns an + * {@link UndoableEdit} that enables undo/redo support. + * + * @param where the starting index. + * @param nitems the number of characters. + * + * @return An object that can undo the removal. + * + * @throws BadLocationException if the character range extends outside the + * bounds of the content OR includes the last character. + */ public UndoableEdit remove(int where, int nitems) throws BadLocationException { - checkLocation(where, nitems); + checkLocation(where, nitems + 1); char[] temp = new char[(this.content.length - nitems)]; this.count = this.count - nitems; - RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where, nitems)); + RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where, + nitems)); // Copy array. System.arraycopy(this.content, 0, temp, 0, where); - System.arraycopy(this.content, where + nitems, temp, where, this.content.length - where - nitems); + System.arraycopy(this.content, where + nitems, temp, where, + this.content.length - where - nitems); this.content = new char[temp.length]; // Then copy the result in the original char array. System.arraycopy(temp, 0, this.content, 0, this.content.length); // Move all the positions. - Vector refPos = getPositionsInRange(this.positions, where, this.content.length + nitems - where); + Vector refPos = getPositionsInRange(this.positions, where, + this.content.length + nitems - where); Iterator iter = refPos.iterator(); while (iter.hasNext()) { @@ -278,31 +332,75 @@ public final class StringContent implements AbstractDocument.Content, Serializab return rundo; } + /** + * Returns a new <code>String</code> containing the characters in the + * specified range. + * + * @param where the start index. + * @param len the number of characters. + * + * @return A string. + * + * @throws BadLocationException if the requested range of characters extends + * outside the bounds of the content. + */ public String getString(int where, int len) throws BadLocationException { checkLocation(where, len); - return new String (this.content, where, len); + return new String(this.content, where, len); } - public void getChars(int where, int len, Segment txt) throws BadLocationException + /** + * Updates <code>txt</code> to contain a direct reference to the underlying + * character array. + * + * @param where the index of the first character. + * @param len the number of characters. + * @param txt a carrier for the return result (<code>null</code> not + * permitted). + * + * @throws BadLocationException if the requested character range is not + * within the bounds of the content. + * @throws NullPointerException if <code>txt</code> is <code>null</code>. + */ + public void getChars(int where, int len, Segment txt) + throws BadLocationException { checkLocation(where, len); - if (txt != null) - { - txt.array = this.content; - txt.offset = where; - txt.count = len; - } + txt.array = this.content; + txt.offset = where; + txt.count = len; } - // This is package-private to avoid an accessor method. + + /** + * @specnote This method is not very well specified and the positions vector + * is implementation specific. The undo positions are managed + * differently in this implementation, this method is only here + * for binary compatibility. + */ + protected void updateUndoPositions(Vector positions) + { + // We do nothing here. + } + + /** + * A utility method that checks the validity of the specified character + * range. + * + * @param where the first character in the range. + * @param len the number of characters in the range. + * + * @throws BadLocationException if the specified range is not within the + * bounds of the content. + */ void checkLocation(int where, int len) throws BadLocationException { if (where < 0) throw new BadLocationException("Invalid location", 1); else if (where > this.count) throw new BadLocationException("Invalid location", this.count); - else if ((where + len)>this.count) + else if ((where + len) > this.count) throw new BadLocationException("Invalid range", this.count); } diff --git a/libjava/classpath/javax/swing/text/StyleConstants.java b/libjava/classpath/javax/swing/text/StyleConstants.java index 598eaf6..c7906b8 100644 --- a/libjava/classpath/javax/swing/text/StyleConstants.java +++ b/libjava/classpath/javax/swing/text/StyleConstants.java @@ -1,5 +1,5 @@ /* StyleConstants.java -- - Copyright (C) 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -43,45 +43,124 @@ import java.awt.Component; import javax.swing.Icon; +/** + * Represents standard attribute keys. This class also contains a set of + * useful static utility methods for querying and populating an + * {@link AttributeSet}. + * + * @since 1.2 + */ public class StyleConstants { + /** + * A value representing left alignment for the + * {@link ParagraphConstants#Alignment} attribute. + */ public static final int ALIGN_LEFT = 0; + + /** + * A value representing center alignment for the + * {@link ParagraphConstants#Alignment} attribute. + */ public static final int ALIGN_CENTER = 1; + + /** + * A value representing right alignment for the + * {@link ParagraphConstants#Alignment} attribute. + */ public static final int ALIGN_RIGHT = 2; + + /** + * A value representing ful justification for the + * {@link ParagraphConstants#Alignment} attribute. + */ public static final int ALIGN_JUSTIFIED = 3; + /** An alias for {@link CharacterConstants#Background}. */ public static final Object Background = CharacterConstants.Background; + + /** An alias for {@link CharacterConstants#BidiLevel}. */ public static final Object BidiLevel = CharacterConstants.BidiLevel; + + /** An alias for {@link CharacterConstants#Bold}. */ public static final Object Bold = CharacterConstants.Bold; - public static final Object ComponentAttribute = CharacterConstants.ComponentAttribute; + + /** An alias for {@link CharacterConstants#ComponentAttribute}. */ + public static final Object ComponentAttribute + = CharacterConstants.ComponentAttribute; + + /** An alias for {@link CharacterConstants#Family}. */ public static final Object Family = CharacterConstants.Family; + + /** An alias for {@link CharacterConstants#Family}. */ public static final Object FontFamily = CharacterConstants.Family; + + /** An alias for {@link CharacterConstants#Size}. */ public static final Object FontSize = CharacterConstants.Size; + + /** An alias for {@link CharacterConstants#Foreground}. */ public static final Object Foreground = CharacterConstants.Foreground; + + /** An alias for {@link CharacterConstants#IconAttribute}. */ public static final Object IconAttribute = CharacterConstants.IconAttribute; + + /** An alias for {@link CharacterConstants#Italic}. */ public static final Object Italic = CharacterConstants.Italic; + + /** An alias for {@link CharacterConstants#Size}. */ public static final Object Size = CharacterConstants.Size; + + /** An alias for {@link CharacterConstants#StrikeThrough}. */ public static final Object StrikeThrough = CharacterConstants.StrikeThrough; + + /** An alias for {@link CharacterConstants#Subscript}. */ public static final Object Subscript = CharacterConstants.Subscript; + + /** An alias for {@link CharacterConstants#Superscript}. */ public static final Object Superscript = CharacterConstants.Superscript; + + /** An alias for {@link CharacterConstants#Underline}. */ public static final Object Underline = CharacterConstants.Underline; + /** An alias for {@link ParagraphConstants#Alignment}. */ public static final Object Alignment = ParagraphConstants.Alignment; - public static final Object FirstLineIndent = ParagraphConstants.FirstLineIndent; + + /** An alias for {@link ParagraphConstants#FirstLineIndent}. */ + public static final Object FirstLineIndent + = ParagraphConstants.FirstLineIndent; + + /** An alias for {@link ParagraphConstants#LeftIndent}. */ public static final Object LeftIndent = ParagraphConstants.LeftIndent; + + /** An alias for {@link ParagraphConstants#LineSpacing}. */ public static final Object LineSpacing = ParagraphConstants.LineSpacing; + + /** An alias for {@link ParagraphConstants#Orientation}. */ public static final Object Orientation = ParagraphConstants.Orientation; + + /** An alias for {@link ParagraphConstants#RightIndent}. */ public static final Object RightIndent = ParagraphConstants.RightIndent; + + /** An alias for {@link ParagraphConstants#SpaceAbove}. */ public static final Object SpaceAbove = ParagraphConstants.SpaceAbove; + + /** An alias for {@link ParagraphConstants#SpaceBelow}. */ public static final Object SpaceBelow = ParagraphConstants.SpaceBelow; + + /** An alias for {@link ParagraphConstants#TabSet}. */ public static final Object TabSet = ParagraphConstants.TabSet; public static final String ComponentElementName = "component"; + public static final String IconElementName = "icon"; - public static final Object ComposedTextAttribute = new StyleConstants("composed text"); + public static final Object ComposedTextAttribute + = new StyleConstants("composed text"); + public static final Object ModelAttribute = new StyleConstants("model"); + public static final Object NameAttribute = new StyleConstants("name"); + public static final Object ResolveAttribute = new StyleConstants("resolver"); String keyname; @@ -93,279 +172,727 @@ public class StyleConstants keyname = k; } + /** + * Returns a string representation of the attribute key. + * + * @return A string representation of the attribute key. + */ public String toString() { return keyname; } + /** + * Returns the alignment specified in the given attributes, or + * {@link #ALIGN_LEFT} if no alignment is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The alignment (typically one of {@link #ALIGN_LEFT}, + * {@link #ALIGN_RIGHT}, {@link #ALIGN_CENTER} or + * {@link #ALIGN_JUSTIFIED}). + * + * @see #setAlignment(MutableAttributeSet, int) + */ public static int getAlignment(AttributeSet a) { - if (a.isDefined(Alignment)) - return ((Integer)a.getAttribute(Alignment)).intValue(); + Integer i = (Integer) a.getAttribute(Alignment); + if (i != null) + return i.intValue(); else return ALIGN_LEFT; } + /** + * Returns the background color specified in the given attributes, or + * {@link Color#BLACK} if no background color is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The background color. + * + * @see #setBackground(MutableAttributeSet, Color) + */ public static Color getBackground(AttributeSet a) { - if (a.isDefined(Background)) - return (Color) a.getAttribute(Background); + Color c = (Color) a.getAttribute(Background); + if (c != null) + return c; else - return Color.WHITE; + return Color.BLACK; } - + + /** + * Returns the bidi level specified in the given attributes, or + * <code>0</code> if no bidi level is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The bidi level. + * + * @see #setBidiLevel(MutableAttributeSet, int) + */ public static int getBidiLevel(AttributeSet a) { - if (a.isDefined(BidiLevel)) - return ((Integer)a.getAttribute(BidiLevel)).intValue(); + Integer i = (Integer) a.getAttribute(BidiLevel); + if (i != null) + return i.intValue(); else return 0; } + /** + * Returns the component specified in the given attributes, or + * <code>null</code> if no component is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The component (possibly <code>null</code>). + * + * @see #setComponent(MutableAttributeSet, Component) + */ public static Component getComponent(AttributeSet a) { - if (a.isDefined(ComponentAttribute)) - return (Component) a.getAttribute(ComponentAttribute); + Component c = (Component) a.getAttribute(ComponentAttribute); + if (c != null) + return c; else - return (Component) null; + return null; } + /** + * Returns the indentation specified in the given attributes, or + * <code>0.0f</code> if no indentation is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The indentation. + * + * @see #setFirstLineIndent(MutableAttributeSet, float) + */ public static float getFirstLineIndent(AttributeSet a) { - if (a.isDefined(FirstLineIndent)) - return ((Float)a.getAttribute(FirstLineIndent)).floatValue(); + Float f = (Float) a.getAttribute(FirstLineIndent); + if (f != null) + return f.floatValue(); else - return 0.f; + return 0.0f; } + /** + * Returns the font family specified in the given attributes, or + * <code>Monospaced</code> if no font family is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The font family. + * + * @see #setFontFamily(MutableAttributeSet, String) + */ public static String getFontFamily(AttributeSet a) { - if (a.isDefined(FontFamily)) - return (String) a.getAttribute(FontFamily); + String ff = (String) a.getAttribute(FontFamily); + if (ff != null) + return ff; else return "Monospaced"; } + /** + * Returns the font size specified in the given attributes, or + * <code>12</code> if no font size is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The font size. + * + * @see #setFontSize(MutableAttributeSet, int) + */ public static int getFontSize(AttributeSet a) { - if (a.isDefined(FontSize)) - return ((Integer)a.getAttribute(FontSize)).intValue(); + Integer i = (Integer) a.getAttribute(FontSize); + if (i != null) + return i.intValue(); else return 12; } + /** + * Returns the foreground color specified in the given attributes, or + * {@link Color#BLACK} if no foreground color is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The foreground color. + * + * @see #setForeground(MutableAttributeSet, Color) + */ public static Color getForeground(AttributeSet a) { - if (a.isDefined(Foreground)) - return (Color) a.getAttribute(Foreground); + Color c = (Color) a.getAttribute(Foreground); + if (c != null) + return c; else return Color.BLACK; } + /** + * Returns the icon specified in the given attributes, or + * <code>null</code> if no icon is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The icon (possibly <code>null</code>). + * + * @see #setIcon(MutableAttributeSet, Icon) + */ public static Icon getIcon(AttributeSet a) { - if (a.isDefined(IconAttribute)) - return (Icon) a.getAttribute(IconAttribute); - else - return (Icon) null; + return (Icon) a.getAttribute(IconAttribute); } + /** + * Returns the left indentation specified in the given attributes, or + * <code>0.0f</code> if no left indentation is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The left indentation. + * + * @see #setLeftIndent(MutableAttributeSet, float) + */ public static float getLeftIndent(AttributeSet a) { - if (a.isDefined(LeftIndent)) - return ((Float)a.getAttribute(LeftIndent)).floatValue(); + Float f = (Float) a.getAttribute(LeftIndent); + if (f != null) + return f.floatValue(); else - return 0.f; + return 0.0f; } + /** + * Returns the line spacing specified in the given attributes, or + * <code>0.0f</code> if no line spacing is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The line spacing. + * + * @see #setLineSpacing(MutableAttributeSet, float) + */ public static float getLineSpacing(AttributeSet a) { - if (a.isDefined(LineSpacing)) - return ((Float)a.getAttribute(LineSpacing)).floatValue(); + Float f = (Float) a.getAttribute(LineSpacing); + if (f != null) + return f.floatValue(); else - return 0.f; + return 0.0f; } + /** + * Returns the right indentation specified in the given attributes, or + * <code>0.0f</code> if no right indentation is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The right indentation. + * + * @see #setRightIndent(MutableAttributeSet, float) + */ public static float getRightIndent(AttributeSet a) { - if (a.isDefined(RightIndent)) - return ((Float)a.getAttribute(RightIndent)).floatValue(); + Float f = (Float) a.getAttribute(RightIndent); + if (f != null) + return f.floatValue(); else - return 0.f; + return 0.0f; } + /** + * Returns the 'space above' specified in the given attributes, or + * <code>0.0f</code> if no 'space above' is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The 'space above'. + * + * @see #setSpaceAbove(MutableAttributeSet, float) + */ public static float getSpaceAbove(AttributeSet a) { - if (a.isDefined(SpaceAbove)) - return ((Float)a.getAttribute(SpaceAbove)).floatValue(); - else - return 0.f; + Float f = (Float) a.getAttribute(SpaceAbove); + if (f != null) + return f.floatValue(); + else + return 0.0f; } + /** + * Returns the 'space below' specified in the given attributes, or + * <code>0.0f</code> if no 'space below' is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The 'space below'. + * + * @see #setSpaceBelow(MutableAttributeSet, float) + */ public static float getSpaceBelow(AttributeSet a) { - if (a.isDefined(SpaceBelow)) - return ((Float)a.getAttribute(SpaceBelow)).floatValue(); + Float f = (Float) a.getAttribute(SpaceBelow); + if (f != null) + return f.floatValue(); else - return 0.f; + return 0.0f; } + /** + * Returns the tab set specified in the given attributes, or + * <code>null</code> if no tab set is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The tab set. + * + * @see #setTabSet(MutableAttributeSet, javax.swing.text.TabSet) + */ public static javax.swing.text.TabSet getTabSet(AttributeSet a) { - if (a.isDefined(StyleConstants.TabSet)) - return (javax.swing.text.TabSet) a.getAttribute(StyleConstants.TabSet); - else - return (javax.swing.text.TabSet) null; + // I'm guessing that the fully qualified class name is to differentiate + // between the TabSet class and the TabSet (attribute) instance on some + // compiler... + return (javax.swing.text.TabSet) a.getAttribute(StyleConstants.TabSet); } + /** + * Returns the value of the bold flag in the given attributes, or + * <code>false</code> if no bold flag is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The bold flag. + * + * @see #setBold(MutableAttributeSet, boolean) + */ public static boolean isBold(AttributeSet a) { - if (a.isDefined(Bold)) - return ((Boolean) a.getAttribute(Bold)).booleanValue(); + Boolean b = (Boolean) a.getAttribute(Bold); + if (b != null) + return b.booleanValue(); else - return false; + return false; } + /** + * Returns the value of the italic flag in the given attributes, or + * <code>false</code> if no italic flag is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The italic flag. + * + * @see #setItalic(MutableAttributeSet, boolean) + */ public static boolean isItalic(AttributeSet a) { - if (a.isDefined(Italic)) - return ((Boolean) a.getAttribute(Italic)).booleanValue(); + Boolean b = (Boolean) a.getAttribute(Italic); + if (b != null) + return b.booleanValue(); else - return false; + return false; } + /** + * Returns the value of the strike-through flag in the given attributes, or + * <code>false</code> if no strike-through flag is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The strike-through flag. + * + * @see #setStrikeThrough(MutableAttributeSet, boolean) + */ public static boolean isStrikeThrough(AttributeSet a) { - if (a.isDefined(StrikeThrough)) - return ((Boolean) a.getAttribute(StrikeThrough)).booleanValue(); + Boolean b = (Boolean) a.getAttribute(StrikeThrough); + if (b != null) + return b.booleanValue(); else - return false; + return false; } + /** + * Returns the value of the subscript flag in the given attributes, or + * <code>false</code> if no subscript flag is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The subscript flag. + * + * @see #setSubscript(MutableAttributeSet, boolean) + */ public static boolean isSubscript(AttributeSet a) { - if (a.isDefined(Subscript)) - return ((Boolean) a.getAttribute(Subscript)).booleanValue(); + Boolean b = (Boolean) a.getAttribute(Subscript); + if (b != null) + return b.booleanValue(); else - return false; + return false; } + /** + * Returns the value of the superscript flag in the given attributes, or + * <code>false</code> if no superscript flag is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The superscript flag. + * + * @see #setSuperscript(MutableAttributeSet, boolean) + */ public static boolean isSuperscript(AttributeSet a) { - if (a.isDefined(Superscript)) - return ((Boolean) a.getAttribute(Superscript)).booleanValue(); - else - return false; + Boolean b = (Boolean) a.getAttribute(Superscript); + if (b != null) + return b.booleanValue(); + else + return false; } + /** + * Returns the value of the underline flag in the given attributes, or + * <code>false</code> if no underline flag is specified. + * + * @param a the attribute set (<code>null</code> not permitted). + * + * @return The underline flag. + * + * @see #setUnderline(MutableAttributeSet, boolean) + */ public static boolean isUnderline(AttributeSet a) { - if (a.isDefined(Underline)) - return ((Boolean) a.getAttribute(Underline)).booleanValue(); + Boolean b = (Boolean) a.getAttribute(Underline); + if (b != null) + return b.booleanValue(); else - return false; + return false; } + /** + * Adds an alignment attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param align the alignment (typically one of + * {@link StyleConstants#ALIGN_LEFT}, + * {@link StyleConstants#ALIGN_RIGHT}, + * {@link StyleConstants#ALIGN_CENTER} or + * {@link StyleConstants#ALIGN_JUSTIFIED}). + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getAlignment(AttributeSet) + */ public static void setAlignment(MutableAttributeSet a, int align) { a.addAttribute(Alignment, new Integer(align)); } - public static void setBackground(MutableAttributeSet a, Color fg) + /** + * Adds a background attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param bg the background (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. + * + * @see #getBackground(AttributeSet) + */ + public static void setBackground(MutableAttributeSet a, Color bg) { - a.addAttribute(Background, fg); + a.addAttribute(Background, bg); } + /** + * Adds a bidi-level attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param lev the level. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getBidiLevel(AttributeSet) + */ public static void setBidiLevel(MutableAttributeSet a, int lev) { a.addAttribute(BidiLevel, new Integer(lev)); } + /** + * Adds a bold attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param b the new value of the bold attribute. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #isBold(AttributeSet) + */ public static void setBold(MutableAttributeSet a, boolean b) { a.addAttribute(Bold, Boolean.valueOf(b)); } + /** + * Adds a component attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param c the component (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. + * + * @see #getComponent(AttributeSet) + */ public static void setComponent(MutableAttributeSet a, Component c) { a.addAttribute(ComponentAttribute, c); } + /** + * Adds a first line indentation attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param i the indentation. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getFirstLineIndent(AttributeSet) + */ public static void setFirstLineIndent(MutableAttributeSet a, float i) { a.addAttribute(FirstLineIndent, new Float(i)); } + /** + * Adds a font family attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param fam the font family name (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. + * + * @see #getFontFamily(AttributeSet) + */ public static void setFontFamily(MutableAttributeSet a, String fam) { a.addAttribute(FontFamily, fam); } + /** + * Adds a font size attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param s the font size (in points). + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getFontSize(AttributeSet) + */ public static void setFontSize(MutableAttributeSet a, int s) { a.addAttribute(FontSize, new Integer(s)); } + /** + * Adds a foreground color attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param fg the foreground color (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. + * + * @see #getForeground(AttributeSet) + */ public static void setForeground(MutableAttributeSet a, Color fg) { a.addAttribute(Foreground, fg); } + /** + * Adds an icon attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param c the icon (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. + * + * @see #getIcon(AttributeSet) + */ public static void setIcon(MutableAttributeSet a, Icon c) { a.addAttribute(IconAttribute, c); } + /** + * Adds an italic attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param b the new value of the italic attribute. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #isItalic(AttributeSet) + */ public static void setItalic(MutableAttributeSet a, boolean b) { a.addAttribute(Italic, Boolean.valueOf(b)); } + /** + * Adds a left indentation attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param i the indentation. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getLeftIndent(AttributeSet) + */ public static void setLeftIndent(MutableAttributeSet a, float i) { a.addAttribute(LeftIndent, new Float(i)); } + /** + * Adds a line spacing attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param i the line spacing. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getLineSpacing(AttributeSet) + */ public static void setLineSpacing(MutableAttributeSet a, float i) { a.addAttribute(LineSpacing, new Float(i)); } + /** + * Adds a right indentation attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param i the right indentation. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getRightIndent(AttributeSet) + */ public static void setRightIndent(MutableAttributeSet a, float i) { a.addAttribute(RightIndent, new Float(i)); } + /** + * Adds a 'space above' attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param i the space above attribute value. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getSpaceAbove(AttributeSet) + */ public static void setSpaceAbove(MutableAttributeSet a, float i) { a.addAttribute(SpaceAbove, new Float(i)); } + /** + * Adds a 'space below' attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param i the space below attribute value. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #getSpaceBelow(AttributeSet) + */ public static void setSpaceBelow(MutableAttributeSet a, float i) { a.addAttribute(SpaceBelow, new Float(i)); } + /** + * Adds a strike-through attribue to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param b the strike-through attribute value. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #isStrikeThrough(AttributeSet) + */ public static void setStrikeThrough(MutableAttributeSet a, boolean b) { a.addAttribute(StrikeThrough, Boolean.valueOf(b)); } + /** + * Adds a subscript attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param b the subscript attribute value. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #isSubscript(AttributeSet) + */ public static void setSubscript(MutableAttributeSet a, boolean b) { a.addAttribute(Subscript, Boolean.valueOf(b)); } + /** + * Adds a superscript attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param b the superscript attribute value. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #isSuperscript(AttributeSet) + */ public static void setSuperscript(MutableAttributeSet a, boolean b) { a.addAttribute(Superscript, Boolean.valueOf(b)); } - public static void setTabSet(MutableAttributeSet a, javax.swing.text.TabSet tabs) + /** + * Adds a {@link TabSet} attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param tabs the tab set (<code>null</code> not permitted). + * + * @throws NullPointerException if either argument is <code>null</code>. + * + * @see #getTabSet(AttributeSet) + */ + public static void setTabSet(MutableAttributeSet a, + javax.swing.text.TabSet tabs) { a.addAttribute(StyleConstants.TabSet, tabs); } + /** + * Adds an underline attribute to the specified set. + * + * @param a the attribute set (<code>null</code> not permitted). + * @param b the underline attribute value. + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * + * @see #isUnderline(AttributeSet) + */ public static void setUnderline(MutableAttributeSet a, boolean b) { a.addAttribute(Underline, Boolean.valueOf(b)); @@ -373,73 +900,173 @@ public class StyleConstants // The remainder are so-called "typesafe enumerations" which // alias subsets of the above constants. + + /** + * A set of keys for attributes that apply to characters. + */ public static class CharacterConstants extends StyleConstants implements AttributeSet.CharacterAttribute { + /** + * Private constructor prevents new instances being created. + * + * @param k the key name. + */ private CharacterConstants(String k) { super(k); } - public static Object Background = ColorConstants.Background; - public static Object BidiLevel = new CharacterConstants("bidiLevel"); - public static Object Bold = FontConstants.Bold; - public static Object ComponentAttribute = new CharacterConstants("component"); - public static Object Family = FontConstants.Family; - public static Object Size = FontConstants.Size; - public static Object Foreground = ColorConstants.Foreground; - public static Object IconAttribute = new CharacterConstants("icon"); - public static Object Italic = FontConstants.Italic; - public static Object StrikeThrough = new CharacterConstants("strikethrough"); - public static Object Subscript = new CharacterConstants("subscript"); - public static Object Superscript = new CharacterConstants("superscript"); - public static Object Underline = new CharacterConstants("underline"); + /** An alias for {@link ColorConstants#Background}. */ + public static final Object Background = ColorConstants.Background; + + /** A key for the bidi level character attribute. */ + public static final Object BidiLevel = new CharacterConstants("bidiLevel"); + + /** An alias for {@link FontConstants#Bold}. */ + public static final Object Bold = FontConstants.Bold; + + /** A key for the component character attribute. */ + public static final Object ComponentAttribute + = new CharacterConstants("component"); + + /** An alias for {@link FontConstants#Family}. */ + public static final Object Family = FontConstants.Family; + + /** An alias for {@link FontConstants#Size}. */ + public static final Object Size = FontConstants.Size; + + /** An alias for {@link ColorConstants#Foreground}. */ + public static final Object Foreground = ColorConstants.Foreground; + + /** A key for the icon character attribute. */ + public static final Object IconAttribute = new CharacterConstants("icon"); + + /** A key for the italic character attribute. */ + public static final Object Italic = FontConstants.Italic; + + /** A key for the strike through character attribute. */ + public static final Object StrikeThrough + = new CharacterConstants("strikethrough"); + + /** A key for the subscript character attribute. */ + public static final Object Subscript = new CharacterConstants("subscript"); + + /** A key for the superscript character attribute. */ + public static final Object Superscript + = new CharacterConstants("superscript"); + + /** A key for the underline character attribute. */ + public static final Object Underline = new CharacterConstants("underline"); + } + /** + * A set of keys for attributes that relate to colors. + */ public static class ColorConstants extends StyleConstants implements AttributeSet.ColorAttribute, AttributeSet.CharacterAttribute { + /** + * Private constructor prevents new instances being created. + * + * @param k the key name. + */ private ColorConstants(String k) { super(k); } - public static Object Foreground = new ColorConstants("foreground"); - public static Object Background = new ColorConstants("background"); + + /** A key for the foreground color attribute. */ + public static final Object Foreground = new ColorConstants("foreground"); + + /** A key for the background color attribute. */ + public static final Object Background = new ColorConstants("background"); } + /** + * A set of keys for attributes that apply to fonts. + */ public static class FontConstants extends StyleConstants implements AttributeSet.FontAttribute, AttributeSet.CharacterAttribute { + /** + * Private constructor prevents new instances being created. + * + * @param k the key name. + */ private FontConstants(String k) { super(k); } - public static Object Bold = new FontConstants("bold"); - public static Object Family = new FontConstants("family"); - public static Object Italic = new FontConstants("italic"); - public static Object Size = new FontConstants("size"); + + /** A key for the bold font attribute. */ + public static final Object Bold = new FontConstants("bold"); + + /** A key for the family font attribute. */ + public static final Object Family = new FontConstants("family"); + + /** A key for the italic font attribute. */ + public static final Object Italic = new FontConstants("italic"); + + /** A key for the size font attribute. */ + public static final Object Size = new FontConstants("size"); } + /** + * A set of keys for attributes that apply to paragraphs. + */ public static class ParagraphConstants extends StyleConstants implements AttributeSet.ParagraphAttribute { + /** + * Private constructor prevents new instances being created. + * + * @param k the key name. + */ private ParagraphConstants(String k) { super(k); } - public static Object Alignment = new ParagraphConstants("Alignment"); - public static Object FirstLineIndent = new ParagraphConstants("FirstLineIndent"); - public static Object LeftIndent = new ParagraphConstants("LeftIndent"); - public static Object LineSpacing = new ParagraphConstants("LineSpacing"); - public static Object Orientation = new ParagraphConstants("Orientation"); - public static Object RightIndent = new ParagraphConstants("RightIndent"); - public static Object SpaceAbove = new ParagraphConstants("SpaceAbove"); - public static Object SpaceBelow = new ParagraphConstants("SpaceBelow"); - public static Object TabSet = new ParagraphConstants("TabSet"); + + /** A key for the alignment paragraph attribute. */ + public static final Object Alignment = new ParagraphConstants("Alignment"); + + /** A key for the first line indentation paragraph attribute. */ + public static final Object FirstLineIndent + = new ParagraphConstants("FirstLineIndent"); + + /** A key for the left indentation paragraph attribute. */ + public static final Object LeftIndent + = new ParagraphConstants("LeftIndent"); + + /** A key for the line spacing paragraph attribute. */ + public static final Object LineSpacing + = new ParagraphConstants("LineSpacing"); + + /** A key for the orientation paragraph attribute. */ + public static final Object Orientation + = new ParagraphConstants("Orientation"); + + /** A key for the right indentation paragraph attribute. */ + public static final Object RightIndent + = new ParagraphConstants("RightIndent"); + + /** A key for the 'space above' paragraph attribute. */ + public static final Object SpaceAbove + = new ParagraphConstants("SpaceAbove"); + + /** A key for the 'space below' paragraph attribute. */ + public static final Object SpaceBelow + = new ParagraphConstants("SpaceBelow"); + + /** A key for the tabset paragraph attribute. */ + public static final Object TabSet = new ParagraphConstants("TabSet"); + } } diff --git a/libjava/classpath/javax/swing/text/StyleContext.java b/libjava/classpath/javax/swing/text/StyleContext.java index dabc0ba..e2643a2 100644 --- a/libjava/classpath/javax/swing/text/StyleContext.java +++ b/libjava/classpath/javax/swing/text/StyleContext.java @@ -48,6 +48,7 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Enumeration; import java.util.EventListener; +import java.util.HashSet; import java.util.Hashtable; import javax.swing.event.ChangeEvent; @@ -370,7 +371,7 @@ public class StyleContext { StringBuffer sb = new StringBuffer(); sb.append("[StyleContext.SmallattributeSet:"); - for (int i = 0; i < attrs.length; ++i) + for (int i = 0; i < attrs.length - 1; ++i) { sb.append(" ("); sb.append(attrs[i].toString()); @@ -406,7 +407,12 @@ public class StyleContext static StyleContext defaultStyleContext = new StyleContext(); static final int compressionThreshold = 9; - + + /** + * These attribute keys are handled specially in serialization. + */ + private static HashSet staticAttributeKeys = new HashSet(); + EventListenerList listenerList; Hashtable styleTable; @@ -737,4 +743,19 @@ public class StyleContext { throw new InternalError("not implemented"); } + + /** + * Registers an attribute key as a well-known keys. When an attribute with + * such a key is written to a stream,, a special syntax is used so that it + * can be recognized when it is read back in. All attribute keys defined + * in <code>StyleContext</code> are registered as static keys. If you define + * additional attribute keys that you want to exist as nonreplicated objects, + * then you should register them using this method. + * + * @param key the key to register as static attribute key + */ + public static void registerStaticAttributeKey(Object key) + { + staticAttributeKeys.add(key); + } } diff --git a/libjava/classpath/javax/swing/text/TableView.java b/libjava/classpath/javax/swing/text/TableView.java index d3113b8..2dcb9eb 100644 --- a/libjava/classpath/javax/swing/text/TableView.java +++ b/libjava/classpath/javax/swing/text/TableView.java @@ -54,7 +54,7 @@ import javax.swing.event.DocumentEvent; * * @author Roman Kennke (kennke@aicas.com) */ -public class TableView +public abstract class TableView extends BoxView { @@ -90,6 +90,18 @@ public class TableView public void replace(int offset, int length, View[] views) { super.replace(offset, length, views); + int viewCount = getViewCount(); + if (columnRequirements == null + || viewCount > columnRequirements.length) + { + columnRequirements = new SizeRequirements[viewCount]; + for (int i = 0; i < columnRequirements.length; i++) + columnRequirements[i] = new SizeRequirements(); + } + if (columnOffsets == null || columnOffsets.length < viewCount) + columnOffsets = new int[viewCount]; + if (columnSpans == null || columnSpans.length < viewCount) + columnSpans = new int[viewCount]; layoutChanged(X_AXIS); } @@ -108,8 +120,6 @@ public class TableView protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { - // TODO: Maybe prepare columnSpans and columnOffsets. - // Some sanity checks. If these preconditions are not met, then the // following code will not work. Also, there must be something // seriously wrong then. @@ -140,7 +150,7 @@ public class TableView { // FIXME: Figure out how to fetch the row heights from the TableView's // element. - super.layoutMajorAxis(targetSpan, axis, offsets, spans); + super.layoutMinorAxis(targetSpan, axis, offsets, spans); } /** @@ -303,7 +313,7 @@ public class TableView /** * The size requirements of the columns. */ - private SizeRequirements[] columnRequirements; + SizeRequirements[] columnRequirements = new SizeRequirements[0]; /** * Creates a new instance of <code>TableView</code>. @@ -313,15 +323,6 @@ public class TableView public TableView(Element el) { super(el, Y_AXIS); - int numChildren = el.getElementCount(); - View[] rows = new View[numChildren]; - for (int i = 0; i < numChildren; ++i) - { - Element rowEl = el.getElement(i); - TableRow rowView = createTableRow(rowEl); - rows[i] = rowView; - } - replace(0, 0, rows); } /** @@ -385,7 +386,10 @@ public class TableView protected void layoutColumns(int targetSpan, int[] offsets, int spans[], SizeRequirements[] reqs) { - // TODO: Figure out what exactly to do here. + updateColumnRequirements(); + SizeRequirements r = calculateMinorAxisRequirements(X_AXIS, null); + SizeRequirements.calculateTiledPositions(targetSpan, r, columnRequirements, + offsets, spans); } /** @@ -462,4 +466,26 @@ public class TableView // and look for a range that contains the given position. return super.getViewAtPosition(pos, a); } + + /** + * Updates the column requirements. + */ + private void updateColumnRequirements() + { + int rowCount = getViewCount(); + for (int r = 0; r < rowCount; ++r) + { + TableRow row = (TableRow) getView(r); + int columnCount = row.getViewCount(); + for (int c = 0; c < columnCount; ++c) + { + View cell = row.getView(c); + SizeRequirements cr = columnRequirements[c]; + cr.minimum = Math.max(cr.minimum, (int) cell.getMinimumSpan(X_AXIS)); + cr.preferred = Math.max(cr.preferred, + (int) cell.getPreferredSpan(X_AXIS)); + cr.maximum = Math.max(cr.maximum, (int) cell.getMaximumSpan(X_AXIS)); + } + } + } } diff --git a/libjava/classpath/javax/swing/text/Utilities.java b/libjava/classpath/javax/swing/text/Utilities.java index 1adc8ff..d109a4a 100644 --- a/libjava/classpath/javax/swing/text/Utilities.java +++ b/libjava/classpath/javax/swing/text/Utilities.java @@ -41,12 +41,8 @@ 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; -import javax.swing.SwingUtilities; - /** * A set of utilities to deal with text. This is used by several other classes * inside this package. @@ -73,6 +69,10 @@ public class Utilities * are taken into account. Tabs are expanded using the * specified {@link TabExpander}. * + * + * The X and Y coordinates denote the start of the <em>baseline</em> where + * the text should be drawn. + * * @param s the text fragment to be drawn. * @param x the x position for drawing. * @param y the y position for drawing. @@ -88,15 +88,14 @@ public class Utilities // This buffers the chars to be drawn. char[] buffer = s.array; - - // The current x and y pixel coordinates. - int pixelX = x; - int pixelY = y; - // The font metrics of the current selected font. FontMetrics metrics = g.getFontMetrics(); int ascent = metrics.getAscent(); + // The current x and y pixel coordinates. + int pixelX = x; + int pixelY = y - ascent; + int pixelWidth = 0; int pos = s.offset; int len = 0; @@ -238,9 +237,10 @@ public class Utilities int pos; int currentX = x0; - for (pos = p0; pos < s.count; pos++) + for (pos = 0; pos < s.count; pos++) { char nextChar = s.array[s.offset+pos]; + if (nextChar == 0) { if (! round) @@ -256,6 +256,7 @@ public class Utilities else currentX = (int) te.nextTabStop(currentX, pos); } + if (currentX > x) { if (! round) @@ -263,7 +264,8 @@ public class Utilities break; } } - return pos; + + return pos + p0; } /** @@ -510,10 +512,10 @@ public class Utilities { int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset); BreakIterator breaker = BreakIterator.getWordInstance(); - breaker.setText(s.toString()); - + breaker.setText(s); + // If mark is equal to the end of the string, just use that position - if (mark == s.count) + if (mark == s.count + s.offset) return mark; // Try to find a word boundary previous to the mark at which we @@ -571,15 +573,29 @@ public class Utilities 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(offs, - Position.Bias.Forward, - SwingUtilities.calculateInnerArea(c, null), - SwingConstants.NORTH, - new Position.Bias[1]); - return pos; + int offs = getRowStart(c, offset); + + if(offs == -1) + return -1; + + // Effectively calculates the y value of the previous line. + Point pt = c.modelToView(offs-1).getLocation(); + + pt.x = x; + + // Calculate a simple fitting offset. + offs = c.viewToModel(pt); + + // Find out the real x positions of the calculated character and its + // neighbour. + int offsX = c.modelToView(offs).getLocation().x; + int offsXNext = c.modelToView(offs+1).getLocation().x; + + // Chose the one which is nearer to us and return its offset. + if (Math.abs(offsX-x) <= Math.abs(offsXNext-x)) + return offs; + else + return offs+1; } /** @@ -598,14 +614,31 @@ public class Utilities 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(offs, - Position.Bias.Forward, - SwingUtilities.calculateInnerArea(c, null), - SwingConstants.SOUTH, - new Position.Bias[1]); - return pos; - } + int offs = getRowEnd(c, offset); + + if(offs == -1) + return -1; + + // Effectively calculates the y value of the previous line. + Point pt = c.modelToView(offs+1).getLocation(); + + pt.x = x; + + // Calculate a simple fitting offset. + offs = c.viewToModel(pt); + + if (offs == c.getDocument().getLength()) + return offs; + + // Find out the real x positions of the calculated character and its + // neighbour. + int offsX = c.modelToView(offs).getLocation().x; + int offsXNext = c.modelToView(offs+1).getLocation().x; + + // Chose the one which is nearer to us and return its offset. + if (Math.abs(offsX-x) <= Math.abs(offsXNext-x)) + return offs; + else + return offs+1; + } } diff --git a/libjava/classpath/javax/swing/text/View.java b/libjava/classpath/javax/swing/text/View.java index b835842..2feaf29 100644 --- a/libjava/classpath/javax/swing/text/View.java +++ b/libjava/classpath/javax/swing/text/View.java @@ -44,6 +44,7 @@ import java.awt.Rectangle; import java.awt.Shape; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; public abstract class View implements SwingConstants @@ -72,8 +73,29 @@ public abstract class View implements SwingConstants public abstract void paint(Graphics g, Shape s); + /** + * Sets the parent for this view. This is the first method that is beeing + * called on a view to setup the view hierarchy. This is also the last method + * beeing called when the view is disconnected from the view hierarchy, in + * this case <code>parent</code> is null. + * + * If <code>parent</code> is <code>null</code>, a call to this method also + * calls <code>setParent</code> on the children, thus disconnecting them from + * the view hierarchy. That means that super must be called when this method + * is overridden. + * + * @param parent the parent to set, <code>null</code> when this view is + * beeing disconnected from the view hierarchy + */ public void setParent(View parent) { + if (parent == null) + { + int numChildren = getViewCount(); + for (int i = 0; i < numChildren; i++) + getView(i).setParent(null); + } + this.parent = parent; } @@ -101,27 +123,65 @@ public abstract class View implements SwingConstants return elt; } + /** + * Returns the preferred span along the specified axis. Normally the view is + * rendered with the span returned here if that is possible. + * + * @param axis the axis + * + * @return the preferred span along the specified axis + */ public abstract float getPreferredSpan(int axis); + /** + * Returns the resize weight of this view. A value of <code>0</code> or less + * means this view is not resizeable. Positive values make the view + * resizeable. The default implementation returns <code>0</code> + * unconditionally. + * + * @param axis the axis + * + * @return the resizability of this view along the specified axis + */ public int getResizeWeight(int axis) { return 0; } + /** + * Returns the maximum span along the specified axis. The default + * implementation will forward to + * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)} + * returns a value > 0, in which case this returns {@link Integer#MIN_VALUE}. + * + * @param axis the axis + * + * @return the maximum span along the specified axis + */ public float getMaximumSpan(int axis) { + float max = Integer.MAX_VALUE; if (getResizeWeight(axis) <= 0) - return getPreferredSpan(axis); - - return Integer.MAX_VALUE; + max = getPreferredSpan(axis); + return max; } + /** + * Returns the minimum span along the specified axis. The default + * implementation will forward to + * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)} + * returns a value > 0, in which case this returns <code>0</code>. + * + * @param axis the axis + * + * @return the minimum span along the specified axis + */ public float getMinimumSpan(int axis) { + float min = 0; if (getResizeWeight(axis) <= 0) - return getPreferredSpan(axis); - - return Integer.MAX_VALUE; + min = getPreferredSpan(axis); + return min; } public void setSize(float width, float height) @@ -129,6 +189,20 @@ public abstract class View implements SwingConstants // The default implementation does nothing. } + /** + * Returns the alignment of this view along the baseline of the parent view. + * An alignment of <code>0.0</code> will align this view with the left edge + * along the baseline, an alignment of <code>0.5</code> will align it + * centered to the baseline, an alignment of <code>1.0</code> will align + * the right edge along the baseline. + * + * The default implementation returns 0.5 unconditionally. + * + * @param axis the axis + * + * @return the alignment of this view along the parents baseline for the + * specified axis + */ public float getAlignment(int axis) { return 0.5f; @@ -160,6 +234,15 @@ public abstract class View implements SwingConstants return parent != null ? parent.getViewFactory() : null; } + /** + * Replaces a couple of child views with new child views. If + * <code>length == 0</code> then this is a simple insertion, if + * <code>views == null</code> this only removes some child views. + * + * @param offset the offset at which to replace + * @param length the number of child views to be removed + * @param views the new views to be inserted, may be <code>null</code> + */ public void replace(int offset, int length, View[] views) { // Default implementation does nothing. @@ -392,6 +475,10 @@ public abstract class View implements SwingConstants * of the change to the model. This calles {@link #forwardUpdateToView} * for each View that must be forwarded to. * + * If <code>ec</code> is not <code>null</code> (this means there have been + * structural changes to the element that this view is responsible for) this + * method should recognize this and don't notify newly added child views. + * * @param ec the ElementChange describing the element changes (may be * <code>null</code> if there were no changes) * @param ev the DocumentEvent describing the changes to the model @@ -404,10 +491,31 @@ public abstract class View implements SwingConstants DocumentEvent ev, Shape shape, ViewFactory vf) { int count = getViewCount(); - for (int i = 0; i < count; i++) + if (count > 0) { - View child = getView(i); - forwardUpdateToView(child, ev, shape, vf); + int startOffset = ev.getOffset(); + int endOffset = startOffset + ev.getLength(); + int startIndex = getViewIndex(startOffset, Position.Bias.Backward); + int endIndex = getViewIndex(endOffset, Position.Bias.Forward); + int index = -1; + int addLength = -1; + if (ec != null) + { + index = ec.getIndex(); + addLength = ec.getChildrenAdded().length; + } + + if (startIndex >= 0 && endIndex >= 0) + { + for (int i = startIndex; i <= endIndex; i++) + { + // Skip newly added child views. + if (index >= 0 && i >= index && i < (index+addLength)) + continue; + View child = getView(i); + forwardUpdateToView(child, ev, shape, vf); + } + } } } @@ -503,9 +611,9 @@ public abstract class View implements SwingConstants if (b2 != Position.Bias.Forward && b2 != Position.Bias.Backward) throw new IllegalArgumentException ("b2 must be either Position.Bias.Forward or Position.Bias.Backward"); - Shape s1 = modelToView(p1, a, b1); - Shape s2 = modelToView(p2, a, b2); - return s1.getBounds().union(s2.getBounds()); + Rectangle s1 = (Rectangle) modelToView(p1, a, b1); + Rectangle s2 = (Rectangle) modelToView(p2, a, b2); + return SwingUtilities.computeUnion(s1.x, s1.y, s1.width, s1.height, s2); } /** @@ -570,7 +678,7 @@ public abstract class View implements SwingConstants * Dumps the complete View hierarchy. This method can be used for debugging * purposes. */ - void dump() + protected void dump() { // Climb up the hierarchy to the parent. View parent = getParent(); @@ -590,7 +698,7 @@ public abstract class View implements SwingConstants { for (int i = 0; i < indent; ++i) System.out.print('.'); - System.out.println(this); + System.out.println(this + "(" + getStartOffset() + "," + getEndOffset() + ": " + getElement()); int count = getViewCount(); for (int i = 0; i < count; ++i) diff --git a/libjava/classpath/javax/swing/text/WrappedPlainView.java b/libjava/classpath/javax/swing/text/WrappedPlainView.java index baba343..e2790a0 100644 --- a/libjava/classpath/javax/swing/text/WrappedPlainView.java +++ b/libjava/classpath/javax/swing/text/WrappedPlainView.java @@ -270,8 +270,7 @@ public class WrappedPlainView extends BoxView implements TabExpander protected int calculateBreakPosition(int p0, int p1) { Container c = getContainer(); - Rectangle alloc = c.isValid() ? c.getBounds() - : new Rectangle(c.getPreferredSize()); + Rectangle alloc = new Rectangle(0, 0, getWidth(), getHeight()); updateMetrics(); try { diff --git a/libjava/classpath/javax/swing/text/html/FormView.java b/libjava/classpath/javax/swing/text/html/FormView.java new file mode 100644 index 0000000..b85c694 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/FormView.java @@ -0,0 +1,230 @@ +/* FormView.java -- A view for a variety of HTML form elements + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JPasswordField; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.UIManager; +import javax.swing.text.AttributeSet; +import javax.swing.text.ComponentView; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; + +/** + * A View that renders HTML form elements like buttons and input fields. + * This is implemented as a {@link ComponentView} that creates different Swing + * component depending on the type and setting of the different form elements. + * + * Namely, this view creates the following components: + * <table> + * <tr><th>Element type</th><th>Swing component</th></tr> + * <tr><td>input, button</td><td>JButton</td></tr> + * <tr><td>input, checkbox</td><td>JButton</td></tr> + * <tr><td>input, image</td><td>JButton</td></tr> + * <tr><td>input, password</td><td>JButton</td></tr> + * <tr><td>input, radio</td><td>JButton</td></tr> + * <tr><td>input, reset</td><td>JButton</td></tr> + * <tr><td>input, submit</td><td>JButton</td></tr> + * <tr><td>input, text</td><td>JButton</td></tr> + * <tr><td>select, size > 1 or with multiple attribute</td> + * <td>JList in JScrollPane</td></tr> + * <tr><td>select, size unspecified or == 1</td><td>JComboBox</td></tr> + * <tr><td>textarea, text</td><td>JTextArea in JScrollPane</td></tr> + * <tr><td>input, file</td><td>JTextField</td></tr> + * </table> + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class FormView + extends ComponentView + implements ActionListener +{ + + /** + * If the value attribute of an <code><input type="submit">> + * tag is not specified, then this string is used. + * + * @deprecated As of JDK1.3 the value is fetched from the UIManager property + * <code>FormView.submitButtonText</code>. + */ + public static final String SUBMIT = + UIManager.getString("FormView.submitButtonText"); + + /** + * If the value attribute of an <code><input type="reset">> + * tag is not specified, then this string is used. + * + * @deprecated As of JDK1.3 the value is fetched from the UIManager property + * <code>FormView.resetButtonText</code>. + */ + public static final String RESET = + UIManager.getString("FormView.resetButtonText"); + + /** + * Creates a new <code>FormView</code>. + * + * @param el the element that is displayed by this view. + */ + public FormView(Element el) + { + super(el); + } + + /** + * Creates the correct AWT component for rendering the form element. + */ + protected Component createComponent() + { + Component comp = null; + Element el = getElement(); + Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute); + if (tag.equals(HTML.Tag.INPUT)) + { + AttributeSet atts = el.getAttributes(); + String type = (String) atts.getAttribute(HTML.Attribute.TYPE); + String value = (String) atts.getAttribute(HTML.Attribute.VALUE); + if (type.equals("button")) + comp = new JButton(value); + else if (type.equals("checkbox")) + comp = new JCheckBox(value); + else if (type.equals("image")) + comp = new JButton(value); // FIXME: Find out how to fetch the image. + else if (type.equals("password")) + comp = new JPasswordField(value); + else if (type.equals("radio")) + comp = new JRadioButton(value); + else if (type.equals("reset")) + { + if (value == null || value.equals("")) + value = RESET; + comp = new JButton(value); + } + else if (type.equals("submit")) + { + if (value == null || value.equals("")) + value = SUBMIT; + comp = new JButton(value); + } + else if (type.equals("text")) + comp = new JTextField(value); + + } + // FIXME: Implement the remaining components. + return comp; + } + + /** + * Determines the maximum span for this view on the specified axis. + * + * @param axis the axis along which to determine the span + * + * @return the maximum span for this view on the specified axis + * + * @throws IllegalArgumentException if the axis is invalid + */ + public float getMaximumSpan(int axis) + { + // FIXME: The specs say that for some components the maximum span == the + // preferred span of the component. This should be figured out and + // implemented accordingly. + float span; + if (axis == X_AXIS) + span = getComponent().getMaximumSize().width; + else if (axis == Y_AXIS) + span = getComponent().getMaximumSize().height; + else + throw new IllegalArgumentException("Invalid axis parameter"); + return span; + } + + /** + * Processes an action from the Swing component. + * + * If the action comes from a submit button, the form is submitted by calling + * {@link #submitData}. In the case of a reset button, the form is reset to + * the original state. If the action comes from a password or text field, + * then the input focus is transferred to the next input element in the form, + * unless this text/password field is the last one, in which case the form + * is submitted. + * + * @param ev the action event + */ + public void actionPerformed(ActionEvent ev) + { + Element el = getElement(); + Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute); + if (tag.equals(HTML.Tag.INPUT)) + { + AttributeSet atts = el.getAttributes(); + String type = (String) atts.getAttribute(HTML.Attribute.TYPE); + if (type.equals("submit")) + submitData(""); // FIXME: How to fetch the actual form data? + } + // FIXME: Implement the remaining actions. + } + + /** + * Submits the form data. A separate thread is created to do the + * transmission. + * + * @param data the form data + */ + protected void submitData(String data) + { + // FIXME: Implement this. + } + + /** + * Submits the form data in response to a click on a + * <code><input type="image"></code> element. + * + * @param imageData the mouse click coordinates + */ + protected void imageSubmit(String imageData) + { + // FIXME: Implement this. + } +} diff --git a/libjava/classpath/javax/swing/text/html/HTML.java b/libjava/classpath/javax/swing/text/html/HTML.java index 0b758d2..2b521cd 100644 --- a/libjava/classpath/javax/swing/text/html/HTML.java +++ b/libjava/classpath/javax/swing/text/html/HTML.java @@ -57,8 +57,7 @@ public class HTML /** * Represents a HTML attribute. */ - public static class Attribute - implements Serializable + public static final class Attribute { /** * The action attribute @@ -464,47 +463,18 @@ public class HTML * The width attribute */ public static final Attribute WIDTH = new Attribute("width"); - private final String name; - - /** - * Creates the attribute with the given name. - */ - protected Attribute(String a_name) - { - name = a_name; - } - - /** - * Calls compareTo on the tag names (Strings) - */ - public int compareTo(Object other) - { - return name.compareTo(((Attribute) other).name); - } /** - * The attributes are equal if the names are equal - * (ignoring case) + * The attribute name. */ - public boolean equals(Object other) - { - if (other == this) - return true; - - if (!(other instanceof Attribute)) - return false; - - Attribute that = (Attribute) other; - - return that.name.equalsIgnoreCase(name); - } + private final String name; /** - * Returns the hash code which corresponds to the string for this tag. + * Creates the attribute with the given name. */ - public int hashCode() + private Attribute(String a_name) { - return name == null ? 0 : name.hashCode(); + name = a_name; } /** @@ -559,7 +529,6 @@ public class HTML * Represents a HTML tag. */ public static class Tag - implements Comparable, Serializable { /** * The <a> tag @@ -1047,42 +1016,6 @@ public class HTML } /** - * Calls compareTo on the tag names (Strings) - */ - public int compareTo(Object other) - { - return name.compareTo(((Tag) other).name); - } - - /** - * The tags are equal if the names are equal (ignoring case). - */ - public boolean equals(Object other) - { - if (other == this) - { - return true; - } - - if (!(other instanceof Tag)) - { - return false; - } - - Tag that = (Tag) other; - - return that.name.equalsIgnoreCase(name); - } - - /** - * Returns the hash code which corresponds to the string for this tag. - */ - public int hashCode() - { - return name == null ? 0 : name.hashCode(); - } - - /** * Returns the tag name. The names of the built-in tags are always * returned in lowercase. */ diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java index 5b2452b..2a96953 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java +++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java @@ -38,10 +38,8 @@ exception statement from your version. */ package javax.swing.text.html; -import java.net.URL; - import java.io.IOException; - +import java.net.URL; import java.util.HashMap; import java.util.Stack; import java.util.Vector; @@ -131,16 +129,17 @@ public class HTMLDocument extends DefaultStyledDocument } /** - * Replaces the contents of the document with the given element specifications. - * This is called before insert if the loading is done in bursts. This is the - * only method called if loading the document entirely in one burst. + * Replaces the contents of the document with the given element + * specifications. This is called before insert if the loading is done + * in bursts. This is the only method called if loading the document + * entirely in one burst. * * @param data - the date that replaces the content of the document */ protected void create(DefaultStyledDocument.ElementSpec[] data) { - // FIXME: Not implemented - System.out.println("create not implemented"); + // Once the super behaviour is properly implemented it should be sufficient + // to simply call super.create(data). super.create(data); } @@ -149,11 +148,35 @@ public class HTMLDocument extends DefaultStyledDocument * * @return the new default root */ - protected AbstractDocument.AbstractElement createDefaultRoot() + protected AbstractElement createDefaultRoot() { - // FIXME: Not implemented - System.out.println("createDefaultRoot not implemented"); - return super.createDefaultRoot(); + AbstractDocument.AttributeContext ctx = getAttributeContext(); + + // Create html element. + AttributeSet atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.HTML); + BranchElement html = (BranchElement) createBranchElement(null, atts); + + // Create body element. + atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.BODY); + BranchElement body = (BranchElement) createBranchElement(html, atts); + html.replace(0, 0, new Element[] { body }); + + // Create p element. + atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.P); + BranchElement p = (BranchElement) createBranchElement(body, atts); + body.replace(0, 0, new Element[] { p }); + + // Create an empty leaf element. + atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, + HTML.Tag.CONTENT); + Element leaf = createLeafElement(p, atts, 0, 1); + p.replace(0, 0, new Element[]{ leaf }); + + return html; } /** @@ -165,28 +188,29 @@ public class HTMLDocument extends DefaultStyledDocument * @param a - the attributes for the element * @param p0 - the beginning of the range >= 0 * @param p1 - the end of the range >= p0 + * * @return the new element */ protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { - // FIXME: Not implemented - System.out.println("createLeafElement not implemented"); - return super.createLeafElement(parent, a, p0, p1); + RunElement el = new RunElement(parent, a, p0, p1); + el.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); + return new RunElement(parent, a, p0, p1); } - /** This method returns an HTMLDocument.BlockElement object representing the + /** + * This method returns an HTMLDocument.BlockElement object representing the * attribute set a and attached to parent. * * @param parent - the parent element * @param a - the attributes for the element + * * @return the new element */ protected Element createBranchElement(Element parent, AttributeSet a) { - // FIXME: Not implemented - System.out.println("createBranchElement not implemented"); - return super.createBranchElement(parent, a); + return new BlockElement(parent, a); } /** @@ -204,9 +228,9 @@ public class HTMLDocument extends DefaultStyledDocument */ protected void insert(int offset, DefaultStyledDocument.ElementSpec[] data) throws BadLocationException - { - super.insert(offset, data); - } + { + super.insert(offset, data); + } /** * Updates document structure as a result of text insertion. This will happen @@ -451,7 +475,7 @@ public class HTMLDocument extends DefaultStyledDocument { public BlockElement (Element parent, AttributeSet a) { - super (parent, a); + super(parent, a); } /** @@ -470,10 +494,14 @@ public class HTMLDocument extends DefaultStyledDocument */ public String getName() { - return (String) getAttribute(StyleConstants.NameAttribute); + Object tag = getAttribute(StyleConstants.NameAttribute); + String name = null; + if (tag != null) + name = tag.toString(); + return name; } } - + /** * RunElement represents a section of text that has a set of * HTML character level attributes assigned to it. @@ -502,7 +530,11 @@ public class HTMLDocument extends DefaultStyledDocument */ public String getName() { - return (String) getAttribute(StyleConstants.NameAttribute); + Object tag = getAttribute(StyleConstants.NameAttribute); + String name = null; + if (tag != null) + name = tag.toString(); + return name; } /** @@ -531,7 +563,13 @@ public class HTMLDocument extends DefaultStyledDocument /** A stack for character attribute sets **/ Stack charAttrStack = new Stack(); - + + /** + * The parse stack. This stack holds HTML.Tag objects that reflect the + * current position in the parsing process. + */ + private Stack parseStack = new Stack(); + /** A mapping between HTML.Tag objects and the actions that handle them **/ HashMap tagToAction; @@ -699,8 +737,8 @@ public class HTMLDocument extends DefaultStyledDocument */ public void start(HTML.Tag t, MutableAttributeSet a) { - // FIXME: Implement. - print ("ParagraphAction.start not implemented"); + // FIXME: What else must be done here? + blockOpen(t, a); } /** @@ -709,8 +747,8 @@ public class HTMLDocument extends DefaultStyledDocument */ public void end(HTML.Tag t) { - // FIXME: Implement. - print ("ParagraphAction.end not implemented"); + // FIXME: What else must be done here? + blockClose(t); } } @@ -1102,7 +1140,11 @@ public class HTMLDocument extends DefaultStyledDocument elements = new DefaultStyledDocument.ElementSpec[parseBuffer.size()]; parseBuffer.copyInto(elements); parseBuffer.removeAllElements(); - insert(offset, elements); + if (offset == 0) + create(elements); + else + insert(offset, elements); + offset += HTMLDocument.this.getLength() - offset; } @@ -1250,12 +1292,27 @@ public class HTMLDocument extends DefaultStyledDocument { printBuffer(); DefaultStyledDocument.ElementSpec element; - element = new DefaultStyledDocument.ElementSpec(attr.copyAttributes(), - DefaultStyledDocument.ElementSpec.StartTagType); + + // If the previous tag is content and the parent is p-implied, then + // we must also close the p-implied. + if (parseStack.size() > 0 && parseStack.peek() == HTML.Tag.IMPLIED) + { + element = new DefaultStyledDocument.ElementSpec(null, + DefaultStyledDocument.ElementSpec.EndTagType); + parseBuffer.addElement(element); + parseStack.pop(); + } + + parseStack.push(t); + AbstractDocument.AttributeContext ctx = getAttributeContext(); + AttributeSet copy = attr.copyAttributes(); + copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t); + element = new DefaultStyledDocument.ElementSpec(copy, + DefaultStyledDocument.ElementSpec.StartTagType); parseBuffer.addElement(element); printBuffer(); } - + /** * Instructs the parse buffer to close the block element associated with * the given HTML.Tag @@ -1266,10 +1323,40 @@ public class HTMLDocument extends DefaultStyledDocument { printBuffer(); DefaultStyledDocument.ElementSpec element; + + // If the previous tag is a start tag then we insert a synthetic + // content tag. + DefaultStyledDocument.ElementSpec prev; + prev = (DefaultStyledDocument.ElementSpec) + parseBuffer.get(parseBuffer.size() - 1); + if (prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType) + { + AbstractDocument.AttributeContext ctx = getAttributeContext(); + AttributeSet attributes = ctx.getEmptySet(); + attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute, + HTML.Tag.CONTENT); + element = new DefaultStyledDocument.ElementSpec(attributes, + DefaultStyledDocument.ElementSpec.ContentType, + new char[0], 0, 0); + parseBuffer.add(element); + } + // If the previous tag is content and the parent is p-implied, then + // we must also close the p-implied. + else if (parseStack.peek() == HTML.Tag.IMPLIED) + { + element = new DefaultStyledDocument.ElementSpec(null, + DefaultStyledDocument.ElementSpec.EndTagType); + parseBuffer.addElement(element); + if (parseStack.size() > 0) + parseStack.pop(); + } + element = new DefaultStyledDocument.ElementSpec(null, DefaultStyledDocument.ElementSpec.EndTagType); parseBuffer.addElement(element); printBuffer(); + if (parseStack.size() > 0) + parseStack.pop(); } /** @@ -1298,16 +1385,42 @@ public class HTMLDocument extends DefaultStyledDocument protected void addContent(char[] data, int offs, int length, boolean generateImpliedPIfNecessary) { + AbstractDocument.AttributeContext ctx = getAttributeContext(); + DefaultStyledDocument.ElementSpec element; + AttributeSet attributes = null; + + // Content must always be embedded inside a paragraph element, + // so we create this if the previous element is not one of + // <p>, <h1> .. <h6>. + boolean createImpliedParagraph = false; + HTML.Tag parent = (HTML.Tag) parseStack.peek(); + if (parent != HTML.Tag.P && parent != HTML.Tag.H1 + && parent != HTML.Tag.H2 + && parent != HTML.Tag.H3 && parent != HTML.Tag.H4 + && parent != HTML.Tag.H5 && parent != HTML.Tag.H6 + && parent != HTML.Tag.TD) + { + attributes = ctx.getEmptySet(); + attributes = ctx.addAttribute(attributes, + StyleConstants.NameAttribute, + HTML.Tag.IMPLIED); + element = new DefaultStyledDocument.ElementSpec(attributes, + DefaultStyledDocument.ElementSpec.StartTagType); + parseBuffer.add(element); + parseStack.push(HTML.Tag.IMPLIED); + } + // Copy the attribute set, don't use the same object because // it may change - AttributeSet attributes = null; if (charAttr != null) attributes = charAttr.copyAttributes(); - - DefaultStyledDocument.ElementSpec element; + else + attributes = ctx.getEmptySet(); + attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute, + HTML.Tag.CONTENT); element = new DefaultStyledDocument.ElementSpec(attributes, - DefaultStyledDocument.ElementSpec.ContentType, - data, offs, length); + DefaultStyledDocument.ElementSpec.ContentType, + data, offs, length); printBuffer(); // Add the element to the buffer diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java index 1ef9768..2d5d1eb 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java +++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java @@ -56,17 +56,11 @@ import javax.accessibility.AccessibleContext; import javax.swing.Action; import javax.swing.JEditorPane; -import javax.swing.text.AbstractDocument; import javax.swing.text.BadLocationException; -import javax.swing.text.BoxView; -import javax.swing.text.ComponentView; import javax.swing.text.Document; import javax.swing.text.EditorKit; import javax.swing.text.Element; -import javax.swing.text.IconView; -import javax.swing.text.LabelView; import javax.swing.text.MutableAttributeSet; -import javax.swing.text.ParagraphView; import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.StyledEditorKit; @@ -532,8 +526,8 @@ public class HTMLEditorKit public View create(Element element) { View view = null; - Object attr = element.getAttributes().getAttribute( - StyleConstants.NameAttribute); + Object attr = + element.getAttributes().getAttribute(StyleConstants.NameAttribute); if (attr instanceof HTML.Tag) { HTML.Tag tag = (HTML.Tag) attr; @@ -553,8 +547,16 @@ public class HTMLEditorKit view = new BlockView(element, View.Y_AXIS); // FIXME: Uncomment when the views have been implemented - /* else if (tag.equals(HTML.Tag.CONTENT)) - view = new InlineView(element); + else if (tag.equals(HTML.Tag.CONTENT)) + view = new InlineView(element); + else if (tag == HTML.Tag.HEAD) + view = new NullView(element); + else if (tag.equals(HTML.Tag.TABLE)) + view = new HTMLTableView(element); + else if (tag.equals(HTML.Tag.TD)) + view = new ParagraphView(element); + + /* else if (tag.equals(HTML.Tag.MENU) || tag.equals(HTML.Tag.DIR) || tag.equals(HTML.Tag.UL) || tag.equals(HTML.Tag.OL)) view = new ListView(element); @@ -564,8 +566,6 @@ public class HTMLEditorKit view = new HRuleView(element); else if (tag.equals(HTML.Tag.BR)) view = new BRView(element); - else if (tag.equals(HTML.Tag.TABLE)) - view = new TableView(element); else if (tag.equals(HTML.Tag.INPUT) || tag.equals(HTML.Tag.SELECT) || tag.equals(HTML.Tag.TEXTAREA)) view = new FormView(element); @@ -575,21 +575,11 @@ public class HTMLEditorKit view = new FrameSetView(element); else if (tag.equals(HTML.Tag.FRAME)) view = new FrameView(element); */ - } - + } if (view == null) { - String name = element.getName(); - if (name.equals(AbstractDocument.ContentElementName)) - view = new LabelView(element); - else if (name.equals(AbstractDocument.ParagraphElementName)) - view = new ParagraphView(element); - else if (name.equals(AbstractDocument.SectionElementName)) - view = new BoxView(element, View.Y_AXIS); - else if (name.equals(StyleConstants.ComponentElementName)) - view = new ComponentView(element); - else if (name.equals(StyleConstants.IconElementName)) - view = new IconView(element); + System.err.println("missing tag->view mapping for: " + element); + view = new NullView(element); } return view; } @@ -958,7 +948,8 @@ public class HTMLEditorKit throw new IOException("Parser is null."); HTMLDocument hd = ((HTMLDocument) doc); - hd.setBase(editorPane.getPage()); + if (editorPane != null) + hd.setBase(editorPane.getPage()); ParserCallback pc = hd.getReader(pos); // FIXME: What should ignoreCharSet be set to? diff --git a/libjava/classpath/javax/swing/text/html/HTMLTableView.java b/libjava/classpath/javax/swing/text/html/HTMLTableView.java new file mode 100644 index 0000000..cac44d8 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/HTMLTableView.java @@ -0,0 +1,82 @@ +/* HTMLTableView.java -- A table view for HTML tables + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.text.Element; +import javax.swing.text.TableView; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; + +/** + * A conrete implementation of TableView that renders HTML tables. + * + * @author Roman Kennke (kennke@aicas.com) + */ +class HTMLTableView + extends TableView +{ + + /** + * Creates a new HTMLTableView for the specified element. + * + * @param el the element for the table view + */ + public HTMLTableView(Element el) + { + super(el); + } + + /** + * Loads the children of the Table. This completely bypasses the ViewFactory + * and creates instances of TableRow instead. + * + * @param vf ignored + */ + protected void loadChildren(ViewFactory vf) + { + Element el = getElement(); + int numChildren = el.getElementCount(); + View[] rows = new View[numChildren]; + for (int i = 0; i < numChildren; ++i) + { + rows[i] = createTableRow(el.getElement(i)); + } + replace(0, getViewCount(), rows); + } +} diff --git a/libjava/classpath/javax/swing/text/html/InlineView.java b/libjava/classpath/javax/swing/text/html/InlineView.java new file mode 100644 index 0000000..77ec86e --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/InlineView.java @@ -0,0 +1,166 @@ +/* InlineView.java -- Renders HTML content + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Shape; + +import javax.swing.event.DocumentEvent; +import javax.swing.text.AttributeSet; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.LabelView; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; + +/** + * Renders HTML content (identified by {@link HTML.Tag#CONTENT}). This is + * basically a {@link LabelView} that is adjusted to understand styles defined + * by stylesheets. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class InlineView + extends LabelView +{ + + /** + * Creates a new <code>InlineView</code> that renders the specified element. + * + * @param element the element for this view + */ + public InlineView(Element element) + { + super(element); + } + + /** + * Receives notification that something was inserted into the document in + * a location that this view is responsible for. + * + * @param e the document event + * @param a the current allocation of this view + * @param f the view factory for creating new views + * + * @since 1.5 + */ + public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + // FIXME: What to do here? + super.insertUpdate(e, a, f); + } + + /** + * Receives notification that something was removed from the document in + * a location that this view is responsible for. + * + * @param e the document event + * @param a the current allocation of this view + * @param f the view factory for creating new views + * + * @since 1.5 + */ + public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + // FIXME: What to do here? + super.removeUpdate(e, a, f); + } + + /** + * Receives notification that attributes have changed in the document in + * a location that this view is responsible for. This calls + * {@link #setPropertiesFromAttributes}. + * + * @param e the document event + * @param a the current allocation of this view + * @param f the view factory for creating new views + * + * @since 1.5 + */ + public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.changedUpdate(e, a, f); + setPropertiesFromAttributes(); + } + + /** + * Returns the attributes that are used for rendering. This is implemented + * to multiplex the attributes specified in the model with a stylesheet. + * + * @return the attributes that are used for rendering + */ + public AttributeSet getAttributes() + { + // FIXME: Implement this. + return super.getAttributes(); + } + + + public int getBreakWeight(int axis, float pos, float len) + { + // FIXME: Implement this. + return super.getBreakWeight(axis, pos, len); + } + + public View breakView(int axis, int offset, float pos, float len) + { + // FIXME: Implement this. + return super.breakView(axis, offset, pos, len); + } + + protected void setPropertiesFromAttributes() + { + // FIXME: Implement this. + super.setPropertiesFromAttributes(); + } + + /** + * Returns the stylesheet used by this view. This returns the stylesheet + * of the <code>HTMLDocument</code> that is rendered by this view. + * + * @return the stylesheet used by this view + */ + protected StyleSheet getStyleSheet() + { + Document doc = getDocument(); + StyleSheet styleSheet = null; + if (doc instanceof HTMLDocument) + styleSheet = ((HTMLDocument) doc).getStyleSheet(); + return styleSheet; + } +} diff --git a/libjava/classpath/javax/swing/text/html/NullView.java b/libjava/classpath/javax/swing/text/html/NullView.java new file mode 100644 index 0000000..4b66c5a --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/NullView.java @@ -0,0 +1,102 @@ +/* NullView.java -- A dummy view that renders nothing + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Graphics; +import java.awt.Shape; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.View; +import javax.swing.text.Position.Bias; + +/** + * A dummy view that renders nothing. This is used for invisible HTML elements + * like <head>. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class NullView + extends View +{ + + /** + * Creates a new NullView. + * + * @param elem the element + */ + public NullView(Element elem) + { + super(elem); + } + + /** + * Does nothing. + */ + public void paint(Graphics g, Shape s) + { + // Nothing to do here. + } + + /** + * Returns zero for both directions. + */ + public float getPreferredSpan(int axis) + { + return 0; + } + + /** + * Returns the allocation of this view, which should be empty anyway. + */ + public Shape modelToView(int pos, Shape a, Bias b) + throws BadLocationException + { + return a; + } + + /** + * Returns the start offset of the element. + */ + public int viewToModel(float x, float y, Shape a, Bias[] b) + { + return getElement().getStartOffset(); + } + +} diff --git a/libjava/classpath/javax/swing/text/html/ObjectView.java b/libjava/classpath/javax/swing/text/html/ObjectView.java new file mode 100644 index 0000000..d6a77c0 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ObjectView.java @@ -0,0 +1,110 @@ +/* ObjectView.java -- A view for HTML object tags + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Component; + +import javax.swing.text.AttributeSet; +import javax.swing.text.ComponentView; +import javax.swing.text.Element; + +/** + * A view for HTML <code><object></code> tags. + * + * This is a {@link ComponentView} that creates special components depending + * on the object specification. If the object tag has a classid attribute, then + * this view will try to load the class specified by this attribute using the + * classloader that loaded the associated document. If the class could be + * loaded, an instance is created and the type narrowed to {@link Component}. + * + * It is also possible to set bean properties on the created component using + * nested <code><param></code> tags. For example: + * <pre> + * <object classid="javax.swing.JLabel"> + * <param name="text" value="sample text"> + * </object> + * </pre> + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class ObjectView extends ComponentView +{ + + /** + * Creates a new <code>ObjectView</code>. + * + * @param el the element for which to create a view + */ + public ObjectView(Element el) + { + super(el); + } + + /** + * Creates a component based on the specification in the element of this + * view. See the class description for details. + */ + protected Component createComponent() + { + Component comp = null; + Element el = getElement(); + AttributeSet atts = el.getAttributes(); + String classId = (String) atts.getAttribute("classid"); + try + { + Class objectClass = Class.forName(classId); + Object instance = objectClass.newInstance(); + comp = (Component) instance; + } + catch (ClassNotFoundException ex) + { + // Ignored. + } + catch (IllegalAccessException ex) + { + // Ignored. + } + catch (InstantiationException ex) + { + // Ignored. + } + // FIXME: Handle param tags and set bean properties accordingly. + return comp; + } +} diff --git a/libjava/classpath/javax/swing/text/html/Option.java b/libjava/classpath/javax/swing/text/html/Option.java new file mode 100644 index 0000000..1def51b --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/Option.java @@ -0,0 +1,157 @@ +/* Option.java -- Value class for <option> list model elements + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import javax.swing.text.AttributeSet; + +/** + * Value class for the combobox model that renders <code><option></code> + * elements. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class Option +{ + + /** + * The attributes of the <option> tag. + */ + private AttributeSet attributes; + + /** + * The label. + */ + private String label; + + /** + * The selected state of this label. + */ + private boolean selected; + + /** + * Creates a new <code>Option</code> instance that uses the specified + * tag attributes. + * + * @param attr the attributes to use + */ + public Option(AttributeSet attr) + { + attributes = attr; + label = null; + selected = false; + // FIXME: Probably initialize something using the attributes. + } + + /** + * Sets the label to use for this <code><option></code> tag. + * + * @param l the label to set + */ + public void setLabel(String l) + { + label = l; + } + + /** + * Returns the label of this <code><option></code> tag. + * + * @return the label of this <code><option></code> tag + */ + public String getLabel() + { + return label; + } + + /** + * Returns the attributes used to render this <code><option></code> + * tag. + * + * @return the attributes used to render this <code><option></code> tag + */ + public AttributeSet getAttributes() + { + return attributes; + } + + /** + * Returns a string representation of this <code><option></code> tag. + * This returns the <code>label</code> property. + * + * @return a string representation of this <code><option></code> tag + */ + public String toString() + { + return label; + } + + /** + * Sets the selected state of this <code><option></code> tag. + * + * @param s the selected state to set + */ + protected void setSelection(boolean s) + { + selected = s; + } + + /** + * Returns <code>true</code> when this option is selected, <code>false</code> + * otherwise. + * + * @return <code>true</code> when this option is selected, <code>false</code> + * otherwise + */ + public boolean isSelected() + { + return selected; + } + + /** + * Returns the string associated with the <code>value</code> attribute or + * the label, if no such attribute is specified. + * + * @return the string associated with the <code>value</code> attribute or + * the label, if no such attribute is specified + */ + public String getValue() + { + // FIXME: Return some attribute here if specified. + return label; + } +} diff --git a/libjava/classpath/javax/swing/text/html/ParagraphView.java b/libjava/classpath/javax/swing/text/html/ParagraphView.java new file mode 100644 index 0000000..2339f4e --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/ParagraphView.java @@ -0,0 +1,209 @@ +/* ParagraphView.java -- Renders a paragraph in HTML + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Graphics; +import java.awt.Shape; + +import javax.swing.SizeRequirements; +import javax.swing.text.AttributeSet; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.View; + +/** + * Renders a paragraph in HTML. This is a subclass of + * {@link javax.swing.text.ParagraphView} with some adjustments for + * understanding stylesheets. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class ParagraphView + extends javax.swing.text.ParagraphView +{ + + /** + * Creates a new ParagraphView for the specified element. + * + * @param element the element + */ + public ParagraphView(Element element) + { + super(element); + } + + /** + * Sets the parent of this view. This is implemented to call the parent + * functionality and then trigger {@link #setPropertiesFromAttributes} in + * order to load the stylesheet attributes. + * + * @param parent the parent view to set + */ + public void setParent(View parent) + { + super.setParent(parent); + if (parent != null) + setPropertiesFromAttributes(); + } + + /** + * Returns the attributes used by this view. This is implemented to multiplex + * the attributes of the model with the attributes of the stylesheet. + */ + public AttributeSet getAttributes() + { + // FIXME: Implement this multiplexing thing. + return super.getAttributes(); + } + + /** + * Loads the visual properties of the ParagraphView from the element's + * attributes and the stylesheet of the HTML document. + */ + protected void setPropertiesFromAttributes() + { + // FIXME: Implement this. + } + + /** + * Returns the stylesheet used by this view. + * + * @return the stylesheet used by this view + */ + protected StyleSheet getStyleSheet() + { + Document doc = getDocument(); + StyleSheet styleSheet = null; + if (doc instanceof HTMLDocument) + styleSheet = ((HTMLDocument) doc).getStyleSheet(); + return styleSheet; + } + + /** + * Calculates the minor axis requirements of this view. This is implemented + * to return the super class'es requirements and modifies the minimumSpan + * slightly so that it is not smaller than the length of the longest word. + * + * @param axis the axis + * @param r the SizeRequirements object to be used as return parameter; + * if <code>null</code> a new one will be created + * + * @return the requirements along the minor layout axis + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements r) + { + // FIXME: Implement the above specified behaviour. + return super.calculateMinorAxisRequirements(axis, r); + } + + /** + * Determines if this view is visible or not. If none of the children is + * visible and the only visible child is the break that ends the paragraph, + * this paragraph is not considered to be visible. + * + * @return the visibility of this paragraph + */ + public boolean isVisible() + { + // FIXME: Implement the above specified behaviour. + return super.isVisible(); + } + + /** + * Paints this view. This delegates to the superclass after the coordinates + * have been updated for tab calculations. + * + * @param g the graphics object + * @param a the current allocation of this view + */ + public void paint(Graphics g, Shape a) + { + // FIXME: Implement the above specified behaviour. + super.paint(g, a); + } + + /** + * Returns the preferred span of this view. If this view is not visible, + * we return <code>0</code>, otherwise the super class is called. + * + * @param axis the axis + * + * @return the preferred span of this view + */ + public float getPreferredSpan(int axis) + { + float span = 0; + if (isVisible()) + span = super.getPreferredSpan(axis); + return span; + } + + /** + * Returns the minimum span of this view. If this view is not visible, + * we return <code>0</code>, otherwise the super class is called. + * + * @param axis the axis + * + * @return the minimum span of this view + */ + public float getMinimumSpan(int axis) + { + float span = 0; + if (isVisible()) + span = super.getMinimumSpan(axis); + return span; + } + + /** + * Returns the maximum span of this view. If this view is not visible, + * we return <code>0</code>, otherwise the super class is called. + * + * @param axis the axis + * + * @return the maximum span of this view + */ + public float getMaximumSpan(int axis) + { + float span = 0; + if (isVisible()) + span = super.getMaximumSpan(axis); + return span; + } +} diff --git a/libjava/classpath/javax/swing/text/package.html b/libjava/classpath/javax/swing/text/package.html index 50043b6..5db555d 100644 --- a/libjava/classpath/javax/swing/text/package.html +++ b/libjava/classpath/javax/swing/text/package.html @@ -40,7 +40,7 @@ exception statement from your version. --> <head><title>GNU Classpath - javax.swing.text</title></head> <body> -<p></p> - +<p>Provides core text classes and interfaces representing models and views +used by the text components for display and editing of text.</p> </body> </html> |