From 2127637945ea6b763966398130e0770fa993c860 Mon Sep 17 00:00:00 2001 From: Mark Wielaard Date: Tue, 17 Jan 2006 18:09:40 +0000 Subject: Imported GNU Classpath 0.20 Imported GNU Classpath 0.20 * Makefile.am (AM_CPPFLAGS): Add classpath/include. * java/nio/charset/spi/CharsetProvider.java: New override file. * java/security/Security.java: Likewise. * sources.am: Regenerated. * Makefile.in: Likewise. From-SVN: r109831 --- .../javax/swing/text/AbstractDocument.java | 29 +- .../classpath/javax/swing/text/ComponentView.java | 30 - .../classpath/javax/swing/text/CompositeView.java | 30 - .../classpath/javax/swing/text/DefaultCaret.java | 81 +- .../javax/swing/text/DefaultEditorKit.java | 79 +- .../javax/swing/text/DefaultFormatter.java | 2 + .../javax/swing/text/DefaultFormatterFactory.java | 280 ++++ .../javax/swing/text/DefaultStyledDocument.java | 1271 ++++++++++++++----- libjava/classpath/javax/swing/text/FlowView.java | 31 - libjava/classpath/javax/swing/text/GapContent.java | 49 +- libjava/classpath/javax/swing/text/GlyphView.java | 30 - libjava/classpath/javax/swing/text/IconView.java | 30 - .../javax/swing/text/InternationalFormatter.java | 4 + .../classpath/javax/swing/text/JTextComponent.java | 35 +- .../classpath/javax/swing/text/MaskFormatter.java | 583 +++++++++ .../javax/swing/text/NumberFormatter.java | 86 ++ .../classpath/javax/swing/text/PasswordView.java | 38 +- .../classpath/javax/swing/text/PlainDocument.java | 40 + libjava/classpath/javax/swing/text/PlainView.java | 34 +- .../classpath/javax/swing/text/StyleContext.java | 5 +- .../javax/swing/text/StyledEditorKit.java | 33 +- libjava/classpath/javax/swing/text/TableView.java | 465 +++++++ libjava/classpath/javax/swing/text/Utilities.java | 19 +- libjava/classpath/javax/swing/text/View.java | 40 +- .../javax/swing/text/WrappedPlainView.java | 30 - .../classpath/javax/swing/text/html/BlockView.java | 301 +++++ libjava/classpath/javax/swing/text/html/CSS.java | 3 +- .../classpath/javax/swing/text/html/CSSParser.java | 568 +++++++++ .../javax/swing/text/html/HTMLDocument.java | 1337 +++++++++++++++++++- .../javax/swing/text/html/HTMLEditorKit.java | 945 +++++++++++++- .../javax/swing/text/html/StyleSheet.java | 937 ++++++++++++++ .../classpath/javax/swing/text/html/default.css | 378 ++++++ 32 files changed, 7158 insertions(+), 665 deletions(-) create mode 100644 libjava/classpath/javax/swing/text/DefaultFormatterFactory.java create mode 100644 libjava/classpath/javax/swing/text/MaskFormatter.java create mode 100644 libjava/classpath/javax/swing/text/NumberFormatter.java create mode 100644 libjava/classpath/javax/swing/text/TableView.java create mode 100644 libjava/classpath/javax/swing/text/html/BlockView.java create mode 100644 libjava/classpath/javax/swing/text/html/CSSParser.java create mode 100644 libjava/classpath/javax/swing/text/html/StyleSheet.java create mode 100644 libjava/classpath/javax/swing/text/html/default.css (limited to 'libjava/classpath/javax/swing/text') diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java index a324425..c735388 100644 --- a/libjava/classpath/javax/swing/text/AbstractDocument.java +++ b/libjava/classpath/javax/swing/text/AbstractDocument.java @@ -541,6 +541,9 @@ public abstract class AbstractDocument implements Document, Serializable writeLock(); UndoableEdit undo = content.insertString(offset, text); + if (undo != null) + event.addEdit(undo); + insertUpdate(event, attributes); writeUnlock(); @@ -1326,7 +1329,14 @@ public abstract class AbstractDocument implements Document, Serializable */ public Object getAttribute(Object key) { - return attributes.getAttribute(key); + Object result = attributes.getAttribute(key); + if (result == null && element_parent != null) + { + AttributeSet parentSet = element_parent.getAttributes(); + if (parentSet != null) + result = parentSet.getAttribute(key); + } + return result; } /** @@ -1809,6 +1819,12 @@ public abstract class AbstractDocument implements Document, Serializable Hashtable changes; /** + * Indicates if this event has been modified or not. This is used to + * determine if this event is thrown. + */ + boolean modified; + + /** * Creates a new DefaultDocumentEvent. * * @param offset the starting offset of the change @@ -1822,6 +1838,7 @@ public abstract class AbstractDocument implements Document, Serializable this.length = length; this.type = type; changes = new Hashtable(); + modified = false; } /** @@ -1836,6 +1853,7 @@ public abstract class AbstractDocument implements Document, Serializable // XXX - Fully qualify ElementChange to work around gcj bug #2499. if (edit instanceof DocumentEvent.ElementChange) { + modified = true; DocumentEvent.ElementChange elEdit = (DocumentEvent.ElementChange) edit; changes.put(elEdit.getElement(), elEdit); @@ -1896,6 +1914,15 @@ public abstract class AbstractDocument implements Document, Serializable // XXX - Fully qualify ElementChange to work around gcj bug #2499. return (DocumentEvent.ElementChange) changes.get(elem); } + + /** + * Returns a String description of the change event. This returns the + * toString method of the Vector of edits. + */ + public String toString() + { + return edits.toString(); + } } /** diff --git a/libjava/classpath/javax/swing/text/ComponentView.java b/libjava/classpath/javax/swing/text/ComponentView.java index 830dda3..2846f8b 100644 --- a/libjava/classpath/javax/swing/text/ComponentView.java +++ b/libjava/classpath/javax/swing/text/ComponentView.java @@ -264,34 +264,4 @@ public class ComponentView extends View Element el = getElement(); return el.getStartOffset(); } - - /** - * Returns the document position that is (visually) nearest to the given - * document position pos in the given direction d. - * - * @param c the text component - * @param pos the document position - * @param b the bias for pos - * @param d the direction, must be either {@link SwingConstants#NORTH}, - * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or - * {@link SwingConstants#EAST} - * @param biasRet an array of {@link Position.Bias} that can hold at least - * one element, which is filled with the bias of the return position - * on method exit - * - * @return the document position that is (visually) nearest to the given - * document position pos in the given direction - * d - * - * @throws BadLocationException if pos is not a valid offset in - * the document model - */ - public int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException - { - // FIXME: Implement this method. - throw new AssertionError("Not yet implemented"); - } } diff --git a/libjava/classpath/javax/swing/text/CompositeView.java b/libjava/classpath/javax/swing/text/CompositeView.java index e6c2e4c..cd66452 100644 --- a/libjava/classpath/javax/swing/text/CompositeView.java +++ b/libjava/classpath/javax/swing/text/CompositeView.java @@ -661,34 +661,4 @@ public abstract class CompositeView { return false; } - - /** - * Returns the document position that is (visually) nearest to the given - * document position pos in the given direction d. - * - * @param c the text component - * @param pos the document position - * @param b the bias for pos - * @param d the direction, must be either {@link SwingConstants#NORTH}, - * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or - * {@link SwingConstants#EAST} - * @param biasRet an array of {@link Position.Bias} that can hold at least - * one element, which is filled with the bias of the return position - * on method exit - * - * @return the document position that is (visually) nearest to the given - * document position pos in the given direction - * d - * - * @throws BadLocationException if pos is not a valid offset in - * the document model - */ - public int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException - { - // TODO: Implement this properly. - throw new AssertionError("Not implemented yet."); - } } diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java index 3ebeceb..776ef69 100644 --- a/libjava/classpath/javax/swing/text/DefaultCaret.java +++ b/libjava/classpath/javax/swing/text/DefaultCaret.java @@ -1,5 +1,5 @@ /* DefaultCaret.java -- - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -430,18 +430,45 @@ public class DefaultCaret extends Rectangle */ public void focusGained(FocusEvent event) { - setVisible(true); + setVisible(true); + updateTimerStatus(); } /** * Sets the caret to invisible. - * + * * @param event the FocusEvent */ public void focusLost(FocusEvent event) { if (event.isTemporary() == false) - setVisible(false); + { + setVisible(false); + // Stop the blinker, if running. + if (blinkTimer != null && blinkTimer.isRunning()) + blinkTimer.stop(); + } + } + + /** + * Install (if not present) and start the timer, if the caret must blink. The + * caret does not blink if it is invisible, or the component is disabled or + * not editable. + */ + private void updateTimerStatus() + { + if (textComponent.isEnabled() && textComponent.isEditable()) + { + if (blinkTimer == null) + initBlinkTimer(); + if (!blinkTimer.isRunning()) + blinkTimer.start(); + } + else + { + if (blinkTimer != null) + blinkTimer.stop(); + } } /** @@ -641,8 +668,10 @@ public class DefaultCaret extends Rectangle } catch (BadLocationException e) { - assert false : "Unexpected bad caret location: " + dot; - return; + AssertionError ae; + ae = new AssertionError("Unexpected bad caret location: " + dot); + ae.initCause(e); + throw ae; } if (rect == null) @@ -802,9 +831,12 @@ public class DefaultCaret extends Rectangle public void setDot(int dot) { if (dot >= 0) - { + { + Document doc = textComponent.getDocument(); + if (doc != null) + this.dot = Math.min(dot, doc.getLength()); + this.dot = Math.max(this.dot, 0); this.mark = dot; - this.dot = dot; handleHighlight(); adjustVisibility(this); appear(); @@ -833,13 +865,17 @@ public class DefaultCaret extends Rectangle visible = true; Rectangle area = null; + int dot = getDot(); try { - area = getComponent().modelToView(getDot()); + area = getComponent().modelToView(dot); } - catch (BadLocationException ex) + catch (BadLocationException e) { - assert false : "Unexpected bad caret location: " + getDot(); + AssertionError ae; + ae = new AssertionError("Unexpected bad caret location: " + dot); + ae.initCause(e); + throw ae; } if (area != null) damage(area); @@ -870,26 +906,19 @@ public class DefaultCaret extends Rectangle if (v != visible) { visible = v; - if (visible) - if (textComponent.isEnabled() && textComponent.isEditable()) - { - if (blinkTimer == null) - initBlinkTimer(); - blinkTimer.start(); - } - else - { - if (blinkTimer != null) - blinkTimer.stop(); - } + updateTimerStatus(); Rectangle area = null; + int dot = getDot(); try { - area = getComponent().modelToView(getDot()); + area = getComponent().modelToView(dot); } - catch (BadLocationException ex) + catch (BadLocationException e) { - assert false: "Unexpected bad caret location: " + getDot(); + AssertionError ae; + ae = new AssertionError("Unexpected bad caret location: " + dot); + ae.initCause(e); + throw ae; } if (area != null) damage(area); diff --git a/libjava/classpath/javax/swing/text/DefaultEditorKit.java b/libjava/classpath/javax/swing/text/DefaultEditorKit.java index 3b3fc1f..88094b8 100644 --- a/libjava/classpath/javax/swing/text/DefaultEditorKit.java +++ b/libjava/classpath/javax/swing/text/DefaultEditorKit.java @@ -38,8 +38,10 @@ exception statement from your version. */ package javax.swing.text; +import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -112,8 +114,7 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - // FIXME: Implement me. Tookit.getSystemClipboard should be used - // for that. + getTextComponent(event).copy(); } } @@ -144,8 +145,7 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - // FIXME: Implement me. Tookit.getSystemClipboard should be used - // for that. + getTextComponent(event).cut(); } } @@ -174,8 +174,7 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - // FIXME: Implement me. Tookit.getSystemClipboard should be used - // for that. + getTextComponent(event).paste(); } } @@ -216,19 +215,9 @@ public class DefaultEditorKit extends EditorKit return; JTextComponent t = getTextComponent(event); - if (t != null) - { - try - { - t.getDocument().insertString(t.getCaret().getDot(), - event.getActionCommand(), null); - } - catch (BadLocationException be) - { - // FIXME: we're not authorized to throw this.. swallow it? - } - } - } + if (t != null && t.isEnabled() && t.isEditable()) + t.replaceSelection(event.getActionCommand()); + } } /** @@ -309,7 +298,8 @@ public class DefaultEditorKit extends EditorKit */ public void actionPerformed(ActionEvent event) { - // FIXME: Implement this. + JTextComponent t = getTextComponent(event); + t.replaceSelection("\t"); } } @@ -710,6 +700,53 @@ public class DefaultEditorKit extends EditorKit new InsertContentAction(), new InsertTabAction(), new PasteAction(), + new TextAction(beginLineAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + try + { + // TODO: There is a more efficent solution, but + // viewToModel doesn't work properly. + Point p = t.modelToView(t.getCaret().getDot()).getLocation(); + int cur = t.getCaretPosition(); + int y = p.y; + while (y == p.y && cur > 0) + y = t.modelToView(--cur).getLocation().y; + if (cur != 0) + cur++; + t.setCaretPosition(cur); + } + catch (BadLocationException ble) + { + // Do nothing here. + } + } + }, + new TextAction(endLineAction) + { + public void actionPerformed(ActionEvent event) + { + JTextComponent t = getTextComponent(event); + try + { + Point p = t.modelToView(t.getCaret().getDot()).getLocation(); + int cur = t.getCaretPosition(); + int y = p.y; + int length = t.getDocument().getLength(); + while (y == p.y && cur < length) + y = t.modelToView(++cur).getLocation().y; + if (cur != length) + cur--; + t.setCaretPosition(cur); + } + catch (BadLocationException ble) + { + // Nothing to do here + } + } + }, new TextAction(deleteNextCharAction) { public void actionPerformed(ActionEvent event) @@ -911,7 +948,7 @@ public class DefaultEditorKit extends EditorKit content.append("\n"); } - document.insertString(offset, content.toString(), + document.insertString(offset, content.substring(0, content.length() - 1), SimpleAttributeSet.EMPTY); } diff --git a/libjava/classpath/javax/swing/text/DefaultFormatter.java b/libjava/classpath/javax/swing/text/DefaultFormatter.java index f9e0f10..493699d 100644 --- a/libjava/classpath/javax/swing/text/DefaultFormatter.java +++ b/libjava/classpath/javax/swing/text/DefaultFormatter.java @@ -400,6 +400,8 @@ public class DefaultFormatter extends JFormattedTextField.AbstractFormatter public String valueToString(Object value) throws ParseException { + if (value == null) + return ""; return value.toString(); } diff --git a/libjava/classpath/javax/swing/text/DefaultFormatterFactory.java b/libjava/classpath/javax/swing/text/DefaultFormatterFactory.java new file mode 100644 index 0000000..84a1676 --- /dev/null +++ b/libjava/classpath/javax/swing/text/DefaultFormatterFactory.java @@ -0,0 +1,280 @@ +/* DefaultFormatterFactory.java -- FIXME: briefly describe file purpose + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.io.Serializable; + +import javax.swing.JFormattedTextField; +import javax.swing.JFormattedTextField.AbstractFormatter; +import javax.swing.JFormattedTextField.AbstractFormatterFactory; + +/** + * This class is Swing's only concrete implementation of + * JFormattedTextField.AbstractFormatterFactory. It holds several + * formatters and determines the best one to be used based on the + * passed-in value from the text field. + * + * @author Anthony Balkissoon abalkiss at redhat dot com + * @since 1.4 + */ +public class DefaultFormatterFactory extends AbstractFormatterFactory implements + Serializable +{ + /** + * The default formatter. + **/ + AbstractFormatter defaultFormatter; + + /** + * The formatter to use when the JFormattedTextField has focus and either the + * value isn't null or the value is null but no nullFormatter + * has been specified. + */ + AbstractFormatter editFormatter; + + /** + * The formatter to use when the JFormattedTextField doesn't havefocus and + * either the value isn't null or the value is null but no + * nullFormatter has been specified. + */ + AbstractFormatter displayFormatter; + + /** + * The formatter to use when the value of the JFormattedTextField is null. + */ + AbstractFormatter nullFormatter; + + /** + * Creates a DefaultFormatterFactory with no formatters + */ + public DefaultFormatterFactory() + { + // Nothing to be done here. + } + + /** + * Creates a new DefaultFormatterFactory with the specified formatters. + * @param defaultFormat the formatter to use if no other appropriate non-null + * formatted can be found. + */ + public DefaultFormatterFactory(AbstractFormatter defaultFormat) + { + defaultFormatter = defaultFormat; + } + + /** + * Creates a new DefaultFormatterFactory with the specified formatters. + * @param defaultFormat the formatter to use if no other appropriate non-null + * formatted can be found. + * @param displayFormat the formatter to use if the JFormattedTextField + * doesn't have focus and either the value is not null or the value is null + * but no nullFormatter has been specified. + */ + public DefaultFormatterFactory(AbstractFormatter defaultFormat, + AbstractFormatter displayFormat) + { + defaultFormatter = defaultFormat; + displayFormatter = displayFormat; + } + + /** + * Creates a new DefaultFormatterFactory with the specified formatters. + * @param defaultFormat the formatter to use if no other appropriate non-null + * formatted can be found. + * @param displayFormat the formatter to use if the JFormattedTextField + * doesn't have focus and either the value is not null or the value is null + * but no nullFormatter has been specified. + * @param editFormat the formatter to use if the JFormattedTextField has + * focus and either the value is not null or the value is null but not + * nullFormatter has been specified. + */ + public DefaultFormatterFactory(AbstractFormatter defaultFormat, + AbstractFormatter displayFormat, + AbstractFormatter editFormat) + { + defaultFormatter = defaultFormat; + displayFormatter = displayFormat; + editFormatter = editFormat; + } + + /** + * Creates a new DefaultFormatterFactory with the specified formatters. + * @param defaultFormat the formatter to use if no other appropriate non-null + * formatted can be found. + * @param displayFormat the formatter to use if the JFormattedTextField + * doesn't have focus and either the value is not null or the value is null + * but no nullFormatter has been specified. + * @param editFormat the formatter to use if the JFormattedTextField has + * focus and either the value is not null or the value is null but not + * nullFormatter has been specified. + * @param nullFormat the formatter to use when the value of the + * JFormattedTextField is null. + */ + public DefaultFormatterFactory(AbstractFormatter defaultFormat, + AbstractFormatter displayFormat, + AbstractFormatter editFormat, + AbstractFormatter nullFormat) + { + defaultFormatter = defaultFormat; + displayFormatter = displayFormat; + editFormatter = editFormat; + nullFormatter = nullFormat; + } + + /** + * Returns the formatted to be used if no other appropriate non-null + * formatter can be found. + * @return the formatted to be used if no other appropriate non-null + * formatter can be found. + */ + public AbstractFormatter getDefaultFormatter() + { + return defaultFormatter; + } + + /** + * Sets the formatted to be used if no other appropriate non-null formatter + * can be found. + * @param defaultFormatter the formatted to be used if no other appropriate + * non-null formatter can be found. + */ + public void setDefaultFormatter(AbstractFormatter defaultFormatter) + { + this.defaultFormatter = defaultFormatter; + } + + /** + * Gets the displayFormatter. This is the formatter to use if + * the JFormattedTextField is not being edited and either the value is not + * null or the value is null and no nullFormatter has been + * specified. + * @return the formatter to use if + * the JFormattedTextField is not being edited and either the value is not + * null or the value is null and no nullFormatter has been + * specified. + */ + public AbstractFormatter getDisplayFormatter() + { + return displayFormatter; + } + + /** + * Sets the displayFormatter. This is the formatter to use if + * the JFormattedTextField is not being edited and either the value is not + * null or the value is null and no nullFormatter has been + * specified. + * @param displayFormatter the formatter to use. + */ + public void setDisplayFormatter(AbstractFormatter displayFormatter) + { + this.displayFormatter = displayFormatter; + } + + /** + * Gets the editFormatter. This is the formatter to use if the + * JFormattedTextField is being edited and either the value is not null or + * the value is null and no nullFormatter has been specified. + * @return the formatter to use if the JFormattedTextField is being edited + * and the value is not null or the value is null but no nullFormatted has + * been specified. + */ + public AbstractFormatter getEditFormatter() + { + return editFormatter; + } + + /** + * Sets the editFormatter. This is the formatter to use if the + * JFormattedTextField is being edited and either the value is not null or + * the value is null and no nullFormatter has been specified. + * @param editFormatter the formatter to use. + */ + public void setEditFormatter(AbstractFormatter editFormatter) + { + this.editFormatter = editFormatter; + } + + /** + * Gets the formatter to use if the value of the JFormattedTextField is null. + * @return the formatter to use for null values. + */ + public AbstractFormatter getNullFormatter() + { + return nullFormatter; + } + + /** + * Sets the nullFormatter. This is the formatter to use if the + * value of the JFormattedTextField is null. + * @param nullFormatter the formatter to use for null values. + */ + public void setNullFormatter(AbstractFormatter nullFormatter) + { + this.nullFormatter = nullFormatter; + } + + /** + * Returns the appropriate formatter based on the state of + * tf. If tf is null we return null, otherwise + * we return one of the following: + * 1. Returns nullFormatter if tf.getValue() is + * null and nullFormatter is not. + * 2. Returns editFormatter if tf.hasFocus() is + * true and editFormatter is not null. + * 3. Returns displayFormatter if tf.hasFocus() is + * false and displayFormatter is not null. + * 4. Otherwise returns defaultFormatter. + */ + public AbstractFormatter getFormatter(JFormattedTextField tf) + { + if (tf == null) + return null; + + if (tf.getValue() == null && nullFormatter != null) + return nullFormatter; + + if (tf.hasFocus() && editFormatter != null) + return editFormatter; + + if (!tf.hasFocus() && displayFormatter != null) + return displayFormatter; + + return defaultFormatter; + } +} diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java index eb56bb0..46b8225 100644 --- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java +++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java @@ -42,11 +42,13 @@ import java.awt.Color; import java.awt.Font; import java.io.Serializable; import java.util.Enumeration; +import java.util.Stack; import java.util.Vector; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; +import javax.swing.event.UndoableEditEvent; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.UndoableEdit; @@ -365,7 +367,6 @@ public class DefaultStyledDocument extends AbstractDocument public String toString() { StringBuilder b = new StringBuilder(); - b.append('<'); switch (type) { case StartTagType: @@ -427,6 +428,29 @@ public class DefaultStyledDocument extends AbstractDocument /** Holds the length of structural changes. */ private int length; + + /** Holds the end offset for structural changes. **/ + private int endOffset; + + /** + * The number of inserted end tags. This is a counter which always gets + * incremented when an end tag is inserted. This is evaluated before + * content insertion to go up the element stack. + */ + private int numEndTags; + + /** + * The number of inserted start tags. This is a counter which always gets + * incremented when an end tag is inserted. This is evaluated before + * content insertion to go up the element stack. + */ + private int numStartTags; + + /** + * The current position in the element tree. This is used for bulk inserts + * using ElementSpecs. + */ + private Stack elementStack; /** * Holds fractured elements during insertion of end and start tags. @@ -450,6 +474,7 @@ public class DefaultStyledDocument extends AbstractDocument public ElementBuffer(Element root) { this.root = root; + elementStack = new Stack(); } /** @@ -463,6 +488,85 @@ public class DefaultStyledDocument extends AbstractDocument } /** + * Updates the element structure of the document in response to removal of + * content. It removes the affected {@link Element}s from the document + * structure. + * + * This method sets some internal parameters and delegates the work + * to {@link #removeUpdate}. + * + * @param offs the offset from which content is remove + * @param len the length of the removed content + * @param ev the document event that records the changes + */ + public void remove(int offs, int len, DefaultDocumentEvent ev) + { + offset = offs; + length = len; + documentEvent = ev; + removeUpdate(); + } + + /** + * Updates the element structure of the document in response to removal of + * content. It removes the affected {@link Element}s from the document + * structure. + */ + 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++) + { + Element paragraph = root.getElement(i); + int contentStart = paragraph.getElementIndex(offset); + int contentEnd = paragraph.getElementIndex(offset + length); + if (contentStart == paragraph.getStartOffset() + && contentEnd == paragraph.getEndOffset()) + { + // 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) + { + removeStart = i; + removeEnd = i; + } + 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); + ((BranchElement) paragraph).replace(contentStart, removeLen, + empty); + documentEvent.addEdit(new ElementEdit(paragraph, contentStart, + removed, empty)); + } + } + // 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); + ((BranchElement) root).replace(removeStart, removeLen, empty); + documentEvent.addEdit(new ElementEdit(root, removeStart, removed, + empty)); + } + } + + /** * Modifies the element structure so that the specified interval starts * and ends at an element boundary. Content and paragraph elements * are split and created as necessary. @@ -493,11 +597,50 @@ public class DefaultStyledDocument extends AbstractDocument { // Split up the element at the start offset if necessary. Element el = getCharacterElement(offset); - split(el, offset); + Element[] res = split(el, offset, 0); + BranchElement par = (BranchElement) el.getParentElement(); + if (res[1] != null) + { + int index = par.getElementIndex(offset); + Element[] removed; + Element[] added; + if (res[0] == null) + { + removed = new Element[0]; + added = new Element[]{ res[1] }; + index++; + } + else + { + removed = new Element[]{ el }; + added = new Element[]{ res[0], res[1] }; + } + par.replace(index, removed.length, added); + addEdit(par, index, removed, added); + } int endOffset = offset + length; el = getCharacterElement(endOffset); - split(el, endOffset); + res = split(el, endOffset, 0); + par = (BranchElement) el.getParentElement(); + if (res[1] != null) + { + int index = par.getElementIndex(offset); + Element[] removed; + Element[] added; + if (res[1] == null) + { + removed = new Element[0]; + added = new Element[]{ res[1] }; + } + else + { + removed = new Element[]{ el }; + added = new Element[]{ res[0], res[1] }; + } + par.replace(index, removed.length, added); + addEdit(par, index, removed, added); + } } /** @@ -505,42 +648,96 @@ public class DefaultStyledDocument extends AbstractDocument * * @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 + * + * @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. + * */ - void split(Element el, int offset) + private Element[] split(Element el, int offset, int space) { - if (el instanceof AbstractElement) + // 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) { - AbstractElement ael = (AbstractElement) el; - int startOffset = ael.getStartOffset(); - int endOffset = ael.getEndOffset(); - int len = endOffset - startOffset; - if (startOffset != offset && endOffset != offset) + int index = el.getElementIndex(offset); + Element child = el.getElement(index); + Element[] result = split(child, offset, space); + Element[] removed; + Element[] added; + Element[] newAdded; + + int count = el.getElementCount(); + if (!(result[1] == null)) { - Element paragraph = ael.getParentElement(); - if (paragraph instanceof BranchElement) + // This is the case when we can keep the first element. + if (result[0] == null) { - BranchElement par = (BranchElement) paragraph; - Element child1 = createLeafElement(par, ael, startOffset, - offset); - Element child2 = createLeafElement(par, ael, offset, - endOffset); - int index = par.getElementIndex(startOffset); - Element[] add = new Element[]{ child1, child2 }; - par.replace(index, 1, add); - documentEvent.addEdit(new ElementEdit(par, index, - new Element[]{ el }, - add)); + 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 - throw new AssertionError("paragraph elements are expected to " - + "be instances of " - + "javax.swing.text.AbstractDocument.BranchElement"); + { + removed = new Element[count - index]; + newAdded = new Element[count - index]; + added = new Element[]{result[0]}; + } + newAdded[0] = result[1]; + for (int i = index; i < count; i++) + { + Element el2 = el.getElement(i); + int ind = i - count + removed.length; + removed[ind] = el2; + if (ind != 0) + newAdded[ind] = el2; + } + + ((BranchElement) el).replace(index, removed.length, added); + addEdit(el, index, removed, added); + BranchElement newPar = + (BranchElement) createBranchElement(el.getParentElement(), + el.getAttributes()); + newPar.replace(0, 0, newAdded); + res = new Element[]{ null, newPar }; + } + else + { + removed = new Element[count - index]; + for (int i = index; i < count; ++i) + removed[i - index] = el.getElement(i); + added = new Element[0]; + ((BranchElement) el).replace(index, removed.length, + added); + addEdit(el, index, removed, added); + BranchElement newPar = + (BranchElement) createBranchElement(el.getParentElement(), + el.getAttributes()); + newPar.replace(0, 0, removed); + res = new Element[]{ null, newPar }; } } - else - throw new AssertionError("content elements are expected to be " - + "instances of " - + "javax.swing.text.AbstractDocument.AbstractElement"); + else if (el instanceof LeafElement) + { + BranchElement par = (BranchElement) el.getParentElement(); + Element el1 = createLeafElement(par, el.getAttributes(), + el.getStartOffset(), offset); + Element el2 = createLeafElement(par, el.getAttributes(), + offset + space, el.getEndOffset()); + res = new Element[]{ el1, el2 }; + } + return res; } /** @@ -560,9 +757,18 @@ public class DefaultStyledDocument extends AbstractDocument public void insert(int offset, int length, ElementSpec[] data, DefaultDocumentEvent ev) { + if (length == 0) + return; this.offset = offset; this.length = length; + this.endOffset = offset + length; documentEvent = ev; + // Push the root and the paragraph at offset onto the element stack. + elementStack.clear(); + elementStack.push(root); + elementStack.push(root.getElement(root.getElementIndex(offset))); + numEndTags = 0; + numStartTags = 0; insertUpdate(data); } @@ -573,202 +779,348 @@ public class DefaultStyledDocument extends AbstractDocument * {@link #insert}. * * @param data the element specifications for the elements to be inserte - */ + */ protected void insertUpdate(ElementSpec[] data) { + if (data[0].getType() == ElementSpec.EndTagType) + { + // fracture deepest child here + BranchElement paragraph = (BranchElement) elementStack.peek(); + Element curr = paragraph.getParentElement(); + int index = curr.getElementIndex(offset); + while (!curr.isLeaf()) + { + index = curr.getElementIndex(offset); + curr = curr.getElement(index); + } + Element parent = curr.getParentElement(); + Element newEl1 = createLeafElement(parent, + curr.getAttributes(), + curr.getStartOffset(), offset); + Element grandParent = parent.getParentElement(); + BranchElement nextBranch = + (BranchElement) grandParent.getElement + (grandParent.getElementIndex(parent.getEndOffset())); + Element firstLeaf = nextBranch.getElement(0); + while (!firstLeaf.isLeaf()) + { + firstLeaf = firstLeaf.getElement(0); + } + BranchElement parent2 = (BranchElement) firstLeaf.getParentElement(); + Element newEl2 = + createLeafElement(parent2, + firstLeaf.getAttributes(), + offset, firstLeaf.getEndOffset()); + parent2.replace(0, 1, new Element[] { newEl2 }); + + + ((BranchElement) parent). + replace(index, 1, new Element[] { newEl1 }); + } + for (int i = 0; i < data.length; i++) { + BranchElement paragraph = (BranchElement) elementStack.peek(); switch (data[i].getType()) { case ElementSpec.StartTagType: - insertStartTag(data[i]); + switch (data[i].getDirection()) + { + case ElementSpec.JoinFractureDirection: + insertFracture(data[i]); + break; + case ElementSpec.JoinNextDirection: + int index = paragraph.getElementIndex(offset); + elementStack.push(paragraph.getElement(index)); + break; + case ElementSpec.OriginateDirection: + Element current = (Element) elementStack.peek(); + Element newParagraph = + insertParagraph((BranchElement) current, offset); + elementStack.push(newParagraph); + break; + default: + break; + } break; case ElementSpec.EndTagType: - insertEndTag(data[i]); + elementStack.pop(); break; - default: + case ElementSpec.ContentType: insertContentTag(data[i]); break; } } + endEdit(); } /** - * Insert a new paragraph after the paragraph at the current position. - * - * @param tag the element spec that describes the element to be inserted + * Finishes an insertion by possibly evaluating the outstanding start and + * end tags. However, this is only performed if the event has received any + * modifications. + */ + private void endEdit() + { + if (documentEvent.modified) + prepareContentInsertion(); + } + + /** + * Evaluates the number of inserted end tags and performs the corresponding + * structural changes. */ - void insertStartTag(ElementSpec tag) + private void prepareContentInsertion() { - BranchElement root = (BranchElement) getDefaultRootElement(); - int index = root.getElementIndex(offset); - if (index == -1) - index = 0; - - BranchElement newParagraph = - (BranchElement) createBranchElement(root, tag.getAttributes()); - newParagraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE)); - - // Add new paragraph into document structure. - Element[] added = new Element[]{newParagraph}; - root.replace(index + 1, 0, added); - ElementEdit edit = new ElementEdit(root, index + 1, new Element[0], - added); - documentEvent.addEdit(edit); - - // Maybe add fractured elements. - if (tag.getDirection() == ElementSpec.JoinFractureDirection) + while (numEndTags > 0) + { + elementStack.pop(); + numEndTags--; + } + + while (numStartTags > 0) { - Element[] newFracture = new Element[fracture.length]; - for (int i = 0; i < fracture.length; i++) + Element current = (Element) elementStack.peek(); + Element newParagraph = + insertParagraph((BranchElement) current, offset); + elementStack.push(newParagraph); + numStartTags--; + } + } + + private Element insertParagraph(BranchElement par, int offset) + { + Element current = par.getElement(par.getElementIndex(offset)); + Element[] res = split(current, offset, 0); + int index = par.getElementIndex(offset); + Element ret; + if (res[1] != null) + { + Element[] removed; + Element[] added; + if (res[0] == null) { - Element oldLeaf = fracture[i]; - Element newLeaf = createLeafElement(newParagraph, - oldLeaf.getAttributes(), - oldLeaf.getStartOffset(), - oldLeaf.getEndOffset()); - newFracture[i] = newLeaf; + removed = new Element[0]; + if (res[1] instanceof BranchElement) + { + added = new Element[]{ res[1] }; + ret = res[1]; + } + else + { + ret = createBranchElement(par, null); + added = new Element[]{ ret, res[1] }; + } + index++; + } + else + { + removed = new Element[]{ current }; + if (res[1] instanceof BranchElement) + { + ret = res[1]; + added = new Element[]{ res[0], res[1] }; + } + else + { + ret = createBranchElement(par, null); + added = new Element[]{ res[0], ret, res[1] }; + } } - newParagraph.replace(0, 0, newFracture); - edit = new ElementEdit(newParagraph, 0, new Element[0], - fracture); - documentEvent.addEdit(edit); - fracture = new Element[0]; + par.replace(index, removed.length, added); + addEdit(par, index, removed, added); } + else + { + ret = createBranchElement(par, null); + Element[] added = new Element[]{ ret }; + par.replace(index, 0, added); + addEdit(par, index, new Element[0], added); + } + return ret; } - + /** - * Inserts an end tag into the document structure. This cuts of the - * current paragraph element, possibly fracturing it's child elements. - * The fractured elements are saved so that they can be joined later - * with a new paragraph element. + * Inserts a fracture into the document structure. + * + * @param tag - the element spec. */ - void insertEndTag(ElementSpec tag) + private void insertFracture(ElementSpec tag) { - BranchElement root = (BranchElement) getDefaultRootElement(); - int parIndex = root.getElementIndex(offset); - BranchElement paragraph = (BranchElement) root.getElement(parIndex); - - int index = paragraph.getElementIndex(offset); - LeafElement content = (LeafElement) paragraph.getElement(index); - // We might have to split the element at offset. - split(content, offset); - index = paragraph.getElementIndex(offset); - - int count = paragraph.getElementCount(); - // Store fractured elements. - fracture = new Element[count - index]; - for (int i = index; i < count; ++i) - fracture[i - index] = paragraph.getElement(i); - - // Delete fractured elements. - paragraph.replace(index, count - index, new Element[0]); - - // Add this action to the document event. - ElementEdit edit = new ElementEdit(paragraph, index, fracture, - new Element[0]); - documentEvent.addEdit(edit); + // This is the parent of the paragraph about to be fractured. We will + // create a new child of this parent. + BranchElement parent = (BranchElement) elementStack.peek(); + int parentIndex = parent.getElementIndex(offset); + + // This is the old paragraph. We must remove all its children that + // occur after offset and move them to a new paragraph. We must + // also recreate its child that occurs at offset to have the proper + // end offset. The remainder of this child will also go in the new + // paragraph. + BranchElement previous = (BranchElement) parent.getElement(parentIndex); + + // This is the new paragraph. + BranchElement newBranch = + (BranchElement) createBranchElement(parent, previous.getAttributes()); + + + // The steps we must take to properly fracture are: + // 1. Recreate the LeafElement at offset to have the correct end offset. + // 2. Create a new LeafElement with the remainder of the LeafElement in + // #1 ==> this is whatever was in that LeafElement to the right of the + // inserted newline. + // 3. Find the paragraph at offset and remove all its children that + // occur _after_ offset. These will be moved to the newly created + // paragraph. + // 4. Move the LeafElement created in #2 and all the LeafElements removed + // in #3 to the newly created paragraph. + // 5. Add the new paragraph to the parent. + int previousIndex = previous.getElementIndex(offset); + int numReplaced = previous.getElementCount() - previousIndex; + Element previousLeaf = previous.getElement(previousIndex); + AttributeSet prevLeafAtts = previous.getAttributes(); + + // This recreates the child at offset to have the proper end offset. + // (Step 1). + Element newPreviousLeaf = + createLeafElement(previous, + prevLeafAtts, previousLeaf.getStartOffset(), + offset); + // This creates the new child, which is the remainder of the old child. + // (Step 2). + + Element firstLeafInNewBranch = + createLeafElement(newBranch, prevLeafAtts, + offset, previousLeaf.getEndOffset()); + + // Now we move the new LeafElement and all the old children that occurred + // after the offset to the new paragraph. (Step 4). + Element[] newLeaves = new Element[numReplaced]; + newLeaves[0] = firstLeafInNewBranch; + for (int i = 1; i < numReplaced; i++) + newLeaves[i] = previous.getElement(previousIndex + i); + newBranch.replace(0, 0, newLeaves); + addEdit(newBranch, 0, null, newLeaves); + + // Now we remove the children after the offset from the previous + // paragraph. (Step 3). + int removeSize = previous.getElementCount() - previousIndex; + Element[] add = new Element[] { newPreviousLeaf }; + Element[] remove = new Element[removeSize]; + for (int j = 0; j < removeSize; j++) + remove[j] = previous.getElement(previousIndex + j); + previous.replace(previousIndex, removeSize, add); + addEdit(previous, previousIndex, remove, add); + + // Finally we add the new paragraph to the parent. (Step 5). + Element[] nb = new Element[] { newBranch }; + int index = parentIndex + 1; + parent.replace(index, 0, nb); + addEdit(parent, index, null, nb); } - + /** * Inserts a content element into the document structure. - * + * * @param tag the element spec */ - void insertContentTag(ElementSpec tag) + private void insertContentTag(ElementSpec tag) { + prepareContentInsertion(); int len = tag.getLength(); int dir = tag.getDirection(); + AttributeSet tagAtts = tag.getAttributes(); if (dir == ElementSpec.JoinPreviousDirection) { - Element prev = getCharacterElement(offset); - BranchElement prevParent = (BranchElement) prev.getParentElement(); - Element join = createLeafElement(prevParent, tag.getAttributes(), - prev.getStartOffset(), - Math.max(prev.getEndOffset(), - offset + len)); - int ind = prevParent.getElementIndex(offset); - if (ind == -1) - ind = 0; - Element[] add = new Element[]{join}; - prevParent.replace(ind, 1, add); - - // Add this action to the document event. - ElementEdit edit = new ElementEdit(prevParent, ind, - new Element[]{prev}, add); - documentEvent.addEdit(edit); + // The mauve tests to this class show that a JoinPrevious insertion + // does not add any edits to the document event. To me this means + // that nothing is done here. The previous element naturally should + // expand so that it covers the new characters. } else if (dir == ElementSpec.JoinNextDirection) { - Element next = getCharacterElement(offset + len); - BranchElement nextParent = (BranchElement) next.getParentElement(); - Element join = createLeafElement(nextParent, tag.getAttributes(), - offset, - next.getEndOffset()); - int ind = nextParent.getElementIndex(offset + len); - if (ind == -1) - ind = 0; - Element[] add = new Element[]{join}; - nextParent.replace(ind, 1, add); - - // Add this action to the document event. - ElementEdit edit = new ElementEdit(nextParent, ind, - new Element[]{next}, add); - documentEvent.addEdit(edit); + // FIXME: + // Have to handle JoinNext differently depending on whether + // or not it comes after a fracture. If comes after a fracture, + // the insertFracture method takes care of everything and nothing + // needs to be done here. Otherwise, we need to adjust the + // Element structure. For now, I check if the elementStack's + // top Element is the immediate parent of the LeafElement at + // offset - if so, we did not come immediately after a + // fracture. This seems awkward and should probably be improved. + // We may be doing too much in insertFracture because we are + // adjusting the offsets, the correct thing to do may be to + // create a new branch element and push it on to element stack + // and then this method here can be more general. + + BranchElement paragraph = (BranchElement) elementStack.peek(); + int index = paragraph.getElementIndex(offset); + Element target = paragraph.getElement(index); + if (target.isLeaf() && paragraph.getElementCount() > (index + 1)) + { + Element next = paragraph.getElement(index + 1); + Element newEl1 = createLeafElement(paragraph, + target.getAttributes(), + target.getStartOffset(), + offset); + Element newEl2 = createLeafElement(paragraph, + next.getAttributes(), offset, + next.getEndOffset()); + Element[] add = new Element[] { newEl1, newEl2 }; + paragraph.replace (index, 2, add); + addEdit(paragraph, index, new Element[] { target, next }, add); + } } - else + else if (dir == ElementSpec.OriginateDirection) { - BranchElement par = (BranchElement) getParagraphElement(offset); - - int ind = par.getElementIndex(offset); - - // Make room for the element. - // Cut previous element. - Element prev = par.getElement(ind); - if (prev != null && prev.getStartOffset() < offset) + BranchElement paragraph = (BranchElement) elementStack.peek(); + int index = paragraph.getElementIndex(offset); + Element current = paragraph.getElement(index); + + Element[] added; + Element[] removed = new Element[] {current}; + Element[] splitRes = split(current, offset, length); + if (splitRes[0] == null) { - Element cutPrev = createLeafElement(par, prev.getAttributes(), - prev.getStartOffset(), - offset); - Element[] remove = new Element[]{prev}; - Element[] add = new Element[]{cutPrev}; - if (prev.getEndOffset() > offset + len) - { - Element rem = createLeafElement(par, prev.getAttributes(), - offset + len, - prev.getEndOffset()); - add = new Element[]{cutPrev, rem}; - } - - par.replace(ind, 1, add); - documentEvent.addEdit(new ElementEdit(par, ind, remove, add)); - ind++; + added = new Element[2]; + added[0] = createLeafElement(paragraph, tagAtts, + offset, endOffset); + added[1] = splitRes[1]; + removed = new Element[0]; + index++; } - // ind now points to the next element. - - // Cut next element if necessary. - Element next = par.getElement(ind); - if (next != null && next.getStartOffset() < offset + len) + else if (current.getStartOffset() == offset) + { + // This is if the new insertion happens immediately before + // the current Element. In this case there are 2 + // resulting Elements. + added = new Element[2]; + added[0] = createLeafElement(paragraph, tagAtts, offset, + endOffset); + added[1] = splitRes[1]; + } + else if (current.getEndOffset() == endOffset) { - Element cutNext = createLeafElement(par, next.getAttributes(), - offset + len, - next.getEndOffset()); - Element[] remove = new Element[]{next}; - Element[] add = new Element[]{cutNext}; - par.replace(ind, 1, add); - documentEvent.addEdit(new ElementEdit(par, ind, remove, - add)); + // This is if the new insertion happens right at the end of + // the current Element. In this case there are + // 2 resulting Elements. + added = new Element[2]; + added[0] = splitRes[0]; + added[1] = createLeafElement(paragraph, tagAtts, offset, + endOffset); } - - // Insert new element. - Element newEl = createLeafElement(par, tag.getAttributes(), - offset, offset + len); - Element[] added = new Element[]{newEl}; - par.replace(ind, 0, added); - // Add this action to the document event. - ElementEdit edit = new ElementEdit(par, ind, new Element[0], - added); - documentEvent.addEdit(edit); + else + { + // This is if the new insertion is in the middle of the + // current Element. In this case + // there will be 3 resulting Elements. + added = new Element[3]; + added[0] = splitRes[0]; + added[1] = createLeafElement(paragraph, tagAtts, offset, + endOffset); + added[2] = splitRes[1]; + } + paragraph.replace(index, removed.length, added); + addEdit(paragraph, index, removed, added); } offset += len; } @@ -800,6 +1152,79 @@ public class DefaultStyledDocument extends AbstractDocument result.replace(0, 0, children); return result; } + + /** + * Adds an ElementChange for a given element modification to the document + * event. If there already is an ElementChange registered for this element, + * this method tries to merge the ElementChanges together. However, this + * is only possible if the indices of the new and old ElementChange are + * equal. + * + * @param e the element + * @param i the index of the change + * @param removed the removed elements, or null + * @param added the added elements, or null + */ + private void addEdit(Element e, int i, Element[] removed, Element[] added) + { + // Perform sanity check first. + DocumentEvent.ElementChange ec = documentEvent.getChange(e); + + // Merge the existing stuff with the new stuff. + Element[] oldAdded = ec == null ? null: ec.getChildrenAdded(); + Element[] newAdded; + if (oldAdded != null && added != null) + { + if (ec.getIndex() <= i) + { + int index = i - ec.getIndex(); + // Merge adds together. + newAdded = new Element[oldAdded.length + added.length]; + System.arraycopy(oldAdded, 0, newAdded, 0, index); + System.arraycopy(added, 0, newAdded, index, added.length); + System.arraycopy(oldAdded, index, newAdded, index + added.length, + oldAdded.length - index); + i = ec.getIndex(); + } + else + throw new AssertionError("Not yet implemented case."); + } + else if (added != null) + newAdded = added; + else if (oldAdded != null) + newAdded = oldAdded; + else + newAdded = new Element[0]; + + Element[] oldRemoved = ec == null ? null: ec.getChildrenRemoved(); + Element[] newRemoved; + if (oldRemoved != null && removed != null) + { + if (ec.getIndex() <= i) + { + int index = i - ec.getIndex(); + // Merge removes together. + newRemoved = new Element[oldRemoved.length + removed.length]; + System.arraycopy(oldAdded, 0, newRemoved, 0, index); + System.arraycopy(removed, 0, newRemoved, index, removed.length); + System.arraycopy(oldRemoved, index, newRemoved, + index + removed.length, + oldRemoved.length - index); + i = ec.getIndex(); + } + else + throw new AssertionError("Not yet implemented case."); + } + else if (removed != null) + newRemoved = removed; + else if (oldRemoved != null) + newRemoved = oldRemoved; + else + newRemoved = new Element[0]; + + // Replace the existing edit for the element with the merged. + documentEvent.addEdit(new ElementEdit(e, i, newRemoved, newAdded)); + } } /** @@ -824,7 +1249,7 @@ public class DefaultStyledDocument extends AbstractDocument */ public String getName() { - return "section"; + return SectionElementName; } } @@ -945,9 +1370,7 @@ public class DefaultStyledDocument extends AbstractDocument // Use createBranchElement() and createLeafElement instead. SectionElement section = new SectionElement(); - BranchElement paragraph = - (BranchElement) createBranchElement(section, null); - paragraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE)); + BranchElement paragraph = new BranchElement(section, null); tmp = new Element[1]; tmp[0] = paragraph; section.replace(0, 0, tmp); @@ -1043,7 +1466,11 @@ public class DefaultStyledDocument extends AbstractDocument { Element paragraph = getParagraphElement(position); AttributeSet attributes = paragraph.getAttributes(); - return (Style) attributes.getResolveParent(); + AttributeSet a = attributes.getResolveParent(); + // If the resolve parent is not of type Style, we return null. + if (a instanceof Style) + return (Style) a; + return null; } /** @@ -1112,50 +1539,54 @@ public class DefaultStyledDocument extends AbstractDocument AttributeSet attributes, boolean replace) { - DefaultDocumentEvent ev = - new DefaultDocumentEvent(offset, length, - DocumentEvent.EventType.CHANGE); - - // Modify the element structure so that the interval begins at an element - // start and ends at an element end. - buffer.change(offset, length, ev); - - Element root = getDefaultRootElement(); - // Visit all paragraph elements within the specified interval - int paragraphCount = root.getElementCount(); - for (int pindex = 0; pindex < paragraphCount; pindex++) + // Exit early if length is 0, so no DocumentEvent is created or fired. + if (length == 0) + return; + try { - Element paragraph = root.getElement(pindex); - // Skip paragraphs that lie outside the interval. - if ((paragraph.getStartOffset() > offset + length) - || (paragraph.getEndOffset() < offset)) - continue; - - // Visit content elements within this paragraph - int contentCount = paragraph.getElementCount(); - for (int cindex = 0; cindex < contentCount; cindex++) + // Must obtain a write lock for this method. writeLock() and + // writeUnlock() should always be in try/finally block to make + // sure that locking happens in a balanced manner. + writeLock(); + DefaultDocumentEvent ev = + new DefaultDocumentEvent( + offset, + length, + DocumentEvent.EventType.CHANGE); + + // Modify the element structure so that the interval begins at an + // element + // 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; + for (int pos = offset; pos < end; ) { - Element content = paragraph.getElement(cindex); - // Skip content that lies outside the interval. - if ((content.getStartOffset() > offset + length) - || (content.getEndOffset() < offset)) - continue; - - if (content instanceof AbstractElement) - { - AbstractElement el = (AbstractElement) content; - if (replace) - el.removeAttributes(el); - el.addAttributes(attributes); - } - else - throw new AssertionError("content elements are expected to be" - + "instances of " - + "javax.swing.text.AbstractDocument.AbstractElement"); + // Get the CharacterElement at offset pos. + curr = getCharacterElement(pos); + if (pos == curr.getEndOffset()) + break; + + MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes(); + ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace)); + // If replace is true, remove all the old attributes. + if (replace) + a.removeAttributes(a); + // Add all the new attributes. + a.addAttributes(attributes); + // Increment pos so we can check the next CharacterElement. + pos = curr.getEndOffset(); } + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + finally + { + writeUnlock(); } - - fireChangedUpdate(ev); } /** @@ -1167,14 +1598,36 @@ public class DefaultStyledDocument extends AbstractDocument public void setLogicalStyle(int position, Style style) { Element el = getParagraphElement(position); - if (el instanceof AbstractElement) - { - AbstractElement ael = (AbstractElement) el; - ael.setResolveParent(style); - } - else - throw new AssertionError("paragraph elements are expected to be" - + "instances of javax.swing.text.AbstractDocument.AbstractElement"); + // getParagraphElement doesn't return null but subclasses might so + // we check for null here. + if (el == null) + return; + try + { + writeLock(); + if (el instanceof AbstractElement) + { + AbstractElement ael = (AbstractElement) el; + ael.setResolveParent(style); + int start = el.getStartOffset(); + int end = el.getEndOffset(); + DefaultDocumentEvent ev = + new DefaultDocumentEvent ( + start, + end - start, + DocumentEvent.EventType.CHANGE); + // FIXME: Add an UndoableEdit to this event and fire it. + fireChangedUpdate(ev); + } + else + throw new + AssertionError("paragraph elements are expected to be" + + "instances of AbstractDocument.AbstractElement"); + } + finally + { + writeUnlock(); + } } /** @@ -1190,15 +1643,47 @@ public class DefaultStyledDocument extends AbstractDocument AttributeSet attributes, boolean replace) { - int index = offset; - while (index < offset + length) + try + { + // Must obtain a write lock for this method. writeLock() and + // writeUnlock() should always be in try/finally blocks to make + // sure that locking occurs in a balanced manner. + writeLock(); + + // Create a DocumentEvent to use for changedUpdate(). + DefaultDocumentEvent ev = + new DefaultDocumentEvent ( + offset, + length, + DocumentEvent.EventType.CHANGE); + + // Have to iterate through all the _paragraph_ elements that are + // contained or partially contained in the interval + // (offset, offset + length). + Element rootElement = getDefaultRootElement(); + int startElement = rootElement.getElementIndex(offset); + int endElement = rootElement.getElementIndex(offset + length - 1); + if (endElement < startElement) + endElement = startElement; + + for (int i = startElement; i <= endElement; i++) + { + Element par = rootElement.getElement(i); + MutableAttributeSet a = (MutableAttributeSet) par.getAttributes(); + // Add the change to the DocumentEvent. + ev.addEdit(new AttributeUndoableEdit(par, attributes, replace)); + // If replace is true remove the old attributes. + if (replace) + a.removeAttributes(a); + // Add the new attributes. + a.addAttributes(attributes); + } + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + finally { - AbstractElement par = (AbstractElement) getParagraphElement(index); - AttributeContext ctx = getAttributeContext(); - if (replace) - par.removeAttributes(par); - par.addAttributes(attributes); - index = par.getElementCount(); + writeUnlock(); } } @@ -1212,9 +1697,14 @@ public class DefaultStyledDocument extends AbstractDocument protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr) { super.insertUpdate(ev, attr); + // If the attribute set is null, use an empty attribute set. + 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(); try { @@ -1229,66 +1719,141 @@ public class DefaultStyledDocument extends AbstractDocument 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 next = getCharacterElement(endOffset); + Element prevParagraph = getParagraphElement(offset); + Element paragraph = getParagraphElement(endOffset); + + int segmentEnd = txt.offset + txt.count; + + // Check to see if we're inserting immediately after a newline. + if (offset > 0) + { + try + { + String s = getText(offset - 1, 1); + if (s.equals("\n")) + { + finalStartDirection = + handleInsertAfterNewline(specs, offset, endOffset, + prevParagraph, + paragraph, + paragraphAttributes); + + prevCharWasNewline = true; + // Find the final start tag from the ones just created. + for (int i = 0; i < specs.size(); i++) + if (((ElementSpec) specs.get(i)).getType() + == ElementSpec.StartTagType) + finalStartTag = (ElementSpec)specs.get(i); + } + } + catch (BadLocationException ble) + { + // This shouldn't happen. + AssertionError ae = new AssertionError(); + ae.initCause(ble); + throw ae; + } + } - for (int i = offset; i < endOffset; ++i) + + for (int i = txt.offset; i < segmentEnd; ++i) { len++; if (txt.array[i] == '\n') { - ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType, - len); - - // If we are at the last index, then check if we could probably be - // joined with the next element. - if (i == endOffset - 1) - { - if (next.getAttributes().isEqual(attr)) - spec.setDirection(ElementSpec.JoinNextDirection); - } - // If we are at the first new element, then check if it could be - // joined with the previous element. - else if (specs.size() == 0) - { - if (prev.getAttributes().isEqual(attr)) - spec.setDirection(ElementSpec.JoinPreviousDirection); - } - - specs.add(spec); + // Add the ElementSpec for the content. + specs.add(new ElementSpec(attr, ElementSpec.ContentType, len)); // Add ElementSpecs for the newline. - ElementSpec endTag = new ElementSpec(null, ElementSpec.EndTagType); - specs.add(endTag); - ElementSpec startTag = new ElementSpec(null, + specs.add(new ElementSpec(null, ElementSpec.EndTagType)); + finalStartTag = new ElementSpec(paragraphAttributes, ElementSpec.StartTagType); - startTag.setDirection(ElementSpec.JoinFractureDirection); - specs.add(startTag); - + specs.add(finalStartTag); len = 0; - offset += len; } } // Create last element if last character hasn't been a newline. - if (len > 0) + if (len > 0) + specs.add(new ElementSpec(attr, ElementSpec.ContentType, len)); + + // Set the direction of the last spec of type StartTagType. + // If we are inserting after a newline then this value comes from + // handleInsertAfterNewline. + if (finalStartTag != null) + { + if (prevCharWasNewline) + finalStartTag.setDirection(finalStartDirection); + else if (prevParagraph.getEndOffset() != endOffset) + { + try + { + String last = getText(endOffset - 1, 1); + if (!last.equals("\n")) + finalStartTag.setDirection(ElementSpec.JoinFractureDirection); + } + catch (BadLocationException ble) + { + // This shouldn't happen. + AssertionError ae = new AssertionError(); + ae.initCause(ble); + throw ae; + } + } + 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); + } + } + + // 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) { - ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType, len); - // If we are at the first new element, then check if it could be - // joined with the previous element. - if (specs.size() == 0) + Element currentRun = + prevParagraph.getElement(prevParagraph.getElementIndex(offset)); + if (currentRun.getEndOffset() == endOffset) { - if (prev.getAttributes().isEqual(attr)) - spec.setDirection(ElementSpec.JoinPreviousDirection); + if (endOffset < getLength() && next.getAttributes().isEqual(attr) + && last.getType() == ElementSpec.ContentType) + last.setDirection(ElementSpec.JoinNextDirection); + } + else + { + if (finalStartTag != null + && finalStartTag.getDirection() == + ElementSpec.JoinFractureDirection + && currentRun.getAttributes().isEqual(attr)) + { + last.setDirection(ElementSpec.JoinNextDirection); + } } - // Check if we could probably be joined with the next element. - else if (next.getAttributes().isEqual(attr)) - spec.setDirection(ElementSpec.JoinNextDirection); - - specs.add(spec); } - + + // 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()]); @@ -1296,6 +1861,47 @@ public class DefaultStyledDocument extends AbstractDocument } /** + * A helper method to set up the ElementSpec buffer for the special + * case of an insertion occurring immediately after a newline. + * @param specs the ElementSpec buffer to initialize. + */ + short handleInsertAfterNewline(Vector specs, int offset, int endOffset, + Element prevParagraph, Element paragraph, + AttributeSet a) + { + if (prevParagraph.getParentElement() == paragraph.getParentElement()) + { + specs.add(new ElementSpec(a, ElementSpec.EndTagType)); + specs.add(new ElementSpec(a, ElementSpec.StartTagType)); + if (prevParagraph.getEndOffset() != endOffset) + return ElementSpec.JoinFractureDirection; + // If there is an Element after this one, use JoinNextDirection. + Element parent = paragraph.getParentElement(); + if (parent.getElementCount() > parent.getElementIndex(offset) + 1) + return ElementSpec.JoinNextDirection; + } + else + { + // TODO: What to do here? + } + return ElementSpec.OriginateDirection; + } + + /** + * Updates the document structure in response to text removal. This is + * forwarded to the {@link ElementBuffer} of this document. Any changes to + * the document structure are added to the specified document event and + * sent to registered listeners. + * + * @param ev the document event that records the changes to the document + */ + protected void removeUpdate(DefaultDocumentEvent ev) + { + super.removeUpdate(ev); + buffer.remove(ev.getOffset(), ev.getLength(), ev); + } + + /** * Returns an enumeration of all style names. * * @return an enumeration of all style names @@ -1316,6 +1922,35 @@ public class DefaultStyledDocument extends AbstractDocument // Nothing to do here. This is intended to be overridden by subclasses. } + void printElements (Element start, int pad) + { + for (int i = 0; i < pad; i++) + System.out.print(" "); + if (pad == 0) + System.out.println ("ROOT ELEMENT ("+start.getStartOffset()+", "+start.getEndOffset()+")"); + else if (start instanceof AbstractDocument.BranchElement) + System.out.println ("BranchElement ("+start.getStartOffset()+", "+start.getEndOffset()+")"); + else + { + { + try + { + System.out.println ("LeafElement ("+start.getStartOffset()+", " + + start.getEndOffset()+"): "+ + start.getDocument(). + getText(start.getStartOffset(), + start.getEndOffset() - + start.getStartOffset())); + } + catch (BadLocationException ble) + { + } + } + } + for (int i = 0; i < start.getElementCount(); i ++) + printElements (start.getElement(i), pad+3); + } + /** * Inserts a bulk of structured content at once. * @@ -1325,36 +1960,53 @@ public class DefaultStyledDocument extends AbstractDocument protected void insert(int offset, ElementSpec[] data) throws BadLocationException { - writeLock(); - // First we insert the content. - int index = offset; - for (int i = 0; i < data.length; i++) + if (data == null || data.length == 0) + return; + try { - ElementSpec spec = data[i]; - if (spec.getArray() != null && spec.getLength() > 0) + // writeLock() and writeUnlock() should always be in a try/finally + // block so that locking balance is guaranteed even if some + // exception is thrown. + writeLock(); + + // First we collect the content to be inserted. + StringBuffer contentBuffer = new StringBuffer(); + for (int i = 0; i < data.length; i++) { - String insertString = new String(spec.getArray(), spec.getOffset(), - spec.getLength()); - content.insertString(index, insertString); + // Collect all inserts into one so we can get the correct + // ElementEdit + ElementSpec spec = data[i]; + if (spec.getArray() != null && spec.getLength() > 0) + contentBuffer.append(spec.getArray(), spec.getOffset(), + spec.getLength()); } - index += spec.getLength(); + + int length = contentBuffer.length(); + + // If there was no content inserted then exit early. + if (length == 0) + return; + + UndoableEdit edit = content.insertString(offset, + contentBuffer.toString()); + + // Create the DocumentEvent with the ElementEdit added + DefaultDocumentEvent ev = + new DefaultDocumentEvent(offset, + length, + DocumentEvent.EventType.INSERT); + ev.addEdit(edit); + + // Finally we must update the document structure and fire the insert + // update event. + buffer.insert(offset, length, data, ev); + fireInsertUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); } - // Update the view structure. - DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, index - offset, - DocumentEvent.EventType.INSERT); - for (int i = 0; i < data.length; i++) + finally { - ElementSpec spec = data[i]; - AttributeSet atts = spec.getAttributes(); - if (atts != null) - insertUpdate(ev, atts); + writeUnlock(); } - - // Finally we must update the document structure and fire the insert update - // event. - buffer.insert(offset, index - offset, data, ev); - fireInsertUpdate(ev); - writeUnlock(); } /** @@ -1382,4 +2034,9 @@ public class DefaultStyledDocument extends AbstractDocument throw err; } } + + static boolean attributeSetsAreSame (AttributeSet a, AttributeSet b) + { + return (a == null && b == null) || (a != null && a.isEqual(b)); + } } diff --git a/libjava/classpath/javax/swing/text/FlowView.java b/libjava/classpath/javax/swing/text/FlowView.java index 765f515..6d4b9cd 100644 --- a/libjava/classpath/javax/swing/text/FlowView.java +++ b/libjava/classpath/javax/swing/text/FlowView.java @@ -396,37 +396,6 @@ public abstract class FlowView extends BoxView throw new AssertionError("This method must not be called in " + "LogicalView."); } - - /** - * Returns the document position that is (visually) nearest to the given - * document position pos in the given direction d. - * - * @param c the text component - * @param pos the document position - * @param b the bias for pos - * @param d the direction, must be either {@link SwingConstants#NORTH}, - * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or - * {@link SwingConstants#EAST} - * @param biasRet an array of {@link Position.Bias} that can hold at least - * one element, which is filled with the bias of the return position - * on method exit - * - * @return the document position that is (visually) nearest to the given - * document position pos in the given direction - * d - * - * @throws BadLocationException if pos is not a valid offset in - * the document model - */ - public int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException - { - assert false : "getNextVisualPositionFrom() must not be called in " - + "LogicalView"; - return 0; - } } /** diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java index 4c65de0..80dcfa5 100644 --- a/libjava/classpath/javax/swing/text/GapContent.java +++ b/libjava/classpath/javax/swing/text/GapContent.java @@ -64,6 +64,7 @@ import javax.swing.undo.UndoableEdit; public class GapContent implements AbstractDocument.Content, Serializable { + /** * A {@link Position} implementation for GapContent. */ @@ -100,15 +101,15 @@ public class GapContent public int compareTo(Object o) { if (o instanceof Integer) - { - int otherMark = ((Integer) o).intValue(); - return mark - otherMark; - } + { + int otherMark = ((Integer) o).intValue(); + return mark - otherMark; + } else - { - GapContentPosition other = (GapContentPosition) o; - return mark - other.mark; - } + { + GapContentPosition other = (GapContentPosition) o; + return mark - other.mark; + } } /** @@ -122,7 +123,6 @@ public class GapContent assert mark <= gapStart || mark >= gapEnd : "mark: " + mark + ", gapStart: " + gapStart + ", gapEnd: " + gapEnd; - if (mark <= gapStart) return mark; else @@ -130,11 +130,11 @@ public class GapContent } } - class UndoInsertString extends AbstractUndoableEdit + class InsertUndo extends AbstractUndoableEdit { public int where, length; String text; - public UndoInsertString(int start, int len) + public InsertUndo(int start, int len) { where = start; length = len; @@ -316,7 +316,7 @@ public class GapContent replace(where, 0, str.toCharArray(), strLen); - return new UndoInsertString(where, strLen); + return new InsertUndo(where, strLen); } /** @@ -449,7 +449,7 @@ public class GapContent // We store the actual array index in the GapContentPosition. The real // offset is then calculated in the GapContentPosition. int mark = offset; - if (offset > gapStart) + if (offset >= gapStart) mark += gapEnd - gapStart; GapContentPosition pos = new GapContentPosition(mark); @@ -584,8 +584,9 @@ public class GapContent { if (gapStart != position) shiftGap(position); + // Remove content - if (rmSize > 0) + if (rmSize > 0) shiftGapEndUp(gapEnd + rmSize); // If gap is too small, enlarge the gap. @@ -644,6 +645,13 @@ public class GapContent new GapContentPosition(offset)); if (index1 < 0) index1 = -(index1 + 1); + + // Search the first index with the specified offset. The binarySearch does + // not necessarily find the first one. + while (index1 > 0 + && ((GapContentPosition) positions.get(index1 - 1)).mark == offset) + index1--; + for (ListIterator i = positions.listIterator(index1); i.hasNext();) { GapContentPosition p = (GapContentPosition) i.next(); @@ -672,6 +680,13 @@ public class GapContent new GapContentPosition(offset)); if (index1 < 0) index1 = -(index1 + 1); + + // Search the first index with the specified offset. The binarySearch does + // not necessarily find the first one. + while (index1 > 0 + && ((GapContentPosition) positions.get(index1 - 1)).mark == offset) + index1--; + for (ListIterator i = positions.listIterator(index1); i.hasNext();) { GapContentPosition p = (GapContentPosition) i.next(); @@ -700,6 +715,12 @@ public class GapContent new GapContentPosition(offset)); if (index1 < 0) index1 = -(index1 + 1); + + // Search the first index with the specified offset. The binarySearch does + // not necessarily find the first one. + while (index1 > 0 + && ((GapContentPosition) positions.get(index1 - 1)).mark == offset) + index1--; for (ListIterator i = positions.listIterator(index1); i.hasNext();) { GapContentPosition p = (GapContentPosition) i.next(); diff --git a/libjava/classpath/javax/swing/text/GlyphView.java b/libjava/classpath/javax/swing/text/GlyphView.java index d3dd75e..47deb50 100644 --- a/libjava/classpath/javax/swing/text/GlyphView.java +++ b/libjava/classpath/javax/swing/text/GlyphView.java @@ -1057,34 +1057,4 @@ public class GlyphView extends View implements TabableView, Cloneable return painter.getNextVisualPositionFrom(this, pos, bias, a, direction, biasRet); } - - /** - * Returns the document position that is (visually) nearest to the given - * document position pos in the given direction d. - * - * @param c the text component - * @param pos the document position - * @param b the bias for pos - * @param d the direction, must be either {@link SwingConstants#NORTH}, - * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or - * {@link SwingConstants#EAST} - * @param biasRet an array of {@link Position.Bias} that can hold at least - * one element, which is filled with the bias of the return position - * on method exit - * - * @return the document position that is (visually) nearest to the given - * document position pos in the given direction - * d - * - * @throws BadLocationException if pos is not a valid offset in - * the document model - */ - public int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException - { - // TODO: Implement this properly. - throw new AssertionError("Not implemented yet."); - } } diff --git a/libjava/classpath/javax/swing/text/IconView.java b/libjava/classpath/javax/swing/text/IconView.java index 86c27dd..af2581a 100644 --- a/libjava/classpath/javax/swing/text/IconView.java +++ b/libjava/classpath/javax/swing/text/IconView.java @@ -158,34 +158,4 @@ public class IconView return el.getStartOffset(); } - /** - * Returns the document position that is (visually) nearest to the given - * document position pos in the given direction d. - * - * @param c the text component - * @param pos the document position - * @param b the bias for pos - * @param d the direction, must be either {@link SwingConstants#NORTH}, - * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or - * {@link SwingConstants#EAST} - * @param biasRet an array of {@link Position.Bias} that can hold at least - * one element, which is filled with the bias of the return position - * on method exit - * - * @return the document position that is (visually) nearest to the given - * document position pos in the given direction - * d - * - * @throws BadLocationException if pos is not a valid offset in - * the document model - */ - public int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException - { - // TODO: Implement this properly. - throw new AssertionError("Not implemented yet."); - } - } diff --git a/libjava/classpath/javax/swing/text/InternationalFormatter.java b/libjava/classpath/javax/swing/text/InternationalFormatter.java index 86300a7..ba3cffa 100644 --- a/libjava/classpath/javax/swing/text/InternationalFormatter.java +++ b/libjava/classpath/javax/swing/text/InternationalFormatter.java @@ -78,6 +78,8 @@ public class InternationalFormatter minimum = null; maximum = null; format = null; + setCommitsOnValidEdit(false); + setOverwriteMode(false); } /** @@ -226,6 +228,8 @@ public class InternationalFormatter public String valueToString(Object value) throws ParseException { + if (value == null) + return ""; if (format != null) return format.format(value); else diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java index 83966bb..afa1f24 100644 --- a/libjava/classpath/javax/swing/text/JTextComponent.java +++ b/libjava/classpath/javax/swing/text/JTextComponent.java @@ -380,12 +380,18 @@ public abstract class JTextComponent extends JComponent public KeyStroke[] allKeys() { KeyStroke[] superKeys = super.allKeys(); - KeyStroke[] mapKeys = map.getBoundKeyStrokes(); - KeyStroke[] bothKeys = new KeyStroke[superKeys.length + mapKeys.length]; - for (int i = 0; i < superKeys.length; ++i) + KeyStroke[] mapKeys = map.getBoundKeyStrokes(); + int skl = 0; + int mkl = 0; + if (superKeys != null) + skl = superKeys.length; + if (mapKeys != null) + mkl = mapKeys.length; + KeyStroke[] bothKeys = new KeyStroke[skl + mkl]; + for (int i = 0; i < skl; ++i) bothKeys[i] = superKeys[i]; - for (int i = 0; i < mapKeys.length; ++i) - bothKeys[i + superKeys.length] = mapKeys[i]; + for (int i = 0; i < mkl; ++i) + bothKeys[i + skl] = mapKeys[i]; return bothKeys; } } @@ -864,7 +870,7 @@ public abstract class JTextComponent extends JComponent Hashtable acts = new Hashtable(actions.length); for (int i = 0; i < actions.length; ++i) acts.put(actions[i].getValue(Action.NAME), actions[i]); - for (int i = 0; i < bindings.length; ++i) + for (int i = 0; i < bindings.length; ++i) if (acts.containsKey(bindings[i].actionName)) map.addActionForKeyStroke(bindings[i].key, (Action) acts.get(bindings[i].actionName)); } @@ -906,33 +912,16 @@ public abstract class JTextComponent extends JComponent public JTextComponent() { Keymap defkeymap = getKeymap(DEFAULT_KEYMAP); - boolean creatingKeymap = false; if (defkeymap == null) { defkeymap = addKeymap(DEFAULT_KEYMAP, null); defkeymap.setDefaultAction(new DefaultEditorKit.DefaultKeyTypedAction()); - creatingKeymap = true; } setFocusable(true); setEditable(true); enableEvents(AWTEvent.KEY_EVENT_MASK); updateUI(); - - // need to do this after updateUI() - if (creatingKeymap) - loadKeymap(defkeymap, - new KeyBinding[] { - new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), - DefaultEditorKit.backwardAction), - new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), - DefaultEditorKit.forwardAction), - new KeyBinding(KeyStroke.getKeyStroke("typed \b"), - DefaultEditorKit.deletePrevCharAction), - new KeyBinding(KeyStroke.getKeyStroke("typed \u007f"), - DefaultEditorKit.deleteNextCharAction) - }, - getActions()); } public void setDocument(Document newDoc) diff --git a/libjava/classpath/javax/swing/text/MaskFormatter.java b/libjava/classpath/javax/swing/text/MaskFormatter.java new file mode 100644 index 0000000..d12b9ea --- /dev/null +++ b/libjava/classpath/javax/swing/text/MaskFormatter.java @@ -0,0 +1,583 @@ +/* MaskFormatter.java -- + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.text.ParseException; + +import javax.swing.JFormattedTextField; + +/** + * @author Anthony Balkissoon abalkiss at redhat dot com + * + */ +public class MaskFormatter extends DefaultFormatter +{ + // The declaration of the valid mask characters + private static final char NUM_CHAR = '#'; + private static final char ESCAPE_CHAR = '\''; + private static final char UPPERCASE_CHAR = 'U'; + private static final char LOWERCASE_CHAR = 'L'; + private static final char ALPHANUM_CHAR = 'A'; + private static final char LETTER_CHAR = '?'; + private static final char ANYTHING_CHAR = '*'; + private static final char HEX_CHAR = 'H'; + + /** The mask for this MaskFormatter **/ + private String mask; + + /** + * A String made up of the characters that are not valid for input for + * this MaskFormatter. + */ + private String invalidChars; + + /** + * A String made up of the characters that are valid for input for + * this MaskFormatter. + */ + private String validChars; + + /** A String used in place of missing chracters if the value does not + * completely fill in the spaces in the mask. + */ + private String placeHolder; + + /** A character used in place of missing characters if the value does + * not completely fill in the spaces in the mask. + */ + private char placeHolderChar = ' '; + + /** + * Whether or not stringToValue should return literal characters in the mask. + */ + private boolean valueContainsLiteralCharacters = true; + + /** A String used for easy access to valid HEX characters **/ + private static String hexString = "0123456789abcdefABCDEF"; + + /** An int to hold the length of the mask, accounting for escaped characters **/ + int maskLength = 0; + + public MaskFormatter () + { + // Override super's default behaviour, in MaskFormatter the default + // is not to allow invalid values + setAllowsInvalid(false); + } + + /** + * Creates a MaskFormatter with the specified mask. + * @specnote doesn't actually throw a ParseException although it + * is declared to do so + * @param mask + * @throws java.text.ParseException + */ + 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); + setMask (mask); + } + + /** + * Returns the mask used in this MaskFormatter. + * @return the mask used in this MaskFormatter. + */ + public String getMask() + { + return mask; + } + + /** + * Returns a String containing the characters that are not valid for input + * for this MaskFormatter. + * @return a String containing the invalid characters. + */ + public String getInvalidCharacters() + { + return invalidChars; + } + + /** + * Sets characters that are not valid for input. If + * invalidCharacters is non-null then no characters contained + * in it will be allowed to be input. + * + * @param invalidCharacters the String specifying invalid characters. + */ + public void setInvalidCharacters (String invalidCharacters) + { + this.invalidChars = invalidCharacters; + } + + /** + * Returns a String containing the characters that are valid for input + * for this MaskFormatter. + * @return a String containing the valid characters. + */ + public String getValidCharacters() + { + return validChars; + } + + /** + * Sets characters that are valid for input. If + * validCharacters is non-null then no characters that are + * not contained in it will be allowed to be input. + * + * @param validCharacters the String specifying valid characters. + */ + public void setValidCharacters (String validCharacters) + { + this.validChars = validCharacters; + } + + /** + * Returns the place holder String that is used in place of missing + * characters when the value doesn't completely fill in the spaces + * in the mask. + * @return the place holder String. + */ + public String getPlaceholder() + { + return placeHolder; + } + + /** + * Sets the string to use if the value does not completely fill in the mask. + * If this is null, the place holder character will be used instead. + * @param placeholder the String to use if the value doesn't completely + * fill in the mask. + */ + public void setPlaceholder (String placeholder) + { + this.placeHolder = placeholder; + } + + /** + * Returns the character used in place of missing characters when the + * value doesn't completely fill the mask. + * @return the place holder character + */ + public char getPlaceholderCharacter() + { + return placeHolderChar; + } + + /** + * Sets the char to use if the value does not completely fill in the mask. + * This is only used if the place holder String has not been set or does + * not completely fill in the mask. + * @param placeholder the char to use if the value doesn't completely + * fill in the mask. + */ + public void setPlaceholderCharacter (char placeholder) + { + this.placeHolderChar = placeholder; + } + + /** + * Returns true if stringToValue should return the literal + * characters in the mask. + * @return true if stringToValue should return the literal + * characters in the mask + */ + public boolean getValueContainsLiteralCharacters() + { + return valueContainsLiteralCharacters; + } + + /** + * Determines whether stringToValue will return literal characters or not. + * @param containsLiteralChars if true, stringToValue will return the + * literal characters in the mask, otherwise it will not. + */ + public void setValueContainsLiteralCharacters (boolean containsLiteralChars) + { + this.valueContainsLiteralCharacters = containsLiteralChars; + } + + /** + * Sets the mask for this MaskFormatter. + * @specnote doesn't actually throw a ParseException even though it is + * declared to do so + * @param mask the new mask for this MaskFormatter + * @throws ParseException if mask is not valid. + */ + public void setMask (String mask) throws ParseException + { + this.mask = mask; + + // Update the cached maskLength. + int end = mask.length() - 1; + maskLength = 0; + for (int i = 0; i <= end; i++) + { + // Handle escape characters properly - they don't add to the maskLength + // but 2 escape characters in a row is really one escape character and + // one literal single quote, so that does add 1 to the maskLength. + if (mask.charAt(i) == '\'') + { + // Escape characters at the end of the mask don't do anything. + if (i != end) + maskLength++; + i++; + } + else + maskLength++; + } + } + + /** + * Installs this MaskFormatter on the JFormattedTextField. + * Invokes valueToString to convert the current value from the + * JFormattedTextField to a String, then installs the Actions from + * getActions, the DocumentFilter from getDocumentFilter, and the + * NavigationFilter from getNavigationFilter. + * + * If valueToString throws a ParseException, this method sets the text + * to an empty String and marks the JFormattedTextField as invalid. + */ + public void install (JFormattedTextField ftf) + { + super.install(ftf); + if (ftf != null) + { + try + { + valueToString(ftf.getValue()); + } + catch (ParseException pe) + { + // Set the text to an empty String and mark the JFormattedTextField + // as invalid. + ftf.setText(""); + setEditValid(false); + } + } + } + + /** + * Parses the text using the mask, valid characters, and invalid characters + * to determine the appropriate Object to return. This strips the literal + * characters if necessary and invokes super.stringToValue. If the paramter + * is invalid for the current mask and valid/invalid character sets this + * method will throw a ParseException. + * + * @param value the String to parse + * @throws ParseException if value doesn't match the mask and valid/invalid + * character sets + */ + 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); + } + + /** + * Strips the literal characters from the given String. + * @param value the String to strip + * @return the stripped String + */ + String stripLiterals(String value) + { + StringBuffer result = new StringBuffer(); + for (int i = 0; i < value.length(); i++) + { + // Only append the characters that don't correspond to literal + // characters in the mask. + switch (mask.charAt(i)) + { + case NUM_CHAR: + case UPPERCASE_CHAR: + case LOWERCASE_CHAR: + case ALPHANUM_CHAR: + case LETTER_CHAR: + case HEX_CHAR: + case ANYTHING_CHAR: + result.append(value.charAt(i)); + break; + default: + } + } + return result.toString(); + } + + /** + * Returns a String representation of the Object value based on the mask. + * + * @param value the value to convert + * @throws ParseException if value is invalid for this mask and valid/invalid + * character sets + */ + 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; + } + + /** + * This method takes in a String and runs it through the mask to make + * sure that it is valid. If convert 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 + { + 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); + + for (int i = 0, j = 0; i < value.length(); i++, j++) + { + literal = false; + resultChar = result.charAt(i); + // This switch block on the mask character checks that the character + // within value at that point is valid according to the + // mask and also converts to upper/lowercase as needed. + switch (mask.charAt(j)) + { + case NUM_CHAR: + if (!Character.isDigit(resultChar)) + throw new ParseException("Number expected", i); + break; + case UPPERCASE_CHAR: + if (!Character.isLetter(resultChar)) + throw new ParseException("Letter expected", i); + if (convert) + result.setCharAt(i, Character.toUpperCase(resultChar)); + break; + case LOWERCASE_CHAR: + if (!Character.isLetter(resultChar)) + throw new ParseException("Letter expected", i); + if (convert) + result.setCharAt(i, Character.toLowerCase(resultChar)); + break; + case ALPHANUM_CHAR: + if (!Character.isLetterOrDigit(resultChar)) + throw new ParseException("Letter or number expected", i); + break; + case LETTER_CHAR: + if (!Character.isLetter(resultChar)) + throw new ParseException("Letter expected", i); + break; + case HEX_CHAR: + if (hexString.indexOf(resultChar) == -1) + throw new ParseException("Hexadecimal character expected", i); + break; + case ANYTHING_CHAR: + 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); + 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 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 + * i characters then the character at position i + * 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/NumberFormatter.java b/libjava/classpath/javax/swing/text/NumberFormatter.java new file mode 100644 index 0000000..a858ff4 --- /dev/null +++ b/libjava/classpath/javax/swing/text/NumberFormatter.java @@ -0,0 +1,86 @@ +/* NumberFormatter.java -- + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.text.Format; +import java.text.NumberFormat; + +/** + * NumberFormatter is an {@link InternationalFormatter} + * that implements value to string and string to value conversion via + * an instance of {@link NumberFormat}. + * + * @author Anthony Balkissoon abalkiss at redhat dot com + * @since 1.4 + */ +public class NumberFormatter extends InternationalFormatter +{ + + /** + * Creates a NumberFormatter with the default NumberFormat from + * NumberFormat.getNumberInstance(). + */ + public NumberFormatter () + { + this (NumberFormat.getNumberInstance()); + } + + /** + * Creates a NumberFormatter with the specified NumberFormat. + * @param format the NumberFormat to use for this NumberFormatter. + */ + public NumberFormatter (NumberFormat format) + { + super(format); + setFormat(format); + } + + /** + * Sets the NumberFormat that this NumberFormatter will use to determine + * legal values for editing and displaying. + * + * @param format the Format to use to determine legal values. + */ + public void setFormat (Format format) + { + // TODO: This should be different from the super implementation + // but I don't yet know how. + super.setFormat(format); + } +} diff --git a/libjava/classpath/javax/swing/text/PasswordView.java b/libjava/classpath/javax/swing/text/PasswordView.java index c3aa66c..e54331c5 100644 --- a/libjava/classpath/javax/swing/text/PasswordView.java +++ b/libjava/classpath/javax/swing/text/PasswordView.java @@ -41,6 +41,7 @@ package javax.swing.text; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Rectangle; import java.awt.Shape; import javax.swing.JPasswordField; @@ -211,7 +212,10 @@ public class PasswordView /** * Provides a mapping from the document model coordinate space to the * coordinate space of the view mapped to it. - * + * + * This method is overridden to provide a correct mapping with respect to the + * echo char and not to the real content. + * * @param pos - the position to convert >= 0 * @param a - the allocated region to render into * @param b - typesafe enumeration to indicate bias to a position in the model. @@ -222,7 +226,35 @@ public class PasswordView public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { - return super.modelToView(pos, a, b); + Shape newAlloc = adjustAllocation(a); + + // Ensure metrics are up-to-date. + updateMetrics(); + + // Get rectangle of the line containing position. + int lineIndex = getElement().getElementIndex(pos); + Rectangle rect = lineToRect(newAlloc, lineIndex); + + // Get the rectangle for position. + Element line = getElement().getElement(lineIndex); + int lineStart = line.getStartOffset(); + Segment segment = getLineBuffer(); + segment.array = new char[pos - lineStart]; + char echoChar = getEchoChar(); + for (int i = 0; i < segment.array.length; ++i) + segment.array[i] = echoChar; + segment.offset = 0; + segment.count = segment.array.length; + + int xoffset = Utilities.getTabbedTextWidth(segment, metrics, rect.x, + this, lineStart); + + // Calc the real rectangle. + rect.x += xoffset; + rect.width = 1; + rect.height = metrics.getHeight(); + + return rect; } /** @@ -239,6 +271,8 @@ public class PasswordView */ public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) { + // FIXME: This only provides a view->model mapping for the real text + // content and does not respect the echo char. return super.viewToModel(fx, fy, a, bias); } } diff --git a/libjava/classpath/javax/swing/text/PlainDocument.java b/libjava/classpath/javax/swing/text/PlainDocument.java index 0c00a06..2200cae 100644 --- a/libjava/classpath/javax/swing/text/PlainDocument.java +++ b/libjava/classpath/javax/swing/text/PlainDocument.java @@ -113,6 +113,46 @@ public class PlainDocument extends AbstractDocument int elementIndex = rootElement.getElementIndex(offset); Element firstElement = rootElement.getElement(elementIndex); + // If we're inserting immediately after a newline we have to fix the + // Element structure. + if (offset > 0) + { + try + { + String s = getText(offset - 1, 1); + if (s.equals("\n")) + { + int newEl2EndOffset = end; + boolean replaceNext = false; + if (rootElement.getElementCount() > elementIndex + 1) + { + replaceNext = true; + newEl2EndOffset = + rootElement.getElement(elementIndex + 1).getEndOffset(); + } + Element newEl1 = + createLeafElement(rootElement, firstElement.getAttributes(), + firstElement.getStartOffset(), offset); + Element newEl2 = + createLeafElement (rootElement, firstElement.getAttributes(), + offset, newEl2EndOffset); + if (replaceNext) + rootElement.replace(elementIndex, 2, new Element[] { newEl1, newEl2 }); + else + rootElement.replace(elementIndex, 1, new Element[] { newEl1, newEl2 }); + firstElement = newEl2; + elementIndex ++; + } + } + catch (BadLocationException ble) + { + // This shouldn't happen. + AssertionError ae = new AssertionError(); + ae.initCause(ble); + throw ae; + } + } + // added and removed are Element arrays used to add an ElementEdit // to the DocumentEvent if there were entire lines added or removed. Element[] removed = new Element[1]; diff --git a/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java index 9f5ee8ad..a318ee7 100644 --- a/libjava/classpath/javax/swing/text/PlainView.java +++ b/libjava/classpath/javax/swing/text/PlainView.java @@ -185,7 +185,6 @@ public class PlainView extends View implements TabExpander JTextComponent textComponent = (JTextComponent) getContainer(); - g.setFont(textComponent.getFont()); selectedColor = textComponent.getSelectedTextColor(); unselectedColor = textComponent.getForeground(); disabledColor = textComponent.getDisabledTextColor(); @@ -513,7 +512,8 @@ public class PlainView extends View implements TabExpander else { Rectangle repaintRec = rec0.union(rec1); - host.repaint(); + host.repaint(repaintRec.x, repaintRec.y, repaintRec.width, + repaintRec.height); } } @@ -530,35 +530,5 @@ public class PlainView extends View implements TabExpander lineBuffer = new Segment(); return lineBuffer; } - - /** - * Returns the document position that is (visually) nearest to the given - * document position pos in the given direction d. - * - * @param c the text component - * @param pos the document position - * @param b the bias for pos - * @param d the direction, must be either {@link SwingConstants#NORTH}, - * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or - * {@link SwingConstants#EAST} - * @param biasRet an array of {@link Position.Bias} that can hold at least - * one element, which is filled with the bias of the return position - * on method exit - * - * @return the document position that is (visually) nearest to the given - * document position pos in the given direction - * d - * - * @throws BadLocationException if pos is not a valid offset in - * the document model - */ - public int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException - { - // TODO: Implement this properly. - throw new AssertionError("Not implemented yet."); - } } diff --git a/libjava/classpath/javax/swing/text/StyleContext.java b/libjava/classpath/javax/swing/text/StyleContext.java index 6c4e2994..dabc0ba 100644 --- a/libjava/classpath/javax/swing/text/StyleContext.java +++ b/libjava/classpath/javax/swing/text/StyleContext.java @@ -362,9 +362,8 @@ public class StyleContext public boolean isEqual(AttributeSet attr) { - return attr != null - && attr.containsAttributes(this) - && this.containsAttributes(attr); + return getAttributeCount() == attr.getAttributeCount() + && this.containsAttributes(attr); } public String toString() diff --git a/libjava/classpath/javax/swing/text/StyledEditorKit.java b/libjava/classpath/javax/swing/text/StyledEditorKit.java index e71f992..c4eef44 100644 --- a/libjava/classpath/javax/swing/text/StyledEditorKit.java +++ b/libjava/classpath/javax/swing/text/StyledEditorKit.java @@ -67,7 +67,7 @@ public class StyledEditorKit extends DefaultEditorKit */ public UnderlineAction() { - super("TODO"); // TODO: Figure out name for this action. + super("font-underline"); } /** @@ -97,7 +97,7 @@ public class StyledEditorKit extends DefaultEditorKit */ public ItalicAction() { - super("TODO"); // TODO: Figure out correct name of this Action. + super("font-italic"); } /** @@ -127,7 +127,7 @@ public class StyledEditorKit extends DefaultEditorKit */ public BoldAction() { - super("TODO"); // TODO: Figure out correct name of this Action. + super("font-bold"); } /** @@ -585,8 +585,26 @@ public class StyledEditorKit extends DefaultEditorKit public Action[] getActions() { Action[] actions1 = super.getActions(); - Action[] myActions = new Action[] { new BoldAction(), new ItalicAction(), - new UnderlineAction() }; + Action[] myActions = new Action[] { + new FontSizeAction("font-size-8", 8), + new FontSizeAction("font-size-10", 10), + new FontSizeAction("font-size-12", 12), + new FontSizeAction("font-size-14", 14), + new FontSizeAction("font-size-16", 16), + new FontSizeAction("font-size-18", 18), + new FontSizeAction("font-size-24", 24), + new FontSizeAction("font-size-36", 36), + new FontSizeAction("font-size-48", 48), + new FontFamilyAction("font-family-Serif", "Serif"), + new FontFamilyAction("font-family-Monospaced", "Monospaced"), + new FontFamilyAction("font-family-SansSerif", "SansSerif"), + new AlignmentAction("left-justify", StyleConstants.ALIGN_LEFT), + new AlignmentAction("center-justify", StyleConstants.ALIGN_CENTER), + new AlignmentAction("right-justify", StyleConstants.ALIGN_RIGHT), + new BoldAction(), + new ItalicAction(), + new UnderlineAction() + }; return TextAction.augmentList(actions1, myActions); } @@ -696,9 +714,8 @@ public class StyledEditorKit extends DefaultEditorKit protected void createInputAttributes(Element element, MutableAttributeSet set) { - AttributeSet atts = element.getAttributes(); - set.removeAttributes(set); // FIXME: Filter out component, icon and element name attributes. - set.addAttributes(atts); + set.removeAttributes(set); + set.addAttributes(element.getAttributes()); } } diff --git a/libjava/classpath/javax/swing/text/TableView.java b/libjava/classpath/javax/swing/text/TableView.java new file mode 100644 index 0000000..d3113b8 --- /dev/null +++ b/libjava/classpath/javax/swing/text/TableView.java @@ -0,0 +1,465 @@ +/* TableView.java -- A view impl for tables inside styled text + 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.Rectangle; +import java.awt.Shape; + +import javax.swing.SizeRequirements; +import javax.swing.event.DocumentEvent; + +/** + * A {@link View} implementation for rendering tables inside styled text. + * Tables are rendered as vertical boxes (see {@link BoxView}). These boxes + * have a number of child views, which are the rows of the table. These are + * horizontal boxes containing the actuall cells of the table. These cells + * can be arbitrary view implementations and are fetched via the + * {@link ViewFactory} returned by {@link View#getViewFactory}. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class TableView + extends BoxView +{ + + /** + * A view implementation that renders a row of a TableView. + * This is implemented as a horizontal box that contains the actual cells + * of the table. + * + * @author Roman Kennke (kennke@aicas.com) + */ + public class TableRow + extends BoxView + { + /** + * Creates a new instance of TableRow. + * + * @param el the element for which to create a row view + */ + public TableRow(Element el) + { + super(el, X_AXIS); + } + + /** + * Replaces some child views with a new set of child views. This is + * implemented to call the superclass behaviour and invalidates the row + * grid so that rows and columns will be recalculated. + * + * @param offset the start offset at which to replace views + * @param length the number of views to remove + * @param views the new set of views + */ + public void replace(int offset, int length, View[] views) + { + super.replace(offset, length, views); + layoutChanged(X_AXIS); + } + + /** + * Lays out the box's child views along the major axis. This is + * reimplemented so that the child views all have the width of their + * column. + * + * @param targetSpan the total span of the view + * @param axis the axis that is laid out + * @param offsets an array that holds the offsets of the child views after + * this method returned + * @param spans an array that holds the spans of the child views after this + * method returned + */ + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + // TODO: Maybe prepare columnSpans and columnOffsets. + + // Some sanity checks. If these preconditions are not met, then the + // following code will not work. Also, there must be something + // seriously wrong then. + assert(offsets.length == columnOffsets.length); + assert(spans.length == columnSpans.length); + assert(offsets.length == spans.length); + for (int i = 0; i < offsets.length; ++i) + { + offsets[i] = columnOffsets[i]; + spans[i] = columnSpans[i]; + } + } + + /** + * Lays out the box's child views along the minor axis (the orthogonal axis + * to the major axis). This is reimplemented to call the super behaviour + * and then adjust the span of the child views that span multiple rows. + * + * @param targetSpan the total span of the view + * @param axis the axis that is laid out + * @param offsets an array that holds the offsets of the child views after + * this method returned + * @param spans an array that holds the spans of the child views after this + * method returned + */ + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + // FIXME: Figure out how to fetch the row heights from the TableView's + // element. + super.layoutMajorAxis(targetSpan, axis, offsets, spans); + } + + /** + * Determines the resizeability of this view along the specified axis. + * + * @param axis the axis of which to fetch the resizability + * + * @return the resize weight or <= 0 if this view is not resizable + * + * @throws IllegalArgumentException when an illegal axis is specified + */ + public int getResizeWeight(int axis) + { + // TODO: Figure out if this is ok. I would think so, but better test + // this. + return 0; + } + + /** + * Returns the child view that represents the specified position in the + * model. This is reimplemented because in this view we do not necessarily + * have a one to one mapping of child elements to child views. + * + * @param pos the model position for which to query the view + * @param a the allocation of this view + * + * @return the view that corresponds to the specified model position or + * null if there is none + */ + protected View getViewAtPosition(int pos, Rectangle a) + { + // FIXME: Do not call super here. Instead walk through the child views + // and look for a range that contains the given position. + return super.getViewAtPosition(pos, a); + } + } + + /** + * This class is deprecated and not used anymore. Table cells are + * rendered by an arbitrary View implementation. + * + * @author Roman Kennke (kennke@aicas.com) + * + * @deprecated Table cells are now rendered by an arbitrary View + * implementation. + */ + public class TableCell + extends BoxView + { + + /** + * The row number of this cell. + */ + private int row; + + /** + * The column number of this cell. + */ + private int column; + + /** + * Creates a new instance. + * + * @param el the element + * + * @deprecated Table cells are now rendered by an arbitrary + * View implementation. + */ + public TableCell(Element el) + { + super(el, X_AXIS); + } + + /** + * Returns the number of columns that this cell spans. + * + * @return the number of columns that this cell spans + * + * @deprecated Table cells are now rendered by an arbitrary + * View implementation. + */ + public int getColumnCount() + { + // TODO: Figure out if this is right. However, this is not so important + // since this class isn't used anyway (except maybe be application code + // that still uses this deprecated class). + return 1; + } + + /** + * Returns the number of rows that this cell spans. + * + * @return the number of rows that this cell spans + * + * @deprecated Table cells are now rendered by an arbitrary + * View implementation. + */ + public int getRowCount() + { + // TODO: Figure out if this is right. However, this is not so important + // since this class isn't used anyway (except maybe be application code + // that still uses this deprecated class). + return 1; + } + + /** + * Sets the grid location of this table cell. + * + * @param r the row of this cell + * @param c the column of this cell + * + * @deprecated Table cells are now rendered by an arbitrary + * View implementation. + */ + public void setGridLocation(int r, int c) + { + row = r; + column = c; + } + + /** + * Returns the row number of this cell. + * + * @return the row number of this cell + * + * @deprecated Table cells are now rendered by an arbitrary + * View implementation. + */ + public int getGridRow() + { + return row; + } + + /** + * Returns the column number of this cell. + * + * @return the column number of this cell + * + * @deprecated Table cells are now rendered by an arbitrary + * View implementation. + */ + public int getGridColumn() + { + return column; + } + } + + /** + * The offsets of the columns of this table. Package private to avoid + * synthetic accessor methods. + */ + int[] columnOffsets; + + /** + * The spans of the columns of this table. Package private to avoid + * synthetic accessor methods. + */ + int[] columnSpans; + + /** + * The size requirements of the columns. + */ + private SizeRequirements[] columnRequirements; + + /** + * Creates a new instance of TableView. + * + * @param el the element for which to create a table view + */ + public TableView(Element el) + { + super(el, Y_AXIS); + int numChildren = el.getElementCount(); + View[] rows = new View[numChildren]; + for (int i = 0; i < numChildren; ++i) + { + Element rowEl = el.getElement(i); + TableRow rowView = createTableRow(rowEl); + rows[i] = rowView; + } + replace(0, 0, rows); + } + + /** + * Replaces a number of child views with a set of new child views. This is + * implemented to call the superclass behaviour and invalidate the layout. + * + * @param offset the offset at which to replace child views + * @param length the number of child views to remove + * @param views the new set of views + */ + public void replace(int offset, int length, View[] views) + { + super.replace(offset, length, views); + layoutChanged(Y_AXIS); + } + + /** + * Creates a view for a table row. + * + * @param el the element that represents the table row + * + * @return a view for rendering the table row + */ + protected TableRow createTableRow(Element el) + { + return new TableRow(el); + } + + /** + * Creates a view for a table cell. This method is deprecated and not used + * anymore. + * + * @param el the element that represents the table cell + * + * @return a view for rendering the table cell + * + * @deprecated Table cells are now rendered by an arbitrary + * View implementation. + */ + protected TableCell createTableCell(Element el) + { + return new TableCell(el); + } + + protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e, + Shape a, ViewFactory vf) + { + // TODO: Figure out what to do here. + } + + /** + * Lays out the columns to fit within the specified target span. + * + * @param targetSpan the total span for the columns + * @param offsets an array that holds the offsets of the columns when this + * method returns + * @param spans an array that holds the spans of the columns when this method + * returns + * @param reqs the size requirements for each column + */ + protected void layoutColumns(int targetSpan, int[] offsets, int spans[], + SizeRequirements[] reqs) + { + // TODO: Figure out what exactly to do here. + } + + /** + * Lays out the child views along the minor axis of the table (that is the + * horizontal axis). This is implemented to call {@link #layoutColumns} to + * layout the column layout of this table, and then forward to the superclass + * to actually lay out the rows. + * + * @param targetSpan the available span along the minor (horizontal) axis + * @param axis the axis + * @param offsets an array that holds the offsets of the columns when this + * method returns + * @param spans an array that holds the spans of the columns when this method + * returns + */ + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + // TODO: Prepare size requirements for the columns. + layoutColumns(targetSpan, columnOffsets, columnSpans, columnRequirements); + super.layoutMinorAxis(targetSpan, axis, offsets, spans); + } + + /** + * Calculates the requirements of this view for the minor (== horizontal) + * axis. + * + * This is reimplemented to calculate the requirements as the sum of the + * size requirements of the columns. + * + * @param axis the axis + * @param req the size requirements object to use, if null a new + * one will be created + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements req) + { + // TODO: Maybe prepare columnRequirements. + SizeRequirements res = req; + if (res == null) + res = new SizeRequirements(); + else + { + res.alignment = 0.5f; + res.maximum = 0; + res.minimum = 0; + res.preferred = 0; + } + + for (int i = 0; i < columnRequirements.length; ++i) + { + res.minimum += columnRequirements[i].minimum; + res.preferred += columnRequirements[i].preferred; + res.maximum += columnRequirements[i].maximum; + // TODO: Do we have to handle alignment somehow? + } + return res; + } + + /** + * Returns the child view that represents the specified position in the + * model. This is reimplemented because in this view we do not necessarily + * have a one to one mapping of child elements to child views. + * + * @param pos the model position for which to query the view + * @param a the allocation of this view + * + * @return the view that corresponds to the specified model position or + * null if there is none + */ + protected View getViewAtPosition(int pos, Rectangle a) + { + // FIXME: Do not call super here. Instead walk through the child views + // and look for a range that contains the given position. + return super.getViewAtPosition(pos, a); + } +} diff --git a/libjava/classpath/javax/swing/text/Utilities.java b/libjava/classpath/javax/swing/text/Utilities.java index 7830b2f..1adc8ff 100644 --- a/libjava/classpath/javax/swing/text/Utilities.java +++ b/libjava/classpath/javax/swing/text/Utilities.java @@ -45,6 +45,7 @@ import java.awt.Rectangle; import java.text.BreakIterator; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; /** * A set of utilities to deal with text. This is used by several other classes @@ -573,10 +574,11 @@ public class Utilities View rootView = c.getUI().getRootView(c); Rectangle r = c.modelToView(offset); int offs = c.viewToModel(new Point(x, r.y)); - int pos = rootView.getNextVisualPositionFrom(c, offs, - Position.Bias.Forward, - SwingConstants.NORTH, - new Position.Bias[1]); + int pos = rootView.getNextVisualPositionFrom(offs, + Position.Bias.Forward, + SwingUtilities.calculateInnerArea(c, null), + SwingConstants.NORTH, + new Position.Bias[1]); return pos; } @@ -599,10 +601,11 @@ public class Utilities View rootView = c.getUI().getRootView(c); Rectangle r = c.modelToView(offset); int offs = c.viewToModel(new Point(x, r.y)); - int pos = rootView.getNextVisualPositionFrom(c, offs, - Position.Bias.Forward, - SwingConstants.SOUTH, - new Position.Bias[1]); + int pos = rootView.getNextVisualPositionFrom(offs, + Position.Bias.Forward, + SwingUtilities.calculateInnerArea(c, null), + SwingConstants.SOUTH, + new Position.Bias[1]); return pos; } } diff --git a/libjava/classpath/javax/swing/text/View.java b/libjava/classpath/javax/swing/text/View.java index daab347..b835842 100644 --- a/libjava/classpath/javax/swing/text/View.java +++ b/libjava/classpath/javax/swing/text/View.java @@ -447,9 +447,11 @@ public abstract class View implements SwingConstants protected void updateLayout(DocumentEvent.ElementChange ec, DocumentEvent ev, Shape shape) { - Rectangle b = shape.getBounds(); - if (ec != null) - preferenceChanged(this, true, true); + if (ec != null && shape != null) + preferenceChanged(null, true, true); + Container c = getContainer(); + if (c != null) + c.repaint(); } /** @@ -599,9 +601,9 @@ public abstract class View implements SwingConstants * Returns the document position that is (visually) nearest to the given * document position pos in the given direction d. * - * @param c the text component * @param pos the document position * @param b the bias for pos + * @param a the allocation for this view * @param d the direction, must be either {@link SwingConstants#NORTH}, * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or * {@link SwingConstants#EAST} @@ -615,9 +617,31 @@ public abstract class View implements SwingConstants * * @throws BadLocationException if pos is not a valid offset in * the document model + * @throws IllegalArgumentException if d is not a valid direction */ - public abstract int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException; + public int getNextVisualPositionFrom(int pos, Position.Bias b, + Shape a, int d, + Position.Bias[] biasRet) + throws BadLocationException + { + int ret = pos; + switch (d) + { + case WEST: + ret = pos - 1; + break; + case EAST: + ret = pos + 1; + break; + case NORTH: + // TODO: Implement this + break; + case SOUTH: + // TODO: Implement this + break; + default: + throw new IllegalArgumentException("Illegal value for d"); + } + return ret; + } } diff --git a/libjava/classpath/javax/swing/text/WrappedPlainView.java b/libjava/classpath/javax/swing/text/WrappedPlainView.java index b03399d..baba343 100644 --- a/libjava/classpath/javax/swing/text/WrappedPlainView.java +++ b/libjava/classpath/javax/swing/text/WrappedPlainView.java @@ -620,36 +620,6 @@ public class WrappedPlainView extends BoxView implements TabExpander currLineStart = currLineEnd; } } - - /** - * Returns the document position that is (visually) nearest to the given - * document position pos in the given direction d. - * - * @param c the text component - * @param pos the document position - * @param b the bias for pos - * @param d the direction, must be either {@link SwingConstants#NORTH}, - * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or - * {@link SwingConstants#EAST} - * @param biasRet an array of {@link Position.Bias} that can hold at least - * one element, which is filled with the bias of the return position - * on method exit - * - * @return the document position that is (visually) nearest to the given - * document position pos in the given direction - * d - * - * @throws BadLocationException if pos is not a valid offset - * in the document model - */ - public int getNextVisualPositionFrom(JTextComponent c, int pos, - Position.Bias b, int d, - Position.Bias[] biasRet) - throws BadLocationException - { - // TODO: Implement this properly. - throw new AssertionError("Not implemented yet."); - } /** * This method is called from insertUpdate and removeUpdate. diff --git a/libjava/classpath/javax/swing/text/html/BlockView.java b/libjava/classpath/javax/swing/text/html/BlockView.java new file mode 100644 index 0000000..6274e7b --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/BlockView.java @@ -0,0 +1,301 @@ +/* BlockView.java -- + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; + +import javax.swing.SizeRequirements; +import javax.swing.event.DocumentEvent; +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; + +/** + * @author Lillian Angel + */ +public class BlockView extends BoxView +{ + + /** + * Creates a new view that represents an html box. + * This can be used for a number of elements. + * + * @param elem - the element to create a view for + * @param axis - either View.X_AXIS or View.Y_AXIS + */ + public BlockView(Element elem, int axis) + { + super(elem, axis); + } + + /** + * Creates the parent view for this. It is called before + * any other methods, if the parent view is working properly. + * Implemented to forward to the superclass and call + * setPropertiesFromAttributes to set the paragraph + * properties. + * + * @param parent - the new parent, or null if the view + * is being removed from a parent it was added to. + */ + public void setParent(View parent) + { + super.setParent(parent); + + if (parent != null) + setPropertiesFromAttributes(); + } + + /** + * Calculates the requirements along the major axis. + * This is implemented to call the superclass and then + * adjust it if the CSS width or height attribute is specified + * and applicable. + * + * @param axis - the axis to check the requirements for. + * @param r - the SizeRequirements. If null, one is created. + * @return the new SizeRequirements object. + */ + 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; + } + + /** + * Calculates the requirements along the minor axis. + * This is implemented to call the superclass and then + * adjust it if the CSS width or height attribute is specified + * and applicable. + * + * @param axis - the axis to check the requirements for. + * @param r - the SizeRequirements. If null, one is created. + * @return the new SizeRequirements object. + */ + 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; + } + + /** + * Lays out the box along the minor axis (the axis that is + * perpendicular to the axis that it represents). The results + * of the layout are placed in the given arrays which are + * the allocations to the children along the minor axis. + * + * @param targetSpan - the total span given to the view, also + * used to layout the children. + * @param axis - the minor axis + * @param offsets - the offsets from the origin of the view for + * all the child views. This is a return value and is filled in by this + * function. + * @param spans - the span of each child view. This is a return value and is + * filled in by this function. + */ + protected void layoutMinorAxis(int targetSpan, int axis, + int[] offsets, int[] spans) + { + // FIXME: Not implemented. + super.layoutMinorAxis(targetSpan, axis, offsets, spans); + } + + /** + * Paints using the given graphics configuration and shape. + * This delegates to the css box painter to paint the + * border and background prior to the interior. + * + * @param g - Graphics configuration + * @param a - the Shape to render into. + */ + 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); + super.paint(g, a); + } + + /** + * Fetches the attributes to use when painting. + * + * @return the attributes of this model. + */ + public AttributeSet getAttributes() + { + return getStyleSheet().getViewAttributes(this); + } + + /** + * Gets the resize weight. + * + * @param axis - the axis to get the resize weight for. + * @return the resize weight. + * @throws IllegalArgumentException - for an invalid axis + */ + public int getResizeWeight(int axis) throws IllegalArgumentException + { + // Can't resize the Y_AXIS + if (axis == Y_AXIS) + return 0; + if (axis == X_AXIS) + return 1; + throw new IllegalArgumentException("Invalid Axis"); + } + + /** + * Gets the alignment. + * + * @param axis - the axis to get the alignment for. + * @return the alignment. + */ + public float getAlignment(int axis) + { + if (axis == X_AXIS) + return 0.0F; + 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; + } + throw new IllegalArgumentException("Invalid Axis"); + } + + /** + * Gives notification from the document that attributes were + * changed in a location that this view is responsible for. + * + * @param ev - the change information + * @param a - the current shape of the view + * @param f - the factory to use to rebuild if the view has children. + */ + public void changedUpdate(DocumentEvent ev, + Shape a, ViewFactory f) + { + super.changedUpdate(ev, a, f); + + // If more elements were added, then need to set the properties for them + int currPos = ev.getOffset(); + if (currPos <= getStartOffset() && (currPos + ev.getLength()) >= getEndOffset()) + setPropertiesFromAttributes(); + } + + /** + * Determines the preferred span along the axis. + * + * @param axis - the view to get the preferred span for. + * @return the span the view would like to be painted into >=0/ + * The view is usually told to paint into the span that is returned, + * although the parent may choose to resize or break the view. + * @throws IllegalArgumentException - for an invalid axis + */ + public float getPreferredSpan(int axis) throws IllegalArgumentException + { + if (axis == X_AXIS || axis == Y_AXIS) + return super.getPreferredSpan(axis); + throw new IllegalArgumentException("Invalid Axis"); + } + + /** + * Determines the minimum span along the axis. + * + * @param axis - the axis to get the minimum span for. + * @return the span the view would like to be painted into >=0/ + * The view is usually told to paint into the span that is returned, + * although the parent may choose to resize or break the view. + * @throws IllegalArgumentException - for an invalid axis + */ + public float getMinimumSpan(int axis) throws IllegalArgumentException + { + if (axis == X_AXIS || axis == Y_AXIS) + return super.getMinimumSpan(axis); + throw new IllegalArgumentException("Invalid Axis"); + } + + /** + * Determines the maximum span along the axis. + * + * @param axis - the axis to get the maximum span for. + * @return the span the view would like to be painted into >=0/ + * The view is usually told to paint into the span that is returned, + * although the parent may choose to resize or break the view. + * @throws IllegalArgumentException - for an invalid axis + */ + public float getMaximumSpan(int axis) throws IllegalArgumentException + { + if (axis == X_AXIS || axis == Y_AXIS) + return super.getMaximumSpan(axis); + throw new IllegalArgumentException("Invalid Axis"); + } + + /** + * Updates any cached values that come from attributes. + */ + protected void setPropertiesFromAttributes() + { + // FIXME: Not implemented (need to use StyleSheet). + } + + /** + * Gets the default style sheet. + * + * @return the style sheet + */ + protected StyleSheet getStyleSheet() + { + StyleSheet styleSheet = new StyleSheet(); + styleSheet.importStyleSheet(getClass().getResource(HTMLEditorKit.DEFAULT_CSS)); + return styleSheet; + } +} diff --git a/libjava/classpath/javax/swing/text/html/CSS.java b/libjava/classpath/javax/swing/text/html/CSS.java index 029ad26..c248e75 100644 --- a/libjava/classpath/javax/swing/text/html/CSS.java +++ b/libjava/classpath/javax/swing/text/html/CSS.java @@ -37,6 +37,7 @@ exception statement from your version. */ package javax.swing.text.html; +import java.io.Serializable; import java.util.HashMap; /** @@ -46,7 +47,7 @@ import java.util.HashMap; * * @author Roman Kennke (kennke@aicas.com) */ -public class CSS +public class CSS implements Serializable { /** * Returns an array of all CSS attributes. diff --git a/libjava/classpath/javax/swing/text/html/CSSParser.java b/libjava/classpath/javax/swing/text/html/CSSParser.java new file mode 100644 index 0000000..0bf76eb --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/CSSParser.java @@ -0,0 +1,568 @@ +/* CSSParser.java -- + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import java.io.*; + +/** + * Parses a CSS document. This works by way of a delegate that implements the + * CSSParserCallback interface. The delegate is notified of the following + * events: + * - Import statement: handleImport + * - Selectors handleSelector. This is invoked for each string. For example if + * the Reader contained p, bar , a {}, the delegate would be notified 4 times, + * for 'p,' 'bar' ',' and 'a'. + * - When a rule starts, startRule + * - Properties in the rule via the handleProperty. This + * is invoked one per property/value key, eg font size: foo;, would cause the + * delegate to be notified once with a value of 'font size'. + * - Values in the rule via the handleValue, this is notified for the total value. + * - When a rule ends, endRule + * + * @author Lillian Angel (langel@redhat.com) + */ +class CSSParser +{ + + /** + * Receives all information about the CSS document structure while parsing it. + * The methods are invoked by parser. + */ + static interface CSSParserCallback + { + /** + * Handles the import statment in the document. + * + * @param imp - the import string + */ + public abstract void handleImport(String imp); + + /** + * Called when the start of a rule is encountered. + */ + public abstract void startRule(); + + /** + * Called when the end of a rule is encountered. + */ + public abstract void endRule(); + + /** + * Handles the selector of a rule. + * + * @param selector - the selector in the rule + */ + public abstract void handleSelector(String selector); + + /** + * Handles the properties in the document. + * + * @param property - the property in the document. + */ + public abstract void handleProperty(String property); + + /** + * Handles the values in the document. + * + * @param value - the value to handle. + */ + public abstract void handleValue(String value); + + } + + /** + * The identifier of the rule. + */ + private static final int IDENTIFIER = 1; + + /** + * The open bracket. + */ + private static final int BRACKET_OPEN = 2; + + /** + * The close bracket. + */ + private static final int BRACKET_CLOSE = 3; + + /** + * The open brace. + */ + private static final int BRACE_OPEN = 4; + + /** + * The close brace. + */ + private static final int BRACE_CLOSE = 5; + + /** + * The open parentheses. + */ + private static final int PAREN_OPEN = 6; + + /** + * The close parentheses. + */ + private static final int PAREN_CLOSE = 7; + + /** + * The end of the document. + */ + private static final int END = -1; + + /** + * The character mapping in the document. + */ + // FIXME: What is this used for? + private static final char[] charMapping = null; + + /** + * Set to true if one character has been read ahead. + */ + private boolean didPushChar; + + /** + * The read ahead character. + */ + private int pushedChar; + + /** + * Temporary place to hold identifiers. + */ + private StringBuffer unitBuffer; + + /** + * Used to indicate blocks. + */ + private int[] unitStack; + + /** + * Number of valid blocks. + */ + private int stackCount; + + /** + * Holds the incoming CSS rules. + */ + private Reader reader; + + /** + * Set to true when the first non @ rule is encountered. + */ + private boolean encounteredRuleSet; + + /** + * The call back used to parse. + */ + private CSSParser.CSSParserCallback callback; + + /** + * nextToken() inserts the string here. + */ + private char[] tokenBuffer; + + /** + * Current number of chars in tokenBufferLength. + */ + private int tokenBufferLength; + + /** + * Set to true if any whitespace is read. + */ + private boolean readWS; + + /** + * Constructor + */ + CSSParser() + { + unitBuffer = new StringBuffer(); + tokenBuffer = new char[10]; + } + + /** + * Appends a character to the token buffer. + * + * @param c - the character to append + */ + private void append(char c) + { + if (tokenBuffer.length >= tokenBufferLength) + { + char[] temp = new char[tokenBufferLength * 2]; + if (tokenBuffer != null) + System.arraycopy(tokenBuffer, 0, temp, 0, tokenBufferLength); + + temp[tokenBufferLength] = c; + tokenBuffer = temp; + } + else + tokenBuffer[tokenBufferLength] = c; + tokenBufferLength++; + } + + /** + * Fetches the next token. + * + * @param c - the character to fetch. + * @return the location + * @throws IOException - any i/o error encountered while reading + */ + private int nextToken(char c) throws IOException + { + readWS = false; + int next = readWS(); + + switch (next) + { + case '\"': + if (tokenBufferLength > 0) + tokenBufferLength--; + return IDENTIFIER; + case '\'': + if (tokenBufferLength > 0) + tokenBufferLength--; + return IDENTIFIER; + case '(': + return PAREN_OPEN; + case ')': + return PAREN_CLOSE; + case '{': + return BRACE_OPEN; + case '}': + return BRACE_CLOSE; + case '[': + return BRACKET_OPEN; + case ']': + return BRACKET_CLOSE; + case -1: + return END; + default: + pushChar(next); + getIdentifier(c); + return IDENTIFIER; + } + } + + /** + * Reads a character from the stream. + * + * @return the number of characters read or -1 if end of stream is reached. + * @throws IOException - any i/o encountered while reading + */ + private int readChar() throws IOException + { + if (didPushChar) + { + didPushChar = false; + return pushedChar; + } + return reader.read(); + } + + /** + * Parses the the contents of the reader using the + * callback. + * + * @param reader - the reader to read from + * @param callback - the callback instance + * @param parsingDeclaration - true if parsing a declaration + * @throws IOException - any i/o error from the reader + */ + void parse(Reader reader, CSSParser.CSSParserCallback callback, + boolean parsingDeclaration) + throws IOException + { + this.reader = reader; + this.callback = callback; + + try + { + if (!parsingDeclaration) + while(getNextStatement()); + else + parseDeclarationBlock(); + } + catch (IOException ioe) + { + // Nothing to do here. + } + } + + /** + * Skips any white space, returning the character after the white space. + * + * @return the character after the whitespace + * @throws IOException - any i/o error from the reader + */ + private int readWS() throws IOException + { + int next = readChar(); + while (Character.isWhitespace((char) next)) + { + readWS = true; + int tempNext = readChar(); + if (tempNext == END) + return next; + next = tempNext; + } + + // Its all whitespace + return END; + } + + /** + * Gets the next statement, returning false if the end is reached. + * A statement is either an At-rule, or a ruleset. + * + * @return false if the end is reached + * @throws IOException - any i/o error from the reader + */ + private boolean getNextStatement() throws IOException + { + int c = nextToken((char) 0); + switch (c) + { + case PAREN_OPEN: + case BRACE_OPEN: + case BRACKET_OPEN: + parseTillClosed(c); + break; + case BRACKET_CLOSE: + case BRACE_CLOSE: + case PAREN_CLOSE: + throw new IOException("Not a proper statement."); + case IDENTIFIER: + if (tokenBuffer[0] == ('@')) + parseAtRule(); + else + parseRuleSet(); + break; + case END: + return false; + } + return true; + } + + /** + * Parses an @ rule, stopping at a matching brace pair, or ;. + * + * @throws IOException - any i/o error from the reader + */ + private void parseAtRule() throws IOException + { + // An At-Rule begins with the "@" character followed immediately by a keyword. + // Following the keyword separated by a space is an At-rule statement appropriate + // to the At-keyword used. If the At-Rule is a simple declarative statement + // (charset, import, fontdef), it is terminated by a semi-colon (";".) + // If the At-Rule is a conditional or informative statement (media, page, font-face), + // it is followed by optional arguments and then a style declaration block inside matching + // curly braces ("{", "}".) At-Rules are sometimes nestable, depending on the context. + // If any part of an At-Rule is not understood, it should be ignored. + + // FIXME: Not Implemented + // call handleimport + } + + /** + * Parses the next rule set, which is a selector followed by a declaration + * block. + * + * @throws IOException - any i/o error from the reader + */ + private void parseRuleSet() throws IOException + { + // call parseDeclarationBlock + // call parse selectors + // call parse identifiers + // call startrule/endrule + // FIXME: Not Implemented + } + + /** + * Parses a set of selectors, returning false if the end of the stream is + * reached. + * + * @return false if the end of stream is reached + * @throws IOException - any i/o error from the reader + */ + private boolean parseSelectors() throws IOException + { + // FIXME: Not Implemented + // call handleselector + return false; + } + + /** + * Parses a declaration block. Which a number of declarations followed by a + * })]. + * + * @throws IOException - any i/o error from the reader + */ + private void parseDeclarationBlock() throws IOException + { + // call parseDeclaration + // FIXME: Not Implemented + } + + /** + * Parses a single declaration, which is an identifier a : and another identifier. + * This returns the last token seen. + * + * @returns the last token + * @throws IOException - any i/o error from the reader + */ + private int parseDeclaration() throws IOException + { + // call handleValue + // FIXME: Not Implemented + return 0; + } + + /** + * Parses identifiers until c is encountered, returning the ending token, + * which will be IDENTIFIER if c is found. + * + * @param c - the stop character + * @param wantsBlocks - true if blocks are wanted + * @return the ending token + * @throws IOException - any i/o error from the reader + */ + private int parseIdentifiers(char c, boolean wantsBlocks) throws IOException + { + // FIXME: Not implemented + // call handleproperty? + return 0; + } + + /** + * Parses till a matching block close is encountered. This is only appropriate + * to be called at the top level (no nesting). + * + * @param i - FIXME + * @throws IOException - any i/o error from the reader + */ + private void parseTillClosed(int i) throws IOException + { + // FIXME: Not Implemented + } + + /** + * Gets an identifier, returning true if the length of the string is greater + * than 0, stopping when c, whitespace, or one of {}()[] is hit. + * + * @param c - the stop character + * @return returns true if the length of the string > 0 + * @throws IOException - any i/o error from the reader + */ + private boolean getIdentifier(char c) throws IOException + { + // FIXME: Not Implemented + return false; + } + + /** + * Reads till c is encountered, escaping characters as necessary. + * + * @param c - the stop character + * @throws IOException - any i/o error from the reader + */ + private void readTill(char c) throws IOException + { + // FIXME: Not Implemented + } + + /** + * Parses a comment block. + * + * @throws IOException - any i/o error from the reader + */ + private void readComment() throws IOException + { + // Should ignore comments. Read until end of comment. + // FIXME: Not implemented + } + + /** + * Called when a block start is encountered ({[. + * + * @param start of block + */ + private void startBlock(int start) + { + // FIXME: Not Implemented + } + + /** + * Called when an end block is encountered )]} + * + * @param end of block + */ + private void endBlock(int end) + { + // FIXME: Not Implemented + } + + /** + * Checks if currently in a block. + * + * @return true if currently in a block. + */ + private boolean inBlock() + { + // FIXME: Not Implemented + return false; + } + + /** + * Supports one character look ahead, this will throw if called twice in a row. + * + * @param c - the character to push. + * @throws IOException - if called twice in a row + */ + private void pushChar(int c) throws IOException + { + if (didPushChar) + throw new IOException("pushChar called twice."); + didPushChar = true; + pushedChar = c; + } +} + + \ No newline at end of file diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java index d048a04..5b2452b 100644 --- a/libjava/classpath/javax/swing/text/html/HTMLDocument.java +++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java @@ -40,17 +40,32 @@ package javax.swing.text.html; import java.net.URL; +import java.io.IOException; + +import java.util.HashMap; +import java.util.Stack; +import java.util.Vector; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.UndoableEditEvent; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.Element; import javax.swing.text.ElementIterator; +import javax.swing.text.GapContent; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; import javax.swing.text.html.HTML.Tag; /** - * TODO: This class is not yet completetely implemented. - * + * TODO: Add more comments here + * * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org) + * @author Anthony Balkissoon (abalkiss@redhat.com) + * @author Lillian Angel (langel@redhat.com) */ public class HTMLDocument extends DefaultStyledDocument { @@ -60,6 +75,195 @@ public class HTMLDocument extends DefaultStyledDocument public static final String AdditionalComments = "AdditionalComments"; URL baseURL = null; boolean preservesUnknownTags = true; + int tokenThreshold = Integer.MAX_VALUE; + HTMLEditorKit.Parser parser; + StyleSheet styleSheet; + AbstractDocument.Content content; + + /** + * Constructs an HTML document using the default buffer size and a default + * StyleSheet. + */ + public HTMLDocument() + { + this(null); + } + + /** + * Constructs an HTML document with the default content storage + * implementation and the specified style/attribute storage mechanism. + * + * @param styles - the style sheet + */ + public HTMLDocument(StyleSheet styles) + { + this(new GapContent(BUFFER_SIZE_DEFAULT), styles); + } + + /** + * Constructs an HTML document with the given content storage implementation + * and the given style/attribute storage mechanism. + * + * @param c - the document's content + * @param styles - the style sheet + */ + 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; + } + + /** + * Gets the style sheet with the document display rules (CSS) that were specified + * in the HTML document. + * + * @return - the style sheet + */ + public StyleSheet getStyleSheet() + { + return styleSheet; + } + + /** + * Replaces the contents of the document with the given element specifications. + * This is called before insert if the loading is done in bursts. This is the + * only method called if loading the document entirely in one burst. + * + * @param data - the date that replaces the content of the document + */ + protected void create(DefaultStyledDocument.ElementSpec[] data) + { + // FIXME: Not implemented + System.out.println("create not implemented"); + super.create(data); + } + + /** + * This method creates a root element for the new document. + * + * @return the new default root + */ + protected AbstractDocument.AbstractElement createDefaultRoot() + { + // FIXME: Not implemented + System.out.println("createDefaultRoot not implemented"); + return super.createDefaultRoot(); + } + + /** + * This method returns an HTMLDocument.RunElement object attached to + * parent representing a run of text from p0 to p1. The run has + * attributes described by a. + * + * @param parent - the parent element + * @param a - the attributes for the element + * @param p0 - the beginning of the range >= 0 + * @param p1 - the end of the range >= p0 + * @return the new element + */ + protected Element createLeafElement(Element parent, AttributeSet a, int p0, + int p1) + { + // FIXME: Not implemented + System.out.println("createLeafElement not implemented"); + return super.createLeafElement(parent, a, p0, p1); + } + + /** This method returns an HTMLDocument.BlockElement object representing the + * attribute set a and attached to parent. + * + * @param parent - the parent element + * @param a - the attributes for the element + * @return the new element + */ + protected Element createBranchElement(Element parent, AttributeSet a) + { + // FIXME: Not implemented + System.out.println("createBranchElement not implemented"); + return super.createBranchElement(parent, a); + } + + /** + * Inserts new elements in bulk. This is how elements get created in the + * document. The parsing determines what structure is needed and creates the + * specification as a set of tokens that describe the edit while leaving the + * document free of a write-lock. This method can then be called in bursts by + * the reader to acquire a write-lock for a shorter duration (i.e. while the + * document is actually being altered). + * + * @param offset - the starting offset + * @param data - the element data + * @throws BadLocationException - if the given position does not + * represent a valid location in the associated document. + */ + protected void insert(int offset, DefaultStyledDocument.ElementSpec[] data) + throws BadLocationException + { + super.insert(offset, data); + } + + /** + * Updates document structure as a result of text insertion. This will happen + * within a write lock. This implementation simply parses the inserted content + * for line breaks and builds up a set of instructions for the element buffer. + * + * @param chng - a description of the document change + * @param attr - the attributes + */ + protected void insertUpdate(AbstractDocument.DefaultDocumentEvent chng, + AttributeSet attr) + { + // FIXME: Not implemented + System.out.println("insertUpdate not implemented"); + super.insertUpdate(chng, attr); + } + + /** + * Returns the parser used by this HTMLDocument to insert HTML. + * + * @return the parser used by this HTMLDocument to insert HTML. + */ + public HTMLEditorKit.Parser getParser() + { + return parser; + } + + /** + * Sets the parser used by this HTMLDocument to insert HTML. + * + * @param p the parser to use + */ + public void setParser (HTMLEditorKit.Parser p) + { + parser = p; + } + /** + * Sets the number of tokens to buffer before trying to display the + * Document. + * + * @param n the number of tokens to buffer + */ + public void setTokenThreshold (int n) + { + tokenThreshold = n; + } + + /** + * Returns the number of tokens that are buffered before the document + * is rendered. + * + * @return the number of tokens buffered + */ + public int getTokenThreshold () + { + return tokenThreshold; + } /** * Returns the location against which to resolve relative URLs. @@ -79,7 +283,7 @@ public class HTMLDocument extends DefaultStyledDocument public void setBase(URL u) { baseURL = u; - //TODO: also set the base of the StyleSheet + styleSheet.setBase(u); } /** @@ -259,10 +463,1133 @@ public class HTMLDocument extends DefaultStyledDocument return null; } + /** + * Gets the name of the element. + * + * @return the name of the element if it exists, null otherwise. + */ public String getName() { - //FIXME: this is supposed to do something different from the super class - return super.getName(); + return (String) getAttribute(StyleConstants.NameAttribute); + } + } + + /** + * RunElement represents a section of text that has a set of + * HTML character level attributes assigned to it. + */ + public class RunElement extends AbstractDocument.LeafElement + { + + /** + * Constructs an element that has no children. It represents content + * within the document. + * + * @param parent - parent of this + * @param a - elements attributes + * @param start - the start offset >= 0 + * @param end - the end offset + */ + public RunElement(Element parent, AttributeSet a, int start, int end) + { + super(parent, a, start, end); + } + + /** + * Gets the name of the element. + * + * @return the name of the element if it exists, null otherwise. + */ + public String getName() + { + return (String) getAttribute(StyleConstants.NameAttribute); + } + + /** + * Gets the resolving parent. HTML attributes do not inherit at the + * model level, so this method returns null. + * + * @return null + */ + public AttributeSet getResolveParent() + { + return null; + } + } + + /** + * A reader to load an HTMLDocument with HTML structure. + * + * @author Anthony Balkissoon abalkiss at redhat dot com + */ + public class HTMLReader extends HTMLEditorKit.ParserCallback + { + /** Holds the current character attribute set **/ + protected MutableAttributeSet charAttr = new SimpleAttributeSet(); + + protected Vector parseBuffer = new Vector(); + + /** A stack for character attribute sets **/ + Stack charAttrStack = new Stack(); + + /** A mapping between HTML.Tag objects and the actions that handle them **/ + HashMap tagToAction; + + /** Tells us whether we've received the '' tag yet **/ + boolean endHTMLEncountered = false; + + /** Variables related to the constructor with explicit insertTag **/ + int popDepth, pushDepth, offset; + HTML.Tag insertTag; + boolean insertTagEncountered = false; + + /** 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); + } + + public class TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. By default this does nothing. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + // Nothing to do here. + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. By default does nothing. + */ + public void end(HTML.Tag t) + { + // Nothing to do here. + } + } + + public class BlockAction extends TagAction + { + /** + * 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) + { + // Tell the parse buffer to open a new block for this tag. + blockOpen(t, a); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // Tell the parse buffer to close this block. + blockClose(t); + } + } + + public class CharacterAction extends TagAction + { + /** + * 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) + { + // Put the old attribute set on the stack. + pushCharacterStyle(); + + // And create the new one by adding the attributes in a. + if (a != null) + charAttr.addAttribute(t, a.copyAttributes()); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + popCharacterStyle(); + } + } + + public class FormAction extends SpecialAction + { + /** + * 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) + { + // FIXME: Implement. + print ("FormAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("FormAction.end not implemented"); + } + } + + public class HiddenAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("HiddenAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("HiddenAction.end not implemented"); + } + } + + public class IsindexAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("IsindexAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("IsindexAction.end not implemented"); + } + } + + public class ParagraphAction extends BlockAction + { + /** + * 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) + { + // FIXME: Implement. + print ("ParagraphAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("ParagraphAction.end not implemented"); + } + } + + public class PreAction extends BlockAction + { + /** + * 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) + { + // FIXME: Implement. + print ("PreAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("PreAction.end not implemented"); + } + } + + public class SpecialAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("SpecialAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("SpecialAction.end not implemented"); + } + } + + class AreaAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("AreaAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("AreaAction.end not implemented"); + } + } + + class BaseAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("BaseAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("BaseAction.end not implemented"); + } + } + + class HeadAction extends BlockAction + { + /** + * 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) + { + // FIXME: Implement. + print ("HeadAction.start not implemented: "+t); + super.start(t, a); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("HeadAction.end not implemented: "+t); + super.end(t); + } + } + + class LinkAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("LinkAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("LinkAction.end not implemented"); + } + } + + class MapAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("MapAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("MapAction.end not implemented"); + } + } + + class MetaAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("MetaAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("MetaAction.end not implemented"); + } + } + + class StyleAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("StyleAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("StyleAction.end not implemented"); + } + } + + class TitleAction extends TagAction + { + /** + * 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) + { + // FIXME: Implement. + print ("TitleAction.start not implemented"); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // FIXME: Implement. + print ("TitleAction.end not implemented"); + } + } + + public HTMLReader(int offset) + { + this (offset, 0, 0, null); + } + + 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; + initTags(); + } + + void initTags() + { + tagToAction = new HashMap(72); + CharacterAction characterAction = new CharacterAction(); + HiddenAction hiddenAction = new HiddenAction(); + AreaAction areaAction = new AreaAction(); + BaseAction baseAction = new BaseAction(); + BlockAction blockAction = new BlockAction(); + SpecialAction specialAction = new SpecialAction(); + ParagraphAction paragraphAction = new ParagraphAction(); + HeadAction headAction = new HeadAction(); + FormAction formAction = new FormAction(); + IsindexAction isindexAction = new IsindexAction(); + LinkAction linkAction = new LinkAction(); + MapAction mapAction = new MapAction(); + PreAction preAction = new PreAction(); + MetaAction metaAction = new MetaAction(); + StyleAction styleAction = new StyleAction(); + TitleAction titleAction = new TitleAction(); + + + tagToAction.put(HTML.Tag.A, characterAction); + tagToAction.put(HTML.Tag.ADDRESS, characterAction); + tagToAction.put(HTML.Tag.APPLET, hiddenAction); + tagToAction.put(HTML.Tag.AREA, areaAction); + tagToAction.put(HTML.Tag.B, characterAction); + tagToAction.put(HTML.Tag.BASE, baseAction); + tagToAction.put(HTML.Tag.BASEFONT, characterAction); + tagToAction.put(HTML.Tag.BIG, characterAction); + tagToAction.put(HTML.Tag.BLOCKQUOTE, blockAction); + tagToAction.put(HTML.Tag.BODY, blockAction); + tagToAction.put(HTML.Tag.BR, specialAction); + tagToAction.put(HTML.Tag.CAPTION, blockAction); + tagToAction.put(HTML.Tag.CENTER, blockAction); + tagToAction.put(HTML.Tag.CITE, characterAction); + tagToAction.put(HTML.Tag.CODE, characterAction); + tagToAction.put(HTML.Tag.DD, blockAction); + tagToAction.put(HTML.Tag.DFN, characterAction); + tagToAction.put(HTML.Tag.DIR, blockAction); + tagToAction.put(HTML.Tag.DIV, blockAction); + 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.FRAME, specialAction); + tagToAction.put(HTML.Tag.FRAMESET, blockAction); + tagToAction.put(HTML.Tag.H1, paragraphAction); + tagToAction.put(HTML.Tag.H2, paragraphAction); + tagToAction.put(HTML.Tag.H3, paragraphAction); + tagToAction.put(HTML.Tag.H4, paragraphAction); + tagToAction.put(HTML.Tag.H5, paragraphAction); + tagToAction.put(HTML.Tag.H6, paragraphAction); + tagToAction.put(HTML.Tag.HEAD, headAction); + tagToAction.put(HTML.Tag.HR, specialAction); + tagToAction.put(HTML.Tag.HTML, blockAction); + tagToAction.put(HTML.Tag.I, characterAction); + tagToAction.put(HTML.Tag.IMG, specialAction); + tagToAction.put(HTML.Tag.INPUT, formAction); + tagToAction.put(HTML.Tag.ISINDEX, isindexAction); + tagToAction.put(HTML.Tag.KBD, characterAction); + tagToAction.put(HTML.Tag.LI, blockAction); + tagToAction.put(HTML.Tag.LINK, linkAction); + tagToAction.put(HTML.Tag.MAP, mapAction); + tagToAction.put(HTML.Tag.MENU, blockAction); + tagToAction.put(HTML.Tag.META, metaAction); + tagToAction.put(HTML.Tag.NOFRAMES, blockAction); + tagToAction.put(HTML.Tag.OBJECT, specialAction); + tagToAction.put(HTML.Tag.OL, blockAction); + tagToAction.put(HTML.Tag.OPTION, formAction); + tagToAction.put(HTML.Tag.P, paragraphAction); + tagToAction.put(HTML.Tag.PARAM, hiddenAction); + tagToAction.put(HTML.Tag.PRE, preAction); + tagToAction.put(HTML.Tag.SAMP, characterAction); + tagToAction.put(HTML.Tag.SCRIPT, hiddenAction); + tagToAction.put(HTML.Tag.SELECT, formAction); + tagToAction.put(HTML.Tag.SMALL, characterAction); + tagToAction.put(HTML.Tag.STRIKE, characterAction); + tagToAction.put(HTML.Tag.S, characterAction); + tagToAction.put(HTML.Tag.STRONG, characterAction); + tagToAction.put(HTML.Tag.STYLE, styleAction); + tagToAction.put(HTML.Tag.SUB, characterAction); + tagToAction.put(HTML.Tag.SUP, characterAction); + tagToAction.put(HTML.Tag.TABLE, blockAction); + tagToAction.put(HTML.Tag.TD, blockAction); + tagToAction.put(HTML.Tag.TEXTAREA, formAction); + tagToAction.put(HTML.Tag.TH, blockAction); + tagToAction.put(HTML.Tag.TITLE, titleAction); + tagToAction.put(HTML.Tag.TR, blockAction); + tagToAction.put(HTML.Tag.TT, characterAction); + tagToAction.put(HTML.Tag.U, characterAction); + tagToAction.put(HTML.Tag.UL, blockAction); + tagToAction.put(HTML.Tag.VAR, characterAction); + } + + /** + * Pushes the current character style onto the stack. + * + */ + protected void pushCharacterStyle() + { + charAttrStack.push(charAttr); + } + + /** + * Pops a character style off of the stack and uses it as the + * current character style. + * + */ + protected void popCharacterStyle() + { + if (!charAttrStack.isEmpty()) + charAttr = (MutableAttributeSet) charAttrStack.pop(); + } + + /** + * Registers a given tag with a given Action. All of the well-known tags + * are registered by default, but this method can change their behaviour + * or add support for custom or currently unsupported tags. + * + * @param t the Tag to register + * @param a the Action for the Tag + */ + protected void registerTag(HTML.Tag t, HTMLDocument.HTMLReader.TagAction a) + { + tagToAction.put (t, a); + } + + /** + * This is the last method called on the HTMLReader, allowing any pending + * changes to be flushed to the HTMLDocument. + */ + public void flush() throws BadLocationException + { + DefaultStyledDocument.ElementSpec[] elements; + elements = new DefaultStyledDocument.ElementSpec[parseBuffer.size()]; + parseBuffer.copyInto(elements); + parseBuffer.removeAllElements(); + insert(offset, elements); + offset += HTMLDocument.this.getLength() - offset; + } + + /** + * This method is called by the parser to indicate a block of + * text was encountered. Should insert the text appropriately. + * + * @param data the text that was inserted + * @param pos the position at which the text was inserted + */ + public void handleText(char[] data, int pos) + { + if (data != null && data.length > 0) + addContent(data, 0, data.length); + } + + /** + * This method is called by the parser and should route the call to + * the proper handler for the tag. + * + * @param t the HTML.Tag + * @param a the attribute set + * @param pos the position at which the tag was encountered + */ + public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + // Don't call the Action if we've already seen . + if (endHTMLEncountered) + return; + + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.start(t, a); + } + + /** + * This method called by parser to handle a comment block. + * + * @param data the comment + * @param pos the position at which the comment was encountered + */ + public void handleComment(char[] data, int pos) + { + // Don't call the Action if we've already seen . + if (endHTMLEncountered) + return; + + TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); + if (action != null) + { + action.start(HTML.Tag.COMMENT, new SimpleAttributeSet()); + action.end (HTML.Tag.COMMENT); + } + } + + /** + * This method is called by the parser and should route the call to + * the proper handler for the tag. + * + * @param t the HTML.Tag + * @param pos the position at which the tag was encountered + */ + public void handleEndTag(HTML.Tag t, int pos) + { + // Don't call the Action if we've already seen . + if (endHTMLEncountered) + return; + + // If this is the tag we need to stop calling the Actions + if (t == HTML.Tag.HTML) + endHTMLEncountered = true; + + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.end(t); + } + + /** + * This is a callback from the parser that should be routed to the + * appropriate handler for the tag. + * + * @param t the HTML.Tag that was encountered + * @param a the attribute set + * @param pos the position at which the tag was encountered + */ + public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + // Don't call the Action if we've already seen . + if (endHTMLEncountered) + return; + + TagAction action = (TagAction) tagToAction.get (t); + if (action != null) + { + action.start(t, a); + action.end(t); + } + } + + /** + * This is invoked after the stream has been parsed but before it has been + * flushed. + * + * @param eol one of \n, \r, or \r\n, whichever was encountered the most in + * parsing the stream + * @since 1.3 + */ + public void handleEndOfLineString(String eol) + { + // FIXME: Implement. + print ("HTMLReader.handleEndOfLineString not implemented yet"); + } + + /** + * Adds the given text to the textarea document. Called only when we are + * within a textarea. + * + * @param data the text to add to the textarea + */ + protected void textAreaContent(char[] data) + { + // FIXME: Implement. + print ("HTMLReader.textAreaContent not implemented yet"); + } + + /** + * Adds the given text that was encountered in a
 element.
+     * 
+     * @param data the text
+     */
+    protected void preContent(char[] data)
+    {
+      // FIXME: Implement
+      print ("HTMLReader.preContent not implemented yet");
+    }
+    
+    /**
+     * Instructs the parse buffer to create a block element with the given 
+     * attributes.
+     * 
+     * @param t the tag that requires opening a new block
+     * @param attr the attribute set for the new block
+     */
+    protected void blockOpen(HTML.Tag t, MutableAttributeSet attr)
+    {
+      printBuffer();
+      DefaultStyledDocument.ElementSpec element;
+      element = new DefaultStyledDocument.ElementSpec(attr.copyAttributes(),
+			DefaultStyledDocument.ElementSpec.StartTagType);
+      parseBuffer.addElement(element);
+      printBuffer();
+    }
+    
+    /**
+     * Instructs the parse buffer to close the block element associated with 
+     * the given HTML.Tag
+     * 
+     * @param t the HTML.Tag that is closing its block
+     */
+    protected void blockClose(HTML.Tag t)
+    {
+      printBuffer();
+      DefaultStyledDocument.ElementSpec element;
+      element = new DefaultStyledDocument.ElementSpec(null,
+				DefaultStyledDocument.ElementSpec.EndTagType);
+      parseBuffer.addElement(element);
+      printBuffer();
     }
+    
+    /**
+     * Adds text to the appropriate context using the current character
+     * attribute set.
+     * 
+     * @param data the text to add
+     * @param offs the offset at which to add it
+     * @param length the length of the text to add
+     */
+    protected void addContent(char[] data, int offs, int length)
+    {
+      addContent(data, offs, length, true);
+    }
+    
+    /**
+     * Adds text to the appropriate context using the current character
+     * attribute set, and possibly generating an IMPLIED Tag if necessary.
+     * 
+     * @param data the text to add
+     * @param offs the offset at which to add it
+     * @param length the length of the text to add
+     * @param generateImpliedPIfNecessary whether or not we should generate
+     * an HTML.Tag.IMPLIED tag if necessary
+     */
+    protected void addContent(char[] data, int offs, int length,
+                              boolean generateImpliedPIfNecessary)
+    {
+      // Copy the attribute set, don't use the same object because 
+      // it may change
+      AttributeSet attributes = null;
+      if (charAttr != null)
+        attributes = charAttr.copyAttributes();
+
+      DefaultStyledDocument.ElementSpec element;
+      element = new DefaultStyledDocument.ElementSpec(attributes,
+			      DefaultStyledDocument.ElementSpec.ContentType,
+                              data, offs, length);
+      
+      printBuffer();
+      // Add the element to the buffer
+      parseBuffer.addElement(element);
+      printBuffer();
+
+      if (parseBuffer.size() > HTMLDocument.this.getTokenThreshold())
+        {
+          try
+            {
+              flush();
+            }
+          catch (BadLocationException ble)
+            {
+              // TODO: what to do here?
+            }
+        }
+    }
+    
+    /**
+     * Adds content that is specified in the attribute set.
+     * 
+     * @param t the HTML.Tag
+     * @param a the attribute set specifying the special content
+     */
+    protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a)
+    {
+      // FIXME: Implement
+      print ("HTMLReader.addSpecialElement not implemented yet");
+    }
+    
+    void printBuffer()
+    {      
+      print ("\n*********BUFFER**********");
+      for (int i = 0; i < parseBuffer.size(); i ++)
+        print ("  "+parseBuffer.get(i));
+      print ("***************************");
+    }
+  }
+  
+  /**
+   * Gets the reader for the parser to use when loading the document with HTML. 
+   * 
+   * @param pos - the starting position
+   * @return - the reader
+   */
+  public HTMLEditorKit.ParserCallback getReader(int pos)
+  {
+    return new HTMLReader(pos);
+  }  
+  
+  /**
+   * Gets the reader for the parser to use when loading the document with HTML. 
+   * 
+   * @param pos - the starting position
+   * @param popDepth - the number of EndTagTypes to generate before inserting
+   * @param pushDepth - the number of StartTagTypes with a direction 
+   * of JoinNextDirection that should be generated before inserting, 
+   * but after the end tags have been generated.
+   * @param insertTag - the first tag to start inserting into document
+   * @return - the reader
+   */
+  public HTMLEditorKit.ParserCallback getReader(int pos,
+                                                int popDepth,
+                                                int pushDepth,
+                                                HTML.Tag insertTag)
+  {
+    return new HTMLReader(pos, popDepth, pushDepth, insertTag);
+  }  
+  
+  /**
+   * Gets the child element that contains the attribute with the value or null.
+   * Not thread-safe.
+   * 
+   * @param e - the element to begin search at
+   * @param attribute - the desired attribute
+   * @param value - the desired value
+   * @return the element found with the attribute and value specified or null
+   * if it is not found.
+   */
+  public Element getElement(Element e, Object attribute, Object value)
+  {
+    if (e != null)
+      {
+        if (e.getAttributes().containsAttribute(attribute, value))
+          return e;
+        
+        int count = e.getElementCount();
+        for (int j = 0; j < count; j++)
+          {
+            Element child = e.getElement(j);
+            if (child.getAttributes().containsAttribute(attribute, value))
+              return child;
+            
+            Element grandChild = getElement(child, attribute, value);
+            if (grandChild != null)
+              return grandChild;
+          }
+      }
+    return null;
+  }
+  
+  /**
+   * Returns the element that has the given id Attribute. If it is not found, 
+   * null is returned. This method works on an Attribute, not a character tag.
+   * This is not thread-safe.
+   * 
+   * @param attrId - the Attribute id to look for
+   * @return the element that has the given id.
+   */
+  public Element getElement(String attrId)
+  {
+    Element root = getDefaultRootElement();
+    return getElement(root, HTML.getAttributeKey(attrId) , attrId);
+  }
+  
+  /**
+   * Replaces the children of the given element with the contents of
+   * the string. The document must have an HTMLEditorKit.Parser set.
+   * This will be seen as at least two events, n inserts followed by a remove.
+   * 
+   * @param elem - the brance element whose children will be replaced
+   * @param htmlText - the string to be parsed and assigned to element.
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalArgumentException - if elem is a leaf 
+   * @throws IllegalStateException - if an HTMLEditorKit.Parser has not been set
+   */
+  public void setInnerHTML(Element elem, String htmlText) 
+    throws BadLocationException, IOException
+  {
+    if (elem.isLeaf())
+      throw new IllegalArgumentException("Element is a leaf");
+    if (parser == null)
+      throw new IllegalStateException("Parser has not been set");
+    // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit?
+    System.out.println("setInnerHTML not implemented");
+  }
+  
+  /**
+   * Replaces the given element in the parent with the string. When replacing
+   * a leaf, this will attempt to make sure there is a newline present if one is
+   * needed. This may result in an additional element being inserted.
+   * This will be seen as at least two events, n inserts followed by a remove.
+   * The HTMLEditorKit.Parser must be set.
+   * 
+   * @param elem - the branch element whose parent will be replaced
+   * @param htmlText - the string to be parsed and assigned to elem
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+  public void setOuterHTML(Element elem, String htmlText) 
+    throws BadLocationException, IOException
+    {
+      if (parser == null)
+        throw new IllegalStateException("Parser has not been set");
+      // FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit?
+      System.out.println("setOuterHTML not implemented");
+    }
+  
+  /**
+   * Inserts the string before the start of the given element.
+   * The parser must be set.
+   * 
+   * @param elem - the element to be the root for the new text.
+   * @param htmlText - the string to be parsed and assigned to elem
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser has not been set
+   */
+  public void insertBeforeStart(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    if (parser == null)
+      throw new IllegalStateException("Parser has not been set");
+    //  FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit?
+    System.out.println("insertBeforeStart not implemented");
+  }
+  
+  /**
+   * Inserts the string at the end of the element. If elem's children
+   * are leaves, and the character at elem.getEndOffset() - 1 is a newline, 
+   * then it will be inserted before the newline. The parser must be set.
+   * 
+   * @param elem - the element to be the root for the new text
+   * @param htmlText - the text to insert
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+  public void insertBeforeEnd(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    if (parser == null)
+      throw new IllegalStateException("Parser has not been set");
+    //  FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit?
+    System.out.println("insertBeforeEnd not implemented");
+  }
+  
+  /**
+   * Inserts the string after the end of the given element.
+   * The parser must be set.
+   * 
+   * @param elem - the element to be the root for the new text
+   * @param htmlText - the text to insert
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+  public void insertAfterEnd(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    if (parser == null)
+      throw new IllegalStateException("Parser has not been set");
+    //  FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit?
+    System.out.println("insertAfterEnd not implemented");
+  }
+  
+  /**
+   * Inserts the string at the start of the element.
+   * The parser must be set.
+   * 
+   * @param elem - the element to be the root for the new text
+   * @param htmlText - the text to insert
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+  public void insertAfterStart(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    if (parser == null)
+      throw new IllegalStateException("Parser has not been set");
+    //  FIXME: Not implemented fully, use InsertHTML* in HTMLEditorKit?
+    System.out.println("insertAfterStart not implemented");
+  }
+  
+  /**
+   * This method sets the attributes associated with the paragraph containing
+   * offset. If replace is false, s is merged with existing attributes. The
+   * length argument determines how many characters are affected by the new
+   * attributes. This is often the entire paragraph.
+   * 
+   * @param offset -
+   *          the offset into the paragraph (must be at least 0)
+   * @param length -
+   *          the number of characters affected (must be at least 0)
+   * @param s -
+   *          the attributes
+   * @param replace -
+   *          whether to replace existing attributes, or merge them
+   */
+  public void setParagraphAttributes(int offset, int length, AttributeSet s,
+                                     boolean replace)
+  {
+    //  FIXME: Not implemented.
+    System.out.println("setParagraphAttributes not implemented");
+    super.setParagraphAttributes(offset, length, s, replace);
+  }
+  
+  /**
+   * This method flags a change in the document.
+   * 
+   *  @param e - the Document event
+   */
+  protected void fireChangedUpdate(DocumentEvent e)
+  {
+    //  FIXME: Not implemented.
+    System.out.println("fireChangedUpdate not implemented");
+    super.fireChangedUpdate(e);    
+  }
+
+  /**
+   * This method fires an event intended to be caught by Undo listeners. It
+   * simply calls the super version inherited from DefaultStyledDocument. With
+   * this method, an HTML editor could easily provide undo support.
+   * 
+   * @param e - the UndoableEditEvent
+   */
+  protected void fireUndoableEditUpdate(UndoableEditEvent e)
+  {
+    super.fireUndoableEditUpdate(e);
   }
 }
diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
index 5189c77..1ef9768 100644
--- a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
+++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
@@ -38,28 +38,563 @@ exception statement from your version. */
 
 package javax.swing.text.html;
 
+
+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.io.IOException;
 import java.io.Reader;
 import java.io.Serializable;
+import java.io.StringReader;
+import java.io.Writer;
 
+import javax.accessibility.Accessible;
+import javax.accessibility.AccessibleContext;
+
+import javax.swing.Action;
+import javax.swing.JEditorPane;
+import javax.swing.text.AbstractDocument;
 import javax.swing.text.BadLocationException;
+import javax.swing.text.BoxView;
+import javax.swing.text.ComponentView;
 import javax.swing.text.Document;
+import javax.swing.text.EditorKit;
+import javax.swing.text.Element;
+import javax.swing.text.IconView;
+import javax.swing.text.LabelView;
 import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.ParagraphView;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
 import javax.swing.text.StyledEditorKit;
+import javax.swing.text.TextAction;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
 import javax.swing.text.html.parser.ParserDelegator;
 
 /**
- * This class is NOT implemented. This file currently holds only
- * declarations of the two enclosing classes, necessary for testing
- * the implemented javax.swing.text.html.parser package.
- *
- * @author No authorship is taken, implement the class and be!
- * TODO: replace this header after implementing the class.
+ * @author Lillian Angel (langel at redhat dot com)
  */
 public class HTMLEditorKit
   extends StyledEditorKit
