aboutsummaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text
diff options
context:
space:
mode:
authorTom Tromey <tromey@gcc.gnu.org>2007-01-09 19:58:05 +0000
committerTom Tromey <tromey@gcc.gnu.org>2007-01-09 19:58:05 +0000
commit97b8365cafc3a344a22d3980b8ed885f5c6d8357 (patch)
tree996a5f57d4a68c53473382e45cb22f574cb3e4db /libjava/classpath/javax/swing/text
parentc648dedbde727ca3f883bb5fd773aa4af70d3369 (diff)
downloadgcc-97b8365cafc3a344a22d3980b8ed885f5c6d8357.zip
gcc-97b8365cafc3a344a22d3980b8ed885f5c6d8357.tar.gz
gcc-97b8365cafc3a344a22d3980b8ed885f5c6d8357.tar.bz2
Merged gcj-eclipse branch to trunk.
From-SVN: r120621
Diffstat (limited to 'libjava/classpath/javax/swing/text')
-rw-r--r--libjava/classpath/javax/swing/text/AbstractDocument.java913
-rw-r--r--libjava/classpath/javax/swing/text/AttributeSet.java2
-rw-r--r--libjava/classpath/javax/swing/text/BoxView.java484
-rw-r--r--libjava/classpath/javax/swing/text/ComponentView.java336
-rw-r--r--libjava/classpath/javax/swing/text/CompositeView.java195
-rw-r--r--libjava/classpath/javax/swing/text/DefaultCaret.java15
-rw-r--r--libjava/classpath/javax/swing/text/DefaultEditorKit.java365
-rw-r--r--libjava/classpath/javax/swing/text/DefaultFormatter.java6
-rw-r--r--libjava/classpath/javax/swing/text/DefaultHighlighter.java389
-rw-r--r--libjava/classpath/javax/swing/text/DefaultStyledDocument.java2036
-rw-r--r--libjava/classpath/javax/swing/text/ElementIterator.java203
-rw-r--r--libjava/classpath/javax/swing/text/FieldView.java4
-rw-r--r--libjava/classpath/javax/swing/text/FlowView.java419
-rw-r--r--libjava/classpath/javax/swing/text/GapContent.java723
-rw-r--r--libjava/classpath/javax/swing/text/GlyphView.java620
-rw-r--r--libjava/classpath/javax/swing/text/InternationalFormatter.java2
-rw-r--r--libjava/classpath/javax/swing/text/JTextComponent.java278
-rw-r--r--libjava/classpath/javax/swing/text/LabelView.java78
-rw-r--r--libjava/classpath/javax/swing/text/MaskFormatter.java377
-rw-r--r--libjava/classpath/javax/swing/text/MutableAttributeSet.java2
-rw-r--r--libjava/classpath/javax/swing/text/ParagraphView.java107
-rw-r--r--libjava/classpath/javax/swing/text/PlainView.java150
-rw-r--r--libjava/classpath/javax/swing/text/Position.java4
-rw-r--r--libjava/classpath/javax/swing/text/SimpleAttributeSet.java18
-rw-r--r--libjava/classpath/javax/swing/text/StringContent.java304
-rw-r--r--libjava/classpath/javax/swing/text/StyleConstants.java11
-rw-r--r--libjava/classpath/javax/swing/text/StyleContext.java600
-rw-r--r--libjava/classpath/javax/swing/text/StyledEditorKit.java46
-rw-r--r--libjava/classpath/javax/swing/text/TextAction.java51
-rw-r--r--libjava/classpath/javax/swing/text/Utilities.java189
-rw-r--r--libjava/classpath/javax/swing/text/View.java104
-rw-r--r--libjava/classpath/javax/swing/text/WrappedPlainView.java199
-rw-r--r--libjava/classpath/javax/swing/text/ZoneView.java442
-rw-r--r--libjava/classpath/javax/swing/text/html/BRView.java5
-rw-r--r--libjava/classpath/javax/swing/text/html/BlockView.java482
-rw-r--r--libjava/classpath/javax/swing/text/html/CSS.java274
-rw-r--r--libjava/classpath/javax/swing/text/html/CSSBorder.java421
-rw-r--r--libjava/classpath/javax/swing/text/html/FormSubmitEvent.java123
-rw-r--r--libjava/classpath/javax/swing/text/html/FormView.java649
-rw-r--r--libjava/classpath/javax/swing/text/html/FrameSetView.java274
-rw-r--r--libjava/classpath/javax/swing/text/html/FrameView.java233
-rw-r--r--libjava/classpath/javax/swing/text/html/HTML.java22
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLDocument.java931
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLEditorKit.java578
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLWriter.java1084
-rw-r--r--libjava/classpath/javax/swing/text/html/ImageView.java403
-rw-r--r--libjava/classpath/javax/swing/text/html/InlineView.java151
-rw-r--r--libjava/classpath/javax/swing/text/html/ListView.java3
-rw-r--r--libjava/classpath/javax/swing/text/html/MultiAttributeSet.java213
-rw-r--r--libjava/classpath/javax/swing/text/html/MultiStyle.java136
-rw-r--r--libjava/classpath/javax/swing/text/html/Option.java12
-rw-r--r--libjava/classpath/javax/swing/text/html/ParagraphView.java131
-rw-r--r--libjava/classpath/javax/swing/text/html/ResetableModel.java50
-rw-r--r--libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java82
-rw-r--r--libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java71
-rw-r--r--libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java84
-rw-r--r--libjava/classpath/javax/swing/text/html/SelectListModel.java106
-rw-r--r--libjava/classpath/javax/swing/text/html/StyleSheet.java1124
-rw-r--r--libjava/classpath/javax/swing/text/html/TableView.java942
-rw-r--r--libjava/classpath/javax/swing/text/html/ViewAttributeSet.java163
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/AttributeList.java6
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/ContentModel.java6
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/DTD.java17
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/DocumentParser.java4
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java4
65 files changed, 14219 insertions, 4237 deletions
diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java
index eb46a8c..eead8de 100644
--- a/libjava/classpath/javax/swing/text/AbstractDocument.java
+++ b/libjava/classpath/javax/swing/text/AbstractDocument.java
@@ -38,11 +38,15 @@ exception statement from your version. */
package javax.swing.text;
+import java.awt.font.TextAttribute;
import java.io.PrintStream;
import java.io.Serializable;
+import java.text.Bidi;
+import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.EventListener;
+import java.util.HashMap;
import java.util.Hashtable;
import java.util.Vector;
@@ -105,6 +109,21 @@ public abstract class AbstractDocument implements Document, Serializable
public static final String ElementNameAttribute = "$ename";
/**
+ * Standard name for the bidi root element.
+ */
+ private static final String BidiRootName = "bidi root";
+
+ /**
+ * Key for storing the asynchronous load priority.
+ */
+ private static final String AsyncLoadPriority = "load priority";
+
+ /**
+ * Key for storing the I18N state.
+ */
+ private static final String I18N = "i18n";
+
+ /**
* The actual content model of this <code>Document</code>.
*/
Content content;
@@ -140,14 +159,10 @@ public abstract class AbstractDocument implements Document, Serializable
private int numReaders = 0;
/**
- * Tells if there are one or more writers waiting.
+ * The number of current writers. If this is > 1 then the same thread entered
+ * the write lock more than once.
*/
- private int numWritersWaiting = 0;
-
- /**
- * A condition variable that readers and writers wait on.
- */
- private Object documentCV = new Object();
+ private int numWriters = 0;
/** An instance of a DocumentFilter.FilterBypass which allows calling
* the insert, remove and replace method without checking for an installed
@@ -158,7 +173,13 @@ public abstract class AbstractDocument implements Document, Serializable
/**
* The bidi root element.
*/
- private Element bidiRoot;
+ private BidiRootElement bidiRoot;
+
+ /**
+ * True when we are currently notifying any listeners. This is used
+ * to detect illegal situations in writeLock().
+ */
+ private transient boolean notifyListeners;
/**
* Creates a new <code>AbstractDocument</code> with the specified
@@ -191,12 +212,25 @@ public abstract class AbstractDocument implements Document, Serializable
content = doc;
context = ctx;
+ // FIXME: Fully implement bidi.
+ bidiRoot = new BidiRootElement();
+
// FIXME: This is determined using a Mauve test. Make the document
// actually use this.
- putProperty("i18n", Boolean.FALSE);
+ putProperty(I18N, Boolean.FALSE);
- // FIXME: Fully implement bidi.
- bidiRoot = new BranchElement(null, null);
+ // Add one child to the bidi root.
+ writeLock();
+ try
+ {
+ Element[] children = new Element[1];
+ children[0] = new BidiElement(bidiRoot, 0, 1, 0);
+ bidiRoot.replace(0, 0, children);
+ }
+ finally
+ {
+ writeUnlock();
+ }
}
/** Returns the DocumentFilter.FilterBypass instance for this
@@ -284,7 +318,8 @@ public abstract class AbstractDocument implements Document, Serializable
* @throws BadLocationException if <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);
}
@@ -296,10 +331,17 @@ public abstract class AbstractDocument implements Document, Serializable
*/
protected void fireChangedUpdate(DocumentEvent event)
{
- DocumentListener[] listeners = getDocumentListeners();
-
- for (int index = 0; index < listeners.length; ++index)
- listeners[index].changedUpdate(event);
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].changedUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
}
/**
@@ -310,10 +352,17 @@ public abstract class AbstractDocument implements Document, Serializable
*/
protected void fireInsertUpdate(DocumentEvent event)
{
- DocumentListener[] listeners = getDocumentListeners();
-
- for (int index = 0; index < listeners.length; ++index)
- listeners[index].insertUpdate(event);
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].insertUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
}
/**
@@ -324,10 +373,17 @@ public abstract class AbstractDocument implements Document, Serializable
*/
protected void fireRemoveUpdate(DocumentEvent event)
{
- DocumentListener[] listeners = getDocumentListeners();
-
- for (int index = 0; index < listeners.length; ++index)
- listeners[index].removeUpdate(event);
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].removeUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
}
/**
@@ -352,7 +408,11 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public int getAsynchronousLoadPriority()
{
- return 0;
+ Object val = getProperty(AsyncLoadPriority);
+ int prio = -1;
+ if (val != null)
+ prio = ((Integer) val).intValue();
+ return prio;
}
/**
@@ -397,7 +457,7 @@ public abstract class AbstractDocument implements Document, Serializable
* @return the thread that currently modifies this <code>Document</code>
* if there is one, otherwise <code>null</code>
*/
- protected final Thread getCurrentWriter()
+ protected final synchronized Thread getCurrentWriter()
{
return currentWriter;
}
@@ -407,7 +467,7 @@ public abstract class AbstractDocument implements Document, Serializable
*
* @return the properties of this <code>Document</code>
*/
- public Dictionary getDocumentProperties()
+ public Dictionary<Object, Object> getDocumentProperties()
{
// FIXME: make me thread-safe
if (properties == null)
@@ -425,14 +485,17 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public final Position getEndPosition()
{
- // FIXME: Properly implement this by calling Content.createPosition().
- return new Position()
- {
- public int getOffset()
- {
- return getLength();
- }
- };
+ Position p;
+ try
+ {
+ p = createPosition(content.length());
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
}
/**
@@ -455,7 +518,7 @@ public abstract class AbstractDocument implements Document, Serializable
*
* @return all registered listeners of the specified type
*/
- public EventListener[] getListeners(Class listenerType)
+ public <T extends EventListener> T[] getListeners(Class<T> listenerType)
{
return listenerList.getListeners(listenerType);
}
@@ -504,14 +567,17 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public final Position getStartPosition()
{
- // FIXME: Properly implement this using Content.createPosition().
- return new Position()
- {
- public int getOffset()
- {
- return 0;
- }
- };
+ Position p;
+ try
+ {
+ p = createPosition(0);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
}
/**
@@ -574,11 +640,19 @@ public abstract class AbstractDocument implements Document, Serializable
// Bail out if we have a bogus insertion (Behavior observed in RI).
if (text == null || text.length() == 0)
return;
-
- if (documentFilter == null)
- insertStringImpl(offset, text, attributes);
- else
- documentFilter.insertString(getBypass(), offset, text, attributes);
+
+ writeLock();
+ try
+ {
+ if (documentFilter == null)
+ insertStringImpl(offset, text, attributes);
+ else
+ documentFilter.insertString(getBypass(), offset, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
+ }
}
void insertStringImpl(int offset, String text, AttributeSet attributes)
@@ -591,23 +665,30 @@ public abstract class AbstractDocument implements Document, Serializable
new DefaultDocumentEvent(offset, text.length(),
DocumentEvent.EventType.INSERT);
- try
- {
- writeLock();
- UndoableEdit undo = content.insertString(offset, text);
- if (undo != null)
- event.addEdit(undo);
-
- insertUpdate(event, attributes);
+ UndoableEdit undo = content.insertString(offset, text);
+ if (undo != null)
+ event.addEdit(undo);
- fireInsertUpdate(event);
- if (undo != null)
- fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
- }
- finally
+ // Check if we need bidi layout.
+ if (getProperty(I18N).equals(Boolean.FALSE))
{
- writeUnlock();
+ Object dir = getProperty(TextAttribute.RUN_DIRECTION);
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
+ putProperty(I18N, Boolean.TRUE);
+ else
+ {
+ char[] chars = text.toCharArray();
+ if (Bidi.requiresBidi(chars, 0, chars.length))
+ putProperty(I18N, Boolean.TRUE);
+ }
}
+
+ insertUpdate(event, attributes);
+
+ fireInsertUpdate(event);
+
+ if (undo != null)
+ fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
}
/**
@@ -620,7 +701,8 @@ public abstract class AbstractDocument implements Document, Serializable
*/
protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
{
- // Do nothing here. Subclasses may want to override this.
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
}
/**
@@ -632,7 +714,8 @@ public abstract class AbstractDocument implements Document, Serializable
*/
protected void postRemoveUpdate(DefaultDocumentEvent chng)
{
- // Do nothing here. Subclasses may want to override this.
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
}
/**
@@ -647,31 +730,338 @@ public abstract class AbstractDocument implements Document, Serializable
if (properties == null)
properties = new Hashtable();
- properties.put(key, value);
+ if (value == null)
+ properties.remove(key);
+ else
+ properties.put(key, value);
+
+ // Update bidi structure if the RUN_DIRECTION is set.
+ if (TextAttribute.RUN_DIRECTION.equals(key))
+ {
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
+ && Boolean.FALSE.equals(getProperty(I18N)))
+ putProperty(I18N, Boolean.TRUE);
+
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ {
+ writeLock();
+ try
+ {
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(0, getLength(),
+ DocumentEvent.EventType.INSERT);
+ updateBidi(ev);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+ }
}
/**
- * Blocks until a read lock can be obtained. Must block if there is
- * currently a writer modifying the <code>Document</code>.
+ * Updates the bidi element structure.
+ *
+ * @param ev the document event for the change
*/
- public final void readLock()
+ private void updateBidi(DefaultDocumentEvent ev)
{
- if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
- return;
- synchronized (documentCV)
+ // Determine start and end offset of the paragraphs to be scanned.
+ int start = 0;
+ int end = 0;
+ DocumentEvent.EventType type = ev.getType();
+ if (type == DocumentEvent.EventType.INSERT
+ || type == DocumentEvent.EventType.CHANGE)
+ {
+ int offs = ev.getOffset();
+ int endOffs = offs + ev.getLength();
+ start = getParagraphElement(offs).getStartOffset();
+ end = getParagraphElement(endOffs).getEndOffset();
+ }
+ else if (type == DocumentEvent.EventType.REMOVE)
{
- while (currentWriter != null || numWritersWaiting > 0)
+ Element par = getParagraphElement(ev.getOffset());
+ start = par.getStartOffset();
+ end = par.getEndOffset();
+ }
+ else
+ assert false : "Unknown event type";
+
+ // Determine the bidi levels for the affected range.
+ Bidi[] bidis = getBidis(start, end);
+
+ int removeFrom = 0;
+ int removeTo = 0;
+
+ int offs = 0;
+ int lastRunStart = 0;
+ int lastRunEnd = 0;
+ int lastRunLevel = 0;
+ ArrayList newEls = new ArrayList();
+ for (int i = 0; i < bidis.length; i++)
+ {
+ Bidi bidi = bidis[i];
+ int numRuns = bidi.getRunCount();
+ for (int r = 0; r < numRuns; r++)
{
- try
+ if (r == 0 && i == 0)
+ {
+ if (start > 0)
+ {
+ // Try to merge with the previous element if it has the
+ // same bidi level as the first run.
+ int prevElIndex = bidiRoot.getElementIndex(start - 1);
+ removeFrom = prevElIndex;
+ Element prevEl = bidiRoot.getElement(prevElIndex);
+ AttributeSet atts = prevEl.getAttributes();
+ int prevElLevel = StyleConstants.getBidiLevel(atts);
+ if (prevElLevel == bidi.getRunLevel(r))
+ {
+ // Merge previous element with current run.
+ lastRunStart = prevEl.getStartOffset() - start;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ }
+ else if (prevEl.getEndOffset() > start)
+ {
+ // Split previous element and replace by 2 new elements.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ newEls.add(new BidiElement(bidiRoot,
+ prevEl.getStartOffset(),
+ start, prevElLevel));
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom++;
+ }
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom = 0;
+ }
+ }
+ if (i == bidis.length - 1 && r == numRuns - 1)
{
- documentCV.wait();
+ if (end <= getLength())
+ {
+ // Try to merge last element with next element.
+ int nextIndex = bidiRoot.getElementIndex(end);
+ Element nextEl = bidiRoot.getElement(nextIndex);
+ AttributeSet atts = nextEl.getAttributes();
+ int nextLevel = StyleConstants.getBidiLevel(atts);
+ int level = bidi.getRunLevel(r);
+ if (lastRunLevel == level && level == nextLevel)
+ {
+ // Merge runs together.
+ if (lastRunStart + start == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ }
+ else if (lastRunLevel == level)
+ {
+ // Merge current and last run.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + endOffs, level));
+ if (start + endOffs == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else if (level == nextLevel)
+ {
+ // Merge current and next run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ else
+ {
+ // Split next element.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ start + endOffs, level));
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else
+ {
+ removeTo = bidiRoot.getElementIndex(end);
+ int level = bidi.getRunLevel(r);
+ int runEnd = offs + bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunStart,
+ start + runEnd, level));
+ }
+ else
+ {
+ // Create element for last run and current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunEnd,
+ start + runEnd,
+ level));
+ }
+ }
}
- catch (InterruptedException ie)
+ else
{
- throw new Error("interrupted trying to get a readLock");
+ int level = bidi.getRunLevel(r);
+ int runEnd = bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ }
+ else
+ {
+ // Create element for last run and update values for
+ // current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ lastRunStart = lastRunEnd;
+ lastRunEnd = offs + runEnd;
+ lastRunLevel = level;
+ }
}
}
- numReaders++;
+ offs += bidi.getLength();
+ }
+
+ // Determine the bidi elements which are to be removed.
+ int numRemoved = 0;
+ if (bidiRoot.getElementCount() > 0)
+ numRemoved = removeTo - removeFrom + 1;
+ Element[] removed = new Element[numRemoved];
+ for (int i = 0; i < numRemoved; i++)
+ removed[i] = bidiRoot.getElement(removeFrom + i);
+
+ Element[] added = new Element[newEls.size()];
+ added = (Element[]) newEls.toArray(added);
+
+ // Update the event.
+ ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
+ ev.addEdit(edit);
+
+ // Update the structure.
+ bidiRoot.replace(removeFrom, numRemoved, added);
+ }
+
+ /**
+ * Determines the Bidi objects for the paragraphs in the specified range.
+ *
+ * @param start the start of the range
+ * @param end the end of the range
+ *
+ * @return the Bidi analysers for the paragraphs in the range
+ */
+ private Bidi[] getBidis(int start, int end)
+ {
+ // Determine the default run direction from the document property.
+ Boolean defaultDir = null;
+ Object o = getProperty(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ defaultDir = (Boolean) o;
+
+ // Scan paragraphs and add their level arrays to the overall levels array.
+ ArrayList bidis = new ArrayList();
+ Segment s = new Segment();
+ for (int i = start; i < end;)
+ {
+ Element par = getParagraphElement(i);
+ int pStart = par.getStartOffset();
+ int pEnd = par.getEndOffset();
+
+ // Determine the default run direction of the paragraph.
+ Boolean dir = defaultDir;
+ o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ dir = (Boolean) o;
+
+ // Bidi over the paragraph.
+ try
+ {
+ getText(pStart, pEnd - pStart, s);
+ }
+ catch (BadLocationException ex)
+ {
+ assert false : "Must not happen";
+ }
+ int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
+ if (dir != null)
+ {
+ if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
+ flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
+ else
+ flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
+ }
+ Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
+ bidis.add(bidi);
+ i = pEnd;
+ }
+ Bidi[] ret = new Bidi[bidis.size()];
+ ret = (Bidi[]) bidis.toArray(ret);
+ return ret;
+ }
+
+ /**
+ * Blocks until a read lock can be obtained. Must block if there is
+ * currently a writer modifying the <code>Document</code>.
+ */
+ public final synchronized void readLock()
+ {
+ try
+ {
+ while (currentWriter != null)
+ {
+ if (currentWriter == Thread.currentThread())
+ return;
+ wait();
+ }
+ numReaders++;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interrupted during grab read lock");
}
}
@@ -679,7 +1069,7 @@ public abstract class AbstractDocument implements Document, Serializable
* Releases the read lock. If this was the only reader on this
* <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
@@ -706,21 +1096,14 @@ public abstract class AbstractDocument implements Document, Serializable
// FIXME: the reference implementation throws a
// javax.swing.text.StateInvariantError here
- if (numReaders == 0)
+ if (numReaders <= 0)
throw new IllegalStateException("document lock failure");
- synchronized (documentCV)
- {
- // If currentWriter is not null, the application code probably had a
- // writeLock and then tried to obtain a readLock, in which case
- // numReaders wasn't incremented
- if (currentWriter == null)
- {
- numReaders --;
- if (numReaders == 0 && numWritersWaiting != 0)
- documentCV.notify();
- }
- }
+ // If currentWriter is not null, the application code probably had a
+ // writeLock and then tried to obtain a readLock, in which case
+ // numReaders wasn't incremented
+ numReaders--;
+ notify();
}
/**
@@ -744,12 +1127,21 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public void remove(int offset, int length) throws BadLocationException
{
- if (documentFilter == null)
- removeImpl(offset, length);
- else
- documentFilter.remove(getBypass(), offset, length);
+ writeLock();
+ try
+ {
+ DocumentFilter f = getDocumentFilter();
+ if (f == null)
+ removeImpl(offset, length);
+ else
+ f.remove(getBypass(), offset, length);
+ }
+ finally
+ {
+ writeUnlock();
+ }
}
-
+
void removeImpl(int offset, int length) throws BadLocationException
{
// The RI silently ignores all requests that have a negative length.
@@ -766,21 +1158,12 @@ public abstract class AbstractDocument implements Document, Serializable
new DefaultDocumentEvent(offset, length,
DocumentEvent.EventType.REMOVE);
- try
- {
- writeLock();
-
- // The order of the operations below is critical!
- removeUpdate(event);
- UndoableEdit temp = content.remove(offset, length);
-
- postRemoveUpdate(event);
- fireRemoveUpdate(event);
- }
- finally
- {
- writeUnlock();
- }
+ // The order of the operations below is critical!
+ removeUpdate(event);
+ UndoableEdit temp = content.remove(offset, length);
+
+ postRemoveUpdate(event);
+ fireRemoveUpdate(event);
}
}
@@ -814,21 +1197,28 @@ public abstract class AbstractDocument implements Document, Serializable
if (length == 0
&& (text == null || text.length() == 0))
return;
-
- if (documentFilter == null)
+
+ writeLock();
+ try
{
- // It is important to call the methods which again do the checks
- // of the arguments and the DocumentFilter because subclasses may
- // have overridden these methods and provide crucial behavior
- // which would be skipped if we call the non-checking variants.
- // An example for this is PlainDocument where insertString can
- // provide a filtering of newlines.
- remove(offset, length);
- insertString(offset, text, attributes);
+ if (documentFilter == null)
+ {
+ // It is important to call the methods which again do the checks
+ // of the arguments and the DocumentFilter because subclasses may
+ // have overridden these methods and provide crucial behavior
+ // which would be skipped if we call the non-checking variants.
+ // An example for this is PlainDocument where insertString can
+ // provide a filtering of newlines.
+ remove(offset, length);
+ insertString(offset, text, attributes);
+ }
+ else
+ documentFilter.replace(getBypass(), offset, length, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
}
- else
- documentFilter.replace(getBypass(), offset, length, text, attributes);
-
}
void replaceImpl(int offset, int length, String text,
@@ -948,7 +1338,8 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public void setAsynchronousLoadPriority(int p)
{
- // TODO: Implement this properly.
+ Integer val = p >= 0 ? new Integer(p) : null;
+ putProperty(AsyncLoadPriority, val);
}
/**
@@ -956,7 +1347,7 @@ public abstract class AbstractDocument implements Document, Serializable
*
* @param p the document properties to set
*/
- public void setDocumentProperties(Dictionary p)
+ public void setDocumentProperties(Dictionary<Object, Object> p)
{
// FIXME: make me thread-safe
properties = p;
@@ -966,26 +1357,27 @@ public abstract class AbstractDocument implements Document, Serializable
* Blocks until a write lock can be obtained. Must wait if there are
* readers currently reading or another thread is currently writing.
*/
- protected final void writeLock()
+ protected synchronized final void writeLock()
{
- if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
- return;
- synchronized (documentCV)
+ try
{
- numWritersWaiting++;
- while (numReaders > 0)
+ while (numReaders > 0 || currentWriter != null)
{
- try
- {
- documentCV.wait();
- }
- catch (InterruptedException ie)
+ if (Thread.currentThread() == currentWriter)
{
- throw new Error("interruped while trying to obtain write lock");
+ if (notifyListeners)
+ throw new IllegalStateException("Mutation during notify");
+ numWriters++;
+ return;
}
+ wait();
}
- numWritersWaiting --;
currentWriter = Thread.currentThread();
+ numWriters = 1;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interupted during grab write lock");
}
}
@@ -993,16 +1385,14 @@ public abstract class AbstractDocument implements Document, Serializable
* Releases the write lock. This allows waiting readers or writers to
* obtain the lock.
*/
- protected final void writeUnlock()
+ protected final synchronized void writeUnlock()
{
- synchronized (documentCV)
- {
- if (Thread.currentThread().equals(currentWriter))
- {
- currentWriter = null;
- documentCV.notifyAll();
- }
- }
+ if (--numWriters <= 0)
+ {
+ numWriters = 0;
+ currentWriter = null;
+ notifyAll();
+ }
}
/**
@@ -1039,6 +1429,7 @@ public abstract class AbstractDocument implements Document, Serializable
public void dump(PrintStream out)
{
((AbstractElement) getDefaultRootElement()).dump(out, 0);
+ ((AbstractElement) getBidiRootElement()).dump(out, 0);
}
/**
@@ -1130,7 +1521,7 @@ public abstract class AbstractDocument implements Document, Serializable
* @return the attributes of <code>old</code> minus the attributes in
* <code>attributes</code>
*/
- AttributeSet removeAttributes(AttributeSet old, Enumeration names);
+ AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
}
/**
@@ -1255,7 +1646,7 @@ public abstract class AbstractDocument implements Document, Serializable
AttributeContext ctx = getAttributeContext();
attributes = ctx.getEmptySet();
if (s != null)
- attributes = ctx.addAttributes(attributes, s);
+ addAttributes(s);
}
/**
@@ -1386,7 +1777,7 @@ public abstract class AbstractDocument implements Document, Serializable
*
* @param names the names of the attributes to be removed
*/
- public void removeAttributes(Enumeration names)
+ public void removeAttributes(Enumeration<?> names)
{
attributes = getAttributeContext().removeAttributes(attributes, names);
}
@@ -1481,7 +1872,7 @@ public abstract class AbstractDocument implements Document, Serializable
*
* @return the names of the attributes of this element
*/
- public Enumeration getAttributeNames()
+ public Enumeration<?> getAttributeNames()
{
return attributes.getAttributeNames();
}
@@ -1567,7 +1958,7 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public String getName()
{
- return (String) getAttribute(NameAttribute);
+ return (String) attributes.getAttribute(ElementNameAttribute);
}
/**
@@ -1644,6 +2035,11 @@ public abstract class AbstractDocument implements Document, Serializable
b.append('\n');
}
}
+ if (getAttributeCount() > 0)
+ {
+ for (int i = 0; i < indent; ++i)
+ b.append(' ');
+ }
b.append(">\n");
// Dump element content for leaf elements.
@@ -1705,6 +2101,11 @@ public abstract class AbstractDocument implements Document, Serializable
private int numChildren;
/**
+ * The last found index in getElementIndex(). Used for faster searching.
+ */
+ private int lastIndex;
+
+ /**
* Creates a new <code>BranchElement</code> with the specified
* parent and attributes.
*
@@ -1717,6 +2118,7 @@ public abstract class AbstractDocument implements Document, Serializable
super(parent, attributes);
children = new Element[1];
numChildren = 0;
+ lastIndex = -1;
}
/**
@@ -1726,7 +2128,7 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public Enumeration children()
{
- if (children.length == 0)
+ if (numChildren == 0)
return null;
Vector tmp = new Vector();
@@ -1785,35 +2187,73 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public int getElementIndex(int offset)
{
- // If offset is less than the start offset of our first child,
- // return 0
- if (offset < getStartOffset())
- return 0;
+ // Implemented using an improved linear search.
+ // This makes use of the fact that searches are not random but often
+ // close to the previous search. So we try to start the binary
+ // search at the last found index.
- // XXX: There is surely a better algorithm
- // as beginning from first element each time.
- for (int index = 0; index < numChildren - 1; ++index)
+ int i0 = 0; // The lower bounds.
+ int i1 = numChildren - 1; // The upper bounds.
+ int index = -1; // The found index.
+
+ int p0 = getStartOffset();
+ int p1; // Start and end offset local variables.
+
+ if (numChildren == 0)
+ index = 0;
+ else if (offset >= getEndOffset())
+ index = numChildren - 1;
+ else
{
- Element elem = children[index];
-
- if ((elem.getStartOffset() <= offset)
- && (offset < elem.getEndOffset()))
- return index;
- // If the next element's start offset is greater than offset
- // then we have to return the closest Element, since no Elements
- // will contain the offset
- if (children[index + 1].getStartOffset() > offset)
+ // Try lastIndex.
+ if (lastIndex >= i0 && lastIndex <= i1)
{
- if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset))
- return index + 1;
+ Element last = getElement(lastIndex);
+ p0 = last.getStartOffset();
+ p1 = last.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ index = lastIndex;
else
- return index;
+ {
+ // Narrow the search bounds using the lastIndex, even
+ // if it hasn't been a hit.
+ if (offset < p0)
+ i1 = lastIndex;
+ else
+ i0 = lastIndex;
+ }
+ }
+ // The actual search.
+ int i = 0;
+ while (i0 <= i1 && index == -1)
+ {
+ i = i0 + (i1 - i0) / 2;
+ Element el = getElement(i);
+ p0 = el.getStartOffset();
+ p1 = el.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ {
+ // Found it!
+ index = i;
+ }
+ else if (offset < p0)
+ i1 = i - 1;
+ else
+ i0 = i + 1;
}
- }
- // If offset is greater than the index of the last element, return
- // the index of the last element.
- return getElementCount() - 1;
+ if (index == -1)
+ {
+ // Didn't find it. Return the boundary index.
+ if (offset < p0)
+ index = i;
+ else
+ index = i + 1;
+ }
+
+ lastIndex = index;
+ }
+ return index;
}
/**
@@ -1957,6 +2397,11 @@ public abstract class AbstractDocument implements Document, Serializable
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = 5230037221564563284L;
+ /**
+ * The threshold that indicates when we switch to using a Hashtable.
+ */
+ private static final int THRESHOLD = 10;
+
/** The starting offset of the change. */
private int offset;
@@ -1967,15 +2412,18 @@ public abstract class AbstractDocument implements Document, Serializable
private DocumentEvent.EventType type;
/**
- * Maps <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>.
@@ -1990,7 +2438,6 @@ public abstract class AbstractDocument implements Document, Serializable
this.offset = offset;
this.length = length;
this.type = type;
- changes = new Hashtable();
modified = false;
}
@@ -2004,9 +2451,27 @@ public abstract class AbstractDocument implements Document, Serializable
public boolean addEdit(UndoableEdit edit)
{
// XXX - Fully qualify ElementChange to work around gcj bug #2499.
- if (edit instanceof DocumentEvent.ElementChange)
+
+ // Start using Hashtable when we pass a certain threshold. This
+ // gives a good memory/performance compromise.
+ if (changes == null && edits.size() > THRESHOLD)
+ {
+ changes = new HashMap();
+ int count = edits.size();
+ for (int i = 0; i < count; i++)
+ {
+ Object o = edits.elementAt(i);
+ if (o instanceof DocumentEvent.ElementChange)
+ {
+ DocumentEvent.ElementChange ec =
+ (DocumentEvent.ElementChange) o;
+ changes.put(ec.getElement(), ec);
+ }
+ }
+ }
+
+ if (changes != null && edit instanceof DocumentEvent.ElementChange)
{
- modified = true;
DocumentEvent.ElementChange elEdit =
(DocumentEvent.ElementChange) edit;
changes.put(elEdit.getElement(), elEdit);
@@ -2065,7 +2530,27 @@ public abstract class AbstractDocument implements Document, Serializable
public DocumentEvent.ElementChange getChange(Element elem)
{
// XXX - Fully qualify ElementChange to work around gcj bug #2499.
- return (DocumentEvent.ElementChange) changes.get(elem);
+ DocumentEvent.ElementChange change = null;
+ if (changes != null)
+ {
+ change = (DocumentEvent.ElementChange) changes.get(elem);
+ }
+ else
+ {
+ int count = edits.size();
+ for (int i = 0; i < count && change == null; i++)
+ {
+ Object o = edits.get(i);
+ if (o instanceof DocumentEvent.ElementChange)
+ {
+ DocumentEvent.ElementChange ec =
+ (DocumentEvent.ElementChange) o;
+ if (elem.equals(ec.getElement()))
+ change = ec;
+ }
+ }
+ }
+ return change;
}
/**
@@ -2333,7 +2818,63 @@ public abstract class AbstractDocument implements Document, Serializable
+ getStartOffset() + "," + getEndOffset() + "\n");
}
}
-
+
+ /**
+ * The root element for bidirectional text.
+ */
+ private class BidiRootElement
+ extends BranchElement
+ {
+ /**
+ * Creates a new bidi root element.
+ */
+ BidiRootElement()
+ {
+ super(null, null);
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiRootName;
+ }
+ }
+
+ /**
+ * A leaf element for the bidi structure.
+ */
+ private class BidiElement
+ extends LeafElement
+ {
+ /**
+ * Creates a new BidiElement.
+ *
+ * @param parent the parent element
+ * @param start the start offset
+ * @param end the end offset
+ * @param level the bidi level
+ */
+ BidiElement(Element parent, int start, int end, int level)
+ {
+ super(parent, new SimpleAttributeSet(), start, end);
+ addAttribute(StyleConstants.BidiLevel, new Integer(level));
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiElementName;
+ }
+ }
+
/** A class whose methods delegate to the insert, remove and replace methods
* of this document which do not check for an installed DocumentFilter.
*/
diff --git a/libjava/classpath/javax/swing/text/AttributeSet.java b/libjava/classpath/javax/swing/text/AttributeSet.java
index 01d148c..2d39881 100644
--- a/libjava/classpath/javax/swing/text/AttributeSet.java
+++ b/libjava/classpath/javax/swing/text/AttributeSet.java
@@ -158,7 +158,7 @@ public interface AttributeSet
* @return the names of the attributes that are stored in this
* <code>AttributeSet</code>
*/
- Enumeration getAttributeNames();
+ Enumeration<?> getAttributeNames();
/**
* Returns the resolving parent of this <code>AttributeSet</code>.
diff --git a/libjava/classpath/javax/swing/text/BoxView.java b/libjava/classpath/javax/swing/text/BoxView.java
index 27e3c0f..0754d9b 100644
--- a/libjava/classpath/javax/swing/text/BoxView.java
+++ b/libjava/classpath/javax/swing/text/BoxView.java
@@ -38,6 +38,7 @@ exception statement from your version. */
package javax.swing.text;
+import java.awt.Container;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -92,11 +93,6 @@ public class BoxView
private int[] span = new int[2];
/**
- * The SizeRequirements of the child views along the X_AXIS and Y_AXIS.
- */
- private SizeRequirements[][] childReqs = new SizeRequirements[2][];
-
- /**
* Creates a new <code>BoxView</code> for the given
* <code>Element</code> and axis. Valid values for the axis are
* {@link View#X_AXIS} and {@link View#Y_AXIS}.
@@ -110,6 +106,8 @@ public class BoxView
myAxis = axis;
layoutValid[0] = false;
layoutValid[1] = false;
+ requirementsValid[X_AXIS] = false;
+ requirementsValid[Y_AXIS] = false;
span[0] = 0;
span[1] = 0;
requirements[0] = new SizeRequirements();
@@ -146,7 +144,10 @@ public class BoxView
*/
public void setAxis(int axis)
{
+ boolean changed = axis != myAxis;
myAxis = axis;
+ if (changed)
+ preferenceChanged(null, true, true);
}
/**
@@ -227,56 +228,49 @@ public class BoxView
*/
public void replace(int offset, int length, View[] views)
{
- int numViews = 0;
- if (views != null)
- numViews = views.length;
+ // Actually perform the replace.
+ super.replace(offset, length, views);
// Resize and copy data for cache arrays.
- // The spansX cache.
- int oldSize = getViewCount();
-
- int[] newSpansX = new int[oldSize - length + numViews];
- System.arraycopy(spans[X_AXIS], 0, newSpansX, 0, offset);
- System.arraycopy(spans[X_AXIS], offset + length, newSpansX,
- offset + numViews,
- oldSize - (offset + length));
- spans[X_AXIS] = newSpansX;
-
- // The spansY cache.
- int[] newSpansY = new int[oldSize - length + numViews];
- System.arraycopy(spans[Y_AXIS], 0, newSpansY, 0, offset);
- System.arraycopy(spans[Y_AXIS], offset + length, newSpansY,
- offset + numViews,
- oldSize - (offset + length));
- spans[Y_AXIS] = newSpansY;
-
- // The offsetsX cache.
- int[] newOffsetsX = new int[oldSize - length + numViews];
- System.arraycopy(offsets[X_AXIS], 0, newOffsetsX, 0, offset);
- System.arraycopy(offsets[X_AXIS], offset + length, newOffsetsX,
- offset + numViews,
- oldSize - (offset + length));
- offsets[X_AXIS] = newOffsetsX;
-
- // The offsetsY cache.
- int[] newOffsetsY = new int[oldSize - length + numViews];
- System.arraycopy(offsets[Y_AXIS], 0, newOffsetsY, 0, offset);
- System.arraycopy(offsets[Y_AXIS], offset + length, newOffsetsY,
- offset + numViews,
- oldSize - (offset + length));
- offsets[Y_AXIS] = newOffsetsY;
+ int newItems = views != null ? views.length : 0;
+ int minor = 1 - myAxis;
+ offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems);
+ spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems);
+ layoutValid[myAxis] = false;
+ requirementsValid[myAxis] = false;
+ offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems);
+ spans[minor] = replaceLayoutArray(spans[minor], offset, newItems);
+ layoutValid[minor] = false;
+ requirementsValid[minor] = false;
+ }
- // Actually perform the replace.
- super.replace(offset, length, views);
+ /**
+ * Helper method. This updates the layout cache arrays in response
+ * to a call to {@link #replace(int, int, View[])}.
+ *
+ * @param oldArray the old array
+ *
+ * @return the replaced array
+ */
+ private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems)
- // Invalidate layout information.
- layoutValid[X_AXIS] = false;
- requirementsValid[X_AXIS] = false;
- layoutValid[Y_AXIS] = false;
- requirementsValid[Y_AXIS] = false;
+ {
+ int num = getViewCount();
+ int[] newArray = new int[num];
+ System.arraycopy(oldArray, 0, newArray, 0, offset);
+ System.arraycopy(oldArray, offset, newArray, offset + newItems,
+ num - newItems - offset);
+ return newArray;
}
/**
+ * A Rectangle instance to be reused in the paint() method below.
+ */
+ private final Rectangle tmpRect = new Rectangle();
+
+ private Rectangle clipRect = new Rectangle();
+
+ /**
* Renders the <code>Element</code> that is associated with this
* <code>View</code>.
*
@@ -285,26 +279,20 @@ public class BoxView
*/
public void paint(Graphics g, Shape a)
{
- Rectangle alloc;
- if (a instanceof Rectangle)
- alloc = (Rectangle) a;
- else
- alloc = a.getBounds();
+ // Try to avoid allocation if possible (almost all cases).
+ Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
- int x = alloc.x + getLeftInset();
- int y = alloc.y + getTopInset();
+ // This returns a cached instance.
+ alloc = getInsideAllocation(alloc);
- Rectangle clip = g.getClipBounds();
- Rectangle tmp = new Rectangle();
int count = getViewCount();
- for (int i = 0; i < count; ++i)
+ for (int i = 0; i < count; i++)
{
- tmp.x = x + getOffset(X_AXIS, i);
- tmp.y = y + getOffset(Y_AXIS, i);
- tmp.width = getSpan(X_AXIS, i);
- tmp.height = getSpan(Y_AXIS, i);
- if (tmp.intersects(clip))
- paintChild(g, tmp, i);
+ View child = getView(i);
+ tmpRect.setBounds(alloc);
+ childAllocation(i, tmpRect);
+ if (g.hitClip(tmpRect.x, tmpRect.y, tmpRect.width, tmpRect.height))
+ paintChild(g, tmpRect, i);
}
}
@@ -373,9 +361,9 @@ public class BoxView
}
/**
- * This method is obsolete and no longer in use. It is replaced by
- * {@link #calculateMajorAxisRequirements(int, SizeRequirements)} and
- * {@link #calculateMinorAxisRequirements(int, SizeRequirements)}.
+ * Calculates size requirements for a baseline layout. This is not
+ * used by the BoxView itself, but by subclasses that wish to perform
+ * a baseline layout, like the FlowView's rows.
*
* @param axis the axis that is examined
* @param sr the <code>SizeRequirements</code> object to hold the result,
@@ -387,50 +375,94 @@ public class BoxView
protected SizeRequirements baselineRequirements(int axis,
SizeRequirements sr)
{
- updateChildRequirements(axis);
+ // Create new instance if sr == null.
+ if (sr == null)
+ sr = new SizeRequirements();
+ sr.alignment = 0.5F;
+
+ // Calculate overall ascent and descent.
+ int totalAscentMin = 0;
+ int totalAscentPref = 0;
+ int totalAscentMax = 0;
+ int totalDescentMin = 0;
+ int totalDescentPref = 0;
+ int totalDescentMax = 0;
+
+ int count = getViewCount();
+ for (int i = 0; i < count; i++)
+ {
+ View v = getView(i);
+ float align = v.getAlignment(axis);
+ int span = (int) v.getPreferredSpan(axis);
+ int ascent = (int) (align * span);
+ int descent = span - ascent;
+
+ totalAscentPref = Math.max(ascent, totalAscentPref);
+ totalDescentPref = Math.max(descent, totalDescentPref);
+ if (v.getResizeWeight(axis) > 0)
+ {
+ // If the view is resizable, then use the min and max size
+ // of the view.
+ span = (int) v.getMinimumSpan(axis);
+ ascent = (int) (align * span);
+ descent = span - ascent;
+ totalAscentMin = Math.max(ascent, totalAscentMin);
+ totalDescentMin = Math.max(descent, totalDescentMin);
+
+ span = (int) v.getMaximumSpan(axis);
+ ascent = (int) (align * span);
+ descent = span - ascent;
+ totalAscentMax = Math.max(ascent, totalAscentMax);
+ totalDescentMax = Math.max(descent, totalDescentMax);
+ }
+ else
+ {
+ // If the view is not resizable, use the preferred span.
+ totalAscentMin = Math.max(ascent, totalAscentMin);
+ totalDescentMin = Math.max(descent, totalDescentMin);
+ totalAscentMax = Math.max(ascent, totalAscentMax);
+ totalDescentMax = Math.max(descent, totalDescentMax);
+ }
+ }
- SizeRequirements res = sr;
- if (res == null)
- res = new SizeRequirements();
+ // Preferred overall span is the sum of the preferred ascent and descent.
+ // With overflow check.
+ sr.preferred = (int) Math.min((long) totalAscentPref
+ + (long) totalDescentPref,
+ Integer.MAX_VALUE);
+
+ // Align along the baseline.
+ if (sr.preferred > 0)
+ sr.alignment = (float) totalAscentPref / sr.preferred;
- float minLeft = 0;
- float minRight = 0;
- float prefLeft = 0;
- float prefRight = 0;
- float maxLeft = 0;
- float maxRight = 0;
- for (int i = 0; i < childReqs[axis].length; i++)
+ if (sr.alignment == 0)
{
- float myMinLeft = childReqs[axis][i].minimum * childReqs[axis][i].alignment;
- float myMinRight = childReqs[axis][i].minimum - myMinLeft;
- minLeft = Math.max(myMinLeft, minLeft);
- minRight = Math.max(myMinRight, minRight);
- float myPrefLeft = childReqs[axis][i].preferred * childReqs[axis][i].alignment;
- float myPrefRight = childReqs[axis][i].preferred - myPrefLeft;
- prefLeft = Math.max(myPrefLeft, prefLeft);
- prefRight = Math.max(myPrefRight, prefRight);
- float myMaxLeft = childReqs[axis][i].maximum * childReqs[axis][i].alignment;
- float myMaxRight = childReqs[axis][i].maximum - myMaxLeft;
- maxLeft = Math.max(myMaxLeft, maxLeft);
- maxRight = Math.max(myMaxRight, maxRight);
+ // Nothing above the baseline, use the descent.
+ sr.minimum = totalDescentMin;
+ sr.maximum = totalDescentMax;
}
- int minSize = (int) (minLeft + minRight);
- int prefSize = (int) (prefLeft + prefRight);
- int maxSize = (int) (maxLeft + maxRight);
- float align = prefLeft / (prefRight + prefLeft);
- if (Float.isNaN(align))
- align = 0;
-
- res.alignment = align;
- res.maximum = maxSize;
- res.preferred = prefSize;
- res.minimum = minSize;
- return res;
+ else if (sr.alignment == 1.0F)
+ {
+ // Nothing below the baseline, use the descent.
+ sr.minimum = totalAscentMin;
+ sr.maximum = totalAscentMax;
+ }
+ else
+ {
+ sr.minimum = Math.max((int) (totalAscentMin / sr.alignment),
+ (int) (totalDescentMin / (1.0F - sr.alignment)));
+ sr.maximum = Math.min((int) (totalAscentMax / sr.alignment),
+ (int) (totalDescentMax / (1.0F - sr.alignment)));
+ }
+ return sr;
}
/**
- * Calculates the layout of the children of this <code>BoxView</code> along
- * the specified axis.
+ * Calculates the baseline layout of the children of this
+ * <code>BoxView</code> along the specified axis.
+ *
+ * This is not used by the BoxView itself, but by subclasses that wish to
+ * perform a baseline layout, like the FlowView's rows.
*
* @param span the target span
* @param axis the axis that is examined
@@ -440,13 +472,36 @@ public class BoxView
protected void baselineLayout(int span, int axis, int[] offsets,
int[] spans)
{
- updateChildRequirements(axis);
- updateRequirements(axis);
+ int totalAscent = (int) (span * getAlignment(axis));
+ int totalDescent = span - totalAscent;
- // Calculate the spans and offsets using the SizeRequirements uility
- // methods.
- SizeRequirements.calculateAlignedPositions(span, requirements[axis],
- childReqs[axis], offsets, spans);
+ int count = getViewCount();
+ for (int i = 0; i < count; i++)
+ {
+ View v = getView(i);
+ float align = v.getAlignment(axis);
+ int viewSpan;
+ if (v.getResizeWeight(axis) > 0)
+ {
+ // If possible, then resize for best fit.
+ int min = (int) v.getMinimumSpan(axis);
+ int max = (int) v.getMaximumSpan(axis);
+ if (align == 0.0F)
+ viewSpan = Math.max(Math.min(max, totalDescent), min);
+ else if (align == 1.0F)
+ viewSpan = Math.max(Math.min(max, totalAscent), min);
+ else
+ {
+ int fit = (int) Math.min(totalAscent / align,
+ totalDescent / (1.0F - align));
+ viewSpan = Math.max(Math.min(max, fit), min);
+ }
+ }
+ else
+ viewSpan = (int) v.getPreferredSpan(axis);
+ offsets[i] = totalAscent - (int) (viewSpan * align);
+ spans[i] = viewSpan;
+ }
}
/**
@@ -476,8 +531,8 @@ public class BoxView
{
View child = getView(i);
min += child.getMinimumSpan(axis);
- pref = child.getPreferredSpan(axis);
- max = child.getMaximumSpan(axis);
+ pref += child.getPreferredSpan(axis);
+ max += child.getMaximumSpan(axis);
}
res.minimum = (int) min;
@@ -509,7 +564,7 @@ public class BoxView
res.minimum = 0;
res.preferred = 0;
- res.maximum = 0;
+ res.maximum = Integer.MAX_VALUE;
res.alignment = 0.5F;
int n = getViewCount();
for (int i = 0; i < n; i++)
@@ -568,9 +623,9 @@ public class BoxView
boolean result = false;
if (myAxis == X_AXIS)
- result = x > r.x;
+ result = x > r.x + r.width;
else
- result = y > r.y;
+ result = y > r.y + r.height;
return result;
}
@@ -589,24 +644,54 @@ public class BoxView
{
View result = null;
int count = getViewCount();
- Rectangle copy = new Rectangle(r);
-
- for (int i = 0; i < count; ++i)
+ if (myAxis == X_AXIS)
{
- copy.setBounds(r);
- // The next call modifies copy.
- childAllocation(i, copy);
- if (copy.contains(x, y))
+ // Border case. Requested point is left from the box.
+ if (x < r.x + offsets[X_AXIS][0])
{
- // Modify r on success.
- r.setBounds(copy);
- result = getView(i);
- break;
+ childAllocation(0, r);
+ result = getView(0);
+ }
+ else
+ {
+ // Search views inside box.
+ for (int i = 0; i < count && result == null; i++)
+ {
+ if (x < r.x + offsets[X_AXIS][i])
+ {
+ childAllocation(i - 1, r);
+ result = getView(i - 1);
+ }
+ }
}
}
-
- if (result == null && count > 0)
- return getView(count - 1);
+ else // Same algorithm for Y_AXIS.
+ {
+ // Border case. Requested point is above the box.
+ if (y < r.y + offsets[Y_AXIS][0])
+ {
+ childAllocation(0, r);
+ result = getView(0);
+ }
+ else
+ {
+ // Search views inside box.
+ for (int i = 0; i < count && result == null; i++)
+ {
+ if (y < r.y + offsets[Y_AXIS][i])
+ {
+ childAllocation(i - 1, r);
+ result = getView(i - 1);
+ }
+ }
+ }
+ }
+ // Not found, other border case: point is right from or below the box.
+ if (result == null)
+ {
+ childAllocation(count - 1, r);
+ result = getView(count - 1);
+ }
return result;
}
@@ -623,9 +708,6 @@ public class BoxView
*/
protected void childAllocation(int index, Rectangle a)
{
- if (! isAllocationValid())
- layout(a.width, a.height);
-
a.x += offsets[X_AXIS][index];
a.y += offsets[Y_AXIS][index];
a.width = spans[X_AXIS][index];
@@ -643,49 +725,32 @@ public class BoxView
*/
protected void layout(int width, int height)
{
- int[] newSpan = new int[]{ width, height };
- int count = getViewCount();
-
- // Update minor axis as appropriate. We need to first update the minor
- // axis layout because that might affect the children's preferences along
- // the major axis.
- int minorAxis = myAxis == X_AXIS ? Y_AXIS : X_AXIS;
- if ((! isLayoutValid(minorAxis)) || newSpan[minorAxis] != span[minorAxis])
- {
- layoutValid[minorAxis] = false;
- span[minorAxis] = newSpan[minorAxis];
- layoutMinorAxis(span[minorAxis], minorAxis, offsets[minorAxis],
- spans[minorAxis]);
-
- // Update the child view's sizes.
- for (int i = 0; i < count; ++i)
- {
- getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
- }
- layoutValid[minorAxis] = true;
- }
-
+ layoutAxis(X_AXIS, width);
+ layoutAxis(Y_AXIS, height);
+ }
- // Update major axis as appropriate.
- if ((! isLayoutValid(myAxis)) || newSpan[myAxis] != span[myAxis])
+ private void layoutAxis(int axis, int s)
+ {
+ if (span[axis] != s)
+ layoutValid[axis] = false;
+ if (! layoutValid[axis])
{
- layoutValid[myAxis] = false;
- span[myAxis] = newSpan[myAxis];
- layoutMajorAxis(span[myAxis], myAxis, offsets[myAxis],
- spans[myAxis]);
+ span[axis] = s;
+ updateRequirements(axis);
+ if (axis == myAxis)
+ layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]);
+ else
+ layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]);
+ layoutValid[axis] = true;
- // Update the child view's sizes.
- for (int i = 0; i < count; ++i)
+ // Push out child layout.
+ int viewCount = getViewCount();
+ for (int i = 0; i < viewCount; i++)
{
- getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
+ View v = getView(i);
+ v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
}
- layoutValid[myAxis] = true;
}
-
- if (layoutValid[myAxis] == false)
- System.err.println("WARNING: Major axis layout must be valid after layout");
- if (layoutValid[minorAxis] == false)
- System.err.println("Minor axis layout must be valid after layout");
}
/**
@@ -708,7 +773,7 @@ public class BoxView
{
View child = getView(i);
spans[i] = (int) child.getPreferredSpan(axis);
- sumPref = spans[i];
+ sumPref += spans[i];
}
// Try to adjust the spans so that we fill the targetSpan.
@@ -776,7 +841,7 @@ public class BoxView
View child = getView(i);
int max = (int) child.getMaximumSpan(axis);
if (max < targetSpan)
- {System.err.println("align: " + child);
+ {
// Align child when it can't be made as wide as the target span.
float align = child.getAlignment(axis);
offsets[i] = (int) ((targetSpan - max) * align);
@@ -811,7 +876,9 @@ public class BoxView
*/
public int getWidth()
{
- return span[X_AXIS];
+ // The RI returns the following here, however, I'd think that is a bug.
+ // return span[X_AXIS] + getLeftInset() - getRightInset();
+ return span[X_AXIS] + getLeftInset() + getRightInset();
}
/**
@@ -821,7 +888,9 @@ public class BoxView
*/
public int getHeight()
{
- return span[Y_AXIS];
+ // The RI returns the following here, however, I'd think that is a bug.
+ // return span[Y_AXIS] + getTopInset() - getBottomInset();
+ return span[Y_AXIS] + getTopInset() + getBottomInset();
}
/**
@@ -833,7 +902,8 @@ public class BoxView
*/
public void setSize(float width, float height)
{
- layout((int) width, (int) height);
+ layout((int) (width - getLeftInset() - getRightInset()),
+ (int) (height - getTopInset() - getBottomInset()));
}
/**
@@ -944,9 +1014,11 @@ public class BoxView
{
if (axis != X_AXIS && axis != Y_AXIS)
throw new IllegalArgumentException("Illegal axis argument");
- int weight = 1;
- if (axis == myAxis)
- weight = 0;
+ updateRequirements(axis);
+ int weight = 0;
+ if ((requirements[axis].preferred != requirements[axis].minimum)
+ || (requirements[axis].preferred != requirements[axis].maximum))
+ weight = 1;
return weight;
}
@@ -973,13 +1045,39 @@ public class BoxView
protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e,
Shape a, ViewFactory vf)
{
- // FIXME: What to do here?
+ boolean wasValid = isLayoutValid(myAxis);
super.forwardUpdate(ec, e, a, vf);
+ // Trigger repaint when one of the children changed the major axis.
+ if (wasValid && ! isLayoutValid(myAxis))
+ {
+ Container c = getContainer();
+ if (a != null && c != null)
+ {
+ int pos = e.getOffset();
+ int index = getViewIndexAtPosition(pos);
+ Rectangle r = getInsideAllocation(a);
+ if (myAxis == X_AXIS)
+ {
+ r.x += offsets[myAxis][index];
+ r.width -= offsets[myAxis][index];
+ }
+ else
+ {
+ r.y += offsets[myAxis][index];
+ r.height -= offsets[myAxis][index];
+ }
+ c.repaint(r.x, r.y, r.width, r.height);
+ }
+ }
}
public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
{
- // FIXME: What to do here?
+ if (! isAllocationValid())
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ setSize(r.width, r.height);
+ }
return super.viewToModel(x, y, a, bias);
}
@@ -990,32 +1088,6 @@ public class BoxView
}
/**
- * Updates the child requirements along the specified axis. The requirements
- * are only updated if the layout for the specified axis is marked as
- * invalid.
- *
- * @param axis the axis to be updated
- */
- private void updateChildRequirements(int axis)
- {
- if (! isLayoutValid(axis))
- {
- int numChildren = getViewCount();
- if (childReqs[axis] == null || childReqs[axis].length != numChildren)
- childReqs[axis] = new SizeRequirements[numChildren];
- for (int i = 0; i < numChildren; ++i)
- {
- View child = getView(i);
- childReqs[axis][i] =
- new SizeRequirements((int) child.getMinimumSpan(axis),
- (int) child.getPreferredSpan(axis),
- (int) child.getMaximumSpan(axis),
- child.getAlignment(axis));
- }
- }
- }
-
- /**
* Updates the view's cached requirements along the specified axis if
* necessary. The requirements are only updated if the layout for the
* specified axis is marked as invalid.
@@ -1024,6 +1096,8 @@ public class BoxView
*/
private void updateRequirements(int axis)
{
+ if (axis != Y_AXIS && axis != X_AXIS)
+ throw new IllegalArgumentException("Illegal axis: " + axis);
if (! requirementsValid[axis])
{
if (axis == myAxis)
diff --git a/libjava/classpath/javax/swing/text/ComponentView.java b/libjava/classpath/javax/swing/text/ComponentView.java
index a7d237a..8de4de6 100644
--- a/libjava/classpath/javax/swing/text/ComponentView.java
+++ b/libjava/classpath/javax/swing/text/ComponentView.java
@@ -39,11 +39,11 @@ package javax.swing.text;
import java.awt.Component;
import java.awt.Container;
+import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
-import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
/**
@@ -62,11 +62,161 @@ public class ComponentView extends View
{
/**
+ * A special container that sits between the component and the hosting
+ * container. This is used to propagate invalidate requests and cache
+ * the component's layout sizes.
+ */
+ private class Interceptor
+ extends Container
+ {
+ Dimension min;
+ Dimension pref;
+ Dimension max;
+ float alignX;
+ float alignY;
+
+ /**
+ * Creates a new instance that hosts the specified component.
+ */
+ Interceptor(Component c)
+ {
+ setLayout(null);
+ add(c);
+ cacheComponentSizes();
+ }
+
+ /**
+ * Intercepts the normal invalidate call and propagates the invalidate
+ * request up using the View's preferenceChanged().
+ */
+ public void invalidate()
+ {
+ super.invalidate();
+ if (getParent() != null)
+ preferenceChanged(null, true, true);
+ }
+
+ /**
+ * This is overridden to simply cache the layout sizes.
+ */
+ public void doLayout()
+ {
+ cacheComponentSizes();
+ }
+
+ /**
+ * Overridden to also reshape the component itself.
+ */
+ public void reshape(int x, int y, int w, int h)
+ {
+ super.reshape(x, y, w, h);
+ if (getComponentCount() > 0)
+ getComponent(0).setSize(w, h);
+ cacheComponentSizes();
+ }
+
+ /**
+ * Overridden to also show the component.
+ */
+ public void show()
+ {
+ super.show();
+ if (getComponentCount() > 0)
+ getComponent(0).setVisible(true);
+ }
+
+ /**
+ * Overridden to also hide the component.
+ */
+ public void hide()
+ {
+ super.hide();
+ if (getComponentCount() > 0)
+ getComponent(0).setVisible(false);
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public Dimension getMinimumSize()
+ {
+ maybeValidate();
+ return min;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public Dimension getPreferredSize()
+ {
+ maybeValidate();
+ return pref;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public Dimension getMaximumSize()
+ {
+ maybeValidate();
+ return max;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public float getAlignmentX()
+ {
+ maybeValidate();
+ return alignX;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public float getAlignmentY()
+ {
+ maybeValidate();
+ return alignY;
+ }
+
+ /**
+ * Validates the container only when necessary.
+ */
+ private void maybeValidate()
+ {
+ if (! isValid())
+ validate();
+ }
+
+ /**
+ * Fetches the component layout sizes into the cache.
+ */
+ private void cacheComponentSizes()
+ {
+ if (getComponentCount() > 0)
+ {
+ Component c = getComponent(0);
+ min = c.getMinimumSize();
+ pref = c.getPreferredSize();
+ max = c.getMaximumSize();
+ alignX = c.getAlignmentX();
+ alignY = c.getAlignmentY();
+ }
+ }
+ }
+
+ /**
* The component that is displayed by this view.
*/
private Component comp;
/**
+ * The intercepting container.
+ */
+ private Interceptor interceptor;
+
+ /**
* Creates a new instance of <code>ComponentView</code> for the specified
* <code>Element</code>.
*
@@ -99,13 +249,20 @@ public class ComponentView extends View
*/
public float getAlignment(int axis)
{
- float align;
- if (axis == X_AXIS)
- align = getComponent().getAlignmentX();
- else if (axis == Y_AXIS)
- align = getComponent().getAlignmentY();
+ float align = 0.0F;
+ // I'd rather throw an IllegalArgumentException for illegal axis,
+ // but the Harmony testsuite indicates fallback to super behaviour.
+ if (interceptor != null && (axis == X_AXIS || axis == Y_AXIS))
+ {
+ if (axis == X_AXIS)
+ align = interceptor.getAlignmentX();
+ else if (axis == Y_AXIS)
+ align = interceptor.getAlignmentY();
+ else
+ assert false : "Must not reach here";
+ }
else
- throw new IllegalArgumentException();
+ align = super.getAlignment(axis);
return align;
}
@@ -118,8 +275,6 @@ public class ComponentView extends View
*/
public final Component getComponent()
{
- if (comp == null)
- comp = createComponent();
return comp;
}
@@ -135,49 +290,70 @@ public class ComponentView extends View
*/
public float getMaximumSpan(int axis)
{
- float span;
- if (axis == X_AXIS)
- span = getComponent().getMaximumSize().width;
- else if (axis == Y_AXIS)
- span = getComponent().getMaximumSize().height;
- else
- throw new IllegalArgumentException();
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis");
+ float span = 0;
+ if (interceptor != null)
+ {
+ if (axis == X_AXIS)
+ span = interceptor.getMaximumSize().width;
+ else if (axis == Y_AXIS)
+ span = interceptor.getMaximumSize().height;
+ else
+ assert false : "Must not reach here";
+ }
return span;
}
public float getMinimumSpan(int axis)
{
- float span;
- if (axis == X_AXIS)
- span = getComponent().getMinimumSize().width;
- else if (axis == Y_AXIS)
- span = getComponent().getMinimumSize().height;
- else
- throw new IllegalArgumentException();
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis");
+ float span = 0;
+ if (interceptor != null)
+ {
+ if (axis == X_AXIS)
+ span = interceptor.getMinimumSize().width;
+ else if (axis == Y_AXIS)
+ span = interceptor.getMinimumSize().height;
+ else
+ assert false : "Must not reach here";
+ }
return span;
}
public float getPreferredSpan(int axis)
{
- float span;
- if (axis == X_AXIS)
- span = getComponent().getPreferredSize().width;
- else if (axis == Y_AXIS)
- span = getComponent().getPreferredSize().height;
- else
- throw new IllegalArgumentException();
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis");
+ float span = 0;
+ if (interceptor != null)
+ {
+ if (axis == X_AXIS)
+ span = interceptor.getPreferredSize().width;
+ else if (axis == Y_AXIS)
+ span = interceptor.getPreferredSize().height;
+ else
+ assert false : "Must not reach here";
+ }
return span;
}
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException
{
- Element el = getElement();
- if (pos < el.getStartOffset() || pos >= el.getEndOffset())
- throw new BadLocationException("Illegal offset for this view", pos);
- Rectangle r = a.getBounds();
- Component c = getComponent();
- return new Rectangle(r.x, r.y, c.getWidth(), c.getHeight());
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ if (pos >= p0 && pos <= p1)
+ {
+ Rectangle viewRect = a.getBounds();
+ if (pos == p1)
+ viewRect.x += viewRect.width;
+ viewRect.width = 0;
+ return viewRect;
+ }
+ else
+ throw new BadLocationException("Illegal position", pos);
}
/**
@@ -191,8 +367,11 @@ public class ComponentView extends View
*/
public void paint(Graphics g, Shape a)
{
- Rectangle r = a.getBounds();
- getComponent().setBounds(r.x, r.y, r.width, r.height);
+ if (interceptor != null)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ interceptor.setBounds(r.x, r.y, r.width, r.height);
+ }
}
/**
@@ -209,15 +388,33 @@ public class ComponentView extends View
*/
public void setParent(final View p)
{
+ super.setParent(p);
if (SwingUtilities.isEventDispatchThread())
- setParentImpl(p);
+ setParentImpl();
else
SwingUtilities.invokeLater
(new Runnable()
{
public void run()
{
- setParentImpl(p);
+ Document doc = getDocument();
+ try
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ setParentImpl();
+ Container host = getContainer();
+ if (host != null)
+ {
+ preferenceChanged(null, true, true);
+ host.repaint();
+ }
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
}
});
}
@@ -225,23 +422,41 @@ public class ComponentView extends View
/**
* The implementation of {@link #setParent}. This is package private to
* avoid a synthetic accessor method.
- *
- * @param p the parent view to set
*/
- private void setParentImpl(View p)
+ void setParentImpl()
{
- super.setParent(p);
+ View p = getParent();
if (p != null)
{
- Component c = getComponent();
- p.getContainer().add(c);
+ Container c = getContainer();
+ if (c != null)
+ {
+ if (interceptor == null)
+ {
+ // Create component and put it inside the interceptor.
+ Component created = createComponent();
+ if (created != null)
+ {
+ comp = created;
+ interceptor = new Interceptor(comp);
+ }
+ }
+ if (interceptor != null)
+ {
+ // Add the interceptor to the hosting container.
+ if (interceptor.getParent() == null)
+ c.add(interceptor, this);
+ }
+ }
}
else
{
- Component c = getComponent();
- Container parent = c.getParent();
- parent.remove(c);
- comp = null;
+ if (interceptor != null)
+ {
+ Container parent = interceptor.getParent();
+ if (parent != null)
+ parent.remove(interceptor);
+ }
}
}
@@ -259,10 +474,21 @@ public class ComponentView extends View
*/
public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
{
- // The element should only have one character position and it is clear
- // that this position is the position that best matches the given screen
- // coordinates, simply because this view has only this one position.
- Element el = getElement();
- return el.getStartOffset();
+ int pos;
+ // I'd rather do the following. The harmony testsuite indicates
+ // that a simple cast is performed.
+ //Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ Rectangle r = (Rectangle) a;
+ if (x < r.x + r.width / 2)
+ {
+ b[0] = Position.Bias.Forward;
+ pos = getStartOffset();
+ }
+ else
+ {
+ b[0] = Position.Bias.Backward;
+ pos = getEndOffset();
+ }
+ return pos;
}
}
diff --git a/libjava/classpath/javax/swing/text/CompositeView.java b/libjava/classpath/javax/swing/text/CompositeView.java
index 6f487b8..570fc95 100644
--- a/libjava/classpath/javax/swing/text/CompositeView.java
+++ b/libjava/classpath/javax/swing/text/CompositeView.java
@@ -38,7 +38,6 @@ exception statement from your version. */
package javax.swing.text;
-import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -57,20 +56,28 @@ public abstract class CompositeView
/**
* The child views of this <code>CompositeView</code>.
*/
- View[] children;
+ private View[] children;
+
+ /**
+ * The number of child views.
+ */
+ private int numChildren;
/**
* The allocation of this <code>View</code> minus its insets. This is
* initialized in {@link #getInsideAllocation} and reused and modified in
* {@link #childAllocation(int, Rectangle)}.
*/
- Rectangle insideAllocation;
+ private final Rectangle insideAllocation = new Rectangle();
/**
* The insets of this <code>CompositeView</code>. This is initialized
* in {@link #setInsets}.
*/
- Insets insets;
+ private short top;
+ private short bottom;
+ private short left;
+ private short right;
/**
* Creates a new <code>CompositeView</code> for the given
@@ -82,7 +89,10 @@ public abstract class CompositeView
{
super(element);
children = new View[0];
- insets = new Insets(0, 0, 0, 0);
+ top = 0;
+ bottom = 0;
+ left = 0;
+ right = 0;
}
/**
@@ -96,16 +106,22 @@ public abstract class CompositeView
*/
protected void loadChildren(ViewFactory f)
{
- Element el = getElement();
- int count = el.getElementCount();
- View[] newChildren = new View[count];
- for (int i = 0; i < count; ++i)
+ if (f != null)
{
- Element child = el.getElement(i);
- View view = f.create(child);
- newChildren[i] = view;
+ Element el = getElement();
+ int count = el.getElementCount();
+ View[] newChildren = new View[count];
+ for (int i = 0; i < count; ++i)
+ {
+ Element child = el.getElement(i);
+ View view = f.create(child);
+ newChildren[i] = view;
+ }
+ // I'd have called replace(0, getViewCount(), newChildren) here
+ // in order to replace all existing views. However according to
+ // Harmony's tests this is not what the RI does.
+ replace(0, 0, newChildren);
}
- replace(0, getViewCount(), newChildren);
}
/**
@@ -118,7 +134,7 @@ public abstract class CompositeView
public void setParent(View parent)
{
super.setParent(parent);
- if (parent != null && ((children == null) || children.length == 0))
+ if (parent != null && numChildren == 0)
loadChildren(getViewFactory());
}
@@ -129,7 +145,7 @@ public abstract class CompositeView
*/
public int getViewCount()
{
- return children.length;
+ return numChildren;
}
/**
@@ -156,24 +172,42 @@ public abstract class CompositeView
*/
public void replace(int offset, int length, View[] views)
{
- // Check for null views to add.
- for (int i = 0; i < views.length; ++i)
- if (views[i] == null)
- throw new NullPointerException("Added views must not be null");
-
- int endOffset = offset + length;
+ // Make sure we have an array. The Harmony testsuite indicates that we
+ // have to do something like this.
+ if (views == null)
+ views = new View[0];
// First we set the parent of the removed children to null.
+ int endOffset = offset + length;
for (int i = offset; i < endOffset; ++i)
- children[i].setParent(null);
+ {
+ if (children[i].getParent() == this)
+ children[i].setParent(null);
+ children[i] = null;
+ }
- View[] newChildren = new View[children.length - length + views.length];
- System.arraycopy(children, 0, newChildren, 0, offset);
- System.arraycopy(views, 0, newChildren, offset, views.length);
- System.arraycopy(children, offset + length, newChildren,
- offset + views.length,
- children.length - (offset + length));
- children = newChildren;
+ // Update the children array.
+ int delta = views.length - length;
+ int src = offset + length;
+ int numMove = numChildren - src;
+ int dst = src + delta;
+ if (numChildren + delta > children.length)
+ {
+ // Grow array.
+ int newLength = Math.max(2 * children.length, numChildren + delta);
+ View[] newChildren = new View[newLength];
+ System.arraycopy(children, 0, newChildren, 0, offset);
+ System.arraycopy(views, 0, newChildren, offset, views.length);
+ System.arraycopy(children, src, newChildren, dst, numMove);
+ children = newChildren;
+ }
+ else
+ {
+ // Patch existing array.
+ System.arraycopy(children, src, children, dst, numMove);
+ System.arraycopy(views, 0, children, offset, views.length);
+ }
+ numChildren += delta;
// Finally we set the parent of the added children to this.
for (int i = 0; i < views.length; ++i)
@@ -248,34 +282,13 @@ public abstract class CompositeView
}
}
}
- else
- {
- throw new BadLocationException("Position " + pos
- + " is not represented by view.", pos);
- }
}
- return ret;
- }
- /**
- * A helper method for {@link #modelToView(int, Position.Bias, int,
- * Position.Bias, Shape)}. This creates a default location when there is
- * no child view that can take responsibility for mapping the position to
- * view coordinates. Depending on the specified bias this will be the
- * left or right edge of this view's allocation.
- *
- * @param a the allocation for this view
- * @param bias the bias
- *
- * @return a default location
- */
- private Shape createDefaultLocation(Shape a, Position.Bias bias)
- {
- Rectangle alloc = a.getBounds();
- Rectangle location = new Rectangle(alloc.x, alloc.y, 1, alloc.height);
- if (bias == Position.Bias.Forward)
- location.x = alloc.x + alloc.width;
- return location;
+ if (ret == null)
+ throw new BadLocationException("Position " + pos
+ + " is not represented by view.", pos);
+
+ return ret;
}
/**
@@ -394,7 +407,7 @@ public abstract class CompositeView
*/
public int getViewIndex(int pos, Position.Bias b)
{
- if (b == Position.Bias.Backward && pos != 0)
+ if (b == Position.Bias.Backward)
pos -= 1;
int i = -1;
if (pos >= getStartOffset() && pos < getEndOffset())
@@ -514,24 +527,17 @@ public abstract class CompositeView
if (a == null)
return null;
- Rectangle alloc = a.getBounds();
+ // Try to avoid allocation of Rectangle here.
+ Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+
// Initialize the inside allocation rectangle. This is done inside
// a synchronized block in order to avoid multiple threads creating
// this instance simultanously.
- Rectangle inside;
- synchronized(this)
- {
- inside = insideAllocation;
- if (inside == null)
- {
- inside = new Rectangle();
- insideAllocation = inside;
- }
- }
- inside.x = alloc.x + insets.left;
- inside.y = alloc.y + insets.top;
- inside.width = alloc.width - insets.left - insets.right;
- inside.height = alloc.height - insets.top - insets.bottom;
+ Rectangle inside = insideAllocation;
+ inside.x = alloc.x + getLeftInset();
+ inside.y = alloc.y + getTopInset();
+ inside.width = alloc.width - getLeftInset() - getRightInset();
+ inside.height = alloc.height - getTopInset() - getBottomInset();
return inside;
}
@@ -546,39 +552,26 @@ public abstract class CompositeView
*/
protected void setParagraphInsets(AttributeSet attributes)
{
- Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent);
- short left = 0;
- if (l != null)
- left = l.shortValue();
- Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent);
- short right = 0;
- if (r != null)
- right = r.shortValue();
- Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove);
- short top = 0;
- if (t != null)
- top = t.shortValue();
- Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow);
- short bottom = 0;
- if (b != null)
- bottom = b.shortValue();
- setInsets(top, left, bottom, right);
+ top = (short) StyleConstants.getSpaceAbove(attributes);
+ bottom = (short) StyleConstants.getSpaceBelow(attributes);
+ left = (short) StyleConstants.getLeftIndent(attributes);
+ right = (short) StyleConstants.getRightIndent(attributes);
}
/**
* Sets the insets of this <code>CompositeView</code>.
*
- * @param top the top inset
- * @param left the left inset
- * @param bottom the bottom inset
- * @param right the right inset
+ * @param t the top inset
+ * @param l the left inset
+ * @param b the bottom inset
+ * @param r the right inset
*/
- protected void setInsets(short top, short left, short bottom, short right)
+ protected void setInsets(short t, short l, short b, short r)
{
- insets.top = top;
- insets.left = left;
- insets.bottom = bottom;
- insets.right = right;
+ top = t;
+ left = l;
+ bottom = b;
+ right = r;
}
/**
@@ -588,7 +581,7 @@ public abstract class CompositeView
*/
protected short getLeftInset()
{
- return (short) insets.left;
+ return left;
}
/**
@@ -598,7 +591,7 @@ public abstract class CompositeView
*/
protected short getRightInset()
{
- return (short) insets.right;
+ return right;
}
/**
@@ -608,7 +601,7 @@ public abstract class CompositeView
*/
protected short getTopInset()
{
- return (short) insets.top;
+ return top;
}
/**
@@ -618,7 +611,7 @@ public abstract class CompositeView
*/
protected short getBottomInset()
{
- return (short) insets.bottom;
+ return bottom;
}
/**
diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java
index 84f47f1..c4c2580 100644
--- a/libjava/classpath/javax/swing/text/DefaultCaret.java
+++ b/libjava/classpath/javax/swing/text/DefaultCaret.java
@@ -804,7 +804,7 @@ public class DefaultCaret extends Rectangle
}
}
}
-
+
private void handleHighlight()
{
Highlighter highlighter = textComponent.getHighlighter();
@@ -946,7 +946,7 @@ public class DefaultCaret extends Rectangle
*
* @return all registered event listeners of the specified type
*/
- public EventListener[] getListeners(Class listenerType)
+ public <T extends EventListener> T[] getListeners(Class<T> listenerType)
{
return listenerList.getListeners(listenerType);
}
@@ -1075,8 +1075,6 @@ public class DefaultCaret extends Rectangle
handleHighlight();
appear();
-
- adjustVisibility(this);
}
}
@@ -1114,8 +1112,6 @@ public class DefaultCaret extends Rectangle
clearHighlight();
appear();
-
- adjustVisibility(this);
}
}
@@ -1154,7 +1150,12 @@ public class DefaultCaret extends Rectangle
// e.printStackTrace();
}
if (area != null)
- damage(area);
+ {
+ adjustVisibility(area);
+ if (getMagicCaretPosition() == null)
+ setMagicCaretPosition(new Point(area.x, area.y));
+ damage(area);
+ }
}
repaint();
}
diff --git a/libjava/classpath/javax/swing/text/DefaultEditorKit.java b/libjava/classpath/javax/swing/text/DefaultEditorKit.java
index 8602e69..aa69dec 100644
--- a/libjava/classpath/javax/swing/text/DefaultEditorKit.java
+++ b/libjava/classpath/javax/swing/text/DefaultEditorKit.java
@@ -38,7 +38,6 @@ exception statement from your version. */
package javax.swing.text;
-import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
@@ -312,19 +311,21 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- int offs = t.getDocument().getLength();
- Caret c = t.getCaret();
- c.setDot(0);
- c.moveDot(offs);
-
- try
- {
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch(BadLocationException ble)
- {
- // Can't happen.
- }
+ if (t != null)
+ {
+ int offs = t.getDocument().getLength();
+ Caret c = t.getCaret();
+ c.setDot(0);
+ c.moveDot(offs);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
}
}
@@ -339,15 +340,18 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- Caret c = t.getCaret();
- c.moveDot(0);
- try
- {
- c.setMagicCaretPosition(t.modelToView(0).getLocation());
- }
- catch(BadLocationException ble)
+ if (t != null)
{
- // Can't happen.
+ Caret c = t.getCaret();
+ c.moveDot(0);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(0).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
}
}
}
@@ -363,16 +367,19 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- int offs = t.getDocument().getLength();
- Caret c = t.getCaret();
- c.moveDot(offs);
- try
- {
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch(BadLocationException ble)
+ if (t != null)
{
- // Can't happen.
+ int offs = t.getDocument().getLength();
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
}
}
}
@@ -389,17 +396,19 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- Caret c = t.getCaret();
- try
+ if (t != null)
{
- int offs = Utilities.getRowStart(t, c.getDot());
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ Caret c = t.getCaret();
+ try
+ {
+ int offs = Utilities.getRowStart(t, c.getDot());
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
}
- catch(BadLocationException ble)
- {
- // Can't happen.
- }
-
}
}
@@ -414,17 +423,19 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- Caret c = t.getCaret();
- try
- {
- int offs = Utilities.getRowEnd(t, c.getDot());
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch(BadLocationException ble)
+ if (t != null)
{
- // Can't happen.
+ Caret c = t.getCaret();
+ try
+ {
+ int offs = Utilities.getRowEnd(t, c.getDot());
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
}
-
}
}
@@ -438,20 +449,21 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- Caret c = t.getCaret();
- try
- {
- int offs1 = Utilities.getRowStart(t, c.getDot());
- int offs2 = Utilities.getRowEnd(t, c.getDot());
-
- c.setDot(offs2);
- c.moveDot(offs1);
-
- c.setMagicCaretPosition(t.modelToView(offs2).getLocation());
- }
- catch(BadLocationException ble)
+ if (t != null)
{
- // Can't happen.
+ Caret c = t.getCaret();
+ try
+ {
+ int offs1 = Utilities.getRowStart(t, c.getDot());
+ int offs2 = Utilities.getRowEnd(t, c.getDot());
+ c.setDot(offs2);
+ c.moveDot(offs1);
+ c.setMagicCaretPosition(t.modelToView(offs2).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
}
}
}
@@ -466,51 +478,52 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- Caret c = t.getCaret();
- int dot = c.getDot();
-
- try
+ if (t != null)
{
- int wordStart = Utilities.getWordStart(t, dot);
-
- if (dot == wordStart)
- {
- // Current cursor position is on the first character in a word.
- c.setDot(wordStart);
- c.moveDot(Utilities.getWordEnd(t, wordStart));
- }
- else
+ Caret c = t.getCaret();
+ int dot = c.getDot();
+ try
{
- // Current cursor position is not on the first character
- // in a word.
- int nextWord = Utilities.getNextWord(t, dot);
- int previousWord = Utilities.getPreviousWord(t, dot);
- int previousWordEnd = Utilities.getWordEnd(t, previousWord);
-
- // Cursor position is in the space between two words. In such a
- // situation just select the space.
- if (dot >= previousWordEnd && dot <= nextWord)
+ int wordStart = Utilities.getWordStart(t, dot);
+
+ if (dot == wordStart)
{
- c.setDot(previousWordEnd);
- c.moveDot(nextWord);
+ // Current cursor position is on the first character in a word.
+ c.setDot(wordStart);
+ c.moveDot(Utilities.getWordEnd(t, wordStart));
}
else
{
- // Cursor position is inside a word. Just select it then.
- c.setDot(previousWord);
- c.moveDot(previousWordEnd);
+ // Current cursor position is not on the first character
+ // in a word.
+ int nextWord = Utilities.getNextWord(t, dot);
+ int previousWord = Utilities.getPreviousWord(t, dot);
+ int previousWordEnd = Utilities.getWordEnd(t, previousWord);
+
+ // Cursor position is in the space between two words. In such a
+ // situation just select the space.
+ if (dot >= previousWordEnd && dot <= nextWord)
+ {
+ c.setDot(previousWordEnd);
+ c.moveDot(nextWord);
+ }
+ else
+ {
+ // Cursor position is inside a word. Just select it then.
+ c.setDot(previousWord);
+ c.moveDot(previousWordEnd);
+ }
}
- }
- // If the position was updated change the magic caret position
- // as well.
- if (c.getDot() != dot)
- c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation());
-
- }
- catch(BadLocationException ble)
- {
- // Can't happen.
+ // If the position was updated change the magic caret position
+ // as well.
+ if (c.getDot() != dot)
+ c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
}
}
}
@@ -715,21 +728,23 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- try
- {
- int offs = Utilities.getRowEnd(t, t.getCaretPosition());
-
- if (offs > -1)
- {
- Caret c = t.getCaret();
- c.setDot(offs);
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- }
- catch (BadLocationException ble)
- {
- // Nothing to do here
- }
+ if (t != null)
+ {
+ try
+ {
+ int offs = Utilities.getRowEnd(t, t.getCaretPosition());
+ if (offs > -1)
+ {
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch (BadLocationException ble)
+ {
+ // Nothing to do here
+ }
+ }
}
}
@@ -744,21 +759,23 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- try
- {
- int offs = Utilities.getRowStart(t, t.getCaretPosition());
-
- if (offs > -1)
- {
- Caret c = t.getCaret();
- c.setDot(offs);
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- }
- catch (BadLocationException ble)
- {
- // Do nothing here.
- }
+ if (t != null)
+ {
+ try
+ {
+ int offs = Utilities.getRowStart(t, t.getCaretPosition());
+ if (offs > -1)
+ {
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch (BadLocationException ble)
+ {
+ // Do nothing here.
+ }
+ }
}
}
@@ -773,16 +790,19 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- Caret c = t.getCaret();
- c.setDot(0);
- try
- {
- c.setMagicCaretPosition(t.modelToView(0).getLocation());
- }
- catch(BadLocationException ble)
- {
- // Can't happen.
- }
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ c.setDot(0);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(0).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
}
}
@@ -797,16 +817,19 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- int offs = t.getDocument().getLength();
- Caret c = t.getCaret();
- c.setDot(offs);
- try
- {
- c.setMagicCaretPosition(t.modelToView(offs).getLocation());
- }
- catch(BadLocationException ble)
+ if (t != null)
{
- // Can't happen.
+ int offs = t.getDocument().getLength();
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
}
}
}
@@ -862,7 +885,9 @@ public class DefaultEditorKit extends EditorKit
*/
public void actionPerformed(ActionEvent event)
{
- getTextComponent(event).copy();
+ JTextComponent target = getTextComponent(event);
+ if (target != null)
+ target.copy();
}
}
@@ -893,7 +918,9 @@ public class DefaultEditorKit extends EditorKit
*/
public void actionPerformed(ActionEvent event)
{
- getTextComponent(event).cut();
+ JTextComponent target = getTextComponent(event);
+ if (target != null)
+ target.cut();
}
}
@@ -922,7 +949,9 @@ public class DefaultEditorKit extends EditorKit
*/
public void actionPerformed(ActionEvent event)
{
- getTextComponent(event).paste();
+ JTextComponent target = getTextComponent(event);
+ if (target != null)
+ target.paste();
}
}
@@ -957,14 +986,26 @@ public class DefaultEditorKit extends EditorKit
{
// first we filter the following events:
// - control characters
- // - key events with the ALT modifier (FIXME: filter that too!)
- int cp = event.getActionCommand().codePointAt(0);
- if (Character.isISOControl(cp))
- return;
-
- JTextComponent t = getTextComponent(event);
- if (t != null && t.isEnabled() && t.isEditable())
- t.replaceSelection(event.getActionCommand());
+ // - key events with the ALT modifier
+ JTextComponent target = getTextComponent(event);
+ if ((target != null) && (event != null))
+ {
+ if ((target.isEditable()) && (target.isEnabled()))
+ {
+ String content = event.getActionCommand();
+ int mod = event.getModifiers();
+ if ((content != null) && (content.length() > 0)
+ && (mod & ActionEvent.ALT_MASK) == 0
+ && (mod & ActionEvent.CTRL_MASK) == 0)
+ {
+ char c = content.charAt(0);
+ if ((c >= 0x20) && (c != 0x7F))
+ {
+ target.replaceSelection(content);
+ }
+ }
+ }
+ }
}
}
@@ -992,7 +1033,8 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- t.replaceSelection("\n");
+ if (t != null)
+ t.replaceSelection("\n");
}
}
@@ -1047,7 +1089,8 @@ public class DefaultEditorKit extends EditorKit
public void actionPerformed(ActionEvent event)
{
JTextComponent t = getTextComponent(event);
- t.replaceSelection("\t");
+ if (t != null)
+ t.replaceSelection("\t");
}
}
diff --git a/libjava/classpath/javax/swing/text/DefaultFormatter.java b/libjava/classpath/javax/swing/text/DefaultFormatter.java
index e42b169..bf7c02a 100644
--- a/libjava/classpath/javax/swing/text/DefaultFormatter.java
+++ b/libjava/classpath/javax/swing/text/DefaultFormatter.java
@@ -216,7 +216,7 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
*/
public DefaultFormatter()
{
- commitsOnValidEdit = true;
+ commitsOnValidEdit = false;
overwriteMode = true;
allowsInvalid = true;
}
@@ -330,7 +330,7 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
*
* @return the class that is used for values
*/
- public Class getValueClass()
+ public Class<?> getValueClass()
{
return valueClass;
}
@@ -342,7 +342,7 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
*
* @see #getValueClass()
*/
- public void setValueClass(Class valueClass)
+ public void setValueClass(Class<?> valueClass)
{
this.valueClass = valueClass;
}
diff --git a/libjava/classpath/javax/swing/text/DefaultHighlighter.java b/libjava/classpath/javax/swing/text/DefaultHighlighter.java
index 59f7731..69563e4 100644
--- a/libjava/classpath/javax/swing/text/DefaultHighlighter.java
+++ b/libjava/classpath/javax/swing/text/DefaultHighlighter.java
@@ -1,4 +1,4 @@
-/* DefaultHighlighter.java --
+/* DefaultHighlighter.java -- The default highlight for Swing
Copyright (C) 2004, 2006 Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -38,18 +38,21 @@ exception statement from your version. */
package javax.swing.text;
-import gnu.classpath.NotImplementedException;
-
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
+import java.util.Iterator;
import javax.swing.SwingUtilities;
import javax.swing.plaf.TextUI;
+/**
+ * The default highlight for Swing text components. It highlights text
+ * by filling the background with a rectangle.
+ */
public class DefaultHighlighter extends LayeredHighlighter
{
public static class DefaultHighlightPainter
@@ -68,11 +71,6 @@ public class DefaultHighlighter extends LayeredHighlighter
return color;
}
- private void paintHighlight(Graphics g, Rectangle rect)
- {
- g.fillRect(rect.x, rect.y, rect.width, rect.height);
- }
-
public void paint(Graphics g, int p0, int p1, Shape bounds,
JTextComponent t)
{
@@ -81,30 +79,31 @@ public class DefaultHighlighter extends LayeredHighlighter
Rectangle rect = bounds.getBounds();
- if (color == null)
- g.setColor(t.getSelectionColor());
- else
- g.setColor(color);
+ Color col = getColor();
+ if (col == null)
+ col = t.getSelectionColor();
+ g.setColor(col);
TextUI ui = t.getUI();
try
- {
-
- Rectangle l0 = ui.modelToView(t, p0, null);
- Rectangle l1 = ui.modelToView(t, p1, null);
-
- // Note: The computed locations may lie outside of the allocation
- // area if the text is scrolled.
+ {
+
+ Rectangle l0 = ui.modelToView(t, p0, null);
+ Rectangle l1 = ui.modelToView(t, p1, null);
- if (l0.y == l1.y)
+ // Note: The computed locations may lie outside of the allocation
+ // area if the text is scrolled.
+
+ if (l0.y == l1.y)
{
SwingUtilities.computeUnion(l0.x, l0.y, l0.width, l0.height, l1);
// Paint only inside the allocation area.
- SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height, l1);
+ SwingUtilities.computeIntersection(rect.x, rect.y, rect.width,
+ rect.height, l1);
- paintHighlight(g, l1);
+ g.fillRect(l1.x, l1.y, l1.width, l1.height);
}
else
{
@@ -115,77 +114,71 @@ public class DefaultHighlighter extends LayeredHighlighter
// out the bounds.
// 3. The final line is painted from the left border to the
// position of p1.
-
- // Highlight first line until the end.
- // If rect.x is non-zero the calculation will properly adjust the
- // area to be painted.
- l0.x -= rect.x;
- l0.width = rect.width - l0.x - rect.x;
-
- paintHighlight(g, l0);
-
- int posBelow = Utilities.getPositionBelow(t, p0, l0.x);
- int p1RowStart = Utilities.getRowStart(t, p1);
- if (posBelow != -1
- && posBelow != p0
- && Utilities.getRowStart(t, posBelow)
- != p1RowStart)
- {
- Rectangle grow = ui.modelToView(t, posBelow);
- grow.x = rect.x;
- grow.width = rect.width;
-
- // Find further lines which have to be highlighted completely.
- int nextPosBelow = posBelow;
- while (nextPosBelow != -1
- && Utilities.getRowStart(t, nextPosBelow) != p1RowStart)
- {
- posBelow = nextPosBelow;
- nextPosBelow = Utilities.getPositionBelow(t, posBelow, l0.x);
-
- if (nextPosBelow == posBelow)
- break;
- }
- // Now posBelow is an offset on the last line which has to be painted
- // completely. (newPosBelow is on the same line as p1)
-
- // Retrieve the rectangle of posBelow and use its y and height
- // value to calculate the final height of the multiple line
- // spanning rectangle.
- Rectangle end = ui.modelToView(t, posBelow);
- grow.height = end.y + end.height - grow.y;
-
- paintHighlight(g, grow);
- }
-
- // Paint last line from its beginning to the position of p1.
- l1.width = l1.x + l1.width - rect.x;
- l1.x = rect.x;
- paintHighlight(g, l1);
- }
+
+ int firstLineWidth = rect.x + rect.width - l0.x;
+ g.fillRect(l0.x, l0.y, firstLineWidth, l0.height);
+ if (l0.y + l0.height != l1.y)
+ {
+ g.fillRect(rect.x, l0.y + l0.height, rect.width,
+ l1.y - l0.y - l0.height);
+ }
+ g.fillRect(rect.x, l1.y, l1.x - rect.x, l1.height);
+ }
}
catch (BadLocationException ex)
{
- AssertionError err = new AssertionError("Unexpected bad location exception");
- err.initCause(ex);
- throw err;
+ // Can't render. Comment out for debugging.
+ // ex.printStackTrace();
}
}
public Shape paintLayer(Graphics g, int p0, int p1, Shape bounds,
JTextComponent c, View view)
{
- throw new InternalError();
+ Color col = getColor();
+ if (col == null)
+ col = c.getSelectionColor();
+ g.setColor(col);
+
+ Rectangle rect = null;
+ if (p0 == view.getStartOffset() && p1 == view.getEndOffset())
+ {
+ // Paint complete bounds region.
+ rect = bounds instanceof Rectangle ? (Rectangle) bounds
+ : bounds.getBounds();
+ }
+ else
+ {
+ // Only partly inside the view.
+ try
+ {
+ Shape s = view.modelToView(p0, Position.Bias.Forward,
+ p1, Position.Bias.Backward,
+ bounds);
+ rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
+ }
+ catch (BadLocationException ex)
+ {
+ // Can't render the highlight.
+ }
+ }
+
+ if (rect != null)
+ {
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ }
+ return rect;
}
}
private class HighlightEntry implements Highlighter.Highlight
{
- int p0;
- int p1;
+ Position p0;
+ Position p1;
Highlighter.HighlightPainter painter;
- public HighlightEntry(int p0, int p1, Highlighter.HighlightPainter painter)
+ public HighlightEntry(Position p0, Position p1,
+ Highlighter.HighlightPainter painter)
{
this.p0 = p0;
this.p1 = p1;
@@ -194,12 +187,12 @@ public class DefaultHighlighter extends LayeredHighlighter
public int getStartOffset()
{
- return p0;
+ return p0.getOffset();
}
public int getEndOffset()
{
- return p1;
+ return p1.getOffset();
}
public Highlighter.HighlightPainter getPainter()
@@ -209,6 +202,58 @@ public class DefaultHighlighter extends LayeredHighlighter
}
/**
+ * A HighlightEntry that is used for LayerPainter painters. In addition
+ * to the info maintained by the HighlightEntry, this class maintains
+ * a painting rectangle. This is used as repaint region when the
+ * highlight changes and the text component needs repainting.
+ */
+ private class LayerHighlightEntry
+ extends HighlightEntry
+ {
+
+ /**
+ * The paint rectangle.
+ */
+ Rectangle paintRect = new Rectangle();
+
+ LayerHighlightEntry(Position p0, Position p1,
+ Highlighter.HighlightPainter p)
+ {
+ super(p0, p1, p);
+ }
+
+ /**
+ * Paints the highlight by calling the LayerPainter. This
+ * restricts the area to be painted by startOffset and endOffset
+ * and manages the paint rectangle.
+ */
+ void paintLayeredHighlight(Graphics g, int p0, int p1, Shape bounds,
+ JTextComponent tc, View view)
+ {
+ p0 = Math.max(getStartOffset(), p0);
+ p1 = Math.min(getEndOffset(), p1);
+
+ Highlighter.HighlightPainter painter = getPainter();
+ if (painter instanceof LayerPainter)
+ {
+ LayerPainter layerPainter = (LayerPainter) painter;
+ Shape area = layerPainter.paintLayer(g, p0, p1, bounds, tc, view);
+ Rectangle rect;
+ if (area instanceof Rectangle && paintRect != null)
+ rect = (Rectangle) area;
+ else
+ rect = area.getBounds();
+
+ if (paintRect.width == 0 || paintRect.height == 0)
+ paintRect = rect.getBounds();
+ else
+ paintRect = SwingUtilities.computeUnion(rect.x, rect.y, rect.width,
+ rect.height, paintRect);
+ }
+ }
+ }
+
+ /**
* @specnote final as of 1.4
*/
public static final LayeredHighlighter.LayerPainter DefaultPainter =
@@ -254,11 +299,19 @@ public class DefaultHighlighter extends LayeredHighlighter
textComponent = null;
}
- public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter painter)
+ public Object addHighlight(int p0, int p1,
+ Highlighter.HighlightPainter painter)
throws BadLocationException
{
checkPositions(p0, p1);
- HighlightEntry entry = new HighlightEntry(p0, p1, painter);
+ HighlightEntry entry;
+ Document doc = textComponent.getDocument();
+ Position pos0 = doc.createPosition(p0);
+ Position pos1 = doc.createPosition(p1);
+ if (getDrawsLayeredHighlights() && painter instanceof LayerPainter)
+ entry = new LayerHighlightEntry(pos0, pos1, painter);
+ else
+ entry = new HighlightEntry(pos0, pos1, painter);
highlights.add(entry);
textComponent.getUI().damageRange(textComponent, p0, p1);
@@ -268,16 +321,67 @@ public class DefaultHighlighter extends LayeredHighlighter
public void removeHighlight(Object tag)
{
+ HighlightEntry entry = (HighlightEntry) tag;
+ if (entry instanceof LayerHighlightEntry)
+ {
+ LayerHighlightEntry lEntry = (LayerHighlightEntry) entry;
+ Rectangle paintRect = lEntry.paintRect;
+ textComponent.repaint(paintRect.x, paintRect.y, paintRect.width,
+ paintRect.height);
+ }
+ else
+ {
+ textComponent.getUI().damageRange(textComponent,
+ entry.getStartOffset(),
+ entry.getEndOffset());
+ }
highlights.remove(tag);
- HighlightEntry entry = (HighlightEntry) tag;
- textComponent.getUI().damageRange(textComponent,
- entry.p0,
- entry.p1);
}
public void removeAllHighlights()
{
+ // Repaint damaged region.
+ int minX = 0;
+ int maxX = 0;
+ int minY = 0;
+ int maxY = 0;
+ int p0 = -1;
+ int p1 = -1;
+ for (Iterator i = highlights.iterator(); i.hasNext();)
+ {
+ HighlightEntry e = (HighlightEntry) i.next();
+ if (e instanceof LayerHighlightEntry)
+ {
+ LayerHighlightEntry le = (LayerHighlightEntry) e;
+ Rectangle r = le.paintRect;
+ minX = Math.min(r.x, minX);
+ maxX = Math.max(r.x + r.width, maxX);
+ minY = Math.min(r.y, minY);
+ maxY = Math.max(r.y + r.height, maxY);
+ }
+ else
+ {
+ if (p0 == -1 || p1 == -1)
+ {
+ p0 = e.getStartOffset();
+ p1 = e.getEndOffset();
+ }
+ else
+ {
+ p0 = Math.min(p0, e.getStartOffset());
+ p1 = Math.max(p1, e.getEndOffset());
+ }
+ }
+ if (minX != maxX && minY != maxY)
+ textComponent.repaint(minX, minY, maxX - minX, maxY - minY);
+ if (p0 != -1 && p1 != -1)
+ {
+ TextUI ui = textComponent.getUI();
+ ui.damageRange(textComponent, p0, p1);
+ }
+
+ }
highlights.clear();
}
@@ -290,94 +394,61 @@ public class DefaultHighlighter extends LayeredHighlighter
public void changeHighlight(Object tag, int n0, int n1)
throws BadLocationException
{
- int o0, o1;
-
- checkPositions(n0, n1);
- HighlightEntry entry = (HighlightEntry) tag;
- o0 = entry.p0;
- o1 = entry.p1;
-
- // Prevent useless write & repaint operations.
- if (o0 == n0 && o1 == n1)
- return;
-
- entry.p0 = n0;
- entry.p1 = n1;
-
+ Document doc = textComponent.getDocument();
TextUI ui = textComponent.getUI();
-
- // Special situation where the old area has to be cleared simply.
- if (n0 == n1)
- ui.damageRange(textComponent, o0, o1);
- // Calculates the areas where a change is really neccessary
- else if ((o1 > n0 && o1 <= n1)
- || (n1 > o0 && n1 <= o1))
+ if (tag instanceof LayerHighlightEntry)
{
- // [fds, fde) - the first damage region
- // [sds, sde] - the second damage region
- int fds, sds;
- int fde, sde;
-
- // Calculate first damaged region.
- if(o0 < n0)
- {
- // Damaged region will be cleared as
- // the old highlight region starts first.
- fds = o0;
- fde = n0;
- }
- else
- {
- // Damaged region will be painted as
- // the new highlight region starts first.
- fds = n0;
- fde = o0;
- }
-
- if (o1 < n1)
+ LayerHighlightEntry le = (LayerHighlightEntry) tag;
+ Rectangle r = le.paintRect;
+ if (r.width > 0 && r.height > 0)
+ textComponent.repaint(r.x, r.y, r.width, r.height);
+ r.width = 0;
+ r.height = 0;
+ le.p0 = doc.createPosition(n0);
+ le.p1 = doc.createPosition(n1);
+ ui.damageRange(textComponent, Math.min(n0, n1), Math.max(n0, n1));
+ }
+ else if (tag instanceof HighlightEntry)
+ {
+ HighlightEntry e = (HighlightEntry) tag;
+ int p0 = e.getStartOffset();
+ int p1 = e.getEndOffset();
+ if (p0 == n0)
{
- // Final region will be painted as the
- // old highlight region finishes first
- sds = o1;
- sde = n1;
+ ui.damageRange(textComponent, Math.min(p1, n1),
+ Math.max(p1, n1));
}
- else
+ else if (n1 == p1)
{
- // Final region will be cleared as the
- // new highlight region finishes first.
- sds = n1;
- sde = o1;
+ ui.damageRange(textComponent, Math.min(p0, n0),
+ Math.max(p0, n0));
}
-
- // If there is no undamaged region in between
- // call damageRange only once.
- if (fde == sds)
- ui.damageRange(textComponent, fds, sde);
else
{
- if (fds != fde)
- ui.damageRange(textComponent, fds, fde);
-
- if (sds != sde)
- ui.damageRange(textComponent, sds, sde);
+ ui.damageRange(textComponent, p0, p1);
+ ui.damageRange(textComponent, n0, n1);
}
+ e.p0 = doc.createPosition(n0);
+ e.p1 = doc.createPosition(n1);
}
- else
- {
- // The two regions do not overlap. So mark
- // both areas as damaged.
- ui.damageRange(textComponent, o0, o1);
- ui.damageRange(textComponent, n0, n1);
- }
-
}
public void paintLayeredHighlights(Graphics g, int p0, int p1,
Shape viewBounds, JTextComponent editor,
View view)
- throws NotImplementedException
{
- // TODO: Implement this properly.
+ for (Iterator i = highlights.iterator(); i.hasNext();)
+ {
+ Object o = i.next();
+ if (o instanceof LayerHighlightEntry)
+ {
+ LayerHighlightEntry entry = (LayerHighlightEntry) o;
+ int start = entry.getStartOffset();
+ int end = entry.getEndOffset();
+ if ((p0 < start && p1 > start) || (p0 >= start && p0 < end))
+ entry.paintLayeredHighlight(g, p0, p1, viewBounds, editor, view);
+ }
+ }
}
public void paint(Graphics g)
@@ -399,7 +470,9 @@ public class DefaultHighlighter extends LayeredHighlighter
for (int index = 0; index < size; ++index)
{
HighlightEntry entry = (HighlightEntry) highlights.get(index);
- entry.painter.paint(g, entry.p0, entry.p1, bounds, textComponent);
+ if (! (entry instanceof LayerHighlightEntry))
+ entry.painter.paint(g, entry.getStartOffset(), entry.getEndOffset(),
+ bounds, textComponent);
}
}
}
diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
index bd21e55..3156ca6 100644
--- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
+++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
@@ -41,7 +41,9 @@ package javax.swing.text;
import java.awt.Color;
import java.awt.Font;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.Iterator;
import java.util.Stack;
import java.util.Vector;
@@ -424,6 +426,58 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
public class ElementBuffer implements Serializable
{
+ /**
+ * Instance of all editing information for an object in the Vector. This class
+ * is used to add information to the DocumentEvent associated with an
+ * insertion/removal/change as well as to store the changes that need to be
+ * made so they can be made all at the same (appropriate) time.
+ */
+ class Edit
+ {
+ /** The element to edit . */
+ Element e;
+
+ /** The index of the change. */
+ int index;
+
+ /** The removed elements. */
+ ArrayList removed = new ArrayList();
+
+ /** The added elements. */
+ ArrayList added = new ArrayList();
+
+ /**
+ * Indicates if this edit contains a fracture.
+ */
+ boolean isFracture;
+
+ /**
+ * Creates a new Edit for the specified element at index i.
+ *
+ * @param el the element
+ * @param i the index
+ */
+ Edit(Element el, int i)
+ {
+ this(el, i, false);
+ }
+
+ /**
+ * Creates a new Edit for the specified element at index i.
+ *
+ * @param el the element
+ * @param i the index
+ * @param frac if this is a fracture edit or not
+ */
+ Edit(Element el, int i, boolean frac)
+ {
+ e = el;
+ index = i;
+ isFracture = frac;
+ }
+
+ }
+
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = 1688745877691146623L;
@@ -442,11 +496,20 @@ public class DefaultStyledDocument extends AbstractDocument implements
/** Holds the position of the change. */
private int pos;
- /** Holds the element that was last fractured. */
- private Element lastFractured;
-
- /** True if a fracture was not created during a insertFracture call. */
- private boolean fracNotCreated;
+ /**
+ * The parent of the fracture.
+ */
+ private Element fracturedParent;
+
+ /**
+ * The fractured child.
+ */
+ private Element fracturedChild;
+
+ /**
+ * Indicates if a fracture has been created.
+ */
+ private boolean createdFracture;
/**
* The current position in the element tree. This is used for bulk inserts
@@ -454,10 +517,17 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
private Stack elementStack;
+ private Edit[] insertPath;
+
+ private boolean recreateLeafs;
+
/**
- * The ElementChange that describes the latest changes.
+ * Vector that contains all the edits. Maybe replace by a HashMap.
*/
- DefaultDocumentEvent documentEvent;
+ private ArrayList edits;
+
+ private boolean offsetLastIndex;
+ private boolean offsetLastIndexReplace;
/**
* Creates a new <code>ElementBuffer</code> for the specified
@@ -469,7 +539,6 @@ public class DefaultStyledDocument extends AbstractDocument implements
public ElementBuffer(Element root)
{
this.root = root;
- elementStack = new Stack();
}
/**
@@ -495,13 +564,9 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
public void remove(int offs, int len, DefaultDocumentEvent ev)
{
- if (len == 0)
- return;
- offset = offs;
- length = len;
- pos = offset;
- documentEvent = ev;
+ prepareEdit(offs, len);
removeUpdate();
+ finishEdit(ev);
}
/**
@@ -511,109 +576,293 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
protected void removeUpdate()
{
- int startParagraph = root.getElementIndex(offset);
- int endParagraph = root.getElementIndex(offset + length);
- Element[] empty = new Element[0];
- int removeStart = -1;
- int removeEnd = -1;
- for (int i = startParagraph; i < endParagraph; i++)
+ removeElements(root, offset, endOffset);
+ }
+
+ private boolean removeElements(Element elem, int rmOffs0, int rmOffs1)
+ {
+ boolean ret = false;
+ if (! elem.isLeaf())
{
- BranchElement paragraph = (BranchElement) root.getElement(i);
- int contentStart = paragraph.getElementIndex(offset);
- int contentEnd = paragraph.getElementIndex(offset + length);
- if (contentStart == paragraph.getStartOffset()
- && contentEnd == paragraph.getEndOffset())
+ // Update stack for changes.
+ int index0 = elem.getElementIndex(rmOffs0);
+ int index1 = elem.getElementIndex(rmOffs1);
+ elementStack.push(new Edit(elem, index0));
+ Edit ec = (Edit) elementStack.peek();
+
+ // If the range is contained by one element,
+ // we just forward the request
+ if (index0 == index1)
{
- // In this case we only need to remove the whole paragraph. We
- // do this in one go after this loop and only record the indices
- // here.
- if (removeStart == -1)
+ Element child0 = elem.getElement(index0);
+ if(rmOffs0 <= child0.getStartOffset()
+ && rmOffs1 >= child0.getEndOffset())
{
- removeStart = i;
- removeEnd = i;
+ // Element totally removed.
+ ec.removed.add(child0);
+ }
+ else if (removeElements(child0, rmOffs0, rmOffs1))
+ {
+ ec.removed.add(child0);
}
- else
- removeEnd = i;
}
else
{
- // In this case we remove a couple of child elements from this
- // paragraph.
- int removeLen = contentEnd - contentStart;
- Element[] removed = new Element[removeLen];
- for (int j = contentStart; j < contentEnd; j++)
- removed[j] = paragraph.getElement(j);
- Edit edit = getEditForParagraphAndIndex(paragraph, contentStart);
- edit.addRemovedElements(removed);
+ // The removal range spans elements. If we can join
+ // the two endpoints, do it. Otherwise we remove the
+ // interior and forward to the endpoints.
+ Element child0 = elem.getElement(index0);
+ Element child1 = elem.getElement(index1);
+ boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
+ if (containsOffs1 && canJoin(child0, child1))
+ {
+ // Remove and join.
+ for (int i = index0; i <= index1; i++)
+ {
+ ec.removed.add(elem.getElement(i));
+ }
+ Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
+ ec.added.add(e);
}
+ else
+ {
+ // Remove interior and forward.
+ int rmIndex0 = index0 + 1;
+ int rmIndex1 = index1 - 1;
+ if (child0.getStartOffset() == rmOffs0
+ || (index0 == 0 && child0.getStartOffset() > rmOffs0
+ && child0.getEndOffset() <= rmOffs1))
+ {
+ // Start element completely consumed.
+ child0 = null;
+ rmIndex0 = index0;
+ }
+ if (! containsOffs1)
+ {
+ child1 = null;
+ rmIndex1++;
+ }
+ else if (child1.getStartOffset() == rmOffs1)
+ {
+ // End element not touched.
+ child1 = null;
+ }
+ if (rmIndex0 <= rmIndex1)
+ {
+ ec.index = rmIndex0;
+ }
+ for (int i = rmIndex0; i <= rmIndex1; i++)
+ {
+ ec.removed.add(elem.getElement(i));
+ }
+ if (child0 != null)
+ {
+ if(removeElements(child0, rmOffs0, rmOffs1))
+ {
+ ec.removed.add(0, child0);
+ ec.index = index0;
+ }
+ }
+ if (child1 != null)
+ {
+ if(removeElements(child1, rmOffs0, rmOffs1))
+ {
+ ec.removed.add(child1);
+ }
+ }
+ }
+ }
+
+ // Perform changes.
+ pop();
+
+ // Return true if we no longer have any children.
+ if(elem.getElementCount() == (ec.removed.size() - ec.added.size()))
+ ret = true;
}
- // Now we remove paragraphs from the root that have been tagged for
- // removal.
- if (removeStart != -1)
- {
- int removeLen = removeEnd - removeStart;
- Element[] removed = new Element[removeLen];
- for (int i = removeStart; i < removeEnd; i++)
- removed[i] = root.getElement(i);
- Edit edit = getEditForParagraphAndIndex((BranchElement) root,
- removeStart);
- edit.addRemovedElements(removed);
- }
+ return ret;
}
/**
- * Performs the actual work for {@link #change}. The elements at the
- * interval boundaries are split up (if necessary) so that the interval
- * boundaries are located at element boundaries.
+ * Creates a document in response to a call to
+ * {@link DefaultStyledDocument#create(ElementSpec[])}.
+ *
+ * @param len the length of the inserted text
+ * @param data the specs for the elements
+ * @param ev the document event
*/
- protected void changeUpdate()
+ void create(int len, ElementSpec[] data, DefaultDocumentEvent ev)
+ {
+ prepareEdit(offset, len);
+ Element el = root;
+ int index = el.getElementIndex(0);
+ while (! el.isLeaf())
+ {
+ Element child = el.getElement(index);
+ Edit edit = new Edit(el, index, false);
+ elementStack.push(edit);
+ el = child;
+ index = el.getElementIndex(0);
+ }
+ Edit ed = (Edit) elementStack.peek();
+ Element child = ed.e.getElement(ed.index);
+ ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(),
+ child.getEndOffset()));
+ ed.removed.add(child);
+ while (elementStack.size() > 1)
+ pop();
+ int n = data.length;
+
+ // Reset root element's attributes.
+ AttributeSet newAtts = null;
+ if (n > 0 && data[0].getType() == ElementSpec.StartTagType)
+ newAtts = data[0].getAttributes();
+ if (newAtts == null)
+ newAtts = SimpleAttributeSet.EMPTY;
+ MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes();
+ ev.addEdit(new AttributeUndoableEdit(root, newAtts, true));
+ mAtts.removeAttributes(mAtts);
+ mAtts.addAttributes(newAtts);
+
+ // Insert the specified elements.
+ for (int i = 1; i < n; i++)
+ insertElement(data[i]);
+
+ // Pop remaining stack.
+ while (elementStack.size() > 0)
+ pop();
+
+ finishEdit(ev);
+ }
+
+ private boolean canJoin(Element e0, Element e1)
+ {
+ boolean ret = false;
+ if ((e0 != null) && (e1 != null))
+ {
+ // Don't join a leaf to a branch.
+ boolean isLeaf0 = e0.isLeaf();
+ boolean isLeaf1 = e1.isLeaf();
+ if(isLeaf0 == isLeaf1)
+ {
+ if (isLeaf0)
+ {
+ // Only join leaves if the attributes match, otherwise
+ // style information will be lost.
+ ret = e0.getAttributes().isEqual(e1.getAttributes());
+ }
+ else
+ {
+ // Only join non-leafs if the names are equal. This may result
+ // in loss of style information, but this is typically
+ // acceptable for non-leafs.
+ String name0 = e0.getName();
+ String name1 = e1.getName();
+ if (name0 != null)
+ ret = name0.equals(name1);
+ else if (name1 != null)
+ ret = name1.equals(name0);
+ else // Both names null.
+ ret = true;
+ }
+ }
+ }
+ return ret;
+ }
+
+ private Element join(Element p, Element left, Element right, int rmOffs0,
+ int rmOffs1)
{
- // Split up the element at the start offset if necessary.
- Element el = getCharacterElement(offset);
- Element[] res = split(el, offset, 0, el.getElementIndex(offset));
- BranchElement par = (BranchElement) el.getParentElement();
- int index = par.getElementIndex(offset);
- Edit edit = getEditForParagraphAndIndex(par, index);
- if (res[1] != null)
+ Element joined = null;
+ if (left.isLeaf() && right.isLeaf())
+ {
+ joined = createLeafElement(p, left.getAttributes(),
+ left.getStartOffset(),
+ right.getEndOffset());
+ }
+ else if ((! left.isLeaf()) && (! right.isLeaf()))
{
- Element[] removed;
- Element[] added;
- if (res[0] == null)
+ // Join two branch elements. This copies the children before
+ // the removal range on the left element, and after the removal
+ // range on the right element. The two elements on the edge
+ // are joined if possible and needed.
+ joined = createBranchElement(p, left.getAttributes());
+ int ljIndex = left.getElementIndex(rmOffs0);
+ int rjIndex = right.getElementIndex(rmOffs1);
+ Element lj = left.getElement(ljIndex);
+ if (lj.getStartOffset() >= rmOffs0)
+ {
+ lj = null;
+ }
+ Element rj = right.getElement(rjIndex);
+ if (rj.getStartOffset() == rmOffs1)
{
- removed = new Element[0];
- added = new Element[] { res[1] };
- index++;
+ rj = null;
+ }
+ ArrayList children = new ArrayList();
+ // Transfer the left.
+ for (int i = 0; i < ljIndex; i++)
+ {
+ children.add(clone(joined, left.getElement(i)));
+ }
+
+ // Transfer the join/middle.
+ if (canJoin(lj, rj))
+ {
+ Element e = join(joined, lj, rj, rmOffs0, rmOffs1);
+ children.add(e);
}
else
{
- removed = new Element[] { el };
- added = new Element[] { res[0], res[1] };
+ if (lj != null)
+ {
+ children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1));
+ }
+ if (rj != null)
+ {
+ children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1));
+ }
+ }
+
+ // Transfer the right.
+ int n = right.getElementCount();
+ for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++)
+ {
+ children.add(clone(joined, right.getElement(i)));
}
- edit.addRemovedElements(removed);
- edit.addAddedElements(added);
+ // Install the children.
+ Element[] c = new Element[children.size()];
+ c = (Element[]) children.toArray(c);
+ ((BranchElement) joined).replace(0, 0, c);
+ }
+ else
+ {
+ assert false : "Must not happen";
}
+ return joined;
+ }
- int endOffset = offset + length;
- el = getCharacterElement(endOffset);
- res = split(el, endOffset, 0, el.getElementIndex(endOffset));
- par = (BranchElement) el.getParentElement();
- if (res[0] != null)
+ /**
+ * Performs the actual work for {@link #change}. The elements at the
+ * interval boundaries are split up (if necessary) so that the interval
+ * boundaries are located at element boundaries.
+ */
+ protected void changeUpdate()
+ {
+ boolean didEnd = split(offset, length);
+ if (! didEnd)
{
- Element[] removed;
- Element[] added;
- if (res[1] == null)
- {
- removed = new Element[0];
- added = new Element[] { res[1] };
- }
- else
+ // need to do the other end
+ while (elementStack.size() != 0)
{
- removed = new Element[] { el };
- added = new Element[] { res[0], res[1] };
+ pop();
}
- edit.addRemovedElements(removed);
- edit.addAddedElements(added);
+ split(offset + length, 0);
+ }
+ while (elementStack.size() != 0)
+ {
+ pop();
}
}
@@ -633,13 +882,9 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
public void change(int offset, int length, DefaultDocumentEvent ev)
{
- if (length == 0)
- return;
- this.offset = offset;
- this.pos = offset;
- this.length = length;
- documentEvent = ev;
+ prepareEdit(offset, length);
changeUpdate();
+ finishEdit(ev);
}
/**
@@ -683,6 +928,39 @@ public class DefaultStyledDocument extends AbstractDocument implements
return clone;
}
+ private Element cloneAsNecessary(Element parent, Element clonee,
+ int rmOffs0, int rmOffs1)
+ {
+ Element cloned;
+ if (clonee.isLeaf())
+ {
+ cloned = createLeafElement(parent, clonee.getAttributes(),
+ clonee.getStartOffset(),
+ clonee.getEndOffset());
+ }
+ else
+ {
+ Element e = createBranchElement(parent, clonee.getAttributes());
+ int n = clonee.getElementCount();
+ ArrayList childrenList = new ArrayList(n);
+ for (int i = 0; i < n; i++)
+ {
+ Element elem = clonee.getElement(i);
+ if (elem.getStartOffset() < rmOffs0
+ || elem.getEndOffset() > rmOffs1)
+ {
+ childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
+ rmOffs1));
+ }
+ }
+ Element[] children = new Element[childrenList.size()];
+ children = (Element[]) childrenList.toArray(children);
+ ((BranchElement) e).replace(0, 0, children);
+ cloned = e;
+ }
+ return cloned;
+ }
+
/**
* Inserts new <code>Element</code> in the document at the specified
* position. Most of the work is done by {@link #insertUpdate}, after some
@@ -701,70 +979,100 @@ public class DefaultStyledDocument extends AbstractDocument implements
public void insert(int offset, int length, ElementSpec[] data,
DefaultDocumentEvent ev)
{
- if (length == 0)
- return;
-
+ if (length > 0)
+ {
+ prepareEdit(offset, length);
+ insertUpdate(data);
+ finishEdit(ev);
+ }
+ }
+
+ /**
+ * Prepares the state of this object for performing an insert.
+ *
+ * @param offset the offset at which is inserted
+ * @param length the length of the inserted region
+ */
+ private void prepareEdit(int offset, int length)
+ {
this.offset = offset;
this.pos = offset;
this.endOffset = offset + length;
this.length = length;
- documentEvent = ev;
-
- edits.removeAllElements();
- elementStack.removeAllElements();
- lastFractured = null;
- fracNotCreated = false;
- insertUpdate(data);
+
+ if (edits == null)
+ edits = new ArrayList();
+ else
+ edits.clear();
+
+ if (elementStack == null)
+ elementStack = new Stack();
+ else
+ elementStack.clear();
+
+ fracturedParent = null;
+ fracturedChild = null;
+ offsetLastIndex = false;
+ offsetLastIndexReplace = false;
+ }
+
+ /**
+ * Finishes an insert. This applies all changes and updates
+ * the DocumentEvent.
+ *
+ * @param ev the document event
+ */
+ private void finishEdit(DefaultDocumentEvent ev)
+ {
// This for loop applies all the changes that were made and updates the
// DocumentEvent.
- int size = edits.size();
- for (int i = 0; i < size; i++)
- {
- Edit curr = (Edit) edits.get(i);
- BranchElement e = (BranchElement) curr.e;
- Element[] removed = curr.getRemovedElements();
- Element[] added = curr.getAddedElements();
- // FIXME: We probably shouldn't create the empty Element[] in the
- // first place.
- if (removed.length > 0 || added.length > 0)
- {
- if (curr.index + removed.length <= e.getElementCount())
- {
- e.replace(curr.index, removed.length, added);
- ElementEdit ee = new ElementEdit(e, curr.index, removed, added);
- ev.addEdit(ee);
- }
- else
- {
- System.err.println("WARNING: Tried to replace elements ");
- System.err.print("beyond boundaries: elementCount: ");
- System.err.println(e.getElementCount());
- System.err.print("index: " + curr.index);
- System.err.println(", removed.length: " + removed.length);
- }
- }
- }
+ for (Iterator i = edits.iterator(); i.hasNext();)
+ {
+ Edit edits = (Edit) i.next();
+ Element[] removed = new Element[edits.removed.size()];
+ removed = (Element[]) edits.removed.toArray(removed);
+ Element[] added = new Element[edits.added.size()];
+ added = (Element[]) edits.added.toArray(added);
+ int index = edits.index;
+ BranchElement parent = (BranchElement) edits.e;
+ parent.replace(index, removed.length, added);
+ ElementEdit ee = new ElementEdit(parent, index, removed, added);
+ ev.addEdit(ee);
+ }
+ edits.clear();
+ elementStack.clear();
}
/**
- * Inserts new content
+ * Inserts new content.
*
- * @param data
- * the element specifications for the elements to be inserted
+ * @param data the element specifications for the elements to be inserted
*/
protected void insertUpdate(ElementSpec[] data)
{
- // Push the root and the paragraph at offset onto the element stack.
+ // Push the current path to the stack.
Element current = root;
- int index;
- while (!current.isLeaf())
+ int index = current.getElementIndex(offset);
+ while (! current.isLeaf())
{
+ Element child = current.getElement(index);
+ int editIndex = child.isLeaf() ? index : index + 1;
+ Edit edit = new Edit(current, editIndex);
+ elementStack.push(edit);
+ current = child;
index = current.getElementIndex(offset);
- elementStack.push(current);
- current = current.getElement(index);
}
-
+
+ // Create a copy of the original path.
+ insertPath = new Edit[elementStack.size()];
+ insertPath = (Edit[]) elementStack.toArray(insertPath);
+
+ // No fracture yet.
+ createdFracture = false;
+
+ // Insert first content tag.
int i = 0;
+ recreateLeafs = false;
int type = data[0].getType();
if (type == ElementSpec.ContentType)
{
@@ -780,127 +1088,132 @@ public class DefaultStyledDocument extends AbstractDocument implements
createFracture(data);
i = 0;
}
-
+
// Handle each ElementSpec individually.
for (; i < data.length; i++)
{
- BranchElement paragraph = (BranchElement) elementStack.peek();
- switch (data[i].getType())
- {
- case ElementSpec.StartTagType:
- switch (data[i].getDirection())
- {
- case ElementSpec.JoinFractureDirection:
- // Fracture the tree and ensure the appropriate element
- // is on top of the stack.
- fracNotCreated = false;
- insertFracture(data[i]);
- if (fracNotCreated)
- {
- if (lastFractured != null)
- elementStack.push(lastFractured.getParentElement());
- else
- elementStack.push(paragraph.getElement(0));
- }
- break;
- case ElementSpec.JoinNextDirection:
- // Push the next paragraph element onto the stack so
- // future insertions are added to it.
- int ix = paragraph.getElementIndex(pos) + 1;
- elementStack.push(paragraph.getElement(ix));
- break;
- default:
- Element br = null;
- if (data.length > i + 1)
- {
- // leaves will be added to paragraph later
- int x = 0;
- if (paragraph.getElementCount() > 0)
- x = paragraph.getElementIndex(pos) + 1;
- Edit e = getEditForParagraphAndIndex(paragraph, x);
- br = (BranchElement) createBranchElement(paragraph,
- data[i].getAttributes());
- e.added.add(br);
- elementStack.push(br);
- }
- else
- // need to add leaves to paragraph now
- br = insertParagraph(paragraph, pos);
- break;
- }
- break;
- case ElementSpec.EndTagType:
- elementStack.pop();
- break;
- case ElementSpec.ContentType:
- insertContentTag(data[i]);
- offset = pos;
- break;
- }
+ insertElement(data[i]);
+ }
+
+ // Fracture if we haven't done yet.
+ if (! createdFracture)
+ fracture(-1);
+
+ // Pop the remaining stack.
+ while (elementStack.size() != 0)
+ pop();
+
+ // Offset last index if necessary.
+ if (offsetLastIndex && offsetLastIndexReplace)
+ insertPath[insertPath.length - 1].index++;
+
+ // Make sure we havea an Edit for each path item that has a change.
+ for (int p = insertPath.length - 1; p >= 0; p--)
+ {
+ Edit edit = insertPath[p];
+ if (edit.e == fracturedParent)
+ edit.added.add(fracturedChild);
+ if ((edit.added.size() > 0 || edit.removed.size() > 0)
+ && ! edits.contains(edit))
+ edits.add(edit);
+ }
+
+ // Remove element that would be created by an insert at 0 with
+ // an initial end tag.
+ if (offset == 0 && fracturedParent != null
+ && data[0].getType() == ElementSpec.EndTagType)
+ {
+ int p;
+ for (p = 0;
+ p < data.length && data[p].getType() == ElementSpec.EndTagType;
+ p++);
+ Edit edit = insertPath[insertPath.length - p - 1];
+ edit.index--;
+ edit.removed.add(0, edit.e.getElement(edit.index));
}
}
-
- /**
- * Inserts a new paragraph.
- *
- * @param par -
- * the parent
- * @param offset -
- * the offset
- * @return the new paragraph
- */
- private Element insertParagraph(BranchElement par, int offset)
+
+ private void pop()
+ {
+ Edit edit = (Edit) elementStack.peek();
+ elementStack.pop();
+ if ((edit.added.size() > 0) || (edit.removed.size() > 0))
+ {
+ edits.add(edit);
+ }
+ else if (! elementStack.isEmpty())
+ {
+ Element e = edit.e;
+ if (e.getElementCount() == 0)
+ {
+ // If we pushed a branch element that didn't get
+ // used, make sure its not marked as having been added.
+ edit = (Edit) elementStack.peek();
+ edit.added.remove(e);
+ }
+ }
+ }
+
+ private void insertElement(ElementSpec spec)
{
- int index = par.getElementIndex(offset);
- Element current = par.getElement(index);
- Element[] res = split(current, offset, 0, 0);
- Edit e = getEditForParagraphAndIndex(par, index + 1);
- Element ret;
- if (res[1] != null)
+ Edit edit = (Edit) elementStack.peek();
+ switch (spec.getType())
{
- Element[] removed;
- Element[] added;
- if (res[0] == null)
+ case ElementSpec.StartTagType:
+ switch (spec.getDirection())
{
- removed = new Element[0];
- if (res[1] instanceof BranchElement)
+ case ElementSpec.JoinFractureDirection:
+ // Fracture the tree and ensure the appropriate element
+ // is on top of the stack.
+ if (! createdFracture)
{
- added = new Element[] { res[1] };
- ret = res[1];
+ fracture(elementStack.size() - 1);
}
- else
+ if (! edit.isFracture)
{
- ret = createBranchElement(par, null);
- added = new Element[] { ret, res[1] };
+ // If the parent isn't a fracture, then the fracture is
+ // in fracturedChild.
+ Edit newEdit = new Edit(fracturedChild, 0, true);
+ elementStack.push(newEdit);
}
- index++;
- }
- else
- {
- removed = new Element[] { current };
- if (res[1] instanceof BranchElement)
+ else
{
- ret = res[1];
- added = new Element[] { res[0], res[1] };
+ // Otherwise use the parent's first child.
+ Element el = edit.e.getElement(0);
+ Edit newEdit = new Edit(el, 0, true);
+ elementStack.push(newEdit);
}
- else
+ break;
+ case ElementSpec.JoinNextDirection:
+ // Push the next paragraph element onto the stack so
+ // future insertions are added to it.
+ Element parent = edit.e.getElement(edit.index);
+ if (parent.isLeaf())
{
- ret = createBranchElement(par, null);
- added = new Element[] { res[0], ret, res[1] };
+ if (edit.index + 1 < edit.e.getElementCount())
+ parent = edit.e.getElement(edit.index + 1);
+ else
+ assert false; // Must not happen.
}
+ elementStack.push(new Edit(parent, 0, true));
+ break;
+ default:
+ Element branch = createBranchElement(edit.e,
+ spec.getAttributes());
+ edit.added.add(branch);
+ elementStack.push(new Edit(branch, 0));
+ break;
}
-
- e.addAddedElements(added);
- e.addRemovedElements(removed);
- }
- else
- {
- ret = createBranchElement(par, null);
- e.addAddedElement(ret);
+ break;
+ case ElementSpec.EndTagType:
+ pop();
+ break;
+ case ElementSpec.ContentType:
+ insertContentTag(spec, edit);
+ break;
}
- return ret;
}
-
+
/**
* Inserts the first tag into the document.
*
@@ -910,67 +1223,71 @@ public class DefaultStyledDocument extends AbstractDocument implements
private void insertFirstContentTag(ElementSpec[] data)
{
ElementSpec first = data[0];
- BranchElement paragraph = (BranchElement) elementStack.peek();
- int index = paragraph.getElementIndex(pos);
- Element current = paragraph.getElement(index);
- int newEndOffset = pos + first.length;
+ Edit edit = (Edit) elementStack.peek();
+ Element current = edit.e.getElement(edit.index);
+ int firstEndOffset = offset + first.length;
boolean onlyContent = data.length == 1;
- Edit edit = getEditForParagraphAndIndex(paragraph, index);
switch (first.getDirection())
{
case ElementSpec.JoinPreviousDirection:
- if (current.getEndOffset() != newEndOffset && !onlyContent)
+ if (current.getEndOffset() != firstEndOffset && ! onlyContent)
{
- Element newEl1 = createLeafElement(paragraph,
+ Element newEl1 = createLeafElement(edit.e,
current.getAttributes(),
current.getStartOffset(),
- newEndOffset);
- edit.addAddedElement(newEl1);
- edit.addRemovedElement(current);
- offset = newEndOffset;
+ firstEndOffset);
+ edit.added.add(newEl1);
+ edit.removed.add(current);
+ if (current.getEndOffset() != endOffset)
+ recreateLeafs = true;
+ else
+ offsetLastIndex = true;
+ }
+ else
+ {
+ offsetLastIndex = true;
+ offsetLastIndexReplace = true;
}
break;
case ElementSpec.JoinNextDirection:
- if (pos != 0)
+ if (offset != 0)
{
- Element newEl1 = createLeafElement(paragraph,
+ Element newEl1 = createLeafElement(edit.e,
current.getAttributes(),
current.getStartOffset(),
- pos);
- edit.addAddedElement(newEl1);
- Element next = paragraph.getElement(index + 1);
-
+ offset);
+ edit.added.add(newEl1);
+ Element next = edit.e.getElement(edit.index + 1);
if (onlyContent)
- newEl1 = createLeafElement(paragraph, next.getAttributes(),
- pos, next.getEndOffset());
+ newEl1 = createLeafElement(edit.e, next.getAttributes(),
+ offset, next.getEndOffset());
else
{
- newEl1 = createLeafElement(paragraph, next.getAttributes(),
- pos, newEndOffset);
- pos = newEndOffset;
+ newEl1 = createLeafElement(edit.e, next.getAttributes(),
+ offset, firstEndOffset);
}
- edit.addAddedElement(newEl1);
- edit.addRemovedElement(current);
- edit.addRemovedElement(next);
+ edit.added.add(newEl1);
+ edit.removed.add(current);
+ edit.removed.add(next);
}
break;
- default:
- if (current.getStartOffset() != pos)
+ default: // OriginateDirection.
+ if (current.getStartOffset() != offset)
{
- Element newEl = createLeafElement(paragraph,
+ Element newEl = createLeafElement(edit.e,
current.getAttributes(),
current.getStartOffset(),
- pos);
- edit.addAddedElement(newEl);
+ offset);
+ edit.added.add(newEl);
}
- edit.addRemovedElement(current);
- Element newEl1 = createLeafElement(paragraph, first.getAttributes(),
- pos, newEndOffset);
- edit.addAddedElement(newEl1);
+ edit.removed.add(current);
+ Element newEl1 = createLeafElement(edit.e, first.getAttributes(),
+ offset, firstEndOffset);
+ edit.added.add(newEl1);
if (current.getEndOffset() != endOffset)
- recreateLeaves(newEndOffset, paragraph, onlyContent);
+ recreateLeafs = true;
else
- offset = newEndOffset;
+ offsetLastIndex = true;
break;
}
}
@@ -981,630 +1298,356 @@ public class DefaultStyledDocument extends AbstractDocument implements
* @param tag -
* the element spec
*/
- private void insertContentTag(ElementSpec tag)
+ private void insertContentTag(ElementSpec tag, Edit edit)
{
- BranchElement paragraph = (BranchElement) elementStack.peek();
int len = tag.getLength();
int dir = tag.getDirection();
- AttributeSet tagAtts = tag.getAttributes();
-
if (dir == ElementSpec.JoinNextDirection)
{
- int index = paragraph.getElementIndex(pos);
- Element target = paragraph.getElement(index);
- Edit edit = getEditForParagraphAndIndex(paragraph, index);
-
- if (paragraph.getStartOffset() > pos)
- {
- Element first = paragraph.getElement(0);
- Element newEl = createLeafElement(paragraph,
- first.getAttributes(), pos,
- first.getEndOffset());
- edit.addAddedElement(newEl);
- edit.addRemovedElement(first);
- }
- else if (paragraph.getElementCount() > (index + 1)
- && (pos == target.getStartOffset() && !target.equals(lastFractured)))
+ if (! edit.isFracture)
{
- Element next = paragraph.getElement(index + 1);
- Element newEl = createLeafElement(paragraph,
- next.getAttributes(), pos,
- next.getEndOffset());
- edit.addAddedElement(newEl);
- edit.addRemovedElement(next);
- edit.addRemovedElement(target);
+ Element first = null;
+ if (insertPath != null)
+ {
+ for (int p = insertPath.length - 1; p >= 0; p--)
+ {
+ if (insertPath[p] == edit)
+ {
+ if (p != insertPath.length - 1)
+ first = edit.e.getElement(edit.index);
+ break;
+ }
+ }
+ }
+ if (first == null)
+ first = edit.e.getElement(edit.index + 1);
+ Element leaf = createLeafElement(edit.e, first.getAttributes(),
+ pos, first.getEndOffset());
+ edit.added.add(leaf);
+ edit.removed.add(first);
}
else
{
- BranchElement parent = (BranchElement) paragraph.getParentElement();
- int i = parent.getElementIndex(pos);
- BranchElement next = (BranchElement) parent.getElement(i + 1);
- AttributeSet atts = tag.getAttributes();
-
- if (next != null)
- {
- Element nextLeaf = next.getElement(0);
- Edit e = getEditForParagraphAndIndex(next, 0);
- Element newEl2 = createLeafElement(next, atts, pos, nextLeaf.getEndOffset());
- e.addAddedElement(newEl2);
- e.addRemovedElement(nextLeaf);
- }
+ Element first = edit.e.getElement(0);
+ Element leaf = createLeafElement(edit.e, first.getAttributes(),
+ pos, first.getEndOffset());
+ edit.added.add(leaf);
+ edit.removed.add(first);
}
}
else
{
- int end = pos + len;
- Element leaf = createLeafElement(paragraph, tag.getAttributes(), pos, end);
-
- // Check for overlap with other leaves/branches
- if (paragraph.getElementCount() > 0)
- {
- int index = paragraph.getElementIndex(pos);
- Element target = paragraph.getElement(index);
- boolean onlyContent = target.isLeaf();
-
- BranchElement toRec = paragraph;
- if (!onlyContent)
- toRec = (BranchElement) target;
-
- // Check if we should place the leaf before or after target
- if (pos > target.getStartOffset())
- index++;
-
- Edit edit = getEditForParagraphAndIndex(paragraph, index);
- edit.addAddedElement(leaf);
- }
- else
- paragraph.replace(0, 0, new Element[] { leaf });
+ Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos,
+ pos + len);
+ edit.added.add(leaf);
}
-
+
pos += len;
+
}
/**
- * This method fractures the child at offset.
+ * This method fractures bottomost leaf in the elementStack. This
+ * happens when the first inserted tag is not content.
*
* @param data
* the ElementSpecs used for the entire insertion
*/
private void createFracture(ElementSpec[] data)
{
- BranchElement paragraph = (BranchElement) elementStack.peek();
- int index = paragraph.getElementIndex(offset);
- Element child = paragraph.getElement(index);
- Edit edit = getEditForParagraphAndIndex(paragraph, index);
- AttributeSet atts = child.getAttributes();
-
+ Edit edit = (Edit) elementStack.peek();
+ Element child = edit.e.getElement(edit.index);
if (offset != 0)
{
- Element newEl1 = createLeafElement(paragraph, atts,
- child.getStartOffset(), offset);
- edit.addAddedElement(newEl1);
- edit.addRemovedElement(child);
- }
- }
-
- /**
- * Recreates a specified part of a the tree after a new leaf
- * has been inserted.
- *
- * @param start - where to start recreating from
- * @param paragraph - the paragraph to recreate
- * @param onlyContent - true if this is the only content
- */
- private void recreateLeaves(int start, BranchElement paragraph, boolean onlyContent)
- {
- int index = paragraph.getElementIndex(start);
- Element child = paragraph.getElement(index);
- AttributeSet atts = child.getAttributes();
-
- if (!onlyContent)
- {
- BranchElement newBranch = (BranchElement) createBranchElement(paragraph,
- atts);
- Element newLeaf = createLeafElement(newBranch, atts, start,
- child.getEndOffset());
- newBranch.replace(0, 0, new Element[] { newLeaf });
-
- BranchElement parent = (BranchElement) paragraph.getParentElement();
- int parSize = parent.getElementCount();
- Edit edit = getEditForParagraphAndIndex(parent, parSize);
- edit.addAddedElement(newBranch);
-
- int paragraphSize = paragraph.getElementCount();
- Element[] removed = new Element[paragraphSize - (index + 1)];
- int s = 0;
- for (int j = index + 1; j < paragraphSize; j++)
- removed[s++] = paragraph.getElement(j);
-
- edit = getEditForParagraphAndIndex(paragraph, index);
- edit.addRemovedElements(removed);
- Element[] added = recreateAfterFracture(removed, newBranch, 0, child.getEndOffset());
- newBranch.replace(1, 0, added);
-
- lastFractured = newLeaf;
- pos = newBranch.getEndOffset();
+ Element newChild = createLeafElement(edit.e, child.getAttributes(),
+ child.getStartOffset(), offset);
+ edit.added.add(newChild);
}
+ edit.removed.add(child);
+ if (child.getEndOffset() != endOffset)
+ recreateLeafs = true;
else
- {
- Element newLeaf = createLeafElement(paragraph, atts, start,
- child.getEndOffset());
- Edit edit = getEditForParagraphAndIndex(paragraph, index);
- edit.addAddedElement(newLeaf);
- }
+ offsetLastIndex = true;
}
-
- /**
- * Splits an element if <code>offset</code> is not already at its
- * boundary.
- *
- * @param el
- * the Element to possibly split
- * @param offset
- * the offset at which to possibly split
- * @param space
- * the amount of space to create between the splitted parts
- * @param editIndex
- * the index of the edit to use
- * @return An array of elements which represent the split result. This array
- * has two elements, the two parts of the split. The first element
- * might be null, which means that the element which should be
- * splitted can remain in place. The second element might also be
- * null, which means that the offset is already at an element
- * boundary and the element doesn't need to be splitted.
- */
- private Element[] split(Element el, int offset, int space, int editIndex)
+
+ private void fracture(int depth)
{
- // If we are at an element boundary, then return an empty array.
- if ((offset == el.getStartOffset() || offset == el.getEndOffset())
- && space == 0 && el.isLeaf())
- return new Element[2];
-
- // If the element is an instance of BranchElement, then we
- // recursivly
- // call this method to perform the split.
- Element[] res = new Element[2];
- if (el instanceof BranchElement)
+ int len = insertPath.length;
+ int lastIndex = -1;
+ boolean recreate = recreateLeafs;
+ Edit lastEdit = insertPath[len - 1];
+ boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount();
+ int deepestChangedIndex = recreate ? len : - 1;
+ int lastChangedIndex = len - 1;
+ createdFracture = true;
+ for (int i = len - 2; i >= 0; i--)
{
- int index = el.getElementIndex(offset);
- Element child = el.getElement(index);
- Element[] result = split(child, offset, space, editIndex);
- Element[] removed;
- Element[] added;
- Element[] newAdded;
-
- int count = el.getElementCount();
- if (result[1] != null)
+ Edit edit = insertPath[i];
+ if (edit.added.size() > 0 || i == depth)
{
- // This is the case when we can keep the first element.
- if (result[0] == null)
- {
- removed = new Element[count - index - 1];
- newAdded = new Element[count - index - 1];
- added = new Element[] {};
-
- }
- // This is the case when we may not keep the first
- // element.
- else
- {
- removed = new Element[count - index];
- newAdded = new Element[count - index];
- added = new Element[] { result[0] };
- }
- newAdded[0] = result[1];
- for (int i = index; i < count; i++)
+ lastIndex = i;
+ if (! recreate && childChanged)
{
- Element el2 = el.getElement(i);
- int ind = i - count + removed.length;
- removed[ind] = el2;
- if (ind != 0)
- newAdded[ind] = el2;
+ recreate = true;
+ if (deepestChangedIndex == -1)
+ deepestChangedIndex = lastChangedIndex + 1;
}
-
- Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex);
- edit.addRemovedElements(removed);
- edit.addAddedElements(added);
-
- BranchElement newPar =
- (BranchElement) createBranchElement(el.getParentElement(),
- el.getAttributes());
- newPar.replace(0, 0, newAdded);
- res = new Element[] { null, newPar };
}
- else
+ if (! childChanged && edit.index < edit.e.getElementCount())
{
- removed = new Element[count - index];
- for (int i = index; i < count; ++i)
- removed[i - index] = el.getElement(i);
-
- Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex);
- edit.addRemovedElements(removed);
-
- BranchElement newPar = (BranchElement) createBranchElement(el.getParentElement(),
- el.getAttributes());
- newPar.replace(0, 0, removed);
- res = new Element[] { null, newPar };
+ childChanged = true;
+ lastChangedIndex = i;
}
}
- else if (el instanceof LeafElement)
+ if (recreate)
{
- BranchElement par = (BranchElement) el.getParentElement();
- Element el1 = createLeafElement(par, el.getAttributes(),
- el.getStartOffset(), offset);
-
- Element el2 = createLeafElement(par, el.getAttributes(),
- offset + space,
- el.getEndOffset());
- res = new Element[] { el1, el2 };
+ if (lastIndex == -1)
+ lastIndex = len - 1;
+ recreate(lastIndex, deepestChangedIndex);
}
- return res;
}
- /**
- * Inserts a fracture into the document structure.
- *
- * @param tag -
- * the element spec.
- */
- private void insertFracture(ElementSpec tag)
+ private void recreate(int startIndex, int endIndex)
{
- // insert the fracture at offset.
- BranchElement parent = (BranchElement) elementStack.peek();
- int parentIndex = parent.getElementIndex(pos);
- AttributeSet parentAtts = parent.getAttributes();
- Element toFracture = parent.getElement(parentIndex);
- int parSize = parent.getElementCount();
- Edit edit = getEditForParagraphAndIndex(parent, parentIndex);
- Element frac = toFracture;
- int leftIns = 0;
- int indexOfFrac = toFracture.getElementIndex(pos);
- int size = toFracture.getElementCount();
-
- // gets the leaf that falls along the fracture
- frac = toFracture.getElement(indexOfFrac);
- while (!frac.isLeaf())
- frac = frac.getElement(frac.getElementIndex(pos));
-
- AttributeSet atts = frac.getAttributes();
- int fracStart = frac.getStartOffset();
- int fracEnd = frac.getEndOffset();
- if (pos >= fracStart && pos < fracEnd)
+ // Recreate the element representing the inserted index.
+ Edit edit = insertPath[startIndex];
+ Element child;
+ Element newChild;
+ int changeLength = insertPath.length;
+
+ if (startIndex + 1 == changeLength)
+ child = edit.e.getElement(edit.index);
+ else
+ child = edit.e.getElement(edit.index - 1);
+
+ if(child.isLeaf())
+ {
+ newChild = createLeafElement(edit.e, child.getAttributes(),
+ Math.max(endOffset, child.getStartOffset()),
+ child.getEndOffset());
+ }
+ else
{
- // recreate left-side of branch and all its children before offset
- // add the fractured leaves to the right branch
- BranchElement rightBranch =
- (BranchElement) createBranchElement(parent, parentAtts);
-
- // Check if left branch has already been edited. If so, we only
- // need to create the right branch.
- BranchElement leftBranch = null;
- Element[] added = null;
- if (edit.added.size() > 0 || edit.removed.size() > 0)
+ newChild = createBranchElement(edit.e, child.getAttributes());
+ }
+ fracturedParent = edit.e;
+ fracturedChild = newChild;
+
+ // Recreate all the elements to the right of the insertion point.
+ Element parent = newChild;
+ while (++startIndex < endIndex)
+ {
+ boolean isEnd = (startIndex + 1) == endIndex;
+ boolean isEndLeaf = (startIndex + 1) == changeLength;
+
+ // Create the newChild, a duplicate of the elment at
+ // index. This isn't done if isEnd and offsetLastIndex are true
+ // indicating a join previous was done.
+ edit = insertPath[startIndex];
+
+ // Determine the child to duplicate, won't have to duplicate
+ // if at end of fracture, or offseting index.
+ if(isEnd)
{
- added = new Element[] { rightBranch };
-
- // don't try to remove left part of tree
- parentIndex++;
+ if(offsetLastIndex || ! isEndLeaf)
+ child = null;
+ else
+ child = edit.e.getElement(edit.index);
}
else
{
- leftBranch =
- (BranchElement) createBranchElement(parent, parentAtts);
- added = new Element[] { leftBranch, rightBranch };
-
- // add fracture to leftBranch
- if (fracStart != pos)
- {
- Element leftFracturedLeaf =
- createLeafElement(leftBranch, atts, fracStart, pos);
- leftBranch.replace(leftIns, 0,
- new Element[] { leftFracturedLeaf });
- }
+ child = edit.e.getElement(edit.index - 1);
}
- if (!toFracture.isLeaf())
+ // Duplicate it.
+ if(child != null)
{
- // add all non-fracture elements to the branches
- if (indexOfFrac > 0 && leftBranch != null)
+ if(child.isLeaf())
{
- Element[] add = new Element[indexOfFrac];
- for (int i = 0; i < indexOfFrac; i++)
- add[i] = toFracture.getElement(i);
- leftIns = add.length;
- leftBranch.replace(0, 0, add);
+ newChild = createLeafElement(parent, child.getAttributes(),
+ Math.max(endOffset, child.getStartOffset()),
+ child.getEndOffset());
}
-
- int count = size - indexOfFrac - 1;
- if (count > 0)
+ else
{
- Element[] add = new Element[count];
- int j = 0;
- int i = indexOfFrac + 1;
- while (j < count)
- add[j++] = toFracture.getElement(i++);
- rightBranch.replace(0, 0, add);
+ newChild = createBranchElement(parent,
+ child.getAttributes());
}
}
-
- // add to fracture to rightBranch
- // Check if we can join the right frac leaf with the next leaf
- int rm = 0;
- int end = fracEnd;
- Element next = rightBranch.getElement(0);
- if (next != null && next.isLeaf()
- && next.getAttributes().isEqual(atts))
- {
- end = next.getEndOffset();
- rm = 1;
- }
+ else
+ newChild = null;
- Element rightFracturedLeaf = createLeafElement(rightBranch, atts,
- pos, end);
- rightBranch.replace(0, rm, new Element[] { rightFracturedLeaf });
+ // Recreate the remaining children (there may be none).
+ int childrenToMove = edit.e.getElementCount() - edit.index;
+ Element[] children;
+ int moveStartIndex;
+ int childStartIndex = 1;
- // recreate those elements after parentIndex and add/remove all
- // new/old elements to parent
- int remove = parSize - parentIndex;
- Element[] removed = new Element[0];
- Element[] added2 = new Element[0];
- if (remove > 0)
- {
- removed = new Element[remove];
- int s = 0;
- for (int j = parentIndex; j < parSize; j++)
- removed[s++] = parent.getElement(j);
- edit.addRemovedElements(removed);
- added2 = recreateAfterFracture(removed, parent, 1,
- rightBranch.getEndOffset());
+ if (newChild == null)
+ {
+ // Last part of fracture.
+ if (isEndLeaf)
+ {
+ childrenToMove--;
+ moveStartIndex = edit.index + 1;
+ }
+ else
+ {
+ moveStartIndex = edit.index;
+ }
+ childStartIndex = 0;
+ children = new Element[childrenToMove];
+ }
+ else
+ {
+ if (! isEnd)
+ {
+ // Branch.
+ childrenToMove++;
+ moveStartIndex = edit.index;
}
-
- edit.addAddedElements(added);
- edit.addAddedElements(added2);
- elementStack.push(rightBranch);
- lastFractured = rightFracturedLeaf;
+ else
+ {
+ // Last leaf, need to recreate part of it.
+ moveStartIndex = edit.index + 1;
+ }
+ children = new Element[childrenToMove];
+ children[0] = newChild;
}
- else
- fracNotCreated = true;
+
+ for (int c = childStartIndex; c < childrenToMove; c++)
+ {
+ Element toMove = edit.e.getElement(moveStartIndex++);
+ children[c] = recreateFracturedElement(parent, toMove);
+ edit.removed.add(toMove);
+ }
+ ((BranchElement) parent).replace(0, 0, children);
+ parent = newChild;
+ }
+
}
- /**
- * Recreates all the elements from the parent to the element on the top of
- * the stack, starting from startFrom with the starting offset of
- * startOffset.
- *
- * @param recreate -
- * the elements to recreate
- * @param parent -
- * the element to add the new elements to
- * @param startFrom -
- * where to start recreating from
- * @param startOffset -
- * the offset of the first element
- * @return the array of added elements
- */
- private Element[] recreateAfterFracture(Element[] recreate,
- BranchElement parent, int startFrom,
- int startOffset)
+ private Element recreateFracturedElement(Element parent, Element toCopy)
{
- Element[] added = new Element[recreate.length - startFrom];
- int j = 0;
- for (int i = startFrom; i < recreate.length; i++)
+ Element recreated;
+ if(toCopy.isLeaf())
{
- Element curr = recreate[i];
- int len = curr.getEndOffset() - curr.getStartOffset();
- if (curr instanceof LeafElement)
- added[j] = createLeafElement(parent, curr.getAttributes(),
- startOffset, startOffset + len);
- else
+ recreated = createLeafElement(parent, toCopy.getAttributes(),
+ Math.max(toCopy.getStartOffset(), endOffset),
+ toCopy.getEndOffset());
+ }
+ else
+ {
+ Element newParent = createBranchElement(parent,
+ toCopy.getAttributes());
+ int childCount = toCopy.getElementCount();
+ Element[] newChildren = new Element[childCount];
+ for (int i = 0; i < childCount; i++)
{
- BranchElement br =
- (BranchElement) createBranchElement(parent,
- curr.getAttributes());
- int bSize = curr.getElementCount();
- for (int k = 0; k < bSize; k++)
- {
- Element bCurr = curr.getElement(k);
- Element[] add = recreateAfterFracture(new Element[] { bCurr }, br, 0,
- startOffset);
- br.replace(0, 0, add);
-
- }
- added[j] = br;
+ newChildren[i] = recreateFracturedElement(newParent,
+ toCopy.getElement(i));
}
- startOffset += len;
- j++;
+ ((BranchElement) newParent).replace(0, 0, newChildren);
+ recreated = newParent;
}
-
- return added;
- }
- }
-
- /**
- * This method looks through the Vector of Edits to see if there is already an
- * Edit object associated with the given paragraph. If there is, then we
- * return it. Otherwise we create a new Edit object, add it to the vector, and
- * return it. Note: this method is package private to avoid accessors.
- *
- * @param index
- * the index associated with the Edit we want to create
- * @param para
- * the paragraph associated with the Edit we want
- * @return the found or created Edit object
- */
- Edit getEditForParagraphAndIndex(BranchElement para, int index)
- {
- Edit curr;
- int size = edits.size();
- for (int i = 0; i < size; i++)
- {
- curr = (Edit) edits.elementAt(i);
- if (curr.e.equals(para))
- return curr;
- }
- curr = new Edit(para, index, null, null);
- edits.add(curr);
-
- return curr;
- }
- /**
- * Instance of all editing information for an object in the Vector. This class
- * is used to add information to the DocumentEvent associated with an
- * insertion/removal/change as well as to store the changes that need to be
- * made so they can be made all at the same (appropriate) time.
- */
- class Edit
- {
- /** The element to edit . */
- Element e;
-
- /** The index of the change. */
- int index;
-
- /** The removed elements. */
- Vector removed = new Vector();
-
- /** The added elements. */
- Vector added = new Vector();
-
- /**
- * Return an array containing the Elements that have been removed from the
- * paragraph associated with this Edit.
- *
- * @return an array of removed Elements
- */
- public Element[] getRemovedElements()
- {
- int size = removed.size();
- Element[] removedElements = new Element[size];
- for (int i = 0; i < size; i++)
- removedElements[i] = (Element) removed.elementAt(i);
- return removedElements;
+ return recreated;
}
- /**
- * Return an array containing the Elements that have been added to the
- * paragraph associated with this Edit.
- *
- * @return an array of added Elements
- */
- public Element[] getAddedElements()
+ private boolean split(int offs, int len)
{
- int size = added.size();
- Element[] addedElements = new Element[size];
- for (int i = 0; i < size; i++)
- addedElements[i] = (Element) added.elementAt(i);
- return addedElements;
- }
-
- /**
- * Checks if e is already in the vector.
- *
- * @param e - the Element to look for
- * @param v - the vector to search
- * @return true if e is in v.
- */
- private boolean contains(Vector v, Element e)
- {
- if (e == null)
- return false;
-
- int i = v.size();
- for (int j = 0; j < i; j++)
+ boolean splitEnd = false;
+ // Push the path to the stack.
+ Element e = root;
+ int index = e.getElementIndex(offs);
+ while (! e.isLeaf())
{
- Element e1 = (Element) v.get(j);
- if ((e1 != null) && (e1.getAttributes().isEqual(e.getAttributes()))
- && (e1.getName().equals(e.getName()))
- && (e1.getStartOffset() == e.getStartOffset())
- && (e1.getEndOffset() == e.getEndOffset())
- && (e1.getParentElement().equals(e.getParentElement()))
- && (e1.getElementCount() == e.getElementCount()))
- return true;
+ elementStack.push(new Edit(e, index));
+ e = e.getElement(index);
+ index = e.getElementIndex(offs);
}
- return false;
- }
- /**
- * Adds one Element to the vector of removed Elements.
- *
- * @param e
- * the Element to add
- */
- public void addRemovedElement(Element e)
- {
- if (!contains(removed, e))
- removed.add(e);
- }
-
- /**
- * Adds each Element in the given array to the vector of removed Elements
- *
- * @param e
- * the array containing the Elements to be added
- */
- public void addRemovedElements(Element[] e)
- {
- if (e == null || e.length == 0)
- return;
- for (int i = 0; i < e.length; i++)
+ Edit ec = (Edit) elementStack.peek();
+ Element child = ec.e.getElement(ec.index);
+ // Make sure there is something to do. If the
+ // offset is already at a boundary then there is
+ // nothing to do.
+ if (child.getStartOffset() < offs && offs < child.getEndOffset())
{
- if (!contains(removed, e[i]))
- removed.add(e[i]);
- }
- }
+ // We need to split, now see if the other end is within
+ // the same parent.
+ int index0 = ec.index;
+ int index1 = index0;
+ if (((offs + len) < ec.e.getEndOffset()) && (len != 0))
+ {
+ // It's a range split in the same parent.
+ index1 = ec.e.getElementIndex(offs+len);
+ if (index1 == index0)
+ {
+ // It's a three-way split.
+ ec.removed.add(child);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ child.getStartOffset(), offs);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ offs, offs + len);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ offs + len, child.getEndOffset());
+ ec.added.add(e);
+ return true;
+ }
+ else
+ {
+ child = ec.e.getElement(index1);
+ if ((offs + len) == child.getStartOffset())
+ {
+ // End is already on a boundary.
+ index1 = index0;
+ }
+ }
+ splitEnd = true;
+ }
- /**
- * Adds one Element to the vector of added Elements.
- *
- * @param e
- * the Element to add
- */
- public void addAddedElement(Element e)
- {
- if (!contains(added, e))
- added.add(e);
- }
+ // Split the first location.
+ pos = offs;
+ child = ec.e.getElement(index0);
+ ec.removed.add(child);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ child.getStartOffset(), pos);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ pos, child.getEndOffset());
+ ec.added.add(e);
+
+ // Pick up things in the middle.
+ for (int i = index0 + 1; i < index1; i++)
+ {
+ child = ec.e.getElement(i);
+ ec.removed.add(child);
+ ec.added.add(child);
+ }
- /**
- * Adds each Element in the given array to the vector of added Elements.
- *
- * @param e
- * the array containing the Elements to be added
- */
- public void addAddedElements(Element[] e)
- {
- if (e == null || e.length == 0)
- return;
- for (int i = 0; i < e.length; i++)
- {
- if (!contains(added, e[i]))
- added.add(e[i]);
+ if (index1 != index0)
+ {
+ child = ec.e.getElement(index1);
+ pos = offs + len;
+ ec.removed.add(child);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ child.getStartOffset(), pos);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ pos, child.getEndOffset());
+
+ ec.added.add(e);
+ }
}
+ return splitEnd;
+
}
- /**
- * Creates a new Edit object with the given parameters
- *
- * @param e
- * the paragraph Element associated with this Edit
- * @param i
- * the index within the paragraph where changes are started
- * @param removed
- * an array containing Elements that should be removed from the
- * paragraph Element
- * @param added
- * an array containing Elements that should be added to the
- * paragraph Element
- */
- public Edit(Element e, int i, Element[] removed, Element[] added)
- {
- this.e = e;
- this.index = i;
- addRemovedElements(removed);
- addAddedElements(added);
- }
}
+
/**
* An element type for sections. This is a simple BranchElement with a unique
* name.
@@ -1674,11 +1717,6 @@ public class DefaultStyledDocument extends AbstractDocument implements
private StyleChangeListener styleChangeListener;
/**
- * Vector that contains all the edits. Maybe replace by a HashMap.
- */
- Vector edits = new Vector();
-
- /**
* Creates a new <code>DefaultStyledDocument</code>.
*/
public DefaultStyledDocument()
@@ -1939,7 +1977,6 @@ public class DefaultStyledDocument extends AbstractDocument implements
// start and ends at an element end.
buffer.change(offset, length, ev);
- Element root = getDefaultRootElement();
// Visit all paragraph elements within the specified interval
int end = offset + length;
Element curr;
@@ -2079,147 +2116,220 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
{
- super.insertUpdate(ev, attr);
- // If the attribute set is null, use an empty attribute set.
+ int offs = ev.getOffset();
+ int len = ev.getLength();
+ int endOffs = offs + len;
if (attr == null)
attr = SimpleAttributeSet.EMPTY;
- int offset = ev.getOffset();
- int length = ev.getLength();
- int endOffset = offset + length;
- AttributeSet paragraphAttributes = getParagraphElement(endOffset).getAttributes();
- Segment txt = new Segment();
+
+ // Paragraph attributes are fetched from the point _after_ the insertion.
+ Element paragraph = getParagraphElement(endOffs);
+ AttributeSet pAttr = paragraph.getAttributes();
+ // Character attributes are fetched from the actual insertion point.
+ Element paragraph2 = getParagraphElement(offs);
+ int contIndex = paragraph2.getElementIndex(offs);
+ Element content = paragraph2.getElement(contIndex);
+ AttributeSet cAttr = content.getAttributes();
+
+ boolean insertAtBoundary = content.getEndOffset() == endOffs;
try
{
- getText(offset, length, txt);
- }
- catch (BadLocationException ex)
- {
- AssertionError ae = new AssertionError("Unexpected bad location");
- ae.initCause(ex);
- throw ae;
- }
+ Segment s = new Segment();
+ ArrayList buf = new ArrayList();
+ ElementSpec lastStartTag = null;
+ boolean insertAfterNewline = false;
+ short lastStartDir = ElementSpec.OriginateDirection;
+
+ // Special handle if we are inserting after a newline.
+ if (offs > 0)
+ {
+ getText(offs - 1, 1, s);
+ if (s.array[s.offset] == '\n')
+ {
+ insertAfterNewline = true;
+ lastStartDir = insertAfterNewline(paragraph, paragraph2,
+ pAttr, buf, offs,
+ endOffs);
+ // Search last start tag.
+ for (int i = buf.size() - 1; i >= 0 && lastStartTag == null;
+ i--)
+ {
+ ElementSpec tag = (ElementSpec) buf.get(i);
+ if (tag.getType() == ElementSpec.StartTagType)
+ {
+ lastStartTag = tag;
+ }
+ }
+ }
- int len = 0;
- Vector specs = new Vector();
- ElementSpec finalStartTag = null;
- short finalStartDirection = ElementSpec.OriginateDirection;
- boolean prevCharWasNewline = false;
- Element prev = getCharacterElement(offset);
- Element next = getCharacterElement(endOffset);
- Element prevParagraph = getParagraphElement(offset);
- Element paragraph = getParagraphElement(endOffset);
+ }
- int segmentEnd = txt.offset + txt.count;
+ // If we are not inserting after a newline, the paragraph attributes
+ // come from the paragraph under the insertion point.
+ if (! insertAfterNewline)
+ pAttr = paragraph2.getAttributes();
- // Check to see if we're inserting immediately after a newline.
- if (offset > 0)
- {
- try
+ // Scan text and build up the specs.
+ getText(offs, len, s);
+ int end = s.offset + s.count;
+ int last = s.offset;
+ for (int i = s.offset; i < end; i++)
{
- String s = getText(offset - 1, 1);
- if (s.equals("\n"))
+ if (s.array[i] == '\n')
{
- finalStartDirection = handleInsertAfterNewline(specs, offset,
- endOffset,
- prevParagraph,
- paragraph,
- paragraphAttributes);
-
- prevCharWasNewline = true;
- // Find the final start tag from the ones just created.
- for (int i = 0; i < specs.size(); i++)
- if (((ElementSpec) specs.get(i)).getType() == ElementSpec.StartTagType)
- finalStartTag = (ElementSpec) specs.get(i);
+ int breakOffs = i + 1;
+ buf.add(new ElementSpec(attr, ElementSpec.ContentType,
+ breakOffs - last));
+ buf.add(new ElementSpec(null, ElementSpec.EndTagType));
+ lastStartTag = new ElementSpec(pAttr,
+ ElementSpec.StartTagType);
+ buf.add(lastStartTag);
+ last = breakOffs;
}
}
- catch (BadLocationException ble)
+
+ // Need to add a tailing content tag if we didn't finish at a boundary.
+ if (last < end)
{
- // This shouldn't happen.
- AssertionError ae = new AssertionError();
- ae.initCause(ble);
- throw ae;
+ buf.add(new ElementSpec(attr, ElementSpec.ContentType,
+ end - last));
}
- }
- for (int i = txt.offset; i < segmentEnd; ++i)
- {
- len++;
- if (txt.array[i] == '\n')
+ // Now we need to fix up the directions of the specs.
+ ElementSpec first = (ElementSpec) buf.get(0);
+ int doclen = getLength();
+
+ // Maybe join-previous the first tag if it is content and has
+ // the same attributes as the previous character run.
+ if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr))
+ first.setDirection(ElementSpec.JoinPreviousDirection);
+
+ // Join-fracture or join-next the last start tag if necessary.
+ if (lastStartTag != null)
+ {
+ if (insertAfterNewline)
+ lastStartTag.setDirection(lastStartDir);
+ else if (paragraph2.getEndOffset() != endOffs)
+ lastStartTag.setDirection(ElementSpec.JoinFractureDirection);
+ else
+ {
+ Element par = paragraph2.getParentElement();
+ int par2Index = par.getElementIndex(offs);
+ if (par2Index + 1 < par.getElementCount()
+ && ! par.getElement(par2Index + 1).isLeaf())
+ lastStartTag.setDirection(ElementSpec.JoinNextDirection);
+ }
+ }
+
+ // Join-next last tag if possible.
+ if (insertAtBoundary && endOffs < doclen)
{
- // Add the ElementSpec for the content.
- specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
-
- // Add ElementSpecs for the newline.
- specs.add(new ElementSpec(null, ElementSpec.EndTagType));
- finalStartTag = new ElementSpec(paragraphAttributes,
- ElementSpec.StartTagType);
- specs.add(finalStartTag);
- len = 0;
+ ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
+ if (lastTag.getType() == ElementSpec.ContentType
+ && ((lastStartTag == null
+ && (paragraph == paragraph2 || insertAfterNewline))
+ || (lastStartTag != null
+ && lastStartTag.getDirection() != ElementSpec.OriginateDirection)))
+ {
+ int nextIndex = paragraph.getElementIndex(endOffs);
+ Element nextRun = paragraph.getElement(nextIndex);
+ if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes()))
+ lastTag.setDirection(ElementSpec.JoinNextDirection);
+ }
+ }
+
+ else if (! insertAtBoundary && lastStartTag != null
+ && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection)
+ {
+ ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
+ if (lastTag.getType() == ElementSpec.ContentType
+ && lastTag.getDirection() != ElementSpec.JoinPreviousDirection
+ && attr.isEqual(cAttr))
+ {
+ lastTag.setDirection(ElementSpec.JoinNextDirection);
+ }
}
- }
- // Create last element if last character hasn't been a newline.
- if (len > 0)
- specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
+ ElementSpec[] specs = new ElementSpec[buf.size()];
+ specs = (ElementSpec[]) buf.toArray(specs);
+ buffer.insert(offs, len, specs, ev);
+ }
+ catch (BadLocationException ex)
+ {
+ // Ignore this. Comment out for debugging.
+ ex.printStackTrace();
+ }
+ super.insertUpdate(ev, attr);
+ }
- // Set the direction of the last spec of type StartTagType.
- // If we are inserting after a newline then this value comes from
- // handleInsertAfterNewline.
- if (finalStartTag != null)
+ private short insertAfterNewline(Element par1, Element par2,
+ AttributeSet attr, ArrayList buf,
+ int offs, int endOffs)
+ {
+ short dir = 0;
+ if (par1.getParentElement() == par2.getParentElement())
{
- if (prevCharWasNewline)
- finalStartTag.setDirection(finalStartDirection);
- else if (prevParagraph.getEndOffset() != endOffset)
- finalStartTag.setDirection(ElementSpec.JoinFractureDirection);
+ ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType);
+ buf.add(tag);
+ tag = new ElementSpec(attr, ElementSpec.StartTagType);
+ buf.add(tag);
+ if (par2.getEndOffset() != endOffs)
+ dir = ElementSpec.JoinFractureDirection;
else
{
- // If there is an element AFTER this one, then set the
- // direction to JoinNextDirection.
- Element parent = prevParagraph.getParentElement();
- int index = parent.getElementIndex(offset);
- if (index + 1 < parent.getElementCount()
- && !parent.getElement(index + 1).isLeaf())
- finalStartTag.setDirection(ElementSpec.JoinNextDirection);
+ Element par = par2.getParentElement();
+ if (par.getElementIndex(offs) + 1 < par.getElementCount())
+ dir = ElementSpec.JoinNextDirection;
}
}
-
- // If we are at the last index, then check if we could probably be
- // joined with the next element.
- // This means:
- // - we must be a ContentTag
- // - if there is a next Element, we must have the same attributes
- // - if there is no next Element, but one will be created,
- // we must have the same attributes as the higher-level run.
- ElementSpec last = (ElementSpec) specs.lastElement();
- if (last.getType() == ElementSpec.ContentType)
+ else
{
- Element currentRun = prevParagraph.getElement(prevParagraph.getElementIndex(offset));
- if (currentRun.getEndOffset() == endOffset)
+ // For text with more than 2 levels, find the common parent of
+ // par1 and par2.
+ ArrayList parentsLeft = new ArrayList();
+ ArrayList parentsRight = new ArrayList();
+ Element e = par2;
+ while (e != null)
{
- if (endOffset < getLength() && next.getAttributes().isEqual(attr)
- && last.getType() == ElementSpec.ContentType)
- last.setDirection(ElementSpec.JoinNextDirection);
+ parentsLeft.add(e);
+ e = e.getParentElement();
}
- else
+ e = par1;
+ int leftIndex = -1;
+ while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1)
{
- if (finalStartTag != null
- && finalStartTag.getDirection() == ElementSpec.JoinFractureDirection
- && currentRun.getAttributes().isEqual(attr))
+ parentsRight.add(e);
+ e = e.getParentElement();
+ }
+
+ if (e != null)
+
+ {
+ // e is now the common parent.
+ // Insert the end tags.
+ for (int c = 0; c < leftIndex; c++)
+ {
+ buf.add(new ElementSpec(null, ElementSpec.EndTagType));
+ }
+ // Insert the start tags.
+ for (int c = parentsRight.size() - 1; c >= 0; c--)
{
- last.setDirection(ElementSpec.JoinNextDirection);
+ Element el = (Element) parentsRight.get(c);
+ ElementSpec tag = new ElementSpec(el.getAttributes(),
+ ElementSpec.StartTagType);
+ if (c > 0)
+ tag.setDirection(ElementSpec.JoinNextDirection);
+ buf.add(tag);
}
+ if (parentsRight.size() > 0)
+ dir = ElementSpec.JoinNextDirection;
+ else
+ dir = ElementSpec.JoinFractureDirection;
}
+ else
+ assert false;
}
-
- // If we are at the first new element, then check if it could be
- // joined with the previous element.
- ElementSpec first = (ElementSpec) specs.firstElement();
- if (prev.getAttributes().isEqual(attr)
- && first.getType() == ElementSpec.ContentType)
- first.setDirection(ElementSpec.JoinPreviousDirection);
-
- ElementSpec[] elSpecs = (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]);
- buffer.insert(offset, length, elSpecs, ev);
+ return dir;
}
/**
@@ -2267,7 +2377,7 @@ public class DefaultStyledDocument extends AbstractDocument implements
*
* @return an enumeration of all style names
*/
- public Enumeration getStyleNames()
+ public Enumeration<?> getStyleNames()
{
StyleContext context = (StyleContext) getAttributeContext();
return context.getStyleNames();
@@ -2322,18 +2432,24 @@ public class DefaultStyledDocument extends AbstractDocument implements
if (length == 0)
return;
- UndoableEdit edit = content.insertString(offset,
- contentBuffer.toString());
+ Content c = getContent();
+ UndoableEdit edit = c.insertString(offset,
+ contentBuffer.toString());
// Create the DocumentEvent with the ElementEdit added
DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
length,
DocumentEvent.EventType.INSERT);
+
ev.addEdit(edit);
// Finally we must update the document structure and fire the insert
// update event.
buffer.insert(offset, length, data, ev);
+
+ super.insertUpdate(ev, null);
+
+ ev.end();
fireInsertUpdate(ev);
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
}
@@ -2353,14 +2469,16 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
protected void create(ElementSpec[] data)
{
- writeLock();
try
{
+
// Clear content if there is some.
int len = getLength();
if (len > 0)
remove(0, len);
+ writeLock();
+
// Now we insert the content.
StringBuilder b = new StringBuilder();
for (int i = 0; i < data.length; ++i)
@@ -2372,38 +2490,18 @@ public class DefaultStyledDocument extends AbstractDocument implements
Content content = getContent();
UndoableEdit cEdit = content.insertString(0, b.toString());
+ len = b.length();
DefaultDocumentEvent ev =
new DefaultDocumentEvent(0, b.length(),
DocumentEvent.EventType.INSERT);
ev.addEdit(cEdit);
- // We do a little trick here to get the new structure: We instantiate
- // a new ElementBuffer with a new root element, insert into that root
- // and then reparent the newly created elements to the old root
- // element.
- BranchElement createRoot =
- (BranchElement) createBranchElement(null, null);
- Element dummyLeaf = createLeafElement(createRoot, null, 0, 1);
- createRoot.replace(0, 0, new Element[]{ dummyLeaf });
- ElementBuffer createBuffer = new ElementBuffer(createRoot);
- createBuffer.insert(0, b.length(), data, new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT));
- // Now the new root is the first child of the createRoot.
- Element newRoot = createRoot.getElement(0);
- BranchElement root = (BranchElement) getDefaultRootElement();
- Element[] added = new Element[newRoot.getElementCount()];
- for (int i = 0; i < added.length; ++i)
- {
- added[i] = newRoot.getElement(i);
- ((AbstractElement) added[i]).element_parent = root;
- }
- Element[] removed = new Element[root.getElementCount()];
- for (int i = 0; i < removed.length; ++i)
- removed[i] = root.getElement(i);
+ buffer.create(len, data, ev);
- // Replace the old elements in root with the new and update the event.
- root.replace(0, removed.length, added);
- ev.addEdit(new ElementEdit(root, 0, removed, added));
+ // For the bidi update.
+ super.insertUpdate(ev, null);
+ ev.end();
fireInsertUpdate(ev);
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
}
diff --git a/libjava/classpath/javax/swing/text/ElementIterator.java b/libjava/classpath/javax/swing/text/ElementIterator.java
index a6a5ff6..112d55e 100644
--- a/libjava/classpath/javax/swing/text/ElementIterator.java
+++ b/libjava/classpath/javax/swing/text/ElementIterator.java
@@ -37,6 +37,8 @@ exception statement from your version. */
package javax.swing.text;
+import java.util.Stack;
+
/**
* This class can be used to iterate over the {@link Element} tree of
* a {@link Document} or an {@link Element}. This iterator performs
@@ -46,20 +48,41 @@ package javax.swing.text;
*/
public class ElementIterator implements Cloneable
{
+ /**
+ * Uses to track the iteration on the stack.
+ */
+ private class ElementRef
+ {
+ /**
+ * The element.
+ */
+ Element element;
+
+ /**
+ * The child index. -1 means the element itself. >= 0 values mean the
+ * n-th child of the element.
+ */
+ int index;
+
+ /**
+ * Creates a new ElementRef.
+ *
+ * @param el the element
+ */
+ ElementRef(Element el)
+ {
+ element = el;
+ index = -1;
+ }
+ }
+
// The root element.
private Element root;
- // The current element.
- private Element currentElement;
- // The depth to which we have descended in the tree.
- private int currentDepth;
-
- // This is at least as big as the current depth, and at index N
- // contains the index of the child element we're currently
- // examining.
- private int[] state;
- // The previous item.
- private Element previousItem;
+ /**
+ * Holds ElementRefs.
+ */
+ private Stack stack;
/**
* Create a new ElementIterator to iterate over the given document.
@@ -67,9 +90,7 @@ public class ElementIterator implements Cloneable
*/
public ElementIterator(Document document)
{
- this.root = document.getDefaultRootElement();
- this.currentElement = root;
- this.state = new int[5];
+ root = document.getDefaultRootElement();
}
/**
@@ -79,8 +100,6 @@ public class ElementIterator implements Cloneable
public ElementIterator(Element root)
{
this.root = root;
- this.currentElement = root;
- this.state = new int[5];
}
/**
@@ -105,7 +124,24 @@ public class ElementIterator implements Cloneable
*/
public Element current()
{
- return currentElement;
+ Element current;
+ if (stack == null)
+ current = first();
+ else
+ {
+ current = null;
+ if (! stack.isEmpty())
+ {
+ ElementRef ref = (ElementRef) stack.peek();
+ Element el = ref.element;
+ int index = ref.index;
+ if (index == -1)
+ current = el;
+ else
+ current = el.getElement(index);
+ }
+ }
+ return current;
}
/**
@@ -113,7 +149,10 @@ public class ElementIterator implements Cloneable
*/
public int depth()
{
- return currentDepth;
+ int depth = 0;
+ if (stack != null)
+ depth = stack.size();
+ return depth;
}
/**
@@ -121,11 +160,15 @@ public class ElementIterator implements Cloneable
*/
public Element first()
{
- // Reset the iterator.
- currentElement = root;
- currentDepth = 0;
- previousItem = null;
- return root;
+ Element first = null;
+ if (root != null)
+ {
+ stack = new Stack();
+ if (root.getElementCount() > 0)
+ stack.push(new ElementRef(root));
+ first = root;
+ }
+ return first;
}
/**
@@ -134,48 +177,96 @@ public class ElementIterator implements Cloneable
*/
public Element next()
{
- previousItem = currentElement;
- if (currentElement == null)
- return null;
- if (! currentElement.isLeaf())
+ Element next;
+ if (stack == null)
+ next = first();
+ else
{
- ++currentDepth;
- if (currentDepth > state.length)
- {
- int[] newState = new int[state.length * 2];
- System.arraycopy(state, 0, newState, 0, state.length);
- state = newState;
- }
- state[currentDepth] = 0;
- currentElement = currentElement.getElement(0);
- return currentElement;
+ next = null;
+ if (! stack.isEmpty())
+ {
+ ElementRef ref = (ElementRef) stack.peek();
+ Element el = ref.element;
+ int index = ref.index;
+ if (el.getElementCount() > index + 1)
+ {
+ Element child = el.getElement(index + 1);
+ if (child.isLeaf())
+ ref.index++;
+ else
+ stack.push(new ElementRef(child));
+ next = child;
+ next = child;
+ }
+ else
+ {
+ stack.pop();
+ if (! stack.isEmpty())
+ {
+ ElementRef top = (ElementRef) stack.peek();
+ top.index++;
+ next = next();
+ }
+ }
+ }
+ // else return null.
}
+ return next;
+ }
- while (currentDepth > 0)
+ /**
+ * Returns the previous item. Does not modify the iterator state.
+ */
+ public Element previous()
+ {
+ Element previous = null;
+ int stackSize;
+ if (stack != null && (stackSize = stack.size()) > 0)
{
- // At a leaf, or done with a non-leaf's children, so go up a
- // level.
- --currentDepth;
- currentElement = currentElement.getParentElement();
- ++state[currentDepth];
- if (state[currentDepth] < currentElement.getElementCount())
- {
- currentElement = currentElement.getElement(state[currentDepth]);
- return currentElement;
- }
+ ElementRef ref = (ElementRef) stack.peek();
+ Element el = ref.element;
+ int index = ref.index;
+ if (index > 0)
+ {
+ previous = deepestLeaf(el.getElement(--index));
+ }
+ else if (index == 0)
+ {
+ previous = el;
+ }
+ else if (index == -1)
+ {
+ ElementRef top = (ElementRef) stack.pop();
+ ElementRef item = (ElementRef) stack.peek();
+ stack.push(top);
+ index = item.index;
+ el = item.element;
+ previous = index == -1 ? el : deepestLeaf(el.getElement(index));
+ }
}
-
- currentElement = null;
- return currentElement;
+ return previous;
}
/**
- * Returns the previous item. Does not modify the iterator state.
+ * Determines and returns the deepest leaf of the element <code>el</code>.
+ *
+ * @param el the base element
+ *
+ * @returnthe deepest leaf of the element <code>el</code>
*/
- public Element previous()
+ private Element deepestLeaf(Element el)
{
- if (currentElement == null || currentElement == root)
- return null;
- return previousItem;
+ Element leaf;
+ if (el.isLeaf())
+ leaf = el;
+ else
+ {
+ int count = el.getElementCount();
+ if (count == 0)
+ leaf = el;
+ else
+ leaf = deepestLeaf(el.getElement(count - 1));
+ }
+ return leaf;
}
}
diff --git a/libjava/classpath/javax/swing/text/FieldView.java b/libjava/classpath/javax/swing/text/FieldView.java
index f41f901..0a078e5 100644
--- a/libjava/classpath/javax/swing/text/FieldView.java
+++ b/libjava/classpath/javax/swing/text/FieldView.java
@@ -45,8 +45,6 @@ import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
import javax.swing.BoundedRangeModel;
import javax.swing.JTextField;
@@ -225,7 +223,7 @@ public class FieldView extends PlainView
public int getResizeWeight(int axis)
{
- return axis = axis == X_AXIS ? 1 : 0;
+ return axis == X_AXIS ? 1 : 0;
}
public Shape modelToView(int pos, Shape a, Position.Bias bias)
diff --git a/libjava/classpath/javax/swing/text/FlowView.java b/libjava/classpath/javax/swing/text/FlowView.java
index 3de95ed..c2bed39 100644
--- a/libjava/classpath/javax/swing/text/FlowView.java
+++ b/libjava/classpath/javax/swing/text/FlowView.java
@@ -38,6 +38,8 @@ exception statement from your version. */
package javax.swing.text;
+import java.awt.Component;
+import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -85,7 +87,17 @@ public abstract class FlowView extends BoxView
*/
public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
{
- // The default implementation does nothing.
+ if (alloc == null)
+ {
+ fv.layoutChanged(X_AXIS);
+ fv.layoutChanged(Y_AXIS);
+ }
+ else
+ {
+ Component host = fv.getContainer();
+ if (host != null)
+ host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+ }
}
/**
@@ -101,7 +113,17 @@ public abstract class FlowView extends BoxView
*/
public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
{
- // The default implementation does nothing.
+ if (alloc == null)
+ {
+ fv.layoutChanged(X_AXIS);
+ fv.layoutChanged(Y_AXIS);
+ }
+ else
+ {
+ Component host = fv.getContainer();
+ if (host != null)
+ host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+ }
}
/**
@@ -117,7 +139,17 @@ public abstract class FlowView extends BoxView
*/
public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
{
- // The default implementation does nothing.
+ if (alloc == null)
+ {
+ fv.layoutChanged(X_AXIS);
+ fv.layoutChanged(Y_AXIS);
+ }
+ else
+ {
+ Component host = fv.getContainer();
+ if (host != null)
+ host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+ }
}
/**
@@ -143,18 +175,35 @@ public abstract class FlowView extends BoxView
*/
public void layout(FlowView fv)
{
+ int start = fv.getStartOffset();
+ int end = fv.getEndOffset();
+
+ // Preserve the views from the logical view from beeing removed.
+ View lv = getLogicalView(fv);
+ int viewCount = lv.getViewCount();
+ for (int i = 0; i < viewCount; i++)
+ {
+ View v = lv.getView(i);
+ v.setParent(lv);
+ }
+
+ // Then remove all views from the flow view.
fv.removeAll();
- Element el = fv.getElement();
- int rowStart = el.getStartOffset();
- int end = el.getEndOffset();
- int rowIndex = 0;
- while (rowStart >= 0 && rowStart < end)
+ for (int rowIndex = 0; start < end; rowIndex++)
{
View row = fv.createRow();
fv.append(row);
- rowStart = layoutRow(fv, rowIndex, rowStart);
- rowIndex++;
+ int next = layoutRow(fv, rowIndex, start);
+ if (row.getViewCount() == 0)
+ {
+ row.append(createView(fv, start, Integer.MAX_VALUE, rowIndex));
+ next = row.getEndOffset();
+ }
+ if (start < next)
+ start = next;
+ else
+ assert false: "May not happen";
}
}
@@ -179,46 +228,69 @@ public abstract class FlowView extends BoxView
int axis = fv.getFlowAxis();
int span = fv.getFlowSpan(rowIndex);
int x = fv.getFlowStart(rowIndex);
- int offset = pos;
- View logicalView = getLogicalView(fv);
- // Special case when span == 0. We need to layout the row as if it had
- // a span of Integer.MAX_VALUE.
- if (span == 0)
- span = Integer.MAX_VALUE;
-
- Row: while (span > 0)
+ int end = fv.getEndOffset();
+
+ // Needed for adjusting indentation in adjustRow().
+ int preX = x;
+ int availableSpan = span;
+
+ TabExpander tabExp = fv instanceof TabExpander ? (TabExpander) fv : null;
+
+ boolean forcedBreak = false;
+ while (pos < end && span >= 0)
{
- if (logicalView.getViewIndex(offset, Position.Bias.Forward) == - 1)
- break;
- View view = createView(fv, offset, span, rowIndex);
- if (view == null)
+ View view = createView(fv, pos, span, rowIndex);
+ if (view == null
+ || (span == 0 && view.getPreferredSpan(axis) > 0))
break;
- int viewSpan = (int) view.getPreferredSpan(axis);
- int breakWeight = view.getBreakWeight(axis, x, span);
-
- row.append(view);
- offset += (view.getEndOffset() - view.getStartOffset());
- x += viewSpan;
- span -= viewSpan;
+ int viewSpan;
+ if (axis == X_AXIS && view instanceof TabableView)
+ viewSpan = (int) ((TabableView) view).getTabbedSpan(x, tabExp);
+ else
+ viewSpan = (int) view.getPreferredSpan(axis);
// Break if the line if the view does not fit in this row or the
// line just must be broken.
- if (span < 0 || breakWeight >= View.ForcedBreakWeight)
+ int breakWeight = view.getBreakWeight(axis, pos, span);
+ if (breakWeight >= ForcedBreakWeight)
{
- int flowStart = fv.getFlowStart(axis);
- int flowSpan = fv.getFlowSpan(axis);
- adjustRow(fv, rowIndex, flowSpan, flowStart);
int rowViewCount = row.getViewCount();
if (rowViewCount > 0)
- offset = row.getView(rowViewCount - 1).getEndOffset();
- else
- offset = - 1;
- break Row;
+ {
+ view = view.breakView(axis, pos, x, span);
+ if (view != null)
+ {
+ if (axis == X_AXIS && view instanceof TabableView)
+ viewSpan =
+ (int) ((TabableView) view).getTabbedSpan(x, tabExp);
+ else
+ viewSpan = (int) view.getPreferredSpan(axis);
+ }
+ else
+ viewSpan = 0;
+ }
+ forcedBreak = true;
+ }
+ span -= viewSpan;
+ x += viewSpan;
+ if (view != null)
+ {
+ row.append(view);
+ pos = view.getEndOffset();
}
+ if (forcedBreak)
+ break;
}
- return offset != pos ? offset : - 1;
+ if (span < 0)
+ adjustRow(fv, rowIndex, availableSpan, preX);
+ else if (row.getViewCount() == 0)
+ {
+ View view = createView(fv, pos, Integer.MAX_VALUE, rowIndex);
+ row.append(view);
+ }
+ return row.getEndOffset();
}
/**
@@ -246,15 +318,11 @@ public abstract class FlowView extends BoxView
int rowIndex)
{
View logicalView = getLogicalView(fv);
- // FIXME: Handle the bias thing correctly.
- int index = logicalView.getViewIndex(startOffset, Position.Bias.Forward);
- View retVal = null;
- if (index >= 0)
- {
- retVal = logicalView.getView(index);
- if (retVal.getStartOffset() != startOffset)
- retVal = retVal.createFragment(startOffset, retVal.getEndOffset());
- }
+ int index = logicalView.getViewIndex(startOffset,
+ Position.Bias.Forward);
+ View retVal = logicalView.getView(index);
+ if (retVal.getStartOffset() != startOffset)
+ retVal = retVal.createFragment(startOffset, retVal.getEndOffset());
return retVal;
}
@@ -279,37 +347,82 @@ public abstract class FlowView extends BoxView
View row = fv.getView(rowIndex);
int count = row.getViewCount();
int breakIndex = -1;
- int maxBreakWeight = View.BadBreakWeight;
- int breakX = x;
- int breakSpan = desiredSpan;
- int currentX = x;
- int currentSpan = desiredSpan;
+ int breakWeight = BadBreakWeight;
+ int breakSpan = 0;
+ int currentSpan = 0;
for (int i = 0; i < count; ++i)
{
View view = row.getView(i);
- int weight = view.getBreakWeight(axis, currentX, currentSpan);
- if (weight >= maxBreakWeight)
+ int spanLeft = desiredSpan - currentSpan;
+ int weight = view.getBreakWeight(axis, x + currentSpan, spanLeft);
+ if (weight >= breakWeight && weight > BadBreakWeight)
{
breakIndex = i;
- breakX = currentX;
breakSpan = currentSpan;
- maxBreakWeight = weight;
+ breakWeight = weight;
+ if (weight >= ForcedBreakWeight)
+ // Don't search further.
+ break;
}
- int size = (int) view.getPreferredSpan(axis);
- currentX += size;
- currentSpan -= size;
+ currentSpan += view.getPreferredSpan(axis);
}
// If there is a potential break location found, break the row at
// this location.
- if (breakIndex > -1)
+ if (breakIndex >= 0)
{
+ int spanLeft = desiredSpan - breakSpan;
View toBeBroken = row.getView(breakIndex);
View brokenView = toBeBroken.breakView(axis,
toBeBroken.getStartOffset(),
- breakX, breakSpan);
+ x + breakSpan, spanLeft);
+ View lv = getLogicalView(fv);
+ for (int i = breakIndex; i < count; i++)
+ {
+ View tmp = row.getView(i);
+ if (contains(lv, tmp))
+ tmp.setParent(lv);
+ else if (tmp.getViewCount() > 0)
+ reparent(tmp, lv);
+ }
row.replace(breakIndex, count - breakIndex,
- new View[]{brokenView});
+ new View[]{ brokenView });
+ }
+
+ }
+
+ /**
+ * Helper method to determine if one view contains another as child.
+ */
+ private boolean contains(View view, View child)
+ {
+ boolean ret = false;
+ int n = view.getViewCount();
+ for (int i = 0; i < n && ret == false; i++)
+ {
+ if (view.getView(i) == child)
+ ret = true;
+ }
+ return ret;
+ }
+
+ /**
+ * Helper method that reparents the <code>view</code> and all of its
+ * decendents to the <code>parent</code> (the logical view).
+ *
+ * @param view the view to reparent
+ * @param parent the new parent
+ */
+ private void reparent(View view, View parent)
+ {
+ int n = view.getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View tmp = view.getView(i);
+ if (contains(parent, tmp))
+ tmp.setParent(parent);
+ else
+ reparent(tmp, parent);
}
}
}
@@ -320,14 +433,135 @@ public abstract class FlowView extends BoxView
* visual representation, this is handled by the physical view implemented
* in the <code>FlowView</code>.
*/
- class LogicalView extends BoxView
+ class LogicalView extends CompositeView
{
/**
* Creates a new LogicalView instance.
*/
- LogicalView(Element el, int axis)
+ LogicalView(Element el)
+ {
+ super(el);
+ }
+
+ /**
+ * Overridden to return the attributes of the parent
+ * (== the FlowView instance).
+ */
+ public AttributeSet getAttributes()
+ {
+ View p = getParent();
+ return p != null ? p.getAttributes() : null;
+ }
+
+ protected void childAllocation(int index, Rectangle a)
+ {
+ // Nothing to do here (not visual).
+ }
+
+ protected View getViewAtPoint(int x, int y, Rectangle r)
+ {
+ // Nothing to do here (not visual).
+ return null;
+ }
+
+ protected boolean isAfter(int x, int y, Rectangle r)
+ {
+ // Nothing to do here (not visual).
+ return false;
+ }
+
+ protected boolean isBefore(int x, int y, Rectangle r)
+ {
+ // Nothing to do here (not visual).
+ return false;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ float max = 0;
+ float pref = 0;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ pref += v.getPreferredSpan(axis);
+ if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
+ >= ForcedBreakWeight)
+ {
+ max = Math.max(max, pref);
+ pref = 0;
+ }
+ }
+ max = Math.max(max, pref);
+ return max;
+ }
+
+ public float getMinimumSpan(int axis)
+ {
+ float max = 0;
+ float min = 0;
+ boolean wrap = true;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
+ == BadBreakWeight)
+ {
+ min += v.getPreferredSpan(axis);
+ wrap = false;
+ }
+ else if (! wrap)
+ {
+ max = Math.max(min, max);
+ wrap = true;
+ min = 0;
+ }
+ }
+ max = Math.max(max, min);
+ return max;
+ }
+
+ public void paint(Graphics g, Shape s)
+ {
+ // Nothing to do here (not visual).
+ }
+
+ /**
+ * Overridden to handle possible leaf elements.
+ */
+ protected void loadChildren(ViewFactory f)
{
- super(el, axis);
+ Element el = getElement();
+ if (el.isLeaf())
+ {
+ View v = new LabelView(el);
+ append(v);
+ }
+ else
+ super.loadChildren(f);
+ }
+
+ /**
+ * Overridden to reparent the children to this logical view, in case
+ * they have been parented by a row.
+ */
+ protected void forwardUpdateToView(View v, DocumentEvent e, Shape a,
+ ViewFactory f)
+ {
+ v.setParent(this);
+ super.forwardUpdateToView(v, e, a, f);
+ }
+
+ /**
+ * Overridden to handle possible leaf element.
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ int index = 0;
+ if (! getElement().isLeaf())
+ index = super.getViewIndexAtPosition(pos);
+ return index;
}
}
@@ -357,11 +591,6 @@ public abstract class FlowView extends BoxView
protected FlowStrategy strategy;
/**
- * Indicates if the flow should be rebuild during the next layout.
- */
- private boolean layoutDirty;
-
- /**
* Creates a new <code>FlowView</code> for the given
* <code>Element</code> and <code>axis</code>.
*
@@ -374,7 +603,7 @@ public abstract class FlowView extends BoxView
{
super(element, axis);
strategy = sharedStrategy;
- layoutDirty = true;
+ layoutSpan = Short.MAX_VALUE;
}
/**
@@ -423,7 +652,7 @@ public abstract class FlowView extends BoxView
*/
public int getFlowStart(int index)
{
- return getLeftInset(); // TODO: Is this correct?
+ return 0;
}
/**
@@ -449,9 +678,11 @@ public abstract class FlowView extends BoxView
{
if (layoutPool == null)
{
- layoutPool = new LogicalView(getElement(), getAxis());
- layoutPool.setParent(this);
+ layoutPool = new LogicalView(getElement());
}
+ layoutPool.setParent(this);
+ // Initialize the flow strategy.
+ strategy.insertUpdate(this, null, null);
}
/**
@@ -466,32 +697,33 @@ public abstract class FlowView extends BoxView
protected void layout(int width, int height)
{
int flowAxis = getFlowAxis();
+ int span;
if (flowAxis == X_AXIS)
- {
- if (layoutSpan != width)
- {
- layoutChanged(Y_AXIS);
- layoutSpan = width;
- }
- }
+ span = (int) width;
else
+ span = (int) height;
+
+ if (layoutSpan != span)
{
- if (layoutSpan != height)
- {
- layoutChanged(X_AXIS);
- layoutSpan = height;
- }
+ layoutChanged(flowAxis);
+ layoutChanged(getAxis());
+ layoutSpan = span;
}
- if (layoutDirty)
+ if (! isLayoutValid(flowAxis))
{
+ int axis = getAxis();
+ int oldSpan = axis == X_AXIS ? getWidth() : getHeight();
strategy.layout(this);
- layoutDirty = false;
+ int newSpan = (int) getPreferredSpan(axis);
+ if (oldSpan != newSpan)
+ {
+ View parent = getParent();
+ if (parent != null)
+ parent.preferenceChanged(this, axis == X_AXIS, axis == Y_AXIS);
+ }
}
- if (getPreferredSpan(getAxis()) != height)
- preferenceChanged(this, false, true);
-
super.layout(width, height);
}
@@ -510,7 +742,6 @@ public abstract class FlowView extends BoxView
// be updated accordingly.
layoutPool.insertUpdate(changes, a, vf);
strategy.insertUpdate(this, changes, getInsideAllocation(a));
- layoutDirty = true;
}
/**
@@ -526,7 +757,6 @@ public abstract class FlowView extends BoxView
{
layoutPool.removeUpdate(changes, a, vf);
strategy.removeUpdate(this, changes, getInsideAllocation(a));
- layoutDirty = true;
}
/**
@@ -542,7 +772,6 @@ public abstract class FlowView extends BoxView
{
layoutPool.changedUpdate(changes, a, vf);
strategy.changedUpdate(this, changes, getInsideAllocation(a));
- layoutDirty = true;
}
/**
@@ -604,7 +833,7 @@ public abstract class FlowView extends BoxView
res = new SizeRequirements();
res.minimum = (int) layoutPool.getMinimumSpan(axis);
res.preferred = Math.max(res.minimum,
- (int) layoutPool.getMinimumSpan(axis));
+ (int) layoutPool.getPreferredSpan(axis));
res.maximum = Integer.MAX_VALUE;
res.alignment = 0.5F;
return res;
diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java
index 760e396..08a318d 100644
--- a/libjava/classpath/javax/swing/text/GapContent.java
+++ b/libjava/classpath/javax/swing/text/GapContent.java
@@ -39,16 +39,13 @@ exception statement from your version. */
package javax.swing.text;
import java.io.Serializable;
-import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.Set;
import java.util.Vector;
-import java.util.WeakHashMap;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
@@ -71,7 +68,7 @@ public class GapContent
/**
* A {@link Position} implementation for <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,33 +128,62 @@ public class GapContent
*/
int getOffset()
{
- assert mark == 0 || mark < gapStart || mark >= gapEnd :
- "Invalid mark: " + mark + ", gapStart: " + gapStart
- + ", gapEnd: " + gapEnd;
-
int res = mark;
- if (mark >= gapEnd)
+ if (mark >= gapStart)
res -= (gapEnd - gapStart);
- return res;
+ return Math.max(0, res);
+ }
+
+ /**
+ * Returns the GapContentPosition that is associated ith this mark.
+ * This fetches the weakly referenced position object.
+ *
+ * @return the GapContentPosition that is associated ith this mark
+ */
+ GapContentPosition getPosition()
+ {
+ return (GapContentPosition) get();
}
+ }
+
+ /**
+ * Stores a reference to a mark that can be resetted to the original value
+ * after a mark has been moved. This is used for undoing actions.
+ */
+ private class UndoPosRef
+ {
+ /**
+ * The mark that might need to be reset.
+ */
+ private Mark mark;
+
/**
- * Implementation of Comparable.
+ * The original offset to reset the mark to.
*/
- public int compareTo(Object o)
+ private int undoOffset;
+
+ /**
+ * Creates a new UndoPosRef.
+ *
+ * @param m the mark
+ */
+ UndoPosRef(Mark m)
{
- Mark other = (Mark) o;
- return mark - other.mark;
+ mark = m;
+ undoOffset = mark.getOffset();
}
+
/**
- * Adjustment for equals().
+ * Resets the position of the mark to the value that it had when
+ * creating this UndoPosRef.
*/
- public boolean equals(Object o)
+ void reset()
{
- if (o == null || !(o instanceof Mark))
- return false;
+ if (undoOffset <= gapStart)
+ mark.mark = undoOffset;
else
- return ((Mark) o).mark == mark;
+ mark.mark = (gapEnd - gapStart) + undoOffset;
}
}
@@ -199,6 +191,8 @@ public class GapContent
{
public int where, length;
String text;
+ private Vector positions;
+
public InsertUndo(int start, int len)
{
where = start;
@@ -209,27 +203,33 @@ public class GapContent
{
super.undo();
try
- {
- text = getString(where, length);
- remove(where, length);
- }
+ {
+ positions = getPositionsInRange(null, where, length);
+ text = getString(where, length);
+ remove(where, length);
+ }
catch (BadLocationException ble)
- {
- throw new CannotUndoException();
- }
+ {
+ throw new CannotUndoException();
+ }
}
public void redo () throws CannotUndoException
{
super.redo();
try
- {
- insertString(where, text);
- }
+ {
+ insertString(where, text);
+ if (positions != null)
+ {
+ updateUndoPositions(positions, where, length);
+ positions = null;
+ }
+ }
catch (BadLocationException ble)
- {
- throw new CannotRedoException();
- }
+ {
+ throw new CannotRedoException();
+ }
}
}
@@ -238,10 +238,17 @@ public class GapContent
{
public int where;
String text;
+
+ /**
+ * The positions in the removed range.
+ */
+ private Vector positions;
+
public UndoRemove(int start, String removedText)
{
where = start;
text = removedText;
+ positions = getPositionsInRange(null, start, removedText.length());
}
public void undo () throws CannotUndoException
@@ -250,6 +257,8 @@ public class GapContent
try
{
insertString(where, text);
+ if (positions != null)
+ updateUndoPositions(positions, where, text.length());
}
catch (BadLocationException ble)
{
@@ -261,13 +270,15 @@ public class GapContent
{
super.redo();
try
- {
- remove(where, text.length());
- }
+ {
+ text = getString(where, text.length());
+ positions = getPositionsInRange(null, where, text.length());
+ remove(where, text.length());
+ }
catch (BadLocationException ble)
- {
- throw new CannotRedoException();
- }
+ {
+ throw new CannotRedoException();
+ }
}
}
@@ -308,7 +319,15 @@ public class GapContent
*/
ArrayList marks;
- WeakHashMap positions;
+ /**
+ * The number of unused marks.
+ */
+ private int garbageMarks;
+
+ /**
+ * A 'static' mark that is used for searching.
+ */
+ private Mark searchMark = new Mark(0);
/**
* Queues all references to GapContentPositions that are about to be
@@ -339,7 +358,6 @@ public class GapContent
gapStart = 1;
gapEnd = size;
buffer[0] = '\n';
- positions = new WeakHashMap();
marks = new ArrayList();
queueOfDeath = new ReferenceQueue();
}
@@ -403,9 +421,10 @@ public class GapContent
throw new BadLocationException("The where argument cannot be greater"
+ " than the content length", where);
+ InsertUndo undo = new InsertUndo(where, strLen);
replace(where, 0, str.toCharArray(), strLen);
- return new InsertUndo(where, strLen);
+ return undo;
}
/**
@@ -429,9 +448,10 @@ public class GapContent
+ " than the content length", where + nitems);
String removedText = getString(where, nitems);
+ UndoRemove undoRemove = new UndoRemove(where, removedText);
replace(where, nitems, null, 0);
- return new UndoRemove(where, removedText);
+ return undoRemove;
}
/**
@@ -495,29 +515,43 @@ public class GapContent
if (len < 0)
throw new BadLocationException("negative length not allowed: ", len);
- // check if requested segment is contiguous
- if ((where < gapStart) && ((gapStart - where) < len))
- {
- // requested segment is not contiguous -> copy the pieces together
- char[] copy = new char[len];
- int lenFirst = gapStart - where; // the length of the first segment
- System.arraycopy(buffer, where, copy, 0, lenFirst);
- System.arraycopy(buffer, gapEnd, copy, lenFirst, len - lenFirst);
- txt.array = copy;
- txt.offset = 0;
- txt.count = len;
- }
- else
- {
- // requested segment is contiguous -> we can simply return the
- // actual content
- txt.array = buffer;
- if (where < gapStart)
+ // Optimized to copy only when really needed.
+ if (where + len <= gapStart)
+ {
+ // Simple case: completely before gap.
+ txt.array = buffer;
txt.offset = where;
- else
- txt.offset = where + (gapEnd - gapStart);
- txt.count = len;
- }
+ txt.count = len;
+ }
+ else if (where > gapStart)
+ {
+ // Completely after gap, adjust offset.
+ txt.array = buffer;
+ txt.offset = gapEnd + where - gapStart;
+ txt.count = len;
+ }
+ else
+ {
+ // Spans the gap.
+ int beforeGap = gapStart - where;
+ if (txt.isPartialReturn())
+ {
+ // Return the part before the gap when partial return is allowed.
+ txt.array = buffer;
+ txt.offset = where;
+ txt.count = beforeGap;
+ }
+ else
+ {
+ // Copy pieces together otherwise.
+ txt.array = new char[len];
+ txt.offset = 0;
+ System.arraycopy(buffer, where, txt.array, 0, beforeGap);
+ System.arraycopy(buffer, gapEnd, txt.array, beforeGap,
+ len - beforeGap);
+ txt.count = len;
+ }
+ }
}
/**
@@ -537,27 +571,33 @@ public class GapContent
// and luckily enough the GapContent can very well deal with offsets
// outside the buffer bounds. So I removed that check.
+ // First do some garbage collections.
+ while (queueOfDeath.poll() != null)
+ garbageMarks++;
+ if (garbageMarks > Math.max(5, marks.size() / 10))
+ garbageCollect();
+
// We try to find a GapContentPosition at the specified offset and return
// that. Otherwise we must create a new one.
- GapContentPosition pos = null;
- Set positionSet = positions.keySet();
- for (Iterator i = positionSet.iterator(); i.hasNext();)
- {
- GapContentPosition p = (GapContentPosition) i.next();
- if (p.getOffset() == offset)
- {
- pos = p;
- break;
- }
- }
-
- // If none was found, then create and return a new one.
- if (pos == null)
+ Mark m;
+ GapContentPosition pos;
+ int index = offset;
+ if (offset >= gapStart)
+ index += (gapEnd - gapStart);
+ searchMark.mark = index;
+ int insertIndex = search(searchMark);
+ if (!(insertIndex < marks.size()
+ && (m = (Mark) marks.get(insertIndex)).mark == index
+ && (pos = m.getPosition()) != null))
{
- pos = new GapContentPosition(offset);
- positions.put(pos, null);
+ // Create new position if none was found.
+ pos = new GapContentPosition();
+ m = new Mark(index, pos, queueOfDeath);
+ pos.mark = m;
+ marks.add(insertIndex, m);
}
-
+ // Otherwise use the found position.
+
return pos;
}
@@ -574,18 +614,29 @@ public class GapContent
assert newSize > (gapEnd - gapStart) : "The new gap size must be greater "
+ "than the old gap size";
- int delta = newSize - gapEnd + gapStart;
- // Update the marks after the gapEnd.
- adjustPositionsInRange(gapEnd, -1, delta);
+ int oldEnd = getGapEnd();
+ int oldSize = getArrayLength();
+ int upper = oldSize - oldEnd;
+ int size = (newSize + 1) * 2;
+ int newEnd = size - upper;
// Copy the data around.
- char[] newBuf = (char[]) allocateArray(length() + newSize);
- System.arraycopy(buffer, 0, newBuf, 0, gapStart);
- System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length
- - gapEnd);
- gapEnd = gapStart + newSize;
+ char[] newBuf = (char[]) allocateArray(size);
+ System.arraycopy(buffer, 0, newBuf, 0, Math.min(size, oldSize));
buffer = newBuf;
-
+ gapEnd = newEnd;
+ if (upper != 0)
+ System.arraycopy(buffer, oldEnd, buffer, newEnd, upper);
+
+ // Adjust marks.
+ int delta = gapEnd - oldEnd;
+ int adjIndex = searchFirst(oldEnd);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ m.mark += delta;
+ }
}
/**
@@ -595,28 +646,44 @@ public class GapContent
*/
protected void shiftGap(int newGapStart)
{
- if (newGapStart == gapStart)
- return;
- int newGapEnd = newGapStart + gapEnd - gapStart;
- if (newGapStart < gapStart)
+ int oldStart = gapStart;
+ int delta = newGapStart - oldStart;
+ int oldEnd = gapEnd;
+ int newGapEnd = oldEnd + delta;
+ int size = oldEnd - oldStart;
+
+ // Shift gap in array.
+ gapStart = newGapStart;
+ gapEnd = newGapEnd;
+ if (delta > 0)
+ System.arraycopy(buffer, oldEnd, buffer, oldStart, delta);
+ else
+ System.arraycopy(buffer, newGapStart, buffer, newGapEnd, -delta);
+
+ // Adjust marks.
+ if (delta > 0)
{
- // Update the positions between newGapStart and (old) gapStart. The marks
- // must be shifted by (gapEnd - gapStart).
- adjustPositionsInRange(newGapStart, gapStart, gapEnd - gapStart);
- System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart
- - newGapStart);
- gapStart = newGapStart;
- gapEnd = newGapEnd;
+ int adjIndex = searchFirst(oldStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= newGapEnd)
+ break;
+ m.mark -= size;
+ }
}
- else
+ else if (delta < 0)
{
- // Update the positions between newGapEnd and (old) gapEnd. The marks
- // must be shifted by (gapEnd - gapStart).
- adjustPositionsInRange(gapEnd, newGapEnd, -(gapEnd - gapStart));
- System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart
- - gapStart);
- gapStart = newGapStart;
- gapEnd = newGapEnd;
+ int adjIndex = searchFirst(newGapStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= oldEnd)
+ break;
+ m.mark += size;
+ }
}
resetMarksAtZero();
}
@@ -636,7 +703,18 @@ public class GapContent
assert newGapStart < gapStart : "The new gap start must be less than the "
+ "old gap start.";
- setPositionsInRange(newGapStart, gapStart, false);
+
+ // Adjust positions.
+ int adjIndex = searchFirst(newGapStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark > gapStart)
+ break;
+ m.mark = gapEnd;
+ }
+
gapStart = newGapStart;
resetMarksAtZero();
}
@@ -656,7 +734,19 @@ public class GapContent
assert newGapEnd > gapEnd : "The new gap end must be greater than the "
+ "old gap end.";
- setPositionsInRange(gapEnd, newGapEnd, false);
+
+ // Adjust marks.
+ int adjIndex = searchFirst(gapEnd);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= newGapEnd)
+ break;
+ m.mark = newGapEnd;
+ }
+
+
gapEnd = newGapEnd;
resetMarksAtZero();
}
@@ -682,23 +772,88 @@ public class GapContent
protected void replace(int position, int rmSize, Object addItems,
int addSize)
{
- if (gapStart != position)
- shiftGap(position);
-
- // Remove content
- if (rmSize > 0)
- shiftGapEndUp(gapEnd + rmSize);
+ if (addSize == 0)
+ {
+ removeImpl(position, rmSize);
+ return;
+ }
+ else if (rmSize > addSize)
+ {
+ removeImpl(position + addSize, rmSize - addSize);
+ }
+ else
+ {
+ int endSize = addSize - rmSize;
+ int end = addImpl(position + rmSize, endSize);
+ System.arraycopy(addItems, rmSize, buffer, end, endSize);
+ addSize = rmSize;
+ }
+ System.arraycopy(addItems, 0, buffer, position, addSize);
+ }
+
+ /**
+ * Adjusts the positions and gap in response to a remove operation.
+ *
+ * @param pos the position at which to remove
+ * @param num the number of removed items
+ */
+ private void removeImpl(int pos, int num)
+ {
+ if (num > 0)
+ {
+ int end = pos + num;
+ int newGapSize = (gapEnd - gapStart) + num;
+ if (end <= gapStart)
+ {
+ if (gapStart != end)
+ {
+ shiftGap(end);
+ }
+ shiftGapStartDown(gapStart - num);
+ }
+ else if (pos >= gapStart)
+ {
+ if (gapStart != pos)
+ {
+ shiftGap(pos);
+ }
+ shiftGapEndUp(gapStart + newGapSize);
+ }
+ else
+ {
+ shiftGapStartDown(pos);
+ shiftGapEndUp(gapStart + newGapSize);
+ }
+ }
+ }
- // If gap is too small, enlarge the gap.
- if ((gapEnd - gapStart) <= addSize)
- shiftEnd((addSize - gapEnd + gapStart + 1) * 2 + gapEnd + DEFAULT_BUFSIZE);
+ /**
+ * Adjusts the positions and gap in response to an add operation.
+ *
+ * @param pos the position at which to add
+ * @param num the number of added items
+ *
+ * @return the adjusted position
+ */
+ private int addImpl(int pos, int num)
+ {
+ int size = gapEnd - gapStart;
+ if (num == 0)
+ {
+ if (pos > gapStart)
+ pos += size;
+ return pos;
+ }
- // Add new items to the buffer.
- if (addItems != null)
+ shiftGap(pos);
+ if (num >= size)
{
- System.arraycopy(addItems, 0, buffer, gapStart, addSize);
- gapStart += addSize;
+ shiftEnd(getArrayLength() - size + num);
+ size = gapEnd - gapStart;
}
+
+ gapStart += num;
+ return pos;
}
/**
@@ -733,97 +888,34 @@ public class GapContent
*/
protected Vector getPositionsInRange(Vector v, int offset, int length)
{
- Vector res = v;
- if (res == null)
- res = new Vector();
- else
- res.clear();
-
- int endOffs = offset + length;
-
- Set positionSet = positions.keySet();
- for (Iterator i = positionSet.iterator(); i.hasNext();)
+ int end = offset + length;
+ int startIndex;
+ int endIndex;
+ if (offset < gapStart)
{
- GapContentPosition p = (GapContentPosition) i.next();
- int offs = p.getOffset();
- if (offs >= offset && offs < endOffs)
- res.add(p);
+ if (offset == 0)
+ startIndex = 0;
+ else
+ startIndex = searchFirst(offset);
+ if (end >= gapStart)
+ endIndex = searchFirst(end + (gapEnd - gapStart) + 1);
+ else
+ endIndex = searchFirst(end + 1);
}
-
- return res;
- }
-
- /**
- * Crunches all positions in the specified range to either the start or
- * end of that interval. The interval boundaries are meant to be inclusive
- * [start, end].
- *
- * @param start the start offset of the range
- * @param end the end offset of the range
- * @param toStart a boolean indicating if the positions should be crunched
- * to the start (true) or to the end (false)
- */
- private void setPositionsInRange(int start, int end, boolean toStart)
- {
- synchronized (this)
+ else
{
- // Find the start and end indices in the positionMarks array.
- Mark m = new Mark(0); // For comparison / search only.
- m.mark = start;
- int startIndex = search(marks, m);
- if (startIndex < 0) // Translate to insertion index, if not found.
- startIndex = - startIndex - 1;
- m.mark = end;
- int endIndex = search(marks, m);
- if (endIndex < 0) // Translate to insertion index - 1, if not found.
- endIndex = - endIndex - 2;
-
- // Actually adjust the marks.
- for (int i = startIndex; i <= endIndex; i++)
- ((Mark) marks.get(i)).mark = toStart ? start : end;
+ startIndex = searchFirst(offset + (gapEnd - gapStart));
+ endIndex = searchFirst(end + (gapEnd - gapStart) + 1);
}
-
- }
-
- /**
- * Adjusts the mark of all <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
@@ -844,14 +936,26 @@ public class GapContent
}
/**
- * @specnote This method is not very well specified and the positions vector
- * is implementation specific. The undo positions are managed
- * differently in this implementation, this method is only here
- * for binary compatibility.
+ * Resets the positions in the specified range to their original offset
+ * after a undo operation is performed. For example, after removing some
+ * content, the positions in the removed range will all be set to one
+ * offset. This method restores the positions to their original offsets
+ * after an undo.
+ *
+ * @param positions the positions to update
+ * @param offset
+ * @param length
*/
protected void updateUndoPositions(Vector positions, int offset, int length)
{
- // We do nothing here.
+ for (Iterator i = positions.iterator(); i.hasNext();)
+ {
+ UndoPosRef undoPosRef = (UndoPosRef) i.next();
+ undoPosRef.reset();
+ }
+
+ // Resort marks.
+ Collections.sort(marks);
}
/**
@@ -892,30 +996,6 @@ public class GapContent
}
/**
- * Polls the queue of death for GapContentPositions, updates the
- * corresponding reference count and removes the corresponding mark
- * if the refcount reaches zero.
- *
- * This is package private to avoid accessor synthetic methods.
- */
- void garbageCollect()
- {
- Reference ref = queueOfDeath.poll();
- while (ref != null)
- {
- if (ref != null)
- {
- GapContentPosition pos = (GapContentPosition) ref.get();
- Mark m = pos.mark;
- m.refCount--;
- if (m.refCount == 0)
- marks.remove(m);
- }
- ref = queueOfDeath.poll();
- }
- }
-
- /**
* Searches the first occurance of object <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
@@ -923,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
*/
- private int search(List l, Object o)
+ int search(Mark o)
{
- int i = Collections.binarySearch(l, o);
- while (i > 0)
+ int foundInd = 0;
+ boolean found = false;
+ int low = 0;
+ int up = marks.size() - 1;
+ int mid = 0;
+ if (up > -1)
{
- Object o2 = l.get(i - 1);
- if (o2.equals(o))
- i--;
+ int cmp = 0;
+ Mark last = (Mark) marks.get(up);
+ cmp = compare(o, last);
+ if (cmp > 0)
+ {
+ foundInd = up + 1;
+ found = true;
+ }
else
+ {
+ while (low <= up && ! found)
+ {
+ mid = low + (up - low) / 2;
+ Mark m = (Mark) marks.get(mid);
+ cmp = compare(o, m);
+ if (cmp == 0)
+ {
+ foundInd = mid;
+ found = true;
+ }
+ else if (cmp < 0)
+ up = mid - 1;
+ else
+ low = mid + 1;
+ }
+
+ if (! found)
+ foundInd = cmp < 0 ? mid : mid + 1;
+ }
+ }
+ return foundInd;
+ }
+
+ private int searchFirst(int index)
+ {
+ searchMark.mark = Math.max(index, 1);
+ int i = search(searchMark);
+ for (int j = i - 1; j >= 0; j--)
+ {
+ Mark m = (Mark) marks.get(j);
+ if (m.mark != index)
break;
+ i--;
}
return i;
}
+
+ /**
+ * Compares two marks.
+ *
+ * @param m1 the first mark
+ * @param m2 the second mark
+ *
+ * @return negative when m1 < m2, positive when m1 > m2 and 0 when equal
+ */
+ private int compare(Mark m1, Mark m2)
+ {
+ return m1.mark - m2.mark;
+ }
+
+ /**
+ * Collects and frees unused marks.
+ */
+ private void garbageCollect()
+ {
+ int count = marks.size();
+ ArrayList clean = new ArrayList();
+ for (int i = 0; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.get() != null)
+ clean.add(m);
+ }
+ marks = clean;
+ garbageMarks = 0;
+ }
}
diff --git a/libjava/classpath/javax/swing/text/GlyphView.java b/libjava/classpath/javax/swing/text/GlyphView.java
index d505274..1e418d2 100644
--- a/libjava/classpath/javax/swing/text/GlyphView.java
+++ b/libjava/classpath/javax/swing/text/GlyphView.java
@@ -38,14 +38,21 @@ exception statement from your version. */
package javax.swing.text;
+import gnu.classpath.SystemProperties;
+
import java.awt.Color;
+import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
+import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
-import java.text.BreakIterator;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextHitInfo;
+import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
@@ -248,10 +255,164 @@ public class GlyphView extends View implements TabableView, Cloneable
}
/**
+ * A GlyphPainter implementation based on TextLayout. This should give
+ * better performance in Java2D environments.
+ */
+ private static class J2DGlyphPainter
+ extends GlyphPainter
+ {
+
+ /**
+ * The text layout.
+ */
+ TextLayout textLayout;
+
+ /**
+ * Creates a new J2DGlyphPainter.
+ *
+ * @param str the string
+ * @param font the font
+ * @param frc the font render context
+ */
+ J2DGlyphPainter(String str, Font font, FontRenderContext frc)
+ {
+ textLayout = new TextLayout(str, font, frc);
+ }
+
+ /**
+ * Returns null so that GlyphView.checkPainter() creates a new instance.
+ */
+ public GlyphPainter getPainter(GlyphView v, int p0, int p1)
+ {
+ return null;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getAscent(GlyphView v)
+ {
+ return textLayout.getAscent();
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public int getBoundedPosition(GlyphView v, int p0, float x, float len)
+ {
+ int pos;
+ TextHitInfo hit = textLayout.hitTestChar(len, 0);
+ if (hit.getCharIndex() == -1 && ! textLayout.isLeftToRight())
+ pos = v.getEndOffset();
+ else
+ {
+ pos = hit.isLeadingEdge() ? hit.getInsertionIndex()
+ : hit.getInsertionIndex() - 1;
+ pos += v.getStartOffset();
+ }
+ return pos;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getDescent(GlyphView v)
+ {
+ return textLayout.getDescent();
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getHeight(GlyphView view)
+ {
+ return textLayout.getAscent() + textLayout.getDescent()
+ + textLayout.getLeading();
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getSpan(GlyphView v, int p0, int p1, TabExpander te, float x)
+ {
+ float span;
+ if (p0 == v.getStartOffset() && p1 == v.getEndOffset())
+ span = textLayout.getAdvance();
+ else
+ {
+ int start = v.getStartOffset();
+ int i0 = p0 - start;
+ int i1 = p1 - start;
+ TextHitInfo hit0 = TextHitInfo.afterOffset(i0);
+ TextHitInfo hit1 = TextHitInfo.afterOffset(i1);
+ float x0 = textLayout.getCaretInfo(hit0)[0];
+ float x1 = textLayout.getCaretInfo(hit1)[0];
+ span = Math.abs(x1 - x0);
+ }
+ return span;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public Shape modelToView(GlyphView v, int pos, Bias b, Shape a)
+ throws BadLocationException
+ {
+ int offs = pos - v.getStartOffset();
+ // Create copy here to protect original shape.
+ Rectangle2D bounds = a.getBounds2D();
+ TextHitInfo hit =
+ b == Position.Bias.Forward ? TextHitInfo.afterOffset(offs)
+ : TextHitInfo.beforeOffset(offs);
+ float[] loc = textLayout.getCaretInfo(hit);
+ bounds.setRect(bounds.getX() + loc[0], bounds.getY(), 1,
+ bounds.getHeight());
+ return bounds;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1)
+ {
+ // Can't paint this with plain graphics.
+ if (g instanceof Graphics2D)
+ {
+ Graphics2D g2d = (Graphics2D) g;
+ Rectangle2D b = a instanceof Rectangle2D ? (Rectangle2D) a
+ : a.getBounds2D();
+ float x = (float) b.getX();
+ float y = (float) b.getY() + textLayout.getAscent()
+ + textLayout.getLeading();
+ // TODO: Try if clipping makes things faster for narrow views.
+ textLayout.draw(g2d, x, y);
+ }
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public int viewToModel(GlyphView v, float x, float y, Shape a,
+ Bias[] biasRet)
+ {
+ Rectangle2D bounds = a instanceof Rectangle2D ? (Rectangle2D) a
+ : a.getBounds2D();
+ TextHitInfo hit = textLayout.hitTestChar(x - (float) bounds.getX(), 0);
+ int pos = hit.getInsertionIndex();
+ biasRet[0] = hit.isLeadingEdge() ? Position.Bias.Forward
+ : Position.Bias.Backward;
+ return pos + v.getStartOffset();
+ }
+
+ }
+
+ /**
* The default <code>GlyphPainter</code> used in <code>GlyphView</code>.
*/
static class DefaultGlyphPainter extends GlyphPainter
{
+ FontMetrics fontMetrics;
+
/**
* Returns the full height of the rendered text.
*
@@ -259,9 +420,8 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public float getHeight(GlyphView view)
{
- Font font = view.getFont();
- FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
- float height = metrics.getHeight();
+ updateFontMetrics(view);
+ float height = fontMetrics.getHeight();
return height;
}
@@ -277,53 +437,27 @@ public class GlyphView extends View implements TabableView, Cloneable
public void paint(GlyphView view, Graphics g, Shape a, int p0,
int p1)
{
- Color oldColor = g.getColor();
- int height = (int) getHeight(view);
+ updateFontMetrics(view);
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ TabExpander tabEx = view.getTabExpander();
Segment txt = view.getText(p0, p1);
- Rectangle bounds = a.getBounds();
- TabExpander tabEx = null;
- View parent = view.getParent();
- if (parent instanceof TabExpander)
- tabEx = (TabExpander) parent;
-
- int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(),
- bounds.x, tabEx, txt.offset);
- // Fill the background of the text run.
- Color background = view.getBackground();
- if (background != null)
- {
- g.setColor(background);
- g.fillRect(bounds.x, bounds.y, width, height);
- }
- // Draw the actual text.
- g.setColor(view.getForeground());
- g.setFont(view.getFont());
- int ascent = g.getFontMetrics().getAscent();
- if (view.isSuperscript())
- // TODO: Adjust font for superscripting.
- Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent - height / 2,
- g, tabEx, txt.offset);
- else if (view.isSubscript())
- // TODO: Adjust font for subscripting.
- Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent + height / 2,
- g, tabEx, txt.offset);
- else
- Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx,
- txt.offset);
- if (view.isStrikeThrough())
- {
- int strikeHeight = (int) (getAscent(view) / 2);
- g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width,
- bounds.y + strikeHeight);
- }
- if (view.isUnderline())
+ // Find out the X location at which we have to paint.
+ int x = r.x;
+ int p = view.getStartOffset();
+ if (p != p0)
{
- int lineHeight = (int) getAscent(view);
- g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width,
- bounds.y + lineHeight);
+ int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx,
+ p);
+ x += width;
}
- g.setColor(oldColor);
+ // Find out Y location.
+ int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent();
+
+ // Render the thing.
+ g.setFont(fontMetrics.getFont());
+ Utilities.drawTabbedText(txt, x, y, g, tabEx, p0);
+
}
/**
@@ -350,15 +484,18 @@ public class GlyphView extends View implements TabableView, Cloneable
Shape a)
throws BadLocationException
{
+ updateFontMetrics(view);
Element el = view.getElement();
- Font font = view.getFont();
- FontMetrics fm = view.getContainer().getFontMetrics(font);
Segment txt = view.getText(el.getStartOffset(), pos);
- int width = fm.charsWidth(txt.array, txt.offset, txt.count);
- int height = fm.getHeight();
- Rectangle bounds = a.getBounds();
+ Rectangle bounds = a instanceof Rectangle ? (Rectangle) a
+ : a.getBounds();
+ TabExpander expander = view.getTabExpander();
+ int width = Utilities.getTabbedTextWidth(txt, fontMetrics, bounds.x,
+ expander,
+ view.getStartOffset());
+ int height = fontMetrics.getHeight();
Rectangle result = new Rectangle(bounds.x + width, bounds.y,
- bounds.x + width, height);
+ 0, height);
return result;
}
@@ -381,11 +518,10 @@ public class GlyphView extends View implements TabableView, Cloneable
public float getSpan(GlyphView view, int p0, int p1,
TabExpander te, float x)
{
- Element el = view.getElement();
- Font font = view.getFont();
- FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
+ updateFontMetrics(view);
Segment txt = view.getText(p0, p1);
- int span = Utilities.getTabbedTextWidth(txt, fm, (int) x, te, p0);
+ int span = Utilities.getTabbedTextWidth(txt, fontMetrics, (int) x, te,
+ p0);
return span;
}
@@ -402,9 +538,8 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public float getAscent(GlyphView v)
{
- Font font = v.getFont();
- FontMetrics fm = v.getContainer().getFontMetrics(font);
- return fm.getAscent();
+ updateFontMetrics(v);
+ return fontMetrics.getAscent();
}
/**
@@ -420,9 +555,8 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public float getDescent(GlyphView v)
{
- Font font = v.getFont();
- FontMetrics fm = v.getContainer().getFontMetrics(font);
- return fm.getDescent();
+ updateFontMetrics(v);
+ return fontMetrics.getDescent();
}
/**
@@ -437,13 +571,12 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public int getBoundedPosition(GlyphView v, int p0, float x, float len)
{
+ updateFontMetrics(v);
TabExpander te = v.getTabExpander();
Segment txt = v.getText(p0, v.getEndOffset());
- Font font = v.getFont();
- FontMetrics fm = v.getContainer().getFontMetrics(font);
- int pos = Utilities.getTabbedTextOffset(txt, fm, (int) x,
+ int pos = Utilities.getTabbedTextOffset(txt, fontMetrics, (int) x,
(int) (x + len), te, p0, false);
- return pos;
+ return pos + p0;
}
/**
@@ -460,9 +593,33 @@ public class GlyphView extends View implements TabableView, Cloneable
public int viewToModel(GlyphView v, float x, float y, Shape a,
Bias[] biasRet)
{
- Rectangle b = a.getBounds();
- int pos = getBoundedPosition(v, v.getStartOffset(), b.x, x - b.x);
- return pos;
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ int p0 = v.getStartOffset();
+ int p1 = v.getEndOffset();
+ TabExpander te = v.getTabExpander();
+ Segment s = v.getText(p0, p1);
+ int offset = Utilities.getTabbedTextOffset(s, fontMetrics, r.x, (int) x,
+ te, p0);
+ int ret = p0 + offset;
+ if (ret == p1)
+ ret--;
+ biasRet[0] = Position.Bias.Forward;
+ return ret;
+ }
+
+ private void updateFontMetrics(GlyphView v)
+ {
+ Font font = v.getFont();
+ if (fontMetrics == null || ! font.equals(fontMetrics.getFont()))
+ {
+ Container c = v.getContainer();
+ FontMetrics fm;
+ if (c != null)
+ fm = c.getFontMetrics(font);
+ else
+ fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
+ fontMetrics = fm;
+ }
}
}
@@ -474,12 +631,22 @@ public class GlyphView extends View implements TabableView, Cloneable
/**
* The start offset within the document for this view.
*/
- private int startOffset;
+ private int offset;
/**
* The end offset within the document for this view.
*/
- private int endOffset;
+ private int length;
+
+ /**
+ * The x location against which the tab expansion is done.
+ */
+ private float tabX;
+
+ /**
+ * The tab expander that is used in this view.
+ */
+ private TabExpander tabExpander;
/**
* Creates a new <code>GlyphView</code> for the given <code>Element</code>.
@@ -489,8 +656,8 @@ public class GlyphView extends View implements TabableView, Cloneable
public GlyphView(Element element)
{
super(element);
- startOffset = -1;
- endOffset = -1;
+ offset = 0;
+ length = 0;
}
/**
@@ -524,7 +691,21 @@ public class GlyphView extends View implements TabableView, Cloneable
protected void checkPainter()
{
if (glyphPainter == null)
- glyphPainter = new DefaultGlyphPainter();
+ {
+ if ("true".equals(
+ SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")))
+ {
+ glyphPainter = new DefaultGlyphPainter();
+ }
+ else
+ {
+ Segment s = getText(getStartOffset(), getEndOffset());
+ glyphPainter = new J2DGlyphPainter(s.toString(), getFont(),
+ new FontRenderContext(null,
+ false,
+ false));
+ }
+ }
}
/**
@@ -536,9 +717,80 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public void paint(Graphics g, Shape a)
{
- Element el = getElement();
checkPainter();
- getGlyphPainter().paint(this, g, a, getStartOffset(), getEndOffset());
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ Container c = getContainer();
+
+ Color fg = getForeground();
+ JTextComponent tc = null;
+ if (c instanceof JTextComponent)
+ {
+ tc = (JTextComponent) c;
+ if (! tc.isEnabled())
+ fg = tc.getDisabledTextColor();
+ }
+ Color bg = getBackground();
+ if (bg != null)
+ {
+ g.setColor(bg);
+ System.err.println("fill background: " + bg);
+ g.fillRect(r.x, r.y, r.width, r.height);
+ }
+
+
+ // Paint layered highlights if there are any.
+ if (tc != null)
+ {
+ Highlighter h = tc.getHighlighter();
+ if (h instanceof LayeredHighlighter)
+ {
+ LayeredHighlighter lh = (LayeredHighlighter) h;
+ lh.paintLayeredHighlights(g, p0, p1, a, tc, this);
+ }
+ }
+
+ g.setColor(fg);
+ glyphPainter.paint(this, g, a, p0, p1);
+ boolean underline = isUnderline();
+ boolean striked = isStrikeThrough();
+ if (underline || striked)
+ {
+ View parent = getParent();
+ // X coordinate.
+ if (parent != null && parent.getEndOffset() == p1)
+ {
+ // Strip whitespace.
+ Segment s = getText(p0, p1);
+ while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1]))
+ {
+ p1--;
+ s.count--;
+ }
+ }
+ int x0 = r.x;
+ int p = getStartOffset();
+ TabExpander tabEx = getTabExpander();
+ if (p != p0)
+ x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0);
+ int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0);
+ // Y coordinate.
+ int y = r.y + r.height - (int) glyphPainter.getDescent(this);
+ if (underline)
+ {
+ int yTmp = y;
+ yTmp += 1;
+ g.drawLine(x0, yTmp, x1, yTmp);
+ }
+ if (striked)
+ {
+ int yTmp = y;
+ yTmp -= (int) glyphPainter.getAscent(this);
+ g.drawLine(x0, yTmp, x1, yTmp);
+ }
+ }
}
@@ -555,19 +807,24 @@ public class GlyphView extends View implements TabableView, Cloneable
float span = 0;
checkPainter();
GlyphPainter painter = getGlyphPainter();
- if (axis == X_AXIS)
+ switch (axis)
{
- Element el = getElement();
+ case X_AXIS:
TabExpander tabEx = null;
View parent = getParent();
if (parent instanceof TabExpander)
tabEx = (TabExpander) parent;
span = painter.getSpan(this, getStartOffset(), getEndOffset(),
tabEx, 0.F);
+ break;
+ case Y_AXIS:
+ span = painter.getHeight(this);
+ if (isSuperscript())
+ span += span / 3;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal axis");
}
- else
- span = painter.getHeight(this);
-
return span;
}
@@ -623,13 +880,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public TabExpander getTabExpander()
{
- TabExpander te = null;
- View parent = getParent();
-
- if (parent instanceof TabExpander)
- te = (TabExpander) parent;
-
- return te;
+ return tabExpander;
}
/**
@@ -642,9 +893,17 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public float getTabbedSpan(float x, TabExpander te)
{
- Element el = getElement();
- return getGlyphPainter().getSpan(this, el.getStartOffset(),
- el.getEndOffset(), te, x);
+ checkPainter();
+ TabExpander old = tabExpander;
+ tabExpander = te;
+ if (tabExpander != old)
+ {
+ // Changing the tab expander will lead to a relayout in the X_AXIS.
+ preferenceChanged(null, true, false);
+ }
+ tabX = x;
+ return getGlyphPainter().getSpan(this, getStartOffset(),
+ getEndOffset(), tabExpander, x);
}
/**
@@ -658,23 +917,8 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public float getPartialSpan(int p0, int p1)
{
- Element el = getElement();
- Document doc = el.getDocument();
- Segment seg = new Segment();
- try
- {
- doc.getText(p0, p1 - p0, seg);
- }
- catch (BadLocationException ex)
- {
- AssertionError ae;
- ae = new AssertionError("BadLocationException must not be thrown "
- + "here");
- ae.initCause(ex);
- throw ae;
- }
- FontMetrics fm = null; // Fetch font metrics somewhere.
- return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0);
+ checkPainter();
+ return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX);
}
/**
@@ -686,10 +930,11 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public int getStartOffset()
{
- int start = startOffset;
- if (start < 0)
- start = super.getStartOffset();
- return start;
+ Element el = getElement();
+ int offs = el.getStartOffset();
+ if (length > 0)
+ offs += offset;
+ return offs;
}
/**
@@ -701,12 +946,17 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public int getEndOffset()
{
- int end = endOffset;
- if (end < 0)
- end = super.getEndOffset();
- return end;
+ Element el = getElement();
+ int offs;
+ if (length > 0)
+ offs = el.getStartOffset() + offset + length;
+ else
+ offs = el.getEndOffset();
+ return offs;
}
+ private Segment cached = new Segment();
+
/**
* Returns the text segment that this view is responsible for.
*
@@ -717,10 +967,9 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public Segment getText(int p0, int p1)
{
- Segment txt = new Segment();
try
{
- getDocument().getText(p0, p1 - p0, txt);
+ getDocument().getText(p0, p1 - p0, cached);
}
catch (BadLocationException ex)
{
@@ -731,7 +980,7 @@ public class GlyphView extends View implements TabableView, Cloneable
throw ae;
}
- return txt;
+ return cached;
}
/**
@@ -743,16 +992,19 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public Font getFont()
{
- Element el = getElement();
- AttributeSet atts = el.getAttributes();
- String family = StyleConstants.getFontFamily(atts);
- int size = StyleConstants.getFontSize(atts);
- int style = Font.PLAIN;
- if (StyleConstants.isBold(atts))
- style |= Font.BOLD;
- if (StyleConstants.isItalic(atts))
- style |= Font.ITALIC;
- Font font = new Font(family, style, size);
+ Document doc = getDocument();
+ Font font = null;
+ if (doc instanceof StyledDocument)
+ {
+ StyledDocument styledDoc = (StyledDocument) doc;
+ font = styledDoc.getFont(getAttributes());
+ }
+ else
+ {
+ Container c = getContainer();
+ if (c != null)
+ font = c.getFont();
+ }
return font;
}
@@ -885,33 +1137,21 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public View breakView(int axis, int p0, float pos, float len)
{
- if (axis == Y_AXIS)
- return this;
-
- checkPainter();
- GlyphPainter painter = getGlyphPainter();
-
- // Try to find a suitable line break.
- BreakIterator lineBreaker = BreakIterator.getLineInstance();
- Segment txt = new Segment();
- try
- {
- int start = getStartOffset();
- int length = getEndOffset() - start;
- getDocument().getText(start, length, txt);
- }
- catch (BadLocationException ex)
+ View brokenView = this;
+ if (axis == X_AXIS)
{
- AssertionError err = new AssertionError("BadLocationException must not "
- + "be thrown here.");
- err.initCause(ex);
- throw err;
+ checkPainter();
+ int end = glyphPainter.getBoundedPosition(this, p0, pos, len);
+ int breakLoc = getBreakLocation(p0, end);
+ if (breakLoc != -1)
+ end = breakLoc;
+ if (p0 != getStartOffset() || end != getEndOffset())
+ {
+ brokenView = createFragment(p0, end);
+ if (brokenView instanceof GlyphView)
+ ((GlyphView) brokenView).tabX = pos;
+ }
}
- int breakLocation =
- Utilities.getBreakLocation(txt, getContainer().getFontMetrics(getFont()),
- (int) pos, (int) (pos + len),
- getTabExpander(), p0);
- View brokenView = createFragment(p0, breakLocation);
return brokenView;
}
@@ -937,28 +1177,36 @@ public class GlyphView extends View implements TabableView, Cloneable
weight = super.getBreakWeight(axis, pos, len);
else
{
- // FIXME: Commented out because the Utilities.getBreakLocation method
- // is still buggy. The GoodBreakWeight is a reasonable workaround for
- // now.
-// int startOffset = getStartOffset();
-// int endOffset = getEndOffset() - 1;
-// Segment s = getText(startOffset, endOffset);
-// Container c = getContainer();
-// FontMetrics fm = c.getFontMetrics(c.getFont());
-// int x0 = (int) pos;
-// int x = (int) (pos + len);
-// int breakLoc = Utilities.getBreakLocation(s, fm, x0, x,
-// getTabExpander(),
-// startOffset);
-// if (breakLoc == startOffset || breakLoc == endOffset)
-// weight = GoodBreakWeight;
-// else
-// weight = ExcellentBreakWeight;
- weight = GoodBreakWeight;
+ checkPainter();
+ int start = getStartOffset();
+ int end = glyphPainter.getBoundedPosition(this, start, pos, len);
+ if (end == 0)
+ weight = BadBreakWeight;
+ else
+ {
+ if (getBreakLocation(start, end) != -1)
+ weight = ExcellentBreakWeight;
+ else
+ weight = GoodBreakWeight;
+ }
}
return weight;
}
+ private int getBreakLocation(int start, int end)
+ {
+ int loc = -1;
+ Segment s = getText(start, end);
+ for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous())
+ {
+ if (Character.isWhitespace(c))
+ {
+ loc = s.getIndex() - s.getBeginIndex() + 1 + start;
+ }
+ }
+ return loc;
+ }
+
/**
* Receives notification that some text attributes have changed within the
* text fragment that this view is responsible for. This calls
@@ -971,7 +1219,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
{
- preferenceChanged(this, true, true);
+ preferenceChanged(null, true, true);
}
/**
@@ -986,7 +1234,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
{
- preferenceChanged(this, true, false);
+ preferenceChanged(null, true, false);
}
/**
@@ -1001,7 +1249,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
{
- preferenceChanged(this, true, false);
+ preferenceChanged(null, true, false);
}
/**
@@ -1015,11 +1263,12 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public View createFragment(int p0, int p1)
{
+ checkPainter();
+ Element el = getElement();
GlyphView fragment = (GlyphView) clone();
- if (p0 != getStartOffset())
- fragment.startOffset = p0;
- if (p1 != getEndOffset())
- fragment.endOffset = p1;
+ fragment.offset = p0 - el.getStartOffset();
+ fragment.length = p1 - p0;
+ fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1);
return fragment;
}
@@ -1031,14 +1280,21 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public float getAlignment(int axis)
{
+ checkPainter();
float align;
if (axis == Y_AXIS)
{
- checkPainter();
GlyphPainter painter = getGlyphPainter();
float height = painter.getHeight(this);
float descent = painter.getDescent(this);
- align = (height - descent) / height;
+ float ascent = painter.getAscent(this);
+ if (isSuperscript())
+ align = 1.0F;
+ else if (isSubscript())
+ align = height > 0 ? (height - (descent + (ascent / 2))) / height
+ : 0;
+ else
+ align = height > 0 ? (height - descent) / height : 0;
}
else
align = super.getAlignment(axis);
diff --git a/libjava/classpath/javax/swing/text/InternationalFormatter.java b/libjava/classpath/javax/swing/text/InternationalFormatter.java
index 8db435c..d6f2359 100644
--- a/libjava/classpath/javax/swing/text/InternationalFormatter.java
+++ b/libjava/classpath/javax/swing/text/InternationalFormatter.java
@@ -285,7 +285,7 @@ public class InternationalFormatter
if (minimum != null && minimum.compareTo(o) > 0)
throw new ParseException("The value may not be less than the"
+ " specified minimum", 0);
- if (maximum != null && minimum.compareTo(o) < 0)
+ if (maximum != null && maximum.compareTo(o) < 0)
throw new ParseException("The value may not be greater than the"
+ " specified maximum", 0);
return o;
diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java
index 6da84bf..68ba1f4 100644
--- a/libjava/classpath/javax/swing/text/JTextComponent.java
+++ b/libjava/classpath/javax/swing/text/JTextComponent.java
@@ -38,8 +38,6 @@ exception statement from your version. */
package javax.swing.text;
-import gnu.classpath.NotImplementedException;
-
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Container;
@@ -47,6 +45,7 @@ import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
+import java.awt.Shape;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
@@ -59,6 +58,7 @@ import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
+import java.text.BreakIterator;
import java.util.Enumeration;
import java.util.Hashtable;
@@ -67,6 +67,7 @@ import javax.accessibility.AccessibleAction;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleEditableText;
import javax.accessibility.AccessibleRole;
+import javax.accessibility.AccessibleState;
import javax.accessibility.AccessibleStateSet;
import javax.accessibility.AccessibleText;
import javax.swing.Action;
@@ -105,12 +106,7 @@ public abstract class JTextComponent extends JComponent
/**
* The caret's offset.
*/
- int dot = 0;
-
- /**
- * The current JTextComponent.
- */
- JTextComponent textComp = JTextComponent.this;
+ private int caretDot;
/**
* Construct an AccessibleJTextComponent.
@@ -118,7 +114,8 @@ public abstract class JTextComponent extends JComponent
public AccessibleJTextComponent()
{
super();
- textComp.addCaretListener(this);
+ JTextComponent.this.addCaretListener(this);
+ caretDot = getCaretPosition();
}
/**
@@ -129,8 +126,7 @@ public abstract class JTextComponent extends JComponent
*/
public int getCaretPosition()
{
- dot = textComp.getCaretPosition();
- return dot;
+ return JTextComponent.this.getCaretPosition();
}
/**
@@ -141,7 +137,7 @@ public abstract class JTextComponent extends JComponent
*/
public String getSelectedText()
{
- return textComp.getSelectedText();
+ return JTextComponent.this.getSelectedText();
}
/**
@@ -156,9 +152,10 @@ public abstract class JTextComponent extends JComponent
*/
public int getSelectionStart()
{
- if (getSelectedText() == null || (textComp.getText().equals("")))
+ if (getSelectedText() == null
+ || (JTextComponent.this.getText().equals("")))
return 0;
- return textComp.getSelectionStart();
+ return JTextComponent.this.getSelectionStart();
}
/**
@@ -173,9 +170,7 @@ public abstract class JTextComponent extends JComponent
*/
public int getSelectionEnd()
{
- if (getSelectedText() == null || (textComp.getText().equals("")))
- return 0;
- return textComp.getSelectionEnd();
+ return JTextComponent.this.getSelectionEnd();
}
/**
@@ -185,10 +180,20 @@ public abstract class JTextComponent extends JComponent
* @param e - the caret update event
*/
public void caretUpdate(CaretEvent e)
- throws NotImplementedException
{
- // TODO: fire appropriate event.
- dot = e.getDot();
+ int dot = e.getDot();
+ int mark = e.getMark();
+ if (caretDot != dot)
+ {
+ firePropertyChange(ACCESSIBLE_CARET_PROPERTY, new Integer(caretDot),
+ new Integer(dot));
+ caretDot = dot;
+ }
+ if (mark != dot)
+ {
+ firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null,
+ getSelectedText());
+ }
}
/**
@@ -197,10 +202,10 @@ public abstract class JTextComponent extends JComponent
* @return the accessible state set of this component
*/
public AccessibleStateSet getAccessibleStateSet()
- throws NotImplementedException
{
AccessibleStateSet state = super.getAccessibleStateSet();
- // TODO: Figure out what state must be added here to the super's state.
+ if (isEditable())
+ state.add(AccessibleState.EDITABLE);
return state;
}
@@ -248,9 +253,9 @@ public abstract class JTextComponent extends JComponent
* @param e - the insertion event
*/
public void insertUpdate(DocumentEvent e)
- throws NotImplementedException
{
- // TODO
+ firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
+ new Integer(e.getOffset()));
}
/**
@@ -261,9 +266,9 @@ public abstract class JTextComponent extends JComponent
* @param e - the removal event
*/
public void removeUpdate(DocumentEvent e)
- throws NotImplementedException
{
- // TODO
+ firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
+ new Integer(e.getOffset()));
}
/**
@@ -274,9 +279,9 @@ public abstract class JTextComponent extends JComponent
* @param e - text change event
*/
public void changedUpdate(DocumentEvent e)
- throws NotImplementedException
{
- // TODO
+ firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
+ new Integer(e.getOffset()));
}
/**
@@ -289,9 +294,8 @@ public abstract class JTextComponent extends JComponent
* @return a character index, or -1
*/
public int getIndexAtPoint(Point p)
- throws NotImplementedException
{
- return 0; // TODO
+ return viewToModel(p);
}
/**
@@ -305,9 +309,51 @@ public abstract class JTextComponent extends JComponent
* @return a character's bounding box, or null
*/
public Rectangle getCharacterBounds(int index)
- throws NotImplementedException
{
- return null; // TODO
+ // This is basically the same as BasicTextUI.modelToView().
+
+ Rectangle bounds = null;
+ if (index >= 0 && index < doc.getLength() - 1)
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ TextUI ui = getUI();
+ if (ui != null)
+ {
+ // Get editor rectangle.
+ Rectangle rect = new Rectangle();
+ Insets insets = getInsets();
+ rect.x = insets.left;
+ rect.y = insets.top;
+ rect.width = getWidth() - insets.left - insets.right;
+ rect.height = getHeight() - insets.top - insets.bottom;
+ View rootView = ui.getRootView(JTextComponent.this);
+ if (rootView != null)
+ {
+ rootView.setSize(rect.width, rect.height);
+ Shape s = rootView.modelToView(index,
+ Position.Bias.Forward,
+ index + 1,
+ Position.Bias.Backward,
+ rect);
+ if (s != null)
+ bounds = s.getBounds();
+ }
+ }
+ }
+ catch (BadLocationException ex)
+ {
+ // Ignore (return null).
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ }
+ return bounds;
}
/**
@@ -317,7 +363,7 @@ public abstract class JTextComponent extends JComponent
*/
public int getCharCount()
{
- return textComp.getText().length();
+ return JTextComponent.this.getText().length();
}
/**
@@ -329,9 +375,26 @@ public abstract class JTextComponent extends JComponent
* @return the character's attributes
*/
public AttributeSet getCharacterAttribute(int index)
- throws NotImplementedException
{
- return null; // TODO
+ AttributeSet atts;
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ Element el = doc.getDefaultRootElement();
+ while (! el.isLeaf())
+ {
+ int i = el.getElementIndex(index);
+ el = el.getElement(i);
+ }
+ atts = el.getAttributes();
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ return atts;
}
/**
@@ -344,9 +407,8 @@ public abstract class JTextComponent extends JComponent
* @return the part of text at that index, or null
*/
public String getAtIndex(int part, int index)
- throws NotImplementedException
{
- return null; // TODO
+ return getAtIndexImpl(part, index, 0);
}
/**
@@ -359,9 +421,8 @@ public abstract class JTextComponent extends JComponent
* @return the part of text after that index, or null
*/
public String getAfterIndex(int part, int index)
- throws NotImplementedException
{
- return null; // TODO
+ return getAtIndexImpl(part, index, 1);
}
/**
@@ -374,11 +435,84 @@ public abstract class JTextComponent extends JComponent
* @return the part of text before that index, or null
*/
public String getBeforeIndex(int part, int index)
- throws NotImplementedException
{
- return null; // TODO
+ return getAtIndexImpl(part, index, -1);
}
-
+
+ /**
+ * Implements getAtIndex(), getBeforeIndex() and getAfterIndex().
+ *
+ * @param part the part to return, either CHARACTER, WORD or SENTENCE
+ * @param index the index
+ * @param dir the direction, -1 for backwards, 0 for here, +1 for forwards
+ *
+ * @return the resulting string
+ */
+ private String getAtIndexImpl(int part, int index, int dir)
+ {
+ String ret = null;
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ BreakIterator iter = null;
+ switch (part)
+ {
+ case CHARACTER:
+ iter = BreakIterator.getCharacterInstance(getLocale());
+ break;
+ case WORD:
+ iter = BreakIterator.getWordInstance(getLocale());
+ break;
+ case SENTENCE:
+ iter = BreakIterator.getSentenceInstance(getLocale());
+ break;
+ default:
+ break;
+ }
+ String text = doc.getText(0, doc.getLength() - 1);
+ iter.setText(text);
+ int start = index;
+ int end = index;
+ switch (dir)
+ {
+ case 0:
+ if (iter.isBoundary(index))
+ {
+ start = index;
+ end = iter.following(index);
+ }
+ else
+ {
+ start = iter.preceding(index);
+ end = iter.next();
+ }
+ break;
+ case 1:
+ start = iter.following(index);
+ end = iter.next();
+ break;
+ case -1:
+ end = iter.preceding(index);
+ start = iter.previous();
+ break;
+ default:
+ assert false;
+ }
+ ret = text.substring(start, end);
+ }
+ catch (BadLocationException ex)
+ {
+ // Ignore (return null).
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ return ret;
+ }
+
/**
* Returns the number of actions for this object. The zero-th
* object represents the default action.
@@ -386,9 +520,8 @@ public abstract class JTextComponent extends JComponent
* @return the number of actions (0-based).
*/
public int getAccessibleActionCount()
- throws NotImplementedException
{
- return 0; // TODO
+ return getActions().length;
}
/**
@@ -400,10 +533,12 @@ public abstract class JTextComponent extends JComponent
* @return description of the i-th action
*/
public String getAccessibleActionDescription(int i)
- throws NotImplementedException
{
- // TODO: Not implemented fully
- return super.getAccessibleDescription();
+ String desc = null;
+ Action[] actions = getActions();
+ if (i >= 0 && i < actions.length)
+ desc = (String) actions[i].getValue(Action.NAME);
+ return desc;
}
/**
@@ -415,9 +550,17 @@ public abstract class JTextComponent extends JComponent
* @return true if the action was performed successfully
*/
public boolean doAccessibleAction(int i)
- throws NotImplementedException
{
- return false; // TODO
+ boolean ret = false;
+ Action[] actions = getActions();
+ if (i >= 0 && i < actions.length)
+ {
+ ActionEvent ev = new ActionEvent(JTextComponent.this,
+ ActionEvent.ACTION_PERFORMED, null);
+ actions[i].actionPerformed(ev);
+ ret = true;
+ }
+ return ret;
}
/**
@@ -426,9 +569,8 @@ public abstract class JTextComponent extends JComponent
* @param s - the new text contents.
*/
public void setTextContents(String s)
- throws NotImplementedException
{
- // TODO
+ setText(s);
}
/**
@@ -438,9 +580,16 @@ public abstract class JTextComponent extends JComponent
* @param s - the new text
*/
public void insertTextAtIndex(int index, String s)
- throws NotImplementedException
{
- replaceText(index, index, s);
+ try
+ {
+ doc.insertString(index, s, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // What should we do with this?
+ ex.printStackTrace();
+ }
}
/**
@@ -453,7 +602,7 @@ public abstract class JTextComponent extends JComponent
{
try
{
- return textComp.getText(start, end - start);
+ return JTextComponent.this.getText(start, end - start);
}
catch (BadLocationException ble)
{
@@ -481,8 +630,8 @@ public abstract class JTextComponent extends JComponent
*/
public void cut(int start, int end)
{
- textComp.select(start, end);
- textComp.cut();
+ JTextComponent.this.select(start, end);
+ JTextComponent.this.cut();
}
/**
@@ -492,8 +641,8 @@ public abstract class JTextComponent extends JComponent
*/
public void paste(int start)
{
- textComp.setCaretPosition(start);
- textComp.paste();
+ JTextComponent.this.setCaretPosition(start);
+ JTextComponent.this.paste();
}
/**
@@ -506,8 +655,8 @@ public abstract class JTextComponent extends JComponent
*/
public void replaceText(int start, int end, String s)
{
- textComp.select(start, end);
- textComp.replaceSelection(s);
+ JTextComponent.this.select(start, end);
+ JTextComponent.this.replaceSelection(s);
}
/**
@@ -518,7 +667,7 @@ public abstract class JTextComponent extends JComponent
*/
public void selectText(int start, int end)
{
- textComp.select(start, end);
+ JTextComponent.this.select(start, end);
}
/**
@@ -529,9 +678,12 @@ public abstract class JTextComponent extends JComponent
* @param s - the new attribute set for the text in the range
*/
public void setAttributes(int start, int end, AttributeSet s)
- throws NotImplementedException
{
- // TODO
+ if (doc instanceof StyledDocument)
+ {
+ StyledDocument sdoc = (StyledDocument) doc;
+ sdoc.setCharacterAttributes(start, end - start, s, true);
+ }
}
}
diff --git a/libjava/classpath/javax/swing/text/LabelView.java b/libjava/classpath/javax/swing/text/LabelView.java
index 03279c4..7cfeae8 100644
--- a/libjava/classpath/javax/swing/text/LabelView.java
+++ b/libjava/classpath/javax/swing/text/LabelView.java
@@ -39,9 +39,11 @@ exception statement from your version. */
package javax.swing.text;
import java.awt.Color;
+import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Shape;
+import java.awt.Toolkit;
import javax.swing.event.DocumentEvent;
@@ -90,6 +92,11 @@ public class LabelView extends GlyphView
boolean superscript;
/**
+ * Indicates if the attributes must be refetched.
+ */
+ private boolean valid;
+
+ /**
* Creates a new <code>GlyphView</code> for the given <code>Element</code>.
*
* @param element the element that is rendered by this GlyphView
@@ -97,7 +104,7 @@ public class LabelView extends GlyphView
public LabelView(Element element)
{
super(element);
- setPropertiesFromAttributes();
+ valid = false;
}
/**
@@ -107,28 +114,25 @@ public class LabelView extends GlyphView
*/
protected void setPropertiesFromAttributes()
{
- Element el = getElement();
- AttributeSet atts = el.getAttributes();
- // We cannot use StyleConstants.getBackground() here, because that returns
- // BLACK as default (when background == null). What we need is the
- // background setting of the text component instead, which is what we get
- // when background == null anyway.
- background = (Color) atts.getAttribute(StyleConstants.Background);
- foreground = StyleConstants.getForeground(atts);
- strikeThrough = StyleConstants.isStrikeThrough(atts);
- subscript = StyleConstants.isSubscript(atts);
- superscript = StyleConstants.isSuperscript(atts);
- underline = StyleConstants.isUnderline(atts);
-
- // Determine the font.
- String family = StyleConstants.getFontFamily(atts);
- int size = StyleConstants.getFontSize(atts);
- int style = Font.PLAIN;
- if (StyleConstants.isBold(atts))
- style |= Font.BOLD;
- if (StyleConstants.isItalic(atts))
- style |= Font.ITALIC;
- font = new Font(family, style, size);
+ AttributeSet atts = getAttributes();
+ setStrikeThrough(StyleConstants.isStrikeThrough(atts));
+ setSubscript(StyleConstants.isSubscript(atts));
+ setSuperscript(StyleConstants.isSuperscript(atts));
+ setUnderline(StyleConstants.isUnderline(atts));
+
+ // Determine the font and colors.
+ Document d = getDocument();
+ if (d instanceof StyledDocument)
+ {
+ StyledDocument doc = (StyledDocument) d;
+ font = doc.getFont(atts);
+ if (atts.isDefined(StyleConstants.Background))
+ background = doc.getBackground(atts);
+ else
+ background = null;
+ foreground = doc.getForeground(atts);
+ }
+ valid = true;
}
/**
@@ -142,7 +146,8 @@ public class LabelView extends GlyphView
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
{
- setPropertiesFromAttributes();
+ valid = false;
+ super.changedUpdate(e, a, vf);
}
/**
@@ -152,6 +157,8 @@ public class LabelView extends GlyphView
*/
public Color getBackground()
{
+ if (! valid)
+ setPropertiesFromAttributes();
return background;
}
@@ -175,6 +182,8 @@ public class LabelView extends GlyphView
*/
public Color getForeground()
{
+ if (! valid)
+ setPropertiesFromAttributes();
return foreground;
}
@@ -185,6 +194,8 @@ public class LabelView extends GlyphView
*/
public Font getFont()
{
+ if (! valid)
+ setPropertiesFromAttributes();
return font;
}
@@ -197,7 +208,16 @@ public class LabelView extends GlyphView
*/
protected FontMetrics getFontMetrics()
{
- return getContainer().getGraphics().getFontMetrics(font);
+ if (! valid)
+ setPropertiesFromAttributes();
+
+ Container c = getContainer();
+ FontMetrics fm;
+ if (c != null)
+ fm = c.getFontMetrics(font);
+ else
+ fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
+ return fm;
}
/**
@@ -209,6 +229,8 @@ public class LabelView extends GlyphView
*/
public boolean isUnderline()
{
+ if (! valid)
+ setPropertiesFromAttributes();
return underline;
}
@@ -232,6 +254,8 @@ public class LabelView extends GlyphView
*/
public boolean isSubscript()
{
+ if (! valid)
+ setPropertiesFromAttributes();
return subscript;
}
@@ -255,6 +279,8 @@ public class LabelView extends GlyphView
*/
public boolean isSuperscript()
{
+ if (! valid)
+ setPropertiesFromAttributes();
return superscript;
}
@@ -278,6 +304,8 @@ public class LabelView extends GlyphView
*/
public boolean isStrikeThrough()
{
+ if (! valid)
+ setPropertiesFromAttributes();
return strikeThrough;
}
diff --git a/libjava/classpath/javax/swing/text/MaskFormatter.java b/libjava/classpath/javax/swing/text/MaskFormatter.java
index d12b9ea..581cceb 100644
--- a/libjava/classpath/javax/swing/text/MaskFormatter.java
+++ b/libjava/classpath/javax/swing/text/MaskFormatter.java
@@ -110,9 +110,7 @@ public class MaskFormatter extends DefaultFormatter
*/
public MaskFormatter (String mask) throws java.text.ParseException
{
- // Override super's default behaviour, in MaskFormatter the default
- // is not to allow invalid values
- setAllowsInvalid(false);
+ this();
setMask (mask);
}
@@ -307,60 +305,124 @@ public class MaskFormatter extends DefaultFormatter
*/
public Object stringToValue (String value) throws ParseException
{
- int vLength = value.length();
-
- // For value to be a valid it must be the same length as the mask
- // note this doesn't take into account symbols that occupy more than
- // one character, this is something we may possibly need to fix.
- if (maskLength != vLength)
- throw new ParseException ("stringToValue passed invalid value", vLength);
-
- // Check if the value is valid according to the mask and valid/invalid
- // sets.
- try
- {
- convertValue(value, false);
- }
- catch (ParseException pe)
- {
- throw new ParseException("stringToValue passed invalid value",
- pe.getErrorOffset());
- }
-
- if (!getValueContainsLiteralCharacters())
- value = stripLiterals(value);
- return super.stringToValue(value);
+ return super.stringToValue(convertStringToValue(value));
}
- /**
- * Strips the literal characters from the given String.
- * @param value the String to strip
- * @return the stripped String
- */
- String stripLiterals(String value)
+ private String convertStringToValue(String value)
+ throws ParseException
{
StringBuffer result = new StringBuffer();
- for (int i = 0; i < value.length(); i++)
+ char valueChar;
+ boolean isPlaceHolder;
+
+ int length = mask.length();
+ for (int i = 0, j = 0; j < length; j++)
{
- // Only append the characters that don't correspond to literal
- // characters in the mask.
- switch (mask.charAt(i))
+ char maskChar = mask.charAt(j);
+
+ if (i < value.length())
+ {
+ isPlaceHolder = false;
+ valueChar = value.charAt(i);
+ if (maskChar != ESCAPE_CHAR && maskChar != valueChar)
+ {
+ if (invalidChars != null
+ && invalidChars.indexOf(valueChar) != -1)
+ throw new ParseException("Invalid character: " + valueChar, i);
+ if (validChars != null
+ && validChars.indexOf(valueChar) == -1)
+ throw new ParseException("Invalid character: " + valueChar, i);
+ }
+ }
+ else if (placeHolder != null && i < placeHolder.length())
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolder.charAt(i);
+ }
+ else
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolderChar;
+ }
+
+ // This switch block on the mask character checks that the character
+ // within <code>value</code> at that point is valid according to the
+ // mask and also converts to upper/lowercase as needed.
+ switch (maskChar)
{
case NUM_CHAR:
+ if (! Character.isDigit(valueChar))
+ throw new ParseException("Number expected: " + valueChar, i);
+ result.append(valueChar);
+ i++;
+ break;
case UPPERCASE_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(Character.toUpperCase(valueChar));
+ i++;
+ break;
case LOWERCASE_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(Character.toLowerCase(valueChar));
+ i++;
+ break;
case ALPHANUM_CHAR:
+ if (! Character.isLetterOrDigit(valueChar))
+ throw new ParseException("Letter or number expected", i);
+ result.append(valueChar);
+ i++;
+ break;
case LETTER_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(valueChar);
+ i++;
+ break;
case HEX_CHAR:
+ if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
+ throw new ParseException("Hexadecimal character expected", i);
+ result.append(valueChar);
+ i++;
+ break;
case ANYTHING_CHAR:
- result.append(value.charAt(i));
+ result.append(valueChar);
+ i++;
+ break;
+ case ESCAPE_CHAR:
+ // Escape character, check the next character to make sure that
+ // the literals match
+ j++;
+ if (j < length)
+ {
+ maskChar = mask.charAt(j);
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ {
+ result.append(maskChar);
+ }
+ i++;
+ }
+ else if (! isPlaceHolder)
+ throw new ParseException("Bad match at trailing escape: ", i);
break;
default:
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ {
+ result.append(maskChar);
+ }
+ i++;
}
}
return result.toString();
}
-
+
/**
* Returns a String representation of the Object value based on the mask.
*
@@ -368,21 +430,10 @@ public class MaskFormatter extends DefaultFormatter
* @throws ParseException if value is invalid for this mask and valid/invalid
* character sets
*/
- public String valueToString (Object value) throws ParseException
+ public String valueToString(Object value) throws ParseException
{
- String result = super.valueToString(value);
- int rLength = result.length();
-
- // If value is longer than the mask, truncate it. Note we may need to
- // account for symbols that are more than one character long.
- if (rLength > maskLength)
- result = result.substring(0, maskLength);
-
- // Verify the validity and convert to upper/lowercase as needed.
- result = convertValue(result, true);
- if (rLength < maskLength)
- return pad(result, rLength);
- return result;
+ String string = value != null ? value.toString() : "";
+ return convertValueToString(string);
}
/**
@@ -390,194 +441,116 @@ public class MaskFormatter extends DefaultFormatter
* sure that it is valid. If <code>convert</code> is true, it also
* converts letters to upper/lowercase as required by the mask.
* @param value the String to convert
- * @param convert true if we should convert letters to upper/lowercase
* @return the converted String
* @throws ParseException if the given String isn't valid for the mask
*/
- String convertValue(String value, boolean convert) throws ParseException
+ private String convertValueToString(String value)
+ throws ParseException
{
- StringBuffer result = new StringBuffer(value);
- char markChar;
- char resultChar;
- boolean literal;
-
- // this boolean is specifically to avoid calling the isCharValid method
- // when neither invalidChars or validChars has been set
- boolean checkCharSets = (invalidChars != null || validChars != null);
+ StringBuffer result = new StringBuffer();
+ char valueChar;
+ boolean isPlaceHolder;
- for (int i = 0, j = 0; i < value.length(); i++, j++)
+ int length = mask.length();
+ for (int i = 0, j = 0; j < length; j++)
{
- literal = false;
- resultChar = result.charAt(i);
+ char maskChar = mask.charAt(j);
+ if (i < value.length())
+ {
+ isPlaceHolder = false;
+ valueChar = value.charAt(i);
+ if (maskChar != ESCAPE_CHAR && valueChar != maskChar)
+ {
+ if (invalidChars != null
+ && invalidChars.indexOf(valueChar) != -1)
+ throw new ParseException("Invalid character: " + valueChar,
+ i);
+ if (validChars != null && validChars.indexOf(valueChar) == -1)
+ throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar,
+ i);
+ }
+ }
+ else if (placeHolder != null && i < placeHolder.length())
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolder.charAt(i);
+ }
+ else
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolderChar;
+ }
+
// This switch block on the mask character checks that the character
// within <code>value</code> at that point is valid according to the
// mask and also converts to upper/lowercase as needed.
- switch (mask.charAt(j))
+ switch (maskChar)
{
case NUM_CHAR:
- if (!Character.isDigit(resultChar))
- throw new ParseException("Number expected", i);
+ if ( ! isPlaceHolder && ! Character.isDigit(valueChar))
+ throw new ParseException("Number expected: " + valueChar, i);
+ result.append(valueChar);
+ i++;
break;
case UPPERCASE_CHAR:
- if (!Character.isLetter(resultChar))
+ if (! Character.isLetter(valueChar))
throw new ParseException("Letter expected", i);
- if (convert)
- result.setCharAt(i, Character.toUpperCase(resultChar));
+ result.append(Character.toUpperCase(valueChar));
+ i++;
break;
case LOWERCASE_CHAR:
- if (!Character.isLetter(resultChar))
+ if (! Character.isLetter(valueChar))
throw new ParseException("Letter expected", i);
- if (convert)
- result.setCharAt(i, Character.toLowerCase(resultChar));
+ result.append(Character.toLowerCase(valueChar));
+ i++;
break;
case ALPHANUM_CHAR:
- if (!Character.isLetterOrDigit(resultChar))
+ if (! Character.isLetterOrDigit(valueChar))
throw new ParseException("Letter or number expected", i);
+ result.append(valueChar);
+ i++;
break;
case LETTER_CHAR:
- if (!Character.isLetter(resultChar))
+ if (! Character.isLetter(valueChar))
throw new ParseException("Letter expected", i);
+ result.append(valueChar);
+ i++;
break;
case HEX_CHAR:
- if (hexString.indexOf(resultChar) == -1)
+ if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
throw new ParseException("Hexadecimal character expected", i);
+ result.append(valueChar);
+ i++;
break;
case ANYTHING_CHAR:
+ result.append(valueChar);
+ i++;
break;
case ESCAPE_CHAR:
// Escape character, check the next character to make sure that
// the literals match
j++;
- literal = true;
- if (resultChar != mask.charAt(j))
- throw new ParseException ("Invalid character: "+resultChar, i);
+ if (j < length)
+ {
+ maskChar = mask.charAt(j);
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ i++;
+ result.append(maskChar);
+ }
break;
default:
- literal = true;
- if (!getValueContainsLiteralCharacters() && convert)
- throw new ParseException ("Invalid character: "+resultChar, i);
- else if (resultChar != mask.charAt(j))
- throw new ParseException ("Invalid character: "+resultChar, i);
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ i++;
+ result.append(maskChar);
}
- // If necessary, check if the character is valid.
- if (!literal && checkCharSets && !isCharValid(resultChar))
- throw new ParseException("invalid character: "+resultChar, i);
-
- }
- return result.toString();
- }
-
- /**
- * Convenience method used by many other methods to check if a character is
- * valid according to the mask, the validChars, and the invalidChars. To
- * be valid a character must:
- * 1. be allowed by the mask
- * 2. be present in any non-null validChars String
- * 3. not be present in any non-null invalidChars String
- * @param testChar the character to test
- * @return true if the character is valid
- */
- boolean isCharValid(char testChar)
- {
- char lower = Character.toLowerCase(testChar);
- char upper = Character.toUpperCase(testChar);
- // If validChars isn't null, the character must appear in it.
- if (validChars != null)
- if (validChars.indexOf(lower) == -1 && validChars.indexOf(upper) == -1)
- return false;
- // If invalidChars isn't null, the character must not appear in it.
- if (invalidChars != null)
- if (invalidChars.indexOf(lower) != -1
- || invalidChars.indexOf(upper) != -1)
- return false;
- return true;
- }
-
- /**
- * Pads the value with literals, the placeholder String and/or placeholder
- * character as appropriate.
- * @param value the value to pad
- * @param currLength the current length of the value
- * @return the padded String
- */
- String pad (String value, int currLength)
- {
- StringBuffer result = new StringBuffer(value);
- int index = currLength;
- while (result.length() < maskLength)
- {
- // The character used to pad may be a literal, a character from the
- // place holder string, or the place holder character. getPadCharAt
- // will find the proper one for us.
- result.append (getPadCharAt(index));
- index++;
}
return result.toString();
}
- /**
- * Returns the character with which to pad the value at the given index
- * position. If the mask has a literal at this position, this is returned
- * otherwise if the place holder string is initialized and is longer than
- * <code>i</code> characters then the character at position <code>i</code>
- * from this String is returned. Else, the place holder character is
- * returned.
- * @param i the index at which we want to pad the value
- * @return the character with which we should pad the value
- */
- char getPadCharAt(int i)
- {
- boolean escaped = false;
- int target = i;
- char maskChar;
- int holderLength = placeHolder == null ? -1 : placeHolder.length();
- // We must iterate through the mask from the beginning, because the given
- // index doesn't account for escaped characters. For example, with the
- // mask "1A'A''A1" index 2 refers to the literalized A, not to the
- // single quotation.
- for (int n = 0; n < mask.length(); n++)
- {
- maskChar = mask.charAt(n);
- if (maskChar == ESCAPE_CHAR && !escaped)
- {
- target++;
- escaped = true;
- }
- else if (escaped == true)
- {
- // Check if target == n which means we've come to the character
- // we want to return and since it is a literal (because escaped
- // is true), we return it.
- if (target == n)
- return maskChar;
- escaped = false;
- }
- if (target == n)
- {
- // We've come to the character we want to return. It wasn't
- // escaped so if it isn't a literal we should return either
- // the character from place holder string or the place holder
- // character, depending on whether or not the place holder
- // string is long enough.
- switch (maskChar)
- {
- case NUM_CHAR:
- case UPPERCASE_CHAR:
- case LOWERCASE_CHAR:
- case ALPHANUM_CHAR:
- case LETTER_CHAR:
- case HEX_CHAR:
- case ANYTHING_CHAR:
- if (holderLength > i)
- return placeHolder.charAt(i);
- else
- return placeHolderChar;
- default:
- return maskChar;
- }
- }
- }
- // This shouldn't happen
- throw new AssertionError("MaskFormatter.getMaskCharAt failed");
- }
}
diff --git a/libjava/classpath/javax/swing/text/MutableAttributeSet.java b/libjava/classpath/javax/swing/text/MutableAttributeSet.java
index 3728b9c..5dd2406 100644
--- a/libjava/classpath/javax/swing/text/MutableAttributeSet.java
+++ b/libjava/classpath/javax/swing/text/MutableAttributeSet.java
@@ -90,7 +90,7 @@ public interface MutableAttributeSet extends AttributeSet
* @throws NullPointerException if <code>names</code> is <code>null</code>
* or contains any <code>null</code> values.
*/
- void removeAttributes(Enumeration names);
+ void removeAttributes(Enumeration<?> names);
/**
* Removes attributes from this set if they are found in the
diff --git a/libjava/classpath/javax/swing/text/ParagraphView.java b/libjava/classpath/javax/swing/text/ParagraphView.java
index c485786..fb4ac65 100644
--- a/libjava/classpath/javax/swing/text/ParagraphView.java
+++ b/libjava/classpath/javax/swing/text/ParagraphView.java
@@ -38,8 +38,12 @@ exception statement from your version. */
package javax.swing.text;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Rectangle;
import java.awt.Shape;
+import javax.swing.SizeRequirements;
import javax.swing.event.DocumentEvent;
/**
@@ -64,30 +68,45 @@ public class ParagraphView extends FlowView implements TabExpander
super(el, X_AXIS);
}
+ /**
+ * Overridden to adjust when we are the first line, and firstLineIndent
+ * is not 0.
+ */
+ public short getLeftInset()
+ {
+ short leftInset = super.getLeftInset();
+ View parent = getParent();
+ if (parent != null)
+ {
+ if (parent.getView(0) == this)
+ leftInset += firstLineIndent;
+ }
+ return leftInset;
+ }
+
public float getAlignment(int axis)
{
float align;
if (axis == X_AXIS)
- align = 0.0F; // TODO: Implement according to justification
+ switch (justification)
+ {
+ case StyleConstants.ALIGN_RIGHT:
+ align = 1.0F;
+ break;
+ case StyleConstants.ALIGN_CENTER:
+ case StyleConstants.ALIGN_JUSTIFIED:
+ align = 0.5F;
+ break;
+ case StyleConstants.ALIGN_LEFT:
+ default:
+ align = 0.0F;
+ }
else
align = super.getAlignment(axis);
return align;
}
/**
- * Allows rows to span the whole parent view.
- */
- public float getMaximumSpan(int axis)
- {
- float max;
- if (axis == X_AXIS)
- max = Float.MAX_VALUE;
- else
- max = super.getMaximumSpan(axis);
- return max;
- }
-
- /**
* Overridden because child views are not necessarily laid out in model
* order.
*/
@@ -107,10 +126,63 @@ public class ParagraphView extends FlowView implements TabExpander
return index;
}
+
+ /**
+ * Overridden to perform a baseline layout. The normal BoxView layout
+ * isn't completely suitable for rows.
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ baselineLayout(targetSpan, axis, offsets, spans);
+ }
+
+ /**
+ * Overridden to perform a baseline layout. The normal BoxView layout
+ * isn't completely suitable for rows.
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ return baselineRequirements(axis, r);
+ }
+
protected void loadChildren(ViewFactory vf)
{
// Do nothing here. The children are added while layouting.
}
+
+ /**
+ * Overridden to determine the minimum start offset of the row's children.
+ */
+ public int getStartOffset()
+ {
+ // Determine minimum start offset of the children.
+ int offset = Integer.MAX_VALUE;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ offset = Math.min(offset, v.getStartOffset());
+ }
+ return offset;
+ }
+
+ /**
+ * Overridden to determine the maximum end offset of the row's children.
+ */
+ public int getEndOffset()
+ {
+ // Determine minimum start offset of the children.
+ int offset = 0;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ offset = Math.max(offset, v.getEndOffset());
+ }
+ return offset;
+ }
}
/**
@@ -192,11 +264,14 @@ public class ParagraphView extends FlowView implements TabExpander
*
* @param ev the document event
* @param a the allocation of this view
- * @param fv the view factory to use for creating new child views
+ * @param vf the view factory to use for creating new child views
*/
- public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory fv)
+ public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf)
{
setPropertiesFromAttributes();
+ layoutChanged(X_AXIS);
+ layoutChanged(Y_AXIS);
+ super.changedUpdate(ev, a, vf);
}
/**
diff --git a/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java
index 48fe37c..e048d5f 100644
--- a/libjava/classpath/javax/swing/text/PlainView.java
+++ b/libjava/classpath/javax/swing/text/PlainView.java
@@ -87,6 +87,16 @@ public class PlainView extends View implements TabExpander
*/
private transient Segment lineBuffer;
+ /**
+ * The base offset for tab calculations.
+ */
+ private int tabBase;
+
+ /**
+ * The tab size.
+ */
+ private int tabSize;
+
public PlainView(Element elem)
{
super(elem);
@@ -104,6 +114,7 @@ public class PlainView extends View implements TabExpander
{
this.font = font;
metrics = component.getFontMetrics(font);
+ tabSize = getTabSize() * metrics.charWidth('m');
}
}
@@ -115,7 +126,7 @@ public class PlainView extends View implements TabExpander
// Ensure metrics are up-to-date.
updateMetrics();
- Rectangle rect = a.getBounds();
+ Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
int fontHeight = metrics.getHeight();
return new Rectangle(rect.x, rect.y + (line * fontHeight),
rect.width, fontHeight);
@@ -132,13 +143,14 @@ public class PlainView extends View implements TabExpander
// Get rectangle of the line containing position.
int lineIndex = getElement().getElementIndex(position);
Rectangle rect = lineToRect(a, lineIndex);
+ tabBase = rect.x;
// Get the rectangle for position.
Element line = getElement().getElement(lineIndex);
int lineStart = line.getStartOffset();
Segment segment = getLineBuffer();
document.getText(lineStart, position - lineStart, segment);
- int xoffset = Utilities.getTabbedTextWidth(segment, metrics, rect.x,
+ int xoffset = Utilities.getTabbedTextWidth(segment, metrics, tabBase,
this, lineStart);
// Calc the real rectangle.
@@ -262,17 +274,47 @@ public class PlainView extends View implements TabExpander
selectionStart = textComponent.getSelectionStart();
selectionEnd = textComponent.getSelectionEnd();
- Rectangle rect = s.getBounds();
+ Rectangle rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
+ tabBase = rect.x;
// FIXME: Text may be scrolled.
Document document = textComponent.getDocument();
- Element root = document.getDefaultRootElement();
- int y = rect.y + metrics.getAscent();
+ Element root = getElement();
int height = metrics.getHeight();
-
+
+ // For layered highlighters we need to paint the layered highlights
+ // before painting any text.
+ LayeredHighlighter hl = null;
+ Highlighter h = textComponent.getHighlighter();
+ if (h instanceof LayeredHighlighter)
+ hl = (LayeredHighlighter) h;
+
int count = root.getElementCount();
- for (int i = 0; i < count; i++)
+
+ // Determine first and last line inside the clip.
+ Rectangle clip = g.getClipBounds();
+ SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height,
+ clip);
+ int line0 = (clip.y - rect.y) / height;
+ line0 = Math.max(0, Math.min(line0, count - 1));
+ int line1 = (clip.y + clip.height - rect.y) / height;
+ line1 = Math.max(0, Math.min(line1, count - 1));
+ int y = rect.y + metrics.getAscent() + height * line0;
+ for (int i = line0; i <= line1; i++)
{
+ if (hl != null)
+ {
+ Element lineEl = root.getElement(i);
+ // Exclude the trailing newline from beeing highlighted.
+ if (i == count)
+ hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
+ lineEl.getEndOffset(), s, textComponent,
+ this);
+ else
+ hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
+ lineEl.getEndOffset() - 1, s,
+ textComponent, this);
+ }
drawLine(i, g, rect.x, y);
y += height;
}
@@ -303,8 +345,13 @@ public class PlainView extends View implements TabExpander
*/
public float nextTabStop(float x, int tabStop)
{
- float tabSizePixels = getTabSize() * metrics.charWidth('m');
- return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
+ float next = x;
+ if (tabSize != 0)
+ {
+ int numTabs = (((int) x) - tabBase) / tabSize;
+ next = tabBase + (numTabs + 1) * tabSize;
+ }
+ return next;
}
/**
@@ -390,41 +437,58 @@ public class PlainView extends View implements TabExpander
*/
public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
{
- Rectangle rec = a.getBounds();
- Document doc = getDocument();
- Element root = doc.getDefaultRootElement();
-
- // PlainView doesn't support line-wrapping so we can find out which
- // Element was clicked on just by the y-position.
- // Since the coordinates may be outside of the coordinate space
- // of the allocation area (e.g. user dragged mouse outside
- // the component) we have to limit the values.
- // This has the nice effect that the user can drag the
- // mouse above or below the component and it will still
- // react to the x values (e.g. when selecting).
- int lineClicked
- = Math.min(Math.max((int) (y - rec.y) / metrics.getHeight(), 0),
- root.getElementCount() - 1);
-
- Element line = root.getElement(lineClicked);
-
- Segment s = getLineBuffer();
- int start = line.getStartOffset();
- // We don't want the \n at the end of the line.
- int end = line.getEndOffset() - 1;
- try
- {
- doc.getText(start, end - start, s);
- }
- catch (BadLocationException ble)
+ Rectangle rec = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ tabBase = rec.x;
+
+ int pos;
+ if ((int) y < rec.y)
+ // Above our area vertically. Return start offset.
+ pos = getStartOffset();
+ else if ((int) y > rec.y + rec.height)
+ // Below our area vertically. Return end offset.
+ pos = getEndOffset() - 1;
+ else
{
- AssertionError ae = new AssertionError("Unexpected bad location");
- ae.initCause(ble);
- throw ae;
+ // Inside the allocation vertically. Determine line and X offset.
+ Document doc = getDocument();
+ Element root = doc.getDefaultRootElement();
+ int line = Math.abs(((int) y - rec.y) / metrics.getHeight());
+ if (line >= root.getElementCount())
+ pos = getEndOffset() - 1;
+ else
+ {
+ Element lineEl = root.getElement(line);
+ if (x < rec.x)
+ // To the left of the allocation area.
+ pos = lineEl.getStartOffset();
+ else if (x > rec.x + rec.width)
+ // To the right of the allocation area.
+ pos = lineEl.getEndOffset() - 1;
+ else
+ {
+ try
+ {
+ int p0 = lineEl.getStartOffset();
+ int p1 = lineEl.getEndOffset();
+ Segment s = new Segment();
+ doc.getText(p0, p1 - p0, s);
+ tabBase = rec.x;
+ pos = p0 + Utilities.getTabbedTextOffset(s, metrics,
+ tabBase, (int) x,
+ this, p0);
+ }
+ catch (BadLocationException ex)
+ {
+ // Should not happen.
+ pos = -1;
+ }
+ }
+
+ }
}
-
- int pos = Utilities.getTabbedTextOffset(s, metrics, rec.x, (int)x, this, start);
- return Math.max (0, pos);
+ // Bias is always forward.
+ b[0] = Position.Bias.Forward;
+ return pos;
}
/**
@@ -654,7 +718,7 @@ public class PlainView extends View implements TabExpander
throw err;
}
- return Utilities.getTabbedTextWidth(buffer, metrics, 0, this,
+ return Utilities.getTabbedTextWidth(buffer, metrics, tabBase, this,
lineEl.getStartOffset());
}
}
diff --git a/libjava/classpath/javax/swing/text/Position.java b/libjava/classpath/javax/swing/text/Position.java
index bb1449e..d02eb83 100644
--- a/libjava/classpath/javax/swing/text/Position.java
+++ b/libjava/classpath/javax/swing/text/Position.java
@@ -42,8 +42,8 @@ public interface Position
{
static final class Bias
{
- public static final Bias Backward = new Bias("backward");
- public static final Bias Forward = new Bias("forward");
+ public static final Bias Backward = new Bias("Backward");
+ public static final Bias Forward = new Bias("Forward");
private String name;
diff --git a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java
index 8684ef8..701fa8a 100644
--- a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java
+++ b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java
@@ -123,9 +123,17 @@ public class SimpleAttributeSet
*/
public Object clone()
{
- SimpleAttributeSet s = new SimpleAttributeSet();
- s.tab = (Hashtable) tab.clone();
- return s;
+ SimpleAttributeSet attr = null;
+ try
+ {
+ attr = (SimpleAttributeSet) super.clone();
+ attr.tab = (Hashtable) tab.clone();
+ }
+ catch (CloneNotSupportedException ex)
+ {
+ assert false;
+ }
+ return attr;
}
/**
@@ -253,7 +261,7 @@ public class SimpleAttributeSet
*
* @return An enumeration of the attribute names.
*/
- public Enumeration getAttributeNames()
+ public Enumeration<?> getAttributeNames()
{
return tab.keys();
}
@@ -367,7 +375,7 @@ public class SimpleAttributeSet
* @throws NullPointerException if <code>names</code> is <code>null</code>
* or contains any <code>null</code> values.
*/
- public void removeAttributes(Enumeration names)
+ public void removeAttributes(Enumeration<?> names)
{
while (names.hasMoreElements())
{
diff --git a/libjava/classpath/javax/swing/text/StringContent.java b/libjava/classpath/javax/swing/text/StringContent.java
index 8014dc3..4a3f9d7 100644
--- a/libjava/classpath/javax/swing/text/StringContent.java
+++ b/libjava/classpath/javax/swing/text/StringContent.java
@@ -39,6 +39,9 @@ exception statement from your version. */
package javax.swing.text;
import java.io.Serializable;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Vector;
@@ -57,6 +60,76 @@ import javax.swing.undo.UndoableEdit;
public final class StringContent
implements AbstractDocument.Content, Serializable
{
+ /**
+ * Stores a reference to a mark that can be resetted to the original value
+ * after a mark has been moved. This is used for undoing actions.
+ */
+ private class UndoPosRef
+ {
+ /**
+ * The mark that might need to be reset.
+ */
+ private Mark mark;
+
+ /**
+ * The original offset to reset the mark to.
+ */
+ private int undoOffset;
+
+ /**
+ * Creates a new UndoPosRef.
+ *
+ * @param m the mark
+ */
+ UndoPosRef(Mark m)
+ {
+ mark = m;
+ undoOffset = mark.mark;
+ }
+
+ /**
+ * Resets the position of the mark to the value that it had when
+ * creating this UndoPosRef.
+ */
+ void reset()
+ {
+ mark.mark = undoOffset;
+ }
+ }
+
+ /**
+ * Holds a mark into the buffer that is used by StickyPosition to find
+ * the actual offset of the position. This is pulled out of the
+ * GapContentPosition object so that the mark and position can be handled
+ * independently, and most important, so that the StickyPosition can
+ * be garbage collected while we still hold a reference to the Mark object.
+ */
+ private class Mark
+ {
+ /**
+ * The actual mark into the buffer.
+ */
+ int mark;
+
+
+ /**
+ * The number of GapContentPosition object that reference this mark. If
+ * it reaches zero, it get's deleted by
+ * {@link StringContent#garbageCollect()}.
+ */
+ int refCount;
+
+ /**
+ * Creates a new Mark object for the specified offset.
+ *
+ * @param offset the offset
+ */
+ Mark(int offset)
+ {
+ mark = offset;
+ }
+ }
+
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = 4755994433709540381L;
@@ -65,7 +138,12 @@ public final class StringContent
private int count;
- private Vector positions = new Vector();
+ /**
+ * Holds the marks for the positions.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ Vector marks;
private class InsertUndo extends AbstractUndoableEdit
{
@@ -75,6 +153,8 @@ public final class StringContent
private String redoContent;
+ private Vector positions;
+
public InsertUndo(int start, int length)
{
super();
@@ -87,10 +167,10 @@ public final class StringContent
super.undo();
try
{
- StringContent.this.checkLocation(this.start, this.length);
- this.redoContent = new String(StringContent.this.content, this.start,
- this.length);
- StringContent.this.remove(this.start, this.length);
+ if (marks != null)
+ positions = getPositionsInRange(null, start, length);
+ redoContent = getString(start, length);
+ remove(start, length);
}
catch (BadLocationException b)
{
@@ -103,7 +183,13 @@ public final class StringContent
super.redo();
try
{
- StringContent.this.insertString(this.start, this.redoContent);
+ insertString(start, redoContent);
+ redoContent = null;
+ if (positions != null)
+ {
+ updateUndoPositions(positions);
+ positions = null;
+ }
}
catch (BadLocationException b)
{
@@ -115,14 +201,19 @@ public final class StringContent
private class RemoveUndo extends AbstractUndoableEdit
{
private int start;
-
+ private int len;
private String undoString;
+ Vector positions;
+
public RemoveUndo(int start, String str)
{
super();
this.start = start;
+ len = str.length();
this.undoString = str;
+ if (marks != null)
+ positions = getPositionsInRange(null, start, str.length());
}
public void undo()
@@ -131,6 +222,12 @@ public final class StringContent
try
{
StringContent.this.insertString(this.start, this.undoString);
+ if (positions != null)
+ {
+ updateUndoPositions(positions);
+ positions = null;
+ }
+ undoString = null;
}
catch (BadLocationException bad)
{
@@ -143,8 +240,10 @@ public final class StringContent
super.redo();
try
{
- int end = this.undoString.length();
- StringContent.this.remove(this.start, end);
+ undoString = getString(start, len);
+ if (marks != null)
+ positions = getPositionsInRange(null, start, len);
+ remove(this.start, len);
}
catch (BadLocationException bad)
{
@@ -155,17 +254,18 @@ public final class StringContent
private class StickyPosition implements Position
{
- private int offset = -1;
+ Mark mark;
public StickyPosition(int offset)
{
- this.offset = offset;
- }
+ // Try to make space.
+ garbageCollect();
- // This is package-private to avoid an accessor method.
- void setOffset(int offset)
- {
- this.offset = this.offset >= 0 ? offset : -1;
+ mark = new Mark(offset);
+ mark.refCount++;
+ marks.add(mark);
+
+ new WeakReference(this, queueOfDeath);
}
/**
@@ -173,11 +273,25 @@ public final class StringContent
*/
public int getOffset()
{
- return offset < 0 ? 0 : offset;
+ return mark.mark;
}
}
/**
+ * Used in {@link #remove(int,int)}.
+ */
+ private static final char[] EMPTY = new char[0];
+
+ /**
+ * Queues all references to GapContentPositions that are about to be
+ * GC'ed. This is used to remove the corresponding marks from the
+ * positionMarks array if the number of references to that mark reaches zero.
+ *
+ * This is package private to avoid accessor synthetic methods.
+ */
+ ReferenceQueue queueOfDeath;
+
+ /**
* Creates a new instance containing the string "\n". This is equivalent
* to calling {@link #StringContent(int)} with an <code>initialLength</code>
* of 10.
@@ -196,6 +310,7 @@ public final class StringContent
public StringContent(int initialLength)
{
super();
+ queueOfDeath = new ReferenceQueue();
if (initialLength < 1)
initialLength = 1;
this.content = new char[initialLength];
@@ -207,14 +322,13 @@ public final class StringContent
int offset,
int length)
{
- Vector refPos = new Vector();
- Iterator iter = this.positions.iterator();
+ Vector refPos = v == null ? new Vector() : v;
+ Iterator iter = marks.iterator();
while(iter.hasNext())
{
- Position p = (Position) iter.next();
- if ((offset <= p.getOffset())
- && (p.getOffset() <= (offset + length)))
- refPos.add(p);
+ Mark m = (Mark) iter.next();
+ if (offset <= m.mark && m.mark <= offset + length)
+ refPos.add(new UndoPosRef(m));
}
return refPos;
}
@@ -231,10 +345,10 @@ public final class StringContent
*/
public Position createPosition(int offset) throws BadLocationException
{
- if (offset < this.count || offset > this.count)
- checkLocation(offset, 0);
+ // Lazily create marks vector.
+ if (marks == null)
+ marks = new Vector();
StickyPosition sp = new StickyPosition(offset);
- this.positions.add(sp);
return sp;
}
@@ -246,7 +360,7 @@ public final class StringContent
*/
public int length()
{
- return this.count;
+ return count;
}
/**
@@ -268,27 +382,23 @@ public final class StringContent
if (str == null)
throw new NullPointerException();
char[] insert = str.toCharArray();
- char[] temp = new char[this.content.length + insert.length];
- this.count += insert.length;
- // Copy array and insert the string.
- if (where > 0)
- System.arraycopy(this.content, 0, temp, 0, where);
- System.arraycopy(insert, 0, temp, where, insert.length);
- System.arraycopy(this.content, where, temp, (where + insert.length),
- (temp.length - where - insert.length));
- if (this.content.length < temp.length)
- this.content = new char[temp.length];
- // Copy the result in the original char array.
- System.arraycopy(temp, 0, this.content, 0, temp.length);
+ replace(where, 0, insert);
+
// Move all the positions.
- Vector refPos = getPositionsInRange(this.positions, where,
- temp.length - where);
- Iterator iter = refPos.iterator();
- while (iter.hasNext())
+ if (marks != null)
{
- StickyPosition p = (StickyPosition)iter.next();
- p.setOffset(p.getOffset() + str.length());
+ Iterator iter = marks.iterator();
+ int start = where;
+ if (start == 0)
+ start = 1;
+ while (iter.hasNext())
+ {
+ Mark m = (Mark) iter.next();
+ if (m.mark >= start)
+ m.mark += str.length();
+ }
}
+
InsertUndo iundo = new InsertUndo(where, insert.length);
return iundo;
}
@@ -308,32 +418,51 @@ public final class StringContent
public UndoableEdit remove(int where, int nitems) throws BadLocationException
{
checkLocation(where, nitems + 1);
- char[] temp = new char[(this.content.length - nitems)];
- this.count = this.count - nitems;
RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where,
nitems));
- // Copy array.
- System.arraycopy(this.content, 0, temp, 0, where);
- System.arraycopy(this.content, where + nitems, temp, where,
- this.content.length - where - nitems);
- this.content = new char[temp.length];
- // Then copy the result in the original char array.
- System.arraycopy(temp, 0, this.content, 0, this.content.length);
+
+ replace(where, nitems, EMPTY);
// Move all the positions.
- Vector refPos = getPositionsInRange(this.positions, where,
- this.content.length + nitems - where);
- Iterator iter = refPos.iterator();
- while (iter.hasNext())
+ if (marks != null)
{
- StickyPosition p = (StickyPosition)iter.next();
- int result = p.getOffset() - nitems;
- p.setOffset(result);
- if (result < 0)
- this.positions.remove(p);
+ Iterator iter = marks.iterator();
+ while (iter.hasNext())
+ {
+ Mark m = (Mark) iter.next();
+ if (m.mark >= where + nitems)
+ m.mark -= nitems;
+ else if (m.mark >= where)
+ m.mark = where;
+ }
}
return rundo;
}
-
+
+ private void replace(int offs, int numRemove, char[] insert)
+ {
+ int insertLength = insert.length;
+ int delta = insertLength - numRemove;
+ int src = offs + numRemove;
+ int numMove = count - src;
+ int dest = src + delta;
+ if (count + delta >= content.length)
+ {
+ // Grow data array.
+ int newLength = Math.max(2 * content.length, count + delta);
+ char[] newContent = new char[newLength];
+ System.arraycopy(content, 0, newContent, 0, offs);
+ System.arraycopy(insert, 0, newContent, offs, insertLength);
+ System.arraycopy(content, src, newContent, dest, numMove);
+ content = newContent;
+ }
+ else
+ {
+ System.arraycopy(content, src, content, dest, numMove);
+ System.arraycopy(insert, 0, content, offs, insertLength);
+ }
+ count += delta;
+ }
+
/**
* Returns a new <code>String</code> containing the characters in the
* specified range.
@@ -348,6 +477,8 @@ public final class StringContent
*/
public String getString(int where, int len) throws BadLocationException
{
+ // The RI throws a StringIndexOutOfBoundsException here, which
+ // smells like a bug. We throw a BadLocationException instead.
checkLocation(where, len);
return new String(this.content, where, len);
}
@@ -368,22 +499,28 @@ public final class StringContent
public void getChars(int where, int len, Segment txt)
throws BadLocationException
{
- checkLocation(where, len);
- txt.array = this.content;
+ if (where + len > count)
+ throw new BadLocationException("Invalid location", where + len);
+ txt.array = content;
txt.offset = where;
txt.count = len;
}
/**
- * @specnote This method is not very well specified and the positions vector
- * is implementation specific. The undo positions are managed
- * differently in this implementation, this method is only here
- * for binary compatibility.
+ * Resets the positions in the specified vector to their original offset
+ * after a undo operation is performed. For example, after removing some
+ * content, the positions in the removed range will all be set to one
+ * offset. This method restores the positions to their original offsets
+ * after an undo.
*/
protected void updateUndoPositions(Vector positions)
{
- // We do nothing here.
+ for (Iterator i = positions.iterator(); i.hasNext();)
+ {
+ UndoPosRef pos = (UndoPosRef) i.next();
+ pos.reset();
+ }
}
/**
@@ -405,6 +542,29 @@ public final class StringContent
else if ((where + len) > this.count)
throw new BadLocationException("Invalid range", this.count);
}
-
+
+ /**
+ * Polls the queue of death for GapContentPositions, updates the
+ * corresponding reference count and removes the corresponding mark
+ * if the refcount reaches zero.
+ *
+ * This is package private to avoid accessor synthetic methods.
+ */
+ void garbageCollect()
+ {
+ Reference ref = queueOfDeath.poll();
+ while (ref != null)
+ {
+ if (ref != null)
+ {
+ StickyPosition pos = (StickyPosition) ref.get();
+ Mark m = pos.mark;
+ m.refCount--;
+ if (m.refCount == 0)
+ marks.remove(m);
+ }
+ ref = queueOfDeath.poll();
+ }
+ }
}
diff --git a/libjava/classpath/javax/swing/text/StyleConstants.java b/libjava/classpath/javax/swing/text/StyleConstants.java
index c7906b8..4e5005c 100644
--- a/libjava/classpath/javax/swing/text/StyleConstants.java
+++ b/libjava/classpath/javax/swing/text/StyleConstants.java
@@ -40,6 +40,7 @@ package javax.swing.text;
import java.awt.Color;
import java.awt.Component;
+import java.util.ArrayList;
import javax.swing.Icon;
@@ -163,6 +164,12 @@ public class StyleConstants
public static final Object ResolveAttribute = new StyleConstants("resolver");
+ /**
+ * All StyleConstants keys. This is used in StyleContext to register
+ * all known keys as static attribute keys for serialization.
+ */
+ static ArrayList keys;
+
String keyname;
// Package-private to avoid accessor constructor for use by
@@ -170,6 +177,9 @@ public class StyleConstants
StyleConstants(String k)
{
keyname = k;
+ if (keys == null)
+ keys = new ArrayList();
+ keys.add(this);
}
/**
@@ -729,6 +739,7 @@ public class StyleConstants
*/
public static void setIcon(MutableAttributeSet a, Icon c)
{
+ a.addAttribute(AbstractDocument.ElementNameAttribute, IconElementName);
a.addAttribute(IconAttribute, c);
}
diff --git a/libjava/classpath/javax/swing/text/StyleContext.java b/libjava/classpath/javax/swing/text/StyleContext.java
index 63df3df..4dded0d 100644
--- a/libjava/classpath/javax/swing/text/StyleContext.java
+++ b/libjava/classpath/javax/swing/text/StyleContext.java
@@ -43,19 +43,25 @@ import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Toolkit;
import java.io.IOException;
+import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
public class StyleContext
- implements Serializable, AbstractDocument.AttributeContext
+ implements Serializable, AbstractDocument.AttributeContext
{
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = 8042858831190784241L;
@@ -66,11 +72,10 @@ public class StyleContext
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = -6690628971806226374L;
- protected ChangeEvent changeEvent;
+ protected transient ChangeEvent changeEvent;
protected EventListenerList listenerList;
- AttributeSet attributes;
- String name;
+ private transient AttributeSet attributes;
public NamedStyle()
{
@@ -84,22 +89,26 @@ public class StyleContext
public NamedStyle(String name, Style parent)
{
- this.name = name;
- this.attributes = getEmptySet();
- this.changeEvent = new ChangeEvent(this);
- this.listenerList = new EventListenerList();
- setResolveParent(parent);
+ attributes = getEmptySet();
+ listenerList = new EventListenerList();
+ if (name != null)
+ setName(name);
+ if (parent != null)
+ setResolveParent(parent);
}
public String getName()
{
+ String name = null;
+ if (isDefined(StyleConstants.NameAttribute))
+ name = getAttribute(StyleConstants.NameAttribute).toString();
return name;
}
public void setName(String n)
{
- name = n;
- fireStateChanged();
+ if (n != null)
+ addAttribute(StyleConstants.NameAttribute, n);
}
public void addChangeListener(ChangeListener l)
@@ -112,7 +121,7 @@ public class StyleContext
listenerList.remove(ChangeListener.class, l);
}
- public EventListener[] getListeners(Class listenerType)
+ public <T extends EventListener> T[] getListeners(Class<T> listenerType)
{
return listenerList.getListeners(listenerType);
}
@@ -127,6 +136,9 @@ public class StyleContext
ChangeListener[] listeners = getChangeListeners();
for (int i = 0; i < listeners.length; ++i)
{
+ // Lazily create event.
+ if (changeEvent == null)
+ changeEvent = new ChangeEvent(this);
listeners[i].stateChanged(changeEvent);
}
}
@@ -155,7 +167,10 @@ public class StyleContext
public AttributeSet copyAttributes()
{
- return attributes.copyAttributes();
+ // The RI returns a NamedStyle as copy, so do we.
+ NamedStyle copy = new NamedStyle();
+ copy.attributes = attributes.copyAttributes();
+ return copy;
}
public Object getAttribute(Object attrName)
@@ -168,7 +183,7 @@ public class StyleContext
return attributes.getAttributeCount();
}
- public Enumeration getAttributeNames()
+ public Enumeration<?> getAttributeNames()
{
return attributes.getAttributeNames();
}
@@ -195,7 +210,7 @@ public class StyleContext
fireStateChanged();
}
- public void removeAttributes(Enumeration names)
+ public void removeAttributes(Enumeration<?> names)
{
attributes = StyleContext.this.removeAttributes(attributes, names);
fireStateChanged();
@@ -210,112 +225,125 @@ public class StyleContext
public void setResolveParent(AttributeSet parent)
{
if (parent != null)
- {
- attributes = StyleContext.this.addAttribute
- (attributes, ResolveAttribute, parent);
- }
- fireStateChanged();
+ addAttribute(StyleConstants.ResolveAttribute, parent);
+ else
+ removeAttribute(StyleConstants.ResolveAttribute);
}
public String toString()
{
- return ("[NamedStyle: name=" + name + ", attrs=" + attributes.toString() + "]");
- }
+ return "NamedStyle:" + getName() + " " + attributes;
+ }
+
+ private void writeObject(ObjectOutputStream s)
+ throws IOException
+ {
+ s.defaultWriteObject();
+ writeAttributeSet(s, attributes);
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException
+ {
+ s.defaultReadObject();
+ attributes = SimpleAttributeSet.EMPTY;
+ readAttributeSet(s, this);
+ }
}
public class SmallAttributeSet
implements AttributeSet
{
final Object [] attrs;
+ private AttributeSet resolveParent;
public SmallAttributeSet(AttributeSet a)
{
- if (a == null)
- attrs = new Object[0];
- else
+ int n = a.getAttributeCount();
+ int i = 0;
+ attrs = new Object[n * 2];
+ Enumeration e = a.getAttributeNames();
+ while (e.hasMoreElements())
{
- int n = a.getAttributeCount();
- int i = 0;
- attrs = new Object[n * 2];
- Enumeration e = a.getAttributeNames();
- while (e.hasMoreElements())
- {
- Object name = e.nextElement();
- attrs[i++] = name;
- attrs[i++] = a.getAttribute(name);
- }
+ Object name = e.nextElement();
+ Object value = a.getAttribute(name);
+ if (name == ResolveAttribute)
+ resolveParent = (AttributeSet) value;
+ attrs[i++] = name;
+ attrs[i++] = value;
}
}
public SmallAttributeSet(Object [] a)
{
- if (a == null)
- attrs = new Object[0];
- else
+ attrs = a;
+ for (int i = 0; i < attrs.length; i += 2)
{
- attrs = new Object[a.length];
- System.arraycopy(a, 0, attrs, 0, a.length);
+ if (attrs[i] == ResolveAttribute)
+ resolveParent = (AttributeSet) attrs[i + 1];
}
}
public Object clone()
{
- return new SmallAttributeSet(this.attrs);
+ return this;
}
public boolean containsAttribute(Object name, Object value)
{
- for (int i = 0; i < attrs.length; i += 2)
- {
- if (attrs[i].equals(name) &&
- attrs[i+1].equals(value))
- return true;
- }
- return false;
+ return value.equals(getAttribute(name));
}
public boolean containsAttributes(AttributeSet a)
{
+ boolean res = true;
Enumeration e = a.getAttributeNames();
- while (e.hasMoreElements())
+ while (e.hasMoreElements() && res)
{
Object name = e.nextElement();
- Object val = a.getAttribute(name);
- if (!containsAttribute(name, val))
- return false;
+ res = a.getAttribute(name).equals(getAttribute(name));
}
- return true;
+ return res;
}
public AttributeSet copyAttributes()
{
- return (AttributeSet) clone();
+ return this;
}
public boolean equals(Object obj)
{
- return
- (obj instanceof AttributeSet)
- && this.isEqual((AttributeSet)obj);
+ boolean eq = false;
+ if (obj instanceof AttributeSet)
+ {
+ AttributeSet atts = (AttributeSet) obj;
+ eq = getAttributeCount() == atts.getAttributeCount()
+ && containsAttributes(atts);
+ }
+ return eq;
}
public Object getAttribute(Object key)
{
- for (int i = 0; i < attrs.length; i += 2)
+ Object att = null;
+ if (key == StyleConstants.ResolveAttribute)
+ att = resolveParent;
+
+ for (int i = 0; i < attrs.length && att == null; i += 2)
{
if (attrs[i].equals(key))
- return attrs[i+1];
+ att = attrs[i + 1];
}
-
+
// Check the resolve parent, unless we're looking for the
- // ResolveAttribute, which would cause an infinite loop
- if (!(key.equals(ResolveAttribute)))
+ // ResolveAttribute, which must not be looked up
+ if (att == null)
{
- Object p = getResolveParent();
- if (p != null && p instanceof AttributeSet)
- return (((AttributeSet)p).getAttribute(key));
+ AttributeSet parent = getResolveParent();
+ if (parent != null)
+ att = parent.getAttribute(key);
}
- return null;
+ return att;
}
public int getAttributeCount()
@@ -323,7 +351,7 @@ public class StyleContext
return attrs.length / 2;
}
- public Enumeration getAttributeNames()
+ public Enumeration<?> getAttributeNames()
{
return new Enumeration()
{
@@ -342,7 +370,7 @@ public class StyleContext
public AttributeSet getResolveParent()
{
- return (AttributeSet) getAttribute(ResolveAttribute);
+ return resolveParent;
}
public int hashCode()
@@ -362,68 +390,96 @@ public class StyleContext
public boolean isEqual(AttributeSet attr)
{
- return getAttributeCount() == attr.getAttributeCount()
+ boolean eq;
+ // If the other one is also a SmallAttributeSet, it is only considered
+ // equal if it's the same instance.
+ if (attr instanceof SmallAttributeSet)
+ eq = attr == this;
+ else
+ eq = getAttributeCount() == attr.getAttributeCount()
&& this.containsAttributes(attr);
+ return eq;
}
public String toString()
{
- StringBuffer sb = new StringBuffer();
- sb.append("[StyleContext.SmallattributeSet:");
- for (int i = 0; i < attrs.length - 1; ++i)
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ for (int i = 0; i < attrs.length; i += 2)
{
- sb.append(" (");
- sb.append(attrs[i].toString());
- sb.append("=");
- sb.append(attrs[i+1].toString());
- sb.append(")");
+ if (attrs[i + 1] instanceof AttributeSet)
+ {
+ sb.append(attrs[i]);
+ sb.append("=AttributeSet,");
+ }
+ else
+ {
+ sb.append(attrs[i]);
+ sb.append('=');
+ sb.append(attrs[i + 1]);
+ sb.append(',');
+ }
}
- sb.append("]");
+ sb.append("}");
return sb.toString();
}
}
- // FIXME: official javadocs suggest that these might be more usefully
- // implemented using a WeakHashMap, but not sure if that works most
- // places or whether it really matters anyways.
- //
- // FIXME: also not sure if these tables ought to be static (singletons),
- // shared across all StyleContexts. I think so, but it's not clear in
- // docs. revert to non-shared if you think it matters.
-
/**
- * The name of the default style.
+ * Register StyleConstant keys as static attribute keys for serialization.
*/
- public static final String DEFAULT_STYLE = "default";
-
+ static
+ {
+ // Don't let problems while doing this prevent class loading.
+ try
+ {
+ for (Iterator i = StyleConstants.keys.iterator(); i.hasNext();)
+ registerStaticAttributeKey(i.next());
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+ }
+
/**
- * The default style for this style context.
+ * The name of the default style.
*/
- NamedStyle defaultStyle = new NamedStyle(DEFAULT_STYLE, null);
+ public static final String DEFAULT_STYLE = "default";
static Hashtable sharedAttributeSets = new Hashtable();
static Hashtable sharedFonts = new Hashtable();
- static StyleContext defaultStyleContext = new StyleContext();
+ static StyleContext defaultStyleContext;
static final int compressionThreshold = 9;
/**
* These attribute keys are handled specially in serialization.
*/
- private static Hashtable staticAttributeKeys = new Hashtable();
+ private static Hashtable writeAttributeKeys;
+ private static Hashtable readAttributeKeys;
+
+ private NamedStyle styles;
+
+ /**
+ * Used for searching attributes in the pool.
+ */
+ private transient MutableAttributeSet search = new SimpleAttributeSet();
+
+ /**
+ * A pool of immutable AttributeSets.
+ */
+ private transient Map attributeSetPool =
+ Collections.synchronizedMap(new WeakHashMap());
- EventListenerList listenerList;
- Hashtable styleTable;
-
/**
* Creates a new instance of the style context. Add the default style
* to the style table.
*/
public StyleContext()
{
- listenerList = new EventListenerList();
- styleTable = new Hashtable();
- styleTable.put(DEFAULT_STYLE, defaultStyle);
+ styles = new NamedStyle(null);
+ addStyle(DEFAULT_STYLE, null);
}
protected SmallAttributeSet createSmallAttributeSet(AttributeSet a)
@@ -438,30 +494,30 @@ public class StyleContext
public void addChangeListener(ChangeListener listener)
{
- listenerList.add(ChangeListener.class, listener);
+ styles.addChangeListener(listener);
}
public void removeChangeListener(ChangeListener listener)
{
- listenerList.remove(ChangeListener.class, listener);
+ styles.removeChangeListener(listener);
}
public ChangeListener[] getChangeListeners()
{
- return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
+ return styles.getChangeListeners();
}
public Style addStyle(String name, Style parent)
{
Style newStyle = new NamedStyle(name, parent);
if (name != null)
- styleTable.put(name, newStyle);
+ styles.addAttribute(name, newStyle);
return newStyle;
}
public void removeStyle(String name)
{
- styleTable.remove(name);
+ styles.removeAttribute(name);
}
/**
@@ -476,16 +532,31 @@ public class StyleContext
*/
public Style getStyle(String name)
{
- return (Style) styleTable.get(name);
+ return (Style) styles.getAttribute(name);
}
/**
* Get the names of the style. The returned enumeration always
* contains at least one member, the default style.
*/
- public Enumeration getStyleNames()
+ public Enumeration<?> getStyleNames()
+ {
+ return styles.getAttributeNames();
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws ClassNotFoundException, IOException
{
- return styleTable.keys();
+ search = new SimpleAttributeSet();
+ attributeSetPool = Collections.synchronizedMap(new WeakHashMap());
+ in.defaultReadObject();
+ }
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ cleanupPool();
+ out.defaultWriteObject();
}
//
@@ -577,132 +648,125 @@ public class StyleContext
public static StyleContext getDefaultStyleContext()
{
+ if (defaultStyleContext == null)
+ defaultStyleContext = new StyleContext();
return defaultStyleContext;
}
- public AttributeSet addAttribute(AttributeSet old, Object name, Object value)
+ public synchronized AttributeSet addAttribute(AttributeSet old, Object name,
+ Object value)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() + 1 < getCompressionThreshold())
{
- ((MutableAttributeSet)old).addAttribute(name, value);
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.addAttribute(name, value);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
+ else
{
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.addAttribute(name, value);
- if (mutable.getAttributeCount() >= getCompressionThreshold())
- return mutable;
- else
- {
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
- }
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.addAttribute(name, value);
+ ret = mas;
}
+ return ret;
}
- public AttributeSet addAttributes(AttributeSet old, AttributeSet attributes)
+ public synchronized AttributeSet addAttributes(AttributeSet old,
+ AttributeSet attributes)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() + attributes.getAttributeCount()
+ < getCompressionThreshold())
{
- ((MutableAttributeSet)old).addAttributes(attributes);
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.addAttributes(attributes);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
+ else
{
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.addAttributes(attributes);
- if (mutable.getAttributeCount() >= getCompressionThreshold())
- return mutable;
- else
- {
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
- }
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.addAttributes(attributes);
+ ret = mas;
}
+ return ret;
}
public AttributeSet getEmptySet()
{
- AttributeSet e = createSmallAttributeSet(null);
- if (sharedAttributeSets.containsKey(e))
- e = (AttributeSet) sharedAttributeSets.get(e);
- else
- sharedAttributeSets.put(e, e);
- return e;
+ return SimpleAttributeSet.EMPTY;
}
public void reclaim(AttributeSet attributes)
{
- if (sharedAttributeSets.containsKey(attributes))
- sharedAttributeSets.remove(attributes);
+ cleanupPool();
}
- public AttributeSet removeAttribute(AttributeSet old, Object name)
+ public synchronized AttributeSet removeAttribute(AttributeSet old,
+ Object name)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() - 1 <= getCompressionThreshold())
{
- ((MutableAttributeSet)old).removeAttribute(name);
- if (old.getAttributeCount() < getCompressionThreshold())
- {
- SmallAttributeSet small = createSmallAttributeSet(old);
- if (!sharedAttributeSets.containsKey(small))
- sharedAttributeSets.put(small,small);
- old = (AttributeSet) sharedAttributeSets.get(small);
- }
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttribute(name);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
- {
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.removeAttribute(name);
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttribute(name);
+ ret = mas;
}
+ return ret;
}
- public AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes)
+ public synchronized AttributeSet removeAttributes(AttributeSet old,
+ AttributeSet attributes)
{
- return removeAttributes(old, attributes.getAttributeNames());
+ AttributeSet ret;
+ if (old.getAttributeCount() <= getCompressionThreshold())
+ {
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttributes(attributes);
+ reclaim(old);
+ ret = searchImmutableSet();
+ }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttributes(attributes);
+ ret = mas;
+ }
+ return ret;
}
- public AttributeSet removeAttributes(AttributeSet old, Enumeration names)
+ public synchronized AttributeSet removeAttributes(AttributeSet old,
+ Enumeration<?> names)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() <= getCompressionThreshold())
{
- ((MutableAttributeSet)old).removeAttributes(names);
- if (old.getAttributeCount() < getCompressionThreshold())
- {
- SmallAttributeSet small = createSmallAttributeSet(old);
- if (!sharedAttributeSets.containsKey(small))
- sharedAttributeSets.put(small,small);
- old = (AttributeSet) sharedAttributeSets.get(small);
- }
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttributes(names);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
- {
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.removeAttributes(names);
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
- }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttributes(names);
+ ret = mas;
+ }
+ return ret;
}
/**
@@ -715,7 +779,7 @@ public class StyleContext
{
if (key == null)
return null;
- return staticAttributeKeys.get(key);
+ return readAttributeKeys.get(key);
}
/**
@@ -742,27 +806,25 @@ public class StyleContext
* stream
* @throws IOException - any I/O error
*/
- public static void readAttributeSet(ObjectInputStream in, MutableAttributeSet a)
+ public static void readAttributeSet(ObjectInputStream in,
+ MutableAttributeSet a)
throws ClassNotFoundException, IOException
{
- if (in == null || a == null)
- return;
-
- Object key = in.readObject();
- Object val = in.readObject();
- while (key != null && val != null)
+ int count = in.readInt();
+ for (int i = 0; i < count; i++)
{
- Object staticKey = staticAttributeKeys.get(key);
- Object staticVal = staticAttributeKeys.get(val);
-
- if (staticKey != null)
- key = staticKey;
- if (staticVal != null)
- val = staticVal;
-
+ Object key = in.readObject();
+ Object val = in.readObject();
+ if (readAttributeKeys != null)
+ {
+ Object staticKey = readAttributeKeys.get(key);
+ if (staticKey != null)
+ key = staticKey;
+ Object staticVal = readAttributeKeys.get(val);
+ if (staticVal != null)
+ val = staticVal;
+ }
a.addAttribute(key, val);
- key = in.readObject();
- val = in.readObject();
}
}
@@ -778,18 +840,35 @@ public class StyleContext
public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a)
throws IOException
{
+ int count = a.getAttributeCount();
+ out.writeInt(count);
Enumeration e = a.getAttributeNames();
while (e.hasMoreElements())
{
- Object oldKey = e.nextElement();
- Object newKey = getStaticAttribute(oldKey);
- Object key = (newKey == null) ? oldKey : newKey;
-
- out.writeObject(key);
- out.writeObject(a.getAttribute(oldKey));
+ Object key = e.nextElement();
+ // Write key.
+ if (key instanceof Serializable)
+ out.writeObject(key);
+ else
+ {
+ Object io = writeAttributeKeys.get(key);
+ if (io == null)
+ throw new NotSerializableException(key.getClass().getName()
+ + ", key: " + key);
+ out.writeObject(io);
+ }
+ // Write value.
+ Object val = a.getAttribute(key);
+ Object io = writeAttributeKeys.get(val);
+ if (val instanceof Serializable)
+ out.writeObject(io != null ? io : val);
+ else
+ {
+ if (io == null)
+ throw new NotSerializableException(val.getClass().getName());
+ out.writeObject(io);
+ }
}
- out.writeObject(null);
- out.writeObject(null);
}
/**
@@ -833,8 +912,79 @@ public class StyleContext
*/
public static void registerStaticAttributeKey(Object key)
{
- if (key != null)
- staticAttributeKeys.put(key.getClass().getName() + "." + key.toString(),
- key);
+ String io = key.getClass().getName() + "." + key.toString();
+ if (writeAttributeKeys == null)
+ writeAttributeKeys = new Hashtable();
+ if (readAttributeKeys == null)
+ readAttributeKeys = new Hashtable();
+ writeAttributeKeys.put(key, io);
+ readAttributeKeys.put(io, key);
+ }
+
+ /**
+ * Returns a string representation of this StyleContext.
+ *
+ * @return a string representation of this StyleContext
+ */
+ public String toString()
+ {
+ cleanupPool();
+ StringBuilder b = new StringBuilder();
+ Iterator i = attributeSetPool.keySet().iterator();
+ while (i.hasNext())
+ {
+ Object att = i.next();
+ b.append(att);
+ b.append('\n');
+ }
+ return b.toString();
+ }
+
+ /**
+ * Searches the AttributeSet pool and returns a pooled instance if available,
+ * or pool a new one.
+ *
+ * @return an immutable attribute set that equals the current search key
+ */
+ private AttributeSet searchImmutableSet()
+ {
+ SmallAttributeSet k = createSmallAttributeSet(search);
+ WeakReference ref = (WeakReference) attributeSetPool.get(k);
+ SmallAttributeSet a;
+ if (ref == null || (a = (SmallAttributeSet) ref.get()) == null)
+ {
+ a = k;
+ attributeSetPool.put(a, new WeakReference(a));
+ }
+ return a;
+ }
+
+ /**
+ * Cleans up the attribute set pool from entries that are no longer
+ * referenced.
+ */
+ private void cleanupPool()
+ {
+ // TODO: How else can we force cleaning up the WeakHashMap?
+ attributeSetPool.size();
+ }
+
+ /**
+ * Returns a MutableAttributeSet that holds a. If a itself is mutable,
+ * this returns a itself, otherwise it creates a new SimpleAtttributeSet
+ * via {@link #createLargeAttributeSet(AttributeSet)}.
+ *
+ * @param a the AttributeSet to create a mutable set for
+ *
+ * @return a mutable attribute set that corresponds to a
+ */
+ private MutableAttributeSet getMutableAttributeSet(AttributeSet a)
+ {
+ MutableAttributeSet mas;
+ if (a instanceof MutableAttributeSet)
+ mas = (MutableAttributeSet) a;
+ else
+ mas = createLargeAttributeSet(a);
+ return mas;
}
}
diff --git a/libjava/classpath/javax/swing/text/StyledEditorKit.java b/libjava/classpath/javax/swing/text/StyledEditorKit.java
index c4eef44..5686943 100644
--- a/libjava/classpath/javax/swing/text/StyledEditorKit.java
+++ b/libjava/classpath/javax/swing/text/StyledEditorKit.java
@@ -142,7 +142,7 @@ public class StyledEditorKit extends DefaultEditorKit
Element el = doc.getCharacterElement(editor.getSelectionStart());
boolean isBold = StyleConstants.isBold(el.getAttributes());
SimpleAttributeSet atts = new SimpleAttributeSet();
- StyleConstants.setItalic(atts, ! isBold);
+ StyleConstants.setBold(atts, ! isBold);
setCharacterAttributes(editor, atts, false);
}
}
@@ -335,35 +335,21 @@ public class StyledEditorKit extends DefaultEditorKit
AttributeSet atts,
boolean replace)
{
- Document doc = editor.getDocument();
- if (doc instanceof StyledDocument)
- {
- StyledDocument styleDoc = (StyledDocument) editor.getDocument();
- EditorKit kit = editor.getEditorKit();
- if (!(kit instanceof StyledEditorKit))
- {
- StyledEditorKit styleKit = (StyledEditorKit) kit;
- int start = editor.getSelectionStart();
- int end = editor.getSelectionEnd();
- int dot = editor.getCaret().getDot();
- if (start == dot && end == dot)
- {
- // If there is no selection, then we only update the
- // input attributes.
- MutableAttributeSet inputAttributes =
- styleKit.getInputAttributes();
- inputAttributes.addAttributes(atts);
- }
- else
- styleDoc.setCharacterAttributes(start, end, atts, replace);
- }
- else
- throw new AssertionError("The EditorKit for StyledTextActions "
- + "is expected to be a StyledEditorKit");
- }
- else
- throw new AssertionError("The Document for StyledTextActions is "
- + "expected to be a StyledDocument.");
+ int p0 = editor.getSelectionStart();
+ int p1 = editor.getSelectionEnd();
+ if (p0 != p1)
+ {
+ StyledDocument doc = getStyledDocument(editor);
+ doc.setCharacterAttributes(p0, p1 - p0, atts, replace);
+ }
+ // Update input attributes.
+ StyledEditorKit kit = getStyledEditorKit(editor);
+ MutableAttributeSet inputAtts = kit.getInputAttributes();
+ if (replace)
+ {
+ inputAtts.removeAttributes(inputAtts);
+ }
+ inputAtts.addAttributes(atts);
}
/**
diff --git a/libjava/classpath/javax/swing/text/TextAction.java b/libjava/classpath/javax/swing/text/TextAction.java
index 144166e..49c49cb 100644
--- a/libjava/classpath/javax/swing/text/TextAction.java
+++ b/libjava/classpath/javax/swing/text/TextAction.java
@@ -38,14 +38,15 @@ exception statement from your version. */
package javax.swing.text;
+import java.awt.Component;
+import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.event.ActionEvent;
-import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Iterator;
import javax.swing.AbstractAction;
import javax.swing.Action;
-import javax.swing.SwingConstants;
/**
* TextAction
@@ -73,10 +74,16 @@ public abstract class TextAction extends AbstractAction
*/
protected final JTextComponent getTextComponent(ActionEvent event)
{
- if (event.getSource() instanceof JTextComponent)
- return (JTextComponent) event.getSource();
-
- return getFocusedComponent();
+ JTextComponent target = null;
+ if (event != null)
+ {
+ Object source = event.getSource();
+ if (source instanceof JTextComponent)
+ target = (JTextComponent) source;
+ }
+ if (target == null)
+ target = getFocusedComponent();
+ return target;
}
/**
@@ -89,16 +96,28 @@ public abstract class TextAction extends AbstractAction
*/
public static final Action[] augmentList(Action[] list1, Action[] list2)
{
- HashSet actionSet = new HashSet();
+ HashMap<Object,Action> actions = new HashMap<Object,Action>();
for (int i = 0; i < list1.length; ++i)
- actionSet.add(list1[i]);
+ {
+ Action a = list1[i];
+ Object name = a.getValue(Action.NAME);
+ actions.put(name != null ? name : "", a);
+ }
for (int i = 0; i < list2.length; ++i)
- actionSet.add(list2[i]);
+ {
+ Action a = list2[i];
+ Object name = a.getValue(Action.NAME);
+ actions.put(name != null ? name : "", a);
+ }
+ Action[] augmented = new Action[actions.size()];
+
+ int i = 0;
+ for (Iterator<Action> it = actions.values().iterator(); it.hasNext(); i++)
+ augmented[i] = it.next();
+ return augmented;
- ArrayList list = new ArrayList(actionSet);
- return (Action[]) list.toArray(new Action[actionSet.size()]);
}
/**
@@ -108,7 +127,13 @@ public abstract class TextAction extends AbstractAction
*/
protected final JTextComponent getFocusedComponent()
{
- return null; // TODO
+ KeyboardFocusManager kfm =
+ KeyboardFocusManager.getCurrentKeyboardFocusManager();
+ Component focused = kfm.getPermanentFocusOwner();
+ JTextComponent textComp = null;
+ if (focused instanceof JTextComponent)
+ textComp = (JTextComponent) focused;
+ return textComp;
}
/** Abstract helper class which implements everything needed for an
diff --git a/libjava/classpath/javax/swing/text/Utilities.java b/libjava/classpath/javax/swing/text/Utilities.java
index 36361f4..d49d806 100644
--- a/libjava/classpath/javax/swing/text/Utilities.java
+++ b/libjava/classpath/javax/swing/text/Utilities.java
@@ -43,7 +43,6 @@ import java.awt.Graphics;
import java.awt.Point;
import java.text.BreakIterator;
-import javax.swing.SwingConstants;
import javax.swing.text.Position.Bias;
/**
@@ -55,10 +54,6 @@ import javax.swing.text.Position.Bias;
*/
public class Utilities
{
- /**
- * The length of the char buffer that holds the characters to be drawn.
- */
- private static final int BUF_LENGTH = 64;
/**
* Creates a new <code>Utilities</code> object.
@@ -94,13 +89,12 @@ public class Utilities
// The font metrics of the current selected font.
FontMetrics metrics = g.getFontMetrics();
+
int ascent = metrics.getAscent();
// The current x and y pixel coordinates.
int pixelX = x;
- int pixelY = y - ascent;
- int pixelWidth = 0;
int pos = s.offset;
int len = 0;
@@ -109,44 +103,43 @@ public class Utilities
for (int offset = s.offset; offset < end; ++offset)
{
char c = buffer[offset];
- if (c == '\t' || c == '\n')
+ switch (c)
{
+ case '\t':
if (len > 0) {
- g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
- pixelX += pixelWidth;
- pixelWidth = 0;
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ len = 0;
}
pos = offset+1;
- len = 0;
+ if (e != null)
+ pixelX = (int) e.nextTabStop((float) pixelX, startOffset + offset
+ - s.offset);
+ else
+ pixelX += metrics.charWidth(' ');
+ x = pixelX;
+ break;
+ case '\n':
+ case '\r':
+ if (len > 0) {
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ len = 0;
+ }
+ x = pixelX;
+ break;
+ default:
+ len += 1;
}
-
- switch (c)
- {
- case '\t':
- // In case we have a tab, we just 'jump' over the tab.
- // When we have no tab expander we just use the width of ' '.
- if (e != null)
- pixelX = (int) e.nextTabStop((float) pixelX,
- startOffset + offset - s.offset);
- else
- pixelX += metrics.charWidth(' ');
- break;
- case '\n':
- // In case we have a newline, we must jump to the next line.
- pixelY += metrics.getHeight();
- pixelX = x;
- break;
- default:
- ++len;
- pixelWidth += metrics.charWidth(buffer[offset]);
- break;
- }
}
if (len > 0)
- g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
+ {
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ }
- return pixelX + pixelWidth;
+ return pixelX;
}
/**
@@ -174,7 +167,9 @@ public class Utilities
// The current maximum width.
int maxWidth = 0;
- for (int offset = s.offset; offset < (s.offset + s.count); ++offset)
+ int end = s.offset + s.count;
+ int count = 0;
+ for (int offset = s.offset; offset < end; offset++)
{
switch (buffer[offset])
{
@@ -182,7 +177,7 @@ public class Utilities
// In case we have a tab, we just 'jump' over the tab.
// When we have no tab expander we just use the width of 'm'.
if (e != null)
- pixelX = (int) e.nextTabStop((float) pixelX,
+ pixelX = (int) e.nextTabStop(pixelX,
startOffset + offset - s.offset);
else
pixelX += metrics.charWidth(' ');
@@ -190,21 +185,18 @@ public class Utilities
case '\n':
// In case we have a newline, we must 'draw'
// the buffer and jump on the next line.
- pixelX += metrics.charWidth(buffer[offset]);
- maxWidth = Math.max(maxWidth, pixelX - x);
- pixelX = x;
- break;
- default:
- // Here we draw the char.
- pixelX += metrics.charWidth(buffer[offset]);
- break;
- }
+ pixelX += metrics.charsWidth(buffer, offset - count, count);
+ count = 0;
+ break;
+ default:
+ count++;
+ }
}
// Take the last line into account.
- maxWidth = Math.max(maxWidth, pixelX - x);
+ pixelX += metrics.charsWidth(buffer, end - count, count);
- return maxWidth;
+ return pixelX - x;
}
/**
@@ -239,43 +231,41 @@ public class Utilities
int x, TabExpander te, int p0,
boolean round)
{
- // At the end of the for loop, this holds the requested model location
- int pos;
+ int found = s.count;
int currentX = x0;
- int width = 0;
+ int nextX = currentX;
- for (pos = 0; pos < s.count; pos++)
+ int end = s.offset + s.count;
+ for (int pos = s.offset; pos < end && found == s.count; pos++)
{
- char nextChar = s.array[s.offset+pos];
-
- if (nextChar == 0)
- break;
+ char nextChar = s.array[pos];
if (nextChar != '\t')
- width = fm.charWidth(nextChar);
+ nextX += fm.charWidth(nextChar);
else
{
if (te == null)
- width = fm.charWidth(' ');
+ nextX += fm.charWidth(' ');
else
- width = ((int) te.nextTabStop(currentX, pos)) - currentX;
+ nextX += ((int) te.nextTabStop(nextX, p0 + pos - s.offset));
}
- if (round)
+ if (x >= currentX && x < nextX)
{
- if (currentX + (width>>1) > x)
- break;
- }
- else
- {
- if (currentX + width > x)
- break;
+ // Found position.
+ if ((! round) || ((x - currentX) < (nextX - x)))
+ {
+ found = pos - s.offset;
+ }
+ else
+ {
+ found = pos + 1 - s.offset;
+ }
}
-
- currentX += width;
+ currentX = nextX;
}
- return pos + p0;
+ return found;
}
/**
@@ -543,28 +533,39 @@ public class Utilities
int x0, int x, TabExpander e,
int startOffset)
{
- int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false);
- BreakIterator breaker = BreakIterator.getWordInstance();
- breaker.setText(s);
-
- // If startOffset and s.offset differ then we need to use
- // that difference two convert the offset between the two metrics.
- int shift = startOffset - s.offset;
-
+ int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset,
+ false);
+ int breakLoc = mark;
// If mark is equal to the end of the string, just use that position.
- if (mark >= shift + s.count)
- return mark;
-
- // Try to find a word boundary previous to the mark at which we
- // can break the text.
- int preceding = breaker.preceding(mark + 1 - shift);
-
- if (preceding != 0)
- return preceding + shift;
-
- // If preceding is 0 we couldn't find a suitable word-boundary so
- // just break it on the character boundary
- return mark;
+ if (mark < s.count - 1)
+ {
+ for (int i = s.offset + mark; i >= s.offset; i--)
+ {
+ char ch = s.array[i];
+ if (ch < 256)
+ {
+ // For ASCII simply scan backwards for whitespace.
+ if (Character.isWhitespace(ch))
+ {
+ breakLoc = i - s.offset + 1;
+ break;
+ }
+ }
+ else
+ {
+ // Only query BreakIterator for complex chars.
+ BreakIterator bi = BreakIterator.getLineInstance();
+ bi.setText(s);
+ int pos = bi.preceding(i + 1);
+ if (pos > s.offset)
+ {
+ breakLoc = breakLoc - s.offset;
+ }
+ break;
+ }
+ }
+ }
+ return breakLoc;
}
/**
@@ -712,12 +713,12 @@ public class Utilities
offset,
Bias.Forward,
direction,
- null)
+ new Position.Bias[1])
: t.getUI().getNextVisualPositionFrom(t,
offset,
Bias.Forward,
direction,
- null);
+ new Position.Bias[1]);
}
catch (BadLocationException ble)
{
diff --git a/libjava/classpath/javax/swing/text/View.java b/libjava/classpath/javax/swing/text/View.java
index 55a63f6..c63ddbc 100644
--- a/libjava/classpath/javax/swing/text/View.java
+++ b/libjava/classpath/javax/swing/text/View.java
@@ -57,7 +57,6 @@ public abstract class View implements SwingConstants
public static final int X_AXIS = 0;
public static final int Y_AXIS = 1;
- private float width, height;
private Element elt;
private View parent;
@@ -93,7 +92,14 @@ public abstract class View implements SwingConstants
{
int numChildren = getViewCount();
for (int i = 0; i < numChildren; i++)
- getView(i).setParent(null);
+ {
+ View child = getView(i);
+ // It is important that we only reset the parent on views that
+ // actually belong to us. In FlowView the child may already be
+ // reparented.
+ if (child.getParent() == this)
+ child.setParent(null);
+ }
}
this.parent = parent;
@@ -263,7 +269,7 @@ public abstract class View implements SwingConstants
public void removeAll()
{
- replace(0, getViewCount(), new View[0]);
+ replace(0, getViewCount(), null);
}
public void remove(int index)
@@ -307,15 +313,16 @@ public abstract class View implements SwingConstants
{
int index = getViewIndex(x, y, allocation);
- if (index < -1)
- return null;
-
- Shape childAllocation = getChildAllocation(index, allocation);
-
- if (childAllocation.getBounds().contains(x, y))
- return getView(index).getToolTipText(x, y, childAllocation);
-
- return null;
+ String text = null;
+ if (index >= 0)
+ {
+ allocation = getChildAllocation(index, allocation);
+ Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation
+ : allocation.getBounds();
+ if (r.contains(x, y))
+ text = getView(index).getToolTipText(x, y, allocation);
+ }
+ return text;
}
/**
@@ -328,13 +335,17 @@ public abstract class View implements SwingConstants
public void preferenceChanged(View child, boolean width, boolean height)
{
- if (parent != null)
- parent.preferenceChanged(this, width, height);
+ View p = getParent();
+ if (p != null)
+ p.preferenceChanged(this, width, height);
}
public int getBreakWeight(int axis, float pos, float len)
{
- return BadBreakWeight;
+ int weight = BadBreakWeight;
+ if (len > getPreferredSpan(axis))
+ weight = GoodBreakWeight;
+ return weight;
}
public View breakView(int axis, int offset, float pos, float len)
@@ -370,12 +381,18 @@ public abstract class View implements SwingConstants
*/
public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
{
- Element el = getElement();
- DocumentEvent.ElementChange ec = ev.getChange(el);
- if (ec != null)
- updateChildren(ec, ev, vf);
- forwardUpdate(ec, ev, shape, vf);
- updateLayout(ec, ev, shape);
+ if (getViewCount() > 0)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ if (! updateChildren(ec, ev, vf))
+ ec = null;
+ }
+ forwardUpdate(ec, ev, shape, vf);
+ updateLayout(ec, ev, shape);
+ }
}
/**
@@ -429,12 +446,18 @@ public abstract class View implements SwingConstants
*/
public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
{
- Element el = getElement();
- DocumentEvent.ElementChange ec = ev.getChange(el);
- if (ec != null)
- updateChildren(ec, ev, vf);
- forwardUpdate(ec, ev, shape, vf);
- updateLayout(ec, ev, shape);
+ if (getViewCount() > 0)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ if (! updateChildren(ec, ev, vf))
+ ec = null;
+ }
+ forwardUpdate(ec, ev, shape, vf);
+ updateLayout(ec, ev, shape);
+ }
}
/**
@@ -465,10 +488,15 @@ public abstract class View implements SwingConstants
Element[] removed = ec.getChildrenRemoved();
int index = ec.getIndex();
- View[] newChildren = new View[added.length];
- for (int i = 0; i < added.length; ++i)
- newChildren[i] = vf.create(added[i]);
- replace(index, removed.length, newChildren);
+ View[] newChildren = null;
+ if (added != null)
+ {
+ newChildren = new View[added.length];
+ for (int i = 0; i < added.length; ++i)
+ newChildren[i] = vf.create(added[i]);
+ }
+ int numRemoved = removed != null ? removed.length : 0;
+ replace(index, numRemoved, newChildren);
return true;
}
@@ -598,10 +626,12 @@ public abstract class View implements SwingConstants
DocumentEvent ev, Shape shape)
{
if (ec != null && shape != null)
- preferenceChanged(null, true, true);
- Container c = getContainer();
- if (c != null)
- c.repaint();
+ {
+ preferenceChanged(null, true, true);
+ Container c = getContainer();
+ if (c != null)
+ c.repaint();
+ }
}
/**
@@ -750,7 +780,9 @@ public abstract class View implements SwingConstants
*/
public int viewToModel(float x, float y, Shape a)
{
- return viewToModel(x, y, a, new Position.Bias[0]);
+ Position.Bias[] biasRet = new Position.Bias[1];
+ biasRet[0] = Position.Bias.Forward;
+ return viewToModel(x, y, a, biasRet);
}
/**
diff --git a/libjava/classpath/javax/swing/text/WrappedPlainView.java b/libjava/classpath/javax/swing/text/WrappedPlainView.java
index a6c369a..00e12b1 100644
--- a/libjava/classpath/javax/swing/text/WrappedPlainView.java
+++ b/libjava/classpath/javax/swing/text/WrappedPlainView.java
@@ -83,7 +83,17 @@ public class WrappedPlainView extends BoxView implements TabExpander
/** The height of the line (used while painting) **/
int lineHeight;
-
+
+ /**
+ * The base offset for tab calculations.
+ */
+ private int tabBase;
+
+ /**
+ * The tab size.
+ */
+ private int tabSize;
+
/**
* The instance returned by {@link #getLineBuffer()}.
*/
@@ -121,10 +131,13 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
public float nextTabStop(float x, int tabStop)
{
- JTextComponent host = (JTextComponent)getContainer();
- float tabSizePixels = getTabSize()
- * host.getFontMetrics(host.getFont()).charWidth('m');
- return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
+ int next = (int) x;
+ if (tabSize != 0)
+ {
+ int numTabs = ((int) x - tabBase) / tabSize;
+ next = tabBase + (numTabs + 1) * tabSize;
+ }
+ return next;
}
/**
@@ -274,44 +287,32 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
protected int calculateBreakPosition(int p0, int p1)
{
- Container c = getContainer();
-
- int li = getLeftInset();
- int ti = getTopInset();
-
- Rectangle alloc = new Rectangle(li, ti,
- getWidth()-getRightInset()-li,
- getHeight()-getBottomInset()-ti);
-
- // Mimic a behavior observed in the RI.
- if (alloc.isEmpty())
- return 0;
-
- updateMetrics();
-
+ Segment s = new Segment();
try
{
- getDocument().getText(p0, p1 - p0, getLineBuffer());
+ getDocument().getText(p0, p1 - p0, s);
}
- catch (BadLocationException ble)
+ catch (BadLocationException ex)
{
- // this shouldn't happen
- throw new InternalError("Invalid offsets p0: " + p0 + " - p1: " + p1);
+ assert false : "Couldn't load text";
}
-
+ int width = getWidth();
+ int pos;
if (wordWrap)
- return Utilities.getBreakLocation(lineBuffer, metrics, alloc.x,
- alloc.x + alloc.width, this, p0);
+ pos = p0 + Utilities.getBreakLocation(s, metrics, tabBase,
+ tabBase + width, this, p0);
else
- return p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x,
- alloc.x + alloc.width, this, 0,
- true);
+ pos = p0 + Utilities.getTabbedTextOffset(s, metrics, tabBase,
+ tabBase + width, this, p0,
+ false);
+ return pos;
}
void updateMetrics()
{
Container component = getContainer();
metrics = component.getFontMetrics(component.getFont());
+ tabSize = getTabSize()* metrics.charWidth('m');
}
/**
@@ -350,9 +351,15 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f)
{
- super.insertUpdate(e, a, viewFactory);
+ // Update children efficiently.
+ updateChildren(e, a);
- // No repaint needed, as this is done by the WrappedLine instances.
+ // Notify children.
+ Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
+ : null;
+ View v = getViewAtPosition(e.getOffset(), r);
+ if (v != null)
+ v.insertUpdate(e, r, f);
}
/**
@@ -361,9 +368,15 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f)
{
- super.removeUpdate(e, a, viewFactory);
-
- // No repaint needed, as this is done by the WrappedLine instances.
+ // Update children efficiently.
+ updateChildren(e, a);
+
+ // Notify children.
+ Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
+ : null;
+ View v = getViewAtPosition(e.getOffset(), r);
+ if (v != null)
+ v.removeUpdate(e, r, f);
}
/**
@@ -373,11 +386,39 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f)
{
- super.changedUpdate(e, a, viewFactory);
-
- // No repaint needed, as this is done by the WrappedLine instances.
+ // Update children efficiently.
+ updateChildren(e, a);
}
-
+
+ /**
+ * Helper method. Updates the child views in response to
+ * insert/remove/change updates. This is here to be a little more efficient
+ * than the BoxView implementation.
+ *
+ * @param ev the document event
+ * @param a the shape
+ */
+ private void updateChildren(DocumentEvent ev, Shape a)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ Element[] removed = ec.getChildrenRemoved();
+ Element[] added = ec.getChildrenAdded();
+ View[] addedViews = new View[added.length];
+ for (int i = 0; i < added.length; i++)
+ addedViews[i] = new WrappedLine(added[i]);
+ replace(ec.getIndex(), removed.length, addedViews);
+ if (a != null)
+ {
+ preferenceChanged(null, true, true);
+ getContainer().repaint();
+ }
+ }
+ updateMetrics();
+ }
+
class WrappedLineCreator implements ViewFactory
{
// Creates a new WrappedLine
@@ -397,6 +438,9 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
public void paint(Graphics g, Shape a)
{
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ tabBase = r.x;
+
JTextComponent comp = (JTextComponent)getContainer();
// Ensure metrics are up-to-date.
updateMetrics();
@@ -434,7 +478,6 @@ public class WrappedPlainView extends BoxView implements TabExpander
public WrappedLine(Element elem)
{
super(elem);
- determineNumLines();
}
/**
@@ -449,10 +492,34 @@ public class WrappedPlainView extends BoxView implements TabExpander
int currStart = getStartOffset();
int currEnd;
int count = 0;
+
+ // Determine layered highlights.
+ Container c = getContainer();
+ LayeredHighlighter lh = null;
+ JTextComponent tc = null;
+ if (c instanceof JTextComponent)
+ {
+ tc = (JTextComponent) c;
+ Highlighter h = tc.getHighlighter();
+ if (h instanceof LayeredHighlighter)
+ lh = (LayeredHighlighter) h;
+ }
+
while (currStart < end)
{
currEnd = calculateBreakPosition(currStart, end);
+ // Paint layered highlights, if any.
+ if (lh != null)
+ {
+ // Exclude trailing newline in last line.
+ if (currEnd == end)
+ lh.paintLayeredHighlights(g, currStart, currEnd - 1, s, tc,
+ this);
+ else
+ lh.paintLayeredHighlights(g, currStart, currEnd, s, tc, this);
+
+ }
drawLine(currStart, currEnd, g, rect.x, rect.y + metrics.getAscent());
rect.y += lineHeight;
@@ -472,37 +539,29 @@ public class WrappedPlainView extends BoxView implements TabExpander
}
}
-
+
/**
* Calculates the number of logical lines that the Element
* needs to be displayed and updates the variable numLines
* accordingly.
*/
- void determineNumLines()
+ private int determineNumLines()
{
- numLines = 0;
+ int nLines = 0;
int end = getEndOffset();
- if (end == 0)
- return;
-
- int breakPoint;
for (int i = getStartOffset(); i < end;)
{
- numLines ++;
+ nLines++;
// careful: check that there's no off-by-one problem here
// depending on which position calculateBreakPosition returns
- breakPoint = calculateBreakPosition(i, end);
-
- if (breakPoint == 0)
- return;
+ int breakPoint = calculateBreakPosition(i, end);
- // If breakPoint is equal to the current index no further
- // line is needed and we can end the loop.
if (breakPoint == i)
- break;
+ i = breakPoint + 1;
else
i = breakPoint;
}
+ return nLines;
}
/**
@@ -547,7 +606,7 @@ public class WrappedPlainView extends BoxView implements TabExpander
// Throwing a BadLocationException is an observed behavior of the RI.
if (rect.isEmpty())
throw new BadLocationException("Unable to calculate view coordinates "
- + "when allocation area is empty.", 5);
+ + "when allocation area is empty.", pos);
Segment s = getLineBuffer();
int lineHeight = metrics.getHeight();
@@ -624,7 +683,7 @@ public class WrappedPlainView extends BoxView implements TabExpander
return currLineStart;
if (y > rect.y + rect.height)
- return end;
+ return end - 1;
// Note: rect.x and rect.width do not represent the width of painted
// text but the area where text *may* be painted. This means the width
@@ -685,22 +744,14 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
void updateDamage (Rectangle a)
{
- // If the allocation area is empty we can't do anything useful.
- // As determining the number of lines is impossible in that state we
- // reset it to an invalid value which can then be recalculated at a
- // later point.
- if (a == null || a.isEmpty())
+ int nLines = determineNumLines();
+ if (numLines != nLines)
{
- numLines = 1;
- return;
+ numLines = nLines;
+ preferenceChanged(this, false, true);
+ getContainer().repaint();
}
-
- int oldNumLines = numLines;
- determineNumLines();
-
- if (numLines != oldNumLines)
- preferenceChanged(this, false, true);
- else
+ else if (a != null)
getContainer().repaint(a.x, a.y, a.width, a.height);
}
@@ -714,7 +765,8 @@ public class WrappedPlainView extends BoxView implements TabExpander
*/
public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f)
{
- updateDamage((Rectangle)a);
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ updateDamage(r);
}
/**
@@ -736,7 +788,8 @@ public class WrappedPlainView extends BoxView implements TabExpander
// However this seems to cause no trouble and as it reduces the
// number of method calls it can stay this way.
- updateDamage((Rectangle)a);
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ updateDamage(r);
}
}
}
diff --git a/libjava/classpath/javax/swing/text/ZoneView.java b/libjava/classpath/javax/swing/text/ZoneView.java
new file mode 100644
index 0000000..6cabc6c
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/ZoneView.java
@@ -0,0 +1,442 @@
+/* ZoneView.java -- An effective BoxView subclass
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Shape;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+import javax.swing.event.DocumentEvent;
+
+/**
+ * A View implementation that delays loading of sub views until they are
+ * needed for display or internal transformations. This can be used for
+ * editors that need to handle large documents more effectivly than the
+ * standard {@link BoxView}.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ *
+ * @since 1.3
+ */
+public class ZoneView
+ extends BoxView
+{
+
+ /**
+ * The default zone view implementation. The specs suggest that this is
+ * a subclass of AsyncBoxView, so do we.
+ */
+ static class Zone
+ extends AsyncBoxView
+ {
+ /**
+ * The start position for this zone.
+ */
+ private Position p0;
+
+ /**
+ * The end position for this zone.
+ */
+ private Position p1;
+
+ /**
+ * Creates a new Zone for the specified element, start and end positions.
+ *
+ * @param el the element
+ * @param pos0 the start position
+ * @param pos1 the end position
+ * @param axis the major axis
+ */
+ Zone(Element el, Position pos0, Position pos1, int axis)
+ {
+ super(el, axis);
+ p0 = pos0;
+ p1 = pos1;
+ }
+
+ /**
+ * Returns the start offset of the zone.
+ *
+ * @return the start offset of the zone
+ */
+ public int getStartOffset()
+ {
+ return p0.getOffset();
+ }
+
+ /**
+ * Returns the end offset of the zone.
+ *
+ * @return the end offset of the zone
+ */
+ public int getEndOffset()
+ {
+ return p1.getOffset();
+ }
+ }
+
+ /**
+ * The maximumZoneSize.
+ */
+ private int maximumZoneSize;
+
+ /**
+ * The maximum number of loaded zones.
+ */
+ private int maxZonesLoaded;
+
+ /**
+ * A queue of loaded zones. When the number of loaded zones exceeds the
+ * maximum number of zones, the oldest zone(s) get unloaded.
+ */
+ private LinkedList loadedZones;
+
+ /**
+ * Creates a new ZoneView for the specified element and axis.
+ *
+ * @param element the element for which to create a ZoneView
+ * @param axis the major layout axis for the box
+ */
+ public ZoneView(Element element, int axis)
+ {
+ super(element, axis);
+ maximumZoneSize = 8192;
+ maxZonesLoaded = 3;
+ loadedZones = new LinkedList();
+ }
+
+ /**
+ * Sets the maximum zone size. Note that zones might still become larger
+ * then the size specified when a singe child view is larger for itself,
+ * because zones are formed on child view boundaries.
+ *
+ * @param size the maximum zone size to set
+ *
+ * @see #getMaximumZoneSize()
+ */
+ public void setMaximumZoneSize(int size)
+ {
+ maximumZoneSize = size;
+ }
+
+ /**
+ * Returns the maximum zone size. Note that zones might still become larger
+ * then the size specified when a singe child view is larger for itself,
+ * because zones are formed on child view boundaries.
+ *
+ * @return the maximum zone size
+ *
+ * @see #setMaximumZoneSize(int)
+ */
+ public int getMaximumZoneSize()
+ {
+ return maximumZoneSize;
+ }
+
+ /**
+ * Sets the maximum number of zones that are allowed to be loaded at the
+ * same time. If the new number of allowed zones is smaller then the
+ * previous settings, this unloads all zones the aren't allowed to be
+ * loaded anymore.
+ *
+ * @param num the number of zones allowed to be loaded at the same time
+ *
+ * @throws IllegalArgumentException if <code>num &lt;= 0</code>
+ *
+ * @see #getMaxZonesLoaded()
+ */
+ public void setMaxZonesLoaded(int num)
+ {
+ if (num < 1)
+ throw new IllegalArgumentException("Illegal number of zones");
+ maxZonesLoaded = num;
+ unloadOldestZones();
+ }
+
+ /**
+ * Returns the number of zones that are allowed to be loaded.
+ *
+ * @return the number of zones that are allowed to be loaded
+ *
+ * @see #setMaxZonesLoaded(int)
+ */
+ public int getMaxZonesLoaded()
+ {
+ return maxZonesLoaded;
+ }
+
+ /**
+ * Gets called after a zone has been loaded. This unloads the oldest zone(s)
+ * when the maximum number of zones is reached.
+ *
+ * @param zone the zone that has been loaded
+ */
+ protected void zoneWasLoaded(View zone)
+ {
+ loadedZones.addLast(zone);
+ unloadOldestZones();
+ }
+
+ /**
+ * This unloads the specified zone. This is implemented to simply remove
+ * all child views from that zone.
+ *
+ * @param zone the zone to be unloaded
+ */
+ protected void unloadZone(View zone)
+ {
+ zone.removeAll();
+ }
+
+ /**
+ * Returns <code>true</code> when the specified zone is loaded,
+ * <code>false</code> otherwise. The default implementation checks if
+ * the zone view has child elements.
+ *
+ * @param zone the zone view to check
+ *
+ * @return <code>true</code> when the specified zone is loaded,
+ * <code>false</code> otherwise
+ */
+ protected boolean isZoneLoaded(View zone)
+ {
+ return zone.getViewCount() > 0;
+ }
+
+ /**
+ * Creates a zone for the specified range. Subclasses can override this
+ * to provide a custom implementation for the zones.
+ *
+ * @param p0 the start of the range
+ * @param p1 the end of the range
+ *
+ * @return the zone
+ */
+ protected View createZone(int p0, int p1)
+ {
+ Document doc = getDocument();
+ Position pos0 = null;
+ Position pos1 = null;
+ try
+ {
+ pos0 = doc.createPosition(p0);
+ pos1 = doc.createPosition(p1);
+ }
+ catch (BadLocationException ex)
+ {
+ assert false : "Must not happen";
+ }
+ Zone zone = new Zone(getElement(), pos0, pos1, getAxis());
+ return zone;
+ }
+
+ // --------------------------------------------------------------------------
+ // CompositeView methods.
+ // --------------------------------------------------------------------------
+
+ /**
+ * Overridden to not load all the child views. This methods creates
+ * initial zones without actually loading them.
+ *
+ * @param vf not used
+ */
+ protected void loadChildren(ViewFactory vf)
+ {
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ append(createZone(p0, p1));
+ checkZoneAt(p0);
+ }
+
+ /**
+ * Returns the index of the child view at the document position
+ * <code>pos</code>.
+ *
+ * This overrides the CompositeView implementation because the ZoneView does
+ * not provide a one to one mapping from Elements to Views.
+ *
+ * @param pos the document position
+ *
+ * @return the index of the child view at the document position
+ * <code>pos</code>
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ int index = -1;
+ boolean found = false;
+ if (pos >= getStartOffset() && pos <= getEndOffset())
+ {
+ int upper = getViewCount() - 1;
+ int lower = 0;
+ index = (upper - lower) / 2 + lower;
+ int bias = 0;
+ do
+ {
+ View child = getView(index);
+ int childStart = child.getStartOffset();
+ int childEnd = child.getEndOffset();
+ if (pos >= childStart && pos < childEnd)
+ found = true;
+ else if (pos < childStart)
+ {
+ upper = index;
+ bias = -1;
+ }
+ else if (pos >= childEnd)
+ {
+ lower = index;
+ bias = 1;
+ }
+ if (! found)
+ {
+ int newIndex = (upper - lower) / 2 + lower;
+ if (newIndex == index)
+ index = newIndex + bias;
+ else
+ index = newIndex;
+ }
+ } while (upper != lower && ! found);
+ }
+ // If no child view actually covers the specified offset, reset index to
+ // -1.
+ if (! found)
+ index = -1;
+ return index;
+ }
+
+ // --------------------------------------------------------------------------
+ // View methods.
+ // --------------------------------------------------------------------------
+
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ // TODO: Implement this.
+ }
+
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ // TODO: Implement this.
+ }
+
+ protected boolean updateChildren(DocumentEvent.ElementChange ec,
+ DocumentEvent e, ViewFactory vf)
+ {
+ // TODO: Implement this.
+ return false;
+ }
+
+ // --------------------------------------------------------------------------
+ // Internal helper methods.
+ // --------------------------------------------------------------------------
+
+ /**
+ * A helper method to unload the oldest zones when there are more loaded
+ * zones then allowed.
+ */
+ private void unloadOldestZones()
+ {
+ int maxZones = getMaxZonesLoaded();
+ while (loadedZones.size() > maxZones)
+ {
+ View zone = (View) loadedZones.removeFirst();
+ unloadZone(zone);
+ }
+ }
+
+ /**
+ * Checks if the zone view at position <code>pos</code> should be split
+ * (its size is greater than maximumZoneSize) and tries to split it.
+ *
+ * @param pos the document position to check
+ */
+ private void checkZoneAt(int pos)
+ {
+ int viewIndex = getViewIndexAtPosition(pos); //, Position.Bias.Forward);
+ View view = getView(viewIndex);
+ int p0 = view.getStartOffset();
+ int p1 = view.getEndOffset();
+ if (p1 - p0 > maximumZoneSize)
+ splitZone(viewIndex, p0, p1);
+ }
+
+ /**
+ * Tries to break the view at the specified index and inside the specified
+ * range into pieces that are acceptable with respect to the maximum zone
+ * size.
+ *
+ * @param index the index of the view to split
+ * @param p0 the start offset
+ * @param p1 the end offset
+ */
+ private void splitZone(int index, int p0, int p1)
+ {
+ ArrayList newZones = new ArrayList();
+ int p = p0;
+ do
+ {
+ p0 = p;
+ p = Math.min(getPreferredZoneEnd(p0), p1);
+ newZones.add(createZone(p0, p));
+ } while (p < p1);
+ View[] newViews = new View[newZones.size()];
+ newViews = (View[]) newZones.toArray(newViews);
+ replace(index, 1, newViews);
+ }
+
+ /**
+ * Calculates the positions at which a zone split is performed. This
+ * tries to create zones sized close to half the maximum zone size.
+ *
+ * @param start the start offset
+ *
+ * @return the preferred end offset
+ */
+ private int getPreferredZoneEnd(int start)
+ {
+ Element el = getElement();
+ int index = el.getElementIndex(start + (maximumZoneSize / 2));
+ Element child = el.getElement(index);
+ int p0 = child.getStartOffset();
+ int p1 = child.getEndOffset();
+ int end = p1;
+ if (p0 - start > maximumZoneSize && p0 > start)
+ end = p0;
+ return end;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/BRView.java b/libjava/classpath/javax/swing/text/html/BRView.java
index 5521fed..7d0d516 100644
--- a/libjava/classpath/javax/swing/text/html/BRView.java
+++ b/libjava/classpath/javax/swing/text/html/BRView.java
@@ -44,8 +44,7 @@ import javax.swing.text.Element;
* Handled the HTML BR tag.
*/
class BRView
- extends NullView
-
+ extends InlineView
{
/**
* Creates the new BR view.
@@ -66,6 +65,6 @@ class BRView
if (axis == X_AXIS)
return ForcedBreakWeight;
else
- return BadBreakWeight;
+ return super.getBreakWeight(axis, pos, len);
}
}
diff --git a/libjava/classpath/javax/swing/text/html/BlockView.java b/libjava/classpath/javax/swing/text/html/BlockView.java
index 6274e7b..b05c983 100644
--- a/libjava/classpath/javax/swing/text/html/BlockView.java
+++ b/libjava/classpath/javax/swing/text/html/BlockView.java
@@ -38,9 +38,12 @@ exception statement from your version. */
package javax.swing.text.html;
+import gnu.javax.swing.text.html.css.Length;
+
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
+import java.util.HashMap;
import javax.swing.SizeRequirements;
import javax.swing.event.DocumentEvent;
@@ -55,7 +58,106 @@ import javax.swing.text.ViewFactory;
*/
public class BlockView extends BoxView
{
-
+
+ /**
+ * Stores information about child positioning according to the
+ * CSS attributes position, left, right, top and bottom.
+ */
+ private static class PositionInfo
+ {
+ // TODO: Use enums when available.
+
+ /**
+ * Static positioning. This is the default and is thus rarely really
+ * used.
+ */
+ static final int STATIC = 0;
+
+ /**
+ * Relative positioning. The box is teaked relative to its static
+ * computed bounds.
+ */
+ static final int RELATIVE = 1;
+
+ /**
+ * Absolute positioning. The box is moved relative to the parent's box.
+ */
+ static final int ABSOLUTE = 2;
+
+ /**
+ * Like ABSOLUTE, with some fixation against the viewport (not yet
+ * implemented).
+ */
+ static final int FIXED = 3;
+
+ /**
+ * The type according to the constants of this class.
+ */
+ int type;
+
+ /**
+ * The left constraint, null if not set.
+ */
+ Length left;
+
+ /**
+ * The right constraint, null if not set.
+ */
+ Length right;
+
+ /**
+ * The top constraint, null if not set.
+ */
+ Length top;
+
+ /**
+ * The bottom constraint, null if not set.
+ */
+ Length bottom;
+
+ /**
+ * Creates a new PositionInfo object.
+ *
+ * @param typ the type to set
+ * @param l the left constraint
+ * @param r the right constraint
+ * @param t the top constraint
+ * @param b the bottom constraint
+ */
+ PositionInfo(int typ, Length l, Length r, Length t, Length b)
+ {
+ type = typ;
+ left = l;
+ right = r;
+ top = t;
+ bottom = b;
+ }
+ }
+
+ /**
+ * The attributes for this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The box painter for this view.
+ *
+ * This is package private because the TableView needs access to it.
+ */
+ StyleSheet.BoxPainter painter;
+
+ /**
+ * The width and height as specified in the stylesheet, null if not
+ * specified. The first value is the X_AXIS, the second the Y_AXIS. You
+ * can index this directly by the X_AXIS and Y_AXIS constants.
+ */
+ private Length[] cssSpans;
+
+ /**
+ * Stores additional CSS layout information.
+ */
+ private HashMap positionInfo;
+
/**
* Creates a new view that represents an html box.
* This can be used for a number of elements.
@@ -66,8 +168,10 @@ public class BlockView extends BoxView
public BlockView(Element elem, int axis)
{
super(elem, axis);
+ cssSpans = new Length[2];
+ positionInfo = new HashMap();
}
-
+
/**
* Creates the parent view for this. It is called before
* any other methods, if the parent view is working properly.
@@ -99,12 +203,27 @@ public class BlockView extends BoxView
protected SizeRequirements calculateMajorAxisRequirements(int axis,
SizeRequirements r)
{
- SizeRequirements sr = super.calculateMajorAxisRequirements(axis, r);
- // FIXME: adjust it if the CSS width or height attribute is specified
- // and applicable
- return sr;
+ if (r == null)
+ r = new SizeRequirements();
+
+ if (setCSSSpan(r, axis))
+ {
+ // If we have set the span from CSS, then we need to adjust
+ // the margins.
+ SizeRequirements parent = super.calculateMajorAxisRequirements(axis,
+ null);
+ int margin = axis == X_AXIS ? getLeftInset() + getRightInset()
+ : getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ constrainSize(axis, r, parent);
+ }
+ else
+ r = super.calculateMajorAxisRequirements(axis, r);
+ return r;
}
-
+
/**
* Calculates the requirements along the minor axis.
* This is implemented to call the superclass and then
@@ -118,12 +237,89 @@ public class BlockView extends BoxView
protected SizeRequirements calculateMinorAxisRequirements(int axis,
SizeRequirements r)
{
- SizeRequirements sr = super.calculateMinorAxisRequirements(axis, r);
- // FIXME: adjust it if the CSS width or height attribute is specified
- // and applicable.
- return sr;
+ if (r == null)
+ r = new SizeRequirements();
+
+ if (setCSSSpan(r, axis))
+ {
+ // If we have set the span from CSS, then we need to adjust
+ // the margins.
+ SizeRequirements parent = super.calculateMinorAxisRequirements(axis,
+ null);
+ int margin = axis == X_AXIS ? getLeftInset() + getRightInset()
+ : getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ constrainSize(axis, r, parent);
+ }
+ else
+ r = super.calculateMinorAxisRequirements(axis, r);
+
+ // Apply text alignment if appropriate.
+ if (axis == X_AXIS)
+ {
+ Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null)
+ {
+ String al = o.toString().trim();
+ if (al.equals("center"))
+ r.alignment = 0.5f;
+ else if (al.equals("right"))
+ r.alignment = 1.0f;
+ else
+ r.alignment = 0.0f;
+ }
+ }
+ return r;
+ }
+
+ /**
+ * Sets the span on the SizeRequirements object according to the
+ * according CSS span value, when it is set.
+ *
+ * @param r the size requirements
+ * @param axis the axis
+ *
+ * @return <code>true</code> when the CSS span has been set,
+ * <code>false</code> otherwise
+ */
+ private boolean setCSSSpan(SizeRequirements r, int axis)
+ {
+ boolean ret = false;
+ Length span = cssSpans[axis];
+ // We can't set relative CSS spans here because we don't know
+ // yet about the allocated span. Instead we use the view's
+ // normal requirements.
+ if (span != null && ! span.isPercentage())
+ {
+ r.minimum = (int) span.getValue();
+ r.preferred = (int) span.getValue();
+ r.maximum = (int) span.getValue();
+ ret = true;
+ }
+ return ret;
}
-
+
+ /**
+ * Constrains the <code>r</code> requirements according to
+ * <code>min</code>.
+ *
+ * @param axis the axis
+ * @param r the requirements to constrain
+ * @param min the constraining requirements
+ */
+ private void constrainSize(int axis, SizeRequirements r,
+ SizeRequirements min)
+ {
+ if (min.minimum > r.minimum)
+ {
+ r.minimum = min.minimum;
+ r.preferred = min.minimum;
+ r.maximum = Math.max(r.maximum, min.maximum);
+ }
+ }
+
/**
* Lays out the box along the minor axis (the axis that is
* perpendicular to the axis that it represents). The results
@@ -142,10 +338,133 @@ public class BlockView extends BoxView
protected void layoutMinorAxis(int targetSpan, int axis,
int[] offsets, int[] spans)
{
- // FIXME: Not implemented.
- super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+ int viewCount = getViewCount();
+ for (int i = 0; i < viewCount; i++)
+ {
+ View view = getView(i);
+ int min = (int) view.getMinimumSpan(axis);
+ int max;
+ // Handle CSS span value of child.
+ Length length = cssSpans[axis];
+ if (length != null)
+ {
+ min = Math.max((int) length.getValue(targetSpan), min);
+ max = min;
+ }
+ else
+ max = (int) view.getMaximumSpan(axis);
+
+ if (max < targetSpan)
+ {
+ // Align child.
+ float align = view.getAlignment(axis);
+ offsets[i] = (int) ((targetSpan - max) * align);
+ spans[i] = max;
+ }
+ else
+ {
+ offsets[i] = 0;
+ spans[i] = Math.max(min, targetSpan);
+ }
+
+ // Adjust according to CSS position info.
+ positionView(targetSpan, axis, i, offsets, spans);
+ }
}
-
+
+ /**
+ * Overridden to perform additional CSS layout (absolute/relative
+ * positioning).
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis,
+ int[] offsets, int[] spans)
+ {
+ super.layoutMajorAxis(targetSpan, axis, offsets, spans);
+
+ // Adjust according to CSS position info.
+ int viewCount = getViewCount();
+ for (int i = 0; i < viewCount; i++)
+ {
+ positionView(targetSpan, axis, i, offsets, spans);
+ }
+ }
+
+ /**
+ * Positions a view according to any additional CSS constraints.
+ *
+ * @param targetSpan the target span
+ * @param axis the axis
+ * @param i the index of the view
+ * @param offsets the offsets get placed here
+ * @param spans the spans get placed here
+ */
+ private void positionView(int targetSpan, int axis, int i, int[] offsets,
+ int[] spans)
+ {
+ View view = getView(i);
+ PositionInfo pos = (PositionInfo) positionInfo.get(view);
+ if (pos != null)
+ {
+ int p0 = -1;
+ int p1 = -1;
+ if (axis == X_AXIS)
+ {
+ Length l = pos.left;
+ if (l != null)
+ p0 = (int) l.getValue(targetSpan);
+ l = pos.right;
+ if (l != null)
+ p1 = (int) l.getValue(targetSpan);
+ }
+ else
+ {
+ Length l = pos.top;
+ if (l != null)
+ p0 = (int) l.getValue(targetSpan);
+ l = pos.bottom;
+ if (l != null)
+ p1 = (int) l.getValue(targetSpan);
+ }
+ if (pos.type == PositionInfo.ABSOLUTE
+ || pos.type == PositionInfo.FIXED)
+ {
+ if (p0 != -1)
+ {
+ offsets[i] = p0;
+ if (p1 != -1)
+ {
+ // Overrides computed width. (Possibly overconstrained
+ // when the width attribute was set too.)
+ spans[i] = targetSpan - p1 - offsets[i];
+ }
+ }
+ else if (p1 != -1)
+ {
+ // Preserve any computed width.
+ offsets[i] = targetSpan - p1 - spans[i];
+ }
+ }
+ else if (pos.type == PositionInfo.RELATIVE)
+ {
+ if (p0 != -1)
+ {
+ offsets[i] += p0;
+ if (p1 != -1)
+ {
+ // Overrides computed width. (Possibly overconstrained
+ // when the width attribute was set too.)
+ spans[i] = spans[i] - p0 - p1 - offsets[i];
+ }
+ }
+ else if (p1 != -1)
+ {
+ // Preserve any computed width.
+ offsets[i] -= p1;
+ }
+ }
+ }
+ }
+
/**
* Paints using the given graphics configuration and shape.
* This delegates to the css box painter to paint the
@@ -156,14 +475,16 @@ public class BlockView extends BoxView
*/
public void paint(Graphics g, Shape a)
{
- Rectangle rect = (Rectangle) a;
- // FIXME: not fully implemented
- getStyleSheet().getBoxPainter(getAttributes()).paint(g, rect.x, rect.y,
- rect.width,
- rect.height, this);
+ Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+
+ // Debug output. Shows blocks in green rectangles.
+ // g.setColor(Color.GREEN);
+ // g.drawRect(rect.x, rect.y, rect.width, rect.height);
+
+ painter.paint(g, rect.x, rect.y, rect.width, rect.height, this);
super.paint(g, a);
}
-
+
/**
* Fetches the attributes to use when painting.
*
@@ -171,7 +492,9 @@ public class BlockView extends BoxView
*/
public AttributeSet getAttributes()
{
- return getStyleSheet().getViewAttributes(this);
+ if (attributes == null)
+ attributes = getStyleSheet().getViewAttributes(this);
+ return attributes;
}
/**
@@ -200,14 +523,17 @@ public class BlockView extends BoxView
public float getAlignment(int axis)
{
if (axis == X_AXIS)
- return 0.0F;
+ return super.getAlignment(axis);
if (axis == Y_AXIS)
{
if (getViewCount() == 0)
return 0.0F;
float prefHeight = getPreferredSpan(Y_AXIS);
- float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS);
- return (firstRowHeight / 2.F) / prefHeight;
+ View first = getView(0);
+ float firstRowHeight = first.getPreferredSpan(Y_AXIS);
+ return prefHeight != 0 ? (firstRowHeight * first.getAlignment(Y_AXIS))
+ / prefHeight
+ : 0;
}
throw new IllegalArgumentException("Invalid Axis");
}
@@ -227,7 +553,8 @@ public class BlockView extends BoxView
// If more elements were added, then need to set the properties for them
int currPos = ev.getOffset();
- if (currPos <= getStartOffset() && (currPos + ev.getLength()) >= getEndOffset())
+ if (currPos <= getStartOffset()
+ && (currPos + ev.getLength()) >= getEndOffset())
setPropertiesFromAttributes();
}
@@ -284,9 +611,33 @@ public class BlockView extends BoxView
*/
protected void setPropertiesFromAttributes()
{
- // FIXME: Not implemented (need to use StyleSheet).
+ // Fetch attributes.
+ StyleSheet ss = getStyleSheet();
+ attributes = ss.getViewAttributes(this);
+
+ // Fetch painter.
+ painter = ss.getBoxPainter(attributes);
+
+ // Update insets.
+ if (attributes != null)
+ {
+ setInsets((short) painter.getInset(TOP, this),
+ (short) painter.getInset(LEFT, this),
+ (short) painter.getInset(BOTTOM, this),
+ (short) painter.getInset(RIGHT, this));
+ }
+
+ // Fetch width and height.
+ float emBase = ss.getEMBase(attributes);
+ float exBase = ss.getEXBase(attributes);
+ cssSpans[X_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.WIDTH);
+ if (cssSpans[X_AXIS] != null)
+ cssSpans[X_AXIS].setFontBases(emBase, exBase);
+ cssSpans[Y_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.HEIGHT);
+ if (cssSpans[Y_AXIS] != null)
+ cssSpans[Y_AXIS].setFontBases(emBase, exBase);
}
-
+
/**
* Gets the default style sheet.
*
@@ -294,8 +645,77 @@ public class BlockView extends BoxView
*/
protected StyleSheet getStyleSheet()
{
- StyleSheet styleSheet = new StyleSheet();
- styleSheet.importStyleSheet(getClass().getResource(HTMLEditorKit.DEFAULT_CSS));
- return styleSheet;
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Overridden to fetch additional CSS layout information.
+ */
+ public void replace(int offset, int length, View[] views)
+ {
+ // First remove unneeded stuff.
+ for (int i = 0; i < length; i++)
+ {
+ View child = getView(i + offset);
+ positionInfo.remove(child);
+ }
+
+ // Call super to actually replace the views.
+ super.replace(offset, length, views);
+
+ // Now fetch the position infos for the new views.
+ for (int i = 0; i < views.length; i++)
+ {
+ fetchLayoutInfo(views[i]);
+ }
+ }
+
+ /**
+ * Fetches and stores the layout info for the specified view.
+ *
+ * @param view the view for which the layout info is stored
+ */
+ private void fetchLayoutInfo(View view)
+ {
+ AttributeSet atts = view.getAttributes();
+ Object o = atts.getAttribute(CSS.Attribute.POSITION);
+ if (o != null && o instanceof String && ! o.equals("static"))
+ {
+ int type;
+ if (o.equals("relative"))
+ type = PositionInfo.RELATIVE;
+ else if (o.equals("absolute"))
+ type = PositionInfo.ABSOLUTE;
+ else if (o.equals("fixed"))
+ type = PositionInfo.FIXED;
+ else
+ type = PositionInfo.STATIC;
+
+ if (type != PositionInfo.STATIC)
+ {
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ Length left = (Length) atts.getAttribute(CSS.Attribute.LEFT);
+ if (left != null)
+ left.setFontBases(emBase, exBase);
+ Length right = (Length) atts.getAttribute(CSS.Attribute.RIGHT);
+ if (right != null)
+ right.setFontBases(emBase, exBase);
+ Length top = (Length) atts.getAttribute(CSS.Attribute.TOP);
+ if (top != null)
+ top.setFontBases(emBase, exBase);
+ Length bottom = (Length) atts.getAttribute(CSS.Attribute.BOTTOM);
+ if (bottom != null)
+ bottom.setFontBases(emBase, exBase);
+ if (left != null || right != null || top != null || bottom != null)
+ {
+ PositionInfo pos = new PositionInfo(type, left, right, top,
+ bottom);
+ positionInfo.put(view, pos);
+ }
+ }
+ }
}
}
diff --git a/libjava/classpath/javax/swing/text/html/CSS.java b/libjava/classpath/javax/swing/text/html/CSS.java
index c248e75..77f94a6 100644
--- a/libjava/classpath/javax/swing/text/html/CSS.java
+++ b/libjava/classpath/javax/swing/text/html/CSS.java
@@ -37,8 +37,19 @@ exception statement from your version. */
package javax.swing.text.html;
+import gnu.javax.swing.text.html.css.BorderStyle;
+import gnu.javax.swing.text.html.css.BorderWidth;
+import gnu.javax.swing.text.html.css.CSSColor;
+import gnu.javax.swing.text.html.css.FontSize;
+import gnu.javax.swing.text.html.css.FontStyle;
+import gnu.javax.swing.text.html.css.FontWeight;
+import gnu.javax.swing.text.html.css.Length;
+
import java.io.Serializable;
import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import javax.swing.text.MutableAttributeSet;
/**
* Provides CSS attributes to be used by the HTML view classes. The constants
@@ -388,6 +399,36 @@ public class CSS implements Serializable
public static final Attribute WORD_SPACING =
new Attribute("word-spacing", true, "normal");
+ // Some GNU Classpath specific extensions.
+ static final Attribute BORDER_TOP_STYLE =
+ new Attribute("border-top-style", false, null);
+ static final Attribute BORDER_BOTTOM_STYLE =
+ new Attribute("border-bottom-style", false, null);
+ static final Attribute BORDER_LEFT_STYLE =
+ new Attribute("border-left-style", false, null);
+ static final Attribute BORDER_RIGHT_STYLE =
+ new Attribute("border-right-style", false, null);
+ static final Attribute BORDER_TOP_COLOR =
+ new Attribute("border-top-color", false, null);
+ static final Attribute BORDER_BOTTOM_COLOR =
+ new Attribute("border-bottom-color", false, null);
+ static final Attribute BORDER_LEFT_COLOR =
+ new Attribute("border-left-color", false, null);
+ static final Attribute BORDER_RIGHT_COLOR =
+ new Attribute("border-right-color", false, null);
+ static final Attribute BORDER_SPACING =
+ new Attribute("border-spacing", false, null);
+ static final Attribute POSITION =
+ new Attribute("position", false, null);
+ static final Attribute LEFT =
+ new Attribute("left", false, null);
+ static final Attribute RIGHT =
+ new Attribute("right", false, null);
+ static final Attribute TOP =
+ new Attribute("top", false, null);
+ static final Attribute BOTTOM =
+ new Attribute("bottom", false, null);
+
/**
* The attribute string.
*/
@@ -459,4 +500,237 @@ public class CSS implements Serializable
return defaultValue;
}
}
+
+ /**
+ * Maps attribute values (String) to some converter class, based on the
+ * key.
+ *
+ * @param att the key
+ * @param v the value
+ *
+ * @return the wrapped value
+ */
+ static Object getValue(Attribute att, String v)
+ {
+ Object o;
+ if (att == Attribute.FONT_SIZE)
+ o = new FontSize(v);
+ else if (att == Attribute.FONT_WEIGHT)
+ o = new FontWeight(v);
+ else if (att == Attribute.FONT_STYLE)
+ o = new FontStyle(v);
+ else if (att == Attribute.COLOR || att == Attribute.BACKGROUND_COLOR
+ || att == Attribute.BORDER_COLOR
+ || att == Attribute.BORDER_TOP_COLOR
+ || att == Attribute.BORDER_BOTTOM_COLOR
+ || att == Attribute.BORDER_LEFT_COLOR
+ || att == Attribute.BORDER_RIGHT_COLOR)
+ o = new CSSColor(v);
+ else if (att == Attribute.MARGIN || att == Attribute.MARGIN_BOTTOM
+ || att == Attribute.MARGIN_LEFT || att == Attribute.MARGIN_RIGHT
+ || att == Attribute.MARGIN_TOP || att == Attribute.WIDTH
+ || att == Attribute.HEIGHT
+ || att == Attribute.PADDING || att == Attribute.PADDING_BOTTOM
+ || att == Attribute.PADDING_LEFT || att == Attribute.PADDING_RIGHT
+ || att == Attribute.PADDING_TOP
+ || att == Attribute.LEFT || att == Attribute.RIGHT
+ || att == Attribute.TOP || att == Attribute.BOTTOM)
+ o = new Length(v);
+ else if (att == Attribute.BORDER_WIDTH || att == Attribute.BORDER_TOP_WIDTH
+ || att == Attribute.BORDER_LEFT_WIDTH
+ || att == Attribute.BORDER_RIGHT_WIDTH
+ || att == Attribute.BORDER_BOTTOM_WIDTH)
+ o = new BorderWidth(v);
+ else
+ o = v;
+ return o;
+ }
+
+ static void addInternal(MutableAttributeSet atts, Attribute a, String v)
+ {
+ if (a == Attribute.BACKGROUND)
+ parseBackgroundShorthand(atts, v);
+ else if (a == Attribute.PADDING)
+ parsePaddingShorthand(atts, v);
+ else if (a == Attribute.MARGIN)
+ parseMarginShorthand(atts, v);
+ else if (a == Attribute.BORDER || a == Attribute.BORDER_LEFT
+ || a == Attribute.BORDER_RIGHT || a == Attribute.BORDER_TOP
+ || a == Attribute.BORDER_BOTTOM)
+ parseBorderShorthand(atts, v, a);
+ }
+
+ /**
+ * Parses the background shorthand and translates it to more specific
+ * background attributes.
+ *
+ * @param atts the attributes
+ * @param v the value
+ */
+ private static void parseBackgroundShorthand(MutableAttributeSet atts,
+ String v)
+ {
+ StringTokenizer tokens = new StringTokenizer(v, " ");
+ while (tokens.hasMoreElements())
+ {
+ String token = tokens.nextToken();
+ if (CSSColor.isValidColor(token))
+ atts.addAttribute(Attribute.BACKGROUND_COLOR,
+ new CSSColor(token));
+ }
+ }
+
+ /**
+ * Parses the padding shorthand and translates to the specific padding
+ * values.
+ *
+ * @param atts the attributes
+ * @param v the actual value
+ */
+ private static void parsePaddingShorthand(MutableAttributeSet atts, String v)
+ {
+ StringTokenizer tokens = new StringTokenizer(v, " ");
+ int numTokens = tokens.countTokens();
+ if (numTokens == 1)
+ {
+ Length l = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l);
+ atts.addAttribute(Attribute.PADDING_LEFT, l);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l);
+ atts.addAttribute(Attribute.PADDING_TOP, l);
+ }
+ else if (numTokens == 2)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l1);
+ atts.addAttribute(Attribute.PADDING_TOP, l1);
+ atts.addAttribute(Attribute.PADDING_LEFT, l2);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l2);
+ }
+ else if (numTokens == 3)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_TOP, l1);
+ atts.addAttribute(Attribute.PADDING_LEFT, l2);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l2);
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l3);
+ }
+ else
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ Length l4 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_TOP, l1);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l2);
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l3);
+ atts.addAttribute(Attribute.PADDING_LEFT, l4);
+ }
+ }
+
+ /**
+ * Parses the margin shorthand and translates to the specific margin
+ * values.
+ *
+ * @param atts the attributes
+ * @param v the actual value
+ */
+ private static void parseMarginShorthand(MutableAttributeSet atts, String v)
+ {
+ StringTokenizer tokens = new StringTokenizer(v, " ");
+ int numTokens = tokens.countTokens();
+ if (numTokens == 1)
+ {
+ Length l = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l);
+ atts.addAttribute(Attribute.MARGIN_TOP, l);
+ }
+ else if (numTokens == 2)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l1);
+ atts.addAttribute(Attribute.MARGIN_TOP, l1);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l2);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l2);
+ }
+ else if (numTokens == 3)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_TOP, l1);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l2);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l2);
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l3);
+ }
+ else
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ Length l4 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_TOP, l1);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l2);
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l3);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l4);
+ }
+ }
+
+ /**
+ * Parses the CSS border shorthand attribute and translates it to the
+ * more specific border attributes.
+ *
+ * @param atts the attribute
+ * @param value the value
+ */
+ private static void parseBorderShorthand(MutableAttributeSet atts,
+ String value, Attribute cssAtt)
+ {
+ StringTokenizer tokens = new StringTokenizer(value, " ");
+ while (tokens.hasMoreTokens())
+ {
+ String token = tokens.nextToken();
+ if (BorderStyle.isValidStyle(token))
+ {
+ if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_LEFT_STYLE, token);
+ if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_RIGHT_STYLE, token);
+ if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_BOTTOM_STYLE, token);
+ if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_TOP_STYLE, token);
+ }
+ else if (BorderWidth.isValid(token))
+ {
+ BorderWidth w = new BorderWidth(token);
+ if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_LEFT_WIDTH, w);
+ if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_RIGHT_WIDTH, w);
+ if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_BOTTOM_WIDTH, w);
+ if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_TOP_WIDTH, w);
+ }
+ else if (CSSColor.isValidColor(token))
+ {
+ CSSColor c = new CSSColor(token);
+ if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_LEFT_COLOR, c);
+ if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_RIGHT_COLOR, c);
+ if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_BOTTOM_COLOR, c);
+ if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_TOP_COLOR, c);
+ }
+ }
+ }
}
diff --git a/libjava/classpath/javax/swing/text/html/CSSBorder.java b/libjava/classpath/javax/swing/text/html/CSSBorder.java
new file mode 100644
index 0000000..fff6b01
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/CSSBorder.java
@@ -0,0 +1,421 @@
+/* CSSBorder.java -- A border for rendering CSS border styles
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import gnu.javax.swing.text.html.css.BorderWidth;
+import gnu.javax.swing.text.html.css.CSSColor;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+
+import javax.swing.border.Border;
+import javax.swing.text.AttributeSet;
+
+/**
+ * A border implementation to render CSS border styles.
+ */
+class CSSBorder
+ implements Border
+{
+
+ /**
+ * The CSS border styles.
+ */
+
+ private static final int STYLE_NOT_SET = -1;
+ private static final int STYLE_NONE = 0;
+ private static final int STYLE_HIDDEN = 1;
+ private static final int STYLE_DOTTED = 2;
+ private static final int STYLE_DASHED = 3;
+ private static final int STYLE_SOLID = 4;
+ private static final int STYLE_DOUBLE = 5;
+ private static final int STYLE_GROOVE = 6;
+ private static final int STYLE_RIDGE = 7;
+ private static final int STYLE_INSET = 8;
+ private static final int STYLE_OUTSET = 9;
+
+ /**
+ * The left insets.
+ */
+ private int left;
+
+ /**
+ * The right insets.
+ */
+ private int right;
+
+ /**
+ * The top insets.
+ */
+ private int top;
+
+ /**
+ * The bottom insets.
+ */
+ private int bottom;
+
+ /**
+ * The border style on the left.
+ */
+ private int leftStyle;
+
+ /**
+ * The border style on the right.
+ */
+ private int rightStyle;
+
+ /**
+ * The border style on the top.
+ */
+ private int topStyle;
+
+ /**
+ * The color for the top border.
+ */
+ private Color topColor;
+
+ /**
+ * The color for the bottom border.
+ */
+ private Color bottomColor;
+
+ /**
+ * The color for the left border.
+ */
+ private Color leftColor;
+
+ /**
+ * The color for the right border.
+ */
+ private Color rightColor;
+
+ /**
+ * The border style on the bottom.
+ */
+ private int bottomStyle;
+
+ /**
+ * Creates a new CSS border and fetches its attributes from the specified
+ * attribute set.
+ *
+ * @param atts the attribute set that contains the border spec
+ */
+ CSSBorder(AttributeSet atts, StyleSheet ss)
+ {
+ // Determine the border styles.
+ int style = getBorderStyle(atts, CSS.Attribute.BORDER_STYLE);
+ if (style == STYLE_NOT_SET)
+ style = STYLE_NONE; // Default to none.
+ topStyle = bottomStyle = leftStyle = rightStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_TOP_STYLE);
+ if (style != STYLE_NOT_SET)
+ topStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_BOTTOM_STYLE);
+ if (style != STYLE_NOT_SET)
+ bottomStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_LEFT_STYLE);
+ if (style != STYLE_NOT_SET)
+ leftStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_RIGHT_STYLE);
+ if (style != STYLE_NOT_SET)
+ rightStyle = style;
+
+ // Determine the border colors.
+ Color color = getBorderColor(atts, CSS.Attribute.BORDER_COLOR);
+ if (color == null)
+ color = Color.BLACK;
+ topColor = bottomColor = leftColor = rightColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_TOP_COLOR);
+ if (color != null)
+ topColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_BOTTOM_COLOR);
+ if (color != null)
+ bottomColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_LEFT_COLOR);
+ if (color != null)
+ leftColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_RIGHT_COLOR);
+ if (color != null)
+ rightColor = color;
+
+ // Determine the border widths.
+ int width = getBorderWidth(atts, CSS.Attribute.BORDER_WIDTH, ss);
+ if (width == -1)
+ width = 0;
+ top = bottom = left = right = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_TOP_WIDTH, ss);
+ if (width >= 0)
+ top = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_BOTTOM_WIDTH, ss);
+ if (width >= 0)
+ bottom = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_LEFT_WIDTH, ss);
+ if (width >= 0)
+ left = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_RIGHT_WIDTH, ss);
+ if (width >= 0)
+ right = width;
+ }
+
+ /**
+ * Determines the border style for a given CSS attribute.
+ *
+ * @param atts the attribute set
+ * @param key the CSS key
+ *
+ * @return the border style according to the constants defined in this class
+ */
+ private int getBorderStyle(AttributeSet atts, CSS.Attribute key)
+ {
+ int style = STYLE_NOT_SET;
+ Object o = atts.getAttribute(key);
+ if (o != null)
+ {
+ String cssStyle = o.toString();
+ if (cssStyle.equals("none"))
+ style = STYLE_NONE;
+ else if (cssStyle.equals("hidden"))
+ style = STYLE_HIDDEN;
+ else if (cssStyle.equals("dotted"))
+ style = STYLE_DOTTED;
+ else if (cssStyle.equals("dashed"))
+ style = STYLE_DASHED;
+ else if (cssStyle.equals("solid"))
+ style = STYLE_SOLID;
+ else if (cssStyle.equals("double"))
+ style = STYLE_DOUBLE;
+ else if (cssStyle.equals("groove"))
+ style = STYLE_GROOVE;
+ else if (cssStyle.equals("ridge"))
+ style = STYLE_RIDGE;
+ else if (cssStyle.equals("inset"))
+ style = STYLE_INSET;
+ else if (cssStyle.equals("outset"))
+ style = STYLE_OUTSET;
+ }
+ return style;
+ }
+
+ /**
+ * Determines the border color for the specified key.
+ *
+ * @param atts the attribute set from which to fetch the color
+ * @param key the CSS key
+ *
+ * @return the border color
+ */
+ private Color getBorderColor(AttributeSet atts, CSS.Attribute key)
+ {
+ Object o = atts.getAttribute(key);
+ Color color = null;
+ if (o instanceof CSSColor)
+ {
+ CSSColor cssColor = (CSSColor) o;
+ color = cssColor.getValue();
+ }
+ return color;
+ }
+
+ /**
+ * Returns the width for the specified key.
+ *
+ * @param atts the attributes to fetch the width from
+ * @param key the CSS key
+ *
+ * @return the width, or -1 of none has been set
+ */
+ private int getBorderWidth(AttributeSet atts, CSS.Attribute key,
+ StyleSheet ss)
+ {
+ int width = -1;
+ Object o = atts.getAttribute(key);
+ if (o instanceof BorderWidth)
+ {
+ BorderWidth w = (BorderWidth) o;
+ w.setFontBases(ss.getEMBase(atts), ss.getEXBase(atts));
+ width = (int) ((BorderWidth) o).getValue();
+ }
+ return width;
+ }
+
+ /**
+ * Returns the border insets.
+ */
+ public Insets getBorderInsets(Component c)
+ {
+ return new Insets(top, left, bottom, right);
+ }
+
+ /**
+ * CSS borders are generally opaque so return true here.
+ */
+ public boolean isBorderOpaque()
+ {
+ return true;
+ }
+
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height)
+ {
+ // Top border.
+ paintBorderLine(g, x, y + top / 2, x + width, y + top / 2, topStyle, top,
+ topColor, false);
+ // Left border.
+ paintBorderLine(g, x + left / 2, y, x + left / 2, y + height, leftStyle,
+ left, leftColor, true);
+ // Bottom border.
+ paintBorderLine(g, x, y + height - bottom / 2, x + width,
+ y + height - bottom / 2, topStyle, bottom, bottomColor,
+ false);
+ // Right border.
+ paintBorderLine(g, x + width - right / 2, y, x + width - right / 2,
+ y + height, topStyle, right, rightColor, true);
+
+ }
+
+ private void paintBorderLine(Graphics g, int x1, int y1, int x2, int y2,
+ int style, int width, Color color,
+ boolean vertical)
+ {
+ switch (style)
+ {
+ case STYLE_DOTTED:
+ paintDottedLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_DASHED:
+ paintDashedLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_SOLID:
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_DOUBLE:
+ paintDoubleLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_GROOVE:
+ paintGrooveLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_RIDGE:
+ paintRidgeLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_OUTSET:
+ paintOutsetLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_INSET:
+ paintInsetLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_NONE:
+ case STYLE_HIDDEN:
+ default:
+ // Nothing to do in these cases.
+ }
+ }
+
+ private void paintDottedLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintDashedLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintSolidLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ int x = Math.min(x1, x2);
+ int y = Math.min(y1, y1);
+ int w = Math.abs(x2 - x1);
+ int h = Math.abs(y2 - y1);
+ if (vertical)
+ {
+ w = width;
+ x -= width / 2;
+ }
+ else
+ {
+ h = width;
+ y -= width / 2;
+ }
+ g.setColor(color);
+ g.fillRect(x, y, w, h);
+ }
+
+ private void paintDoubleLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintGrooveLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintRidgeLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintOutsetLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintInsetLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java
new file mode 100644
index 0000000..bc7c36f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java
@@ -0,0 +1,123 @@
+/* FormSubmitEvent.java -- Event fired on form submit
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.net.URL;
+
+import javax.swing.text.Element;
+
+/**
+ * The event fired on form submit.
+ *
+ * @since 1.5
+ */
+public class FormSubmitEvent
+ extends HTMLFrameHyperlinkEvent
+{
+
+ // FIXME: Use enums when available.
+ /**
+ * The submit method.
+ */
+ public static class MethodType
+ {
+ /**
+ * Indicates a form submit with HTTP method POST.
+ */
+ public static final MethodType POST = new MethodType();
+
+ /**
+ * Indicates a form submit with HTTP method GET.
+ */
+ public static final MethodType GET = new MethodType();
+
+ private MethodType()
+ {
+ }
+ }
+
+ /**
+ * The submit method.
+ */
+ private MethodType method;
+
+ /**
+ * The actual submit data.
+ */
+ private String data;
+
+ /**
+ * Creates a new FormSubmitEvent.
+ *
+ * @param source the source
+ * @param type the type of hyperlink update
+ * @param url the action url
+ * @param el the associated element
+ * @param target the target attribute
+ * @param m the submit method
+ * @param d the submit data
+ */
+ FormSubmitEvent(Object source, EventType type, URL url, Element el,
+ String target, MethodType m, String d)
+ {
+ super(source, type, url, el, target);
+ method = m;
+ data = d;
+ }
+
+ /**
+ * Returns the submit data.
+ *
+ * @return the submit data
+ */
+ public String getData()
+ {
+ return data;
+ }
+
+ /**
+ * Returns the HTTP submit method.
+ *
+ * @return the HTTP submit method
+ */
+ public MethodType getMethod()
+ {
+ return method;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/FormView.java b/libjava/classpath/javax/swing/text/html/FormView.java
index d540210..ef362bd 100644
--- a/libjava/classpath/javax/swing/text/html/FormView.java
+++ b/libjava/classpath/javax/swing/text/html/FormView.java
@@ -44,16 +44,36 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import javax.swing.ButtonModel;
+import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JEditorPane;
+import javax.swing.JList;
import javax.swing.JPasswordField;
import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
import javax.swing.UIManager;
+import javax.swing.event.HyperlinkEvent;
import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
import javax.swing.text.ComponentView;
+import javax.swing.text.Document;
import javax.swing.text.Element;
+import javax.swing.text.ElementIterator;
import javax.swing.text.StyleConstants;
/**
@@ -105,6 +125,231 @@ public class FormView
}
/**
+ * Actually submits the form data.
+ */
+ private class SubmitThread
+ extends Thread
+ {
+ /**
+ * The submit data.
+ */
+ private String data;
+
+ /**
+ * Creates a new SubmitThread.
+ *
+ * @param d the submit data
+ */
+ SubmitThread(String d)
+ {
+ data = d;
+ }
+
+ /**
+ * Actually performs the submit.
+ */
+ public void run()
+ {
+ if (data.length() > 0)
+ {
+ final String method = getMethod();
+ final URL actionURL = getActionURL();
+ final String target = getTarget();
+ URLConnection conn;
+ final JEditorPane editor = (JEditorPane) getContainer();
+ final HTMLDocument doc = (HTMLDocument) editor.getDocument();
+ HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit();
+ if (kit.isAutoFormSubmission())
+ {
+ try
+ {
+ final URL url;
+ if (method != null && method.equals("post"))
+ {
+ // Perform POST.
+ url = actionURL;
+ conn = url.openConnection();
+ postData(conn, data);
+ }
+ else
+ {
+ // Default to GET.
+ url = new URL(actionURL + "?" + data);
+ }
+ Runnable loadDoc = new Runnable()
+ {
+ public void run()
+ {
+ if (doc.isFrameDocument())
+ {
+ editor.fireHyperlinkUpdate(createSubmitEvent(method,
+ actionURL,
+ target));
+ }
+ else
+ {
+ try
+ {
+ editor.setPage(url);
+ }
+ catch (IOException ex)
+ {
+ // Oh well.
+ ex.printStackTrace();
+ }
+ }
+ }
+ };
+ SwingUtilities.invokeLater(loadDoc);
+ }
+ catch (MalformedURLException ex)
+ {
+ ex.printStackTrace();
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ else
+ {
+ editor.fireHyperlinkUpdate(createSubmitEvent(method,actionURL,
+ target));
+ }
+ }
+ }
+
+ /**
+ * Determines the submit method.
+ *
+ * @return the submit method
+ */
+ private String getMethod()
+ {
+ AttributeSet formAtts = getFormAttributes();
+ String method = null;
+ if (formAtts != null)
+ {
+ method = (String) formAtts.getAttribute(HTML.Attribute.METHOD);
+ }
+ return method;
+ }
+
+ /**
+ * Determines the action URL.
+ *
+ * @return the action URL
+ */
+ private URL getActionURL()
+ {
+ AttributeSet formAtts = getFormAttributes();
+ HTMLDocument doc = (HTMLDocument) getElement().getDocument();
+ URL url = doc.getBase();
+ if (formAtts != null)
+ {
+ String action =
+ (String) formAtts.getAttribute(HTML.Attribute.ACTION);
+ if (action != null)
+ {
+ try
+ {
+ url = new URL(url, action);
+ }
+ catch (MalformedURLException ex)
+ {
+ url = null;
+ }
+ }
+ }
+ return url;
+ }
+
+ /**
+ * Fetches the target attribute.
+ *
+ * @return the target attribute or _self if none is present
+ */
+ private String getTarget()
+ {
+ AttributeSet formAtts = getFormAttributes();
+ String target = null;
+ if (formAtts != null)
+ {
+ target = (String) formAtts.getAttribute(HTML.Attribute.TARGET);
+ if (target != null)
+ target = target.toLowerCase();
+ }
+ if (target == null)
+ target = "_self";
+ return target;
+ }
+
+ /**
+ * Posts the form data over the specified connection.
+ *
+ * @param conn the connection
+ */
+ private void postData(URLConnection conn, String data)
+ {
+ conn.setDoOutput(true);
+ PrintWriter out = null;
+ try
+ {
+ out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
+ out.print(data);
+ out.flush();
+ }
+ catch (IOException ex)
+ {
+ // Deal with this!
+ ex.printStackTrace();
+ }
+ finally
+ {
+ if (out != null)
+ out.close();
+ }
+ }
+
+ /**
+ * Determines the attributes from the relevant form tag.
+ *
+ * @return the attributes from the relevant form tag, <code>null</code>
+ * when there is no form tag
+ */
+ private AttributeSet getFormAttributes()
+ {
+ AttributeSet atts = null;
+ Element form = getFormElement();
+ if (form != null)
+ atts = form.getAttributes();
+ return atts;
+ }
+
+ /**
+ * Creates the submit event that should be fired.
+ *
+ * This is package private to avoid accessor methods.
+ *
+ * @param method the submit method
+ * @param actionURL the action URL
+ * @param target the target
+ *
+ * @return the submit event
+ */
+ FormSubmitEvent createSubmitEvent(String method, URL actionURL,
+ String target)
+ {
+ FormSubmitEvent.MethodType m = "post".equals(method)
+ ? FormSubmitEvent.MethodType.POST
+ : FormSubmitEvent.MethodType.GET;
+ return new FormSubmitEvent(FormView.this,
+ HyperlinkEvent.EventType.ACTIVATED,
+ actionURL, getElement(), target, m, data);
+ }
+ }
+
+ /**
* If the value attribute of an <code>&lt;input type=&quot;submit&quot;&gt>
* tag is not specified, then this string is used.
*
@@ -125,6 +370,11 @@ public class FormView
UIManager.getString("FormView.resetButtonText");
/**
+ * If this is true, the maximum size is set to the preferred size.
+ */
+ private boolean maxIsPreferred;
+
+ /**
* Creates a new <code>FormView</code>.
*
* @param el the element that is displayed by this view.
@@ -141,39 +391,161 @@ public class FormView
{
Component comp = null;
Element el = getElement();
- Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute);
+ AttributeSet atts = el.getAttributes();
+ Object tag = atts.getAttribute(StyleConstants.NameAttribute);
+ Object model = atts.getAttribute(StyleConstants.ModelAttribute);
if (tag.equals(HTML.Tag.INPUT))
{
- AttributeSet atts = el.getAttributes();
String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
- String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
if (type.equals("button"))
- comp = new JButton(value);
+ {
+ String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ JButton b = new JButton(value);
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
+ }
else if (type.equals("checkbox"))
- comp = new JCheckBox(value);
+ {
+ if (model instanceof ResetableToggleButtonModel)
+ {
+ ResetableToggleButtonModel m =
+ (ResetableToggleButtonModel) model;
+ JCheckBox c = new JCheckBox();
+ c.setModel(m);
+ comp = c;
+ maxIsPreferred = true;
+ }
+ }
else if (type.equals("image"))
- comp = new JButton(value); // FIXME: Find out how to fetch the image.
+ {
+ String src = (String) atts.getAttribute(HTML.Attribute.SRC);
+ JButton b;
+ try
+ {
+ URL base = ((HTMLDocument) el.getDocument()).getBase();
+ URL srcURL = new URL(base, src);
+ ImageIcon icon = new ImageIcon(srcURL);
+ b = new JButton(icon);
+ }
+ catch (MalformedURLException ex)
+ {
+ b = new JButton(src);
+ }
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
+ }
else if (type.equals("password"))
- comp = new JPasswordField(value);
+ {
+ int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
+ -1);
+ JTextField tf = new JPasswordField();
+ if (size > 0)
+ tf.setColumns(size);
+ else
+ tf.setColumns(20);
+ if (model != null)
+ tf.setDocument((Document) model);
+ tf.addActionListener(this);
+ comp = tf;
+ maxIsPreferred = true;
+ }
else if (type.equals("radio"))
- comp = new JRadioButton(value);
+ {
+ if (model instanceof ResetableToggleButtonModel)
+ {
+ ResetableToggleButtonModel m =
+ (ResetableToggleButtonModel) model;
+ JRadioButton c = new JRadioButton();
+ c.setModel(m);
+ comp = c;
+ maxIsPreferred = true;
+ }
+ }
else if (type.equals("reset"))
{
- if (value == null || value.equals(""))
- value = RESET;
- comp = new JButton(value);
+ String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = UIManager.getString("FormView.resetButtonText");
+ JButton b = new JButton(value);
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
}
else if (type.equals("submit"))
{
- if (value == null || value.equals(""))
- value = SUBMIT;
- comp = new JButton(value);
+ String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = UIManager.getString("FormView.submitButtonText");
+ JButton b = new JButton(value);
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
}
else if (type.equals("text"))
- comp = new JTextField(value);
-
+ {
+ int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
+ -1);
+ JTextField tf = new JTextField();
+ if (size > 0)
+ tf.setColumns(size);
+ else
+ tf.setColumns(20);
+ if (model != null)
+ tf.setDocument((Document) model);
+ tf.addActionListener(this);
+ comp = tf;
+ maxIsPreferred = true;
+ }
+ }
+ else if (tag == HTML.Tag.TEXTAREA)
+ {
+ JTextArea textArea = new JTextArea((Document) model);
+ int rows = HTML.getIntegerAttributeValue(atts, HTML.Attribute.ROWS, 1);
+ textArea.setRows(rows);
+ int cols = HTML.getIntegerAttributeValue(atts, HTML.Attribute.COLS, 20);
+ textArea.setColumns(cols);
+ maxIsPreferred = true;
+ comp = new JScrollPane(textArea,
+ JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+ JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else if (tag == HTML.Tag.SELECT)
+ {
+ if (model instanceof SelectListModel)
+ {
+ SelectListModel slModel = (SelectListModel) model;
+ JList list = new JList(slModel);
+ int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
+ 1);
+ list.setVisibleRowCount(size);
+ list.setSelectionModel(slModel.getSelectionModel());
+ comp = new JScrollPane(list);
+ }
+ else if (model instanceof SelectComboBoxModel)
+ {
+ SelectComboBoxModel scbModel = (SelectComboBoxModel) model;
+ comp = new JComboBox(scbModel);
+ }
+ maxIsPreferred = true;
}
- // FIXME: Implement the remaining components.
return comp;
}
@@ -188,16 +560,11 @@ public class FormView
*/
public float getMaximumSpan(int axis)
{
- // FIXME: The specs say that for some components the maximum span == the
- // preferred span of the component. This should be figured out and
- // implemented accordingly.
float span;
- if (axis == X_AXIS)
- span = getComponent().getMaximumSize().width;
- else if (axis == Y_AXIS)
- span = getComponent().getMaximumSize().height;
+ if (maxIsPreferred)
+ span = getPreferredSpan(axis);
else
- throw new IllegalArgumentException("Invalid axis parameter");
+ span = super.getMaximumSpan(axis);
return span;
}
@@ -222,7 +589,9 @@ public class FormView
AttributeSet atts = el.getAttributes();
String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
if (type.equals("submit"))
- submitData(""); // FIXME: How to fetch the actual form data?
+ submitData(getFormData());
+ else if (type.equals("reset"))
+ resetForm();
}
// FIXME: Implement the remaining actions.
}
@@ -235,7 +604,8 @@ public class FormView
*/
protected void submitData(String data)
{
- // FIXME: Implement this.
+ SubmitThread submitThread = new SubmitThread(data);
+ submitThread.start();
}
/**
@@ -272,4 +642,229 @@ public class FormView
}
return data;
}
+
+ /**
+ * Determines and returns the enclosing form element if there is any.
+ *
+ * This is package private to avoid accessor methods.
+ *
+ * @return the enclosing form element, or <code>null</code> if there is no
+ * enclosing form element
+ */
+ Element getFormElement()
+ {
+ Element form = null;
+ Element el = getElement();
+ while (el != null && form == null)
+ {
+ AttributeSet atts = el.getAttributes();
+ if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FORM)
+ form = el;
+ else
+ el = el.getParentElement();
+ }
+ return form;
+ }
+
+ /**
+ * Determines the form data that is about to be submitted.
+ *
+ * @return the form data
+ */
+ private String getFormData()
+ {
+ Element form = getFormElement();
+ StringBuilder b = new StringBuilder();
+ if (form != null)
+ {
+ ElementIterator i = new ElementIterator(form);
+ Element next;
+ while ((next = i.next()) != null)
+ {
+ if (next.isLeaf())
+ {
+ AttributeSet atts = next.getAttributes();
+ String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
+ if (type != null && type.equals("submit")
+ && next != getElement())
+ {
+ // Skip this. This is not the actual submit trigger.
+ }
+ else if (type == null || ! type.equals("image"))
+ {
+ getElementFormData(next, b);
+ }
+ }
+ }
+ }
+ return b.toString();
+ }
+
+ /**
+ * Fetches the form data from the specified element and appends it to
+ * the data string.
+ *
+ * @param el the element from which to fetch form data
+ * @param b the data string
+ */
+ private void getElementFormData(Element el, StringBuilder b)
+ {
+ AttributeSet atts = el.getAttributes();
+ String name = (String) atts.getAttribute(HTML.Attribute.NAME);
+ if (name != null)
+ {
+ String value = null;
+ HTML.Tag tag = (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute);
+ if (tag == HTML.Tag.SELECT)
+ {
+ getSelectData(atts, b);
+ }
+ else
+ {
+ if (tag == HTML.Tag.INPUT)
+ value = getInputFormData(atts);
+ else if (tag == HTML.Tag.TEXTAREA)
+ value = getTextAreaData(atts);
+ if (name != null && value != null)
+ {
+ addData(b, name, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Fetches form data from select boxes.
+ *
+ * @param atts the attributes of the element
+ *
+ * @param b the form data string to append to
+ */
+ private void getSelectData(AttributeSet atts, StringBuilder b)
+ {
+ String name = (String) atts.getAttribute(HTML.Attribute.NAME);
+ if (name != null)
+ {
+ Object m = atts.getAttribute(StyleConstants.ModelAttribute);
+ if (m instanceof SelectListModel)
+ {
+ SelectListModel sl = (SelectListModel) m;
+ ListSelectionModel lsm = sl.getSelectionModel();
+ for (int i = 0; i < sl.getSize(); i++)
+ {
+ if (lsm.isSelectedIndex(i))
+ {
+ Option o = (Option) sl.getElementAt(i);
+ addData(b, name, o.getValue());
+ }
+ }
+ }
+ else if (m instanceof SelectComboBoxModel)
+ {
+ SelectComboBoxModel scb = (SelectComboBoxModel) m;
+ Option o = (Option) scb.getSelectedItem();
+ if (o != null)
+ addData(b, name, o.getValue());
+ }
+ }
+ }
+
+ /**
+ * Fetches form data from a textarea.
+ *
+ * @param atts the attributes
+ *
+ * @return the form data
+ */
+ private String getTextAreaData(AttributeSet atts)
+ {
+ Document doc = (Document) atts.getAttribute(StyleConstants.ModelAttribute);
+ String data;
+ try
+ {
+ data = doc.getText(0, doc.getLength());
+ }
+ catch (BadLocationException ex)
+ {
+ data = null;
+ }
+ return data;
+ }
+
+ /**
+ * Fetches form data from an input tag.
+ *
+ * @param atts the attributes from which to fetch the data
+ *
+ * @return the field value
+ */
+ private String getInputFormData(AttributeSet atts)
+ {
+ String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
+ Object model = atts.getAttribute(StyleConstants.ModelAttribute);
+ String value = null;
+ if (type.equals("text") || type.equals("password"))
+ {
+ Document doc = (Document) model;
+ try
+ {
+ value = doc.getText(0, doc.getLength());
+ }
+ catch (BadLocationException ex)
+ {
+ // Sigh.
+ assert false;
+ }
+ }
+ else if (type.equals("hidden") || type.equals("submit"))
+ {
+ value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = "";
+ }
+ // TODO: Implement the others. radio, checkbox and file.
+ return value;
+ }
+
+ /**
+ * Actually adds the specified data to the string. It URL encodes
+ * the name and value and handles separation of the fields.
+ *
+ * @param b the string at which the form data to be added
+ * @param name the name of the field
+ * @param value the value
+ */
+ private void addData(StringBuilder b, String name, String value)
+ {
+ if (b.length() > 0)
+ b.append('&');
+ String encName = URLEncoder.encode(name);
+ b.append(encName);
+ b.append('=');
+ String encValue = URLEncoder.encode(value);
+ b.append(encValue);
+ }
+
+ /**
+ * Resets the form data to their initial state.
+ */
+ private void resetForm()
+ {
+ Element form = getFormElement();
+ if (form != null)
+ {
+ ElementIterator iter = new ElementIterator(form);
+ Element next;
+ while ((next = iter.next()) != null)
+ {
+ if (next.isLeaf())
+ {
+ AttributeSet atts = next.getAttributes();
+ Object m = atts.getAttribute(StyleConstants.ModelAttribute);
+ if (m instanceof ResetableModel)
+ ((ResetableModel) m).reset();
+ }
+ }
+ }
+ }
}
diff --git a/libjava/classpath/javax/swing/text/html/FrameSetView.java b/libjava/classpath/javax/swing/text/html/FrameSetView.java
new file mode 100644
index 0000000..e3252d7
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/FrameSetView.java
@@ -0,0 +1,274 @@
+/* FrameSetView.java -- Implements HTML frameset
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.StringTokenizer;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BoxView;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+
+/**
+ * Implements HTML framesets. This is implemented as a vertical box that
+ * holds the rows of the frameset. Each row is again a horizontal box that
+ * holds the actual columns.
+ */
+public class FrameSetView
+ extends BoxView
+{
+
+ /**
+ * A row of a frameset.
+ */
+ private class FrameSetRow
+ extends BoxView
+ {
+ private int row;
+ FrameSetRow(Element el, int r)
+ {
+ super(el, X_AXIS);
+ row = r;
+ }
+
+ protected void loadChildren(ViewFactory f)
+ {
+ // Load the columns here.
+ Element el = getElement();
+ View[] columns = new View[numViews[X_AXIS]];
+ int offset = row * numViews[X_AXIS];
+ for (int c = 0; c < numViews[X_AXIS]; c++)
+ {
+ Element child = el.getElement(offset + c);
+ columns[c] = f.create(child);
+ }
+ replace(0, 0, columns);
+ }
+
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ int numRows = numViews[X_AXIS];
+ int[] abs = absolute[X_AXIS];
+ int[] rel = relative[X_AXIS];
+ int[] perc = percent[X_AXIS];
+ layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc);
+ }
+ }
+
+ /**
+ * Holds the absolute layout information for the views along one axis. The
+ * indices are absolute[axis][index], where axis is either X_AXIS (columns)
+ * or Y_AXIS (rows). Rows or columns that don't have absolute layout have
+ * a -1 in this array.
+ */
+ int[][] absolute;
+
+ /**
+ * Holds the relative (*) layout information for the views along one axis.
+ * The indices are relative[axis][index], where axis is either X_AXIS
+ * (columns) or Y_AXIS (rows). Rows or columns that don't have relative
+ * layout have a Float.NaN in this array.
+ */
+ int[][] relative;
+
+ /**
+ * Holds the relative (%) layout information for the views along one axis.
+ * The indices are relative[axis][index], where axis is either X_AXIS
+ * (columns) or Y_AXIS (rows). Rows or columns that don't have relative
+ * layout have a Float.NaN in this array.
+ *
+ * The percentage is divided by 100 so that we hold the actual fraction here.
+ */
+ int[][] percent;
+
+ /**
+ * The number of children in each direction.
+ */
+ int[] numViews;
+
+ FrameSetView(Element el)
+ {
+ super(el, Y_AXIS);
+ numViews = new int[2];
+ absolute = new int[2][];
+ relative = new int[2][];
+ percent = new int[2][];
+ }
+
+ /**
+ * Loads the children and places them inside the grid.
+ */
+ protected void loadChildren(ViewFactory f)
+ {
+ parseRowsCols();
+ // Set up the rows.
+ View[] rows = new View[numViews[Y_AXIS]];
+ for (int r = 0; r < numViews[Y_AXIS]; r++)
+ {
+ rows[r] = new FrameSetRow(getElement(), r);
+ }
+ replace(0, 0, rows);
+ }
+
+ /**
+ * Parses the rows and cols attributes and sets up the layout info.
+ */
+ private void parseRowsCols()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ String cols = (String) atts.getAttribute(HTML.Attribute.COLS);
+ if (cols == null) // Defaults to '100%' when not specified.
+ cols = "100%";
+ parseLayout(cols, X_AXIS);
+ String rows = (String) atts.getAttribute(HTML.Attribute.ROWS);
+ if (rows == null) // Defaults to '100%' when not specified.
+ rows = "100%";
+ parseLayout(rows, Y_AXIS);
+ }
+
+ /**
+ * Parses the cols or rows attribute and places the layout info in the
+ * appropriate arrays.
+ *
+ * @param att the attributes to parse
+ * @param axis the axis
+ */
+ private void parseLayout(String att, int axis)
+ {
+ StringTokenizer tokens = new StringTokenizer(att, ",");
+ numViews[axis] = tokens.countTokens();
+ absolute[axis] = new int[numViews[axis]];
+ relative[axis] = new int[numViews[axis]];
+ percent[axis] = new int[numViews[axis]];
+ for (int index = 0; tokens.hasMoreTokens(); index++)
+ {
+ String token = tokens.nextToken();
+ int p = token.indexOf('%');
+ int s = token.indexOf('*');
+ if (p != -1)
+ {
+ // Percent value.
+ String number = token.substring(0, p);
+ try
+ {
+ percent[axis][index] = Integer.parseInt(number);
+ }
+ catch (NumberFormatException ex)
+ {
+ // Leave value as 0 then.
+ }
+ }
+ else if (s != -1)
+ {
+ // Star relative value.
+ String number = token.substring(0, s);
+ try
+ {
+ relative[axis][index] = Integer.parseInt(number);
+ }
+ catch (NumberFormatException ex)
+ {
+ // Leave value as 0 then.
+ }
+ }
+ else
+ {
+ // Absolute value.
+ try
+ {
+ absolute[axis][index] = Integer.parseInt(token);
+ }
+ catch (NumberFormatException ex)
+ {
+ // Leave value as 0 then.
+ }
+ }
+ }
+ }
+
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ int numRows = numViews[Y_AXIS];
+ int[] abs = absolute[Y_AXIS];
+ int[] rel = relative[Y_AXIS];
+ int[] perc = percent[Y_AXIS];
+ layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc);
+ }
+
+ void layoutViews(int targetSpan, int axis, int[] offsets, int[] spans,
+ int numViews, int[] abs, int[] rel, int[] perc)
+ {
+ // We need two passes. In the first pass we layout the absolute and
+ // percent values and accumulate the needed space. In the second pass
+ // the relative values are distributed and the offsets are set.
+ int total = 0;
+ int relTotal = 0;
+ for (int i = 0; i < numViews; i++)
+ {
+ if (abs[i] > 0)
+ {
+ spans[i] = abs[i];
+ total += spans[i];
+ }
+ else if (perc[i] > 0)
+ {
+ spans[i] = (targetSpan * perc[i]) / 100;
+ total += spans[i];
+ }
+ else if (rel[i] > 0)
+ {
+ relTotal += rel[i];
+ }
+ }
+ int offs = 0;
+ for (int i = 0; i < numViews; i++)
+ {
+ if (relTotal > 0 && rel[i] > 0)
+ {
+ spans[i] = targetSpan * (rel[i] / relTotal);
+ }
+ offsets[i] = offs;
+ offs += spans[i];
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/FrameView.java b/libjava/classpath/javax/swing/text/html/FrameView.java
new file mode 100644
index 0000000..cd4e44a
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/FrameView.java
@@ -0,0 +1,233 @@
+/* FrameView.java -- Renders HTML frame tags
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.JEditorPane;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.ComponentView;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+
+/**
+ * A view that is responsible for rendering HTML frame tags.
+ * This is accomplished by a specialized {@link ComponentView}
+ * that embeds a JEditorPane with an own document.
+ */
+class FrameView
+ extends ComponentView
+ implements HyperlinkListener
+{
+
+ /**
+ * Creates a new FrameView for the specified element.
+ *
+ * @param el the element for the view
+ */
+ FrameView(Element el)
+ {
+ super(el);
+ }
+
+ /**
+ * Creates the element that will be embedded in the view.
+ * This will be a JEditorPane with the appropriate content set.
+ *
+ * @return the element that will be embedded in the view
+ */
+ protected Component createComponent()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ JEditorPane html = new JEditorPane();
+ html.addHyperlinkListener(this);
+ URL base = ((HTMLDocument) el.getDocument()).getBase();
+ String srcAtt = (String) atts.getAttribute(HTML.Attribute.SRC);
+ if (srcAtt != null && ! srcAtt.equals(""))
+ {
+ try
+ {
+ URL page = new URL(base, srcAtt);
+ html.setPage(page);
+ ((HTMLDocument) html.getDocument()).setFrameDocument(true);
+ }
+ catch (MalformedURLException ex)
+ {
+ // Leave page empty.
+ }
+ catch (IOException ex)
+ {
+ // Leave page empty.
+ }
+ }
+ return html;
+ }
+
+ /**
+ * Catches hyperlink events on that frame's editor and forwards it to
+ * the outermost editorpane.
+ */
+ public void hyperlinkUpdate(HyperlinkEvent event)
+ {
+ JEditorPane outer = getTopEditorPane();
+ if (outer != null)
+ {
+ if (event instanceof HTMLFrameHyperlinkEvent)
+ {
+ HTMLFrameHyperlinkEvent hfhe = (HTMLFrameHyperlinkEvent) event;
+ if (hfhe.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
+ {
+ String target = hfhe.getTarget();
+ if (event instanceof FormSubmitEvent)
+ {
+ handleFormSubmitEvent(hfhe, outer, target);
+ }
+ else // No FormSubmitEvent.
+ {
+ handleHyperlinkEvent(hfhe, outer, target);
+ }
+ }
+ }
+ else
+ {
+ // Simply forward this event.
+ outer.fireHyperlinkUpdate(event);
+ }
+ }
+ }
+
+ /**
+ * Handles normal hyperlink events.
+ *
+ * @param event the event
+ * @param outer the top editor
+ * @param target the target
+ */
+ private void handleHyperlinkEvent(HyperlinkEvent event,
+ JEditorPane outer, String target)
+ {
+ if (target.equals("_top"))
+ {
+ try
+ {
+ outer.setPage(event.getURL());
+ }
+ catch (IOException ex)
+ {
+ // Well...
+ ex.printStackTrace();
+ }
+ }
+ if (! outer.isEditable())
+ {
+ outer.fireHyperlinkUpdate
+ (new HTMLFrameHyperlinkEvent(outer,
+ event.getEventType(),
+ event.getURL(),
+ event.getDescription(),
+ getElement(),
+ target));
+ }
+ }
+
+ /**
+ * Handles form submit events.
+ *
+ * @param event the event
+ * @param outer the top editor
+ * @param target the target
+ */
+ private void handleFormSubmitEvent(HTMLFrameHyperlinkEvent event,
+ JEditorPane outer,
+ String target)
+ {
+ HTMLEditorKit kit = (HTMLEditorKit) outer.getEditorKit();
+ if (kit != null && kit.isAutoFormSubmission())
+ {
+ if (target.equals("_top"))
+ {
+ try
+ {
+ outer.setPage(event.getURL());
+ }
+ catch (IOException ex)
+ {
+ // Well...
+ ex.printStackTrace();
+ }
+ }
+ else
+ {
+ HTMLDocument doc =
+ (HTMLDocument) outer.getDocument();
+ doc.processHTMLFrameHyperlinkEvent(event);
+ }
+ }
+ else
+ {
+ outer.fireHyperlinkUpdate(event);
+ }
+ }
+
+ /**
+ * Determines the topmost editor in a nested frameset.
+ *
+ * @return the topmost editor in a nested frameset
+ */
+ private JEditorPane getTopEditorPane()
+ {
+ View parent = getParent();
+ View top = null;
+ while (parent != null)
+ {
+ if (parent instanceof FrameSetView)
+ top = parent;
+ }
+ JEditorPane editor = null;
+ if (top != null)
+ editor = (JEditorPane) top.getContainer();
+ return editor;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/HTML.java b/libjava/classpath/javax/swing/text/html/HTML.java
index 2c908f6..93c05da 100644
--- a/libjava/classpath/javax/swing/text/html/HTML.java
+++ b/libjava/classpath/javax/swing/text/html/HTML.java
@@ -465,6 +465,16 @@ public class HTML
public static final Attribute WIDTH = new Attribute("width");
/**
+ * This is used to reflect the pseudo class for the a tag.
+ */
+ static final Attribute PSEUDO_CLASS = new Attribute("_pseudo");
+
+ /**
+ * This is used to reflect the dynamic class for the a tag.
+ */
+ static final Attribute DYNAMIC_CLASS = new Attribute("_dynamic");
+
+ /**
* The attribute name.
*/
private final String name;
@@ -1119,8 +1129,8 @@ public class HTML
static final int BLOCK = 2;
static final int PREFORMATTED = 4;
static final int SYNTHETIC = 8;
- private static Map tagMap;
- private static Map attrMap;
+ private static Map<String,Tag> tagMap;
+ private static Map<String,Attribute> attrMap;
/**
* The public constructor (does nothing). It it seldom required to have
@@ -1159,7 +1169,7 @@ public class HTML
if (attrMap == null)
{
// Create the map on demand.
- attrMap = new TreeMap();
+ attrMap = new TreeMap<String,Attribute>();
Attribute[] attrs = getAllAttributeKeys();
@@ -1169,7 +1179,7 @@ public class HTML
}
}
- return (Attribute) attrMap.get(attName.toLowerCase());
+ return attrMap.get(attName.toLowerCase());
}
/**
@@ -1228,7 +1238,7 @@ public class HTML
if (tagMap == null)
{
// Create the mao on demand.
- tagMap = new TreeMap();
+ tagMap = new TreeMap<String,Tag>();
Tag[] tags = getAllTags();
@@ -1238,6 +1248,6 @@ public class HTML
}
}
- return (Tag) tagMap.get(tagName.toLowerCase());
+ return tagMap.get(tagName.toLowerCase());
}
}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java
index 0bfc338..f3d3ce3 100644
--- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java
+++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java
@@ -39,19 +39,22 @@ exception statement from your version. */
package javax.swing.text.html;
import gnu.classpath.NotImplementedException;
-import gnu.javax.swing.text.html.CharacterAttributeTranslator;
-import gnu.javax.swing.text.html.parser.htmlAttributeSet;
import java.io.IOException;
import java.io.StringReader;
+import java.net.MalformedURLException;
import java.net.URL;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;
import java.util.Vector;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultButtonModel;
import javax.swing.JEditorPane;
+import javax.swing.ListSelectionModel;
import javax.swing.event.DocumentEvent;
-import javax.swing.event.HyperlinkEvent.EventType;
+import javax.swing.event.UndoableEditEvent;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
@@ -60,6 +63,7 @@ import javax.swing.text.Element;
import javax.swing.text.ElementIterator;
import javax.swing.text.GapContent;
import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.PlainDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.html.HTML.Tag;
@@ -87,16 +91,24 @@ public class HTMLDocument extends DefaultStyledDocument
boolean preservesUnknownTags = true;
int tokenThreshold = Integer.MAX_VALUE;
HTMLEditorKit.Parser parser;
- StyleSheet styleSheet;
- AbstractDocument.Content content;
-
+
+ /**
+ * Indicates whether this document is inside a frame or not.
+ */
+ private boolean frameDocument;
+
+ /**
+ * Package private to avoid accessor methods.
+ */
+ String baseTarget;
+
/**
* Constructs an HTML document using the default buffer size and a default
* StyleSheet.
*/
public HTMLDocument()
{
- this(null);
+ this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
}
/**
@@ -119,14 +131,7 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public HTMLDocument(AbstractDocument.Content c, StyleSheet styles)
{
- this.content = c;
- if (styles == null)
- {
- styles = new StyleSheet();
- styles.importStyleSheet(getClass().getResource(HTMLEditorKit.
- DEFAULT_CSS));
- }
- this.styleSheet = styles;
+ super(c, styles);
}
/**
@@ -137,7 +142,7 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public StyleSheet getStyleSheet()
{
- return styleSheet;
+ return (StyleSheet) getAttributeContext();
}
/**
@@ -191,8 +196,6 @@ public class HTMLDocument extends DefaultStyledDocument
protected Element createLeafElement(Element parent, AttributeSet a, int p0,
int p1)
{
- RunElement el = new RunElement(parent, a, p0, p1);
- el.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
return new RunElement(parent, a, p0, p1);
}
@@ -269,7 +272,7 @@ public class HTMLDocument extends DefaultStyledDocument
public void setBase(URL u)
{
baseURL = u;
- styleSheet.setBase(u);
+ getStyleSheet().setBase(u);
}
/**
@@ -374,11 +377,119 @@ public class HTMLDocument extends DefaultStyledDocument
}
public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event)
- throws NotImplementedException
{
- // TODO: Implement this properly.
+ String target = event.getTarget();
+ Element el = event.getSourceElement();
+ URL url = event.getURL();
+ if (target.equals("_self"))
+ {
+ updateFrame(el, url);
+ }
+ else if (target.equals("_parent"))
+ {
+ updateFrameSet(el.getParentElement(), url);
+ }
+ else
+ {
+ Element targetFrame = findFrame(target);
+ if (targetFrame != null)
+ updateFrame(targetFrame, url);
+ }
}
-
+
+ /**
+ * Finds the named frame inside this document.
+ *
+ * @param target the name to look for
+ *
+ * @return the frame if there is a matching frame, <code>null</code>
+ * otherwise
+ */
+ private Element findFrame(String target)
+ {
+ ElementIterator i = new ElementIterator(this);
+ Element next = null;
+ while ((next = i.next()) != null)
+ {
+ AttributeSet atts = next.getAttributes();
+ if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FRAME)
+ {
+ String name = (String) atts.getAttribute(HTML.Attribute.NAME);
+ if (name != null && name.equals(target))
+ break;
+ }
+ }
+ return next;
+ }
+
+ /**
+ * Updates the frame that is represented by the specified element to
+ * refer to the specified URL.
+ *
+ * @param el the element
+ * @param url the new url
+ */
+ private void updateFrame(Element el, URL url)
+ {
+ try
+ {
+ writeLock();
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(el.getStartOffset(), 1,
+ DocumentEvent.EventType.CHANGE);
+ AttributeSet elAtts = el.getAttributes();
+ AttributeSet copy = elAtts.copyAttributes();
+ MutableAttributeSet matts = (MutableAttributeSet) elAtts;
+ ev.addEdit(new AttributeUndoableEdit(el, copy, false));
+ matts.removeAttribute(HTML.Attribute.SRC);
+ matts.addAttribute(HTML.Attribute.SRC, url.toString());
+ ev.end();
+ fireChangedUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ /**
+ * Updates the frameset that is represented by the specified element
+ * to create a frame that refers to the specified URL.
+ *
+ * @param el the element
+ * @param url the url
+ */
+ private void updateFrameSet(Element el, URL url)
+ {
+ int start = el.getStartOffset();
+ int end = el.getEndOffset();
+
+ StringBuilder html = new StringBuilder();
+ html.append("<frame");
+ if (url != null)
+ {
+ html.append(" src=\"");
+ html.append(url.toString());
+ html.append("\"");
+ }
+ html.append('>');
+ if (getParser() == null)
+ setParser(new HTMLEditorKit().getParser());
+ try
+ {
+ setOuterHTML(el, html.toString());
+ }
+ catch (BadLocationException ex)
+ {
+ ex.printStackTrace();
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
/**
* Gets an iterator for the given HTML.Tag.
* @param t the requested HTML.Tag
@@ -461,6 +572,8 @@ public class HTMLDocument extends DefaultStyledDocument
String name = null;
if (tag != null)
name = tag.toString();
+ if (name == null)
+ name = super.getName();
return name;
}
}
@@ -497,6 +610,8 @@ public class HTMLDocument extends DefaultStyledDocument
String name = null;
if (tag != null)
name = tag.toString();
+ if (name == null)
+ name = super.getName();
return name;
}
@@ -518,24 +633,33 @@ public class HTMLDocument extends DefaultStyledDocument
* @author Anthony Balkissoon abalkiss at redhat dot com
*/
public class HTMLReader extends HTMLEditorKit.ParserCallback
- {
+ {
+ /**
+ * The maximum token threshold. We don't grow it larger than this.
+ */
+ private static final int MAX_THRESHOLD = 10000;
+
+ /**
+ * The threshold growth factor.
+ */
+ private static final int GROW_THRESHOLD = 5;
+
/**
* Holds the current character attribute set *
*/
protected MutableAttributeSet charAttr = new SimpleAttributeSet();
- protected Vector parseBuffer = new Vector();
-
+ protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
+
+ /**
+ * The parse stack. It holds the current element tree path.
+ */
+ private Stack<HTML.Tag> parseStack = new Stack<HTML.Tag>();
+
/**
* A stack for character attribute sets *
*/
Stack charAttrStack = new Stack();
-
- /**
- * The parse stack. This stack holds HTML.Tag objects that reflect the
- * current position in the parsing process.
- */
- Stack parseStack = new Stack();
/** A mapping between HTML.Tag objects and the actions that handle them **/
HashMap tagToAction;
@@ -571,13 +695,68 @@ public class HTMLDocument extends DefaultStyledDocument
/** A temporary variable that helps with the printing out of debug information **/
boolean debug = false;
-
- void print (String line)
- {
- if (debug)
- System.out.println (line);
- }
-
+
+ /**
+ * This is true when we are inside a pre tag.
+ */
+ boolean inPreTag = false;
+
+ /**
+ * This is true when we are inside a style tag. This will add text
+ * content inside this style tag beeing parsed as CSS.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ boolean inStyleTag = false;
+
+ /**
+ * This is true when we are inside a &lt;textarea&gt; tag. Any text
+ * content will then be added to the text area.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ boolean inTextArea = false;
+
+ /**
+ * This contains all stylesheets that are somehow read, either
+ * via embedded style tags, or via linked stylesheets. The
+ * elements will be String objects containing a stylesheet each.
+ */
+ ArrayList styles;
+
+ /**
+ * The document model for a textarea.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ ResetablePlainDocument textAreaDocument;
+
+ /**
+ * The current model of a select tag. Can be a ComboBoxModel or a
+ * ListModel depending on the type of the select box.
+ */
+ Object selectModel;
+
+ /**
+ * The current option beeing read.
+ */
+ Option option;
+
+ /**
+ * The current number of options in the current select model.
+ */
+ int numOptions;
+
+ /**
+ * The current button groups mappings.
+ */
+ HashMap buttonGroups;
+
+ /**
+ * The token threshold. This gets increased while loading.
+ */
+ private int threshold;
+
public class TagAction
{
/**
@@ -633,13 +812,12 @@ public class HTMLDocument extends DefaultStyledDocument
// Put the old attribute set on the stack.
pushCharacterStyle();
- // Translate tag.. return if succesful.
- if(CharacterAttributeTranslator.translateTag(charAttr, t, a))
- return;
+ // Initialize with link pseudo class.
+ if (t == HTML.Tag.A)
+ a.addAttribute(HTML.Attribute.PSEUDO_CLASS, "link");
// Just add the attributes in <code>a</code>.
- if (a != null)
- charAttr.addAttribute(t, a.copyAttributes());
+ charAttr.addAttribute(t, a.copyAttributes());
}
/**
@@ -651,7 +829,11 @@ public class HTMLDocument extends DefaultStyledDocument
popCharacterStyle();
}
}
-
+
+ /**
+ * Processes elements that make up forms: &lt;input&gt;, &lt;textarea&gt;,
+ * &lt;select&gt; and &lt;option&gt;.
+ */
public class FormAction extends SpecialAction
{
/**
@@ -659,10 +841,73 @@ public class HTMLDocument extends DefaultStyledDocument
* of tags associated with this Action.
*/
public void start(HTML.Tag t, MutableAttributeSet a)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("FormAction.start not implemented");
+ if (t == HTML.Tag.INPUT)
+ {
+ String type = (String) a.getAttribute(HTML.Attribute.TYPE);
+ if (type == null)
+ {
+ type = "text"; // Default to 'text' when nothing was specified.
+ a.addAttribute(HTML.Attribute.TYPE, type);
+ }
+ setModel(type, a);
+ }
+ else if (t == HTML.Tag.TEXTAREA)
+ {
+ inTextArea = true;
+ textAreaDocument = new ResetablePlainDocument();
+ a.addAttribute(StyleConstants.ModelAttribute, textAreaDocument);
+ }
+ else if (t == HTML.Tag.SELECT)
+ {
+ int size = HTML.getIntegerAttributeValue(a, HTML.Attribute.SIZE,
+ 1);
+ boolean multi = a.getAttribute(HTML.Attribute.MULTIPLE) != null;
+ if (size > 1 || multi)
+ {
+ SelectListModel m = new SelectListModel();
+ if (multi)
+ m.getSelectionModel().setSelectionMode(ListSelectionModel
+ .MULTIPLE_INTERVAL_SELECTION);
+ selectModel = m;
+ }
+ else
+ {
+ selectModel = new SelectComboBoxModel();
+ }
+ a.addAttribute(StyleConstants.ModelAttribute, selectModel);
+ }
+ if (t == HTML.Tag.OPTION)
+ {
+ option = new Option(a);
+ if (selectModel instanceof SelectListModel)
+ {
+ SelectListModel m = (SelectListModel) selectModel;
+ m.addElement(option);
+ if (option.isSelected())
+ {
+ m.getSelectionModel().addSelectionInterval(numOptions,
+ numOptions);
+ m.addInitialSelection(numOptions);
+ }
+ }
+ else if (selectModel instanceof SelectComboBoxModel)
+ {
+ SelectComboBoxModel m = (SelectComboBoxModel) selectModel;
+ m.addElement(option);
+ if (option.isSelected())
+ {
+ m.setSelectedItem(option);
+ m.setInitialSelection(option);
+ }
+ }
+ numOptions++;
+ }
+ else
+ {
+ // Build the element.
+ super.start(t, a);
+ }
}
/**
@@ -670,13 +915,106 @@ public class HTMLDocument extends DefaultStyledDocument
* with this Action.
*/
public void end(HTML.Tag t)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("FormAction.end not implemented");
+ if (t == HTML.Tag.OPTION)
+ {
+ option = null;
+ }
+ else
+ {
+ if (t == HTML.Tag.TEXTAREA)
+ {
+ inTextArea = false;
+ }
+ else if (t == HTML.Tag.SELECT)
+ {
+ selectModel = null;
+ numOptions = 0;
+ }
+ // Finish the element.
+ super.end(t);
+ }
+ }
+
+ private void setModel(String type, MutableAttributeSet attrs)
+ {
+ if (type.equals("submit") || type.equals("reset")
+ || type.equals("image"))
+ {
+ // Create button.
+ attrs.addAttribute(StyleConstants.ModelAttribute,
+ new DefaultButtonModel());
+ }
+ else if (type.equals("text") || type.equals("password"))
+ {
+ String text = (String) attrs.getAttribute(HTML.Attribute.VALUE);
+ ResetablePlainDocument doc = new ResetablePlainDocument();
+ if (text != null)
+ {
+ doc.setInitialText(text);
+ try
+ {
+ doc.insertString(0, text, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't happen.
+ assert false;
+ }
+ }
+ attrs.addAttribute(StyleConstants.ModelAttribute, doc);
+ }
+ else if (type.equals("file"))
+ {
+ attrs.addAttribute(StyleConstants.ModelAttribute,
+ new PlainDocument());
+ }
+ else if (type.equals("checkbox") || type.equals("radio"))
+ {
+ ResetableToggleButtonModel model =
+ new ResetableToggleButtonModel();
+ if (attrs.getAttribute(HTML.Attribute.SELECTED) != null)
+ {
+ model.setSelected(true);
+ model.setInitial(true);
+ }
+ if (type.equals("radio"))
+ {
+ String name = (String) attrs.getAttribute(HTML.Attribute.NAME);
+ if (name != null)
+ {
+ if (buttonGroups == null)
+ buttonGroups = new HashMap();
+ ButtonGroup group = (ButtonGroup) buttonGroups.get(name);
+ if (group == null)
+ {
+ group = new ButtonGroup();
+ buttonGroups.put(name, group);
+ }
+ model.setGroup(group);
+ }
+ }
+ attrs.addAttribute(StyleConstants.ModelAttribute, model);
+ }
+ }
+ }
+
+ /**
+ * Called for form tags.
+ */
+ class FormTagAction
+ extends BlockAction
+ {
+ /**
+ * Clears the button group mapping.
+ */
+ public void end(HTML.Tag t)
+ {
+ super.end(t);
+ buttonGroups = null;
}
}
-
+
/**
* This action indicates that the content between starting and closing HTML
* elements (like script - /script) should not be visible. The content is
@@ -707,7 +1045,10 @@ public class HTMLDocument extends DefaultStyledDocument
blockClose(t);
}
}
-
+
+ /**
+ * Handles &lt;isindex&gt; tags.
+ */
public class IsindexAction extends TagAction
{
/**
@@ -715,10 +1056,10 @@ public class HTMLDocument extends DefaultStyledDocument
* of tags associated with this Action.
*/
public void start(HTML.Tag t, MutableAttributeSet a)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("IsindexAction.start not implemented");
+ blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+ addSpecialElement(t, a);
+ blockClose(HTML.Tag.IMPLIED);
}
}
@@ -730,7 +1071,7 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void start(HTML.Tag t, MutableAttributeSet a)
{
- blockOpen(t, a);
+ super.start(t, a);
}
/**
@@ -739,10 +1080,13 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void end(HTML.Tag t)
{
- blockClose(t);
+ super.end(t);
}
}
-
+
+ /**
+ * This action is performed when a &lt;pre&gt; tag is parsed.
+ */
public class PreAction extends BlockAction
{
/**
@@ -750,11 +1094,11 @@ public class HTMLDocument extends DefaultStyledDocument
* of tags associated with this Action.
*/
public void start(HTML.Tag t, MutableAttributeSet a)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("PreAction.start not implemented");
- super.start(t, a);
+ inPreTag = true;
+ blockOpen(t, a);
+ a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
+ blockOpen(HTML.Tag.IMPLIED, a);
}
/**
@@ -762,11 +1106,10 @@ public class HTMLDocument extends DefaultStyledDocument
* with this Action.
*/
public void end(HTML.Tag t)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("PreAction.end not implemented");
- super.end(t);
+ blockClose(HTML.Tag.IMPLIED);
+ inPreTag = false;
+ blockClose(t);
}
}
@@ -798,7 +1141,6 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("AreaAction.start not implemented");
}
/**
@@ -809,10 +1151,44 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("AreaAction.end not implemented");
}
}
-
+
+ /**
+ * Converts HTML tags to CSS attributes.
+ */
+ class ConvertAction
+ extends TagAction
+ {
+
+ public void start(HTML.Tag tag, MutableAttributeSet atts)
+ {
+ pushCharacterStyle();
+ charAttr.addAttribute(tag, atts.copyAttributes());
+ StyleSheet styleSheet = getStyleSheet();
+ // TODO: Add other tags here.
+ if (tag == HTML.Tag.FONT)
+ {
+ String color = (String) atts.getAttribute(HTML.Attribute.COLOR);
+ if (color != null)
+ styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
+ String face = (String) atts.getAttribute(HTML.Attribute.FACE);
+ if (face != null)
+ styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY,
+ face);
+ String size = (String) atts.getAttribute(HTML.Attribute.SIZE);
+ if (size != null)
+ styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE,
+ size);
+ }
+ }
+
+ public void end(HTML.Tag tag)
+ {
+ popCharacterStyle();
+ }
+ }
+
class BaseAction extends TagAction
{
/**
@@ -820,22 +1196,9 @@ public class HTMLDocument extends DefaultStyledDocument
* of tags associated with this Action.
*/
public void start(HTML.Tag t, MutableAttributeSet a)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("BaseAction.start not implemented");
+ baseTarget = (String) a.getAttribute(HTML.Attribute.TARGET);
}
-
- /**
- * Called when an end tag is seen for one of the types of tags associated
- * with this Action.
- */
- public void end(HTML.Tag t)
- throws NotImplementedException
- {
- // FIXME: Implement.
- print ("BaseAction.end not implemented");
- }
}
class HeadAction extends BlockAction
@@ -848,7 +1211,6 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("HeadAction.start not implemented: "+t);
super.start(t, a);
}
@@ -857,37 +1219,87 @@ public class HTMLDocument extends DefaultStyledDocument
* with this Action.
*/
public void end(HTML.Tag t)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("HeadAction.end not implemented: "+t);
+ // We read in all the stylesheets that are embedded or referenced
+ // inside the header.
+ if (styles != null)
+ {
+ int numStyles = styles.size();
+ for (int i = 0; i < numStyles; i++)
+ {
+ String style = (String) styles.get(i);
+ getStyleSheet().addRule(style);
+ }
+ }
super.end(t);
- }
+ }
}
- class LinkAction extends TagAction
+ class LinkAction extends HiddenAction
{
/**
* This method is called when a start tag is seen for one of the types
* of tags associated with this Action.
*/
public void start(HTML.Tag t, MutableAttributeSet a)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("LinkAction.start not implemented");
+ super.start(t, a);
+ String type = (String) a.getAttribute(HTML.Attribute.TYPE);
+ if (type == null)
+ type = "text/css";
+ if (type.equals("text/css"))
+ {
+ String rel = (String) a.getAttribute(HTML.Attribute.REL);
+ String media = (String) a.getAttribute(HTML.Attribute.MEDIA);
+ String title = (String) a.getAttribute(HTML.Attribute.TITLE);
+ if (media == null)
+ media = "all";
+ else
+ media = media.toLowerCase();
+ if (rel != null)
+ {
+ rel = rel.toLowerCase();
+ if ((media.indexOf("all") != -1
+ || media.indexOf("screen") != -1)
+ && (rel.equals("stylesheet")))
+ {
+ String href = (String) a.getAttribute(HTML.Attribute.HREF);
+ URL url = null;
+ try
+ {
+ url = new URL(baseURL, href);
+ }
+ catch (MalformedURLException ex)
+ {
+ try
+ {
+ url = new URL(href);
+ }
+ catch (MalformedURLException ex2)
+ {
+ url = null;
+ }
+ }
+ if (url != null)
+ {
+ try
+ {
+ getStyleSheet().importStyleSheet(url);
+ }
+ catch (Exception ex)
+ {
+ // Don't let exceptions and runtime exceptions
+ // in CSS parsing disprupt the HTML parsing
+ // process. But inform the user/developer
+ // on the console about it.
+ ex.printStackTrace();
+ }
+ }
+ }
+ }
+ }
}
- /**
- * Called when an end tag is seen for one of the types of tags associated
- * with this Action.
- */
- public void end(HTML.Tag t)
- throws NotImplementedException
- {
- // FIXME: Implement.
- print ("LinkAction.end not implemented");
- }
}
class MapAction extends TagAction
@@ -900,7 +1312,6 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("MapAction.start not implemented");
}
/**
@@ -911,7 +1322,6 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("MapAction.end not implemented");
}
}
@@ -925,7 +1335,6 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("MetaAction.start not implemented");
}
/**
@@ -936,10 +1345,9 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("MetaAction.end not implemented");
}
}
-
+
class StyleAction extends TagAction
{
/**
@@ -947,10 +1355,8 @@ public class HTMLDocument extends DefaultStyledDocument
* of tags associated with this Action.
*/
public void start(HTML.Tag t, MutableAttributeSet a)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("StyleAction.start not implemented");
+ inStyleTag = true;
}
/**
@@ -958,10 +1364,8 @@ public class HTMLDocument extends DefaultStyledDocument
* with this Action.
*/
public void end(HTML.Tag t)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("StyleAction.end not implemented");
+ inStyleTag = false;
}
}
@@ -975,7 +1379,6 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("TitleAction.start not implemented");
}
/**
@@ -986,7 +1389,6 @@ public class HTMLDocument extends DefaultStyledDocument
throws NotImplementedException
{
// FIXME: Implement.
- print ("TitleAction.end not implemented");
}
}
@@ -998,13 +1400,11 @@ public class HTMLDocument extends DefaultStyledDocument
public HTMLReader(int offset, int popDepth, int pushDepth,
HTML.Tag insertTag)
{
- print ("HTMLReader created with pop: "+popDepth
- + " push: "+pushDepth + " offset: "+offset
- + " tag: "+insertTag);
this.insertTag = insertTag;
this.offset = offset;
this.popDepth = popDepth;
this.pushDepth = pushDepth;
+ threshold = getTokenThreshold();
initTags();
}
@@ -1028,7 +1428,7 @@ public class HTMLDocument extends DefaultStyledDocument
StyleAction styleAction = new StyleAction();
TitleAction titleAction = new TitleAction();
-
+ ConvertAction convertAction = new ConvertAction();
tagToAction.put(HTML.Tag.A, characterAction);
tagToAction.put(HTML.Tag.ADDRESS, characterAction);
tagToAction.put(HTML.Tag.APPLET, hiddenAction);
@@ -1051,8 +1451,8 @@ public class HTMLDocument extends DefaultStyledDocument
tagToAction.put(HTML.Tag.DL, blockAction);
tagToAction.put(HTML.Tag.DT, paragraphAction);
tagToAction.put(HTML.Tag.EM, characterAction);
- tagToAction.put(HTML.Tag.FONT, characterAction);
- tagToAction.put(HTML.Tag.FORM, blockAction);
+ tagToAction.put(HTML.Tag.FONT, convertAction);
+ tagToAction.put(HTML.Tag.FORM, new FormTagAction());
tagToAction.put(HTML.Tag.FRAME, specialAction);
tagToAction.put(HTML.Tag.FRAMESET, blockAction);
tagToAction.put(HTML.Tag.H1, paragraphAction);
@@ -1142,18 +1542,28 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void flush() throws BadLocationException
{
- DefaultStyledDocument.ElementSpec[] elements;
- elements = new DefaultStyledDocument.ElementSpec[parseBuffer.size()];
- parseBuffer.copyInto(elements);
- parseBuffer.removeAllElements();
- if (offset == 0)
- create(elements);
- else
- insert(offset, elements);
+ flushImpl();
+ }
- offset += HTMLDocument.this.getLength() - offset;
+ /**
+ * Flushes the buffer and handle partial inserts.
+ *
+ */
+ private void flushImpl()
+ throws BadLocationException
+ {
+ int oldLen = getLength();
+ int size = parseBuffer.size();
+ ElementSpec[] elems = new ElementSpec[size];
+ parseBuffer.copyInto(elems);
+ if (oldLen == 0)
+ create(elems);
+ else
+ insert(offset, elems);
+ parseBuffer.removeAllElements();
+ offset += getLength() - oldLen;
}
-
+
/**
* This method is called by the parser to indicate a block of
* text was encountered. Should insert the text appropriately.
@@ -1163,8 +1573,24 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void handleText(char[] data, int pos)
{
- if (data != null && data.length > 0)
- addContent(data, 0, data.length);
+ if (shouldInsert() && data != null && data.length > 0)
+ {
+ if (inTextArea)
+ textAreaContent(data);
+ else if (inPreTag)
+ preContent(data);
+ else if (option != null)
+ option.setLabel(new String(data));
+ else if (inStyleTag)
+ {
+ if (styles == null)
+ styles = new ArrayList();
+ styles.add(new String(data));
+ }
+ else
+ addContent(data, 0, data.length);
+
+ }
}
/**
@@ -1214,8 +1640,7 @@ public class HTMLDocument extends DefaultStyledDocument
TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT);
if (action != null)
{
- action.start(HTML.Tag.COMMENT,
- htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET);
+ action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
action.end(HTML.Tag.COMMENT);
}
}
@@ -1277,7 +1702,6 @@ public class HTMLDocument extends DefaultStyledDocument
public void handleEndOfLineString(String eol)
{
// FIXME: Implement.
- print ("HTMLReader.handleEndOfLineString not implemented yet");
}
/**
@@ -1287,22 +1711,48 @@ public class HTMLDocument extends DefaultStyledDocument
* @param data the text to add to the textarea
*/
protected void textAreaContent(char[] data)
- throws NotImplementedException
{
- // FIXME: Implement.
- print ("HTMLReader.textAreaContent not implemented yet");
+ try
+ {
+ int offset = textAreaDocument.getLength();
+ String text = new String(data);
+ textAreaDocument.setInitialText(text);
+ textAreaDocument.insertString(offset, text, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // Must not happen as we insert at a model location that we
+ // got from the document itself.
+ assert false;
+ }
}
/**
* Adds the given text that was encountered in a <PRE> element.
- *
+ * This adds synthesized lines to hold the text runs.
+ *
* @param data the text
*/
protected void preContent(char[] data)
- throws NotImplementedException
{
- // FIXME: Implement
- print ("HTMLReader.preContent not implemented yet");
+ int start = 0;
+ for (int i = 0; i < data.length; i++)
+ {
+ if (data[i] == '\n')
+ {
+ addContent(data, start, i - start + 1);
+ blockClose(HTML.Tag.IMPLIED);
+ MutableAttributeSet atts = new SimpleAttributeSet();
+ atts.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
+ blockOpen(HTML.Tag.IMPLIED, atts);
+ start = i + 1;
+ }
+ }
+ if (start < data.length)
+ {
+ // Add remaining last line.
+ addContent(data, start, data.length - start);
+ }
}
/**
@@ -1314,17 +1764,48 @@ public class HTMLDocument extends DefaultStyledDocument
*/
protected void blockOpen(HTML.Tag t, MutableAttributeSet attr)
{
- printBuffer();
- DefaultStyledDocument.ElementSpec element;
+ if (inImpliedParagraph())
+ blockClose(HTML.Tag.IMPLIED);
+ // Push the new tag on top of the stack.
parseStack.push(t);
+
+ DefaultStyledDocument.ElementSpec element;
+
AbstractDocument.AttributeContext ctx = getAttributeContext();
AttributeSet copy = attr.copyAttributes();
copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t);
element = new DefaultStyledDocument.ElementSpec(copy,
DefaultStyledDocument.ElementSpec.StartTagType);
parseBuffer.addElement(element);
- printBuffer();
+ }
+
+ /**
+ * Returns true when we are currently inside a paragraph, either
+ * a real one or an implied, false otherwise.
+ *
+ * @return
+ */
+ private boolean inParagraph()
+ {
+ boolean inParagraph = false;
+ if (! parseStack.isEmpty())
+ {
+ HTML.Tag top = parseStack.peek();
+ inParagraph = top == HTML.Tag.P || top == HTML.Tag.IMPLIED;
+ }
+ return inParagraph;
+ }
+
+ private boolean inImpliedParagraph()
+ {
+ boolean inParagraph = false;
+ if (! parseStack.isEmpty())
+ {
+ HTML.Tag top = parseStack.peek();
+ inParagraph = top == HTML.Tag.IMPLIED;
+ }
+ return inParagraph;
}
/**
@@ -1335,32 +1816,29 @@ public class HTMLDocument extends DefaultStyledDocument
*/
protected void blockClose(HTML.Tag t)
{
- printBuffer();
DefaultStyledDocument.ElementSpec element;
+ if (inImpliedParagraph() && t != HTML.Tag.IMPLIED)
+ blockClose(HTML.Tag.IMPLIED);
+
+ // Pull the token from the stack.
+ if (! parseStack.isEmpty()) // Just to be sure.
+ parseStack.pop();
+
// If the previous tag is a start tag then we insert a synthetic
// content tag.
DefaultStyledDocument.ElementSpec prev;
- prev = (DefaultStyledDocument.ElementSpec)
- parseBuffer.get(parseBuffer.size() - 1);
- if (prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
+ prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec)
+ parseBuffer.get(parseBuffer.size() - 1) : null;
+ if (prev != null &&
+ prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
{
- AbstractDocument.AttributeContext ctx = getAttributeContext();
- AttributeSet attributes = ctx.getEmptySet();
- attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute,
- HTML.Tag.CONTENT);
- element = new DefaultStyledDocument.ElementSpec(attributes,
- DefaultStyledDocument.ElementSpec.ContentType,
- new char[0], 0, 0);
- parseBuffer.add(element);
+ addContent(new char[]{' '}, 0, 1);
}
element = new DefaultStyledDocument.ElementSpec(null,
DefaultStyledDocument.ElementSpec.EndTagType);
parseBuffer.addElement(element);
- printBuffer();
- if (parseStack.size() > 0)
- parseStack.pop();
}
/**
@@ -1389,6 +1867,11 @@ public class HTMLDocument extends DefaultStyledDocument
protected void addContent(char[] data, int offs, int length,
boolean generateImpliedPIfNecessary)
{
+ if (generateImpliedPIfNecessary && ! inParagraph())
+ {
+ blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+ }
+
AbstractDocument.AttributeContext ctx = getAttributeContext();
DefaultStyledDocument.ElementSpec element;
AttributeSet attributes = null;
@@ -1405,16 +1888,16 @@ public class HTMLDocument extends DefaultStyledDocument
DefaultStyledDocument.ElementSpec.ContentType,
data, offs, length);
- printBuffer();
// Add the element to the buffer
parseBuffer.addElement(element);
- printBuffer();
- if (parseBuffer.size() > HTMLDocument.this.getTokenThreshold())
+ if (parseBuffer.size() > threshold)
{
+ if (threshold <= MAX_THRESHOLD)
+ threshold *= GROW_THRESHOLD;
try
{
- flush();
+ flushImpl();
}
catch (BadLocationException ble)
{
@@ -1431,29 +1914,23 @@ public class HTMLDocument extends DefaultStyledDocument
*/
protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a)
{
+ if (t != HTML.Tag.FRAME && ! inParagraph())
+ {
+ blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+ }
+
a.addAttribute(StyleConstants.NameAttribute, t);
- // Migrate from the rather htmlAttributeSet to the faster, lighter and
- // unchangeable alternative implementation.
- AttributeSet copy = a.copyAttributes();
-
// The two spaces are required because some special elements like HR
// must be broken. At least two characters are needed to break into the
// two parts.
DefaultStyledDocument.ElementSpec spec =
- new DefaultStyledDocument.ElementSpec(copy,
+ new DefaultStyledDocument.ElementSpec(a.copyAttributes(),
DefaultStyledDocument.ElementSpec.ContentType,
- new char[] {' ', ' '}, 0, 2 );
+ new char[] {' '}, 0, 1 );
parseBuffer.add(spec);
}
- void printBuffer()
- {
- print ("\n*********BUFFER**********");
- for (int i = 0; i < parseBuffer.size(); i ++)
- print (" "+parseBuffer.get(i));
- print ("***************************");
- }
}
/**
@@ -1533,10 +2010,6 @@ public class HTMLDocument extends DefaultStyledDocument
}
};
- // Set the parent HTML tag.
- reader.parseStack.push(parent.getAttributes().getAttribute(
- StyleConstants.NameAttribute));
-
return reader;
}
@@ -1728,4 +2201,98 @@ public void setOuterHTML(Element elem, String htmlText)
// TODO charset
getParser().parse(new StringReader(htmlText), reader, true);
}
+
+ /**
+ * Overridden to tag content with the synthetic HTML.Tag.CONTENT
+ * tag.
+ */
+ protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att)
+ {
+ if (att == null)
+ {
+ SimpleAttributeSet sas = new SimpleAttributeSet();
+ sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
+ att = sas;
+ }
+ super.insertUpdate(evt, att);
+ }
+
+ /**
+ * Returns <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise
+ */
+ boolean isFrameDocument()
+ {
+ return frameDocument;
+ }
+
+ /**
+ * Set <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise.
+ *
+ * @param frameDoc <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise
+ */
+ void setFrameDocument(boolean frameDoc)
+ {
+ frameDocument = frameDoc;
+ }
+
+ /**
+ * Returns the target that is specified in the base tag, if this is the case.
+ *
+ * @return the target that is specified in the base tag, if this is the case
+ */
+ String getBaseTarget()
+ {
+ return baseTarget;
+ }
+
+ /**
+ * Updates the A tag's pseudo class value in response to a hyperlink
+ * action.
+ *
+ * @param el the corresponding element
+ * @param value the new value
+ */
+ void updateSpecialClass(Element el, HTML.Attribute cl, String value)
+ {
+ try
+ {
+ writeLock();
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(el.getStartOffset(), 1,
+ DocumentEvent.EventType.CHANGE);
+ AttributeSet elAtts = el.getAttributes();
+ AttributeSet anchorAtts = (AttributeSet) elAtts.getAttribute(HTML.Tag.A);
+ if (anchorAtts != null)
+ {
+ AttributeSet copy = elAtts.copyAttributes();
+ StyleSheet ss = getStyleSheet();
+ if (value != null)
+ {
+ anchorAtts = ss.addAttribute(anchorAtts, cl, value);
+ }
+ else
+ {
+ anchorAtts = ss.removeAttribute(anchorAtts, cl);
+ }
+ MutableAttributeSet matts = (MutableAttributeSet) elAtts;
+ ev.addEdit(new AttributeUndoableEdit(el, copy, false));
+ matts.removeAttribute(HTML.Tag.A);
+ matts.addAttribute(HTML.Tag.A, anchorAtts);
+ ev.end();
+ fireChangedUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+ }
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
index 5d77be8..0ede1c7 100644
--- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
+++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
@@ -39,32 +39,38 @@ exception statement from your version. */
package javax.swing.text.html;
-import gnu.classpath.NotImplementedException;
-
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.Cursor;
+import java.awt.Point;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.swing.Action;
import javax.swing.JEditorPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.StyleConstants;
-import javax.swing.text.StyleContext;
+import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.TextAction;
import javax.swing.text.View;
@@ -74,7 +80,7 @@ import javax.swing.text.html.parser.ParserDelegator;
/* Move these imports here after javax.swing.text.html to make it compile
with jikes. */
import gnu.javax.swing.text.html.parser.GnuParserDelegator;
-import gnu.javax.swing.text.html.parser.HTML_401Swing;
+import gnu.javax.swing.text.html.parser.HTML_401F;
/**
* @author Lillian Angel (langel at redhat dot com)
@@ -92,7 +98,12 @@ public class HTMLEditorKit
extends MouseAdapter
implements MouseMotionListener, Serializable
{
-
+
+ /**
+ * The element of the last anchor tag.
+ */
+ private Element lastAnchorElement;
+
/**
* Constructor
*/
@@ -110,11 +121,14 @@ public class HTMLEditorKit
*/
public void mouseClicked(MouseEvent e)
{
- /*
- These MouseInputAdapter methods generate mouse appropriate events around
- hyperlinks (entering, exiting, and activating).
- */
- // FIXME: Not implemented.
+ JEditorPane editor = (JEditorPane) e.getSource();
+ if (! editor.isEditable() && SwingUtilities.isLeftMouseButton(e))
+ {
+ Point loc = e.getPoint();
+ int pos = editor.viewToModel(loc);
+ if (pos >= 0)
+ activateLink(pos, editor, e.getX(), e.getY());
+ }
}
/**
@@ -124,11 +138,7 @@ public class HTMLEditorKit
*/
public void mouseDragged(MouseEvent e)
{
- /*
- These MouseInputAdapter methods generate mouse appropriate events around
- hyperlinks (entering, exiting, and activating).
- */
- // FIXME: Not implemented.
+ // Nothing to do here.
}
/**
@@ -138,29 +148,159 @@ public class HTMLEditorKit
*/
public void mouseMoved(MouseEvent e)
{
- /*
- These MouseInputAdapter methods generate mouse appropriate events around
- hyperlinks (entering, exiting, and activating).
- */
- // FIXME: Not implemented.
+ JEditorPane editor = (JEditorPane) e.getSource();
+ HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit();
+ if (! editor.isEditable())
+ {
+ Document doc = editor.getDocument();
+ if (doc instanceof HTMLDocument)
+ {
+ Cursor newCursor = kit.getDefaultCursor();
+ HTMLDocument htmlDoc = (HTMLDocument) doc;
+ Point loc = e.getPoint();
+ int pos = editor.viewToModel(loc);
+ Element el = htmlDoc.getCharacterElement(pos);
+ if (pos < el.getStartOffset() || pos >= el.getEndOffset())
+ el = null;
+ if (el != null)
+ {
+ AttributeSet aAtts = (AttributeSet)
+ el.getAttributes().getAttribute(HTML.Tag.A);
+ if (aAtts != null)
+ {
+ if (el != lastAnchorElement)
+ {
+ if (lastAnchorElement != null)
+ htmlDoc.updateSpecialClass(lastAnchorElement,
+ HTML.Attribute.DYNAMIC_CLASS,
+ null);
+ lastAnchorElement = el;
+ htmlDoc.updateSpecialClass(el,
+ HTML.Attribute.DYNAMIC_CLASS,
+ "hover");
+ }
+ newCursor = kit.getLinkCursor();
+ }
+ else
+ {
+ if (lastAnchorElement != null)
+ htmlDoc.updateSpecialClass(lastAnchorElement,
+ HTML.Attribute.DYNAMIC_CLASS,
+ null);
+ lastAnchorElement = null;
+ }
+ }
+ else
+ {
+ if (lastAnchorElement != null)
+ htmlDoc.updateSpecialClass(lastAnchorElement,
+ HTML.Attribute.DYNAMIC_CLASS,
+ null);
+ lastAnchorElement = null;
+ }
+ if (editor.getCursor() != newCursor)
+ {
+ editor.setCursor(newCursor);
+ }
+ }
+ }
}
-
+
/**
* If the given position represents a link, then linkActivated is called
- * on the JEditorPane. Implemented to forward to the method with the same
- * name, but pos == editor == -1.
- *
- * @param pos - the position
- * @param editor - the editor pane
+ * on the JEditorPane.
+ *
+ * @param pos the position
+ * @param editor the editor pane
+ */
+ protected void activateLink(int pos, JEditorPane editor)
+ {
+ activateLink(pos, editor);
+ }
+
+ private void activateLink(int pos, JEditorPane editor, int x, int y)
+ {
+ // TODO: This is here for future extension for mapped links support.
+ // For the time beeing we implement simple hyperlinks.
+ Document doc = editor.getDocument();
+ if (doc instanceof HTMLDocument)
+ {
+ HTMLDocument htmlDoc = (HTMLDocument) doc;
+ Element el = htmlDoc.getCharacterElement(pos);
+ AttributeSet atts = el.getAttributes();
+ AttributeSet anchorAtts =
+ (AttributeSet) atts.getAttribute(HTML.Tag.A);
+ String href = null;
+ if (anchorAtts != null)
+ {
+ href = (String) anchorAtts.getAttribute(HTML.Attribute.HREF);
+ htmlDoc.updateSpecialClass(el, HTML.Attribute.PSEUDO_CLASS,
+ "visited");
+ }
+ else
+ {
+ // TODO: Implement link maps here.
+ }
+ HyperlinkEvent event = null;
+ if (href != null)
+ event = createHyperlinkEvent(editor, htmlDoc, href,
+ anchorAtts, el);
+ if (event != null)
+ editor.fireHyperlinkUpdate(event);
+ }
+
+ }
+
+ /**
+ * Creates a HyperlinkEvent for the specified href and anchor if
+ * possible. If for some reason this won't work, return null.
+ *
+ * @param editor the editor
+ * @param doc the document
+ * @param href the href link
+ * @param anchor the anchor
+ * @param el the element
+ *
+ * @return the hyperlink event, or <code>null</code> if we couldn't
+ * create one
*/
- protected void activateLink(int pos,
- JEditorPane editor)
+ private HyperlinkEvent createHyperlinkEvent(JEditorPane editor,
+ HTMLDocument doc,
+ String href,
+ AttributeSet anchor,
+ Element el)
{
- /*
- This method creates and fires a HyperlinkEvent if the document is an
- instance of HTMLDocument and the href tag of the link is not null.
- */
- // FIXME: Not implemented.
+ URL url;
+ try
+ {
+ URL base = doc.getBase();
+ url = new URL(base, href);
+
+ }
+ catch (MalformedURLException ex)
+ {
+ url = null;
+ }
+ HyperlinkEvent ev;
+ if (doc.isFrameDocument())
+ {
+ String target = null;
+ if (anchor != null)
+ target = (String) anchor.getAttribute(HTML.Attribute.TARGET);
+ if (target == null || target.equals(""))
+ target = doc.getBaseTarget();
+ if (target == null || target.equals(""))
+ target = "_self";
+ ev = new HTMLFrameHyperlinkEvent(editor,
+ HyperlinkEvent.EventType.ACTIVATED,
+ url, href, el, target);
+ }
+ else
+ {
+ ev = new HyperlinkEvent(editor, HyperlinkEvent.EventType.ACTIVATED,
+ url, href, el);
+ }
+ return ev;
}
}
@@ -201,7 +341,7 @@ public class HTMLEditorKit
* Tag to check for in the document.
*/
protected HTML.Tag parentTag;
-
+
/**
* Initializes all fields.
*
@@ -305,20 +445,9 @@ public class HTMLEditorKit
Element insertElement,
String html, HTML.Tag parentTag,
HTML.Tag addTag)
- throws NotImplementedException
{
- /*
- As its name implies, this protected method is used when HTML is inserted at a
- boundary. (A boundary in this case is an offset in doc that exactly matches the
- beginning offset of the parentTag.) It performs the extra work required to keep
- the tag stack in shape and then calls insertHTML(). The editor and doc argu-
- ments are the editor pane and document where the HTML should go. The offset
- argument represents the cursor location or selection start in doc. The insert-
- Element and parentTag arguments are used to calculate the proper number of
- tag pops and pushes before inserting the HTML (via html and addTag, which are
- passed directly to insertHTML()).
- */
- // FIXME: not implemented
+ insertAtBoundry(editor, doc, offset, insertElement,
+ html, parentTag, addTag);
}
/**
@@ -344,8 +473,50 @@ public class HTMLEditorKit
String html, HTML.Tag parentTag,
HTML.Tag addTag)
{
- insertAtBoundary(editor, doc, offset, insertElement,
- html, parentTag, addTag);
+ Element parent = insertElement;
+ Element el;
+ // Find common parent element.
+ if (offset > 0 || insertElement == null)
+ {
+ el = doc.getDefaultRootElement();
+ while (el != null && el.getStartOffset() != offset
+ && ! el.isLeaf())
+ el = el.getElement(el.getElementIndex(offset));
+ parent = el != null ? el.getParentElement() : null;
+ }
+ if (parent != null)
+ {
+ int pops = 0;
+ int pushes = 0;
+ if (offset == 0 && insertElement != null)
+ {
+ el = parent;
+ while (el != null && ! el.isLeaf())
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pops++;
+ }
+ }
+ else
+ {
+ el = parent;
+ offset--;
+ while (el != null && ! el.isLeaf())
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pops++;
+ }
+ el = parent;
+ offset++;
+ while (el != null && el != insertElement)
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pushes++;
+ }
+ }
+ pops = Math.max(0, pops - 1);
+ insertHTML(editor, doc, offset, html, pops, pushes, addTag);
+ }
}
/**
@@ -355,16 +526,97 @@ public class HTMLEditorKit
*/
public void actionPerformed(ActionEvent ae)
{
- Object source = ae.getSource();
- if (source instanceof JEditorPane)
+ JEditorPane source = getEditor(ae);
+ if (source != null)
+ {
+ HTMLDocument d = getHTMLDocument(source);
+ int offset = source.getSelectionStart();
+ int length = d.getLength();
+ boolean inserted = true;
+ if (! tryInsert(source, d, offset, parentTag, addTag))
+ {
+ inserted = tryInsert(source, d, offset, alternateParentTag,
+ alternateAddTag);
+ }
+ if (inserted)
+ adjustSelection(source, d, offset, length);
+ }
+ }
+
+ /**
+ * Tries to insert the html chunk to the specified <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)
+ {
+ int depth = elementCountToTag(doc, offset - 1, tag);
+ if (depth != -1)
+ {
+ insertHTML(pane, doc, offset, html, depth, 0, addTag);
+ inserted = true;
+ }
+ }
+ return inserted;
+ }
+
+ /**
+ * Adjusts the selection after an insertion has been performed.
+ *
+ * @param pane the editor pane
+ * @param doc the document
+ * @param offset the insert offset
+ * @param oldLen the old document length
+ */
+ private void adjustSelection(JEditorPane pane, HTMLDocument doc,
+ int offset, int oldLen)
+ {
+ int newLen = doc.getLength();
+ if (newLen != oldLen && offset < newLen)
{
- JEditorPane pane = ((JEditorPane) source);
- Document d = pane.getDocument();
- if (d instanceof HTMLDocument)
- insertHTML(pane, (HTMLDocument) d, 0, html, 0, 0, addTag);
- // FIXME: is this correct parameters?
+ if (offset > 0)
+ {
+ String text;
+ try
+ {
+ text = doc.getText(offset - 1, 1);
+ }
+ catch (BadLocationException ex)
+ {
+ text = null;
+ }
+ if (text != null && text.length() > 0
+ && text.charAt(0) == '\n')
+ {
+ pane.select(offset, offset);
+ }
+ else
+ {
+ pane.select(offset + 1, offset + 1);
+ }
+ }
+ else
+ {
+ pane.select(1, 1);
+ }
}
- // FIXME: else not implemented
}
}
@@ -540,53 +792,56 @@ public class HTMLEditorKit
{
HTML.Tag tag = (HTML.Tag) attr;
- if (tag.equals(HTML.Tag.IMPLIED) || tag.equals(HTML.Tag.P)
- || tag.equals(HTML.Tag.H1) || tag.equals(HTML.Tag.H2)
- || tag.equals(HTML.Tag.H3) || tag.equals(HTML.Tag.H4)
- || tag.equals(HTML.Tag.H5) || tag.equals(HTML.Tag.H6)
- || tag.equals(HTML.Tag.DT))
+ if (tag == HTML.Tag.IMPLIED || tag == HTML.Tag.P
+ || tag == HTML.Tag.H1 || tag == HTML.Tag.H2
+ || tag == HTML.Tag.H3 || tag == HTML.Tag.H4
+ || tag == HTML.Tag.H5 || tag == HTML.Tag.H6
+ || tag == HTML.Tag.DT)
view = new ParagraphView(element);
- else if (tag.equals(HTML.Tag.LI) || tag.equals(HTML.Tag.DL)
- || tag.equals(HTML.Tag.DD) || tag.equals(HTML.Tag.BODY)
- || tag.equals(HTML.Tag.HTML) || tag.equals(HTML.Tag.CENTER)
- || tag.equals(HTML.Tag.DIV)
- || tag.equals(HTML.Tag.BLOCKQUOTE)
- || tag.equals(HTML.Tag.PRE))
+ else if (tag == HTML.Tag.LI || tag == HTML.Tag.DL
+ || tag == HTML.Tag.DD || tag == HTML.Tag.BODY
+ || tag == HTML.Tag.HTML || tag == HTML.Tag.CENTER
+ || tag == HTML.Tag.DIV
+ || tag == HTML.Tag.BLOCKQUOTE
+ || tag == HTML.Tag.PRE
+ || tag == HTML.Tag.FORM
+ // Misplaced TD and TH tags get mapped as vertical block.
+ // Note that correctly placed tags get mapped in TableView.
+ || tag == HTML.Tag.TD || tag == HTML.Tag.TH)
view = new BlockView(element, View.Y_AXIS);
- else if (tag.equals(HTML.Tag.IMG))
+ else if (tag == HTML.Tag.TR)
+ // Misplaced TR tags get mapped as horizontal blocks.
+ // Note that correctly placed tags get mapped in TableView.
+ view = new BlockView(element, View.X_AXIS);
+ else if (tag == HTML.Tag.IMG)
view = new ImageView(element);
- // FIXME: Uncomment when the views have been implemented
- else if (tag.equals(HTML.Tag.CONTENT))
+ else if (tag == HTML.Tag.CONTENT)
view = new InlineView(element);
else if (tag == HTML.Tag.HEAD)
view = new NullView(element);
- else if (tag.equals(HTML.Tag.TABLE))
+ else if (tag == HTML.Tag.TABLE)
view = new javax.swing.text.html.TableView(element);
- else if (tag.equals(HTML.Tag.TD))
- view = new ParagraphView(element);
- else if (tag.equals(HTML.Tag.HR))
+ else if (tag == HTML.Tag.HR)
view = new HRuleView(element);
- else if (tag.equals(HTML.Tag.BR))
+ else if (tag == HTML.Tag.BR)
view = new BRView(element);
+ else if (tag == HTML.Tag.INPUT || tag == HTML.Tag.SELECT
+ || tag == HTML.Tag.TEXTAREA)
+ view = new FormView(element);
- /*
- else if (tag.equals(HTML.Tag.MENU) || tag.equals(HTML.Tag.DIR)
- || tag.equals(HTML.Tag.UL) || tag.equals(HTML.Tag.OL))
+ else if (tag == HTML.Tag.MENU || tag == HTML.Tag.DIR
+ || tag == HTML.Tag.UL || tag == HTML.Tag.OL)
view = new ListView(element);
- else if (tag.equals(HTML.Tag.INPUT) || tag.equals(HTML.Tag.SELECT)
- || tag.equals(HTML.Tag.TEXTAREA))
- view = new FormView(element);
- else if (tag.equals(HTML.Tag.OBJECT))
- view = new ObjectView(element);
- else if (tag.equals(HTML.Tag.FRAMESET))
+ else if (tag == HTML.Tag.FRAMESET)
view = new FrameSetView(element);
- else if (tag.equals(HTML.Tag.FRAME))
- view = new FrameView(element); */
+ else if (tag == HTML.Tag.FRAME)
+ view = new FrameView(element);
+ else if (tag == HTML.Tag.OBJECT)
+ view = new ObjectView(element);
}
if (view == null)
{
- System.err.println("missing tag->view mapping for: " + element);
view = new NullView(element);
}
return view;
@@ -797,14 +1052,42 @@ public class HTMLEditorKit
/**
* Actions for HTML
*/
- private static final Action[] defaultActions = {
- // FIXME: Add default actions for html
+ private static final Action[] defaultActions =
+ {
+ new InsertHTMLTextAction("InsertTable",
+ "<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.
};
/**
* The current style sheet.
*/
- StyleSheet styleSheet;
+ private StyleSheet styleSheet;
/**
* The ViewFactory for HTMLFactory.
@@ -829,12 +1112,7 @@ public class HTMLEditorKit
/**
* The mouse listener used for links.
*/
- LinkController mouseListener;
-
- /**
- * Style context for this editor.
- */
- StyleContext styleContext;
+ private LinkController linkController;
/** The content type */
String contentType = "text/html";
@@ -844,17 +1122,22 @@ public class HTMLEditorKit
/** The editor pane used. */
JEditorPane editorPane;
-
+
+ /**
+ * Whether or not the editor kit handles form submissions.
+ *
+ * @see #isAutoFormSubmission()
+ * @see #setAutoFormSubmission(boolean)
+ */
+ private boolean autoFormSubmission;
+
/**
* Constructs an HTMLEditorKit, creates a StyleContext, and loads the style sheet.
*/
public HTMLEditorKit()
{
- super();
- styleContext = new StyleContext();
- styleSheet = new StyleSheet();
- styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS));
- // FIXME: Set inputAttributes with default.css
+ linkController = new LinkController();
+ autoFormSubmission = true;
}
/**
@@ -877,8 +1160,15 @@ public class HTMLEditorKit
*/
public Document createDefaultDocument()
{
- HTMLDocument document = new HTMLDocument(getStyleSheet());
+ // Protect the shared stylesheet.
+ StyleSheet styleSheet = getStyleSheet();
+ StyleSheet ss = new StyleSheet();
+ ss.addStyleSheet(styleSheet);
+
+ HTMLDocument document = new HTMLDocument(ss);
document.setParser(getParser());
+ document.setAsynchronousLoadPriority(4);
+ document.setTokenThreshold(100);
return document;
}
@@ -892,7 +1182,7 @@ public class HTMLEditorKit
{
if (parser == null)
{
- parser = new GnuParserDelegator(HTML_401Swing.getInstance());
+ parser = new GnuParserDelegator(HTML_401F.getInstance());
}
return parser;
}
@@ -923,8 +1213,7 @@ public class HTMLEditorKit
if (parser == null)
throw new IOException("Parser is null.");
- ParserCallback pc = ((HTMLDocument) doc).getReader
- (offset, popDepth, pushDepth, insertTag);
+ ParserCallback pc = doc.getReader(offset, popDepth, pushDepth, insertTag);
// FIXME: What should ignoreCharSet be set to?
@@ -991,8 +1280,15 @@ public class HTMLEditorKit
{
if (doc instanceof HTMLDocument)
{
- // FIXME: Not implemented. Use HTMLWriter.
- out.write(doc.getText(pos, len));
+ HTMLWriter writer = new HTMLWriter(out, (HTMLDocument) doc, pos, len);
+ writer.write();
+ }
+ else if (doc instanceof StyledDocument)
+ {
+ MinimalHTMLWriter writer = new MinimalHTMLWriter(out,
+ (StyledDocument) doc,
+ pos, len);
+ writer.write();
}
else
super.write(out, doc, pos, len);
@@ -1017,7 +1313,9 @@ public class HTMLEditorKit
public Object clone()
{
// FIXME: Need to clone all fields
- return (HTMLEditorKit) super.clone();
+ HTMLEditorKit copy = (HTMLEditorKit) super.clone();
+ copy.linkController = new LinkController();
+ return copy;
}
/**
@@ -1044,10 +1342,9 @@ public class HTMLEditorKit
public void install(JEditorPane c)
{
super.install(c);
- mouseListener = new LinkController();
- c.addMouseListener(mouseListener);
+ c.addMouseListener(linkController);
+ c.addMouseMotionListener(linkController);
editorPane = c;
- // FIXME: need to set up hyperlinklistener object
}
/**
@@ -1059,8 +1356,8 @@ public class HTMLEditorKit
public void deinstall(JEditorPane c)
{
super.deinstall(c);
- c.removeMouseListener(mouseListener);
- mouseListener = null;
+ c.removeMouseListener(linkController);
+ c.removeMouseMotionListener(linkController);
editorPane = null;
}
@@ -1154,8 +1451,19 @@ public class HTMLEditorKit
{
if (styleSheet == null)
{
- styleSheet = new StyleSheet();
- styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS));
+ try
+ {
+ styleSheet = new StyleSheet();
+ Class c = HTMLEditorKit.class;
+ InputStream in = c.getResourceAsStream(DEFAULT_CSS);
+ InputStreamReader r = new InputStreamReader(in);
+ styleSheet.loadRules(r, null);
+ r.close();
+ }
+ catch (IOException ex)
+ {
+ // No style available.
+ }
}
return styleSheet;
}
@@ -1173,4 +1481,40 @@ public class HTMLEditorKit
{
styleSheet = s;
}
+
+ /**
+ * Returns <code>true</code> when forms should be automatically submitted
+ * by the editor kit. Set this to <code>false</code> when you want to
+ * intercept form submission. In this case you'd want to listen for
+ * hyperlink events on the document and handle FormSubmitEvents specially.
+ *
+ * The default is <code>true</code>.
+ *
+ * @return <code>true</code> when forms should be automatically submitted
+ * by the editor kit, <code>false</code> otherwise
+ *
+ * @since 1.5
+ *
+ * @see #setAutoFormSubmission(boolean)
+ * @see FormSubmitEvent
+ */
+ public boolean isAutoFormSubmission()
+ {
+ return autoFormSubmission;
+ }
+
+ /**
+ * Sets whether or not the editor kit should automatically submit forms.
+ *
+ * @param auto <code>true</code> when the editor kit should handle form
+ * submission, <code>false</code> otherwise
+ *
+ * @since 1.5
+ *
+ * @see #isAutoFormSubmission()
+ */
+ public void setAutoFormSubmission(boolean auto)
+ {
+ autoFormSubmission = auto;
+ }
}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLWriter.java b/libjava/classpath/javax/swing/text/html/HTMLWriter.java
new file mode 100644
index 0000000..44119c7
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/HTMLWriter.java
@@ -0,0 +1,1084 @@
+/* HTMLWriter.java --
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text.html;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import java.util.Enumeration;
+import java.util.HashSet;
+
+import javax.swing.ComboBoxModel;
+
+import javax.swing.text.AbstractWriter;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
+
+import javax.swing.text.html.HTML;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.Option;
+
+/**
+ * HTMLWriter,
+ * A Writer for HTMLDocuments.
+ *
+ * @author David Fu (fchoong at netbeans.jp)
+ */
+
+public class HTMLWriter
+ extends AbstractWriter
+{
+ /**
+ * We keep a reference of the writer passed by the construct.
+ */
+ private Writer outWriter = null;
+
+ /**
+ * We keep a reference of the HTMLDocument passed by the construct.
+ */
+ private HTMLDocument htmlDoc = null;
+
+ /**
+ * Used to keep track of which embeded has been written out.
+ */
+ private HashSet openEmbededTagHashSet = null;
+
+ private String new_line_str = "" + NEWLINE;
+
+ private char[] html_entity_char_arr = {'<', '>', '&', '"'};
+
+ private String[] html_entity_escape_str_arr = {"&lt;", "&gt;", "&amp;",
+ "&quot;"};
+
+ // variables used to output Html Fragment
+ private int doc_pos = -1;
+ private int doc_len = -1;
+ private int doc_offset_remaining = -1;
+ private int doc_len_remaining = -1;
+ private HashSet htmlFragmentParentHashSet = null;
+ private Element startElem = null;
+ private Element endElem = null;
+ private boolean fg_pass_start_elem = false;
+ private boolean fg_pass_end_elem = false;
+
+ /**
+ * Constructs a HTMLWriter.
+ *
+ * @param writer writer to write output to
+ * @param doc the HTMLDocument to output
+ */
+ public HTMLWriter(Writer writer, HTMLDocument doc)
+ {
+ super(writer, doc);
+ outWriter = writer;
+ htmlDoc = doc;
+ openEmbededTagHashSet = new HashSet();
+ } // public HTMLWriter(Writer writer, HTMLDocument doc)
+
+ /**
+ * Constructs a HTMLWriter which outputs a Html Fragment.
+ *
+ * @param writer <code>Writer</code> to write output to
+ * @param doc the <code>javax.swing.text.html.HTMLDocument</code>
+ * to output
+ * @param pos position to start outputing the document
+ * @param len amount to output the document
+ */
+ public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len)
+ {
+ super(writer, doc, pos, len);
+ outWriter = writer;
+ htmlDoc = doc;
+ openEmbededTagHashSet = new HashSet();
+
+ doc_pos = pos;
+ doc_offset_remaining = pos;
+ doc_len = len;
+ doc_len_remaining = len;
+ htmlFragmentParentHashSet = new HashSet();
+ } // public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len)
+
+ /**
+ * Call this method to start outputing HTML.
+ *
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ public void write()
+ throws IOException, BadLocationException
+ {
+ Element rootElem = htmlDoc.getDefaultRootElement();
+
+ if (doc_pos == -1 && doc_len == -1)
+ {
+ // Normal traversal.
+ traverse(rootElem);
+ } // if(doc_pos == -1 && doc_len == -1)
+ else
+ {
+ // Html fragment traversal.
+ if (doc_pos == -1 || doc_len == -1)
+ throw new BadLocationException("Bad Location("
+ + doc_pos + ", " + doc_len + ")", doc_pos);
+
+ startElem = htmlDoc.getCharacterElement(doc_pos);
+
+ int start_offset = startElem.getStartOffset();
+
+ // Positions before start_offset will not be traversed, and thus
+ // will not be counted.
+ if (start_offset > 0)
+ doc_offset_remaining = doc_offset_remaining - start_offset;
+
+ Element tempParentElem = startElem;
+
+ while ((tempParentElem = tempParentElem.getParentElement()) != null)
+ {
+ if (!htmlFragmentParentHashSet.contains(tempParentElem))
+ htmlFragmentParentHashSet.add(tempParentElem);
+ } // while((tempParentElem = tempParentElem.getParentElement())
+ // != null)
+
+ // NOTE: 20061030 - fchoong - the last index should not be included.
+ endElem = htmlDoc.getCharacterElement(doc_pos + doc_len - 1);
+
+ tempParentElem = endElem;
+
+ while ((tempParentElem = tempParentElem.getParentElement()) != null)
+ {
+ if (!htmlFragmentParentHashSet.contains(tempParentElem))
+ htmlFragmentParentHashSet.add(tempParentElem);
+ } // while((tempParentElem = tempParentElem.getParentElement())
+ // != null)
+
+ traverseHtmlFragment(rootElem);
+
+ } // else
+
+ // NOTE: close out remaining open embeded tags.
+ Object[] tag_arr = openEmbededTagHashSet.toArray();
+
+ for (int i = 0; i < tag_arr.length; i++)
+ {
+ writeRaw("</" + tag_arr[i].toString() + ">");
+ } // for(int i = 0; i < tag_arr.length; i++)
+
+ } // public void write() throws IOException, BadLocationException
+
+ /**
+ * Writes all the attributes in the attrSet, except for attrbutes with
+ * keys of <code>javax.swing.text.html.HTML.Tag</code>,
+ * <code>javax.swing.text.StyleConstants</code> or
+ * <code>javax.swing.text.html.HTML.Attribute.ENDTAG</code>.
+ *
+ * @param attrSet attrSet to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void writeAttributes(AttributeSet attrSet)
+ throws IOException
+ {
+ Enumeration attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ // HTML.Attribute.ENDTAG is an instance, not a class.
+ if (!((key instanceof HTML.Tag) || (key instanceof StyleConstants)
+ || (key == HTML.Attribute.ENDTAG)))
+ {
+ if (key == HTML.Attribute.SELECTED)
+ writeRaw(" selected");
+ else if (key == HTML.Attribute.CHECKED)
+ writeRaw(" checked");
+ else
+ writeRaw(" " + key + "=\"" + value + "\"");
+ } // if(!((key instanceof HTML.Tag) || (key instanceof
+ // StyleConstants) || (key == HTML.Attribute.ENDTAG)))
+ } // while(attrNameEnum.hasMoreElements())
+
+ } // protected void writeAttributes(AttributeSet attrSet) throws IOException
+
+ /**
+ * Writes out an empty tag. i.e. a tag without any child elements.
+ *
+ * @param paramElem the element to output as an empty tag
+ *
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void emptyTag(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ String elem_name = paramElem.getName();
+ AttributeSet attrSet = paramElem.getAttributes();
+
+ writeRaw("<" + elem_name);
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ if (isBlockTag(attrSet))
+ {
+ writeRaw("</" + elem_name + ">");
+ } // if(isBlockTag(attrSet))
+
+ } // protected void emptyTag(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Determines if it is a block tag or not.
+ *
+ * @param attrSet the attrSet of the element
+ *
+ * @return <code>true</code> if it is a block tag
+ * <code>false</code> if it is a not block tag
+ */
+ protected boolean isBlockTag(AttributeSet attrSet)
+ {
+ return ((HTML.Tag)
+ attrSet.getAttribute(StyleConstants.NameAttribute)).isBlock();
+ } // protected boolean isBlockTag(AttributeSet attrSet)
+
+ /**
+ * Writes out a start tag. Synthesized elements are skipped.
+ *
+ * @param paramElem the element to output as a start tag
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void startTag(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ // NOTE: Sysnthesized elements do no call this method at all.
+ String elem_name = paramElem.getName();
+ AttributeSet attrSet = paramElem.getAttributes();
+
+ indent();
+ writeRaw("<" + elem_name);
+ writeAttributes(attrSet);
+ writeRaw(">");
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+ incrIndent();
+
+ } // protected void startTag(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Writes out the contents of a textarea.
+ *
+ * @param attrSet the attrSet of the element to output as a text area
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void textAreaContent(AttributeSet attrSet)
+ throws IOException, BadLocationException
+ {
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+ indent();
+ writeRaw("<textarea");
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ Document tempDocument =
+ (Document) attrSet.getAttribute(StyleConstants.ModelAttribute);
+
+ writeRaw(tempDocument.getText(0, tempDocument.getLength()));
+ indent();
+ writeRaw("</textarea>");
+
+ } // protected void textAreaContent(AttributeSet attrSet)
+ // throws IOException, BadLocationException
+
+ /**
+ * Writes out text, within the appropriate range if it is specified.
+ *
+ * @param paramElem the element to output as a text
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void text(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ int offset = paramElem.getStartOffset();
+ int len = paramElem.getEndOffset() - paramElem.getStartOffset();
+ String txt_value = htmlDoc.getText(offset, len);
+
+ writeContent(txt_value);
+
+ } // protected void text(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Writes out the contents of a select element.
+ *
+ * @param attrSet the attrSet of the element to output as a select box
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void selectContent(AttributeSet attrSet)
+ throws IOException
+ {
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+ indent();
+ writeRaw("<select");
+ writeAttributes(attrSet);
+ writeRaw(">");
+ incrIndent();
+ writeLineSeparator(); // extra formatting to look more like the RI.
+
+ ComboBoxModel comboBoxModel =
+ (ComboBoxModel) attrSet.getAttribute(StyleConstants.ModelAttribute);
+
+ for (int i = 0; i < comboBoxModel.getSize(); i++)
+ {
+ writeOption((Option) comboBoxModel.getElementAt(i));
+ } // for(int i = 0; i < comboBoxModel.getSize(); i++)
+
+ decrIndent();
+ indent();
+ writeRaw("</select>");
+
+ } // protected void selectContent(AttributeSet attrSet) throws IOException
+
+ /**
+ * Writes out the contents of an option element.
+ *
+ * @param option the option object to output as a select option
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void writeOption(Option option)
+ throws IOException
+ {
+ indent();
+ writeRaw("<option");
+ writeAttributes(option.getAttributes());
+ writeRaw(">");
+
+ writeContent(option.getLabel());
+
+ writeRaw("</option>");
+ writeLineSeparator(); // extra formatting to look more like the RI.
+
+ } // protected void writeOption(Option option) throws IOException
+
+ /**
+ * Writes out an end tag.
+ *
+ * @param paramElem the element to output as an end tag
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void endTag(Element paramElem)
+ throws IOException
+ {
+ String elem_name = paramElem.getName();
+
+ //writeLineSeparator(); // Extra formatting to look more like the RI.
+ decrIndent();
+ indent();
+ writeRaw("</" + elem_name + ">");
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+
+ } // protected void endTag(Element paramElem) throws IOException
+
+ /**
+ * Writes out the comment.
+ *
+ * @param paramElem the element to output as a comment
+ */
+ protected void comment(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ AttributeSet attrSet = paramElem.getAttributes();
+
+ String comment_str = (String) attrSet.getAttribute(HTML.Attribute.COMMENT);
+
+ writeRaw("<!--" + comment_str + "-->");
+
+ } // protected void comment(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Determines if element is a synthesized
+ * <code>javax.swing.text.Element</code> or not.
+ *
+ * @param element the element to test
+ *
+ * @return <code>true</code> if it is a synthesized element,
+ * <code>false</code> if it is a not synthesized element
+ */
+ protected boolean synthesizedElement(Element element)
+ {
+ AttributeSet attrSet = element.getAttributes();
+ Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
+
+ if (tagType == HTML.Tag.CONTENT || tagType == HTML.Tag.COMMENT
+ || tagType == HTML.Tag.IMPLIED)
+ return true;
+ else
+ return false;
+ } // protected boolean synthesizedElement(Element element)
+
+ /**
+ * Determines if
+ * <code>javax.swing.text.StyleConstants.NameAttribute</code>
+ * matches tag or not.
+ *
+ * @param attrSet the <code>javax.swing.text.AttributeSet</code> of
+ * element to be matched
+ * @param tag the HTML.Tag to match
+ *
+ * @return <code>true</code> if it matches,
+ * <code>false</code> if it does not match
+ */
+ protected boolean matchNameAttribute(AttributeSet attrSet, HTML.Tag tag)
+ {
+ Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
+
+ if (tagType == tag)
+ return true;
+ else
+ return false;
+ } // protected boolean matchNameAttribute(AttributeSet attrSet,
+ // HTML.Tag tag)
+
+ /**
+ * Writes out an embedded tag. The tags not already in
+ * openEmbededTagHashSet will written out.
+ *
+ * @param attrSet the <code>javax.swing.text.AttributeSet</code> of
+ * the element to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void writeEmbeddedTags(AttributeSet attrSet)
+ throws IOException
+ {
+ Enumeration attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ if (key instanceof HTML.Tag)
+ {
+ if (!openEmbededTagHashSet.contains(key))
+ {
+ writeRaw("<" + key);
+ writeAttributes((AttributeSet) value);
+ writeRaw(">");
+ openEmbededTagHashSet.add(key);
+ } // if(!openEmbededTagHashSet.contains(key))
+ } // if(key instanceof HTML.Tag)
+ } // while(attrNameEnum.hasMoreElements())
+
+ } // protected void writeEmbeddedTags(AttributeSet attrSet)
+ // throws IOException
+
+ /**
+ * Closes out an unwanted embedded tag. The tags from the
+ * openEmbededTagHashSet not found in attrSet will be written out.
+ *
+ * @param attrSet the AttributeSet of the element to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
+ throws IOException
+ {
+ Object[] tag_arr = openEmbededTagHashSet.toArray();
+
+ for (int i = 0; i < tag_arr.length; i++)
+ {
+ HTML.Tag key = (HTML.Tag) tag_arr[i];
+
+ if (!attrSet.isDefined(key))
+ {
+ writeRaw("</" + key.toString() + ">");
+ openEmbededTagHashSet.remove(key);
+ } // if(!attrSet.isDefined(key))
+ } // for(int i = 0; i < tag_arr.length; i++)
+
+ } // protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
+ // throws IOException
+
+ /**
+ * Writes out a line separator. Overwrites the parent to write out a new
+ * line.
+ *
+ * @throws IOException on any I/O exceptions.
+ */
+ protected void writeLineSeparator()
+ throws IOException
+ {
+ writeRaw(new_line_str);
+ } // protected void writeLineSeparator() throws IOException
+
+ /**
+ * Write to the writer. Character entites such as &lt;, &gt;
+ * are escaped appropriately.
+ *
+ * @param chars char array to write out
+ * @param off offset
+ * @param len length
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void output(char[] chars, int off, int len)
+ throws IOException
+ {
+ StringBuffer strBuffer = new StringBuffer();
+
+ for (int i = 0; i < chars.length; i++)
+ {
+ if (isCharHtmlEntity(chars[i]))
+ strBuffer.append(escapeCharHtmlEntity(chars[i]));
+ else
+ strBuffer.append(chars[i]);
+ } // for(int i = 0; i < chars.length; i++)
+
+ writeRaw(strBuffer.toString());
+
+ } // protected void output(char[] chars, int off, int len)
+ // throws IOException
+
+ //-------------------------------------------------------------------------
+ // private methods
+
+ /**
+ * The main method used to traverse through the elements.
+ *
+ * @param paramElem element to traverse
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void traverse(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ Element currElem = paramElem;
+
+ AttributeSet attrSet = currElem.getAttributes();
+
+ closeOutUnwantedEmbeddedTags(attrSet);
+
+ // handle the tag
+ if (synthesizedElement(paramElem))
+ {
+ if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ {
+ writeEmbeddedTags(attrSet);
+ text(currElem);
+ } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ {
+ comment(currElem);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverse(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+ } // if(child_elem_count > 0)
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ } // if(synthesizedElement(paramElem))
+ else
+ {
+ // NOTE: 20061030 - fchoong - title is treated specially here.
+ // based on RI behavior.
+ if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ {
+ boolean fg_is_end_tag = false;
+ Enumeration attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ if (key == HTML.Attribute.ENDTAG && value.equals("true"))
+ fg_is_end_tag = true;
+ } // while(attrNameEnum.hasMoreElements())
+
+ if (fg_is_end_tag)
+ writeRaw("</title>");
+ else
+ {
+ indent();
+ writeRaw("<title>");
+
+ String title_str =
+ (String) htmlDoc.getProperty(HTMLDocument.TitleProperty);
+
+ if (title_str != null)
+ writeContent(title_str);
+
+ } // else
+ } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.PRE))
+ {
+ // We pursue more stringent formating here.
+ attrSet = paramElem.getAttributes();
+
+ indent();
+ writeRaw("<pre");
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ int child_elem_count = currElem.getElementCount();
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverse(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ writeRaw("</pre>");
+
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ {
+ selectContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ {
+ textAreaContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ else
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ startTag(currElem);
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverse(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ endTag(currElem);
+
+ } // if(child_elem_count > 0)
+ else
+ {
+ emptyTag(currElem);
+ } // else
+ } // else
+ } // else
+
+ } // private void traverse(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * The method used to traverse through a html fragment.
+ *
+ * @param paramElem element to traverse
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void traverseHtmlFragment(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ // NOTE: This method is similar to traverse(Element paramElem)
+ Element currElem = paramElem;
+
+ boolean fg_is_fragment_parent_elem = false;
+ boolean fg_is_start_and_end_elem = false;
+
+ if (htmlFragmentParentHashSet.contains(paramElem))
+ fg_is_fragment_parent_elem = true;
+
+ if (paramElem == startElem)
+ fg_pass_start_elem = true;
+
+ if (paramElem == startElem && paramElem == endElem)
+ fg_is_start_and_end_elem = true;
+
+ AttributeSet attrSet = currElem.getAttributes();
+
+ closeOutUnwantedEmbeddedTags(attrSet);
+
+ if (fg_is_fragment_parent_elem || (fg_pass_start_elem
+ && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
+ {
+ // handle the tag
+ if (synthesizedElement(paramElem))
+ {
+ if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ {
+ writeEmbeddedTags(attrSet);
+
+ int content_offset = paramElem.getStartOffset();
+ int content_length = currElem.getEndOffset() - content_offset;
+
+ if (doc_offset_remaining > 0)
+ {
+ if (content_length > doc_offset_remaining)
+ {
+ int split_len = content_length;
+
+ split_len = split_len - doc_offset_remaining;
+
+ if (split_len > doc_len_remaining)
+ split_len = doc_len_remaining;
+
+ // we need to split it.
+ String txt_value = htmlDoc.getText(content_offset
+ + doc_offset_remaining, split_len);
+
+ writeContent(txt_value);
+
+ doc_offset_remaining = 0; // the offset is used up.
+ doc_len_remaining = doc_len_remaining - split_len;
+ } // if(content_length > doc_offset_remaining)
+ else
+ {
+ // doc_offset_remaining is greater than the entire
+ // length of content
+ doc_offset_remaining = doc_offset_remaining
+ - content_length;
+ } // else
+ } // if(doc_offset_remaining > 0)
+ else if (content_length <= doc_len_remaining)
+ {
+ // we can fit the entire content.
+ text(currElem);
+ doc_len_remaining = doc_len_remaining - content_length;
+ } // else if(content_length <= doc_len_remaining)
+ else
+ {
+ // we need to split it.
+ String txt_value = htmlDoc.getText(content_offset,
+ doc_len_remaining);
+
+ writeContent(txt_value);
+
+ doc_len_remaining = 0;
+ } // else
+
+ } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ {
+ comment(currElem);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverseHtmlFragment(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+ } // if(child_elem_count > 0)
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ } // if(synthesizedElement(paramElem))
+ else
+ {
+ // NOTE: 20061030 - fchoong - the isLeaf() condition seems to
+ // generate the closest behavior to the RI.
+ if (paramElem.isLeaf())
+ {
+ if (doc_offset_remaining > 0)
+ {
+ doc_offset_remaining--;
+ } // if(doc_offset_remaining > 0)
+ else if (doc_len_remaining > 0)
+ {
+ doc_len_remaining--;
+ } // else if(doc_len_remaining > 0)
+ } // if(paramElem.isLeaf())
+
+ // NOTE: 20061030 - fchoong - title is treated specially here.
+ // based on RI behavior.
+ if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ {
+ boolean fg_is_end_tag = false;
+ Enumeration attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ if (key == HTML.Attribute.ENDTAG && value.equals("true"))
+ fg_is_end_tag = true;
+ } // while(attrNameEnum.hasMoreElements())
+
+ if (fg_is_end_tag)
+ writeRaw("</title>");
+ else
+ {
+ indent();
+ writeRaw("<title>");
+
+ String title_str =
+ (String) htmlDoc.getProperty(HTMLDocument.TitleProperty);
+
+ if (title_str != null)
+ writeContent(title_str);
+
+ } // else
+ } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.PRE))
+ {
+ // We pursue more stringent formating here.
+ attrSet = paramElem.getAttributes();
+
+ indent();
+ writeRaw("<pre");
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ int child_elem_count = currElem.getElementCount();
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverseHtmlFragment(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ writeRaw("</pre>");
+
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ {
+ selectContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ {
+ textAreaContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ else
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ startTag(currElem);
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverseHtmlFragment(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ endTag(currElem);
+
+ } // if(child_elem_count > 0)
+ else
+ {
+ emptyTag(currElem);
+ } // else
+ } // else
+ } // else
+
+ } // if(fg_is_fragment_parent_elem || (fg_pass_start_elem
+ // && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
+
+ if (paramElem == endElem)
+ fg_pass_end_elem = true;
+
+ } // private void traverseHtmlFragment(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Write to the writer without any modifications.
+ *
+ * @param param_str the str to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void writeRaw(String param_str)
+ throws IOException
+ {
+ super.output(param_str.toCharArray(), 0, param_str.length());
+ } // private void writeRaw(char[] chars, int off, int len)
+ // throws IOException
+
+ /**
+ * Write to the writer, escaping HTML character entitie where neccessary.
+ *
+ * @param param_str the str to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void writeContent(String param_str)
+ throws IOException
+ {
+ char[] str_char_arr = param_str.toCharArray();
+
+ if (hasHtmlEntity(param_str))
+ output(str_char_arr, 0, str_char_arr.length);
+ else
+ super.output(str_char_arr, 0, str_char_arr.length);
+
+ } // private void writeContent(String param_str) throws IOException
+
+ /**
+ * Use this for debugging. Writes out all attributes regardless of type.
+ *
+ * @param attrSet the <code>javax.swing.text.AttributeSet</code> to
+ * write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void writeAllAttributes(AttributeSet attrSet)
+ throws IOException
+ {
+ Enumeration attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ writeRaw(" " + key + "=\"" + value + "\"");
+ writeRaw(" " + key.getClass().toString() + "=\""
+ + value.getClass().toString() + "\"");
+ } // while(attrNameEnum.hasMoreElements())
+
+ } // private void writeAllAttributes(AttributeSet attrSet)
+ // throws IOException
+
+ /**
+ * Tests if the str contains any html entities.
+ *
+ * @param param_str the str to test
+ *
+ * @return <code>true</code> if it has a html entity
+ * <code>false</code> if it does not have a html entity
+ */
+ private boolean hasHtmlEntity(String param_str)
+ {
+ boolean ret_bool = false;
+
+ for (int i = 0; i < html_entity_char_arr.length; i++)
+ {
+ if (param_str.indexOf(html_entity_char_arr[i]) != -1)
+ {
+ ret_bool = true;
+ break;
+ } // if(param_str.indexOf(html_entity_char_arr[i]) != -1)
+ } // for(int i = 0; i < html_entity_char_arr.length; i++)
+
+ return ret_bool;
+ } // private boolean hasHtmlEntity(String param_str)
+
+ /**
+ * Tests if the char is a html entities.
+ *
+ * @param param_char the char to test
+ *
+ * @return <code>true</code> if it is a html entity
+ * <code>false</code> if it is not a html entity.
+ */
+ private boolean isCharHtmlEntity(char param_char)
+ {
+ boolean ret_bool = false;
+
+ for (int i = 0; i < html_entity_char_arr.length; i++)
+ {
+ if (param_char == html_entity_char_arr[i])
+ {
+ ret_bool = true;
+ break;
+ } // if(param_char == html_entity_char_arr[i])
+ } // for(int i = 0; i < html_entity_char_arr.length; i++)
+
+ return ret_bool;
+ } // private boolean hasHtmlEntity(String param_str)
+
+ /**
+ * Escape html entities.
+ *
+ * @param param_char the char to escape
+ *
+ * @return escaped html entity. Original char is returned as a str if is
+ * is not a html entity
+ */
+ private String escapeCharHtmlEntity(char param_char)
+ {
+ String ret_str = "" + param_char;
+
+ for (int i = 0; i < html_entity_char_arr.length; i++)
+ {
+ if (param_char == html_entity_char_arr[i])
+ {
+ ret_str = html_entity_escape_str_arr[i];
+ break;
+ } // if(param_char == html_entity_char_arr[i])
+ } // for(int i = 0; i < html_entity_char_arr.length; i++)
+
+ return ret_str;
+ } // private String escapeCharHtmlEntity(char param_char)
+
+} // public class HTMLWriter extends AbstractWriter \ No newline at end of file
diff --git a/libjava/classpath/javax/swing/text/html/ImageView.java b/libjava/classpath/javax/swing/text/html/ImageView.java
index 84b0210..bf906e4 100644
--- a/libjava/classpath/javax/swing/text/html/ImageView.java
+++ b/libjava/classpath/javax/swing/text/html/ImageView.java
@@ -1,18 +1,21 @@
package javax.swing.text.html;
-import gnu.javax.swing.text.html.CombinedAttributes;
import gnu.javax.swing.text.html.ImageViewIconFactory;
+import gnu.javax.swing.text.html.css.Length;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Rectangle;
import java.awt.Shape;
+import java.awt.Toolkit;
+import java.awt.image.ImageObserver;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.Icon;
-import javax.swing.ImageIcon;
+import javax.swing.SwingUtilities;
+import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
@@ -29,15 +32,38 @@ import javax.swing.text.html.HTML.Attribute;
public class ImageView extends View
{
/**
+ * Tracks image loading state and performs the necessary layout updates.
+ */
+ class Observer
+ implements ImageObserver
+ {
+
+ public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height)
+ {
+ boolean widthChanged = false;
+ if ((flags & ImageObserver.WIDTH) != 0 && spans[X_AXIS] == null)
+ widthChanged = true;
+ boolean heightChanged = false;
+ if ((flags & ImageObserver.HEIGHT) != 0 && spans[Y_AXIS] == null)
+ heightChanged = true;
+ if (widthChanged || heightChanged)
+ safePreferenceChanged(ImageView.this, widthChanged, heightChanged);
+ boolean ret = (flags & ALLBITS) != 0;
+ return ret;
+ }
+
+ }
+
+ /**
* True if the image loads synchronuosly (on demand). By default, the image
* loads asynchronuosly.
*/
boolean loadOnDemand;
-
+
/**
* The image icon, wrapping the image,
*/
- ImageIcon imageIcon;
+ Image image;
/**
* The image state.
@@ -45,6 +71,58 @@ public class ImageView extends View
byte imageState = MediaTracker.LOADING;
/**
+ * True when the image needs re-loading, false otherwise.
+ */
+ private boolean reloadImage;
+
+ /**
+ * True when the image properties need re-loading, false otherwise.
+ */
+ private boolean reloadProperties;
+
+ /**
+ * True when the width is set as CSS/HTML attribute.
+ */
+ private boolean haveWidth;
+
+ /**
+ * True when the height is set as CSS/HTML attribute.
+ */
+ private boolean haveHeight;
+
+ /**
+ * True when the image is currently loading.
+ */
+ private boolean loading;
+
+ /**
+ * The current width of the image.
+ */
+ private int width;
+
+ /**
+ * The current height of the image.
+ */
+ private int height;
+
+ /**
+ * Our ImageObserver for tracking the loading state.
+ */
+ private ImageObserver observer;
+
+ /**
+ * The CSS width and height.
+ *
+ * Package private to avoid synthetic accessor methods.
+ */
+ Length[] spans;
+
+ /**
+ * The cached attributes.
+ */
+ private AttributeSet attributes;
+
+ /**
* Creates the image view that represents the given element.
*
* @param element the element, represented by this image view.
@@ -52,25 +130,36 @@ public class ImageView extends View
public ImageView(Element element)
{
super(element);
+ spans = new Length[2];
+ observer = new Observer();
+ reloadProperties = true;
+ reloadImage = true;
+ loadOnDemand = false;
}
/**
* Load or reload the image. This method initiates the image reloading. After
* the image is ready, the repaint event will be scheduled. The current image,
* if it already exists, will be discarded.
- *
- * @param itsTime
- * also load if the "on demand" property is set
*/
- void reloadImage(boolean itsTime)
+ private void reloadImage()
{
- URL url = getImageURL();
- if (url == null)
- imageState = (byte) MediaTracker.ERRORED;
- else if (!(loadOnDemand && !itsTime))
- imageIcon = new ImageIcon(url);
- else
- imageState = (byte) MediaTracker.LOADING;
+ loading = true;
+ reloadImage = false;
+ haveWidth = false;
+ haveHeight = false;
+ image = null;
+ width = 0;
+ height = 0;
+ try
+ {
+ loadImage();
+ updateSize();
+ }
+ finally
+ {
+ loading = false;
+ }
}
/**
@@ -146,12 +235,9 @@ public class ImageView extends View
*/
public AttributeSet getAttributes()
{
- StyleSheet styles = getStyleSheet();
- if (styles == null)
- return super.getAttributes();
- else
- return CombinedAttributes.combine(super.getAttributes(),
- styles.getViewAttributes(this));
+ if (attributes == null)
+ attributes = getStyleSheet().getViewAttributes(this);
+ return attributes;
}
/**
@@ -159,10 +245,8 @@ public class ImageView extends View
*/
public Image getImage()
{
- if (imageIcon == null)
- return null;
- else
- return imageIcon.getImage();
+ updateState();
+ return image;
}
/**
@@ -175,19 +259,22 @@ public class ImageView extends View
*/
public URL getImageURL()
{
- Object url = getAttributes().getAttribute(Attribute.SRC);
- if (url == null)
- return null;
-
- try
+ Element el = getElement();
+ String src = (String) el.getAttributes().getAttribute(Attribute.SRC);
+ URL url = null;
+ if (src != null)
{
- return new URL(url.toString());
- }
- catch (MalformedURLException e)
- {
- // The URL is malformed - no image.
- return null;
+ URL base = ((HTMLDocument) getDocument()).getBase();
+ try
+ {
+ url = new URL(base, src);
+ }
+ catch (MalformedURLException ex)
+ {
+ // Return null.
+ }
}
+ return url;
}
/**
@@ -242,9 +329,8 @@ public class ImageView extends View
if (axis == View.X_AXIS)
{
- Object w = attrs.getAttribute(Attribute.WIDTH);
- if (w != null)
- return Integer.parseInt(w.toString());
+ if (spans[axis] != null)
+ return spans[axis].getValue();
else if (image != null)
return image.getWidth(getContainer());
else
@@ -252,9 +338,8 @@ public class ImageView extends View
}
else if (axis == View.Y_AXIS)
{
- Object w = attrs.getAttribute(Attribute.HEIGHT);
- if (w != null)
- return Integer.parseInt(w.toString());
+ if (spans[axis] != null)
+ return spans[axis].getValue();
else if (image != null)
return image.getHeight(getContainer());
else
@@ -271,11 +356,8 @@ public class ImageView extends View
*/
protected StyleSheet getStyleSheet()
{
- Document d = getElement().getDocument();
- if (d instanceof HTMLDocument)
- return ((HTMLDocument) d).getStyleSheet();
- else
- return null;
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
}
/**
@@ -288,7 +370,7 @@ public class ImageView extends View
{
return getAltText();
}
-
+
/**
* Paints the image or one of the two image state icons. The image is resized
* to the shape bounds. If there is no image available, the alternative text
@@ -302,83 +384,22 @@ public class ImageView extends View
*/
public void paint(Graphics g, Shape bounds)
{
- Rectangle r = bounds.getBounds();
-
- if (imageIcon == null)
-
- {
- // Loading image on demand, rendering the loading icon so far.
- reloadImage(true);
-
- // The reloadImage sets the imageIcon, unless the URL is broken
- // or malformed.
- if (imageIcon != null)
- {
- if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE)
- {
- // Render "not ready" icon, unless the image is ready
- // immediately.
- renderIcon(g, r, getLoadingImageIcon());
- // Add the listener to repaint when the icon will be ready.
- imageIcon.setImageObserver(getContainer());
- return;
- }
- }
- else
- {
- renderIcon(g, r, getNoImageIcon());
- return;
- }
- }
-
- imageState = (byte) imageIcon.getImageLoadStatus();
-
- switch (imageState)
- {
- case MediaTracker.ABORTED:
- case MediaTracker.ERRORED:
- renderIcon(g, r, getNoImageIcon());
- break;
- case MediaTracker.LOADING:
- // If the image is not loaded completely, we still render it, as the
- // partial image may be available.
- case MediaTracker.COMPLETE:
+ updateState();
+ Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds
+ : bounds.getBounds();
+ Image image = getImage();
+ if (image != null)
{
- // Paint the scaled image.
- Image scaled = imageIcon.getImage().getScaledInstance(
- r.width,
- r.height,
- Image.SCALE_DEFAULT);
- ImageIcon painter = new ImageIcon(scaled);
- painter.paintIcon(getContainer(), g, r.x, r.y);
- }
- break;
+ g.drawImage(image, r.x, r.y, r.width, r.height, observer);
}
- }
-
- /**
- * Render "no image" icon and the alternative "no image" text. The text is
- * rendered right from the icon and is aligned to the icon bottom.
- */
- private void renderIcon(Graphics g, Rectangle bounds, Icon icon)
- {
- Shape current = g.getClip();
- try
+ else
{
- g.setClip(bounds);
+ Icon icon = getNoImageIcon();
if (icon != null)
- {
- icon.paintIcon(getContainer(), g, bounds.x, bounds.y);
- g.drawString(getAltText(), bounds.x + icon.getIconWidth(),
- bounds.y + icon.getIconHeight());
- }
- }
- finally
- {
- g.setClip(current);
+ icon.paintIcon(getContainer(), g, r.x, r.y);
}
}
-
+
/**
* Set if the image should be loaded only when needed (synchronuosly). By
* default, the image loads asynchronuosly. If the image is not yet ready, the
@@ -395,9 +416,20 @@ public class ImageView extends View
*/
protected void setPropertiesFromAttributes()
{
- // In the current implementation, nothing is cached yet, unless the image
- // itself.
- imageIcon = null;
+ AttributeSet atts = getAttributes();
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ spans[X_AXIS] = (Length) atts.getAttribute(CSS.Attribute.WIDTH);
+ if (spans[X_AXIS] != null)
+ {
+ spans[X_AXIS].setFontBases(emBase, exBase);
+ }
+ spans[Y_AXIS] = (Length) atts.getAttribute(CSS.Attribute.HEIGHT);
+ if (spans[Y_AXIS] != null)
+ {
+ spans[Y_AXIS].setFontBases(emBase, exBase);
+ }
}
/**
@@ -433,9 +465,130 @@ public class ImageView extends View
*/
public void setSize(float width, float height)
{
- if (imageIcon == null)
- reloadImage(false);
+ updateState();
+ // TODO: Implement this when we have an alt view for the alt=... attribute.
}
-
+ /**
+ * This makes sure that the image and properties have been loaded.
+ */
+ private void updateState()
+ {
+ if (reloadImage)
+ reloadImage();
+ if (reloadProperties)
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Actually loads the image.
+ */
+ private void loadImage()
+ {
+ URL src = getImageURL();
+ Image newImage = null;
+ if (src != null)
+ {
+ // Call getImage(URL) to allow the toolkit caching of that image URL.
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ newImage = tk.getImage(src);
+ tk.prepareImage(newImage, -1, -1, observer);
+ if (newImage != null && getLoadsSynchronously())
+ {
+ // Load image synchronously.
+ MediaTracker tracker = new MediaTracker(getContainer());
+ tracker.addImage(newImage, 0);
+ try
+ {
+ tracker.waitForID(0);
+ }
+ catch (InterruptedException ex)
+ {
+ Thread.interrupted();
+ }
+
+ }
+ }
+ image = newImage;
+ }
+
+ /**
+ * Updates the size parameters of the image.
+ */
+ private void updateSize()
+ {
+ int newW = 0;
+ int newH = 0;
+ Image newIm = getImage();
+ if (newIm != null)
+ {
+ AttributeSet atts = getAttributes();
+ // Fetch width.
+ Length l = spans[X_AXIS];
+ if (l != null)
+ {
+ newW = (int) l.getValue();
+ haveWidth = true;
+ }
+ else
+ {
+ newW = newIm.getWidth(observer);
+ }
+ // Fetch height.
+ l = spans[Y_AXIS];
+ if (l != null)
+ {
+ newH = (int) l.getValue();
+ haveHeight = true;
+ }
+ else
+ {
+ newW = newIm.getWidth(observer);
+ }
+ // Go and trigger loading.
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if (haveWidth || haveHeight)
+ tk.prepareImage(newIm, width, height, observer);
+ else
+ tk.prepareImage(newIm, -1, -1, observer);
+ }
+ }
+
+ /**
+ * Calls preferenceChanged from the event dispatch thread and within
+ * a read lock to protect us from threading issues.
+ *
+ * @param v the view
+ * @param width true when the width changed
+ * @param height true when the height changed
+ */
+ void safePreferenceChanged(final View v, final boolean width,
+ final boolean height)
+ {
+ if (SwingUtilities.isEventDispatchThread())
+ {
+ Document doc = getDocument();
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ preferenceChanged(v, width, height);
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ }
+ else
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ safePreferenceChanged(v, width, height);
+ }
+ });
+ }
+ }
}
diff --git a/libjava/classpath/javax/swing/text/html/InlineView.java b/libjava/classpath/javax/swing/text/html/InlineView.java
index 77ec86e..58edc73 100644
--- a/libjava/classpath/javax/swing/text/html/InlineView.java
+++ b/libjava/classpath/javax/swing/text/html/InlineView.java
@@ -38,13 +38,17 @@ exception statement from your version. */
package javax.swing.text.html;
+import java.awt.FontMetrics;
import java.awt.Shape;
+import java.text.BreakIterator;
import javax.swing.event.DocumentEvent;
import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.LabelView;
+import javax.swing.text.Segment;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
@@ -60,6 +64,23 @@ public class InlineView
{
/**
+ * The attributes used by this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The span of the longest word in this view.
+ *
+ * @see #getLongestWord()
+ */
+ private float longestWord;
+
+ /**
+ * Indicates if we may wrap or not.
+ */
+ private boolean nowrap;
+
+ /**
* Creates a new <code>InlineView</code> that renders the specified element.
*
* @param element the element for this view
@@ -115,6 +136,9 @@ public class InlineView
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
{
super.changedUpdate(e, a, f);
+ StyleSheet ss = getStyleSheet();
+ attributes = ss.getViewAttributes(this);
+ preferenceChanged(null, true, true);
setPropertiesFromAttributes();
}
@@ -126,15 +150,23 @@ public class InlineView
*/
public AttributeSet getAttributes()
{
- // FIXME: Implement this.
- return super.getAttributes();
+ if (attributes == null)
+ {
+ StyleSheet ss = getStyleSheet();
+ attributes = ss.getViewAttributes(this);
+ }
+ return attributes;
}
public int getBreakWeight(int axis, float pos, float len)
{
- // FIXME: Implement this.
- return super.getBreakWeight(axis, pos, len);
+ int weight;
+ if (nowrap)
+ weight = BadBreakWeight;
+ else
+ weight = super.getBreakWeight(axis, pos, len);
+ return weight;
}
public View breakView(int axis, int offset, float pos, float len)
@@ -143,10 +175,48 @@ public class InlineView
return super.breakView(axis, offset, pos, len);
}
+ /**
+ * Loads the character style properties from the stylesheet.
+ */
protected void setPropertiesFromAttributes()
{
- // FIXME: Implement this.
super.setPropertiesFromAttributes();
+ AttributeSet atts = getAttributes();
+ Object o = atts.getAttribute(CSS.Attribute.TEXT_DECORATION);
+
+ // Check for underline.
+ boolean b = false;
+ if (o != null && o.toString().contains("underline"))
+ b = true;
+ setUnderline(b);
+
+ // Check for line-through.
+ b = false;
+ if (o != null && o.toString().contains("line-through"))
+ b = true;
+ setStrikeThrough(b);
+
+ // Check for vertical alignment (subscript/superscript).
+ o = atts.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
+
+ // Subscript.
+ b = false;
+ if (o != null && o.toString().contains("sub"))
+ b = true;
+ setSubscript(b);
+
+ // Superscript.
+ b = false;
+ if (o != null && o.toString().contains("sup"))
+ b = true;
+ setSuperscript(b);
+
+ // Fetch nowrap setting.
+ o = atts.getAttribute(CSS.Attribute.WHITE_SPACE);
+ if (o != null && o.equals("nowrap"))
+ nowrap = true;
+ else
+ nowrap = false;
}
/**
@@ -163,4 +233,75 @@ public class InlineView
styleSheet = ((HTMLDocument) doc).getStyleSheet();
return styleSheet;
}
+
+ /**
+ * Returns the minimum span for the specified axis. This returns the
+ * width of the longest word for the X axis and the super behaviour for
+ * the Y axis. This is a slight deviation from the reference implementation.
+ * IMO this should improve rendering behaviour so that an InlineView never
+ * gets smaller than the longest word in it.
+ */
+ public float getMinimumSpan(int axis)
+ {
+ float min = super.getMinimumSpan(axis);
+ if (axis == X_AXIS)
+ min = Math.max(getLongestWord(), min);
+ return min;
+ }
+
+ /**
+ * Returns the span of the longest word in this view.
+ *
+ * @return the span of the longest word in this view
+ */
+ private float getLongestWord()
+ {
+ if (longestWord == -1)
+ longestWord = calculateLongestWord();
+ return longestWord;
+ }
+
+ /**
+ * Calculates the span of the longest word in this view.
+ *
+ * @return the span of the longest word in this view
+ */
+ private float calculateLongestWord()
+ {
+ float span = 0;
+ try
+ {
+ Document doc = getDocument();
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ Segment s = new Segment();
+ doc.getText(p0, p1 - p0, s);
+ BreakIterator iter = BreakIterator.getWordInstance();
+ iter.setText(s);
+ int wordStart = p0;
+ int wordEnd = p0;
+ int start = iter.first();
+ for (int end = iter.next(); end != BreakIterator.DONE;
+ start = end, end = iter.next())
+ {
+ if ((end - start) > (wordEnd - wordStart))
+ {
+ wordStart = start;
+ wordEnd = end;
+ }
+ }
+ if (wordEnd - wordStart > 0)
+ {
+ FontMetrics fm = getFontMetrics();
+ int offset = s.offset + wordStart - s.getBeginIndex();
+ span = fm.charsWidth(s.array, offset, wordEnd - wordStart);
+ }
+ }
+ catch (BadLocationException ex)
+ {
+ // Return 0.
+ }
+ return span;
+ }
+
}
diff --git a/libjava/classpath/javax/swing/text/html/ListView.java b/libjava/classpath/javax/swing/text/html/ListView.java
index c07d359..3e809bb 100644
--- a/libjava/classpath/javax/swing/text/html/ListView.java
+++ b/libjava/classpath/javax/swing/text/html/ListView.java
@@ -94,9 +94,6 @@ public class ListView
public void paint(Graphics g, Shape allocation)
{
super.paint(g, allocation);
- // FIXME: Why is this overridden? I think that painting would be done
- // by the superclass and the stylesheet... Maybe find out when this
- // stuff is implemented properly.
}
/**
diff --git a/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java
new file mode 100644
index 0000000..0f11450
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java
@@ -0,0 +1,213 @@
+/* MultiAttributeSet.java -- Multiplexes between a set of AttributeSets
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+
+/**
+ * An AttributeSet impl that multiplexes between a set of other AttributeSets.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+class MultiAttributeSet
+ implements AttributeSet
+{
+
+ /**
+ * The Enumeration for the multiplexed names.
+ */
+ private class MultiNameEnumeration
+ implements Enumeration
+ {
+ /**
+ * The index of the current AttributeSet.
+ */
+ private int index;
+
+ /**
+ * The names Enumeration of the current AttributeSet.
+ */
+ private Enumeration current;
+
+ /**
+ * Creates a new instance.
+ */
+ MultiNameEnumeration()
+ {
+ index = 0;
+ current = multi[0].getAttributeNames();
+ }
+
+ public boolean hasMoreElements()
+ {
+ return current.hasMoreElements() || index < multi.length - 1;
+ }
+
+ public Object nextElement()
+ {
+ if (! current.hasMoreElements())
+ {
+ if (index < multi.length - 1)
+ {
+ index++;
+ current = multi[index].getAttributeNames();
+ }
+ else
+ throw new NoSuchElementException();
+ }
+ return current.nextElement();
+ }
+
+ }
+
+ /**
+ * The AttributeSets to multiplex.
+ */
+ AttributeSet[] multi;
+
+ /**
+ * Provided for subclasses that need to initialize via {@link #init}.
+ */
+ MultiAttributeSet()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param m the AttributeSets to multiplex
+ */
+ MultiAttributeSet(AttributeSet[] m)
+ {
+ init(m);
+ }
+
+ /**
+ * Provided for subclasses to initialize the attribute set.
+ *
+ * @param m the attributes to multiplex
+ */
+ void init(AttributeSet[] m)
+ {
+ multi = m;
+ }
+
+ public boolean containsAttribute(Object name, Object value)
+ {
+ boolean ret = false;
+ for (int i = 0; i < multi.length && ret == false; i++)
+ {
+ if (multi[i].containsAttribute(name, value))
+ ret = true;
+ }
+ return ret;
+ }
+
+ public boolean containsAttributes(AttributeSet attributes)
+ {
+ boolean ret = true;
+ Enumeration e = attributes.getAttributeNames();
+ while (ret && e.hasMoreElements())
+ {
+ Object key = e.nextElement();
+ ret = attributes.getAttribute(key).equals(getAttribute(key));
+ }
+ return ret;
+ }
+
+ public AttributeSet copyAttributes()
+ {
+ SimpleAttributeSet copy = new SimpleAttributeSet();
+ for (int i = 0; i < multi.length; i++)
+ {
+ copy.addAttributes(multi[i]);
+ }
+ return copy;
+ }
+
+ public Object getAttribute(Object key)
+ {
+ Object ret = null;
+ for (int i = 0; i < multi.length && ret == null; i++)
+ {
+ ret = multi[i].getAttribute(key);
+ }
+ return ret;
+ }
+
+ public int getAttributeCount()
+ {
+ int n = 0;
+ for (int i = 0; i < multi.length; i++)
+ {
+ n += multi[i].getAttributeCount();
+ }
+ return n;
+ }
+
+ public Enumeration getAttributeNames()
+ {
+ return new MultiNameEnumeration();
+ }
+
+ public AttributeSet getResolveParent()
+ {
+ return null;
+ }
+
+ public boolean isDefined(Object attrName)
+ {
+ boolean ret = false;
+ for (int i = 0; i < multi.length && ! ret; i++)
+ ret = multi[i].isDefined(attrName);
+ return ret;
+ }
+
+ public boolean isEqual(AttributeSet attr)
+ {
+ return getAttributeCount() == attr.getAttributeCount()
+ && containsAttributes(attr);
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/MultiStyle.java b/libjava/classpath/javax/swing/text/html/MultiStyle.java
new file mode 100644
index 0000000..3937bff
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/MultiStyle.java
@@ -0,0 +1,136 @@
+/* MultiStyle.java -- Multiplexes between several Styles
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+
+import javax.swing.event.ChangeListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.Style;
+
+/**
+ * A Style implementation that is able to multiplex between several other
+ * Styles. This is used for CSS style resolving.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class MultiStyle
+ extends MultiAttributeSet
+ implements Style
+{
+
+ // FIXME: Fix the implementation to also return attributes that
+ // are added to this style, etc. However, this is not really needed
+ // now for CSS, but would be nice for correctness.
+
+ /**
+ * The name of the style.
+ */
+ private String name;
+
+ /**
+ * The attributes added to this style.
+ */
+ private SimpleAttributeSet attributes;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param n the name
+ * @param m the styles to multiplex
+ */
+ public MultiStyle(String n, AttributeSet[] m)
+ {
+ super(m);
+ name = n;
+ attributes = new SimpleAttributeSet();
+ }
+
+ /**
+ * Returns the name of the style.
+ *
+ * @return the name of the style
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ public void addChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement.
+ }
+
+ public void removeChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement.
+ }
+
+ public void addAttribute(Object name, Object value)
+ {
+ attributes.addAttribute(name, value);
+ }
+
+ public void addAttributes(AttributeSet atts)
+ {
+ attributes.addAttributes(atts);
+ }
+
+ public void removeAttribute(Object name)
+ {
+ attributes.removeAttribute(name);
+ }
+
+ public void removeAttributes(Enumeration names)
+ {
+ attributes.removeAttribute(names);
+ }
+
+ public void removeAttributes(AttributeSet atts)
+ {
+ attributes.removeAttribute(atts);
+ }
+
+ public void setResolveParent(AttributeSet parent)
+ {
+ // TODO: Implement.
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/Option.java b/libjava/classpath/javax/swing/text/html/Option.java
index 1def51b..18d5c2b 100644
--- a/libjava/classpath/javax/swing/text/html/Option.java
+++ b/libjava/classpath/javax/swing/text/html/Option.java
@@ -72,10 +72,10 @@ public class Option
*/
public Option(AttributeSet attr)
{
- attributes = attr;
+ // Protect the attribute set.
+ attributes = attr.copyAttributes();
label = null;
- selected = false;
- // FIXME: Probably initialize something using the attributes.
+ selected = attr.getAttribute(HTML.Attribute.SELECTED) != null;
}
/**
@@ -151,7 +151,9 @@ public class Option
*/
public String getValue()
{
- // FIXME: Return some attribute here if specified.
- return label;
+ String value = (String) attributes.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = label;
+ return value;
}
}
diff --git a/libjava/classpath/javax/swing/text/html/ParagraphView.java b/libjava/classpath/javax/swing/text/html/ParagraphView.java
index 2339f4e..d149627 100644
--- a/libjava/classpath/javax/swing/text/html/ParagraphView.java
+++ b/libjava/classpath/javax/swing/text/html/ParagraphView.java
@@ -38,13 +38,17 @@ exception statement from your version. */
package javax.swing.text.html;
+import gnu.javax.swing.text.html.css.Length;
+
import java.awt.Graphics;
+import java.awt.Rectangle;
import java.awt.Shape;
import javax.swing.SizeRequirements;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
import javax.swing.text.View;
/**
@@ -55,10 +59,30 @@ import javax.swing.text.View;
* @author Roman Kennke (kennke@aicas.com)
*/
public class ParagraphView
- extends javax.swing.text.ParagraphView
+ extends javax.swing.text.ParagraphView
{
/**
+ * The attributes used by this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The stylesheet's box painter.
+ */
+ private StyleSheet.BoxPainter painter;
+
+ /**
+ * The width as specified in the stylesheet or null if not specified.
+ */
+ private Length cssWidth;
+
+ /**
+ * The height as specified in the stylesheet or null if not specified.
+ */
+ private Length cssHeight;
+
+ /**
* Creates a new ParagraphView for the specified element.
*
* @param element the element
@@ -88,8 +112,11 @@ public class ParagraphView
*/
public AttributeSet getAttributes()
{
- // FIXME: Implement this multiplexing thing.
- return super.getAttributes();
+ if (attributes == null)
+ {
+ attributes = getStyleSheet().getViewAttributes(this);
+ }
+ return attributes;
}
/**
@@ -98,7 +125,44 @@ public class ParagraphView
*/
protected void setPropertiesFromAttributes()
{
- // FIXME: Implement this.
+ super.setPropertiesFromAttributes();
+
+ // Fetch CSS attributes.
+ attributes = getAttributes();
+ if (attributes != null)
+ {
+ super.setPropertiesFromAttributes();
+ Object o = attributes.getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null)
+ {
+ String align = o.toString();
+ if (align.equals("left"))
+ setJustification(StyleConstants.ALIGN_LEFT);
+ else if (align.equals("right"))
+ setJustification(StyleConstants.ALIGN_RIGHT);
+ else if (align.equals("center"))
+ setJustification(StyleConstants.ALIGN_CENTER);
+ else if (align.equals("justify"))
+ setJustification(StyleConstants.ALIGN_JUSTIFIED);
+ }
+
+ // Fetch StyleSheet's box painter.
+ painter = getStyleSheet().getBoxPainter(attributes);
+ setInsets((short) painter.getInset(TOP, this),
+ (short) painter.getInset(LEFT, this),
+ (short) painter.getInset(BOTTOM, this),
+ (short) painter.getInset(RIGHT, this));
+
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(attributes);
+ float exBase = ss.getEXBase(attributes);
+ cssWidth = (Length) attributes.getAttribute(CSS.Attribute.WIDTH);
+ if (cssWidth != null)
+ cssWidth.setFontBases(emBase, exBase);
+ cssHeight = (Length) attributes.getAttribute(CSS.Attribute.WIDTH);
+ if (cssHeight != null)
+ cssHeight.setFontBases(emBase, exBase);
+ }
}
/**
@@ -129,8 +193,52 @@ public class ParagraphView
protected SizeRequirements calculateMinorAxisRequirements(int axis,
SizeRequirements r)
{
- // FIXME: Implement the above specified behaviour.
- return super.calculateMinorAxisRequirements(axis, r);
+ r = super.calculateMinorAxisRequirements(axis, r);
+ if (! setCSSSpan(r, axis))
+ {
+ int margin = axis == X_AXIS ? getLeftInset() + getRightInset()
+ : getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ }
+ return r;
+ }
+
+ /**
+ * Sets the span on the SizeRequirements object according to the
+ * according CSS span value, when it is set.
+ *
+ * @param r the size requirements
+ * @param axis the axis
+ *
+ * @return <code>true</code> when the CSS span has been set,
+ * <code>false</code> otherwise
+ */
+ private boolean setCSSSpan(SizeRequirements r, int axis)
+ {
+ boolean ret = false;
+ if (axis == X_AXIS)
+ {
+ if (cssWidth != null && ! cssWidth.isPercentage())
+ {
+ r.minimum = (int) cssWidth.getValue();
+ r.preferred = (int) cssWidth.getValue();
+ r.maximum = (int) cssWidth.getValue();
+ ret = true;
+ }
+ }
+ else
+ {
+ if (cssHeight != null && ! cssWidth.isPercentage())
+ {
+ r.minimum = (int) cssHeight.getValue();
+ r.preferred = (int) cssHeight.getValue();
+ r.maximum = (int) cssHeight.getValue();
+ ret = true;
+ }
+ }
+ return ret;
}
/**
@@ -147,15 +255,20 @@ public class ParagraphView
}
/**
- * Paints this view. This delegates to the superclass after the coordinates
- * have been updated for tab calculations.
+ * Paints this view. This paints the box using the stylesheet's
+ * box painter for this view and delegates to the super class paint()
+ * afterwards.
*
* @param g the graphics object
* @param a the current allocation of this view
*/
public void paint(Graphics g, Shape a)
{
- // FIXME: Implement the above specified behaviour.
+ if (a != null)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ painter.paint(g, r.x, r.y, r.width, r.height, this);
+ }
super.paint(g, a);
}
diff --git a/libjava/classpath/javax/swing/text/html/ResetableModel.java b/libjava/classpath/javax/swing/text/html/ResetableModel.java
new file mode 100644
index 0000000..17f65b9
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ResetableModel.java
@@ -0,0 +1,50 @@
+/* ResetableModel.java -- Form models that can be resetted
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+/**
+ * Form models that can be resetted implement this.
+ */
+interface ResetableModel
+{
+ /**
+ * Resets the model.
+ */
+ void reset();
+}
diff --git a/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java
new file mode 100644
index 0000000..6177f9b
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java
@@ -0,0 +1,82 @@
+/* ResetablePlainDocument.java -- A plain document for use in the HTML renderer
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+/**
+ * A PlainDocument that can be resetted.
+ */
+class ResetablePlainDocument
+ extends PlainDocument
+ implements ResetableModel
+{
+ /**
+ * The initial text.
+ */
+ private String initial;
+
+ /**
+ * Stores the initial text.
+ *
+ * @param text the initial text
+ */
+ void setInitialText(String text)
+ {
+ initial = text;
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ try
+ {
+ replace(0, getLength(), initial, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't happen.
+ assert false;
+ }
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java
new file mode 100644
index 0000000..619c24e
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java
@@ -0,0 +1,71 @@
+/* ResetableToggleButtonModel.java -- A toggle button model with reset support
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JToggleButton.ToggleButtonModel;
+
+class ResetableToggleButtonModel
+ extends ToggleButtonModel
+ implements ResetableModel
+{
+
+ /**
+ * The initial state.
+ */
+ private boolean initial;
+
+ /**
+ * Sets the initial selection value.
+ *
+ * @param state the initial value
+ */
+ public void setInitial(boolean state)
+ {
+ initial = state;
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ setSelected(initial);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java
new file mode 100644
index 0000000..9997464
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java
@@ -0,0 +1,84 @@
+/* SelectComboBoxModel.java -- A special ComboBoxModel for use in HTML renderer
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.DefaultComboBoxModel;
+
+/**
+ * A special ComboBoxModel that supports storing the initial value so that
+ * the combobox can be resetted later.
+ */
+class SelectComboBoxModel
+ extends DefaultComboBoxModel
+ implements ResetableModel
+{
+
+ /**
+ * The initial selection.
+ */
+ private Option initial;
+
+ /**
+ * Sets the initial selection.
+ *
+ * @param option the initial selection
+ */
+ void setInitialSelection(Option option)
+ {
+ initial = option;
+ }
+
+ /**
+ * Returns the initial selection.
+ *
+ * @return the initial selection
+ */
+ Option getInitialSelection()
+ {
+ return initial;
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ setSelectedItem(initial);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/SelectListModel.java b/libjava/classpath/javax/swing/text/html/SelectListModel.java
new file mode 100644
index 0000000..23bfaa1
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/SelectListModel.java
@@ -0,0 +1,106 @@
+/* OptionListModel.java -- A special ListModel for use in the HTML renderer
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.BitSet;
+
+import javax.swing.DefaultListModel;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.ListSelectionModel;
+
+/**
+ * A special list model that encapsulates its selection model and supports
+ * storing of the initial value so that it can be resetted.
+ */
+class SelectListModel
+ extends DefaultListModel
+ implements ResetableModel
+{
+ /**
+ * The selection model.
+ */
+ private DefaultListSelectionModel selectionModel;
+
+ /**
+ * The initial selection.
+ */
+ private BitSet initialSelection;
+
+ /**
+ * Creates a new SelectListModel.
+ */
+ SelectListModel()
+ {
+ selectionModel = new DefaultListSelectionModel();
+ initialSelection = new BitSet();
+ }
+
+ /**
+ * Sets the initial selection.
+ *
+ * @param init the initial selection
+ */
+ void addInitialSelection(int init)
+ {
+ initialSelection.set(init);
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ selectionModel.clearSelection();
+ for (int i = initialSelection.size(); i >= 0; i--)
+ {
+ if (initialSelection.get(i))
+ selectionModel.addSelectionInterval(i, i);
+ }
+ }
+
+ /**
+ * Returns the associated selection model.
+ *
+ * @return the associated selection model
+ */
+ ListSelectionModel getSelectionModel()
+ {
+ return selectionModel;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/StyleSheet.java b/libjava/classpath/javax/swing/text/html/StyleSheet.java
index d92abde..01f19fd 100644
--- a/libjava/classpath/javax/swing/text/html/StyleSheet.java
+++ b/libjava/classpath/javax/swing/text/html/StyleSheet.java
@@ -38,28 +38,47 @@ exception statement from your version. */
package javax.swing.text.html;
-import gnu.javax.swing.text.html.CharacterAttributeTranslator;
+import gnu.javax.swing.text.html.css.BorderWidth;
+import gnu.javax.swing.text.html.css.CSSColor;
+import gnu.javax.swing.text.html.css.CSSParser;
+import gnu.javax.swing.text.html.css.CSSParserCallback;
+import gnu.javax.swing.text.html.css.FontSize;
+import gnu.javax.swing.text.html.css.FontStyle;
+import gnu.javax.swing.text.html.css.FontWeight;
+import gnu.javax.swing.text.html.css.Length;
+import gnu.javax.swing.text.html.css.Selector;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
-
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.geom.Rectangle2D;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
-
-import java.net.MalformedURLException;
import java.net.URL;
-
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Enumeration;
-import java.util.Vector;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.swing.border.Border;
+import javax.swing.event.ChangeListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.Element;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.View;
@@ -79,21 +98,168 @@ import javax.swing.text.View;
*
* The rules are stored as named styles, and other information is stored to
* translate the context of an element to a rule.
- *
+ *
* @author Lillian Angel (langel@redhat.com)
*/
public class StyleSheet extends StyleContext
{
+ /**
+ * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ class CSSStyleSheetParserCallback
+ implements CSSParserCallback
+ {
+ /**
+ * The current styles.
+ */
+ private CSSStyle[] styles;
+
+ /**
+ * The precedence of the stylesheet to be parsed.
+ */
+ private int precedence;
+
+ /**
+ * Creates a new CSS parser. This parser parses a CSS stylesheet with
+ * the specified precedence.
+ *
+ * @param prec the precedence, according to the constants defined in
+ * CSSStyle
+ */
+ CSSStyleSheetParserCallback(int prec)
+ {
+ precedence = prec;
+ }
+
+ /**
+ * Called at the beginning of a statement.
+ *
+ * @param sel the selector
+ */
+ public void startStatement(Selector[] sel)
+ {
+ styles = new CSSStyle[sel.length];
+ for (int i = 0; i < sel.length; i++)
+ styles[i] = new CSSStyle(precedence, sel[i]);
+ }
+
+ /**
+ * Called at the end of a statement.
+ */
+ public void endStatement()
+ {
+ for (int i = 0; i < styles.length; i++)
+ css.add(styles[i]);
+ styles = null;
+ }
+
+ /**
+ * Called when a declaration is parsed.
+ *
+ * @param property the property
+ * @param value the value
+ */
+ public void declaration(String property, String value)
+ {
+ CSS.Attribute cssAtt = CSS.getAttribute(property);
+ Object val = CSS.getValue(cssAtt, value);
+ for (int i = 0; i < styles.length; i++)
+ {
+ CSSStyle style = styles[i];
+ CSS.addInternal(style, cssAtt, value);
+ if (cssAtt != null)
+ style.addAttribute(cssAtt, val);
+ }
+ }
+
+ }
+
+ /**
+ * Represents a style that is defined by a CSS rule.
+ */
+ private class CSSStyle
+ extends SimpleAttributeSet
+ implements Style, Comparable
+ {
+
+ static final int PREC_UA = 0;
+ static final int PREC_NORM = 100000;
+ static final int PREC_AUTHOR_NORMAL = 200000;
+ static final int PREC_AUTHOR_IMPORTANT = 300000;
+ static final int PREC_USER_IMPORTANT = 400000;
+
+ /**
+ * The priority of this style when matching CSS selectors.
+ */
+ private int precedence;
+
+ /**
+ * The selector for this rule.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ Selector selector;
+
+ CSSStyle(int prec, Selector sel)
+ {
+ precedence = prec;
+ selector = sel;
+ }
+
+ public String getName()
+ {
+ // TODO: Implement this for correctness.
+ return null;
+ }
+
+ public void addChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement this for correctness.
+ }
+
+ public void removeChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement this for correctness.
+ }
+
+ /**
+ * Sorts the rule according to the style's precedence and the
+ * selectors specificity.
+ */
+ public int compareTo(Object o)
+ {
+ CSSStyle other = (CSSStyle) o;
+ return other.precedence + other.selector.getSpecificity()
+ - precedence - selector.getSpecificity();
+ }
+
+ }
+
/** The base URL */
URL base;
/** Base font size (int) */
int baseFontSize;
- /** The style sheets stored. */
- StyleSheet[] styleSheet;
-
+ /**
+ * The linked style sheets stored.
+ */
+ private ArrayList linked;
+
+ /**
+ * Maps element names (selectors) to AttributSet (the corresponding style
+ * information).
+ */
+ ArrayList css = new ArrayList();
+
+ /**
+ * Maps selectors to their resolved styles.
+ */
+ private HashMap resolvedStyles;
+
/**
* Constructs a StyleSheet.
*/
@@ -101,6 +267,7 @@ public class StyleSheet extends StyleContext
{
super();
baseFontSize = 4; // Default font size from CSS
+ resolvedStyles = new HashMap();
}
/**
@@ -114,10 +281,198 @@ public class StyleSheet extends StyleContext
*/
public Style getRule(HTML.Tag t, Element e)
{
- // FIXME: Not implemented.
- return null;
+ // Create list of the element and all of its parents, starting
+ // with the bottommost element.
+ ArrayList path = new ArrayList();
+ Element el;
+ AttributeSet atts;
+ for (el = e; el != null; el = el.getParentElement())
+ path.add(el);
+
+ // Create fully qualified selector.
+ StringBuilder selector = new StringBuilder();
+ int count = path.size();
+ // We append the actual element after this loop.
+ for (int i = count - 1; i > 0; i--)
+ {
+ el = (Element) path.get(i);
+ atts = el.getAttributes();
+ Object name = atts.getAttribute(StyleConstants.NameAttribute);
+ selector.append(name.toString());
+ if (atts.isDefined(HTML.Attribute.ID))
+ {
+ selector.append('#');
+ selector.append(atts.getAttribute(HTML.Attribute.ID));
+ }
+ if (atts.isDefined(HTML.Attribute.CLASS))
+ {
+ selector.append('.');
+ selector.append(atts.getAttribute(HTML.Attribute.CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS));
+ }
+ selector.append(' ');
+ }
+ selector.append(t.toString());
+ el = (Element) path.get(0);
+ atts = el.getAttributes();
+ // For leaf elements, we have to fetch the tag specific attributes.
+ if (el.isLeaf())
+ {
+ Object o = atts.getAttribute(t);
+ if (o instanceof AttributeSet)
+ atts = (AttributeSet) o;
+ else
+ atts = null;
+ }
+ if (atts != null)
+ {
+ if (atts.isDefined(HTML.Attribute.ID))
+ {
+ selector.append('#');
+ selector.append(atts.getAttribute(HTML.Attribute.ID));
+ }
+ if (atts.isDefined(HTML.Attribute.CLASS))
+ {
+ selector.append('.');
+ selector.append(atts.getAttribute(HTML.Attribute.CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS));
+ }
+ }
+ return getResolvedStyle(selector.toString(), path, t);
}
-
+
+ /**
+ * Fetches a resolved style. If there is no resolved style for the
+ * specified selector, the resolve the style using
+ * {@link #resolveStyle(String, List, HTML.Tag)}.
+ *
+ * @param selector the selector for which to resolve the style
+ * @param path the Element path, used in the resolving algorithm
+ * @param tag the tag for which to resolve
+ *
+ * @return the resolved style
+ */
+ private Style getResolvedStyle(String selector, List path, HTML.Tag tag)
+ {
+ Style style = (Style) resolvedStyles.get(selector);
+ if (style == null)
+ style = resolveStyle(selector, path, tag);
+ return style;
+ }
+
+ /**
+ * Resolves a style. This creates arrays that hold the tag names,
+ * class and id attributes and delegates the work to
+ * {@link #resolveStyle(String, String[], Map[])}.
+ *
+ * @param selector the selector
+ * @param path the Element path
+ * @param tag the tag
+ *
+ * @return the resolved style
+ */
+ private Style resolveStyle(String selector, List path, HTML.Tag tag)
+ {
+ int count = path.size();
+ String[] tags = new String[count];
+ Map[] attributes = new Map[count];
+ for (int i = 0; i < count; i++)
+ {
+ Element el = (Element) path.get(i);
+ AttributeSet atts = el.getAttributes();
+ if (i == 0 && el.isLeaf())
+ {
+ Object o = atts.getAttribute(tag);
+ if (o instanceof AttributeSet)
+ atts = (AttributeSet) o;
+ else
+ atts = null;
+ }
+ if (atts != null)
+ {
+ HTML.Tag t =
+ (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute);
+ if (t != null)
+ tags[i] = t.toString();
+ else
+ tags[i] = null;
+ attributes[i] = attributeSetToMap(atts);
+ }
+ else
+ {
+ tags[i] = null;
+ attributes[i] = null;
+ }
+ }
+ tags[0] = tag.toString();
+ return resolveStyle(selector, tags, attributes);
+ }
+
+ /**
+ * Performs style resolving.
+ *
+ * @param selector the selector
+ * @param tags the tags
+ * @param attributes the attributes of the tags
+ *
+ * @return the resolved style
+ */
+ private Style resolveStyle(String selector, String[] tags, Map[] attributes)
+ {
+ // FIXME: This style resolver is not correct. But it works good enough for
+ // the default.css.
+ int count = tags.length;
+ ArrayList styles = new ArrayList();
+ for (Iterator i = css.iterator(); i.hasNext();)
+ {
+ CSSStyle style = (CSSStyle) i.next();
+ if (style.selector.matches(tags, attributes))
+ styles.add(style);
+ }
+
+ // Add styles from linked stylesheets.
+ if (linked != null)
+ {
+ for (int i = linked.size() - 1; i >= 0; i--)
+ {
+ StyleSheet ss = (StyleSheet) linked.get(i);
+ for (int j = ss.css.size() - 1; j >= 0; j--)
+ {
+ CSSStyle style = (CSSStyle) ss.css.get(j);
+ if (style.selector.matches(tags, attributes))
+ styles.add(style);
+ }
+ }
+ }
+
+ // Sort selectors.
+ Collections.sort(styles);
+ Style[] styleArray = new Style[styles.size()];
+ styleArray = (Style[]) styles.toArray(styleArray);
+ Style resolved = new MultiStyle(selector,
+ (Style[]) styles.toArray(styleArray));
+ resolvedStyles.put(selector, resolved);
+ return resolved;
+ }
+
/**
* Gets the rule that best matches the selector. selector is a space
* separated String of element names. The attributes of the returned
@@ -128,27 +483,40 @@ public class StyleSheet extends StyleContext
*/
public Style getRule(String selector)
{
- // FIXME: Not implemented.
- return null;
+ CSSStyle best = null;
+ for (Iterator i = css.iterator(); i.hasNext();)
+ {
+ CSSStyle style = (CSSStyle) i.next();
+ if (style.compareTo(best) < 0)
+ best = style;
+ }
+ return best;
}
/**
- * Adds a set if rules to the sheet. The rules are expected to be in valid
+ * Adds a set of rules to the sheet. The rules are expected to be in valid
* CSS format. This is called as a result of parsing a <style> tag
*
* @param rule - the rule to add to the sheet
*/
public void addRule(String rule)
{
- CssParser cp = new CssParser();
+ CSSStyleSheetParserCallback cb =
+ new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
+ // FIXME: Handle ref.
+ StringReader in = new StringReader(rule);
+ CSSParser parser = new CSSParser(in, cb);
try
- {
- cp.parse(base, new StringReader(rule), false, false);
- }
- catch (IOException io)
- {
- // Do nothing here.
- }
+ {
+ parser.parse();
+ }
+ catch (IOException ex)
+ {
+ // Shouldn't happen. And if, then don't let it bork the outside code.
+ }
+ // Clean up resolved styles cache so that the new styles are recognized
+ // on next stylesheet request.
+ resolvedStyles.clear();
}
/**
@@ -176,10 +544,14 @@ public class StyleSheet extends StyleContext
* parameter.
* @throws IOException - For any IO error while reading
*/
- public void loadRules(Reader in, URL ref) throws IOException
+ public void loadRules(Reader in, URL ref)
+ throws IOException
{
- CssParser cp = new CssParser();
- cp.parse(ref, in, false, false);
+ CSSStyleSheetParserCallback cb =
+ new CSSStyleSheetParserCallback(CSSStyle.PREC_UA);
+ // FIXME: Handle ref.
+ CSSParser parser = new CSSParser(in, cb);
+ parser.parse();
}
/**
@@ -191,8 +563,7 @@ public class StyleSheet extends StyleContext
*/
public AttributeSet getViewAttributes(View v)
{
- // FIXME: Not implemented.
- return null;
+ return new ViewAttributeSet(v, this);
}
/**
@@ -215,11 +586,9 @@ public class StyleSheet extends StyleContext
*/
public void addStyleSheet(StyleSheet ss)
{
- if (styleSheet == null)
- styleSheet = new StyleSheet[] {ss};
- else
- System.arraycopy(new StyleSheet[] {ss}, 0, styleSheet,
- styleSheet.length, 1);
+ if (linked == null)
+ linked = new ArrayList();
+ linked.add(ss);
}
/**
@@ -229,31 +598,9 @@ public class StyleSheet extends StyleContext
*/
public void removeStyleSheet(StyleSheet ss)
{
- if (styleSheet.length == 1 && styleSheet[0].equals(ss))
- styleSheet = null;
- else
+ if (linked != null)
{
- for (int i = 0; i < styleSheet.length; i++)
- {
- StyleSheet curr = styleSheet[i];
- if (curr.equals(ss))
- {
- StyleSheet[] tmp = new StyleSheet[styleSheet.length - 1];
- if (i != 0 && i != (styleSheet.length - 1))
- {
- System.arraycopy(styleSheet, 0, tmp, 0, i);
- System.arraycopy(styleSheet, i + 1, tmp, i,
- styleSheet.length - i - 1);
- }
- else if (i == 0)
- System.arraycopy(styleSheet, 1, tmp, 0, styleSheet.length - 1);
- else
- System.arraycopy(styleSheet, 0, tmp, 0, styleSheet.length - 1);
-
- styleSheet = tmp;
- break;
- }
- }
+ linked.remove(ss);
}
}
@@ -264,18 +611,41 @@ public class StyleSheet extends StyleContext
*/
public StyleSheet[] getStyleSheets()
{
- return styleSheet;
+ StyleSheet[] linkedSS;
+ if (linked != null)
+ {
+ linkedSS = new StyleSheet[linked.size()];
+ linkedSS = (StyleSheet[]) linked.toArray(linkedSS);
+ }
+ else
+ {
+ linkedSS = null;
+ }
+ return linkedSS;
}
/**
* Imports a style sheet from the url. The rules are directly added to the
- * receiver.
+ * receiver. This is usually called when a <link> tag is resolved in an
+ * HTML document.
*
- * @param url - the URL to import the StyleSheet from.
+ * @param url the URL to import the StyleSheet from
*/
public void importStyleSheet(URL url)
{
- // FIXME: Not implemented
+ try
+ {
+ InputStream in = url.openStream();
+ Reader r = new BufferedReader(new InputStreamReader(in));
+ CSSStyleSheetParserCallback cb =
+ new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
+ CSSParser parser = new CSSParser(r, cb);
+ parser.parse();
+ }
+ catch (IOException ex)
+ {
+ // We can't do anything about it I guess.
+ }
}
/**
@@ -310,7 +680,9 @@ public class StyleSheet extends StyleContext
public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
String value)
{
- attr.addAttribute(key, value);
+ Object val = CSS.getValue(key, value);
+ CSS.addInternal(attr, key, value);
+ attr.addAttribute(key, val);
}
/**
@@ -340,8 +712,90 @@ public class StyleSheet extends StyleContext
*/
public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet)
{
- // FIXME: Not implemented.
- return null;
+ AttributeSet cssAttr = htmlAttrSet.copyAttributes();
+
+ // The HTML align attribute maps directly to the CSS text-align attribute.
+ Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o);
+
+ // The HTML width attribute maps directly to CSS width.
+ o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH,
+ new Length(o.toString()));
+
+ // The HTML height attribute maps directly to CSS height.
+ o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT,
+ new Length(o.toString()));
+
+ o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap");
+
+ // Map cellspacing attr of tables to CSS border-spacing.
+ o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING,
+ new Length(o.toString()));
+
+ // For table cells and headers, fetch the cellpadding value from the
+ // parent table and set it as CSS padding attribute.
+ HTML.Tag tag = (HTML.Tag)
+ htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
+ if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH)
+ && htmlAttrSet instanceof Element)
+ {
+ Element el = (Element) htmlAttrSet;
+ AttributeSet tableAttrs = el.getParentElement().getParentElement()
+ .getAttributes();
+ o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING);
+ if (o != null)
+ {
+ Length l = new Length(o.toString());
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l);
+ }
+ o = tableAttrs.getAttribute(HTML.Attribute.BORDER);
+ cssAttr = translateBorder(cssAttr, o);
+ }
+
+ // Translate border attribute.
+ o = cssAttr.getAttribute(HTML.Attribute.BORDER);
+ cssAttr = translateBorder(cssAttr, o);
+
+ // TODO: Add more mappings.
+ return cssAttr;
+ }
+
+ /**
+ * Translates a HTML border attribute to a corresponding set of CSS
+ * attributes.
+ *
+ * @param cssAttr the original set of CSS attributes to add to
+ * @param o the value of the border attribute
+ *
+ * @return the new set of CSS attributes
+ */
+ private AttributeSet translateBorder(AttributeSet cssAttr, Object o)
+ {
+ if (o != null)
+ {
+ BorderWidth l = new BorderWidth(o.toString());
+ if (l.getValue() > 0)
+ {
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE,
+ "solid");
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR,
+ new CSSColor("black"));
+ }
+ }
+ return cssAttr;
}
/**
@@ -416,10 +870,10 @@ public class StyleSheet extends StyleContext
* @param names - the attribute names
* @return the update attribute set
*/
- public AttributeSet removeAttributes(AttributeSet old, Enumeration names)
+ public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names)
{
// FIXME: Not implemented.
- return super.removeAttributes(old, names);
+ return super.removeAttributes(old, names);
}
/**
@@ -455,9 +909,95 @@ public class StyleSheet extends StyleContext
*/
public Font getFont(AttributeSet a)
{
- return super.getFont(a);
+ int realSize = getFontSize(a);
+
+ // Decrement size for subscript and superscript.
+ Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
+ if (valign != null)
+ {
+ String v = valign.toString();
+ if (v.contains("sup") || v.contains("sub"))
+ realSize -= 2;
+ }
+
+ // TODO: Convert font family.
+ String family = "SansSerif";
+
+ int style = Font.PLAIN;
+ FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT);
+ if (weight != null)
+ style |= weight.getValue();
+ FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE);
+ if (fStyle != null)
+ style |= fStyle.getValue();
+ return new Font(family, style, realSize);
}
-
+
+ /**
+ * Determines the EM base value based on the specified attributes.
+ *
+ * @param atts the attibutes
+ *
+ * @return the EM base value
+ */
+ float getEMBase(AttributeSet atts)
+ {
+ Font font = getFont(atts);
+ FontRenderContext ctx = new FontRenderContext(null, false, false);
+ Rectangle2D bounds = font.getStringBounds("M", ctx);
+ return (float) bounds.getWidth();
+ }
+
+ /**
+ * Determines the EX base value based on the specified attributes.
+ *
+ * @param atts the attibutes
+ *
+ * @return the EX base value
+ */
+ float getEXBase(AttributeSet atts)
+ {
+ Font font = getFont(atts);
+ FontRenderContext ctx = new FontRenderContext(null, false, false);
+ Rectangle2D bounds = font.getStringBounds("x", ctx);
+ return (float) bounds.getHeight();
+ }
+
+ /**
+ * Resolves the fontsize for a given set of attributes.
+ *
+ * @param atts the attributes
+ *
+ * @return the resolved font size
+ */
+ private int getFontSize(AttributeSet atts)
+ {
+ int size = 12;
+ if (atts.isDefined(CSS.Attribute.FONT_SIZE))
+ {
+ FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE);
+ if (fs.isRelative())
+ {
+ int parSize = 12;
+ AttributeSet resolver = atts.getResolveParent();
+ if (resolver != null)
+ parSize = getFontSize(resolver);
+ size = fs.getValue(parSize);
+ }
+ else
+ {
+ size = fs.getValue();
+ }
+ }
+ else
+ {
+ AttributeSet resolver = atts.getResolveParent();
+ if (resolver != null)
+ size = getFontSize(resolver);
+ }
+ return size;
+ }
+
/**
* Takes a set of attributes and turns it into a foreground
* color specification. This is used to specify things like, brigher, more hue
@@ -468,7 +1008,11 @@ public class StyleSheet extends StyleContext
*/
public Color getForeground(AttributeSet a)
{
- return super.getForeground(a);
+ CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR);
+ Color color = null;
+ if (c != null)
+ color = c.getValue();
+ return color;
}
/**
@@ -481,7 +1025,11 @@ public class StyleSheet extends StyleContext
*/
public Color getBackground(AttributeSet a)
{
- return super.getBackground(a);
+ CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR);
+ Color color = null;
+ if (c != null)
+ color = c.getValue();
+ return color;
}
/**
@@ -492,7 +1040,7 @@ public class StyleSheet extends StyleContext
*/
public BoxPainter getBoxPainter(AttributeSet a)
{
- return new BoxPainter(a);
+ return new BoxPainter(a, this);
}
/**
@@ -503,7 +1051,7 @@ public class StyleSheet extends StyleContext
*/
public ListPainter getListPainter(AttributeSet a)
{
- return new ListPainter(a);
+ return new ListPainter(a, this);
}
/**
@@ -595,7 +1143,7 @@ public class StyleSheet extends StyleContext
*/
public Color stringToColor(String colorName)
{
- return CharacterAttributeTranslator.getColor(colorName);
+ return CSSColor.convertValue(colorName);
}
/**
@@ -609,22 +1157,112 @@ public class StyleSheet extends StyleContext
*/
public static class BoxPainter extends Object implements Serializable
{
-
+
/**
- * Attribute set for painter
+ * The left inset.
*/
- AttributeSet as;
-
+ private float leftInset;
+
+ /**
+ * The right inset.
+ */
+ private float rightInset;
+
+ /**
+ * The top inset.
+ */
+ private float topInset;
+
+ /**
+ * The bottom inset.
+ */
+ private float bottomInset;
+
+ /**
+ * The border of the box.
+ */
+ private Border border;
+
+ private float leftPadding;
+ private float rightPadding;
+ private float topPadding;
+ private float bottomPadding;
+
+ /**
+ * The background color.
+ */
+ private Color background;
+
/**
* Package-private constructor.
*
* @param as - AttributeSet for painter
*/
- BoxPainter(AttributeSet as)
+ BoxPainter(AttributeSet as, StyleSheet ss)
{
- this.as = as;
+ float emBase = ss.getEMBase(as);
+ float exBase = ss.getEXBase(as);
+ // Fetch margins.
+ Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ leftInset = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ rightInset = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ topInset = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ bottomInset = l.getValue();
+ }
+
+ // Fetch padding.
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ leftPadding = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ rightPadding = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ topPadding = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ bottomPadding = l.getValue();
+ }
+
+ // Determine border.
+ border = new CSSBorder(as, ss);
+
+ // Determine background.
+ background = ss.getBackground(as);
+
}
+
/**
* Gets the inset needed on a given side to account for the margin, border
* and padding.
@@ -638,8 +1276,37 @@ public class StyleSheet extends StyleContext
*/
public float getInset(int size, View v)
{
- // FIXME: Not implemented.
- return 0;
+ float inset;
+ switch (size)
+ {
+ case View.TOP:
+ inset = topInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).top;
+ inset += topPadding;
+ break;
+ case View.BOTTOM:
+ inset = bottomInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).bottom;
+ inset += bottomPadding;
+ break;
+ case View.LEFT:
+ inset = leftInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).left;
+ inset += leftPadding;
+ break;
+ case View.RIGHT:
+ inset = rightInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).right;
+ inset += rightPadding;
+ break;
+ default:
+ inset = 0.0F;
+ }
+ return inset;
}
/**
@@ -655,7 +1322,19 @@ public class StyleSheet extends StyleContext
*/
public void paint(Graphics g, float x, float y, float w, float h, View v)
{
- // FIXME: Not implemented.
+ int inX = (int) (x + leftInset);
+ int inY = (int) (y + topInset);
+ int inW = (int) (w - leftInset - rightInset);
+ int inH = (int) (h - topInset - bottomInset);
+ if (background != null)
+ {
+ g.setColor(background);
+ g.fillRect(inX, inY, inW, inH);
+ }
+ if (border != null)
+ {
+ border.paintBorder(null, g, inX, inY, inW, inH);
+ }
}
}
@@ -666,24 +1345,41 @@ public class StyleSheet extends StyleContext
*
* @author Lillian Angel (langel@redhat.com)
*/
- public static class ListPainter extends Object implements Serializable
+ public static class ListPainter implements Serializable
{
-
+
/**
* Attribute set for painter
*/
- AttributeSet as;
-
+ private AttributeSet attributes;
+
+ /**
+ * The associated style sheet.
+ */
+ private StyleSheet styleSheet;
+
+ /**
+ * The bullet type.
+ */
+ private String type;
+
/**
* Package-private constructor.
*
* @param as - AttributeSet for painter
*/
- ListPainter(AttributeSet as)
+ ListPainter(AttributeSet as, StyleSheet ss)
{
- this.as = as;
+ attributes = as;
+ styleSheet = ss;
+ type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
}
-
+
+ /**
+ * Cached rectangle re-used in the paint method below.
+ */
+ private final Rectangle tmpRect = new Rectangle();
+
/**
* Paints the CSS list decoration according to the attributes given.
*
@@ -698,210 +1394,66 @@ public class StyleSheet extends StyleContext
public void paint(Graphics g, float x, float y, float w, float h, View v,
int item)
{
- // FIXME: Not implemented.
- }
- }
-
- /**
- * The parser callback for the CSSParser.
- */
- class CssParser implements CSSParser.CSSParserCallback
- {
- /**
- * A vector of all the selectors.
- * Each element is an array of all the selector tokens
- * in a single rule.
- */
- Vector selectors;
-
- /** A vector of all the selector tokens in a rule. */
- Vector selectorTokens;
-
- /** Name of the current property. */
- String propertyName;
-
- /** The set of CSS declarations */
- MutableAttributeSet declaration;
-
- /**
- * True if parsing a declaration, that is the Reader will not
- * contain a selector.
- */
- boolean parsingDeclaration;
-
- /** True if the attributes are coming from a linked/imported style. */
- boolean isLink;
-
- /** The base URL */
- URL base;
-
- /** The parser */
- CSSParser parser;
-
- /**
- * Constructor
- */
- CssParser()
- {
- selectors = new Vector();
- selectorTokens = new Vector();
- parser = new CSSParser();
- base = StyleSheet.this.base;
- declaration = new SimpleAttributeSet();
- }
-
- /**
- * Parses the passed in CSS declaration into an AttributeSet.
- *
- * @param s - the declaration
- * @return the set of attributes containing the property and value.
- */
- public AttributeSet parseDeclaration(String s)
- {
- try
- {
- return parseDeclaration(new StringReader(s));
- }
- catch (IOException e)
- {
- // Do nothing here.
- }
- return null;
- }
-
- /**
- * Parses the passed in CSS declaration into an AttributeSet.
- *
- * @param r - the reader
- * @return the attribute set
- * @throws IOException from the reader
- */
- public AttributeSet parseDeclaration(Reader r) throws IOException
- {
- parse(base, r, true, false);
- return declaration;
- }
-
- /**
- * Parse the given CSS stream
- *
- * @param base - the url
- * @param r - the reader
- * @param parseDec - True if parsing a declaration
- * @param isLink - True if parsing a link
- */
- public void parse(URL base, Reader r, boolean parseDec, boolean isLink) throws IOException
- {
- parsingDeclaration = parseDec;
- this.isLink = isLink;
- this.base = base;
-
- // flush out all storage
- propertyName = null;
- selectors.clear();
- selectorTokens.clear();
- declaration.removeAttributes(declaration);
-
- parser.parse(r, this, parseDec);
- }
-
- /**
- * Invoked when a valid @import is encountered,
- * will call importStyleSheet if a MalformedURLException
- * is not thrown in creating the URL.
- *
- * @param s - the string after @import
- */
- public void handleImport(String s)
- {
- if (s != null)
+ // FIXME: This is a very simplistic list rendering. We still need
+ // to implement different bullet types (see type field) and custom
+ // bullets via images.
+ View itemView = v.getView(item);
+ AttributeSet viewAtts = itemView.getAttributes();
+ Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute);
+ // Only paint something here when the child view is an LI tag
+ // and the calling view is some of the list tags then).
+ if (tag != null && tag == HTML.Tag.LI)
{
- try
+ g.setColor(Color.BLACK);
+ int centerX = (int) (x - 12);
+ int centerY = -1;
+ // For paragraphs (almost all cases) center bullet vertically
+ // in the middle of the first line.
+ tmpRect.setBounds((int) x, (int) y, (int) w, (int) h);
+ if (itemView.getViewCount() > 0)
{
- if (s.startsWith("url(") && s.endsWith(")"))
- s = s.substring(4, s.length() - 1);
- if (s.indexOf("\"") >= 0)
- s = s.replaceAll("\"","");
-
- URL url = new URL(s);
- if (url == null && base != null)
- url = new URL(base, s);
-
- importStyleSheet(url);
+ View v1 = itemView.getView(0);
+ if (v1 instanceof ParagraphView && v1.getViewCount() > 0)
+ {
+ Shape a1 = itemView.getChildAllocation(0, tmpRect);
+ Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1
+ : a1.getBounds();
+ ParagraphView par = (ParagraphView) v1;
+ Shape a = par.getChildAllocation(0, r1);
+ if (a != null)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a
+ : a.getBounds();
+ centerY = (int) (r.height / 2 + r.y);
+ }
+ }
}
- catch (MalformedURLException e)
+ if (centerY == -1)
{
- // Do nothing here.
+ centerY =(int) (h / 2 + y);
}
+ g.fillOval(centerX - 3, centerY - 3, 6, 6);
}
}
+ }
- /**
- * A selector has been encountered.
- *
- * @param s - a selector (e.g. P or UL or even P,)
- */
- public void handleSelector(String s)
- {
- if (s.endsWith(","))
- s = s.substring(0, s.length() - 1);
-
- selectorTokens.addElement(s);
- addSelector();
- }
-
- /**
- * Invoked when the start of a rule is encountered.
- */
- public void startRule()
- {
- addSelector();
- }
-
- /**
- * Invoked when a property name is encountered.
- *
- * @param s - the property
- */
- public void handleProperty(String s)
- {
- propertyName = s;
- }
-
- /**
- * Invoked when a property value is encountered.
+ /**
+ * Converts an AttributeSet to a Map. This is used for CSS resolving.
*
- * @param s - the value
- */
- public void handleValue(String s)
- {
- // call addCSSAttribute
- // FIXME: Not implemented
- }
-
- /**
- * Invoked when the end of a rule is encountered.
- */
- public void endRule()
- {
- // FIXME: Not implemented
- // add rules
- propertyName = null;
- }
-
- /**
- * Adds the selector to the vector.
- */
- private void addSelector()
- {
- int length = selectorTokens.size();
- if (length > 0)
- {
- Object[] sel = new Object[length];
- System.arraycopy(selectorTokens.toArray(), 0, sel, 0, length);
- selectors.add(sel);
- selectorTokens.clear();
- }
- }
+ * @param atts the attributes to convert
+ *
+ * @return the converted map
+ */
+ private Map attributeSetToMap(AttributeSet atts)
+ {
+ HashMap map = new HashMap();
+ Enumeration keys = atts.getAttributeNames();
+ while (keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = atts.getAttribute(key);
+ map.put(key.toString(), value.toString());
+ }
+ return map;
}
}
diff --git a/libjava/classpath/javax/swing/text/html/TableView.java b/libjava/classpath/javax/swing/text/html/TableView.java
index c2edc8c..f87d7b3 100644
--- a/libjava/classpath/javax/swing/text/html/TableView.java
+++ b/libjava/classpath/javax/swing/text/html/TableView.java
@@ -38,49 +38,318 @@ exception statement from your version. */
package javax.swing.text.html;
-import javax.swing.text.Document;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import gnu.javax.swing.text.html.css.Length;
+
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.AttributeSet;
import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
/**
- * A conrete implementation of TableView that renders HTML tables.
- *
- * @author Roman Kennke (kennke@aicas.com)
+ * A view implementation that renders HTML tables.
+ *
+ * This is basically a vertical BoxView that contains the rows of the table
+ * and the rows are horizontal BoxViews that contain the actual columns.
*/
class TableView
- extends javax.swing.text.TableView
+ extends BlockView
+ implements ViewFactory
{
+
/**
* Represents a single table row.
*/
- public class RowView extends TableRow
+ class RowView
+ extends BlockView
{
/**
- * Creates a new instance of the <code>RowView</code>.
+ * Has true at column positions where an above row's cell overlaps into
+ * this row.
+ */
+ boolean[] overlap;
+
+ /**
+ * Stores the row index of this row.
+ */
+ int rowIndex;
+
+ /**
+ * Creates a new RowView.
*
- * @param el the element for which to create a row view
+ * @param el the element for the row view
+ */
+ RowView(Element el)
+ {
+ super(el, X_AXIS);
+ }
+
+ public void replace(int offset, int len, View[] views)
+ {
+ gridValid = false;
+ super.replace(offset, len, views);
+ }
+
+ /**
+ * Overridden to make rows not resizable along the Y axis.
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float span;
+ if (axis == Y_AXIS)
+ span = super.getPreferredSpan(axis);
+ else
+ span = Integer.MAX_VALUE;
+ return span;
+ }
+
+ public float getMinimumSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = totalColumnRequirements.minimum;
+ else
+ span = super.getMinimumSpan(axis);
+ return span;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = totalColumnRequirements.preferred;
+ else
+ span = super.getPreferredSpan(axis);
+ return span;
+ }
+
+ /**
+ * Calculates the overall size requirements for the row along the
+ * major axis. This will be the sum of the column requirements.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ if (r == null)
+ r = new SizeRequirements();
+ int adjust = (columnRequirements.length + 1) * cellSpacing;
+ r.minimum = totalColumnRequirements.minimum + adjust;
+ r.preferred = totalColumnRequirements.preferred + adjust;
+ r.maximum = totalColumnRequirements.maximum + adjust;
+ r.alignment = 0.0F;
+ return r;
+ }
+
+ /**
+ * Lays out the columns in this row.
*/
- public RowView(Element el)
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
+ int spans[])
{
- super(el);
+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+
+ // Adjust columns that have rowSpan > 1.
+ int numCols = getViewCount();
+ for (int i = 0; i < numCols; i++)
+ {
+ View v = getView(i);
+ if (v instanceof CellView)
+ {
+ CellView cell = (CellView) v;
+ if (cell.rowSpan > 1)
+ {
+ for (int r = 1; r < cell.rowSpan; r++)
+ {
+ spans[i] += TableView.this.getSpan(axis, rowIndex + r);
+ spans[i] += cellSpacing;
+ }
+ }
+ }
+ }
}
-
+
+ /**
+ * Lays out the columns in this row.
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int spans[])
+ {
+ updateGrid();
+ int numCols = offsets.length;
+ int realColumn = 0;
+ int colCount = getViewCount();
+ for (int i = 0; i < numColumns;)
+ {
+ if (! overlap[i] && realColumn < colCount)
+ {
+ View v = getView(realColumn);
+ if (v instanceof CellView)
+ {
+ CellView cv = (CellView) v;
+ offsets[realColumn] = columnOffsets[i];
+ spans[realColumn] = 0;
+ for (int j = 0; j < cv.colSpan; j++, i++)
+ {
+ spans[realColumn] += columnSpans[i];
+ if (j < cv.colSpan - 1)
+ spans[realColumn] += cellSpacing;
+ }
+ }
+ realColumn++;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ }
+ }
+
/**
- * Get the associated style sheet from the document.
- *
- * @return the associated style sheet.
+ * A view that renders HTML table cells (TD and TH tags).
*/
- protected StyleSheet getStyleSheet()
+ class CellView
+ extends BlockView
+ {
+
+ /**
+ * The number of columns that this view spans.
+ */
+ int colSpan;
+
+ /**
+ * The number of rows that this cell spans.
+ */
+ int rowSpan;
+
+ /**
+ * Creates a new CellView for the specified element.
+ *
+ * @param el the element for which to create the colspan
+ */
+ CellView(Element el)
{
- Document d = getElement().getDocument();
- if (d instanceof HTMLDocument)
- return ((HTMLDocument) d).getStyleSheet();
- else
- return null;
- }
+ super(el, Y_AXIS);
+ }
+
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ r.maximum = Integer.MAX_VALUE;
+ return r;
+ }
+
+ /**
+ * Overridden to fetch the columnSpan attibute.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ super.setPropertiesFromAttributes();
+ colSpan = 1;
+ AttributeSet atts = getAttributes();
+ Object o = atts.getAttribute(HTML.Attribute.COLSPAN);
+ if (o != null)
+ {
+ try
+ {
+ colSpan = Integer.parseInt(o.toString());
+ }
+ catch (NumberFormatException ex)
+ {
+ // Couldn't parse the colspan, assume 1.
+ colSpan = 1;
+ }
+ }
+ rowSpan = 1;
+ o = atts.getAttribute(HTML.Attribute.ROWSPAN);
+ if (o != null)
+ {
+ try
+ {
+ rowSpan = Integer.parseInt(o.toString());
+ }
+ catch (NumberFormatException ex)
+ {
+ // Couldn't parse the colspan, assume 1.
+ rowSpan = 1;
+ }
+ }
+ }
}
+
+ /**
+ * The attributes of this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The column requirements.
+ *
+ * Package private to avoid accessor methods.
+ */
+ SizeRequirements[] columnRequirements;
+
+ /**
+ * The overall requirements across all columns.
+ *
+ * Package private to avoid accessor methods.
+ */
+ SizeRequirements totalColumnRequirements;
+
+ /**
+ * The column layout, offsets.
+ *
+ * Package private to avoid accessor methods.
+ */
+ int[] columnOffsets;
+
+ /**
+ * The column layout, spans.
+ *
+ * Package private to avoid accessor methods.
+ */
+ int[] columnSpans;
+
+ /**
+ * The widths of the columns that have been explicitly specified.
+ */
+ Length[] columnWidths;
+
+ /**
+ * The total number of columns.
+ */
+ int numColumns;
+
+ /**
+ * The table width.
+ */
+ private Length width;
+
+ /**
+ * Indicates if the grid setup is ok.
+ */
+ boolean gridValid = false;
+
+ /**
+ * Additional space that is added _between_ table cells.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ int cellSpacing;
+
+ /**
+ * A cached Rectangle object for reuse in paint().
+ */
+ private Rectangle tmpRect;
+
/**
* Creates a new HTML table view for the specified element.
*
@@ -88,50 +357,619 @@ class TableView
*/
public TableView(Element el)
{
- super(el);
+ super(el, Y_AXIS);
+ totalColumnRequirements = new SizeRequirements();
+ tmpRect = new Rectangle();
}
-
+
/**
- * Get the associated style sheet from the document.
- *
- * @return the associated style sheet.
+ * Implementation of the ViewFactory interface for creating the
+ * child views correctly.
*/
- protected StyleSheet getStyleSheet()
+ public View create(Element elem)
{
- Document d = getElement().getDocument();
- if (d instanceof HTMLDocument)
- return ((HTMLDocument) d).getStyleSheet();
+ View view = null;
+ AttributeSet atts = elem.getAttributes();
+ Object name = atts.getAttribute(StyleConstants.NameAttribute);
+ AttributeSet pAtts = elem.getParentElement().getAttributes();
+ Object pName = pAtts.getAttribute(StyleConstants.NameAttribute);
+
+ if (name == HTML.Tag.TR && pName == HTML.Tag.TABLE)
+ view = new RowView(elem);
+ else if ((name == HTML.Tag.TD || name == HTML.Tag.TH)
+ && pName == HTML.Tag.TR)
+ view = new CellView(elem);
+ else if (name == HTML.Tag.CAPTION)
+ view = new ParagraphView(elem);
else
- return null;
- }
-
+ {
+ // If we haven't mapped the element, then fall back to the standard
+ // view factory.
+ View parent = getParent();
+ if (parent != null)
+ {
+ ViewFactory vf = parent.getViewFactory();
+ if (vf != null)
+ view = vf.create(elem);
+ }
+ }
+ return view;
+ }
+
+ /**
+ * Returns this object as view factory so that we get our TR, TD, TH
+ * and CAPTION subelements created correctly.
+ */
+ public ViewFactory getViewFactory()
+ {
+ return this;
+ }
+
+ /**
+ * Returns the attributes of this view. This is overridden to provide
+ * the attributes merged with the CSS stuff.
+ */
+ public AttributeSet getAttributes()
+ {
+ if (attributes == null)
+ attributes = getStyleSheet().getViewAttributes(this);
+ return attributes;
+ }
+
+ /**
+ * Returns the stylesheet associated with this view.
+ *
+ * @return the stylesheet associated with this view
+ */
+ protected StyleSheet getStyleSheet()
+ {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Overridden to calculate the size requirements according to the
+ * columns distribution.
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ updateGrid();
+ calculateColumnRequirements();
+
+ // Calculate the horizontal requirements according to the superclass.
+ // This will return the maximum of the row's widths.
+ r = super.calculateMinorAxisRequirements(axis, r);
+
+ // Try to set the CSS width if it fits.
+ if (width != null)
+ {
+ int w = (int) width.getValue();
+ if (r.minimum < w)
+ r.minimum = w;
+ }
+
+ // Adjust requirements when we have cell spacing.
+ int adjust = (columnRequirements.length + 1) * cellSpacing;
+ r.minimum += adjust;
+ r.preferred += adjust;
+
+ // Apply the alignment.
+ AttributeSet atts = getAttributes();
+ Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN);
+ r.alignment = 0.0F;
+ if (o != null)
+ {
+ String al = o.toString();
+ if (al.equals("left"))
+ r.alignment = 0.0F;
+ else if (al.equals("center"))
+ r.alignment = 0.5F;
+ else if (al.equals("right"))
+ r.alignment = 1.0F;
+ }
+
+ // Make it not resize in the horizontal direction.
+ r.maximum = r.preferred;
+ return r;
+ }
+
+ /**
+ * Overridden to perform the table layout before calling the super
+ * implementation.
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ updateGrid();
+
+ // Mark all rows as invalid along their minor axis to force correct
+ // layout of multi-row cells.
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View row = getView(i);
+ if (row instanceof RowView)
+ ((RowView) row).layoutChanged(axis);
+ }
+
+ layoutColumns(targetSpan);
+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+ }
+
+ /**
+ * Calculates the size requirements for the columns.
+ */
+ private void calculateColumnRequirements()
+ {
+ int numRows = getViewCount();
+ totalColumnRequirements.minimum = 0;
+ totalColumnRequirements.preferred = 0;
+ totalColumnRequirements.maximum = 0;
+
+ // In this first pass we find out a suitable total width to fit in
+ // all columns of all rows.
+ for (int r = 0; r < numRows; r++)
+ {
+ View rowView = getView(r);
+ int numCols;
+ if (rowView instanceof RowView)
+ numCols = ((RowView) rowView).getViewCount();
+ else
+ numCols = 0;
+
+ // We collect the normal (non-relative) column requirements in the
+ // total variable and the relative requirements in the relTotal
+ // variable. In the end we create the maximum of both to get the
+ // real requirements.
+ SizeRequirements total = new SizeRequirements();
+ SizeRequirements relTotal = new SizeRequirements();
+ float totalPercent = 0.F;
+ int realCol = 0;
+ for (int c = 0; c < numCols; c++)
+ {
+ View v = rowView.getView(c);
+ if (v instanceof CellView)
+ {
+ CellView cellView = (CellView) v;
+ int colSpan = cellView.colSpan;
+ if (colSpan > 1)
+ {
+ int cellMin = (int) cellView.getMinimumSpan(X_AXIS);
+ int cellPref = (int) cellView.getPreferredSpan(X_AXIS);
+ int cellMax = (int) cellView.getMaximumSpan(X_AXIS);
+ int currentMin = 0;
+ int currentPref = 0;
+ long currentMax = 0;
+ for (int i = 0; i < colSpan; i++)
+ {
+ SizeRequirements req = columnRequirements[realCol];
+ currentMin += req.minimum;
+ currentPref += req.preferred;
+ currentMax += req.maximum;
+ }
+ int deltaMin = cellMin - currentMin;
+ int deltaPref = cellPref - currentPref;
+ int deltaMax = (int) (cellMax - currentMax);
+ // Distribute delta.
+ for (int i = 0; i < colSpan; i++)
+ {
+ SizeRequirements req = columnRequirements[realCol];
+ if (deltaMin > 0)
+ req.minimum += deltaMin / colSpan;
+ if (deltaPref > 0)
+ req.preferred += deltaPref / colSpan;
+ if (deltaMax > 0)
+ req.maximum += deltaMax / colSpan;
+ if (columnWidths[realCol] == null
+ || ! columnWidths[realCol].isPercentage())
+ {
+ total.minimum += req.minimum;
+ total.preferred += req.preferred;
+ total.maximum += req.maximum;
+ }
+ else
+ {
+ relTotal.minimum =
+ Math.max(relTotal.minimum,
+ (int) (req.minimum
+ * columnWidths[realCol].getValue()));
+ relTotal.preferred =
+ Math.max(relTotal.preferred,
+ (int) (req.preferred
+ * columnWidths[realCol].getValue()));
+ relTotal.maximum =
+ Math.max(relTotal.maximum,
+ (int) (req.maximum
+ * columnWidths[realCol].getValue()));
+ totalPercent += columnWidths[realCol].getValue();
+ }
+ }
+ realCol += colSpan;
+ }
+ else
+ {
+ // Shortcut for colSpan == 1.
+ SizeRequirements req = columnRequirements[realCol];
+ req.minimum = Math.max(req.minimum,
+ (int) cellView.getMinimumSpan(X_AXIS));
+ req.preferred = Math.max(req.preferred,
+ (int) cellView.getPreferredSpan(X_AXIS));
+ req.maximum = Math.max(req.maximum,
+ (int) cellView.getMaximumSpan(X_AXIS));
+ if (columnWidths[realCol] == null
+ || ! columnWidths[realCol].isPercentage())
+ {
+ total.minimum += columnRequirements[realCol].minimum;
+ total.preferred +=
+ columnRequirements[realCol].preferred;
+ total.maximum += columnRequirements[realCol].maximum;
+ }
+ else
+ {
+ relTotal.minimum =
+ Math.max(relTotal.minimum,
+ (int) (req.minimum
+ / columnWidths[c].getValue()));
+ relTotal.preferred =
+ Math.max(relTotal.preferred,
+ (int) (req.preferred
+ / columnWidths[c].getValue()));
+ relTotal.maximum =
+ Math.max(relTotal.maximum,
+ (int) (req.maximum
+ / columnWidths[c].getValue()));
+ totalPercent += columnWidths[c].getValue();
+ }
+ realCol += 1;
+ }
+ }
+ }
+
+ // Update the total requirements as follows:
+ // 1. Multiply the absolute requirements with 1 - totalPercent. This
+ // gives the total requirements based on the wishes of the absolute
+ // cells.
+ // 2. Take the maximum of this value and the total relative
+ // requirements. Now we should have enough space for whatever cell
+ // in this column.
+ // 3. Take the maximum of this value and the previous maximum value.
+ total.minimum *= 1.F / (1.F - totalPercent);
+ total.preferred *= 1.F / (1.F - totalPercent);
+ total.maximum *= 1.F / (1.F - totalPercent);
+
+ int rowTotalMin = Math.max(total.minimum, relTotal.minimum);
+ int rowTotalPref = Math.max(total.preferred, relTotal.preferred);
+ int rowTotalMax = Math.max(total.maximum, relTotal.maximum);
+ totalColumnRequirements.minimum =
+ Math.max(totalColumnRequirements.minimum, rowTotalMin);
+ totalColumnRequirements.preferred =
+ Math.max(totalColumnRequirements.preferred, rowTotalPref);
+ totalColumnRequirements.maximum =
+ Math.max(totalColumnRequirements.maximum, rowTotalMax);
+ }
+
+ // Now we know what we want and can fix up the actual relative
+ // column requirements.
+ int numCols = columnRequirements.length;
+ for (int i = 0; i < numCols; i++)
+ {
+ if (columnWidths[i] != null)
+ {
+ columnRequirements[i].minimum = (int)
+ columnWidths[i].getValue(totalColumnRequirements.minimum);
+ columnRequirements[i].preferred = (int)
+ columnWidths[i].getValue(totalColumnRequirements.preferred);
+ columnRequirements[i].maximum = (int)
+ columnWidths[i].getValue(totalColumnRequirements.maximum);
+ }
+ }
+ }
+
/**
- * Creates a view for a table row.
- *
- * @param el the element that represents the table row
- * @return a view for rendering the table row
- * (and instance of {@link RowView}).
+ * Lays out the columns.
+ *
+ * @param targetSpan the target span into which the table is laid out
*/
- protected TableRow createTableRow(Element el)
+ private void layoutColumns(int targetSpan)
{
- return new RowView(el);
- }
-
+ // Set the spans to the preferred sizes. Determine the space
+ // that we have to adjust the sizes afterwards.
+ long sumPref = 0;
+ int n = columnRequirements.length;
+ for (int i = 0; i < n; i++)
+ {
+ SizeRequirements col = columnRequirements[i];
+ if (columnWidths[i] != null)
+ columnSpans[i] = (int) columnWidths[i].getValue(targetSpan);
+ else
+ columnSpans[i] = col.preferred;
+ sumPref += columnSpans[i];
+ }
+
+ // Try to adjust the spans so that we fill the targetSpan.
+ // For adjustments we have to use the targetSpan minus the cumulated
+ // cell spacings.
+ long diff = targetSpan - (n + 1) * cellSpacing - sumPref;
+ float factor = 0.0F;
+ int[] diffs = null;
+ if (diff != 0)
+ {
+ long total = 0;
+ diffs = new int[n];
+ for (int i = 0; i < n; i++)
+ {
+ // Only adjust the width if we haven't set a column width here.
+ if (columnWidths[i] == null)
+ {
+ SizeRequirements col = columnRequirements[i];
+ int span;
+ if (diff < 0)
+ {
+ span = col.minimum;
+ diffs[i] = columnSpans[i] - span;
+ }
+ else
+ {
+ span = col.maximum;
+ diffs[i] = span - columnSpans[i];
+ }
+ total += span;
+ }
+ else
+ total += columnSpans[i];
+ }
+
+ float maxAdjust = Math.abs(total - sumPref);
+ factor = diff / maxAdjust;
+ factor = Math.min(factor, 1.0F);
+ factor = Math.max(factor, -1.0F);
+ }
+
+ // Actually perform adjustments.
+ int totalOffs = cellSpacing;
+ for (int i = 0; i < n; i++)
+ {
+ columnOffsets[i] = totalOffs;
+ if (diff != 0)
+ {
+ float adjust = factor * diffs[i];
+ columnSpans[i] += Math.round(adjust);
+ }
+ // Avoid overflow here.
+ totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i]
+ + (long) cellSpacing, Integer.MAX_VALUE);
+ }
+ }
+
/**
- * Loads the children of the Table. This completely bypasses the ViewFactory
- * and creates instances of TableRow instead.
+ * Updates the arrays that contain the row and column data in response
+ * to a change to the table structure.
*
- * @param vf ignored
+ * Package private to avoid accessor methods.
+ */
+ void updateGrid()
+ {
+ if (! gridValid)
+ {
+ AttributeSet atts = getAttributes();
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ int maxColumns = 0;
+ int numRows = getViewCount();
+ for (int r = 0; r < numRows; r++)
+ {
+ View rowView = getView(r);
+ int numCols = 0;
+ if (rowView instanceof RowView)
+ {
+ int numCells = ((RowView) rowView).getViewCount();
+ for (int i = 0; i < numCells; i++)
+ {
+ View v = rowView.getView(i);
+ if (v instanceof CellView)
+ numCols += ((CellView) v).colSpan;
+ }
+ }
+ maxColumns = Math.max(numCols, maxColumns);
+ }
+ numColumns = maxColumns;
+ columnWidths = new Length[maxColumns];
+ int[] rowSpans = new int[maxColumns];
+ for (int r = 0; r < numRows; r++)
+ {
+ View view = getView(r);
+ if (view instanceof RowView)
+ {
+ RowView rowView = (RowView) view;
+ rowView.rowIndex = r;
+ rowView.overlap = new boolean[maxColumns];
+ int colIndex = 0;
+ int colCount = rowView.getViewCount();
+ for (int c = 0; c < maxColumns;)
+ {
+ if (rowSpans[c] > 0)
+ {
+ rowSpans[c]--;
+ rowView.overlap[c] = true;
+ c++;
+ }
+ else if (colIndex < colCount)
+ {
+ View v = rowView.getView(colIndex);
+ colIndex++;
+ if (v instanceof CellView)
+ {
+ CellView cv = (CellView) v;
+ Object o =
+ cv.getAttributes().getAttribute(CSS.Attribute.WIDTH);
+ if (o != null && columnWidths[c] == null
+ && o instanceof Length)
+ {
+ columnWidths[c]= (Length) o;
+ columnWidths[c].setFontBases(emBase, exBase);
+ }
+ int rs = cv.rowSpan - 1;
+ for (int col = cv.colSpan - 1; col >= 0; col--)
+ {
+ rowSpans[c] = rs;
+ c++;
+ }
+ }
+ }
+ else
+ {
+ c++;
+ }
+ }
+ }
+ }
+ columnRequirements = new SizeRequirements[maxColumns];
+ for (int i = 0; i < maxColumns; i++)
+ columnRequirements[i] = new SizeRequirements();
+ columnOffsets = new int[maxColumns];
+ columnSpans = new int[maxColumns];
+
+ gridValid = true;
+ }
+ }
+
+ /**
+ * Overridden to restrict the table width to the preferred size.
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = super.getPreferredSpan(axis);
+ else
+ span = super.getMaximumSpan(axis);
+ return span;
+ }
+
+ /**
+ * Overridden to fetch the CSS attributes when view gets connected.
+ */
+ public void setParent(View parent)
+ {
+ super.setParent(parent);
+ if (parent != null)
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Fetches CSS and HTML layout attributes.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ super.setPropertiesFromAttributes();
+
+ // Fetch and parse cell spacing.
+ AttributeSet atts = getAttributes();
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ Object o = atts.getAttribute(CSS.Attribute.BORDER_SPACING);
+ if (o != null && o instanceof Length)
+ {
+ Length l = (Length) o;
+ l.setFontBases(emBase, exBase);
+ cellSpacing = (int) l.getValue();
+ }
+ o = atts.getAttribute(CSS.Attribute.WIDTH);
+ if (o != null && o instanceof Length)
+ {
+ width = (Length) o;
+ width.setFontBases(emBase, exBase);
+ }
+ }
+
+ /**
+ * Overridden to adjust for cellSpacing.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ int adjust = (getViewCount() + 1) * cellSpacing;
+ r.minimum += adjust;
+ r.preferred += adjust;
+ r.maximum += adjust;
+ return r;
+ }
+
+ /**
+ * Overridden to adjust for cellSpacing.
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int spans[])
+ {
+ // Mark all rows as invalid along their minor axis to force correct
+ // layout of multi-row cells.
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View row = getView(i);
+ if (row instanceof RowView)
+ ((RowView) row).layoutChanged(axis);
+ }
+
+ int adjust = (getViewCount() + 1) * cellSpacing;
+ super.layoutMajorAxis(targetSpan - adjust, axis, offsets, spans);
+ for (int i = 0; i < offsets.length; i++)
+ {
+ offsets[i] += (i + 1) * cellSpacing;
+ }
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.insertUpdate(e, a, this);
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.removeUpdate(e, a, this);
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
*/
- protected void loadChildren(ViewFactory vf)
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
{
- Element el = getElement();
- int numChildren = el.getElementCount();
- View[] rows = new View[numChildren];
- for (int i = 0; i < numChildren; ++i)
+ super.changedUpdate(e, a, this);
+ }
+
+ public void replace(int offset, int len, View[] views)
+ {
+ gridValid = false;
+ super.replace(offset, len, views);
+ }
+
+ /**
+ * We can't use the super class's paint() method because it might cut
+ * off multi-row children. Instead we trigger painting for all rows
+ * and let the rows sort out what to paint and what not.
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ painter.paint(g, rect.x, rect.y, rect.width, rect.height, this);
+ int nRows = getViewCount();
+ Rectangle inside = getInsideAllocation(a);
+ for (int r = 0; r < nRows; r++)
{
- rows[i] = createTableRow(el.getElement(i));
+ tmpRect.setBounds(inside);
+ childAllocation(r, tmpRect);
+ paintChild(g, tmpRect, r);
}
- replace(0, getViewCount(), rows);
}
+
}
diff --git a/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java
new file mode 100644
index 0000000..25db89f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java
@@ -0,0 +1,163 @@
+/* ViewAttributeSet.java -- The AttributeSet used by HTML views
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.View;
+
+/**
+ * An AttributeSet implemenation that is used by the HTML views. This
+ * AttributeSet is created by StyleSheet.getViewAttributes() and combines
+ * the following attributes:
+ * - The original attributes of the View's element.
+ * - Any translated (HTML->CSS) attributes, as returned by
+ * StyleSheet.translateHTMLToCS().
+ * - CSS Styles as resolved by the CSS stylesheet.
+ *
+ * In addition to that, it resolves attributes to the parent views, if
+ * a CSS attribute is requested that is inheritable.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+class ViewAttributeSet
+ extends MultiAttributeSet
+{
+
+ /**
+ * The view for which we are the AttributeSet.
+ */
+ private View view;
+
+ /**
+ * The stylesheet to use.
+ */
+ private StyleSheet styleSheet;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param v the view for which to do the AttributeSet
+ */
+ ViewAttributeSet(View v, StyleSheet ss)
+ {
+ styleSheet = ss;
+ view = v;
+ ArrayList atts = new ArrayList();
+
+ Element el = v.getElement();
+ AttributeSet elAtts = el.getAttributes();
+ AttributeSet htmlAtts = styleSheet.translateHTMLToCSS(elAtts);
+ if (htmlAtts.getAttributeCount() > 0)
+ atts.add(htmlAtts);
+
+ if (el.isLeaf())
+ {
+ Enumeration n = elAtts.getAttributeNames();
+ while (n.hasMoreElements())
+ {
+ Object key = n.nextElement();
+ if (key instanceof HTML.Tag)
+ {
+ AttributeSet rule = styleSheet.getRule((HTML.Tag) key, el);
+ if (rule != null)
+ atts.add(rule);
+ }
+ }
+ }
+ else
+ {
+ HTML.Tag tag =
+ (HTML.Tag) elAtts.getAttribute(StyleConstants.NameAttribute);
+ AttributeSet rule = styleSheet.getRule(tag, el);
+ if (rule != null)
+ atts.add(rule);
+ }
+
+ AttributeSet[] atts1 = new AttributeSet[atts.size()];
+ atts1 = (AttributeSet[]) atts.toArray(atts1);
+ init(atts1);
+ }
+
+ /**
+ * Fetches the attribute for the specific ckey. If the attribute
+ * can't be found and the key is a CSS.Attribute that is inherited,
+ * then the attribute is looked up in the resolve parent.
+ */
+ public Object getAttribute(Object key)
+ {
+ Object val = super.getAttribute(key);
+ if (val == null)
+ {
+ // Didn't find value. If the key is a CSS.Attribute, and is
+ // inherited, then ask the resolve parent.
+ if (key instanceof CSS.Attribute)
+ {
+ CSS.Attribute cssKey = (CSS.Attribute) key;
+ if (cssKey.isInherited())
+ {
+ AttributeSet resolveParent = getResolveParent();
+ if (resolveParent != null)
+ val = resolveParent.getAttribute(cssKey);
+ }
+ }
+ }
+ return val;
+ }
+
+ /**
+ * Returns the resolve parent of this AttributeSet. This is the AttributeSet
+ * returned by the parent view if available.
+ */
+ public AttributeSet getResolveParent()
+ {
+ AttributeSet parent = null;
+ if (view != null)
+ {
+ View parentView = view.getParent();
+ if (parentView != null)
+ parent = parentView.getAttributes();
+ }
+ return parent;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/AttributeList.java b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java
index 5bca0bf..d48266d 100644
--- a/libjava/classpath/javax/swing/text/html/parser/AttributeList.java
+++ b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java
@@ -122,7 +122,7 @@ public final class AttributeList
* null, if this parameter was not specified.
* Values, defined in DTD, are case insensitive.
*/
- public Vector values;
+ public Vector<?> values;
/**
* The modifier of this attribute. This field contains one of the
@@ -176,7 +176,7 @@ public final class AttributeList
* Equals to null for the last attribute definition.
*/
public AttributeList(String a_name, int a_type, int a_modifier,
- String a_default, Vector allowed_values,
+ String a_default, Vector<?> allowed_values,
AttributeList a_next
)
{
@@ -251,7 +251,7 @@ public final class AttributeList
/**
* Get the allowed values of this attribute.
*/
- public Enumeration getValues()
+ public Enumeration<?> getValues()
{
return values.elements();
}
diff --git a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java
index 70e9c2a..d5c4418 100644
--- a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java
+++ b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java
@@ -151,13 +151,15 @@ public final class ContentModel
* discarded.
* @param elements - a vector to add the values to.
*/
- public void getElements(Vector elements)
+ public void getElements(Vector<Element> elements)
{
ContentModel c = this;
while (c != null)
{
- elements.add(c.content);
+ // FIXME: correct?
+ if (c.content instanceof Element)
+ elements.add((Element) c.content);
c = c.next;
}
}
diff --git a/libjava/classpath/javax/swing/text/html/parser/DTD.java b/libjava/classpath/javax/swing/text/html/parser/DTD.java
index 16bc5b0..ae3c184 100644
--- a/libjava/classpath/javax/swing/text/html/parser/DTD.java
+++ b/libjava/classpath/javax/swing/text/html/parser/DTD.java
@@ -88,7 +88,7 @@ public class DTD
/**
* The table of existing available DTDs.
*/
- static Hashtable dtdHash = new Hashtable();
+ static Hashtable<String,DTD> dtdHash = new Hashtable<String,DTD>();
/**
* The applet element for this DTD.
@@ -148,12 +148,13 @@ public class DTD
/**
* The element for accessing all DTD elements by name.
*/
- public Hashtable elementHash = new Hashtable();
+ public Hashtable<String,Element> elementHash =
+ new Hashtable<String,Element>();
/**
* The entity table for accessing all DTD entities by name.
*/
- public Hashtable entityHash = new Hashtable();
+ public Hashtable<Object, Entity> entityHash = new Hashtable<Object, Entity>();
/**
* The name of this DTD.
@@ -165,7 +166,7 @@ public class DTD
* javax.swing.text.html.parser.Element#index field of all elements
* in this vector is set to the element position in this vector.
*/
- public Vector elements = new Vector();
+ public Vector<Element> elements = new Vector<Element>();
/** Create a new DTD with the specified name. */
protected DTD(String a_name)
@@ -224,7 +225,7 @@ public class DTD
String name = Entity.mapper.get(id);
if (name != null)
- return (Entity) entityHash.get(name);
+ return entityHash.get(name);
else
return null;
}
@@ -269,7 +270,7 @@ public class DTD
*/
public void defineAttributes(String forElement, AttributeList attributes)
{
- Element e = (Element) elementHash.get(forElement.toLowerCase());
+ Element e = elementHash.get(forElement.toLowerCase());
if (e == null)
e = newElement(forElement);
@@ -420,7 +421,7 @@ public class DTD
if (allowed_values != null)
{
StringTokenizer st = new StringTokenizer(allowed_values, " \t|");
- Vector v = new Vector(st.countTokens());
+ Vector<String> v = new Vector<String>(st.countTokens());
while (st.hasMoreTokens())
v.add(st.nextToken());
@@ -571,7 +572,7 @@ public class DTD
*/
private Element newElement(String name)
{
- Element e = (Element) elementHash.get(name.toLowerCase());
+ Element e = elementHash.get(name.toLowerCase());
if (e == null)
{
diff --git a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java
index 062606d..f717d69 100644
--- a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java
+++ b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java
@@ -38,13 +38,13 @@ exception statement from your version. */
package javax.swing.text.html.parser;
-import gnu.javax.swing.text.html.parser.htmlAttributeSet;
import javax.swing.text.html.parser.Parser;
import java.io.IOException;
import java.io.Reader;
import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.html.HTMLEditorKit;
/**
@@ -117,7 +117,7 @@ public class DocumentParser
protected final void handleStartTag(TagElement tag)
{
parser.handleStartTag(tag);
- htmlAttributeSet attributes = gnu.getAttributes();
+ SimpleAttributeSet attributes = gnu.getAttributes();
if (tag.fictional())
attributes.addAttribute(HTMLEditorKit.ParserCallback.IMPLIED,
diff --git a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java
index 70636d9..cdd339b 100644
--- a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java
+++ b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java
@@ -38,13 +38,13 @@ exception statement from your version. */
package javax.swing.text.html.parser;
import gnu.javax.swing.text.html.parser.HTML_401F;
-import gnu.javax.swing.text.html.parser.htmlAttributeSet;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
@@ -93,7 +93,7 @@ public class ParserDelegator
protected final void handleStartTag(TagElement tag)
{
- htmlAttributeSet attributes = gnu.getAttributes();
+ SimpleAttributeSet attributes = gnu.getAttributes();
if (tag.fictional())
attributes.addAttribute(ParserCallback.IMPLIED, Boolean.TRUE);