diff options
Diffstat (limited to 'javax/swing/text')
-rw-r--r-- | javax/swing/text/AbstractDocument.java | 203 | ||||
-rw-r--r-- | javax/swing/text/BoxView.java | 263 | ||||
-rw-r--r-- | javax/swing/text/CompositeView.java | 35 | ||||
-rw-r--r-- | javax/swing/text/DefaultStyledDocument.java | 113 | ||||
-rw-r--r-- | javax/swing/text/FlowView.java | 2 | ||||
-rw-r--r-- | javax/swing/text/GapContent.java | 548 | ||||
-rw-r--r-- | javax/swing/text/GlyphView.java | 177 | ||||
-rw-r--r-- | javax/swing/text/StyleContext.java | 22 | ||||
-rw-r--r-- | javax/swing/text/Utilities.java | 117 | ||||
-rw-r--r-- | javax/swing/text/html/BlockView.java | 16 | ||||
-rw-r--r-- | javax/swing/text/html/CSS.java | 9 | ||||
-rw-r--r-- | javax/swing/text/html/HTMLDocument.java | 93 | ||||
-rw-r--r-- | javax/swing/text/html/HTMLEditorKit.java | 203 | ||||
-rw-r--r-- | javax/swing/text/html/ImageView.java | 295 | ||||
-rw-r--r-- | javax/swing/text/html/ParagraphView.java | 16 | ||||
-rw-r--r-- | javax/swing/text/html/StyleSheet.java | 245 | ||||
-rw-r--r-- | javax/swing/text/html/TableView.java | 194 |
17 files changed, 1673 insertions, 878 deletions
diff --git a/javax/swing/text/AbstractDocument.java b/javax/swing/text/AbstractDocument.java index 54797fdb0..76f1602f4 100644 --- a/javax/swing/text/AbstractDocument.java +++ b/javax/swing/text/AbstractDocument.java @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.Dictionary; import java.util.Enumeration; import java.util.EventListener; +import java.util.HashMap; import java.util.Hashtable; import java.util.Vector; @@ -158,14 +159,10 @@ public abstract class AbstractDocument implements Document, Serializable private int numReaders = 0; /** - * Tells if there are one or more writers waiting. + * The number of current writers. If this is > 1 then the same thread entered + * the write lock more than once. */ - private int numWritersWaiting = 0; - - /** - * A condition variable that readers and writers wait on. - */ - private Object documentCV = new Object(); + private int numWriters = 0; /** An instance of a DocumentFilter.FilterBypass which allows calling * the insert, remove and replace method without checking for an installed @@ -315,7 +312,8 @@ public abstract class AbstractDocument implements Document, Serializable * @throws BadLocationException if <code>offset</code> is not a valid * location in the documents content model */ - public Position createPosition(final int offset) throws BadLocationException + public synchronized Position createPosition(final int offset) + throws BadLocationException { return content.createPosition(offset); } @@ -432,7 +430,7 @@ public abstract class AbstractDocument implements Document, Serializable * @return the thread that currently modifies this <code>Document</code> * if there is one, otherwise <code>null</code> */ - protected final Thread getCurrentWriter() + protected final synchronized Thread getCurrentWriter() { return currentWriter; } @@ -1022,25 +1020,21 @@ public abstract class AbstractDocument implements Document, Serializable * Blocks until a read lock can be obtained. Must block if there is * currently a writer modifying the <code>Document</code>. */ - public final void readLock() + public final synchronized void readLock() { - if (currentWriter != null && currentWriter.equals(Thread.currentThread())) - return; - synchronized (documentCV) + try { - while (currentWriter != null || numWritersWaiting > 0) + while (currentWriter != null) { - - try - { - documentCV.wait(); - } - catch (InterruptedException ie) - { - throw new Error("interrupted trying to get a readLock"); - } + if (currentWriter == Thread.currentThread()) + return; + wait(); } - numReaders++; + numReaders++; + } + catch (InterruptedException ex) + { + throw new Error("Interrupted during grab read lock"); } } @@ -1048,7 +1042,7 @@ public abstract class AbstractDocument implements Document, Serializable * Releases the read lock. If this was the only reader on this * <code>Document</code>, writing may begin now. */ - public final void readUnlock() + public final synchronized void readUnlock() { // Note we could have a problem here if readUnlock was called without a // prior call to readLock but the specs simply warn users to ensure that @@ -1075,21 +1069,14 @@ public abstract class AbstractDocument implements Document, Serializable // FIXME: the reference implementation throws a // javax.swing.text.StateInvariantError here - if (numReaders == 0) + if (numReaders <= 0) throw new IllegalStateException("document lock failure"); - synchronized (documentCV) - { - // If currentWriter is not null, the application code probably had a - // writeLock and then tried to obtain a readLock, in which case - // numReaders wasn't incremented - if (currentWriter == null) - { - numReaders --; - if (numReaders == 0 && numWritersWaiting != 0) - documentCV.notify(); - } - } + // If currentWriter is not null, the application code probably had a + // writeLock and then tried to obtain a readLock, in which case + // numReaders wasn't incremented + numReaders--; + notify(); } /** @@ -1113,12 +1100,21 @@ public abstract class AbstractDocument implements Document, Serializable */ public void remove(int offset, int length) throws BadLocationException { - if (documentFilter == null) - removeImpl(offset, length); - else - documentFilter.remove(getBypass(), offset, length); + writeLock(); + try + { + DocumentFilter f = getDocumentFilter(); + if (f == null) + removeImpl(offset, length); + else + f.remove(getBypass(), offset, length); + } + finally + { + writeUnlock(); + } } - + void removeImpl(int offset, int length) throws BadLocationException { // The RI silently ignores all requests that have a negative length. @@ -1135,21 +1131,12 @@ public abstract class AbstractDocument implements Document, Serializable new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.REMOVE); - try - { - writeLock(); - - // The order of the operations below is critical! - removeUpdate(event); - UndoableEdit temp = content.remove(offset, length); - - postRemoveUpdate(event); - fireRemoveUpdate(event); - } - finally - { - writeUnlock(); - } + // The order of the operations below is critical! + removeUpdate(event); + UndoableEdit temp = content.remove(offset, length); + + postRemoveUpdate(event); + fireRemoveUpdate(event); } } @@ -1343,26 +1330,25 @@ public abstract class AbstractDocument implements Document, Serializable * Blocks until a write lock can be obtained. Must wait if there are * readers currently reading or another thread is currently writing. */ - protected final void writeLock() + protected synchronized final void writeLock() { - if (currentWriter != null && currentWriter.equals(Thread.currentThread())) - return; - synchronized (documentCV) + try { - numWritersWaiting++; - while (numReaders > 0) + while (numReaders > 0 || currentWriter != null) { - try + if (Thread.currentThread() == currentWriter) { - documentCV.wait(); - } - catch (InterruptedException ie) - { - throw new Error("interruped while trying to obtain write lock"); + numWriters++; + return; } + wait(); } - numWritersWaiting --; currentWriter = Thread.currentThread(); + numWriters = 1; + } + catch (InterruptedException ex) + { + throw new Error("Interupted during grab write lock"); } } @@ -1370,16 +1356,14 @@ public abstract class AbstractDocument implements Document, Serializable * Releases the write lock. This allows waiting readers or writers to * obtain the lock. */ - protected final void writeUnlock() + protected final synchronized void writeUnlock() { - synchronized (documentCV) - { - if (Thread.currentThread().equals(currentWriter)) - { - currentWriter = null; - documentCV.notifyAll(); - } - } + if (--numWriters <= 0) + { + numWriters = 0; + currentWriter = null; + notifyAll(); + } } /** @@ -2384,6 +2368,11 @@ public abstract class AbstractDocument implements Document, Serializable /** The serialization UID (compatible with JDK1.5). */ private static final long serialVersionUID = 5230037221564563284L; + /** + * The threshold that indicates when we switch to using a Hashtable. + */ + private static final int THRESHOLD = 10; + /** The starting offset of the change. */ private int offset; @@ -2394,15 +2383,18 @@ public abstract class AbstractDocument implements Document, Serializable private DocumentEvent.EventType type; /** - * Maps <code>Element</code> to their change records. + * Maps <code>Element</code> to their change records. This is only + * used when the changes array gets too big. We can use an + * (unsync'ed) HashMap here, since changes to this are (should) always + * be performed inside a write lock. */ - Hashtable changes; + private HashMap changes; /** * Indicates if this event has been modified or not. This is used to * determine if this event is thrown. */ - boolean modified; + private boolean modified; /** * Creates a new <code>DefaultDocumentEvent</code>. @@ -2417,7 +2409,6 @@ public abstract class AbstractDocument implements Document, Serializable this.offset = offset; this.length = length; this.type = type; - changes = new Hashtable(); modified = false; } @@ -2431,9 +2422,27 @@ public abstract class AbstractDocument implements Document, Serializable public boolean addEdit(UndoableEdit edit) { // XXX - Fully qualify ElementChange to work around gcj bug #2499. - if (edit instanceof DocumentEvent.ElementChange) + + // Start using Hashtable when we pass a certain threshold. This + // gives a good memory/performance compromise. + if (changes == null && edits.size() > THRESHOLD) + { + changes = new HashMap(); + int count = edits.size(); + for (int i = 0; i < count; i++) + { + Object o = edits.elementAt(i); + if (o instanceof DocumentEvent.ElementChange) + { + DocumentEvent.ElementChange ec = + (DocumentEvent.ElementChange) o; + changes.put(ec.getElement(), ec); + } + } + } + + if (changes != null && edit instanceof DocumentEvent.ElementChange) { - modified = true; DocumentEvent.ElementChange elEdit = (DocumentEvent.ElementChange) edit; changes.put(elEdit.getElement(), elEdit); @@ -2492,7 +2501,27 @@ public abstract class AbstractDocument implements Document, Serializable public DocumentEvent.ElementChange getChange(Element elem) { // XXX - Fully qualify ElementChange to work around gcj bug #2499. - return (DocumentEvent.ElementChange) changes.get(elem); + DocumentEvent.ElementChange change = null; + if (changes != null) + { + change = (DocumentEvent.ElementChange) changes.get(elem); + } + else + { + int count = edits.size(); + for (int i = 0; i < count && change == null; i++) + { + Object o = edits.get(i); + if (o instanceof DocumentEvent.ElementChange) + { + DocumentEvent.ElementChange ec = + (DocumentEvent.ElementChange) o; + if (elem.equals(ec.getElement())) + change = ec; + } + } + } + return change; } /** diff --git a/javax/swing/text/BoxView.java b/javax/swing/text/BoxView.java index 962d06219..72bc07e75 100644 --- a/javax/swing/text/BoxView.java +++ b/javax/swing/text/BoxView.java @@ -38,6 +38,7 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.Container; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; @@ -105,6 +106,8 @@ public class BoxView myAxis = axis; layoutValid[0] = false; layoutValid[1] = false; + requirementsValid[X_AXIS] = false; + requirementsValid[Y_AXIS] = false; span[0] = 0; span[1] = 0; requirements[0] = new SizeRequirements(); @@ -141,7 +144,10 @@ public class BoxView */ public void setAxis(int axis) { + boolean changed = axis != myAxis; myAxis = axis; + if (changed) + preferenceChanged(null, true, true); } /** @@ -222,35 +228,20 @@ public class BoxView */ public void replace(int offset, int length, View[] views) { - int oldNumChildren = getViewCount(); - // Actually perform the replace. super.replace(offset, length, views); // Resize and copy data for cache arrays. int newItems = views != null ? views.length : 0; - int delta = newItems - length; - int src = offset + length; - int numMove = oldNumChildren - src; - int dst = src + delta; - offsets[X_AXIS] = replaceLayoutArray(offsets[X_AXIS], offset, - oldNumChildren, delta, src, dst, - numMove); - spans[X_AXIS] = replaceLayoutArray(spans[X_AXIS], offset, - oldNumChildren, delta, src, dst, - numMove); - offsets[Y_AXIS] = replaceLayoutArray(offsets[Y_AXIS], offset, - oldNumChildren, delta, src, dst, - numMove); - spans[Y_AXIS] = replaceLayoutArray(spans[Y_AXIS], offset, - oldNumChildren, delta, src, dst, - numMove); - - // Invalidate layout information. - layoutValid[X_AXIS] = false; - requirementsValid[X_AXIS] = false; - layoutValid[Y_AXIS] = false; - requirementsValid[Y_AXIS] = false; + int minor = 1 - myAxis; + offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems); + spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems); + layoutValid[myAxis] = false; + requirementsValid[myAxis] = false; + offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems); + spans[minor] = replaceLayoutArray(spans[minor], offset, newItems); + layoutValid[minor] = false; + requirementsValid[minor] = false; } /** @@ -261,27 +252,25 @@ public class BoxView * * @return the replaced array */ - private int[] replaceLayoutArray(int[] oldArray, int offset, int numChildren, - int delta, int src, int dst, int numMove) + private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems) { - int[] newArray; - if (numChildren + delta > oldArray.length) - { - int newLength = Math.max(2 * oldArray.length, numChildren + delta); - newArray = new int[newLength]; - System.arraycopy(oldArray, 0, newArray, 0, offset); - System.arraycopy(oldArray, src, newArray, dst, numMove); - } - else - { - newArray = oldArray; - System.arraycopy(newArray, src, newArray, dst, numMove); - } + int num = getViewCount(); + int[] newArray = new int[num]; + System.arraycopy(oldArray, 0, newArray, 0, offset); + System.arraycopy(oldArray, offset, newArray, offset + newItems, + num - newItems - offset); return newArray; } /** + * A Rectangle instance to be reused in the paint() method below. + */ + private final Rectangle tmpRect = new Rectangle(); + + private Rectangle clipRect = new Rectangle(); + + /** * Renders the <code>Element</code> that is associated with this * <code>View</code>. * @@ -290,26 +279,95 @@ public class BoxView */ public void paint(Graphics g, Shape a) { - Rectangle alloc; - if (a instanceof Rectangle) - alloc = (Rectangle) a; - else - alloc = a.getBounds(); - - int x = alloc.x + getLeftInset(); - int y = alloc.y + getTopInset(); + // Try to avoid allocation if possible (almost all cases). + Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + + // This returns a cached instance. + alloc = getInsideAllocation(alloc); + + // The following algorithm optimizes painting of a BoxView by taking + // advantage of the layout order of the box children. + // + // 1. It first searches a child that which's allocation is inside the clip. + // This is accomplished by an efficient binary search. This assumes + // that the children of the BoxView are laid out in the same order + // as their index within the view. This is true for the BoxView, but + // might not be the case for all subclasses. + // 2. Starting from the found view, it paints the children in both + // directions until the first view is hit that is outside the clip. + + // First we search a child view that is inside the clip. + + // Fetch the clip rect and calculate the center point of it. + clipRect = g.getClipBounds(clipRect); + int cX = clipRect.x + clipRect.width / 2; + int cY = clipRect.y + clipRect.height / 2; + + int viewCount = getViewCount(); + int up = viewCount; + int low = 0; + int mid = (up - low) / 2; + View start = getView(mid); + + int newMid; + // Use another cached instance here to avoid allocations during + // painting. + tmpRect.setBounds(alloc); + // This modifies tmpRect. + childAllocation(mid, tmpRect); + while (! clipRect.intersects(tmpRect)) + { + if (isBefore(cX, cY, tmpRect)) + { + up = mid; + newMid = (up - low) / 2 + low; + mid = (newMid == mid) ? newMid - 1 : newMid; + } + else if (isAfter(cX, cY, tmpRect)) + { + low = mid; + newMid = (up - low) / 2 + low; + mid = (newMid == mid) ? newMid + 1 : newMid; + } + else + break; + if (mid >= 0 && mid < viewCount) + { + start = getView(mid); + tmpRect.setBounds(alloc); + childAllocation(mid, tmpRect); + } + else + break; + } - Rectangle clip = g.getClipBounds(); - Rectangle tmp = new Rectangle(); - int count = getViewCount(); - for (int i = 0; i < count; ++i) + if (mid >= 0 && mid < viewCount) { - tmp.x = x + getOffset(X_AXIS, i); - tmp.y = y + getOffset(Y_AXIS, i); - tmp.width = getSpan(X_AXIS, i); - tmp.height = getSpan(Y_AXIS, i); - if (tmp.intersects(clip)) - paintChild(g, tmp, i); + // Ok, we found one view that is inside the clip rect. Now paint the + // children before it that are inside the clip. + boolean inClip = true; + for (int i = mid - 1; i >= 0 && inClip; i--) + { + start = getView(i); + tmpRect.setBounds(alloc); + childAllocation(i, tmpRect); + inClip = clipRect.intersects(tmpRect); + if (inClip) + paintChild(g, tmpRect, i); + } + + // Now paint the found view and all views after it that lie inside the + // clip. + inClip = true; + for (int i = mid; i < viewCount && inClip; i++) + { + start = getView(i); + tmpRect.setBounds(alloc); + childAllocation(i, tmpRect); + inClip = clipRect.intersects(tmpRect); + if (inClip) + paintChild(g, tmpRect, i); + } } } @@ -742,49 +800,32 @@ public class BoxView */ protected void layout(int width, int height) { - int[] newSpan = new int[]{ width, height }; - int count = getViewCount(); - - // Update minor axis as appropriate. We need to first update the minor - // axis layout because that might affect the children's preferences along - // the major axis. - int minorAxis = myAxis == X_AXIS ? Y_AXIS : X_AXIS; - if ((! isLayoutValid(minorAxis)) || newSpan[minorAxis] != span[minorAxis]) - { - layoutValid[minorAxis] = false; - span[minorAxis] = newSpan[minorAxis]; - layoutMinorAxis(span[minorAxis], minorAxis, offsets[minorAxis], - spans[minorAxis]); - - // Update the child view's sizes. - for (int i = 0; i < count; ++i) - { - getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); - } - layoutValid[minorAxis] = true; - } - + layoutAxis(X_AXIS, width); + layoutAxis(Y_AXIS, height); + } - // Update major axis as appropriate. - if ((! isLayoutValid(myAxis)) || newSpan[myAxis] != span[myAxis]) + private void layoutAxis(int axis, int s) + { + if (span[axis] != s) + layoutValid[axis] = false; + if (! layoutValid[axis]) { - layoutValid[myAxis] = false; - span[myAxis] = newSpan[myAxis]; - layoutMajorAxis(span[myAxis], myAxis, offsets[myAxis], - spans[myAxis]); + span[axis] = s; + updateRequirements(axis); + if (axis == myAxis) + layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]); + else + layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]); + layoutValid[axis] = true; - // Update the child view's sizes. - for (int i = 0; i < count; ++i) + // Push out child layout. + int viewCount = getViewCount(); + for (int i = 0; i < viewCount; i++) { - getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); + View v = getView(i); + v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); } - layoutValid[myAxis] = true; } - - if (layoutValid[myAxis] == false) - System.err.println("WARNING: Major axis layout must be valid after layout"); - if (layoutValid[minorAxis] == false) - System.err.println("Minor axis layout must be valid after layout"); } /** @@ -807,7 +848,7 @@ public class BoxView { View child = getView(i); spans[i] = (int) child.getPreferredSpan(axis); - sumPref = spans[i]; + sumPref += spans[i]; } // Try to adjust the spans so that we fill the targetSpan. @@ -1048,9 +1089,11 @@ public class BoxView { if (axis != X_AXIS && axis != Y_AXIS) throw new IllegalArgumentException("Illegal axis argument"); - int weight = 1; - if (axis == myAxis) - weight = 0; + updateRequirements(axis); + int weight = 0; + if ((requirements[axis].preferred != requirements[axis].minimum) + || (requirements[axis].preferred != requirements[axis].maximum)) + weight = 1; return weight; } @@ -1077,8 +1120,30 @@ public class BoxView protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e, Shape a, ViewFactory vf) { - // FIXME: What to do here? + boolean wasValid = isLayoutValid(myAxis); super.forwardUpdate(ec, e, a, vf); + // Trigger repaint when one of the children changed the major axis. + if (wasValid && ! isLayoutValid(myAxis)) + { + Container c = getContainer(); + if (a != null && c != null) + { + int pos = e.getOffset(); + int index = getViewIndexAtPosition(pos); + Rectangle r = getInsideAllocation(a); + if (myAxis == X_AXIS) + { + r.x += offsets[myAxis][index]; + r.width -= offsets[myAxis][index]; + } + else + { + r.y += offsets[myAxis][index]; + r.height -= offsets[myAxis][index]; + } + c.repaint(r.x, r.y, r.width, r.height); + } + } } public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) diff --git a/javax/swing/text/CompositeView.java b/javax/swing/text/CompositeView.java index d4467f314..570fc955c 100644 --- a/javax/swing/text/CompositeView.java +++ b/javax/swing/text/CompositeView.java @@ -68,7 +68,7 @@ public abstract class CompositeView * initialized in {@link #getInsideAllocation} and reused and modified in * {@link #childAllocation(int, Rectangle)}. */ - Rectangle insideAllocation; + private final Rectangle insideAllocation = new Rectangle(); /** * The insets of this <code>CompositeView</code>. This is initialized @@ -282,12 +282,12 @@ public abstract class CompositeView } } } - else - { - throw new BadLocationException("Position " + pos - + " is not represented by view.", pos); - } } + + if (ret == null) + throw new BadLocationException("Position " + pos + + " is not represented by view.", pos); + return ret; } @@ -527,24 +527,17 @@ public abstract class CompositeView if (a == null) return null; - Rectangle alloc = a.getBounds(); + // Try to avoid allocation of Rectangle here. + Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + // Initialize the inside allocation rectangle. This is done inside // a synchronized block in order to avoid multiple threads creating // this instance simultanously. - Rectangle inside; - synchronized(this) - { - inside = insideAllocation; - if (inside == null) - { - inside = new Rectangle(); - insideAllocation = inside; - } - } - inside.x = alloc.x + left; - inside.y = alloc.y + top; - inside.width = alloc.width - left - right; - inside.height = alloc.height - top - bottom; + Rectangle inside = insideAllocation; + inside.x = alloc.x + getLeftInset(); + inside.y = alloc.y + getTopInset(); + inside.width = alloc.width - getLeftInset() - getRightInset(); + inside.height = alloc.height - getTopInset() - getBottomInset(); return inside; } diff --git a/javax/swing/text/DefaultStyledDocument.java b/javax/swing/text/DefaultStyledDocument.java index 367666053..3156ca67f 100644 --- a/javax/swing/text/DefaultStyledDocument.java +++ b/javax/swing/text/DefaultStyledDocument.java @@ -683,6 +683,58 @@ public class DefaultStyledDocument extends AbstractDocument implements return ret; } + /** + * Creates a document in response to a call to + * {@link DefaultStyledDocument#create(ElementSpec[])}. + * + * @param len the length of the inserted text + * @param data the specs for the elements + * @param ev the document event + */ + void create(int len, ElementSpec[] data, DefaultDocumentEvent ev) + { + prepareEdit(offset, len); + Element el = root; + int index = el.getElementIndex(0); + while (! el.isLeaf()) + { + Element child = el.getElement(index); + Edit edit = new Edit(el, index, false); + elementStack.push(edit); + el = child; + index = el.getElementIndex(0); + } + Edit ed = (Edit) elementStack.peek(); + Element child = ed.e.getElement(ed.index); + ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(), + child.getEndOffset())); + ed.removed.add(child); + while (elementStack.size() > 1) + pop(); + int n = data.length; + + // Reset root element's attributes. + AttributeSet newAtts = null; + if (n > 0 && data[0].getType() == ElementSpec.StartTagType) + newAtts = data[0].getAttributes(); + if (newAtts == null) + newAtts = SimpleAttributeSet.EMPTY; + MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes(); + ev.addEdit(new AttributeUndoableEdit(root, newAtts, true)); + mAtts.removeAttributes(mAtts); + mAtts.addAttributes(newAtts); + + // Insert the specified elements. + for (int i = 1; i < n; i++) + insertElement(data[i]); + + // Pop remaining stack. + while (elementStack.size() > 0) + pop(); + + finishEdit(ev); + } + private boolean canJoin(Element e0, Element e1) { boolean ret = false; @@ -987,6 +1039,8 @@ public class DefaultStyledDocument extends AbstractDocument implements ElementEdit ee = new ElementEdit(parent, index, removed, added); ev.addEdit(ee); } + edits.clear(); + elementStack.clear(); } /** @@ -1034,7 +1088,7 @@ public class DefaultStyledDocument extends AbstractDocument implements createFracture(data); i = 0; } - + // Handle each ElementSpec individually. for (; i < data.length; i++) { @@ -1069,14 +1123,13 @@ public class DefaultStyledDocument extends AbstractDocument implements if (offset == 0 && fracturedParent != null && data[0].getType() == ElementSpec.EndTagType) { - for (int p = 0; + int p; + for (p = 0; p < data.length && data[p].getType() == ElementSpec.EndTagType; - p++) - { - Edit edit = insertPath[insertPath.length - p - 1]; - edit.index--; - edit.removed.add(0, edit.e.getElement(edit.index)); - } + p++); + Edit edit = insertPath[insertPath.length - p - 1]; + edit.index--; + edit.removed.add(0, edit.e.getElement(edit.index)); } } @@ -2379,18 +2432,24 @@ public class DefaultStyledDocument extends AbstractDocument implements if (length == 0) return; - UndoableEdit edit = content.insertString(offset, - contentBuffer.toString()); + Content c = getContent(); + UndoableEdit edit = c.insertString(offset, + contentBuffer.toString()); // Create the DocumentEvent with the ElementEdit added DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT); + ev.addEdit(edit); // Finally we must update the document structure and fire the insert // update event. buffer.insert(offset, length, data, ev); + + super.insertUpdate(ev, null); + + ev.end(); fireInsertUpdate(ev); fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); } @@ -2410,14 +2469,16 @@ public class DefaultStyledDocument extends AbstractDocument implements */ protected void create(ElementSpec[] data) { - writeLock(); try { + // Clear content if there is some. int len = getLength(); if (len > 0) remove(0, len); + writeLock(); + // Now we insert the content. StringBuilder b = new StringBuilder(); for (int i = 0; i < data.length; ++i) @@ -2429,38 +2490,18 @@ public class DefaultStyledDocument extends AbstractDocument implements Content content = getContent(); UndoableEdit cEdit = content.insertString(0, b.toString()); + len = b.length(); DefaultDocumentEvent ev = new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT); ev.addEdit(cEdit); - // We do a little trick here to get the new structure: We instantiate - // a new ElementBuffer with a new root element, insert into that root - // and then reparent the newly created elements to the old root - // element. - BranchElement createRoot = - (BranchElement) createBranchElement(null, null); - Element dummyLeaf = createLeafElement(createRoot, null, 0, 1); - createRoot.replace(0, 0, new Element[]{ dummyLeaf }); - ElementBuffer createBuffer = new ElementBuffer(createRoot); - createBuffer.insert(0, b.length(), data, new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT)); - // Now the new root is the first child of the createRoot. - Element newRoot = createRoot.getElement(0); - BranchElement root = (BranchElement) getDefaultRootElement(); - Element[] added = new Element[newRoot.getElementCount()]; - for (int i = 0; i < added.length; ++i) - { - added[i] = newRoot.getElement(i); - ((AbstractElement) added[i]).element_parent = root; - } - Element[] removed = new Element[root.getElementCount()]; - for (int i = 0; i < removed.length; ++i) - removed[i] = root.getElement(i); + buffer.create(len, data, ev); - // Replace the old elements in root with the new and update the event. - root.replace(0, removed.length, added); - ev.addEdit(new ElementEdit(root, 0, removed, added)); + // For the bidi update. + super.insertUpdate(ev, null); + ev.end(); fireInsertUpdate(ev); fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); } diff --git a/javax/swing/text/FlowView.java b/javax/swing/text/FlowView.java index 9609f3fc8..c2bed399f 100644 --- a/javax/swing/text/FlowView.java +++ b/javax/swing/text/FlowView.java @@ -488,7 +488,7 @@ public abstract class FlowView extends BoxView if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) >= ForcedBreakWeight) { - max = Math.max(pref, pref); + max = Math.max(max, pref); pref = 0; } } diff --git a/javax/swing/text/GapContent.java b/javax/swing/text/GapContent.java index 990e9d464..08a318d8b 100644 --- a/javax/swing/text/GapContent.java +++ b/javax/swing/text/GapContent.java @@ -39,16 +39,13 @@ exception statement from your version. */ package javax.swing.text; import java.io.Serializable; -import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Set; import java.util.Vector; -import java.util.WeakHashMap; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; @@ -71,7 +68,7 @@ public class GapContent /** * A {@link Position} implementation for <code>GapContent</code>. */ - private class GapContentPosition + class GapContentPosition implements Position { @@ -82,39 +79,6 @@ public class GapContent Mark mark; /** - * Creates a new GapContentPosition object. - * - * @param offset the offset of this Position - */ - GapContentPosition(int offset) - { - // Try to find the mark in the positionMarks array, and store the index - // to it. - synchronized (GapContent.this) - { - // Try to make space. - garbageCollect(); - Mark m = new Mark(offset); - int i = search(marks, m); - if (i >= 0) // mark found - { - m = (Mark) marks.get(i); - } - else - { - i = -i - 1; - marks.add(i, m); - } - m.refCount++; - mark = m; - } - - // Register this position in the death queue, so we can cleanup the marks - // when this position object gets GC'ed. - new WeakReference(this, queueOfDeath); - } - - /** * Returns the current offset of this Position within the content. * * @return the current offset of this Position within the content. @@ -133,7 +97,7 @@ public class GapContent * be garbage collected while we still hold a reference to the Mark object. */ private class Mark - implements Comparable + extends WeakReference { /** * The actual mark into the buffer. @@ -141,21 +105,20 @@ public class GapContent int mark; /** - * The number of GapContentPosition object that reference this mark. If - * it reaches zero, it get's deleted by {@link GapContent#garbageCollect()}. - */ - int refCount; - - /** * Creates a new Mark object for the specified offset. * * @param offset the offset */ Mark(int offset) { + super(null); + mark = offset; + } + + Mark(int offset, GapContentPosition pos, ReferenceQueue queue) + { + super(pos, queue); mark = offset; - if (mark >= gapStart && mark != 0) - mark += (gapEnd - gapStart); } /** @@ -165,34 +128,23 @@ public class GapContent */ int getOffset() { - assert mark == 0 || mark <= gapStart || mark >= gapEnd : - "Invalid mark: " + mark + ", gapStart: " + gapStart - + ", gapEnd: " + gapEnd; - int res = mark; - if (mark >= gapEnd) + if (mark >= gapStart) res -= (gapEnd - gapStart); - return res; + return Math.max(0, res); } /** - * Implementation of Comparable. - */ - public int compareTo(Object o) - { - Mark other = (Mark) o; - return mark - other.mark; - } - /** - * Adjustment for equals(). + * Returns the GapContentPosition that is associated ith this mark. + * This fetches the weakly referenced position object. + * + * @return the GapContentPosition that is associated ith this mark */ - public boolean equals(Object o) + GapContentPosition getPosition() { - if (o == null || !(o instanceof Mark)) - return false; - else - return ((Mark) o).mark == mark; + return (GapContentPosition) get(); } + } /** @@ -367,7 +319,15 @@ public class GapContent */ ArrayList marks; - WeakHashMap positions; + /** + * The number of unused marks. + */ + private int garbageMarks; + + /** + * A 'static' mark that is used for searching. + */ + private Mark searchMark = new Mark(0); /** * Queues all references to GapContentPositions that are about to be @@ -398,7 +358,6 @@ public class GapContent gapStart = 1; gapEnd = size; buffer[0] = '\n'; - positions = new WeakHashMap(); marks = new ArrayList(); queueOfDeath = new ReferenceQueue(); } @@ -612,27 +571,33 @@ public class GapContent // and luckily enough the GapContent can very well deal with offsets // outside the buffer bounds. So I removed that check. + // First do some garbage collections. + while (queueOfDeath.poll() != null) + garbageMarks++; + if (garbageMarks > Math.max(5, marks.size() / 10)) + garbageCollect(); + // We try to find a GapContentPosition at the specified offset and return // that. Otherwise we must create a new one. - GapContentPosition pos = null; - Set positionSet = positions.keySet(); - for (Iterator i = positionSet.iterator(); i.hasNext();) - { - GapContentPosition p = (GapContentPosition) i.next(); - if (p.getOffset() == offset) - { - pos = p; - break; - } - } - - // If none was found, then create and return a new one. - if (pos == null) + Mark m; + GapContentPosition pos; + int index = offset; + if (offset >= gapStart) + index += (gapEnd - gapStart); + searchMark.mark = index; + int insertIndex = search(searchMark); + if (!(insertIndex < marks.size() + && (m = (Mark) marks.get(insertIndex)).mark == index + && (pos = m.getPosition()) != null)) { - pos = new GapContentPosition(offset); - positions.put(pos, null); + // Create new position if none was found. + pos = new GapContentPosition(); + m = new Mark(index, pos, queueOfDeath); + pos.mark = m; + marks.add(insertIndex, m); } - + // Otherwise use the found position. + return pos; } @@ -649,18 +614,29 @@ public class GapContent assert newSize > (gapEnd - gapStart) : "The new gap size must be greater " + "than the old gap size"; - int delta = newSize - gapEnd + gapStart; - // Update the marks after the gapEnd. - adjustPositionsInRange(gapEnd, -1, delta); + int oldEnd = getGapEnd(); + int oldSize = getArrayLength(); + int upper = oldSize - oldEnd; + int size = (newSize + 1) * 2; + int newEnd = size - upper; // Copy the data around. - char[] newBuf = (char[]) allocateArray(length() + newSize); - System.arraycopy(buffer, 0, newBuf, 0, gapStart); - System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length - - gapEnd); - gapEnd = gapStart + newSize; + char[] newBuf = (char[]) allocateArray(size); + System.arraycopy(buffer, 0, newBuf, 0, Math.min(size, oldSize)); buffer = newBuf; - + gapEnd = newEnd; + if (upper != 0) + System.arraycopy(buffer, oldEnd, buffer, newEnd, upper); + + // Adjust marks. + int delta = gapEnd - oldEnd; + int adjIndex = searchFirst(oldEnd); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + m.mark += delta; + } } /** @@ -670,28 +646,44 @@ public class GapContent */ protected void shiftGap(int newGapStart) { - if (newGapStart == gapStart) - return; - int newGapEnd = newGapStart + gapEnd - gapStart; - if (newGapStart < gapStart) + int oldStart = gapStart; + int delta = newGapStart - oldStart; + int oldEnd = gapEnd; + int newGapEnd = oldEnd + delta; + int size = oldEnd - oldStart; + + // Shift gap in array. + gapStart = newGapStart; + gapEnd = newGapEnd; + if (delta > 0) + System.arraycopy(buffer, oldEnd, buffer, oldStart, delta); + else + System.arraycopy(buffer, newGapStart, buffer, newGapEnd, -delta); + + // Adjust marks. + if (delta > 0) { - // Update the positions between newGapStart and (old) gapStart. The marks - // must be shifted by (gapEnd - gapStart). - adjustPositionsInRange(newGapStart, gapStart, gapEnd - gapStart); - System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart - - newGapStart); - gapStart = newGapStart; - gapEnd = newGapEnd; + int adjIndex = searchFirst(oldStart); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark >= newGapEnd) + break; + m.mark -= size; + } } - else + else if (delta < 0) { - // Update the positions between newGapEnd and (old) gapEnd. The marks - // must be shifted by (gapEnd - gapStart). - adjustPositionsInRange(gapEnd, newGapEnd, -(gapEnd - gapStart)); - System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart - - gapStart); - gapStart = newGapStart; - gapEnd = newGapEnd; + int adjIndex = searchFirst(newGapStart); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark >= oldEnd) + break; + m.mark += size; + } } resetMarksAtZero(); } @@ -711,7 +703,18 @@ public class GapContent assert newGapStart < gapStart : "The new gap start must be less than the " + "old gap start."; - setPositionsInRange(newGapStart, gapStart, false); + + // Adjust positions. + int adjIndex = searchFirst(newGapStart); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark > gapStart) + break; + m.mark = gapEnd; + } + gapStart = newGapStart; resetMarksAtZero(); } @@ -731,7 +734,19 @@ public class GapContent assert newGapEnd > gapEnd : "The new gap end must be greater than the " + "old gap end."; - setPositionsInRange(gapEnd, newGapEnd, false); + + // Adjust marks. + int adjIndex = searchFirst(gapEnd); + int count = marks.size(); + for (int i = adjIndex; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.mark >= newGapEnd) + break; + m.mark = newGapEnd; + } + + gapEnd = newGapEnd; resetMarksAtZero(); } @@ -757,23 +772,88 @@ public class GapContent protected void replace(int position, int rmSize, Object addItems, int addSize) { - if (gapStart != position) - shiftGap(position); - - // Remove content - if (rmSize > 0) - shiftGapEndUp(gapEnd + rmSize); + if (addSize == 0) + { + removeImpl(position, rmSize); + return; + } + else if (rmSize > addSize) + { + removeImpl(position + addSize, rmSize - addSize); + } + else + { + int endSize = addSize - rmSize; + int end = addImpl(position + rmSize, endSize); + System.arraycopy(addItems, rmSize, buffer, end, endSize); + addSize = rmSize; + } + System.arraycopy(addItems, 0, buffer, position, addSize); + } + + /** + * Adjusts the positions and gap in response to a remove operation. + * + * @param pos the position at which to remove + * @param num the number of removed items + */ + private void removeImpl(int pos, int num) + { + if (num > 0) + { + int end = pos + num; + int newGapSize = (gapEnd - gapStart) + num; + if (end <= gapStart) + { + if (gapStart != end) + { + shiftGap(end); + } + shiftGapStartDown(gapStart - num); + } + else if (pos >= gapStart) + { + if (gapStart != pos) + { + shiftGap(pos); + } + shiftGapEndUp(gapStart + newGapSize); + } + else + { + shiftGapStartDown(pos); + shiftGapEndUp(gapStart + newGapSize); + } + } + } - // If gap is too small, enlarge the gap. - if ((gapEnd - gapStart) <= addSize) - shiftEnd((addSize - gapEnd + gapStart + 1) * 2 + gapEnd + DEFAULT_BUFSIZE); + /** + * Adjusts the positions and gap in response to an add operation. + * + * @param pos the position at which to add + * @param num the number of added items + * + * @return the adjusted position + */ + private int addImpl(int pos, int num) + { + int size = gapEnd - gapStart; + if (num == 0) + { + if (pos > gapStart) + pos += size; + return pos; + } - // Add new items to the buffer. - if (addItems != null) + shiftGap(pos); + if (num >= size) { - System.arraycopy(addItems, 0, buffer, gapStart, addSize); - gapStart += addSize; + shiftEnd(getArrayLength() - size + num); + size = gapEnd - gapStart; } + + gapStart += num; + return pos; } /** @@ -808,95 +888,34 @@ public class GapContent */ protected Vector getPositionsInRange(Vector v, int offset, int length) { - Vector res = v; - if (res == null) - res = new Vector(); - - int endOffs = offset + length; - - Set positionSet = positions.keySet(); - for (Iterator i = positionSet.iterator(); i.hasNext();) + int end = offset + length; + int startIndex; + int endIndex; + if (offset < gapStart) { - GapContentPosition p = (GapContentPosition) i.next(); - int offs = p.getOffset(); - if (offs >= offset && offs <= endOffs) - res.add(new UndoPosRef(p.mark)); + if (offset == 0) + startIndex = 0; + else + startIndex = searchFirst(offset); + if (end >= gapStart) + endIndex = searchFirst(end + (gapEnd - gapStart) + 1); + else + endIndex = searchFirst(end + 1); } - - return res; - } - - /** - * Crunches all positions in the specified range to either the start or - * end of that interval. The interval boundaries are meant to be inclusive - * [start, end]. - * - * @param start the start offset of the range - * @param end the end offset of the range - * @param toStart a boolean indicating if the positions should be crunched - * to the start (true) or to the end (false) - */ - private void setPositionsInRange(int start, int end, boolean toStart) - { - synchronized (this) + else { - // Find the start and end indices in the positionMarks array. - Mark m = new Mark(0); // For comparison / search only. - m.mark = start; - int startIndex = search(marks, m); - if (startIndex < 0) // Translate to insertion index, if not found. - startIndex = - startIndex - 1; - m.mark = end; - int endIndex = search(marks, m); - if (endIndex < 0) // Translate to insertion index - 1, if not found. - endIndex = - endIndex - 2; - - // Actually adjust the marks. - for (int i = startIndex; i <= endIndex; i++) - ((Mark) marks.get(i)).mark = toStart ? start : end; + startIndex = searchFirst(offset + (gapEnd - gapStart)); + endIndex = searchFirst(end + (gapEnd - gapStart) + 1); } - - } - - /** - * Adjusts the mark of all <code>Position</code>s that are in the range - * specified by <code>offset</code> and </code>length</code> within - * the buffer array by <code>increment</code> - * - * @param startOffs the start offset of the range to search - * @param endOffs the length of the range to search, -1 means all to the end - * @param incr the increment - */ - private void adjustPositionsInRange(int startOffs, int endOffs, int incr) - { - synchronized (this) + if (v == null) + v = new Vector(); + for (int i = startIndex; i < endIndex; i++) { - // Find the start and end indices in the positionMarks array. - Mark m = new Mark(0); // For comparison / search only. - - m.mark = startOffs; - int startIndex = search(marks, m); - if (startIndex < 0) // Translate to insertion index, if not found. - startIndex = - startIndex - 1; - - m.mark = endOffs; - int endIndex; - if (endOffs == -1) - endIndex = marks.size() - 1; - else - { - endIndex = search(marks, m); - if (endIndex < 0) // Translate to insertion index - 1, if not found. - endIndex = - endIndex - 2; - } - // Actually adjust the marks. - for (int i = startIndex; i <= endIndex; i++) { - ((Mark) marks.get(i)).mark += incr; - } + v.add(new UndoPosRef((Mark) marks.get(i))); } - + return v; } - + /** * Resets all <code>Position</code> that have an offset of <code>0</code>, * to also have an array index of <code>0</code>. This might be necessary @@ -977,30 +996,6 @@ public class GapContent } /** - * Polls the queue of death for GapContentPositions, updates the - * corresponding reference count and removes the corresponding mark - * if the refcount reaches zero. - * - * This is package private to avoid accessor synthetic methods. - */ - void garbageCollect() - { - Reference ref = queueOfDeath.poll(); - while (ref != null) - { - if (ref != null) - { - GapContentPosition pos = (GapContentPosition) ref.get(); - Mark m = pos.mark; - m.refCount--; - if (m.refCount == 0) - marks.remove(m); - } - ref = queueOfDeath.poll(); - } - } - - /** * Searches the first occurance of object <code>o</code> in list * <code>l</code>. This performs a binary search by calling * {@link Collections#binarySearch(List, Object)} and when an object has been @@ -1008,22 +1003,93 @@ public class GapContent * list. The meaning of the return value is the same as in * <code>Collections.binarySearch()</code>. * - * @param l the list to search through * @param o the object to be searched * * @return the index of the first occurance of o in l, or -i + 1 if not found */ - int search(List l, Object o) + int search(Mark o) { - int i = Collections.binarySearch(l, o); - while (i > 0) + int foundInd = 0; + boolean found = false; + int low = 0; + int up = marks.size() - 1; + int mid = 0; + if (up > -1) { - Object o2 = l.get(i - 1); - if (o2.equals(o)) - i--; + int cmp = 0; + Mark last = (Mark) marks.get(up); + cmp = compare(o, last); + if (cmp > 0) + { + foundInd = up + 1; + found = true; + } else + { + while (low <= up && ! found) + { + mid = low + (up - low) / 2; + Mark m = (Mark) marks.get(mid); + cmp = compare(o, m); + if (cmp == 0) + { + foundInd = mid; + found = true; + } + else if (cmp < 0) + up = mid - 1; + else + low = mid + 1; + } + + if (! found) + foundInd = cmp < 0 ? mid : mid + 1; + } + } + return foundInd; + } + + private int searchFirst(int index) + { + searchMark.mark = Math.max(index, 1); + int i = search(searchMark); + for (int j = i - 1; j >= 0; j--) + { + Mark m = (Mark) marks.get(j); + if (m.mark != index) break; + i--; } return i; } + + /** + * Compares two marks. + * + * @param m1 the first mark + * @param m2 the second mark + * + * @return negative when m1 < m2, positive when m1 > m2 and 0 when equal + */ + private int compare(Mark m1, Mark m2) + { + return m1.mark - m2.mark; + } + + /** + * Collects and frees unused marks. + */ + private void garbageCollect() + { + int count = marks.size(); + ArrayList clean = new ArrayList(); + for (int i = 0; i < count; i++) + { + Mark m = (Mark) marks.get(i); + if (m.get() != null) + clean.add(m); + } + marks = clean; + garbageMarks = 0; + } } diff --git a/javax/swing/text/GlyphView.java b/javax/swing/text/GlyphView.java index 35c8dd5d7..d5070a6a9 100644 --- a/javax/swing/text/GlyphView.java +++ b/javax/swing/text/GlyphView.java @@ -278,44 +278,27 @@ public class GlyphView extends View implements TabableView, Cloneable public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1) { - Color oldColor = g.getColor(); - int height = (int) getHeight(view); + updateFontMetrics(view); + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); + TabExpander tabEx = view.getTabExpander(); Segment txt = view.getText(p0, p1); - Rectangle bounds = a.getBounds(); - TabExpander tabEx = null; - View parent = view.getParent(); - if (parent instanceof TabExpander) - tabEx = (TabExpander) parent; - - int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(), - bounds.x, tabEx, txt.offset); - // Fill the background of the text run. - Color background = view.getBackground(); - if (background != null) - { - g.setColor(background); - g.fillRect(bounds.x, bounds.y, width, height); - } - // Draw the actual text. - g.setColor(view.getForeground()); - g.setFont(view.getFont()); - int ascent = g.getFontMetrics().getAscent(); - Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx, - txt.offset); - - if (view.isStrikeThrough()) - { - int strikeHeight = (int) (getAscent(view) / 2); - g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.x + width, - bounds.y + strikeHeight); - } - if (view.isUnderline()) + + // Find out the X location at which we have to paint. + int x = r.x; + int p = view.getStartOffset(); + if (p != p0) { - int lineHeight = (int) getAscent(view); - g.drawLine(bounds.x, bounds.y + lineHeight, bounds.x + width, - bounds.y + lineHeight); + int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx, + p); + x += width; } - g.setColor(oldColor); + // Find out Y location. + int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent(); + + // Render the thing. + g.setFont(fontMetrics.getFont()); + Utilities.drawTabbedText(txt, x, y, g, tabEx, p0); + } /** @@ -497,6 +480,16 @@ public class GlyphView extends View implements TabableView, Cloneable private int length; /** + * The x location against which the tab expansion is done. + */ + private float tabX; + + /** + * The tab expander that is used in this view. + */ + private TabExpander tabExpander; + + /** * Creates a new <code>GlyphView</code> for the given <code>Element</code>. * * @param element the element that is rendered by this GlyphView @@ -555,11 +548,29 @@ public class GlyphView extends View implements TabableView, Cloneable int p0 = getStartOffset(); int p1 = getEndOffset(); + Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); Container c = getContainer(); - // Paint layered highlights if there are any. + + Color fg = getForeground(); + JTextComponent tc = null; if (c instanceof JTextComponent) { - JTextComponent tc = (JTextComponent) c; + tc = (JTextComponent) c; + if (! tc.isEnabled()) + fg = tc.getDisabledTextColor(); + } + Color bg = getBackground(); + if (bg != null) + { + g.setColor(bg); + System.err.println("fill background: " + bg); + g.fillRect(r.x, r.y, r.width, r.height); + } + + + // Paint layered highlights if there are any. + if (tc != null) + { Highlighter h = tc.getHighlighter(); if (h instanceof LayeredHighlighter) { @@ -568,7 +579,45 @@ public class GlyphView extends View implements TabableView, Cloneable } } - getGlyphPainter().paint(this, g, a, p0, p1); + g.setColor(fg); + glyphPainter.paint(this, g, a, p0, p1); + boolean underline = isUnderline(); + boolean striked = isStrikeThrough(); + if (underline || striked) + { + View parent = getParent(); + // X coordinate. + if (parent != null && parent.getEndOffset() == p1) + { + // Strip whitespace. + Segment s = getText(p0, p1); + while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1])) + { + p1--; + s.count--; + } + } + int x0 = r.x; + int p = getStartOffset(); + TabExpander tabEx = getTabExpander(); + if (p != p0) + x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0); + int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0); + // Y coordinate. + int y = r.y + r.height - (int) glyphPainter.getDescent(this); + if (underline) + { + int yTmp = y; + yTmp += 1; + g.drawLine(x0, yTmp, x1, yTmp); + } + if (striked) + { + int yTmp = y; + yTmp -= (int) glyphPainter.getAscent(this); + g.drawLine(x0, yTmp, x1, yTmp); + } + } } @@ -658,13 +707,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public TabExpander getTabExpander() { - TabExpander te = null; - View parent = getParent(); - - if (parent instanceof TabExpander) - te = (TabExpander) parent; - - return te; + return tabExpander; } /** @@ -678,8 +721,16 @@ public class GlyphView extends View implements TabableView, Cloneable public float getTabbedSpan(float x, TabExpander te) { checkPainter(); + TabExpander old = tabExpander; + tabExpander = te; + if (tabExpander != old) + { + // Changing the tab expander will lead to a relayout in the X_AXIS. + preferenceChanged(null, true, false); + } + tabX = x; return getGlyphPainter().getSpan(this, getStartOffset(), - getEndOffset(), te, x); + getEndOffset(), tabExpander, x); } /** @@ -693,23 +744,8 @@ public class GlyphView extends View implements TabableView, Cloneable */ public float getPartialSpan(int p0, int p1) { - Element el = getElement(); - Document doc = el.getDocument(); - Segment seg = new Segment(); - try - { - doc.getText(p0, p1 - p0, seg); - } - catch (BadLocationException ex) - { - AssertionError ae; - ae = new AssertionError("BadLocationException must not be thrown " - + "here"); - ae.initCause(ex); - throw ae; - } - FontMetrics fm = null; // Fetch font metrics somewhere. - return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0); + checkPainter(); + return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX); } /** @@ -746,6 +782,8 @@ public class GlyphView extends View implements TabableView, Cloneable return offs; } + private Segment cached = new Segment(); + /** * Returns the text segment that this view is responsible for. * @@ -756,10 +794,9 @@ public class GlyphView extends View implements TabableView, Cloneable */ public Segment getText(int p0, int p1) { - Segment txt = new Segment(); try { - getDocument().getText(p0, p1 - p0, txt); + getDocument().getText(p0, p1 - p0, cached); } catch (BadLocationException ex) { @@ -770,7 +807,7 @@ public class GlyphView extends View implements TabableView, Cloneable throw ae; } - return txt; + return cached; } /** @@ -938,6 +975,8 @@ public class GlyphView extends View implements TabableView, Cloneable if (p0 != getStartOffset() || end != getEndOffset()) { brokenView = createFragment(p0, end); + if (brokenView instanceof GlyphView) + ((GlyphView) brokenView).tabX = pos; } } return brokenView; @@ -1007,7 +1046,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - preferenceChanged(this, true, true); + preferenceChanged(null, true, true); } /** @@ -1022,7 +1061,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - preferenceChanged(this, true, false); + preferenceChanged(null, true, false); } /** @@ -1037,7 +1076,7 @@ public class GlyphView extends View implements TabableView, Cloneable */ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf) { - preferenceChanged(this, true, false); + preferenceChanged(null, true, false); } /** diff --git a/javax/swing/text/StyleContext.java b/javax/swing/text/StyleContext.java index b01d1060f..4dded0d04 100644 --- a/javax/swing/text/StyleContext.java +++ b/javax/swing/text/StyleContext.java @@ -48,10 +48,12 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.ref.WeakReference; +import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.Hashtable; import java.util.Iterator; +import java.util.Map; import java.util.WeakHashMap; import javax.swing.event.ChangeEvent; @@ -467,7 +469,8 @@ public class StyleContext /** * A pool of immutable AttributeSets. */ - private transient WeakHashMap attributeSetPool = new WeakHashMap(); + private transient Map attributeSetPool = + Collections.synchronizedMap(new WeakHashMap()); /** * Creates a new instance of the style context. Add the default style @@ -545,7 +548,7 @@ public class StyleContext throws ClassNotFoundException, IOException { search = new SimpleAttributeSet(); - attributeSetPool = new WeakHashMap(); + attributeSetPool = Collections.synchronizedMap(new WeakHashMap()); in.defaultReadObject(); } @@ -650,7 +653,8 @@ public class StyleContext return defaultStyleContext; } - public AttributeSet addAttribute(AttributeSet old, Object name, Object value) + public synchronized AttributeSet addAttribute(AttributeSet old, Object name, + Object value) { AttributeSet ret; if (old.getAttributeCount() + 1 < getCompressionThreshold()) @@ -670,7 +674,8 @@ public class StyleContext return ret; } - public AttributeSet addAttributes(AttributeSet old, AttributeSet attributes) + public synchronized AttributeSet addAttributes(AttributeSet old, + AttributeSet attributes) { AttributeSet ret; if (old.getAttributeCount() + attributes.getAttributeCount() @@ -701,7 +706,8 @@ public class StyleContext cleanupPool(); } - public AttributeSet removeAttribute(AttributeSet old, Object name) + public synchronized AttributeSet removeAttribute(AttributeSet old, + Object name) { AttributeSet ret; if (old.getAttributeCount() - 1 <= getCompressionThreshold()) @@ -721,7 +727,8 @@ public class StyleContext return ret; } - public AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes) + public synchronized AttributeSet removeAttributes(AttributeSet old, + AttributeSet attributes) { AttributeSet ret; if (old.getAttributeCount() <= getCompressionThreshold()) @@ -741,7 +748,8 @@ public class StyleContext return ret; } - public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) + public synchronized AttributeSet removeAttributes(AttributeSet old, + Enumeration<?> names) { AttributeSet ret; if (old.getAttributeCount() <= getCompressionThreshold()) diff --git a/javax/swing/text/Utilities.java b/javax/swing/text/Utilities.java index fa2d1ab52..d49d806cf 100644 --- a/javax/swing/text/Utilities.java +++ b/javax/swing/text/Utilities.java @@ -89,12 +89,12 @@ public class Utilities // The font metrics of the current selected font. FontMetrics metrics = g.getFontMetrics(); + int ascent = metrics.getAscent(); // The current x and y pixel coordinates. int pixelX = x; - int pixelWidth = 0; int pos = s.offset; int len = 0; @@ -103,39 +103,43 @@ public class Utilities for (int offset = s.offset; offset < end; ++offset) { char c = buffer[offset]; - if (c == '\t') + switch (c) { + case '\t': if (len > 0) { g.drawChars(buffer, pos, len, pixelX, y); - pixelX += pixelWidth; - pixelWidth = 0; + pixelX += metrics.charsWidth(buffer, pos, len); + len = 0; } pos = offset+1; - len = 0; + if (e != null) + pixelX = (int) e.nextTabStop((float) pixelX, startOffset + offset + - s.offset); + else + pixelX += metrics.charWidth(' '); + x = pixelX; + break; + case '\n': + case '\r': + if (len > 0) { + g.drawChars(buffer, pos, len, pixelX, y); + pixelX += metrics.charsWidth(buffer, pos, len); + len = 0; + } + x = pixelX; + break; + default: + len += 1; } - - switch (c) - { - case '\t': - // In case we have a tab, we just 'jump' over the tab. - // When we have no tab expander we just use the width of ' '. - if (e != null) - pixelX = (int) e.nextTabStop(pixelX, - startOffset + offset - s.offset); - else - pixelX += metrics.charWidth(' '); - break; - default: - ++len; - pixelWidth += metrics.charWidth(buffer[offset]); - break; - } } if (len > 0) - g.drawChars(buffer, pos, len, pixelX, y); + { + g.drawChars(buffer, pos, len, pixelX, y); + pixelX += metrics.charsWidth(buffer, pos, len); + } - return pixelX + pixelWidth; + return pixelX; } /** @@ -163,7 +167,9 @@ public class Utilities // The current maximum width. int maxWidth = 0; - for (int offset = s.offset; offset < (s.offset + s.count); ++offset) + int end = s.offset + s.count; + int count = 0; + for (int offset = s.offset; offset < end; offset++) { switch (buffer[offset]) { @@ -179,21 +185,18 @@ public class Utilities case '\n': // In case we have a newline, we must 'draw' // the buffer and jump on the next line. - pixelX += metrics.charWidth(buffer[offset]); - maxWidth = Math.max(maxWidth, pixelX - x); - pixelX = x; - break; - default: - // Here we draw the char. - pixelX += metrics.charWidth(buffer[offset]); - break; - } + pixelX += metrics.charsWidth(buffer, offset - count, count); + count = 0; + break; + default: + count++; + } } // Take the last line into account. - maxWidth = Math.max(maxWidth, pixelX - x); + pixelX += metrics.charsWidth(buffer, end - count, count); - return maxWidth; + return pixelX - x; } /** @@ -228,43 +231,41 @@ public class Utilities int x, TabExpander te, int p0, boolean round) { - // At the end of the for loop, this holds the requested model location - int pos; + int found = s.count; int currentX = x0; - int width = 0; + int nextX = currentX; - for (pos = 0; pos < s.count; pos++) + int end = s.offset + s.count; + for (int pos = s.offset; pos < end && found == s.count; pos++) { - char nextChar = s.array[s.offset+pos]; - - if (nextChar == 0) - break; + char nextChar = s.array[pos]; if (nextChar != '\t') - width = fm.charWidth(nextChar); + nextX += fm.charWidth(nextChar); else { if (te == null) - width = fm.charWidth(' '); + nextX += fm.charWidth(' '); else - width = ((int) te.nextTabStop(currentX, pos)) - currentX; + nextX += ((int) te.nextTabStop(nextX, p0 + pos - s.offset)); } - if (round) + if (x >= currentX && x < nextX) { - if (currentX + (width>>1) > x) - break; - } - else - { - if (currentX + width > x) - break; + // Found position. + if ((! round) || ((x - currentX) < (nextX - x))) + { + found = pos - s.offset; + } + else + { + found = pos + 1 - s.offset; + } } - - currentX += width; + currentX = nextX; } - return pos; + return found; } /** diff --git a/javax/swing/text/html/BlockView.java b/javax/swing/text/html/BlockView.java index d7519ef9a..2e781412c 100644 --- a/javax/swing/text/html/BlockView.java +++ b/javax/swing/text/html/BlockView.java @@ -171,6 +171,22 @@ public class BlockView extends BoxView } else r = super.calculateMinorAxisRequirements(axis, r); + + // Apply text alignment if appropriate. + if (axis == X_AXIS) + { + Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN); + if (o != null) + { + String al = o.toString().trim(); + if (al.equals("center")) + r.alignment = 0.5f; + else if (al.equals("right")) + r.alignment = 1.0f; + else + r.alignment = 0.0f; + } + } return r; } diff --git a/javax/swing/text/html/CSS.java b/javax/swing/text/html/CSS.java index 6461dca9a..c82b6c537 100644 --- a/javax/swing/text/html/CSS.java +++ b/javax/swing/text/html/CSS.java @@ -415,6 +415,8 @@ public class CSS implements Serializable new Attribute("border-left-color", false, null); static final Attribute BORDER_RIGHT_COLOR = new Attribute("border-right-color", false, null); + static final Attribute BORDER_SPACING = + new Attribute("border-spacing", false, null); /** * The attribute string. @@ -516,7 +518,10 @@ public class CSS implements Serializable else if (att == Attribute.MARGIN || att == Attribute.MARGIN_BOTTOM || att == Attribute.MARGIN_LEFT || att == Attribute.MARGIN_RIGHT || att == Attribute.MARGIN_TOP || att == Attribute.WIDTH - || att == Attribute.HEIGHT) + || att == Attribute.HEIGHT + || att == Attribute.PADDING || att == Attribute.PADDING_BOTTOM + || att == Attribute.PADDING_LEFT || att == Attribute.PADDING_RIGHT + || att == Attribute.PADDING_TOP) o = new Length(v); else if (att == Attribute.BORDER_WIDTH || att == Attribute.BORDER_TOP_WIDTH || att == Attribute.BORDER_LEFT_WIDTH @@ -543,7 +548,7 @@ public class CSS implements Serializable String token = tokens.nextToken(); if (CSSColor.isValidColor(token)) atts.addAttribute(Attribute.BACKGROUND_COLOR, - getValue(Attribute.BACKGROUND_COLOR, token)); + new CSSColor(token)); } } } diff --git a/javax/swing/text/html/HTMLDocument.java b/javax/swing/text/html/HTMLDocument.java index 26e3fb4bc..ee59d7025 100644 --- a/javax/swing/text/html/HTMLDocument.java +++ b/javax/swing/text/html/HTMLDocument.java @@ -184,8 +184,6 @@ public class HTMLDocument extends DefaultStyledDocument protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { - RunElement el = new RunElement(parent, a, p0, p1); - el.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); return new RunElement(parent, a, p0, p1); } @@ -454,6 +452,8 @@ public class HTMLDocument extends DefaultStyledDocument String name = null; if (tag != null) name = tag.toString(); + if (name == null) + name = super.getName(); return name; } } @@ -490,6 +490,8 @@ public class HTMLDocument extends DefaultStyledDocument String name = null; if (tag != null) name = tag.toString(); + if (name == null) + name = super.getName(); return name; } @@ -511,7 +513,17 @@ public class HTMLDocument extends DefaultStyledDocument * @author Anthony Balkissoon abalkiss at redhat dot com */ public class HTMLReader extends HTMLEditorKit.ParserCallback - { + { + /** + * The maximum token threshold. We don't grow it larger than this. + */ + private static final int MAX_THRESHOLD = 10000; + + /** + * The threshold growth factor. + */ + private static final int GROW_THRESHOLD = 5; + /** * Holds the current character attribute set * */ @@ -523,12 +535,6 @@ 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. - */ - Stack parseStack = new Stack(); /** A mapping between HTML.Tag objects and the actions that handle them **/ HashMap tagToAction; @@ -610,6 +616,11 @@ public class HTMLDocument extends DefaultStyledDocument */ Document textAreaDocument; + /** + * The token threshold. This gets increased while loading. + */ + private int threshold; + public class TagAction { /** @@ -816,7 +827,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public void start(HTML.Tag t, MutableAttributeSet a) { - blockOpen(t, a); + super.start(t, a); inParagraph = true; } @@ -826,7 +837,7 @@ public class HTMLDocument extends DefaultStyledDocument */ public void end(HTML.Tag t) { - blockClose(t); + super.end(t); inParagraph = false; } } @@ -1162,6 +1173,7 @@ public class HTMLDocument extends DefaultStyledDocument this.offset = offset; this.popDepth = popDepth; this.pushDepth = pushDepth; + threshold = getTokenThreshold(); initTags(); } @@ -1299,18 +1311,28 @@ public class HTMLDocument extends DefaultStyledDocument */ public void flush() throws BadLocationException { - DefaultStyledDocument.ElementSpec[] elements; - elements = new DefaultStyledDocument.ElementSpec[parseBuffer.size()]; - parseBuffer.copyInto(elements); - parseBuffer.removeAllElements(); - if (offset == 0) - create(elements); - else - insert(offset, elements); + flushImpl(); + } - offset += HTMLDocument.this.getLength() - offset; + /** + * Flushes the buffer and handle partial inserts. + * + */ + private void flushImpl() + throws BadLocationException + { + int oldLen = getLength(); + int size = parseBuffer.size(); + ElementSpec[] elems = new ElementSpec[size]; + parseBuffer.copyInto(elems); + if (oldLen == 0) + create(elems); + else + insert(offset, elems); + parseBuffer.removeAllElements(); + offset += getLength() - oldLen; } - + /** * This method is called by the parser to indicate a block of * text was encountered. Should insert the text appropriately. @@ -1512,7 +1534,6 @@ public class HTMLDocument extends DefaultStyledDocument DefaultStyledDocument.ElementSpec element; - parseStack.push(t); AbstractDocument.AttributeContext ctx = getAttributeContext(); AttributeSet copy = attr.copyAttributes(); copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t); @@ -1542,25 +1563,17 @@ public class HTMLDocument extends DefaultStyledDocument // If the previous tag is a start tag then we insert a synthetic // content tag. DefaultStyledDocument.ElementSpec prev; - prev = (DefaultStyledDocument.ElementSpec) - parseBuffer.get(parseBuffer.size() - 1); - if (prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType) + prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec) + parseBuffer.get(parseBuffer.size() - 1) : null; + if (prev != null && + prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType) { - AbstractDocument.AttributeContext ctx = getAttributeContext(); - AttributeSet attributes = ctx.getEmptySet(); - attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute, - HTML.Tag.CONTENT); - element = new DefaultStyledDocument.ElementSpec(attributes, - DefaultStyledDocument.ElementSpec.ContentType, - new char[0], 0, 0); - parseBuffer.add(element); + addContent(new char[]{' '}, 0, 1); } element = new DefaultStyledDocument.ElementSpec(null, DefaultStyledDocument.ElementSpec.EndTagType); parseBuffer.addElement(element); - if (parseStack.size() > 0) - parseStack.pop(); } /** @@ -1615,11 +1628,13 @@ public class HTMLDocument extends DefaultStyledDocument // Add the element to the buffer parseBuffer.addElement(element); - if (parseBuffer.size() > HTMLDocument.this.getTokenThreshold()) + if (parseBuffer.size() > threshold) { + if (threshold <= MAX_THRESHOLD) + threshold *= GROW_THRESHOLD; try { - flush(); + flushImpl(); } catch (BadLocationException ble) { @@ -1734,10 +1749,6 @@ public class HTMLDocument extends DefaultStyledDocument } }; - // Set the parent HTML tag. - reader.parseStack.push(parent.getAttributes().getAttribute( - StyleConstants.NameAttribute)); - return reader; } diff --git a/javax/swing/text/html/HTMLEditorKit.java b/javax/swing/text/html/HTMLEditorKit.java index 85d5221d3..f3a3d90b6 100644 --- a/javax/swing/text/html/HTMLEditorKit.java +++ b/javax/swing/text/html/HTMLEditorKit.java @@ -39,8 +39,6 @@ exception statement from your version. */ package javax.swing.text.html; -import gnu.classpath.NotImplementedException; - import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -290,7 +288,7 @@ public class HTMLEditorKit * Tag to check for in the document. */ protected HTML.Tag parentTag; - + /** * Initializes all fields. * @@ -394,20 +392,9 @@ public class HTMLEditorKit Element insertElement, String html, HTML.Tag parentTag, HTML.Tag addTag) - throws NotImplementedException { - /* - As its name implies, this protected method is used when HTML is inserted at a - boundary. (A boundary in this case is an offset in doc that exactly matches the - beginning offset of the parentTag.) It performs the extra work required to keep - the tag stack in shape and then calls insertHTML(). The editor and doc argu- - ments are the editor pane and document where the HTML should go. The offset - argument represents the cursor location or selection start in doc. The insert- - Element and parentTag arguments are used to calculate the proper number of - tag pops and pushes before inserting the HTML (via html and addTag, which are - passed directly to insertHTML()). - */ - // FIXME: not implemented + insertAtBoundry(editor, doc, offset, insertElement, + html, parentTag, addTag); } /** @@ -433,8 +420,50 @@ public class HTMLEditorKit String html, HTML.Tag parentTag, HTML.Tag addTag) { - insertAtBoundary(editor, doc, offset, insertElement, - html, parentTag, addTag); + Element parent = insertElement; + Element el; + // Find common parent element. + if (offset > 0 || insertElement == null) + { + el = doc.getDefaultRootElement(); + while (el != null && el.getStartOffset() != offset + && ! el.isLeaf()) + el = el.getElement(el.getElementIndex(offset)); + parent = el != null ? el.getParentElement() : null; + } + if (parent != null) + { + int pops = 0; + int pushes = 0; + if (offset == 0 && insertElement != null) + { + el = parent; + while (el != null && ! el.isLeaf()) + { + el = el.getElement(el.getElementIndex(offset)); + pops++; + } + } + else + { + el = parent; + offset--; + while (el != null && ! el.isLeaf()) + { + el = el.getElement(el.getElementIndex(offset)); + pops++; + } + el = parent; + offset++; + while (el != null && el != insertElement) + { + el = el.getElement(el.getElementIndex(offset)); + pushes++; + } + } + pops = Math.max(0, pops - 1); + insertHTML(editor, doc, offset, html, pops, pushes, addTag); + } } /** @@ -444,16 +473,97 @@ public class HTMLEditorKit */ public void actionPerformed(ActionEvent ae) { - Object source = ae.getSource(); - if (source instanceof JEditorPane) + JEditorPane source = getEditor(ae); + if (source != null) + { + HTMLDocument d = getHTMLDocument(source); + int offset = source.getSelectionStart(); + int length = d.getLength(); + boolean inserted = true; + if (! tryInsert(source, d, offset, parentTag, addTag)) + { + inserted = tryInsert(source, d, offset, alternateParentTag, + alternateAddTag); + } + if (inserted) + adjustSelection(source, d, offset, length); + } + } + + /** + * Tries to insert the html chunk to the specified <code>addTag</code>. + * + * @param pane the editor + * @param doc the document + * @param offset the offset at which to insert + * @param tag the tag at which to insert + * @param addTag the add tag + * + * @return <code>true</code> when the html has been inserted successfully, + * <code>false</code> otherwise + */ + private boolean tryInsert(JEditorPane pane, HTMLDocument doc, int offset, + HTML.Tag tag, HTML.Tag addTag) + { + boolean inserted = false; + Element el = findElementMatchingTag(doc, offset, tag); + if (el != null && el.getStartOffset() == offset) + { + insertAtBoundary(pane, doc, offset, el, html, tag, addTag); + inserted = true; + } + else if (offset > 0) { - JEditorPane pane = ((JEditorPane) source); - Document d = pane.getDocument(); - if (d instanceof HTMLDocument) - insertHTML(pane, (HTMLDocument) d, 0, html, 0, 0, addTag); - // FIXME: is this correct parameters? + int depth = elementCountToTag(doc, offset - 1, tag); + if (depth != -1) + { + insertHTML(pane, doc, offset, html, depth, 0, addTag); + inserted = true; + } + } + return inserted; + } + + /** + * Adjusts the selection after an insertion has been performed. + * + * @param pane the editor pane + * @param doc the document + * @param offset the insert offset + * @param oldLen the old document length + */ + private void adjustSelection(JEditorPane pane, HTMLDocument doc, + int offset, int oldLen) + { + int newLen = doc.getLength(); + if (newLen != oldLen && offset < newLen) + { + if (offset > 0) + { + String text; + try + { + text = doc.getText(offset - 1, 1); + } + catch (BadLocationException ex) + { + text = null; + } + if (text != null && text.length() > 0 + && text.charAt(0) == '\n') + { + pane.select(offset, offset); + } + else + { + pane.select(offset + 1, offset + 1); + } + } + else + { + pane.select(1, 1); + } } - // FIXME: else not implemented } } @@ -885,8 +995,36 @@ public class HTMLEditorKit /** * Actions for HTML */ - private static final Action[] defaultActions = { - // FIXME: Add default actions for html + private static final Action[] defaultActions = + { + new InsertHTMLTextAction("InsertTable", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableRow", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.TABLE, HTML.Tag.TR, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertTableCell", + "<table border=1><tr><td></td></tr></table>", + HTML.Tag.TR, HTML.Tag.TD, + HTML.Tag.BODY, HTML.Tag.TABLE), + new InsertHTMLTextAction("InsertUnorderedList", + "<ul><li></li></ul>", + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertUnorderedListItem", + "<ul><li></li></ul>", + HTML.Tag.UL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.UL), + new InsertHTMLTextAction("InsertOrderedList", + "<ol><li></li></ol>", + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertOrderedListItem", + "<ol><li></li></ol>", + HTML.Tag.OL, HTML.Tag.LI, + HTML.Tag.BODY, HTML.Tag.OL), + new InsertHTMLTextAction("InsertPre", + "<pre></pre>", HTML.Tag.BODY, HTML.Tag.PRE) + // TODO: The reference impl has an InsertHRAction too. }; /** @@ -956,8 +1094,15 @@ public class HTMLEditorKit */ public Document createDefaultDocument() { - HTMLDocument document = new HTMLDocument(getStyleSheet()); + // Protect the shared stylesheet. + StyleSheet styleSheet = getStyleSheet(); + StyleSheet ss = new StyleSheet(); + ss.addStyleSheet(styleSheet); + + HTMLDocument document = new HTMLDocument(ss); document.setParser(getParser()); + document.setAsynchronousLoadPriority(4); + document.setTokenThreshold(100); return document; } diff --git a/javax/swing/text/html/ImageView.java b/javax/swing/text/html/ImageView.java index ff0d3ea40..f073c6d05 100644 --- a/javax/swing/text/html/ImageView.java +++ b/javax/swing/text/html/ImageView.java @@ -2,17 +2,19 @@ package javax.swing.text.html; import gnu.javax.swing.text.html.CombinedAttributes; import gnu.javax.swing.text.html.ImageViewIconFactory; +import gnu.javax.swing.text.html.css.Length; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.Toolkit; +import java.awt.image.ImageObserver; import java.net.MalformedURLException; import java.net.URL; import javax.swing.Icon; -import javax.swing.ImageIcon; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; @@ -29,15 +31,39 @@ import javax.swing.text.html.HTML.Attribute; public class ImageView extends View { /** + * Tracks image loading state and performs the necessary layout updates. + */ + class Observer + implements ImageObserver + { + + public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height) + { + boolean widthChanged = false; + if ((flags & ImageObserver.WIDTH) != 0 + && ! getElement().getAttributes().isDefined(HTML.Attribute.WIDTH)) + widthChanged = true; + boolean heightChanged = false; + if ((flags & ImageObserver.HEIGHT) != 0 + && ! getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT)) + widthChanged = true; + if (widthChanged || heightChanged) + preferenceChanged(ImageView.this, widthChanged, heightChanged); + return (flags & ALLBITS) != 0; + } + + } + + /** * True if the image loads synchronuosly (on demand). By default, the image * loads asynchronuosly. */ boolean loadOnDemand; - + /** * The image icon, wrapping the image, */ - ImageIcon imageIcon; + Image image; /** * The image state. @@ -45,6 +71,46 @@ public class ImageView extends View byte imageState = MediaTracker.LOADING; /** + * True when the image needs re-loading, false otherwise. + */ + private boolean reloadImage; + + /** + * True when the image properties need re-loading, false otherwise. + */ + private boolean reloadProperties; + + /** + * True when the width is set as CSS/HTML attribute. + */ + private boolean haveWidth; + + /** + * True when the height is set as CSS/HTML attribute. + */ + private boolean haveHeight; + + /** + * True when the image is currently loading. + */ + private boolean loading; + + /** + * The current width of the image. + */ + private int width; + + /** + * The current height of the image. + */ + private int height; + + /** + * Our ImageObserver for tracking the loading state. + */ + private ImageObserver observer; + + /** * Creates the image view that represents the given element. * * @param element the element, represented by this image view. @@ -52,25 +118,34 @@ public class ImageView extends View public ImageView(Element element) { super(element); + observer = new Observer(); + reloadProperties = true; + reloadImage = true; } /** * Load or reload the image. This method initiates the image reloading. After * the image is ready, the repaint event will be scheduled. The current image, * if it already exists, will be discarded. - * - * @param itsTime - * also load if the "on demand" property is set */ - void reloadImage(boolean itsTime) + private void reloadImage() { - URL url = getImageURL(); - if (url == null) - imageState = (byte) MediaTracker.ERRORED; - else if (!(loadOnDemand && !itsTime)) - imageIcon = new ImageIcon(url); - else - imageState = (byte) MediaTracker.LOADING; + loading = true; + reloadImage = false; + haveWidth = false; + haveHeight = false; + image = null; + width = 0; + height = 0; + try + { + loadImage(); + updateSize(); + } + finally + { + loading = false; + } } /** @@ -159,10 +234,8 @@ public class ImageView extends View */ public Image getImage() { - if (imageIcon == null) - return null; - else - return imageIcon.getImage(); + updateState(); + return image; } /** @@ -245,9 +318,9 @@ public class ImageView extends View if (axis == View.X_AXIS) { - Object w = attrs.getAttribute(Attribute.WIDTH); - if (w != null) - return Integer.parseInt(w.toString()); + Object w = attrs.getAttribute(CSS.Attribute.WIDTH); + if (w instanceof Length) + return ((Length) w).getValue(); else if (image != null) return image.getWidth(getContainer()); else @@ -255,9 +328,9 @@ public class ImageView extends View } else if (axis == View.Y_AXIS) { - Object w = attrs.getAttribute(Attribute.HEIGHT); - if (w != null) - return Integer.parseInt(w.toString()); + Object w = attrs.getAttribute(CSS.Attribute.HEIGHT); + if (w instanceof Length) + return ((Length) w).getValue(); else if (image != null) return image.getHeight(getContainer()); else @@ -291,7 +364,7 @@ public class ImageView extends View { return getAltText(); } - + /** * Paints the image or one of the two image state icons. The image is resized * to the shape bounds. If there is no image available, the alternative text @@ -305,83 +378,22 @@ public class ImageView extends View */ public void paint(Graphics g, Shape bounds) { - Rectangle r = bounds.getBounds(); - - if (imageIcon == null) - - { - // Loading image on demand, rendering the loading icon so far. - reloadImage(true); - - // The reloadImage sets the imageIcon, unless the URL is broken - // or malformed. - if (imageIcon != null) - { - if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE) - { - // Render "not ready" icon, unless the image is ready - // immediately. - renderIcon(g, r, getLoadingImageIcon()); - // Add the listener to repaint when the icon will be ready. - imageIcon.setImageObserver(getContainer()); - return; - } - } - else - { - renderIcon(g, r, getNoImageIcon()); - return; - } - } - - imageState = (byte) imageIcon.getImageLoadStatus(); - - switch (imageState) - { - case MediaTracker.ABORTED: - case MediaTracker.ERRORED: - renderIcon(g, r, getNoImageIcon()); - break; - case MediaTracker.LOADING: - // If the image is not loaded completely, we still render it, as the - // partial image may be available. - case MediaTracker.COMPLETE: + updateState(); + Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds + : bounds.getBounds(); + Image image = getImage(); + if (image != null) { - // Paint the scaled image. - Image scaled = imageIcon.getImage().getScaledInstance( - r.width, - r.height, - Image.SCALE_DEFAULT); - ImageIcon painter = new ImageIcon(scaled); - painter.paintIcon(getContainer(), g, r.x, r.y); + g.drawImage(image, r.x, r.y, r.width, r.height, observer); } - break; - } - } - - /** - * Render "no image" icon and the alternative "no image" text. The text is - * rendered right from the icon and is aligned to the icon bottom. - */ - private void renderIcon(Graphics g, Rectangle bounds, Icon icon) - { - Shape current = g.getClip(); - try + else { - g.setClip(bounds); + Icon icon = getNoImageIcon(); if (icon != null) - { - icon.paintIcon(getContainer(), g, bounds.x, bounds.y); - g.drawString(getAltText(), bounds.x + icon.getIconWidth(), - bounds.y + icon.getIconHeight()); - } - } - finally - { - g.setClip(current); + icon.paintIcon(getContainer(), g, r.x, r.y); } } - + /** * Set if the image should be loaded only when needed (synchronuosly). By * default, the image loads asynchronuosly. If the image is not yet ready, the @@ -398,9 +410,7 @@ public class ImageView extends View */ protected void setPropertiesFromAttributes() { - // In the current implementation, nothing is cached yet, unless the image - // itself. - imageIcon = null; + // FIXME: Implement this properly. } /** @@ -436,9 +446,90 @@ public class ImageView extends View */ public void setSize(float width, float height) { - if (imageIcon == null) - reloadImage(false); + updateState(); + // TODO: Implement this when we have an alt view for the alt=... attribute. } - + /** + * This makes sure that the image and properties have been loaded. + */ + private void updateState() + { + if (reloadImage) + reloadImage(); + if (reloadProperties) + setPropertiesFromAttributes(); + } + + /** + * Actually loads the image. + */ + private void loadImage() + { + URL src = getImageURL(); + Image newImage = null; + if (src != null) + { + // Call getImage(URL) to allow the toolkit caching of that image URL. + newImage = Toolkit.getDefaultToolkit().getImage(src); + if (newImage != null && getLoadsSynchronously()) + { + // Load image synchronously. + MediaTracker tracker = new MediaTracker(getContainer()); + tracker.addImage(newImage, 0); + try + { + tracker.waitForID(0); + } + catch (InterruptedException ex) + { + Thread.interrupted(); + } + + } + } + image = newImage; + } + + /** + * Updates the size parameters of the image. + */ + private void updateSize() + { + int newW = 0; + int newH = 0; + Image newIm = getImage(); + if (newIm != null) + { + AttributeSet atts = getAttributes(); + // Fetch width. + Length l = (Length) atts.getAttribute(CSS.Attribute.WIDTH); + if (l != null) + { + newW = (int) l.getValue(); + haveWidth = true; + } + else + { + newW = newIm.getWidth(observer); + } + // Fetch height. + l = (Length) atts.getAttribute(CSS.Attribute.HEIGHT); + if (l != null) + { + newH = (int) l.getValue(); + haveHeight = true; + } + else + { + newW = newIm.getWidth(observer); + } + // Go and trigger loading. + Toolkit tk = Toolkit.getDefaultToolkit(); + if (haveWidth || haveHeight) + tk.prepareImage(newIm, width, height, observer); + else + tk.prepareImage(newIm, -1, -1, observer); + } + } } diff --git a/javax/swing/text/html/ParagraphView.java b/javax/swing/text/html/ParagraphView.java index e3f2817be..8443515d3 100644 --- a/javax/swing/text/html/ParagraphView.java +++ b/javax/swing/text/html/ParagraphView.java @@ -187,28 +187,14 @@ public class ParagraphView SizeRequirements r) { r = super.calculateMinorAxisRequirements(axis, r); - if (setCSSSpan(r, axis)) + if (! setCSSSpan(r, axis)) { - // If we have set the span from CSS, then we need to adjust - // the margins. - SizeRequirements parent = super.calculateMinorAxisRequirements(axis, - null); int margin = axis == X_AXIS ? getLeftInset() + getRightInset() : getTopInset() + getBottomInset(); r.minimum -= margin; r.preferred -= margin; r.maximum -= margin; } - else - { - float min = 0; - int n = getLayoutViewCount(); - for (int i = 0; i < n; i++) - min = Math.max(getLayoutView(i).getMinimumSpan(axis), min); - r.minimum = (int) min; - r.preferred = Math.max(r.preferred, r.minimum); - r.maximum = Math.max(r.maximum, r.preferred); - } return r; } diff --git a/javax/swing/text/html/StyleSheet.java b/javax/swing/text/html/StyleSheet.java index add22e01c..3322a390c 100644 --- a/javax/swing/text/html/StyleSheet.java +++ b/javax/swing/text/html/StyleSheet.java @@ -50,6 +50,8 @@ import gnu.javax.swing.text.html.css.Selector; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -107,9 +109,9 @@ public class StyleSheet extends StyleContext implements CSSParserCallback { /** - * The current style. + * The current styles. */ - private CSSStyle style; + private CSSStyle[] styles; /** * The precedence of the stylesheet to be parsed. @@ -133,9 +135,11 @@ public class StyleSheet extends StyleContext * * @param sel the selector */ - public void startStatement(Selector sel) + public void startStatement(Selector[] sel) { - style = new CSSStyle(precedence, sel); + styles = new CSSStyle[sel.length]; + for (int i = 0; i < sel.length; i++) + styles[i] = new CSSStyle(precedence, sel[i]); } /** @@ -143,8 +147,9 @@ public class StyleSheet extends StyleContext */ public void endStatement() { - css.add(style); - style = null; + for (int i = 0; i < styles.length; i++) + css.add(styles[i]); + styles = null; } /** @@ -157,9 +162,13 @@ public class StyleSheet extends StyleContext { CSS.Attribute cssAtt = CSS.getAttribute(property); Object val = CSS.getValue(cssAtt, value); - CSS.addInternal(style, cssAtt, value); - if (cssAtt != null) - style.addAttribute(cssAtt, val); + for (int i = 0; i < styles.length; i++) + { + CSSStyle style = styles[i]; + CSS.addInternal(style, cssAtt, value); + if (cssAtt != null) + style.addAttribute(cssAtt, val); + } } } @@ -172,11 +181,11 @@ public class StyleSheet extends StyleContext implements Style, Comparable { - static final int PREC_UA = 400000; - static final int PREC_NORM = 300000; + static final int PREC_UA = 0; + static final int PREC_NORM = 100000; static final int PREC_AUTHOR_NORMAL = 200000; - static final int PREC_AUTHOR_IMPORTANT = 100000; - static final int PREC_USER_IMPORTANT = 0; + static final int PREC_AUTHOR_IMPORTANT = 300000; + static final int PREC_USER_IMPORTANT = 400000; /** * The priority of this style when matching CSS selectors. @@ -231,8 +240,10 @@ public class StyleSheet extends StyleContext /** Base font size (int) */ int baseFontSize; - /** The style sheets stored. */ - StyleSheet[] styleSheet; + /** + * The linked style sheets stored. + */ + private ArrayList linked; /** * Maps element names (selectors) to AttributSet (the corresponding style @@ -424,6 +435,21 @@ public class StyleSheet extends StyleContext styles.add(style); } + // Add styles from linked stylesheets. + if (linked != null) + { + for (int i = linked.size() - 1; i >= 0; i--) + { + StyleSheet ss = (StyleSheet) linked.get(i); + for (int j = ss.css.size() - 1; j >= 0; j--) + { + CSSStyle style = (CSSStyle) ss.css.get(j); + if (style.selector.matches(tags, classes, ids)) + styles.add(style); + } + } + } + // Sort selectors. Collections.sort(styles); Style[] styleArray = new Style[styles.size()]; @@ -444,7 +470,6 @@ public class StyleSheet extends StyleContext */ public Style getRule(String selector) { - Selector sel = new Selector(selector); CSSStyle best = null; for (Iterator i = css.iterator(); i.hasNext();) { @@ -477,6 +502,9 @@ public class StyleSheet extends StyleContext // Shouldn't happen. And if, then we System.err.println("IOException while parsing stylesheet: " + ex.getMessage()); } + // Clean up resolved styles cache so that the new styles are recognized + // on next stylesheet request. + resolvedStyles.clear(); } /** @@ -546,11 +574,9 @@ public class StyleSheet extends StyleContext */ public void addStyleSheet(StyleSheet ss) { - if (styleSheet == null) - styleSheet = new StyleSheet[] {ss}; - else - System.arraycopy(new StyleSheet[] {ss}, 0, styleSheet, - styleSheet.length, 1); + if (linked == null) + linked = new ArrayList(); + linked.add(ss); } /** @@ -560,31 +586,9 @@ public class StyleSheet extends StyleContext */ public void removeStyleSheet(StyleSheet ss) { - if (styleSheet.length == 1 && styleSheet[0].equals(ss)) - styleSheet = null; - else + if (linked != null) { - for (int i = 0; i < styleSheet.length; i++) - { - StyleSheet curr = styleSheet[i]; - if (curr.equals(ss)) - { - StyleSheet[] tmp = new StyleSheet[styleSheet.length - 1]; - if (i != 0 && i != (styleSheet.length - 1)) - { - System.arraycopy(styleSheet, 0, tmp, 0, i); - System.arraycopy(styleSheet, i + 1, tmp, i, - styleSheet.length - i - 1); - } - else if (i == 0) - System.arraycopy(styleSheet, 1, tmp, 0, styleSheet.length - 1); - else - System.arraycopy(styleSheet, 0, tmp, 0, styleSheet.length - 1); - - styleSheet = tmp; - break; - } - } + linked.remove(ss); } } @@ -595,7 +599,17 @@ public class StyleSheet extends StyleContext */ public StyleSheet[] getStyleSheets() { - return styleSheet; + StyleSheet[] linkedSS; + if (linked != null) + { + linkedSS = new StyleSheet[linked.size()]; + linkedSS = (StyleSheet[]) linked.toArray(linkedSS); + } + else + { + linkedSS = null; + } + return linkedSS; } /** @@ -697,18 +711,39 @@ public class StyleSheet extends StyleContext o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH); if (o != null) cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH, - CSS.getValue(CSS.Attribute.WIDTH, o.toString())); + new Length(o.toString())); // The HTML height attribute maps directly to CSS height. o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT); if (o != null) cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT, - CSS.getValue(CSS.Attribute.HEIGHT, o.toString())); + new Length(o.toString())); o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP); if (o != null) cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap"); + // Map cellspacing attr of tables to CSS border-spacing. + o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING, + new Length(o.toString())); + + // For table cells and headers, fetch the cellpadding value from the + // parent table and set it as CSS padding attribute. + HTML.Tag tag = (HTML.Tag) + htmlAttrSet.getAttribute(StyleConstants.NameAttribute); + if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH) + && htmlAttrSet instanceof Element) + { + Element el = (Element) htmlAttrSet; + AttributeSet tableAttrs = el.getParentElement().getParentElement() + .getAttributes(); + o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING); + if (o != null) + cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING, + new Length(o.toString())); + } // TODO: Add more mappings. return cssAttr; } @@ -824,10 +859,7 @@ public class StyleSheet extends StyleContext */ public Font getFont(AttributeSet a) { - FontSize size = (FontSize) a.getAttribute(CSS.Attribute.FONT_SIZE); - int realSize = 12; - if (size != null) - realSize = size.getValue(); + int realSize = getFontSize(a); // Decrement size for subscript and superscript. Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN); @@ -852,6 +884,41 @@ public class StyleSheet extends StyleContext } /** + * Resolves the fontsize for a given set of attributes. + * + * @param atts the attributes + * + * @return the resolved font size + */ + private int getFontSize(AttributeSet atts) + { + int size = 12; + if (atts.isDefined(CSS.Attribute.FONT_SIZE)) + { + FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE); + if (fs.isRelative()) + { + int parSize = 12; + AttributeSet resolver = atts.getResolveParent(); + if (resolver != null) + parSize = getFontSize(resolver); + size = fs.getValue(parSize); + } + else + { + size = fs.getValue(); + } + } + else + { + AttributeSet resolver = atts.getResolveParent(); + if (resolver != null) + size = getFontSize(resolver); + } + return size; + } + + /** * Takes a set of attributes and turns it into a foreground * color specification. This is used to specify things like, brigher, more hue * etc. @@ -1036,6 +1103,11 @@ public class StyleSheet extends StyleContext */ private Border border; + private float leftPadding; + private float rightPadding; + private float topPadding; + private float bottomPadding; + /** * The background color. */ @@ -1048,9 +1120,17 @@ public class StyleSheet extends StyleContext */ BoxPainter(AttributeSet as, StyleSheet ss) { - Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); + // Fetch margins. + Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN); + if (l != null) + { + topInset = bottomInset = leftInset = rightInset = l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT); if (l != null) leftInset = l.getValue(); + else if (as.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.UL) + System.err.println("UL margin left value: " + l + " atts: " + as); l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT); if (l != null) rightInset = l.getValue(); @@ -1061,6 +1141,26 @@ public class StyleSheet extends StyleContext if (l != null) bottomInset = l.getValue(); + // Fetch padding. + l = (Length) as.getAttribute(CSS.Attribute.PADDING); + if (l != null) + { + leftPadding = rightPadding = topPadding = bottomPadding = + l.getValue(); + } + l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT); + if (l != null) + leftPadding = l.getValue(); + l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT); + if (l != null) + rightPadding = l.getValue(); + l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP); + if (l != null) + topPadding = l.getValue(); + l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM); + if (l != null) + bottomPadding = l.getValue(); + // Determine border. border = new CSSBorder(as); @@ -1090,21 +1190,25 @@ public class StyleSheet extends StyleContext inset = topInset; if (border != null) inset += border.getBorderInsets(null).top; + inset += topPadding; break; case View.BOTTOM: inset = bottomInset; if (border != null) inset += border.getBorderInsets(null).bottom; + inset += bottomPadding; break; case View.LEFT: inset = leftInset; if (border != null) inset += border.getBorderInsets(null).left; + inset += leftPadding; break; case View.RIGHT: inset = rightInset; if (border != null) inset += border.getBorderInsets(null).right; + inset += rightPadding; break; default: inset = 0.0F; @@ -1176,6 +1280,11 @@ public class StyleSheet extends StyleContext } /** + * Cached rectangle re-used in the paint method below. + */ + private final Rectangle tmpRect = new Rectangle(); + + /** * Paints the CSS list decoration according to the attributes given. * * @param g - the graphics configuration @@ -1200,7 +1309,35 @@ public class StyleSheet extends StyleContext if (tag != null && tag == HTML.Tag.LI) { g.setColor(Color.BLACK); - g.fillOval((int) x - 15, (int) (h / 2 - 3 + y), 6, 6); + int centerX = (int) (x - 12); + int centerY = -1; + // For paragraphs (almost all cases) center bullet vertically + // in the middle of the first line. + tmpRect.setBounds((int) x, (int) y, (int) w, (int) h); + if (itemView.getViewCount() > 0) + { + View v1 = itemView.getView(0); + if (v1 instanceof ParagraphView && v1.getViewCount() > 0) + { + Shape a1 = itemView.getChildAllocation(0, tmpRect); + Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1 + : a1.getBounds(); + ParagraphView par = (ParagraphView) v1; + Shape a = par.getChildAllocation(0, r1); + if (a != null) + { + Rectangle r = a instanceof Rectangle ? (Rectangle) a + : a.getBounds(); + centerY = (int) (r.height / 2 + r.y); + } + } + } + if (centerY == -1) + { + System.err.println("WARNING LI child is not a paragraph view " + itemView.getView(0) + ", " + itemView.getViewCount()); + centerY =(int) (h / 2 + y); + } + g.fillOval(centerX - 3, centerY - 3, 6, 6); } } } diff --git a/javax/swing/text/html/TableView.java b/javax/swing/text/html/TableView.java index 2bd11ffcf..971d54cb6 100644 --- a/javax/swing/text/html/TableView.java +++ b/javax/swing/text/html/TableView.java @@ -38,9 +38,12 @@ exception statement from your version. */ package javax.swing.text.html; +import java.awt.Shape; + import gnu.javax.swing.text.html.css.Length; import javax.swing.SizeRequirements; +import javax.swing.event.DocumentEvent; import javax.swing.text.AttributeSet; import javax.swing.text.BoxView; import javax.swing.text.Element; @@ -75,6 +78,12 @@ class TableView super(el, X_AXIS); } + public void replace(int offset, int len, View[] views) + { + super.replace(offset, len, views); + gridValid = false; + } + /** * Overridden to make rows not resizable along the Y axis. */ @@ -84,7 +93,27 @@ class TableView if (axis == Y_AXIS) span = super.getPreferredSpan(axis); else - span = super.getMaximumSpan(axis); + span = Integer.MAX_VALUE; + return span; + } + + public float getMinimumSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = totalColumnRequirements.minimum; + else + span = super.getMinimumSpan(axis); + return span; + } + + public float getPreferredSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = totalColumnRequirements.preferred; + else + span = super.getPreferredSpan(axis); return span; } @@ -97,9 +126,10 @@ class TableView { if (r == null) r = new SizeRequirements(); - r.minimum = totalColumnRequirements.minimum; - r.preferred = totalColumnRequirements.preferred; - r.maximum = totalColumnRequirements.maximum; + int adjust = (columnRequirements.length + 1) * cellSpacing; + r.minimum = totalColumnRequirements.minimum + adjust; + r.preferred = totalColumnRequirements.preferred + adjust; + r.maximum = totalColumnRequirements.maximum + adjust; r.alignment = 0.0F; return r; } @@ -123,6 +153,8 @@ class TableView for (int j = 0; j < cv.colSpan; j++, realColumn++) { spans[i] += columnSpans[realColumn]; + if (j < cv.colSpan - 1) + spans[i] += cellSpacing; } } } @@ -151,6 +183,14 @@ class TableView super(el, Y_AXIS); } + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + r = super.calculateMajorAxisRequirements(axis, r); + r.maximum = Integer.MAX_VALUE; + return r; + } + /** * Overridden to fetch the columnSpan attibute. */ @@ -183,8 +223,10 @@ class TableView /** * The column requirements. + * + * Package private to avoid accessor methods. */ - private SizeRequirements[] columnRequirements; + SizeRequirements[] columnRequirements; /** * The overall requirements across all columns. @@ -215,7 +257,14 @@ class TableView /** * Indicates if the grid setup is ok. */ - private boolean gridValid; + boolean gridValid; + + /** + * Additional space that is added _between_ table cells. + * + * This is package private to avoid accessor methods. + */ + int cellSpacing; /** * Creates a new HTML table view for the specified element. @@ -318,6 +367,11 @@ class TableView r.minimum = width; } + // Adjust requirements when we have cell spacing. + int adjust = (columnRequirements.length + 1) * cellSpacing; + r.minimum += adjust; + r.preferred += adjust; + // Apply the alignment. Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN); r.alignment = 0.0F; @@ -332,6 +386,8 @@ class TableView r.alignment = 1.0F; } + // Make it not resize in the horizontal direction. + r.maximum = r.preferred; return r; } @@ -343,6 +399,16 @@ class TableView int[] spans) { updateGrid(); + + // Mark all rows as invalid. + int n = getViewCount(); + for (int i = 0; i < n; i++) + { + View row = getView(i); + if (row instanceof RowView) + ((RowView) row).layoutChanged(axis); + } + layoutColumns(targetSpan); super.layoutMinorAxis(targetSpan, axis, offsets, spans); } @@ -361,8 +427,12 @@ class TableView // all columns of all rows. for (int r = 0; r < numRows; r++) { - RowView rowView = (RowView) getView(r); - int numCols = rowView.getViewCount(); + View rowView = getView(r); + int numCols; + if (rowView instanceof RowView) + numCols = ((RowView) rowView).getViewCount(); + else + numCols = 0; // We collect the normal (non-relative) column requirements in the // total variable and the relative requirements in the relTotal @@ -533,7 +603,9 @@ class TableView } // Try to adjust the spans so that we fill the targetSpan. - long diff = targetSpan - sumPref; + // For adjustments we have to use the targetSpan minus the cumulated + // cell spacings. + long diff = targetSpan - (n + 1) * cellSpacing - sumPref; float factor = 0.0F; int[] diffs = null; if (diff != 0) @@ -570,7 +642,7 @@ class TableView } // Actually perform adjustments. - int totalOffs = 0; + int totalOffs = cellSpacing; for (int i = 0; i < n; i++) { columnOffsets[i] = totalOffs; @@ -580,8 +652,8 @@ class TableView columnSpans[i] += Math.round(adjust); } // Avoid overflow here. - totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i], - Integer.MAX_VALUE); + totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i] + + (long) cellSpacing, Integer.MAX_VALUE); } } @@ -597,15 +669,23 @@ class TableView int numRows = getViewCount(); for (int r = 0; r < numRows; r++) { - RowView rowView = (RowView) getView(r); - int numCols = rowView.getViewCount(); + View rowView = getView(r); + int numCols; + if (rowView instanceof RowView) + numCols = ((RowView) rowView).getViewCount(); + else + numCols = 0; maxColumns = Math.max(numCols, maxColumns); } columnWidths = new Length[maxColumns]; for (int r = 0; r < numRows; r++) { - RowView rowView = (RowView) getView(r); - int numCols = rowView.getViewCount(); + View rowView = getView(r); + int numCols; + if (rowView instanceof RowView) + numCols = ((RowView) rowView).getViewCount(); + else + numCols = 0; int colIndex = 0; for (int c = 0; c < numCols; c++) { @@ -644,4 +724,86 @@ class TableView span = super.getMaximumSpan(axis); return span; } + + /** + * Overridden to fetch the CSS attributes when view gets connected. + */ + public void setParent(View parent) + { + super.setParent(parent); + if (parent != null) + setPropertiesFromAttributes(); + } + + /** + * Fetches CSS and HTML layout attributes. + */ + private void setPropertiesFromAttributes() + { + // Fetch and parse cell spacing. + Object o = getAttributes().getAttribute(CSS.Attribute.BORDER_SPACING); + if (o != null && o instanceof Length) + { + Length l = (Length) o; + cellSpacing = (int) l.getValue(); + } + } + + /** + * Overridden to adjust for cellSpacing. + */ + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + r = super.calculateMajorAxisRequirements(axis, r); + int adjust = (getViewCount() + 1) * cellSpacing; + r.minimum += adjust; + r.preferred += adjust; + r.maximum += adjust; + return r; + } + + /** + * Overridden to adjust for cellSpacing. + */ + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) + { + int adjust = (getViewCount() + 1) * cellSpacing; + super.layoutMajorAxis(targetSpan - adjust, axis, offsets, spans); + for (int i = 0; i < offsets.length; i++) + { + offsets[i] += (i + 1) * cellSpacing; + } + } + + /** + * Overridden to replace view factory with this one. + */ + public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.insertUpdate(e, a, this); + } + + /** + * Overridden to replace view factory with this one. + */ + public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.removeUpdate(e, a, this); + } + + /** + * Overridden to replace view factory with this one. + */ + public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) + { + super.changedUpdate(e, a, this); + } + + public void replace(int offset, int len, View[] views) + { + super.replace(offset, len, views); + gridValid = false; + } } |