-  implements Serializable, Cloneable
+  implements Serializable, Cloneable, Accessible
 {
+  
+  /**
+   * Fires the hyperlink events on the associated component
+   * when needed.
+   */
+  public static class LinkController
+    extends MouseAdapter
+    implements MouseMotionListener, Serializable
+    {
+      
+      /**
+       * Constructor
+       */
+      public LinkController() 
+      {
+        super();
+      }
+      
+      /**
+       * Dispatched when the mouse is clicked. If the component
+       * is read-only, then the clicked event is used to drive an
+       * attempt to follow the reference specified by a link
+       * 
+       * @param e - the mouse event
+       */
+      public void mouseClicked(MouseEvent e)
+      {
+        /*
+         These MouseInputAdapter methods generate mouse appropriate events around
+         hyperlinks (entering, exiting, and activating).
+         */
+        // FIXME: Not implemented.
+      }
+      
+      /**
+       * Dispatched when the mouse is dragged on a component.
+       * 
+       * @param e - the mouse event.
+       */
+      public void mouseDragged(MouseEvent e)
+      {
+        /*
+        These MouseInputAdapter methods generate mouse appropriate events around
+        hyperlinks (entering, exiting, and activating).
+        */
+        // FIXME: Not implemented.     
+      }
+      
+      /**
+       * Dispatched when the mouse cursor has moved into the component.
+       * 
+       * @param e - the mouse event.
+       */
+      public void mouseMoved(MouseEvent e)
+      {
+        /*
+        These MouseInputAdapter methods generate mouse appropriate events around
+        hyperlinks (entering, exiting, and activating).
+        */
+        // FIXME: Not implemented.
+      }
+      
+      /**
+       * 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
+       */
+      protected void activateLink(int pos,
+                                  JEditorPane editor)
+      {
+        /*
+          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.
+      }
+    }
+  
+  /**
+   * This class is used to insert a string of HTML into an existing
+   * document. At least 2 HTML.Tags need to be supplied. The first Tag (parentTag)
+   * identifies the parent in the document to add the elements to. The second, (addTag), 
+   * identifies that the first tag should be added to the document as seen in the string.
+   * The parser will generate all appropriate (opening/closing tags_ even if they are not
+   * in the HTML string passed in.
+   */
+  public static class InsertHTMLTextAction
+    extends HTMLTextAction
+    {
+      
+      /**
+       * Tag in HTML to start adding tags from.
+       */
+      protected HTML.Tag addTag;
+      
+      /**
+       * Alternate tag in HTML to start adding tags from if parentTag is
+       * not found and alternateParentTag is not found.
+       */      
+      protected HTML.Tag alternateAddTag;
+      
+      /**
+       * Alternate tag to check if parentTag is not found.
+       */
+      protected HTML.Tag alternateParentTag;
+      
+      /**
+       * HTML to insert.
+       */
+      protected String html;
+      
+      /**
+       * Tag to check for in the document.
+       */
+      protected HTML.Tag parentTag;
+      
+      /**
+       * Initializes all fields.
+       * 
+       * @param name - the name of the document.
+       * @param html - the html to insert
+       * @param parentTag - the parent tag to check for
+       * @param addTag - the tag to start adding from
+       */
+      public InsertHTMLTextAction(String name, String html, 
+                                  HTML.Tag parentTag, HTML.Tag addTag)
+      {
+        this(name, html, parentTag, addTag, null, null);
+      }
+      
+      /**
+       * Initializes all fields and calls super
+       * 
+       * @param name - the name of the document.
+       * @param html - the html to insert
+       * @param parentTag - the parent tag to check for
+       * @param addTag - the tag to start adding from
+       * @param alternateParentTag - the alternate parent tag
+       * @param alternateAddTag - the alternate add tag
+       */
+      public InsertHTMLTextAction(String name, String html, HTML.Tag parentTag, 
+                                  HTML.Tag addTag, HTML.Tag alternateParentTag, 
+                                  HTML.Tag alternateAddTag) 
+      {
+        super(name);
+        // Fields are for easy access when the action is applied to an actual
+        // document.
+        this.html = html;
+        this.parentTag = parentTag;
+        this.addTag = addTag;
+        this.alternateParentTag = alternateParentTag;
+        this.alternateAddTag = alternateAddTag;
+      }
+      
+      /**
+       * HTMLEditorKit.insertHTML is called. If an exception is
+       * thrown, it is wrapped in a RuntimeException and thrown.
+       * 
+       * @param editor - the editor to use to get the editorkit
+       * @param doc -
+       *          the Document to insert the HTML into.
+       * @param offset -
+       *          where to begin inserting the HTML.
+       * @param html -
+       *          the String to insert
+       * @param popDepth -
+       *          the number of ElementSpec.EndTagTypes to generate before
+       *          inserting
+       * @param pushDepth -
+       *          the number of ElementSpec.StartTagTypes with a direction of
+       *          ElementSpec.JoinNextDirection that should be generated before
+       * @param addTag -
+       *          the first tag to start inserting into document
+       */
+      protected void insertHTML(JEditorPane editor, HTMLDocument doc, int offset,
+                              String html, int popDepth, int pushDepth,
+                              HTML.Tag addTag)
+      {
+        try
+          {
+            super.getHTMLEditorKit(editor).insertHTML(doc, offset, html,
+                                                      popDepth, pushDepth, addTag);
+          }
+        catch (IOException e)
+          {
+            throw (RuntimeException) new RuntimeException("Parser is null.").initCause(e);
+          }
+        catch (BadLocationException ex)
+          {
+            throw (RuntimeException) new RuntimeException("BadLocationException: "
+                                              + offset).initCause(ex);
+          }
+      }
+      
+      /**
+       * Invoked when inserting at a boundary. Determines the number of pops,
+       * and then the number of pushes that need to be performed. The it calls
+       * insertHTML.
+       * 
+       * @param editor -
+       *          the editor to use to get the editorkit
+       * @param doc -
+       *          the Document to insert the HTML into.
+       * @param offset -
+       *          where to begin inserting the HTML.
+       * @param insertElement -
+       *          the element to insert
+       * @param html -
+       *          the html to insert
+       * @param parentTag -
+       *          the parent tag
+       * @param addTag -
+       *          the first tag
+       */
+      protected void insertAtBoundary(JEditorPane editor,
+                                      HTMLDocument doc, int offset,
+                                      Element insertElement,
+                                      String html, HTML.Tag parentTag,
+                                      HTML.Tag addTag)
+      {
+        /*
+        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
+      }
+      
+      /**
+       * Invoked when inserting at a boundary. Determines the number of pops, 
+       * and then the number of pushes that need to be performed. The it calls
+       * insertHTML.
+       * 
+       * @param editor - the editor to use to get the editorkit
+       * @param doc -
+       *          the Document to insert the HTML into.
+       * @param offset -
+       *          where to begin inserting the HTML.
+       * @param insertElement - the element to insert
+       * @param html - the html to insert
+       * @param parentTag - the parent tag
+       * @param addTag - the first tag
+       * 
+       * @deprecated as of v1.3, use insertAtBoundary
+       */
+      protected void insertAtBoundry(JEditorPane editor,
+                                     HTMLDocument doc,
+                                     int offset, Element insertElement,
+                                     String html, HTML.Tag parentTag,
+                                     HTML.Tag addTag)
+      {
+        insertAtBoundary(editor, doc, offset, insertElement,
+                         html, parentTag, addTag);
+      }
+      
+      /**
+       * Inserts the HTML.
+       * 
+       * @param ae - the action performed
+       */
+      public void actionPerformed(ActionEvent ae)
+      {
+        Object source = ae.getSource();
+        if (source instanceof JEditorPane)
+          {
+            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?
+          }
+        // FIXME: else not implemented
+      }
+  }
+  
+  /**
+   * Abstract Action class that helps inserting HTML into an existing document.
+   */
+  public abstract static class HTMLTextAction
+    extends StyledEditorKit.StyledTextAction
+    {
+      
+      /**
+       * Constructor
+       */
+      public HTMLTextAction(String name) 
+      {
+        super(name);
+      }
+      
+      /**
+       * Gets the HTMLDocument from the JEditorPane.
+       * 
+       * @param e - the editor pane
+       * @return the html document.
+       */
+      protected HTMLDocument getHTMLDocument(JEditorPane e)
+      {
+        Document d = e.getDocument();
+        if (d instanceof HTMLDocument)
+          return (HTMLDocument) d;
+        throw new IllegalArgumentException("Document is not a HTMLDocument.");
+      }
+      
+      /**
+       * Gets the HTMLEditorKit
+       *  
+       * @param e - the JEditorPane to get the HTMLEditorKit from.
+       * @return the HTMLEditorKit
+       */
+      protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) 
+      {
+        EditorKit d = e.getEditorKit();
+        if (d instanceof HTMLEditorKit)
+          return (HTMLEditorKit) d;
+        throw new IllegalArgumentException("EditorKit is not a HTMLEditorKit.");
+      }
+      
+      /**
+       * Returns an array of Elements that contain the offset.
+       * The first elements corresponds to the roots of the doc.
+       * 
+       * @param doc - the document to get the Elements from.
+       * @param offset - the offset the Elements must contain
+       * @return an array of all the elements containing the offset.
+       */
+      protected Element[] getElementsAt(HTMLDocument doc,
+                                        int offset)
+      {
+        return getElementsAt(doc.getDefaultRootElement(), offset, 0);
+      }
+      
+      /**
+       * Helper function to get all elements using recursion.
+       */
+      private Element[] getElementsAt(Element root, int offset, int depth)
+      {
+        Element[] elements = null;
+        if (root != null)
+          {
+            if (root.isLeaf())
+              {
+                elements = new Element[depth + 1];
+                elements[depth] = root;
+                return elements;
+              }
+            elements = getElementsAt(root.getElement(root.getElementIndex(offset)),
+                                     offset, depth + 1);
+            elements[depth] = root;
+          }
+        return elements;
+      }
+      
+      /**
+       * Returns the number of elements, starting at the deepest point, needed
+       * to get an element representing tag. -1 if no elements are found, 0 if
+       * the parent of the leaf at offset represents the tag.
+       * 
+       * @param doc -
+       *          the document to search
+       * @param offset -
+       *          the offset to check
+       * @param tag -
+       *          the tag to look for
+       * @return - the number of elements needed to get an element representing
+       *         tag.
+       */
+      protected int elementCountToTag(HTMLDocument doc,
+                                      int offset, HTML.Tag tag)
+      {
+        Element root = doc.getDefaultRootElement();
+        int num = -1;
+        Element next = root.getElement(root.getElementIndex(offset));
+        
+        while (!next.isLeaf())
+          {
+            num++;
+            if (next.getAttributes().
+                getAttribute(StyleConstants.NameAttribute).equals(tag))
+              return num;
+            next = next.getElement(next.getElementIndex(offset));
+          }
+        return num;
+      }
+      
+      /**
+       * Gets the deepest element at offset with the
+       * matching tag.
+       * 
+       * @param doc - the document to search
+       * @param offset - the offset to check for
+       * @param tag - the tag to match
+       * @return - the element that is found, null if not found.
+       */
+      protected Element findElementMatchingTag(HTMLDocument doc,
+                                               int offset, HTML.Tag tag)
+      {
+        Element element = doc.getDefaultRootElement();
+        Element tagElement = null;
+        
+        while (element != null)
+          {
+            Object otag = element.getAttributes().getAttribute(
+                                     StyleConstants.NameAttribute);
+            if (otag instanceof HTML.Tag && otag.equals(tag))
+              tagElement = element;
+            element = element.getElement(element.getElementIndex(offset));
+          }
+        
+        return tagElement;
+      }
+    }
+  
+  /**
+   * A {@link ViewFactory} that is able to create {@link View}s for
+   * the Elements that are supported.
+   */
+  public static class HTMLFactory
+    implements ViewFactory
+  {
+    
+    /**
+     * Constructor
+     */
+    public HTMLFactory()
+    {
+      // Do Nothing here.
+    }
+    
+    /**
+     * Creates a {@link View} for the specified Element.
+     *
+     * @param element the Element to create a View
+     *        for
+     * @return the View for the specified Element
+     *         or null if the type of element is
+     *         not supported
+     */
+    public View create(Element element)
+    {
+      View view = null;
+      Object attr = element.getAttributes().getAttribute(
+                                StyleConstants.NameAttribute);
+      if (attr instanceof HTML.Tag)
+        {
+          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))
+            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))
+            view = new BlockView(element, View.Y_AXIS);
+          
+          // FIXME: Uncomment when the views have been implemented
+         /* else if (tag.equals(HTML.Tag.CONTENT))
+            view = new InlineView(element); 
+          else if (tag.equals(HTML.Tag.MENU) || tag.equals(HTML.Tag.DIR)
+                   || tag.equals(HTML.Tag.UL) || tag.equals(HTML.Tag.OL))
+            view = new ListView(element);
+          else if (tag.equals(HTML.Tag.IMG))
+            view = new ImageView(element);
+          else if (tag.equals(HTML.Tag.HR))
+            view = new HRuleView(element);
+          else if (tag.equals(HTML.Tag.BR))
+            view = new BRView(element);
+          else if (tag.equals(HTML.Tag.TABLE))
+            view = new TableView(element);
+          else if (tag.equals(HTML.Tag.INPUT) || tag.equals(HTML.Tag.SELECT)
+                   || tag.equals(HTML.Tag.TEXTAREA))
+            view = new FormView(element);
+          else if (tag.equals(HTML.Tag.OBJECT))
+            view = new ObjectView(element);
+          else if (tag.equals(HTML.Tag.FRAMESET))
+            view = new FrameSetView(element);
+          else if (tag.equals(HTML.Tag.FRAME))
+            view = new FrameView(element); */
+        }      
+      
+      if (view == null)
+        {
+          String name = element.getName();
+          if (name.equals(AbstractDocument.ContentElementName))
+            view = new LabelView(element);
+          else if (name.equals(AbstractDocument.ParagraphElementName))
+            view = new ParagraphView(element);
+          else if (name.equals(AbstractDocument.SectionElementName))
+            view = new BoxView(element, View.Y_AXIS);
+          else if (name.equals(StyleConstants.ComponentElementName))
+            view = new ComponentView(element);
+          else if (name.equals(StyleConstants.IconElementName))
+            view = new IconView(element);
+        }
+      return view;
+    }
+  }
+  
   /**
    * The abstract HTML parser declaration.
    */
@@ -76,9 +611,7 @@ public class HTMLEditorKit
      * @throws IOException, normally if the reader throws one.
      */
     public abstract void parse(Reader reader, ParserCallback callback,
-                               boolean ignoreCharSet
-                              )
-                        throws IOException;
+                               boolean ignoreCharSet) throws IOException;
   }
 
   /**
@@ -96,11 +629,19 @@ public class HTMLEditorKit
     public static final Object IMPLIED = "_implied_";
 
     /**
+     * Constructor
+     */
+    public ParserCallback()
+    {
+      // Nothing to do here.
+    }
+    
+    /**
      * The parser calls this method after it finishes parsing the document.
      */
     public void flush() throws BadLocationException
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
 
     /**
@@ -110,7 +651,7 @@ public class HTMLEditorKit
      */
     public void handleComment(char[] comment, int position)
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
 
     /**
@@ -121,7 +662,7 @@ public class HTMLEditorKit
      */
     public void handleEndOfLineString(String end_of_line)
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
 
     /**
@@ -133,7 +674,7 @@ public class HTMLEditorKit
      */
     public void handleEndTag(HTML.Tag tag, int position)
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
 
     /**
@@ -144,7 +685,7 @@ public class HTMLEditorKit
      */
     public void handleError(String message, int position)
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
 
     /**
@@ -157,7 +698,7 @@ public class HTMLEditorKit
     public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes,
                                 int position)
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
 
     /**
@@ -168,10 +709,9 @@ public class HTMLEditorKit
      * @param position The tag position in the text being parsed
      */
     public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes,
-                               int position
-                              )
+                               int position)
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
 
     /**
@@ -181,7 +721,7 @@ public class HTMLEditorKit
      */
     public void handleText(char[] text, int position)
     {
-      // TODO: What to do here, if anything?
+      // Nothing to do here.
     }
   }
 
@@ -255,7 +795,83 @@ public class HTMLEditorKit
    * The "ident paragraph right" action.
    */
   public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
-
+  
+  /**
+   * Actions for HTML 
+   */
+  private static final Action[] defaultActions = {
+    // FIXME: Add default actions for html
+  };
+  
+  /**
+   * The current style sheet.
+   */
+  StyleSheet styleSheet;
+  
+  /**
+   * The ViewFactory for HTMLFactory.
+   */
+  HTMLFactory viewFactory;
+  
+  /**
+   * The Cursor for links.
+   */
+  Cursor linkCursor;
+  
+  /**
+   * The default cursor.
+   */
+  Cursor defaultCursor;
+  
+  /**
+   * The parser.
+   */
+  Parser parser;
+  
+  /**
+   * The mouse listener used for links.
+   */
+  LinkController mouseListener;
+  
+  /**
+   * Style context for this editor.
+   */
+  StyleContext styleContext;
+  
+  /** The content type */
+  String contentType = "text/html";
+  
+  /** The input attributes defined by default.css */
+  MutableAttributeSet inputAttributes;
+  
+  /** The editor pane used. */
+  JEditorPane editorPane;
+    
+  /**
+   * 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    
+  }
+  
+  /**
+   * Gets a factory suitable for producing views of any 
+   * models that are produced by this kit.
+   * 
+   * @return the view factory suitable for producing views.
+   */
+  public ViewFactory getViewFactory()
+  {
+    if (viewFactory == null)
+      viewFactory = new HTMLFactory();
+    return viewFactory;
+  }
+  
   /**
    * Create a text storage model for this type of editor.
    *
@@ -263,18 +879,297 @@ public class HTMLEditorKit
    */
   public Document createDefaultDocument()
   {
-    HTMLDocument document = new HTMLDocument();
+    HTMLDocument document = new HTMLDocument(getStyleSheet());
+    document.setParser(getParser());
     return document;
   }
 
   /**
    * Get the parser that this editor kit uses for reading HTML streams. This
    * method can be overridden to use the alternative parser.
-   *
+   * 
    * @return the HTML parser (by default, {@link ParserDelegator}).
    */
   protected Parser getParser()
   {
-    return new ParserDelegator();
+    if (parser == null)
+      parser = new ParserDelegator();
+    return parser;
+  }
+  
+  /**
+   * Inserts HTML into an existing document.
+   * 
+   * @param doc - the Document to insert the HTML into.
+   * @param offset - where to begin inserting the HTML.
+   * @param html - the String to insert
+   * @param popDepth - the number of ElementSpec.EndTagTypes 
+   * to generate before inserting
+   * @param pushDepth - the number of ElementSpec.StartTagTypes 
+   * with a direction of ElementSpec.JoinNextDirection that 
+   * should be generated before
+   * @param insertTag - the first tag to start inserting into document
+   * @throws IOException - on any I/O error
+   * @throws BadLocationException - if pos represents an invalid location
+   * within the document
+   */
+  public void insertHTML(HTMLDocument doc, int offset, String html,
+                         int popDepth, int pushDepth, HTML.Tag insertTag)
+      throws BadLocationException, IOException
+  {
+    Parser parser = getParser();
+    if (offset < 0 || offset > doc.getLength())
+      throw new BadLocationException("Bad location", offset);
+    if (parser == null)
+      throw new IOException("Parser is null.");
+
+    ParserCallback pc = ((HTMLDocument) doc).getReader
+                          (offset, popDepth, pushDepth, insertTag);
+
+    // FIXME: What should ignoreCharSet be set to?
+    
+    // parser.parse inserts html into the buffer
+    parser.parse(new StringReader(html), pc, false);
+    pc.flush();
+  }
+  
+  /**
+   * Inserts content from the given stream. Inserting HTML into a non-empty 
+   * document must be inside the body Element, if you do not insert into 
+   * the body an exception will be thrown. When inserting into a non-empty 
+   * document all tags outside of the body (head, title) will be dropped.
+   * 
+   * @param in - the stream to read from
+   * @param doc - the destination for the insertion
+   * @param pos - the location in the document to place the content
+   * @throws IOException - on any I/O error
+   * @throws BadLocationException - if pos represents an invalid location
+   * within the document
+   */
+  public void read(Reader in, Document doc, int pos) throws IOException,
+      BadLocationException
+  {
+    if (doc instanceof HTMLDocument)
+      {
+        Parser parser = getParser();
+        if (pos < 0 || pos > doc.getLength())
+          throw new BadLocationException("Bad location", pos);
+        if (parser == null)
+          throw new IOException("Parser is null.");
+        
+        HTMLDocument hd = ((HTMLDocument) doc);
+        hd.setBase(editorPane.getPage());
+        ParserCallback pc = hd.getReader(pos);
+        
+        // FIXME: What should ignoreCharSet be set to?
+        
+        // parser.parse inserts html into the buffer
+        parser.parse(in, pc, false);
+        pc.flush();
+      }
+    else
+      // read in DefaultEditorKit is called.
+      // the string is inserted in the document as usual.
+      super.read(in, doc, pos);
+  }
+  
+  /**
+   * Writes content from a document to the given stream in 
+   * an appropriate format.
+   * 
+   * @param out - the stream to write to
+   * @param doc - the source for the write
+   * @param pos - the location in the document to get the content.
+   * @param len - the amount to write out
+   * @throws IOException - on any I/O error
+   * @throws BadLocationException - if pos represents an invalid location
+   * within the document
+   */
+  public void write(Writer out, Document doc, int pos, int len)
+      throws IOException, BadLocationException
+  {
+    if (doc instanceof HTMLDocument)
+      {
+        // FIXME: Not implemented. Use HTMLWriter.
+        out.write(doc.getText(pos, len));
+      }
+    else
+      super.write(out, doc, pos, len);
+  }
+  
+  /**
+   * Gets the content type that the kit supports.
+   * This kit supports the type text/html.
+   * 
+   * @returns the content type supported.
+   */
+  public String getContentType()
+  {
+    return contentType;
+  } 
+  
+  /**
+   * Creates a copy of the editor kit.
+   * 
+   * @return a copy of this.
+   */
+  public Object clone()
+  {
+    // FIXME: Need to clone all fields
+    return (HTMLEditorKit) super.clone();
+  }
+  
+  /**
+   * Copies the key/values in elements AttributeSet into set. 
+   * This does not copy component, icon, or element names attributes.
+   * This is called anytime the caret moves over a different location. 
+   * 
+   * @param element - the element to create the input attributes for.
+   * @param set - the set to copy the values into.
+   */
+  protected void createInputAttributes(Element element,
+                                       MutableAttributeSet set)
+  {
+    set.removeAttributes(set);
+    set.addAttributes(element.getAttributes());
+    // FIXME: Not fully implemented.
+  }
+  
+  /**
+   * Called when this is installed into the JEditorPane.
+   * 
+   * @param c - the JEditorPane installed into.
+   */
+  public void install(JEditorPane c)
+  {
+    super.install(c);
+    mouseListener = new LinkController();
+    c.addMouseListener(mouseListener);
+    editorPane = c;
+    // FIXME: need to set up hyperlinklistener object
+  }
+  
+  /**
+   * Called when the this is removed from the JEditorPane.
+   * It unregisters any listeners.
+   * 
+   * @param c - the JEditorPane being removed from.
+   */
+  public void deinstall(JEditorPane c)
+  {
+    super.deinstall(c);
+    c.removeMouseListener(mouseListener);
+    mouseListener = null;
+    editorPane = null;
+  }
+  
+  /**
+   * Gets the AccessibleContext associated with this.
+   * 
+   * @return the AccessibleContext for this.
+   */
+  public AccessibleContext getAccessibleContext()
+  {
+    // FIXME: Should return an instance of 
+    // javax.swing.text.html.AccessibleHTML$RootHTMLAccessibleContext
+    // Not implemented yet.
+    return null;
+  }
+  
+  /**
+   * Gets the action list. This list is supported by the superclass
+   * augmented by the collection of actions defined locally for style
+   * operations.
+   * 
+   * @return an array of all the actions
+   */
+  public Action[] getActions()
+  {
+    return TextAction.augmentList(super.getActions(), defaultActions);
+  }
+  
+  /**
+   * Returns the default cursor.
+   * 
+   * @return the default cursor
+   */
+  public Cursor getDefaultCursor()
+  {
+    if (defaultCursor == null)
+      defaultCursor = Cursor.getDefaultCursor();
+    return defaultCursor;
+  }
+  
+  /**
+   * Returns the cursor for links.
+   * 
+   * @return the cursor for links.
+   */
+  public Cursor getLinkCursor()
+  {
+    if (linkCursor == null)
+      linkCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    return linkCursor;
+  }
+  
+  /**
+   * Sets the Cursor for links.
+   * 
+   * @param cursor - the new cursor for links.
+   */
+  public void setLinkCursor(Cursor cursor)
+  {
+    linkCursor = cursor;
+  }
+  
+  /**
+   * Sets the default cursor.
+   * 
+   * @param cursor - the new default cursor.
+   */
+  public void setDefaultCursor(Cursor cursor)
+  {
+    defaultCursor = cursor;
+  }
+  
+  /**
+   * Gets the input attributes used for the styled editing actions.
+   * 
+   * @return the attribute set
+   */
+  public MutableAttributeSet getInputAttributes()
+  {
+    return inputAttributes;
+  }
+  
+  /**
+   * Get the set of styles currently being used to render the HTML elements. 
+   * By default the resource specified by DEFAULT_CSS gets loaded, and is 
+   * shared by all HTMLEditorKit instances.
+   * 
+   * @return the style sheet.
+   */
+  public StyleSheet getStyleSheet()
+  {
+    if (styleSheet == null)
+      {
+        styleSheet = new StyleSheet();
+        styleSheet.importStyleSheet(getClass().getResource(DEFAULT_CSS));
+      }
+    return styleSheet;
+  }
+  
+  /**
+   * Set the set of styles to be used to render the various HTML elements. 
+   * These styles are specified in terms of CSS specifications. Each document 
+   * produced by the kit will have a copy of the sheet which it can add the 
+   * document specific styles to. By default, the StyleSheet specified is shared 
+   * by all HTMLEditorKit instances. 
+   * 
+   * @param s - the new style sheet
+   */
+  public void setStyleSheet(StyleSheet s)
+  {
+    styleSheet = s;
   }
-}
\ No newline at end of file
+}
diff --git a/libjava/classpath/javax/swing/text/html/StyleSheet.java b/libjava/classpath/javax/swing/text/html/StyleSheet.java
new file mode 100644
index 0000000..2466a28
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/StyleSheet.java
@@ -0,0 +1,937 @@
+/* StyleSheet.java -- 
+   Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StringReader;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+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.StyleContext;
+import javax.swing.text.View;
+
+
+/**
+ * This class adds support for defining the visual characteristics of HTML views
+ * being rendered. This enables views to be customized by a look-and-feel, mulitple
+ * views over the same model can be rendered differently. Each EditorPane has its 
+ * own StyleSheet, but by default one sheet will be shared by all of the HTMLEditorKit
+ * instances. An HTMLDocument can also have a StyleSheet, which holds specific CSS
+ * specs. 
+ * 
+ *  In order for Views to store less state and therefore be more lightweight, 
+ *  the StyleSheet can act as a factory for painters that handle some of the 
+ *  rendering tasks. Since the StyleSheet may be used by views over multiple
+ *  documents the HTML attributes don't effect the selector being used.
+ *  
+ *  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
+{
+
+  /** The base URL */
+  URL base;
+  
+  /** Base font size (int) */
+  int baseFontSize;
+  
+  /** The style sheets stored. */
+  StyleSheet[] styleSheet;
+  
+  /**
+   * Constructs a StyleSheet.
+   */
+  public StyleSheet()
+  {
+    super();
+    baseFontSize = 4; // Default font size from CSS
+  }
+
+  /**
+   * Gets the style used to render the given tag. The element represents the tag
+   * and can be used to determine the nesting, where the attributes will differ
+   * if there is nesting inside of elements.
+   * 
+   * @param t - the tag to translate to visual attributes
+   * @param e - the element representing the tag
+   * @return the set of CSS attributes to use to render the tag.
+   */
+  public Style getRule(HTML.Tag t, Element e)
+  {
+    // FIXME: Not implemented.
+    return null;
+  }
+  
+  /**
+   * Gets the rule that best matches the selector. selector is a space
+   * separated String of element names. The attributes of the returned 
+   * Style will change as rules are added and removed.
+   * 
+   * @param selector - the element names separated by spaces
+   * @return the set of CSS attributes to use to render
+   */
+  public Style getRule(String selector)
+  {
+    // FIXME: Not implemented.
+    return null; 
+  }
+  
+  /**
+   * Adds a set if rules to the sheet. The rules are expected to be in valid
+   * CSS format. This is called as a result of parsing a