summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/text')
-rw-r--r--libjava/classpath/javax/swing/text/AbstractDocument.java2906
-rw-r--r--libjava/classpath/javax/swing/text/AbstractWriter.java481
-rw-r--r--libjava/classpath/javax/swing/text/AsyncBoxView.java1480
-rw-r--r--libjava/classpath/javax/swing/text/AttributeSet.java195
-rw-r--r--libjava/classpath/javax/swing/text/BadLocationException.java78
-rw-r--r--libjava/classpath/javax/swing/text/BoxView.java1112
-rw-r--r--libjava/classpath/javax/swing/text/Caret.java207
-rw-r--r--libjava/classpath/javax/swing/text/ChangedCharSetException.java100
-rw-r--r--libjava/classpath/javax/swing/text/ComponentView.java494
-rw-r--r--libjava/classpath/javax/swing/text/CompositeView.java792
-rw-r--r--libjava/classpath/javax/swing/text/DateFormatter.java85
-rw-r--r--libjava/classpath/javax/swing/text/DefaultCaret.java1287
-rw-r--r--libjava/classpath/javax/swing/text/DefaultEditorKit.java1702
-rw-r--r--libjava/classpath/javax/swing/text/DefaultFormatter.java433
-rw-r--r--libjava/classpath/javax/swing/text/DefaultFormatterFactory.java280
-rw-r--r--libjava/classpath/javax/swing/text/DefaultHighlighter.java478
-rw-r--r--libjava/classpath/javax/swing/text/DefaultStyledDocument.java2526
-rw-r--r--libjava/classpath/javax/swing/text/DefaultTextUI.java62
-rw-r--r--libjava/classpath/javax/swing/text/Document.java221
-rw-r--r--libjava/classpath/javax/swing/text/DocumentFilter.java83
-rw-r--r--libjava/classpath/javax/swing/text/EditorKit.java98
-rw-r--r--libjava/classpath/javax/swing/text/Element.java54
-rw-r--r--libjava/classpath/javax/swing/text/ElementIterator.java272
-rw-r--r--libjava/classpath/javax/swing/text/EmptyAttributeSet.java153
-rw-r--r--libjava/classpath/javax/swing/text/FieldView.java310
-rw-r--r--libjava/classpath/javax/swing/text/FlowView.java841
-rw-r--r--libjava/classpath/javax/swing/text/GapContent.java1095
-rw-r--r--libjava/classpath/javax/swing/text/GlyphView.java1333
-rw-r--r--libjava/classpath/javax/swing/text/Highlighter.java78
-rw-r--r--libjava/classpath/javax/swing/text/IconView.java175
-rw-r--r--libjava/classpath/javax/swing/text/InternationalFormatter.java356
-rw-r--r--libjava/classpath/javax/swing/text/JTextComponent.java2059
-rw-r--r--libjava/classpath/javax/swing/text/Keymap.java58
-rw-r--r--libjava/classpath/javax/swing/text/LabelView.java322
-rw-r--r--libjava/classpath/javax/swing/text/LayeredHighlighter.java57
-rw-r--r--libjava/classpath/javax/swing/text/LayoutQueue.java116
-rw-r--r--libjava/classpath/javax/swing/text/MaskFormatter.java558
-rw-r--r--libjava/classpath/javax/swing/text/MutableAttributeSet.java117
-rw-r--r--libjava/classpath/javax/swing/text/NavigationFilter.java98
-rw-r--r--libjava/classpath/javax/swing/text/NumberFormatter.java86
-rw-r--r--libjava/classpath/javax/swing/text/ParagraphView.java528
-rw-r--r--libjava/classpath/javax/swing/text/PasswordView.java254
-rw-r--r--libjava/classpath/javax/swing/text/PlainDocument.java292
-rw-r--r--libjava/classpath/javax/swing/text/PlainView.java724
-rw-r--r--libjava/classpath/javax/swing/text/Position.java62
-rw-r--r--libjava/classpath/javax/swing/text/Segment.java297
-rw-r--r--libjava/classpath/javax/swing/text/SimpleAttributeSet.java415
-rw-r--r--libjava/classpath/javax/swing/text/StringContent.java569
-rw-r--r--libjava/classpath/javax/swing/text/Style.java64
-rw-r--r--libjava/classpath/javax/swing/text/StyleConstants.java1083
-rw-r--r--libjava/classpath/javax/swing/text/StyleContext.java990
-rw-r--r--libjava/classpath/javax/swing/text/StyledDocument.java140
-rw-r--r--libjava/classpath/javax/swing/text/StyledEditorKit.java707
-rw-r--r--libjava/classpath/javax/swing/text/TabExpander.java43
-rw-r--r--libjava/classpath/javax/swing/text/TabSet.java210
-rw-r--r--libjava/classpath/javax/swing/text/TabStop.java190
-rw-r--r--libjava/classpath/javax/swing/text/TabableView.java44
-rw-r--r--libjava/classpath/javax/swing/text/TableView.java491
-rw-r--r--libjava/classpath/javax/swing/text/TextAction.java240
-rw-r--r--libjava/classpath/javax/swing/text/Utilities.java730
-rw-r--r--libjava/classpath/javax/swing/text/View.java881
-rw-r--r--libjava/classpath/javax/swing/text/ViewFactory.java50
-rw-r--r--libjava/classpath/javax/swing/text/WrappedPlainView.java795
-rw-r--r--libjava/classpath/javax/swing/text/ZoneView.java442
-rw-r--r--libjava/classpath/javax/swing/text/html/BRView.java70
-rw-r--r--libjava/classpath/javax/swing/text/html/BlockView.java721
-rw-r--r--libjava/classpath/javax/swing/text/html/CSS.java736
-rw-r--r--libjava/classpath/javax/swing/text/html/CSSBorder.java421
-rw-r--r--libjava/classpath/javax/swing/text/html/CSSParser.java561
-rw-r--r--libjava/classpath/javax/swing/text/html/FormSubmitEvent.java123
-rw-r--r--libjava/classpath/javax/swing/text/html/FormView.java870
-rw-r--r--libjava/classpath/javax/swing/text/html/FrameSetView.java274
-rw-r--r--libjava/classpath/javax/swing/text/html/FrameView.java233
-rw-r--r--libjava/classpath/javax/swing/text/html/HRuleView.java189
-rw-r--r--libjava/classpath/javax/swing/text/html/HTML.java1253
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLDocument.java2298
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLEditorKit.java1520
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java130
-rw-r--r--libjava/classpath/javax/swing/text/html/HTMLWriter.java1088
-rw-r--r--libjava/classpath/javax/swing/text/html/ImageView.java591
-rw-r--r--libjava/classpath/javax/swing/text/html/InlineView.java307
-rw-r--r--libjava/classpath/javax/swing/text/html/ListView.java128
-rw-r--r--libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java453
-rw-r--r--libjava/classpath/javax/swing/text/html/MultiAttributeSet.java213
-rw-r--r--libjava/classpath/javax/swing/text/html/MultiStyle.java136
-rw-r--r--libjava/classpath/javax/swing/text/html/NullView.java102
-rw-r--r--libjava/classpath/javax/swing/text/html/ObjectView.java110
-rw-r--r--libjava/classpath/javax/swing/text/html/Option.java159
-rw-r--r--libjava/classpath/javax/swing/text/html/ParagraphView.java322
-rw-r--r--libjava/classpath/javax/swing/text/html/ResetableModel.java50
-rw-r--r--libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java82
-rw-r--r--libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java70
-rw-r--r--libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java84
-rw-r--r--libjava/classpath/javax/swing/text/html/SelectListModel.java106
-rw-r--r--libjava/classpath/javax/swing/text/html/StyleSheet.java1455
-rw-r--r--libjava/classpath/javax/swing/text/html/TableView.java974
-rw-r--r--libjava/classpath/javax/swing/text/html/ViewAttributeSet.java163
-rw-r--r--libjava/classpath/javax/swing/text/html/package.html50
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/AttributeList.java294
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/ContentModel.java223
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/DTD.java609
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/DTDConstants.java292
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/DocumentParser.java268
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/Element.java317
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/Entity.java183
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/Parser.java446
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java207
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/TagElement.java142
-rw-r--r--libjava/classpath/javax/swing/text/html/parser/package.html50
-rw-r--r--libjava/classpath/javax/swing/text/package.html46
-rw-r--r--libjava/classpath/javax/swing/text/rtf/ControlWordToken.java86
-rw-r--r--libjava/classpath/javax/swing/text/rtf/RTFEditorKit.java114
-rw-r--r--libjava/classpath/javax/swing/text/rtf/RTFParseException.java65
-rw-r--r--libjava/classpath/javax/swing/text/rtf/RTFParser.java203
-rw-r--r--libjava/classpath/javax/swing/text/rtf/RTFScanner.java294
-rw-r--r--libjava/classpath/javax/swing/text/rtf/TextToken.java65
-rw-r--r--libjava/classpath/javax/swing/text/rtf/Token.java91
-rw-r--r--libjava/classpath/javax/swing/text/rtf/package.html48
118 files changed, 53564 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java
new file mode 100644
index 000000000..25915bb5a
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/AbstractDocument.java
@@ -0,0 +1,2906 @@
+/* AbstractDocument.java --
+ Copyright (C) 2002, 2004, 2005, 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 gnu.java.lang.CPStringBuilder;
+
+import java.awt.font.TextAttribute;
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.text.Bidi;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.EventListenerList;
+import javax.swing.event.UndoableEditEvent;
+import javax.swing.event.UndoableEditListener;
+import javax.swing.text.DocumentFilter;
+import javax.swing.tree.TreeNode;
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CompoundEdit;
+import javax.swing.undo.UndoableEdit;
+
+/**
+ * An abstract base implementation for the {@link Document} interface.
+ * This class provides some common functionality for all <code>Element</code>s,
+ * most notably it implements a locking mechanism to make document modification
+ * thread-safe.
+ *
+ * @author original author unknown
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public abstract class AbstractDocument implements Document, Serializable
+{
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 6842927725919637215L;
+
+ /**
+ * Standard error message to indicate a bad location.
+ */
+ protected static final String BAD_LOCATION = "document location failure";
+
+ /**
+ * Standard name for unidirectional <code>Element</code>s.
+ */
+ public static final String BidiElementName = "bidi level";
+
+ /**
+ * Standard name for content <code>Element</code>s. These are usually
+ * {@link LeafElement}s.
+ */
+ public static final String ContentElementName = "content";
+
+ /**
+ * Standard name for paragraph <code>Element</code>s. These are usually
+ * {@link BranchElement}s.
+ */
+ public static final String ParagraphElementName = "paragraph";
+
+ /**
+ * Standard name for section <code>Element</code>s. These are usually
+ * {@link DefaultStyledDocument.SectionElement}s.
+ */
+ public static final String SectionElementName = "section";
+
+ /**
+ * Attribute key for storing the element name.
+ */
+ public static final String ElementNameAttribute = "$ename";
+
+ /**
+ * Standard name for the bidi root element.
+ */
+ private static final String BidiRootName = "bidi root";
+
+ /**
+ * Key for storing the asynchronous load priority.
+ */
+ private static final String AsyncLoadPriority = "load priority";
+
+ /**
+ * Key for storing the I18N state.
+ */
+ private static final String I18N = "i18n";
+
+ /**
+ * The actual content model of this <code>Document</code>.
+ */
+ Content content;
+
+ /**
+ * The AttributeContext for this <code>Document</code>.
+ */
+ AttributeContext context;
+
+ /**
+ * The currently installed <code>DocumentFilter</code>.
+ */
+ DocumentFilter documentFilter;
+
+ /**
+ * The documents properties.
+ */
+ Dictionary properties;
+
+ /**
+ * Manages event listeners for this <code>Document</code>.
+ */
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Stores the current writer thread. Used for locking.
+ */
+ private Thread currentWriter = null;
+
+ /**
+ * The number of readers. Used for locking.
+ */
+ private int numReaders = 0;
+
+ /**
+ * The number of current writers. If this is > 1 then the same thread entered
+ * the write lock more than once.
+ */
+ private int numWriters = 0;
+
+ /** An instance of a DocumentFilter.FilterBypass which allows calling
+ * the insert, remove and replace method without checking for an installed
+ * document filter.
+ */
+ private DocumentFilter.FilterBypass bypass;
+
+ /**
+ * The bidi root element.
+ */
+ private BidiRootElement bidiRoot;
+
+ /**
+ * True when we are currently notifying any listeners. This is used
+ * to detect illegal situations in writeLock().
+ */
+ private transient boolean notifyListeners;
+
+ /**
+ * Creates a new <code>AbstractDocument</code> with the specified
+ * {@link Content} model.
+ *
+ * @param doc the <code>Content</code> model to be used in this
+ * <code>Document<code>
+ *
+ * @see GapContent
+ * @see StringContent
+ */
+ protected AbstractDocument(Content doc)
+ {
+ this(doc, StyleContext.getDefaultStyleContext());
+ }
+
+ /**
+ * Creates a new <code>AbstractDocument</code> with the specified
+ * {@link Content} model and {@link AttributeContext}.
+ *
+ * @param doc the <code>Content</code> model to be used in this
+ * <code>Document<code>
+ * @param ctx the <code>AttributeContext</code> to use
+ *
+ * @see GapContent
+ * @see StringContent
+ */
+ protected AbstractDocument(Content doc, AttributeContext ctx)
+ {
+ content = doc;
+ context = ctx;
+
+ // FIXME: Fully implement bidi.
+ bidiRoot = new BidiRootElement();
+
+ // FIXME: This is determined using a Mauve test. Make the document
+ // actually use this.
+ putProperty(I18N, Boolean.FALSE);
+
+ // Add one child to the bidi root.
+ writeLock();
+ try
+ {
+ Element[] children = new Element[1];
+ children[0] = new BidiElement(bidiRoot, 0, 1, 0);
+ bidiRoot.replace(0, 0, children);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ /** Returns the DocumentFilter.FilterBypass instance for this
+ * document and create it if it does not exist yet.
+ *
+ * @return This document's DocumentFilter.FilterBypass instance.
+ */
+ private DocumentFilter.FilterBypass getBypass()
+ {
+ if (bypass == null)
+ bypass = new Bypass();
+
+ return bypass;
+ }
+
+ /**
+ * Returns the paragraph {@link Element} that holds the specified position.
+ *
+ * @param pos the position for which to get the paragraph element
+ *
+ * @return the paragraph {@link Element} that holds the specified position
+ */
+ public abstract Element getParagraphElement(int pos);
+
+ /**
+ * Returns the default root {@link Element} of this <code>Document</code>.
+ * Usual <code>Document</code>s only have one root element and return this.
+ * However, there may be <code>Document</code> implementations that
+ * support multiple root elements, they have to return a default root element
+ * here.
+ *
+ * @return the default root {@link Element} of this <code>Document</code>
+ */
+ public abstract Element getDefaultRootElement();
+
+ /**
+ * Creates and returns a branch element with the specified
+ * <code>parent</code> and <code>attributes</code>. Note that the new
+ * <code>Element</code> is linked to the parent <code>Element</code>
+ * through {@link Element#getParentElement}, but it is not yet added
+ * to the parent <code>Element</code> as child.
+ *
+ * @param parent the parent <code>Element</code> for the new branch element
+ * @param attributes the text attributes to be installed in the new element
+ *
+ * @return the new branch <code>Element</code>
+ *
+ * @see BranchElement
+ */
+ protected Element createBranchElement(Element parent,
+ AttributeSet attributes)
+ {
+ return new BranchElement(parent, attributes);
+ }
+
+ /**
+ * Creates and returns a leaf element with the specified
+ * <code>parent</code> and <code>attributes</code>. Note that the new
+ * <code>Element</code> is linked to the parent <code>Element</code>
+ * through {@link Element#getParentElement}, but it is not yet added
+ * to the parent <code>Element</code> as child.
+ *
+ * @param parent the parent <code>Element</code> for the new branch element
+ * @param attributes the text attributes to be installed in the new element
+ *
+ * @return the new branch <code>Element</code>
+ *
+ * @see LeafElement
+ */
+ protected Element createLeafElement(Element parent, AttributeSet attributes,
+ int start, int end)
+ {
+ return new LeafElement(parent, attributes, start, end);
+ }
+
+ /**
+ * Creates a {@link Position} that keeps track of the location at the
+ * specified <code>offset</code>.
+ *
+ * @param offset the location in the document to keep track by the new
+ * <code>Position</code>
+ *
+ * @return the newly created <code>Position</code>
+ *
+ * @throws BadLocationException if <code>offset</code> is not a valid
+ * location in the documents content model
+ */
+ public synchronized Position createPosition(final int offset)
+ throws BadLocationException
+ {
+ return content.createPosition(offset);
+ }
+
+ /**
+ * Notifies all registered listeners when the document model changes.
+ *
+ * @param event the <code>DocumentEvent</code> to be fired
+ */
+ protected void fireChangedUpdate(DocumentEvent event)
+ {
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].changedUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
+ }
+
+ /**
+ * Notifies all registered listeners when content is inserted in the document
+ * model.
+ *
+ * @param event the <code>DocumentEvent</code> to be fired
+ */
+ protected void fireInsertUpdate(DocumentEvent event)
+ {
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].insertUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
+ }
+
+ /**
+ * Notifies all registered listeners when content is removed from the
+ * document model.
+ *
+ * @param event the <code>DocumentEvent</code> to be fired
+ */
+ protected void fireRemoveUpdate(DocumentEvent event)
+ {
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].removeUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
+ }
+
+ /**
+ * Notifies all registered listeners when an <code>UndoableEdit</code> has
+ * been performed on this <code>Document</code>.
+ *
+ * @param event the <code>UndoableEditEvent</code> to be fired
+ */
+ protected void fireUndoableEditUpdate(UndoableEditEvent event)
+ {
+ UndoableEditListener[] listeners = getUndoableEditListeners();
+
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].undoableEditHappened(event);
+ }
+
+ /**
+ * Returns the asynchronous loading priority. Returns <code>-1</code> if this
+ * document should not be loaded asynchronously.
+ *
+ * @return the asynchronous loading priority
+ */
+ public int getAsynchronousLoadPriority()
+ {
+ Object val = getProperty(AsyncLoadPriority);
+ int prio = -1;
+ if (val != null)
+ prio = ((Integer) val).intValue();
+ return prio;
+ }
+
+ /**
+ * Returns the {@link AttributeContext} used in this <code>Document</code>.
+ *
+ * @return the {@link AttributeContext} used in this <code>Document</code>
+ */
+ protected final AttributeContext getAttributeContext()
+ {
+ return context;
+ }
+
+ /**
+ * Returns the root element for bidirectional content.
+ *
+ * @return the root element for bidirectional content
+ */
+ public Element getBidiRootElement()
+ {
+ return bidiRoot;
+ }
+
+ /**
+ * Returns the {@link Content} model for this <code>Document</code>
+ *
+ * @return the {@link Content} model for this <code>Document</code>
+ *
+ * @see GapContent
+ * @see StringContent
+ */
+ protected final Content getContent()
+ {
+ return content;
+ }
+
+ /**
+ * Returns the thread that currently modifies this <code>Document</code>
+ * if there is one, otherwise <code>null</code>. This can be used to
+ * distinguish between a method call that is part of an ongoing modification
+ * or if it is a separate modification for which a new lock must be aquired.
+ *
+ * @return the thread that currently modifies this <code>Document</code>
+ * if there is one, otherwise <code>null</code>
+ */
+ protected final synchronized Thread getCurrentWriter()
+ {
+ return currentWriter;
+ }
+
+ /**
+ * Returns the properties of this <code>Document</code>.
+ *
+ * @return the properties of this <code>Document</code>
+ */
+ public Dictionary<Object, Object> getDocumentProperties()
+ {
+ // FIXME: make me thread-safe
+ if (properties == null)
+ properties = new Hashtable();
+
+ return properties;
+ }
+
+ /**
+ * Returns a {@link Position} which will always mark the end of the
+ * <code>Document</code>.
+ *
+ * @return a {@link Position} which will always mark the end of the
+ * <code>Document</code>
+ */
+ public final Position getEndPosition()
+ {
+ Position p;
+ try
+ {
+ p = createPosition(content.length());
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
+ }
+
+ /**
+ * Returns the length of this <code>Document</code>'s content.
+ *
+ * @return the length of this <code>Document</code>'s content
+ */
+ public int getLength()
+ {
+ // We return Content.getLength() -1 here because there is always an
+ // implicit \n at the end of the Content which does count in Content
+ // but not in Document.
+ return content.length() - 1;
+ }
+
+ /**
+ * Returns all registered listeners of a given listener type.
+ *
+ * @param listenerType the type of the listeners to be queried
+ *
+ * @return all registered listeners of the specified type
+ */
+ public <T extends EventListener> T[] getListeners(Class<T> listenerType)
+ {
+ return listenerList.getListeners(listenerType);
+ }
+
+ /**
+ * Returns a property from this <code>Document</code>'s property list.
+ *
+ * @param key the key of the property to be fetched
+ *
+ * @return the property for <code>key</code> or <code>null</code> if there
+ * is no such property stored
+ */
+ public final Object getProperty(Object key)
+ {
+ // FIXME: make me thread-safe
+ Object value = null;
+ if (properties != null)
+ value = properties.get(key);
+
+ return value;
+ }
+
+ /**
+ * Returns all root elements of this <code>Document</code>. By default
+ * this just returns the single root element returned by
+ * {@link #getDefaultRootElement()}. <code>Document</code> implementations
+ * that support multiple roots must override this method and return all roots
+ * here.
+ *
+ * @return all root elements of this <code>Document</code>
+ */
+ public Element[] getRootElements()
+ {
+ Element[] elements = new Element[2];
+ elements[0] = getDefaultRootElement();
+ elements[1] = getBidiRootElement();
+ return elements;
+ }
+
+ /**
+ * Returns a {@link Position} which will always mark the beginning of the
+ * <code>Document</code>.
+ *
+ * @return a {@link Position} which will always mark the beginning of the
+ * <code>Document</code>
+ */
+ public final Position getStartPosition()
+ {
+ Position p;
+ try
+ {
+ p = createPosition(0);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
+ }
+
+ /**
+ * Returns a piece of this <code>Document</code>'s content.
+ *
+ * @param offset the start offset of the content
+ * @param length the length of the content
+ *
+ * @return the piece of content specified by <code>offset</code> and
+ * <code>length</code>
+ *
+ * @throws BadLocationException if <code>offset</code> or <code>offset +
+ * length</code> are invalid locations with this
+ * <code>Document</code>
+ */
+ public String getText(int offset, int length) throws BadLocationException
+ {
+ return content.getString(offset, length);
+ }
+
+ /**
+ * Fetches a piece of this <code>Document</code>'s content and stores
+ * it in the given {@link Segment}.
+ *
+ * @param offset the start offset of the content
+ * @param length the length of the content
+ * @param segment the <code>Segment</code> to store the content in
+ *
+ * @throws BadLocationException if <code>offset</code> or <code>offset +
+ * length</code> are invalid locations with this
+ * <code>Document</code>
+ */
+ public void getText(int offset, int length, Segment segment)
+ throws BadLocationException
+ {
+ content.getChars(offset, length, segment);
+ }
+
+ /**
+ * Inserts a String into this <code>Document</code> at the specified
+ * position and assigning the specified attributes to it.
+ *
+ * <p>If a {@link DocumentFilter} is installed in this document, the
+ * corresponding method of the filter object is called.</p>
+ *
+ * <p>The method has no effect when <code>text</code> is <code>null</code>
+ * or has a length of zero.</p>
+ *
+ *
+ * @param offset the location at which the string should be inserted
+ * @param text the content to be inserted
+ * @param attributes the text attributes to be assigned to that string
+ *
+ * @throws BadLocationException if <code>offset</code> is not a valid
+ * location in this <code>Document</code>
+ */
+ public void insertString(int offset, String text, AttributeSet attributes)
+ throws BadLocationException
+ {
+ // Bail out if we have a bogus insertion (Behavior observed in RI).
+ if (text == null || text.length() == 0)
+ return;
+
+ writeLock();
+ try
+ {
+ if (documentFilter == null)
+ insertStringImpl(offset, text, attributes);
+ else
+ documentFilter.insertString(getBypass(), offset, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ void insertStringImpl(int offset, String text, AttributeSet attributes)
+ throws BadLocationException
+ {
+ // Just return when no text to insert was given.
+ if (text == null || text.length() == 0)
+ return;
+ DefaultDocumentEvent event =
+ new DefaultDocumentEvent(offset, text.length(),
+ DocumentEvent.EventType.INSERT);
+
+ UndoableEdit undo = content.insertString(offset, text);
+ if (undo != null)
+ event.addEdit(undo);
+
+ // Check if we need bidi layout.
+ if (getProperty(I18N).equals(Boolean.FALSE))
+ {
+ Object dir = getProperty(TextAttribute.RUN_DIRECTION);
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
+ putProperty(I18N, Boolean.TRUE);
+ else
+ {
+ char[] chars = text.toCharArray();
+ if (Bidi.requiresBidi(chars, 0, chars.length))
+ putProperty(I18N, Boolean.TRUE);
+ }
+ }
+
+ insertUpdate(event, attributes);
+
+ fireInsertUpdate(event);
+
+ if (undo != null)
+ fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
+ }
+
+ /**
+ * Called to indicate that text has been inserted into this
+ * <code>Document</code>. The default implementation does nothing.
+ * This method is executed within a write lock.
+ *
+ * @param chng the <code>DefaultDocumentEvent</code> describing the change
+ * @param attr the attributes of the changed content
+ */
+ protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
+ {
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
+ }
+
+ /**
+ * Called after some content has been removed from this
+ * <code>Document</code>. The default implementation does nothing.
+ * This method is executed within a write lock.
+ *
+ * @param chng the <code>DefaultDocumentEvent</code> describing the change
+ */
+ protected void postRemoveUpdate(DefaultDocumentEvent chng)
+ {
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
+ }
+
+ /**
+ * Stores a property in this <code>Document</code>'s property list.
+ *
+ * @param key the key of the property to be stored
+ * @param value the value of the property to be stored
+ */
+ public final void putProperty(Object key, Object value)
+ {
+ // FIXME: make me thread-safe
+ if (properties == null)
+ properties = new Hashtable();
+
+ if (value == null)
+ properties.remove(key);
+ else
+ properties.put(key, value);
+
+ // Update bidi structure if the RUN_DIRECTION is set.
+ if (TextAttribute.RUN_DIRECTION.equals(key))
+ {
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
+ && Boolean.FALSE.equals(getProperty(I18N)))
+ putProperty(I18N, Boolean.TRUE);
+
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ {
+ writeLock();
+ try
+ {
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(0, getLength(),
+ DocumentEvent.EventType.INSERT);
+ updateBidi(ev);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the bidi element structure.
+ *
+ * @param ev the document event for the change
+ */
+ private void updateBidi(DefaultDocumentEvent ev)
+ {
+ // Determine start and end offset of the paragraphs to be scanned.
+ int start = 0;
+ int end = 0;
+ DocumentEvent.EventType type = ev.getType();
+ if (type == DocumentEvent.EventType.INSERT
+ || type == DocumentEvent.EventType.CHANGE)
+ {
+ int offs = ev.getOffset();
+ int endOffs = offs + ev.getLength();
+ start = getParagraphElement(offs).getStartOffset();
+ end = getParagraphElement(endOffs).getEndOffset();
+ }
+ else if (type == DocumentEvent.EventType.REMOVE)
+ {
+ Element par = getParagraphElement(ev.getOffset());
+ start = par.getStartOffset();
+ end = par.getEndOffset();
+ }
+ else
+ assert false : "Unknown event type";
+
+ // Determine the bidi levels for the affected range.
+ Bidi[] bidis = getBidis(start, end);
+
+ int removeFrom = 0;
+ int removeTo = 0;
+
+ int offs = 0;
+ int lastRunStart = 0;
+ int lastRunEnd = 0;
+ int lastRunLevel = 0;
+ ArrayList newEls = new ArrayList();
+ for (int i = 0; i < bidis.length; i++)
+ {
+ Bidi bidi = bidis[i];
+ int numRuns = bidi.getRunCount();
+ for (int r = 0; r < numRuns; r++)
+ {
+ if (r == 0 && i == 0)
+ {
+ if (start > 0)
+ {
+ // Try to merge with the previous element if it has the
+ // same bidi level as the first run.
+ int prevElIndex = bidiRoot.getElementIndex(start - 1);
+ removeFrom = prevElIndex;
+ Element prevEl = bidiRoot.getElement(prevElIndex);
+ AttributeSet atts = prevEl.getAttributes();
+ int prevElLevel = StyleConstants.getBidiLevel(atts);
+ if (prevElLevel == bidi.getRunLevel(r))
+ {
+ // Merge previous element with current run.
+ lastRunStart = prevEl.getStartOffset() - start;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ }
+ else if (prevEl.getEndOffset() > start)
+ {
+ // Split previous element and replace by 2 new elements.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ newEls.add(new BidiElement(bidiRoot,
+ prevEl.getStartOffset(),
+ start, prevElLevel));
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom++;
+ }
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom = 0;
+ }
+ }
+ if (i == bidis.length - 1 && r == numRuns - 1)
+ {
+ if (end <= getLength())
+ {
+ // Try to merge last element with next element.
+ int nextIndex = bidiRoot.getElementIndex(end);
+ Element nextEl = bidiRoot.getElement(nextIndex);
+ AttributeSet atts = nextEl.getAttributes();
+ int nextLevel = StyleConstants.getBidiLevel(atts);
+ int level = bidi.getRunLevel(r);
+ if (lastRunLevel == level && level == nextLevel)
+ {
+ // Merge runs together.
+ if (lastRunStart + start == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ }
+ else if (lastRunLevel == level)
+ {
+ // Merge current and last run.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + endOffs, level));
+ if (start + endOffs == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else if (level == nextLevel)
+ {
+ // Merge current and next run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ else
+ {
+ // Split next element.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ start + endOffs, level));
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else
+ {
+ removeTo = bidiRoot.getElementIndex(end);
+ int level = bidi.getRunLevel(r);
+ int runEnd = offs + bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunStart,
+ start + runEnd, level));
+ }
+ else
+ {
+ // Create element for last run and current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunEnd,
+ start + runEnd,
+ level));
+ }
+ }
+ }
+ else
+ {
+ int level = bidi.getRunLevel(r);
+ int runEnd = bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ }
+ else
+ {
+ // Create element for last run and update values for
+ // current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ lastRunStart = lastRunEnd;
+ lastRunEnd = offs + runEnd;
+ lastRunLevel = level;
+ }
+ }
+ }
+ offs += bidi.getLength();
+ }
+
+ // Determine the bidi elements which are to be removed.
+ int numRemoved = 0;
+ if (bidiRoot.getElementCount() > 0)
+ numRemoved = removeTo - removeFrom + 1;
+ Element[] removed = new Element[numRemoved];
+ for (int i = 0; i < numRemoved; i++)
+ removed[i] = bidiRoot.getElement(removeFrom + i);
+
+ Element[] added = new Element[newEls.size()];
+ added = (Element[]) newEls.toArray(added);
+
+ // Update the event.
+ ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
+ ev.addEdit(edit);
+
+ // Update the structure.
+ bidiRoot.replace(removeFrom, numRemoved, added);
+ }
+
+ /**
+ * Determines the Bidi objects for the paragraphs in the specified range.
+ *
+ * @param start the start of the range
+ * @param end the end of the range
+ *
+ * @return the Bidi analysers for the paragraphs in the range
+ */
+ private Bidi[] getBidis(int start, int end)
+ {
+ // Determine the default run direction from the document property.
+ Boolean defaultDir = null;
+ Object o = getProperty(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ defaultDir = (Boolean) o;
+
+ // Scan paragraphs and add their level arrays to the overall levels array.
+ ArrayList bidis = new ArrayList();
+ Segment s = new Segment();
+ for (int i = start; i < end;)
+ {
+ Element par = getParagraphElement(i);
+ int pStart = par.getStartOffset();
+ int pEnd = par.getEndOffset();
+
+ // Determine the default run direction of the paragraph.
+ Boolean dir = defaultDir;
+ o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ dir = (Boolean) o;
+
+ // Bidi over the paragraph.
+ try
+ {
+ getText(pStart, pEnd - pStart, s);
+ }
+ catch (BadLocationException ex)
+ {
+ assert false : "Must not happen";
+ }
+ int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
+ if (dir != null)
+ {
+ if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
+ flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
+ else
+ flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
+ }
+ Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
+ bidis.add(bidi);
+ i = pEnd;
+ }
+ Bidi[] ret = new Bidi[bidis.size()];
+ ret = (Bidi[]) bidis.toArray(ret);
+ return ret;
+ }
+
+ /**
+ * Blocks until a read lock can be obtained. Must block if there is
+ * currently a writer modifying the <code>Document</code>.
+ */
+ public final synchronized void readLock()
+ {
+ try
+ {
+ while (currentWriter != null)
+ {
+ if (currentWriter == Thread.currentThread())
+ return;
+ wait();
+ }
+ numReaders++;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interrupted during grab read lock");
+ }
+ }
+
+ /**
+ * Releases the read lock. If this was the only reader on this
+ * <code>Document</code>, writing may begin now.
+ */
+ public final synchronized void readUnlock()
+ {
+ // Note we could have a problem here if readUnlock was called without a
+ // prior call to readLock but the specs simply warn users to ensure that
+ // balance by using a finally block:
+ // readLock()
+ // try
+ // {
+ // doSomethingHere
+ // }
+ // finally
+ // {
+ // readUnlock();
+ // }
+
+ // All that the JDK seems to check for is that you don't call unlock
+ // more times than you've previously called lock, but it doesn't make
+ // sure that the threads calling unlock were the same ones that called lock
+
+ // If the current thread holds the write lock, and attempted to also obtain
+ // a readLock, then numReaders hasn't been incremented and we don't need
+ // to unlock it here.
+ if (currentWriter == Thread.currentThread())
+ return;
+
+ // FIXME: the reference implementation throws a
+ // javax.swing.text.StateInvariantError here
+ if (numReaders <= 0)
+ throw new IllegalStateException("document lock failure");
+
+ // If currentWriter is not null, the application code probably had a
+ // writeLock and then tried to obtain a readLock, in which case
+ // numReaders wasn't incremented
+ numReaders--;
+ notify();
+ }
+
+ /**
+ * Removes a piece of content from this <code>Document</code>.
+ *
+ * <p>If a {@link DocumentFilter} is installed in this document, the
+ * corresponding method of the filter object is called. The
+ * <code>DocumentFilter</code> is called even if <code>length</code>
+ * is zero. This is different from {@link #replace}.</p>
+ *
+ * <p>Note: When <code>length</code> is zero or below the call is not
+ * forwarded to the underlying {@link AbstractDocument.Content} instance
+ * of this document and no exception is thrown.</p>
+ *
+ * @param offset the start offset of the fragment to be removed
+ * @param length the length of the fragment to be removed
+ *
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + length</code> or invalid locations within this
+ * document
+ */
+ public void remove(int offset, int length) throws BadLocationException
+ {
+ writeLock();
+ try
+ {
+ DocumentFilter f = getDocumentFilter();
+ if (f == null)
+ removeImpl(offset, length);
+ else
+ f.remove(getBypass(), offset, length);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ void removeImpl(int offset, int length) throws BadLocationException
+ {
+ // The RI silently ignores all requests that have a negative length.
+ // Don't ask my why, but that's how it is.
+ if (length > 0)
+ {
+ if (offset < 0 || offset > getLength())
+ throw new BadLocationException("Invalid remove position", offset);
+
+ if (offset + length > getLength())
+ throw new BadLocationException("Invalid remove length", offset);
+
+ DefaultDocumentEvent event =
+ new DefaultDocumentEvent(offset, length,
+ DocumentEvent.EventType.REMOVE);
+
+ // The order of the operations below is critical!
+ removeUpdate(event);
+ UndoableEdit temp = content.remove(offset, length);
+
+ postRemoveUpdate(event);
+ fireRemoveUpdate(event);
+ }
+ }
+
+ /**
+ * Replaces a piece of content in this <code>Document</code> with
+ * another piece of content.
+ *
+ * <p>If a {@link DocumentFilter} is installed in this document, the
+ * corresponding method of the filter object is called.</p>
+ *
+ * <p>The method has no effect if <code>length</code> is zero (and
+ * only zero) and, at the same time, <code>text</code> is
+ * <code>null</code> or has zero length.</p>
+ *
+ * @param offset the start offset of the fragment to be removed
+ * @param length the length of the fragment to be removed
+ * @param text the text to replace the content with
+ * @param attributes the text attributes to assign to the new content
+ *
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + length</code> or invalid locations within this
+ * document
+ *
+ * @since 1.4
+ */
+ public void replace(int offset, int length, String text,
+ AttributeSet attributes)
+ throws BadLocationException
+ {
+ // Bail out if we have a bogus replacement (Behavior observed in RI).
+ if (length == 0
+ && (text == null || text.length() == 0))
+ return;
+
+ writeLock();
+ try
+ {
+ if (documentFilter == null)
+ {
+ // It is important to call the methods which again do the checks
+ // of the arguments and the DocumentFilter because subclasses may
+ // have overridden these methods and provide crucial behavior
+ // which would be skipped if we call the non-checking variants.
+ // An example for this is PlainDocument where insertString can
+ // provide a filtering of newlines.
+ remove(offset, length);
+ insertString(offset, text, attributes);
+ }
+ else
+ documentFilter.replace(getBypass(), offset, length, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ void replaceImpl(int offset, int length, String text,
+ AttributeSet attributes)
+ throws BadLocationException
+ {
+ removeImpl(offset, length);
+ insertStringImpl(offset, text, attributes);
+ }
+
+ /**
+ * Adds a <code>DocumentListener</code> object to this document.
+ *
+ * @param listener the listener to add
+ */
+ public void addDocumentListener(DocumentListener listener)
+ {
+ listenerList.add(DocumentListener.class, listener);
+ }
+
+ /**
+ * Removes a <code>DocumentListener</code> object from this document.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeDocumentListener(DocumentListener listener)
+ {
+ listenerList.remove(DocumentListener.class, listener);
+ }
+
+ /**
+ * Returns all registered <code>DocumentListener</code>s.
+ *
+ * @return all registered <code>DocumentListener</code>s
+ */
+ public DocumentListener[] getDocumentListeners()
+ {
+ return (DocumentListener[]) getListeners(DocumentListener.class);
+ }
+
+ /**
+ * Adds an {@link UndoableEditListener} to this <code>Document</code>.
+ *
+ * @param listener the listener to add
+ */
+ public void addUndoableEditListener(UndoableEditListener listener)
+ {
+ listenerList.add(UndoableEditListener.class, listener);
+ }
+
+ /**
+ * Removes an {@link UndoableEditListener} from this <code>Document</code>.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeUndoableEditListener(UndoableEditListener listener)
+ {
+ listenerList.remove(UndoableEditListener.class, listener);
+ }
+
+ /**
+ * Returns all registered {@link UndoableEditListener}s.
+ *
+ * @return all registered {@link UndoableEditListener}s
+ */
+ public UndoableEditListener[] getUndoableEditListeners()
+ {
+ return (UndoableEditListener[]) getListeners(UndoableEditListener.class);
+ }
+
+ /**
+ * Called before some content gets removed from this <code>Document</code>.
+ * The default implementation does nothing but may be overridden by
+ * subclasses to modify the <code>Document</code> structure in response
+ * to a remove request. The method is executed within a write lock.
+ *
+ * @param chng the <code>DefaultDocumentEvent</code> describing the change
+ */
+ protected void removeUpdate(DefaultDocumentEvent chng)
+ {
+ // Do nothing here. Subclasses may wish to override this.
+ }
+
+ /**
+ * Called to render this <code>Document</code> visually. It obtains a read
+ * lock, ensuring that no changes will be made to the <code>document</code>
+ * during the rendering process. It then calls the {@link Runnable#run()}
+ * method on <code>runnable</code>. This method <em>must not</em> attempt
+ * to modifiy the <code>Document</code>, since a deadlock will occur if it
+ * tries to obtain a write lock. When the {@link Runnable#run()} method
+ * completes (either naturally or by throwing an exception), the read lock
+ * is released. Note that there is nothing in this method related to
+ * the actual rendering. It could be used to execute arbitrary code within
+ * a read lock.
+ *
+ * @param runnable the {@link Runnable} to execute
+ */
+ public void render(Runnable runnable)
+ {
+ readLock();
+ try
+ {
+ runnable.run();
+ }
+ finally
+ {
+ readUnlock();
+ }
+ }
+
+ /**
+ * Sets the asynchronous loading priority for this <code>Document</code>.
+ * A value of <code>-1</code> indicates that this <code>Document</code>
+ * should be loaded synchronously.
+ *
+ * @param p the asynchronous loading priority to set
+ */
+ public void setAsynchronousLoadPriority(int p)
+ {
+ Integer val = p >= 0 ? new Integer(p) : null;
+ putProperty(AsyncLoadPriority, val);
+ }
+
+ /**
+ * Sets the properties of this <code>Document</code>.
+ *
+ * @param p the document properties to set
+ */
+ public void setDocumentProperties(Dictionary<Object, Object> p)
+ {
+ // FIXME: make me thread-safe
+ properties = p;
+ }
+
+ /**
+ * Blocks until a write lock can be obtained. Must wait if there are
+ * readers currently reading or another thread is currently writing.
+ */
+ protected synchronized final void writeLock()
+ {
+ try
+ {
+ while (numReaders > 0 || currentWriter != null)
+ {
+ if (Thread.currentThread() == currentWriter)
+ {
+ if (notifyListeners)
+ throw new IllegalStateException("Mutation during notify");
+ numWriters++;
+ return;
+ }
+ wait();
+ }
+ currentWriter = Thread.currentThread();
+ numWriters = 1;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interupted during grab write lock");
+ }
+ }
+
+ /**
+ * Releases the write lock. This allows waiting readers or writers to
+ * obtain the lock.
+ */
+ protected final synchronized void writeUnlock()
+ {
+ if (--numWriters <= 0)
+ {
+ numWriters = 0;
+ currentWriter = null;
+ notifyAll();
+ }
+ }
+
+ /**
+ * Returns the currently installed {@link DocumentFilter} for this
+ * <code>Document</code>.
+ *
+ * @return the currently installed {@link DocumentFilter} for this
+ * <code>Document</code>
+ *
+ * @since 1.4
+ */
+ public DocumentFilter getDocumentFilter()
+ {
+ return documentFilter;
+ }
+
+ /**
+ * Sets the {@link DocumentFilter} for this <code>Document</code>.
+ *
+ * @param filter the <code>DocumentFilter</code> to set
+ *
+ * @since 1.4
+ */
+ public void setDocumentFilter(DocumentFilter filter)
+ {
+ this.documentFilter = filter;
+ }
+
+ /**
+ * Dumps diagnostic information to the specified <code>PrintStream</code>.
+ *
+ * @param out the stream to write the diagnostic information to
+ */
+ public void dump(PrintStream out)
+ {
+ ((AbstractElement) getDefaultRootElement()).dump(out, 0);
+ ((AbstractElement) getBidiRootElement()).dump(out, 0);
+ }
+
+ /**
+ * Defines a set of methods for managing text attributes for one or more
+ * <code>Document</code>s.
+ *
+ * Replicating {@link AttributeSet}s throughout a <code>Document</code> can
+ * be very expensive. Implementations of this interface are intended to
+ * provide intelligent management of <code>AttributeSet</code>s, eliminating
+ * costly duplication.
+ *
+ * @see StyleContext
+ */
+ public interface AttributeContext
+ {
+ /**
+ * Returns an {@link AttributeSet} that contains the attributes
+ * of <code>old</code> plus the new attribute specified by
+ * <code>name</code> and <code>value</code>.
+ *
+ * @param old the attribute set to be merged with the new attribute
+ * @param name the name of the attribute to be added
+ * @param value the value of the attribute to be added
+ *
+ * @return the old attributes plus the new attribute
+ */
+ AttributeSet addAttribute(AttributeSet old, Object name, Object value);
+
+ /**
+ * Returns an {@link AttributeSet} that contains the attributes
+ * of <code>old</code> plus the new attributes in <code>attributes</code>.
+ *
+ * @param old the set of attributes where to add the new attributes
+ * @param attributes the attributes to be added
+ *
+ * @return an {@link AttributeSet} that contains the attributes
+ * of <code>old</code> plus the new attributes in
+ * <code>attributes</code>
+ */
+ AttributeSet addAttributes(AttributeSet old, AttributeSet attributes);
+
+ /**
+ * Returns an empty {@link AttributeSet}.
+ *
+ * @return an empty {@link AttributeSet}
+ */
+ AttributeSet getEmptySet();
+
+ /**
+ * Called to indicate that the attributes in <code>attributes</code> are
+ * no longer used.
+ *
+ * @param attributes the attributes are no longer used
+ */
+ void reclaim(AttributeSet attributes);
+
+ /**
+ * Returns a {@link AttributeSet} that has the attribute with the specified
+ * <code>name</code> removed from <code>old</code>.
+ *
+ * @param old the attribute set from which an attribute is removed
+ * @param name the name of the attribute to be removed
+ *
+ * @return the attributes of <code>old</code> minus the attribute
+ * specified by <code>name</code>
+ */
+ AttributeSet removeAttribute(AttributeSet old, Object name);
+
+ /**
+ * Removes all attributes in <code>attributes</code> from <code>old</code>
+ * and returns the resulting <code>AttributeSet</code>.
+ *
+ * @param old the set of attributes from which to remove attributes
+ * @param attributes the attributes to be removed from <code>old</code>
+ *
+ * @return the attributes of <code>old</code> minus the attributes in
+ * <code>attributes</code>
+ */
+ AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes);
+
+ /**
+ * Removes all attributes specified by <code>names</code> from
+ * <code>old</code> and returns the resulting <code>AttributeSet</code>.
+ *
+ * @param old the set of attributes from which to remove attributes
+ * @param names the names of the attributes to be removed from
+ * <code>old</code>
+ *
+ * @return the attributes of <code>old</code> minus the attributes in
+ * <code>attributes</code>
+ */
+ AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
+ }
+
+ /**
+ * A sequence of data that can be edited. This is were the actual content
+ * in <code>AbstractDocument</code>'s is stored.
+ */
+ public interface Content
+ {
+ /**
+ * Creates a {@link Position} that keeps track of the location at
+ * <code>offset</code>.
+ *
+ * @return a {@link Position} that keeps track of the location at
+ * <code>offset</code>.
+ *
+ * @throw BadLocationException if <code>offset</code> is not a valid
+ * location in this <code>Content</code> model
+ */
+ Position createPosition(int offset) throws BadLocationException;
+
+ /**
+ * Returns the length of the content.
+ *
+ * @return the length of the content
+ */
+ int length();
+
+ /**
+ * Inserts a string into the content model.
+ *
+ * @param where the offset at which to insert the string
+ * @param str the string to be inserted
+ *
+ * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
+ * not supported by this <code>Content</code> model
+ *
+ * @throws BadLocationException if <code>where</code> is not a valid
+ * location in this <code>Content</code> model
+ */
+ UndoableEdit insertString(int where, String str)
+ throws BadLocationException;
+
+ /**
+ * Removes a piece of content from the content model.
+ *
+ * @param where the offset at which to remove content
+ * @param nitems the number of characters to be removed
+ *
+ * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
+ * not supported by this <code>Content</code> model
+ *
+ * @throws BadLocationException if <code>where</code> is not a valid
+ * location in this <code>Content</code> model
+ */
+ UndoableEdit remove(int where, int nitems) throws BadLocationException;
+
+ /**
+ * Returns a piece of content.
+ *
+ * @param where the start offset of the requested fragment
+ * @param len the length of the requested fragment
+ *
+ * @return the requested fragment
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + len</code>is not a valid
+ * location in this <code>Content</code> model
+ */
+ String getString(int where, int len) throws BadLocationException;
+
+ /**
+ * Fetches a piece of content and stores it in <code>txt</code>.
+ *
+ * @param where the start offset of the requested fragment
+ * @param len the length of the requested fragment
+ * @param txt the <code>Segment</code> where to fragment is stored into
+ *
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + len</code>is not a valid
+ * location in this <code>Content</code> model
+ */
+ void getChars(int where, int len, Segment txt) throws BadLocationException;
+ }
+
+ /**
+ * An abstract base implementation of the {@link Element} interface.
+ */
+ public abstract class AbstractElement
+ implements Element, MutableAttributeSet, TreeNode, Serializable
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 1712240033321461704L;
+
+ /** The number of characters that this Element spans. */
+ int count;
+
+ /** The starting offset of this Element. */
+ int offset;
+
+ /** The attributes of this Element. */
+ AttributeSet attributes;
+
+ /** The parent element. */
+ Element element_parent;
+
+ /** The parent in the TreeNode interface. */
+ TreeNode tree_parent;
+
+ /** The children of this element. */
+ Vector tree_children;
+
+ /**
+ * Creates a new instance of <code>AbstractElement</code> with a
+ * specified parent <code>Element</code> and <code>AttributeSet</code>.
+ *
+ * @param p the parent of this <code>AbstractElement</code>
+ * @param s the attributes to be assigned to this
+ * <code>AbstractElement</code>
+ */
+ public AbstractElement(Element p, AttributeSet s)
+ {
+ element_parent = p;
+ AttributeContext ctx = getAttributeContext();
+ attributes = ctx.getEmptySet();
+ if (s != null)
+ addAttributes(s);
+ }
+
+ /**
+ * Returns the child nodes of this <code>Element</code> as an
+ * <code>Enumeration</code> of {@link TreeNode}s.
+ *
+ * @return the child nodes of this <code>Element</code> as an
+ * <code>Enumeration</code> of {@link TreeNode}s
+ */
+ public abstract Enumeration children();
+
+ /**
+ * Returns <code>true</code> if this <code>AbstractElement</code>
+ * allows children.
+ *
+ * @return <code>true</code> if this <code>AbstractElement</code>
+ * allows children
+ */
+ public abstract boolean getAllowsChildren();
+
+ /**
+ * Returns the child of this <code>AbstractElement</code> at
+ * <code>index</code>.
+ *
+ * @param index the position in the child list of the child element to
+ * be returned
+ *
+ * @return the child of this <code>AbstractElement</code> at
+ * <code>index</code>
+ */
+ public TreeNode getChildAt(int index)
+ {
+ return (TreeNode) tree_children.get(index);
+ }
+
+ /**
+ * Returns the number of children of this <code>AbstractElement</code>.
+ *
+ * @return the number of children of this <code>AbstractElement</code>
+ */
+ public int getChildCount()
+ {
+ return tree_children.size();
+ }
+
+ /**
+ * Returns the index of a given child <code>TreeNode</code> or
+ * <code>-1</code> if <code>node</code> is not a child of this
+ * <code>AbstractElement</code>.
+ *
+ * @param node the node for which the index is requested
+ *
+ * @return the index of a given child <code>TreeNode</code> or
+ * <code>-1</code> if <code>node</code> is not a child of this
+ * <code>AbstractElement</code>
+ */
+ public int getIndex(TreeNode node)
+ {
+ return tree_children.indexOf(node);
+ }
+
+ /**
+ * Returns the parent <code>TreeNode</code> of this
+ * <code>AbstractElement</code> or <code>null</code> if this element
+ * has no parent.
+ *
+ * @return the parent <code>TreeNode</code> of this
+ * <code>AbstractElement</code> or <code>null</code> if this
+ * element has no parent
+ */
+ public TreeNode getParent()
+ {
+ return tree_parent;
+ }
+
+ /**
+ * Returns <code>true</code> if this <code>AbstractElement</code> is a
+ * leaf element, <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if this <code>AbstractElement</code> is a
+ * leaf element, <code>false</code> otherwise
+ */
+ public abstract boolean isLeaf();
+
+ /**
+ * Adds an attribute to this element.
+ *
+ * @param name the name of the attribute to be added
+ * @param value the value of the attribute to be added
+ */
+ public void addAttribute(Object name, Object value)
+ {
+ attributes = getAttributeContext().addAttribute(attributes, name, value);
+ }
+
+ /**
+ * Adds a set of attributes to this element.
+ *
+ * @param attrs the attributes to be added to this element
+ */
+ public void addAttributes(AttributeSet attrs)
+ {
+ attributes = getAttributeContext().addAttributes(attributes, attrs);
+ }
+
+ /**
+ * Removes an attribute from this element.
+ *
+ * @param name the name of the attribute to be removed
+ */
+ public void removeAttribute(Object name)
+ {
+ attributes = getAttributeContext().removeAttribute(attributes, name);
+ }
+
+ /**
+ * Removes a set of attributes from this element.
+ *
+ * @param attrs the attributes to be removed
+ */
+ public void removeAttributes(AttributeSet attrs)
+ {
+ attributes = getAttributeContext().removeAttributes(attributes, attrs);
+ }
+
+ /**
+ * Removes a set of attribute from this element.
+ *
+ * @param names the names of the attributes to be removed
+ */
+ public void removeAttributes(Enumeration<?> names)
+ {
+ attributes = getAttributeContext().removeAttributes(attributes, names);
+ }
+
+ /**
+ * Sets the parent attribute set against which the element can resolve
+ * attributes that are not defined in itself.
+ *
+ * @param parent the resolve parent to set
+ */
+ public void setResolveParent(AttributeSet parent)
+ {
+ attributes = getAttributeContext().addAttribute(attributes,
+ ResolveAttribute,
+ parent);
+ }
+
+ /**
+ * Returns <code>true</code> if this element contains the specified
+ * attribute.
+ *
+ * @param name the name of the attribute to check
+ * @param value the value of the attribute to check
+ *
+ * @return <code>true</code> if this element contains the specified
+ * attribute
+ */
+ public boolean containsAttribute(Object name, Object value)
+ {
+ return attributes.containsAttribute(name, value);
+ }
+
+ /**
+ * Returns <code>true</code> if this element contains all of the
+ * specified attributes.
+ *
+ * @param attrs the attributes to check
+ *
+ * @return <code>true</code> if this element contains all of the
+ * specified attributes
+ */
+ public boolean containsAttributes(AttributeSet attrs)
+ {
+ return attributes.containsAttributes(attrs);
+ }
+
+ /**
+ * Returns a copy of the attributes of this element.
+ *
+ * @return a copy of the attributes of this element
+ */
+ public AttributeSet copyAttributes()
+ {
+ return attributes.copyAttributes();
+ }
+
+ /**
+ * Returns the attribute value with the specified key. If this attribute
+ * is not defined in this element and this element has a resolving
+ * parent, the search goes upward to the resolve parent chain.
+ *
+ * @param key the key of the requested attribute
+ *
+ * @return the attribute value for <code>key</code> of <code>null</code>
+ * if <code>key</code> is not found locally and cannot be resolved
+ * in this element's resolve parents
+ */
+ public Object getAttribute(Object key)
+ {
+ Object result = attributes.getAttribute(key);
+ if (result == null)
+ {
+ AttributeSet resParent = getResolveParent();
+ if (resParent != null)
+ result = resParent.getAttribute(key);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the number of defined attributes in this element.
+ *
+ * @return the number of defined attributes in this element
+ */
+ public int getAttributeCount()
+ {
+ return attributes.getAttributeCount();
+ }
+
+ /**
+ * Returns the names of the attributes of this element.
+ *
+ * @return the names of the attributes of this element
+ */
+ public Enumeration<?> getAttributeNames()
+ {
+ return attributes.getAttributeNames();
+ }
+
+ /**
+ * Returns the resolve parent of this element.
+ * This is taken from the AttributeSet, but if this is null,
+ * this method instead returns the Element's parent's
+ * AttributeSet
+ *
+ * @return the resolve parent of this element
+ *
+ * @see #setResolveParent(AttributeSet)
+ */
+ public AttributeSet getResolveParent()
+ {
+ return attributes.getResolveParent();
+ }
+
+ /**
+ * Returns <code>true</code> if an attribute with the specified name
+ * is defined in this element, <code>false</code> otherwise.
+ *
+ * @param attrName the name of the requested attributes
+ *
+ * @return <code>true</code> if an attribute with the specified name
+ * is defined in this element, <code>false</code> otherwise
+ */
+ public boolean isDefined(Object attrName)
+ {
+ return attributes.isDefined(attrName);
+ }
+
+ /**
+ * Returns <code>true</code> if the specified <code>AttributeSet</code>
+ * is equal to this element's <code>AttributeSet</code>, <code>false</code>
+ * otherwise.
+ *
+ * @param attrs the attributes to compare this element to
+ *
+ * @return <code>true</code> if the specified <code>AttributeSet</code>
+ * is equal to this element's <code>AttributeSet</code>,
+ * <code>false</code> otherwise
+ */
+ public boolean isEqual(AttributeSet attrs)
+ {
+ return attributes.isEqual(attrs);
+ }
+
+ /**
+ * Returns the attributes of this element.
+ *
+ * @return the attributes of this element
+ */
+ public AttributeSet getAttributes()
+ {
+ return this;
+ }
+
+ /**
+ * Returns the {@link Document} to which this element belongs.
+ *
+ * @return the {@link Document} to which this element belongs
+ */
+ public Document getDocument()
+ {
+ return AbstractDocument.this;
+ }
+
+ /**
+ * Returns the child element at the specified <code>index</code>.
+ *
+ * @param index the index of the requested child element
+ *
+ * @return the requested element
+ */
+ public abstract Element getElement(int index);
+
+ /**
+ * Returns the name of this element.
+ *
+ * @return the name of this element
+ */
+ public String getName()
+ {
+ return (String) attributes.getAttribute(ElementNameAttribute);
+ }
+
+ /**
+ * Returns the parent element of this element.
+ *
+ * @return the parent element of this element
+ */
+ public Element getParentElement()
+ {
+ return element_parent;
+ }
+
+ /**
+ * Returns the offset inside the document model that is after the last
+ * character of this element.
+ *
+ * @return the offset inside the document model that is after the last
+ * character of this element
+ */
+ public abstract int getEndOffset();
+
+ /**
+ * Returns the number of child elements of this element.
+ *
+ * @return the number of child elements of this element
+ */
+ public abstract int getElementCount();
+
+ /**
+ * Returns the index of the child element that spans the specified
+ * offset in the document model.
+ *
+ * @param offset the offset for which the responsible element is searched
+ *
+ * @return the index of the child element that spans the specified
+ * offset in the document model
+ */
+ public abstract int getElementIndex(int offset);
+
+ /**
+ * Returns the start offset if this element inside the document model.
+ *
+ * @return the start offset if this element inside the document model
+ */
+ public abstract int getStartOffset();
+
+ /**
+ * Prints diagnostic output to the specified stream.
+ *
+ * @param stream the stream to write to
+ * @param indent the indentation level
+ */
+ public void dump(PrintStream stream, int indent)
+ {
+ CPStringBuilder b = new CPStringBuilder();
+ for (int i = 0; i < indent; ++i)
+ b.append(' ');
+ b.append('<');
+ b.append(getName());
+ // Dump attributes if there are any.
+ if (getAttributeCount() > 0)
+ {
+ b.append('\n');
+ Enumeration attNames = getAttributeNames();
+ while (attNames.hasMoreElements())
+ {
+ for (int i = 0; i < indent + 2; ++i)
+ b.append(' ');
+ Object attName = attNames.nextElement();
+ b.append(attName);
+ b.append('=');
+ Object attribute = getAttribute(attName);
+ b.append(attribute);
+ b.append('\n');
+ }
+ }
+ if (getAttributeCount() > 0)
+ {
+ for (int i = 0; i < indent; ++i)
+ b.append(' ');
+ }
+ b.append(">\n");
+
+ // Dump element content for leaf elements.
+ if (isLeaf())
+ {
+ for (int i = 0; i < indent + 2; ++i)
+ b.append(' ');
+ int start = getStartOffset();
+ int end = getEndOffset();
+ b.append('[');
+ b.append(start);
+ b.append(',');
+ b.append(end);
+ b.append("][");
+ try
+ {
+ b.append(getDocument().getText(start, end - start));
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError("BadLocationException "
+ + "must not be thrown "
+ + "here.");
+ err.initCause(ex);
+ throw err;
+ }
+ b.append("]\n");
+ }
+ stream.print(b.toString());
+
+ // Dump child elements if any.
+ int count = getElementCount();
+ for (int i = 0; i < count; ++i)
+ {
+ Element el = getElement(i);
+ if (el instanceof AbstractElement)
+ ((AbstractElement) el).dump(stream, indent + 2);
+ }
+ }
+ }
+
+ /**
+ * An implementation of {@link Element} to represent composite
+ * <code>Element</code>s that contain other <code>Element</code>s.
+ */
+ public class BranchElement extends AbstractElement
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -6037216547466333183L;
+
+ /**
+ * The child elements of this BranchElement.
+ */
+ private Element[] children;
+
+ /**
+ * The number of children in the branch element.
+ */
+ private int numChildren;
+
+ /**
+ * The last found index in getElementIndex(). Used for faster searching.
+ */
+ private int lastIndex;
+
+ /**
+ * Creates a new <code>BranchElement</code> with the specified
+ * parent and attributes.
+ *
+ * @param parent the parent element of this <code>BranchElement</code>
+ * @param attributes the attributes to set on this
+ * <code>BranchElement</code>
+ */
+ public BranchElement(Element parent, AttributeSet attributes)
+ {
+ super(parent, attributes);
+ children = new Element[1];
+ numChildren = 0;
+ lastIndex = -1;
+ }
+
+ /**
+ * Returns the children of this <code>BranchElement</code>.
+ *
+ * @return the children of this <code>BranchElement</code>
+ */
+ public Enumeration children()
+ {
+ if (numChildren == 0)
+ return null;
+
+ Vector tmp = new Vector();
+
+ for (int index = 0; index < numChildren; ++index)
+ tmp.add(children[index]);
+
+ return tmp.elements();
+ }
+
+ /**
+ * Returns <code>true</code> since <code>BranchElements</code> allow
+ * child elements.
+ *
+ * @return <code>true</code> since <code>BranchElements</code> allow
+ * child elements
+ */
+ public boolean getAllowsChildren()
+ {
+ return true;
+ }
+
+ /**
+ * Returns the child element at the specified <code>index</code>.
+ *
+ * @param index the index of the requested child element
+ *
+ * @return the requested element
+ */
+ public Element getElement(int index)
+ {
+ if (index < 0 || index >= numChildren)
+ return null;
+
+ return children[index];
+ }
+
+ /**
+ * Returns the number of child elements of this element.
+ *
+ * @return the number of child elements of this element
+ */
+ public int getElementCount()
+ {
+ return numChildren;
+ }
+
+ /**
+ * Returns the index of the child element that spans the specified
+ * offset in the document model.
+ *
+ * @param offset the offset for which the responsible element is searched
+ *
+ * @return the index of the child element that spans the specified
+ * offset in the document model
+ */
+ public int getElementIndex(int offset)
+ {
+ // Implemented using an improved linear search.
+ // This makes use of the fact that searches are not random but often
+ // close to the previous search. So we try to start the binary
+ // search at the last found index.
+
+ int i0 = 0; // The lower bounds.
+ int i1 = numChildren - 1; // The upper bounds.
+ int index = -1; // The found index.
+
+ int p0 = getStartOffset();
+ int p1; // Start and end offset local variables.
+
+ if (numChildren == 0)
+ index = 0;
+ else if (offset >= getEndOffset())
+ index = numChildren - 1;
+ else
+ {
+ // Try lastIndex.
+ if (lastIndex >= i0 && lastIndex <= i1)
+ {
+ Element last = getElement(lastIndex);
+ p0 = last.getStartOffset();
+ p1 = last.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ index = lastIndex;
+ else
+ {
+ // Narrow the search bounds using the lastIndex, even
+ // if it hasn't been a hit.
+ if (offset < p0)
+ i1 = lastIndex;
+ else
+ i0 = lastIndex;
+ }
+ }
+ // The actual search.
+ int i = 0;
+ while (i0 <= i1 && index == -1)
+ {
+ i = i0 + (i1 - i0) / 2;
+ Element el = getElement(i);
+ p0 = el.getStartOffset();
+ p1 = el.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ {
+ // Found it!
+ index = i;
+ }
+ else if (offset < p0)
+ i1 = i - 1;
+ else
+ i0 = i + 1;
+ }
+
+ if (index == -1)
+ {
+ // Didn't find it. Return the boundary index.
+ if (offset < p0)
+ index = i;
+ else
+ index = i + 1;
+ }
+
+ lastIndex = index;
+ }
+ return index;
+ }
+
+ /**
+ * Returns the offset inside the document model that is after the last
+ * character of this element.
+ * This is the end offset of the last child element. If this element
+ * has no children, this method throws a <code>NullPointerException</code>.
+ *
+ * @return the offset inside the document model that is after the last
+ * character of this element
+ *
+ * @throws NullPointerException if this branch element has no children
+ */
+ public int getEndOffset()
+ {
+ // This might accss one cached element or trigger an NPE for
+ // numChildren == 0. This is checked by a Mauve test.
+ Element child = numChildren > 0 ? children[numChildren - 1]
+ : children[0];
+ return child.getEndOffset();
+ }
+
+ /**
+ * Returns the name of this element. This is {@link #ParagraphElementName}
+ * in this case.
+ *
+ * @return the name of this element
+ */
+ public String getName()
+ {
+ return ParagraphElementName;
+ }
+
+ /**
+ * Returns the start offset of this element inside the document model.
+ * This is the start offset of the first child element. If this element
+ * has no children, this method throws a <code>NullPointerException</code>.
+ *
+ * @return the start offset of this element inside the document model
+ *
+ * @throws NullPointerException if this branch element has no children and
+ * no startOffset value has been cached
+ */
+ public int getStartOffset()
+ {
+ // Do not explicitly throw an NPE here. If the first element is null
+ // then the NPE gets thrown anyway. If it isn't, then it either
+ // holds a real value (for numChildren > 0) or a cached value
+ // (for numChildren == 0) as we don't fully remove elements in replace()
+ // when removing single elements.
+ // This is checked by a Mauve test.
+ return children[0].getStartOffset();
+ }
+
+ /**
+ * Returns <code>false</code> since <code>BranchElement</code> are no
+ * leafes.
+ *
+ * @return <code>false</code> since <code>BranchElement</code> are no
+ * leafes
+ */
+ public boolean isLeaf()
+ {
+ return false;
+ }
+
+ /**
+ * Returns the <code>Element</code> at the specified <code>Document</code>
+ * offset.
+ *
+ * @return the <code>Element</code> at the specified <code>Document</code>
+ * offset
+ *
+ * @see #getElementIndex(int)
+ */
+ public Element positionToElement(int position)
+ {
+ // XXX: There is surely a better algorithm
+ // as beginning from first element each time.
+ for (int index = 0; index < numChildren; ++index)
+ {
+ Element elem = children[index];
+
+ if ((elem.getStartOffset() <= position)
+ && (position < elem.getEndOffset()))
+ return elem;
+ }
+
+ return null;
+ }
+
+ /**
+ * Replaces a set of child elements with a new set of child elemens.
+ *
+ * @param offset the start index of the elements to be removed
+ * @param length the number of elements to be removed
+ * @param elements the new elements to be inserted
+ */
+ public void replace(int offset, int length, Element[] elements)
+ {
+ int delta = elements.length - length;
+ int copyFrom = offset + length; // From where to copy.
+ int copyTo = copyFrom + delta; // Where to copy to.
+ int numMove = numChildren - copyFrom; // How many elements are moved.
+ if (numChildren + delta > children.length)
+ {
+ // Gotta grow the array.
+ int newSize = Math.max(2 * children.length, numChildren + delta);
+ Element[] target = new Element[newSize];
+ System.arraycopy(children, 0, target, 0, offset);
+ System.arraycopy(elements, 0, target, offset, elements.length);
+ System.arraycopy(children, copyFrom, target, copyTo, numMove);
+ children = target;
+ }
+ else
+ {
+ System.arraycopy(children, copyFrom, children, copyTo, numMove);
+ System.arraycopy(elements, 0, children, offset, elements.length);
+ }
+ numChildren += delta;
+ }
+
+ /**
+ * Returns a string representation of this element.
+ *
+ * @return a string representation of this element
+ */
+ public String toString()
+ {
+ return ("BranchElement(" + getName() + ") "
+ + getStartOffset() + "," + getEndOffset() + "\n");
+ }
+ }
+
+ /**
+ * Stores the changes when a <code>Document</code> is beeing modified.
+ */
+ public class DefaultDocumentEvent extends CompoundEdit
+ implements DocumentEvent
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 5230037221564563284L;
+
+ /**
+ * The threshold that indicates when we switch to using a Hashtable.
+ */
+ private static final int THRESHOLD = 10;
+
+ /** The starting offset of the change. */
+ private int offset;
+
+ /** The length of the change. */
+ private int length;
+
+ /** The type of change. */
+ private DocumentEvent.EventType type;
+
+ /**
+ * Maps <code>Element</code> to their change records. This is only
+ * used when the changes array gets too big. We can use an
+ * (unsync'ed) HashMap here, since changes to this are (should) always
+ * be performed inside a write lock.
+ */
+ private HashMap changes;
+
+ /**
+ * Indicates if this event has been modified or not. This is used to
+ * determine if this event is thrown.
+ */
+ private boolean modified;
+
+ /**
+ * Creates a new <code>DefaultDocumentEvent</code>.
+ *
+ * @param offset the starting offset of the change
+ * @param length the length of the change
+ * @param type the type of change
+ */
+ public DefaultDocumentEvent(int offset, int length,
+ DocumentEvent.EventType type)
+ {
+ this.offset = offset;
+ this.length = length;
+ this.type = type;
+ modified = false;
+ }
+
+ /**
+ * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this
+ * edit is an instance of {@link ElementEdit}, then this record can
+ * later be fetched by calling {@link #getChange}.
+ *
+ * @param edit the undoable edit to add
+ */
+ public boolean addEdit(UndoableEdit edit)
+ {
+ // Start using Hashtable when we pass a certain threshold. This
+ // gives a good memory/performance compromise.
+ if (changes == null && edits.size() > THRESHOLD)
+ {
+ changes = new HashMap();
+ int count = edits.size();
+ for (int i = 0; i < count; i++)
+ {
+ Object o = edits.elementAt(i);
+ if (o instanceof ElementChange)
+ {
+ ElementChange ec = (ElementChange) o;
+ changes.put(ec.getElement(), ec);
+ }
+ }
+ }
+
+ if (changes != null && edit instanceof ElementChange)
+ {
+ ElementChange elEdit = (ElementChange) edit;
+ changes.put(elEdit.getElement(), elEdit);
+ }
+ return super.addEdit(edit);
+ }
+
+ /**
+ * Returns the document that has been modified.
+ *
+ * @return the document that has been modified
+ */
+ public Document getDocument()
+ {
+ return AbstractDocument.this;
+ }
+
+ /**
+ * Returns the length of the modification.
+ *
+ * @return the length of the modification
+ */
+ public int getLength()
+ {
+ return length;
+ }
+
+ /**
+ * Returns the start offset of the modification.
+ *
+ * @return the start offset of the modification
+ */
+ public int getOffset()
+ {
+ return offset;
+ }
+
+ /**
+ * Returns the type of the modification.
+ *
+ * @return the type of the modification
+ */
+ public DocumentEvent.EventType getType()
+ {
+ return type;
+ }
+
+ /**
+ * Returns the changes for an element.
+ *
+ * @param elem the element for which the changes are requested
+ *
+ * @return the changes for <code>elem</code> or <code>null</code> if
+ * <code>elem</code> has not been changed
+ */
+ public ElementChange getChange(Element elem)
+ {
+ ElementChange change = null;
+ if (changes != null)
+ {
+ change = (ElementChange) changes.get(elem);
+ }
+ else
+ {
+ int count = edits.size();
+ for (int i = 0; i < count && change == null; i++)
+ {
+ Object o = edits.get(i);
+ if (o instanceof ElementChange)
+ {
+ ElementChange ec = (ElementChange) o;
+ if (elem.equals(ec.getElement()))
+ change = ec;
+ }
+ }
+ }
+ return change;
+ }
+
+ /**
+ * Returns a String description of the change event. This returns the
+ * toString method of the Vector of edits.
+ */
+ public String toString()
+ {
+ return edits.toString();
+ }
+ }
+
+ /**
+ * An implementation of {@link DocumentEvent.ElementChange} to be added
+ * to {@link DefaultDocumentEvent}s.
+ */
+ public static class ElementEdit extends AbstractUndoableEdit
+ implements DocumentEvent.ElementChange
+ {
+ /** The serial version UID of ElementEdit. */
+ private static final long serialVersionUID = -1216620962142928304L;
+
+ /**
+ * The changed element.
+ */
+ private Element elem;
+
+ /**
+ * The index of the change.
+ */
+ private int index;
+
+ /**
+ * The removed elements.
+ */
+ private Element[] removed;
+
+ /**
+ * The added elements.
+ */
+ private Element[] added;
+
+ /**
+ * Creates a new <code>ElementEdit</code>.
+ *
+ * @param elem the changed element
+ * @param index the index of the change
+ * @param removed the removed elements
+ * @param added the added elements
+ */
+ public ElementEdit(Element elem, int index,
+ Element[] removed, Element[] added)
+ {
+ this.elem = elem;
+ this.index = index;
+ this.removed = removed;
+ this.added = added;
+ }
+
+ /**
+ * Returns the added elements.
+ *
+ * @return the added elements
+ */
+ public Element[] getChildrenAdded()
+ {
+ return added;
+ }
+
+ /**
+ * Returns the removed elements.
+ *
+ * @return the removed elements
+ */
+ public Element[] getChildrenRemoved()
+ {
+ return removed;
+ }
+
+ /**
+ * Returns the changed element.
+ *
+ * @return the changed element
+ */
+ public Element getElement()
+ {
+ return elem;
+ }
+
+ /**
+ * Returns the index of the change.
+ *
+ * @return the index of the change
+ */
+ public int getIndex()
+ {
+ return index;
+ }
+ }
+
+ /**
+ * An implementation of {@link Element} that represents a leaf in the
+ * document structure. This is used to actually store content.
+ */
+ public class LeafElement extends AbstractElement
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -8906306331347768017L;
+
+ /**
+ * Manages the start offset of this element.
+ */
+ private Position startPos;
+
+ /**
+ * Manages the end offset of this element.
+ */
+ private Position endPos;
+
+ /**
+ * Creates a new <code>LeafElement</code>.
+ *
+ * @param parent the parent of this <code>LeafElement</code>
+ * @param attributes the attributes to be set
+ * @param start the start index of this element inside the document model
+ * @param end the end index of this element inside the document model
+ */
+ public LeafElement(Element parent, AttributeSet attributes, int start,
+ int end)
+ {
+ super(parent, attributes);
+ try
+ {
+ startPos = createPosition(start);
+ endPos = createPosition(end);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError as;
+ as = new AssertionError("BadLocationException thrown "
+ + "here. start=" + start
+ + ", end=" + end
+ + ", length=" + getLength());
+ as.initCause(ex);
+ throw as;
+ }
+ }
+
+ /**
+ * Returns <code>null</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>null</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public Enumeration children()
+ {
+ return null;
+ }
+
+ /**
+ * Returns <code>false</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>false</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public boolean getAllowsChildren()
+ {
+ return false;
+ }
+
+ /**
+ * Returns <code>null</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>null</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public Element getElement(int index)
+ {
+ return null;
+ }
+
+ /**
+ * Returns <code>0</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>0</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public int getElementCount()
+ {
+ return 0;
+ }
+
+ /**
+ * Returns <code>-1</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>-1</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public int getElementIndex(int offset)
+ {
+ return -1;
+ }
+
+ /**
+ * Returns the end offset of this <code>Element</code> inside the
+ * document.
+ *
+ * @return the end offset of this <code>Element</code> inside the
+ * document
+ */
+ public int getEndOffset()
+ {
+ return endPos.getOffset();
+ }
+
+ /**
+ * Returns the name of this <code>Element</code>. This is
+ * {@link #ContentElementName} in this case.
+ *
+ * @return the name of this <code>Element</code>
+ */
+ public String getName()
+ {
+ String name = super.getName();
+ if (name == null)
+ name = ContentElementName;
+ return name;
+ }
+
+ /**
+ * Returns the start offset of this <code>Element</code> inside the
+ * document.
+ *
+ * @return the start offset of this <code>Element</code> inside the
+ * document
+ */
+ public int getStartOffset()
+ {
+ return startPos.getOffset();
+ }
+
+ /**
+ * Returns <code>true</code>.
+ *
+ * @return <code>true</code>
+ */
+ public boolean isLeaf()
+ {
+ return true;
+ }
+
+ /**
+ * Returns a string representation of this <code>Element</code>.
+ *
+ * @return a string representation of this <code>Element</code>
+ */
+ public String toString()
+ {
+ return ("LeafElement(" + getName() + ") "
+ + getStartOffset() + "," + getEndOffset() + "\n");
+ }
+ }
+
+ /**
+ * The root element for bidirectional text.
+ */
+ private class BidiRootElement
+ extends BranchElement
+ {
+ /**
+ * Creates a new bidi root element.
+ */
+ BidiRootElement()
+ {
+ super(null, null);
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiRootName;
+ }
+ }
+
+ /**
+ * A leaf element for the bidi structure.
+ */
+ private class BidiElement
+ extends LeafElement
+ {
+ /**
+ * Creates a new BidiElement.
+ *
+ * @param parent the parent element
+ * @param start the start offset
+ * @param end the end offset
+ * @param level the bidi level
+ */
+ BidiElement(Element parent, int start, int end, int level)
+ {
+ super(parent, new SimpleAttributeSet(), start, end);
+ addAttribute(StyleConstants.BidiLevel, new Integer(level));
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiElementName;
+ }
+ }
+
+ /** A class whose methods delegate to the insert, remove and replace methods
+ * of this document which do not check for an installed DocumentFilter.
+ */
+ class Bypass extends DocumentFilter.FilterBypass
+ {
+
+ public Document getDocument()
+ {
+ return AbstractDocument.this;
+ }
+
+ public void insertString(int offset, String string, AttributeSet attr)
+ throws BadLocationException
+ {
+ AbstractDocument.this.insertStringImpl(offset, string, attr);
+ }
+
+ public void remove(int offset, int length)
+ throws BadLocationException
+ {
+ AbstractDocument.this.removeImpl(offset, length);
+ }
+
+ public void replace(int offset, int length, String string,
+ AttributeSet attrs)
+ throws BadLocationException
+ {
+ AbstractDocument.this.replaceImpl(offset, length, string, attrs);
+ }
+
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/AbstractWriter.java b/libjava/classpath/javax/swing/text/AbstractWriter.java
new file mode 100644
index 000000000..e7df26e9e
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/AbstractWriter.java
@@ -0,0 +1,481 @@
+/* AbstractWriter.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.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Enumeration;
+
+/**
+ * This is an abstract base class for writing Document instances to a
+ * Writer. A concrete subclass must implement a method to iterate
+ * over the Elements of the Document and correctly format them.
+ */
+public abstract class AbstractWriter
+{
+ /**
+ * The default line separator character.
+ * @specnote although this is a constant, it is not static in the JDK
+ */
+ protected static final char NEWLINE = '\n';
+
+ // Where we write.
+ private Writer writer;
+ // How we iterate over the document.
+ private ElementIterator iter;
+ // The document over which we iterate.
+ private Document document;
+ // Maximum number of characters per line.
+ private int maxLineLength = 100;
+ // Number of characters we have currently written.
+ private int lineLength;
+ // True if we can apply line wrapping.
+ private boolean canWrapLines; // FIXME default?
+ // The number of spaces per indentation level.
+ private int indentSpace = 2;
+ // The current indentation level.
+ private int indentLevel;
+ // True if we have indented this line.
+ private boolean indented;
+ // Starting offset in document.
+ private int startOffset;
+ // Ending offset in document.
+ private int endOffset;
+ // The line separator string.
+ private String lineSeparator = "" + NEWLINE;
+ // The characters making up the line separator.
+ private char[] lineSeparatorChars = lineSeparator.toCharArray();
+
+ /**
+ * Create a new AbstractWriter with the indicated Writer and
+ * Document. The full range of the Document will be used. The
+ * internal ElementIterator will be initialized with the Document's
+ * root node.
+ */
+ protected AbstractWriter(Writer writer, Document doc)
+ {
+ this.writer = writer;
+ this.iter = new ElementIterator(doc);
+ this.document = doc;
+ this.startOffset = 0;
+ this.endOffset = doc.getLength();
+ }
+
+ /**
+ * Create a new AbstractWriter with the indicated Writer and
+ * Document. The full range of the Document will be used. The
+ * internal ElementIterator will be initialized with the Document's
+ * root node.
+ */
+ protected AbstractWriter(Writer writer, Document doc, int pos, int len)
+ {
+ this.writer = writer;
+ this.iter = new ElementIterator(doc);
+ this.document = doc;
+ this.startOffset = pos;
+ this.endOffset = pos + len;
+ }
+
+ /**
+ * Create a new AbstractWriter with the indicated Writer and
+ * Element. The full range of the Element will be used.
+ */
+ protected AbstractWriter(Writer writer, Element elt)
+ {
+ this.writer = writer;
+ this.iter = new ElementIterator(elt);
+ this.document = elt.getDocument();
+ this.startOffset = elt.getStartOffset();
+ this.endOffset = elt.getEndOffset();
+ }
+
+ /**
+ * Create a new AbstractWriter with the indicated Writer and
+ * Element. The full range of the Element will be used. The range
+ * will be limited to the indicated range of the Document.
+ */
+ protected AbstractWriter(Writer writer, Element elt, int pos, int len)
+ {
+ this.writer = writer;
+ this.iter = new ElementIterator(elt);
+ this.document = elt.getDocument();
+ this.startOffset = pos;
+ this.endOffset = pos + len;
+ }
+
+ /**
+ * Return the ElementIterator for this writer.
+ */
+ protected ElementIterator getElementIterator()
+ {
+ return iter;
+ }
+
+ /**
+ * Return the Writer to which we are writing.
+ * @since 1.3
+ */
+ protected Writer getWriter()
+ {
+ return writer;
+ }
+
+ /**
+ * Return this writer's Document.
+ */
+ protected Document getDocument()
+ {
+ return document;
+ }
+
+ /**
+ * This method must be overridden by a concrete subclass. It is
+ * responsible for iterating over the Elements of the Document and
+ * writing them out.
+ */
+ protected abstract void write() throws IOException, BadLocationException;
+
+ /**
+ * Return the text of the Document that is associated with the given
+ * Element. If the Element is not a leaf Element, this will throw
+ * BadLocationException.
+ *
+ * @throws BadLocationException if the element is not a leaf
+ */
+ protected String getText(Element elt) throws BadLocationException
+ {
+ if (! elt.isLeaf())
+ throw new BadLocationException("Element is not a leaf",
+ elt.getStartOffset());
+ return document.getText(elt.getStartOffset(),
+ elt.getEndOffset() - elt.getStartOffset());
+ }
+
+ /**
+ * This method calls Writer.write on the indicated data, and updates
+ * the current line length. This method does not look for newlines
+ * in the written data; the caller is responsible for that.
+ *
+ * @since 1.3
+ */
+ protected void output(char[] data, int start, int len) throws IOException
+ {
+ writer.write(data, start, len);
+ lineLength += len;
+ }
+
+ /**
+ * Write a line separator using the output method, and then reset
+ * the current line length.
+ *
+ * @since 1.3
+ */
+ protected void writeLineSeparator() throws IOException
+ {
+ output(lineSeparatorChars, 0, lineSeparatorChars.length);
+ lineLength = 0;
+ indented = false;
+ }
+
+ /**
+ * Write a single character.
+ */
+ protected void write(char ch) throws IOException
+ {
+ write(new char[] { ch }, 0, 1);
+ }
+
+ /**
+ * Write a String.
+ */
+ protected void write(String s) throws IOException
+ {
+ char[] v = s.toCharArray();
+ write(v, 0, v.length);
+ }
+
+ /**
+ * Write a character array to the output Writer, properly handling
+ * newlines and, if needed, wrapping lines as they are output.
+ * @since 1.3
+ */
+ protected void write(char[] data, int start, int len) throws IOException
+ {
+ if (getCanWrapLines())
+ {
+ // FIXME: should we be handling newlines specially here?
+ for (int i = 0; i < len; )
+ {
+ int start_i = i;
+ // Find next space.
+ while (i < len && data[start + i] != ' ')
+ ++i;
+ if (i < len && lineLength + i - start_i >= maxLineLength)
+ writeLineSeparator();
+ else if (i < len)
+ {
+ // Write the trailing space.
+ ++i;
+ }
+ // Write out the text.
+ output(data, start + start_i, start + i - start_i);
+ }
+ }
+ else
+ {
+ int saved_i = start;
+ for (int i = start; i < start + len; ++i)
+ {
+ if (data[i] == NEWLINE)
+ {
+ output(data, saved_i, i - saved_i);
+ writeLineSeparator();
+ }
+ }
+ if (saved_i < start + len - 1)
+ output(data, saved_i, start + len - saved_i);
+ }
+ }
+
+ /**
+ * Indent this line by emitting spaces, according to the current
+ * indent level and the current number of spaces per indent. After
+ * this method is called, the current line is no longer considered
+ * to be empty, even if no spaces are actually written.
+ */
+ protected void indent() throws IOException
+ {
+ int spaces = indentLevel * indentSpace;
+ if (spaces > 0)
+ {
+ char[] v = new char[spaces];
+ Arrays.fill(v, ' ');
+ write(v, 0, v.length);
+ }
+ indented = true;
+ }
+
+ /**
+ * Return the index of the Document at which output starts.
+ * @since 1.3
+ */
+ public int getStartOffset()
+ {
+ return startOffset;
+ }
+
+ /**
+ * Return the index of the Document at which output ends.
+ * @since 1.3
+ */
+ public int getEndOffset()
+ {
+ return endOffset;
+ }
+
+ /**
+ * Return true if the Element's range overlaps our desired output
+ * range; false otherwise.
+ */
+ protected boolean inRange(Element elt)
+ {
+ int eltStart = elt.getStartOffset();
+ int eltEnd = elt.getEndOffset();
+ return ((eltStart >= startOffset && eltStart < endOffset)
+ || (eltEnd >= startOffset && eltEnd < endOffset));
+ }
+
+ /**
+ * Output the text of the indicated Element, properly clipping it to
+ * the range of the Document specified when the AbstractWriter was
+ * created.
+ */
+ protected void text(Element elt) throws BadLocationException, IOException
+ {
+ int eltStart = elt.getStartOffset();
+ int eltEnd = elt.getEndOffset();
+
+ eltStart = Math.max(eltStart, startOffset);
+ eltEnd = Math.min(eltEnd, endOffset);
+ write(document.getText(eltStart, eltEnd));
+ }
+
+ /**
+ * Set the maximum line length.
+ */
+ protected void setLineLength(int maxLineLength)
+ {
+ this.maxLineLength = maxLineLength;
+ }
+
+ /**
+ * Return the maximum line length.
+ * @since 1.3
+ */
+ protected int getLineLength()
+ {
+ return maxLineLength;
+ }
+
+ /**
+ * Set the current line length.
+ * @since 1.3
+ */
+ protected void setCurrentLineLength(int lineLength)
+ {
+ this.lineLength = lineLength;
+ }
+
+ /**
+ * Return the current line length.
+ * @since 1.3
+ */
+ protected int getCurrentLineLength()
+ {
+ return lineLength;
+ }
+
+ /**
+ * Return true if the line is empty, false otherwise. The line is
+ * empty if nothing has been written since the last newline, and
+ * indent has not been invoked.
+ */
+ protected boolean isLineEmpty()
+ {
+ return lineLength == 0 && ! indented;
+ }
+
+ /**
+ * Set the flag indicating whether lines will wrap. This affects
+ * the behavior of write().
+ * @since 1.3
+ */
+ protected void setCanWrapLines(boolean canWrapLines)
+ {
+ this.canWrapLines = canWrapLines;
+ }
+
+ /**
+ * Return true if lines printed via write() will wrap, false
+ * otherwise.
+ * @since 1.3
+ */
+ protected boolean getCanWrapLines()
+ {
+ return canWrapLines;
+ }
+
+ /**
+ * Set the number of spaces per indent level.
+ * @since 1.3
+ */
+ protected void setIndentSpace(int indentSpace)
+ {
+ this.indentSpace = indentSpace;
+ }
+
+ /**
+ * Return the number of spaces per indent level.
+ * @since 1.3
+ */
+ protected int getIndentSpace()
+ {
+ return indentSpace;
+ }
+
+ /**
+ * Set the current line separator.
+ * @since 1.3
+ */
+ public void setLineSeparator(String lineSeparator)
+ {
+ this.lineSeparator = lineSeparator;
+ this.lineSeparatorChars = lineSeparator.toCharArray();
+ }
+
+ /**
+ * Return the current line separator.
+ * @since 1.3
+ */
+ public String getLineSeparator()
+ {
+ return lineSeparator;
+ }
+
+ /**
+ * Increment the indent level.
+ */
+ protected void incrIndent()
+ {
+ ++indentLevel;
+ }
+
+ /**
+ * Decrement the indent level.
+ */
+ protected void decrIndent()
+ {
+ --indentLevel;
+ }
+
+ /**
+ * Return the current indent level.
+ * @since 1.3
+ */
+ protected int getIndentLevel()
+ {
+ return indentLevel;
+ }
+
+ /**
+ * Print the given AttributeSet as a sequence of assignment-like
+ * strings, e.g. "key=value".
+ */
+ protected void writeAttributes(AttributeSet attrs) throws IOException
+ {
+ Enumeration e = attrs.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ Object name = e.nextElement();
+ Object val = attrs.getAttribute(name);
+ write(name + "=" + val);
+ writeLineSeparator();
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/AsyncBoxView.java b/libjava/classpath/javax/swing/text/AsyncBoxView.java
new file mode 100644
index 000000000..aca77aa3b
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/AsyncBoxView.java
@@ -0,0 +1,1480 @@
+/* AsyncBoxView.java -- A box view that performs layout asynchronously
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.util.ArrayList;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.Position.Bias;
+
+/**
+ * A {@link View} implementation that lays out its child views in a box, either
+ * vertically or horizontally. The difference to {@link BoxView} is that the
+ * layout is performed in an asynchronous manner. This helps to keep the
+ * eventqueue free from non-GUI related tasks.
+ *
+ * This view is currently not used in standard text components. In order to
+ * use it you would have to implement a special {@link EditorKit} with a
+ * {@link ViewFactory} that returns this view. For example:
+ *
+ * <pre>
+ * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory
+ * {
+ * public View create(Element el)
+ * {
+ * if (el.getName().equals(AbstractDocument.SectionElementName))
+ * return new AsyncBoxView(el, View.Y_AXIS);
+ * return super.getViewFactory().create(el);
+ * }
+ * public ViewFactory getViewFactory() {
+ * return this;
+ * }
+ * }
+ * </pre>
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ *
+ * @since 1.3
+ */
+public class AsyncBoxView
+ extends View
+{
+
+ /**
+ * Manages the effective position of child views. That keeps the visible
+ * layout stable while the AsyncBoxView might be changing until the layout
+ * thread decides to publish the new layout.
+ */
+ public class ChildLocator
+ {
+
+ /**
+ * The last valid location.
+ */
+ protected ChildState lastValidOffset;
+
+ /**
+ * The last allocation.
+ */
+ protected Rectangle lastAlloc;
+
+ /**
+ * A Rectangle used for child allocation calculation to avoid creation
+ * of lots of garbage Rectangle objects.
+ */
+ protected Rectangle childAlloc;
+
+ /**
+ * Creates a new ChildLocator.
+ */
+ public ChildLocator()
+ {
+ lastAlloc = new Rectangle();
+ childAlloc = new Rectangle();
+ }
+
+ /**
+ * Receives notification that a child has changed. This is called by
+ * child state objects that have changed it's major span.
+ *
+ * This sets the {@link #lastValidOffset} field to <code>cs</code> if
+ * the new child state's view start offset is smaller than the start offset
+ * of the current child state's view or when <code>lastValidOffset</code>
+ * is <code>null</code>.
+ *
+ * @param cs the child state object that has changed
+ */
+ public synchronized void childChanged(ChildState cs)
+ {
+ if (lastValidOffset == null
+ || cs.getChildView().getStartOffset()
+ < lastValidOffset.getChildView().getStartOffset())
+ {
+ lastValidOffset = cs;
+ }
+ }
+
+ /**
+ * Returns the view index of the view that occupies the specified area, or
+ * <code>-1</code> if there is no such child view.
+ *
+ * @param x the x coordinate (relative to <code>a</code>)
+ * @param y the y coordinate (relative to <code>a</code>)
+ * @param a the current allocation of this view
+ *
+ * @return the view index of the view that occupies the specified area, or
+ * <code>-1</code> if there is no such child view
+ */
+ public int getViewIndexAtPoint(float x, float y, Shape a)
+ {
+ setAllocation(a);
+ float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x
+ : y - lastAlloc.y;
+ int index = getViewIndexAtVisualOffset(targetOffset);
+ return index;
+ }
+
+ /**
+ * Returns the current allocation for a child view. This updates the
+ * offsets for all children <em>before</em> the requested child view.
+ *
+ * @param index the index of the child view
+ * @param a the current allocation of this view
+ *
+ * @return the current allocation for a child view
+ */
+ public synchronized Shape getChildAllocation(int index, Shape a)
+ {
+ if (a == null)
+ return null;
+ setAllocation(a);
+ ChildState cs = getChildState(index);
+ if (cs.getChildView().getStartOffset()
+ > lastValidOffset.getChildView().getStartOffset())
+ {
+ updateChildOffsetsToIndex(index);
+ }
+ Shape ca = getChildAllocation(index);
+ return ca;
+ }
+
+ /**
+ * Paints all child views.
+ *
+ * @param g the graphics context to use
+ */
+ public synchronized void paintChildren(Graphics g)
+ {
+ Rectangle clip = g.getClipBounds();
+ float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x
+ : clip.y - lastAlloc.y;
+ int index = getViewIndexAtVisualOffset(targetOffset);
+ int n = getViewCount();
+ float offs = getChildState(index).getMajorOffset();
+ for (int i = index; i < n; i++)
+ {
+ ChildState cs = getChildState(i);
+ cs.setMajorOffset(offs);
+ Shape ca = getChildAllocation(i);
+ if (ca.intersects(clip))
+ {
+ synchronized (cs)
+ {
+ View v = cs.getChildView();
+ v.paint(g, ca);
+ }
+ }
+ else
+ {
+ // done painting intersection
+ break;
+ }
+ offs += cs.getMajorSpan();
+ }
+ }
+
+ /**
+ * Returns the current allocation of the child view with the specified
+ * index. Note that this will <em>not</em> update any location information.
+ *
+ * @param index the index of the requested child view
+ *
+ * @return the current allocation of the child view with the specified
+ * index
+ */
+ protected Shape getChildAllocation(int index)
+ {
+ ChildState cs = getChildState(index);
+ if (! cs.isLayoutValid())
+ cs.run();
+
+ if (getMajorAxis() == X_AXIS)
+ {
+ childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset();
+ childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset();
+ childAlloc.width = (int) cs.getMajorSpan();
+ childAlloc.height = (int) cs.getMinorSpan();
+ }
+ else
+ {
+ childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset();
+ childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset();
+ childAlloc.height = (int) cs.getMajorSpan();
+ childAlloc.width = (int) cs.getMinorSpan();
+ }
+ return childAlloc;
+ }
+
+ /**
+ * Sets the current allocation for this view.
+ *
+ * @param a the allocation to set
+ */
+ protected void setAllocation(Shape a)
+ {
+ if (a instanceof Rectangle)
+ lastAlloc.setBounds((Rectangle) a);
+ else
+ lastAlloc.setBounds(a.getBounds());
+
+ setSize(lastAlloc.width, lastAlloc.height);
+ }
+
+ /**
+ * Returns the index of the view at the specified offset along the major
+ * layout axis.
+ *
+ * @param targetOffset the requested offset
+ *
+ * @return the index of the view at the specified offset along the major
+ * layout axis
+ */
+ protected int getViewIndexAtVisualOffset(float targetOffset)
+ {
+ int n = getViewCount();
+ if (n > 0)
+ {
+ if (lastValidOffset == null)
+ lastValidOffset = getChildState(0);
+ if (targetOffset > majorSpan)
+ return 0;
+ else if (targetOffset > lastValidOffset.getMajorOffset())
+ return updateChildOffsets(targetOffset);
+ else
+ {
+ float offs = 0f;
+ for (int i = 0; i < n; i++)
+ {
+ ChildState cs = getChildState(i);
+ float nextOffs = offs + cs.getMajorSpan();
+ if (targetOffset < nextOffs)
+ return i;
+ offs = nextOffs;
+ }
+ }
+ }
+ return n - 1;
+ }
+
+ /**
+ * Updates all the child view offsets up to the specified targetOffset.
+ *
+ * @param targetOffset the offset up to which the child view offsets are
+ * updated
+ *
+ * @return the index of the view at the specified offset
+ */
+ private int updateChildOffsets(float targetOffset)
+ {
+ int n = getViewCount();
+ int targetIndex = n - 1;
+ int pos = lastValidOffset.getChildView().getStartOffset();
+ int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
+ float start = lastValidOffset.getMajorOffset();
+ float lastOffset = start;
+ for (int i = startIndex; i < n; i++)
+ {
+ ChildState cs = getChildState(i);
+ cs.setMajorOffset(lastOffset);
+ lastOffset += cs.getMajorSpan();
+ if (targetOffset < lastOffset)
+ {
+ targetIndex = i;
+ lastValidOffset = cs;
+ break;
+ }
+ }
+ return targetIndex;
+ }
+
+ /**
+ * Updates the offsets of the child views up to the specified index.
+ *
+ * @param index the index up to which the offsets are updated
+ */
+ private void updateChildOffsetsToIndex(int index)
+ {
+ int pos = lastValidOffset.getChildView().getStartOffset();
+ int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
+ float lastOffset = lastValidOffset.getMajorOffset();
+ for (int i = startIndex; i <= index; i++)
+ {
+ ChildState cs = getChildState(i);
+ cs.setMajorOffset(lastOffset);
+ lastOffset += cs.getMajorSpan();
+ }
+ }
+ }
+
+ /**
+ * Represents the layout state of a child view.
+ */
+ public class ChildState
+ implements Runnable
+ {
+
+ /**
+ * The child view for this state record.
+ */
+ private View childView;
+
+ /**
+ * Indicates if the minor axis requirements of this child view are valid
+ * or not.
+ */
+ private boolean minorValid;
+
+ /**
+ * Indicates if the major axis requirements of this child view are valid
+ * or not.
+ */
+ private boolean majorValid;
+
+ /**
+ * Indicates if the current child size is valid. This is package private
+ * to avoid synthetic accessor method.
+ */
+ boolean childSizeValid;
+
+ /**
+ * The child views minimumSpan. This is package private to avoid accessor
+ * method.
+ */
+ float minimum;
+
+ /**
+ * The child views preferredSpan. This is package private to avoid accessor
+ * method.
+ */
+ float preferred;
+
+ /**
+ * The current span of the child view along the major axis.
+ */
+ private float majorSpan;
+
+ /**
+ * The current offset of the child view along the major axis.
+ */
+ private float majorOffset;
+
+ /**
+ * The current span of the child view along the minor axis.
+ */
+ private float minorSpan;
+
+ /**
+ * The current offset of the child view along the major axis.
+ */
+ private float minorOffset;
+
+ /**
+ * The child views maximumSpan.
+ */
+ private float maximum;
+
+ /**
+ * Creates a new <code>ChildState</code> object for the specified child
+ * view.
+ *
+ * @param view the child view for which to create the state record
+ */
+ public ChildState(View view)
+ {
+ childView = view;
+ }
+
+ /**
+ * Returns the child view for which this <code>ChildState</code> represents
+ * the layout state.
+ *
+ * @return the child view for this child state object
+ */
+ public View getChildView()
+ {
+ return childView;
+ }
+
+ /**
+ * Returns <code>true</code> if the current layout information is valid,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the current layout information is valid,
+ * <code>false</code> otherwise
+ */
+ public boolean isLayoutValid()
+ {
+ return minorValid && majorValid && childSizeValid;
+ }
+
+ /**
+ * Performs the layout update for the child view managed by this
+ * <code>ChildState</code>.
+ */
+ public void run()
+ {
+ Document doc = getDocument();
+ if (doc instanceof AbstractDocument)
+ {
+ AbstractDocument abstractDoc = (AbstractDocument) doc;
+ abstractDoc.readLock();
+ }
+
+ try
+ {
+
+ if (!(minorValid && majorValid && childSizeValid)
+ && childView.getParent() == AsyncBoxView.this)
+ {
+ synchronized(AsyncBoxView.this)
+ {
+ changing = this;
+ }
+ update();
+ synchronized(AsyncBoxView.this)
+ {
+ changing = null;
+ }
+ // Changing the major axis may cause the minor axis
+ // requirements to have changed, so we need to do this again.
+ update();
+ }
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ {
+ AbstractDocument abstractDoc = (AbstractDocument) doc;
+ abstractDoc.readUnlock();
+ }
+ }
+ }
+
+ /**
+ * Performs the actual update after the run methods has made its checks
+ * and locked the document.
+ */
+ private void update()
+ {
+ int majorAxis = getMajorAxis();
+ boolean minorUpdated = false;
+ synchronized (this)
+ {
+ if (! minorValid)
+ {
+ int minorAxis = getMinorAxis();
+ minimum = childView.getMinimumSpan(minorAxis);
+ preferred = childView.getPreferredSpan(minorAxis);
+ maximum = childView.getMaximumSpan(minorAxis);
+ minorValid = true;
+ minorUpdated = true;
+ }
+ }
+ if (minorUpdated)
+ minorRequirementChange(this);
+
+ boolean majorUpdated = false;
+ float delta = 0.0F;
+ synchronized (this)
+ {
+ if (! majorValid)
+ {
+ float oldSpan = majorSpan;
+ majorSpan = childView.getPreferredSpan(majorAxis);
+ delta = majorSpan - oldSpan;
+ majorValid = true;
+ majorUpdated = true;
+ }
+ }
+ if (majorUpdated)
+ {
+ majorRequirementChange(this, delta);
+ locator.childChanged(this);
+ }
+
+ synchronized (this)
+ {
+ if (! childSizeValid)
+ {
+ float w;
+ float h;
+ if (majorAxis == X_AXIS)
+ {
+ w = majorSpan;
+ h = getMinorSpan();
+ }
+ else
+ {
+ w = getMinorSpan();
+ h = majorSpan;
+ }
+ childSizeValid = true;
+ childView.setSize(w, h);
+ }
+ }
+ }
+
+ /**
+ * Returns the span of the child view along the minor layout axis.
+ *
+ * @return the span of the child view along the minor layout axis
+ */
+ public float getMinorSpan()
+ {
+ float retVal;
+ if (maximum < minorSpan)
+ retVal = maximum;
+ else
+ retVal = Math.max(minimum, minorSpan);
+ return retVal;
+ }
+
+ /**
+ * Returns the offset of the child view along the minor layout axis.
+ *
+ * @return the offset of the child view along the minor layout axis
+ */
+ public float getMinorOffset()
+ {
+ float retVal;
+ if (maximum < minorSpan)
+ {
+ float align = childView.getAlignment(getMinorAxis());
+ retVal = ((minorSpan - maximum) * align);
+ }
+ else
+ retVal = 0f;
+
+ return retVal;
+ }
+
+ /**
+ * Returns the span of the child view along the major layout axis.
+ *
+ * @return the span of the child view along the major layout axis
+ */
+
+ public float getMajorSpan()
+ {
+ return majorSpan;
+ }
+
+ /**
+ * Returns the offset of the child view along the major layout axis.
+ *
+ * @return the offset of the child view along the major layout axis
+ */
+ public float getMajorOffset()
+ {
+ return majorOffset;
+ }
+
+ /**
+ * Sets the offset of the child view along the major layout axis. This
+ * should only be called by the ChildLocator of that child view.
+ *
+ * @param offset the offset to set
+ */
+ public void setMajorOffset(float offset)
+ {
+ majorOffset = offset;
+ }
+
+ /**
+ * Mark the preferences changed for that child. This forwards to
+ * {@link AsyncBoxView#preferenceChanged}.
+ *
+ * @param width <code>true</code> if the width preference has changed
+ * @param height <code>true</code> if the height preference has changed
+ */
+ public void preferenceChanged(boolean width, boolean height)
+ {
+ if (getMajorAxis() == X_AXIS)
+ {
+ if (width)
+ majorValid = false;
+ if (height)
+ minorValid = false;
+ }
+ else
+ {
+ if (width)
+ minorValid = false;
+ if (height)
+ majorValid = false;
+ }
+ childSizeValid = false;
+ }
+ }
+
+ /**
+ * Flushes the requirements changes upwards asynchronously.
+ */
+ private class FlushTask implements Runnable
+ {
+ /**
+ * Starts the flush task. This obtains a readLock on the document
+ * and then flushes all the updates using
+ * {@link AsyncBoxView#flushRequirementChanges()} after updating the
+ * requirements.
+ */
+ public void run()
+ {
+ try
+ {
+ // Acquire a lock on the document.
+ Document doc = getDocument();
+ if (doc instanceof AbstractDocument)
+ {
+ AbstractDocument abstractDoc = (AbstractDocument) doc;
+ abstractDoc.readLock();
+ }
+
+ int n = getViewCount();
+ if (minorChanged && (n > 0))
+ {
+ LayoutQueue q = getLayoutQueue();
+ ChildState min = getChildState(0);
+ ChildState pref = getChildState(0);
+ for (int i = 1; i < n; i++)
+ {
+ ChildState cs = getChildState(i);
+ if (cs.minimum > min.minimum)
+ min = cs;
+ if (cs.preferred > pref.preferred)
+ pref = cs;
+ }
+ synchronized (AsyncBoxView.this)
+ {
+ minReq = min;
+ prefReq = pref;
+ }
+ }
+
+ flushRequirementChanges();
+ }
+ finally
+ {
+ // Release the lock on the document.
+ Document doc = getDocument();
+ if (doc instanceof AbstractDocument)
+ {
+ AbstractDocument abstractDoc = (AbstractDocument) doc;
+ abstractDoc.readUnlock();
+ }
+ }
+ }
+
+ }
+
+ /**
+ * The major layout axis.
+ */
+ private int majorAxis;
+
+ /**
+ * The top inset.
+ */
+ private float topInset;
+
+ /**
+ * The bottom inset.
+ */
+ private float bottomInset;
+
+ /**
+ * The left inset.
+ */
+ private float leftInset;
+
+ /**
+ * Indicates if the major span should be treated as beeing estimated or not.
+ */
+ private boolean estimatedMajorSpan;
+
+ /**
+ * The right inset.
+ */
+ private float rightInset;
+
+ /**
+ * The children and their layout statistics.
+ */
+ private ArrayList childStates;
+
+ /**
+ * The currently changing child state. May be null if there is no child state
+ * updating at the moment. This is package private to avoid a synthetic
+ * accessor method inside ChildState.
+ */
+ ChildState changing;
+
+ /**
+ * Represents the minimum requirements. This is used in
+ * {@link #getMinimumSpan(int)}.
+ */
+ ChildState minReq;
+
+ /**
+ * Represents the minimum requirements. This is used in
+ * {@link #getPreferredSpan(int)}.
+ */
+ ChildState prefReq;
+
+ /**
+ * Indicates that the major axis requirements have changed.
+ */
+ private boolean majorChanged;
+
+ /**
+ * Indicates that the minor axis requirements have changed. This is package
+ * private to avoid synthetic accessor method.
+ */
+ boolean minorChanged;
+
+ /**
+ * The current span along the major layout axis. This is package private to
+ * avoid synthetic accessor method.
+ */
+ float majorSpan;
+
+ /**
+ * The current span along the minor layout axis. This is package private to
+ * avoid synthetic accessor method.
+ */
+ float minorSpan;
+
+ /**
+ * This tasked is placed on the layout queue to flush updates up to the
+ * parent view.
+ */
+ private Runnable flushTask;
+
+ /**
+ * The child locator for this view.
+ */
+ protected ChildLocator locator;
+
+ /**
+ * Creates a new <code>AsyncBoxView</code> that represents the specified
+ * element and layouts its children along the specified axis.
+ *
+ * @param elem the element
+ * @param axis the layout axis
+ */
+ public AsyncBoxView(Element elem, int axis)
+ {
+ super(elem);
+ majorAxis = axis;
+ childStates = new ArrayList();
+ flushTask = new FlushTask();
+ locator = new ChildLocator();
+ minorSpan = Short.MAX_VALUE;
+ }
+
+ /**
+ * Returns the major layout axis.
+ *
+ * @return the major layout axis
+ */
+ public int getMajorAxis()
+ {
+ return majorAxis;
+ }
+
+ /**
+ * Returns the minor layout axis, that is the axis orthogonal to the major
+ * layout axis.
+ *
+ * @return the minor layout axis
+ */
+ public int getMinorAxis()
+ {
+ return majorAxis == X_AXIS ? Y_AXIS : X_AXIS;
+ }
+
+ /**
+ * Returns the view at the specified <code>index</code>.
+ *
+ * @param index the index of the requested child view
+ *
+ * @return the view at the specified <code>index</code>
+ */
+ public View getView(int index)
+ {
+ View view = null;
+ synchronized(childStates)
+ {
+ if ((index >= 0) && (index < childStates.size()))
+ {
+ ChildState cs = (ChildState) childStates.get(index);
+ view = cs.getChildView();
+ }
+ }
+ return view;
+ }
+
+ /**
+ * Returns the number of child views.
+ *
+ * @return the number of child views
+ */
+ public int getViewCount()
+ {
+ synchronized(childStates)
+ {
+ return childStates.size();
+ }
+ }
+
+ /**
+ * Returns the view index of the child view that represents the specified
+ * model position.
+ *
+ * @param pos the model position for which we search the view index
+ * @param bias the bias
+ *
+ * @return the view index of the child view that represents the specified
+ * model position
+ */
+ public int getViewIndex(int pos, Position.Bias bias)
+ {
+ int retVal = -1;
+
+ if (bias == Position.Bias.Backward)
+ pos = Math.max(0, pos - 1);
+
+ // TODO: A possible optimization would be to implement a binary search
+ // here.
+ int numChildren = childStates.size();
+ if (numChildren > 0)
+ {
+ for (int i = 0; i < numChildren; ++i)
+ {
+ View child = ((ChildState) childStates.get(i)).getChildView();
+ if (child.getStartOffset() <= pos && child.getEndOffset() > pos)
+ {
+ retVal = i;
+ break;
+ }
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * Returns the top inset.
+ *
+ * @return the top inset
+ */
+ public float getTopInset()
+ {
+ return topInset;
+ }
+
+ /**
+ * Sets the top inset.
+ *
+ * @param top the top inset
+ */
+ public void setTopInset(float top)
+ {
+ topInset = top;
+ }
+
+ /**
+ * Returns the bottom inset.
+ *
+ * @return the bottom inset
+ */
+ public float getBottomInset()
+ {
+ return bottomInset;
+ }
+
+ /**
+ * Sets the bottom inset.
+ *
+ * @param bottom the bottom inset
+ */
+ public void setBottomInset(float bottom)
+ {
+ bottomInset = bottom;
+ }
+
+ /**
+ * Returns the left inset.
+ *
+ * @return the left inset
+ */
+ public float getLeftInset()
+ {
+ return leftInset;
+ }
+
+ /**
+ * Sets the left inset.
+ *
+ * @param left the left inset
+ */
+ public void setLeftInset(float left)
+ {
+ leftInset = left;
+ }
+
+ /**
+ * Returns the right inset.
+ *
+ * @return the right inset
+ */
+ public float getRightInset()
+ {
+ return rightInset;
+ }
+
+ /**
+ * Sets the right inset.
+ *
+ * @param right the right inset
+ */
+ public void setRightInset(float right)
+ {
+ rightInset = right;
+ }
+
+ /**
+ * Loads the child views of this view. This is triggered by
+ * {@link #setParent(View)}.
+ *
+ * @param f the view factory to build child views with
+ */
+ protected void loadChildren(ViewFactory f)
+ {
+ Element e = getElement();
+ int n = e.getElementCount();
+ if (n > 0)
+ {
+ View[] added = new View[n];
+ for (int i = 0; i < n; i++)
+ {
+ added[i] = f.create(e.getElement(i));
+ }
+ replace(0, 0, added);
+ }
+ }
+
+ /**
+ * Returns the span along an axis that is taken up by the insets.
+ *
+ * @param axis the axis
+ *
+ * @return the span along an axis that is taken up by the insets
+ *
+ * @since 1.4
+ */
+ protected float getInsetSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = leftInset + rightInset;
+ else
+ span = topInset + bottomInset;
+ return span;
+ }
+
+ /**
+ * Sets the <code>estimatedMajorSpan</code> property that determines if
+ * the major span should be treated as beeing estimated.
+ *
+ * @param estimated if the major span should be treated as estimated or not
+ *
+ * @since 1.4
+ */
+ protected void setEstimatedMajorSpan(boolean estimated)
+ {
+ estimatedMajorSpan = estimated;
+ }
+
+ /**
+ * Determines whether the major span should be treated as estimated or as
+ * beeing accurate.
+ *
+ * @return <code>true</code> if the major span should be treated as
+ * estimated, <code>false</code> if the major span should be treated
+ * as accurate
+ *
+ * @since 1.4
+ */
+ protected boolean getEstimatedMajorSpan()
+ {
+ return estimatedMajorSpan;
+ }
+
+ /**
+ * Receives notification from the child states that the requirements along
+ * the minor axis have changed.
+ *
+ * @param cs the child state from which this notification is messaged
+ */
+ protected synchronized void minorRequirementChange(ChildState cs)
+ {
+ minorChanged = true;
+ }
+
+ /**
+ * Receives notification from the child states that the requirements along
+ * the major axis have changed.
+ *
+ * @param cs the child state from which this notification is messaged
+ */
+ protected void majorRequirementChange(ChildState cs, float delta)
+ {
+ if (! estimatedMajorSpan)
+ majorSpan += delta;
+ majorChanged = true;
+ }
+
+ /**
+ * Sets the parent for this view. This calls loadChildren if
+ * <code>parent</code> is not <code>null</code> and there have not been any
+ * child views initializes.
+ *
+ * @param parent the new parent view; <code>null</code> if this view is
+ * removed from the view hierarchy
+ *
+ * @see View#setParent(View)
+ */
+ public void setParent(View parent)
+ {
+ super.setParent(parent);
+ if ((parent != null) && (getViewCount() == 0))
+ {
+ ViewFactory f = getViewFactory();
+ loadChildren(f);
+ }
+ }
+
+ /**
+ * Sets the size of this view. This is ususally called before {@link #paint}
+ * is called to make sure the view has a valid layout.
+ *
+ * This implementation queues layout requests for every child view if the
+ * minor axis span has changed. (The major axis span is requested to never
+ * change for this view).
+ *
+ * @param width the width of the view
+ * @param height the height of the view
+ */
+ public void setSize(float width, float height)
+ {
+ float targetSpan;
+ if (majorAxis == X_AXIS)
+ targetSpan = height - getTopInset() - getBottomInset();
+ else
+ targetSpan = width - getLeftInset() - getRightInset();
+
+ if (targetSpan != minorSpan)
+ {
+ minorSpan = targetSpan;
+
+ int n = getViewCount();
+ LayoutQueue q = getLayoutQueue();
+ for (int i = 0; i < n; i++)
+ {
+ ChildState cs = getChildState(i);
+ cs.childSizeValid = false;
+ q.addTask(cs);
+ }
+ q.addTask(flushTask);
+ }
+ }
+
+ /**
+ * Replaces child views with new child views.
+ *
+ * This creates ChildState objects for all the new views and adds layout
+ * requests for them to the layout queue.
+ *
+ * @param offset the offset at which to remove/insert
+ * @param length the number of child views to remove
+ * @param views the new child views to insert
+ */
+ public void replace(int offset, int length, View[] views)
+ {
+ synchronized(childStates)
+ {
+ LayoutQueue q = getLayoutQueue();
+ for (int i = 0; i < length; i++)
+ childStates.remove(offset);
+
+ for (int i = views.length - 1; i >= 0; i--)
+ childStates.add(offset, createChildState(views[i]));
+
+ // We need to go through the new child states _after_ they have been
+ // added to the childStates list, otherwise the layout tasks may find
+ // an incomplete child list. That means we have to loop through
+ // them again, but what else can we do?
+ if (views.length != 0)
+ {
+ for (int i = 0; i < views.length; i++)
+ {
+ ChildState cs = (ChildState) childStates.get(i + offset);
+ cs.getChildView().setParent(this);
+ q.addTask(cs);
+ }
+ q.addTask(flushTask);
+ }
+ }
+ }
+
+ /**
+ * Paints the view. This requests the {@link ChildLocator} to paint the views
+ * after setting the allocation on it.
+ *
+ * @param g the graphics context to use
+ * @param s the allocation for this view
+ */
+ public void paint(Graphics g, Shape s)
+ {
+ synchronized (locator)
+ {
+ locator.setAllocation(s);
+ locator.paintChildren(g);
+ }
+ }
+
+ /**
+ * Returns the preferred span of this view along the specified layout axis.
+ *
+ * @return the preferred span of this view along the specified layout axis
+ */
+ public float getPreferredSpan(int axis)
+ {
+ float retVal;
+ if (majorAxis == axis)
+ retVal = majorSpan;
+
+ else if (prefReq != null)
+ {
+ View child = prefReq.getChildView();
+ retVal = child.getPreferredSpan(axis);
+ }
+
+ // If we have no layout information yet, then return insets + 30 as
+ // an estimation.
+ else
+ {
+ if (axis == X_AXIS)
+ retVal = getLeftInset() + getRightInset() + 30;
+ else
+ retVal = getTopInset() + getBottomInset() + 30;
+ }
+ return retVal;
+ }
+
+ /**
+ * Maps a model location to view coordinates.
+ *
+ * @param pos the model location
+ * @param a the current allocation of this view
+ * @param b the bias
+ *
+ * @return the view allocation for the specified model location
+ */
+ public Shape modelToView(int pos, Shape a, Bias b)
+ throws BadLocationException
+ {
+ int index = getViewIndexAtPosition(pos, b);
+ Shape ca = locator.getChildAllocation(index, a);
+
+ ChildState cs = getChildState(index);
+ synchronized (cs)
+ {
+ View cv = cs.getChildView();
+ Shape v = cv.modelToView(pos, ca, b);
+ return v;
+ }
+ }
+
+ /**
+ * Maps view coordinates to a model location.
+ *
+ * @param x the x coordinate (relative to <code>a</code>)
+ * @param y the y coordinate (relative to <code>a</code>)
+ * @param b holds the bias of the model location on method exit
+ *
+ * @return the model location for the specified view location
+ */
+ public int viewToModel(float x, float y, Shape a, Bias[] b)
+ {
+ int pos;
+ int index;
+ Shape ca;
+
+ synchronized (locator)
+ {
+ index = locator.getViewIndexAtPoint(x, y, a);
+ ca = locator.getChildAllocation(index, a);
+ }
+
+ ChildState cs = getChildState(index);
+ synchronized (cs)
+ {
+ View v = cs.getChildView();
+ pos = v.viewToModel(x, y, ca, b);
+ }
+ return pos;
+ }
+
+ /**
+ * Returns the child allocation for the child view with the specified
+ * <code>index</code>.
+ *
+ * @param index the index of the child view
+ * @param a the current allocation of this view
+ *
+ * @return the allocation of the child view
+ */
+ public Shape getChildAllocation(int index, Shape a)
+ {
+ Shape ca = locator.getChildAllocation(index, a);
+ return ca;
+ }
+
+ /**
+ * Returns the maximum span of this view along the specified axis.
+ * This is implemented to return the <code>preferredSpan</code> for the
+ * major axis (that means the box can't be resized along the major axis) and
+ * {@link Short#MAX_VALUE} for the minor axis.
+ *
+ * @param axis the axis
+ *
+ * @return the maximum span of this view along the specified axis
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float max;
+ if (axis == majorAxis)
+ max = getPreferredSpan(axis);
+ else
+ max = Short.MAX_VALUE;
+ return max;
+ }
+
+ /**
+ * Returns the minimum span along the specified axis.
+ */
+ public float getMinimumSpan(int axis)
+ {
+ float min;
+ if (axis == majorAxis)
+ min = getPreferredSpan(axis);
+ else
+ {
+ if (minReq != null)
+ {
+ View child = minReq.getChildView();
+ min = child.getMinimumSpan(axis);
+ }
+ else
+ {
+ // No layout information yet. Return insets + 5 as some kind of
+ // estimation.
+ if (axis == X_AXIS)
+ min = getLeftInset() + getRightInset() + 5;
+ else
+ min = getTopInset() + getBottomInset() + 5;
+ }
+ }
+ return min;
+ }
+
+ /**
+ * Receives notification that one of the child views has changed its
+ * layout preferences along one or both axis.
+ *
+ * This queues a layout request for that child view if necessary.
+ *
+ * @param view the view that has changed its preferences
+ * @param width <code>true</code> if the width preference has changed
+ * @param height <code>true</code> if the height preference has changed
+ */
+ public synchronized void preferenceChanged(View view, boolean width,
+ boolean height)
+ {
+ if (view == null)
+ getParent().preferenceChanged(this, width, height);
+ else
+ {
+ if (changing != null)
+ {
+ View cv = changing.getChildView();
+ if (cv == view)
+ {
+ changing.preferenceChanged(width, height);
+ return;
+ }
+ }
+ int index = getViewIndexAtPosition(view.getStartOffset(),
+ Position.Bias.Forward);
+ ChildState cs = getChildState(index);
+ cs.preferenceChanged(width, height);
+ LayoutQueue q = getLayoutQueue();
+ q.addTask(cs);
+ q.addTask(flushTask);
+ }
+ }
+
+ /**
+ * Updates the layout for this view. This is implemented to trigger
+ * {@link ChildLocator#childChanged} for the changed view, if there is
+ * any.
+ *
+ * @param ec the element change, may be <code>null</code> if there were
+ * no changes to the element of this view
+ * @param e the document event
+ * @param a the current allocation of this view
+ */
+ protected void updateLayout(DocumentEvent.ElementChange ec,
+ DocumentEvent e, Shape a)
+ {
+ if (ec != null)
+ {
+ int index = Math.max(ec.getIndex() - 1, 0);
+ ChildState cs = getChildState(index);
+ locator.childChanged(cs);
+ }
+ }
+
+
+ /**
+ * Returns the <code>ChildState</code> object associated with the child view
+ * at the specified <code>index</code>.
+ *
+ * @param index the index of the child view for which to query the state
+ *
+ * @return the child state for the specified child view
+ */
+ protected ChildState getChildState(int index) {
+ synchronized (childStates)
+ {
+ return (ChildState) childStates.get(index);
+ }
+ }
+
+ /**
+ * Returns the <code>LayoutQueue</code> used for layouting the box view.
+ * This simply returns {@link LayoutQueue#getDefaultQueue()}.
+ *
+ * @return the <code>LayoutQueue</code> used for layouting the box view
+ */
+ protected LayoutQueue getLayoutQueue()
+ {
+ return LayoutQueue.getDefaultQueue();
+ }
+
+ /**
+ * Returns the child view index of the view that represents the specified
+ * position in the document model.
+ *
+ * @param pos the position in the model
+ * @param b the bias
+ *
+ * @return the child view index of the view that represents the specified
+ * position in the document model
+ */
+ protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b)
+ {
+ if (b == Position.Bias.Backward)
+ pos = Math.max(0, pos - 1);
+ Element elem = getElement();
+ return elem.getElementIndex(pos);
+ }
+
+ /**
+ * Creates a <code>ChildState</code> object for the specified view.
+ *
+ * @param v the view for which to create a child state object
+ *
+ * @return the created child state
+ */
+ protected ChildState createChildState(View v)
+ {
+ return new ChildState(v);
+ }
+
+ /**
+ * Flushes the requirements changes upwards to the parent view. This is
+ * called from the layout thread.
+ */
+ protected synchronized void flushRequirementChanges()
+ {
+ if (majorChanged || minorChanged)
+ {
+ View p = getParent();
+ if (p != null)
+ {
+ boolean horizontal;
+ boolean vertical;
+ if (majorAxis == X_AXIS)
+ {
+ horizontal = majorChanged;
+ vertical = minorChanged;
+ }
+ else
+ {
+ vertical = majorChanged;
+ horizontal = minorChanged;
+ }
+
+ p.preferenceChanged(this, horizontal, vertical);
+ majorChanged = false;
+ minorChanged = false;
+
+ Component c = getContainer();
+ if (c != null)
+ c.repaint();
+ }
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/AttributeSet.java b/libjava/classpath/javax/swing/text/AttributeSet.java
new file mode 100644
index 000000000..a596cd4c3
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/AttributeSet.java
@@ -0,0 +1,195 @@
+/* AttributeSet.java --
+ Copyright (C) 2002, 2004, 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.util.Enumeration;
+
+/**
+ * A set of attributes. An attribute has a key and a value. They typically
+ * describe features of a piece of text that make up its graphical
+ * representation.
+ *
+ * An <code>AttributeSet</code> may have a resolving parent,
+ * that is another <code>AttributeSet</code> that is searched for attribute
+ * keys that are not stored locally in this <code>AttributeSet</code>.
+ *
+ * @author original author unknown
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public interface AttributeSet
+{
+ /**
+ * Used as keys to identify character-run attributes.
+ */
+ static interface CharacterAttribute
+ {
+ // This interface is a marker interface and has no methods.
+ }
+
+ /**
+ * Used as keys to identify color attributes.
+ */
+ static interface ColorAttribute
+ {
+ // This interface is a marker interface and has no methods.
+ }
+
+ /**
+ * Used as keys to identify font attributes.
+ */
+ static interface FontAttribute
+ {
+ // This interface is a marker interface and has no methods.
+ }
+
+ /**
+ * Used as keys to identify paragraph level attributes.
+ */
+ static interface ParagraphAttribute
+ {
+ // This interface is a marker interface and has no methods.
+ }
+
+ /**
+ * Key of the attribute that is used to describe the name of an
+ * <code>AttributeSet</code>.
+ */
+ Object NameAttribute = StyleConstants.NameAttribute;
+
+ /**
+ * Key of the attribute that is used to identify the resolving parent of
+ * an <code>AttributeSet</code>.
+ */
+ Object ResolveAttribute = StyleConstants.ResolveAttribute;
+
+ /**
+ * Returns <code>true</code> if this <code>AttributeSet</code> contains
+ * an attribute with the specified <code>name</code> and <code>value</code>,
+ * <code>false</code> otherwise.
+ *
+ * @param name the name of the requested attribute
+ * @param value the value of the requested attribute
+ *
+ * @return <code>true</code> if this <code>AttributeSet</code> contains
+ * an attribute with the specified <code>name</code> and
+ * <code>value</code>, <code>false</code> otherwise
+ */
+ boolean containsAttribute(Object name, Object value);
+
+ /**
+ * Returns <code>true</code> of this <code>AttributeSet</code> contains all
+ * of the specified <code>attributes</code>.
+ *
+ * @param attributes the requested attributes
+ *
+ * @return <code>true</code> of this <code>AttributeSet</code> contains all
+ * of the specified <code>attributes</code>
+ */
+ boolean containsAttributes(AttributeSet attributes);
+
+ /**
+ * Creates and returns a copy of this <code>AttributeSet</code>.
+ *
+ * @return a copy of this <code>AttributeSet</code>
+ */
+ AttributeSet copyAttributes();
+
+ /**
+ * Returns the attribute with the specified <code>key</code> or
+ * <code>null</code> if no such attribute is defined in this
+ * <code>AttributeSet</code> and its resolving parents.
+ *
+ * @param key the key of the attribute that is looked up
+ *
+ * @return the attribute with the specified <code>key</code> or
+ * <code>null</code> if no such attribute is defined in this
+ * <code>AttributeSet</code> and its resolving parents
+ */
+ Object getAttribute(Object key);
+
+ /**
+ * Returns the number of attributes that are stored locally in this
+ * <code>AttributeSet</code>.
+ *
+ * @return the number of attributes that are stored locally in this
+ * <code>AttributeSet</code>
+ */
+ int getAttributeCount();
+
+ /**
+ * Returns the names of the attributes that are stored in this
+ * <code>AttributeSet</code>.
+ *
+ * @return the names of the attributes that are stored in this
+ * <code>AttributeSet</code>
+ */
+ Enumeration<?> getAttributeNames();
+
+ /**
+ * Returns the resolving parent of this <code>AttributeSet</code>.
+ * If a key is not stored locally, then a {@link #getAttribute(Object)}
+ * request is resolved up in the resolving parent of this
+ * <code>AttributeSet</code>.
+ *
+ * @return the resolving parent of this <code>AttributeSet</code>
+ */
+ AttributeSet getResolveParent();
+
+ /**
+ * Returns <code>true</code> if an attribute with the specified name is
+ * defined locally in this <code>AttributeSet</code>, without resolving
+ * through the resolving parents.
+ *
+ * @return <code>true</code> if an attribute with the specified name is
+ * defined locally in this <code>AttributeSet</code>
+ */
+ boolean isDefined(Object attrName);
+
+ /**
+ * Returns <code>true</code> if all of the attributes in <code>attr</code>
+ * are equal to the attributes in this <code>AttributeSet</code>,
+ * <code>false</code> otherwise.
+ *
+ * @param attr the attributes to be compared to <code>this</code>
+ *
+ * @return <code>true</code> if all of the attributes in <code>attr</code>
+ * are equal to the attributes in this <code>AttributeSet</code>,
+ * <code>false</code> otherwise
+ */
+ boolean isEqual(AttributeSet attr);
+}
diff --git a/libjava/classpath/javax/swing/text/BadLocationException.java b/libjava/classpath/javax/swing/text/BadLocationException.java
new file mode 100644
index 000000000..70591402c
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/BadLocationException.java
@@ -0,0 +1,78 @@
+/* BadLocationException.java --
+ Copyright (C) 2002, 2004, 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;
+
+/**
+ * Indicates that an invalid location within a <code>Document</code> has been
+ * accessed.
+ *
+ * @author original author unknown
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class BadLocationException extends Exception
+{
+ /** The serial version UID for BadLocationException. */
+ private static final long serialVersionUID = -7712259886815656766L;
+
+ /**
+ * The invalid location.
+ */
+ int offset;
+
+ /**
+ * Constructs a <code>BadLocationException</code>
+ *
+ * @param str a string indicating what was wrong with the arguments
+ * @param offset offset within the document that was requested &gt;= 0
+ */
+ public BadLocationException(String str, int offset)
+ {
+ super(str);
+ this.offset = offset;
+ }
+
+ /**
+ * Returns the offset into the document that was not legal.
+ *
+ * @return the offset into the document that was not legal
+ */
+ public int offsetRequested()
+ {
+ return offset;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/BoxView.java b/libjava/classpath/javax/swing/text/BoxView.java
new file mode 100644
index 000000000..325364d2b
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/BoxView.java
@@ -0,0 +1,1112 @@
+/* BoxView.java -- An composite view
+ Copyright (C) 2005, 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.Container;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+
+/**
+ * An implementation of {@link CompositeView} that arranges its children in
+ * a box along one axis. This is comparable to how the <code>BoxLayout</code>
+ * works, but for <code>View</code> children.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class BoxView
+ extends CompositeView
+{
+
+ /**
+ * The axis along which this <code>BoxView</code> is laid out.
+ */
+ private int myAxis;
+
+ /**
+ * Indicates if the layout is valid along X_AXIS or Y_AXIS.
+ */
+ private boolean[] layoutValid = new boolean[2];
+
+ /**
+ * Indicates if the requirements for an axis are valid.
+ */
+ private boolean[] requirementsValid = new boolean[2];
+
+ /**
+ * The spans along the X_AXIS and Y_AXIS.
+ */
+ private int[][] spans = new int[2][];
+
+ /**
+ * The offsets of the children along the X_AXIS and Y_AXIS.
+ */
+ private int[][] offsets = new int[2][];
+
+ /**
+ * The size requirements along the X_AXIS and Y_AXIS.
+ */
+ private SizeRequirements[] requirements = new SizeRequirements[2];
+
+ /**
+ * The current span along X_AXIS or Y_AXIS.
+ */
+ private int[] span = new int[2];
+
+ /**
+ * Creates a new <code>BoxView</code> for the given
+ * <code>Element</code> and axis. Valid values for the axis are
+ * {@link View#X_AXIS} and {@link View#Y_AXIS}.
+ *
+ * @param element the element that is rendered by this BoxView
+ * @param axis the axis along which the box is laid out
+ */
+ public BoxView(Element element, int axis)
+ {
+ super(element);
+ myAxis = axis;
+ layoutValid[0] = false;
+ layoutValid[1] = false;
+ requirementsValid[X_AXIS] = false;
+ requirementsValid[Y_AXIS] = false;
+ span[0] = 0;
+ span[1] = 0;
+ requirements[0] = new SizeRequirements();
+ requirements[1] = new SizeRequirements();
+
+ // Initialize the cache arrays.
+ spans[0] = new int[0];
+ spans[1] = new int[0];
+ offsets[0] = new int[0];
+ offsets[1] = new int[0];
+ }
+
+ /**
+ * Returns the axis along which this <code>BoxView</code> is laid out.
+ *
+ * @return the axis along which this <code>BoxView</code> is laid out
+ *
+ * @since 1.3
+ */
+ public int getAxis()
+ {
+ return myAxis;
+ }
+
+ /**
+ * Sets the axis along which this <code>BoxView</code> is laid out.
+ *
+ * Valid values for the axis are {@link View#X_AXIS} and
+ * {@link View#Y_AXIS}.
+ *
+ * @param axis the axis along which this <code>BoxView</code> is laid out
+ *
+ * @since 1.3
+ */
+ public void setAxis(int axis)
+ {
+ boolean changed = axis != myAxis;
+ myAxis = axis;
+ if (changed)
+ preferenceChanged(null, true, true);
+ }
+
+ /**
+ * Marks the layout along the specified axis as invalid. This is triggered
+ * automatically when any of the child view changes its preferences
+ * via {@link #preferenceChanged(View, boolean, boolean)}.
+ *
+ * The layout will be updated the next time when
+ * {@link #setSize(float, float)} is called, typically from within the
+ * {@link #paint(Graphics, Shape)} method.
+ *
+ * Valid values for the axis are {@link View#X_AXIS} and
+ * {@link View#Y_AXIS}.
+ *
+ * @param axis an <code>int</code> value
+ *
+ * @since 1.3
+ */
+ public void layoutChanged(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Invalid axis parameter.");
+ layoutValid[axis] = false;
+ }
+
+ /**
+ * Returns <code>true</code> if the layout along the specified
+ * <code>axis</code> is valid, <code>false</code> otherwise.
+ *
+ * Valid values for the axis are {@link View#X_AXIS} and
+ * {@link View#Y_AXIS}.
+ *
+ * @param axis the axis
+ *
+ * @return <code>true</code> if the layout along the specified
+ * <code>axis</code> is valid, <code>false</code> otherwise
+ *
+ * @since 1.4
+ */
+ protected boolean isLayoutValid(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Invalid axis parameter.");
+ return layoutValid[axis];
+ }
+
+ /**
+ * Paints the child <code>View</code> at the specified <code>index</code>.
+ * This method modifies the actual values in <code>alloc</code> so make
+ * sure you have a copy of the original values if you need them.
+ *
+ * @param g the <code>Graphics</code> context to paint to
+ * @param alloc the allocated region for the child to paint into
+ * @param index the index of the child to be painted
+ *
+ * @see #childAllocation(int, Rectangle)
+ */
+ protected void paintChild(Graphics g, Rectangle alloc, int index)
+ {
+ View child = getView(index);
+ child.paint(g, alloc);
+ }
+
+ /**
+ * Replaces child views by some other child views. If there are no views to
+ * remove (<code>length == 0</code>), the result is a simple insert, if
+ * there are no children to add (<code>view == null</code>) the result
+ * is a simple removal.
+ *
+ * In addition this invalidates the layout and resizes the internal cache
+ * for the child allocations. The old children's cached allocations can
+ * still be accessed (although they are not guaranteed to be valid), and
+ * the new children will have an initial offset and span of 0.
+ *
+ * @param offset the start offset from where to remove children
+ * @param length the number of children to remove
+ * @param views the views that replace the removed children
+ */
+ public void replace(int offset, int length, View[] views)
+ {
+ // Actually perform the replace.
+ super.replace(offset, length, views);
+
+ // Resize and copy data for cache arrays.
+ int newItems = views != null ? views.length : 0;
+ int minor = 1 - myAxis;
+ offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems);
+ spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems);
+ layoutValid[myAxis] = false;
+ requirementsValid[myAxis] = false;
+ offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems);
+ spans[minor] = replaceLayoutArray(spans[minor], offset, newItems);
+ layoutValid[minor] = false;
+ requirementsValid[minor] = false;
+ }
+
+ /**
+ * Helper method. This updates the layout cache arrays in response
+ * to a call to {@link #replace(int, int, View[])}.
+ *
+ * @param oldArray the old array
+ *
+ * @return the replaced array
+ */
+ private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems)
+
+ {
+ int num = getViewCount();
+ int[] newArray = new int[num];
+ System.arraycopy(oldArray, 0, newArray, 0, offset);
+ System.arraycopy(oldArray, offset, newArray, offset + newItems,
+ num - newItems - offset);
+ return newArray;
+ }
+
+ /**
+ * A Rectangle instance to be reused in the paint() method below.
+ */
+ private final Rectangle tmpRect = new Rectangle();
+
+ private Rectangle clipRect = new Rectangle();
+
+ /**
+ * Renders the <code>Element</code> that is associated with this
+ * <code>View</code>.
+ *
+ * @param g the <code>Graphics</code> context to render to
+ * @param a the allocated region for the <code>Element</code>
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ // Try to avoid allocation if possible (almost all cases).
+ Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+
+ // This returns a cached instance.
+ alloc = getInsideAllocation(alloc);
+
+ int count = getViewCount();
+ for (int i = 0; i < count; i++)
+ {
+ View child = getView(i);
+ tmpRect.setBounds(alloc);
+ childAllocation(i, tmpRect);
+ if (g.hitClip(tmpRect.x, tmpRect.y, tmpRect.width, tmpRect.height))
+ paintChild(g, tmpRect, i);
+ }
+ }
+
+ /**
+ * Returns the preferred span of the content managed by this
+ * <code>View</code> along the specified <code>axis</code>.
+ *
+ * @param axis the axis
+ *
+ * @return the preferred span of this <code>View</code>.
+ */
+ public float getPreferredSpan(int axis)
+ {
+ updateRequirements(axis);
+ // Add margin.
+ float margin;
+ if (axis == X_AXIS)
+ margin = getLeftInset() + getRightInset();
+ else
+ margin = getTopInset() + getBottomInset();
+ return requirements[axis].preferred + margin;
+ }
+
+ /**
+ * Returns the maximum span of this view along the specified axis.
+ * This returns <code>Integer.MAX_VALUE</code> for the minor axis
+ * and the preferred span for the major axis.
+ *
+ * @param axis the axis
+ *
+ * @return the maximum span of this view along the specified axis
+ */
+ public float getMaximumSpan(int axis)
+ {
+ updateRequirements(axis);
+ // Add margin.
+ float margin;
+ if (axis == X_AXIS)
+ margin = getLeftInset() + getRightInset();
+ else
+ margin = getTopInset() + getBottomInset();
+ return requirements[axis].maximum + margin;
+ }
+
+ /**
+ * Returns the minimum span of this view along the specified axis.
+ * This calculates the minimum span using
+ * {@link #calculateMajorAxisRequirements} or
+ * {@link #calculateMinorAxisRequirements} (depending on the axis) and
+ * returns the resulting minimum span.
+ *
+ * @param axis the axis
+ *
+ * @return the minimum span of this view along the specified axis
+ */
+ public float getMinimumSpan(int axis)
+ {
+ updateRequirements(axis);
+ // Add margin.
+ float margin;
+ if (axis == X_AXIS)
+ margin = getLeftInset() + getRightInset();
+ else
+ margin = getTopInset() + getBottomInset();
+ return requirements[axis].minimum + margin;
+ }
+
+ /**
+ * Calculates size requirements for a baseline layout. This is not
+ * used by the BoxView itself, but by subclasses that wish to perform
+ * a baseline layout, like the FlowView's rows.
+ *
+ * @param axis the axis that is examined
+ * @param sr the <code>SizeRequirements</code> object to hold the result,
+ * if <code>null</code>, a new one is created
+ *
+ * @return the size requirements for this <code>BoxView</code> along
+ * the specified axis
+ */
+ protected SizeRequirements baselineRequirements(int axis,
+ SizeRequirements sr)
+ {
+ // Create new instance if sr == null.
+ if (sr == null)
+ sr = new SizeRequirements();
+ sr.alignment = 0.5F;
+
+ // Calculate overall ascent and descent.
+ int totalAscentMin = 0;
+ int totalAscentPref = 0;
+ int totalAscentMax = 0;
+ int totalDescentMin = 0;
+ int totalDescentPref = 0;
+ int totalDescentMax = 0;
+
+ int count = getViewCount();
+ for (int i = 0; i < count; i++)
+ {
+ View v = getView(i);
+ float align = v.getAlignment(axis);
+ int span = (int) v.getPreferredSpan(axis);
+ int ascent = (int) (align * span);
+ int descent = span - ascent;
+
+ totalAscentPref = Math.max(ascent, totalAscentPref);
+ totalDescentPref = Math.max(descent, totalDescentPref);
+ if (v.getResizeWeight(axis) > 0)
+ {
+ // If the view is resizable, then use the min and max size
+ // of the view.
+ span = (int) v.getMinimumSpan(axis);
+ ascent = (int) (align * span);
+ descent = span - ascent;
+ totalAscentMin = Math.max(ascent, totalAscentMin);
+ totalDescentMin = Math.max(descent, totalDescentMin);
+
+ span = (int) v.getMaximumSpan(axis);
+ ascent = (int) (align * span);
+ descent = span - ascent;
+ totalAscentMax = Math.max(ascent, totalAscentMax);
+ totalDescentMax = Math.max(descent, totalDescentMax);
+ }
+ else
+ {
+ // If the view is not resizable, use the preferred span.
+ totalAscentMin = Math.max(ascent, totalAscentMin);
+ totalDescentMin = Math.max(descent, totalDescentMin);
+ totalAscentMax = Math.max(ascent, totalAscentMax);
+ totalDescentMax = Math.max(descent, totalDescentMax);
+ }
+ }
+
+ // Preferred overall span is the sum of the preferred ascent and descent.
+ // With overflow check.
+ sr.preferred = (int) Math.min((long) totalAscentPref
+ + (long) totalDescentPref,
+ Integer.MAX_VALUE);
+
+ // Align along the baseline.
+ if (sr.preferred > 0)
+ sr.alignment = (float) totalAscentPref / sr.preferred;
+
+ if (sr.alignment == 0)
+ {
+ // Nothing above the baseline, use the descent.
+ sr.minimum = totalDescentMin;
+ sr.maximum = totalDescentMax;
+ }
+ else if (sr.alignment == 1.0F)
+ {
+ // Nothing below the baseline, use the descent.
+ sr.minimum = totalAscentMin;
+ sr.maximum = totalAscentMax;
+ }
+ else
+ {
+ sr.minimum = Math.max((int) (totalAscentMin / sr.alignment),
+ (int) (totalDescentMin / (1.0F - sr.alignment)));
+ sr.maximum = Math.min((int) (totalAscentMax / sr.alignment),
+ (int) (totalDescentMax / (1.0F - sr.alignment)));
+ }
+ return sr;
+ }
+
+ /**
+ * Calculates the baseline layout of the children of this
+ * <code>BoxView</code> along the specified axis.
+ *
+ * This is not used by the BoxView itself, but by subclasses that wish to
+ * perform a baseline layout, like the FlowView's rows.
+ *
+ * @param span the target span
+ * @param axis the axis that is examined
+ * @param offsets an empty array, filled with the offsets of the children
+ * @param spans an empty array, filled with the spans of the children
+ */
+ protected void baselineLayout(int span, int axis, int[] offsets,
+ int[] spans)
+ {
+ int totalAscent = (int) (span * getAlignment(axis));
+ int totalDescent = span - totalAscent;
+
+ int count = getViewCount();
+ for (int i = 0; i < count; i++)
+ {
+ View v = getView(i);
+ float align = v.getAlignment(axis);
+ int viewSpan;
+ if (v.getResizeWeight(axis) > 0)
+ {
+ // If possible, then resize for best fit.
+ int min = (int) v.getMinimumSpan(axis);
+ int max = (int) v.getMaximumSpan(axis);
+ if (align == 0.0F)
+ viewSpan = Math.max(Math.min(max, totalDescent), min);
+ else if (align == 1.0F)
+ viewSpan = Math.max(Math.min(max, totalAscent), min);
+ else
+ {
+ int fit = (int) Math.min(totalAscent / align,
+ totalDescent / (1.0F - align));
+ viewSpan = Math.max(Math.min(max, fit), min);
+ }
+ }
+ else
+ viewSpan = (int) v.getPreferredSpan(axis);
+ offsets[i] = totalAscent - (int) (viewSpan * align);
+ spans[i] = viewSpan;
+ }
+ }
+
+ /**
+ * Calculates the size requirements of this <code>BoxView</code> along
+ * its major axis, that is the axis specified in the constructor.
+ *
+ * @param axis the axis that is examined
+ * @param sr the <code>SizeRequirements</code> object to hold the result,
+ * if <code>null</code>, a new one is created
+ *
+ * @return the size requirements for this <code>BoxView</code> along
+ * the specified axis
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements sr)
+ {
+ SizeRequirements res = sr;
+ if (res == null)
+ res = new SizeRequirements();
+
+ float min = 0;
+ float pref = 0;
+ float max = 0;
+
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View child = getView(i);
+ min += child.getMinimumSpan(axis);
+ pref += child.getPreferredSpan(axis);
+ max += child.getMaximumSpan(axis);
+ }
+
+ res.minimum = (int) min;
+ res.preferred = (int) pref;
+ res.maximum = (int) max;
+ res.alignment = 0.5F;
+
+ return res;
+ }
+
+ /**
+ * Calculates the size requirements of this <code>BoxView</code> along
+ * its minor axis, that is the axis opposite to the axis specified in the
+ * constructor.
+ *
+ * @param axis the axis that is examined
+ * @param sr the <code>SizeRequirements</code> object to hold the result,
+ * if <code>null</code>, a new one is created
+ *
+ * @return the size requirements for this <code>BoxView</code> along
+ * the specified axis
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis,
+ SizeRequirements sr)
+ {
+ SizeRequirements res = sr;
+ if (res == null)
+ res = new SizeRequirements();
+
+ res.minimum = 0;
+ res.preferred = 0;
+ res.maximum = Integer.MAX_VALUE;
+ res.alignment = 0.5F;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View child = getView(i);
+ res.minimum = Math.max((int) child.getMinimumSpan(axis), res.minimum);
+ res.preferred = Math.max((int) child.getPreferredSpan(axis),
+ res.preferred);
+ res.maximum = Math.max((int) child.getMaximumSpan(axis), res.maximum);
+ }
+
+ return res;
+ }
+
+
+ /**
+ * Returns <code>true</code> if the specified point lies before the
+ * given <code>Rectangle</code>, <code>false</code> otherwise.
+ *
+ * &quot;Before&quot; is typically defined as being to the left or above.
+ *
+ * @param x the X coordinate of the point
+ * @param y the Y coordinate of the point
+ * @param r the rectangle to test the point against
+ *
+ * @return <code>true</code> if the specified point lies before the
+ * given <code>Rectangle</code>, <code>false</code> otherwise
+ */
+ protected boolean isBefore(int x, int y, Rectangle r)
+ {
+ boolean result = false;
+
+ if (myAxis == X_AXIS)
+ result = x < r.x;
+ else
+ result = y < r.y;
+
+ return result;
+ }
+
+ /**
+ * Returns <code>true</code> if the specified point lies after the
+ * given <code>Rectangle</code>, <code>false</code> otherwise.
+ *
+ * &quot;After&quot; is typically defined as being to the right or below.
+ *
+ * @param x the X coordinate of the point
+ * @param y the Y coordinate of the point
+ * @param r the rectangle to test the point against
+ *
+ * @return <code>true</code> if the specified point lies after the
+ * given <code>Rectangle</code>, <code>false</code> otherwise
+ */
+ protected boolean isAfter(int x, int y, Rectangle r)
+ {
+ boolean result = false;
+
+ if (myAxis == X_AXIS)
+ result = x > r.x + r.width;
+ else
+ result = y > r.y + r.height;
+
+ return result;
+ }
+
+ /**
+ * Returns the child <code>View</code> at the specified location.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param r the inner allocation of this <code>BoxView</code> on entry,
+ * the allocation of the found child on exit
+ *
+ * @return the child <code>View</code> at the specified location
+ */
+ protected View getViewAtPoint(int x, int y, Rectangle r)
+ {
+ View result = null;
+ int count = getViewCount();
+ if (myAxis == X_AXIS)
+ {
+ // Border case. Requested point is left from the box.
+ if (x < r.x + offsets[X_AXIS][0])
+ {
+ childAllocation(0, r);
+ result = getView(0);
+ }
+ else
+ {
+ // Search views inside box.
+ for (int i = 0; i < count && result == null; i++)
+ {
+ if (x < r.x + offsets[X_AXIS][i])
+ {
+ childAllocation(i - 1, r);
+ result = getView(i - 1);
+ }
+ }
+ }
+ }
+ else // Same algorithm for Y_AXIS.
+ {
+ // Border case. Requested point is above the box.
+ if (y < r.y + offsets[Y_AXIS][0])
+ {
+ childAllocation(0, r);
+ result = getView(0);
+ }
+ else
+ {
+ // Search views inside box.
+ for (int i = 0; i < count && result == null; i++)
+ {
+ if (y < r.y + offsets[Y_AXIS][i])
+ {
+ childAllocation(i - 1, r);
+ result = getView(i - 1);
+ }
+ }
+ }
+ }
+ // Not found, other border case: point is right from or below the box.
+ if (result == null)
+ {
+ childAllocation(count - 1, r);
+ result = getView(count - 1);
+ }
+ return result;
+ }
+
+ /**
+ * Computes the allocation for a child <code>View</code>. The parameter
+ * <code>a</code> stores the allocation of this <code>CompositeView</code>
+ * and is then adjusted to hold the allocation of the child view.
+ *
+ * @param index
+ * the index of the child <code>View</code>
+ * @param a
+ * the allocation of this <code>CompositeView</code> before the
+ * call, the allocation of the child on exit
+ */
+ protected void childAllocation(int index, Rectangle a)
+ {
+ a.x += offsets[X_AXIS][index];
+ a.y += offsets[Y_AXIS][index];
+ a.width = spans[X_AXIS][index];
+ a.height = spans[Y_AXIS][index];
+ }
+
+ /**
+ * Lays out the children of this <code>BoxView</code> with the specified
+ * bounds.
+ *
+ * @param width the width of the allocated region for the children (that
+ * is the inner allocation of this <code>BoxView</code>
+ * @param height the height of the allocated region for the children (that
+ * is the inner allocation of this <code>BoxView</code>
+ */
+ protected void layout(int width, int height)
+ {
+ layoutAxis(X_AXIS, width);
+ layoutAxis(Y_AXIS, height);
+ }
+
+ private void layoutAxis(int axis, int s)
+ {
+ if (span[axis] != s)
+ layoutValid[axis] = false;
+ if (! layoutValid[axis])
+ {
+ span[axis] = s;
+ updateRequirements(axis);
+ if (axis == myAxis)
+ layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]);
+ else
+ layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]);
+ layoutValid[axis] = true;
+
+ // Push out child layout.
+ int viewCount = getViewCount();
+ for (int i = 0; i < viewCount; i++)
+ {
+ View v = getView(i);
+ v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
+ }
+ }
+ }
+
+ /**
+ * Performs the layout along the major axis of a <code>BoxView</code>.
+ *
+ * @param targetSpan the (inner) span of the <code>BoxView</code> in which
+ * to layout the children
+ * @param axis the axis along which the layout is performed
+ * @param offsets the array that holds the offsets of the children on exit
+ * @param spans the array that holds the spans of the children on exit
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ // Set the spans to the preferred sizes. Determine the space
+ // that we have to adjust the sizes afterwards.
+ long sumPref = 0;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View child = getView(i);
+ spans[i] = (int) child.getPreferredSpan(axis);
+ sumPref += spans[i];
+ }
+
+ // Try to adjust the spans so that we fill the targetSpan.
+ long diff = targetSpan - sumPref;
+ float factor = 0.0F;
+ int[] diffs = null;
+ if (diff != 0)
+ {
+ long total = 0;
+ diffs = new int[n];
+ for (int i = 0; i < n; i++)
+ {
+ View child = getView(i);
+ int span;
+ if (diff < 0)
+ {
+ span = (int) child.getMinimumSpan(axis);
+ diffs[i] = spans[i] - span;
+ }
+ else
+ {
+ span = (int) child.getMaximumSpan(axis);
+ diffs[i] = span - spans[i];
+ }
+ total += span;
+ }
+
+ float maxAdjust = Math.abs(total - sumPref);
+ factor = diff / maxAdjust;
+ factor = Math.min(factor, 1.0F);
+ factor = Math.max(factor, -1.0F);
+ }
+
+ // Actually perform adjustments.
+ int totalOffs = 0;
+ for (int i = 0; i < n; i++)
+ {
+ offsets[i] = totalOffs;
+ if (diff != 0)
+ {
+ float adjust = factor * diffs[i];
+ spans[i] += Math.round(adjust);
+ }
+ // Avoid overflow here.
+ totalOffs = (int) Math.min((long) totalOffs + (long) spans[i],
+ Integer.MAX_VALUE);
+ }
+ }
+
+ /**
+ * Performs the layout along the minor axis of a <code>BoxView</code>.
+ *
+ * @param targetSpan the (inner) span of the <code>BoxView</code> in which
+ * to layout the children
+ * @param axis the axis along which the layout is performed
+ * @param offsets the array that holds the offsets of the children on exit
+ * @param spans the array that holds the spans of the children on exit
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ int count = getViewCount();
+ for (int i = 0; i < count; i++)
+ {
+ View child = getView(i);
+ int max = (int) child.getMaximumSpan(axis);
+ if (max < targetSpan)
+ {
+ // Align child when it can't be made as wide as the target span.
+ float align = child.getAlignment(axis);
+ offsets[i] = (int) ((targetSpan - max) * align);
+ spans[i] = max;
+ }
+ else
+ {
+ // Expand child to target width if possible.
+ int min = (int) child.getMinimumSpan(axis);
+ offsets[i] = 0;
+ spans[i] = Math.max(min, targetSpan);
+ }
+ }
+ }
+
+ /**
+ * Returns <code>true</code> if the cached allocations for the children
+ * are still valid, <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the cached allocations for the children
+ * are still valid, <code>false</code> otherwise
+ */
+ protected boolean isAllocationValid()
+ {
+ return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS);
+ }
+
+ /**
+ * Return the current width of the box. This is the last allocated width.
+ *
+ * @return the current width of the box
+ */
+ public int getWidth()
+ {
+ // The RI returns the following here, however, I'd think that is a bug.
+ // return span[X_AXIS] + getLeftInset() - getRightInset();
+ return span[X_AXIS] + getLeftInset() + getRightInset();
+ }
+
+ /**
+ * Return the current height of the box. This is the last allocated height.
+ *
+ * @return the current height of the box
+ */
+ public int getHeight()
+ {
+ // The RI returns the following here, however, I'd think that is a bug.
+ // return span[Y_AXIS] + getTopInset() - getBottomInset();
+ return span[Y_AXIS] + getTopInset() + getBottomInset();
+ }
+
+ /**
+ * Sets the size of the view. If the actual size has changed, the layout
+ * is updated accordingly.
+ *
+ * @param width the new width
+ * @param height the new height
+ */
+ public void setSize(float width, float height)
+ {
+ layout((int) (width - getLeftInset() - getRightInset()),
+ (int) (height - getTopInset() - getBottomInset()));
+ }
+
+ /**
+ * Returns the span for the child view with the given index for the specified
+ * axis.
+ *
+ * @param axis the axis to examine, either <code>X_AXIS</code> or
+ * <code>Y_AXIS</code>
+ * @param childIndex the index of the child for for which to return the span
+ *
+ * @return the span for the child view with the given index for the specified
+ * axis
+ */
+ protected int getSpan(int axis, int childIndex)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis argument");
+ return spans[axis][childIndex];
+ }
+
+ /**
+ * Returns the offset for the child view with the given index for the
+ * specified axis.
+ *
+ * @param axis the axis to examine, either <code>X_AXIS</code> or
+ * <code>Y_AXIS</code>
+ * @param childIndex the index of the child for for which to return the span
+ *
+ * @return the offset for the child view with the given index for the
+ * specified axis
+ */
+ protected int getOffset(int axis, int childIndex)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis argument");
+ return offsets[axis][childIndex];
+ }
+
+ /**
+ * Returns the alignment for this box view for the specified axis. The
+ * axis that is tiled (the major axis) will be requested to be aligned
+ * centered (0.5F). The minor axis alignment depends on the child view's
+ * total alignment.
+ *
+ * @param axis the axis which is examined
+ *
+ * @return the alignment for this box view for the specified axis
+ */
+ public float getAlignment(int axis)
+ {
+ updateRequirements(axis);
+ return requirements[axis].alignment;
+ }
+
+ /**
+ * Called by a child View when its preferred span has changed.
+ *
+ * @param width indicates that the preferred width of the child changed.
+ * @param height indicates that the preferred height of the child changed.
+ * @param child the child View.
+ */
+ public void preferenceChanged(View child, boolean width, boolean height)
+ {
+ if (width)
+ {
+ layoutValid[X_AXIS] = false;
+ requirementsValid[X_AXIS] = false;
+ }
+ if (height)
+ {
+ layoutValid[Y_AXIS] = false;
+ requirementsValid[Y_AXIS] = false;
+ }
+ super.preferenceChanged(child, width, height);
+ }
+
+ /**
+ * Maps the document model position <code>pos</code> to a Shape
+ * in the view coordinate space. This method overrides CompositeView's
+ * method to make sure the children are allocated properly before
+ * calling the super's behaviour.
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias bias)
+ throws BadLocationException
+ {
+ // Make sure everything is allocated properly and then call super
+ if (! isAllocationValid())
+ {
+ Rectangle bounds = a.getBounds();
+ setSize(bounds.width, bounds.height);
+ }
+ return super.modelToView(pos, a, bias);
+ }
+
+ /**
+ * Returns the resize weight of this view. A value of <code>0</code> or less
+ * means this view is not resizeable. Positive values make the view
+ * resizeable. This implementation returns <code>0</code> for the major
+ * axis and <code>1</code> for the minor axis of this box view.
+ *
+ * @param axis the axis
+ *
+ * @return the resizability of this view along the specified axis
+ *
+ * @throws IllegalArgumentException if <code>axis</code> is invalid
+ */
+ public int getResizeWeight(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis argument");
+ updateRequirements(axis);
+ int weight = 0;
+ if ((requirements[axis].preferred != requirements[axis].minimum)
+ || (requirements[axis].preferred != requirements[axis].maximum))
+ weight = 1;
+ return weight;
+ }
+
+ /**
+ * Returns the child allocation for the child view with the specified
+ * <code>index</code>. If the layout is invalid, this returns
+ * <code>null</code>.
+ *
+ * @param index the child view index
+ * @param a the allocation to this view
+ *
+ * @return the child allocation for the child view with the specified
+ * <code>index</code> or <code>null</code> if the layout is invalid
+ * or <code>a</code> is null
+ */
+ public Shape getChildAllocation(int index, Shape a)
+ {
+ Shape ret = null;
+ if (isAllocationValid() && a != null)
+ ret = super.getChildAllocation(index, a);
+ return ret;
+ }
+
+ protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e,
+ Shape a, ViewFactory vf)
+ {
+ boolean wasValid = isLayoutValid(myAxis);
+ super.forwardUpdate(ec, e, a, vf);
+ // Trigger repaint when one of the children changed the major axis.
+ if (wasValid && ! isLayoutValid(myAxis))
+ {
+ Container c = getContainer();
+ if (a != null && c != null)
+ {
+ int pos = e.getOffset();
+ int index = getViewIndexAtPosition(pos);
+ Rectangle r = getInsideAllocation(a);
+ if (myAxis == X_AXIS)
+ {
+ r.x += offsets[myAxis][index];
+ r.width -= offsets[myAxis][index];
+ }
+ else
+ {
+ r.y += offsets[myAxis][index];
+ r.height -= offsets[myAxis][index];
+ }
+ c.repaint(r.x, r.y, r.width, r.height);
+ }
+ }
+ }
+
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
+ {
+ if (! isAllocationValid())
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ setSize(r.width, r.height);
+ }
+ return super.viewToModel(x, y, a, bias);
+ }
+
+ protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias)
+ {
+ // FIXME: What to do here?
+ return super.flipEastAndWestAtEnds(position, bias);
+ }
+
+ /**
+ * Updates the view's cached requirements along the specified axis if
+ * necessary. The requirements are only updated if the layout for the
+ * specified axis is marked as invalid.
+ *
+ * @param axis the axis
+ */
+ private void updateRequirements(int axis)
+ {
+ if (axis != Y_AXIS && axis != X_AXIS)
+ throw new IllegalArgumentException("Illegal axis: " + axis);
+ if (! requirementsValid[axis])
+ {
+ if (axis == myAxis)
+ requirements[axis] = calculateMajorAxisRequirements(axis,
+ requirements[axis]);
+ else
+ requirements[axis] = calculateMinorAxisRequirements(axis,
+ requirements[axis]);
+ requirementsValid[axis] = true;
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/Caret.java b/libjava/classpath/javax/swing/text/Caret.java
new file mode 100644
index 000000000..06972f904
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Caret.java
@@ -0,0 +1,207 @@
+/* Caret.java --
+ Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Graphics;
+import java.awt.Point;
+
+import javax.swing.event.ChangeListener;
+
+/**
+ * Defines the method to be implemented by a caret that can be used in Swing
+ * text components.
+ *
+ * @author original author unknown
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public interface Caret
+{
+ /**
+ * Registers a {@link ChangeListener} that is notified whenever that state
+ * of this <code>Caret</code> changes.
+ *
+ * @param l the listener to register to this caret
+ */
+ void addChangeListener(ChangeListener l);
+
+ /**
+ * Removes a {@link ChangeListener} from the list of registered listeners.
+ *
+ * @param l the listener to remove
+ */
+ void removeChangeListener(ChangeListener l);
+
+ /**
+ * Installs this <code>Caret</code> on the specified text component. This
+ * usually involves setting up listeners.
+ *
+ * This method is called by {@link JTextComponent#setCaret(Caret)} after
+ * this caret has been set on the text component.
+ *
+ * @param c the text component to install this caret to
+ */
+ void install(JTextComponent c);
+
+ /**
+ * Deinstalls this <code>Caret</code> from the specified text component.
+ * This usually involves removing listeners from the text component.
+ *
+ * This method is called by {@link JTextComponent#setCaret(Caret)} before
+ * this caret is removed from the text component.
+ *
+ * @param c the text component to deinstall this caret from
+ */
+ void deinstall(JTextComponent c);
+
+ /**
+ * Returns the blink rate of this <code>Caret</code> in milliseconds.
+ * A value of <code>0</code> means that the caret does not blink.
+ *
+ * @return the blink rate of this <code>Caret</code> or <code>0</code> if
+ * this caret does not blink
+ */
+ int getBlinkRate();
+
+ /**
+ * Sets the blink rate of this <code>Caret</code> in milliseconds.
+ * A value of <code>0</code> means that the caret does not blink.
+ *
+ * @param rate the new blink rate to set
+ */
+ void setBlinkRate(int rate);
+
+ /**
+ * Returns the current position of this <code>Caret</code> within the
+ * <code>Document</code>.
+ *
+ * @return the current position of this <code>Caret</code> within the
+ * <code>Document</code>
+ */
+ int getDot();
+
+ /**
+ * Sets the current position of this <code>Caret</code> within the
+ * <code>Document</code>. This also sets the <code>mark</code> to the
+ * new location.
+ *
+ * @param dot the new position to be set
+ *
+ * @see #moveDot(int)
+ */
+ void setDot(int dot);
+
+ /**
+ * Moves the <code>dot</code> location without touching the
+ * <code>mark</code>. This is used when making a selection.
+ *
+ * @param dot the location where to move the dot
+ *
+ * @see #setDot(int)
+ */
+ void moveDot(int dot);
+
+ /**
+ * Returns the current position of the <code>mark</code>. The
+ * <code>mark</code> marks the location in the <code>Document</code> that
+ * is the end of a selection. If there is no selection, the <code>mark</code>
+ * is the same as the <code>dot</code>.
+ *
+ * @return the current position of the mark
+ */
+ int getMark();
+
+ /**
+ * Returns the current visual position of this <code>Caret</code>.
+ *
+ * @return the current visual position of this <code>Caret</code>
+ *
+ * @see #setMagicCaretPosition
+ */
+ Point getMagicCaretPosition();
+
+ /**
+ * Sets the current visual position of this <code>Caret</code>.
+ *
+ * @param p the Point to use for the saved location. May be <code>null</code>
+ * to indicate that there is no visual location
+ */
+ void setMagicCaretPosition(Point p);
+
+ /**
+ * Returns <code>true</code> if the selection is currently visible,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the selection is currently visible,
+ * <code>false</code> otherwise
+ */
+ boolean isSelectionVisible();
+
+ /**
+ * Sets the visiblity state of the selection.
+ *
+ * @param v <code>true</code> if the selection should be visible,
+ * <code>false</code> otherwise
+ */
+ void setSelectionVisible(boolean v);
+
+ /**
+ * Returns <code>true</code> if this <code>Caret</code> is currently visible,
+ * and <code>false</code> if it is not.
+ *
+ * @return <code>true</code> if this <code>Caret</code> is currently visible,
+ * and <code>false</code> if it is not
+ */
+ boolean isVisible();
+
+ /**
+ * Sets the visibility state of the caret. <code>true</code> shows the
+ * <code>Caret</code>, <code>false</code> hides it.
+ *
+ * @param v the visibility to set
+ */
+ void setVisible(boolean v);
+
+ /**
+ * Paints this <code>Caret</code> to the specified <code>Graphics</code>
+ * context.
+ *
+ * @param g the graphics context to render to
+ */
+ void paint(Graphics g);
+}
diff --git a/libjava/classpath/javax/swing/text/ChangedCharSetException.java b/libjava/classpath/javax/swing/text/ChangedCharSetException.java
new file mode 100644
index 000000000..7fba29a30
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/ChangedCharSetException.java
@@ -0,0 +1,100 @@
+/* ChangedCharSetException.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.io.IOException;
+import java.io.Serializable;
+
+/**
+ * The exception is thrown when the document charset is changed.
+ *
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ */
+public class ChangedCharSetException
+ extends IOException
+ implements Serializable
+{
+ /**
+ * Use serialVersionUID for interoperability.
+ * This value corresponds the version 1.4.
+ */
+ private static final long serialVersionUID = 9119851554465432389L;
+
+ /**
+ * The char set specification.
+ */
+ private final String m_charSetSpec;
+
+ /**
+ * The char set key.
+ */
+ private final boolean m_charSetKey;
+
+ /**
+ * Constructs a new char set exception with two additional parameters,
+ * defining the circumstances under that the exception was raised.
+ */
+ public ChangedCharSetException(String charSetSpec, boolean charSetKey)
+ {
+ m_charSetSpec = charSetSpec;
+ m_charSetKey = charSetKey;
+ }
+
+ /**
+ * Get the value of the first parameter, previously passed to the
+ * constructor.
+ *
+ * @return the value of the first parameter
+ */
+ public String getCharSetSpec()
+ {
+ return m_charSetSpec;
+ }
+
+ /**
+ * Get the value of the second parameter, previously passed to the
+ * constructor.
+ *
+ * @return the value of the second parameter
+ */
+ public boolean keyEqualsCharSet()
+ {
+ return m_charSetKey;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/ComponentView.java b/libjava/classpath/javax/swing/text/ComponentView.java
new file mode 100644
index 000000000..3680b4245
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/ComponentView.java
@@ -0,0 +1,494 @@
+/* ComponentView.java --
+ Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.SwingUtilities;
+
+/**
+ * A {@link View} implementation that is able to render arbitrary
+ * {@link Component}s. This uses the attribute
+ * {@link StyleConstants#ComponentAttribute} to determine the
+ * <code>Component</code> that should be rendered. This <code>Component</code>
+ * becomes a direct child of the <code>JTextComponent</code> that contains
+ * this <code>ComponentView</code>, so this view must not be shared between
+ * multiple <code>JTextComponent</code>s.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ * @author original author unknown
+ */
+public class ComponentView extends View
+{
+
+ /**
+ * A special container that sits between the component and the hosting
+ * container. This is used to propagate invalidate requests and cache
+ * the component's layout sizes.
+ */
+ private class Interceptor
+ extends Container
+ {
+ Dimension min;
+ Dimension pref;
+ Dimension max;
+ float alignX;
+ float alignY;
+
+ /**
+ * Creates a new instance that hosts the specified component.
+ */
+ Interceptor(Component c)
+ {
+ setLayout(null);
+ add(c);
+ cacheComponentSizes();
+ }
+
+ /**
+ * Intercepts the normal invalidate call and propagates the invalidate
+ * request up using the View's preferenceChanged().
+ */
+ public void invalidate()
+ {
+ super.invalidate();
+ if (getParent() != null)
+ preferenceChanged(null, true, true);
+ }
+
+ /**
+ * This is overridden to simply cache the layout sizes.
+ */
+ public void doLayout()
+ {
+ cacheComponentSizes();
+ }
+
+ /**
+ * Overridden to also reshape the component itself.
+ */
+ public void reshape(int x, int y, int w, int h)
+ {
+ super.reshape(x, y, w, h);
+ if (getComponentCount() > 0)
+ getComponent(0).setSize(w, h);
+ cacheComponentSizes();
+ }
+
+ /**
+ * Overridden to also show the component.
+ */
+ public void show()
+ {
+ super.show();
+ if (getComponentCount() > 0)
+ getComponent(0).setVisible(true);
+ }
+
+ /**
+ * Overridden to also hide the component.
+ */
+ public void hide()
+ {
+ super.hide();
+ if (getComponentCount() > 0)
+ getComponent(0).setVisible(false);
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public Dimension getMinimumSize()
+ {
+ maybeValidate();
+ return min;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public Dimension getPreferredSize()
+ {
+ maybeValidate();
+ return pref;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public Dimension getMaximumSize()
+ {
+ maybeValidate();
+ return max;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public float getAlignmentX()
+ {
+ maybeValidate();
+ return alignX;
+ }
+
+ /**
+ * Overridden to return the cached value.
+ */
+ public float getAlignmentY()
+ {
+ maybeValidate();
+ return alignY;
+ }
+
+ /**
+ * Validates the container only when necessary.
+ */
+ private void maybeValidate()
+ {
+ if (! isValid())
+ validate();
+ }
+
+ /**
+ * Fetches the component layout sizes into the cache.
+ */
+ private void cacheComponentSizes()
+ {
+ if (getComponentCount() > 0)
+ {
+ Component c = getComponent(0);
+ min = c.getMinimumSize();
+ pref = c.getPreferredSize();
+ max = c.getMaximumSize();
+ alignX = c.getAlignmentX();
+ alignY = c.getAlignmentY();
+ }
+ }
+ }
+
+ /**
+ * The component that is displayed by this view.
+ */
+ private Component comp;
+
+ /**
+ * The intercepting container.
+ */
+ private Interceptor interceptor;
+
+ /**
+ * Creates a new instance of <code>ComponentView</code> for the specified
+ * <code>Element</code>.
+ *
+ * @param elem the element that this <code>View</code> is rendering
+ */
+ public ComponentView(Element elem)
+ {
+ super(elem);
+ }
+
+ /**
+ * Creates the <code>Component</code> that this <code>View</code> is
+ * rendering. The <code>Component</code> is determined using
+ * the {@link StyleConstants#ComponentAttribute} of the associated
+ * <code>Element</code>.
+ *
+ * @return the component that is rendered
+ */
+ protected Component createComponent()
+ {
+ return StyleConstants.getComponent(getElement().getAttributes());
+ }
+
+ /**
+ * Returns the alignment of this <code>View</code> along the specified axis.
+ *
+ * @param axis either {@link View#X_AXIS} or {@link View#Y_AXIS}
+ *
+ * @return the alignment of this <code>View</code> along the specified axis
+ */
+ public float getAlignment(int axis)
+ {
+ float align = 0.0F;
+ // I'd rather throw an IllegalArgumentException for illegal axis,
+ // but the Harmony testsuite indicates fallback to super behaviour.
+ if (interceptor != null && (axis == X_AXIS || axis == Y_AXIS))
+ {
+ if (axis == X_AXIS)
+ align = interceptor.getAlignmentX();
+ else if (axis == Y_AXIS)
+ align = interceptor.getAlignmentY();
+ else
+ assert false : "Must not reach here";
+ }
+ else
+ align = super.getAlignment(axis);
+ return align;
+ }
+
+ /**
+ * Returns the <code>Component</code> that is rendered by this
+ * <code>ComponentView</code>.
+ *
+ * @return the <code>Component</code> that is rendered by this
+ * <code>ComponentView</code>
+ */
+ public final Component getComponent()
+ {
+ return comp;
+ }
+
+ /**
+ * Returns the maximum span of this <code>View</code> along the specified
+ * axis.
+ *
+ * This will return {@link Component#getMaximumSize()} for the specified
+ * axis.
+ *
+ * @return the maximum span of this <code>View</code> along the specified
+ * axis
+ */
+ public float getMaximumSpan(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis");
+ float span = 0;
+ if (interceptor != null)
+ {
+ if (axis == X_AXIS)
+ span = interceptor.getMaximumSize().width;
+ else if (axis == Y_AXIS)
+ span = interceptor.getMaximumSize().height;
+ else
+ assert false : "Must not reach here";
+ }
+ return span;
+ }
+
+ public float getMinimumSpan(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis");
+ float span = 0;
+ if (interceptor != null)
+ {
+ if (axis == X_AXIS)
+ span = interceptor.getMinimumSize().width;
+ else if (axis == Y_AXIS)
+ span = interceptor.getMinimumSize().height;
+ else
+ assert false : "Must not reach here";
+ }
+ return span;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis");
+ float span = 0;
+ if (interceptor != null)
+ {
+ if (axis == X_AXIS)
+ span = interceptor.getPreferredSize().width;
+ else if (axis == Y_AXIS)
+ span = interceptor.getPreferredSize().height;
+ else
+ assert false : "Must not reach here";
+ }
+ return span;
+ }
+
+ public Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException
+ {
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ if (pos >= p0 && pos <= p1)
+ {
+ Rectangle viewRect = a.getBounds();
+ if (pos == p1)
+ viewRect.x += viewRect.width;
+ viewRect.width = 0;
+ return viewRect;
+ }
+ else
+ throw new BadLocationException("Illegal position", pos);
+ }
+
+ /**
+ * The real painting behavour is performed by normal component painting,
+ * triggered by the text component that hosts this view. This method does
+ * not paint by itself. However, it sets the size of the component according
+ * to the allocation that is passed here.
+ *
+ * @param g the graphics context
+ * @param a the allocation of the child
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ if (interceptor != null)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ interceptor.setBounds(r.x, r.y, r.width, r.height);
+ }
+ }
+
+ /**
+ * This sets up the component when the view is added to its parent, or
+ * cleans up the view when it is removed from its parent.
+ *
+ * When this view is added to a parent view, the component of this view
+ * is added to the container that hosts this view. When <code>p</code> is
+ * <code>null</code>, then the view is removed from it's parent and we have
+ * to also remove the component from it's parent container.
+ *
+ * @param p the parent view or <code>null</code> if this view is removed
+ * from it's parent
+ */
+ public void setParent(final View p)
+ {
+ super.setParent(p);
+ if (SwingUtilities.isEventDispatchThread())
+ setParentImpl();
+ else
+ SwingUtilities.invokeLater
+ (new Runnable()
+ {
+ public void run()
+ {
+ Document doc = getDocument();
+ try
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ setParentImpl();
+ Container host = getContainer();
+ if (host != null)
+ {
+ preferenceChanged(null, true, true);
+ host.repaint();
+ }
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ }
+ });
+ }
+
+ /**
+ * The implementation of {@link #setParent}. This is package private to
+ * avoid a synthetic accessor method.
+ */
+ void setParentImpl()
+ {
+ View p = getParent();
+ if (p != null)
+ {
+ Container c = getContainer();
+ if (c != null)
+ {
+ if (interceptor == null)
+ {
+ // Create component and put it inside the interceptor.
+ Component created = createComponent();
+ if (created != null)
+ {
+ comp = created;
+ interceptor = new Interceptor(comp);
+ }
+ }
+ if (interceptor != null)
+ {
+ // Add the interceptor to the hosting container.
+ if (interceptor.getParent() == null)
+ c.add(interceptor, this);
+ }
+ }
+ }
+ else
+ {
+ if (interceptor != null)
+ {
+ Container parent = interceptor.getParent();
+ if (parent != null)
+ parent.remove(interceptor);
+ }
+ }
+ }
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model.
+ *
+ * @param x the x coordinate in the view space
+ * @param y the y coordinate in the view space
+ * @param a the allocation of this <code>View</code>
+ * @param b the bias to use
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code>
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
+ {
+ int pos;
+ // I'd rather do the following. The harmony testsuite indicates
+ // that a simple cast is performed.
+ //Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ Rectangle r = (Rectangle) a;
+ if (x < r.x + r.width / 2)
+ {
+ b[0] = Position.Bias.Forward;
+ pos = getStartOffset();
+ }
+ else
+ {
+ b[0] = Position.Bias.Backward;
+ pos = getEndOffset();
+ }
+ return pos;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/CompositeView.java b/libjava/classpath/javax/swing/text/CompositeView.java
new file mode 100644
index 000000000..ae3c79d2f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/CompositeView.java
@@ -0,0 +1,792 @@
+/* CompositeView.java -- An abstract view that manages child views
+ Copyright (C) 2005, 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.SwingConstants;
+
+/**
+ * An abstract base implementation of {@link View} that manages child
+ * <code>View</code>s.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public abstract class CompositeView
+ extends View
+{
+
+ /**
+ * The child views of this <code>CompositeView</code>.
+ */
+ private View[] children;
+
+ /**
+ * The number of child views.
+ */
+ private int numChildren;
+
+ /**
+ * The allocation of this <code>View</code> minus its insets. This is
+ * initialized in {@link #getInsideAllocation} and reused and modified in
+ * {@link #childAllocation(int, Rectangle)}.
+ */
+ private final Rectangle insideAllocation = new Rectangle();
+
+ /**
+ * The insets of this <code>CompositeView</code>. This is initialized
+ * in {@link #setInsets}.
+ */
+ private short top;
+ private short bottom;
+ private short left;
+ private short right;
+
+ /**
+ * Creates a new <code>CompositeView</code> for the given
+ * <code>Element</code>.
+ *
+ * @param element the element that is rendered by this CompositeView
+ */
+ public CompositeView(Element element)
+ {
+ super(element);
+ children = new View[0];
+ top = 0;
+ bottom = 0;
+ left = 0;
+ right = 0;
+ }
+
+ /**
+ * Loads the child views of this <code>CompositeView</code>. This method
+ * is called from {@link #setParent} to initialize the child views of
+ * this composite view.
+ *
+ * @param f the view factory to use for creating new child views
+ *
+ * @see #setParent
+ */
+ protected void loadChildren(ViewFactory f)
+ {
+ if (f != null)
+ {
+ Element el = getElement();
+ int count = el.getElementCount();
+ View[] newChildren = new View[count];
+ for (int i = 0; i < count; ++i)
+ {
+ Element child = el.getElement(i);
+ View view = f.create(child);
+ newChildren[i] = view;
+ }
+ // I'd have called replace(0, getViewCount(), newChildren) here
+ // in order to replace all existing views. However according to
+ // Harmony's tests this is not what the RI does.
+ replace(0, 0, newChildren);
+ }
+ }
+
+ /**
+ * Sets the parent of this <code>View</code>.
+ * In addition to setting the parent, this calls {@link #loadChildren}, if
+ * this <code>View</code> does not already have its children initialized.
+ *
+ * @param parent the parent to set
+ */
+ public void setParent(View parent)
+ {
+ super.setParent(parent);
+ if (parent != null && numChildren == 0)
+ loadChildren(getViewFactory());
+ }
+
+ /**
+ * Returns the number of child views.
+ *
+ * @return the number of child views
+ */
+ public int getViewCount()
+ {
+ return numChildren;
+ }
+
+ /**
+ * Returns the child view at index <code>n</code>.
+ *
+ * @param n the index of the requested child view
+ *
+ * @return the child view at index <code>n</code>
+ */
+ public View getView(int n)
+ {
+ return children[n];
+ }
+
+ /**
+ * Replaces child views by some other child views. If there are no views to
+ * remove (<code>length == 0</code>), the result is a simple insert, if
+ * there are no children to add (<code>view == null</code>) the result
+ * is a simple removal.
+ *
+ * @param offset the start offset from where to remove children
+ * @param length the number of children to remove
+ * @param views the views that replace the removed children
+ */
+ public void replace(int offset, int length, View[] views)
+ {
+ // Make sure we have an array. The Harmony testsuite indicates that we
+ // have to do something like this.
+ if (views == null)
+ views = new View[0];
+
+ // First we set the parent of the removed children to null.
+ int endOffset = offset + length;
+ for (int i = offset; i < endOffset; ++i)
+ {
+ if (children[i].getParent() == this)
+ children[i].setParent(null);
+ children[i] = null;
+ }
+
+ // Update the children array.
+ int delta = views.length - length;
+ int src = offset + length;
+ int numMove = numChildren - src;
+ int dst = src + delta;
+ if (numChildren + delta > children.length)
+ {
+ // Grow array.
+ int newLength = Math.max(2 * children.length, numChildren + delta);
+ View[] newChildren = new View[newLength];
+ System.arraycopy(children, 0, newChildren, 0, offset);
+ System.arraycopy(views, 0, newChildren, offset, views.length);
+ System.arraycopy(children, src, newChildren, dst, numMove);
+ children = newChildren;
+ }
+ else
+ {
+ // Patch existing array.
+ System.arraycopy(children, src, children, dst, numMove);
+ System.arraycopy(views, 0, children, offset, views.length);
+ }
+ numChildren += delta;
+
+ // Finally we set the parent of the added children to this.
+ for (int i = 0; i < views.length; ++i)
+ views[i].setParent(this);
+ }
+
+ /**
+ * Returns the allocation for the specified child <code>View</code>.
+ *
+ * @param index the index of the child view
+ * @param a the allocation for this view
+ *
+ * @return the allocation for the specified child <code>View</code>
+ */
+ public Shape getChildAllocation(int index, Shape a)
+ {
+ Rectangle r = getInsideAllocation(a);
+ childAllocation(index, r);
+ return r;
+ }
+
+ /**
+ * Maps a position in the document into the coordinate space of the View.
+ * The output rectangle usually reflects the font height but has a width
+ * of zero.
+ *
+ * @param pos the position of the character in the model
+ * @param a the area that is occupied by the view
+ * @param bias either {@link Position.Bias#Forward} or
+ * {@link Position.Bias#Backward} depending on the preferred
+ * direction bias. If <code>null</code> this defaults to
+ * <code>Position.Bias.Forward</code>
+ *
+ * @return a rectangle that gives the location of the document position
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>pos</code> is invalid
+ * @throws IllegalArgumentException if b is not one of the above listed
+ * valid values
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias bias)
+ throws BadLocationException
+ {
+ boolean backward = bias == Position.Bias.Backward;
+ int testpos = backward ? Math.max(0, pos - 1) : pos;
+
+ Shape ret = null;
+ if (! backward || testpos >= getStartOffset())
+ {
+ int childIndex = getViewIndexAtPosition(testpos);
+ if (childIndex != -1 && childIndex < getViewCount())
+ {
+ View child = getView(childIndex);
+ if (child != null && testpos >= child.getStartOffset()
+ && testpos < child.getEndOffset())
+ {
+ Shape childAlloc = getChildAllocation(childIndex, a);
+ if (childAlloc != null)
+ {
+ ret = child.modelToView(pos, childAlloc, bias);
+ // Handle corner case.
+ if (ret == null && child.getEndOffset() == pos)
+ {
+ childIndex++;
+ if (childIndex < getViewCount())
+ {
+ child = getView(childIndex);
+ childAlloc = getChildAllocation(childIndex, a);
+ ret = child.modelToView(pos, childAlloc, bias);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (ret == null)
+ throw new BadLocationException("Position " + pos
+ + " is not represented by view.", pos);
+
+ return ret;
+ }
+
+ /**
+ * Maps a region in the document into the coordinate space of the View.
+ *
+ * @param p1 the beginning position inside the document
+ * @param b1 the direction bias for the beginning position
+ * @param p2 the end position inside the document
+ * @param b2 the direction bias for the end position
+ * @param a the area that is occupied by the view
+ *
+ * @return a rectangle that gives the span of the document region
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
+ * invalid
+ * @throws IllegalArgumentException if b1 or b2 is not one of the above
+ * listed valid values
+ */
+ public Shape modelToView(int p1, Position.Bias b1,
+ int p2, Position.Bias b2, Shape a)
+ throws BadLocationException
+ {
+ // TODO: This is most likely not 100% ok, figure out what else is to
+ // do here.
+ return super.modelToView(p1, b1, p2, b2, a);
+ }
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model.
+ *
+ * @param x the x coordinate in the view space, x >= 0
+ * @param y the y coordinate in the view space, y >= 0
+ * @param a the allocation of this <code>View</code>
+ * @param b the bias to use
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code> >= 0
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
+ {
+ if (x >= 0 && y >= 0)
+ {
+ Rectangle r = getInsideAllocation(a);
+ View view = getViewAtPoint((int) x, (int) y, r);
+ return view.viewToModel(x, y, r, b);
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the next model location that is visible in eiter north / south
+ * direction or east / west direction. This is used to determine the placement
+ * of the caret when navigating around the document with the arrow keys. This
+ * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom}
+ * and {@link #getNextEastWestVisualPositionFrom}.
+ *
+ * @param pos
+ * the model position to start search from
+ * @param b
+ * the bias for <code>pos</code>
+ * @param a
+ * the allocated region for this view
+ * @param direction
+ * the direction from the current position, can be one of the
+ * following:
+ * <ul>
+ * <li>{@link SwingConstants#WEST}</li>
+ * <li>{@link SwingConstants#EAST}</li>
+ * <li>{@link SwingConstants#NORTH}</li>
+ * <li>{@link SwingConstants#SOUTH}</li>
+ * </ul>
+ * @param biasRet
+ * the bias of the return value gets stored here
+ * @return the position inside the model that represents the next visual
+ * location
+ * @throws BadLocationException
+ * if <code>pos</code> is not a valid location inside the document
+ * model
+ * @throws IllegalArgumentException
+ * if <code>direction</code> is invalid
+ */
+ public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
+ int direction, Position.Bias[] biasRet)
+ throws BadLocationException
+ {
+ int retVal = -1;
+ switch (direction)
+ {
+ case SwingConstants.WEST:
+ case SwingConstants.EAST:
+ retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction,
+ biasRet);
+ break;
+ case SwingConstants.NORTH:
+ case SwingConstants.SOUTH:
+ retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
+ biasRet);
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal value for direction.");
+ }
+ return retVal;
+ }
+
+ /**
+ * Returns the index of the child view that represents the specified
+ * model location.
+ *
+ * @param pos the model location for which to determine the child view index
+ * @param b the bias to be applied to <code>pos</code>
+ *
+ * @return the index of the child view that represents the specified
+ * model location
+ */
+ public int getViewIndex(int pos, Position.Bias b)
+ {
+ if (b == Position.Bias.Backward)
+ pos -= 1;
+ int i = -1;
+ if (pos >= getStartOffset() && pos < getEndOffset())
+ i = getViewIndexAtPosition(pos);
+ return i;
+ }
+
+ /**
+ * Returns <code>true</code> if the specified point lies before the
+ * given <code>Rectangle</code>, <code>false</code> otherwise.
+ *
+ * &quot;Before&quot; is typically defined as being to the left or above.
+ *
+ * @param x the X coordinate of the point
+ * @param y the Y coordinate of the point
+ * @param r the rectangle to test the point against
+ *
+ * @return <code>true</code> if the specified point lies before the
+ * given <code>Rectangle</code>, <code>false</code> otherwise
+ */
+ protected abstract boolean isBefore(int x, int y, Rectangle r);
+
+ /**
+ * Returns <code>true</code> if the specified point lies after the
+ * given <code>Rectangle</code>, <code>false</code> otherwise.
+ *
+ * &quot;After&quot; is typically defined as being to the right or below.
+ *
+ * @param x the X coordinate of the point
+ * @param y the Y coordinate of the point
+ * @param r the rectangle to test the point against
+ *
+ * @return <code>true</code> if the specified point lies after the
+ * given <code>Rectangle</code>, <code>false</code> otherwise
+ */
+ protected abstract boolean isAfter(int x, int y, Rectangle r);
+
+ /**
+ * Returns the child <code>View</code> at the specified location.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param r the inner allocation of this <code>BoxView</code> on entry,
+ * the allocation of the found child on exit
+ *
+ * @return the child <code>View</code> at the specified location
+ */
+ protected abstract View getViewAtPoint(int x, int y, Rectangle r);
+
+ /**
+ * Computes the allocation for a child <code>View</code>. The parameter
+ * <code>a</code> stores the allocation of this <code>CompositeView</code>
+ * and is then adjusted to hold the allocation of the child view.
+ *
+ * @param index the index of the child <code>View</code>
+ * @param a the allocation of this <code>CompositeView</code> before the
+ * call, the allocation of the child on exit
+ */
+ protected abstract void childAllocation(int index, Rectangle a);
+
+ /**
+ * Returns the child <code>View</code> that contains the given model
+ * position. The given <code>Rectangle</code> gives the parent's allocation
+ * and is changed to the child's allocation on exit.
+ *
+ * @param pos the model position to query the child <code>View</code> for
+ * @param a the parent allocation on entry and the child allocation on exit
+ *
+ * @return the child view at the given model position
+ */
+ protected View getViewAtPosition(int pos, Rectangle a)
+ {
+ View view = null;
+ int i = getViewIndexAtPosition(pos);
+ if (i >= 0 && i < getViewCount() && a != null)
+ {
+ view = getView(i);
+ childAllocation(i, a);
+ }
+ return view;
+ }
+
+ /**
+ * Returns the index of the child <code>View</code> for the given model
+ * position.
+ *
+ * @param pos the model position for whicht the child <code>View</code> is
+ * queried
+ *
+ * @return the index of the child <code>View</code> for the given model
+ * position
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ // We have a 1:1 mapping of elements to views here, so we forward
+ // this to the element.
+ Element el = getElement();
+ return el.getElementIndex(pos);
+ }
+
+ /**
+ * Returns the allocation that is given to this <code>CompositeView</code>
+ * minus this <code>CompositeView</code>'s insets.
+ *
+ * Also this translates from an immutable allocation to a mutable allocation
+ * that is typically reused and further narrowed, like in
+ * {@link #childAllocation}.
+ *
+ * @param a the allocation given to this <code>CompositeView</code>
+ *
+ * @return the allocation that is given to this <code>CompositeView</code>
+ * minus this <code>CompositeView</code>'s insets or
+ * <code>null</code> if a was <code>null</code>
+ */
+ protected Rectangle getInsideAllocation(Shape a)
+ {
+ if (a == null)
+ return null;
+
+ // Try to avoid allocation of Rectangle here.
+ Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+
+ // Initialize the inside allocation rectangle. This is done inside
+ // a synchronized block in order to avoid multiple threads creating
+ // this instance simultanously.
+ Rectangle inside = insideAllocation;
+ inside.x = alloc.x + getLeftInset();
+ inside.y = alloc.y + getTopInset();
+ inside.width = alloc.width - getLeftInset() - getRightInset();
+ inside.height = alloc.height - getTopInset() - getBottomInset();
+ return inside;
+ }
+
+ /**
+ * Sets the insets defined by attributes in <code>attributes</code>. This
+ * queries the attribute keys {@link StyleConstants#SpaceAbove},
+ * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and
+ * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to
+ * actually set the insets on this <code>CompositeView</code>.
+ *
+ * @param attributes the attributes from which to query the insets
+ */
+ protected void setParagraphInsets(AttributeSet attributes)
+ {
+ top = (short) StyleConstants.getSpaceAbove(attributes);
+ bottom = (short) StyleConstants.getSpaceBelow(attributes);
+ left = (short) StyleConstants.getLeftIndent(attributes);
+ right = (short) StyleConstants.getRightIndent(attributes);
+ }
+
+ /**
+ * Sets the insets of this <code>CompositeView</code>.
+ *
+ * @param t the top inset
+ * @param l the left inset
+ * @param b the bottom inset
+ * @param r the right inset
+ */
+ protected void setInsets(short t, short l, short b, short r)
+ {
+ top = t;
+ left = l;
+ bottom = b;
+ right = r;
+ }
+
+ /**
+ * Returns the left inset of this <code>CompositeView</code>.
+ *
+ * @return the left inset of this <code>CompositeView</code>
+ */
+ protected short getLeftInset()
+ {
+ return left;
+ }
+
+ /**
+ * Returns the right inset of this <code>CompositeView</code>.
+ *
+ * @return the right inset of this <code>CompositeView</code>
+ */
+ protected short getRightInset()
+ {
+ return right;
+ }
+
+ /**
+ * Returns the top inset of this <code>CompositeView</code>.
+ *
+ * @return the top inset of this <code>CompositeView</code>
+ */
+ protected short getTopInset()
+ {
+ return top;
+ }
+
+ /**
+ * Returns the bottom inset of this <code>CompositeView</code>.
+ *
+ * @return the bottom inset of this <code>CompositeView</code>
+ */
+ protected short getBottomInset()
+ {
+ return bottom;
+ }
+
+ /**
+ * Returns the next model location that is visible in north or south
+ * direction.
+ * This is used to determine the
+ * placement of the caret when navigating around the document with
+ * the arrow keys.
+ *
+ * @param pos the model position to start search from
+ * @param b the bias for <code>pos</code>
+ * @param a the allocated region for this view
+ * @param direction the direction from the current position, can be one of
+ * the following:
+ * <ul>
+ * <li>{@link SwingConstants#NORTH}</li>
+ * <li>{@link SwingConstants#SOUTH}</li>
+ * </ul>
+ * @param biasRet the bias of the return value gets stored here
+ *
+ * @return the position inside the model that represents the next visual
+ * location
+ *
+ * @throws BadLocationException if <code>pos</code> is not a valid location
+ * inside the document model
+ * @throws IllegalArgumentException if <code>direction</code> is invalid
+ */
+ protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
+ Shape a, int direction,
+ Position.Bias[] biasRet)
+ throws BadLocationException
+ {
+ // TODO: It is unknown to me how this method has to be implemented and
+ // there is no specification telling me how to do it properly. Therefore
+ // the implementation was done for cases that are known.
+ //
+ // If this method ever happens to act silly for your particular case then
+ // it is likely that it is a cause of not knowing about your case when it
+ // was implemented first. You are free to fix the behavior.
+ //
+ // Here are the assumptions that lead to the implementation:
+ // If direction is NORTH chose the View preceding the one that contains the
+ // offset 'pos' (imagine the views are stacked on top of each other where
+ // the top is 0 and the bottom is getViewCount()-1.
+ // Consecutively when the direction is SOUTH the View following the one
+ // the offset 'pos' lies in is questioned.
+ //
+ // This limitation is described as PR 27345.
+ int index = getViewIndex(pos, b);
+ View v = null;
+
+ if (index == -1)
+ return pos;
+
+ switch (direction)
+ {
+ case NORTH:
+ // If we cannot calculate a proper offset return the one that was
+ // provided.
+ if (index <= 0)
+ return pos;
+
+ v = getView(index - 1);
+ break;
+ case SOUTH:
+ // If we cannot calculate a proper offset return the one that was
+ // provided.
+ if (index >= getViewCount() - 1)
+ return pos;
+
+ v = getView(index + 1);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ return v.getNextVisualPositionFrom(pos, b, a, direction, biasRet);
+ }
+
+ /**
+ * Returns the next model location that is visible in east or west
+ * direction.
+ * This is used to determine the
+ * placement of the caret when navigating around the document with
+ * the arrow keys.
+ *
+ * @param pos the model position to start search from
+ * @param b the bias for <code>pos</code>
+ * @param a the allocated region for this view
+ * @param direction the direction from the current position, can be one of
+ * the following:
+ * <ul>
+ * <li>{@link SwingConstants#EAST}</li>
+ * <li>{@link SwingConstants#WEST}</li>
+ * </ul>
+ * @param biasRet the bias of the return value gets stored here
+ *
+ * @return the position inside the model that represents the next visual
+ * location
+ *
+ * @throws BadLocationException if <code>pos</code> is not a valid location
+ * inside the document model
+ * @throws IllegalArgumentException if <code>direction</code> is invalid
+ */
+ protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
+ Shape a, int direction,
+ Position.Bias[] biasRet)
+ throws BadLocationException
+ {
+ // TODO: It is unknown to me how this method has to be implemented and
+ // there is no specification telling me how to do it properly. Therefore
+ // the implementation was done for cases that are known.
+ //
+ // If this method ever happens to act silly for your particular case then
+ // it is likely that it is a cause of not knowing about your case when it
+ // was implemented first. You are free to fix the behavior.
+ //
+ // Here are the assumptions that lead to the implementation:
+ // If direction is EAST increase the offset by one and ask the View to
+ // which that index belong to calculate the 'next visual position'.
+ // If the direction is WEST do the same with offset 'pos' being decreased
+ // by one.
+ // This behavior will fail in a right-to-left or bidi environment!
+ //
+ // This limitation is described as PR 27346.
+ int index;
+
+ View v = null;
+
+ switch (direction)
+ {
+ case EAST:
+ index = getViewIndex(pos + 1, b);
+ // If we cannot calculate a proper offset return the one that was
+ // provided.
+ if (index == -1)
+ return pos;
+
+ v = getView(index);
+ break;
+ case WEST:
+ index = getViewIndex(pos - 1, b);
+ // If we cannot calculate a proper offset return the one that was
+ // provided.
+ if (index == -1)
+ return pos;
+
+ v = getView(index);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ return v.getNextVisualPositionFrom(pos,
+ b,
+ a,
+ direction,
+ biasRet);
+ }
+
+ /**
+ * Determines if the next view in horinzontal direction is located to
+ * the east or west of the view at position <code>pos</code>. Usually
+ * the <code>View</code>s are laid out from the east to the west, so
+ * we unconditionally return <code>false</code> here. Subclasses that
+ * support bidirectional text may wish to override this method.
+ *
+ * @param pos the position in the document
+ * @param bias the bias to be applied to <code>pos</code>
+ *
+ * @return <code>true</code> if the next <code>View</code> is located
+ * to the EAST, <code>false</code> otherwise
+ */
+ protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias)
+ {
+ return false;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/DateFormatter.java b/libjava/classpath/javax/swing/text/DateFormatter.java
new file mode 100644
index 000000000..869f9a090
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DateFormatter.java
@@ -0,0 +1,85 @@
+/* DateFormatter.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.DateFormat;
+
+/**
+ * <code>DateFormatter</code> is an {@link InternationalFormatter}
+ * that implements value to string and string to value conversion via
+ * an instance of {@link DateFormat}.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class DateFormatter extends InternationalFormatter
+{
+
+ /** The serialVersoinUID. */
+ private static final long serialVersionUID = 5423279572591848797L;
+
+ /**
+ * Creates a new instance using the default {@link DateFormat} object
+ * returned by {@link DateFormat#getDateInstance()}.
+ */
+ public DateFormatter()
+ {
+ this(DateFormat.getDateInstance());
+ }
+
+ /**
+ * Creates a new instance of <code>DateFormatter</code> using the
+ * specified <code>DateFormat</code>
+ *
+ * @param format the <code>DateFormat</code> to use
+ */
+ public DateFormatter(DateFormat format)
+ {
+ super();
+ setFormat(format);
+ }
+
+ /**
+ * Sets the format that is used by this <code>DateFormatter</code>.
+ *
+ * @param format the <code>DateFormat</code> to use
+ */
+ public void setFormat(DateFormat format)
+ {
+ super.setFormat(format);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java
new file mode 100644
index 000000000..acfcdb368
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DefaultCaret.java
@@ -0,0 +1,1287 @@
+/* DefaultCaret.java --
+ Copyright (C) 2002, 2004, 2005, 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.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.EventListener;
+
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.EventListenerList;
+import javax.swing.text.Position.Bias;
+
+/**
+ * The default implementation of the {@link Caret} interface.
+ *
+ * @author original author unknown
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class DefaultCaret extends Rectangle
+ implements Caret, FocusListener, MouseListener, MouseMotionListener
+{
+
+ /** A text component in the current VM which currently has a
+ * text selection or <code>null</code>.
+ */
+ static JTextComponent componentWithSelection;
+
+ /** An implementation of NavigationFilter.FilterBypass which delegates
+ * to the corresponding methods of the <code>DefaultCaret</code>.
+ *
+ * @author Robert Schuster (robertschuster@fsfe.org)
+ */
+ class Bypass extends NavigationFilter.FilterBypass
+ {
+
+ public Caret getCaret()
+ {
+ return DefaultCaret.this;
+ }
+
+ public void moveDot(int dot, Bias bias)
+ {
+ DefaultCaret.this.moveDotImpl(dot);
+ }
+
+ public void setDot(int dot, Bias bias)
+ {
+ DefaultCaret.this.setDotImpl(dot);
+ }
+
+ }
+
+ /**
+ * Controls the blinking of the caret.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ */
+ private class BlinkTimerListener implements ActionListener
+ {
+ /**
+ * Forces the next event to be ignored. The next event should be ignored
+ * if we force the caret to appear. We do not know how long will it take
+ * to fire the comming event; this may be near immediately. Better to leave
+ * the caret visible one iteration longer.
+ */
+ boolean ignoreNextEvent;
+
+ /**
+ * Receives notification when the blink timer fires and updates the visible
+ * state of the caret.
+ *
+ * @param event the action event
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ if (ignoreNextEvent)
+ ignoreNextEvent = false;
+ else
+ {
+ visible = !visible;
+ repaint();
+ }
+ }
+ }
+
+ /**
+ * Listens for changes in the text component's document and updates the
+ * caret accordingly.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+ private class DocumentHandler implements DocumentListener
+ {
+ /**
+ * Receives notification that some text attributes have changed. No action
+ * is taken here.
+ *
+ * @param event the document event
+ */
+ public void changedUpdate(DocumentEvent event)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Receives notification that some text has been inserted from the text
+ * component. The caret is moved forward accordingly.
+ *
+ * @param event the document event
+ */
+ public void insertUpdate(DocumentEvent event)
+ {
+ if (policy == ALWAYS_UPDATE ||
+ (SwingUtilities.isEventDispatchThread() &&
+ policy == UPDATE_WHEN_ON_EDT))
+ {
+ int dot = getDot();
+ setDot(dot + event.getLength());
+ }
+ }
+
+ /**
+ * Receives notification that some text has been removed into the text
+ * component. The caret is moved backwards accordingly.
+ *
+ * @param event the document event
+ */
+ public void removeUpdate(DocumentEvent event)
+ {
+ if (policy == ALWAYS_UPDATE
+ || (SwingUtilities.isEventDispatchThread()
+ && policy == UPDATE_WHEN_ON_EDT))
+ {
+ int dot = getDot();
+ setDot(dot - event.getLength());
+ }
+ else if (policy == NEVER_UPDATE
+ || (! SwingUtilities.isEventDispatchThread()
+ && policy == UPDATE_WHEN_ON_EDT))
+ {
+ int docLength = event.getDocument().getLength();
+ if (getDot() > docLength)
+ setDot(docLength);
+ }
+ }
+ }
+
+ /**
+ * Listens for property changes on the text document. This is used to add and
+ * remove our document listener, if the document of the text component has
+ * changed.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+ private class PropertyChangeHandler implements PropertyChangeListener
+ {
+
+ /**
+ * Receives notification when a property has changed on the text component.
+ * This adds/removes our document listener from the text component's
+ * document when the document changes.
+ *
+ * @param e the property change event
+ */
+ public void propertyChange(PropertyChangeEvent e)
+ {
+ String name = e.getPropertyName();
+
+ if (name.equals("document"))
+ {
+ Document oldDoc = (Document) e.getOldValue();
+ if (oldDoc != null)
+ oldDoc.removeDocumentListener(documentListener);
+
+ Document newDoc = (Document) e.getNewValue();
+ if (newDoc != null)
+ newDoc.addDocumentListener(documentListener);
+ }
+ else if (name.equals("editable"))
+ {
+ active = (((Boolean) e.getNewValue()).booleanValue()
+ && textComponent.isEnabled());
+ }
+ else if (name.equals("enabled"))
+ {
+ active = (((Boolean) e.getNewValue()).booleanValue()
+ && textComponent.isEditable());
+ }
+
+ }
+
+ }
+
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 4325555698756477346L;
+
+ /**
+ * Indicates the Caret position should always be updated after Document
+ * changes even if the updates are not performed on the Event Dispatching
+ * thread.
+ *
+ * @since 1.5
+ */
+ public static final int ALWAYS_UPDATE = 2;
+
+ /**
+ * Indicates the Caret position should not be changed unless the Document
+ * length becomes less than the Caret position, in which case the Caret
+ * is moved to the end of the Document.
+ *
+ * @since 1.5
+ */
+ public static final int NEVER_UPDATE = 1;
+
+ /**
+ * Indicates the Caret position should be updated only if Document changes
+ * are made on the Event Dispatcher thread.
+ *
+ * @since 1.5
+ */
+ public static final int UPDATE_WHEN_ON_EDT = 0;
+
+ /** Keeps track of the current update policy **/
+ int policy = UPDATE_WHEN_ON_EDT;
+
+ /**
+ * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}.
+ */
+ protected ChangeEvent changeEvent = new ChangeEvent(this);
+
+ /**
+ * Stores all registered event listeners.
+ */
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Our document listener.
+ */
+ DocumentListener documentListener;
+
+ /**
+ * Our property listener.
+ */
+ PropertyChangeListener propertyChangeListener;
+
+ /**
+ * The text component in which this caret is installed.
+ *
+ * (Package private to avoid synthetic accessor method.)
+ */
+ JTextComponent textComponent;
+
+ /**
+ * Indicates if the selection should be visible or not.
+ */
+ private boolean selectionVisible = true;
+
+ /**
+ * The blink rate of this <code>Caret</code>.
+ */
+ private int blinkRate = 500;
+
+ /**
+ * The current dot position.
+ */
+ private int dot = 0;
+
+ /**
+ * The current mark position.
+ */
+ private int mark = 0;
+
+ /**
+ * The current visual caret position.
+ */
+ private Point magicCaretPosition = null;
+
+ /**
+ * Indicates if this <code>Caret</code> is currently visible or not. This is
+ * package private to avoid an accessor method.
+ */
+ boolean visible = false;
+
+ /** Indicates whether the text component where the caret is installed is
+ * editable and enabled. If either of these properties is <code>false</code>
+ * the caret is not drawn.
+ */
+ boolean active = true;
+
+ /**
+ * The current highlight entry.
+ */
+ private Object highlightEntry;
+
+ private Timer blinkTimer;
+
+ private BlinkTimerListener blinkListener;
+
+ /**
+ * A <code>NavigationFilter.FilterBypass</code> instance which
+ * is provided to the a <code>NavigationFilter</code> to
+ * unconditionally set or move the caret.
+ */
+ NavigationFilter.FilterBypass bypass;
+
+ /**
+ * Creates a new <code>DefaultCaret</code> instance.
+ */
+ public DefaultCaret()
+ {
+ // Nothing to do here.
+ }
+
+ /** Returns the caret's <code>NavigationFilter.FilterBypass</code> instance
+ * and creates it if it does not yet exist.
+ *
+ * @return The caret's <code>NavigationFilter.FilterBypass</code> instance.
+ */
+ private NavigationFilter.FilterBypass getBypass()
+ {
+ return (bypass == null) ? bypass = new Bypass() : bypass;
+ }
+
+ /**
+ * Sets the Caret update policy.
+ *
+ * @param policy the new policy. Valid values are:
+ * ALWAYS_UPDATE: always update the Caret position, even when Document
+ * updates don't occur on the Event Dispatcher thread.
+ * NEVER_UPDATE: don't update the Caret position unless the Document
+ * length becomes less than the Caret position (then update the
+ * Caret to the end of the Document).
+ * UPDATE_WHEN_ON_EDT: update the Caret position when the
+ * Document updates occur on the Event Dispatcher thread. This is the
+ * default.
+ *
+ * @since 1.5
+ * @throws IllegalArgumentException if policy is not one of the above.
+ */
+ public void setUpdatePolicy (int policy)
+ {
+ if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE
+ && policy != UPDATE_WHEN_ON_EDT)
+ throw new
+ IllegalArgumentException
+ ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT");
+ this.policy = policy;
+ }
+
+ /**
+ * Gets the caret update policy.
+ *
+ * @return the caret update policy.
+ * @since 1.5
+ */
+ public int getUpdatePolicy ()
+ {
+ return policy;
+ }
+
+ /**
+ * Moves the caret position when the mouse is dragged over the text
+ * component, modifying the selectiony.
+ *
+ * <p>When the text component where the caret is installed is disabled,
+ * the selection is not change but you can still scroll the text and
+ * update the caret's location.</p>
+ *
+ * @param event the <code>MouseEvent</code> describing the drag operation
+ */
+ public void mouseDragged(MouseEvent event)
+ {
+ if (event.getButton() == MouseEvent.BUTTON1)
+ {
+ if (textComponent.isEnabled())
+ moveCaret(event);
+ else
+ positionCaret(event);
+ }
+ }
+
+ /**
+ * Indicates a mouse movement over the text component. Does nothing here.
+ *
+ * @param event the <code>MouseEvent</code> describing the mouse operation
+ */
+ public void mouseMoved(MouseEvent event)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * When the click is received from Button 1 then the following actions
+ * are performed here:
+ *
+ * <ul>
+ * <li>If we receive a double click, the caret position (dot) is set
+ * to the position associated to the mouse click and the word at
+ * this location is selected. If there is no word at the pointer
+ * the gap is selected instead.</li>
+ * <li>If we receive a triple click, the caret position (dot) is set
+ * to the position associated to the mouse click and the line at
+ * this location is selected.</li>
+ * </ul>
+ *
+ * @param event the <code>MouseEvent</code> describing the click operation
+ */
+ public void mouseClicked(MouseEvent event)
+ {
+ // Do not modify selection if component is disabled.
+ if (!textComponent.isEnabled())
+ return;
+
+ int count = event.getClickCount();
+
+ if (event.getButton() == MouseEvent.BUTTON1 && count >= 2)
+ {
+ int newDot = getComponent().viewToModel(event.getPoint());
+ JTextComponent t = getComponent();
+
+ try
+ {
+ if (count == 3)
+ {
+ setDot(Utilities.getRowStart(t, newDot));
+ moveDot( Utilities.getRowEnd(t, newDot));
+ }
+ else
+ {
+ int wordStart = Utilities.getWordStart(t, newDot);
+
+ // When the mouse points at the offset of the first character
+ // in a word Utilities().getPreviousWord will not return that
+ // word but we want to select that. We have to use
+ // Utilities.getWordStart() to get it.
+ if (newDot == wordStart)
+ {
+ setDot(wordStart);
+ moveDot(Utilities.getWordEnd(t, wordStart));
+ }
+ else
+ {
+ int nextWord = Utilities.getNextWord(t, newDot);
+ int previousWord = Utilities.getPreviousWord(t, newDot);
+ int previousWordEnd = Utilities.getWordEnd(t, previousWord);
+
+ // If the user clicked in the space between two words,
+ // then select the space.
+ if (newDot >= previousWordEnd && newDot <= nextWord)
+ {
+ setDot(previousWordEnd);
+ moveDot(nextWord);
+ }
+ // Otherwise select the word under the mouse pointer.
+ else
+ {
+ setDot(previousWord);
+ moveDot(previousWordEnd);
+ }
+ }
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // TODO: Swallowing ok here?
+ }
+ }
+
+ }
+
+ /**
+ * Indicates that the mouse has entered the text component. Nothing is done
+ * here.
+ *
+ * @param event the <code>MouseEvent</code> describing the mouse operation
+ */
+ public void mouseEntered(MouseEvent event)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Indicates that the mouse has exited the text component. Nothing is done
+ * here.
+ *
+ * @param event the <code>MouseEvent</code> describing the mouse operation
+ */
+ public void mouseExited(MouseEvent event)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * If the button 1 is pressed, the caret position is updated to the
+ * position of the mouse click and the text component requests the input
+ * focus if it is enabled. If the SHIFT key is held down, the caret will
+ * be moved, which might select the text between the old and new location.
+ *
+ * @param event the <code>MouseEvent</code> describing the press operation
+ */
+ public void mousePressed(MouseEvent event)
+ {
+
+ // The implementation assumes that consuming the event makes the AWT event
+ // mechanism forget about this event instance and not transfer focus.
+ // By observing how the RI reacts the following behavior has been
+ // implemented (in regard to text components):
+ // - a left-click moves the caret
+ // - a left-click when shift is held down expands the selection
+ // - a right-click or click with any additional mouse button
+ // on a text component is ignored
+ // - a middle-click positions the caret and pastes the clipboard
+ // contents.
+ // - a middle-click when shift is held down is ignored
+
+ if (SwingUtilities.isLeftMouseButton(event))
+ {
+ // Handle the caret.
+ if (event.isShiftDown() && getDot() != -1)
+ {
+ moveCaret(event);
+ }
+ else
+ {
+ positionCaret(event);
+ }
+
+ // Handle the focus.
+ if (textComponent != null && textComponent.isEnabled()
+ && textComponent.isRequestFocusEnabled())
+ {
+ textComponent.requestFocus();
+ }
+
+ // TODO: Handle double click for selecting words.
+ }
+ else if(event.getButton() == MouseEvent.BUTTON2)
+ {
+ // Special handling for X11-style pasting.
+ if (! event.isShiftDown())
+ {
+ positionCaret(event);
+ textComponent.paste();
+ }
+ }
+ }
+
+ /**
+ * Indicates that a mouse button has been released on the text component.
+ * Nothing is done here.
+ *
+ * @param event the <code>MouseEvent</code> describing the mouse operation
+ */
+ public void mouseReleased(MouseEvent event)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Sets the caret to <code>visible</code> if the text component is editable.
+ *
+ * @param event the <code>FocusEvent</code>
+ */
+ public void focusGained(FocusEvent event)
+ {
+ if (textComponent.isEditable())
+ {
+ setVisible(true);
+ updateTimerStatus();
+ }
+ }
+
+ /**
+ * Sets the caret to <code>invisible</code>.
+ *
+ * @param event the <code>FocusEvent</code>
+ */
+ public void focusLost(FocusEvent event)
+ {
+ if (textComponent.isEditable() && event.isTemporary() == 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();
+ }
+ }
+
+ /**
+ * Moves the caret to the position specified in the <code>MouseEvent</code>.
+ * This will cause a selection if the dot and mark are different.
+ *
+ * @param event the <code>MouseEvent</code> from which to fetch the position
+ */
+ protected void moveCaret(MouseEvent event)
+ {
+ int newDot = getComponent().viewToModel(event.getPoint());
+ moveDot(newDot);
+ }
+
+ /**
+ * Repositions the caret to the position specified in the
+ * <code>MouseEvent</code>.
+ *
+ * @param event the <code>MouseEvent</code> from which to fetch the position
+ */
+ protected void positionCaret(MouseEvent event)
+ {
+ int newDot = getComponent().viewToModel(event.getPoint());
+ setDot(newDot);
+ }
+
+ /**
+ * Deinstalls this <code>Caret</code> from the specified
+ * <code>JTextComponent</code>. This removes any listeners that have been
+ * registered by this <code>Caret</code>.
+ *
+ * @param c the text component from which to install this caret
+ */
+ public void deinstall(JTextComponent c)
+ {
+ textComponent.removeFocusListener(this);
+ textComponent.removeMouseListener(this);
+ textComponent.removeMouseMotionListener(this);
+ textComponent.getDocument().removeDocumentListener(documentListener);
+ documentListener = null;
+ textComponent.removePropertyChangeListener(propertyChangeListener);
+ propertyChangeListener = null;
+ textComponent = null;
+
+ // Deinstall blink timer if present.
+ if (blinkTimer != null)
+ blinkTimer.stop();
+ blinkTimer = null;
+ }
+
+ /**
+ * Installs this <code>Caret</code> on the specified
+ * <code>JTextComponent</code>. This registers a couple of listeners
+ * on the text component.
+ *
+ * @param c the text component on which to install this caret
+ */
+ public void install(JTextComponent c)
+ {
+ textComponent = c;
+ textComponent.addFocusListener(this);
+ textComponent.addMouseListener(this);
+ textComponent.addMouseMotionListener(this);
+ propertyChangeListener = new PropertyChangeHandler();
+ textComponent.addPropertyChangeListener(propertyChangeListener);
+ documentListener = new DocumentHandler();
+
+ Document doc = textComponent.getDocument();
+ if (doc != null)
+ doc.addDocumentListener(documentListener);
+
+ active = textComponent.isEditable() && textComponent.isEnabled();
+
+ repaint();
+ }
+
+ /**
+ * Sets the current visual position of this <code>Caret</code>.
+ *
+ * @param p the Point to use for the saved location. May be <code>null</code>
+ * to indicate that there is no visual location
+ */
+ public void setMagicCaretPosition(Point p)
+ {
+ magicCaretPosition = p;
+ }
+
+ /**
+ * Returns the current visual position of this <code>Caret</code>.
+ *
+ * @return the current visual position of this <code>Caret</code>
+ *
+ * @see #setMagicCaretPosition
+ */
+ public Point getMagicCaretPosition()
+ {
+ return magicCaretPosition;
+ }
+
+ /**
+ * Returns the current position of the <code>mark</code>. The
+ * <code>mark</code> marks the location in the <code>Document</code> that
+ * is the end of a selection. If there is no selection, the <code>mark</code>
+ * is the same as the <code>dot</code>.
+ *
+ * @return the current position of the mark
+ */
+ public int getMark()
+ {
+ return mark;
+ }
+
+ private void clearHighlight()
+ {
+ Highlighter highlighter = textComponent.getHighlighter();
+
+ if (highlighter == null)
+ return;
+
+ if (selectionVisible)
+ {
+ try
+ {
+ if (highlightEntry != null)
+ highlighter.changeHighlight(highlightEntry, 0, 0);
+
+ // Free the global variable which stores the text component with an active
+ // selection.
+ if (componentWithSelection == textComponent)
+ componentWithSelection = null;
+ }
+ catch (BadLocationException e)
+ {
+ // This should never happen.
+ throw new InternalError();
+ }
+ }
+ else
+ {
+ if (highlightEntry != null)
+ {
+ highlighter.removeHighlight(highlightEntry);
+ highlightEntry = null;
+ }
+ }
+ }
+
+ private void handleHighlight()
+ {
+ Highlighter highlighter = textComponent.getHighlighter();
+
+ if (highlighter == null)
+ return;
+
+ int p0 = Math.min(dot, mark);
+ int p1 = Math.max(dot, mark);
+
+ if (selectionVisible)
+ {
+ try
+ {
+ if (highlightEntry == null)
+ highlightEntry = highlighter.addHighlight(p0, p1, getSelectionPainter());
+ else
+ highlighter.changeHighlight(highlightEntry, p0, p1);
+
+ // If another component currently has a text selection clear that selection
+ // first.
+ if (componentWithSelection != null)
+ if (componentWithSelection != textComponent)
+ {
+ Caret c = componentWithSelection.getCaret();
+ c.setDot(c.getDot());
+ }
+ componentWithSelection = textComponent;
+
+ }
+ catch (BadLocationException e)
+ {
+ // This should never happen.
+ throw new InternalError();
+ }
+ }
+ else
+ {
+ if (highlightEntry != null)
+ {
+ highlighter.removeHighlight(highlightEntry);
+ highlightEntry = null;
+ }
+ }
+ }
+
+ /**
+ * Sets the visiblity state of the selection.
+ *
+ * @param v <code>true</code> if the selection should be visible,
+ * <code>false</code> otherwise
+ */
+ public void setSelectionVisible(boolean v)
+ {
+ if (selectionVisible == v)
+ return;
+
+ selectionVisible = v;
+ handleHighlight();
+ repaint();
+ }
+
+ /**
+ * Returns <code>true</code> if the selection is currently visible,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the selection is currently visible,
+ * <code>false</code> otherwise
+ */
+ public boolean isSelectionVisible()
+ {
+ return selectionVisible;
+ }
+
+ /**
+ * Causes the <code>Caret</code> to repaint itself.
+ */
+ protected final void repaint()
+ {
+ getComponent().repaint(x, y, width, height);
+ }
+
+ /**
+ * Paints this <code>Caret</code> using the specified <code>Graphics</code>
+ * context.
+ *
+ * @param g the graphics context to use
+ */
+ public void paint(Graphics g)
+ {
+ JTextComponent comp = getComponent();
+ if (comp == null)
+ return;
+
+ // Make sure the dot has a sane position.
+ dot = Math.min(dot, textComponent.getDocument().getLength());
+ dot = Math.max(dot, 0);
+
+ Rectangle rect = null;
+
+ try
+ {
+ rect = textComponent.modelToView(dot);
+ }
+ catch (BadLocationException e)
+ {
+ // Let's ignore that. This shouldn't really occur. But if it
+ // does (it seems that this happens when the model is mutating),
+ // it causes no real damage. Uncomment this for debugging.
+ // e.printStackTrace();
+ }
+
+ if (rect == null)
+ return;
+
+ // Check if paint has possibly been called directly, without a previous
+ // call to damage(). In this case we need to do some cleanup first.
+ if ((x != rect.x) || (y != rect.y))
+ {
+ repaint(); // Erase previous location of caret.
+ x = rect.x;
+ y = rect.y;
+ width = 1;
+ height = rect.height;
+ }
+
+ // Now draw the caret on the new position if visible.
+ if (visible && active)
+ {
+ g.setColor(textComponent.getCaretColor());
+ g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height - 1);
+ }
+ }
+
+ /**
+ * Returns all registered event listeners of the specified type.
+ *
+ * @param listenerType the type of listener to return
+ *
+ * @return all registered event listeners of the specified type
+ */
+ public <T extends EventListener> T[] getListeners(Class<T> listenerType)
+ {
+ return listenerList.getListeners(listenerType);
+ }
+
+ /**
+ * Registers a {@link ChangeListener} that is notified whenever that state
+ * of this <code>Caret</code> changes.
+ *
+ * @param listener the listener to register to this caret
+ */
+ public void addChangeListener(ChangeListener listener)
+ {
+ listenerList.add(ChangeListener.class, listener);
+ }
+
+ /**
+ * Removes a {@link ChangeListener} from the list of registered listeners.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeChangeListener(ChangeListener listener)
+ {
+ listenerList.remove(ChangeListener.class, listener);
+ }
+
+ /**
+ * Returns all registered {@link ChangeListener}s of this <code>Caret</code>.
+ *
+ * @return all registered {@link ChangeListener}s of this <code>Caret</code>
+ */
+ public ChangeListener[] getChangeListeners()
+ {
+ return (ChangeListener[]) getListeners(ChangeListener.class);
+ }
+
+ /**
+ * Notifies all registered {@link ChangeListener}s that the state
+ * of this <code>Caret</code> has changed.
+ */
+ protected void fireStateChanged()
+ {
+ ChangeListener[] listeners = getChangeListeners();
+
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].stateChanged(changeEvent);
+ }
+
+ /**
+ * Returns the <code>JTextComponent</code> on which this <code>Caret</code>
+ * is installed.
+ *
+ * @return the <code>JTextComponent</code> on which this <code>Caret</code>
+ * is installed
+ */
+ protected final JTextComponent getComponent()
+ {
+ return textComponent;
+ }
+
+ /**
+ * Returns the blink rate of this <code>Caret</code> in milliseconds.
+ * A value of <code>0</code> means that the caret does not blink.
+ *
+ * @return the blink rate of this <code>Caret</code> or <code>0</code> if
+ * this caret does not blink
+ */
+ public int getBlinkRate()
+ {
+ return blinkRate;
+ }
+
+ /**
+ * Sets the blink rate of this <code>Caret</code> in milliseconds.
+ * A value of <code>0</code> means that the caret does not blink.
+ *
+ * @param rate the new blink rate to set
+ */
+ public void setBlinkRate(int rate)
+ {
+ if (blinkTimer != null)
+ blinkTimer.setDelay(rate);
+ blinkRate = rate;
+ }
+
+ /**
+ * Returns the current position of this <code>Caret</code> within the
+ * <code>Document</code>.
+ *
+ * @return the current position of this <code>Caret</code> within the
+ * <code>Document</code>
+ */
+ public int getDot()
+ {
+ return dot;
+ }
+
+ /**
+ * Moves the <code>dot</code> location without touching the
+ * <code>mark</code>. This is used when making a selection.
+ *
+ * <p>If the underlying text component has a {@link NavigationFilter}
+ * installed the caret will call the corresponding method of that object.</p>
+ *
+ * @param dot the location where to move the dot
+ *
+ * @see #setDot(int)
+ */
+ public void moveDot(int dot)
+ {
+ NavigationFilter filter = textComponent.getNavigationFilter();
+ if (filter != null)
+ filter.moveDot(getBypass(), dot, Bias.Forward);
+ else
+ moveDotImpl(dot);
+ }
+
+ void moveDotImpl(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);
+
+ handleHighlight();
+
+ appear();
+ }
+ }
+
+ /**
+ * Sets the current position of this <code>Caret</code> within the
+ * <code>Document</code>. This also sets the <code>mark</code> to the new
+ * location.
+ *
+ * <p>If the underlying text component has a {@link NavigationFilter}
+ * installed the caret will call the corresponding method of that object.</p>
+ *
+ * @param dot
+ * the new position to be set
+ * @see #moveDot(int)
+ */
+ public void setDot(int dot)
+ {
+ NavigationFilter filter = textComponent.getNavigationFilter();
+ if (filter != null)
+ filter.setDot(getBypass(), dot, Bias.Forward);
+ else
+ setDotImpl(dot);
+ }
+
+ void setDotImpl(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 = this.dot;
+
+ clearHighlight();
+
+ appear();
+ }
+ }
+
+ /**
+ * Show the caret (may be hidden due blinking) and adjust the timer not to
+ * hide it (possibly immediately).
+ *
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ */
+ void appear()
+ {
+ // All machinery is only required if the carret is blinking.
+ if (blinkListener != null)
+ {
+ blinkListener.ignoreNextEvent = true;
+
+ // If the caret is visible, erase the current position by repainting
+ // over.
+ if (visible)
+ repaint();
+
+ // Draw the caret in the new position.
+ visible = true;
+
+ Rectangle area = null;
+ int dot = getDot();
+ try
+ {
+ area = getComponent().modelToView(dot);
+ }
+ catch (BadLocationException e)
+ {
+ // Let's ignore that. This shouldn't really occur. But if it
+ // does (it seems that this happens when the model is mutating),
+ // it causes no real damage. Uncomment this for debugging.
+ // e.printStackTrace();
+ }
+ if (area != null)
+ {
+ adjustVisibility(area);
+ if (getMagicCaretPosition() == null)
+ setMagicCaretPosition(new Point(area.x, area.y));
+ damage(area);
+ }
+ }
+ repaint();
+ }
+
+ /**
+ * Returns <code>true</code> if this <code>Caret</code> is blinking,
+ * and <code>false</code> if not. The returned value is independent of
+ * the visiblity of this <code>Caret</code> as returned by {@link #isVisible()}.
+ *
+ * @return <code>true</code> if this <code>Caret</code> is blinking,
+ * and <code>false</code> if not.
+ * @see #isVisible()
+ * @since 1.5
+ */
+ public boolean isActive()
+ {
+ if (blinkTimer != null)
+ return blinkTimer.isRunning();
+
+ return false;
+ }
+
+ /**
+ * Returns <code>true</code> if this <code>Caret</code> is currently visible,
+ * and <code>false</code> if it is not.
+ *
+ * @return <code>true</code> if this <code>Caret</code> is currently visible,
+ * and <code>false</code> if it is not
+ */
+ public boolean isVisible()
+ {
+ return visible && active;
+ }
+
+ /**
+ * Sets the visibility state of the caret. <code>true</code> shows the
+ * <code>Caret</code>, <code>false</code> hides it.
+ *
+ * @param v the visibility to set
+ */
+ public void setVisible(boolean v)
+ {
+ if (v != visible)
+ {
+ visible = v;
+ updateTimerStatus();
+ Rectangle area = null;
+ int dot = getDot();
+ try
+ {
+ area = getComponent().modelToView(dot);
+ }
+ catch (BadLocationException e)
+ {
+ AssertionError ae;
+ ae = new AssertionError("Unexpected bad caret location: " + dot);
+ ae.initCause(e);
+ throw ae;
+ }
+ if (area != null)
+ damage(area);
+ }
+ }
+
+ /**
+ * Returns the {@link Highlighter.HighlightPainter} that should be used
+ * to paint the selection.
+ *
+ * @return the {@link Highlighter.HighlightPainter} that should be used
+ * to paint the selection
+ */
+ protected Highlighter.HighlightPainter getSelectionPainter()
+ {
+ return DefaultHighlighter.DefaultPainter;
+ }
+
+ /**
+ * Updates the carets rectangle properties to the specified rectangle and
+ * repaints the caret.
+ *
+ * @param r the rectangle to set as the caret rectangle
+ */
+ protected void damage(Rectangle r)
+ {
+ if (r == null)
+ return;
+ x = r.x;
+ y = r.y;
+ width = 1;
+ // height is normally set in paint and we leave it untouched. However, we
+ // must set a valid value here, since otherwise the painting mechanism
+ // sets a zero clip and never calls paint.
+ if (height <= 0)
+ try
+ {
+ height = textComponent.modelToView(dot).height;
+ }
+ catch (BadLocationException ble)
+ {
+ // Should not happen.
+ throw new InternalError("Caret location not within document range.");
+ }
+
+ repaint();
+ }
+
+ /**
+ * Adjusts the text component so that the caret is visible. This default
+ * implementation simply calls
+ * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component.
+ * Subclasses may wish to change this.
+ */
+ protected void adjustVisibility(Rectangle rect)
+ {
+ getComponent().scrollRectToVisible(rect);
+ }
+
+ /**
+ * Initializes the blink timer.
+ */
+ private void initBlinkTimer()
+ {
+ // Setup the blink timer.
+ blinkListener = new BlinkTimerListener();
+ blinkTimer = new Timer(getBlinkRate(), blinkListener);
+ blinkTimer.setRepeats(true);
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/DefaultEditorKit.java b/libjava/classpath/javax/swing/text/DefaultEditorKit.java
new file mode 100644
index 000000000..b3b7e6077
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DefaultEditorKit.java
@@ -0,0 +1,1702 @@
+/* DefaultEditorKit.java --
+ Copyright (C) 2002, 2004, 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 gnu.java.lang.CPStringBuilder;
+
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.swing.Action;
+import javax.swing.SwingConstants;
+
+/**
+ * The default implementation of {@link EditorKit}. This <code>EditorKit</code>
+ * a plain text <code>Document</code> and several commands that together
+ * make up a basic editor, like cut / copy + paste.
+ *
+ * @author original author unknown
+ * @author Roman Kennke (roman@kennke.org)
+ * @author Robert Schuster (robertschuster@fsfe.org)
+ */
+public class DefaultEditorKit extends EditorKit
+{
+ static class SelectionPreviousWordAction
+ extends TextAction
+ {
+ SelectionPreviousWordAction()
+ {
+ super(selectionPreviousWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getPreviousWord(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class SelectionNextWordAction
+ extends TextAction
+ {
+ SelectionNextWordAction()
+ {
+ super(selectionNextWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getNextWord(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class SelectionBeginWordAction extends TextAction
+ {
+ SelectionBeginWordAction()
+ {
+ super(selectionBeginWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordStart(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class SelectionEndWordAction extends TextAction
+ {
+ SelectionEndWordAction()
+ {
+ super(selectionEndWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordEnd(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class BeginWordAction extends TextAction
+ {
+ BeginWordAction()
+ {
+ super(beginWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordStart(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class EndWordAction extends TextAction
+ {
+ EndWordAction()
+ {
+ super(endWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getWordEnd(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class PreviousWordAction
+ extends TextAction
+ {
+ PreviousWordAction()
+ {
+ super(previousWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getPreviousWord(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class NextWordAction
+ extends TextAction
+ {
+ NextWordAction()
+ {
+ super(nextWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ JTextComponent t = getTextComponent(event);
+
+ if (t != null)
+ {
+ int offs = Utilities.getNextWord(t, t.getCaretPosition());
+
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+
+ static class SelectAllAction
+ extends TextAction
+ {
+ SelectAllAction()
+ {
+ super(selectAllAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ int offs = t.getDocument().getLength();
+ Caret c = t.getCaret();
+ c.setDot(0);
+ c.moveDot(offs);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class SelectionBeginAction
+ extends TextAction
+ {
+ SelectionBeginAction()
+ {
+ super(selectionBeginAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ c.moveDot(0);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(0).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class SelectionEndAction
+ extends TextAction
+ {
+ SelectionEndAction()
+ {
+ super(selectionEndAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ int offs = t.getDocument().getLength();
+ Caret c = t.getCaret();
+ c.moveDot(offs);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class SelectionBeginLineAction
+ extends TextAction
+ {
+
+ SelectionBeginLineAction()
+ {
+ super(selectionBeginLineAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ try
+ {
+ int offs = Utilities.getRowStart(t, c.getDot());
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class SelectionEndLineAction
+ extends TextAction
+ {
+ SelectionEndLineAction()
+ {
+ super(selectionEndLineAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ try
+ {
+ int offs = Utilities.getRowEnd(t, c.getDot());
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class SelectLineAction extends TextAction
+ {
+ SelectLineAction()
+ {
+ super(selectLineAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ try
+ {
+ int offs1 = Utilities.getRowStart(t, c.getDot());
+ int offs2 = Utilities.getRowEnd(t, c.getDot());
+ c.setDot(offs2);
+ c.moveDot(offs1);
+ c.setMagicCaretPosition(t.modelToView(offs2).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class SelectWordAction extends TextAction
+ {
+ SelectWordAction()
+ {
+ super(selectWordAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ int dot = c.getDot();
+ try
+ {
+ int wordStart = Utilities.getWordStart(t, dot);
+
+ if (dot == wordStart)
+ {
+ // Current cursor position is on the first character in a word.
+ c.setDot(wordStart);
+ c.moveDot(Utilities.getWordEnd(t, wordStart));
+ }
+ else
+ {
+ // Current cursor position is not on the first character
+ // in a word.
+ int nextWord = Utilities.getNextWord(t, dot);
+ int previousWord = Utilities.getPreviousWord(t, dot);
+ int previousWordEnd = Utilities.getWordEnd(t, previousWord);
+
+ // Cursor position is in the space between two words. In such a
+ // situation just select the space.
+ if (dot >= previousWordEnd && dot <= nextWord)
+ {
+ c.setDot(previousWordEnd);
+ c.moveDot(nextWord);
+ }
+ else
+ {
+ // Cursor position is inside a word. Just select it then.
+ c.setDot(previousWord);
+ c.moveDot(previousWordEnd);
+ }
+ }
+
+ // If the position was updated change the magic caret position
+ // as well.
+ if (c.getDot() != dot)
+ c.setMagicCaretPosition(t.modelToView(c.getDot()).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class SelectionDownAction
+ extends TextAction.VerticalMovementAction
+ {
+ SelectionDownAction()
+ {
+ super(selectionDownAction, SwingConstants.SOUTH);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.moveDot(offs);
+ }
+
+ }
+
+ static class SelectionUpAction
+ extends TextAction.VerticalMovementAction
+ {
+ SelectionUpAction()
+ {
+ super(selectionUpAction, SwingConstants.NORTH);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.moveDot(offs);
+ }
+
+ }
+
+ static class SelectionForwardAction
+ extends TextAction.HorizontalMovementAction
+ {
+ SelectionForwardAction()
+ {
+ super(selectionForwardAction, SwingConstants.EAST);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.moveDot(offs);
+ }
+ }
+
+ static class SelectionBackwardAction
+ extends TextAction.HorizontalMovementAction
+ {
+ SelectionBackwardAction()
+ {
+ super(selectionBackwardAction, SwingConstants.WEST);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.moveDot(offs);
+ }
+ }
+
+ static class DownAction
+ extends TextAction.VerticalMovementAction
+ {
+ DownAction()
+ {
+ super(downAction, SwingConstants.SOUTH);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.setDot(offs);
+ }
+ }
+
+ static class UpAction
+ extends TextAction.VerticalMovementAction
+ {
+ UpAction()
+ {
+ super(upAction, SwingConstants.NORTH);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.setDot(offs);
+ }
+
+ }
+
+ static class ForwardAction
+ extends TextAction.HorizontalMovementAction
+ {
+ ForwardAction()
+ {
+ super(forwardAction, SwingConstants.EAST);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.setDot(offs);
+ }
+
+ }
+
+ static class BackwardAction
+ extends TextAction.HorizontalMovementAction
+ {
+ BackwardAction()
+ {
+ super(backwardAction, SwingConstants.WEST);
+ }
+
+ protected void actionPerformedImpl(Caret c, int offs)
+ {
+ c.setDot(offs);
+ }
+
+ }
+
+ static class DeletePrevCharAction
+ extends TextAction
+ {
+ DeletePrevCharAction()
+ {
+ super(deletePrevCharAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ try
+ {
+ int pos = t.getSelectionStart();
+ int len = t.getSelectionEnd() - pos;
+
+ if (len > 0)
+ t.getDocument().remove(pos, len);
+ else if (pos > 0)
+ {
+ pos--;
+ t.getDocument().remove(pos, 1);
+ Caret c = t.getCaret();
+ c.setDot(pos);
+ c.setMagicCaretPosition(t.modelToView(pos).getLocation());
+ }
+ }
+ catch (BadLocationException e)
+ {
+ // FIXME: we're not authorized to throw this.. swallow it?
+ }
+ }
+ }
+ }
+
+ static class DeleteNextCharAction
+ extends TextAction
+ {
+ DeleteNextCharAction()
+ {
+ super(deleteNextCharAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ try
+ {
+ int pos = t.getSelectionStart();
+ int len = t.getSelectionEnd() - pos;
+
+ if (len > 0)
+ t.getDocument().remove(pos, len);
+ else if (pos < t.getDocument().getLength())
+ t.getDocument().remove(pos, 1);
+
+ Caret c = t.getCaret();
+ c.setDot(pos);
+ c.setMagicCaretPosition(t.modelToView(pos).getLocation());
+ }
+ catch (BadLocationException e)
+ {
+ // FIXME: we're not authorized to throw this.. swallow it?
+ }
+ }
+ }
+ }
+
+ static class EndLineAction
+ extends TextAction
+ {
+ EndLineAction()
+ {
+ super(endLineAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ try
+ {
+ int offs = Utilities.getRowEnd(t, t.getCaretPosition());
+ if (offs > -1)
+ {
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch (BadLocationException ble)
+ {
+ // Nothing to do here
+ }
+ }
+ }
+ }
+
+ static class BeginLineAction
+ extends TextAction
+ {
+ BeginLineAction()
+ {
+ super(beginLineAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ try
+ {
+ int offs = Utilities.getRowStart(t, t.getCaretPosition());
+ if (offs > -1)
+ {
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch (BadLocationException ble)
+ {
+ // Do nothing here.
+ }
+ }
+ }
+ }
+
+ static class BeginAction extends TextAction
+ {
+
+ BeginAction()
+ {
+ super(beginAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ c.setDot(0);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(0).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ static class EndAction extends TextAction
+ {
+
+ EndAction()
+ {
+ super(endAction);
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ {
+ int offs = t.getDocument().getLength();
+ Caret c = t.getCaret();
+ c.setDot(offs);
+ try
+ {
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ catch(BadLocationException ble)
+ {
+ // Can't happen.
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a beep on the PC speaker.
+ *
+ * @see Toolkit#beep()
+ */
+ public static class BeepAction extends TextAction
+ {
+ /**
+ * Creates a new <code>BeepAction</code>.
+ */
+ public BeepAction()
+ {
+ super(beepAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ Toolkit.getDefaultToolkit().beep();
+ }
+ }
+
+ /**
+ * Copies the selected content into the system clipboard.
+ *
+ * @see Toolkit#getSystemClipboard()
+ * @see CutAction
+ * @see PasteAction
+ */
+ public static class CopyAction extends TextAction
+ {
+
+ /**
+ * Create a new <code>CopyAction</code>.
+ */
+ public CopyAction()
+ {
+ super(copyAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent target = getTextComponent(event);
+ if (target != null)
+ target.copy();
+ }
+ }
+
+
+ /**
+ * Copies the selected content into the system clipboard and deletes the
+ * selection.
+ *
+ * @see Toolkit#getSystemClipboard()
+ * @see CopyAction
+ * @see PasteAction
+ */
+ public static class CutAction extends TextAction
+ {
+
+ /**
+ * Create a new <code>CutAction</code>.
+ */
+ public CutAction()
+ {
+ super(cutAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent target = getTextComponent(event);
+ if (target != null)
+ target.cut();
+ }
+ }
+
+ /**
+ * Copies content from the system clipboard into the editor.
+ *
+ * @see Toolkit#getSystemClipboard()
+ * @see CopyAction
+ * @see CutAction
+ */
+ public static class PasteAction extends TextAction
+ {
+
+ /**
+ * Create a new <code>PasteAction</code>.
+ */
+ public PasteAction()
+ {
+ super(pasteAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent target = getTextComponent(event);
+ if (target != null)
+ target.paste();
+ }
+ }
+
+ /**
+ * This action is executed as default action when a KEY_TYPED
+ * event is received and no keymap entry exists for that. The purpose
+ * of this action is to filter out a couple of characters. This includes
+ * the control characters and characters with the ALT-modifier.
+ *
+ * If an event does not get filtered, it is inserted into the document
+ * of the text component. If there is some text selected in the text
+ * component, this text will be replaced.
+ */
+ public static class DefaultKeyTypedAction
+ extends TextAction
+ {
+
+ /**
+ * Creates a new <code>DefaultKeyTypedAction</code>.
+ */
+ public DefaultKeyTypedAction()
+ {
+ super(defaultKeyTypedAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ // first we filter the following events:
+ // - control characters
+ // - key events with the ALT modifier
+ JTextComponent target = getTextComponent(event);
+ if ((target != null) && (event != null))
+ {
+ if ((target.isEditable()) && (target.isEnabled()))
+ {
+ String content = event.getActionCommand();
+ int mod = event.getModifiers();
+ if ((content != null) && (content.length() > 0)
+ && (mod & ActionEvent.ALT_MASK) == 0
+ && (mod & ActionEvent.CTRL_MASK) == 0)
+ {
+ char c = content.charAt(0);
+ if ((c >= 0x20) && (c != 0x7F))
+ {
+ target.replaceSelection(content);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This action inserts a newline character into the document
+ * of the text component. This is typically triggered by hitting
+ * ENTER on the keyboard.
+ */
+ public static class InsertBreakAction extends TextAction
+ {
+
+ /**
+ * Creates a new <code>InsertBreakAction</code>.
+ */
+ public InsertBreakAction()
+ {
+ super(insertBreakAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ t.replaceSelection("\n");
+ }
+ }
+
+ /**
+ * Places content into the associated editor. If there currently is a
+ * selection, this selection is replaced.
+ */
+ // FIXME: Figure out what this Action is supposed to do. Obviously text
+ // that is entered by the user is inserted through DefaultKeyTypedAction.
+ public static class InsertContentAction extends TextAction
+ {
+
+ /**
+ * Creates a new <code>InsertContentAction</code>.
+ */
+ public InsertContentAction()
+ {
+ super(insertContentAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ // FIXME: Figure out what this Action is supposed to do. Obviously text
+ // that is entered by the user is inserted through DefaultKeyTypedAction.
+ }
+ }
+
+ /**
+ * Inserts a TAB character into the text editor.
+ */
+ public static class InsertTabAction extends TextAction
+ {
+
+ /**
+ * Creates a new <code>TabAction</code>.
+ */
+ public InsertTabAction()
+ {
+ super(insertTabAction);
+ }
+
+ /**
+ * Performs the <code>Action</code>.
+ *
+ * @param event the action event describing the user action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ if (t != null)
+ t.replaceSelection("\t");
+ }
+ }
+
+ /**
+ * The serial version of DefaultEditorKit.
+ */
+ private static final long serialVersionUID = 9017245433028523428L;
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one character
+ * backwards.
+ *
+ * @see #getActions()
+ */
+ public static final String backwardAction = "caret-backward";
+
+ /**
+ * The name of the <code>Action</code> that creates a beep in the speaker.
+ *
+ * @see #getActions()
+ */
+ public static final String beepAction = "beep";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the <code>Document</code>.
+ *
+ * @see #getActions()
+ */
+ public static final String beginAction = "caret-begin";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the current line.
+ *
+ * @see #getActions()
+ */
+ public static final String beginLineAction = "caret-begin-line";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the current paragraph.
+ *
+ * @see #getActions()
+ */
+ public static final String beginParagraphAction = "caret-begin-paragraph";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the current word.
+ *
+ * @see #getActions()
+ */
+ public static final String beginWordAction = "caret-begin-word";
+
+ /**
+ * The name of the <code>Action</code> that copies the selected content
+ * into the system clipboard.
+ *
+ * @see #getActions()
+ */
+ public static final String copyAction = "copy-to-clipboard";
+
+ /**
+ * The name of the <code>Action</code> that copies the selected content
+ * into the system clipboard and removes the selection.
+ *
+ * @see #getActions()
+ */
+ public static final String cutAction = "cut-to-clipboard";
+
+ /**
+ * The name of the <code>Action</code> that is performed by default if
+ * a key is typed and there is no keymap entry.
+ *
+ * @see #getActions()
+ */
+ public static final String defaultKeyTypedAction = "default-typed";
+
+ /**
+ * The name of the <code>Action</code> that deletes the character that
+ * follows the current caret position.
+ *
+ * @see #getActions()
+ */
+ public static final String deleteNextCharAction = "delete-next";
+
+ /**
+ * The name of the <code>Action</code> that deletes the character that
+ * precedes the current caret position.
+ *
+ * @see #getActions()
+ */
+ public static final String deletePrevCharAction = "delete-previous";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one line down.
+ *
+ * @see #getActions()
+ */
+ public static final String downAction = "caret-down";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the <code>Document</code>.
+ *
+ * @see #getActions()
+ */
+ public static final String endAction = "caret-end";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the current line.
+ *
+ * @see #getActions()
+ */
+ public static final String endLineAction = "caret-end-line";
+
+ /**
+ * When a document is read and an CRLF is encountered, then we add a property
+ * with this name and a value of &quot;\r\n&quot;.
+ */
+ public static final String EndOfLineStringProperty = "__EndOfLine__";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the current paragraph.
+ *
+ * @see #getActions()
+ */
+ public static final String endParagraphAction = "caret-end-paragraph";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the current word.
+ *
+ * @see #getActions()
+ */
+ public static final String endWordAction = "caret-end-word";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one character
+ * forward.
+ *
+ * @see #getActions()
+ */
+ public static final String forwardAction = "caret-forward";
+
+ /**
+ * The name of the <code>Action</code> that inserts a line break.
+ *
+ * @see #getActions()
+ */
+ public static final String insertBreakAction = "insert-break";
+
+ /**
+ * The name of the <code>Action</code> that inserts some content.
+ *
+ * @see #getActions()
+ */
+ public static final String insertContentAction = "insert-content";
+
+ /**
+ * The name of the <code>Action</code> that inserts a TAB.
+ *
+ * @see #getActions()
+ */
+ public static final String insertTabAction = "insert-tab";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the next word.
+ *
+ * @see #getActions()
+ */
+ public static final String nextWordAction = "caret-next-word";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one page down.
+ *
+ * @see #getActions()
+ */
+ public static final String pageDownAction = "page-down";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one page up.
+ *
+ * @see #getActions()
+ */
+ public static final String pageUpAction = "page-up";
+
+ /**
+ * The name of the <code>Action</code> that copies content from the system
+ * clipboard into the document.
+ *
+ * @see #getActions()
+ */
+ public static final String pasteAction = "paste-from-clipboard";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the previous word.
+ *
+ * @see #getActions()
+ */
+ public static final String previousWordAction = "caret-previous-word";
+
+ /**
+ * The name of the <code>Action</code> that sets the editor in read only
+ * mode.
+ *
+ * @see #getActions()
+ */
+ public static final String readOnlyAction = "set-read-only";
+
+ /**
+ * The name of the <code>Action</code> that selects the whole document.
+ *
+ * @see #getActions()
+ */
+ public static final String selectAllAction = "select-all";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one character
+ * backwards, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionBackwardAction = "selection-backward";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the document, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionBeginAction = "selection-begin";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the current line, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionBeginLineAction = "selection-begin-line";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the current paragraph, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionBeginParagraphAction =
+ "selection-begin-paragraph";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the current word, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionBeginWordAction = "selection-begin-word";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one line down,
+ * possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionDownAction = "selection-down";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the document, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionEndAction = "selection-end";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the current line, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionEndLineAction = "selection-end-line";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the current paragraph, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionEndParagraphAction =
+ "selection-end-paragraph";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the end
+ * of the current word, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionEndWordAction = "selection-end-word";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one character
+ * forwards, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionForwardAction = "selection-forward";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the next word, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionNextWordAction = "selection-next-word";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret to the beginning
+ * of the previous word, possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionPreviousWordAction =
+ "selection-previous-word";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one line up,
+ * possibly extending the current selection.
+ *
+ * @see #getActions()
+ */
+ public static final String selectionUpAction = "selection-up";
+
+ /**
+ * The name of the <code>Action</code> that selects the line around the
+ * caret.
+ *
+ * @see #getActions()
+ */
+ public static final String selectLineAction = "select-line";
+
+ /**
+ * The name of the <code>Action</code> that selects the paragraph around the
+ * caret.
+ *
+ * @see #getActions()
+ */
+ public static final String selectParagraphAction = "select-paragraph";
+
+ /**
+ * The name of the <code>Action</code> that selects the word around the
+ * caret.
+ *
+ * @see #getActions()
+ */
+ public static final String selectWordAction = "select-word";
+
+ /**
+ * The name of the <code>Action</code> that moves the caret one line up.
+ *
+ * @see #getActions()
+ */
+ public static final String upAction = "caret-up";
+
+ /**
+ * The name of the <code>Action</code> that sets the editor in read-write
+ * mode.
+ *
+ * @see #getActions()
+ */
+ public static final String writableAction = "set-writable";
+
+ /**
+ * Creates a new <code>DefaultEditorKit</code>.
+ */
+ public DefaultEditorKit()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * The <code>Action</code>s that are supported by the
+ * <code>DefaultEditorKit</code>.
+ */
+ private static Action[] defaultActions =
+ new Action[] {
+ // These classes are public because they are so in the RI.
+ new BeepAction(),
+ new CopyAction(),
+ new CutAction(),
+ new DefaultKeyTypedAction(),
+ new InsertBreakAction(),
+ new InsertContentAction(),
+ new InsertTabAction(),
+ new PasteAction(),
+
+ // These are (package-)private inner classes.
+ new DeleteNextCharAction(),
+ new DeletePrevCharAction(),
+
+ new BeginLineAction(),
+ new SelectionBeginLineAction(),
+
+ new EndLineAction(),
+ new SelectionEndLineAction(),
+
+ new BackwardAction(),
+ new SelectionBackwardAction(),
+
+ new ForwardAction(),
+ new SelectionForwardAction(),
+
+ new UpAction(),
+ new SelectionUpAction(),
+
+ new DownAction(),
+ new SelectionDownAction(),
+
+ new NextWordAction(),
+ new SelectionNextWordAction(),
+
+ new PreviousWordAction(),
+ new SelectionPreviousWordAction(),
+
+ new BeginAction(),
+ new SelectionBeginAction(),
+
+ new EndAction(),
+ new SelectionEndAction(),
+
+ new BeginWordAction(),
+ new SelectionBeginWordAction(),
+
+ new EndWordAction(),
+ new SelectionEndWordAction(),
+
+ new SelectAllAction(),
+ new SelectLineAction(),
+ new SelectWordAction()
+ };
+
+ /**
+ * Creates the <code>Caret</code> for this <code>EditorKit</code>. This
+ * returns a {@link DefaultCaret} in this case.
+ *
+ * @return the <code>Caret</code> for this <code>EditorKit</code>
+ */
+ public Caret createCaret()
+ {
+ return new DefaultCaret();
+ }
+
+ /**
+ * Creates the default {@link Document} that this <code>EditorKit</code>
+ * supports. This is a {@link PlainDocument} in this case.
+ *
+ * @return the default {@link Document} that this <code>EditorKit</code>
+ * supports
+ */
+ public Document createDefaultDocument()
+ {
+ return new PlainDocument();
+ }
+
+ /**
+ * Returns the <code>Action</code>s supported by this <code>EditorKit</code>.
+ *
+ * @return the <code>Action</code>s supported by this <code>EditorKit</code>
+ */
+ public Action[] getActions()
+ {
+ return defaultActions;
+ }
+
+ /**
+ * Returns the content type that this <code>EditorKit</code> supports.
+ * The <code>DefaultEditorKit</code> supports the content type
+ * <code>text/plain</code>.
+ *
+ * @return the content type that this <code>EditorKit</code> supports
+ */
+ public String getContentType()
+ {
+ return "text/plain";
+ }
+
+ /**
+ * Returns a {@link ViewFactory} that is able to create {@link View}s for
+ * the <code>Element</code>s that are used in this <code>EditorKit</code>'s
+ * model. This returns null which lets the UI of the text component supply
+ * <code>View</code>s.
+ *
+ * @return a {@link ViewFactory} that is able to create {@link View}s for
+ * the <code>Element</code>s that are used in this
+ * <code>EditorKit</code>'s model
+ */
+ public ViewFactory getViewFactory()
+ {
+ return null;
+ }
+
+ /**
+ * Reads a document of the supported content type from an {@link InputStream}
+ * into the actual {@link Document} object.
+ *
+ * @param in the stream from which to read the document
+ * @param document the document model into which the content is read
+ * @param offset the offset inside to document where the content is inserted
+ *
+ * @throws BadLocationException if <code>offset</code> is an invalid location
+ * inside <code>document</code>
+ * @throws IOException if something goes wrong while reading from
+ * <code>in</code>
+ */
+ public void read(InputStream in, Document document, int offset)
+ throws BadLocationException, IOException
+ {
+ read(new InputStreamReader(in), document, offset);
+ }
+
+ /**
+ * Reads a document of the supported content type from a {@link Reader}
+ * into the actual {@link Document} object.
+ *
+ * @param in the reader from which to read the document
+ * @param document the document model into which the content is read
+ * @param offset the offset inside to document where the content is inserted
+ *
+ * @throws BadLocationException if <code>offset</code> is an invalid location
+ * inside <code>document</code>
+ * @throws IOException if something goes wrong while reading from
+ * <code>in</code>
+ */
+ public void read(Reader in, Document document, int offset)
+ throws BadLocationException, IOException
+ {
+ BufferedReader reader = new BufferedReader(in);
+
+ String line;
+ CPStringBuilder content = new CPStringBuilder();
+
+ while ((line = reader.readLine()) != null)
+ {
+ content.append(line);
+ content.append("\n");
+ }
+
+ document.insertString(offset, content.substring(0, content.length() - 1),
+ SimpleAttributeSet.EMPTY);
+ }
+
+ /**
+ * Writes the <code>Document</code> (or a fragment of the
+ * <code>Document</code>) to an {@link OutputStream} in the
+ * supported content type format.
+ *
+ * @param out the stream to write to
+ * @param document the document that should be written out
+ * @param offset the beginning offset from where to write
+ * @param len the length of the fragment to write
+ *
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + len</code>is an invalid location inside
+ * <code>document</code>
+ * @throws IOException if something goes wrong while writing to
+ * <code>out</code>
+ */
+ public void write(OutputStream out, Document document, int offset, int len)
+ throws BadLocationException, IOException
+ {
+ write(new OutputStreamWriter(out), document, offset, len);
+ }
+
+ /**
+ * Writes the <code>Document</code> (or a fragment of the
+ * <code>Document</code>) to a {@link Writer} in the
+ * supported content type format.
+ *
+ * @param out the writer to write to
+ * @param document the document that should be written out
+ * @param offset the beginning offset from where to write
+ * @param len the length of the fragment to write
+ *
+ * @throws BadLocationException if <code>offset</code> is an
+ * invalid location inside <code>document</code>.
+ * @throws IOException if something goes wrong while writing to
+ * <code>out</code>
+ */
+ public void write(Writer out, Document document, int offset, int len)
+ throws BadLocationException, IOException
+ {
+ // Throw a BLE if offset is invalid
+ if (offset < 0 || offset > document.getLength())
+ throw new BadLocationException("Tried to write to invalid location",
+ offset);
+
+ // If they gave an overly large len, just adjust it
+ if (offset + len > document.getLength())
+ len = document.getLength() - offset;
+
+ out.write(document.getText(offset, len));
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/DefaultFormatter.java b/libjava/classpath/javax/swing/text/DefaultFormatter.java
new file mode 100644
index 000000000..cd9829d11
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DefaultFormatter.java
@@ -0,0 +1,433 @@
+/* DefaultFormatter.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.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.text.ParseException;
+
+import javax.swing.JFormattedTextField;
+
+/**
+ * The <code>DefaultFormatter</code> is a concrete formatter for use in
+ * {@link JFormattedTextField}s.
+ *
+ * It can format arbitrary values by invoking
+ * their {@link Object#toString} method.
+ *
+ * In order to convert a String back to
+ * a value, the value class must provide a single argument constructor that
+ * takes a String object as argument value. If no such constructor is found,
+ * the String itself is passed back by #stringToValue.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
+ implements Cloneable, Serializable
+{
+
+ /**
+ * A {@link DocumentFilter} that intercepts modification of the
+ * JFormattedTextField's Document and commits the value depending
+ * on the value of the <code>commitsOnValidEdit</code> property.
+ *
+ */
+ // FIXME: Handle allowsInvalid and overwriteMode properties
+ private class FormatterDocumentFilter
+ extends DocumentFilter
+ {
+ /**
+ * Invoked when text is removed from a text component.
+ *
+ * @param bypass the FilterBypass to use to mutate the document
+ * @param offset the start position of the modification
+ * @param length the length of the removed text
+ *
+ * @throws BadLocationException if offset or lenght are invalid in
+ * the Document
+ */
+ public void remove(DocumentFilter.FilterBypass bypass, int offset,
+ int length)
+ throws BadLocationException
+ {
+ super.remove(bypass, offset, length);
+ checkValidInput();
+ commitIfAllowed();
+ }
+
+ /**
+ * Invoked when text is inserted into a text component.
+ *
+ * @param bypass the FilterBypass to use to mutate the document
+ * @param offset the start position of the modification
+ * @param text the inserted text
+ * @param attributes the attributes of the inserted text
+ *
+ * @throws BadLocationException if offset or lenght are invalid in
+ * the Document
+ */
+ public void insertString(DocumentFilter.FilterBypass bypass, int offset,
+ String text, AttributeSet attributes)
+ throws BadLocationException
+ {
+ if (overwriteMode == true)
+ replace(bypass, offset, text.length(), text, attributes);
+ else
+ super.insertString(bypass, offset, text, attributes);
+ checkValidInput();
+ commitIfAllowed();
+ }
+
+ /**
+ * Invoked when text is replaced in a text component.
+ *
+ * @param bypass the FilterBypass to use to mutate the document
+ * @param offset the start position of the modification
+ * @param length the length of the removed text
+ * @param text the inserted text
+ * @param attributes the attributes of the inserted text
+ *
+ * @throws BadLocationException if offset or lenght are invalid in
+ * the Document
+ */
+ public void replace(DocumentFilter.FilterBypass bypass, int offset,
+ int length, String text, AttributeSet attributes)
+ throws BadLocationException
+ {
+ super.replace(bypass, offset, length, text, attributes);
+ checkValidInput();
+ commitIfAllowed();
+ }
+
+ /**
+ * Commits the value to the JTextTextField if the property
+ * <code>commitsOnValidEdit</code> is set to <code>true</code>.
+ */
+ private void commitIfAllowed()
+ {
+ if (commitsOnValidEdit == true)
+ try
+ {
+ getFormattedTextField().commitEdit();
+ }
+ catch (ParseException ex)
+ {
+ // ignore invalid edits
+ }
+ }
+
+ /**
+ * Checks if the value in the input field is valid. If the
+ * property allowsInvalid is set to <code>false</code>, then
+ * the string in the input field is not allowed to be entered.
+ */
+ private void checkValidInput()
+ {
+ JFormattedTextField ftf = getFormattedTextField();
+ try
+ {
+ Object newval = stringToValue(ftf.getText());
+ }
+ catch (ParseException ex)
+ {
+ if (!allowsInvalid)
+ {
+ // roll back the input if invalid edits are not allowed
+ try
+ {
+ ftf.setText(valueToString(ftf.getValue()));
+ }
+ catch (ParseException pe)
+ {
+ // if that happens, something serious must be wrong
+ AssertionError ae;
+ ae = new AssertionError("values must be parseable");
+ ae.initCause(pe);
+ throw ae;
+ }
+ }
+ }
+ }
+ }
+
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -355018354457785329L;
+
+ /**
+ * Indicates if the value should be committed after every
+ * valid modification of the Document.
+ */
+ boolean commitsOnValidEdit;
+
+ /**
+ * If <code>true</code> newly inserted characters overwrite existing
+ * values, otherwise insertion is done the normal way.
+ */
+ boolean overwriteMode;
+
+ /**
+ * If <code>true</code> invalid edits are allowed for a limited
+ * time.
+ */
+ boolean allowsInvalid;
+
+ /**
+ * The class that is used for values.
+ */
+ Class valueClass;
+
+ /**
+ * Creates a new instance of <code>DefaultFormatter</code>.
+ */
+ public DefaultFormatter()
+ {
+ commitsOnValidEdit = false;
+ overwriteMode = true;
+ allowsInvalid = true;
+ }
+
+ /**
+ * Installs the formatter on the specified {@link JFormattedTextField}.
+ *
+ * This method does the following things:
+ * <ul>
+ * <li>Display the value of #valueToString in the
+ * <code>JFormattedTextField</code></li>
+ * <li>Install the Actions from #getActions on the <code>JTextField</code>
+ * </li>
+ * <li>Install the DocumentFilter returned by #getDocumentFilter</li>
+ * <li>Install the NavigationFilter returned by #getNavigationFilter</li>
+ * </ul>
+ *
+ * This method is typically not overridden by subclasses. Instead override
+ * one of the mentioned methods in order to customize behaviour.
+ *
+ * @param ftf the {@link JFormattedTextField} in which this formatter
+ * is installed
+ */
+ public void install(JFormattedTextField ftf)
+ {
+ super.install(ftf);
+ }
+
+ /**
+ * Returns <code>true</code> if the value should be committed after
+ * each valid modification of the input field, <code>false</code> if
+ * it should never be committed by this formatter.
+ *
+ * @return the state of the <code>commitsOnValidEdit</code> property
+ *
+ * @see #setCommitsOnValidEdit
+ */
+ public boolean getCommitsOnValidEdit()
+ {
+ return commitsOnValidEdit;
+ }
+
+ /**
+ * Sets the value of the <code>commitsOnValidEdit</code> property.
+ *
+ * @param commitsOnValidEdit the new state of the
+ * <code>commitsOnValidEdit</code> property
+ *
+ * @see #getCommitsOnValidEdit
+ */
+ public void setCommitsOnValidEdit(boolean commitsOnValidEdit)
+ {
+ this.commitsOnValidEdit = commitsOnValidEdit;
+ }
+
+ /**
+ * Returns the value of the <code>overwriteMode</code> property.
+ * If that is set to <code>true</code> then newly inserted characters
+ * overwrite existing values, otherwise the characters are inserted like
+ * normal. The default is <code>true</code>.
+ *
+ * @return the value of the <code>overwriteMode</code> property
+ */
+ public boolean getOverwriteMode()
+ {
+ return overwriteMode;
+ }
+
+ /**
+ * Sets the value of the <code>overwriteMode</code> property.
+ *
+ * If that is set to <code>true</code> then newly inserted characters
+ * overwrite existing values, otherwise the characters are inserted like
+ * normal. The default is <code>true</code>.
+ *
+ * @param overwriteMode the new value for the <code>overwriteMode</code>
+ * property
+ */
+ public void setOverwriteMode(boolean overwriteMode)
+ {
+ this.overwriteMode = overwriteMode;
+ }
+
+ /**
+ * Returns whether or not invalid edits are allowed or not. If invalid
+ * edits are allowed, the JFormattedTextField may temporarily contain invalid
+ * characters.
+ *
+ * @return the value of the allowsInvalid property
+ */
+ public boolean getAllowsInvalid()
+ {
+ return allowsInvalid;
+ }
+
+ /**
+ * Sets the value of the <code>allowsInvalid</code> property.
+ *
+ * @param allowsInvalid the new value for the property
+ *
+ * @see #getAllowsInvalid()
+ */
+ public void setAllowsInvalid(boolean allowsInvalid)
+ {
+ this.allowsInvalid = allowsInvalid;
+ }
+
+ /**
+ * Returns the class that is used for values. When Strings are converted
+ * back to values, this class is used to create new value objects.
+ *
+ * @return the class that is used for values
+ */
+ public Class<?> getValueClass()
+ {
+ return valueClass;
+ }
+
+ /**
+ * Sets the class that is used for values.
+ *
+ * @param valueClass the class that is used for values
+ *
+ * @see #getValueClass()
+ */
+ public void setValueClass(Class<?> valueClass)
+ {
+ this.valueClass = valueClass;
+ }
+
+ /**
+ * Converts a String (from the JFormattedTextField input) to a value.
+ * In order to achieve this, the formatter tries to instantiate an object
+ * of the class returned by #getValueClass() using a single argument
+ * constructor that takes a String argument. If such a constructor cannot
+ * be found, the String itself is returned.
+ *
+ * @param string the string to convert
+ *
+ * @return the value for the string
+ *
+ * @throws ParseException if the string cannot be converted into
+ * a value object (e.g. invalid input)
+ */
+ public Object stringToValue(String string)
+ throws ParseException
+ {
+ Object value = string;
+ Class valueClass = getValueClass();
+ if (valueClass == null)
+ {
+ JFormattedTextField jft = getFormattedTextField();
+ if (jft != null)
+ valueClass = jft.getValue().getClass();
+ }
+ if (valueClass != null)
+ try
+ {
+ Constructor constr = valueClass.getConstructor
+ (new Class[]{String.class});
+ value = constr.newInstance(new Object[]{ string });
+ }
+ catch (NoSuchMethodException ex)
+ {
+ // leave value as string
+ }
+ catch (Exception ex)
+ {
+ throw new ParseException(string, 0);
+ }
+ return value;
+ }
+
+ /**
+ * Converts a value object into a String. This is done by invoking the
+ * {@link Object#toString()} method on the value.
+ *
+ * @param value the value to be converted
+ *
+ * @return the string representation of the value
+ *
+ * @throws ParseException if the value cannot be converted
+ */
+ public String valueToString(Object value)
+ throws ParseException
+ {
+ if (value == null)
+ return "";
+ return value.toString();
+ }
+
+ /**
+ * Creates and returns a clone of this DefaultFormatter.
+ *
+ * @return a clone of this object
+ *
+ * @throws CloneNotSupportedException not thrown here
+ */
+ public Object clone()
+ throws CloneNotSupportedException
+ {
+ return super.clone();
+ }
+
+ /**
+ * Returns the DocumentFilter that is used to restrict input.
+ *
+ * @return the DocumentFilter that is used to restrict input
+ */
+ protected DocumentFilter getDocumentFilter()
+ {
+ return new FormatterDocumentFilter();
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/DefaultFormatterFactory.java b/libjava/classpath/javax/swing/text/DefaultFormatterFactory.java
new file mode 100644
index 000000000..a6a9defaf
--- /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 <code>nullFormatter</code>
+ * 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
+ * <code>nullFormatter</code> 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 <code>nullFormatter</code> 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 <code>nullFormatter</code> 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
+ * <code>nullFormatter</code> 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 <code>nullFormatter</code> 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
+ * <code>nullFormatter</code> 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 <code>displayFormatter</code>. 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 <code>nullFormatter<code> 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 <code>nullFormatter<code> has been
+ * specified.
+ */
+ public AbstractFormatter getDisplayFormatter()
+ {
+ return displayFormatter;
+ }
+
+ /**
+ * Sets the <code>displayFormatter</code>. 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 <code>nullFormatter<code> has been
+ * specified.
+ * @param displayFormatter the formatter to use.
+ */
+ public void setDisplayFormatter(AbstractFormatter displayFormatter)
+ {
+ this.displayFormatter = displayFormatter;
+ }
+
+ /**
+ * Gets the <code>editFormatter</code>. 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 <code>nullFormatter<code> 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 <code>editFormatter</code>. 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 <code>nullFormatter<code> 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 <code>nullFormatter</code>. 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
+ * <code>tf</code>. If <code>tf<code> is null we return null, otherwise
+ * we return one of the following:
+ * 1. Returns <code>nullFormatter</code> if <code>tf.getValue()</code> is
+ * null and <code>nullFormatter</code> is not.
+ * 2. Returns <code>editFormatter</code> if <code>tf.hasFocus()</code> is
+ * true and <code>editFormatter</code> is not null.
+ * 3. Returns <code>displayFormatter</code> if <code>tf.hasFocus()</code> is
+ * false and <code>displayFormatter</code> is not null.
+ * 4. Otherwise returns <code>defaultFormatter</code>.
+ */
+ 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/DefaultHighlighter.java b/libjava/classpath/javax/swing/text/DefaultHighlighter.java
new file mode 100644
index 000000000..a4264d31a
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DefaultHighlighter.java
@@ -0,0 +1,478 @@
+/* DefaultHighlighter.java -- The default highlight for Swing
+ Copyright (C) 2004, 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.Color;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.swing.SwingUtilities;
+import javax.swing.plaf.TextUI;
+
+/**
+ * The default highlight for Swing text components. It highlights text
+ * by filling the background with a rectangle.
+ */
+public class DefaultHighlighter extends LayeredHighlighter
+{
+ public static class DefaultHighlightPainter
+ extends LayerPainter
+ {
+ private Color color;
+
+ public DefaultHighlightPainter(Color c)
+ {
+ super();
+ color = c;
+ }
+
+ public Color getColor()
+ {
+ return color;
+ }
+
+ public void paint(Graphics g, int p0, int p1, Shape bounds,
+ JTextComponent t)
+ {
+ if (p0 == p1)
+ return;
+
+ Rectangle rect = bounds.getBounds();
+
+ Color col = getColor();
+ if (col == null)
+ col = t.getSelectionColor();
+ g.setColor(col);
+
+ TextUI ui = t.getUI();
+
+ try
+ {
+
+ Rectangle l0 = ui.modelToView(t, p0, null);
+ Rectangle l1 = ui.modelToView(t, p1, null);
+
+ // Note: The computed locations may lie outside of the allocation
+ // area if the text is scrolled.
+
+ if (l0.y == l1.y)
+ {
+ SwingUtilities.computeUnion(l0.x, l0.y, l0.width, l0.height, l1);
+
+ // Paint only inside the allocation area.
+ SwingUtilities.computeIntersection(rect.x, rect.y, rect.width,
+ rect.height, l1);
+
+ g.fillRect(l1.x, l1.y, l1.width, l1.height);
+ }
+ else
+ {
+ // 1. The line of p0 is painted from the position of p0
+ // to the right border.
+ // 2. All lines between the ones where p0 and p1 lie on
+ // are completely highlighted. The allocation area is used to find
+ // out the bounds.
+ // 3. The final line is painted from the left border to the
+ // position of p1.
+
+ int firstLineWidth = rect.x + rect.width - l0.x;
+ g.fillRect(l0.x, l0.y, firstLineWidth, l0.height);
+ if (l0.y + l0.height != l1.y)
+ {
+ g.fillRect(rect.x, l0.y + l0.height, rect.width,
+ l1.y - l0.y - l0.height);
+ }
+ g.fillRect(rect.x, l1.y, l1.x - rect.x, l1.height);
+ }
+ }
+ catch (BadLocationException ex)
+ {
+ // Can't render. Comment out for debugging.
+ // ex.printStackTrace();
+ }
+ }
+
+ public Shape paintLayer(Graphics g, int p0, int p1, Shape bounds,
+ JTextComponent c, View view)
+ {
+ Color col = getColor();
+ if (col == null)
+ col = c.getSelectionColor();
+ g.setColor(col);
+
+ Rectangle rect = null;
+ if (p0 == view.getStartOffset() && p1 == view.getEndOffset())
+ {
+ // Paint complete bounds region.
+ rect = bounds instanceof Rectangle ? (Rectangle) bounds
+ : bounds.getBounds();
+ }
+ else
+ {
+ // Only partly inside the view.
+ try
+ {
+ Shape s = view.modelToView(p0, Position.Bias.Forward,
+ p1, Position.Bias.Backward,
+ bounds);
+ rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
+ }
+ catch (BadLocationException ex)
+ {
+ // Can't render the highlight.
+ }
+ }
+
+ if (rect != null)
+ {
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ }
+ return rect;
+ }
+ }
+
+ private class HighlightEntry implements Highlighter.Highlight
+ {
+ Position p0;
+ Position p1;
+ Highlighter.HighlightPainter painter;
+
+ public HighlightEntry(Position p0, Position p1,
+ Highlighter.HighlightPainter painter)
+ {
+ this.p0 = p0;
+ this.p1 = p1;
+ this.painter = painter;
+ }
+
+ public int getStartOffset()
+ {
+ return p0.getOffset();
+ }
+
+ public int getEndOffset()
+ {
+ return p1.getOffset();
+ }
+
+ public Highlighter.HighlightPainter getPainter()
+ {
+ return painter;
+ }
+ }
+
+ /**
+ * A HighlightEntry that is used for LayerPainter painters. In addition
+ * to the info maintained by the HighlightEntry, this class maintains
+ * a painting rectangle. This is used as repaint region when the
+ * highlight changes and the text component needs repainting.
+ */
+ private class LayerHighlightEntry
+ extends HighlightEntry
+ {
+
+ /**
+ * The paint rectangle.
+ */
+ Rectangle paintRect = new Rectangle();
+
+ LayerHighlightEntry(Position p0, Position p1,
+ Highlighter.HighlightPainter p)
+ {
+ super(p0, p1, p);
+ }
+
+ /**
+ * Paints the highlight by calling the LayerPainter. This
+ * restricts the area to be painted by startOffset and endOffset
+ * and manages the paint rectangle.
+ */
+ void paintLayeredHighlight(Graphics g, int p0, int p1, Shape bounds,
+ JTextComponent tc, View view)
+ {
+ p0 = Math.max(getStartOffset(), p0);
+ p1 = Math.min(getEndOffset(), p1);
+
+ Highlighter.HighlightPainter painter = getPainter();
+ if (painter instanceof LayerPainter)
+ {
+ LayerPainter layerPainter = (LayerPainter) painter;
+ Shape area = layerPainter.paintLayer(g, p0, p1, bounds, tc, view);
+ Rectangle rect;
+ if (area instanceof Rectangle && paintRect != null)
+ rect = (Rectangle) area;
+ else
+ rect = area.getBounds();
+
+ if (paintRect.width == 0 || paintRect.height == 0)
+ paintRect = rect.getBounds();
+ else
+ paintRect = SwingUtilities.computeUnion(rect.x, rect.y, rect.width,
+ rect.height, paintRect);
+ }
+ }
+ }
+
+ /**
+ * @specnote final as of 1.4
+ */
+ public static final LayeredHighlighter.LayerPainter DefaultPainter =
+ new DefaultHighlightPainter(null);
+
+ private JTextComponent textComponent;
+ private ArrayList highlights = new ArrayList();
+ private boolean drawsLayeredHighlights = true;
+
+ public DefaultHighlighter()
+ {
+ // Nothing to do here.
+ }
+
+ public boolean getDrawsLayeredHighlights()
+ {
+ return drawsLayeredHighlights;
+ }
+
+ public void setDrawsLayeredHighlights(boolean newValue)
+ {
+ drawsLayeredHighlights = newValue;
+ }
+
+ private void checkPositions(int p0, int p1)
+ throws BadLocationException
+ {
+ if (p0 < 0)
+ throw new BadLocationException("DefaultHighlighter", p0);
+
+ if (p1 < p0)
+ throw new BadLocationException("DefaultHighlighter", p1);
+ }
+
+ public void install(JTextComponent c)
+ {
+ textComponent = c;
+ removeAllHighlights();
+ }
+
+ public void deinstall(JTextComponent c)
+ {
+ textComponent = null;
+ }
+
+ public Object addHighlight(int p0, int p1,
+ Highlighter.HighlightPainter painter)
+ throws BadLocationException
+ {
+ checkPositions(p0, p1);
+ HighlightEntry entry;
+ Document doc = textComponent.getDocument();
+ Position pos0 = doc.createPosition(p0);
+ Position pos1 = doc.createPosition(p1);
+ if (getDrawsLayeredHighlights() && painter instanceof LayerPainter)
+ entry = new LayerHighlightEntry(pos0, pos1, painter);
+ else
+ entry = new HighlightEntry(pos0, pos1, painter);
+ highlights.add(entry);
+
+ textComponent.getUI().damageRange(textComponent, p0, p1);
+
+ return entry;
+ }
+
+ public void removeHighlight(Object tag)
+ {
+ HighlightEntry entry = (HighlightEntry) tag;
+ if (entry instanceof LayerHighlightEntry)
+ {
+ LayerHighlightEntry lEntry = (LayerHighlightEntry) entry;
+ Rectangle paintRect = lEntry.paintRect;
+ textComponent.repaint(paintRect.x, paintRect.y, paintRect.width,
+ paintRect.height);
+ }
+ else
+ {
+ textComponent.getUI().damageRange(textComponent,
+ entry.getStartOffset(),
+ entry.getEndOffset());
+ }
+ highlights.remove(tag);
+
+ }
+
+ public void removeAllHighlights()
+ {
+ // Repaint damaged region.
+ int minX = 0;
+ int maxX = 0;
+ int minY = 0;
+ int maxY = 0;
+ int p0 = -1;
+ int p1 = -1;
+ for (Iterator i = highlights.iterator(); i.hasNext();)
+ {
+ HighlightEntry e = (HighlightEntry) i.next();
+ if (e instanceof LayerHighlightEntry)
+ {
+ LayerHighlightEntry le = (LayerHighlightEntry) e;
+ Rectangle r = le.paintRect;
+ minX = Math.min(r.x, minX);
+ maxX = Math.max(r.x + r.width, maxX);
+ minY = Math.min(r.y, minY);
+ maxY = Math.max(r.y + r.height, maxY);
+ }
+ else
+ {
+ if (p0 == -1 || p1 == -1)
+ {
+ p0 = e.getStartOffset();
+ p1 = e.getEndOffset();
+ }
+ else
+ {
+ p0 = Math.min(p0, e.getStartOffset());
+ p1 = Math.max(p1, e.getEndOffset());
+ }
+ }
+ if (minX != maxX && minY != maxY)
+ textComponent.repaint(minX, minY, maxX - minX, maxY - minY);
+ if (p0 != -1 && p1 != -1)
+ {
+ TextUI ui = textComponent.getUI();
+ ui.damageRange(textComponent, p0, p1);
+ }
+
+ }
+ highlights.clear();
+ }
+
+ public Highlighter.Highlight[] getHighlights()
+ {
+ return (Highlighter.Highlight[])
+ highlights.toArray(new Highlighter.Highlight[highlights.size()]);
+ }
+
+ public void changeHighlight(Object tag, int n0, int n1)
+ throws BadLocationException
+ {
+ Document doc = textComponent.getDocument();
+ TextUI ui = textComponent.getUI();
+ if (tag instanceof LayerHighlightEntry)
+ {
+ LayerHighlightEntry le = (LayerHighlightEntry) tag;
+ Rectangle r = le.paintRect;
+ if (r.width > 0 && r.height > 0)
+ textComponent.repaint(r.x, r.y, r.width, r.height);
+ r.width = 0;
+ r.height = 0;
+ le.p0 = doc.createPosition(n0);
+ le.p1 = doc.createPosition(n1);
+ ui.damageRange(textComponent, Math.min(n0, n1), Math.max(n0, n1));
+ }
+ else if (tag instanceof HighlightEntry)
+ {
+ HighlightEntry e = (HighlightEntry) tag;
+ int p0 = e.getStartOffset();
+ int p1 = e.getEndOffset();
+ if (p0 == n0)
+ {
+ ui.damageRange(textComponent, Math.min(p1, n1),
+ Math.max(p1, n1));
+ }
+ else if (n1 == p1)
+ {
+ ui.damageRange(textComponent, Math.min(p0, n0),
+ Math.max(p0, n0));
+ }
+ else
+ {
+ ui.damageRange(textComponent, p0, p1);
+ ui.damageRange(textComponent, n0, n1);
+ }
+ e.p0 = doc.createPosition(n0);
+ e.p1 = doc.createPosition(n1);
+ }
+ }
+
+ public void paintLayeredHighlights(Graphics g, int p0, int p1,
+ Shape viewBounds, JTextComponent editor,
+ View view)
+ {
+ for (Iterator i = highlights.iterator(); i.hasNext();)
+ {
+ Object o = i.next();
+ if (o instanceof LayerHighlightEntry)
+ {
+ LayerHighlightEntry entry = (LayerHighlightEntry) o;
+ int start = entry.getStartOffset();
+ int end = entry.getEndOffset();
+ if ((p0 < start && p1 > start) || (p0 >= start && p0 < end))
+ entry.paintLayeredHighlight(g, p0, p1, viewBounds, editor, view);
+ }
+ }
+ }
+
+ public void paint(Graphics g)
+ {
+ int size = highlights.size();
+
+ // Check if there are any highlights.
+ if (size == 0)
+ return;
+
+ // Prepares the rectangle of the inner drawing area.
+ Insets insets = textComponent.getInsets();
+ Shape bounds =
+ new Rectangle(insets.left,
+ insets.top,
+ textComponent.getWidth() - insets.left - insets.right,
+ textComponent.getHeight() - insets.top - insets.bottom);
+
+ for (int index = 0; index < size; ++index)
+ {
+ HighlightEntry entry = (HighlightEntry) highlights.get(index);
+ if (! (entry instanceof LayerHighlightEntry))
+ entry.painter.paint(g, entry.getStartOffset(), entry.getEndOffset(),
+ bounds, textComponent);
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
new file mode 100644
index 000000000..9021a1990
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
@@ -0,0 +1,2526 @@
+/* DefaultStyledDocument.java --
+ Copyright (C) 2004, 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 gnu.java.lang.CPStringBuilder;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Stack;
+import java.util.Vector;
+
+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;
+
+/**
+ * The default implementation of {@link StyledDocument}. The document is
+ * modeled as an {@link Element} tree, which has a {@link SectionElement} as
+ * single root, which has one or more {@link AbstractDocument.BranchElement}s
+ * as paragraph nodes and each paragraph node having one or more
+ * {@link AbstractDocument.LeafElement}s as content nodes.
+ *
+ * @author Michael Koch (konqueror@gmx.de)
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class DefaultStyledDocument extends AbstractDocument implements
+ StyledDocument
+{
+
+ /**
+ * An {@link UndoableEdit} that can undo attribute changes to an element.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+ public static class AttributeUndoableEdit extends AbstractUndoableEdit
+ {
+ /**
+ * A copy of the old attributes.
+ */
+ protected AttributeSet copy;
+
+ /**
+ * The new attributes.
+ */
+ protected AttributeSet newAttributes;
+
+ /**
+ * If the new attributes replaced the old attributes or if they only were
+ * added to them.
+ */
+ protected boolean isReplacing;
+
+ /**
+ * The element that has changed.
+ */
+ protected Element element;
+
+ /**
+ * Creates a new <code>AttributeUndoableEdit</code>.
+ *
+ * @param el
+ * the element that changes attributes
+ * @param newAtts
+ * the new attributes
+ * @param replacing
+ * if the new attributes replace the old or only append to them
+ */
+ public AttributeUndoableEdit(Element el, AttributeSet newAtts,
+ boolean replacing)
+ {
+ element = el;
+ newAttributes = newAtts;
+ isReplacing = replacing;
+ copy = el.getAttributes().copyAttributes();
+ }
+
+ /**
+ * Undos the attribute change. The <code>copy</code> field is set as
+ * attributes on <code>element</code>.
+ */
+ public void undo()
+ {
+ super.undo();
+ AttributeSet atts = element.getAttributes();
+ if (atts instanceof MutableAttributeSet)
+ {
+ MutableAttributeSet mutable = (MutableAttributeSet) atts;
+ mutable.removeAttributes(atts);
+ mutable.addAttributes(copy);
+ }
+ }
+
+ /**
+ * Redos an attribute change. This adds <code>newAttributes</code> to the
+ * <code>element</code>'s attribute set, possibly clearing all attributes
+ * if <code>isReplacing</code> is true.
+ */
+ public void redo()
+ {
+ super.undo();
+ AttributeSet atts = element.getAttributes();
+ if (atts instanceof MutableAttributeSet)
+ {
+ MutableAttributeSet mutable = (MutableAttributeSet) atts;
+ if (isReplacing)
+ mutable.removeAttributes(atts);
+ mutable.addAttributes(newAttributes);
+ }
+ }
+ }
+
+ /**
+ * Carries specification information for new {@link Element}s that should be
+ * created in {@link ElementBuffer}. This allows the parsing process to be
+ * decoupled from the <code>Element</code> creation process.
+ */
+ public static class ElementSpec
+ {
+ /**
+ * This indicates a start tag. This is a possible value for {@link #getType}.
+ */
+ public static final short StartTagType = 1;
+
+ /**
+ * This indicates an end tag. This is a possible value for {@link #getType}.
+ */
+ public static final short EndTagType = 2;
+
+ /**
+ * This indicates a content element. This is a possible value for
+ * {@link #getType}.
+ */
+ public static final short ContentType = 3;
+
+ /**
+ * This indicates that the data associated with this spec should be joined
+ * with what precedes it. This is a possible value for {@link #getDirection}.
+ */
+ public static final short JoinPreviousDirection = 4;
+
+ /**
+ * This indicates that the data associated with this spec should be joined
+ * with what follows it. This is a possible value for {@link #getDirection}.
+ */
+ public static final short JoinNextDirection = 5;
+
+ /**
+ * This indicates that the data associated with this spec should be used to
+ * create a new element. This is a possible value for {@link #getDirection}.
+ */
+ public static final short OriginateDirection = 6;
+
+ /**
+ * This indicates that the data associated with this spec should be joined
+ * to the fractured element. This is a possible value for
+ * {@link #getDirection}.
+ */
+ public static final short JoinFractureDirection = 7;
+
+ /**
+ * The type of the tag.
+ */
+ short type;
+
+ /**
+ * The direction of the tag.
+ */
+ short direction;
+
+ /**
+ * The offset of the content.
+ */
+ int offset;
+
+ /**
+ * The length of the content.
+ */
+ int length;
+
+ /**
+ * The actual content.
+ */
+ char[] content;
+
+ /**
+ * The attributes for the tag.
+ */
+ AttributeSet attributes;
+
+ /**
+ * Creates a new <code>ElementSpec</code> with no content, length or
+ * offset. This is most useful for start and end tags.
+ *
+ * @param a
+ * the attributes for the element to be created
+ * @param type
+ * the type of the tag
+ */
+ public ElementSpec(AttributeSet a, short type)
+ {
+ this(a, type, 0);
+ }
+
+ /**
+ * Creates a new <code>ElementSpec</code> that specifies the length but
+ * not the offset of an element. Such <code>ElementSpec</code>s are
+ * processed sequentially from a known starting point.
+ *
+ * @param a
+ * the attributes for the element to be created
+ * @param type
+ * the type of the tag
+ * @param len
+ * the length of the element
+ */
+ public ElementSpec(AttributeSet a, short type, int len)
+ {
+ this(a, type, null, 0, len);
+ }
+
+ /**
+ * Creates a new <code>ElementSpec</code> with document content.
+ *
+ * @param a
+ * the attributes for the element to be created
+ * @param type
+ * the type of the tag
+ * @param txt
+ * the actual content
+ * @param offs
+ * the offset into the <code>txt</code> array
+ * @param len
+ * the length of the element
+ */
+ public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)
+ {
+ attributes = a;
+ this.type = type;
+ offset = offs;
+ length = len;
+ content = txt;
+ direction = OriginateDirection;
+ }
+
+ /**
+ * Sets the type of the element.
+ *
+ * @param type
+ * the type of the element to be set
+ */
+ public void setType(short type)
+ {
+ this.type = type;
+ }
+
+ /**
+ * Returns the type of the element.
+ *
+ * @return the type of the element
+ */
+ public short getType()
+ {
+ return type;
+ }
+
+ /**
+ * Sets the direction of the element.
+ *
+ * @param dir
+ * the direction of the element to be set
+ */
+ public void setDirection(short dir)
+ {
+ direction = dir;
+ }
+
+ /**
+ * Returns the direction of the element.
+ *
+ * @return the direction of the element
+ */
+ public short getDirection()
+ {
+ return direction;
+ }
+
+ /**
+ * Returns the attributes of the element.
+ *
+ * @return the attributes of the element
+ */
+ public AttributeSet getAttributes()
+ {
+ return attributes;
+ }
+
+ /**
+ * Returns the actual content of the element.
+ *
+ * @return the actual content of the element
+ */
+ public char[] getArray()
+ {
+ return content;
+ }
+
+ /**
+ * Returns the offset of the content.
+ *
+ * @return the offset of the content
+ */
+ public int getOffset()
+ {
+ return offset;
+ }
+
+ /**
+ * Returns the length of the content.
+ *
+ * @return the length of the content
+ */
+ public int getLength()
+ {
+ return length;
+ }
+
+ /**
+ * Returns a String representation of this <code>ElementSpec</code>
+ * describing the type, direction and length of this
+ * <code>ElementSpec</code>.
+ *
+ * @return a String representation of this <code>ElementSpec</code>
+ */
+ public String toString()
+ {
+ CPStringBuilder b = new CPStringBuilder();
+ switch (type)
+ {
+ case StartTagType:
+ b.append("StartTag");
+ break;
+ case EndTagType:
+ b.append("EndTag");
+ break;
+ case ContentType:
+ b.append("Content");
+ break;
+ default:
+ b.append("??");
+ break;
+ }
+
+ b.append(':');
+
+ switch (direction)
+ {
+ case JoinPreviousDirection:
+ b.append("JoinPrevious");
+ break;
+ case JoinNextDirection:
+ b.append("JoinNext");
+ break;
+ case OriginateDirection:
+ b.append("Originate");
+ break;
+ case JoinFractureDirection:
+ b.append("Fracture");
+ break;
+ default:
+ b.append("??");
+ break;
+ }
+
+ b.append(':');
+ b.append(length);
+
+ return b.toString();
+ }
+ }
+
+ /**
+ * Performs all <em>structural</code> changes to the <code>Element</code>
+ * hierarchy. This class was implemented with much help from the document:
+ * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html.
+ */
+ public class ElementBuffer implements Serializable
+ {
+ /**
+ * Instance of all editing information for an object in the Vector. This class
+ * is used to add information to the DocumentEvent associated with an
+ * insertion/removal/change as well as to store the changes that need to be
+ * made so they can be made all at the same (appropriate) time.
+ */
+ class Edit
+ {
+ /** The element to edit . */
+ Element e;
+
+ /** The index of the change. */
+ int index;
+
+ /** The removed elements. */
+ ArrayList removed = new ArrayList();
+
+ /** The added elements. */
+ ArrayList added = new ArrayList();
+
+ /**
+ * Indicates if this edit contains a fracture.
+ */
+ boolean isFracture;
+
+ /**
+ * Creates a new Edit for the specified element at index i.
+ *
+ * @param el the element
+ * @param i the index
+ */
+ Edit(Element el, int i)
+ {
+ this(el, i, false);
+ }
+
+ /**
+ * Creates a new Edit for the specified element at index i.
+ *
+ * @param el the element
+ * @param i the index
+ * @param frac if this is a fracture edit or not
+ */
+ Edit(Element el, int i, boolean frac)
+ {
+ e = el;
+ index = i;
+ isFracture = frac;
+ }
+
+ }
+
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 1688745877691146623L;
+
+ /** The root element of the hierarchy. */
+ private Element root;
+
+ /** Holds the offset for structural changes. */
+ private int offset;
+
+ /** Holds the end offset for structural changes. */
+ private int endOffset;
+
+ /** Holds the length of structural changes. */
+ private int length;
+
+ /** Holds the position of the change. */
+ private int pos;
+
+ /**
+ * The parent of the fracture.
+ */
+ private Element fracturedParent;
+
+ /**
+ * The fractured child.
+ */
+ private Element fracturedChild;
+
+ /**
+ * Indicates if a fracture has been created.
+ */
+ private boolean createdFracture;
+
+ /**
+ * The current position in the element tree. This is used for bulk inserts
+ * using ElementSpecs.
+ */
+ private Stack elementStack;
+
+ private Edit[] insertPath;
+
+ private boolean recreateLeafs;
+
+ /**
+ * Vector that contains all the edits. Maybe replace by a HashMap.
+ */
+ private ArrayList edits;
+
+ private boolean offsetLastIndex;
+ private boolean offsetLastIndexReplace;
+
+ /**
+ * Creates a new <code>ElementBuffer</code> for the specified
+ * <code>root</code> element.
+ *
+ * @param root
+ * the root element for this <code>ElementBuffer</code>
+ */
+ public ElementBuffer(Element root)
+ {
+ this.root = root;
+ }
+
+ /**
+ * Returns the root element of this <code>ElementBuffer</code>.
+ *
+ * @return the root element of this <code>ElementBuffer</code>
+ */
+ public Element getRootElement()
+ {
+ return root;
+ }
+
+ /**
+ * Removes the content. This method sets some internal parameters and
+ * delegates the work to {@link #removeUpdate}.
+ *
+ * @param offs
+ * the offset from which content is remove
+ * @param len
+ * the length of the removed content
+ * @param ev
+ * the document event that records the changes
+ */
+ public void remove(int offs, int len, DefaultDocumentEvent ev)
+ {
+ prepareEdit(offs, len);
+ removeUpdate();
+ finishEdit(ev);
+ }
+
+ /**
+ * 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()
+ {
+ removeElements(root, offset, endOffset);
+ }
+
+ private boolean removeElements(Element elem, int rmOffs0, int rmOffs1)
+ {
+ boolean ret = false;
+ if (! elem.isLeaf())
+ {
+ // Update stack for changes.
+ int index0 = elem.getElementIndex(rmOffs0);
+ int index1 = elem.getElementIndex(rmOffs1);
+ elementStack.push(new Edit(elem, index0));
+ Edit ec = (Edit) elementStack.peek();
+
+ // If the range is contained by one element,
+ // we just forward the request
+ if (index0 == index1)
+ {
+ Element child0 = elem.getElement(index0);
+ if(rmOffs0 <= child0.getStartOffset()
+ && rmOffs1 >= child0.getEndOffset())
+ {
+ // Element totally removed.
+ ec.removed.add(child0);
+ }
+ else if (removeElements(child0, rmOffs0, rmOffs1))
+ {
+ ec.removed.add(child0);
+ }
+ }
+ else
+ {
+ // The removal range spans elements. If we can join
+ // the two endpoints, do it. Otherwise we remove the
+ // interior and forward to the endpoints.
+ Element child0 = elem.getElement(index0);
+ Element child1 = elem.getElement(index1);
+ boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
+ if (containsOffs1 && canJoin(child0, child1))
+ {
+ // Remove and join.
+ for (int i = index0; i <= index1; i++)
+ {
+ ec.removed.add(elem.getElement(i));
+ }
+ Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
+ ec.added.add(e);
+ }
+ else
+ {
+ // Remove interior and forward.
+ int rmIndex0 = index0 + 1;
+ int rmIndex1 = index1 - 1;
+ if (child0.getStartOffset() == rmOffs0
+ || (index0 == 0 && child0.getStartOffset() > rmOffs0
+ && child0.getEndOffset() <= rmOffs1))
+ {
+ // Start element completely consumed.
+ child0 = null;
+ rmIndex0 = index0;
+ }
+ if (! containsOffs1)
+ {
+ child1 = null;
+ rmIndex1++;
+ }
+ else if (child1.getStartOffset() == rmOffs1)
+ {
+ // End element not touched.
+ child1 = null;
+ }
+ if (rmIndex0 <= rmIndex1)
+ {
+ ec.index = rmIndex0;
+ }
+ for (int i = rmIndex0; i <= rmIndex1; i++)
+ {
+ ec.removed.add(elem.getElement(i));
+ }
+ if (child0 != null)
+ {
+ if(removeElements(child0, rmOffs0, rmOffs1))
+ {
+ ec.removed.add(0, child0);
+ ec.index = index0;
+ }
+ }
+ if (child1 != null)
+ {
+ if(removeElements(child1, rmOffs0, rmOffs1))
+ {
+ ec.removed.add(child1);
+ }
+ }
+ }
+ }
+
+ // Perform changes.
+ pop();
+
+ // Return true if we no longer have any children.
+ if(elem.getElementCount() == (ec.removed.size() - ec.added.size()))
+ ret = true;
+ }
+ return ret;
+ }
+
+ /**
+ * Creates a document in response to a call to
+ * {@link DefaultStyledDocument#create(ElementSpec[])}.
+ *
+ * @param len the length of the inserted text
+ * @param data the specs for the elements
+ * @param ev the document event
+ */
+ void create(int len, ElementSpec[] data, DefaultDocumentEvent ev)
+ {
+ prepareEdit(offset, len);
+ Element el = root;
+ int index = el.getElementIndex(0);
+ while (! el.isLeaf())
+ {
+ Element child = el.getElement(index);
+ Edit edit = new Edit(el, index, false);
+ elementStack.push(edit);
+ el = child;
+ index = el.getElementIndex(0);
+ }
+ Edit ed = (Edit) elementStack.peek();
+ Element child = ed.e.getElement(ed.index);
+ ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(),
+ child.getEndOffset()));
+ ed.removed.add(child);
+ while (elementStack.size() > 1)
+ pop();
+ int n = data.length;
+
+ // Reset root element's attributes.
+ AttributeSet newAtts = null;
+ if (n > 0 && data[0].getType() == ElementSpec.StartTagType)
+ newAtts = data[0].getAttributes();
+ if (newAtts == null)
+ newAtts = SimpleAttributeSet.EMPTY;
+ MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes();
+ ev.addEdit(new AttributeUndoableEdit(root, newAtts, true));
+ mAtts.removeAttributes(mAtts);
+ mAtts.addAttributes(newAtts);
+
+ // Insert the specified elements.
+ for (int i = 1; i < n; i++)
+ insertElement(data[i]);
+
+ // Pop remaining stack.
+ while (elementStack.size() > 0)
+ pop();
+
+ finishEdit(ev);
+ }
+
+ private boolean canJoin(Element e0, Element e1)
+ {
+ boolean ret = false;
+ if ((e0 != null) && (e1 != null))
+ {
+ // Don't join a leaf to a branch.
+ boolean isLeaf0 = e0.isLeaf();
+ boolean isLeaf1 = e1.isLeaf();
+ if(isLeaf0 == isLeaf1)
+ {
+ if (isLeaf0)
+ {
+ // Only join leaves if the attributes match, otherwise
+ // style information will be lost.
+ ret = e0.getAttributes().isEqual(e1.getAttributes());
+ }
+ else
+ {
+ // Only join non-leafs if the names are equal. This may result
+ // in loss of style information, but this is typically
+ // acceptable for non-leafs.
+ String name0 = e0.getName();
+ String name1 = e1.getName();
+ if (name0 != null)
+ ret = name0.equals(name1);
+ else if (name1 != null)
+ ret = name1.equals(name0);
+ else // Both names null.
+ ret = true;
+ }
+ }
+ }
+ return ret;
+ }
+
+ private Element join(Element p, Element left, Element right, int rmOffs0,
+ int rmOffs1)
+ {
+ Element joined = null;
+ if (left.isLeaf() && right.isLeaf())
+ {
+ joined = createLeafElement(p, left.getAttributes(),
+ left.getStartOffset(),
+ right.getEndOffset());
+ }
+ else if ((! left.isLeaf()) && (! right.isLeaf()))
+ {
+ // Join two branch elements. This copies the children before
+ // the removal range on the left element, and after the removal
+ // range on the right element. The two elements on the edge
+ // are joined if possible and needed.
+ joined = createBranchElement(p, left.getAttributes());
+ int ljIndex = left.getElementIndex(rmOffs0);
+ int rjIndex = right.getElementIndex(rmOffs1);
+ Element lj = left.getElement(ljIndex);
+ if (lj.getStartOffset() >= rmOffs0)
+ {
+ lj = null;
+ }
+ Element rj = right.getElement(rjIndex);
+ if (rj.getStartOffset() == rmOffs1)
+ {
+ rj = null;
+ }
+ ArrayList children = new ArrayList();
+ // Transfer the left.
+ for (int i = 0; i < ljIndex; i++)
+ {
+ children.add(clone(joined, left.getElement(i)));
+ }
+
+ // Transfer the join/middle.
+ if (canJoin(lj, rj))
+ {
+ Element e = join(joined, lj, rj, rmOffs0, rmOffs1);
+ children.add(e);
+ }
+ else
+ {
+ if (lj != null)
+ {
+ children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1));
+ }
+ if (rj != null)
+ {
+ children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1));
+ }
+ }
+
+ // Transfer the right.
+ int n = right.getElementCount();
+ for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++)
+ {
+ children.add(clone(joined, right.getElement(i)));
+ }
+
+ // Install the children.
+ Element[] c = new Element[children.size()];
+ c = (Element[]) children.toArray(c);
+ ((BranchElement) joined).replace(0, 0, c);
+ }
+ else
+ {
+ assert false : "Must not happen";
+ }
+ return joined;
+ }
+
+ /**
+ * Performs the actual work for {@link #change}. The elements at the
+ * interval boundaries are split up (if necessary) so that the interval
+ * boundaries are located at element boundaries.
+ */
+ protected void changeUpdate()
+ {
+ boolean didEnd = split(offset, length);
+ if (! didEnd)
+ {
+ // need to do the other end
+ while (elementStack.size() != 0)
+ {
+ pop();
+ }
+ split(offset + length, 0);
+ }
+ while (elementStack.size() != 0)
+ {
+ pop();
+ }
+ }
+
+ /**
+ * Modifies the element structure so that the specified interval starts and
+ * ends at an element boundary. Content and paragraph elements are split and
+ * created as necessary. This also updates the
+ * <code>DefaultDocumentEvent</code> to reflect the structural changes.
+ * The bulk work is delegated to {@link #changeUpdate()}.
+ *
+ * @param offset
+ * the start index of the interval to be changed
+ * @param length
+ * the length of the interval to be changed
+ * @param ev
+ * the <code>DefaultDocumentEvent</code> describing the change
+ */
+ public void change(int offset, int length, DefaultDocumentEvent ev)
+ {
+ prepareEdit(offset, length);
+ changeUpdate();
+ finishEdit(ev);
+ }
+
+ /**
+ * Creates and returns a deep clone of the specified <code>clonee</code>
+ * with the specified parent as new parent.
+ *
+ * This method can only clone direct instances of {@link BranchElement}
+ * or {@link LeafElement}.
+ *
+ * @param parent the new parent
+ * @param clonee the element to be cloned
+ *
+ * @return the cloned element with the new parent
+ */
+ public Element clone(Element parent, Element clonee)
+ {
+ Element clone = clonee;
+ // We can only handle AbstractElements here.
+ if (clonee instanceof BranchElement)
+ {
+ BranchElement branchEl = (BranchElement) clonee;
+ BranchElement branchClone =
+ new BranchElement(parent, branchEl.getAttributes());
+ // Also clone all of the children.
+ int numChildren = branchClone.getElementCount();
+ Element[] cloneChildren = new Element[numChildren];
+ for (int i = 0; i < numChildren; ++i)
+ {
+ cloneChildren[i] = clone(branchClone,
+ branchClone.getElement(i));
+ }
+ branchClone.replace(0, 0, cloneChildren);
+ clone = branchClone;
+ }
+ else if (clonee instanceof LeafElement)
+ {
+ clone = new LeafElement(parent, clonee.getAttributes(),
+ clonee.getStartOffset(),
+ clonee.getEndOffset());
+ }
+ return clone;
+ }
+
+ private Element cloneAsNecessary(Element parent, Element clonee,
+ int rmOffs0, int rmOffs1)
+ {
+ Element cloned;
+ if (clonee.isLeaf())
+ {
+ cloned = createLeafElement(parent, clonee.getAttributes(),
+ clonee.getStartOffset(),
+ clonee.getEndOffset());
+ }
+ else
+ {
+ Element e = createBranchElement(parent, clonee.getAttributes());
+ int n = clonee.getElementCount();
+ ArrayList childrenList = new ArrayList(n);
+ for (int i = 0; i < n; i++)
+ {
+ Element elem = clonee.getElement(i);
+ if (elem.getStartOffset() < rmOffs0
+ || elem.getEndOffset() > rmOffs1)
+ {
+ childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
+ rmOffs1));
+ }
+ }
+ Element[] children = new Element[childrenList.size()];
+ children = (Element[]) childrenList.toArray(children);
+ ((BranchElement) e).replace(0, 0, children);
+ cloned = e;
+ }
+ return cloned;
+ }
+
+ /**
+ * Inserts new <code>Element</code> in the document at the specified
+ * position. Most of the work is done by {@link #insertUpdate}, after some
+ * fields have been prepared for it.
+ *
+ * @param offset
+ * the location in the document at which the content is inserted
+ * @param length
+ * the length of the inserted content
+ * @param data
+ * the element specifications for the content to be inserted
+ * @param ev
+ * the document event that is updated to reflect the structural
+ * changes
+ */
+ public void insert(int offset, int length, ElementSpec[] data,
+ DefaultDocumentEvent ev)
+ {
+ if (length > 0)
+ {
+ prepareEdit(offset, length);
+ insertUpdate(data);
+ finishEdit(ev);
+ }
+ }
+
+ /**
+ * Prepares the state of this object for performing an insert.
+ *
+ * @param offset the offset at which is inserted
+ * @param length the length of the inserted region
+ */
+ private void prepareEdit(int offset, int length)
+ {
+ this.offset = offset;
+ this.pos = offset;
+ this.endOffset = offset + length;
+ this.length = length;
+
+ if (edits == null)
+ edits = new ArrayList();
+ else
+ edits.clear();
+
+ if (elementStack == null)
+ elementStack = new Stack();
+ else
+ elementStack.clear();
+
+ fracturedParent = null;
+ fracturedChild = null;
+ offsetLastIndex = false;
+ offsetLastIndexReplace = false;
+ }
+
+ /**
+ * Finishes an insert. This applies all changes and updates
+ * the DocumentEvent.
+ *
+ * @param ev the document event
+ */
+ private void finishEdit(DefaultDocumentEvent ev)
+ {
+ // This for loop applies all the changes that were made and updates the
+ // DocumentEvent.
+ for (Iterator i = edits.iterator(); i.hasNext();)
+ {
+ Edit edits = (Edit) i.next();
+ Element[] removed = new Element[edits.removed.size()];
+ removed = (Element[]) edits.removed.toArray(removed);
+ Element[] added = new Element[edits.added.size()];
+ added = (Element[]) edits.added.toArray(added);
+ int index = edits.index;
+ BranchElement parent = (BranchElement) edits.e;
+ parent.replace(index, removed.length, added);
+ ElementEdit ee = new ElementEdit(parent, index, removed, added);
+ ev.addEdit(ee);
+ }
+ edits.clear();
+ elementStack.clear();
+ }
+
+ /**
+ * Inserts new content.
+ *
+ * @param data the element specifications for the elements to be inserted
+ */
+ protected void insertUpdate(ElementSpec[] data)
+ {
+ // Push the current path to the stack.
+ Element current = root;
+ int index = current.getElementIndex(offset);
+ while (! current.isLeaf())
+ {
+ Element child = current.getElement(index);
+ int editIndex = child.isLeaf() ? index : index + 1;
+ Edit edit = new Edit(current, editIndex);
+ elementStack.push(edit);
+ current = child;
+ index = current.getElementIndex(offset);
+ }
+
+ // Create a copy of the original path.
+ insertPath = new Edit[elementStack.size()];
+ insertPath = (Edit[]) elementStack.toArray(insertPath);
+
+ // No fracture yet.
+ createdFracture = false;
+
+ // Insert first content tag.
+ int i = 0;
+ recreateLeafs = false;
+ int type = data[0].getType();
+ if (type == ElementSpec.ContentType)
+ {
+ // If the first tag is content we must treat it separately to allow
+ // for joining properly to previous Elements and to ensure that
+ // no extra LeafElements are erroneously inserted.
+ insertFirstContentTag(data);
+ pos += data[0].length;
+ i = 1;
+ }
+ else
+ {
+ createFracture(data);
+ i = 0;
+ }
+
+ // Handle each ElementSpec individually.
+ for (; i < data.length; i++)
+ {
+ insertElement(data[i]);
+ }
+
+ // Fracture if we haven't done yet.
+ if (! createdFracture)
+ fracture(-1);
+
+ // Pop the remaining stack.
+ while (elementStack.size() != 0)
+ pop();
+
+ // Offset last index if necessary.
+ if (offsetLastIndex && offsetLastIndexReplace)
+ insertPath[insertPath.length - 1].index++;
+
+ // Make sure we havea an Edit for each path item that has a change.
+ for (int p = insertPath.length - 1; p >= 0; p--)
+ {
+ Edit edit = insertPath[p];
+ if (edit.e == fracturedParent)
+ edit.added.add(fracturedChild);
+ if ((edit.added.size() > 0 || edit.removed.size() > 0)
+ && ! edits.contains(edit))
+ edits.add(edit);
+ }
+
+ // Remove element that would be created by an insert at 0 with
+ // an initial end tag.
+ if (offset == 0 && fracturedParent != null
+ && data[0].getType() == ElementSpec.EndTagType)
+ {
+ int p;
+ for (p = 0;
+ p < data.length && data[p].getType() == ElementSpec.EndTagType;
+ p++)
+ ;
+
+ Edit edit = insertPath[insertPath.length - p - 1];
+ edit.index--;
+ edit.removed.add(0, edit.e.getElement(edit.index));
+ }
+ }
+
+ private void pop()
+ {
+ Edit edit = (Edit) elementStack.peek();
+ elementStack.pop();
+ if ((edit.added.size() > 0) || (edit.removed.size() > 0))
+ {
+ edits.add(edit);
+ }
+ else if (! elementStack.isEmpty())
+ {
+ Element e = edit.e;
+ if (e.getElementCount() == 0)
+ {
+ // If we pushed a branch element that didn't get
+ // used, make sure its not marked as having been added.
+ edit = (Edit) elementStack.peek();
+ edit.added.remove(e);
+ }
+ }
+ }
+
+ private void insertElement(ElementSpec spec)
+ {
+ if (elementStack.isEmpty())
+ return;
+
+ Edit edit = (Edit) elementStack.peek();
+ switch (spec.getType())
+ {
+ case ElementSpec.StartTagType:
+ switch (spec.getDirection())
+ {
+ case ElementSpec.JoinFractureDirection:
+ // Fracture the tree and ensure the appropriate element
+ // is on top of the stack.
+ if (! createdFracture)
+ {
+ fracture(elementStack.size() - 1);
+ }
+ if (! edit.isFracture)
+ {
+ // If the parent isn't a fracture, then the fracture is
+ // in fracturedChild.
+ Edit newEdit = new Edit(fracturedChild, 0, true);
+ elementStack.push(newEdit);
+ }
+ else
+ {
+ // Otherwise use the parent's first child.
+ Element el = edit.e.getElement(0);
+ Edit newEdit = new Edit(el, 0, true);
+ elementStack.push(newEdit);
+ }
+ break;
+ case ElementSpec.JoinNextDirection:
+ // Push the next paragraph element onto the stack so
+ // future insertions are added to it.
+ Element parent = edit.e.getElement(edit.index);
+ if (parent.isLeaf())
+ {
+ if (edit.index + 1 < edit.e.getElementCount())
+ parent = edit.e.getElement(edit.index + 1);
+ else
+ assert false; // Must not happen.
+ }
+ elementStack.push(new Edit(parent, 0, true));
+ break;
+ default:
+ Element branch = createBranchElement(edit.e,
+ spec.getAttributes());
+ edit.added.add(branch);
+ elementStack.push(new Edit(branch, 0));
+ break;
+ }
+ break;
+ case ElementSpec.EndTagType:
+ pop();
+ break;
+ case ElementSpec.ContentType:
+ insertContentTag(spec, edit);
+ break;
+ }
+ }
+
+ /**
+ * Inserts the first tag into the document.
+ *
+ * @param data -
+ * the data to be inserted.
+ */
+ private void insertFirstContentTag(ElementSpec[] data)
+ {
+ ElementSpec first = data[0];
+ Edit edit = (Edit) elementStack.peek();
+ Element current = edit.e.getElement(edit.index);
+ int firstEndOffset = offset + first.length;
+ boolean onlyContent = data.length == 1;
+ switch (first.getDirection())
+ {
+ case ElementSpec.JoinPreviousDirection:
+ if (current.getEndOffset() != firstEndOffset && ! onlyContent)
+ {
+ Element newEl1 = createLeafElement(edit.e,
+ current.getAttributes(),
+ current.getStartOffset(),
+ firstEndOffset);
+ edit.added.add(newEl1);
+ edit.removed.add(current);
+ if (current.getEndOffset() != endOffset)
+ recreateLeafs = true;
+ else
+ offsetLastIndex = true;
+ }
+ else
+ {
+ offsetLastIndex = true;
+ offsetLastIndexReplace = true;
+ }
+ break;
+ case ElementSpec.JoinNextDirection:
+ if (offset != 0)
+ {
+ Element newEl1 = createLeafElement(edit.e,
+ current.getAttributes(),
+ current.getStartOffset(),
+ offset);
+ edit.added.add(newEl1);
+ Element next = edit.e.getElement(edit.index + 1);
+ if (onlyContent)
+ newEl1 = createLeafElement(edit.e, next.getAttributes(),
+ offset, next.getEndOffset());
+ else
+ {
+ newEl1 = createLeafElement(edit.e, next.getAttributes(),
+ offset, firstEndOffset);
+ }
+ edit.added.add(newEl1);
+ edit.removed.add(current);
+ edit.removed.add(next);
+ }
+ break;
+ default: // OriginateDirection.
+ if (current.getStartOffset() != offset)
+ {
+ Element newEl = createLeafElement(edit.e,
+ current.getAttributes(),
+ current.getStartOffset(),
+ offset);
+ edit.added.add(newEl);
+ }
+ edit.removed.add(current);
+ Element newEl1 = createLeafElement(edit.e, first.getAttributes(),
+ offset, firstEndOffset);
+ edit.added.add(newEl1);
+ if (current.getEndOffset() != endOffset)
+ recreateLeafs = true;
+ else
+ offsetLastIndex = true;
+ break;
+ }
+ }
+
+ /**
+ * Inserts a content element into the document structure.
+ *
+ * @param tag -
+ * the element spec
+ */
+ private void insertContentTag(ElementSpec tag, Edit edit)
+ {
+ int len = tag.getLength();
+ int dir = tag.getDirection();
+ if (dir == ElementSpec.JoinNextDirection)
+ {
+ if (! edit.isFracture)
+ {
+ Element first = null;
+ if (insertPath != null)
+ {
+ for (int p = insertPath.length - 1; p >= 0; p--)
+ {
+ if (insertPath[p] == edit)
+ {
+ if (p != insertPath.length - 1)
+ first = edit.e.getElement(edit.index);
+ break;
+ }
+ }
+ }
+ if (first == null)
+ first = edit.e.getElement(edit.index + 1);
+ Element leaf = createLeafElement(edit.e, first.getAttributes(),
+ pos, first.getEndOffset());
+ edit.added.add(leaf);
+ edit.removed.add(first);
+ }
+ else
+ {
+ Element first = edit.e.getElement(0);
+ Element leaf = createLeafElement(edit.e, first.getAttributes(),
+ pos, first.getEndOffset());
+ edit.added.add(leaf);
+ edit.removed.add(first);
+ }
+ }
+ else
+ {
+ Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos,
+ pos + len);
+ edit.added.add(leaf);
+ }
+
+ pos += len;
+
+ }
+
+ /**
+ * This method fractures bottomost leaf in the elementStack. This
+ * happens when the first inserted tag is not content.
+ *
+ * @param data
+ * the ElementSpecs used for the entire insertion
+ */
+ private void createFracture(ElementSpec[] data)
+ {
+ Edit edit = (Edit) elementStack.peek();
+ Element child = edit.e.getElement(edit.index);
+ if (offset != 0)
+ {
+ Element newChild = createLeafElement(edit.e, child.getAttributes(),
+ child.getStartOffset(), offset);
+ edit.added.add(newChild);
+ }
+ edit.removed.add(child);
+ if (child.getEndOffset() != endOffset)
+ recreateLeafs = true;
+ else
+ offsetLastIndex = true;
+ }
+
+ private void fracture(int depth)
+ {
+ int len = insertPath.length;
+ int lastIndex = -1;
+ boolean recreate = recreateLeafs;
+ Edit lastEdit = insertPath[len - 1];
+ boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount();
+ int deepestChangedIndex = recreate ? len : - 1;
+ int lastChangedIndex = len - 1;
+ createdFracture = true;
+ for (int i = len - 2; i >= 0; i--)
+ {
+ Edit edit = insertPath[i];
+ if (edit.added.size() > 0 || i == depth)
+ {
+ lastIndex = i;
+ if (! recreate && childChanged)
+ {
+ recreate = true;
+ if (deepestChangedIndex == -1)
+ deepestChangedIndex = lastChangedIndex + 1;
+ }
+ }
+ if (! childChanged && edit.index < edit.e.getElementCount())
+ {
+ childChanged = true;
+ lastChangedIndex = i;
+ }
+ }
+ if (recreate)
+ {
+ if (lastIndex == -1)
+ lastIndex = len - 1;
+ recreate(lastIndex, deepestChangedIndex);
+ }
+ }
+
+ private void recreate(int startIndex, int endIndex)
+ {
+ // Recreate the element representing the inserted index.
+ Edit edit = insertPath[startIndex];
+ Element child;
+ Element newChild;
+ int changeLength = insertPath.length;
+
+ if (startIndex + 1 == changeLength)
+ child = edit.e.getElement(edit.index);
+ else
+ child = edit.e.getElement(edit.index - 1);
+
+ if(child.isLeaf())
+ {
+ newChild = createLeafElement(edit.e, child.getAttributes(),
+ Math.max(endOffset, child.getStartOffset()),
+ child.getEndOffset());
+ }
+ else
+ {
+ newChild = createBranchElement(edit.e, child.getAttributes());
+ }
+ fracturedParent = edit.e;
+ fracturedChild = newChild;
+
+ // Recreate all the elements to the right of the insertion point.
+ Element parent = newChild;
+ while (++startIndex < endIndex)
+ {
+ boolean isEnd = (startIndex + 1) == endIndex;
+ boolean isEndLeaf = (startIndex + 1) == changeLength;
+
+ // Create the newChild, a duplicate of the elment at
+ // index. This isn't done if isEnd and offsetLastIndex are true
+ // indicating a join previous was done.
+ edit = insertPath[startIndex];
+
+ // Determine the child to duplicate, won't have to duplicate
+ // if at end of fracture, or offseting index.
+ if(isEnd)
+ {
+ if(offsetLastIndex || ! isEndLeaf)
+ child = null;
+ else
+ child = edit.e.getElement(edit.index);
+ }
+ else
+ {
+ child = edit.e.getElement(edit.index - 1);
+ }
+
+ // Duplicate it.
+ if(child != null)
+ {
+ if(child.isLeaf())
+ {
+ newChild = createLeafElement(parent, child.getAttributes(),
+ Math.max(endOffset, child.getStartOffset()),
+ child.getEndOffset());
+ }
+ else
+ {
+ newChild = createBranchElement(parent,
+ child.getAttributes());
+ }
+ }
+ else
+ newChild = null;
+
+ // Recreate the remaining children (there may be none).
+ int childrenToMove = edit.e.getElementCount() - edit.index;
+ Element[] children;
+ int moveStartIndex;
+ int childStartIndex = 1;
+
+ if (newChild == null)
+ {
+ // Last part of fracture.
+ if (isEndLeaf)
+ {
+ childrenToMove--;
+ moveStartIndex = edit.index + 1;
+ }
+ else
+ {
+ moveStartIndex = edit.index;
+ }
+ childStartIndex = 0;
+ children = new Element[childrenToMove];
+ }
+ else
+ {
+ if (! isEnd)
+ {
+ // Branch.
+ childrenToMove++;
+ moveStartIndex = edit.index;
+ }
+ else
+ {
+ // Last leaf, need to recreate part of it.
+ moveStartIndex = edit.index + 1;
+ }
+ children = new Element[childrenToMove];
+ children[0] = newChild;
+ }
+
+ for (int c = childStartIndex; c < childrenToMove; c++)
+ {
+ Element toMove = edit.e.getElement(moveStartIndex++);
+ children[c] = recreateFracturedElement(parent, toMove);
+ edit.removed.add(toMove);
+ }
+ ((BranchElement) parent).replace(0, 0, children);
+ parent = newChild;
+ }
+
+ }
+
+ private Element recreateFracturedElement(Element parent, Element toCopy)
+ {
+ Element recreated;
+ if(toCopy.isLeaf())
+ {
+ recreated = createLeafElement(parent, toCopy.getAttributes(),
+ Math.max(toCopy.getStartOffset(), endOffset),
+ toCopy.getEndOffset());
+ }
+ else
+ {
+ Element newParent = createBranchElement(parent,
+ toCopy.getAttributes());
+ int childCount = toCopy.getElementCount();
+ Element[] newChildren = new Element[childCount];
+ for (int i = 0; i < childCount; i++)
+ {
+ newChildren[i] = recreateFracturedElement(newParent,
+ toCopy.getElement(i));
+ }
+ ((BranchElement) newParent).replace(0, 0, newChildren);
+ recreated = newParent;
+ }
+ return recreated;
+ }
+
+ private boolean split(int offs, int len)
+ {
+ boolean splitEnd = false;
+ // Push the path to the stack.
+ Element e = root;
+ int index = e.getElementIndex(offs);
+ while (! e.isLeaf())
+ {
+ elementStack.push(new Edit(e, index));
+ e = e.getElement(index);
+ index = e.getElementIndex(offs);
+ }
+
+ Edit ec = (Edit) elementStack.peek();
+ Element child = ec.e.getElement(ec.index);
+ // Make sure there is something to do. If the
+ // offset is already at a boundary then there is
+ // nothing to do.
+ if (child.getStartOffset() < offs && offs < child.getEndOffset())
+ {
+ // We need to split, now see if the other end is within
+ // the same parent.
+ int index0 = ec.index;
+ int index1 = index0;
+ if (((offs + len) < ec.e.getEndOffset()) && (len != 0))
+ {
+ // It's a range split in the same parent.
+ index1 = ec.e.getElementIndex(offs+len);
+ if (index1 == index0)
+ {
+ // It's a three-way split.
+ ec.removed.add(child);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ child.getStartOffset(), offs);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ offs, offs + len);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ offs + len, child.getEndOffset());
+ ec.added.add(e);
+ return true;
+ }
+ else
+ {
+ child = ec.e.getElement(index1);
+ if ((offs + len) == child.getStartOffset())
+ {
+ // End is already on a boundary.
+ index1 = index0;
+ }
+ }
+ splitEnd = true;
+ }
+
+ // Split the first location.
+ pos = offs;
+ child = ec.e.getElement(index0);
+ ec.removed.add(child);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ child.getStartOffset(), pos);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ pos, child.getEndOffset());
+ ec.added.add(e);
+
+ // Pick up things in the middle.
+ for (int i = index0 + 1; i < index1; i++)
+ {
+ child = ec.e.getElement(i);
+ ec.removed.add(child);
+ ec.added.add(child);
+ }
+
+ if (index1 != index0)
+ {
+ child = ec.e.getElement(index1);
+ pos = offs + len;
+ ec.removed.add(child);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ child.getStartOffset(), pos);
+ ec.added.add(e);
+ e = createLeafElement(ec.e, child.getAttributes(),
+ pos, child.getEndOffset());
+
+ ec.added.add(e);
+ }
+ }
+ return splitEnd;
+
+ }
+
+ }
+
+
+ /**
+ * An element type for sections. This is a simple BranchElement with a unique
+ * name.
+ */
+ protected class SectionElement extends BranchElement
+ {
+ /**
+ * Creates a new SectionElement.
+ */
+ public SectionElement()
+ {
+ super(null, null);
+ }
+
+ /**
+ * Returns the name of the element. This method always returns
+ * &quot;section&quot;.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return SectionElementName;
+ }
+ }
+
+ /**
+ * Receives notification when any of the document's style changes and calls
+ * {@link DefaultStyledDocument#styleChanged(Style)}.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+ private class StyleChangeListener implements ChangeListener
+ {
+
+ /**
+ * Receives notification when any of the document's style changes and calls
+ * {@link DefaultStyledDocument#styleChanged(Style)}.
+ *
+ * @param event
+ * the change event
+ */
+ public void stateChanged(ChangeEvent event)
+ {
+ Style style = (Style) event.getSource();
+ styleChanged(style);
+ }
+ }
+
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 940485415728614849L;
+
+ /**
+ * The default size to use for new content buffers.
+ */
+ public static final int BUFFER_SIZE_DEFAULT = 4096;
+
+ /**
+ * The <code>EditorBuffer</code> that is used to manage to
+ * <code>Element</code> hierarchy.
+ */
+ protected DefaultStyledDocument.ElementBuffer buffer;
+
+ /**
+ * Listens for changes on this document's styles and notifies styleChanged().
+ */
+ private StyleChangeListener styleChangeListener;
+
+ /**
+ * Creates a new <code>DefaultStyledDocument</code>.
+ */
+ public DefaultStyledDocument()
+ {
+ this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
+ }
+
+ /**
+ * Creates a new <code>DefaultStyledDocument</code> that uses the specified
+ * {@link StyleContext}.
+ *
+ * @param context
+ * the <code>StyleContext</code> to use
+ */
+ public DefaultStyledDocument(StyleContext context)
+ {
+ this(new GapContent(BUFFER_SIZE_DEFAULT), context);
+ }
+
+ /**
+ * Creates a new <code>DefaultStyledDocument</code> that uses the specified
+ * {@link StyleContext} and {@link Content} buffer.
+ *
+ * @param content
+ * the <code>Content</code> buffer to use
+ * @param context
+ * the <code>StyleContext</code> to use
+ */
+ public DefaultStyledDocument(AbstractDocument.Content content,
+ StyleContext context)
+ {
+ super(content, context);
+ buffer = new ElementBuffer(createDefaultRoot());
+ setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
+ }
+
+ /**
+ * Adds a style into the style hierarchy. Unspecified style attributes can be
+ * resolved in the <code>parent</code> style, if one is specified. While it
+ * is legal to add nameless styles (<code>nm == null</code),
+ * you must be aware that the client application is then responsible
+ * for managing the style hierarchy, since unnamed styles cannot be
+ * looked up by their name.
+ *
+ * @param nm the name of the style or <code>null</code> if the style should
+ * be unnamed
+ * @param parent the parent in which unspecified style attributes are
+ * resolved, or <code>null</code> if that is not necessary
+ *
+ * @return the newly created <code>Style</code>
+ */
+ public Style addStyle(String nm, Style parent)
+ {
+ StyleContext context = (StyleContext) getAttributeContext();
+ Style newStyle = context.addStyle(nm, parent);
+
+ // Register change listener.
+ if (styleChangeListener == null)
+ styleChangeListener = new StyleChangeListener();
+ newStyle.addChangeListener(styleChangeListener);
+
+ return newStyle;
+ }
+
+ /**
+ * Create the default root element for this kind of <code>Document</code>.
+ *
+ * @return the default root element for this kind of <code>Document</code>
+ */
+ protected AbstractDocument.AbstractElement createDefaultRoot()
+ {
+ Element[] tmp;
+ SectionElement section = new SectionElement();
+
+ BranchElement paragraph = new BranchElement(section, null);
+ tmp = new Element[1];
+ tmp[0] = paragraph;
+ section.replace(0, 0, tmp);
+
+ Element leaf = new LeafElement(paragraph, null, 0, 1);
+ tmp = new Element[1];
+ tmp[0] = leaf;
+ paragraph.replace(0, 0, tmp);
+
+ return section;
+ }
+
+ /**
+ * Returns the <code>Element</code> that corresponds to the character at the
+ * specified position.
+ *
+ * @param position
+ * the position of which we query the corresponding
+ * <code>Element</code>
+ * @return the <code>Element</code> that corresponds to the character at the
+ * specified position
+ */
+ public Element getCharacterElement(int position)
+ {
+ Element element = getDefaultRootElement();
+
+ while (!element.isLeaf())
+ {
+ int index = element.getElementIndex(position);
+ element = element.getElement(index);
+ }
+
+ return element;
+ }
+
+ /**
+ * Extracts a background color from a set of attributes.
+ *
+ * @param attributes
+ * the attributes from which to get a background color
+ * @return the background color that correspond to the attributes
+ */
+ public Color getBackground(AttributeSet attributes)
+ {
+ StyleContext context = (StyleContext) getAttributeContext();
+ return context.getBackground(attributes);
+ }
+
+ /**
+ * Returns the default root element.
+ *
+ * @return the default root element
+ */
+ public Element getDefaultRootElement()
+ {
+ return buffer.getRootElement();
+ }
+
+ /**
+ * Extracts a font from a set of attributes.
+ *
+ * @param attributes
+ * the attributes from which to get a font
+ * @return the font that correspond to the attributes
+ */
+ public Font getFont(AttributeSet attributes)
+ {
+ StyleContext context = (StyleContext) getAttributeContext();
+ return context.getFont(attributes);
+ }
+
+ /**
+ * Extracts a foreground color from a set of attributes.
+ *
+ * @param attributes
+ * the attributes from which to get a foreground color
+ * @return the foreground color that correspond to the attributes
+ */
+ public Color getForeground(AttributeSet attributes)
+ {
+ StyleContext context = (StyleContext) getAttributeContext();
+ return context.getForeground(attributes);
+ }
+
+ /**
+ * Returns the logical <code>Style</code> for the specified position.
+ *
+ * @param position
+ * the position from which to query to logical style
+ * @return the logical <code>Style</code> for the specified position
+ */
+ public Style getLogicalStyle(int position)
+ {
+ Element paragraph = getParagraphElement(position);
+ AttributeSet attributes = paragraph.getAttributes();
+ 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;
+ }
+
+ /**
+ * Returns the paragraph element for the specified position. If the position
+ * is outside the bounds of the document's root element, then the closest
+ * element is returned. That is the last paragraph if
+ * <code>position >= endIndex</code> or the first paragraph if
+ * <code>position < startIndex</code>.
+ *
+ * @param position
+ * the position for which to query the paragraph element
+ * @return the paragraph element for the specified position
+ */
+ public Element getParagraphElement(int position)
+ {
+ Element e = getDefaultRootElement();
+ while (!e.isLeaf())
+ e = e.getElement(e.getElementIndex(position));
+
+ if (e != null)
+ return e.getParentElement();
+ return e;
+ }
+
+ /**
+ * Looks up and returns a named <code>Style</code>.
+ *
+ * @param nm
+ * the name of the <code>Style</code>
+ * @return the found <code>Style</code> of <code>null</code> if no such
+ * <code>Style</code> exists
+ */
+ public Style getStyle(String nm)
+ {
+ StyleContext context = (StyleContext) getAttributeContext();
+ return context.getStyle(nm);
+ }
+
+ /**
+ * Removes a named <code>Style</code> from the style hierarchy.
+ *
+ * @param nm
+ * the name of the <code>Style</code> to be removed
+ */
+ public void removeStyle(String nm)
+ {
+ StyleContext context = (StyleContext) getAttributeContext();
+ context.removeStyle(nm);
+ }
+
+ /**
+ * Sets text attributes for the fragment specified by <code>offset</code>
+ * and <code>length</code>.
+ *
+ * @param offset
+ * the start offset of the fragment
+ * @param length
+ * the length of the fragment
+ * @param attributes
+ * the text attributes to set
+ * @param replace
+ * if <code>true</code>, the attributes of the current selection
+ * are overridden, otherwise they are merged
+ */
+ public void setCharacterAttributes(int offset, int length,
+ AttributeSet attributes, boolean replace)
+ {
+ // Exit early if length is 0, so no DocumentEvent is created or fired.
+ if (length == 0)
+ return;
+ try
+ {
+ // Must obtain a write lock for this method. writeLock() and
+ // 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);
+
+ // Visit all paragraph elements within the specified interval
+ int end = offset + length;
+ Element curr;
+ for (int pos = offset; pos < end;)
+ {
+ // Get the CharacterElement at offset pos.
+ curr = getCharacterElement(pos);
+ if (pos == curr.getEndOffset())
+ break;
+
+ MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes();
+ ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace));
+ // If replace is true, remove all the old attributes.
+ 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();
+ }
+ }
+
+ /**
+ * Sets the logical style for the paragraph at the specified position.
+ *
+ * @param position
+ * the position at which the logical style is added
+ * @param style
+ * the style to set for the current paragraph
+ */
+ public void setLogicalStyle(int position, Style style)
+ {
+ Element el = getParagraphElement(position);
+ // 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);
+ fireChangedUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+ }
+ else
+ throw new AssertionError(
+ "paragraph elements are expected to be"
+ + "instances of AbstractDocument.AbstractElement");
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ /**
+ * Sets text attributes for the paragraph at the specified fragment.
+ *
+ * @param offset
+ * the beginning of the fragment
+ * @param length
+ * the length of the fragment
+ * @param attributes
+ * the text attributes to set
+ * @param replace
+ * if <code>true</code>, the attributes of the current selection
+ * are overridden, otherwise they are merged
+ */
+ public void setParagraphAttributes(int offset, int length,
+ AttributeSet attributes, boolean replace)
+ {
+ 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
+ {
+ writeUnlock();
+ }
+ }
+
+ /**
+ * Called in response to content insert actions. This is used to update the
+ * element structure.
+ *
+ * @param ev
+ * the <code>DocumentEvent</code> describing the change
+ * @param attr
+ * the attributes for the change
+ */
+ protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
+ {
+ int offs = ev.getOffset();
+ int len = ev.getLength();
+ int endOffs = offs + len;
+ if (attr == null)
+ attr = SimpleAttributeSet.EMPTY;
+
+ // Paragraph attributes are fetched from the point _after_ the insertion.
+ Element paragraph = getParagraphElement(endOffs);
+ AttributeSet pAttr = paragraph.getAttributes();
+ // Character attributes are fetched from the actual insertion point.
+ Element paragraph2 = getParagraphElement(offs);
+ int contIndex = paragraph2.getElementIndex(offs);
+ Element content = paragraph2.getElement(contIndex);
+ AttributeSet cAttr = content.getAttributes();
+
+ boolean insertAtBoundary = content.getEndOffset() == endOffs;
+ try
+ {
+ Segment s = new Segment();
+ ArrayList buf = new ArrayList();
+ ElementSpec lastStartTag = null;
+ boolean insertAfterNewline = false;
+ short lastStartDir = ElementSpec.OriginateDirection;
+
+ // Special handle if we are inserting after a newline.
+ if (offs > 0)
+ {
+ getText(offs - 1, 1, s);
+ if (s.array[s.offset] == '\n')
+ {
+ insertAfterNewline = true;
+ lastStartDir = insertAfterNewline(paragraph, paragraph2,
+ pAttr, buf, offs,
+ endOffs);
+ // Search last start tag.
+ for (int i = buf.size() - 1; i >= 0 && lastStartTag == null;
+ i--)
+ {
+ ElementSpec tag = (ElementSpec) buf.get(i);
+ if (tag.getType() == ElementSpec.StartTagType)
+ {
+ lastStartTag = tag;
+ }
+ }
+ }
+
+ }
+
+ // If we are not inserting after a newline, the paragraph attributes
+ // come from the paragraph under the insertion point.
+ if (! insertAfterNewline)
+ pAttr = paragraph2.getAttributes();
+
+ // Scan text and build up the specs.
+ getText(offs, len, s);
+ int end = s.offset + s.count;
+ int last = s.offset;
+ for (int i = s.offset; i < end; i++)
+ {
+ if (s.array[i] == '\n')
+ {
+ int breakOffs = i + 1;
+ buf.add(new ElementSpec(attr, ElementSpec.ContentType,
+ breakOffs - last));
+ buf.add(new ElementSpec(null, ElementSpec.EndTagType));
+ lastStartTag = new ElementSpec(pAttr,
+ ElementSpec.StartTagType);
+ buf.add(lastStartTag);
+ last = breakOffs;
+ }
+ }
+
+ // Need to add a tailing content tag if we didn't finish at a boundary.
+ if (last < end)
+ {
+ buf.add(new ElementSpec(attr, ElementSpec.ContentType,
+ end - last));
+ }
+
+ // Now we need to fix up the directions of the specs.
+ ElementSpec first = (ElementSpec) buf.get(0);
+ int doclen = getLength();
+
+ // Maybe join-previous the first tag if it is content and has
+ // the same attributes as the previous character run.
+ if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr))
+ first.setDirection(ElementSpec.JoinPreviousDirection);
+
+ // Join-fracture or join-next the last start tag if necessary.
+ if (lastStartTag != null)
+ {
+ if (insertAfterNewline)
+ lastStartTag.setDirection(lastStartDir);
+ else if (paragraph2.getEndOffset() != endOffs)
+ lastStartTag.setDirection(ElementSpec.JoinFractureDirection);
+ else
+ {
+ Element par = paragraph2.getParentElement();
+ int par2Index = par.getElementIndex(offs);
+ if (par2Index + 1 < par.getElementCount()
+ && ! par.getElement(par2Index + 1).isLeaf())
+ lastStartTag.setDirection(ElementSpec.JoinNextDirection);
+ }
+ }
+
+ // Join-next last tag if possible.
+ if (insertAtBoundary && endOffs < doclen)
+ {
+ ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
+ if (lastTag.getType() == ElementSpec.ContentType
+ && ((lastStartTag == null
+ && (paragraph == paragraph2 || insertAfterNewline))
+ || (lastStartTag != null
+ && lastStartTag.getDirection() != ElementSpec.OriginateDirection)))
+ {
+ int nextIndex = paragraph.getElementIndex(endOffs);
+ Element nextRun = paragraph.getElement(nextIndex);
+ if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes()))
+ lastTag.setDirection(ElementSpec.JoinNextDirection);
+ }
+ }
+
+ else if (! insertAtBoundary && lastStartTag != null
+ && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection)
+ {
+ ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
+ if (lastTag.getType() == ElementSpec.ContentType
+ && lastTag.getDirection() != ElementSpec.JoinPreviousDirection
+ && attr.isEqual(cAttr))
+ {
+ lastTag.setDirection(ElementSpec.JoinNextDirection);
+ }
+ }
+
+ ElementSpec[] specs = new ElementSpec[buf.size()];
+ specs = (ElementSpec[]) buf.toArray(specs);
+ buffer.insert(offs, len, specs, ev);
+ }
+ catch (BadLocationException ex)
+ {
+ // Ignore this. Comment out for debugging.
+ ex.printStackTrace();
+ }
+ super.insertUpdate(ev, attr);
+ }
+
+ private short insertAfterNewline(Element par1, Element par2,
+ AttributeSet attr, ArrayList buf,
+ int offs, int endOffs)
+ {
+ short dir = 0;
+ if (par1.getParentElement() == par2.getParentElement())
+ {
+ ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType);
+ buf.add(tag);
+ tag = new ElementSpec(attr, ElementSpec.StartTagType);
+ buf.add(tag);
+ if (par2.getEndOffset() != endOffs)
+ dir = ElementSpec.JoinFractureDirection;
+ else
+ {
+ Element par = par2.getParentElement();
+ if (par.getElementIndex(offs) + 1 < par.getElementCount())
+ dir = ElementSpec.JoinNextDirection;
+ }
+ }
+ else
+ {
+ // For text with more than 2 levels, find the common parent of
+ // par1 and par2.
+ ArrayList parentsLeft = new ArrayList();
+ ArrayList parentsRight = new ArrayList();
+ Element e = par2;
+ while (e != null)
+ {
+ parentsLeft.add(e);
+ e = e.getParentElement();
+ }
+ e = par1;
+ int leftIndex = -1;
+ while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1)
+ {
+ parentsRight.add(e);
+ e = e.getParentElement();
+ }
+
+ if (e != null)
+
+ {
+ // e is now the common parent.
+ // Insert the end tags.
+ for (int c = 0; c < leftIndex; c++)
+ {
+ buf.add(new ElementSpec(null, ElementSpec.EndTagType));
+ }
+ // Insert the start tags.
+ for (int c = parentsRight.size() - 1; c >= 0; c--)
+ {
+ Element el = (Element) parentsRight.get(c);
+ ElementSpec tag = new ElementSpec(el.getAttributes(),
+ ElementSpec.StartTagType);
+ if (c > 0)
+ tag.setDirection(ElementSpec.JoinNextDirection);
+ buf.add(tag);
+ }
+ if (parentsRight.size() > 0)
+ dir = ElementSpec.JoinNextDirection;
+ else
+ dir = ElementSpec.JoinFractureDirection;
+ }
+ else
+ assert false;
+ }
+ return dir;
+ }
+
+ /**
+ * 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 (paragraph.getStartOffset() != endOffset)
+ return ElementSpec.JoinFractureDirection;
+ // If there is an Element after this one, use JoinNextDirection.
+ Element parent = paragraph.getParentElement();
+ if (parent.getElementCount() > (parent.getElementIndex(offset) + 1))
+ return ElementSpec.JoinNextDirection;
+ }
+ 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
+ */
+ public Enumeration<?> getStyleNames()
+ {
+ StyleContext context = (StyleContext) getAttributeContext();
+ return context.getStyleNames();
+ }
+
+ /**
+ * Called when any of this document's styles changes.
+ *
+ * @param style
+ * the style that changed
+ */
+ protected void styleChanged(Style style)
+ {
+ // Nothing to do here. This is intended to be overridden by subclasses.
+ }
+
+ /**
+ * Inserts a bulk of structured content at once.
+ *
+ * @param offset
+ * the offset at which the content should be inserted
+ * @param data
+ * the actual content spec to be inserted
+ */
+ protected void insert(int offset, ElementSpec[] data)
+ throws BadLocationException
+ {
+ if (data == null || data.length == 0)
+ return;
+ try
+ {
+ // writeLock() and writeUnlock() should always be in a try/finally
+ // block so that locking balance is guaranteed even if some
+ // exception is thrown.
+ writeLock();
+
+ // First we collect the content to be inserted.
+ CPStringBuilder contentBuffer = new CPStringBuilder();
+ for (int i = 0; i < data.length; i++)
+ {
+ // 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());
+ }
+
+ int length = contentBuffer.length();
+
+ // If there was no content inserted then exit early.
+ if (length == 0)
+ return;
+
+ Content c = getContent();
+ UndoableEdit edit = c.insertString(offset,
+ contentBuffer.toString());
+
+ // Create the DocumentEvent with the ElementEdit added
+ DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
+ length,
+ DocumentEvent.EventType.INSERT);
+
+ ev.addEdit(edit);
+
+ // Finally we must update the document structure and fire the insert
+ // update event.
+ buffer.insert(offset, length, data, ev);
+
+ super.insertUpdate(ev, null);
+
+ ev.end();
+ fireInsertUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ /**
+ * Initializes the <code>DefaultStyledDocument</code> with the specified
+ * data.
+ *
+ * @param data
+ * the specification of the content with which the document is
+ * initialized
+ */
+ protected void create(ElementSpec[] data)
+ {
+ try
+ {
+
+ // Clear content if there is some.
+ int len = getLength();
+ if (len > 0)
+ remove(0, len);
+
+ writeLock();
+
+ // Now we insert the content.
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < data.length; ++i)
+ {
+ ElementSpec el = data[i];
+ if (el.getArray() != null && el.getLength() > 0)
+ b.append(el.getArray(), el.getOffset(), el.getLength());
+ }
+ Content content = getContent();
+ UndoableEdit cEdit = content.insertString(0, b.toString());
+
+ len = b.length();
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(0, b.length(),
+ DocumentEvent.EventType.INSERT);
+ ev.addEdit(cEdit);
+
+ buffer.create(len, data, ev);
+
+ // For the bidi update.
+ super.insertUpdate(ev, null);
+
+ ev.end();
+ fireInsertUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError("Unexpected bad location");
+ err.initCause(ex);
+ throw err;
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/DefaultTextUI.java b/libjava/classpath/javax/swing/text/DefaultTextUI.java
new file mode 100644
index 000000000..c347668b9
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DefaultTextUI.java
@@ -0,0 +1,62 @@
+/* DefaultTextUI.java -- Deprecated base UI for text components
+ Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import javax.swing.plaf.basic.BasicTextUI;
+
+/**
+ * This class is deprecated and should not be used anymore. The base UI for
+ * all text components is now {@link BasicTextUI}.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ * @deprecated as of 1.5 use {@link BasicTextUI} instead
+ */
+public abstract class DefaultTextUI extends BasicTextUI
+{
+ /**
+ * This class is deprecated and should not be used anymore. The base UI for
+ * all text components is now {@link BasicTextUI}.
+ *
+ * @deprecated use {@link BasicTextUI} instead
+ */
+ public DefaultTextUI()
+ {
+ // Nothing to do here.
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/Document.java b/libjava/classpath/javax/swing/text/Document.java
new file mode 100644
index 000000000..f23767f58
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Document.java
@@ -0,0 +1,221 @@
+/* Document.java --
+ Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text;
+
+import javax.swing.event.DocumentListener;
+import javax.swing.event.UndoableEditListener;
+
+/**
+ * A Document is the model that backs up all text components in Swing.
+ * This interface supports different kinds of implementations, from
+ * simple plain text model up to complex styled HTML or RTF models.
+ */
+public interface Document
+{
+ /**
+ * The key for the property that describes the source of a document.
+ */
+ String StreamDescriptionProperty = "stream";
+
+ /**
+ * The key for the property that is the title of a document.
+ */
+ String TitleProperty = "title";
+
+ /**
+ * Adds a {@link DocumentListener} to this document.
+ *
+ * @param listener the DocumentListener to add
+ */
+ void addDocumentListener(DocumentListener listener);
+
+ /**
+ * Adds an {@link UndoableEditListener} to this document.
+ *
+ * @param listener the UndoableEditListener to add
+ */
+ void addUndoableEditListener(UndoableEditListener listener);
+
+ /**
+ * Creates a mark in the character content at the specified offset.
+ *
+ * @param offs the offset where to place the mark
+ *
+ * @return the created Position object
+ *
+ * @throws BadLocationException of the specified offset is not a valid
+ * position in the documents content
+ */
+ Position createPosition(int offs)
+ throws BadLocationException;
+
+ /**
+ * Returns the default root element. Views should be using this element
+ * unless other mechanisms for assigning views to element structure is
+ * provided.
+ *
+ * @return the default root element
+ */
+ Element getDefaultRootElement();
+
+ /**
+ * Returns the position that marks the end of the document.
+ *
+ * @return the position that marks the end of the document
+ */
+ Position getEndPosition();
+
+ /**
+ * Returns the length of the document content.
+ *
+ * @return the length of the document content
+ */
+ int getLength();
+
+ /**
+ * Returns a document property with the specified key.
+ *
+ * @param key the (non-null) key for the property to fetch
+ *
+ * @return the property for <code>key</code> or null if no such property
+ * is stored
+ */
+ Object getProperty(Object key);
+
+ /**
+ * Returns the root elements of the document content.
+ *
+ * @return the root elements of the document content
+ */
+ Element[] getRootElements();
+
+ /**
+ * Returns the position that marks the beginning of the document
+ * content.
+ *
+ * @return the start position
+ */
+ Position getStartPosition();
+
+ /**
+ * Returns the textual content starting at <code>offset</code> with
+ * a length of <code>length</code>.
+ *
+ * @param offset the beginning of the text fragment to fetch
+ * @param length the length of the text fragment to fetch
+ *
+ * @return the text fragment starting at <code>offset</code> with
+ * a length of <code>length</code>
+ *
+ * @throws BadLocationException if <code>offset</code> or <code>length</code>
+ * are no valid locations in the document content
+ */
+ String getText(int offset, int length)
+ throws BadLocationException;
+
+ /**
+ * Fetch the textual content starting at <code>offset</code> with
+ * a length of <code>length</code> and store it in <code>txt</code>.
+ *
+ * @param offset the beginning of the text fragment to fetch
+ * @param length the length of the text fragment to fetch
+ * @param txt the Segment where to store the text fragment
+ *
+ * @throws BadLocationException if <code>offset</code> or <code>length</code>
+ * are no valid locations in the document content
+ */
+ void getText(int offset, int length, Segment txt)
+ throws BadLocationException;
+
+ /**
+ * Inserts a piece of text with an AttributeSet at the specified
+ * <code>offset</code>.
+ *
+ * @param offset the location where to insert the content
+ * @param str the textual content to insert
+ * @param a the Attributes associated with the piece of text
+ *
+ * @throws BadLocationException if <code>offset</code>
+ * is not a valid location in the document content
+ */
+ void insertString(int offset, String str, AttributeSet a)
+ throws BadLocationException;
+
+ /**
+ * Sets a document property.
+ *
+ * @param key the key of the property
+ * @param value the value of the property
+ */
+ void putProperty(Object key, Object value);
+
+ /**
+ * Removes a piece of content.
+ *
+ * @param offs the location of the fragment to remove
+ * @param len the length of the fragment to remove
+ *
+ * @throws BadLocationException if <code>offs</code> or <code>len</code>
+ * are no valid locations in the document content
+ */
+ void remove(int offs, int len)
+ throws BadLocationException;
+
+ /**
+ * Removes a DocumentListener from this Document.
+ *
+ * @param listener the DocumentListener to remove
+ */
+ void removeDocumentListener(DocumentListener listener);
+
+ /**
+ * Removes an UndoableEditListener from this Document.
+ *
+ * @param listener the UndoableEditListener to remove
+ */
+ void removeUndoableEditListener(UndoableEditListener listener);
+
+ /**
+ * This allows the Document to be rendered safely. It is made sure that
+ * the Runnable can read the document without any changes while reading.
+ * The Runnable is not allowed to change the Document itself.
+ *
+ * @param r the Runnable that renders the Document
+ */
+ void render(Runnable r);
+}
diff --git a/libjava/classpath/javax/swing/text/DocumentFilter.java b/libjava/classpath/javax/swing/text/DocumentFilter.java
new file mode 100644
index 000000000..1f7e8a689
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/DocumentFilter.java
@@ -0,0 +1,83 @@
+/* DocumentFilter.java --
+ Copyright (C) 2003, 2004 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;
+
+public class DocumentFilter
+{
+ public abstract static class FilterBypass
+ {
+ public FilterBypass()
+ {
+ // Do nothing here.
+ }
+
+ public abstract Document getDocument();
+
+ public abstract void insertString(int offset, String string,
+ AttributeSet attr)
+ throws BadLocationException;
+
+ public abstract void remove(int offset, int length)
+ throws BadLocationException;
+
+ public abstract void replace(int offset, int length, String string,
+ AttributeSet attrs)
+ throws BadLocationException;
+ }
+
+ public void insertString(DocumentFilter.FilterBypass fb, int offset,
+ String string, AttributeSet attr)
+ throws BadLocationException
+ {
+ fb.insertString(offset, string, attr);
+ }
+
+ public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
+ throws BadLocationException
+ {
+ fb.remove(offset, length);
+ }
+
+ public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
+ String text, AttributeSet attr)
+ throws BadLocationException
+ {
+ fb.replace(offset, length, text, attr);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/EditorKit.java b/libjava/classpath/javax/swing/text/EditorKit.java
new file mode 100644
index 000000000..62b4a6486
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/EditorKit.java
@@ -0,0 +1,98 @@
+/* EditorKit.java --
+ Copyright (C) 2002, 2004 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.Writer;
+
+import javax.swing.Action;
+import javax.swing.JEditorPane;
+
+public abstract class EditorKit implements Cloneable, Serializable
+{
+ private static final long serialVersionUID = -5044124649345887822L;
+
+ public EditorKit()
+ {
+ // Nothing to do here.
+ }
+
+ public Object clone()
+ {
+ try
+ {
+ return super.clone();
+ }
+ catch (CloneNotSupportedException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Called when the kit is being removed from the JEditorPane.
+ */
+ public void deinstall(JEditorPane c)
+ {
+ // This default implementation does nothing.
+ }
+
+ public void install(JEditorPane c)
+ {
+ // This default implementation does nothing.
+ }
+
+ public abstract Caret createCaret();
+ public abstract Document createDefaultDocument();
+ public abstract Action[] getActions();
+ public abstract String getContentType();
+ public abstract ViewFactory getViewFactory();
+ public abstract void read(InputStream in, Document doc, int pos)
+ throws BadLocationException, IOException;
+ public abstract void read(Reader in, Document doc, int pos)
+ throws BadLocationException, IOException;
+ public abstract void write(OutputStream out, Document doc, int pos, int len)
+ throws BadLocationException, IOException;
+ public abstract void write(Writer out, Document doc, int pos, int len)
+ throws BadLocationException, IOException;
+}
diff --git a/libjava/classpath/javax/swing/text/Element.java b/libjava/classpath/javax/swing/text/Element.java
new file mode 100644
index 000000000..83d883565
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Element.java
@@ -0,0 +1,54 @@
+/* Element.java --
+ Copyright (C) 2002 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;
+
+
+
+public interface Element
+{
+ AttributeSet getAttributes();
+ Document getDocument();
+ Element getElement(int index);
+ int getElementCount();
+ int getElementIndex(int offset);
+ int getEndOffset();
+ String getName();
+ Element getParentElement();
+ int getStartOffset();
+ boolean isLeaf();
+ }
diff --git a/libjava/classpath/javax/swing/text/ElementIterator.java b/libjava/classpath/javax/swing/text/ElementIterator.java
new file mode 100644
index 000000000..141137e2c
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/ElementIterator.java
@@ -0,0 +1,272 @@
+/* ElementIterator.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.util.Stack;
+
+/**
+ * This class can be used to iterate over the {@link Element} tree of
+ * a {@link Document} or an {@link Element}. This iterator performs
+ * an "in-order" traversal -- first it visits a node, then each of the
+ * node's children in order. No locking is performed during the
+ * iteration; that is up to the caller.
+ */
+public class ElementIterator implements Cloneable
+{
+ /**
+ * Uses to track the iteration on the stack.
+ */
+ private class ElementRef
+ {
+ /**
+ * The element.
+ */
+ Element element;
+
+ /**
+ * The child index. -1 means the element itself. >= 0 values mean the
+ * n-th child of the element.
+ */
+ int index;
+
+ /**
+ * Creates a new ElementRef.
+ *
+ * @param el the element
+ */
+ ElementRef(Element el)
+ {
+ element = el;
+ index = -1;
+ }
+ }
+
+ // The root element.
+ private Element root;
+
+ /**
+ * Holds ElementRefs.
+ */
+ private Stack stack;
+
+ /**
+ * Create a new ElementIterator to iterate over the given document.
+ * @param document the Document over which we iterate
+ */
+ public ElementIterator(Document document)
+ {
+ root = document.getDefaultRootElement();
+ }
+
+ /**
+ * Create a new ElementIterator to iterate over the given document.
+ * @param root the Document over which we iterate
+ */
+ public ElementIterator(Element root)
+ {
+ this.root = root;
+ }
+
+ /**
+ * Returns a new ElementIterator which is a clone of this
+ * ElementIterator.
+ */
+ public Object clone()
+ {
+ try
+ {
+ return super.clone();
+ }
+ catch (CloneNotSupportedException _)
+ {
+ // Can't happen.
+ return null;
+ }
+ }
+
+ /**
+ * Returns the current element.
+ */
+ public Element current()
+ {
+ Element current;
+ if (stack == null)
+ current = first();
+ else
+ {
+ current = null;
+ if (! stack.isEmpty())
+ {
+ ElementRef ref = (ElementRef) stack.peek();
+ Element el = ref.element;
+ int index = ref.index;
+ if (index == -1)
+ current = el;
+ else
+ current = el.getElement(index);
+ }
+ }
+ return current;
+ }
+
+ /**
+ * Returns the depth to which we have descended in the tree.
+ */
+ public int depth()
+ {
+ int depth = 0;
+ if (stack != null)
+ depth = stack.size();
+ return depth;
+ }
+
+ /**
+ * Returns the first element in the tree.
+ */
+ public Element first()
+ {
+ Element first = null;
+ if (root != null)
+ {
+ stack = new Stack();
+ if (root.getElementCount() > 0)
+ stack.push(new ElementRef(root));
+ first = root;
+ }
+ return first;
+ }
+
+ /**
+ * Advance the iterator and return the next element of the tree,
+ * performing an "in-order" traversal.
+ */
+ public Element next()
+ {
+ Element next;
+ if (stack == null)
+ next = first();
+ else
+ {
+ next = null;
+ if (! stack.isEmpty())
+ {
+ ElementRef ref = (ElementRef) stack.peek();
+ Element el = ref.element;
+ int index = ref.index;
+ if (el.getElementCount() > index + 1)
+ {
+ Element child = el.getElement(index + 1);
+ if (child.isLeaf())
+ ref.index++;
+ else
+ stack.push(new ElementRef(child));
+ next = child;
+ next = child;
+ }
+ else
+ {
+ stack.pop();
+ if (! stack.isEmpty())
+ {
+ ElementRef top = (ElementRef) stack.peek();
+ top.index++;
+ next = next();
+ }
+ }
+ }
+ // else return null.
+ }
+ return next;
+ }
+
+ /**
+ * Returns the previous item. Does not modify the iterator state.
+ */
+ public Element previous()
+ {
+ Element previous = null;
+ int stackSize;
+ if (stack != null && (stackSize = stack.size()) > 0)
+ {
+ ElementRef ref = (ElementRef) stack.peek();
+ Element el = ref.element;
+ int index = ref.index;
+ if (index > 0)
+ {
+ previous = deepestLeaf(el.getElement(--index));
+ }
+ else if (index == 0)
+ {
+ previous = el;
+ }
+ else if (index == -1)
+ {
+ ElementRef top = (ElementRef) stack.pop();
+ ElementRef item = (ElementRef) stack.peek();
+ stack.push(top);
+ index = item.index;
+ el = item.element;
+ previous = index == -1 ? el : deepestLeaf(el.getElement(index));
+ }
+ }
+ return previous;
+ }
+
+ /**
+ * Determines and returns the deepest leaf of the element <code>el</code>.
+ *
+ * @param el the base element
+ *
+ * @returnthe deepest leaf of the element <code>el</code>
+ */
+ private Element deepestLeaf(Element el)
+ {
+ Element leaf;
+ if (el.isLeaf())
+ leaf = el;
+ else
+ {
+ int count = el.getElementCount();
+ if (count == 0)
+ leaf = el;
+ else
+ leaf = deepestLeaf(el.getElement(count - 1));
+ }
+ return leaf;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/EmptyAttributeSet.java b/libjava/classpath/javax/swing/text/EmptyAttributeSet.java
new file mode 100644
index 000000000..92263bc1a
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/EmptyAttributeSet.java
@@ -0,0 +1,153 @@
+/* EmptyAttributeSet.java -- An empty attribute set
+ 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.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+ * An immutable, empty attribute set.
+ *
+ * @see SimpleAttributeSet#EMPTY
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+final class EmptyAttributeSet
+ implements AttributeSet
+{
+
+ /**
+ * Always return false as this AttributeSet doesn't contain any attributes.
+ */
+ public boolean containsAttribute(Object name, Object value)
+ {
+ return false;
+ }
+
+ /**
+ * Return true only if the attributes argument also contains no attributes.
+ */
+ public boolean containsAttributes(AttributeSet attributes)
+ {
+ return attributes.getAttributeCount() == 0;
+ }
+
+ /**
+ * Return this, as this is immutable.
+ */
+ public AttributeSet copyAttributes()
+ {
+ return this;
+ }
+
+ /**
+ * Always return null as this AttributeSet doesn't contain any attributes.
+ */
+ public Object getAttribute(Object key)
+ {
+ return null;
+ }
+
+ /**
+ * Always return 0.
+ */
+ public int getAttributeCount()
+ {
+ return 0;
+ }
+
+ /**
+ * Returns an empty Enumeration.
+ */
+ public Enumeration getAttributeNames()
+ {
+ return new Enumeration()
+ {
+ public boolean hasMoreElements()
+ {
+ return false;
+ }
+
+ public Object nextElement()
+ {
+ throw new NoSuchElementException("No more elements");
+ }
+
+ };
+ }
+
+ /**
+ * Always return null as this has no resolve parent.
+ */
+ public AttributeSet getResolveParent()
+ {
+ return null;
+ }
+
+ /**
+ * Always return false as this AttributeSet doesn't contain any attributes.
+ */
+ public boolean isDefined(Object attrName)
+ {
+ return false;
+ }
+
+ /**
+ * Other attribute sets are equal if they are empty too.
+ */
+ public boolean isEqual(AttributeSet attr)
+ {
+ return attr.getAttributeCount() == 0;
+ }
+
+ /**
+ * Other objects are equal if it's the same instance as this, or if
+ * it's another attribute set without attributes.
+ */
+ public boolean equals(Object o)
+ {
+ boolean eq = o == this;
+ if (! eq)
+ {
+ eq = (o instanceof AttributeSet)
+ && ((AttributeSet) o).getAttributeCount() == 0;
+ }
+ return eq;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/FieldView.java b/libjava/classpath/javax/swing/text/FieldView.java
new file mode 100644
index 000000000..c47bef9f7
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/FieldView.java
@@ -0,0 +1,310 @@
+/* FieldView.java --
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.BoundedRangeModel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.DocumentEvent;
+
+public class FieldView extends PlainView
+{
+ BoundedRangeModel horizontalVisibility;
+
+ /** Caches the preferred span of the X axis. It is invalidated by
+ * setting it to -1f. This is done when text in the document
+ * is inserted, removed or changed. The value is corrected as
+ * soon as calculateHorizontalSpan() is called.
+ */
+ float cachedSpan = -1f;
+
+ public FieldView(Element elem)
+ {
+ super(elem);
+
+ }
+
+ /** Checks whether the given container is a JTextField. If so
+ * it retrieves the textfield's horizontalVisibility instance.
+ *
+ * <p>This method should be only called when the view's container
+ * is valid. Naturally that would be the setParent() method however
+ * that method is not overridden in the RI and that is why we chose
+ * paint() instead.</p>
+ */
+ private void checkContainer()
+ {
+ Container c = getContainer();
+
+ if (c instanceof JTextField)
+ {
+ horizontalVisibility = ((JTextField) c).getHorizontalVisibility();
+
+ // Provokes a repaint when the BoundedRangeModel's values change
+ // (which is what the RI does).
+ horizontalVisibility.addChangeListener(new ChangeListener(){
+ public void stateChanged(ChangeEvent event) {
+ getContainer().repaint();
+ }
+ });
+
+ // It turned out that the span calculated at this point is wrong
+ // and needs to be recalculated (e.g. a different font setting is
+ // not taken into account).
+ calculateHorizontalSpan();
+
+ // Initializes the BoundedRangeModel properly.
+ updateVisibility();
+ }
+
+ }
+
+ private void updateVisibility()
+ {
+ JTextField tf = (JTextField) getContainer();
+ Insets insets = tf.getInsets();
+
+ int width = tf.getWidth() - insets.left - insets.right;
+
+ horizontalVisibility.setMaximum(Math.max((int) ((cachedSpan != -1f)
+ ? cachedSpan
+ : calculateHorizontalSpan()),
+ width));
+
+ horizontalVisibility.setExtent(width - 1);
+ }
+
+ protected FontMetrics getFontMetrics()
+ {
+ Component container = getContainer();
+ return container.getFontMetrics(container.getFont());
+ }
+
+ /**
+ * Vertically centers the single line of text within the
+ * bounds of the input shape. The returned Rectangle is centered
+ * vertically within <code>shape</code> and has a height of the
+ * preferred span along the Y axis. Horizontal adjustment is done according
+ * to the horizontalAligment property of the component that is rendered.
+ *
+ * @param shape the shape within which the line is beeing centered
+ */
+ protected Shape adjustAllocation(Shape shape)
+ {
+ // Return null when the original allocation is null (like the RI).
+ if (shape == null)
+ return null;
+
+ Rectangle rectIn = shape.getBounds();
+ // vertical adjustment
+ int height = (int) getPreferredSpan(Y_AXIS);
+ int y = rectIn.y + (rectIn.height - height) / 2;
+ // horizontal adjustment
+ JTextField textField = (JTextField) getContainer();
+ int width = (int) ((cachedSpan != -1f) ? cachedSpan : calculateHorizontalSpan());
+ int x;
+ if (horizontalVisibility != null && horizontalVisibility.getExtent() < width)
+ x = rectIn.x - horizontalVisibility.getValue();
+ else
+ switch (textField.getHorizontalAlignment())
+ {
+ case JTextField.CENTER:
+ x = rectIn.x + (rectIn.width - width) / 2;
+ break;
+ case JTextField.RIGHT:
+ x = rectIn.x + (rectIn.width - width - 1);
+ break;
+ case JTextField.TRAILING:
+ if (textField.getComponentOrientation().isLeftToRight())
+ x = rectIn.x + (rectIn.width - width - 1);
+ else
+ x = rectIn.x;
+ break;
+ case JTextField.LEADING:
+ if (textField.getComponentOrientation().isLeftToRight())
+ x = rectIn.x;
+ else
+ x = rectIn.x + (rectIn.width - width - 1);
+ break;
+ case JTextField.LEFT:
+ default:
+ x = rectIn.x;
+ break;
+ }
+
+ return new Rectangle(x, y, width, height);
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException();
+
+
+ if (axis == Y_AXIS)
+ return super.getPreferredSpan(axis);
+
+ if (cachedSpan != -1f)
+ return cachedSpan;
+
+ return calculateHorizontalSpan();
+ }
+
+ /** Calculates and sets the horizontal span and stores the value
+ * in cachedSpan.
+ */
+ private float calculateHorizontalSpan()
+ {
+ Segment s = getLineBuffer();
+ Element elem = getElement();
+
+ try
+ {
+ elem.getDocument().getText(elem.getStartOffset(),
+ elem.getEndOffset() - 1,
+ s);
+
+ return cachedSpan = Utilities.getTabbedTextWidth(s, getFontMetrics(), 0, this, s.offset);
+ }
+ catch (BadLocationException e)
+ {
+ // Should never happen
+ AssertionError ae = new AssertionError();
+ ae.initCause(e);
+ throw ae;
+ }
+ }
+
+ public int getResizeWeight(int axis)
+ {
+ return axis == X_AXIS ? 1 : 0;
+ }
+
+ public Shape modelToView(int pos, Shape a, Position.Bias bias)
+ throws BadLocationException
+ {
+ Shape newAlloc = adjustAllocation(a);
+ return super.modelToView(pos, newAlloc, bias);
+ }
+
+ public void paint(Graphics g, Shape s)
+ {
+ if (horizontalVisibility == null)
+ checkContainer();
+
+ Shape newAlloc = adjustAllocation(s);
+
+ Shape clip = g.getClip();
+ if (clip != null)
+ {
+ // Reason for this: The allocation area is always determined by the
+ // size of the component (and its insets) regardless of whether
+ // parts of the component are invisible or not (e.g. when the
+ // component is part of a JScrollPane and partly moved out of
+ // the user-visible range). However the clip of the Graphics
+ // instance may be adjusted properly to that condition but
+ // does not handle insets. By calculating the intersection
+ // we get the correct clip to paint the text in all cases.
+ Rectangle r = s.getBounds();
+ Rectangle cb = clip.getBounds();
+ SwingUtilities.computeIntersection(r.x, r.y, r.width, r.height, cb);
+
+ g.setClip(cb);
+ }
+ else
+ g.setClip(s);
+
+ super.paint(g, newAlloc);
+ g.setClip(clip);
+
+ }
+
+ public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
+ {
+ cachedSpan = -1f;
+
+ if (horizontalVisibility != null)
+ updateVisibility();
+
+ Shape newAlloc = adjustAllocation(shape);
+
+ super.insertUpdate(ev, newAlloc, vf);
+ getContainer().repaint();
+ }
+
+ public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
+ {
+ cachedSpan = -1f;
+
+ if (horizontalVisibility != null)
+ updateVisibility();
+
+ Shape newAlloc = adjustAllocation(shape);
+ super.removeUpdate(ev, newAlloc, vf);
+ getContainer().repaint();
+ }
+
+ public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
+ {
+ cachedSpan = -1f;
+
+ if (horizontalVisibility != null)
+ updateVisibility();
+
+ Shape newAlloc = adjustAllocation(shape);
+ super.changedUpdate(ev, newAlloc, vf);
+ getContainer().repaint();
+ }
+
+ public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias)
+ {
+ return super.viewToModel(fx, fy, adjustAllocation(a), bias);
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/FlowView.java b/libjava/classpath/javax/swing/text/FlowView.java
new file mode 100644
index 000000000..a9a80833a
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/FlowView.java
@@ -0,0 +1,841 @@
+/* FlowView.java -- A composite View
+ Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+
+/**
+ * A <code>View</code> that can flows it's children into it's layout space.
+ *
+ * The <code>FlowView</code> manages a set of logical views (that are
+ * the children of the {@link #layoutPool} field). These are translated
+ * at layout time into a set of physical views. These are the views that
+ * are managed as the real child views. Each of these child views represents
+ * a row and are laid out within a box using the superclasses behaviour.
+ * The concrete implementation of the rows must be provided by subclasses.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public abstract class FlowView extends BoxView
+{
+ /**
+ * A strategy for translating the logical views of a <code>FlowView</code>
+ * into the real views.
+ */
+ public static class FlowStrategy
+ {
+ /**
+ * Creates a new instance of <code>FlowStragegy</code>.
+ */
+ public FlowStrategy()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Receives notification from a <code>FlowView</code> that some content
+ * has been inserted into the document at a location that the
+ * <code>FlowView</code> is responsible for.
+ *
+ * The default implementation simply calls {@link #layout}.
+ *
+ * @param fv the flow view that sends the notification
+ * @param e the document event describing the change
+ * @param alloc the current allocation of the flow view
+ */
+ public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
+ {
+ if (alloc == null)
+ {
+ fv.layoutChanged(X_AXIS);
+ fv.layoutChanged(Y_AXIS);
+ }
+ else
+ {
+ Component host = fv.getContainer();
+ if (host != null)
+ host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+ }
+ }
+
+ /**
+ * Receives notification from a <code>FlowView</code> that some content
+ * has been removed from the document at a location that the
+ * <code>FlowView</code> is responsible for.
+ *
+ * The default implementation simply calls {@link #layout}.
+ *
+ * @param fv the flow view that sends the notification
+ * @param e the document event describing the change
+ * @param alloc the current allocation of the flow view
+ */
+ public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
+ {
+ if (alloc == null)
+ {
+ fv.layoutChanged(X_AXIS);
+ fv.layoutChanged(Y_AXIS);
+ }
+ else
+ {
+ Component host = fv.getContainer();
+ if (host != null)
+ host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+ }
+ }
+
+ /**
+ * Receives notification from a <code>FlowView</code> that some attributes
+ * have changed in the document at a location that the
+ * <code>FlowView</code> is responsible for.
+ *
+ * The default implementation simply calls {@link #layout}.
+ *
+ * @param fv the flow view that sends the notification
+ * @param e the document event describing the change
+ * @param alloc the current allocation of the flow view
+ */
+ public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
+ {
+ if (alloc == null)
+ {
+ fv.layoutChanged(X_AXIS);
+ fv.layoutChanged(Y_AXIS);
+ }
+ else
+ {
+ Component host = fv.getContainer();
+ if (host != null)
+ host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+ }
+ }
+
+ /**
+ * Returns the logical view of the managed <code>FlowView</code>.
+ *
+ * @param fv the flow view for which to return the logical view
+ *
+ * @return the logical view of the managed <code>FlowView</code>
+ */
+ protected View getLogicalView(FlowView fv)
+ {
+ return fv.layoutPool;
+ }
+
+ /**
+ * Performs the layout for the whole view. By default this rebuilds
+ * all the physical views from the logical views of the managed FlowView.
+ *
+ * This is called by {@link FlowView#layout} to update the layout of
+ * the view.
+ *
+ * @param fv the flow view for which we perform the layout
+ */
+ public void layout(FlowView fv)
+ {
+ int start = fv.getStartOffset();
+ int end = fv.getEndOffset();
+
+ // Preserve the views from the logical view from beeing removed.
+ View lv = getLogicalView(fv);
+ int viewCount = lv.getViewCount();
+ for (int i = 0; i < viewCount; i++)
+ {
+ View v = lv.getView(i);
+ v.setParent(lv);
+ }
+
+ // Then remove all views from the flow view.
+ fv.removeAll();
+
+ for (int rowIndex = 0; start < end; rowIndex++)
+ {
+ View row = fv.createRow();
+ fv.append(row);
+ int next = layoutRow(fv, rowIndex, start);
+ if (row.getViewCount() == 0)
+ {
+ row.append(createView(fv, start, Integer.MAX_VALUE, rowIndex));
+ next = row.getEndOffset();
+ }
+ if (start < next)
+ start = next;
+ else
+ assert false: "May not happen";
+ }
+ }
+
+ /**
+ * Lays out one row of the flow view. This is called by {@link #layout} to
+ * fill one row with child views until the available span is exhausted. The
+ * default implementation fills the row by calling
+ * {@link #createView(FlowView, int, int, int)} until the available space is
+ * exhausted, a forced break is encountered or there are no more views in
+ * the logical view. If the available space is exhausted,
+ * {@link #adjustRow(FlowView, int, int, int)} is called to fit the row into
+ * the available span.
+ *
+ * @param fv the flow view for which we perform the layout
+ * @param rowIndex the index of the row
+ * @param pos the model position for the beginning of the row
+ * @return the start position of the next row
+ */
+ protected int layoutRow(FlowView fv, int rowIndex, int pos)
+ {
+ View row = fv.getView(rowIndex);
+ int axis = fv.getFlowAxis();
+ int span = fv.getFlowSpan(rowIndex);
+ int x = fv.getFlowStart(rowIndex);
+ int end = fv.getEndOffset();
+
+ // Needed for adjusting indentation in adjustRow().
+ int preX = x;
+ int availableSpan = span;
+
+ TabExpander tabExp = fv instanceof TabExpander ? (TabExpander) fv : null;
+
+ boolean forcedBreak = false;
+ while (pos < end && span >= 0)
+ {
+ View view = createView(fv, pos, span, rowIndex);
+ if (view == null
+ || (span == 0 && view.getPreferredSpan(axis) > 0))
+ break;
+
+ int viewSpan;
+ if (axis == X_AXIS && view instanceof TabableView)
+ viewSpan = (int) ((TabableView) view).getTabbedSpan(x, tabExp);
+ else
+ viewSpan = (int) view.getPreferredSpan(axis);
+
+ // Break if the line if the view does not fit in this row or the
+ // line just must be broken.
+ int breakWeight = view.getBreakWeight(axis, pos, span);
+ if (breakWeight >= ForcedBreakWeight)
+ {
+ int rowViewCount = row.getViewCount();
+ if (rowViewCount > 0)
+ {
+ view = view.breakView(axis, pos, x, span);
+ if (view != null)
+ {
+ if (axis == X_AXIS && view instanceof TabableView)
+ viewSpan =
+ (int) ((TabableView) view).getTabbedSpan(x, tabExp);
+ else
+ viewSpan = (int) view.getPreferredSpan(axis);
+ }
+ else
+ viewSpan = 0;
+ }
+ forcedBreak = true;
+ }
+ span -= viewSpan;
+ x += viewSpan;
+ if (view != null)
+ {
+ row.append(view);
+ pos = view.getEndOffset();
+ }
+ if (forcedBreak)
+ break;
+ }
+
+ if (span < 0)
+ adjustRow(fv, rowIndex, availableSpan, preX);
+ else if (row.getViewCount() == 0)
+ {
+ View view = createView(fv, pos, Integer.MAX_VALUE, rowIndex);
+ row.append(view);
+ }
+ return row.getEndOffset();
+ }
+
+ /**
+ * Creates physical views that form the rows of the flow view. This
+ * can be an entire view from the logical view (if it fits within the
+ * available span), a fragment of such a view (if it doesn't fit in the
+ * available span and can be broken down) or <code>null</code> (if it does
+ * not fit in the available span and also cannot be broken down).
+ *
+ * The default implementation fetches the logical view at the specified
+ * <code>startOffset</code>. If that view has a different startOffset than
+ * specified in the argument, a fragment is created using
+ * {@link View#createFragment(int, int)} that has the correct startOffset
+ * and the logical view's endOffset.
+ *
+ * @param fv the flow view
+ * @param startOffset the start offset for the view to be created
+ * @param spanLeft the available span
+ * @param rowIndex the index of the row
+ *
+ * @return a view to fill the row with, or <code>null</code> if there
+ * is no view or view fragment that fits in the available span
+ */
+ protected View createView(FlowView fv, int startOffset, int spanLeft,
+ int rowIndex)
+ {
+ View logicalView = getLogicalView(fv);
+ int index = logicalView.getViewIndex(startOffset,
+ Position.Bias.Forward);
+ View retVal = logicalView.getView(index);
+ if (retVal.getStartOffset() != startOffset)
+ retVal = retVal.createFragment(startOffset, retVal.getEndOffset());
+ return retVal;
+ }
+
+ /**
+ * Tries to adjust the specified row to fit within the desired span. The
+ * default implementation iterates through the children of the specified
+ * row to find the view that has the highest break weight and - if there
+ * is more than one view with such a break weight - which is nearest to
+ * the end of the row. If there is such a view that has a break weight >
+ * {@link View#BadBreakWeight}, this view is broken using the
+ * {@link View#breakView(int, int, float, float)} method and this view and
+ * all views after the now broken view are replaced by the broken view.
+ *
+ * @param fv the flow view
+ * @param rowIndex the index of the row to be adjusted
+ * @param desiredSpan the layout span
+ * @param x the X location at which the row starts
+ */
+ protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
+ // Determine the last view that has the highest break weight.
+ int axis = fv.getFlowAxis();
+ View row = fv.getView(rowIndex);
+ int count = row.getViewCount();
+ int breakIndex = -1;
+ int breakWeight = BadBreakWeight;
+ int breakSpan = 0;
+ int currentSpan = 0;
+ for (int i = 0; i < count; ++i)
+ {
+ View view = row.getView(i);
+ int spanLeft = desiredSpan - currentSpan;
+ int weight = view.getBreakWeight(axis, x + currentSpan, spanLeft);
+ if (weight >= breakWeight && weight > BadBreakWeight)
+ {
+ breakIndex = i;
+ breakSpan = currentSpan;
+ breakWeight = weight;
+ if (weight >= ForcedBreakWeight)
+ // Don't search further.
+ break;
+ }
+ currentSpan += view.getPreferredSpan(axis);
+ }
+
+ // If there is a potential break location found, break the row at
+ // this location.
+ if (breakIndex >= 0)
+ {
+ int spanLeft = desiredSpan - breakSpan;
+ View toBeBroken = row.getView(breakIndex);
+ View brokenView = toBeBroken.breakView(axis,
+ toBeBroken.getStartOffset(),
+ x + breakSpan, spanLeft);
+ View lv = getLogicalView(fv);
+ for (int i = breakIndex; i < count; i++)
+ {
+ View tmp = row.getView(i);
+ if (contains(lv, tmp))
+ tmp.setParent(lv);
+ else if (tmp.getViewCount() > 0)
+ reparent(tmp, lv);
+ }
+ row.replace(breakIndex, count - breakIndex,
+ new View[]{ brokenView });
+ }
+
+ }
+
+ /**
+ * Helper method to determine if one view contains another as child.
+ */
+ private boolean contains(View view, View child)
+ {
+ boolean ret = false;
+ int n = view.getViewCount();
+ for (int i = 0; i < n && ret == false; i++)
+ {
+ if (view.getView(i) == child)
+ ret = true;
+ }
+ return ret;
+ }
+
+ /**
+ * Helper method that reparents the <code>view</code> and all of its
+ * decendents to the <code>parent</code> (the logical view).
+ *
+ * @param view the view to reparent
+ * @param parent the new parent
+ */
+ private void reparent(View view, View parent)
+ {
+ int n = view.getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View tmp = view.getView(i);
+ if (contains(parent, tmp))
+ tmp.setParent(parent);
+ else
+ reparent(tmp, parent);
+ }
+ }
+ }
+
+ /**
+ * This special subclass of <code>View</code> is used to represent
+ * the logical representation of this view. It does not support any
+ * visual representation, this is handled by the physical view implemented
+ * in the <code>FlowView</code>.
+ */
+ class LogicalView extends CompositeView
+ {
+ /**
+ * Creates a new LogicalView instance.
+ */
+ LogicalView(Element el)
+ {
+ super(el);
+ }
+
+ /**
+ * Overridden to return the attributes of the parent
+ * (== the FlowView instance).
+ */
+ public AttributeSet getAttributes()
+ {
+ View p = getParent();
+ return p != null ? p.getAttributes() : null;
+ }
+
+ protected void childAllocation(int index, Rectangle a)
+ {
+ // Nothing to do here (not visual).
+ }
+
+ protected View getViewAtPoint(int x, int y, Rectangle r)
+ {
+ // Nothing to do here (not visual).
+ return null;
+ }
+
+ protected boolean isAfter(int x, int y, Rectangle r)
+ {
+ // Nothing to do here (not visual).
+ return false;
+ }
+
+ protected boolean isBefore(int x, int y, Rectangle r)
+ {
+ // Nothing to do here (not visual).
+ return false;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ float max = 0;
+ float pref = 0;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ pref += v.getPreferredSpan(axis);
+ if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
+ >= ForcedBreakWeight)
+ {
+ max = Math.max(max, pref);
+ pref = 0;
+ }
+ }
+ max = Math.max(max, pref);
+ return max;
+ }
+
+ public float getMinimumSpan(int axis)
+ {
+ float max = 0;
+ float min = 0;
+ boolean wrap = true;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
+ == BadBreakWeight)
+ {
+ min += v.getPreferredSpan(axis);
+ wrap = false;
+ }
+ else if (! wrap)
+ {
+ max = Math.max(min, max);
+ wrap = true;
+ min = 0;
+ }
+ }
+ max = Math.max(max, min);
+ return max;
+ }
+
+ public void paint(Graphics g, Shape s)
+ {
+ // Nothing to do here (not visual).
+ }
+
+ /**
+ * Overridden to handle possible leaf elements.
+ */
+ protected void loadChildren(ViewFactory f)
+ {
+ Element el = getElement();
+ if (el.isLeaf())
+ {
+ View v = new LabelView(el);
+ append(v);
+ }
+ else
+ super.loadChildren(f);
+ }
+
+ /**
+ * Overridden to reparent the children to this logical view, in case
+ * they have been parented by a row.
+ */
+ protected void forwardUpdateToView(View v, DocumentEvent e, Shape a,
+ ViewFactory f)
+ {
+ v.setParent(this);
+ super.forwardUpdateToView(v, e, a, f);
+ }
+
+ /**
+ * Overridden to handle possible leaf element.
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ int index = 0;
+ if (! getElement().isLeaf())
+ index = super.getViewIndexAtPosition(pos);
+ return index;
+ }
+ }
+
+ /**
+ * The shared instance of FlowStrategy.
+ */
+ static final FlowStrategy sharedStrategy = new FlowStrategy();
+
+ /**
+ * The span of the <code>FlowView</code> that should be flowed.
+ */
+ protected int layoutSpan;
+
+ /**
+ * Represents the logical child elements of this view, encapsulated within
+ * one parent view (an instance of a package private <code>LogicalView</code>
+ * class). These will be translated to a set of real views that are then
+ * displayed on screen. This translation is performed by the inner class
+ * {@link FlowStrategy}.
+ */
+ protected View layoutPool;
+
+ /**
+ * The <code>FlowStrategy</code> to use for translating between the
+ * logical and physical view.
+ */
+ protected FlowStrategy strategy;
+
+ /**
+ * Creates a new <code>FlowView</code> for the given
+ * <code>Element</code> and <code>axis</code>.
+ *
+ * @param element the element that is rendered by this FlowView
+ * @param axis the axis along which the view is tiled, either
+ * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>, the flow
+ * axis is orthogonal to this one
+ */
+ public FlowView(Element element, int axis)
+ {
+ super(element, axis);
+ strategy = sharedStrategy;
+ layoutSpan = Short.MAX_VALUE;
+ }
+
+ /**
+ * Returns the axis along which the view should be flowed. This is
+ * orthogonal to the axis along which the boxes are tiled.
+ *
+ * @return the axis along which the view should be flowed
+ */
+ public int getFlowAxis()
+ {
+ int axis = getAxis();
+ int flowAxis;
+
+ if (axis == X_AXIS)
+ flowAxis = Y_AXIS;
+ else
+ flowAxis = X_AXIS;
+
+ return flowAxis;
+
+ }
+
+ /**
+ * Returns the span of the flow for the specified child view. A flow
+ * layout can be shaped by providing different span values for different
+ * child indices. The default implementation returns the entire available
+ * span inside the view.
+ *
+ * @param index the index of the child for which to return the span
+ *
+ * @return the span of the flow for the specified child view
+ */
+ public int getFlowSpan(int index)
+ {
+ return layoutSpan;
+ }
+
+ /**
+ * Returns the location along the flow axis where the flow span starts
+ * given a child view index. The flow can be shaped by providing
+ * different values here.
+ *
+ * @param index the index of the child for which to return the flow location
+ *
+ * @return the location along the flow axis where the flow span starts
+ */
+ public int getFlowStart(int index)
+ {
+ return 0;
+ }
+
+ /**
+ * Creates a new view that represents a row within a flow.
+ *
+ * @return a view for a new row
+ */
+ protected abstract View createRow();
+
+ /**
+ * Loads the children of this view. The <code>FlowView</code> does not
+ * directly load its children. Instead it creates a logical view
+ * ({@link #layoutPool}) which is filled by the logical child views.
+ * The real children are created at layout time and each represent one
+ * row.
+ *
+ * This method is called by {@link View#setParent} in order to initialize
+ * the view.
+ *
+ * @param vf the view factory to use for creating the child views
+ */
+ protected void loadChildren(ViewFactory vf)
+ {
+ if (layoutPool == null)
+ {
+ layoutPool = new LogicalView(getElement());
+ }
+ layoutPool.setParent(this);
+ // Initialize the flow strategy.
+ strategy.insertUpdate(this, null, null);
+ }
+
+ /**
+ * Performs the layout of this view. If the span along the flow axis changed,
+ * this first calls {@link FlowStrategy#layout} in order to rebuild the
+ * rows of this view. Then the superclass's behaviour is called to arrange
+ * the rows within the box.
+ *
+ * @param width the width of the view
+ * @param height the height of the view
+ */
+ protected void layout(int width, int height)
+ {
+ int flowAxis = getFlowAxis();
+ int span;
+ if (flowAxis == X_AXIS)
+ span = (int) width;
+ else
+ span = (int) height;
+
+ if (layoutSpan != span)
+ {
+ layoutChanged(flowAxis);
+ layoutChanged(getAxis());
+ layoutSpan = span;
+ }
+
+ if (! isLayoutValid(flowAxis))
+ {
+ int axis = getAxis();
+ int oldSpan = axis == X_AXIS ? getWidth() : getHeight();
+ strategy.layout(this);
+ int newSpan = (int) getPreferredSpan(axis);
+ if (oldSpan != newSpan)
+ {
+ View parent = getParent();
+ if (parent != null)
+ parent.preferenceChanged(this, axis == X_AXIS, axis == Y_AXIS);
+ }
+ }
+
+ super.layout(width, height);
+ }
+
+ /**
+ * Receice notification that some content has been inserted in the region
+ * that this view is responsible for. This calls
+ * {@link FlowStrategy#insertUpdate}.
+ *
+ * @param changes the document event describing the changes
+ * @param a the current allocation of the view
+ * @param vf the view factory that is used for creating new child views
+ */
+ public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
+ {
+ // First we must send the insertUpdate to the logical view so it can
+ // be updated accordingly.
+ layoutPool.insertUpdate(changes, a, vf);
+ strategy.insertUpdate(this, changes, getInsideAllocation(a));
+ }
+
+ /**
+ * Receice notification that some content has been removed from the region
+ * that this view is responsible for. This calls
+ * {@link FlowStrategy#removeUpdate}.
+ *
+ * @param changes the document event describing the changes
+ * @param a the current allocation of the view
+ * @param vf the view factory that is used for creating new child views
+ */
+ public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
+ {
+ layoutPool.removeUpdate(changes, a, vf);
+ strategy.removeUpdate(this, changes, getInsideAllocation(a));
+ }
+
+ /**
+ * Receice notification that some attributes changed in the region
+ * that this view is responsible for. This calls
+ * {@link FlowStrategy#changedUpdate}.
+ *
+ * @param changes the document event describing the changes
+ * @param a the current allocation of the view
+ * @param vf the view factory that is used for creating new child views
+ */
+ public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
+ {
+ layoutPool.changedUpdate(changes, a, vf);
+ strategy.changedUpdate(this, changes, getInsideAllocation(a));
+ }
+
+ /**
+ * Returns the index of the child <code>View</code> for the given model
+ * position.
+ *
+ * This is implemented to iterate over the children of this
+ * view (the rows) and return the index of the first view that contains
+ * the given position.
+ *
+ * @param pos the model position for whicht the child <code>View</code> is
+ * queried
+ *
+ * @return the index of the child <code>View</code> for the given model
+ * position
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ // First make sure we have a valid layout.
+ if (!isAllocationValid())
+ layout(getWidth(), getHeight());
+
+ int count = getViewCount();
+ int result = -1;
+
+ for (int i = 0; i < count; ++i)
+ {
+ View child = getView(i);
+ int start = child.getStartOffset();
+ int end = child.getEndOffset();
+ if (start <= pos && end > pos)
+ {
+ result = i;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Calculates the size requirements of this <code>BoxView</code> along
+ * its minor axis, that is the axis opposite to the axis specified in the
+ * constructor.
+ *
+ * This is overridden and forwards the request to the logical view.
+ *
+ * @param axis the axis that is examined
+ * @param r the <code>SizeRequirements</code> object to hold the result,
+ * if <code>null</code>, a new one is created
+ *
+ * @return the size requirements for this <code>BoxView</code> along
+ * the specified axis
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ SizeRequirements res = r;
+ if (res == null)
+ res = new SizeRequirements();
+ res.minimum = (int) layoutPool.getMinimumSpan(axis);
+ res.preferred = Math.max(res.minimum,
+ (int) layoutPool.getPreferredSpan(axis));
+ res.maximum = Integer.MAX_VALUE;
+ res.alignment = 0.5F;
+ return res;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/GapContent.java b/libjava/classpath/javax/swing/text/GapContent.java
new file mode 100644
index 000000000..2e68fb5fe
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/GapContent.java
@@ -0,0 +1,1095 @@
+/* GapContent.java --
+ Copyright (C) 2002, 2004, 2005, 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.io.Serializable;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.swing.undo.UndoableEdit;
+
+/**
+ * This implementation of {@link AbstractDocument.Content} uses a gapped buffer.
+ * This takes advantage of the fact that text area content is mostly inserted
+ * sequentially. The buffer is a char array that maintains a gap at the current
+ * insertion point. If characters a inserted at gap boundaries, the cost is
+ * minimal (simple array access). The array only has to be shifted around when
+ * the insertion point moves (then the gap also moves and one array copy is
+ * necessary) or when the gap is filled up and the buffer has to be enlarged.
+ */
+public class GapContent
+ implements AbstractDocument.Content, Serializable
+{
+
+ /**
+ * A {@link Position} implementation for <code>GapContent</code>.
+ */
+ class GapContentPosition
+ implements Position
+ {
+
+ /**
+ * The index to the positionMarks array entry, which in turn holds the
+ * mark into the buffer array.
+ */
+ Mark mark;
+
+ /**
+ * Returns the current offset of this Position within the content.
+ *
+ * @return the current offset of this Position within the content.
+ */
+ public int getOffset()
+ {
+ return mark.getOffset();
+ }
+ }
+
+ /**
+ * Holds a mark into the buffer that is used by GapContentPosition to find
+ * the actual offset of the position. This is pulled out of the
+ * GapContentPosition object so that the mark and position can be handled
+ * independently, and most important, so that the GapContentPosition can
+ * be garbage collected while we still hold a reference to the Mark object.
+ */
+ private class Mark
+ extends WeakReference
+ {
+ /**
+ * The actual mark into the buffer.
+ */
+ int mark;
+
+ /**
+ * Creates a new Mark object for the specified offset.
+ *
+ * @param offset the offset
+ */
+ Mark(int offset)
+ {
+ super(null);
+ mark = offset;
+ }
+
+ Mark(int offset, GapContentPosition pos, ReferenceQueue queue)
+ {
+ super(pos, queue);
+ mark = offset;
+ }
+
+ /**
+ * Returns the offset of the mark.
+ *
+ * @return the offset of the mark
+ */
+ int getOffset()
+ {
+ int res = mark;
+ if (mark >= gapStart)
+ res -= (gapEnd - gapStart);
+ return Math.max(0, res);
+ }
+
+ /**
+ * Returns the GapContentPosition that is associated ith this mark.
+ * This fetches the weakly referenced position object.
+ *
+ * @return the GapContentPosition that is associated ith this mark
+ */
+ GapContentPosition getPosition()
+ {
+ return (GapContentPosition) get();
+ }
+
+ }
+
+ /**
+ * Stores a reference to a mark that can be resetted to the original value
+ * after a mark has been moved. This is used for undoing actions.
+ */
+ private class UndoPosRef
+ {
+ /**
+ * The mark that might need to be reset.
+ */
+ private Mark mark;
+
+ /**
+ * The original offset to reset the mark to.
+ */
+ private int undoOffset;
+
+ /**
+ * Creates a new UndoPosRef.
+ *
+ * @param m the mark
+ */
+ UndoPosRef(Mark m)
+ {
+ mark = m;
+ undoOffset = mark.getOffset();
+ }
+
+ /**
+ * Resets the position of the mark to the value that it had when
+ * creating this UndoPosRef.
+ */
+ void reset()
+ {
+ if (undoOffset <= gapStart)
+ mark.mark = undoOffset;
+ else
+ mark.mark = (gapEnd - gapStart) + undoOffset;
+ }
+ }
+
+ private class InsertUndo extends AbstractUndoableEdit
+ {
+ public int where, length;
+ String text;
+ private Vector positions;
+
+ public InsertUndo(int start, int len)
+ {
+ where = start;
+ length = len;
+ }
+
+ public void undo () throws CannotUndoException
+ {
+ super.undo();
+ try
+ {
+ positions = getPositionsInRange(null, where, length);
+ text = getString(where, length);
+ remove(where, length);
+ }
+ catch (BadLocationException ble)
+ {
+ throw new CannotUndoException();
+ }
+ }
+
+ public void redo () throws CannotUndoException
+ {
+ super.redo();
+ try
+ {
+ insertString(where, text);
+ if (positions != null)
+ {
+ updateUndoPositions(positions, where, length);
+ positions = null;
+ }
+ }
+ catch (BadLocationException ble)
+ {
+ throw new CannotRedoException();
+ }
+ }
+
+ }
+
+ private class UndoRemove extends AbstractUndoableEdit
+ {
+ public int where;
+ String text;
+
+ /**
+ * The positions in the removed range.
+ */
+ private Vector positions;
+
+ public UndoRemove(int start, String removedText)
+ {
+ where = start;
+ text = removedText;
+ positions = getPositionsInRange(null, start, removedText.length());
+ }
+
+ public void undo () throws CannotUndoException
+ {
+ super.undo();
+ try
+ {
+ insertString(where, text);
+ if (positions != null)
+ updateUndoPositions(positions, where, text.length());
+ }
+ catch (BadLocationException ble)
+ {
+ throw new CannotUndoException();
+ }
+ }
+
+ public void redo () throws CannotUndoException
+ {
+ super.redo();
+ try
+ {
+ text = getString(where, text.length());
+ positions = getPositionsInRange(null, where, text.length());
+ remove(where, text.length());
+ }
+ catch (BadLocationException ble)
+ {
+ throw new CannotRedoException();
+ }
+ }
+
+ }
+
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -6226052713477823730L;
+
+ /**
+ * This is the default buffer size and the amount of bytes that a buffer is
+ * extended if it is full.
+ */
+ static final int DEFAULT_BUFSIZE = 10;
+
+ /**
+ * The text buffer.
+ */
+ char[] buffer;
+
+ /**
+ * The index of the first character of the gap.
+ */
+ int gapStart;
+
+ /**
+ * The index of the character after the last character of the gap.
+ */
+ int gapEnd;
+
+ // FIXME: We might want to track GC'ed GapContentPositions and remove their
+ // corresponding marks, or alternativly, perform some regular cleanup of
+ // the positionMarks array.
+
+ /**
+ * Holds the marks for positions. These marks are referenced by the
+ * GapContentPosition instances by an index into this array.
+ *
+ * This is package private to avoid accessor synthetic methods.
+ */
+ ArrayList marks;
+
+ /**
+ * The number of unused marks.
+ */
+ private int garbageMarks;
+
+ /**
+ * A 'static' mark that is used for searching.
+ */
+ private Mark searchMark = new Mark(0);
+
+ /**
+ * Queues all references to GapContentPositions that are about to be
+ * GC'ed. This is used to remove the corresponding marks from the
+ * positionMarks array if the number of references to that mark reaches zero.
+ *
+ * This is package private to avoid accessor synthetic methods.
+ */
+ ReferenceQueue queueOfDeath;
+
+ /**
+ * Creates a new GapContent object.
+ */
+ public GapContent()
+ {
+ this(DEFAULT_BUFSIZE);
+ }
+
+ /**
+ * Creates a new GapContent object with a specified initial size.
+ *
+ * @param size the initial size of the buffer
+ */
+ public GapContent(int size)
+ {
+ size = Math.max(size, 2);
+ buffer = (char[]) allocateArray(size);
+ gapStart = 1;
+ gapEnd = size;
+ buffer[0] = '\n';
+ marks = new ArrayList();
+ queueOfDeath = new ReferenceQueue();
+ }
+
+ /**
+ * Allocates an array of the specified length that can then be used as
+ * buffer.
+ *
+ * @param size the size of the array to be allocated
+ *
+ * @return the allocated array
+ */
+ protected Object allocateArray(int size)
+ {
+ return new char[size];
+ }
+
+ /**
+ * Returns the length of the allocated buffer array.
+ *
+ * @return the length of the allocated buffer array
+ */
+ protected int getArrayLength()
+ {
+ return buffer.length;
+ }
+
+ /**
+ * Returns the length of the content.
+ *
+ * @return the length of the content
+ */
+ public int length()
+ {
+ return buffer.length - (gapEnd - gapStart);
+ }
+
+ /**
+ * Inserts a string at the specified position.
+ *
+ * @param where the position where the string is inserted
+ * @param str the string that is to be inserted
+ *
+ * @return an UndoableEdit object
+ *
+ * @throws BadLocationException if <code>where</code> is not a valid
+ * location in the buffer
+ */
+ public UndoableEdit insertString(int where, String str)
+ throws BadLocationException
+ {
+ // check arguments
+ int length = length();
+ int strLen = str.length();
+
+ if (where < 0)
+ throw new BadLocationException("The where argument cannot be smaller"
+ + " than the zero", where);
+
+ if (where > length)
+ throw new BadLocationException("The where argument cannot be greater"
+ + " than the content length", where);
+
+ InsertUndo undo = new InsertUndo(where, strLen);
+ replace(where, 0, str.toCharArray(), strLen);
+
+ return undo;
+ }
+
+ /**
+ * Removes a piece of content at th specified position.
+ *
+ * @param where the position where the content is to be removed
+ * @param nitems number of characters to be removed
+ *
+ * @return an UndoableEdit object
+ *
+ * @throws BadLocationException if <code>where</code> is not a valid
+ * location in the buffer
+ */
+ public UndoableEdit remove(int where, int nitems) throws BadLocationException
+ {
+ // check arguments
+ int length = length();
+
+ if ((where + nitems) >= length)
+ throw new BadLocationException("where + nitems cannot be greater"
+ + " than the content length", where + nitems);
+
+ String removedText = getString(where, nitems);
+ UndoRemove undoRemove = new UndoRemove(where, removedText);
+ replace(where, nitems, null, 0);
+
+ return undoRemove;
+ }
+
+ /**
+ * Returns a piece of content as String.
+ *
+ * @param where the start location of the fragment
+ * @param len the length of the fragment
+ *
+ * @throws BadLocationException if <code>where</code> or
+ * <code>where + len</code> are no valid locations in the buffer
+ */
+ public String getString(int where, int len) throws BadLocationException
+ {
+ Segment seg = new Segment();
+ try
+ {
+ getChars(where, len, seg);
+ return new String(seg.array, seg.offset, seg.count);
+ }
+ catch (StringIndexOutOfBoundsException ex)
+ {
+ int invalid = 0;
+ if (seg.offset < 0 || seg.offset >= seg.array.length)
+ invalid = seg.offset;
+ else
+ invalid = seg.offset + seg.count;
+ throw new BadLocationException("Illegal location: array.length = "
+ + seg.array.length + ", offset = "
+ + seg.offset + ", count = "
+ + seg.count, invalid);
+ }
+ }
+
+ /**
+ * Fetches a piece of content and stores it in a {@link Segment} object.
+ *
+ * If the requested piece of text spans the gap, the content is copied into a
+ * new array. If it doesn't then it is contiguous and the actual content
+ * store is returned.
+ *
+ * @param where the start location of the fragment
+ * @param len the length of the fragment
+ * @param txt the Segment object to store the fragment in
+ *
+ * @throws BadLocationException if <code>where</code> or
+ * <code>where + len</code> are no valid locations in the buffer
+ */
+ public void getChars(int where, int len, Segment txt)
+ throws BadLocationException
+ {
+ // check arguments
+ int length = length();
+ if (where < 0)
+ throw new BadLocationException("the where argument may not be below zero", where);
+ if (where >= length)
+ throw new BadLocationException("the where argument cannot be greater"
+ + " than the content length", where);
+ if ((where + len) > length)
+ throw new BadLocationException("len plus where cannot be greater"
+ + " than the content length", len + where);
+ if (len < 0)
+ throw new BadLocationException("negative length not allowed: ", len);
+
+ // Optimized to copy only when really needed.
+ if (where + len <= gapStart)
+ {
+ // Simple case: completely before gap.
+ txt.array = buffer;
+ txt.offset = where;
+ txt.count = len;
+ }
+ else if (where > gapStart)
+ {
+ // Completely after gap, adjust offset.
+ txt.array = buffer;
+ txt.offset = gapEnd + where - gapStart;
+ txt.count = len;
+ }
+ else
+ {
+ // Spans the gap.
+ int beforeGap = gapStart - where;
+ if (txt.isPartialReturn())
+ {
+ // Return the part before the gap when partial return is allowed.
+ txt.array = buffer;
+ txt.offset = where;
+ txt.count = beforeGap;
+ }
+ else
+ {
+ // Copy pieces together otherwise.
+ txt.array = new char[len];
+ txt.offset = 0;
+ System.arraycopy(buffer, where, txt.array, 0, beforeGap);
+ System.arraycopy(buffer, gapEnd, txt.array, beforeGap,
+ len - beforeGap);
+ txt.count = len;
+ }
+ }
+ }
+
+ /**
+ * Creates and returns a mark at the specified position.
+ *
+ * @param offset the position at which to create the mark
+ *
+ * @return the create Position object for the mark
+ *
+ * @throws BadLocationException if the offset is not a valid position in the
+ * buffer
+ */
+ public Position createPosition(final int offset) throws BadLocationException
+ {
+ // Implementation note: We used to perform explicit check on the offset
+ // here. However, this makes some Mauve and Intel/Harmony tests fail
+ // and luckily enough the GapContent can very well deal with offsets
+ // outside the buffer bounds. So I removed that check.
+
+ // First do some garbage collections.
+ while (queueOfDeath.poll() != null)
+ garbageMarks++;
+ if (garbageMarks > Math.max(5, marks.size() / 10))
+ garbageCollect();
+
+ // We try to find a GapContentPosition at the specified offset and return
+ // that. Otherwise we must create a new one.
+ Mark m;
+ GapContentPosition pos;
+ int index = offset;
+ if (offset >= gapStart)
+ index += (gapEnd - gapStart);
+ searchMark.mark = index;
+ int insertIndex = search(searchMark);
+ if (!(insertIndex < marks.size()
+ && (m = (Mark) marks.get(insertIndex)).mark == index
+ && (pos = m.getPosition()) != null))
+ {
+ // Create new position if none was found.
+ pos = new GapContentPosition();
+ m = new Mark(index, pos, queueOfDeath);
+ pos.mark = m;
+ marks.add(insertIndex, m);
+ }
+ // Otherwise use the found position.
+
+ return pos;
+ }
+
+ /**
+ * Enlarges the gap. This allocates a new bigger buffer array, copy the
+ * segment before the gap as it is and the segment after the gap at the end
+ * of the new buffer array. This does change the gapEnd mark but not the
+ * gapStart mark.
+ *
+ * @param newSize the new size of the gap
+ */
+ protected void shiftEnd(int newSize)
+ {
+ assert newSize > (gapEnd - gapStart) : "The new gap size must be greater "
+ + "than the old gap size";
+
+ int oldEnd = getGapEnd();
+ int oldSize = getArrayLength();
+ int upper = oldSize - oldEnd;
+ int size = (newSize + 1) * 2;
+ int newEnd = size - upper;
+
+ // Copy the data around.
+ char[] newBuf = (char[]) allocateArray(size);
+ System.arraycopy(buffer, 0, newBuf, 0, Math.min(size, oldSize));
+ buffer = newBuf;
+ gapEnd = newEnd;
+ if (upper != 0)
+ System.arraycopy(buffer, oldEnd, buffer, newEnd, upper);
+
+ // Adjust marks.
+ int delta = gapEnd - oldEnd;
+ int adjIndex = searchFirst(oldEnd);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ m.mark += delta;
+ }
+ }
+
+ /**
+ * Shifts the gap to the specified position.
+ *
+ * @param newGapStart the new start position of the gap
+ */
+ protected void shiftGap(int newGapStart)
+ {
+ int oldStart = gapStart;
+ int delta = newGapStart - oldStart;
+ int oldEnd = gapEnd;
+ int newGapEnd = oldEnd + delta;
+ int size = oldEnd - oldStart;
+
+ // Shift gap in array.
+ gapStart = newGapStart;
+ gapEnd = newGapEnd;
+ if (delta > 0)
+ System.arraycopy(buffer, oldEnd, buffer, oldStart, delta);
+ else
+ System.arraycopy(buffer, newGapStart, buffer, newGapEnd, -delta);
+
+ // Adjust marks.
+ if (delta > 0)
+ {
+ int adjIndex = searchFirst(oldStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= newGapEnd)
+ break;
+ m.mark -= size;
+ }
+ }
+ else if (delta < 0)
+ {
+ int adjIndex = searchFirst(newGapStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= oldEnd)
+ break;
+ m.mark += size;
+ }
+ }
+ resetMarksAtZero();
+ }
+
+ /**
+ * Shifts the gap start downwards. This does not affect the content of the
+ * buffer. This only updates the gap start and all the marks that are between
+ * the old gap start and the new gap start. They all are squeezed to the start
+ * of the gap, because their location has been removed.
+ *
+ * @param newGapStart the new gap start
+ */
+ protected void shiftGapStartDown(int newGapStart)
+ {
+ if (newGapStart == gapStart)
+ return;
+
+ assert newGapStart < gapStart : "The new gap start must be less than the "
+ + "old gap start.";
+
+ // Adjust positions.
+ int adjIndex = searchFirst(newGapStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark > gapStart)
+ break;
+ m.mark = gapEnd;
+ }
+
+ gapStart = newGapStart;
+ resetMarksAtZero();
+ }
+
+ /**
+ * Shifts the gap end upwards. This does not affect the content of the
+ * buffer. This only updates the gap end and all the marks that are between
+ * the old gap end and the new end start. They all are squeezed to the end
+ * of the gap, because their location has been removed.
+ *
+ * @param newGapEnd the new gap start
+ */
+ protected void shiftGapEndUp(int newGapEnd)
+ {
+ if (newGapEnd == gapEnd)
+ return;
+
+ assert newGapEnd > gapEnd : "The new gap end must be greater than the "
+ + "old gap end.";
+
+ // Adjust marks.
+ int adjIndex = searchFirst(gapEnd);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= newGapEnd)
+ break;
+ m.mark = newGapEnd;
+ }
+
+
+ gapEnd = newGapEnd;
+ resetMarksAtZero();
+ }
+
+ /**
+ * Returns the allocated buffer array.
+ *
+ * @return the allocated buffer array
+ */
+ protected final Object getArray()
+ {
+ return buffer;
+ }
+
+ /**
+ * Replaces a portion of the storage with the specified items.
+ *
+ * @param position the position at which to remove items
+ * @param rmSize the number of items to remove
+ * @param addItems the items to add at location
+ * @param addSize the number of items to add
+ */
+ protected void replace(int position, int rmSize, Object addItems,
+ int addSize)
+ {
+ if (addSize == 0)
+ {
+ removeImpl(position, rmSize);
+ return;
+ }
+ else if (rmSize > addSize)
+ {
+ removeImpl(position + addSize, rmSize - addSize);
+ }
+ else
+ {
+ int endSize = addSize - rmSize;
+ int end = addImpl(position + rmSize, endSize);
+ System.arraycopy(addItems, rmSize, buffer, end, endSize);
+ addSize = rmSize;
+ }
+ System.arraycopy(addItems, 0, buffer, position, addSize);
+ }
+
+ /**
+ * Adjusts the positions and gap in response to a remove operation.
+ *
+ * @param pos the position at which to remove
+ * @param num the number of removed items
+ */
+ private void removeImpl(int pos, int num)
+ {
+ if (num > 0)
+ {
+ int end = pos + num;
+ int newGapSize = (gapEnd - gapStart) + num;
+ if (end <= gapStart)
+ {
+ if (gapStart != end)
+ {
+ shiftGap(end);
+ }
+ shiftGapStartDown(gapStart - num);
+ }
+ else if (pos >= gapStart)
+ {
+ if (gapStart != pos)
+ {
+ shiftGap(pos);
+ }
+ shiftGapEndUp(gapStart + newGapSize);
+ }
+ else
+ {
+ shiftGapStartDown(pos);
+ shiftGapEndUp(gapStart + newGapSize);
+ }
+ }
+ }
+
+ /**
+ * Adjusts the positions and gap in response to an add operation.
+ *
+ * @param pos the position at which to add
+ * @param num the number of added items
+ *
+ * @return the adjusted position
+ */
+ private int addImpl(int pos, int num)
+ {
+ int size = gapEnd - gapStart;
+ if (num == 0)
+ {
+ if (pos > gapStart)
+ pos += size;
+ return pos;
+ }
+
+ shiftGap(pos);
+ if (num >= size)
+ {
+ shiftEnd(getArrayLength() - size + num);
+ size = gapEnd - gapStart;
+ }
+
+ gapStart += num;
+ return pos;
+ }
+
+ /**
+ * Returns the start index of the gap within the buffer array.
+ *
+ * @return the start index of the gap within the buffer array
+ */
+ protected final int getGapStart()
+ {
+ return gapStart;
+ }
+
+ /**
+ * Returns the end index of the gap within the buffer array.
+ *
+ * @return the end index of the gap within the buffer array
+ */
+ protected final int getGapEnd()
+ {
+ return gapEnd;
+ }
+
+ /**
+ * Returns all <code>Position</code>s that are in the range specified by
+ * <code>offset</code> and </code>length</code> within the buffer array.
+ *
+ * @param v the vector to use; if <code>null</code>, a new Vector is allocated
+ * @param offset the start offset of the range to search
+ * @param length the length of the range to search
+ *
+ * @return the positions within the specified range
+ */
+ protected Vector getPositionsInRange(Vector v, int offset, int length)
+ {
+ int end = offset + length;
+ int startIndex;
+ int endIndex;
+ if (offset < gapStart)
+ {
+ if (offset == 0)
+ startIndex = 0;
+ else
+ startIndex = searchFirst(offset);
+ if (end >= gapStart)
+ endIndex = searchFirst(end + (gapEnd - gapStart) + 1);
+ else
+ endIndex = searchFirst(end + 1);
+ }
+ else
+ {
+ startIndex = searchFirst(offset + (gapEnd - gapStart));
+ endIndex = searchFirst(end + (gapEnd - gapStart) + 1);
+ }
+ if (v == null)
+ v = new Vector();
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ v.add(new UndoPosRef((Mark) marks.get(i)));
+ }
+ return v;
+ }
+
+ /**
+ * Resets all <code>Position</code> that have an offset of <code>0</code>,
+ * to also have an array index of <code>0</code>. This might be necessary
+ * after a call to <code>shiftGap(0)</code>, since then the marks at offset
+ * <code>0</code> get shifted to <code>gapEnd</code>.
+ */
+ protected void resetMarksAtZero()
+ {
+ if (gapStart != 0)
+ return;
+
+ for (int i = 0; i < marks.size(); i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark <= gapEnd)
+ m.mark = 0;
+ }
+ }
+
+ /**
+ * Resets the positions in the specified range to their original offset
+ * after a undo operation is performed. For example, after removing some
+ * content, the positions in the removed range will all be set to one
+ * offset. This method restores the positions to their original offsets
+ * after an undo.
+ *
+ * @param positions the positions to update
+ * @param offset
+ * @param length
+ */
+ protected void updateUndoPositions(Vector positions, int offset, int length)
+ {
+ for (Iterator i = positions.iterator(); i.hasNext();)
+ {
+ UndoPosRef undoPosRef = (UndoPosRef) i.next();
+ undoPosRef.reset();
+ }
+
+ // Resort marks.
+ Collections.sort(marks);
+ }
+
+ /**
+ * Outputs debugging info to System.err. It prints out the buffer array,
+ * the gapStart is marked by a &lt; sign, the gapEnd is marked by a &gt;
+ * sign and each position is marked by a # sign.
+ */
+ private void dump()
+ {
+ System.err.println("GapContent debug information");
+ System.err.println("buffer length: " + buffer.length);
+ System.err.println("gap start: " + gapStart);
+ System.err.println("gap end: " + gapEnd);
+ for (int i = 0; i < buffer.length; i++)
+ {
+ if (i == gapStart)
+ System.err.print('<');
+ if (i == gapEnd)
+ System.err.print('>');
+
+ if (!Character.isISOControl(buffer[i]))
+ System.err.print(buffer[i]);
+ else
+ System.err.print('.');
+ }
+ System.err.println();
+ }
+
+ /**
+ * Prints out the position marks.
+ */
+ private void dumpMarks()
+ {
+ System.out.print("positionMarks: ");
+ for (int i = 0; i < marks.size(); i++)
+ System.out.print(((Mark) marks.get(i)).mark + ", ");
+ System.out.println();
+ }
+
+ /**
+ * Searches the first occurance of object <code>o</code> in list
+ * <code>l</code>. This performs a binary search by calling
+ * {@link Collections#binarySearch(List, Object)} and when an object has been
+ * found, it searches backwards to the first occurance of that object in the
+ * list. The meaning of the return value is the same as in
+ * <code>Collections.binarySearch()</code>.
+ *
+ * @param o the object to be searched
+ *
+ * @return the index of the first occurance of o in l, or -i + 1 if not found
+ */
+ int search(Mark o)
+ {
+ int foundInd = 0;
+ boolean found = false;
+ int low = 0;
+ int up = marks.size() - 1;
+ int mid = 0;
+ if (up > -1)
+ {
+ int cmp = 0;
+ Mark last = (Mark) marks.get(up);
+ cmp = compare(o, last);
+ if (cmp > 0)
+ {
+ foundInd = up + 1;
+ found = true;
+ }
+ else
+ {
+ while (low <= up && ! found)
+ {
+ mid = low + (up - low) / 2;
+ Mark m = (Mark) marks.get(mid);
+ cmp = compare(o, m);
+ if (cmp == 0)
+ {
+ foundInd = mid;
+ found = true;
+ }
+ else if (cmp < 0)
+ up = mid - 1;
+ else
+ low = mid + 1;
+ }
+
+ if (! found)
+ foundInd = cmp < 0 ? mid : mid + 1;
+ }
+ }
+ return foundInd;
+ }
+
+ private int searchFirst(int index)
+ {
+ searchMark.mark = Math.max(index, 1);
+ int i = search(searchMark);
+ for (int j = i - 1; j >= 0; j--)
+ {
+ Mark m = (Mark) marks.get(j);
+ if (m.mark != index)
+ break;
+ i--;
+ }
+ return i;
+ }
+
+ /**
+ * Compares two marks.
+ *
+ * @param m1 the first mark
+ * @param m2 the second mark
+ *
+ * @return negative when m1 < m2, positive when m1 > m2 and 0 when equal
+ */
+ private int compare(Mark m1, Mark m2)
+ {
+ return m1.mark - m2.mark;
+ }
+
+ /**
+ * Collects and frees unused marks.
+ */
+ private void garbageCollect()
+ {
+ int count = marks.size();
+ ArrayList clean = new ArrayList();
+ for (int i = 0; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.get() != null)
+ clean.add(m);
+ }
+ marks = clean;
+ garbageMarks = 0;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/GlyphView.java b/libjava/classpath/javax/swing/text/GlyphView.java
new file mode 100644
index 000000000..3f4ccf9c2
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/GlyphView.java
@@ -0,0 +1,1333 @@
+/* GlyphView.java -- A view to render styled text
+ 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 gnu.classpath.SystemProperties;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Toolkit;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextHitInfo;
+import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.SwingConstants;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.Position.Bias;
+
+/**
+ * Renders a run of styled text. This {@link View} subclass paints the
+ * characters of the <code>Element</code> it is responsible for using
+ * the style information from that <code>Element</code>.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class GlyphView extends View implements TabableView, Cloneable
+{
+
+ /**
+ * An abstract base implementation for a glyph painter for
+ * <code>GlyphView</code>.
+ */
+ public abstract static class GlyphPainter
+ {
+ /**
+ * Creates a new <code>GlyphPainer</code>.
+ */
+ public GlyphPainter()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Returns the ascent of the font that is used by this glyph painter.
+ *
+ * @param v the glyph view
+ *
+ * @return the ascent of the font that is used by this glyph painter
+ */
+ public abstract float getAscent(GlyphView v);
+
+ /**
+ * Returns the descent of the font that is used by this glyph painter.
+ *
+ * @param v the glyph view
+ *
+ * @return the descent of the font that is used by this glyph painter
+ */
+ public abstract float getDescent(GlyphView v);
+
+ /**
+ * Returns the full height of the rendered text.
+ *
+ * @return the full height of the rendered text
+ */
+ public abstract float getHeight(GlyphView view);
+
+ /**
+ * Determines the model offset, so that the text between <code>p0</code>
+ * and this offset fits within the span starting at <code>x</code> with
+ * the length of <code>len</code>.
+ *
+ * @param v the glyph view
+ * @param p0 the starting offset in the model
+ * @param x the start location in the view
+ * @param len the length of the span in the view
+ */
+ public abstract int getBoundedPosition(GlyphView v, int p0, float x,
+ float len);
+
+ /**
+ * Paints the glyphs.
+ *
+ * @param view the glyph view to paint
+ * @param g the graphics context to use for painting
+ * @param a the allocation of the glyph view
+ * @param p0 the start position (in the model) from which to paint
+ * @param p1 the end position (in the model) to which to paint
+ */
+ public abstract void paint(GlyphView view, Graphics g, Shape a, int p0,
+ int p1);
+
+ /**
+ * Maps a position in the document into the coordinate space of the View.
+ * The output rectangle usually reflects the font height but has a width
+ * of zero.
+ *
+ * @param view the glyph view
+ * @param pos the position of the character in the model
+ * @param a the area that is occupied by the view
+ * @param b either {@link Position.Bias#Forward} or
+ * {@link Position.Bias#Backward} depending on the preferred
+ * direction bias. If <code>null</code> this defaults to
+ * <code>Position.Bias.Forward</code>
+ *
+ * @return a rectangle that gives the location of the document position
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>pos</code> is invalid
+ * @throws IllegalArgumentException if b is not one of the above listed
+ * valid values
+ */
+ public abstract Shape modelToView(GlyphView view, int pos, Position.Bias b,
+ Shape a)
+ throws BadLocationException;
+
+ /**
+ * Maps a visual position into a document location.
+ *
+ * @param v the glyph view
+ * @param x the X coordinate of the visual position
+ * @param y the Y coordinate of the visual position
+ * @param a the allocated region
+ * @param biasRet filled with the bias of the model location on method exit
+ *
+ * @return the model location that represents the specified view location
+ */
+ public abstract int viewToModel(GlyphView v, float x, float y, Shape a,
+ Position.Bias[] biasRet);
+
+ /**
+ * Determine the span of the glyphs from location <code>p0</code> to
+ * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
+ * then TABs are expanded using this <code>TabExpander</code>.
+ * The parameter <code>x</code> is the location at which the view is
+ * located (this is important when using TAB expansion).
+ *
+ * @param view the glyph view
+ * @param p0 the starting location in the document model
+ * @param p1 the end location in the document model
+ * @param te the tab expander to use
+ * @param x the location at which the view is located
+ *
+ * @return the span of the glyphs from location <code>p0</code> to
+ * location <code>p1</code>, possibly using TAB expansion
+ */
+ public abstract float getSpan(GlyphView view, int p0, int p1,
+ TabExpander te, float x);
+
+
+ /**
+ * Returns the model location that should be used to place a caret when
+ * moving the caret through the document.
+ *
+ * @param v the glyph view
+ * @param pos the current model location
+ * @param b the bias for <code>p</code>
+ * @param a the allocated region for the glyph view
+ * @param direction the direction from the current position; Must be one of
+ * {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
+ * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
+ * @param biasRet filled with the bias of the resulting location when method
+ * returns
+ *
+ * @return the location within the document that should be used to place the
+ * caret when moving the caret around the document
+ *
+ * @throws BadLocationException if <code>pos</code> is an invalid model
+ * location
+ * @throws IllegalArgumentException if <code>d</code> is invalid
+ */
+ public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b,
+ Shape a, int direction,
+ Position.Bias[] biasRet)
+ throws BadLocationException
+
+ {
+ int result = pos;
+ switch (direction)
+ {
+ case SwingConstants.EAST:
+ result = pos + 1;
+ break;
+ case SwingConstants.WEST:
+ result = pos - 1;
+ break;
+ case SwingConstants.NORTH:
+ case SwingConstants.SOUTH:
+ default:
+ // This should be handled in enclosing view, since the glyph view
+ // does not layout vertically.
+ break;
+ }
+ return result;
+ }
+
+ /**
+ * Returns a painter that can be used to render the specified glyph view.
+ * If this glyph painter is stateful, then it should return a new instance.
+ * However, if this painter is stateless it should return itself. The
+ * default behaviour is to return itself.
+ *
+ * @param v the glyph view for which to create a painter
+ * @param p0 the start offset of the rendered area
+ * @param p1 the end offset of the rendered area
+ *
+ * @return a painter that can be used to render the specified glyph view
+ */
+ public GlyphPainter getPainter(GlyphView v, int p0, int p1)
+ {
+ return this;
+ }
+ }
+
+ /**
+ * A GlyphPainter implementation based on TextLayout. This should give
+ * better performance in Java2D environments.
+ */
+ private static class J2DGlyphPainter
+ extends GlyphPainter
+ {
+
+ /**
+ * The text layout.
+ */
+ TextLayout textLayout;
+
+ /**
+ * Creates a new J2DGlyphPainter.
+ *
+ * @param str the string
+ * @param font the font
+ * @param frc the font render context
+ */
+ J2DGlyphPainter(String str, Font font, FontRenderContext frc)
+ {
+ textLayout = new TextLayout(str, font, frc);
+ }
+
+ /**
+ * Returns null so that GlyphView.checkPainter() creates a new instance.
+ */
+ public GlyphPainter getPainter(GlyphView v, int p0, int p1)
+ {
+ return null;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getAscent(GlyphView v)
+ {
+ return textLayout.getAscent();
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public int getBoundedPosition(GlyphView v, int p0, float x, float len)
+ {
+ int pos;
+ TextHitInfo hit = textLayout.hitTestChar(len, 0);
+ if (hit.getCharIndex() == -1 && ! textLayout.isLeftToRight())
+ pos = v.getEndOffset();
+ else
+ {
+ pos = hit.isLeadingEdge() ? hit.getInsertionIndex()
+ : hit.getInsertionIndex() - 1;
+ pos += v.getStartOffset();
+ }
+ return pos;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getDescent(GlyphView v)
+ {
+ return textLayout.getDescent();
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getHeight(GlyphView view)
+ {
+ return textLayout.getAscent() + textLayout.getDescent()
+ + textLayout.getLeading();
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public float getSpan(GlyphView v, int p0, int p1, TabExpander te, float x)
+ {
+ float span;
+ if (p0 == v.getStartOffset() && p1 == v.getEndOffset())
+ span = textLayout.getAdvance();
+ else
+ {
+ int start = v.getStartOffset();
+ int i0 = p0 - start;
+ int i1 = p1 - start;
+ TextHitInfo hit0 = TextHitInfo.afterOffset(i0);
+ TextHitInfo hit1 = TextHitInfo.afterOffset(i1);
+ float x0 = textLayout.getCaretInfo(hit0)[0];
+ float x1 = textLayout.getCaretInfo(hit1)[0];
+ span = Math.abs(x1 - x0);
+ }
+ return span;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public Shape modelToView(GlyphView v, int pos, Bias b, Shape a)
+ throws BadLocationException
+ {
+ int offs = pos - v.getStartOffset();
+ // Create copy here to protect original shape.
+ Rectangle2D bounds = a.getBounds2D();
+ TextHitInfo hit =
+ b == Position.Bias.Forward ? TextHitInfo.afterOffset(offs)
+ : TextHitInfo.beforeOffset(offs);
+ float[] loc = textLayout.getCaretInfo(hit);
+ bounds.setRect(bounds.getX() + loc[0], bounds.getY(), 1,
+ bounds.getHeight());
+ return bounds;
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1)
+ {
+ // Can't paint this with plain graphics.
+ if (g instanceof Graphics2D)
+ {
+ Graphics2D g2d = (Graphics2D) g;
+ Rectangle2D b = a instanceof Rectangle2D ? (Rectangle2D) a
+ : a.getBounds2D();
+ float x = (float) b.getX();
+ float y = (float) b.getY() + textLayout.getAscent()
+ + textLayout.getLeading();
+ // TODO: Try if clipping makes things faster for narrow views.
+ textLayout.draw(g2d, x, y);
+ }
+ }
+
+ /**
+ * Delegates to the text layout.
+ */
+ public int viewToModel(GlyphView v, float x, float y, Shape a,
+ Bias[] biasRet)
+ {
+ Rectangle2D bounds = a instanceof Rectangle2D ? (Rectangle2D) a
+ : a.getBounds2D();
+ TextHitInfo hit = textLayout.hitTestChar(x - (float) bounds.getX(), 0);
+ int pos = hit.getInsertionIndex();
+ biasRet[0] = hit.isLeadingEdge() ? Position.Bias.Forward
+ : Position.Bias.Backward;
+ return pos + v.getStartOffset();
+ }
+
+ }
+
+ /**
+ * The default <code>GlyphPainter</code> used in <code>GlyphView</code>.
+ */
+ static class DefaultGlyphPainter extends GlyphPainter
+ {
+ FontMetrics fontMetrics;
+
+ /**
+ * Returns the full height of the rendered text.
+ *
+ * @return the full height of the rendered text
+ */
+ public float getHeight(GlyphView view)
+ {
+ updateFontMetrics(view);
+ float height = fontMetrics.getHeight();
+ return height;
+ }
+
+ /**
+ * Paints the glyphs.
+ *
+ * @param view the glyph view to paint
+ * @param g the graphics context to use for painting
+ * @param a the allocation of the glyph view
+ * @param p0 the start position (in the model) from which to paint
+ * @param p1 the end position (in the model) to which to paint
+ */
+ public void paint(GlyphView view, Graphics g, Shape a, int p0,
+ int p1)
+ {
+ updateFontMetrics(view);
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ TabExpander tabEx = view.getTabExpander();
+ Segment txt = view.getText(p0, p1);
+
+ // Find out the X location at which we have to paint.
+ int x = r.x;
+ int p = view.getStartOffset();
+ if (p != p0)
+ {
+ int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx,
+ p);
+ x += width;
+ }
+ // Find out Y location.
+ int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent();
+
+ // Render the thing.
+ g.setFont(fontMetrics.getFont());
+ Utilities.drawTabbedText(txt, x, y, g, tabEx, p0);
+
+ }
+
+ /**
+ * Maps a position in the document into the coordinate space of the View.
+ * The output rectangle usually reflects the font height but has a width
+ * of zero.
+ *
+ * @param view the glyph view
+ * @param pos the position of the character in the model
+ * @param a the area that is occupied by the view
+ * @param b either {@link Position.Bias#Forward} or
+ * {@link Position.Bias#Backward} depending on the preferred
+ * direction bias. If <code>null</code> this defaults to
+ * <code>Position.Bias.Forward</code>
+ *
+ * @return a rectangle that gives the location of the document position
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>pos</code> is invalid
+ * @throws IllegalArgumentException if b is not one of the above listed
+ * valid values
+ */
+ public Shape modelToView(GlyphView view, int pos, Position.Bias b,
+ Shape a)
+ throws BadLocationException
+ {
+ updateFontMetrics(view);
+ Element el = view.getElement();
+ Segment txt = view.getText(el.getStartOffset(), pos);
+ Rectangle bounds = a instanceof Rectangle ? (Rectangle) a
+ : a.getBounds();
+ TabExpander expander = view.getTabExpander();
+ int width = Utilities.getTabbedTextWidth(txt, fontMetrics, bounds.x,
+ expander,
+ view.getStartOffset());
+ int height = fontMetrics.getHeight();
+ Rectangle result = new Rectangle(bounds.x + width, bounds.y,
+ 0, height);
+ return result;
+ }
+
+ /**
+ * Determine the span of the glyphs from location <code>p0</code> to
+ * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
+ * then TABs are expanded using this <code>TabExpander</code>.
+ * The parameter <code>x</code> is the location at which the view is
+ * located (this is important when using TAB expansion).
+ *
+ * @param view the glyph view
+ * @param p0 the starting location in the document model
+ * @param p1 the end location in the document model
+ * @param te the tab expander to use
+ * @param x the location at which the view is located
+ *
+ * @return the span of the glyphs from location <code>p0</code> to
+ * location <code>p1</code>, possibly using TAB expansion
+ */
+ public float getSpan(GlyphView view, int p0, int p1,
+ TabExpander te, float x)
+ {
+ updateFontMetrics(view);
+ Segment txt = view.getText(p0, p1);
+ int span = Utilities.getTabbedTextWidth(txt, fontMetrics, (int) x, te,
+ p0);
+ return span;
+ }
+
+ /**
+ * Returns the ascent of the text run that is rendered by this
+ * <code>GlyphPainter</code>.
+ *
+ * @param v the glyph view
+ *
+ * @return the ascent of the text run that is rendered by this
+ * <code>GlyphPainter</code>
+ *
+ * @see FontMetrics#getAscent()
+ */
+ public float getAscent(GlyphView v)
+ {
+ updateFontMetrics(v);
+ return fontMetrics.getAscent();
+ }
+
+ /**
+ * Returns the descent of the text run that is rendered by this
+ * <code>GlyphPainter</code>.
+ *
+ * @param v the glyph view
+ *
+ * @return the descent of the text run that is rendered by this
+ * <code>GlyphPainter</code>
+ *
+ * @see FontMetrics#getDescent()
+ */
+ public float getDescent(GlyphView v)
+ {
+ updateFontMetrics(v);
+ return fontMetrics.getDescent();
+ }
+
+ /**
+ * Determines the model offset, so that the text between <code>p0</code>
+ * and this offset fits within the span starting at <code>x</code> with
+ * the length of <code>len</code>.
+ *
+ * @param v the glyph view
+ * @param p0 the starting offset in the model
+ * @param x the start location in the view
+ * @param len the length of the span in the view
+ */
+ public int getBoundedPosition(GlyphView v, int p0, float x, float len)
+ {
+ updateFontMetrics(v);
+ TabExpander te = v.getTabExpander();
+ Segment txt = v.getText(p0, v.getEndOffset());
+ int pos = Utilities.getTabbedTextOffset(txt, fontMetrics, (int) x,
+ (int) (x + len), te, p0, false);
+ return pos + p0;
+ }
+
+ /**
+ * Maps a visual position into a document location.
+ *
+ * @param v the glyph view
+ * @param x the X coordinate of the visual position
+ * @param y the Y coordinate of the visual position
+ * @param a the allocated region
+ * @param biasRet filled with the bias of the model location on method exit
+ *
+ * @return the model location that represents the specified view location
+ */
+ public int viewToModel(GlyphView v, float x, float y, Shape a,
+ Bias[] biasRet)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ int p0 = v.getStartOffset();
+ int p1 = v.getEndOffset();
+ TabExpander te = v.getTabExpander();
+ Segment s = v.getText(p0, p1);
+ int offset = Utilities.getTabbedTextOffset(s, fontMetrics, r.x, (int) x,
+ te, p0);
+ int ret = p0 + offset;
+ if (ret == p1)
+ ret--;
+ biasRet[0] = Position.Bias.Forward;
+ return ret;
+ }
+
+ private void updateFontMetrics(GlyphView v)
+ {
+ Font font = v.getFont();
+ if (fontMetrics == null || ! font.equals(fontMetrics.getFont()))
+ {
+ Container c = v.getContainer();
+ FontMetrics fm;
+ if (c != null)
+ fm = c.getFontMetrics(font);
+ else
+ fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
+ fontMetrics = fm;
+ }
+ }
+ }
+
+ /**
+ * The GlyphPainer used for painting the glyphs.
+ */
+ GlyphPainter glyphPainter;
+
+ /**
+ * The start offset within the document for this view.
+ */
+ private int offset;
+
+ /**
+ * The end offset within the document for this view.
+ */
+ private int length;
+
+ /**
+ * The x location against which the tab expansion is done.
+ */
+ private float tabX;
+
+ /**
+ * The tab expander that is used in this view.
+ */
+ private TabExpander tabExpander;
+
+ /**
+ * Creates a new <code>GlyphView</code> for the given <code>Element</code>.
+ *
+ * @param element the element that is rendered by this GlyphView
+ */
+ public GlyphView(Element element)
+ {
+ super(element);
+ offset = 0;
+ length = 0;
+ }
+
+ /**
+ * Returns the <code>GlyphPainter</code> that is used by this
+ * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed
+ * <code>null</code> is returned.
+ *
+ * @return the glyph painter that is used by this
+ * glyph view or <code>null</code> if no glyph painter has been
+ * installed
+ */
+ public GlyphPainter getGlyphPainter()
+ {
+ return glyphPainter;
+ }
+
+ /**
+ * Sets the {@link GlyphPainter} to be used for this <code>GlyphView</code>.
+ *
+ * @param painter the glyph painter to be used for this glyph view
+ */
+ public void setGlyphPainter(GlyphPainter painter)
+ {
+ glyphPainter = painter;
+ }
+
+ /**
+ * Checks if a <code>GlyphPainer</code> is installed. If this is not the
+ * case, a default painter is installed.
+ */
+ protected void checkPainter()
+ {
+ if (glyphPainter == null)
+ {
+ if ("true".equals(
+ SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")))
+ {
+ glyphPainter = new DefaultGlyphPainter();
+ }
+ else
+ {
+ Segment s = getText(getStartOffset(), getEndOffset());
+ glyphPainter = new J2DGlyphPainter(s.toString(), getFont(),
+ new FontRenderContext(null,
+ false,
+ false));
+ }
+ }
+ }
+
+ /**
+ * Renders the <code>Element</code> that is associated with this
+ * <code>View</code>.
+ *
+ * @param g the <code>Graphics</code> context to render to
+ * @param a the allocated region for the <code>Element</code>
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ checkPainter();
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ Container c = getContainer();
+
+ Color fg = getForeground();
+ JTextComponent tc = null;
+ if (c instanceof JTextComponent)
+ {
+ tc = (JTextComponent) c;
+ if (! tc.isEnabled())
+ fg = tc.getDisabledTextColor();
+ }
+ Color bg = getBackground();
+ if (bg != null)
+ {
+ g.setColor(bg);
+ g.fillRect(r.x, r.y, r.width, r.height);
+ }
+
+
+ // Paint layered highlights if there are any.
+ if (tc != null)
+ {
+ Highlighter h = tc.getHighlighter();
+ if (h instanceof LayeredHighlighter)
+ {
+ LayeredHighlighter lh = (LayeredHighlighter) h;
+ lh.paintLayeredHighlights(g, p0, p1, a, tc, this);
+ }
+ }
+
+ g.setColor(fg);
+ glyphPainter.paint(this, g, a, p0, p1);
+ boolean underline = isUnderline();
+ boolean striked = isStrikeThrough();
+ if (underline || striked)
+ {
+ View parent = getParent();
+ // X coordinate.
+ if (parent != null && parent.getEndOffset() == p1)
+ {
+ // Strip whitespace.
+ Segment s = getText(p0, p1);
+ while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1]))
+ {
+ p1--;
+ s.count--;
+ }
+ }
+ int x0 = r.x;
+ int p = getStartOffset();
+ TabExpander tabEx = getTabExpander();
+ if (p != p0)
+ x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0);
+ int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0);
+ // Y coordinate.
+ int y = r.y + r.height - (int) glyphPainter.getDescent(this);
+ if (underline)
+ {
+ int yTmp = y;
+ yTmp += 1;
+ g.drawLine(x0, yTmp, x1, yTmp);
+ }
+ if (striked)
+ {
+ int yTmp = y;
+ yTmp -= (int) glyphPainter.getAscent(this);
+ g.drawLine(x0, yTmp, x1, yTmp);
+ }
+ }
+ }
+
+
+ /**
+ * Returns the preferred span of the content managed by this
+ * <code>View</code> along the specified <code>axis</code>.
+ *
+ * @param axis the axis
+ *
+ * @return the preferred span of this <code>View</code>.
+ */
+ public float getPreferredSpan(int axis)
+ {
+ float span = 0;
+ checkPainter();
+ GlyphPainter painter = getGlyphPainter();
+ switch (axis)
+ {
+ case X_AXIS:
+ TabExpander tabEx = null;
+ View parent = getParent();
+ if (parent instanceof TabExpander)
+ tabEx = (TabExpander) parent;
+ span = painter.getSpan(this, getStartOffset(), getEndOffset(),
+ tabEx, 0.F);
+ break;
+ case Y_AXIS:
+ span = painter.getHeight(this);
+ if (isSuperscript())
+ span += span / 3;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal axis");
+ }
+ return span;
+ }
+
+ /**
+ * Maps a position in the document into the coordinate space of the View.
+ * The output rectangle usually reflects the font height but has a width
+ * of zero.
+ *
+ * @param pos the position of the character in the model
+ * @param a the area that is occupied by the view
+ * @param b either {@link Position.Bias#Forward} or
+ * {@link Position.Bias#Backward} depending on the preferred
+ * direction bias. If <code>null</code> this defaults to
+ * <code>Position.Bias.Forward</code>
+ *
+ * @return a rectangle that gives the location of the document position
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>pos</code> is invalid
+ * @throws IllegalArgumentException if b is not one of the above listed
+ * valid values
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException
+ {
+ GlyphPainter p = getGlyphPainter();
+ return p.modelToView(this, pos, b, a);
+ }
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model.
+ *
+ * @param x the x coordinate in the view space
+ * @param y the y coordinate in the view space
+ * @param a the allocation of this <code>View</code>
+ * @param b the bias to use
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code>
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
+ {
+ checkPainter();
+ GlyphPainter painter = getGlyphPainter();
+ return painter.viewToModel(this, x, y, a, b);
+ }
+
+ /**
+ * Return the {@link TabExpander} to use.
+ *
+ * @return the {@link TabExpander} to use
+ */
+ public TabExpander getTabExpander()
+ {
+ return tabExpander;
+ }
+
+ /**
+ * Returns the preferred span of this view for tab expansion.
+ *
+ * @param x the location of the view
+ * @param te the tab expander to use
+ *
+ * @return the preferred span of this view for tab expansion
+ */
+ public float getTabbedSpan(float x, TabExpander te)
+ {
+ checkPainter();
+ TabExpander old = tabExpander;
+ tabExpander = te;
+ if (tabExpander != old)
+ {
+ // Changing the tab expander will lead to a relayout in the X_AXIS.
+ preferenceChanged(null, true, false);
+ }
+ tabX = x;
+ return getGlyphPainter().getSpan(this, getStartOffset(),
+ getEndOffset(), tabExpander, x);
+ }
+
+ /**
+ * Returns the span of a portion of the view. This is used in TAB expansion
+ * for fragments that don't contain TABs.
+ *
+ * @param p0 the start index
+ * @param p1 the end index
+ *
+ * @return the span of the specified portion of the view
+ */
+ public float getPartialSpan(int p0, int p1)
+ {
+ checkPainter();
+ return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX);
+ }
+
+ /**
+ * Returns the start offset in the document model of the portion
+ * of text that this view is responsible for.
+ *
+ * @return the start offset in the document model of the portion
+ * of text that this view is responsible for
+ */
+ public int getStartOffset()
+ {
+ Element el = getElement();
+ int offs = el.getStartOffset();
+ if (length > 0)
+ offs += offset;
+ return offs;
+ }
+
+ /**
+ * Returns the end offset in the document model of the portion
+ * of text that this view is responsible for.
+ *
+ * @return the end offset in the document model of the portion
+ * of text that this view is responsible for
+ */
+ public int getEndOffset()
+ {
+ Element el = getElement();
+ int offs;
+ if (length > 0)
+ offs = el.getStartOffset() + offset + length;
+ else
+ offs = el.getEndOffset();
+ return offs;
+ }
+
+ private Segment cached = new Segment();
+
+ /**
+ * Returns the text segment that this view is responsible for.
+ *
+ * @param p0 the start index in the document model
+ * @param p1 the end index in the document model
+ *
+ * @return the text segment that this view is responsible for
+ */
+ public Segment getText(int p0, int p1)
+ {
+ try
+ {
+ getDocument().getText(p0, p1 - p0, cached);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError ae;
+ ae = new AssertionError("BadLocationException should not be "
+ + "thrown here. p0 = " + p0 + ", p1 = " + p1);
+ ae.initCause(ex);
+ throw ae;
+ }
+
+ return cached;
+ }
+
+ /**
+ * Returns the font for the text run for which this <code>GlyphView</code>
+ * is responsible.
+ *
+ * @return the font for the text run for which this <code>GlyphView</code>
+ * is responsible
+ */
+ public Font getFont()
+ {
+ Document doc = getDocument();
+ Font font = null;
+ if (doc instanceof StyledDocument)
+ {
+ StyledDocument styledDoc = (StyledDocument) doc;
+ font = styledDoc.getFont(getAttributes());
+ }
+ else
+ {
+ Container c = getContainer();
+ if (c != null)
+ font = c.getFont();
+ }
+ return font;
+ }
+
+ /**
+ * Returns the foreground color which should be used to paint the text.
+ * This is fetched from the associated element's text attributes using
+ * {@link StyleConstants#getForeground}.
+ *
+ * @return the foreground color which should be used to paint the text
+ */
+ public Color getForeground()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ return StyleConstants.getForeground(atts);
+ }
+
+ /**
+ * Returns the background color which should be used to paint the text.
+ * This is fetched from the associated element's text attributes using
+ * {@link StyleConstants#getBackground}.
+ *
+ * @return the background color which should be used to paint the text
+ */
+ public Color getBackground()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ // We cannot use StyleConstants.getBackground() here, because that returns
+ // BLACK as default (when background == null). What we need is the
+ // background setting of the text component instead, which is what we get
+ // when background == null anyway.
+ return (Color) atts.getAttribute(StyleConstants.Background);
+ }
+
+ /**
+ * Determines whether the text should be rendered strike-through or not. This
+ * is determined using the method
+ * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of
+ * this view.
+ *
+ * @return whether the text should be rendered strike-through or not
+ */
+ public boolean isStrikeThrough()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ return StyleConstants.isStrikeThrough(atts);
+ }
+
+ /**
+ * Determines whether the text should be rendered as subscript or not. This
+ * is determined using the method
+ * {@link StyleConstants#isSubscript(AttributeSet)} on the element of
+ * this view.
+ *
+ * @return whether the text should be rendered as subscript or not
+ */
+ public boolean isSubscript()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ return StyleConstants.isSubscript(atts);
+ }
+
+ /**
+ * Determines whether the text should be rendered as superscript or not. This
+ * is determined using the method
+ * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of
+ * this view.
+ *
+ * @return whether the text should be rendered as superscript or not
+ */
+ public boolean isSuperscript()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ return StyleConstants.isSuperscript(atts);
+ }
+
+ /**
+ * Determines whether the text should be rendered as underlined or not. This
+ * is determined using the method
+ * {@link StyleConstants#isUnderline(AttributeSet)} on the element of
+ * this view.
+ *
+ * @return whether the text should be rendered as underlined or not
+ */
+ public boolean isUnderline()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ return StyleConstants.isUnderline(atts);
+ }
+
+ /**
+ * Creates and returns a shallow clone of this GlyphView. This is used by
+ * the {@link #createFragment} and {@link #breakView} methods.
+ *
+ * @return a shallow clone of this GlyphView
+ */
+ protected final Object clone()
+ {
+ try
+ {
+ return super.clone();
+ }
+ catch (CloneNotSupportedException ex)
+ {
+ AssertionError err = new AssertionError("CloneNotSupportedException "
+ + "must not be thrown here");
+ err.initCause(ex);
+ throw err;
+ }
+ }
+
+ /**
+ * Tries to break the view near the specified view span <code>len</code>.
+ * The glyph view can only be broken in the X direction. For Y direction it
+ * returns itself.
+ *
+ * @param axis the axis for breaking, may be {@link View#X_AXIS} or
+ * {@link View#Y_AXIS}
+ * @param p0 the model location where the fragment should start
+ * @param pos the view position along the axis where the fragment starts
+ * @param len the desired length of the fragment view
+ *
+ * @return the fragment view, or <code>this</code> if breaking was not
+ * possible
+ */
+ public View breakView(int axis, int p0, float pos, float len)
+ {
+ View brokenView = this;
+ if (axis == X_AXIS)
+ {
+ checkPainter();
+ int end = glyphPainter.getBoundedPosition(this, p0, pos, len);
+ int breakLoc = getBreakLocation(p0, end);
+ if (breakLoc != -1)
+ end = breakLoc;
+ if (p0 != getStartOffset() || end != getEndOffset())
+ {
+ brokenView = createFragment(p0, end);
+ if (brokenView instanceof GlyphView)
+ ((GlyphView) brokenView).tabX = pos;
+ }
+ }
+ return brokenView;
+ }
+
+ /**
+ * Determines how well the specified view location is suitable for inserting
+ * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then
+ * this method forwards to the superclass, if <code>axis</code> is
+ * <code>View.X_AXIS</code> then this method returns
+ * {@link View#ExcellentBreakWeight} if there is a suitable break location
+ * (usually whitespace) within the specified view span, or
+ * {@link View#GoodBreakWeight} if not.
+ *
+ * @param axis the axis along which the break weight is requested
+ * @param pos the starting view location
+ * @param len the length of the span at which the view should be broken
+ *
+ * @return the break weight
+ */
+ public int getBreakWeight(int axis, float pos, float len)
+ {
+ int weight;
+ if (axis == Y_AXIS)
+ weight = super.getBreakWeight(axis, pos, len);
+ else
+ {
+ checkPainter();
+ int start = getStartOffset();
+ int end = glyphPainter.getBoundedPosition(this, start, pos, len);
+ if (end == 0)
+ weight = BadBreakWeight;
+ else
+ {
+ if (getBreakLocation(start, end) != -1)
+ weight = ExcellentBreakWeight;
+ else
+ weight = GoodBreakWeight;
+ }
+ }
+ return weight;
+ }
+
+ private int getBreakLocation(int start, int end)
+ {
+ int loc = -1;
+ Segment s = getText(start, end);
+ for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous())
+ {
+ if (Character.isWhitespace(c))
+ {
+ loc = s.getIndex() - s.getBeginIndex() + 1 + start;
+ }
+ }
+ return loc;
+ }
+
+ /**
+ * Receives notification that some text attributes have changed within the
+ * text fragment that this view is responsible for. This calls
+ * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
+ * both width and height.
+ *
+ * @param e the document event describing the change; not used here
+ * @param a the view allocation on screen; not used here
+ * @param vf the view factory; not used here
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ preferenceChanged(null, true, true);
+ }
+
+ /**
+ * Receives notification that some text has been inserted within the
+ * text fragment that this view is responsible for. This calls
+ * {@link View#preferenceChanged(View, boolean, boolean)} for the
+ * direction in which the glyphs are rendered.
+ *
+ * @param e the document event describing the change; not used here
+ * @param a the view allocation on screen; not used here
+ * @param vf the view factory; not used here
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ preferenceChanged(null, true, false);
+ }
+
+ /**
+ * Receives notification that some text has been removed within the
+ * text fragment that this view is responsible for. This calls
+ * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
+ * width.
+ *
+ * @param e the document event describing the change; not used here
+ * @param a the view allocation on screen; not used here
+ * @param vf the view factory; not used here
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ preferenceChanged(null, true, false);
+ }
+
+ /**
+ * Creates a fragment view of this view that starts at <code>p0</code> and
+ * ends at <code>p1</code>.
+ *
+ * @param p0 the start location for the fragment view
+ * @param p1 the end location for the fragment view
+ *
+ * @return the fragment view
+ */
+ public View createFragment(int p0, int p1)
+ {
+ checkPainter();
+ Element el = getElement();
+ GlyphView fragment = (GlyphView) clone();
+ fragment.offset = p0 - el.getStartOffset();
+ fragment.length = p1 - p0;
+ fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1);
+ return fragment;
+ }
+
+ /**
+ * Returns the alignment of this view along the specified axis. For the Y
+ * axis this is <code>(height - descent) / height</code> for the used font,
+ * so that it is aligned along the baseline.
+ * For the X axis the superclass is called.
+ */
+ public float getAlignment(int axis)
+ {
+ checkPainter();
+ float align;
+ if (axis == Y_AXIS)
+ {
+ GlyphPainter painter = getGlyphPainter();
+ float height = painter.getHeight(this);
+ float descent = painter.getDescent(this);
+ float ascent = painter.getAscent(this);
+ if (isSuperscript())
+ align = 1.0F;
+ else if (isSubscript())
+ align = height > 0 ? (height - (descent + (ascent / 2))) / height
+ : 0;
+ else
+ align = height > 0 ? (height - descent) / height : 0;
+ }
+ else
+ align = super.getAlignment(axis);
+
+ return align;
+ }
+
+ /**
+ * Returns the model location that should be used to place a caret when
+ * moving the caret through the document.
+ *
+ * @param pos the current model location
+ * @param bias the bias for <code>p</code>
+ * @param a the allocated region for the glyph view
+ * @param direction the direction from the current position; Must be one of
+ * {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
+ * {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
+ * @param biasRet filled with the bias of the resulting location when method
+ * returns
+ *
+ * @return the location within the document that should be used to place the
+ * caret when moving the caret around the document
+ *
+ * @throws BadLocationException if <code>pos</code> is an invalid model
+ * location
+ * @throws IllegalArgumentException if <code>d</code> is invalid
+ */
+ public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a,
+ int direction, Position.Bias[] biasRet)
+ throws BadLocationException
+ {
+ checkPainter();
+ GlyphPainter painter = getGlyphPainter();
+ return painter.getNextVisualPositionFrom(this, pos, bias, a, direction,
+ biasRet);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/Highlighter.java b/libjava/classpath/javax/swing/text/Highlighter.java
new file mode 100644
index 000000000..b4b671ac4
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Highlighter.java
@@ -0,0 +1,78 @@
+/* Highlighter.java --
+ Copyright (C) 2004 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.Graphics;
+import java.awt.Shape;
+
+
+public interface Highlighter
+{
+ public interface Highlight
+ {
+ int getEndOffset();
+
+ int getStartOffset();
+
+ HighlightPainter getPainter();
+ }
+
+ public interface HighlightPainter
+ {
+ void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c);
+ }
+
+ void install(JTextComponent c);
+
+ void deinstall(JTextComponent c);
+
+ Object addHighlight(int p0, int p1, HighlightPainter p)
+ throws BadLocationException;
+
+ void removeAllHighlights();
+
+ void removeHighlight(Object tag);
+
+ void changeHighlight(Object tag, int p0, int p1)
+ throws BadLocationException;
+
+ Highlight[] getHighlights();
+
+ void paint(Graphics g);
+}
diff --git a/libjava/classpath/javax/swing/text/IconView.java b/libjava/classpath/javax/swing/text/IconView.java
new file mode 100644
index 000000000..7bb7635b4
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/IconView.java
@@ -0,0 +1,175 @@
+/* IconView.java -- A view to render icons
+ Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.Icon;
+import javax.swing.JTextPane;
+
+/**
+ * A View that can render an icon. This view is created by the
+ * {@link StyledEditorKit}'s view factory for all elements that have name
+ * {@link StyleConstants#IconElementName}. This is usually created by
+ * inserting an icon into <code>JTextPane</code> using
+ * {@link JTextPane#insertIcon(Icon)}
+ *
+ * The icon is determined using the attribute
+ * {@link StyleConstants#IconAttribute}, which's value must be an {@link Icon}.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class IconView
+ extends View
+{
+
+ /**
+ * Creates a new <code>IconView</code> for the given <code>Element</code>.
+ *
+ * @param element the element that is rendered by this IconView
+ */
+ public IconView(Element element)
+ {
+ super(element);
+ }
+
+ /**
+ * Renders the <code>Element</code> that is associated with this
+ * <code>View</code>.
+ *
+ * @param g the <code>Graphics</code> context to render to
+ * @param a the allocated region for the <code>Element</code>
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ Icon icon = StyleConstants.getIcon(getElement().getAttributes());
+ Rectangle b = a.getBounds();
+ icon.paintIcon(getContainer(), g, b.x, b.y);
+ }
+
+ /**
+ * Returns the preferred span of the content managed by this
+ * <code>View</code> along the specified <code>axis</code>.
+ *
+ * @param axis the axis
+ *
+ * @return the preferred span of this <code>View</code>.
+ */
+ public float getPreferredSpan(int axis)
+ {
+ Icon icon = StyleConstants.getIcon(getElement().getAttributes());
+ float span;
+ if (axis == X_AXIS)
+ span = icon.getIconWidth();
+ else if (axis == Y_AXIS)
+ span = icon.getIconHeight();
+ else
+ throw new IllegalArgumentException();
+ return span;
+ }
+
+ /**
+ * Maps a position in the document into the coordinate space of the View.
+ * The output rectangle usually reflects the font height but has a width
+ * of zero.
+ *
+ * @param pos the position of the character in the model
+ * @param a the area that is occupied by the view
+ * @param b either {@link Position.Bias#Forward} or
+ * {@link Position.Bias#Backward} depending on the preferred
+ * direction bias. If <code>null</code> this defaults to
+ * <code>Position.Bias.Forward</code>
+ *
+ * @return a rectangle that gives the location of the document position
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>pos</code> is invalid
+ * @throws IllegalArgumentException if b is not one of the above listed
+ * valid values
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException
+ {
+ Element el = getElement();
+ Rectangle r = a.getBounds();
+ Icon icon = StyleConstants.getIcon(el.getAttributes());
+ return new Rectangle(r.x, r.y, icon.getIconWidth(), icon.getIconHeight());
+ }
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model.
+ *
+ * @param x the x coordinate in the view space
+ * @param y the y coordinate in the view space
+ * @param a the allocation of this <code>View</code>
+ * @param b the bias to use
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code>
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
+ {
+ // The element should only have one character position and it is clear
+ // that this position is the position that best matches the given screen
+ // coordinates, simply because this view has only this one position.
+ Element el = getElement();
+ return el.getStartOffset();
+ }
+
+ /**
+ * Returns the alignment for this view. This will be 1.0 for the Y_AXIS,
+ * and the super behaviour for the X_AXIS.
+ *
+ * @param axis the axis for which to calculate the alignment
+ *
+ * @return the alignment
+ */
+ public float getAlignment(int axis)
+ {
+ float align;
+ if (axis == Y_AXIS)
+ align = 1.0F;
+ else
+ align = super.getAlignment(axis);
+ return align;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/InternationalFormatter.java b/libjava/classpath/javax/swing/text/InternationalFormatter.java
new file mode 100644
index 000000000..8dcd03a3f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/InternationalFormatter.java
@@ -0,0 +1,356 @@
+/* InternationalFormatter.java --
+Copyright (C) 2005, 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.text.AttributedCharacterIterator;
+import java.text.Format;
+import java.text.ParseException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.JFormattedTextField;
+
+/**
+ * This extends {@link DefaultFormatter} so that the value to string
+ * conversion is done via a {@link Format} object. This allows
+ * various additional formats to be handled by JFormattedField.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class InternationalFormatter
+ extends DefaultFormatter
+{
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 2436068675711756856L;
+
+ /** The format that handles value to string conversion. */
+ Format format;
+
+ /** The minimal permissable value. */
+ Comparable minimum;
+
+ /** The maximal permissable value. */
+ Comparable maximum;
+
+ /**
+ * Creates a new InternationalFormatter with no Format specified.
+ */
+ public InternationalFormatter()
+ {
+ super();
+ minimum = null;
+ maximum = null;
+ format = null;
+ setCommitsOnValidEdit(false);
+ setOverwriteMode(false);
+ }
+
+ /**
+ * Creates a new InternationalFormatter that uses the specified
+ * Format object for value to string conversion.
+ *
+ * @param format the Format object to use for value to string conversion
+ */
+ public InternationalFormatter(Format format)
+ {
+ this();
+ setFormat(format);
+ }
+
+ /**
+ * Sets the Format object that is used to convert values to strings.
+ *
+ * @param format the Format to use for value to string conversion
+ *
+ * @see Format
+ */
+ public void setFormat(Format format)
+ {
+ this.format = format;
+ }
+
+ /**
+ * Returns the currently used Format object that is used to format
+ * the JFormattedField.
+ *
+ * @return the current Format
+ */
+ public Format getFormat()
+ {
+ return format;
+ }
+
+ /**
+ * Sets the minimum value that is allowed by this Formatter. The minimum
+ * value is given as an object that implements the {@link Comparable}
+ * interface.
+ *
+ * If <code>minValue</code> is null, then the Formatter has no restrictions
+ * at the lower end.
+ *
+ * If value class is not yet specified and <code>minValue</code> is not
+ * null, then <code>valueClass</code> is set to the class of the minimum
+ * value.
+ *
+ * @param minValue the minimum permissable value
+ *
+ * @see Comparable
+ */
+ public void setMinimum(Comparable minValue)
+ {
+ minimum = minValue;
+ if (valueClass == null && minValue != null)
+ valueClass = minValue.getClass();
+ }
+
+ /**
+ * Returns the minimal value that is allowed by this Formatter.
+ *
+ * A <code>null</code> value means that there is no restriction.
+ *
+ * @return the minimal value that is allowed by this Formatter or
+ * <code>null</code> if there is no restriction
+ */
+ public Comparable getMinimum()
+ {
+ return minimum;
+ }
+
+ /**
+ * Sets the maximum value that is allowed by this Formatter. The maximum
+ * value is given as an object that implements the {@link Comparable}
+ * interface.
+ *
+ * If <code>maxValue</code> is null, then the Formatter has no restrictions
+ * at the upper end.
+ *
+ * If value class is not yet specified and <code>maxValue</code> is not
+ * null, then <code>valueClass</code> is set to the class of the maximum
+ * value.
+ *
+ * @param maxValue the maximum permissable value
+ *
+ * @see Comparable
+ */
+ public void setMaximum(Comparable maxValue)
+ {
+ maximum = maxValue;
+ if (valueClass == null && maxValue != null)
+ valueClass = maxValue.getClass();
+ }
+
+ /**
+ * Returns the maximal value that is allowed by this Formatter.
+ *
+ * A <code>null</code> value means that there is no restriction.
+ *
+ * @return the maximal value that is allowed by this Formatter or
+ * <code>null</code> if there is no restriction
+ */
+ public Comparable getMaximum()
+ {
+ return maximum;
+ }
+
+ /**
+ * Installs the formatter on the specified {@link JFormattedTextField}.
+ *
+ * This method does the following things:
+ * <ul>
+ * <li>Display the value of #valueToString in the
+ * <code>JFormattedTextField</code></li>
+ * <li>Install the Actions from #getActions on the <code>JTextField</code>
+ * </li>
+ * <li>Install the DocumentFilter returned by #getDocumentFilter</li>
+ * <li>Install the NavigationFilter returned by #getNavigationFilter</li>
+ * </ul>
+ *
+ * This method is typically not overridden by subclasses. Instead override
+ * one of the mentioned methods in order to customize behaviour.
+ *
+ * @param ftf the {@link JFormattedTextField} in which this formatter
+ * is installed
+ */
+ public void install(JFormattedTextField ftf)
+ {
+ super.install(ftf);
+ }
+
+ /**
+ * Converts a value object into a String. This is done by invoking
+ * {@link Format#format(Object)} on the specified <code>Format</code> object.
+ * If no format is set, then {@link DefaultFormatter#valueToString(Object)}
+ * is called as a fallback.
+ *
+ * @param value the value to be converted
+ *
+ * @return the string representation of the value
+ *
+ * @throws ParseException if the value cannot be converted
+ */
+ public String valueToString(Object value)
+ throws ParseException
+ {
+ if (value == null)
+ return "";
+ if (format != null)
+ return format.format(value);
+ else
+ return super.valueToString(value);
+ }
+
+ /**
+ * Converts a String (from the JFormattedTextField input) to a value.
+ * This is achieved by invoking {@link Format#parseObject(String)} on
+ * the specified <code>Format</code> object.
+ *
+ * This implementation differs slightly from {@link DefaultFormatter},
+ * it does:
+ * <ol>
+ * <li>Convert the string to an <code>Object</code> using the
+ * <code>Formatter</code>.</li>
+ * <li>If a <code>valueClass</code> has been set, this object is passed to
+ * {@link DefaultFormatter#stringToValue(String)} so that the value
+ * has the correct type. This may or may not work correctly, depending on
+ * the implementation of toString() in the value class and if the class
+ * implements a constructor that takes one String as argument.</li>
+ * <li>If no {@link ParseException} has been thrown so far, we check if the
+ * value exceeds either <code>minimum</code> or <code>maximum</code> if
+ * one of those has been specified and throw a <code>ParseException</code>
+ * if it does.</li>
+ * <li>Return the value.</li>
+ * </ol>
+ *
+ * If no format has been specified, then
+ * {@link DefaultFormatter#stringToValue(String)} is invoked as fallback.
+ *
+ * @param string the string to convert
+ *
+ * @return the value for the string
+ *
+ * @throws ParseException if the string cannot be converted into
+ * a value object (e.g. invalid input)
+ */
+ public Object stringToValue(String string)
+ throws ParseException
+ {
+ if (format != null)
+ {
+ Object o = format.parseObject(string);
+
+ // If a value class has been set, call super in order to get
+ // the class right. That is what the JDK API docs suggest, so we do
+ // it that way.
+ if (valueClass != null)
+ o = super.stringToValue(o.toString());
+
+ // Check for minimum and maximum bounds
+ if (minimum != null && minimum.compareTo(o) > 0)
+ throw new ParseException("The value may not be less than the"
+ + " specified minimum", 0);
+ if (maximum != null && maximum.compareTo(o) < 0)
+ throw new ParseException("The value may not be greater than the"
+ + " specified maximum", 0);
+ return o;
+ }
+ else
+ return super.stringToValue(string);
+ }
+
+ /**
+ * Returns the {@link Format.Field} constants that are associated with
+ * the specified position in the text.
+ *
+ * If <code>offset</code> is not a valid location in the input field,
+ * an empty array of fields is returned.
+ *
+ * @param offset the position in the text from which we want to fetch
+ * the fields constants
+ *
+ * @return the field values associated with the specified position in
+ * the text
+ */
+ public Format.Field[] getFields(int offset)
+ {
+ // TODO: don't know if this is correct
+ AttributedCharacterIterator aci = format.formatToCharacterIterator
+ (getFormattedTextField().getValue());
+ aci.setIndex(offset);
+ Map atts = aci.getAttributes();
+ Set keys = atts.keySet();
+ Format.Field[] fields = new Format.Field[keys.size()];
+ int index = 0;
+ for (Iterator i = keys.iterator(); i.hasNext(); index++)
+ fields[index] = (Format.Field) i.next();
+ return fields;
+ }
+
+ /**
+ * This creates and returns a clone of this Formatter.
+ *
+ * @return a clone of this formatter
+ *
+ * @throws CloneNotSupportedException not thrown here, since cloning is
+ * supported
+ */
+ public Object clone()
+ throws CloneNotSupportedException
+ {
+ // TODO: it has to be considered, if we should return a deep or shallow
+ // clone here. for now we return a shallow clone
+ Object clone = super.clone();
+ return clone;
+ }
+
+ /**
+ * Returns the Actions that are supported by this Formatter.
+ *
+ * @specnote the JDK API docs say here: <cite>If
+ * <code>getSupportsIncrement</code> returns true, this returns two
+ * Actions suitable for incrementing/decrementing the value.</cite>
+ * The questsion is, which method <code>getSupportsIncrement</code>?
+ * There is no such method in the whole API! So we just call
+ * super.getActions here.
+ */
+ protected Action[] getActions()
+ {
+ return super.getActions();
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/JTextComponent.java b/libjava/classpath/javax/swing/text/JTextComponent.java
new file mode 100644
index 000000000..a118cf86d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/JTextComponent.java
@@ -0,0 +1,2059 @@
+/* JTextComponent.java --
+ Copyright (C) 2002, 2004, 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 gnu.java.lang.CPStringBuilder;
+
+import java.awt.AWTEvent;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputMethodListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.text.BreakIterator;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.accessibility.Accessible;
+import javax.accessibility.AccessibleAction;
+import javax.accessibility.AccessibleContext;
+import javax.accessibility.AccessibleEditableText;
+import javax.accessibility.AccessibleRole;
+import javax.accessibility.AccessibleState;
+import javax.accessibility.AccessibleStateSet;
+import javax.accessibility.AccessibleText;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JViewport;
+import javax.swing.KeyStroke;
+import javax.swing.Scrollable;
+import javax.swing.SwingConstants;
+import javax.swing.TransferHandler;
+import javax.swing.UIManager;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.plaf.ActionMapUIResource;
+import javax.swing.plaf.InputMapUIResource;
+import javax.swing.plaf.TextUI;
+
+public abstract class JTextComponent extends JComponent
+ implements Scrollable, Accessible
+{
+ /**
+ * AccessibleJTextComponent implements accessibility hooks for
+ * JTextComponent. It allows an accessibility driver to read and
+ * manipulate the text component's contents as well as update UI
+ * elements such as the caret.
+ */
+ public class AccessibleJTextComponent extends AccessibleJComponent implements
+ AccessibleText, CaretListener, DocumentListener, AccessibleAction,
+ AccessibleEditableText
+ {
+ private static final long serialVersionUID = 7664188944091413696L;
+
+ /**
+ * The caret's offset.
+ */
+ private int caretDot;
+
+ /**
+ * Construct an AccessibleJTextComponent.
+ */
+ public AccessibleJTextComponent()
+ {
+ super();
+ JTextComponent.this.addCaretListener(this);
+ caretDot = getCaretPosition();
+ }
+
+ /**
+ * Retrieve the current caret position. The index of the first
+ * caret position is 0.
+ *
+ * @return caret position
+ */
+ public int getCaretPosition()
+ {
+ return JTextComponent.this.getCaretPosition();
+ }
+
+ /**
+ * Retrieve the current text selection. If no text is selected
+ * this method returns null.
+ *
+ * @return the currently selected text or null
+ */
+ public String getSelectedText()
+ {
+ return JTextComponent.this.getSelectedText();
+ }
+
+ /**
+ * Retrieve the index of the first character in the current text
+ * selection. If there is no text in the text component, this
+ * method returns 0. If there is text in the text component, but
+ * there is no selection, this method returns the current caret
+ * position.
+ *
+ * @return the index of the first character in the selection, the
+ * current caret position or 0
+ */
+ public int getSelectionStart()
+ {
+ if (getSelectedText() == null
+ || (JTextComponent.this.getText().equals("")))
+ return 0;
+ return JTextComponent.this.getSelectionStart();
+ }
+
+ /**
+ * Retrieve the index of the last character in the current text
+ * selection. If there is no text in the text component, this
+ * method returns 0. If there is text in the text component, but
+ * there is no selection, this method returns the current caret
+ * position.
+ *
+ * @return the index of the last character in the selection, the
+ * current caret position or 0
+ */
+ public int getSelectionEnd()
+ {
+ return JTextComponent.this.getSelectionEnd();
+ }
+
+ /**
+ * Handle a change in the caret position and fire any applicable
+ * property change events.
+ *
+ * @param e - the caret update event
+ */
+ public void caretUpdate(CaretEvent e)
+ {
+ int dot = e.getDot();
+ int mark = e.getMark();
+ if (caretDot != dot)
+ {
+ firePropertyChange(ACCESSIBLE_CARET_PROPERTY, new Integer(caretDot),
+ new Integer(dot));
+ caretDot = dot;
+ }
+ if (mark != dot)
+ {
+ firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null,
+ getSelectedText());
+ }
+ }
+
+ /**
+ * Retreive the accessible state set of this component.
+ *
+ * @return the accessible state set of this component
+ */
+ public AccessibleStateSet getAccessibleStateSet()
+ {
+ AccessibleStateSet state = super.getAccessibleStateSet();
+ if (isEditable())
+ state.add(AccessibleState.EDITABLE);
+ return state;
+ }
+
+ /**
+ * Retrieve the accessible role of this component.
+ *
+ * @return the accessible role of this component
+ *
+ * @see AccessibleRole
+ */
+ public AccessibleRole getAccessibleRole()
+ {
+ return AccessibleRole.TEXT;
+ }
+
+ /**
+ * Retrieve an AccessibleEditableText object that controls this
+ * text component.
+ *
+ * @return this
+ */
+ public AccessibleEditableText getAccessibleEditableText()
+ {
+ return this;
+ }
+
+ /**
+ * Retrieve an AccessibleText object that controls this text
+ * component.
+ *
+ * @return this
+ *
+ * @see AccessibleText
+ */
+ public AccessibleText getAccessibleText()
+ {
+ return this;
+ }
+
+ /**
+ * Handle a text insertion event and fire an
+ * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change
+ * event.
+ *
+ * @param e - the insertion event
+ */
+ public void insertUpdate(DocumentEvent e)
+ {
+ firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
+ new Integer(e.getOffset()));
+ }
+
+ /**
+ * Handle a text removal event and fire an
+ * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change
+ * event.
+ *
+ * @param e - the removal event
+ */
+ public void removeUpdate(DocumentEvent e)
+ {
+ firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
+ new Integer(e.getOffset()));
+ }
+
+ /**
+ * Handle a text change event and fire an
+ * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change
+ * event.
+ *
+ * @param e - text change event
+ */
+ public void changedUpdate(DocumentEvent e)
+ {
+ firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
+ new Integer(e.getOffset()));
+ }
+
+ /**
+ * Get the index of the character at the given point, in component
+ * pixel co-ordinates. If the point argument is invalid this
+ * method returns -1.
+ *
+ * @param p - a point in component pixel co-ordinates
+ *
+ * @return a character index, or -1
+ */
+ public int getIndexAtPoint(Point p)
+ {
+ return viewToModel(p);
+ }
+
+ /**
+ * Calculate the bounding box of the character at the given index.
+ * The returned x and y co-ordinates are relative to this text
+ * component's top-left corner. If the index is invalid this
+ * method returns null.
+ *
+ * @param index - the character index
+ *
+ * @return a character's bounding box, or null
+ */
+ public Rectangle getCharacterBounds(int index)
+ {
+ // This is basically the same as BasicTextUI.modelToView().
+
+ Rectangle bounds = null;
+ if (index >= 0 && index < doc.getLength() - 1)
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ TextUI ui = getUI();
+ if (ui != null)
+ {
+ // Get editor rectangle.
+ Rectangle rect = new Rectangle();
+ Insets insets = getInsets();
+ rect.x = insets.left;
+ rect.y = insets.top;
+ rect.width = getWidth() - insets.left - insets.right;
+ rect.height = getHeight() - insets.top - insets.bottom;
+ View rootView = ui.getRootView(JTextComponent.this);
+ if (rootView != null)
+ {
+ rootView.setSize(rect.width, rect.height);
+ Shape s = rootView.modelToView(index,
+ Position.Bias.Forward,
+ index + 1,
+ Position.Bias.Backward,
+ rect);
+ if (s != null)
+ bounds = s.getBounds();
+ }
+ }
+ }
+ catch (BadLocationException ex)
+ {
+ // Ignore (return null).
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ }
+ return bounds;
+ }
+
+ /**
+ * Return the length of the text in this text component.
+ *
+ * @return a character length
+ */
+ public int getCharCount()
+ {
+ return JTextComponent.this.getText().length();
+ }
+
+ /**
+ * Gets the character attributes of the character at index. If
+ * the index is out of bounds, null is returned.
+ *
+ * @param index - index of the character
+ *
+ * @return the character's attributes
+ */
+ public AttributeSet getCharacterAttribute(int index)
+ {
+ AttributeSet atts;
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ Element el = doc.getDefaultRootElement();
+ while (! el.isLeaf())
+ {
+ int i = el.getElementIndex(index);
+ el = el.getElement(i);
+ }
+ atts = el.getAttributes();
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ return atts;
+ }
+
+ /**
+ * Gets the text located at index. null is returned if the index
+ * or part is invalid.
+ *
+ * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE}
+ * @param index - index of the part
+ *
+ * @return the part of text at that index, or null
+ */
+ public String getAtIndex(int part, int index)
+ {
+ return getAtIndexImpl(part, index, 0);
+ }
+
+ /**
+ * Gets the text located after index. null is returned if the index
+ * or part is invalid.
+ *
+ * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE}
+ * @param index - index after the part
+ *
+ * @return the part of text after that index, or null
+ */
+ public String getAfterIndex(int part, int index)
+ {
+ return getAtIndexImpl(part, index, 1);
+ }
+
+ /**
+ * Gets the text located before index. null is returned if the index
+ * or part is invalid.
+ *
+ * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE}
+ * @param index - index before the part
+ *
+ * @return the part of text before that index, or null
+ */
+ public String getBeforeIndex(int part, int index)
+ {
+ return getAtIndexImpl(part, index, -1);
+ }
+
+ /**
+ * Implements getAtIndex(), getBeforeIndex() and getAfterIndex().
+ *
+ * @param part the part to return, either CHARACTER, WORD or SENTENCE
+ * @param index the index
+ * @param dir the direction, -1 for backwards, 0 for here, +1 for forwards
+ *
+ * @return the resulting string
+ */
+ private String getAtIndexImpl(int part, int index, int dir)
+ {
+ String ret = null;
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ BreakIterator iter = null;
+ switch (part)
+ {
+ case CHARACTER:
+ iter = BreakIterator.getCharacterInstance(getLocale());
+ break;
+ case WORD:
+ iter = BreakIterator.getWordInstance(getLocale());
+ break;
+ case SENTENCE:
+ iter = BreakIterator.getSentenceInstance(getLocale());
+ break;
+ default:
+ break;
+ }
+ String text = doc.getText(0, doc.getLength() - 1);
+ iter.setText(text);
+ int start = index;
+ int end = index;
+ switch (dir)
+ {
+ case 0:
+ if (iter.isBoundary(index))
+ {
+ start = index;
+ end = iter.following(index);
+ }
+ else
+ {
+ start = iter.preceding(index);
+ end = iter.next();
+ }
+ break;
+ case 1:
+ start = iter.following(index);
+ end = iter.next();
+ break;
+ case -1:
+ end = iter.preceding(index);
+ start = iter.previous();
+ break;
+ default:
+ assert false;
+ }
+ ret = text.substring(start, end);
+ }
+ catch (BadLocationException ex)
+ {
+ // Ignore (return null).
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ return ret;
+ }
+
+ /**
+ * Returns the number of actions for this object. The zero-th
+ * object represents the default action.
+ *
+ * @return the number of actions (0-based).
+ */
+ public int getAccessibleActionCount()
+ {
+ return getActions().length;
+ }
+
+ /**
+ * Returns the description of the i-th action. Null is returned if
+ * i is out of bounds.
+ *
+ * @param i - the action to get the description for
+ *
+ * @return description of the i-th action
+ */
+ public String getAccessibleActionDescription(int i)
+ {
+ String desc = null;
+ Action[] actions = getActions();
+ if (i >= 0 && i < actions.length)
+ desc = (String) actions[i].getValue(Action.NAME);
+ return desc;
+ }
+
+ /**
+ * Performs the i-th action. Nothing happens if i is
+ * out of bounds.
+ *
+ * @param i - the action to perform
+ *
+ * @return true if the action was performed successfully
+ */
+ public boolean doAccessibleAction(int i)
+ {
+ boolean ret = false;
+ Action[] actions = getActions();
+ if (i >= 0 && i < actions.length)
+ {
+ ActionEvent ev = new ActionEvent(JTextComponent.this,
+ ActionEvent.ACTION_PERFORMED, null);
+ actions[i].actionPerformed(ev);
+ ret = true;
+ }
+ return ret;
+ }
+
+ /**
+ * Sets the text contents.
+ *
+ * @param s - the new text contents.
+ */
+ public void setTextContents(String s)
+ {
+ setText(s);
+ }
+
+ /**
+ * Inserts the text at the given index.
+ *
+ * @param index - the index to insert the new text at.
+ * @param s - the new text
+ */
+ public void insertTextAtIndex(int index, String s)
+ {
+ try
+ {
+ doc.insertString(index, s, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // What should we do with this?
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Gets the text between two indexes.
+ *
+ * @param start - the starting index (inclusive)
+ * @param end - the ending index (exclusive)
+ */
+ public String getTextRange(int start, int end)
+ {
+ try
+ {
+ return JTextComponent.this.getText(start, end - start);
+ }
+ catch (BadLocationException ble)
+ {
+ return "";
+ }
+ }
+
+ /**
+ * Deletes the text between two indexes.
+ *
+ * @param start - the starting index (inclusive)
+ * @param end - the ending index (exclusive)
+ */
+ public void delete(int start, int end)
+ {
+ replaceText(start, end, "");
+ }
+
+ /**
+ * Cuts the text between two indexes. The text is put
+ * into the system clipboard.
+ *
+ * @param start - the starting index (inclusive)
+ * @param end - the ending index (exclusive)
+ */
+ public void cut(int start, int end)
+ {
+ JTextComponent.this.select(start, end);
+ JTextComponent.this.cut();
+ }
+
+ /**
+ * Pastes the text from the system clipboard to the given index.
+ *
+ * @param start - the starting index
+ */
+ public void paste(int start)
+ {
+ JTextComponent.this.setCaretPosition(start);
+ JTextComponent.this.paste();
+ }
+
+ /**
+ * Replaces the text between two indexes with the given text.
+ *
+ *
+ * @param start - the starting index (inclusive)
+ * @param end - the ending index (exclusive)
+ * @param s - the text to paste
+ */
+ public void replaceText(int start, int end, String s)
+ {
+ JTextComponent.this.select(start, end);
+ JTextComponent.this.replaceSelection(s);
+ }
+
+ /**
+ * Selects the text between two indexes.
+ *
+ * @param start - the starting index (inclusive)
+ * @param end - the ending index (exclusive)
+ */
+ public void selectText(int start, int end)
+ {
+ JTextComponent.this.select(start, end);
+ }
+
+ /**
+ * Sets the attributes of all the text between two indexes.
+ *
+ * @param start - the starting index (inclusive)
+ * @param end - the ending index (exclusive)
+ * @param s - the new attribute set for the text in the range
+ */
+ public void setAttributes(int start, int end, AttributeSet s)
+ {
+ if (doc instanceof StyledDocument)
+ {
+ StyledDocument sdoc = (StyledDocument) doc;
+ sdoc.setCharacterAttributes(start, end - start, s, true);
+ }
+ }
+ }
+
+ public static class KeyBinding
+ {
+ public KeyStroke key;
+ public String actionName;
+
+ /**
+ * Creates a new <code>KeyBinding</code> instance.
+ *
+ * @param key a <code>KeyStroke</code> value
+ * @param actionName a <code>String</code> value
+ */
+ public KeyBinding(KeyStroke key, String actionName)
+ {
+ this.key = key;
+ this.actionName = actionName;
+ }
+ }
+
+ /**
+ * According to <a
+ * href="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html">this
+ * report</a>, a pair of private classes wraps a {@link
+ * javax.swing.text.Keymap} in the new {@link InputMap} / {@link
+ * ActionMap} interfaces, such that old Keymap-using code can make use of
+ * the new framework.
+ *
+ * <p>A little bit of experimentation with these classes reveals the following
+ * structure:
+ *
+ * <ul>
+ *
+ * <li>KeymapWrapper extends {@link InputMap} and holds a reference to
+ * the underlying {@link Keymap}.</li>
+ *
+ * <li>KeymapWrapper maps {@link KeyStroke} objects to {@link Action}
+ * objects, by delegation to the underlying {@link Keymap}.</li>
+ *
+ * <li>KeymapActionMap extends {@link ActionMap} also holds a reference to
+ * the underlying {@link Keymap} but only appears to use it for listing
+ * its keys. </li>
+ *
+ * <li>KeymapActionMap maps all {@link Action} objects to
+ * <em>themselves</em>, whether they exist in the underlying {@link
+ * Keymap} or not, and passes other objects to the parent {@link
+ * ActionMap} for resolving.
+ *
+ * </ul>
+ */
+
+ private class KeymapWrapper extends InputMap
+ {
+ Keymap map;
+
+ public KeymapWrapper(Keymap k)
+ {
+ map = k;
+ }
+
+ public int size()
+ {
+ return map.getBoundKeyStrokes().length + super.size();
+ }
+
+ public Object get(KeyStroke ks)
+ {
+ Action mapped = null;
+ Keymap m = map;
+ while(mapped == null && m != null)
+ {
+ mapped = m.getAction(ks);
+ if (mapped == null && ks.getKeyEventType() == KeyEvent.KEY_TYPED)
+ mapped = m.getDefaultAction();
+ if (mapped == null)
+ m = m.getResolveParent();
+ }
+
+ if (mapped == null)
+ return super.get(ks);
+ else
+ return mapped;
+ }
+
+ public KeyStroke[] keys()
+ {
+ KeyStroke[] superKeys = super.keys();
+ KeyStroke[] mapKeys = map.getBoundKeyStrokes();
+ KeyStroke[] bothKeys = new KeyStroke[superKeys.length + mapKeys.length];
+ for (int i = 0; i < superKeys.length; ++i)
+ bothKeys[i] = superKeys[i];
+ for (int i = 0; i < mapKeys.length; ++i)
+ bothKeys[i + superKeys.length] = mapKeys[i];
+ return bothKeys;
+ }
+
+ public KeyStroke[] allKeys()
+ {
+ KeyStroke[] superKeys = super.allKeys();
+ 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 < mkl; ++i)
+ bothKeys[i + skl] = mapKeys[i];
+ return bothKeys;
+ }
+ }
+
+ private class KeymapActionMap extends ActionMap
+ {
+ Keymap map;
+
+ public KeymapActionMap(Keymap k)
+ {
+ map = k;
+ }
+
+ public Action get(Object cmd)
+ {
+ if (cmd instanceof Action)
+ return (Action) cmd;
+ else
+ return super.get(cmd);
+ }
+
+ public int size()
+ {
+ return map.getBoundKeyStrokes().length + super.size();
+ }
+
+ public Object[] keys()
+ {
+ Object[] superKeys = super.keys();
+ Object[] mapKeys = map.getBoundKeyStrokes();
+ Object[] bothKeys = new Object[superKeys.length + mapKeys.length];
+ for (int i = 0; i < superKeys.length; ++i)
+ bothKeys[i] = superKeys[i];
+ for (int i = 0; i < mapKeys.length; ++i)
+ bothKeys[i + superKeys.length] = mapKeys[i];
+ return bothKeys;
+ }
+
+ public Object[] allKeys()
+ {
+ Object[] superKeys = super.allKeys();
+ Object[] mapKeys = map.getBoundKeyStrokes();
+ Object[] bothKeys = new Object[superKeys.length + mapKeys.length];
+ for (int i = 0; i < superKeys.length; ++i)
+ bothKeys[i] = superKeys[i];
+ for (int i = 0; i < mapKeys.length; ++i)
+ bothKeys[i + superKeys.length] = mapKeys[i];
+ return bothKeys;
+ }
+
+ }
+
+ static class DefaultKeymap implements Keymap
+ {
+ String name;
+ Keymap parent;
+ Hashtable map;
+ Action defaultAction;
+
+ public DefaultKeymap(String name)
+ {
+ this.name = name;
+ this.map = new Hashtable();
+ }
+
+ public void addActionForKeyStroke(KeyStroke key, Action a)
+ {
+ map.put(key, a);
+ }
+
+ /**
+ * Looks up a KeyStroke either in the current map or the parent Keymap;
+ * does <em>not</em> return the default action if lookup fails.
+ *
+ * @param key The KeyStroke to look up an Action for.
+ *
+ * @return The mapping for <code>key</code>, or <code>null</code>
+ * if no mapping exists in this Keymap or any of its parents.
+ */
+ public Action getAction(KeyStroke key)
+ {
+ if (map.containsKey(key))
+ return (Action) map.get(key);
+ else if (parent != null)
+ return parent.getAction(key);
+ else
+ return null;
+ }
+
+ public Action[] getBoundActions()
+ {
+ Action [] ret = new Action[map.size()];
+ Enumeration e = map.elements();
+ int i = 0;
+ while (e.hasMoreElements())
+ {
+ ret[i++] = (Action) e.nextElement();
+ }
+ return ret;
+ }
+
+ public KeyStroke[] getBoundKeyStrokes()
+ {
+ KeyStroke [] ret = new KeyStroke[map.size()];
+ Enumeration e = map.keys();
+ int i = 0;
+ while (e.hasMoreElements())
+ {
+ ret[i++] = (KeyStroke) e.nextElement();
+ }
+ return ret;
+ }
+
+ public Action getDefaultAction()
+ {
+ return defaultAction;
+ }
+
+ public KeyStroke[] getKeyStrokesForAction(Action a)
+ {
+ int i = 0;
+ Enumeration e = map.keys();
+ while (e.hasMoreElements())
+ {
+ if (map.get(e.nextElement()).equals(a))
+ ++i;
+ }
+ KeyStroke [] ret = new KeyStroke[i];
+ i = 0;
+ e = map.keys();
+ while (e.hasMoreElements())
+ {
+ KeyStroke k = (KeyStroke) e.nextElement();
+ if (map.get(k).equals(a))
+ ret[i++] = k;
+ }
+ return ret;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public Keymap getResolveParent()
+ {
+ return parent;
+ }
+
+ public boolean isLocallyDefined(KeyStroke key)
+ {
+ return map.containsKey(key);
+ }
+
+ public void removeBindings()
+ {
+ map.clear();
+ }
+
+ public void removeKeyStrokeBinding(KeyStroke key)
+ {
+ map.remove(key);
+ }
+
+ public void setDefaultAction(Action a)
+ {
+ defaultAction = a;
+ }
+
+ public void setResolveParent(Keymap p)
+ {
+ parent = p;
+ }
+ }
+
+ class DefaultTransferHandler extends TransferHandler
+ {
+ public boolean canImport(JComponent component, DataFlavor[] flavors)
+ {
+ JTextComponent textComponent = (JTextComponent) component;
+
+ if (! (textComponent.isEnabled()
+ && textComponent.isEditable()
+ && flavors != null))
+ return false;
+
+ for (int i = 0; i < flavors.length; ++i)
+ if (flavors[i].equals(DataFlavor.stringFlavor))
+ return true;
+
+ return false;
+ }
+
+ public void exportToClipboard(JComponent component, Clipboard clipboard,
+ int action)
+ {
+ JTextComponent textComponent = (JTextComponent) component;
+ int start = textComponent.getSelectionStart();
+ int end = textComponent.getSelectionEnd();
+
+ if (start == end)
+ return;
+
+ try
+ {
+ // Copy text to clipboard.
+ String data = textComponent.getDocument().getText(start, end);
+ StringSelection selection = new StringSelection(data);
+ clipboard.setContents(selection, null);
+
+ // Delete selected text on cut action.
+ if (action == MOVE)
+ doc.remove(start, end - start);
+ }
+ catch (BadLocationException e)
+ {
+ // Ignore this and do nothing.
+ }
+ }
+
+ public int getSourceActions()
+ {
+ return NONE;
+ }
+
+ public boolean importData(JComponent component, Transferable transferable)
+ {
+ DataFlavor flavor = null;
+ DataFlavor[] flavors = transferable.getTransferDataFlavors();
+
+ if (flavors == null)
+ return false;
+
+ for (int i = 0; i < flavors.length; ++i)
+ if (flavors[i].equals(DataFlavor.stringFlavor))
+ flavor = flavors[i];
+
+ if (flavor == null)
+ return false;
+
+ try
+ {
+ JTextComponent textComponent = (JTextComponent) component;
+ String data = (String) transferable.getTransferData(flavor);
+ textComponent.replaceSelection(data);
+ return true;
+ }
+ catch (IOException e)
+ {
+ // Ignored.
+ }
+ catch (UnsupportedFlavorException e)
+ {
+ // Ignored.
+ }
+
+ return false;
+ }
+ }
+
+ private static final long serialVersionUID = -8796518220218978795L;
+
+ public static final String DEFAULT_KEYMAP = "default";
+ public static final String FOCUS_ACCELERATOR_KEY = "focusAcceleratorKey";
+
+ private static DefaultTransferHandler defaultTransferHandler;
+ private static Hashtable keymaps = new Hashtable();
+ private Keymap keymap;
+ private char focusAccelerator = '\0';
+ private NavigationFilter navigationFilter;
+
+ /**
+ * Get a Keymap from the global keymap table, by name.
+ *
+ * @param n The name of the Keymap to look up
+ *
+ * @return A Keymap associated with the provided name, or
+ * <code>null</code> if no such Keymap exists
+ *
+ * @see #addKeymap
+ * @see #removeKeymap
+ * @see #keymaps
+ */
+ public static Keymap getKeymap(String n)
+ {
+ return (Keymap) keymaps.get(n);
+ }
+
+ /**
+ * Remove a Keymap from the global Keymap table, by name.
+ *
+ * @param n The name of the Keymap to remove
+ *
+ * @return The keymap removed from the global table
+ *
+ * @see #addKeymap
+ * @see #getKeymap()
+ * @see #keymaps
+ */
+ public static Keymap removeKeymap(String n)
+ {
+ Keymap km = (Keymap) keymaps.get(n);
+ keymaps.remove(n);
+ return km;
+ }
+
+ /**
+ * Create a new Keymap with a specific name and parent, and add the new
+ * Keymap to the global keymap table. The name may be <code>null</code>,
+ * in which case the new Keymap will <em>not</em> be added to the global
+ * Keymap table. The parent may also be <code>null</code>, which is
+ * harmless.
+ *
+ * @param n The name of the new Keymap, or <code>null</code>
+ * @param parent The parent of the new Keymap, or <code>null</code>
+ *
+ * @return The newly created Keymap
+ *
+ * @see #removeKeymap
+ * @see #getKeymap()
+ * @see #keymaps
+ */
+ public static Keymap addKeymap(String n, Keymap parent)
+ {
+ Keymap k = new DefaultKeymap(n);
+ k.setResolveParent(parent);
+ if (n != null)
+ keymaps.put(n, k);
+ return k;
+ }
+
+ /**
+ * Get the current Keymap of this component.
+ *
+ * @return The component's current Keymap
+ *
+ * @see #setKeymap
+ * @see #keymap
+ */
+ public Keymap getKeymap()
+ {
+ return keymap;
+ }
+
+ /**
+ * Set the current Keymap of this component, installing appropriate
+ * {@link KeymapWrapper} and {@link KeymapActionMap} objects in the
+ * {@link InputMap} and {@link ActionMap} parent chains, respectively,
+ * and fire a property change event with name <code>"keymap"</code>.
+ *
+ * @see #getKeymap()
+ * @see #keymap
+ */
+ public void setKeymap(Keymap k)
+ {
+
+ // phase 1: replace the KeymapWrapper entry in the InputMap chain.
+ // the goal here is to always maintain the following ordering:
+ //
+ // [InputMap]? -> [KeymapWrapper]? -> [InputMapUIResource]*
+ //
+ // that is to say, component-specific InputMaps need to remain children
+ // of Keymaps, and Keymaps need to remain children of UI-installed
+ // InputMaps (and the order of each group needs to be preserved, of
+ // course).
+
+ KeymapWrapper kw = (k == null ? null : new KeymapWrapper(k));
+ InputMap childInputMap = getInputMap(JComponent.WHEN_FOCUSED);
+ if (childInputMap == null)
+ setInputMap(JComponent.WHEN_FOCUSED, kw);
+ else
+ {
+ while (childInputMap.getParent() != null
+ && !(childInputMap.getParent() instanceof KeymapWrapper)
+ && !(childInputMap.getParent() instanceof InputMapUIResource))
+ childInputMap = childInputMap.getParent();
+
+ // option 1: there is nobody to replace at the end of the chain
+ if (childInputMap.getParent() == null)
+ childInputMap.setParent(kw);
+
+ // option 2: there is already a KeymapWrapper in the chain which
+ // needs replacing (possibly with its own parents, possibly without)
+ else if (childInputMap.getParent() instanceof KeymapWrapper)
+ {
+ if (kw == null)
+ childInputMap.setParent(childInputMap.getParent().getParent());
+ else
+ {
+ kw.setParent(childInputMap.getParent().getParent());
+ childInputMap.setParent(kw);
+ }
+ }
+
+ // option 3: there is an InputMapUIResource in the chain, which marks
+ // the place where we need to stop and insert ourselves
+ else if (childInputMap.getParent() instanceof InputMapUIResource)
+ {
+ if (kw != null)
+ {
+ kw.setParent(childInputMap.getParent());
+ childInputMap.setParent(kw);
+ }
+ }
+ }
+
+ // phase 2: replace the KeymapActionMap entry in the ActionMap chain
+
+ KeymapActionMap kam = (k == null ? null : new KeymapActionMap(k));
+ ActionMap childActionMap = getActionMap();
+ if (childActionMap == null)
+ setActionMap(kam);
+ else
+ {
+ while (childActionMap.getParent() != null
+ && !(childActionMap.getParent() instanceof KeymapActionMap)
+ && !(childActionMap.getParent() instanceof ActionMapUIResource))
+ childActionMap = childActionMap.getParent();
+
+ // option 1: there is nobody to replace at the end of the chain
+ if (childActionMap.getParent() == null)
+ childActionMap.setParent(kam);
+
+ // option 2: there is already a KeymapActionMap in the chain which
+ // needs replacing (possibly with its own parents, possibly without)
+ else if (childActionMap.getParent() instanceof KeymapActionMap)
+ {
+ if (kam == null)
+ childActionMap.setParent(childActionMap.getParent().getParent());
+ else
+ {
+ kam.setParent(childActionMap.getParent().getParent());
+ childActionMap.setParent(kam);
+ }
+ }
+
+ // option 3: there is an ActionMapUIResource in the chain, which marks
+ // the place where we need to stop and insert ourselves
+ else if (childActionMap.getParent() instanceof ActionMapUIResource)
+ {
+ if (kam != null)
+ {
+ kam.setParent(childActionMap.getParent());
+ childActionMap.setParent(kam);
+ }
+ }
+ }
+
+ // phase 3: update the explicit keymap field
+
+ Keymap old = keymap;
+ keymap = k;
+ firePropertyChange("keymap", old, k);
+ }
+
+ /**
+ * Resolves a set of bindings against a set of actions and inserts the
+ * results into a {@link Keymap}. Specifically, for each provided binding
+ * <code>b</code>, if there exists a provided action <code>a</code> such
+ * that <code>a.getValue(Action.NAME) == b.ActionName</code> then an
+ * entry is added to the Keymap mapping <code>b</code> to
+ * <code>a</code>.
+ *
+ * @param map The Keymap to add new mappings to
+ * @param bindings The set of bindings to add to the Keymap
+ * @param actions The set of actions to resolve binding names against
+ *
+ * @see Action#NAME
+ * @see Action#getValue
+ * @see KeyBinding#actionName
+ */
+ public static void loadKeymap(Keymap map,
+ JTextComponent.KeyBinding[] bindings,
+ Action[] actions)
+ {
+ 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)
+ if (acts.containsKey(bindings[i].actionName))
+ map.addActionForKeyStroke(bindings[i].key, (Action) acts.get(bindings[i].actionName));
+ }
+
+ /**
+ * Returns the set of available Actions this component's associated
+ * editor can run. Equivalent to calling
+ * <code>getUI().getEditorKit().getActions()</code>. This set of Actions
+ * is a reasonable value to provide as a parameter to {@link
+ * #loadKeymap}, when resolving a set of {@link KeyBinding} objects
+ * against this component.
+ *
+ * @return The set of available Actions on this component's {@link EditorKit}
+ *
+ * @see TextUI#getEditorKit
+ * @see EditorKit#getActions()
+ */
+ public Action[] getActions()
+ {
+ return getUI().getEditorKit(this).getActions();
+ }
+
+ // These are package-private to avoid an accessor method.
+ Document doc;
+ Caret caret;
+ boolean editable;
+
+ private Highlighter highlighter;
+ private Color caretColor;
+ private Color disabledTextColor;
+ private Color selectedTextColor;
+ private Color selectionColor;
+ private Insets margin;
+ private boolean dragEnabled;
+
+ /**
+ * Creates a new <code>JTextComponent</code> instance.
+ */
+ public JTextComponent()
+ {
+ Keymap defkeymap = getKeymap(DEFAULT_KEYMAP);
+ if (defkeymap == null)
+ {
+ defkeymap = addKeymap(DEFAULT_KEYMAP, null);
+ defkeymap.setDefaultAction(new DefaultEditorKit.DefaultKeyTypedAction());
+ }
+
+ setFocusable(true);
+ setEditable(true);
+ enableEvents(AWTEvent.KEY_EVENT_MASK);
+ setOpaque(true);
+ updateUI();
+ }
+
+ public void setDocument(Document newDoc)
+ {
+ Document oldDoc = doc;
+ try
+ {
+ if (oldDoc instanceof AbstractDocument)
+ ((AbstractDocument) oldDoc).readLock();
+
+ doc = newDoc;
+ firePropertyChange("document", oldDoc, newDoc);
+ }
+ finally
+ {
+ if (oldDoc instanceof AbstractDocument)
+ ((AbstractDocument) oldDoc).readUnlock();
+ }
+ revalidate();
+ repaint();
+ }
+
+ public Document getDocument()
+ {
+ return doc;
+ }
+
+ /**
+ * Get the <code>AccessibleContext</code> of this object.
+ *
+ * @return an <code>AccessibleContext</code> object
+ */
+ public AccessibleContext getAccessibleContext()
+ {
+ return new AccessibleJTextComponent();
+ }
+
+ public void setMargin(Insets m)
+ {
+ margin = m;
+ }
+
+ public Insets getMargin()
+ {
+ return margin;
+ }
+
+ public void setText(String text)
+ {
+ try
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).replace(0, doc.getLength(), text, null);
+ else
+ {
+ doc.remove(0, doc.getLength());
+ doc.insertString(0, text, null);
+ }
+ }
+ catch (BadLocationException e)
+ {
+ // This can never happen.
+ throw (InternalError) new InternalError().initCause(e);
+ }
+ }
+
+ /**
+ * Retrieves the current text in this text document.
+ *
+ * @return the text
+ *
+ * @exception NullPointerException if the underlaying document is null
+ */
+ public String getText()
+ {
+ if (doc == null)
+ return null;
+
+ try
+ {
+ return doc.getText(0, doc.getLength());
+ }
+ catch (BadLocationException e)
+ {
+ // This should never happen.
+ return "";
+ }
+ }
+
+ /**
+ * Retrieves a part of the current text in this document.
+ *
+ * @param offset the postion of the first character
+ * @param length the length of the text to retrieve
+ *
+ * @return the text
+ *
+ * @exception BadLocationException if arguments do not hold pre-conditions
+ */
+ public String getText(int offset, int length)
+ throws BadLocationException
+ {
+ return getDocument().getText(offset, length);
+ }
+
+ /**
+ * Retrieves the currently selected text in this text document.
+ *
+ * @return the selected text
+ *
+ * @exception NullPointerException if the underlaying document is null
+ */
+ public String getSelectedText()
+ {
+ int start = getSelectionStart();
+ int offset = getSelectionEnd() - start;
+
+ if (offset <= 0)
+ return null;
+
+ try
+ {
+ return doc.getText(start, offset);
+ }
+ catch (BadLocationException e)
+ {
+ // This should never happen.
+ return null;
+ }
+ }
+
+ /**
+ * Returns a string that specifies the name of the Look and Feel class
+ * that renders this component.
+ *
+ * @return the string "TextComponentUI"
+ */
+ public String getUIClassID()
+ {
+ return "TextComponentUI";
+ }
+
+ /**
+ * Returns a string representation of this JTextComponent.
+ */
+ protected String paramString()
+ {
+ // TODO: Do something useful here.
+ return super.paramString();
+ }
+
+ /**
+ * This method returns the label's UI delegate.
+ *
+ * @return The label's UI delegate.
+ */
+ public TextUI getUI()
+ {
+ return (TextUI) ui;
+ }
+
+ /**
+ * This method sets the label's UI delegate.
+ *
+ * @param newUI The label's UI delegate.
+ */
+ public void setUI(TextUI newUI)
+ {
+ super.setUI(newUI);
+ }
+
+ /**
+ * This method resets the label's UI delegate to the default UI for the
+ * current look and feel.
+ */
+ public void updateUI()
+ {
+ setUI((TextUI) UIManager.getUI(this));
+ }
+
+ public Dimension getPreferredScrollableViewportSize()
+ {
+ return getPreferredSize();
+ }
+
+ public int getScrollableUnitIncrement(Rectangle visible, int orientation,
+ int direction)
+ {
+ // We return 1/10 of the visible area as documented in Sun's API docs.
+ if (orientation == SwingConstants.HORIZONTAL)
+ return visible.width / 10;
+ else if (orientation == SwingConstants.VERTICAL)
+ return visible.height / 10;
+ else
+ throw new IllegalArgumentException("orientation must be either "
+ + "javax.swing.SwingConstants.VERTICAL "
+ + "or "
+ + "javax.swing.SwingConstants.HORIZONTAL"
+ );
+ }
+
+ public int getScrollableBlockIncrement(Rectangle visible, int orientation,
+ int direction)
+ {
+ // We return the whole visible area as documented in Sun's API docs.
+ if (orientation == SwingConstants.HORIZONTAL)
+ return visible.width;
+ else if (orientation == SwingConstants.VERTICAL)
+ return visible.height;
+ else
+ throw new IllegalArgumentException("orientation must be either "
+ + "javax.swing.SwingConstants.VERTICAL "
+ + "or "
+ + "javax.swing.SwingConstants.HORIZONTAL"
+ );
+ }
+
+ /**
+ * Checks whether this text component it editable.
+ *
+ * @return true if editable, false otherwise
+ */
+ public boolean isEditable()
+ {
+ return editable;
+ }
+
+ /**
+ * Enables/disabled this text component's editability.
+ *
+ * @param newValue true to make it editable, false otherwise.
+ */
+ public void setEditable(boolean newValue)
+ {
+ if (editable == newValue)
+ return;
+
+ boolean oldValue = editable;
+ editable = newValue;
+ firePropertyChange("editable", oldValue, newValue);
+ }
+
+ /**
+ * The <code>Caret</code> object used in this text component.
+ *
+ * @return the caret object
+ */
+ public Caret getCaret()
+ {
+ return caret;
+ }
+
+ /**
+ * Sets a new <code>Caret</code> for this text component.
+ *
+ * @param newCaret the new <code>Caret</code> to set
+ */
+ public void setCaret(Caret newCaret)
+ {
+ if (caret != null)
+ caret.deinstall(this);
+
+ Caret oldCaret = caret;
+ caret = newCaret;
+
+ if (caret != null)
+ caret.install(this);
+
+ firePropertyChange("caret", oldCaret, newCaret);
+ }
+
+ public Color getCaretColor()
+ {
+ return caretColor;
+ }
+
+ public void setCaretColor(Color newColor)
+ {
+ Color oldCaretColor = caretColor;
+ caretColor = newColor;
+ firePropertyChange("caretColor", oldCaretColor, newColor);
+ }
+
+ public Color getDisabledTextColor()
+ {
+ return disabledTextColor;
+ }
+
+ public void setDisabledTextColor(Color newColor)
+ {
+ Color oldColor = disabledTextColor;
+ disabledTextColor = newColor;
+ firePropertyChange("disabledTextColor", oldColor, newColor);
+ }
+
+ public Color getSelectedTextColor()
+ {
+ return selectedTextColor;
+ }
+
+ public void setSelectedTextColor(Color newColor)
+ {
+ Color oldColor = selectedTextColor;
+ selectedTextColor = newColor;
+ firePropertyChange("selectedTextColor", oldColor, newColor);
+ }
+
+ public Color getSelectionColor()
+ {
+ return selectionColor;
+ }
+
+ public void setSelectionColor(Color newColor)
+ {
+ Color oldColor = selectionColor;
+ selectionColor = newColor;
+ firePropertyChange("selectionColor", oldColor, newColor);
+ }
+
+ /**
+ * Retrisves the current caret position.
+ *
+ * @return the current position
+ */
+ public int getCaretPosition()
+ {
+ return caret.getDot();
+ }
+
+ /**
+ * Sets the caret to a new position.
+ *
+ * @param position the new position
+ */
+ public void setCaretPosition(int position)
+ {
+ if (doc == null)
+ return;
+
+ if (position < 0 || position > doc.getLength())
+ throw new IllegalArgumentException();
+
+ caret.setDot(position);
+ }
+
+ /**
+ * Moves the caret to a given position. This selects the text between
+ * the old and the new position of the caret.
+ */
+ public void moveCaretPosition(int position)
+ {
+ if (doc == null)
+ return;
+
+ if (position < 0 || position > doc.getLength())
+ throw new IllegalArgumentException();
+
+ caret.moveDot(position);
+ }
+
+ public Highlighter getHighlighter()
+ {
+ return highlighter;
+ }
+
+ public void setHighlighter(Highlighter newHighlighter)
+ {
+ if (highlighter != null)
+ highlighter.deinstall(this);
+
+ Highlighter oldHighlighter = highlighter;
+ highlighter = newHighlighter;
+
+ if (highlighter != null)
+ highlighter.install(this);
+
+ firePropertyChange("highlighter", oldHighlighter, newHighlighter);
+ }
+
+ /**
+ * Returns the start postion of the currently selected text.
+ *
+ * @return the start postion
+ */
+ public int getSelectionStart()
+ {
+ return Math.min(caret.getDot(), caret.getMark());
+ }
+
+ /**
+ * Selects the text from the given postion to the selection end position.
+ *
+ * @param start the start positon of the selected text.
+ */
+ public void setSelectionStart(int start)
+ {
+ select(start, getSelectionEnd());
+ }
+
+ /**
+ * Returns the end postion of the currently selected text.
+ *
+ * @return the end postion
+ */
+ public int getSelectionEnd()
+ {
+ return Math.max(caret.getDot(), caret.getMark());
+ }
+
+ /**
+ * Selects the text from the selection start postion to the given position.
+ *
+ * @param end the end positon of the selected text.
+ */
+ public void setSelectionEnd(int end)
+ {
+ select(getSelectionStart(), end);
+ }
+
+ /**
+ * Selects a part of the content of the text component.
+ *
+ * @param start the start position of the selected text
+ * @param end the end position of the selected text
+ */
+ public void select(int start, int end)
+ {
+ int length = doc.getLength();
+
+ start = Math.max(start, 0);
+ start = Math.min(start, length);
+
+ end = Math.max(end, start);
+ end = Math.min(end, length);
+
+ setCaretPosition(start);
+ moveCaretPosition(end);
+ }
+
+ /**
+ * Selects the whole content of the text component.
+ */
+ public void selectAll()
+ {
+ select(0, doc.getLength());
+ }
+
+ public synchronized void replaceSelection(String content)
+ {
+ int dot = caret.getDot();
+ int mark = caret.getMark();
+
+ // If content is empty delete selection.
+ if (content == null)
+ {
+ caret.setDot(dot);
+ return;
+ }
+
+ try
+ {
+ int start = getSelectionStart();
+ int end = getSelectionEnd();
+
+ // Remove selected text.
+ if (dot != mark)
+ doc.remove(start, end - start);
+
+ // Insert new text.
+ doc.insertString(start, content, null);
+
+ // Set dot to new position,
+ dot = start + content.length();
+ setCaretPosition(dot);
+
+ // and update it's magic position.
+ caret.setMagicCaretPosition(modelToView(dot).getLocation());
+ }
+ catch (BadLocationException e)
+ {
+ // This should never happen.
+ }
+ }
+
+ public boolean getScrollableTracksViewportHeight()
+ {
+ if (getParent() instanceof JViewport)
+ return getParent().getHeight() > getPreferredSize().height;
+
+ return false;
+ }
+
+ public boolean getScrollableTracksViewportWidth()
+ {
+ boolean res = false;
+ Container c = getParent();
+ if (c instanceof JViewport)
+ res = ((JViewport) c).getExtentSize().width > getPreferredSize().width;
+
+ return res;
+ }
+
+ /**
+ * Adds a <code>CaretListener</code> object to this text component.
+ *
+ * @param listener the listener to add
+ */
+ public void addCaretListener(CaretListener listener)
+ {
+ listenerList.add(CaretListener.class, listener);
+ }
+
+ /**
+ * Removed a <code>CaretListener</code> object from this text component.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeCaretListener(CaretListener listener)
+ {
+ listenerList.remove(CaretListener.class, listener);
+ }
+
+ /**
+ * Returns all added <code>CaretListener</code> objects.
+ *
+ * @return an array of listeners
+ */
+ public CaretListener[] getCaretListeners()
+ {
+ return (CaretListener[]) getListeners(CaretListener.class);
+ }
+
+ /**
+ * Notifies all registered <code>CaretListener</code> objects that the caret
+ * was updated.
+ *
+ * @param event the event to send
+ */
+ protected void fireCaretUpdate(CaretEvent event)
+ {
+ CaretListener[] listeners = getCaretListeners();
+
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].caretUpdate(event);
+ }
+
+ /**
+ * Adds an <code>InputListener</code> object to this text component.
+ *
+ * @param listener the listener to add
+ */
+ public void addInputMethodListener(InputMethodListener listener)
+ {
+ listenerList.add(InputMethodListener.class, listener);
+ }
+
+ /**
+ * Removes an <code>InputListener</code> object from this text component.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeInputMethodListener(InputMethodListener listener)
+ {
+ listenerList.remove(InputMethodListener.class, listener);
+ }
+
+ /**
+ * Returns all added <code>InputMethodListener</code> objects.
+ *
+ * @return an array of listeners
+ */
+ public InputMethodListener[] getInputMethodListeners()
+ {
+ return (InputMethodListener[]) getListeners(InputMethodListener.class);
+ }
+
+ public Rectangle modelToView(int position) throws BadLocationException
+ {
+ return getUI().modelToView(this, position);
+ }
+
+ public boolean getDragEnabled()
+ {
+ return dragEnabled;
+ }
+
+ public void setDragEnabled(boolean enabled)
+ {
+ dragEnabled = enabled;
+ }
+
+ public int viewToModel(Point pt)
+ {
+ return getUI().viewToModel(this, pt);
+ }
+
+ public void copy()
+ {
+ if (isEnabled())
+ doTransferAction("copy", TransferHandler.getCopyAction());
+ }
+
+ public void cut()
+ {
+ if (editable && isEnabled())
+ doTransferAction("cut", TransferHandler.getCutAction());
+ }
+
+ public void paste()
+ {
+ if (editable && isEnabled())
+ doTransferAction("paste", TransferHandler.getPasteAction());
+ }
+
+ private void doTransferAction(String name, Action action)
+ {
+ // Install default TransferHandler if none set.
+ if (getTransferHandler() == null)
+ {
+ if (defaultTransferHandler == null)
+ defaultTransferHandler = new DefaultTransferHandler();
+
+ setTransferHandler(defaultTransferHandler);
+ }
+
+ // Perform action.
+ ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
+ action.getValue(Action.NAME).toString());
+ action.actionPerformed(event);
+ }
+
+ public void setFocusAccelerator(char newKey)
+ {
+ if (focusAccelerator == newKey)
+ return;
+
+ char oldKey = focusAccelerator;
+ focusAccelerator = newKey;
+ firePropertyChange(FOCUS_ACCELERATOR_KEY, oldKey, newKey);
+ }
+
+ public char getFocusAccelerator()
+ {
+ return focusAccelerator;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public NavigationFilter getNavigationFilter()
+ {
+ return navigationFilter;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public void setNavigationFilter(NavigationFilter filter)
+ {
+ navigationFilter = filter;
+ }
+
+ /**
+ * Read and set the content this component. If not overridden, the
+ * method reads the component content as a plain text.
+ *
+ * The second parameter of this method describes the input stream. It can
+ * be String, URL, File and so on. If not null, this object is added to
+ * the properties of the associated document under the key
+ * {@link Document#StreamDescriptionProperty}.
+ *
+ * @param input an input stream to read from.
+ * @param streamDescription an object, describing the stream.
+ *
+ * @throws IOException if the reader throws it.
+ *
+ * @see #getDocument()
+ * @see Document#getProperty(Object)
+ */
+ public void read(Reader input, Object streamDescription)
+ throws IOException
+ {
+ if (streamDescription != null)
+ {
+ Document d = getDocument();
+ if (d != null)
+ d.putProperty(Document.StreamDescriptionProperty, streamDescription);
+ }
+
+ CPStringBuilder b = new CPStringBuilder();
+ int c;
+
+ // Read till -1 (EOF).
+ while ((c = input.read()) >= 0)
+ b.append((char) c);
+
+ setText(b.toString());
+ }
+
+ /**
+ * Write the content of this component to the given stream. If not
+ * overridden, the method writes the component content as a plain text.
+ *
+ * @param output the writer to write into.
+ *
+ * @throws IOException if the writer throws it.
+ */
+ public void write(Writer output)
+ throws IOException
+ {
+ output.write(getText());
+ }
+
+ /**
+ * Returns the tooltip text for this text component for the given mouse
+ * event. This forwards the call to
+ * {@link TextUI#getToolTipText(JTextComponent, Point)}.
+ *
+ * @param ev the mouse event
+ *
+ * @return the tooltip text for this text component for the given mouse
+ * event
+ */
+ public String getToolTipText(MouseEvent ev)
+ {
+ return getUI().getToolTipText(this, ev.getPoint());
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/Keymap.java b/libjava/classpath/javax/swing/text/Keymap.java
new file mode 100644
index 000000000..e1b305f5f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Keymap.java
@@ -0,0 +1,58 @@
+/* Keymap.java --
+ Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text;
+
+import javax.swing.Action;
+import javax.swing.KeyStroke;
+
+public interface Keymap
+{
+ void addActionForKeyStroke(KeyStroke key, Action a);
+ Action getAction(KeyStroke key);
+ Action[] getBoundActions();
+ KeyStroke[] getBoundKeyStrokes();
+ Action getDefaultAction();
+ KeyStroke[] getKeyStrokesForAction(Action a);
+ String getName();
+ Keymap getResolveParent();
+ boolean isLocallyDefined(KeyStroke key);
+ void removeBindings();
+ void removeKeyStrokeBinding(KeyStroke keys);
+ void setDefaultAction(Action a);
+ void setResolveParent(Keymap parent);
+}
diff --git a/libjava/classpath/javax/swing/text/LabelView.java b/libjava/classpath/javax/swing/text/LabelView.java
new file mode 100644
index 000000000..7cfeae862
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/LabelView.java
@@ -0,0 +1,322 @@
+/* LabelView.java -- A view to render styled text
+ Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Shape;
+import java.awt.Toolkit;
+
+import javax.swing.event.DocumentEvent;
+
+/**
+ * A {@link GlyphView} that caches the textattributes for most effective
+ * rendering.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class LabelView extends GlyphView
+{
+
+ /**
+ * The background color.
+ */
+ Color background;
+
+ /**
+ * The foreground color.
+ */
+ Color foreground;
+
+ /**
+ * The background color.
+ */
+ Font font;
+
+ /**
+ * The strikethrough flag.
+ */
+ boolean strikeThrough;
+
+ /**
+ * The underline flag.
+ */
+ boolean underline;
+
+ /**
+ * The subscript flag.
+ */
+ boolean subscript;
+
+ /**
+ * The superscript flag.
+ */
+ boolean superscript;
+
+ /**
+ * Indicates if the attributes must be refetched.
+ */
+ private boolean valid;
+
+ /**
+ * Creates a new <code>GlyphView</code> for the given <code>Element</code>.
+ *
+ * @param element the element that is rendered by this GlyphView
+ */
+ public LabelView(Element element)
+ {
+ super(element);
+ valid = false;
+ }
+
+ /**
+ * Loads the properties of this label view from the element's text
+ * attributes. This method is called from the constructor and the
+ * {@link #changedUpdate} method
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ AttributeSet atts = getAttributes();
+ setStrikeThrough(StyleConstants.isStrikeThrough(atts));
+ setSubscript(StyleConstants.isSubscript(atts));
+ setSuperscript(StyleConstants.isSuperscript(atts));
+ setUnderline(StyleConstants.isUnderline(atts));
+
+ // Determine the font and colors.
+ Document d = getDocument();
+ if (d instanceof StyledDocument)
+ {
+ StyledDocument doc = (StyledDocument) d;
+ font = doc.getFont(atts);
+ if (atts.isDefined(StyleConstants.Background))
+ background = doc.getBackground(atts);
+ else
+ background = null;
+ foreground = doc.getForeground(atts);
+ }
+ valid = true;
+ }
+
+ /**
+ * Receives notification when text attributes change in the chunk of
+ * text that this view is responsible for. This simply calls
+ * {@link #setPropertiesFromAttributes()}.
+ *
+ * @param e the document event
+ * @param a the allocation of this view
+ * @param vf the view factory to use for creating new views
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ valid = false;
+ super.changedUpdate(e, a, vf);
+ }
+
+ /**
+ * Returns the background color for the glyphs.
+ *
+ * @return the background color for the glyphs
+ */
+ public Color getBackground()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+ return background;
+ }
+
+ /**
+ * Sets the background color for the glyphs. A value of <code>null</code>
+ * means the background of the parent view should shine through.
+ *
+ * @param bg the background to set or <code>null</code>
+ *
+ * @since 1.5
+ */
+ protected void setBackground(Color bg)
+ {
+ background = bg;
+ }
+
+ /**
+ * Returns the foreground color for the glyphs.
+ *
+ * @return the foreground color for the glyphs
+ */
+ public Color getForeground()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+ return foreground;
+ }
+
+ /**
+ * Returns the font for the glyphs.
+ *
+ * @return the font for the glyphs
+ */
+ public Font getFont()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+ return font;
+ }
+
+ /**
+ * Returns the font metrics of the current font.
+ *
+ * @return the font metrics of the current font
+ *
+ * @deprecated this is not used anymore
+ */
+ protected FontMetrics getFontMetrics()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+
+ Container c = getContainer();
+ FontMetrics fm;
+ if (c != null)
+ fm = c.getFontMetrics(font);
+ else
+ fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
+ return fm;
+ }
+
+ /**
+ * Returns <code>true</code> if the glyphs are rendered underlined,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the glyphs are rendered underlined,
+ * <code>false</code> otherwise
+ */
+ public boolean isUnderline()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+ return underline;
+ }
+
+ /**
+ * Sets the underline flag.
+ *
+ * @param flag <code>true</code> if the glyphs are rendered underlined,
+ * <code>false</code> otherwise
+ */
+ protected void setUnderline(boolean flag)
+ {
+ underline = flag;
+ }
+
+ /**
+ * Returns <code>true</code> if the glyphs are rendered as subscript,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the glyphs are rendered as subscript,
+ * <code>false</code> otherwise
+ */
+ public boolean isSubscript()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+ return subscript;
+ }
+
+ /**
+ * Sets the subscript flag.
+ *
+ * @param flag <code>true</code> if the glyphs are rendered as subscript,
+ * <code>false</code> otherwise
+ */
+ protected void setSubscript(boolean flag)
+ {
+ subscript = flag;
+ }
+
+ /**
+ * Returns <code>true</code> if the glyphs are rendered as superscript,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the glyphs are rendered as superscript,
+ * <code>false</code> otherwise
+ */
+ public boolean isSuperscript()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+ return superscript;
+ }
+
+ /**
+ * Sets the superscript flag.
+ *
+ * @param flag <code>true</code> if the glyphs are rendered as superscript,
+ * <code>false</code> otherwise
+ */
+ protected void setSuperscript(boolean flag)
+ {
+ superscript = flag;
+ }
+
+ /**
+ * Returns <code>true</code> if the glyphs are rendered strike-through,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the glyphs are rendered strike-through,
+ * <code>false</code> otherwise
+ */
+ public boolean isStrikeThrough()
+ {
+ if (! valid)
+ setPropertiesFromAttributes();
+ return strikeThrough;
+ }
+
+ /**
+ * Sets the strike-through flag.
+ *
+ * @param flag <code>true</code> if the glyphs are rendered strike-through,
+ * <code>false</code> otherwise
+ */
+ protected void setStrikeThrough(boolean flag)
+ {
+ strikeThrough = flag;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/LayeredHighlighter.java b/libjava/classpath/javax/swing/text/LayeredHighlighter.java
new file mode 100644
index 000000000..3eac26b38
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/LayeredHighlighter.java
@@ -0,0 +1,57 @@
+/* LayeredHighlighter.java --
+ Copyright (C) 2004 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.Graphics;
+import java.awt.Shape;
+
+public abstract class LayeredHighlighter
+ implements Highlighter
+{
+ public abstract static class LayerPainter
+ implements Highlighter.HighlightPainter
+ {
+ public abstract Shape paintLayer(Graphics g, int p0, int p1,
+ Shape viewBounds, JTextComponent editor,
+ View view);
+ }
+
+ public abstract void paintLayeredHighlights(Graphics g, int p0, int p1,
+ Shape viewBounds,
+ JTextComponent editor, View view);
+}
diff --git a/libjava/classpath/javax/swing/text/LayoutQueue.java b/libjava/classpath/javax/swing/text/LayoutQueue.java
new file mode 100644
index 000000000..10fadd55e
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/LayoutQueue.java
@@ -0,0 +1,116 @@
+/* LayoutQueue.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.util.LinkedList;
+
+/**
+ * This is a queue which holds {@link Runnable} objects. It is
+ * intended for deferring layout operations.
+ */
+public class LayoutQueue
+{
+ // The default layout queue.
+ private static LayoutQueue defaultQueue = new LayoutQueue();
+
+ // The queue of tasks.
+ private LinkedList list = new LinkedList();
+
+ /**
+ * Create a new layout queue.
+ */
+ public LayoutQueue()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Add a layout task to the queue.
+ */
+ public void addTask(Runnable task)
+ {
+ synchronized (list)
+ {
+ list.addLast(task);
+ list.notify();
+ }
+ }
+
+ /**
+ * Called by a worker thread to retrieve the next layout task. This
+ * will block until a new task is available. This method will
+ * return null if the thread is interrupted while waiting.
+ */
+ protected Runnable waitForWork()
+ {
+ synchronized (list)
+ {
+ while (list.size() == 0)
+ {
+ try
+ {
+ list.wait();
+ }
+ catch (InterruptedException _)
+ {
+ // This seemed like a good idea, but it has not been
+ // tested on the JDK.
+ return null;
+ }
+ }
+ return (Runnable) list.removeFirst();
+ }
+ }
+
+ /**
+ * Return the default layout queue.
+ */
+ public static synchronized LayoutQueue getDefaultQueue()
+ {
+ return defaultQueue;
+ }
+
+ /**
+ * Set the default layout queue.
+ */
+ public static synchronized void setDefaultQueue(LayoutQueue q)
+ {
+ defaultQueue = q;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/MaskFormatter.java b/libjava/classpath/javax/swing/text/MaskFormatter.java
new file mode 100644
index 000000000..c8f631ae3
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/MaskFormatter.java
@@ -0,0 +1,558 @@
+/* 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 gnu.java.lang.CPStringBuilder;
+
+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
+ {
+ this();
+ 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
+ * <code>invalidCharacters</code> 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
+ * <code>validCharacters</code> 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 <code>mask</code> 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
+ {
+ return super.stringToValue(convertStringToValue(value));
+ }
+
+ private String convertStringToValue(String value)
+ throws ParseException
+ {
+ CPStringBuilder result = new CPStringBuilder();
+ char valueChar;
+ boolean isPlaceHolder;
+
+ int length = mask.length();
+ for (int i = 0, j = 0; j < length; j++)
+ {
+ char maskChar = mask.charAt(j);
+
+ if (i < value.length())
+ {
+ isPlaceHolder = false;
+ valueChar = value.charAt(i);
+ if (maskChar != ESCAPE_CHAR && maskChar != valueChar)
+ {
+ if (invalidChars != null
+ && invalidChars.indexOf(valueChar) != -1)
+ throw new ParseException("Invalid character: " + valueChar, i);
+ if (validChars != null
+ && validChars.indexOf(valueChar) == -1)
+ throw new ParseException("Invalid character: " + valueChar, i);
+ }
+ }
+ else if (placeHolder != null && i < placeHolder.length())
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolder.charAt(i);
+ }
+ else
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolderChar;
+ }
+
+ // This switch block on the mask character checks that the character
+ // within <code>value</code> at that point is valid according to the
+ // mask and also converts to upper/lowercase as needed.
+ switch (maskChar)
+ {
+ case NUM_CHAR:
+ if (! Character.isDigit(valueChar))
+ throw new ParseException("Number expected: " + valueChar, i);
+ result.append(valueChar);
+ i++;
+ break;
+ case UPPERCASE_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(Character.toUpperCase(valueChar));
+ i++;
+ break;
+ case LOWERCASE_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(Character.toLowerCase(valueChar));
+ i++;
+ break;
+ case ALPHANUM_CHAR:
+ if (! Character.isLetterOrDigit(valueChar))
+ throw new ParseException("Letter or number expected", i);
+ result.append(valueChar);
+ i++;
+ break;
+ case LETTER_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(valueChar);
+ i++;
+ break;
+ case HEX_CHAR:
+ if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
+ throw new ParseException("Hexadecimal character expected", i);
+ result.append(valueChar);
+ i++;
+ break;
+ case ANYTHING_CHAR:
+ result.append(valueChar);
+ i++;
+ break;
+ case ESCAPE_CHAR:
+ // Escape character, check the next character to make sure that
+ // the literals match
+ j++;
+ if (j < length)
+ {
+ maskChar = mask.charAt(j);
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ {
+ result.append(maskChar);
+ }
+ i++;
+ }
+ else if (! isPlaceHolder)
+ throw new ParseException("Bad match at trailing escape: ", i);
+ break;
+ default:
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ {
+ result.append(maskChar);
+ }
+ i++;
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a String representation of the Object value based on the mask.
+ *
+ * @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 string = value != null ? value.toString() : "";
+ return convertValueToString(string);
+ }
+
+ /**
+ * This method takes in a String and runs it through the mask to make
+ * sure that it is valid. If <code>convert</code> is true, it also
+ * converts letters to upper/lowercase as required by the mask.
+ * @param value the String to convert
+ * @return the converted String
+ * @throws ParseException if the given String isn't valid for the mask
+ */
+ private String convertValueToString(String value)
+ throws ParseException
+ {
+ CPStringBuilder result = new CPStringBuilder();
+ char valueChar;
+ boolean isPlaceHolder;
+
+ int length = mask.length();
+ for (int i = 0, j = 0; j < length; j++)
+ {
+ char maskChar = mask.charAt(j);
+ if (i < value.length())
+ {
+ isPlaceHolder = false;
+ valueChar = value.charAt(i);
+ if (maskChar != ESCAPE_CHAR && valueChar != maskChar)
+ {
+ if (invalidChars != null
+ && invalidChars.indexOf(valueChar) != -1)
+ throw new ParseException("Invalid character: " + valueChar,
+ i);
+ if (validChars != null && validChars.indexOf(valueChar) == -1)
+ throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar,
+ i);
+ }
+ }
+ else if (placeHolder != null && i < placeHolder.length())
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolder.charAt(i);
+ }
+ else
+ {
+ isPlaceHolder = true;
+ valueChar = placeHolderChar;
+ }
+
+ // This switch block on the mask character checks that the character
+ // within <code>value</code> at that point is valid according to the
+ // mask and also converts to upper/lowercase as needed.
+ switch (maskChar)
+ {
+ case NUM_CHAR:
+ if ( ! isPlaceHolder && ! Character.isDigit(valueChar))
+ throw new ParseException("Number expected: " + valueChar, i);
+ result.append(valueChar);
+ i++;
+ break;
+ case UPPERCASE_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(Character.toUpperCase(valueChar));
+ i++;
+ break;
+ case LOWERCASE_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(Character.toLowerCase(valueChar));
+ i++;
+ break;
+ case ALPHANUM_CHAR:
+ if (! Character.isLetterOrDigit(valueChar))
+ throw new ParseException("Letter or number expected", i);
+ result.append(valueChar);
+ i++;
+ break;
+ case LETTER_CHAR:
+ if (! Character.isLetter(valueChar))
+ throw new ParseException("Letter expected", i);
+ result.append(valueChar);
+ i++;
+ break;
+ case HEX_CHAR:
+ if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
+ throw new ParseException("Hexadecimal character expected", i);
+ result.append(valueChar);
+ i++;
+ break;
+ case ANYTHING_CHAR:
+ result.append(valueChar);
+ i++;
+ break;
+ case ESCAPE_CHAR:
+ // Escape character, check the next character to make sure that
+ // the literals match
+ j++;
+ if (j < length)
+ {
+ maskChar = mask.charAt(j);
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ i++;
+ result.append(maskChar);
+ }
+ break;
+ default:
+ if (! isPlaceHolder && getValueContainsLiteralCharacters()
+ && valueChar != maskChar)
+ throw new ParseException ("Invalid character: "+ valueChar, i);
+ if (getValueContainsLiteralCharacters())
+ i++;
+ result.append(maskChar);
+ }
+ }
+ return result.toString();
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/MutableAttributeSet.java b/libjava/classpath/javax/swing/text/MutableAttributeSet.java
new file mode 100644
index 000000000..eb52be5c8
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/MutableAttributeSet.java
@@ -0,0 +1,117 @@
+/* MutableAttributeSet.java --
+ Copyright (C) 2002, 2004, 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.util.Enumeration;
+
+/**
+ * An {@link AttributeSet} that supports modification of the stored
+ * attributes.
+ *
+ * @author Andrew Selkirk
+ * @since 1.2
+ */
+public interface MutableAttributeSet extends AttributeSet
+{
+ /**
+ * Adds an attribute with the given <code>name</code> and <code>value</code>
+ * to the set. If the set already contains an attribute with the given
+ * <code>name</code>, the attribute value is updated.
+ *
+ * @param name the attribute name (<code>null</code> not permitted).
+ * @param value the value (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ */
+ void addAttribute(Object name, Object value);
+
+ /**
+ * Adds all the attributes from <code>attributes</code> to this set.
+ *
+ * @param attributes the set of attributes to add (<code>null</code> not
+ * permitted).
+ *
+ * @throws NullPointerException if <code>attributes</code> is
+ * <code>null</code>.
+ */
+ void addAttributes(AttributeSet attributes);
+
+ /**
+ * Removes the attribute with the specified <code>name</code>, if this
+ * attribute is defined. This method will only remove an attribute from
+ * this set, not from the resolving parent.
+ *
+ * @param name the attribute name (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>name</code> is <code>null</code>.
+ */
+ void removeAttribute(Object name);
+
+ /**
+ * Removes the attributes listed in <code>names</code>.
+ *
+ * @param names the attribute names (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>names</code> is <code>null</code>
+ * or contains any <code>null</code> values.
+ */
+ void removeAttributes(Enumeration<?> names);
+
+ /**
+ * Removes attributes from this set if they are found in the
+ * given set. Only attributes whose key AND value are removed.
+ * Removes attributes only from this set, not from the resolving parent.
+ * Since the resolving parent is stored as an attribute, if
+ * <code>attributes</code> has the same resolving parent as this set, the
+ * parent will be removed from this set.
+ *
+ * @param attributes the attributes (<code>null</code> not permitted).
+ */
+ void removeAttributes(AttributeSet attributes);
+
+ /**
+ * Sets the reolving parent for this set. When looking up an attribute, if
+ * it is not found in this set, then the resolving parent is also used for
+ * the lookup.
+ *
+ * @param parent the parent attribute set (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>parent</code> is <code>null</code>.
+ */
+ void setResolveParent(AttributeSet parent);
+}
diff --git a/libjava/classpath/javax/swing/text/NavigationFilter.java b/libjava/classpath/javax/swing/text/NavigationFilter.java
new file mode 100644
index 000000000..647ac70bf
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/NavigationFilter.java
@@ -0,0 +1,98 @@
+/* NavigationFilter.java --
+ Copyright (C) 2003, 2004 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;
+
+public class NavigationFilter
+{
+ public abstract static class FilterBypass
+ {
+ public FilterBypass()
+ {
+ // Do nothing here.
+ }
+
+ public abstract Caret getCaret();
+ public abstract void moveDot(int dot, Position.Bias bias);
+ public abstract void setDot(int dot, Position.Bias bias);
+ }
+
+ public NavigationFilter()
+ {
+ // Do nothing here.
+ }
+
+ public void moveDot(NavigationFilter.FilterBypass fb, int dot,
+ Position.Bias bias)
+ {
+ fb.moveDot(dot, bias);
+ }
+
+ public void setDot(NavigationFilter.FilterBypass fb, int dot,
+ Position.Bias bias)
+ {
+ fb.setDot(dot, bias);
+ }
+
+ /**
+ * Returns the next visual position in the specified direction at which one
+ * would place a caret. The default implementation forwards to the text
+ * component's root view. Subclasses may wish to restrict that more.
+ *
+ * @param c the text component
+ * @param pos the current model position
+ * @param bias the bias of <code>pos</code>
+ * @param dir the direction, one of {@link javax.swing.SwingConstants#NORTH},
+ * {@link javax.swing.SwingConstants#SOUTH},
+ * {@link javax.swing.SwingConstants#WEST} or
+ * {@link javax.swing.SwingConstants#EAST}
+ * @param retBias the bias of the returned position
+ *
+ * @return the next model location to place the caret
+ *
+ * @throws BadLocationException when <code>pos</code> is not a valid model
+ * position
+ */
+ public int getNextVisualPositionFrom(JTextComponent c, int pos,
+ Position.Bias bias, int dir,
+ Position.Bias[] retBias)
+ throws BadLocationException
+ {
+ return c.getUI().getNextVisualPositionFrom(c, pos, bias, dir, retBias);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/NumberFormatter.java b/libjava/classpath/javax/swing/text/NumberFormatter.java
new file mode 100644
index 000000000..ce5eef990
--- /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;
+
+/**
+ * <code>NumberFormatter</code> 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/ParagraphView.java b/libjava/classpath/javax/swing/text/ParagraphView.java
new file mode 100644
index 000000000..4d4c7a044
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/ParagraphView.java
@@ -0,0 +1,528 @@
+/* ParagraphView.java -- A composite View
+ Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Shape;
+
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+
+/**
+ * A {@link FlowView} that flows it's children horizontally and boxes the rows
+ * vertically.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class ParagraphView extends FlowView implements TabExpander
+{
+ /**
+ * A specialized horizontal <code>BoxView</code> that represents exactly
+ * one row in a <code>ParagraphView</code>.
+ */
+ class Row extends BoxView
+ {
+ /**
+ * Creates a new instance of <code>Row</code>.
+ */
+ Row(Element el)
+ {
+ super(el, X_AXIS);
+ }
+
+ /**
+ * Overridden to adjust when we are the first line, and firstLineIndent
+ * is not 0.
+ */
+ public short getLeftInset()
+ {
+ short leftInset = super.getLeftInset();
+ View parent = getParent();
+ if (parent != null)
+ {
+ if (parent.getView(0) == this)
+ leftInset += firstLineIndent;
+ }
+ return leftInset;
+ }
+
+ public float getAlignment(int axis)
+ {
+ float align;
+ if (axis == X_AXIS)
+ switch (justification)
+ {
+ case StyleConstants.ALIGN_RIGHT:
+ align = 1.0F;
+ break;
+ case StyleConstants.ALIGN_CENTER:
+ case StyleConstants.ALIGN_JUSTIFIED:
+ align = 0.5F;
+ break;
+ case StyleConstants.ALIGN_LEFT:
+ default:
+ align = 0.0F;
+ }
+ else
+ align = super.getAlignment(axis);
+ return align;
+ }
+
+ /**
+ * Overridden because child views are not necessarily laid out in model
+ * order.
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ int index = -1;
+ if (pos >= getStartOffset() && pos < getEndOffset())
+ {
+ int nviews = getViewCount();
+ for (int i = 0; i < nviews && index == -1; i++)
+ {
+ View child = getView(i);
+ if (pos >= child.getStartOffset() && pos < child.getEndOffset())
+ index = i;
+ }
+ }
+ return index;
+ }
+
+
+ /**
+ * Overridden to perform a baseline layout. The normal BoxView layout
+ * isn't completely suitable for rows.
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ baselineLayout(targetSpan, axis, offsets, spans);
+ }
+
+ /**
+ * Overridden to perform a baseline layout. The normal BoxView layout
+ * isn't completely suitable for rows.
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ return baselineRequirements(axis, r);
+ }
+
+ protected void loadChildren(ViewFactory vf)
+ {
+ // Do nothing here. The children are added while layouting.
+ }
+
+ /**
+ * Overridden to determine the minimum start offset of the row's children.
+ */
+ public int getStartOffset()
+ {
+ // Determine minimum start offset of the children.
+ int offset = Integer.MAX_VALUE;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ offset = Math.min(offset, v.getStartOffset());
+ }
+ return offset;
+ }
+
+ /**
+ * Overridden to determine the maximum end offset of the row's children.
+ */
+ public int getEndOffset()
+ {
+ // Determine minimum start offset of the children.
+ int offset = 0;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View v = getView(i);
+ offset = Math.max(offset, v.getEndOffset());
+ }
+ return offset;
+ }
+ }
+
+ /**
+ * The indentation of the first line of the paragraph.
+ */
+ protected int firstLineIndent;
+
+ /**
+ * The justification of the paragraph.
+ */
+ private int justification;
+
+ /**
+ * The line spacing of this paragraph.
+ */
+ private float lineSpacing;
+
+ /**
+ * The TabSet of this paragraph.
+ */
+ private TabSet tabSet;
+
+ /**
+ * Creates a new <code>ParagraphView</code> for the given
+ * <code>Element</code>.
+ *
+ * @param element the element that is rendered by this ParagraphView
+ */
+ public ParagraphView(Element element)
+ {
+ super(element, Y_AXIS);
+ }
+
+ public float nextTabStop(float x, int tabOffset)
+ {
+ throw new InternalError("Not implemented yet");
+ }
+
+ /**
+ * Creates a new view that represents a row within a flow.
+ *
+ * @return a view for a new row
+ */
+ protected View createRow()
+ {
+ return new Row(getElement());
+ }
+
+ /**
+ * Returns the alignment for this paragraph view for the specified axis.
+ * For the X_AXIS the paragraph view will be aligned at it's left edge
+ * (0.0F). For the Y_AXIS the paragraph view will be aligned at the
+ * center of it's first row.
+ *
+ * @param axis the axis which is examined
+ *
+ * @return the alignment for this paragraph view for the specified axis
+ */
+ public float getAlignment(int axis)
+ {
+ float align;
+ if (axis == X_AXIS)
+ align = 0.5F;
+ else if (getViewCount() > 0)
+ {
+ float prefHeight = getPreferredSpan(Y_AXIS);
+ float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS);
+ align = (firstRowHeight / 2.F) / prefHeight;
+ }
+ else
+ align = 0.5F;
+ return align;
+ }
+
+ /**
+ * Receives notification when some attributes of the displayed element
+ * changes. This triggers a refresh of the cached attributes of this
+ * paragraph.
+ *
+ * @param ev the document event
+ * @param a the allocation of this view
+ * @param vf the view factory to use for creating new child views
+ */
+ public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf)
+ {
+ setPropertiesFromAttributes();
+ layoutChanged(X_AXIS);
+ layoutChanged(Y_AXIS);
+ super.changedUpdate(ev, a, vf);
+ }
+
+ /**
+ * Fetches the cached properties from the element's attributes.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ setFirstLineIndent(StyleConstants.getFirstLineIndent(atts));
+ setLineSpacing(StyleConstants.getLineSpacing(atts));
+ setJustification(StyleConstants.getAlignment(atts));
+ tabSet = StyleConstants.getTabSet(atts);
+ }
+
+ /**
+ * Sets the indentation of the first line of the paragraph.
+ *
+ * @param i the indentation to set
+ */
+ protected void setFirstLineIndent(float i)
+ {
+ firstLineIndent = (int) i;
+ }
+
+ /**
+ * Sets the justification of the paragraph.
+ *
+ * @param j the justification to set
+ */
+ protected void setJustification(int j)
+ {
+ justification = j;
+ }
+
+ /**
+ * Sets the line spacing for this paragraph.
+ *
+ * @param s the line spacing to set
+ */
+ protected void setLineSpacing(float s)
+ {
+ lineSpacing = s;
+ }
+
+ /**
+ * Returns the i-th view from the logical views, before breaking into rows.
+ *
+ * @param i the index of the logical view to return
+ *
+ * @return the i-th view from the logical views, before breaking into rows
+ */
+ protected View getLayoutView(int i)
+ {
+ return layoutPool.getView(i);
+ }
+
+ /**
+ * Returns the number of logical child views.
+ *
+ * @return the number of logical child views
+ */
+ protected int getLayoutViewCount()
+ {
+ return layoutPool.getViewCount();
+ }
+
+ /**
+ * Returns the TabSet used by this ParagraphView.
+ *
+ * @return the TabSet used by this ParagraphView
+ */
+ protected TabSet getTabSet()
+ {
+ return tabSet;
+ }
+
+ /**
+ * Finds the next offset in the document that has one of the characters
+ * specified in <code>string</code>. If there is no such character found,
+ * this returns -1.
+ *
+ * @param string the characters to search for
+ * @param start the start offset
+ *
+ * @return the next offset in the document that has one of the characters
+ * specified in <code>string</code>
+ */
+ protected int findOffsetToCharactersInString(char[] string, int start)
+ {
+ int offset = -1;
+ Document doc = getDocument();
+ Segment text = new Segment();
+ try
+ {
+ doc.getText(start, doc.getLength() - start, text);
+ int index = start;
+
+ searchLoop:
+ while (true)
+ {
+ char ch = text.next();
+ if (ch == Segment.DONE)
+ break;
+
+ for (int j = 0; j < string.length; ++j)
+ {
+ if (string[j] == ch)
+ {
+ offset = index;
+ break searchLoop;
+ }
+ }
+ index++;
+ }
+ }
+ catch (BadLocationException ex)
+ {
+ // Ignore this and return -1.
+ }
+ return offset;
+ }
+
+ protected int getClosestPositionTo(int pos, Position.Bias bias, Shape a,
+ int direction, Position.Bias[] biasRet,
+ int rowIndex, int x)
+ throws BadLocationException
+ {
+ // FIXME: Implement this properly. However, this looks like it might
+ // have been replaced by viewToModel.
+ return pos;
+ }
+
+ /**
+ * Returns the size that is used by this view (or it's child views) between
+ * <code>startOffset</code> and <code>endOffset</code>. If the child views
+ * implement the {@link TabableView} interface, then this is used to
+ * determine the span, otherwise we use the preferred span of the child
+ * views.
+ *
+ * @param startOffset the start offset
+ * @param endOffset the end offset
+ *
+ * @return the span used by the view between <code>startOffset</code> and
+ * <code>endOffset</cod>
+ */
+ protected float getPartialSize(int startOffset, int endOffset)
+ {
+ int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
+ int endIndex = getViewIndex(endOffset, Position.Bias.Forward);
+ float span;
+ if (startIndex == endIndex)
+ {
+ View child = getView(startIndex);
+ if (child instanceof TabableView)
+ {
+ TabableView tabable = (TabableView) child;
+ span = tabable.getPartialSpan(startOffset, endOffset);
+ }
+ else
+ span = child.getPreferredSpan(X_AXIS);
+ }
+ else if (endIndex - startIndex == 1)
+ {
+ View child1 = getView(startIndex);
+ if (child1 instanceof TabableView)
+ {
+ TabableView tabable = (TabableView) child1;
+ span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
+ }
+ else
+ span = child1.getPreferredSpan(X_AXIS);
+ View child2 = getView(endIndex);
+ if (child2 instanceof TabableView)
+ {
+ TabableView tabable = (TabableView) child2;
+ span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
+ }
+ else
+ span += child2.getPreferredSpan(X_AXIS);
+ }
+ else
+ {
+ // Start with the first view.
+ View child1 = getView(startIndex);
+ if (child1 instanceof TabableView)
+ {
+ TabableView tabable = (TabableView) child1;
+ span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
+ }
+ else
+ span = child1.getPreferredSpan(X_AXIS);
+
+ // Add up the view spans between the start and the end view.
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ View child = getView(i);
+ span += child.getPreferredSpan(X_AXIS);
+ }
+
+ // Add the span of the last view.
+ View child2 = getView(endIndex);
+ if (child2 instanceof TabableView)
+ {
+ TabableView tabable = (TabableView) child2;
+ span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
+ }
+ else
+ span += child2.getPreferredSpan(X_AXIS);
+ }
+ return span;
+ }
+
+ /**
+ * Returns the location where the tabs are calculated from. This returns
+ * <code>0.0F</code> by default.
+ *
+ * @return the location where the tabs are calculated from
+ */
+ protected float getTabBase()
+ {
+ return 0.0F;
+ }
+
+ /**
+ * @specnote This method is specified to take a Row parameter, which is a
+ * private inner class of that class, which makes it unusable from
+ * application code. Also, this method seems to be replaced by
+ * {@link FlowStrategy#adjustRow(FlowView, int, int, int)}.
+ *
+ */
+ protected void adjustRow(Row r, int desiredSpan, int x)
+ {
+ }
+
+ /**
+ * @specnote This method's signature differs from the one defined in
+ * {@link View} and is therefore never called. It is probably there
+ * for historical reasons.
+ */
+ public View breakView(int axis, float len, Shape a)
+ {
+ // This method is not used.
+ return null;
+ }
+
+ /**
+ * @specnote This method's signature differs from the one defined in
+ * {@link View} and is therefore never called. It is probably there
+ * for historical reasons.
+ */
+ public int getBreakWeight(int axis, float len)
+ {
+ // This method is not used.
+ return 0;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/PasswordView.java b/libjava/classpath/javax/swing/text/PasswordView.java
new file mode 100644
index 000000000..62b141932
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/PasswordView.java
@@ -0,0 +1,254 @@
+/* PasswordView.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+ This file is part of GNU Classpath.
+
+ GNU Classpath is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU Classpath is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GNU Classpath; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library. Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module. An independent module is a module which is not derived from
+ or based on this library. If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so. If you do not wish to do so, delete this
+ exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.JPasswordField;
+
+public class PasswordView
+ extends FieldView
+{
+ /**
+ * Buffer for putting the echo char into it and
+ * then using it to draw it into the view.
+ */
+ private char[] oneCharBuffer = new char[1];
+
+ public PasswordView(Element elem)
+ {
+ super(elem);
+ }
+
+ /**
+ * Draws one echo character at a given position.
+ *
+ * @param g the <code>Graphics</code> object to draw to
+ * @param x the x-position
+ * @param y the y-position
+ * @param ch the echo character
+ *
+ * @return the next x position right of the drawn character
+ */
+ protected int drawEchoCharacter(Graphics g, int x, int y, char ch)
+ {
+ // Update font metrics.
+ updateMetrics();
+
+ // Draw character.
+ oneCharBuffer[0] = ch;
+ g.drawChars(oneCharBuffer, 0, 1, x, y);
+
+ // Return new x position right of drawn character.
+ return x + metrics.charWidth(ch);
+ }
+
+ private char getEchoChar()
+ {
+ char ch = ((JPasswordField) getContainer()).getEchoChar();
+
+ if (ch == 0)
+ ch = '*';
+
+ return ch;
+ }
+
+ /**
+ * Draws selected text at a given position.
+ *
+ * @param g the <code>Graphics</code> object to draw to
+ * @param x the x-position
+ * @param y the y-position
+ * @param p0 the position of the first character to draw
+ * @param p1 the position of the first character not to draw
+ *
+ * @return the next x position right of the drawn character
+ */
+ protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ // Update font metrics.
+ updateMetrics();
+
+ // Get echo character.
+ char ch = getEchoChar();
+
+ // Set color for selected text.
+ g.setColor(selectedColor);
+ g.setColor(Color.BLACK);
+
+ // Draw echo character using drawEchoCharacter() method.
+ for (int index = p0; index < p1; ++index)
+ x = drawEchoCharacter(g, x, y, ch);
+ return x;
+ }
+
+ /**
+ * Draws unselected text at a given position.
+ *
+ * @param g the <code>Graphics</code> object to draw to
+ * @param x the x-position of the start of the baseline
+ * @param y the y-position of the start of the baseline
+ * @param p0 the position of the first character to draw
+ * @param p1 the position of the first character not to draw
+ *
+ * @return the next x position right of the drawn character
+ */
+ protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ // Update font metrics.
+ updateMetrics();
+
+ // Get echo character.
+ char ch = getEchoChar();
+
+ // Set color for unselected text.
+ g.setColor(unselectedColor);
+ g.setColor(Color.BLACK);
+
+ // Draw echo character using drawEchoCharacter() method.
+ for (int index = p0; index < p1; ++index)
+ x = drawEchoCharacter(g, x, y, ch);
+ return x;
+ }
+
+ /**
+ * Determines the preferred span for this view along an axis.
+ *
+ * @param axis to get the preferred span of
+ * @return the preferred span of the axis
+ */
+ public float getPreferredSpan(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException();
+
+ FontMetrics fm = getFontMetrics();
+
+ if (axis == Y_AXIS)
+ return fm.getHeight();
+
+ String text;
+ Element elem = getElement();
+
+ try
+ {
+ text = elem.getDocument().getText(elem.getStartOffset(),
+ elem.getEndOffset());
+ }
+ catch (BadLocationException e)
+ {
+ // This should never happen.
+ text = "";
+ }
+ return fm.stringWidth(text);
+ }
+
+ /**
+ * 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.
+ * @return the bounding box of the given position
+ * @throws BadLocationException if the given position does not
+ * represent a valid location in the associated document
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException
+ {
+ 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;
+ }
+
+ /**
+ * Provides a mapping from the view coordinate space to the logical
+ * coordinate space of the model.
+ *
+ * @param fx - the X coordinate >= 0.0f
+ * @param fy - the Y coordinate >= 0.0f
+ * @param a - the allocated region to render into
+ * @param bias - typesafe enumeration to indicate bias to a position in the model.
+ * @return the location within the model that best represents
+ * the given point in the view
+ *
+ */
+ 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
new file mode 100644
index 000000000..070c760c0
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/PlainDocument.java
@@ -0,0 +1,292 @@
+/* PlainDocument.java --
+ Copyright (C) 2002, 2004, 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.util.ArrayList;
+
+/**
+ * A simple document class which maps lines to {@link Element}s.
+ *
+ * @author Anthony Balkissoon (abalkiss@redhat.com)
+ * @author Graydon Hoare (graydon@redhat.com)
+ * @author Roman Kennke (roman@kennke.org)
+ * @author Michael Koch (konqueror@gmx.de)
+ * @author Robert Schuster (robertschuster@fsfe.org)
+ */
+public class PlainDocument extends AbstractDocument
+{
+ private static final long serialVersionUID = 4758290289196893664L;
+
+ public static final String lineLimitAttribute = "lineLimit";
+ public static final String tabSizeAttribute = "tabSize";
+
+ /**
+ * The default root element of this document. This is made type Element
+ * because the RI seems to accept other types of elements as well from
+ * createDefaultRoot() (when overridden by a subclass).
+ */
+ private Element rootElement;
+
+ public PlainDocument()
+ {
+ this(new GapContent());
+ }
+
+ public PlainDocument(AbstractDocument.Content content)
+ {
+ super(content);
+ rootElement = createDefaultRoot();
+
+ // This property has been determined using a Mauve test.
+ putProperty("tabSize", new Integer(8));
+ }
+
+ private void reindex()
+ {
+ Element[] lines;
+ try
+ {
+ String str = content.getString(0, content.length());
+
+ ArrayList elts = new ArrayList();
+ int j = 0;
+ for (int i = str.indexOf('\n', 0); i != -1; i = str.indexOf('\n', i + 1))
+ {
+ elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, j, i + 1));
+ j = i + 1;
+ }
+
+ if (j < content.length())
+ elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, j, content.length()));
+
+ lines = new Element[elts.size()];
+ for (int i = 0; i < elts.size(); ++i)
+ lines[i] = (Element) elts.get(i);
+ }
+ catch (BadLocationException e)
+ {
+ lines = new Element[1];
+ lines[0] = createLeafElement(rootElement, SimpleAttributeSet.EMPTY, 0, 1);
+ }
+
+ ((BranchElement) rootElement).replace(0, rootElement.getElementCount(), lines);
+ }
+
+ protected AbstractDocument.AbstractElement createDefaultRoot()
+ {
+ BranchElement root =
+ (BranchElement) createBranchElement(null, null);
+
+ Element[] array = new Element[1];
+ array[0] = createLeafElement(root, null, 0, 1);
+ root.replace(0, 0, array);
+
+ return root;
+ }
+
+ protected void insertUpdate(DefaultDocumentEvent event,
+ AttributeSet attributes)
+ {
+
+ String text = null;
+ int offset = event.getOffset();
+ int length = event.getLength();
+ try
+ {
+ text = getText(offset, length);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError();
+ err.initCause(ex);
+ throw err;
+ }
+
+ boolean hasLineBreak = text.indexOf('\n') != -1;
+ boolean prevCharIsLineBreak = false;
+ try
+ {
+ prevCharIsLineBreak =
+ offset > 0 && getText(offset - 1, 1).charAt(0) == '\n';
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError();
+ err.initCause(ex);
+ throw err;
+ }
+ boolean lastCharIsLineBreak = text.charAt(text.length() - 1) == '\n';
+ int lineIndex = -1;
+ int lineStart = -1;
+ int lineEnd = -1;
+ Element[] removed = null;
+ BranchElement root = (BranchElement) rootElement;
+ boolean updateStructure = true;
+
+ if (prevCharIsLineBreak && ! lastCharIsLineBreak)
+ {
+ // We must fix the structure a little if the previous char
+ // is a linebreak and the last char isn't.
+ lineIndex = root.getElementIndex(offset - 1);
+ Element prevLine = root.getElement(lineIndex);
+ Element nextLine = root.getElement(lineIndex + 1);
+ lineStart = prevLine.getStartOffset();
+ lineEnd = nextLine.getEndOffset();
+ removed = new Element[]{ prevLine, nextLine };
+ }
+ else if (hasLineBreak)
+ {
+ lineIndex = root.getElementIndex(offset);
+ Element line = root.getElement(lineIndex);
+ lineStart = line.getStartOffset();
+ lineEnd = line.getEndOffset();
+ removed = new Element[]{ line };
+ }
+ else
+ {
+ updateStructure = false;
+ }
+
+ if (updateStructure)
+ {
+ // Break the lines between lineStart and lineEnd.
+ ArrayList lines = new ArrayList();
+ int len = lineEnd - lineStart;
+ try
+ {
+ text = getText(lineStart, len);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError();
+ err.initCause(ex);
+ throw err;
+ }
+ int prevLineBreak = 0;
+ int lineBreak = text.indexOf('\n');
+ do
+ {
+ lineBreak++;
+ lines.add(createLeafElement(root, null, lineStart + prevLineBreak,
+ lineStart + lineBreak));
+ prevLineBreak = lineBreak;
+ lineBreak = text.indexOf('\n', prevLineBreak);
+ } while (prevLineBreak < len);
+
+ // Update the element structure and prepare document event.
+ Element[] added = (Element[]) lines.toArray(new Element[lines.size()]);
+ event.addEdit(new ElementEdit(root, lineIndex, removed, added));
+ root.replace(lineIndex, removed.length, added);
+ }
+ super.insertUpdate(event, attributes);
+ }
+
+ protected void removeUpdate(DefaultDocumentEvent event)
+ {
+ super.removeUpdate(event);
+
+ // added and removed are Element arrays used to add an ElementEdit
+ // to the DocumentEvent if there were entire lines added or removed
+ // from the Document
+ Element[] added = new Element[1];
+ Element[] removed;
+ int p0 = event.getOffset();
+
+ // check if we must collapse some elements
+ int i1 = rootElement.getElementIndex(p0);
+ int i2 = rootElement.getElementIndex(p0 + event.getLength());
+ if (i1 != i2)
+ {
+ // If there were lines removed then we have to add an ElementEdit
+ // to the DocumentEvent so we set it up now by filling the Element
+ // arrays "removed" and "added" appropriately
+ removed = new Element [i2 - i1 + 1];
+ for (int i = i1; i <= i2; i++)
+ removed[i-i1] = rootElement.getElement(i);
+
+ int start = rootElement.getElement(i1).getStartOffset();
+ int end = rootElement.getElement(i2).getEndOffset();
+ added[0] = createLeafElement(rootElement,
+ SimpleAttributeSet.EMPTY,
+ start, end);
+
+ // Now create and add the ElementEdit
+ ElementEdit e = new ElementEdit(rootElement, i1, removed, added);
+ event.addEdit(e);
+
+ // collapse elements if the removal spans more than 1 line
+ ((BranchElement) rootElement).replace(i1, i2 - i1 + 1, added);
+ }
+ }
+
+ public Element getDefaultRootElement()
+ {
+ return rootElement;
+ }
+
+ public Element getParagraphElement(int pos)
+ {
+ Element root = getDefaultRootElement();
+ return root.getElement(root.getElementIndex(pos));
+ }
+
+ /**
+ * Inserts a string into the document. If the document property
+ * '<code>filterNewLines</code>' is set to <code>Boolean.TRUE</code>, then
+ * all newlines in the inserted string are replaced by space characters,
+ * otherwise the superclasses behaviour is executed.
+ *
+ * Inserting content causes a write lock to be acquired during this method
+ * call.
+ *
+ * @param offs the offset at which to insert the string
+ * @param str the string to be inserted
+ * @param atts the text attributes of the string to be inserted
+ *
+ * @throws BadLocationException
+ */
+ public void insertString(int offs, String str, AttributeSet atts)
+ throws BadLocationException
+ {
+ String string = str;
+ if (str != null && Boolean.TRUE.equals(getProperty("filterNewlines")))
+ string = str.replaceAll("\n", " ");
+ super.insertString(offs, string, atts);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java
new file mode 100644
index 000000000..16112fdb1
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/PlainView.java
@@ -0,0 +1,724 @@
+/* PlainView.java --
+ Copyright (C) 2004, 2005, 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.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentEvent.ElementChange;
+
+public class PlainView extends View implements TabExpander
+{
+ Color selectedColor;
+ Color unselectedColor;
+
+ /**
+ * The color that is used to draw disabled text fields.
+ */
+ Color disabledColor;
+
+ /**
+ * While painting this is the textcomponent's current start index
+ * of the selection.
+ */
+ int selectionStart;
+
+ /**
+ * While painting this is the textcomponent's current end index
+ * of the selection.
+ */
+ int selectionEnd;
+
+ Font font;
+
+ /** The length of the longest line in the Document **/
+ float maxLineLength = -1;
+
+ /** The longest line in the Document **/
+ Element longestLine = null;
+
+ protected FontMetrics metrics;
+
+ /**
+ * The instance returned by {@link #getLineBuffer()}.
+ */
+ private transient Segment lineBuffer;
+
+ /**
+ * The base offset for tab calculations.
+ */
+ private int tabBase;
+
+ /**
+ * The tab size.
+ */
+ private int tabSize;
+
+ public PlainView(Element elem)
+ {
+ super(elem);
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected void updateMetrics()
+ {
+ Component component = getContainer();
+ Font font = component.getFont();
+
+ if (this.font != font)
+ {
+ this.font = font;
+ metrics = component.getFontMetrics(font);
+ tabSize = getTabSize() * metrics.charWidth('m');
+ }
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected Rectangle lineToRect(Shape a, int line)
+ {
+ // Ensure metrics are up-to-date.
+ updateMetrics();
+
+ Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ int fontHeight = metrics.getHeight();
+ return new Rectangle(rect.x, rect.y + (line * fontHeight),
+ rect.width, fontHeight);
+ }
+
+ public Shape modelToView(int position, Shape a, Position.Bias b)
+ throws BadLocationException
+ {
+ // Ensure metrics are up-to-date.
+ updateMetrics();
+
+ Document document = getDocument();
+
+ // Get rectangle of the line containing position.
+ int lineIndex = getElement().getElementIndex(position);
+ Rectangle rect = lineToRect(a, lineIndex);
+ tabBase = rect.x;
+
+ // Get the rectangle for position.
+ Element line = getElement().getElement(lineIndex);
+ int lineStart = line.getStartOffset();
+ Segment segment = getLineBuffer();
+ document.getText(lineStart, position - lineStart, segment);
+ int xoffset = Utilities.getTabbedTextWidth(segment, metrics, tabBase,
+ this, lineStart);
+
+ // Calc the real rectangle.
+ rect.x += xoffset;
+ rect.width = 1;
+ rect.height = metrics.getHeight();
+
+ return rect;
+ }
+
+ /**
+ * Draws a line of text. The X and Y coordinates specify the start of
+ * the <em>baseline</em> of the line.
+ *
+ * @param lineIndex the index of the line
+ * @param g the graphics to use for drawing the text
+ * @param x the X coordinate of the baseline
+ * @param y the Y coordinate of the baseline
+ */
+ protected void drawLine(int lineIndex, Graphics g, int x, int y)
+ {
+ try
+ {
+ Element line = getElement().getElement(lineIndex);
+ int startOffset = line.getStartOffset();
+ int endOffset = line.getEndOffset() - 1;
+
+ if (selectionStart <= startOffset)
+ // Selection starts before the line ...
+ if (selectionEnd <= startOffset)
+ {
+ // end ends before the line: Draw completely unselected text.
+ drawUnselectedText(g, x, y, startOffset, endOffset);
+ }
+ else if (selectionEnd <= endOffset)
+ {
+ // and ends within the line: First part is selected,
+ // second is not.
+ x = drawSelectedText(g, x, y, startOffset, selectionEnd);
+ drawUnselectedText(g, x, y, selectionEnd, endOffset);
+ }
+ else
+ // and ends behind the line: Draw completely selected text.
+ drawSelectedText(g, x, y, startOffset, endOffset);
+ else if (selectionStart < endOffset)
+ // Selection starts within the line ..
+ if (selectionEnd < endOffset)
+ {
+ // and ends within it: First part unselected, second part
+ // selected, third part unselected.
+ x = drawUnselectedText(g, x, y, startOffset, selectionStart);
+ x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
+ drawUnselectedText(g, x, y, selectionEnd, endOffset);
+ }
+ else
+ {
+ // and ends behind the line: First part unselected, second
+ // part selected.
+ x = drawUnselectedText(g, x, y, startOffset, selectionStart);
+ drawSelectedText(g, x, y, selectionStart, endOffset);
+ }
+ else
+ // Selection is behind this line: Draw completely unselected text.
+ drawUnselectedText(g, x, y, startOffset, endOffset);
+ }
+ catch (BadLocationException e)
+ {
+ AssertionError ae = new AssertionError("Unexpected bad location");
+ ae.initCause(e);
+ throw ae;
+ }
+ }
+
+ protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ g.setColor(selectedColor);
+ Segment segment = getLineBuffer();
+ getDocument().getText(p0, p1 - p0, segment);
+ return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
+ }
+
+ /**
+ * Draws a chunk of unselected text.
+ *
+ * @param g the graphics to use for drawing the text
+ * @param x the X coordinate of the baseline
+ * @param y the Y coordinate of the baseline
+ * @param p0 the start position in the text model
+ * @param p1 the end position in the text model
+ *
+ * @return the X location of the end of the range
+ *
+ * @throws BadLocationException if <code>p0</code> or <code>p1</code> are
+ * invalid
+ */
+ protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ JTextComponent textComponent = (JTextComponent) getContainer();
+ if (textComponent.isEnabled())
+ g.setColor(unselectedColor);
+ else
+ g.setColor(disabledColor);
+
+ Segment segment = getLineBuffer();
+ getDocument().getText(p0, p1 - p0, segment);
+ return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
+ }
+
+ public void paint(Graphics g, Shape s)
+ {
+ // Ensure metrics are up-to-date.
+ updateMetrics();
+
+ JTextComponent textComponent = (JTextComponent) getContainer();
+
+ selectedColor = textComponent.getSelectedTextColor();
+ unselectedColor = textComponent.getForeground();
+ disabledColor = textComponent.getDisabledTextColor();
+ selectionStart = textComponent.getSelectionStart();
+ selectionEnd = textComponent.getSelectionEnd();
+
+ Rectangle rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
+ tabBase = rect.x;
+
+ // FIXME: Text may be scrolled.
+ Document document = textComponent.getDocument();
+ Element root = getElement();
+ int height = metrics.getHeight();
+
+ // For layered highlighters we need to paint the layered highlights
+ // before painting any text.
+ LayeredHighlighter hl = null;
+ Highlighter h = textComponent.getHighlighter();
+ if (h instanceof LayeredHighlighter)
+ hl = (LayeredHighlighter) h;
+
+ int count = root.getElementCount();
+
+ // Determine first and last line inside the clip.
+ Rectangle clip = g.getClipBounds();
+ SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height,
+ clip);
+ int line0 = (clip.y - rect.y) / height;
+ line0 = Math.max(0, Math.min(line0, count - 1));
+ int line1 = (clip.y + clip.height - rect.y) / height;
+ line1 = Math.max(0, Math.min(line1, count - 1));
+ int y = rect.y + metrics.getAscent() + height * line0;
+ for (int i = line0; i <= line1; i++)
+ {
+ if (hl != null)
+ {
+ Element lineEl = root.getElement(i);
+ // Exclude the trailing newline from beeing highlighted.
+ if (i == count)
+ hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
+ lineEl.getEndOffset(), s, textComponent,
+ this);
+ else
+ hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
+ lineEl.getEndOffset() - 1, s,
+ textComponent, this);
+ }
+ drawLine(i, g, rect.x, y);
+ y += height;
+ }
+ }
+
+ /**
+ * Returns the tab size of a tab. Checks the Document's
+ * properties for PlainDocument.tabSizeAttribute and returns it if it is
+ * defined, otherwise returns 8.
+ *
+ * @return the tab size.
+ */
+ protected int getTabSize()
+ {
+ Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
+ if (tabSize == null)
+ return 8;
+ return ((Integer)tabSize).intValue();
+ }
+
+ /**
+ * Returns the next tab stop position after a given reference position.
+ *
+ * This implementation ignores the <code>tabStop</code> argument.
+ *
+ * @param x the current x position in pixels
+ * @param tabStop the position within the text stream that the tab occured at
+ */
+ public float nextTabStop(float x, int tabStop)
+ {
+ float next = x;
+ if (tabSize != 0)
+ {
+ int numTabs = (((int) x) - tabBase) / tabSize;
+ next = tabBase + (numTabs + 1) * tabSize;
+ }
+ return next;
+ }
+
+ /**
+ * Returns the length of the longest line, used for getting the span
+ * @return the length of the longest line
+ */
+ float determineMaxLineLength()
+ {
+ // if the longest line is cached, return the cached value
+ if (maxLineLength != -1)
+ return maxLineLength;
+
+ // otherwise we have to go through all the lines and find it
+ Element el = getElement();
+ Segment seg = getLineBuffer();
+ float span = 0;
+ for (int i = 0; i < el.getElementCount(); i++)
+ {
+ Element child = el.getElement(i);
+ int start = child.getStartOffset();
+ int end = child.getEndOffset() - 1;
+ try
+ {
+ el.getDocument().getText(start, end - start, seg);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError ae = new AssertionError("Unexpected bad location");
+ ae.initCause(ex);
+ throw ae;
+ }
+
+ if (seg == null || seg.array == null || seg.count == 0)
+ continue;
+
+ int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
+ if (width > span)
+ {
+ longestLine = child;
+ span = width;
+ }
+ }
+ maxLineLength = span;
+ return maxLineLength;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException();
+
+ // make sure we have the metrics
+ updateMetrics();
+
+ Element el = getElement();
+ float span;
+
+ switch (axis)
+ {
+ case X_AXIS:
+ span = determineMaxLineLength();
+ break;
+ case Y_AXIS:
+ default:
+ span = metrics.getHeight() * el.getElementCount();
+ break;
+ }
+
+ return span;
+ }
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model.
+ *
+ * @param x the x coordinate in the view space
+ * @param y the y coordinate in the view space
+ * @param a the allocation of this <code>View</code>
+ * @param b the bias to use
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code>
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
+ {
+ Rectangle rec = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ tabBase = rec.x;
+
+ int pos;
+ if ((int) y < rec.y)
+ // Above our area vertically. Return start offset.
+ pos = getStartOffset();
+ else if ((int) y > rec.y + rec.height)
+ // Below our area vertically. Return end offset.
+ pos = getEndOffset() - 1;
+ else
+ {
+ // Inside the allocation vertically. Determine line and X offset.
+ Document doc = getDocument();
+ Element root = doc.getDefaultRootElement();
+ int line = Math.abs(((int) y - rec.y) / metrics.getHeight());
+ if (line >= root.getElementCount())
+ pos = getEndOffset() - 1;
+ else
+ {
+ Element lineEl = root.getElement(line);
+ if (x < rec.x)
+ // To the left of the allocation area.
+ pos = lineEl.getStartOffset();
+ else if (x > rec.x + rec.width)
+ // To the right of the allocation area.
+ pos = lineEl.getEndOffset() - 1;
+ else
+ {
+ try
+ {
+ int p0 = lineEl.getStartOffset();
+ int p1 = lineEl.getEndOffset();
+ Segment s = new Segment();
+ doc.getText(p0, p1 - p0, s);
+ tabBase = rec.x;
+ pos = p0 + Utilities.getTabbedTextOffset(s, metrics,
+ tabBase, (int) x,
+ this, p0);
+ }
+ catch (BadLocationException ex)
+ {
+ // Should not happen.
+ pos = -1;
+ }
+ }
+
+ }
+ }
+ // Bias is always forward.
+ b[0] = Position.Bias.Forward;
+ return pos;
+ }
+
+ /**
+ * Since insertUpdate and removeUpdate each deal with children
+ * Elements being both added and removed, they both have to perform
+ * the same checks. So they both simply call this method.
+ * @param changes the DocumentEvent for the changes to the Document.
+ * @param a the allocation of the View.
+ * @param f the ViewFactory to use for rebuilding.
+ */
+ protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ // This happens during initialization.
+ if (metrics == null)
+ {
+ updateMetrics();
+ preferenceChanged(null, true, true);
+ return;
+ }
+
+ Element element = getElement();
+
+ // Find longest line if it hasn't been initialized yet.
+ if (longestLine == null)
+ findLongestLine(0, element.getElementCount() - 1);
+
+ ElementChange change = changes.getChange(element);
+ if (changes.getType() == DocumentEvent.EventType.INSERT)
+ {
+ // Handles character/line insertion.
+
+ // Determine if lines have been added. In this case we repaint
+ // differently.
+ boolean linesAdded = true;
+ if (change == null)
+ linesAdded = false;
+
+ // Determine the start line.
+ int start;
+ if (linesAdded)
+ start = change.getIndex();
+ else
+ start = element.getElementIndex(changes.getOffset());
+
+ // Determine the length of the updated region.
+ int length = 0;
+ if (linesAdded)
+ length = change.getChildrenAdded().length - 1;
+
+ // Update the longest line and length.
+ int oldMaxLength = (int) maxLineLength;
+ if (longestLine.getEndOffset() < changes.getOffset()
+ || longestLine.getStartOffset() > changes.getOffset()
+ + changes.getLength())
+ {
+ findLongestLine(start, start + length);
+ }
+ else
+ {
+ findLongestLine(0, element.getElementCount() - 1);
+ }
+
+ // Trigger a preference change so that the layout gets updated
+ // correctly.
+ preferenceChanged(null, maxLineLength != oldMaxLength, linesAdded);
+
+ // Damage the updated line range.
+ int endLine = start;
+ if (linesAdded)
+ endLine = element.getElementCount() - 1;
+ damageLineRange(start, endLine, a, getContainer());
+
+ }
+ else
+ {
+ // Handles character/lines removals.
+
+ // Update the longest line and length and trigger preference changed.
+ int oldMaxLength = (int) maxLineLength;
+ if (change != null)
+ {
+ // Line(s) have been removed.
+ findLongestLine(0, element.getElementCount() - 1);
+ preferenceChanged(null, maxLineLength != oldMaxLength, true);
+ }
+ else
+ {
+ // No line has been removed.
+ int lineNo = getElement().getElementIndex(changes.getOffset());
+ Element line = getElement().getElement(lineNo);
+ if (longestLine == line)
+ {
+ findLongestLine(0, element.getElementCount() - 1);
+ preferenceChanged(null, maxLineLength != oldMaxLength, false);
+ }
+ damageLineRange(lineNo, lineNo, a, getContainer());
+ }
+ }
+ }
+
+ /**
+ * This method is called when something is inserted into the Document
+ * that this View is displaying.
+ *
+ * @param changes the DocumentEvent for the changes.
+ * @param a the allocation of the View
+ * @param f the ViewFactory used to rebuild
+ */
+ public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ updateDamage(changes, a, f);
+ }
+
+ /**
+ * This method is called when something is removed from the Document
+ * that this View is displaying.
+ *
+ * @param changes the DocumentEvent for the changes.
+ * @param a the allocation of the View
+ * @param f the ViewFactory used to rebuild
+ */
+ public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ updateDamage(changes, a, f);
+ }
+
+ /**
+ * This method is called when attributes were changed in the
+ * Document in a location that this view is responsible for.
+ */
+ public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ updateDamage(changes, a, f);
+ }
+
+ /**
+ * Repaint the given line range. This is called from insertUpdate,
+ * changedUpdate, and removeUpdate when no new lines were added
+ * and no lines were removed, to repaint the line that was
+ * modified.
+ *
+ * @param line0 the start of the range
+ * @param line1 the end of the range
+ * @param a the rendering region of the host
+ * @param host the Component that uses this View (used to call repaint
+ * on that Component)
+ *
+ * @since 1.4
+ */
+ protected void damageLineRange (int line0, int line1, Shape a, Component host)
+ {
+ if (a == null)
+ return;
+
+ Rectangle rec0 = lineToRect(a, line0);
+ Rectangle rec1 = lineToRect(a, line1);
+
+ if (rec0 == null || rec1 == null)
+ // something went wrong, repaint the entire host to be safe
+ host.repaint();
+ else
+ {
+ Rectangle repaintRec = SwingUtilities.computeUnion(rec0.x, rec0.y,
+ rec0.width,
+ rec0.height, rec1);
+ host.repaint(repaintRec.x, repaintRec.y, repaintRec.width,
+ repaintRec.height);
+ }
+ }
+
+ /**
+ * Provides a {@link Segment} object, that can be used to fetch text from
+ * the document.
+ *
+ * @returna {@link Segment} object, that can be used to fetch text from
+ * the document
+ */
+ protected final Segment getLineBuffer()
+ {
+ if (lineBuffer == null)
+ lineBuffer = new Segment();
+ return lineBuffer;
+ }
+
+ /**
+ * Finds and updates the longest line in the view inside an interval of
+ * lines.
+ *
+ * @param start the start of the search interval
+ * @param end the end of the search interval
+ */
+ private void findLongestLine(int start, int end)
+ {
+ for (int i = start; i <= end; i++)
+ {
+ int w = getLineLength(i);
+ if (w > maxLineLength)
+ {
+ maxLineLength = w;
+ longestLine = getElement().getElement(i);
+ }
+ }
+ }
+
+ /**
+ * Determines the length of the specified line.
+ *
+ * @param line the number of the line
+ *
+ * @return the length of the line in pixels
+ */
+ private int getLineLength(int line)
+ {
+ Element lineEl = getElement().getElement(line);
+ Segment buffer = getLineBuffer();
+ try
+ {
+ Document doc = getDocument();
+ doc.getText(lineEl.getStartOffset(),
+ lineEl.getEndOffset() - lineEl.getStartOffset() - 1,
+ buffer);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError("Unexpected bad location");
+ err.initCause(ex);
+ throw err;
+ }
+
+ return Utilities.getTabbedTextWidth(buffer, metrics, tabBase, this,
+ lineEl.getStartOffset());
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/Position.java b/libjava/classpath/javax/swing/text/Position.java
new file mode 100644
index 000000000..56c8b6e68
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Position.java
@@ -0,0 +1,62 @@
+/* Position.java --
+ Copyright (C) 2002, 2004 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;
+
+
+public interface Position
+{
+ static final class Bias
+ {
+ public static final Bias Backward = new Bias("Backward");
+ public static final Bias Forward = new Bias("Forward");
+
+ private String name;
+
+ private Bias(String n)
+ {
+ name = n;
+ }
+
+ public String toString()
+ {
+ return name;
+ }
+ }
+
+ int getOffset();
+}
diff --git a/libjava/classpath/javax/swing/text/Segment.java b/libjava/classpath/javax/swing/text/Segment.java
new file mode 100644
index 000000000..7486ab338
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Segment.java
@@ -0,0 +1,297 @@
+/* Segment.java --
+ Copyright (C) 2002, 2004, 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.text.CharacterIterator;
+
+/**
+ * A text fragment represented by a sequence of characters stored in an array.
+ */
+public class Segment implements Cloneable, CharacterIterator
+{
+ private boolean partialReturn;
+
+ /** The current index. */
+ private int current;
+
+ /** Storage for the characters (may contain additional characters). */
+ public char[] array;
+
+ /** The number of characters in the segment. */
+ public int count;
+
+ /** The offset of the first character in the segment. */
+ public int offset;
+
+ /**
+ * Creates a new <code>Segment</code>.
+ */
+ public Segment()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Creates a new <code>Segment</code>.
+ *
+ * @param array the underlying character data.
+ * @param offset the offset of the first character in the segment.
+ * @param count the number of characters in the segment.
+ */
+ public Segment(char[] array, int offset, int count)
+ {
+ this.array = array;
+ this.offset = offset;
+ this.count = count;
+ }
+
+ /**
+ * Clones the segment (note that the underlying character array is not cloned,
+ * just the reference to it).
+ *
+ * @return A clone of the segment.
+ */
+ public Object clone()
+ {
+ try
+ {
+ return super.clone();
+ }
+ catch (CloneNotSupportedException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the character at the current index. If the segment consists of
+ * zero characters, or the current index has passed the end of the
+ * characters in the segment, this method returns {@link #DONE}.
+ *
+ * @return The character at the current index.
+ */
+ public char current()
+ {
+ if (count == 0
+ || current >= getEndIndex())
+ return DONE;
+
+ return array[current];
+ }
+
+ /**
+ * Sets the current index to the first character in the segment and returns
+ * that character. If the segment contains zero characters, this method
+ * returns {@link #DONE}.
+ *
+ * @return The first character in the segment, or {@link #DONE} if the
+ * segment contains zero characters.
+ */
+ public char first()
+ {
+ if (count == 0)
+ return DONE;
+
+ current = getBeginIndex();
+ return array[current];
+ }
+
+ /**
+ * Returns the index of the first character in the segment.
+ *
+ * @return The index of the first character.
+ */
+ public int getBeginIndex()
+ {
+ return offset;
+ }
+
+ /**
+ * Returns the end index for the segment (one position beyond the last
+ * character in the segment - note that this can be outside the range of the
+ * underlying character array).
+ *
+ * @return The end index for the segment.
+ */
+ public int getEndIndex()
+ {
+ return offset + count;
+ }
+
+ /**
+ * Returns the index of the current character in the segment.
+ *
+ * @return The index of the current character.
+ */
+ public int getIndex()
+ {
+ return current;
+ }
+
+ /**
+ * Sets the current index to point to the last character in the segment and
+ * returns that character. If the segment contains zero characters, the
+ * current index is set to {@link #getEndIndex()} and this method returns
+ * {@link #DONE}.
+ *
+ * @return The last character in the segment, or {@link #DONE} if the
+ * segment contains zero characters.
+ */
+ public char last()
+ {
+ if (count == 0)
+ {
+ current = getEndIndex();
+ return DONE;
+ }
+
+ current = getEndIndex() - 1;
+ return array[current];
+ }
+
+ /**
+ * Sets the current index to point to the next character in the segment and
+ * returns that character. If the next character position is past the end of
+ * the segment, the index is set to {@link #getEndIndex()} and the method
+ * returns {@link #DONE}. If the segment contains zero characters, this
+ * method returns {@link #DONE}.
+ *
+ * @return The next character in the segment or {@link #DONE} (if the next
+ * character position is past the end of the segment or if the
+ * segment contains zero characters).
+ */
+ public char next()
+ {
+ if (count == 0)
+ return DONE;
+
+ if ((current + 1) >= getEndIndex())
+ {
+ current = getEndIndex();
+ return DONE;
+ }
+
+ current++;
+ return array[current];
+ }
+
+ /**
+ * Sets the current index to point to the previous character in the segment
+ * and returns that character. If the current index is equal to
+ * {@link #getBeginIndex()}, or if the segment contains zero characters, this
+ * method returns {@link #DONE}.
+ *
+ * @return The previous character in the segment or {@link #DONE} (if the
+ * current character position is at the beginning of the segment or
+ * if the segment contains zero characters).
+ */
+ public char previous()
+ {
+ if (count == 0
+ || current == getBeginIndex())
+ return DONE;
+
+ current--;
+ return array[current];
+ }
+
+ /**
+ * Sets the current index and returns the character at that position (or
+ * {@link #DONE} if the index is equal to {@link #getEndIndex()}.
+ *
+ * @param position the current position.
+ *
+ * @return The character at the specified <code>position</code>, or
+ * {@link #DONE} if <code>position</code> is equal to
+ * {@link #getEndIndex()}.
+ *
+ * @throws IllegalArgumentException if <code>position</code> is not in the
+ * range {@link #getBeginIndex()} to {@link #getEndIndex()}.
+ */
+ public char setIndex(int position)
+ {
+ if (position < getBeginIndex()
+ || position > getEndIndex())
+ throw new IllegalArgumentException("position: " + position
+ + ", beginIndex: " + getBeginIndex()
+ + ", endIndex: " + getEndIndex()
+ + ", text: " + toString());
+
+ current = position;
+
+ if (position == getEndIndex())
+ return DONE;
+
+ return array[current];
+ }
+
+ /**
+ * Returns a <code>String</code> containing the same characters as this
+ * <code>Segment</code>.
+ *
+ * @return A <code>String</code> containing the same characters as this
+ * <code>Segment</code>.
+ */
+ public String toString()
+ {
+ return (array != null) ? new String(array, offset, count) : "";
+ }
+
+ /**
+ * Sets the partial return flag.
+ *
+ * @param p the new value of the flag.
+ *
+ * @since 1.4
+ */
+ public void setPartialReturn(boolean p)
+ {
+ partialReturn = p;
+ }
+
+ /**
+ * Returns the partial return flag.
+ *
+ * @return The partial return flag.
+ * @since 1.4
+ */
+ public boolean isPartialReturn()
+ {
+ return partialReturn;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/SimpleAttributeSet.java b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java
new file mode 100644
index 000000000..02299019b
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/SimpleAttributeSet.java
@@ -0,0 +1,415 @@
+/* SimpleAttributeSet.java --
+ Copyright (C) 2004, 2005, 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.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * A set of attributes.
+ */
+public class SimpleAttributeSet
+ implements MutableAttributeSet, Serializable, Cloneable
+{
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 8267656273837665219L;
+
+ /**
+ * An empty attribute set.
+ */
+ public static final AttributeSet EMPTY = new EmptyAttributeSet();
+
+ /** Storage for the attributes. */
+ Hashtable tab;
+
+ /**
+ * Creates a new attribute set that is initially empty.
+ */
+ public SimpleAttributeSet()
+ {
+ tab = new Hashtable();
+ }
+
+ /**
+ * Creates a new <code>SimpleAttributeSet</code> with the same attributes
+ * and resolve parent as the specified set.
+ *
+ * @param a the attributes (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ */
+ public SimpleAttributeSet(AttributeSet a)
+ {
+ tab = new Hashtable();
+ addAttributes(a);
+ }
+
+ /**
+ * Adds an attribute with the given <code>name</code> and <code>value</code>
+ * to the set. If the set already contains an attribute with the given
+ * <code>name</code>, the attribute value is updated.
+ *
+ * @param name the attribute name (<code>null</code> not permitted).
+ * @param value the value (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ */
+ public void addAttribute(Object name, Object value)
+ {
+ tab.put(name, value);
+ }
+
+ /**
+ * Adds all the attributes from <code>attributes</code> to this set.
+ *
+ * @param attributes the set of attributes to add (<code>null</code> not
+ * permitted).
+ *
+ * @throws NullPointerException if <code>attributes</code> is
+ * <code>null</code>.
+ */
+ public void addAttributes(AttributeSet attributes)
+ {
+ Enumeration e = attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ Object name = e.nextElement();
+ Object val = attributes.getAttribute(name);
+ tab.put(name, val);
+ }
+ }
+
+ /**
+ * Returns a clone of the attribute set.
+ *
+ * @return A clone of the attribute set.
+ */
+ public Object clone()
+ {
+ SimpleAttributeSet attr = null;
+ try
+ {
+ attr = (SimpleAttributeSet) super.clone();
+ attr.tab = (Hashtable) tab.clone();
+ }
+ catch (CloneNotSupportedException ex)
+ {
+ assert false;
+ }
+ return attr;
+ }
+
+ /**
+ * Returns true if the given name and value represent an attribute
+ * found either in this AttributeSet or in its resolve parent hierarchy.
+ * @param name the key for the attribute
+ * @param value the value for the attribute
+ * @return true if the attribute is found here or in this set's resolve
+ * parent hierarchy
+ */
+ public boolean containsAttribute(Object name, Object value)
+ {
+ if (value == null)
+ throw new NullPointerException("Null 'value' argument.");
+ if (tab.containsKey(name))
+ return tab.get(name).equals(value);
+ else
+ {
+ AttributeSet p = getResolveParent();
+ if (p != null)
+ return p.containsAttribute(name, value);
+ else
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the given name and value are found in this AttributeSet.
+ * Does not check the resolve parent.
+ * @param name the key for the attribute
+ * @param value the value for the attribute
+ * @return true if the attribute is found in this AttributeSet
+ */
+ boolean containsAttributeLocally(Object name, Object value)
+ {
+ return tab.containsKey(name)
+ && tab.get(name).equals(value);
+ }
+
+ /**
+ * Returns <code>true</code> of this <code>AttributeSet</code> contains all
+ * of the specified <code>attributes</code>.
+ *
+ * @param attributes the requested attributes
+ *
+ * @return <code>true</code> of this <code>AttributeSet</code> contains all
+ * of the specified <code>attributes</code>
+ */
+ public boolean containsAttributes(AttributeSet attributes)
+ {
+ Enumeration e = attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ Object name = e.nextElement();
+ Object val = attributes.getAttribute(name);
+ if (! containsAttribute(name, val))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Creates and returns a copy of this <code>AttributeSet</code>.
+ *
+ * @return a copy of this <code>AttributeSet</code>
+ */
+ public AttributeSet copyAttributes()
+ {
+ return (AttributeSet) clone();
+ }
+
+ /**
+ * Checks this set for equality with an arbitrary object.
+ *
+ * @param obj the object (<code>null</code> permitted).
+ *
+ * @return <code>true</code> if this set is equal to <code>obj</code>, and
+ * <code>false</code> otherwise.
+ */
+ public boolean equals(Object obj)
+ {
+ return
+ (obj instanceof AttributeSet)
+ && this.isEqual((AttributeSet) obj);
+ }
+
+ /**
+ * Returns the value of the specified attribute, or <code>null</code> if
+ * there is no attribute with that name. If the attribute is not defined
+ * directly in this set, the parent hierarchy (if there is one) will be
+ * used.
+ *
+ * @param name the attribute (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>name</code> is <code>null</code>.
+ */
+ public Object getAttribute(Object name)
+ {
+ Object val = tab.get(name);
+ if (val != null)
+ return val;
+
+ AttributeSet p = getResolveParent();
+ if (p != null)
+ return p.getAttribute(name);
+
+ return null;
+ }
+
+ /**
+ * Returns the number of attributes stored in this set, plus 1 if a parent
+ * has been specified (the reference to the parent is stored as a special
+ * attribute). The attributes stored in the parent do NOT contribute
+ * to the count.
+ *
+ * @return The attribute count.
+ */
+ public int getAttributeCount()
+ {
+ return tab.size();
+ }
+
+ /**
+ * Returns an enumeration of the attribute names.
+ *
+ * @return An enumeration of the attribute names.
+ */
+ public Enumeration<?> getAttributeNames()
+ {
+ return tab.keys();
+ }
+
+ /**
+ * Returns the resolving parent.
+ *
+ * @return The resolving parent (possibly <code>null</code>).
+ *
+ * @see #setResolveParent(AttributeSet)
+ */
+ public AttributeSet getResolveParent()
+ {
+ return (AttributeSet) tab.get(ResolveAttribute);
+ }
+
+ /**
+ * Returns a hash code for this instance.
+ *
+ * @return A hash code.
+ */
+ public int hashCode()
+ {
+ return tab.hashCode();
+ }
+
+ /**
+ * Returns <code>true</code> if the given attribute is defined in this set,
+ * and <code>false</code> otherwise. The parent attribute set is not
+ * checked.
+ *
+ * @param attrName the attribute name (<code>null</code> not permitted).
+ */
+ public boolean isDefined(Object attrName)
+ {
+ return tab.containsKey(attrName);
+ }
+
+ /**
+ * Returns <code>true</code> if the set contains no attributes, and
+ * <code>false</code> otherwise. Note that the resolving parent is
+ * stored as an attribute, so this method will return <code>false</code> if
+ * a resolving parent is set.
+ *
+ * @return <code>true</code> if the set contains no attributes, and
+ * <code>false</code> otherwise.
+ */
+ public boolean isEmpty()
+ {
+ return tab.isEmpty();
+ }
+
+ /**
+ * Returns true if the given set has the same number of attributes
+ * as this set and <code>containsAttributes(attr)</code> returns
+ * <code>true</code>.
+ *
+ * @param attr the attribute set (<code>null</code> not permitted).
+ *
+ * @return A boolean.
+ *
+ * @throws NullPointerException if <code>attr</code> is <code>null</code>.
+ */
+ public boolean isEqual(AttributeSet attr)
+ {
+ return getAttributeCount() == attr.getAttributeCount()
+ && this.containsAttributes(attr);
+ }
+
+ /**
+ * Removes the attribute with the specified <code>name</code>, if this
+ * attribute is defined. This method will only remove an attribute from
+ * this set, not from the resolving parent.
+ *
+ * @param name the attribute name (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>name</code> is <code>null</code>.
+ */
+ public void removeAttribute(Object name)
+ {
+ tab.remove(name);
+ }
+
+ /**
+ * Removes attributes from this set if they are found in the
+ * given set. Only attributes whose key AND value are removed.
+ * Removes attributes only from this set, not from the resolving parent.
+ * Since the resolving parent is stored as an attribute, if
+ * <code>attributes</code> has the same resolving parent as this set, the
+ * parent will be removed from this set.
+ *
+ * @param attributes the attributes (<code>null</code> not permitted).
+ */
+ public void removeAttributes(AttributeSet attributes)
+ {
+ Enumeration e = attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ Object name = e.nextElement();
+ Object val = attributes.getAttribute(name);
+ if (containsAttributeLocally(name, val))
+ removeAttribute(name);
+ }
+ }
+
+ /**
+ * Removes the attributes listed in <code>names</code>.
+ *
+ * @param names the attribute names (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>names</code> is <code>null</code>
+ * or contains any <code>null</code> values.
+ */
+ public void removeAttributes(Enumeration<?> names)
+ {
+ while (names.hasMoreElements())
+ {
+ removeAttribute(names.nextElement());
+ }
+ }
+
+ /**
+ * Sets the reolving parent for this set. When looking up an attribute, if
+ * it is not found in this set, then the resolving parent is also used for
+ * the lookup.
+ * <p>
+ * Note that the parent is stored as an attribute, and will contribute 1 to
+ * the count returned by {@link #getAttributeCount()}.
+ *
+ * @param parent the parent attribute set (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if <code>parent</code> is <code>null</code>.
+ *
+ * @see #setResolveParent(AttributeSet)
+ */
+ public void setResolveParent(AttributeSet parent)
+ {
+ addAttribute(ResolveAttribute, parent);
+ }
+
+ /**
+ * Returns a string representation of this instance, typically used for
+ * debugging purposes.
+ *
+ * @return A string representation of this instance.
+ */
+ public String toString()
+ {
+ return tab.toString();
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/StringContent.java b/libjava/classpath/javax/swing/text/StringContent.java
new file mode 100644
index 000000000..a017de1c9
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/StringContent.java
@@ -0,0 +1,569 @@
+/* StringContent.java --
+ Copyright (C) 2005, 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.io.Serializable;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.swing.undo.UndoableEdit;
+
+/**
+ * An implementation of the <code>AbstractDocument.Content</code>
+ * interface useful for small documents or debugging. The character
+ * content is a simple character array. It's not really efficient.
+ *
+ * <p>Do not use this class for large size.</p>
+ */
+public final class StringContent
+ implements AbstractDocument.Content, Serializable
+{
+ /**
+ * Stores a reference to a mark that can be resetted to the original value
+ * after a mark has been moved. This is used for undoing actions.
+ */
+ private class UndoPosRef
+ {
+ /**
+ * The mark that might need to be reset.
+ */
+ private Mark mark;
+
+ /**
+ * The original offset to reset the mark to.
+ */
+ private int undoOffset;
+
+ /**
+ * Creates a new UndoPosRef.
+ *
+ * @param m the mark
+ */
+ UndoPosRef(Mark m)
+ {
+ mark = m;
+ undoOffset = mark.mark;
+ }
+
+ /**
+ * Resets the position of the mark to the value that it had when
+ * creating this UndoPosRef.
+ */
+ void reset()
+ {
+ mark.mark = undoOffset;
+ }
+ }
+
+ /**
+ * Holds a mark into the buffer that is used by StickyPosition to find
+ * the actual offset of the position. This is pulled out of the
+ * GapContentPosition object so that the mark and position can be handled
+ * independently, and most important, so that the StickyPosition can
+ * be garbage collected while we still hold a reference to the Mark object.
+ */
+ private class Mark
+ {
+ /**
+ * The actual mark into the buffer.
+ */
+ int mark;
+
+
+ /**
+ * The number of GapContentPosition object that reference this mark. If
+ * it reaches zero, it get's deleted by
+ * {@link StringContent#garbageCollect()}.
+ */
+ int refCount;
+
+ /**
+ * Creates a new Mark object for the specified offset.
+ *
+ * @param offset the offset
+ */
+ Mark(int offset)
+ {
+ mark = offset;
+ }
+ }
+
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 4755994433709540381L;
+
+ // This is package-private to avoid an accessor method.
+ char[] content;
+
+ private int count;
+
+ /**
+ * Holds the marks for the positions.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ Vector marks;
+
+ private class InsertUndo extends AbstractUndoableEdit
+ {
+ private int start;
+
+ private int length;
+
+ private String redoContent;
+
+ private Vector positions;
+
+ public InsertUndo(int start, int length)
+ {
+ super();
+ this.start = start;
+ this.length = length;
+ }
+
+ public void undo()
+ {
+ super.undo();
+ try
+ {
+ if (marks != null)
+ positions = getPositionsInRange(null, start, length);
+ redoContent = getString(start, length);
+ remove(start, length);
+ }
+ catch (BadLocationException b)
+ {
+ throw new CannotUndoException();
+ }
+ }
+
+ public void redo()
+ {
+ super.redo();
+ try
+ {
+ insertString(start, redoContent);
+ redoContent = null;
+ if (positions != null)
+ {
+ updateUndoPositions(positions);
+ positions = null;
+ }
+ }
+ catch (BadLocationException b)
+ {
+ throw new CannotRedoException();
+ }
+ }
+ }
+
+ private class RemoveUndo extends AbstractUndoableEdit
+ {
+ private int start;
+ private int len;
+ private String undoString;
+
+ Vector positions;
+
+ public RemoveUndo(int start, String str)
+ {
+ super();
+ this.start = start;
+ len = str.length();
+ this.undoString = str;
+ if (marks != null)
+ positions = getPositionsInRange(null, start, str.length());
+ }
+
+ public void undo()
+ {
+ super.undo();
+ try
+ {
+ StringContent.this.insertString(this.start, this.undoString);
+ if (positions != null)
+ {
+ updateUndoPositions(positions);
+ positions = null;
+ }
+ undoString = null;
+ }
+ catch (BadLocationException bad)
+ {
+ throw new CannotUndoException();
+ }
+ }
+
+ public void redo()
+ {
+ super.redo();
+ try
+ {
+ undoString = getString(start, len);
+ if (marks != null)
+ positions = getPositionsInRange(null, start, len);
+ remove(this.start, len);
+ }
+ catch (BadLocationException bad)
+ {
+ throw new CannotRedoException();
+ }
+ }
+ }
+
+ private class StickyPosition implements Position
+ {
+ Mark mark;
+
+ public StickyPosition(int offset)
+ {
+ // Try to make space.
+ garbageCollect();
+
+ mark = new Mark(offset);
+ mark.refCount++;
+ marks.add(mark);
+
+ new WeakReference(this, queueOfDeath);
+ }
+
+ /**
+ * Should be >=0.
+ */
+ public int getOffset()
+ {
+ return mark.mark;
+ }
+ }
+
+ /**
+ * Used in {@link #remove(int,int)}.
+ */
+ private static final char[] EMPTY = new char[0];
+
+ /**
+ * Queues all references to GapContentPositions that are about to be
+ * GC'ed. This is used to remove the corresponding marks from the
+ * positionMarks array if the number of references to that mark reaches zero.
+ *
+ * This is package private to avoid accessor synthetic methods.
+ */
+ ReferenceQueue queueOfDeath;
+
+ /**
+ * Creates a new instance containing the string "\n". This is equivalent
+ * to calling {@link #StringContent(int)} with an <code>initialLength</code>
+ * of 10.
+ */
+ public StringContent()
+ {
+ this(10);
+ }
+
+ /**
+ * Creates a new instance containing the string "\n".
+ *
+ * @param initialLength the initial length of the underlying character
+ * array used to store the content.
+ */
+ public StringContent(int initialLength)
+ {
+ super();
+ queueOfDeath = new ReferenceQueue();
+ if (initialLength < 1)
+ initialLength = 1;
+ this.content = new char[initialLength];
+ this.content[0] = '\n';
+ this.count = 1;
+ }
+
+ protected Vector getPositionsInRange(Vector v,
+ int offset,
+ int length)
+ {
+ Vector refPos = v == null ? new Vector() : v;
+ Iterator iter = marks.iterator();
+ while(iter.hasNext())
+ {
+ Mark m = (Mark) iter.next();
+ if (offset <= m.mark && m.mark <= offset + length)
+ refPos.add(new UndoPosRef(m));
+ }
+ return refPos;
+ }
+
+ /**
+ * Creates a position reference for the character at the given offset. The
+ * position offset will be automatically updated when new characters are
+ * inserted into or removed from the content.
+ *
+ * @param offset the character offset.
+ *
+ * @throws BadLocationException if offset is outside the bounds of the
+ * content.
+ */
+ public Position createPosition(int offset) throws BadLocationException
+ {
+ // Lazily create marks vector.
+ if (marks == null)
+ marks = new Vector();
+ StickyPosition sp = new StickyPosition(offset);
+ return sp;
+ }
+
+ /**
+ * Returns the length of the string content, including the '\n' character at
+ * the end.
+ *
+ * @return The length of the string content.
+ */
+ public int length()
+ {
+ return count;
+ }
+
+ /**
+ * Inserts <code>str</code> at the given position and returns an
+ * {@link UndoableEdit} that enables undo/redo support.
+ *
+ * @param where the insertion point (must be less than
+ * <code>length()</code>).
+ * @param str the string to insert (<code>null</code> not permitted).
+ *
+ * @return An object that can undo the insertion.
+ */
+ public UndoableEdit insertString(int where, String str)
+ throws BadLocationException
+ {
+ checkLocation(where, 0);
+ if (where == this.count)
+ throw new BadLocationException("Invalid location", 1);
+ if (str == null)
+ throw new NullPointerException();
+ char[] insert = str.toCharArray();
+ replace(where, 0, insert);
+
+ // Move all the positions.
+ if (marks != null)
+ {
+ Iterator iter = marks.iterator();
+ int start = where;
+ if (start == 0)
+ start = 1;
+ while (iter.hasNext())
+ {
+ Mark m = (Mark) iter.next();
+ if (m.mark >= start)
+ m.mark += str.length();
+ }
+ }
+
+ InsertUndo iundo = new InsertUndo(where, insert.length);
+ return iundo;
+ }
+
+ /**
+ * Removes the specified range of characters and returns an
+ * {@link UndoableEdit} that enables undo/redo support.
+ *
+ * @param where the starting index.
+ * @param nitems the number of characters.
+ *
+ * @return An object that can undo the removal.
+ *
+ * @throws BadLocationException if the character range extends outside the
+ * bounds of the content OR includes the last character.
+ */
+ public UndoableEdit remove(int where, int nitems) throws BadLocationException
+ {
+ checkLocation(where, nitems + 1);
+ RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where,
+ nitems));
+
+ replace(where, nitems, EMPTY);
+ // Move all the positions.
+ if (marks != null)
+ {
+ Iterator iter = marks.iterator();
+ while (iter.hasNext())
+ {
+ Mark m = (Mark) iter.next();
+ if (m.mark >= where + nitems)
+ m.mark -= nitems;
+ else if (m.mark >= where)
+ m.mark = where;
+ }
+ }
+ return rundo;
+ }
+
+ private void replace(int offs, int numRemove, char[] insert)
+ {
+ int insertLength = insert.length;
+ int delta = insertLength - numRemove;
+ int src = offs + numRemove;
+ int numMove = count - src;
+ int dest = src + delta;
+ if (count + delta >= content.length)
+ {
+ // Grow data array.
+ int newLength = Math.max(2 * content.length, count + delta);
+ char[] newContent = new char[newLength];
+ System.arraycopy(content, 0, newContent, 0, offs);
+ System.arraycopy(insert, 0, newContent, offs, insertLength);
+ System.arraycopy(content, src, newContent, dest, numMove);
+ content = newContent;
+ }
+ else
+ {
+ System.arraycopy(content, src, content, dest, numMove);
+ System.arraycopy(insert, 0, content, offs, insertLength);
+ }
+ count += delta;
+ }
+
+ /**
+ * Returns a new <code>String</code> containing the characters in the
+ * specified range.
+ *
+ * @param where the start index.
+ * @param len the number of characters.
+ *
+ * @return A string.
+ *
+ * @throws BadLocationException if the requested range of characters extends
+ * outside the bounds of the content.
+ */
+ public String getString(int where, int len) throws BadLocationException
+ {
+ // The RI throws a StringIndexOutOfBoundsException here, which
+ // smells like a bug. We throw a BadLocationException instead.
+ checkLocation(where, len);
+ return new String(this.content, where, len);
+ }
+
+ /**
+ * Updates <code>txt</code> to contain a direct reference to the underlying
+ * character array.
+ *
+ * @param where the index of the first character.
+ * @param len the number of characters.
+ * @param txt a carrier for the return result (<code>null</code> not
+ * permitted).
+ *
+ * @throws BadLocationException if the requested character range is not
+ * within the bounds of the content.
+ * @throws NullPointerException if <code>txt</code> is <code>null</code>.
+ */
+ public void getChars(int where, int len, Segment txt)
+ throws BadLocationException
+ {
+ if (where + len > count)
+ throw new BadLocationException("Invalid location", where + len);
+ txt.array = content;
+ txt.offset = where;
+ txt.count = len;
+ }
+
+
+ /**
+ * Resets the positions in the specified vector to their original offset
+ * after a undo operation is performed. For example, after removing some
+ * content, the positions in the removed range will all be set to one
+ * offset. This method restores the positions to their original offsets
+ * after an undo.
+ */
+ protected void updateUndoPositions(Vector positions)
+ {
+ for (Iterator i = positions.iterator(); i.hasNext();)
+ {
+ UndoPosRef pos = (UndoPosRef) i.next();
+ pos.reset();
+ }
+ }
+
+ /**
+ * A utility method that checks the validity of the specified character
+ * range.
+ *
+ * @param where the first character in the range.
+ * @param len the number of characters in the range.
+ *
+ * @throws BadLocationException if the specified range is not within the
+ * bounds of the content.
+ */
+ void checkLocation(int where, int len) throws BadLocationException
+ {
+ if (where < 0)
+ throw new BadLocationException("Invalid location", 1);
+ else if (where > this.count)
+ throw new BadLocationException("Invalid location", this.count);
+ else if ((where + len) > this.count)
+ throw new BadLocationException("Invalid range", this.count);
+ }
+
+ /**
+ * Polls the queue of death for GapContentPositions, updates the
+ * corresponding reference count and removes the corresponding mark
+ * if the refcount reaches zero.
+ *
+ * This is package private to avoid accessor synthetic methods.
+ */
+ void garbageCollect()
+ {
+ Reference ref = queueOfDeath.poll();
+ while (ref != null)
+ {
+ if (ref != null)
+ {
+ StickyPosition pos = (StickyPosition) ref.get();
+ Mark m = pos.mark;
+ m.refCount--;
+ if (m.refCount == 0)
+ marks.remove(m);
+ }
+ ref = queueOfDeath.poll();
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/Style.java b/libjava/classpath/javax/swing/text/Style.java
new file mode 100644
index 000000000..801082874
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Style.java
@@ -0,0 +1,64 @@
+/* Style.java --
+ Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text;
+
+import javax.swing.event.ChangeListener;
+
+public interface Style extends MutableAttributeSet
+{
+ /**
+ * Returns the name of the style.
+ *
+ * @return the name
+ */
+ String getName();
+
+ /**
+ * Adds a <code>ChangeListener</code> object to the style.
+ *
+ * @param listener the listener object to add
+ */
+ void addChangeListener(ChangeListener listener);
+
+ /**
+ * Removes a <code>ChangeListener</code> from to the style.
+ *
+ * @param listener the listener object to remove,
+ */
+ void removeChangeListener(ChangeListener listener);
+}
diff --git a/libjava/classpath/javax/swing/text/StyleConstants.java b/libjava/classpath/javax/swing/text/StyleConstants.java
new file mode 100644
index 000000000..bdc63719d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/StyleConstants.java
@@ -0,0 +1,1083 @@
+/* StyleConstants.java --
+ Copyright (C) 2004, 2005, 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.Color;
+import java.awt.Component;
+import java.util.ArrayList;
+
+import javax.swing.Icon;
+
+/**
+ * Represents standard attribute keys. This class also contains a set of
+ * useful static utility methods for querying and populating an
+ * {@link AttributeSet}.
+ *
+ * @since 1.2
+ */
+public class StyleConstants
+{
+ /**
+ * A value representing left alignment for the
+ * {@link ParagraphConstants#Alignment} attribute.
+ */
+ public static final int ALIGN_LEFT = 0;
+
+ /**
+ * A value representing center alignment for the
+ * {@link ParagraphConstants#Alignment} attribute.
+ */
+ public static final int ALIGN_CENTER = 1;
+
+ /**
+ * A value representing right alignment for the
+ * {@link ParagraphConstants#Alignment} attribute.
+ */
+ public static final int ALIGN_RIGHT = 2;
+
+ /**
+ * A value representing ful justification for the
+ * {@link ParagraphConstants#Alignment} attribute.
+ */
+ public static final int ALIGN_JUSTIFIED = 3;
+
+ /** An alias for {@link CharacterConstants#Background}. */
+ public static final Object Background = CharacterConstants.Background;
+
+ /** An alias for {@link CharacterConstants#BidiLevel}. */
+ public static final Object BidiLevel = CharacterConstants.BidiLevel;
+
+ /** An alias for {@link CharacterConstants#Bold}. */
+ public static final Object Bold = CharacterConstants.Bold;
+
+ /** An alias for {@link CharacterConstants#ComponentAttribute}. */
+ public static final Object ComponentAttribute
+ = CharacterConstants.ComponentAttribute;
+
+ /** An alias for {@link CharacterConstants#Family}. */
+ public static final Object Family = CharacterConstants.Family;
+
+ /** An alias for {@link CharacterConstants#Family}. */
+ public static final Object FontFamily = CharacterConstants.Family;
+
+ /** An alias for {@link CharacterConstants#Size}. */
+ public static final Object FontSize = CharacterConstants.Size;
+
+ /** An alias for {@link CharacterConstants#Foreground}. */
+ public static final Object Foreground = CharacterConstants.Foreground;
+
+ /** An alias for {@link CharacterConstants#IconAttribute}. */
+ public static final Object IconAttribute = CharacterConstants.IconAttribute;
+
+ /** An alias for {@link CharacterConstants#Italic}. */
+ public static final Object Italic = CharacterConstants.Italic;
+
+ /** An alias for {@link CharacterConstants#Size}. */
+ public static final Object Size = CharacterConstants.Size;
+
+ /** An alias for {@link CharacterConstants#StrikeThrough}. */
+ public static final Object StrikeThrough = CharacterConstants.StrikeThrough;
+
+ /** An alias for {@link CharacterConstants#Subscript}. */
+ public static final Object Subscript = CharacterConstants.Subscript;
+
+ /** An alias for {@link CharacterConstants#Superscript}. */
+ public static final Object Superscript = CharacterConstants.Superscript;
+
+ /** An alias for {@link CharacterConstants#Underline}. */
+ public static final Object Underline = CharacterConstants.Underline;
+
+ /** An alias for {@link ParagraphConstants#Alignment}. */
+ public static final Object Alignment = ParagraphConstants.Alignment;
+
+ /** An alias for {@link ParagraphConstants#FirstLineIndent}. */
+ public static final Object FirstLineIndent
+ = ParagraphConstants.FirstLineIndent;
+
+ /** An alias for {@link ParagraphConstants#LeftIndent}. */
+ public static final Object LeftIndent = ParagraphConstants.LeftIndent;
+
+ /** An alias for {@link ParagraphConstants#LineSpacing}. */
+ public static final Object LineSpacing = ParagraphConstants.LineSpacing;
+
+ /** An alias for {@link ParagraphConstants#Orientation}. */
+ public static final Object Orientation = ParagraphConstants.Orientation;
+
+ /** An alias for {@link ParagraphConstants#RightIndent}. */
+ public static final Object RightIndent = ParagraphConstants.RightIndent;
+
+ /** An alias for {@link ParagraphConstants#SpaceAbove}. */
+ public static final Object SpaceAbove = ParagraphConstants.SpaceAbove;
+
+ /** An alias for {@link ParagraphConstants#SpaceBelow}. */
+ public static final Object SpaceBelow = ParagraphConstants.SpaceBelow;
+
+ /** An alias for {@link ParagraphConstants#TabSet}. */
+ public static final Object TabSet = ParagraphConstants.TabSet;
+
+ public static final String ComponentElementName = "component";
+
+ public static final String IconElementName = "icon";
+
+ public static final Object ComposedTextAttribute
+ = new StyleConstants("composed text");
+
+ public static final Object ModelAttribute = new StyleConstants("model");
+
+ public static final Object NameAttribute = new StyleConstants("name");
+
+ public static final Object ResolveAttribute = new StyleConstants("resolver");
+
+ /**
+ * All StyleConstants keys. This is used in StyleContext to register
+ * all known keys as static attribute keys for serialization.
+ */
+ static ArrayList keys;
+
+ String keyname;
+
+ // Package-private to avoid accessor constructor for use by
+ // subclasses.
+ StyleConstants(String k)
+ {
+ keyname = k;
+ if (keys == null)
+ keys = new ArrayList();
+ keys.add(this);
+ }
+
+ /**
+ * Returns a string representation of the attribute key.
+ *
+ * @return A string representation of the attribute key.
+ */
+ public String toString()
+ {
+ return keyname;
+ }
+
+ /**
+ * Returns the alignment specified in the given attributes, or
+ * {@link #ALIGN_LEFT} if no alignment is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The alignment (typically one of {@link #ALIGN_LEFT},
+ * {@link #ALIGN_RIGHT}, {@link #ALIGN_CENTER} or
+ * {@link #ALIGN_JUSTIFIED}).
+ *
+ * @see #setAlignment(MutableAttributeSet, int)
+ */
+ public static int getAlignment(AttributeSet a)
+ {
+ Integer i = (Integer) a.getAttribute(Alignment);
+ if (i != null)
+ return i.intValue();
+ else
+ return ALIGN_LEFT;
+ }
+
+ /**
+ * Returns the background color specified in the given attributes, or
+ * {@link Color#BLACK} if no background color is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The background color.
+ *
+ * @see #setBackground(MutableAttributeSet, Color)
+ */
+ public static Color getBackground(AttributeSet a)
+ {
+ Color c = (Color) a.getAttribute(Background);
+ if (c != null)
+ return c;
+ else
+ return Color.BLACK;
+ }
+
+ /**
+ * Returns the bidi level specified in the given attributes, or
+ * <code>0</code> if no bidi level is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The bidi level.
+ *
+ * @see #setBidiLevel(MutableAttributeSet, int)
+ */
+ public static int getBidiLevel(AttributeSet a)
+ {
+ Integer i = (Integer) a.getAttribute(BidiLevel);
+ if (i != null)
+ return i.intValue();
+ else
+ return 0;
+ }
+
+ /**
+ * Returns the component specified in the given attributes, or
+ * <code>null</code> if no component is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The component (possibly <code>null</code>).
+ *
+ * @see #setComponent(MutableAttributeSet, Component)
+ */
+ public static Component getComponent(AttributeSet a)
+ {
+ Component c = (Component) a.getAttribute(ComponentAttribute);
+ if (c != null)
+ return c;
+ else
+ return null;
+ }
+
+ /**
+ * Returns the indentation specified in the given attributes, or
+ * <code>0.0f</code> if no indentation is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The indentation.
+ *
+ * @see #setFirstLineIndent(MutableAttributeSet, float)
+ */
+ public static float getFirstLineIndent(AttributeSet a)
+ {
+ Float f = (Float) a.getAttribute(FirstLineIndent);
+ if (f != null)
+ return f.floatValue();
+ else
+ return 0.0f;
+ }
+
+ /**
+ * Returns the font family specified in the given attributes, or
+ * <code>Monospaced</code> if no font family is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The font family.
+ *
+ * @see #setFontFamily(MutableAttributeSet, String)
+ */
+ public static String getFontFamily(AttributeSet a)
+ {
+ String ff = (String) a.getAttribute(FontFamily);
+ if (ff != null)
+ return ff;
+ else
+ return "Monospaced";
+ }
+
+ /**
+ * Returns the font size specified in the given attributes, or
+ * <code>12</code> if no font size is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The font size.
+ *
+ * @see #setFontSize(MutableAttributeSet, int)
+ */
+ public static int getFontSize(AttributeSet a)
+ {
+ Integer i = (Integer) a.getAttribute(FontSize);
+ if (i != null)
+ return i.intValue();
+ else
+ return 12;
+ }
+
+ /**
+ * Returns the foreground color specified in the given attributes, or
+ * {@link Color#BLACK} if no foreground color is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The foreground color.
+ *
+ * @see #setForeground(MutableAttributeSet, Color)
+ */
+ public static Color getForeground(AttributeSet a)
+ {
+ Color c = (Color) a.getAttribute(Foreground);
+ if (c != null)
+ return c;
+ else
+ return Color.BLACK;
+ }
+
+ /**
+ * Returns the icon specified in the given attributes, or
+ * <code>null</code> if no icon is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The icon (possibly <code>null</code>).
+ *
+ * @see #setIcon(MutableAttributeSet, Icon)
+ */
+ public static Icon getIcon(AttributeSet a)
+ {
+ return (Icon) a.getAttribute(IconAttribute);
+ }
+
+ /**
+ * Returns the left indentation specified in the given attributes, or
+ * <code>0.0f</code> if no left indentation is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The left indentation.
+ *
+ * @see #setLeftIndent(MutableAttributeSet, float)
+ */
+ public static float getLeftIndent(AttributeSet a)
+ {
+ Float f = (Float) a.getAttribute(LeftIndent);
+ if (f != null)
+ return f.floatValue();
+ else
+ return 0.0f;
+ }
+
+ /**
+ * Returns the line spacing specified in the given attributes, or
+ * <code>0.0f</code> if no line spacing is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The line spacing.
+ *
+ * @see #setLineSpacing(MutableAttributeSet, float)
+ */
+ public static float getLineSpacing(AttributeSet a)
+ {
+ Float f = (Float) a.getAttribute(LineSpacing);
+ if (f != null)
+ return f.floatValue();
+ else
+ return 0.0f;
+ }
+
+ /**
+ * Returns the right indentation specified in the given attributes, or
+ * <code>0.0f</code> if no right indentation is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The right indentation.
+ *
+ * @see #setRightIndent(MutableAttributeSet, float)
+ */
+ public static float getRightIndent(AttributeSet a)
+ {
+ Float f = (Float) a.getAttribute(RightIndent);
+ if (f != null)
+ return f.floatValue();
+ else
+ return 0.0f;
+ }
+
+ /**
+ * Returns the 'space above' specified in the given attributes, or
+ * <code>0.0f</code> if no 'space above' is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The 'space above'.
+ *
+ * @see #setSpaceAbove(MutableAttributeSet, float)
+ */
+ public static float getSpaceAbove(AttributeSet a)
+ {
+ Float f = (Float) a.getAttribute(SpaceAbove);
+ if (f != null)
+ return f.floatValue();
+ else
+ return 0.0f;
+ }
+
+ /**
+ * Returns the 'space below' specified in the given attributes, or
+ * <code>0.0f</code> if no 'space below' is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The 'space below'.
+ *
+ * @see #setSpaceBelow(MutableAttributeSet, float)
+ */
+ public static float getSpaceBelow(AttributeSet a)
+ {
+ Float f = (Float) a.getAttribute(SpaceBelow);
+ if (f != null)
+ return f.floatValue();
+ else
+ return 0.0f;
+ }
+
+ /**
+ * Returns the tab set specified in the given attributes, or
+ * <code>null</code> if no tab set is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The tab set.
+ *
+ * @see #setTabSet(MutableAttributeSet, javax.swing.text.TabSet)
+ */
+ public static javax.swing.text.TabSet getTabSet(AttributeSet a)
+ {
+ // I'm guessing that the fully qualified class name is to differentiate
+ // between the TabSet class and the TabSet (attribute) instance on some
+ // compiler...
+ return (javax.swing.text.TabSet) a.getAttribute(StyleConstants.TabSet);
+ }
+
+ /**
+ * Returns the value of the bold flag in the given attributes, or
+ * <code>false</code> if no bold flag is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The bold flag.
+ *
+ * @see #setBold(MutableAttributeSet, boolean)
+ */
+ public static boolean isBold(AttributeSet a)
+ {
+ Boolean b = (Boolean) a.getAttribute(Bold);
+ if (b != null)
+ return b.booleanValue();
+ else
+ return false;
+ }
+
+ /**
+ * Returns the value of the italic flag in the given attributes, or
+ * <code>false</code> if no italic flag is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The italic flag.
+ *
+ * @see #setItalic(MutableAttributeSet, boolean)
+ */
+ public static boolean isItalic(AttributeSet a)
+ {
+ Boolean b = (Boolean) a.getAttribute(Italic);
+ if (b != null)
+ return b.booleanValue();
+ else
+ return false;
+ }
+
+ /**
+ * Returns the value of the strike-through flag in the given attributes, or
+ * <code>false</code> if no strike-through flag is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The strike-through flag.
+ *
+ * @see #setStrikeThrough(MutableAttributeSet, boolean)
+ */
+ public static boolean isStrikeThrough(AttributeSet a)
+ {
+ Boolean b = (Boolean) a.getAttribute(StrikeThrough);
+ if (b != null)
+ return b.booleanValue();
+ else
+ return false;
+ }
+
+ /**
+ * Returns the value of the subscript flag in the given attributes, or
+ * <code>false</code> if no subscript flag is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The subscript flag.
+ *
+ * @see #setSubscript(MutableAttributeSet, boolean)
+ */
+ public static boolean isSubscript(AttributeSet a)
+ {
+ Boolean b = (Boolean) a.getAttribute(Subscript);
+ if (b != null)
+ return b.booleanValue();
+ else
+ return false;
+ }
+
+ /**
+ * Returns the value of the superscript flag in the given attributes, or
+ * <code>false</code> if no superscript flag is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The superscript flag.
+ *
+ * @see #setSuperscript(MutableAttributeSet, boolean)
+ */
+ public static boolean isSuperscript(AttributeSet a)
+ {
+ Boolean b = (Boolean) a.getAttribute(Superscript);
+ if (b != null)
+ return b.booleanValue();
+ else
+ return false;
+ }
+
+ /**
+ * Returns the value of the underline flag in the given attributes, or
+ * <code>false</code> if no underline flag is specified.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ *
+ * @return The underline flag.
+ *
+ * @see #setUnderline(MutableAttributeSet, boolean)
+ */
+ public static boolean isUnderline(AttributeSet a)
+ {
+ Boolean b = (Boolean) a.getAttribute(Underline);
+ if (b != null)
+ return b.booleanValue();
+ else
+ return false;
+ }
+
+ /**
+ * Adds an alignment attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param align the alignment (typically one of
+ * {@link StyleConstants#ALIGN_LEFT},
+ * {@link StyleConstants#ALIGN_RIGHT},
+ * {@link StyleConstants#ALIGN_CENTER} or
+ * {@link StyleConstants#ALIGN_JUSTIFIED}).
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getAlignment(AttributeSet)
+ */
+ public static void setAlignment(MutableAttributeSet a, int align)
+ {
+ a.addAttribute(Alignment, new Integer(align));
+ }
+
+ /**
+ * Adds a background attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param bg the background (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ *
+ * @see #getBackground(AttributeSet)
+ */
+ public static void setBackground(MutableAttributeSet a, Color bg)
+ {
+ a.addAttribute(Background, bg);
+ }
+
+ /**
+ * Adds a bidi-level attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param lev the level.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getBidiLevel(AttributeSet)
+ */
+ public static void setBidiLevel(MutableAttributeSet a, int lev)
+ {
+ a.addAttribute(BidiLevel, new Integer(lev));
+ }
+
+ /**
+ * Adds a bold attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param b the new value of the bold attribute.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #isBold(AttributeSet)
+ */
+ public static void setBold(MutableAttributeSet a, boolean b)
+ {
+ a.addAttribute(Bold, Boolean.valueOf(b));
+ }
+
+ /**
+ * Adds a component attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param c the component (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ *
+ * @see #getComponent(AttributeSet)
+ */
+ public static void setComponent(MutableAttributeSet a, Component c)
+ {
+ a.addAttribute(ComponentAttribute, c);
+ }
+
+ /**
+ * Adds a first line indentation attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param i the indentation.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getFirstLineIndent(AttributeSet)
+ */
+ public static void setFirstLineIndent(MutableAttributeSet a, float i)
+ {
+ a.addAttribute(FirstLineIndent, new Float(i));
+ }
+
+ /**
+ * Adds a font family attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param fam the font family name (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ *
+ * @see #getFontFamily(AttributeSet)
+ */
+ public static void setFontFamily(MutableAttributeSet a, String fam)
+ {
+ a.addAttribute(FontFamily, fam);
+ }
+
+ /**
+ * Adds a font size attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param s the font size (in points).
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getFontSize(AttributeSet)
+ */
+ public static void setFontSize(MutableAttributeSet a, int s)
+ {
+ a.addAttribute(FontSize, new Integer(s));
+ }
+
+ /**
+ * Adds a foreground color attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param fg the foreground color (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ *
+ * @see #getForeground(AttributeSet)
+ */
+ public static void setForeground(MutableAttributeSet a, Color fg)
+ {
+ a.addAttribute(Foreground, fg);
+ }
+
+ /**
+ * Adds an icon attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param c the icon (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ *
+ * @see #getIcon(AttributeSet)
+ */
+ public static void setIcon(MutableAttributeSet a, Icon c)
+ {
+ a.addAttribute(AbstractDocument.ElementNameAttribute, IconElementName);
+ a.addAttribute(IconAttribute, c);
+ }
+
+ /**
+ * Adds an italic attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param b the new value of the italic attribute.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #isItalic(AttributeSet)
+ */
+ public static void setItalic(MutableAttributeSet a, boolean b)
+ {
+ a.addAttribute(Italic, Boolean.valueOf(b));
+ }
+
+ /**
+ * Adds a left indentation attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param i the indentation.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getLeftIndent(AttributeSet)
+ */
+ public static void setLeftIndent(MutableAttributeSet a, float i)
+ {
+ a.addAttribute(LeftIndent, new Float(i));
+ }
+
+ /**
+ * Adds a line spacing attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param i the line spacing.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getLineSpacing(AttributeSet)
+ */
+ public static void setLineSpacing(MutableAttributeSet a, float i)
+ {
+ a.addAttribute(LineSpacing, new Float(i));
+ }
+
+ /**
+ * Adds a right indentation attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param i the right indentation.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getRightIndent(AttributeSet)
+ */
+ public static void setRightIndent(MutableAttributeSet a, float i)
+ {
+ a.addAttribute(RightIndent, new Float(i));
+ }
+
+ /**
+ * Adds a 'space above' attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param i the space above attribute value.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getSpaceAbove(AttributeSet)
+ */
+ public static void setSpaceAbove(MutableAttributeSet a, float i)
+ {
+ a.addAttribute(SpaceAbove, new Float(i));
+ }
+
+ /**
+ * Adds a 'space below' attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param i the space below attribute value.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #getSpaceBelow(AttributeSet)
+ */
+ public static void setSpaceBelow(MutableAttributeSet a, float i)
+ {
+ a.addAttribute(SpaceBelow, new Float(i));
+ }
+
+ /**
+ * Adds a strike-through attribue to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param b the strike-through attribute value.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #isStrikeThrough(AttributeSet)
+ */
+ public static void setStrikeThrough(MutableAttributeSet a, boolean b)
+ {
+ a.addAttribute(StrikeThrough, Boolean.valueOf(b));
+ }
+
+ /**
+ * Adds a subscript attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param b the subscript attribute value.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #isSubscript(AttributeSet)
+ */
+ public static void setSubscript(MutableAttributeSet a, boolean b)
+ {
+ a.addAttribute(Subscript, Boolean.valueOf(b));
+ }
+
+ /**
+ * Adds a superscript attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param b the superscript attribute value.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #isSuperscript(AttributeSet)
+ */
+ public static void setSuperscript(MutableAttributeSet a, boolean b)
+ {
+ a.addAttribute(Superscript, Boolean.valueOf(b));
+ }
+
+ /**
+ * Adds a {@link TabSet} attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param tabs the tab set (<code>null</code> not permitted).
+ *
+ * @throws NullPointerException if either argument is <code>null</code>.
+ *
+ * @see #getTabSet(AttributeSet)
+ */
+ public static void setTabSet(MutableAttributeSet a,
+ javax.swing.text.TabSet tabs)
+ {
+ a.addAttribute(StyleConstants.TabSet, tabs);
+ }
+
+ /**
+ * Adds an underline attribute to the specified set.
+ *
+ * @param a the attribute set (<code>null</code> not permitted).
+ * @param b the underline attribute value.
+ *
+ * @throws NullPointerException if <code>a</code> is <code>null</code>.
+ *
+ * @see #isUnderline(AttributeSet)
+ */
+ public static void setUnderline(MutableAttributeSet a, boolean b)
+ {
+ a.addAttribute(Underline, Boolean.valueOf(b));
+ }
+
+ // The remainder are so-called "typesafe enumerations" which
+ // alias subsets of the above constants.
+
+ /**
+ * A set of keys for attributes that apply to characters.
+ */
+ public static class CharacterConstants
+ extends StyleConstants
+ implements AttributeSet.CharacterAttribute
+ {
+ /**
+ * Private constructor prevents new instances being created.
+ *
+ * @param k the key name.
+ */
+ private CharacterConstants(String k)
+ {
+ super(k);
+ }
+
+ /** An alias for {@link ColorConstants#Background}. */
+ public static final Object Background = ColorConstants.Background;
+
+ /** A key for the bidi level character attribute. */
+ public static final Object BidiLevel = new CharacterConstants("bidiLevel");
+
+ /** An alias for {@link FontConstants#Bold}. */
+ public static final Object Bold = FontConstants.Bold;
+
+ /** A key for the component character attribute. */
+ public static final Object ComponentAttribute
+ = new CharacterConstants("component");
+
+ /** An alias for {@link FontConstants#Family}. */
+ public static final Object Family = FontConstants.Family;
+
+ /** An alias for {@link FontConstants#Size}. */
+ public static final Object Size = FontConstants.Size;
+
+ /** An alias for {@link ColorConstants#Foreground}. */
+ public static final Object Foreground = ColorConstants.Foreground;
+
+ /** A key for the icon character attribute. */
+ public static final Object IconAttribute = new CharacterConstants("icon");
+
+ /** A key for the italic character attribute. */
+ public static final Object Italic = FontConstants.Italic;
+
+ /** A key for the strike through character attribute. */
+ public static final Object StrikeThrough
+ = new CharacterConstants("strikethrough");
+
+ /** A key for the subscript character attribute. */
+ public static final Object Subscript = new CharacterConstants("subscript");
+
+ /** A key for the superscript character attribute. */
+ public static final Object Superscript
+ = new CharacterConstants("superscript");
+
+ /** A key for the underline character attribute. */
+ public static final Object Underline = new CharacterConstants("underline");
+
+ }
+
+ /**
+ * A set of keys for attributes that relate to colors.
+ */
+ public static class ColorConstants
+ extends StyleConstants
+ implements AttributeSet.ColorAttribute, AttributeSet.CharacterAttribute
+ {
+ /**
+ * Private constructor prevents new instances being created.
+ *
+ * @param k the key name.
+ */
+ private ColorConstants(String k)
+ {
+ super(k);
+ }
+
+ /** A key for the foreground color attribute. */
+ public static final Object Foreground = new ColorConstants("foreground");
+
+ /** A key for the background color attribute. */
+ public static final Object Background = new ColorConstants("background");
+ }
+
+ /**
+ * A set of keys for attributes that apply to fonts.
+ */
+ public static class FontConstants
+ extends StyleConstants
+ implements AttributeSet.FontAttribute, AttributeSet.CharacterAttribute
+ {
+ /**
+ * Private constructor prevents new instances being created.
+ *
+ * @param k the key name.
+ */
+ private FontConstants(String k)
+ {
+ super(k);
+ }
+
+ /** A key for the bold font attribute. */
+ public static final Object Bold = new FontConstants("bold");
+
+ /** A key for the family font attribute. */
+ public static final Object Family = new FontConstants("family");
+
+ /** A key for the italic font attribute. */
+ public static final Object Italic = new FontConstants("italic");
+
+ /** A key for the size font attribute. */
+ public static final Object Size = new FontConstants("size");
+ }
+
+ /**
+ * A set of keys for attributes that apply to paragraphs.
+ */
+ public static class ParagraphConstants
+ extends StyleConstants
+ implements AttributeSet.ParagraphAttribute
+ {
+ /**
+ * Private constructor prevents new instances being created.
+ *
+ * @param k the key name.
+ */
+ private ParagraphConstants(String k)
+ {
+ super(k);
+ }
+
+ /** A key for the alignment paragraph attribute. */
+ public static final Object Alignment = new ParagraphConstants("Alignment");
+
+ /** A key for the first line indentation paragraph attribute. */
+ public static final Object FirstLineIndent
+ = new ParagraphConstants("FirstLineIndent");
+
+ /** A key for the left indentation paragraph attribute. */
+ public static final Object LeftIndent
+ = new ParagraphConstants("LeftIndent");
+
+ /** A key for the line spacing paragraph attribute. */
+ public static final Object LineSpacing
+ = new ParagraphConstants("LineSpacing");
+
+ /** A key for the orientation paragraph attribute. */
+ public static final Object Orientation
+ = new ParagraphConstants("Orientation");
+
+ /** A key for the right indentation paragraph attribute. */
+ public static final Object RightIndent
+ = new ParagraphConstants("RightIndent");
+
+ /** A key for the 'space above' paragraph attribute. */
+ public static final Object SpaceAbove
+ = new ParagraphConstants("SpaceAbove");
+
+ /** A key for the 'space below' paragraph attribute. */
+ public static final Object SpaceBelow
+ = new ParagraphConstants("SpaceBelow");
+
+ /** A key for the tabset paragraph attribute. */
+ public static final Object TabSet = new ParagraphConstants("TabSet");
+
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/StyleContext.java b/libjava/classpath/javax/swing/text/StyleContext.java
new file mode 100644
index 000000000..295398835
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/StyleContext.java
@@ -0,0 +1,990 @@
+/* StyleContext.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Toolkit;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+public class StyleContext
+ implements Serializable, AbstractDocument.AttributeContext
+{
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 8042858831190784241L;
+
+ public class NamedStyle
+ implements Serializable, Style
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -6690628971806226374L;
+
+ protected transient ChangeEvent changeEvent;
+ protected EventListenerList listenerList;
+
+ private transient AttributeSet attributes;
+
+ public NamedStyle()
+ {
+ this(null, null);
+ }
+
+ public NamedStyle(Style parent)
+ {
+ this(null, parent);
+ }
+
+ public NamedStyle(String name, Style parent)
+ {
+ attributes = getEmptySet();
+ listenerList = new EventListenerList();
+ if (name != null)
+ setName(name);
+ if (parent != null)
+ setResolveParent(parent);
+ }
+
+ public String getName()
+ {
+ String name = null;
+ if (isDefined(StyleConstants.NameAttribute))
+ name = getAttribute(StyleConstants.NameAttribute).toString();
+ return name;
+ }
+
+ public void setName(String n)
+ {
+ if (n != null)
+ addAttribute(StyleConstants.NameAttribute, n);
+ }
+
+ public void addChangeListener(ChangeListener l)
+ {
+ listenerList.add(ChangeListener.class, l);
+ }
+
+ public void removeChangeListener(ChangeListener l)
+ {
+ listenerList.remove(ChangeListener.class, l);
+ }
+
+ public <T extends EventListener> T[] getListeners(Class<T> listenerType)
+ {
+ return listenerList.getListeners(listenerType);
+ }
+
+ public ChangeListener[] getChangeListeners()
+ {
+ return (ChangeListener[]) getListeners(ChangeListener.class);
+ }
+
+ protected void fireStateChanged()
+ {
+ ChangeListener[] listeners = getChangeListeners();
+ for (int i = 0; i < listeners.length; ++i)
+ {
+ // Lazily create event.
+ if (changeEvent == null)
+ changeEvent = new ChangeEvent(this);
+ listeners[i].stateChanged(changeEvent);
+ }
+ }
+
+ public void addAttribute(Object name, Object value)
+ {
+ attributes = StyleContext.this.addAttribute(attributes, name, value);
+ fireStateChanged();
+ }
+
+ public void addAttributes(AttributeSet attr)
+ {
+ attributes = StyleContext.this.addAttributes(attributes, attr);
+ fireStateChanged();
+ }
+
+ public boolean containsAttribute(Object name, Object value)
+ {
+ return attributes.containsAttribute(name, value);
+ }
+
+ public boolean containsAttributes(AttributeSet attrs)
+ {
+ return attributes.containsAttributes(attrs);
+ }
+
+ public AttributeSet copyAttributes()
+ {
+ // The RI returns a NamedStyle as copy, so do we.
+ NamedStyle copy = new NamedStyle();
+ copy.attributes = attributes.copyAttributes();
+ return copy;
+ }
+
+ public Object getAttribute(Object attrName)
+ {
+ return attributes.getAttribute(attrName);
+ }
+
+ public int getAttributeCount()
+ {
+ return attributes.getAttributeCount();
+ }
+
+ public Enumeration<?> getAttributeNames()
+ {
+ return attributes.getAttributeNames();
+ }
+
+ public boolean isDefined(Object attrName)
+ {
+ return attributes.isDefined(attrName);
+ }
+
+ public boolean isEqual(AttributeSet attr)
+ {
+ return attributes.isEqual(attr);
+ }
+
+ public void removeAttribute(Object name)
+ {
+ attributes = StyleContext.this.removeAttribute(attributes, name);
+ fireStateChanged();
+ }
+
+ public void removeAttributes(AttributeSet attrs)
+ {
+ attributes = StyleContext.this.removeAttributes(attributes, attrs);
+ fireStateChanged();
+ }
+
+ public void removeAttributes(Enumeration<?> names)
+ {
+ attributes = StyleContext.this.removeAttributes(attributes, names);
+ fireStateChanged();
+ }
+
+
+ public AttributeSet getResolveParent()
+ {
+ return attributes.getResolveParent();
+ }
+
+ public void setResolveParent(AttributeSet parent)
+ {
+ if (parent != null)
+ addAttribute(StyleConstants.ResolveAttribute, parent);
+ else
+ removeAttribute(StyleConstants.ResolveAttribute);
+ }
+
+ public String toString()
+ {
+ return "NamedStyle:" + getName() + " " + attributes;
+ }
+
+ private void writeObject(ObjectOutputStream s)
+ throws IOException
+ {
+ s.defaultWriteObject();
+ writeAttributeSet(s, attributes);
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException
+ {
+ s.defaultReadObject();
+ attributes = SimpleAttributeSet.EMPTY;
+ readAttributeSet(s, this);
+ }
+ }
+
+ public class SmallAttributeSet
+ implements AttributeSet
+ {
+ final Object [] attrs;
+ private AttributeSet resolveParent;
+ public SmallAttributeSet(AttributeSet a)
+ {
+ int n = a.getAttributeCount();
+ int i = 0;
+ attrs = new Object[n * 2];
+ Enumeration e = a.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ Object name = e.nextElement();
+ Object value = a.getAttribute(name);
+ if (name == ResolveAttribute)
+ resolveParent = (AttributeSet) value;
+ attrs[i++] = name;
+ attrs[i++] = value;
+ }
+ }
+
+ public SmallAttributeSet(Object [] a)
+ {
+ attrs = a;
+ for (int i = 0; i < attrs.length; i += 2)
+ {
+ if (attrs[i] == ResolveAttribute)
+ resolveParent = (AttributeSet) attrs[i + 1];
+ }
+ }
+
+ public Object clone()
+ {
+ return this;
+ }
+
+ public boolean containsAttribute(Object name, Object value)
+ {
+ return value.equals(getAttribute(name));
+ }
+
+ public boolean containsAttributes(AttributeSet a)
+ {
+ boolean res = true;
+ Enumeration e = a.getAttributeNames();
+ while (e.hasMoreElements() && res)
+ {
+ Object name = e.nextElement();
+ res = a.getAttribute(name).equals(getAttribute(name));
+ }
+ return res;
+ }
+
+ public AttributeSet copyAttributes()
+ {
+ return this;
+ }
+
+ public boolean equals(Object obj)
+ {
+ boolean eq = false;
+ if (obj instanceof AttributeSet)
+ {
+ AttributeSet atts = (AttributeSet) obj;
+ eq = getAttributeCount() == atts.getAttributeCount()
+ && containsAttributes(atts);
+ }
+ return eq;
+ }
+
+ public Object getAttribute(Object key)
+ {
+ Object att = null;
+ if (key == StyleConstants.ResolveAttribute)
+ att = resolveParent;
+
+ for (int i = 0; i < attrs.length && att == null; i += 2)
+ {
+ if (attrs[i].equals(key))
+ att = attrs[i + 1];
+ }
+
+ // Check the resolve parent, unless we're looking for the
+ // ResolveAttribute, which must not be looked up
+ if (att == null)
+ {
+ AttributeSet parent = getResolveParent();
+ if (parent != null)
+ att = parent.getAttribute(key);
+ }
+
+ return att;
+ }
+
+ public int getAttributeCount()
+ {
+ return attrs.length / 2;
+ }
+
+ public Enumeration<?> getAttributeNames()
+ {
+ return new Enumeration()
+ {
+ int i = 0;
+ public boolean hasMoreElements()
+ {
+ return i < attrs.length;
+ }
+ public Object nextElement()
+ {
+ i += 2;
+ return attrs[i-2];
+ }
+ };
+ }
+
+ public AttributeSet getResolveParent()
+ {
+ return resolveParent;
+ }
+
+ public int hashCode()
+ {
+ return java.util.Arrays.asList(attrs).hashCode();
+ }
+
+ public boolean isDefined(Object key)
+ {
+ for (int i = 0; i < attrs.length; i += 2)
+ {
+ if (attrs[i].equals(key))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isEqual(AttributeSet attr)
+ {
+ boolean eq;
+ // If the other one is also a SmallAttributeSet, it is only considered
+ // equal if it's the same instance.
+ if (attr instanceof SmallAttributeSet)
+ eq = attr == this;
+ else
+ eq = getAttributeCount() == attr.getAttributeCount()
+ && this.containsAttributes(attr);
+ return eq;
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ for (int i = 0; i < attrs.length; i += 2)
+ {
+ if (attrs[i + 1] instanceof AttributeSet)
+ {
+ sb.append(attrs[i]);
+ sb.append("=AttributeSet,");
+ }
+ else
+ {
+ sb.append(attrs[i]);
+ sb.append('=');
+ sb.append(attrs[i + 1]);
+ sb.append(',');
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Register StyleConstant keys as static attribute keys for serialization.
+ */
+ static
+ {
+ // Don't let problems while doing this prevent class loading.
+ try
+ {
+ for (Iterator i = StyleConstants.keys.iterator(); i.hasNext();)
+ registerStaticAttributeKey(i.next());
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+ }
+
+ /**
+ * The name of the default style.
+ */
+ public static final String DEFAULT_STYLE = "default";
+
+ static Hashtable sharedAttributeSets = new Hashtable();
+ static Hashtable sharedFonts = new Hashtable();
+
+ static StyleContext defaultStyleContext;
+ static final int compressionThreshold = 9;
+
+ /**
+ * These attribute keys are handled specially in serialization.
+ */
+ private static Hashtable writeAttributeKeys;
+ private static Hashtable readAttributeKeys;
+
+ private NamedStyle styles;
+
+ /**
+ * Used for searching attributes in the pool.
+ */
+ private transient MutableAttributeSet search = new SimpleAttributeSet();
+
+ /**
+ * A pool of immutable AttributeSets.
+ */
+ private transient Map attributeSetPool =
+ Collections.synchronizedMap(new WeakHashMap());
+
+ /**
+ * Creates a new instance of the style context. Add the default style
+ * to the style table.
+ */
+ public StyleContext()
+ {
+ styles = new NamedStyle(null);
+ addStyle(DEFAULT_STYLE, null);
+ }
+
+ protected SmallAttributeSet createSmallAttributeSet(AttributeSet a)
+ {
+ return new SmallAttributeSet(a);
+ }
+
+ protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
+ {
+ return new SimpleAttributeSet(a);
+ }
+
+ public void addChangeListener(ChangeListener listener)
+ {
+ styles.addChangeListener(listener);
+ }
+
+ public void removeChangeListener(ChangeListener listener)
+ {
+ styles.removeChangeListener(listener);
+ }
+
+ public ChangeListener[] getChangeListeners()
+ {
+ return styles.getChangeListeners();
+ }
+
+ public Style addStyle(String name, Style parent)
+ {
+ Style newStyle = new NamedStyle(name, parent);
+ if (name != null)
+ styles.addAttribute(name, newStyle);
+ return newStyle;
+ }
+
+ public void removeStyle(String name)
+ {
+ styles.removeAttribute(name);
+ }
+
+ /**
+ * Get the style from the style table. If the passed name
+ * matches {@link #DEFAULT_STYLE}, returns the default style.
+ * Otherwise returns the previously defined style of
+ * <code>null</code> if the style with the given name is not defined.
+ *
+ * @param name the name of the style.
+ *
+ * @return the style with the given name or null if no such defined.
+ */
+ public Style getStyle(String name)
+ {
+ return (Style) styles.getAttribute(name);
+ }
+
+ /**
+ * Get the names of the style. The returned enumeration always
+ * contains at least one member, the default style.
+ */
+ public Enumeration<?> getStyleNames()
+ {
+ return styles.getAttributeNames();
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws ClassNotFoundException, IOException
+ {
+ search = new SimpleAttributeSet();
+ attributeSetPool = Collections.synchronizedMap(new WeakHashMap());
+ in.defaultReadObject();
+ }
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ cleanupPool();
+ out.defaultWriteObject();
+ }
+
+ //
+ // StyleContexts only understand the "simple" model of fonts present in
+ // pre-java2d systems: fonts are a family name, a size (integral number
+ // of points), and a mask of style parameters (plain, bold, italic, or
+ // bold|italic). We have an inner class here called SimpleFontSpec which
+ // holds such triples.
+ //
+ // A SimpleFontSpec can be built for *any* AttributeSet because the size,
+ // family, and style keys in an AttributeSet have default values (defined
+ // over in StyleConstants).
+ //
+ // We keep a static cache mapping SimpleFontSpecs to java.awt.Fonts, so
+ // that we reuse Fonts between styles and style contexts.
+ //
+
+ private static class SimpleFontSpec
+ {
+ String family;
+ int style;
+ int size;
+ public SimpleFontSpec(String family,
+ int style,
+ int size)
+ {
+ this.family = family;
+ this.style = style;
+ this.size = size;
+ }
+ public boolean equals(Object obj)
+ {
+ return (obj != null)
+ && (obj instanceof SimpleFontSpec)
+ && (((SimpleFontSpec)obj).family.equals(this.family))
+ && (((SimpleFontSpec)obj).style == this.style)
+ && (((SimpleFontSpec)obj).size == this.size);
+ }
+ public int hashCode()
+ {
+ return family.hashCode() + style + size;
+ }
+ }
+
+ public Font getFont(AttributeSet attr)
+ {
+ String family = StyleConstants.getFontFamily(attr);
+ int style = Font.PLAIN;
+ if (StyleConstants.isBold(attr))
+ style += Font.BOLD;
+ if (StyleConstants.isItalic(attr))
+ style += Font.ITALIC;
+ int size = StyleConstants.getFontSize(attr);
+ return getFont(family, style, size);
+ }
+
+ public Font getFont(String family, int style, int size)
+ {
+ SimpleFontSpec spec = new SimpleFontSpec(family, style, size);
+ if (sharedFonts.containsKey(spec))
+ return (Font) sharedFonts.get(spec);
+ else
+ {
+ Font tmp = new Font(family, style, size);
+ sharedFonts.put(spec, tmp);
+ return tmp;
+ }
+ }
+
+ public FontMetrics getFontMetrics(Font f)
+ {
+ return Toolkit.getDefaultToolkit().getFontMetrics(f);
+ }
+
+ public Color getForeground(AttributeSet a)
+ {
+ return StyleConstants.getForeground(a);
+ }
+
+ public Color getBackground(AttributeSet a)
+ {
+ return StyleConstants.getBackground(a);
+ }
+
+ protected int getCompressionThreshold()
+ {
+ return compressionThreshold;
+ }
+
+ public static StyleContext getDefaultStyleContext()
+ {
+ if (defaultStyleContext == null)
+ defaultStyleContext = new StyleContext();
+ return defaultStyleContext;
+ }
+
+ public synchronized AttributeSet addAttribute(AttributeSet old, Object name,
+ Object value)
+ {
+ AttributeSet ret;
+ if (old.getAttributeCount() + 1 < getCompressionThreshold())
+ {
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.addAttribute(name, value);
+ reclaim(old);
+ ret = searchImmutableSet();
+ }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.addAttribute(name, value);
+ ret = mas;
+ }
+ return ret;
+ }
+
+ public synchronized AttributeSet addAttributes(AttributeSet old,
+ AttributeSet attributes)
+ {
+ AttributeSet ret;
+ if (old.getAttributeCount() + attributes.getAttributeCount()
+ < getCompressionThreshold())
+ {
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.addAttributes(attributes);
+ reclaim(old);
+ ret = searchImmutableSet();
+ }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.addAttributes(attributes);
+ ret = mas;
+ }
+ return ret;
+ }
+
+ public AttributeSet getEmptySet()
+ {
+ return SimpleAttributeSet.EMPTY;
+ }
+
+ public void reclaim(AttributeSet attributes)
+ {
+ cleanupPool();
+ }
+
+ public synchronized AttributeSet removeAttribute(AttributeSet old,
+ Object name)
+ {
+ AttributeSet ret;
+ if (old.getAttributeCount() - 1 <= getCompressionThreshold())
+ {
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttribute(name);
+ reclaim(old);
+ ret = searchImmutableSet();
+ }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttribute(name);
+ ret = mas;
+ }
+ return ret;
+ }
+
+ public synchronized AttributeSet removeAttributes(AttributeSet old,
+ AttributeSet attributes)
+ {
+ AttributeSet ret;
+ if (old.getAttributeCount() <= getCompressionThreshold())
+ {
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttributes(attributes);
+ reclaim(old);
+ ret = searchImmutableSet();
+ }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttributes(attributes);
+ ret = mas;
+ }
+ return ret;
+ }
+
+ public synchronized AttributeSet removeAttributes(AttributeSet old,
+ Enumeration<?> names)
+ {
+ AttributeSet ret;
+ if (old.getAttributeCount() <= getCompressionThreshold())
+ {
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttributes(names);
+ reclaim(old);
+ ret = searchImmutableSet();
+ }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttributes(names);
+ ret = mas;
+ }
+ return ret;
+ }
+
+ /**
+ * Gets the object previously registered with registerStaticAttributeKey.
+ *
+ * @param key - the key that was registered.
+ * @return the object previously registered with registerStaticAttributeKey.
+ */
+ public static Object getStaticAttribute(Object key)
+ {
+ if (key == null)
+ return null;
+ return readAttributeKeys.get(key);
+ }
+
+ /**
+ * Returns the String that key will be registered with
+ * registerStaticAttributeKey.
+ *
+ * @param key - the key that will be registered.
+ * @return the string the key will be registered with.
+ */
+ public static Object getStaticAttributeKey(Object key)
+ {
+ return key.getClass().getName() + "." + key.toString();
+ }
+
+ /**
+ * Reads a set of attributes from the given object input stream. This will
+ * attempt to restore keys that were static objects by considering only the
+ * keys that have were registered with registerStaticAttributeKey. The
+ * attributes retrieved will be placed into the given set.
+ *
+ * @param in - the stream to read from
+ * @param a - the set of attributes
+ * @throws ClassNotFoundException - may be encountered when reading from
+ * stream
+ * @throws IOException - any I/O error
+ */
+ public static void readAttributeSet(ObjectInputStream in,
+ MutableAttributeSet a)
+ throws ClassNotFoundException, IOException
+ {
+ int count = in.readInt();
+ for (int i = 0; i < count; i++)
+ {
+ Object key = in.readObject();
+ Object val = in.readObject();
+ if (readAttributeKeys != null)
+ {
+ Object staticKey = readAttributeKeys.get(key);
+ if (staticKey != null)
+ key = staticKey;
+ Object staticVal = readAttributeKeys.get(val);
+ if (staticVal != null)
+ val = staticVal;
+ }
+ a.addAttribute(key, val);
+ }
+ }
+
+ /**
+ * Serialize an attribute set in a way that is compatible with it
+ * being read in again by {@link #readAttributeSet(ObjectInputStream, MutableAttributeSet)}.
+ * In particular registered static keys are transformed properly.
+ *
+ * @param out - stream to write to
+ * @param a - the attribute set
+ * @throws IOException - any I/O error
+ */
+ public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a)
+ throws IOException
+ {
+ int count = a.getAttributeCount();
+ out.writeInt(count);
+ Enumeration e = a.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ Object key = e.nextElement();
+ // Write key.
+ if (key instanceof Serializable)
+ out.writeObject(key);
+ else
+ {
+ Object io = writeAttributeKeys.get(key);
+ if (io == null)
+ throw new NotSerializableException(key.getClass().getName()
+ + ", key: " + key);
+ out.writeObject(io);
+ }
+ // Write value.
+ Object val = a.getAttribute(key);
+ Object io = writeAttributeKeys.get(val);
+ if (val instanceof Serializable)
+ out.writeObject(io != null ? io : val);
+ else
+ {
+ if (io == null)
+ throw new NotSerializableException(val.getClass().getName());
+ out.writeObject(io);
+ }
+ }
+ }
+
+ /**
+ * Handles reading in the attributes.
+ * @see #readAttributeSet(ObjectInputStream, MutableAttributeSet)
+ *
+ * @param in - the stream to read from
+ * @param a - the set of attributes
+ * @throws ClassNotFoundException - may be encountered when reading from stream
+ * @throws IOException - any I/O error
+ */
+ public void readAttributes(ObjectInputStream in, MutableAttributeSet a)
+ throws ClassNotFoundException, IOException
+ {
+ readAttributeSet(in, a);
+ }
+
+ /**
+ * Handles writing of the given attributes.
+ * @see #writeAttributeSet(ObjectOutputStream, AttributeSet)
+ *
+ * @param out - stream to write to
+ * @param a - the attribute set
+ * @throws IOException - any I/O error
+ */
+ public void writeAttributes(ObjectOutputStream out, AttributeSet a)
+ throws IOException
+ {
+ writeAttributeSet(out, a);
+ }
+
+ /**
+ * Registers an attribute key as a well-known keys. When an attribute with
+ * such a key is written to a stream, a special syntax is used so that it
+ * can be recognized when it is read back in. All attribute keys defined
+ * in <code>StyleContext</code> are registered as static keys. If you define
+ * additional attribute keys that you want to exist as nonreplicated objects,
+ * then you should register them using this method.
+ *
+ * @param key the key to register as static attribute key
+ */
+ public static void registerStaticAttributeKey(Object key)
+ {
+ String io = key.getClass().getName() + "." + key.toString();
+ if (writeAttributeKeys == null)
+ writeAttributeKeys = new Hashtable();
+ if (readAttributeKeys == null)
+ readAttributeKeys = new Hashtable();
+ writeAttributeKeys.put(key, io);
+ readAttributeKeys.put(io, key);
+ }
+
+ /**
+ * Returns a string representation of this StyleContext.
+ *
+ * @return a string representation of this StyleContext
+ */
+ public String toString()
+ {
+ cleanupPool();
+ StringBuilder b = new StringBuilder();
+ Iterator i = attributeSetPool.keySet().iterator();
+ while (i.hasNext())
+ {
+ Object att = i.next();
+ b.append(att);
+ b.append('\n');
+ }
+ return b.toString();
+ }
+
+ /**
+ * Searches the AttributeSet pool and returns a pooled instance if available,
+ * or pool a new one.
+ *
+ * @return an immutable attribute set that equals the current search key
+ */
+ private AttributeSet searchImmutableSet()
+ {
+ SmallAttributeSet k = createSmallAttributeSet(search);
+ WeakReference ref = (WeakReference) attributeSetPool.get(k);
+ SmallAttributeSet a;
+ if (ref == null || (a = (SmallAttributeSet) ref.get()) == null)
+ {
+ a = k;
+ attributeSetPool.put(a, new WeakReference(a));
+ }
+ return a;
+ }
+
+ /**
+ * Cleans up the attribute set pool from entries that are no longer
+ * referenced.
+ */
+ private void cleanupPool()
+ {
+ // TODO: How else can we force cleaning up the WeakHashMap?
+ attributeSetPool.size();
+ }
+
+ /**
+ * Returns a MutableAttributeSet that holds a. If a itself is mutable,
+ * this returns a itself, otherwise it creates a new SimpleAtttributeSet
+ * via {@link #createLargeAttributeSet(AttributeSet)}.
+ *
+ * @param a the AttributeSet to create a mutable set for
+ *
+ * @return a mutable attribute set that corresponds to a
+ */
+ private MutableAttributeSet getMutableAttributeSet(AttributeSet a)
+ {
+ MutableAttributeSet mas;
+ if (a instanceof MutableAttributeSet)
+ mas = (MutableAttributeSet) a;
+ else
+ mas = createLargeAttributeSet(a);
+ return mas;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/StyledDocument.java b/libjava/classpath/javax/swing/text/StyledDocument.java
new file mode 100644
index 000000000..49e471c9c
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/StyledDocument.java
@@ -0,0 +1,140 @@
+/* StyledDcoument.java --
+ Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text;
+
+import java.awt.Color;
+import java.awt.Font;
+
+/**
+ * StyledDocument
+ * @author Andrew Selkirk
+ * @version 1.0
+ */
+public interface StyledDocument extends Document
+{
+ /**
+ * addStyle
+ * @param nm TODO
+ * @param parent TODO
+ * @returns Style
+ */
+ Style addStyle(String nm, Style parent);
+
+ /**
+ * removeStyle
+ * @param nm TODO
+ */
+ void removeStyle(String nm);
+
+ /**
+ * getStyle
+ * @param nm TODO
+ * @returns Style
+ */
+ Style getStyle(String nm);
+
+ /**
+ * setCharacterAttributes
+ * @param offset TODO
+ * @param length TODO
+ * @param set TODO
+ * @param replace TODO
+ */
+ void setCharacterAttributes(int offset, int length, AttributeSet set,
+ boolean replace);
+
+ /**
+ * setParagraphAttributes
+ * @param offset TODO
+ * @param length TODO
+ * @param set TODO
+ * @param replace TODO
+ */
+ void setParagraphAttributes(int offset, int length, AttributeSet set,
+ boolean replace);
+
+ /**
+ * getLogicalStyle
+ * @param position TODO
+ * @returns Style
+ */
+ Style getLogicalStyle(int position);
+
+ /**
+ * setLogicalStyle
+ * @param position TODO
+ * @param style TODO
+ */
+ void setLogicalStyle(int position, Style style);
+
+ /**
+ * getParagraphElement
+ * @param position TODO
+ * @returns Element
+ */
+ Element getParagraphElement(int position);
+
+ /**
+ * getCharacterElement
+ * @param position TODO
+ * @returns Element
+ */
+ Element getCharacterElement(int position);
+
+ /**
+ * getForeground
+ * @param set TODO
+ * @returns Color
+ */
+ Color getForeground(AttributeSet set);
+
+ /**
+ * getBackground
+ * @param set TODO
+ * @returns Color
+ */
+ Color getBackground(AttributeSet set);
+
+ /**
+ * getFont
+ * @param set TODO
+ * @returns Font
+ */
+ Font getFont(AttributeSet set);
+
+}
diff --git a/libjava/classpath/javax/swing/text/StyledEditorKit.java b/libjava/classpath/javax/swing/text/StyledEditorKit.java
new file mode 100644
index 000000000..744585f08
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/StyledEditorKit.java
@@ -0,0 +1,707 @@
+/* StyledEditorKit.java --
+ Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+
+import javax.swing.Action;
+import javax.swing.JEditorPane;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+
+/**
+ * An {@link EditorKit} that supports editing styled text.
+ *
+ * @author Andrew Selkirk
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public class StyledEditorKit extends DefaultEditorKit
+{
+ /** The serialVersionUID. */
+ private static final long serialVersionUID = 7002391892985555948L;
+
+ /**
+ * Toggles the underline attribute for the selected text.
+ */
+ public static class UnderlineAction extends StyledEditorKit.StyledTextAction
+ {
+ /**
+ * Creates an instance of <code>UnderlineAction</code>.
+ */
+ public UnderlineAction()
+ {
+ super("font-underline");
+ }
+
+ /**
+ * Performs the action.
+ *
+ * @param event the <code>ActionEvent</code> that describes the action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JEditorPane editor = getEditor(event);
+ StyledDocument doc = getStyledDocument(editor);
+ Element el = doc.getCharacterElement(editor.getSelectionStart());
+ boolean isUnderline = StyleConstants.isUnderline(el.getAttributes());
+ SimpleAttributeSet atts = new SimpleAttributeSet();
+ StyleConstants.setUnderline(atts, ! isUnderline);
+ setCharacterAttributes(editor, atts, false);
+ }
+ }
+
+ /**
+ * Toggles the italic attribute for the selected text.
+ */
+ public static class ItalicAction extends StyledEditorKit.StyledTextAction
+ {
+ /**
+ * Creates an instance of <code>ItalicAction</code>.
+ */
+ public ItalicAction()
+ {
+ super("font-italic");
+ }
+
+ /**
+ * Performs the action.
+ *
+ * @param event the <code>ActionEvent</code> that describes the action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JEditorPane editor = getEditor(event);
+ StyledDocument doc = getStyledDocument(editor);
+ Element el = doc.getCharacterElement(editor.getSelectionStart());
+ boolean isItalic = StyleConstants.isItalic(el.getAttributes());
+ SimpleAttributeSet atts = new SimpleAttributeSet();
+ StyleConstants.setItalic(atts, ! isItalic);
+ setCharacterAttributes(editor, atts, false);
+ }
+ }
+
+ /**
+ * Toggles the bold attribute for the selected text.
+ */
+ public static class BoldAction extends StyledEditorKit.StyledTextAction
+ {
+ /**
+ * Creates an instance of <code>BoldAction</code>.
+ */
+ public BoldAction()
+ {
+ super("font-bold");
+ }
+
+ /**
+ * Performs the action.
+ *
+ * @param event the <code>ActionEvent</code> that describes the action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ JEditorPane editor = getEditor(event);
+ StyledDocument doc = getStyledDocument(editor);
+ Element el = doc.getCharacterElement(editor.getSelectionStart());
+ boolean isBold = StyleConstants.isBold(el.getAttributes());
+ SimpleAttributeSet atts = new SimpleAttributeSet();
+ StyleConstants.setBold(atts, ! isBold);
+ setCharacterAttributes(editor, atts, false);
+ }
+ }
+
+ /**
+ * Sets the alignment attribute on the selected text.
+ */
+ public static class AlignmentAction extends StyledEditorKit.StyledTextAction
+ {
+ /**
+ * The aligment to set.
+ */
+ private int a;
+
+ /**
+ * Creates a new instance of <code>AlignmentAction</code> to set the
+ * alignment to <code>a</code>.
+ *
+ * @param nm the name of the Action
+ * @param a the alignment to set
+ */
+ public AlignmentAction(String nm, int a)
+ {
+ super(nm);
+ this.a = a;
+ }
+
+ /**
+ * Performs the action.
+ *
+ * @param event the <code>ActionEvent</code> that describes the action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ SimpleAttributeSet atts = new SimpleAttributeSet();
+ StyleConstants.setAlignment(atts, a);
+ setParagraphAttributes(getEditor(event), atts, false);
+ }
+ }
+
+ /**
+ * Sets the foreground color attribute on the selected text.
+ */
+ public static class ForegroundAction extends StyledEditorKit.StyledTextAction
+ {
+ /**
+ * The foreground color to set.
+ */
+ private Color fg;
+
+ /**
+ * Creates a new instance of <code>ForegroundAction</code> to set the
+ * foreground color to <code>fg</code>.
+ *
+ * @param nm the name of the Action
+ * @param fg the foreground color to set
+ */
+ public ForegroundAction(String nm, Color fg)
+ {
+ super(nm);
+ this.fg = fg;
+ }
+
+ /**
+ * Performs the action.
+ *
+ * @param event the <code>ActionEvent</code> that describes the action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ SimpleAttributeSet atts = new SimpleAttributeSet();
+ StyleConstants.setForeground(atts, fg);
+ setCharacterAttributes(getEditor(event), atts, false);
+ }
+ }
+
+ /**
+ * Sets the font size attribute on the selected text.
+ */
+ public static class FontSizeAction extends StyledEditorKit.StyledTextAction
+ {
+ /**
+ * The font size to set.
+ */
+ private int size;
+
+ /**
+ * Creates a new instance of <code>FontSizeAction</code> to set the
+ * font size to <code>size</code>.
+ *
+ * @param nm the name of the Action
+ * @param size the font size to set
+ */
+ public FontSizeAction(String nm, int size)
+ {
+ super(nm);
+ this.size = size;
+ }
+
+ /**
+ * Performs the action.
+ *
+ * @param event the <code>ActionEvent</code> that describes the action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ SimpleAttributeSet atts = new SimpleAttributeSet();
+ StyleConstants.setFontSize(atts, size);
+ setCharacterAttributes(getEditor(event), atts, false);
+ }
+ }
+
+ /**
+ * Sets the font family attribute on the selected text.
+ */
+ public static class FontFamilyAction extends StyledEditorKit.StyledTextAction
+ {
+ /**
+ * The font family to set.
+ */
+ private String family;
+
+ /**
+ * Creates a new instance of <code>FontFamilyAction</code> to set the
+ * font family to <code>family</code>.
+ *
+ * @param nm the name of the Action
+ * @param family the font family to set
+ */
+ public FontFamilyAction(String nm, String family)
+ {
+ super(nm);
+ this.family = family;
+ }
+
+ /**
+ * Performs the action.
+ *
+ * @param event the <code>ActionEvent</code> that describes the action
+ */
+ public void actionPerformed(ActionEvent event)
+ {
+ SimpleAttributeSet atts = new SimpleAttributeSet();
+ StyleConstants.setFontFamily(atts, family);
+ setCharacterAttributes(getEditor(event), atts, false);
+ }
+ }
+
+ /**
+ * The abstract superclass of all styled TextActions. This class
+ * provides some useful methods to manipulate the text attributes.
+ */
+ public abstract static class StyledTextAction extends TextAction
+ {
+ /**
+ * Creates a new instance of <code>StyledTextAction</code>.
+ *
+ * @param nm the name of the <code>StyledTextAction</code>
+ */
+ public StyledTextAction(String nm)
+ {
+ super(nm);
+ }
+
+ /**
+ * Returns the <code>JEditorPane</code> component from which the
+ * <code>ActionEvent</code> originated.
+ *
+ * @param event the <code>ActionEvent</code>
+ * @return the <code>JEditorPane</code> component from which the
+ * <code>ActionEvent</code> originated
+ */
+ protected final JEditorPane getEditor(ActionEvent event)
+ {
+ return (JEditorPane) getTextComponent(event);
+ }
+
+ /**
+ * Sets the specified character attributes on the currently selected
+ * text of <code>editor</code>. If <code>editor</code> does not have
+ * a selection, then the attributes are used as input attributes
+ * for newly inserted content.
+ *
+ * @param editor the <code>JEditorPane</code> component
+ * @param atts the text attributes to set
+ * @param replace if <code>true</code> the current attributes of the
+ * selection are replaces, otherwise they are merged
+ */
+ protected final void setCharacterAttributes(JEditorPane editor,
+ AttributeSet atts,
+ boolean replace)
+ {
+ int p0 = editor.getSelectionStart();
+ int p1 = editor.getSelectionEnd();
+ if (p0 != p1)
+ {
+ StyledDocument doc = getStyledDocument(editor);
+ doc.setCharacterAttributes(p0, p1 - p0, atts, replace);
+ }
+ // Update input attributes.
+ StyledEditorKit kit = getStyledEditorKit(editor);
+ MutableAttributeSet inputAtts = kit.getInputAttributes();
+ if (replace)
+ {
+ inputAtts.removeAttributes(inputAtts);
+ }
+ inputAtts.addAttributes(atts);
+ }
+
+ /**
+ * Returns the {@link StyledDocument} that is used by <code>editor</code>.
+ *
+ * @param editor the <code>JEditorPane</code> from which to get the
+ * <code>StyledDocument</code>
+ *
+ * @return the {@link StyledDocument} that is used by <code>editor</code>
+ */
+ protected final StyledDocument getStyledDocument(JEditorPane editor)
+ {
+ Document doc = editor.getDocument();
+ if (!(doc instanceof StyledDocument))
+ throw new AssertionError("The Document for StyledEditorKits is "
+ + "expected to be a StyledDocument.");
+
+ return (StyledDocument) doc;
+ }
+
+ /**
+ * Returns the {@link StyledEditorKit} that is used by <code>editor</code>.
+ *
+ * @param editor the <code>JEditorPane</code> from which to get the
+ * <code>StyledEditorKit</code>
+ *
+ * @return the {@link StyledEditorKit} that is used by <code>editor</code>
+ */
+ protected final StyledEditorKit getStyledEditorKit(JEditorPane editor)
+ {
+ EditorKit kit = editor.getEditorKit();
+ if (!(kit instanceof StyledEditorKit))
+ throw new AssertionError("The EditorKit for StyledDocuments is "
+ + "expected to be a StyledEditorKit.");
+
+ return (StyledEditorKit) kit;
+ }
+
+ /**
+ * Sets the specified character attributes on the paragraph that
+ * contains the currently selected
+ * text of <code>editor</code>. If <code>editor</code> does not have
+ * a selection, then the attributes are set on the paragraph that
+ * contains the current caret position.
+ *
+ * @param editor the <code>JEditorPane</code> component
+ * @param atts the text attributes to set
+ * @param replace if <code>true</code> the current attributes of the
+ * selection are replaces, otherwise they are merged
+ */
+ protected final void setParagraphAttributes(JEditorPane editor,
+ AttributeSet atts,
+ boolean replace)
+ {
+ Document doc = editor.getDocument();
+ if (doc instanceof StyledDocument)
+ {
+ StyledDocument styleDoc = (StyledDocument) editor.getDocument();
+ EditorKit kit = editor.getEditorKit();
+ if (!(kit instanceof StyledEditorKit))
+ {
+ StyledEditorKit styleKit = (StyledEditorKit) kit;
+ int start = editor.getSelectionStart();
+ int end = editor.getSelectionEnd();
+ int dot = editor.getCaret().getDot();
+ if (start == dot && end == dot)
+ {
+ // If there is no selection, then we only update the
+ // input attributes.
+ MutableAttributeSet inputAttributes =
+ styleKit.getInputAttributes();
+ inputAttributes.addAttributes(atts);
+ }
+ else
+ styleDoc.setParagraphAttributes(start, end, atts, replace);
+ }
+ else
+ throw new AssertionError("The EditorKit for StyledTextActions "
+ + "is expected to be a StyledEditorKit");
+ }
+ else
+ throw new AssertionError("The Document for StyledTextActions is "
+ + "expected to be a StyledDocument.");
+ }
+ }
+
+ /**
+ * A {@link ViewFactory} that is able to create {@link View}s for
+ * the <code>Element</code>s that are supported by
+ * <code>StyledEditorKit</code>, namely the following types of Elements:
+ *
+ * <ul>
+ * <li>{@link AbstractDocument#ContentElementName}</li>
+ * <li>{@link AbstractDocument#ParagraphElementName}</li>
+ * <li>{@link AbstractDocument#SectionElementName}</li>
+ * <li>{@link StyleConstants#ComponentElementName}</li>
+ * <li>{@link StyleConstants#IconElementName}</li>
+ * </ul>
+ */
+ static class StyledViewFactory
+ implements ViewFactory
+ {
+ /**
+ * Creates a {@link View} for the specified <code>Element</code>.
+ *
+ * @param element the <code>Element</code> to create a <code>View</code>
+ * for
+ * @return the <code>View</code> for the specified <code>Element</code>
+ * or <code>null</code> if the type of <code>element</code> is
+ * not supported
+ */
+ public View create(Element element)
+ {
+ String name = element.getName();
+ View view = null;
+ 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);
+ else
+ throw new AssertionError("Unknown Element type: "
+ + element.getClass().getName() + " : "
+ + name);
+ return view;
+ }
+ }
+
+ /**
+ * Keeps track of the caret position and updates the currentRun
+ * <code>Element</code> and the <code>inputAttributes</code>.
+ */
+ class CaretTracker
+ implements CaretListener
+ {
+ /**
+ * Notifies an update of the caret position.
+ *
+ * @param ev the event for the caret update
+ */
+ public void caretUpdate(CaretEvent ev)
+ {
+ Object source = ev.getSource();
+ if (!(source instanceof JTextComponent))
+ throw new AssertionError("CaretEvents are expected to come from a"
+ + "JTextComponent.");
+
+ JTextComponent text = (JTextComponent) source;
+ Document doc = text.getDocument();
+ if (!(doc instanceof StyledDocument))
+ throw new AssertionError("The Document used by StyledEditorKits is"
+ + "expected to be a StyledDocument");
+
+ StyledDocument styleDoc = (StyledDocument) doc;
+ currentRun = styleDoc.getCharacterElement(ev.getDot());
+ createInputAttributes(currentRun, inputAttributes);
+ }
+ }
+
+ /**
+ * Stores the <code>Element</code> at the current caret position. This
+ * is updated by {@link CaretTracker}.
+ */
+ Element currentRun;
+
+ /**
+ * The current input attributes. This is updated by {@link CaretTracker}.
+ */
+ MutableAttributeSet inputAttributes;
+
+ /**
+ * The CaretTracker that keeps track of the current input attributes, and
+ * the current character run Element.
+ */
+ CaretTracker caretTracker;
+
+ /**
+ * The ViewFactory for StyledEditorKits.
+ */
+ StyledViewFactory viewFactory;
+
+ /**
+ * Creates a new instance of <code>StyledEditorKit</code>.
+ */
+ public StyledEditorKit()
+ {
+ inputAttributes = new SimpleAttributeSet();
+ }
+
+ /**
+ * Creates an exact copy of this <code>StyledEditorKit</code>.
+ *
+ * @return an exact copy of this <code>StyledEditorKit</code>
+ */
+ public Object clone()
+ {
+ StyledEditorKit clone = (StyledEditorKit) super.clone();
+ // FIXME: Investigate which fields must be copied.
+ return clone;
+ }
+
+ /**
+ * Returns the <code>Action</code>s supported by this {@link EditorKit}.
+ * This includes the {@link BoldAction}, {@link ItalicAction} and
+ * {@link UnderlineAction} as well as the <code>Action</code>s supported
+ * by {@link DefaultEditorKit}.
+ *
+ * The other <code>Action</code>s of <code>StyledEditorKit</code> are not
+ * returned here, since they require a parameter and thus custom
+ * instantiation.
+ *
+ * @return the <code>Action</code>s supported by this {@link EditorKit}
+ */
+ public Action[] getActions()
+ {
+ Action[] actions1 = super.getActions();
+ 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);
+ }
+
+ /**
+ * Returns the current input attributes. These are automatically set on
+ * any newly inserted content, if not specified otherwise.
+ *
+ * @return the current input attributes
+ */
+ public MutableAttributeSet getInputAttributes()
+ {
+ return inputAttributes;
+ }
+
+ /**
+ * Returns the {@link Element} that represents the character run at the
+ * current caret position.
+ *
+ * @return the {@link Element} that represents the character run at the
+ * current caret position
+ */
+ public Element getCharacterAttributeRun()
+ {
+ return currentRun;
+ }
+
+ /**
+ * Creates the default {@link Document} supported by this
+ * <code>EditorKit</code>. This is an instance of
+ * {@link DefaultStyledDocument} in this case but may be overridden by
+ * subclasses.
+ *
+ * @return an instance of <code>DefaultStyledDocument</code>
+ */
+ public Document createDefaultDocument()
+ {
+ return new DefaultStyledDocument();
+ }
+
+ /**
+ * Installs this <code>EditorKit</code> on the specified {@link JEditorPane}.
+ * This basically involves setting up required listeners on the
+ * <code>JEditorPane</code>.
+ *
+ * @param component the <code>JEditorPane</code> to install this
+ * <code>EditorKit</code> on
+ */
+ public void install(JEditorPane component)
+ {
+ CaretTracker tracker = new CaretTracker();
+ component.addCaretListener(tracker);
+ }
+
+ /**
+ * Deinstalls this <code>EditorKit</code> from the specified
+ * {@link JEditorPane}. This basically involves removing all listeners from
+ * <code>JEditorPane</code> that have been set up by this
+ * <code>EditorKit</code>.
+ *
+ * @param component the <code>JEditorPane</code> from which to deinstall this
+ * <code>EditorKit</code>
+ */
+ public void deinstall(JEditorPane component)
+ {
+ CaretTracker t = caretTracker;
+ if (t != null)
+ component.removeCaretListener(t);
+ caretTracker = null;
+ }
+
+ /**
+ * Returns a {@link ViewFactory} that is able to create {@link View}s
+ * for {@link Element}s that are supported by this <code>EditorKit</code>,
+ * namely the following types of <code>Element</code>s:
+ *
+ * <ul>
+ * <li>{@link AbstractDocument#ContentElementName}</li>
+ * <li>{@link AbstractDocument#ParagraphElementName}</li>
+ * <li>{@link AbstractDocument#SectionElementName}</li>
+ * <li>{@link StyleConstants#ComponentElementName}</li>
+ * <li>{@link StyleConstants#IconElementName}</li>
+ * </ul>
+ *
+ * @return a {@link ViewFactory} that is able to create {@link View}s
+ * for {@link Element}s that are supported by this <code>EditorKit</code>
+ */
+ public ViewFactory getViewFactory()
+ {
+ if (viewFactory == null)
+ viewFactory = new StyledViewFactory();
+ return viewFactory;
+ }
+
+ /**
+ * Copies the text attributes from <code>element</code> to <code>set</code>.
+ * This is called everytime when the caret position changes to keep
+ * track of the current input attributes. The attributes in <code>set</code>
+ * are cleaned before adding the attributes of <code>element</code>.
+ *
+ * This method filters out attributes for element names, <code>Icon</code>s
+ * and <code>Component</code>s.
+ *
+ * @param element the <code>Element</code> from which to copy the text
+ * attributes
+ * @param set the inputAttributes to copy the attributes to
+ */
+ protected void createInputAttributes(Element element,
+ MutableAttributeSet set)
+ {
+ // FIXME: Filter out component, icon and element name attributes.
+ set.removeAttributes(set);
+ set.addAttributes(element.getAttributes());
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/TabExpander.java b/libjava/classpath/javax/swing/text/TabExpander.java
new file mode 100644
index 000000000..a1e4571cf
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/TabExpander.java
@@ -0,0 +1,43 @@
+/* TabExpander.java --
+ Copyright (C) 2004 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;
+
+public interface TabExpander
+{
+ float nextTabStop(float x, int tabOffset);
+}
diff --git a/libjava/classpath/javax/swing/text/TabSet.java b/libjava/classpath/javax/swing/text/TabSet.java
new file mode 100644
index 000000000..c32596536
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/TabSet.java
@@ -0,0 +1,210 @@
+/* TabSet.java --
+ Copyright (C) 2004, 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 gnu.java.lang.CPStringBuilder;
+
+import java.io.Serializable;
+
+/**
+ * A set of tab stops. Instances of this class are immutable.
+ */
+public class TabSet implements Serializable
+{
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 2367703481999080593L;
+
+ /** Storage for the tab stops. */
+ TabStop[] tabs;
+
+ /**
+ * Creates a new <code>TabSet</code> containing the specified tab stops.
+ *
+ * @param t the tab stops (<code>null</code> permitted).
+ */
+ public TabSet(TabStop[] t)
+ {
+ if (t != null)
+ tabs = (TabStop[]) t.clone();
+ else
+ tabs = new TabStop[0];
+ }
+
+ /**
+ * Returns the tab stop with the specified index.
+ *
+ * @param i the index.
+ *
+ * @return The tab stop.
+ *
+ * @throws IllegalArgumentException if <code>i</code> is not in the range
+ * <code>0</code> to <code>getTabCount() - 1</code>.
+ */
+ public TabStop getTab(int i)
+ {
+ if (i < 0 || i >= tabs.length)
+ throw new IllegalArgumentException("Index out of bounds.");
+ return tabs[i];
+ }
+
+ /**
+ * Returns the tab following the specified location.
+ *
+ * @param location the location.
+ *
+ * @return The tab following the specified location (or <code>null</code>).
+ */
+ public TabStop getTabAfter(float location)
+ {
+ int idx = getTabIndexAfter(location);
+ if (idx == -1)
+ return null;
+ else
+ return tabs[idx];
+ }
+
+ /**
+ * Returns the number of tab stops in this tab set.
+ *
+ * @return The number of tab stops in this tab set.
+ */
+ public int getTabCount()
+ {
+ return tabs.length;
+ }
+
+ /**
+ * Returns the index of the specified tab, or -1 if the tab is not found.
+ *
+ * @param tab the tab (<code>null</code> permitted).
+ *
+ * @return The index of the specified tab, or -1.
+ */
+ public int getTabIndex(TabStop tab)
+ {
+ for (int i = 0; i < tabs.length; ++i)
+ if (tabs[i] == tab)
+ return i;
+ return -1;
+ }
+
+ /**
+ * Returns the index of the tab at or after the specified location.
+ *
+ * @param location the tab location.
+ *
+ * @return The index of the tab stop, or -1.
+ */
+ public int getTabIndexAfter(float location)
+ {
+ for (int i = 0; i < tabs.length; i++)
+ {
+ if (location <= tabs[i].getPosition())
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Tests this <code>TabSet</code> for equality with an arbitrary object.
+ *
+ * @param obj the object (<code>null</code> permitted).
+ *
+ * @return <code>true</code> if this <code>TabSet</code> is equal to
+ * <code>obj</code>, and <code>false</code> otherwise.
+ *
+ * @since 1.5
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj == this)
+ return true;
+ if (!(obj instanceof TabSet))
+ return false;
+ TabSet that = (TabSet) obj;
+ int tabCount = getTabCount();
+ if (tabCount != that.getTabCount())
+ return false;
+ for (int i = 0; i < tabCount; i++)
+ {
+ if (!this.getTab(i).equals(that.getTab(i)))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a hash code for this <code>TabSet</code>.
+ *
+ * @return A hash code.
+ *
+ * @since 1.5
+ */
+ public int hashCode()
+ {
+ // this hash code won't match Sun's, but that shouldn't matter...
+ int result = 193;
+ int tabs = getTabCount();
+ for (int i = 0; i < tabs; i++)
+ {
+ TabStop t = getTab(i);
+ if (t != null)
+ result = 37 * result + t.hashCode();
+ }
+ return result;
+ }
+
+ /**
+ * Returns a string representation of this <code>TabSet</code>.
+ *
+ * @return A string representation of this <code>TabSet</code>.
+ */
+ public String toString()
+ {
+ CPStringBuilder sb = new CPStringBuilder();
+ sb.append("[ ");
+ for (int i = 0; i < tabs.length; ++i)
+ {
+ if (i != 0)
+ sb.append(" - ");
+ sb.append(tabs[i].toString());
+ }
+ sb.append(" ]");
+ return sb.toString();
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/TabStop.java b/libjava/classpath/javax/swing/text/TabStop.java
new file mode 100644
index 000000000..03a56116f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/TabStop.java
@@ -0,0 +1,190 @@
+/* TabStop.java --
+ Copyright (C) 2004, 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.io.Serializable;
+
+/**
+ * Represents a tab position in some text.
+ */
+public class TabStop implements Serializable
+{
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -5381995917363605058L;
+
+ public static final int ALIGN_LEFT = 0;
+ public static final int ALIGN_RIGHT = 1;
+ public static final int ALIGN_CENTER = 2;
+ public static final int ALIGN_DECIMAL = 4;
+ public static final int ALIGN_BAR = 5;
+
+ public static final int LEAD_NONE = 0;
+ public static final int LEAD_DOTS = 1;
+ public static final int LEAD_HYPHENS = 2;
+ public static final int LEAD_UNDERLINE = 3;
+ public static final int LEAD_THICKLINE = 4;
+ public static final int LEAD_EQUALS = 5;
+
+ float pos;
+ int align;
+ int leader;
+
+ /**
+ * Creates a new <code>TabStop</code> for the specified tab position.
+ *
+ * @param pos the tab position.
+ */
+ public TabStop(float pos)
+ {
+ this(pos, ALIGN_LEFT, LEAD_NONE);
+ }
+
+ /**
+ * Creates a new <code>TabStop</code> with the specified attributes.
+ *
+ * @param pos the tab position.
+ * @param align the alignment (one of {@link #ALIGN_LEFT},
+ * {@link #ALIGN_CENTER}, {@link #ALIGN_RIGHT}, {@link #ALIGN_DECIMAL}
+ * or {@link #ALIGN_BAR}).
+ * @param leader the leader (one of {@link #LEAD_NONE}, {@link #LEAD_DOTS},
+ * {@link #LEAD_EQUALS}, {@link #LEAD_HYPHENS}, {@link #LEAD_THICKLINE}
+ * or {@link #LEAD_UNDERLINE}).
+ */
+ public TabStop(float pos, int align, int leader)
+ {
+ this.pos = pos;
+ this.align = align;
+ this.leader = leader;
+ }
+
+ /**
+ * Tests this <code>TabStop</code> for equality with an arbitrary object.
+ *
+ * @param other the other object (<code>null</code> permitted).
+ *
+ * @return <code>true</code> if this <code>TabStop</code> is equal to
+ * the specified object, and <code>false</code> otherwise.
+ */
+ public boolean equals(Object other)
+ {
+ return (other != null)
+ && (other instanceof TabStop)
+ && (((TabStop)other).getPosition() == this.getPosition())
+ && (((TabStop)other).getLeader() == this.getLeader())
+ && (((TabStop)other).getAlignment() == this.getAlignment());
+ }
+
+ /**
+ * Returns the tab alignment. This should be one of {@link #ALIGN_LEFT},
+ * {@link #ALIGN_CENTER}, {@link #ALIGN_RIGHT}, {@link #ALIGN_DECIMAL} or
+ * {@link #ALIGN_BAR}.
+ *
+ * @return The tab alignment.
+ */
+ public int getAlignment()
+ {
+ return align;
+ }
+
+ /**
+ * Returns the leader type. This should be one of {@link #LEAD_NONE},
+ * {@link #LEAD_DOTS}, {@link #LEAD_EQUALS}, {@link #LEAD_HYPHENS},
+ * {@link #LEAD_THICKLINE} or {@link #LEAD_UNDERLINE}.
+ *
+ * @return The leader type.
+ */
+ public int getLeader()
+ {
+ return leader;
+ }
+
+ /**
+ * Returns the tab position.
+ *
+ * @return The tab position.
+ */
+ public float getPosition()
+ {
+ return pos;
+ }
+
+ /**
+ * Returns a hash code for this <code>TabStop</code>.
+ *
+ * @return A hash code.
+ */
+ public int hashCode()
+ {
+ return (int) pos + (int) leader + (int) align;
+ }
+
+ /**
+ * Returns a string describing this <code>TabStop</code>.
+ *
+ * @return A string describing this <code>TabStop</code>.
+ */
+ public String toString()
+ {
+ String prefix = "";
+ switch (align)
+ {
+ case ALIGN_RIGHT:
+ prefix = "right ";
+ break;
+
+ case ALIGN_CENTER:
+ prefix = "center ";
+ break;
+
+ case ALIGN_DECIMAL:
+ prefix = "decimal ";
+ break;
+
+ case ALIGN_BAR:
+ prefix = "bar ";
+ break;
+
+ default:
+ break;
+ }
+
+ return prefix + "tab @" + pos
+ + ((leader == LEAD_NONE) ? "" : " (w/leaders)");
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/TabableView.java b/libjava/classpath/javax/swing/text/TabableView.java
new file mode 100644
index 000000000..e50270bb6
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/TabableView.java
@@ -0,0 +1,44 @@
+/* TabableView.java --
+ Copyright (C) 2004 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;
+
+public interface TabableView
+{
+ float getPartialSpan(int p0, int p1);
+ float getTabbedSpan(float x, TabExpander expander);
+}
diff --git a/libjava/classpath/javax/swing/text/TableView.java b/libjava/classpath/javax/swing/text/TableView.java
new file mode 100644
index 000000000..bdf5004a9
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/TableView.java
@@ -0,0 +1,491 @@
+/* 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 abstract class TableView
+ extends BoxView
+{
+
+ /**
+ * A view implementation that renders a row of a <code>TableView</code>.
+ * 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 <code>TableRow</code>.
+ *
+ * @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);
+ int viewCount = getViewCount();
+ if (columnRequirements == null
+ || viewCount > columnRequirements.length)
+ {
+ columnRequirements = new SizeRequirements[viewCount];
+ for (int i = 0; i < columnRequirements.length; i++)
+ columnRequirements[i] = new SizeRequirements();
+ }
+ if (columnOffsets == null || columnOffsets.length < viewCount)
+ columnOffsets = new int[viewCount];
+ if (columnSpans == null || columnSpans.length < viewCount)
+ columnSpans = new int[viewCount];
+ layoutChanged(X_AXIS);
+ }
+
+ /**
+ * 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)
+ {
+ // 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.layoutMinorAxis(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 &lt;= 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
+ * <code>null</code> 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 <code>View</code> implementation.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ *
+ * @deprecated Table cells are now rendered by an arbitrary <code>View</code>
+ * 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
+ * <code>View</code> 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
+ * <code>View</code> 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
+ * <code>View</code> 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
+ * <code>View</code> 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
+ * <code>View</code> 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
+ * <code>View</code> 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.
+ */
+ SizeRequirements[] columnRequirements = new SizeRequirements[0];
+
+ /**
+ * Creates a new instance of <code>TableView</code>.
+ *
+ * @param el the element for which to create a table view
+ */
+ public TableView(Element el)
+ {
+ super(el, Y_AXIS);
+ }
+
+ /**
+ * 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
+ * <code>View</code> 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)
+ {
+ updateColumnRequirements();
+ SizeRequirements r = calculateMinorAxisRequirements(X_AXIS, null);
+ SizeRequirements.calculateTiledPositions(targetSpan, r, columnRequirements,
+ offsets, spans);
+ }
+
+ /**
+ * 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 <code>null</code> 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
+ * <code>null</code> 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);
+ }
+
+ /**
+ * Updates the column requirements.
+ */
+ private void updateColumnRequirements()
+ {
+ int rowCount = getViewCount();
+ for (int r = 0; r < rowCount; ++r)
+ {
+ TableRow row = (TableRow) getView(r);
+ int columnCount = row.getViewCount();
+ for (int c = 0; c < columnCount; ++c)
+ {
+ View cell = row.getView(c);
+ SizeRequirements cr = columnRequirements[c];
+ cr.minimum = Math.max(cr.minimum, (int) cell.getMinimumSpan(X_AXIS));
+ cr.preferred = Math.max(cr.preferred,
+ (int) cell.getPreferredSpan(X_AXIS));
+ cr.maximum = Math.max(cr.maximum, (int) cell.getMaximumSpan(X_AXIS));
+ }
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/TextAction.java b/libjava/classpath/javax/swing/text/TextAction.java
new file mode 100644
index 000000000..70e19ef1d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/TextAction.java
@@ -0,0 +1,240 @@
+/* TextAction.java --
+ Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Component;
+import java.awt.KeyboardFocusManager;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+/**
+ * TextAction
+ * @author Andrew Selkirk
+ */
+public abstract class TextAction extends AbstractAction
+{
+ /**
+ * Constructor TextAction
+ * @param name TODO
+ */
+ public TextAction(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Returns the <code>JTextComponent</code> object associated with the given
+ * <code>ActionEvent</code>. If the source of the event is not a
+ * <code>JTextComponent</code> the currently focused text component is returned.
+ *
+ * @param event the action event
+ *
+ * @return the <code>JTextComponent</code>
+ */
+ protected final JTextComponent getTextComponent(ActionEvent event)
+ {
+ JTextComponent target = null;
+ if (event != null)
+ {
+ Object source = event.getSource();
+ if (source instanceof JTextComponent)
+ target = (JTextComponent) source;
+ }
+ if (target == null)
+ target = getFocusedComponent();
+ return target;
+ }
+
+ /**
+ * Creates a new array of <code>Action</code> containing both given arrays.
+ *
+ * @param list1 the first action array
+ * @param list2 the second action array
+ *
+ * @return the augmented array of actions
+ */
+ public static final Action[] augmentList(Action[] list1, Action[] list2)
+ {
+ HashMap<Object,Action> actions = new HashMap<Object,Action>();
+
+ for (int i = 0; i < list1.length; ++i)
+ {
+ Action a = list1[i];
+ Object name = a.getValue(Action.NAME);
+ actions.put(name != null ? name : "", a);
+ }
+
+ for (int i = 0; i < list2.length; ++i)
+ {
+ Action a = list2[i];
+ Object name = a.getValue(Action.NAME);
+ actions.put(name != null ? name : "", a);
+ }
+ Action[] augmented = new Action[actions.size()];
+
+ int i = 0;
+ for (Iterator<Action> it = actions.values().iterator(); it.hasNext(); i++)
+ augmented[i] = it.next();
+ return augmented;
+
+ }
+
+ /**
+ * Returns the current focused <code>JTextComponent</code> object.
+ *
+ * @return the <code>JTextComponent</code>
+ */
+ protected final JTextComponent getFocusedComponent()
+ {
+ KeyboardFocusManager kfm =
+ KeyboardFocusManager.getCurrentKeyboardFocusManager();
+ Component focused = kfm.getPermanentFocusOwner();
+ JTextComponent textComp = null;
+ if (focused instanceof JTextComponent)
+ textComp = (JTextComponent) focused;
+ return textComp;
+ }
+
+ /** Abstract helper class which implements everything needed for an
+ * Action implementation in <code>DefaultEditorKit</code> which
+ * does horizontal movement (and selection).
+ */
+ abstract static class HorizontalMovementAction extends TextAction
+ {
+ int dir;
+
+ HorizontalMovementAction(String name, int direction)
+ {
+ super(name);
+ dir = direction;
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ try
+ {
+ if (t != null)
+ {
+ int offs
+ = Utilities.getNextVisualPositionFrom(t,
+ t.getCaretPosition(),
+ dir);
+
+ Caret c = t.getCaret();
+
+ actionPerformedImpl(c, offs);
+
+ c.setMagicCaretPosition(t.modelToView(offs).getLocation());
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ throw
+ (InternalError) new InternalError("Illegal offset").initCause(ble);
+ }
+
+ }
+
+ protected abstract void actionPerformedImpl(Caret c, int offs)
+ throws BadLocationException;
+ }
+
+ /** Abstract helper class which implements everything needed for an
+ * Action implementation in <code>DefaultEditorKit</code> which
+ * does vertical movement (and selection).
+ */
+ abstract static class VerticalMovementAction extends TextAction
+ {
+ int dir;
+
+ VerticalMovementAction(String name, int direction)
+ {
+ super(name);
+ dir = direction;
+ }
+
+ public void actionPerformed(ActionEvent event)
+ {
+ JTextComponent t = getTextComponent(event);
+ try
+ {
+ if (t != null)
+ {
+ Caret c = t.getCaret();
+ // The magic caret position may be null when the caret
+ // has not moved yet.
+ Point mcp = c.getMagicCaretPosition();
+
+ int pos;
+ if (mcp != null)
+ {
+ mcp.y = t.modelToView(c.getDot()).y;
+ pos = t.viewToModel(mcp);
+ }
+ else
+ pos = c.getDot();
+
+ pos = Utilities.getNextVisualPositionFrom(t,
+ t.getCaretPosition(),
+ dir);
+
+ if (pos > -1)
+ actionPerformedImpl(c, pos);
+ }
+ }
+ catch(BadLocationException ble)
+ {
+ throw
+ (InternalError) new InternalError("Illegal offset").initCause(ble);
+ }
+ }
+
+ protected abstract void actionPerformedImpl(Caret c, int offs)
+ throws BadLocationException;
+
+ }
+
+
+}
diff --git a/libjava/classpath/javax/swing/text/Utilities.java b/libjava/classpath/javax/swing/text/Utilities.java
new file mode 100644
index 000000000..622139232
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/Utilities.java
@@ -0,0 +1,730 @@
+/* Utilities.java --
+ Copyright (C) 2004, 2005, 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.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.text.BreakIterator;
+
+import javax.swing.text.Position.Bias;
+
+/**
+ * A set of utilities to deal with text. This is used by several other classes
+ * inside this package.
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ * @author Robert Schuster (robertschuster@fsfe.org)
+ */
+public class Utilities
+{
+
+ /**
+ * Creates a new <code>Utilities</code> object.
+ */
+ public Utilities()
+ {
+ // Nothing to be done here.
+ }
+
+ /**
+ * Draws the given text segment. Contained tabs and newline characters
+ * are taken into account. Tabs are expanded using the
+ * specified {@link TabExpander}.
+ *
+ *
+ * The X and Y coordinates denote the start of the <em>baseline</em> where
+ * the text should be drawn.
+ *
+ * @param s the text fragment to be drawn.
+ * @param x the x position for drawing.
+ * @param y the y position for drawing.
+ * @param g the {@link Graphics} context for drawing.
+ * @param e the {@link TabExpander} which specifies the Tab-expanding
+ * technique.
+ * @param startOffset starting offset in the text.
+ * @return the x coordinate at the end of the drawn text.
+ */
+ public static final int drawTabbedText(Segment s, int x, int y, Graphics g,
+ TabExpander e, int startOffset)
+ {
+ // This buffers the chars to be drawn.
+ char[] buffer = s.array;
+
+ // The font metrics of the current selected font.
+ FontMetrics metrics = g.getFontMetrics();
+
+ int ascent = metrics.getAscent();
+
+ // The current x and y pixel coordinates.
+ int pixelX = x;
+
+ int pos = s.offset;
+ int len = 0;
+
+ int end = s.offset + s.count;
+
+ for (int offset = s.offset; offset < end; ++offset)
+ {
+ char c = buffer[offset];
+ switch (c)
+ {
+ case '\t':
+ if (len > 0) {
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ len = 0;
+ }
+ pos = offset+1;
+ if (e != null)
+ pixelX = (int) e.nextTabStop((float) pixelX, startOffset + offset
+ - s.offset);
+ else
+ pixelX += metrics.charWidth(' ');
+ x = pixelX;
+ break;
+ case '\n':
+ case '\r':
+ if (len > 0) {
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ len = 0;
+ }
+ x = pixelX;
+ break;
+ default:
+ len += 1;
+ }
+ }
+
+ if (len > 0)
+ {
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ }
+
+ return pixelX;
+ }
+
+ /**
+ * Determines the width, that the given text <code>s</code> would take
+ * if it was printed with the given {@link java.awt.FontMetrics} on the
+ * specified screen position.
+ * @param s the text fragment
+ * @param metrics the font metrics of the font to be used
+ * @param x the x coordinate of the point at which drawing should be done
+ * @param e the {@link TabExpander} to be used
+ * @param startOffset the index in <code>s</code> where to start
+ * @returns the width of the given text s. This takes tabs and newlines
+ * into account.
+ */
+ public static final int getTabbedTextWidth(Segment s, FontMetrics metrics,
+ int x, TabExpander e,
+ int startOffset)
+ {
+ // This buffers the chars to be drawn.
+ char[] buffer = s.array;
+
+ // The current x coordinate.
+ int pixelX = x;
+
+ // The current maximum width.
+ int maxWidth = 0;
+
+ int end = s.offset + s.count;
+ int count = 0;
+ for (int offset = s.offset; offset < end; offset++)
+ {
+ switch (buffer[offset])
+ {
+ case '\t':
+ // In case we have a tab, we just 'jump' over the tab.
+ // When we have no tab expander we just use the width of 'm'.
+ if (e != null)
+ pixelX = (int) e.nextTabStop(pixelX,
+ startOffset + offset - s.offset);
+ else
+ pixelX += metrics.charWidth(' ');
+ break;
+ case '\n':
+ // In case we have a newline, we must 'draw'
+ // the buffer and jump on the next line.
+ pixelX += metrics.charsWidth(buffer, offset - count, count);
+ count = 0;
+ break;
+ default:
+ count++;
+ }
+ }
+
+ // Take the last line into account.
+ pixelX += metrics.charsWidth(buffer, end - count, count);
+
+ return pixelX - x;
+ }
+
+ /**
+ * Provides a facility to map screen coordinates into a model location. For a
+ * given text fragment and start location within this fragment, this method
+ * determines the model location so that the resulting fragment fits best
+ * into the span <code>[x0, x]</code>.
+ *
+ * The parameter <code>round</code> controls which model location is returned
+ * if the view coordinates are on a character: If <code>round</code> is
+ * <code>true</code>, then the result is rounded up to the next character, so
+ * that the resulting fragment is the smallest fragment that is larger than
+ * the specified span. If <code>round</code> is <code>false</code>, then the
+ * resulting fragment is the largest fragment that is smaller than the
+ * specified span.
+ *
+ * @param s the text segment
+ * @param fm the font metrics to use
+ * @param x0 the starting screen location
+ * @param x the target screen location at which the requested fragment should
+ * end
+ * @param te the tab expander to use; if this is <code>null</code>, TABs are
+ * expanded to one space character
+ * @param p0 the starting model location
+ * @param round if <code>true</code> round up to the next location, otherwise
+ * round down to the current location
+ *
+ * @return the model location, so that the resulting fragment fits within the
+ * specified span
+ */
+ public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
+ int x, TabExpander te, int p0,
+ boolean round)
+ {
+ int found = s.count;
+ int currentX = x0;
+ int nextX = currentX;
+
+ int end = s.offset + s.count;
+ for (int pos = s.offset; pos < end && found == s.count; pos++)
+ {
+ char nextChar = s.array[pos];
+
+ if (nextChar != '\t')
+ nextX += fm.charWidth(nextChar);
+ else
+ {
+ if (te == null)
+ nextX += fm.charWidth(' ');
+ else
+ nextX += ((int) te.nextTabStop(nextX, p0 + pos - s.offset));
+ }
+
+ if (x >= currentX && x < nextX)
+ {
+ // Found position.
+ if ((! round) || ((x - currentX) < (nextX - x)))
+ {
+ found = pos - s.offset;
+ }
+ else
+ {
+ found = pos + 1 - s.offset;
+ }
+ }
+ currentX = nextX;
+ }
+
+ return found;
+ }
+
+ /**
+ * Provides a facility to map screen coordinates into a model location. For a
+ * given text fragment and start location within this fragment, this method
+ * determines the model location so that the resulting fragment fits best
+ * into the span <code>[x0, x]</code>.
+ *
+ * This method rounds up to the next location, so that the resulting fragment
+ * will be the smallest fragment of the text, that is greater than the
+ * specified span.
+ *
+ * @param s the text segment
+ * @param fm the font metrics to use
+ * @param x0 the starting screen location
+ * @param x the target screen location at which the requested fragment should
+ * end
+ * @param te the tab expander to use; if this is <code>null</code>, TABs are
+ * expanded to one space character
+ * @param p0 the starting model location
+ *
+ * @return the model location, so that the resulting fragment fits within the
+ * specified span
+ */
+ public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
+ int x, TabExpander te, int p0)
+ {
+ return getTabbedTextOffset(s, fm, x0, x, te, p0, true);
+ }
+
+ /**
+ * Finds the start of the next word for the given offset.
+ *
+ * @param c
+ * the text component
+ * @param offs
+ * the offset in the document
+ * @return the location in the model of the start of the next word.
+ * @throws BadLocationException
+ * if the offset is invalid.
+ */
+ public static final int getNextWord(JTextComponent c, int offs)
+ throws BadLocationException
+ {
+ if (offs < 0 || offs > (c.getText().length() - 1))
+ throw new BadLocationException("invalid offset specified", offs);
+ String text = c.getText();
+ BreakIterator wb = BreakIterator.getWordInstance();
+ wb.setText(text);
+
+ int last = wb.following(offs);
+ int current = wb.next();
+ int cp;
+
+ while (current != BreakIterator.DONE)
+ {
+ for (int i = last; i < current; i++)
+ {
+ cp = text.codePointAt(i);
+
+ // Return the last found bound if there is a letter at the current
+ // location or is not whitespace (meaning it is a number or
+ // punctuation). The first case means that 'last' denotes the
+ // beginning of a word while the second case means it is the start
+ // of something else.
+ if (Character.isLetter(cp)
+ || !Character.isWhitespace(cp))
+ return last;
+ }
+ last = current;
+ current = wb.next();
+ }
+
+ throw new BadLocationException("no more words", offs);
+ }
+
+ /**
+ * Finds the start of the previous word for the given offset.
+ *
+ * @param c
+ * the text component
+ * @param offs
+ * the offset in the document
+ * @return the location in the model of the start of the previous word.
+ * @throws BadLocationException
+ * if the offset is invalid.
+ */
+ public static final int getPreviousWord(JTextComponent c, int offs)
+ throws BadLocationException
+ {
+ String text = c.getText();
+
+ if (offs <= 0 || offs > text.length())
+ throw new BadLocationException("invalid offset specified", offs);
+
+ BreakIterator wb = BreakIterator.getWordInstance();
+ wb.setText(text);
+ int last = wb.preceding(offs);
+ int current = wb.previous();
+ int cp;
+
+ while (current != BreakIterator.DONE)
+ {
+ for (int i = last; i < offs; i++)
+ {
+ cp = text.codePointAt(i);
+
+ // Return the last found bound if there is a letter at the current
+ // location or is not whitespace (meaning it is a number or
+ // punctuation). The first case means that 'last' denotes the
+ // beginning of a word while the second case means it is the start
+ // of some else.
+ if (Character.isLetter(cp)
+ || !Character.isWhitespace(cp))
+ return last;
+ }
+ last = current;
+ current = wb.previous();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Finds the start of a word for the given location.
+ * @param c the text component
+ * @param offs the offset location
+ * @return the location of the word beginning
+ * @throws BadLocationException if the offset location is invalid
+ */
+ public static final int getWordStart(JTextComponent c, int offs)
+ throws BadLocationException
+ {
+ String text = c.getText();
+
+ if (offs < 0 || offs > text.length())
+ throw new BadLocationException("invalid offset specified", offs);
+
+ BreakIterator wb = BreakIterator.getWordInstance();
+ wb.setText(text);
+
+ if (wb.isBoundary(offs))
+ return offs;
+
+ return wb.preceding(offs);
+ }
+
+ /**
+ * Finds the end of a word for the given location.
+ * @param c the text component
+ * @param offs the offset location
+ * @return the location of the word end
+ * @throws BadLocationException if the offset location is invalid
+ */
+ public static final int getWordEnd(JTextComponent c, int offs)
+ throws BadLocationException
+ {
+ if (offs < 0 || offs >= c.getText().length())
+ throw new BadLocationException("invalid offset specified", offs);
+
+ String text = c.getText();
+ BreakIterator wb = BreakIterator.getWordInstance();
+ wb.setText(text);
+ return wb.following(offs);
+ }
+
+ /**
+ * Get the model position of the end of the row that contains the
+ * specified model position. Return null if the given JTextComponent
+ * does not have a size.
+ * @param c the JTextComponent
+ * @param offs the model position
+ * @return the model position of the end of the row containing the given
+ * offset
+ * @throws BadLocationException if the offset is invalid
+ */
+ public static final int getRowEnd(JTextComponent c, int offs)
+ throws BadLocationException
+ {
+ String text = c.getText();
+ if (text == null)
+ return -1;
+
+ // Do a binary search for the smallest position X > offs
+ // such that that character at positino X is not on the same
+ // line as the character at position offs
+ int high = offs + ((text.length() - 1 - offs) / 2);
+ int low = offs;
+ int oldHigh = text.length() + 1;
+ while (true)
+ {
+ if (c.modelToView(high).y != c.modelToView(offs).y)
+ {
+ oldHigh = high;
+ high = low + ((high + 1 - low) / 2);
+ if (oldHigh == high)
+ return high - 1;
+ }
+ else
+ {
+ low = high;
+ high += ((oldHigh - high) / 2);
+ if (low == high)
+ return low;
+ }
+ }
+ }
+
+ /**
+ * Get the model position of the start of the row that contains the specified
+ * model position. Return null if the given JTextComponent does not have a
+ * size.
+ *
+ * @param c the JTextComponent
+ * @param offs the model position
+ * @return the model position of the start of the row containing the given
+ * offset
+ * @throws BadLocationException if the offset is invalid
+ */
+ public static final int getRowStart(JTextComponent c, int offs)
+ throws BadLocationException
+ {
+ String text = c.getText();
+ if (text == null)
+ return -1;
+
+ // Do a binary search for the greatest position X < offs
+ // such that the character at position X is not on the same
+ // row as the character at position offs
+ int high = offs;
+ int low = 0;
+ int oldLow = 0;
+ while (true)
+ {
+ if (c.modelToView(low).y != c.modelToView(offs).y)
+ {
+ oldLow = low;
+ low = high - ((high + 1 - low) / 2);
+ if (oldLow == low)
+ return low + 1;
+ }
+ else
+ {
+ high = low;
+ low -= ((low - oldLow) / 2);
+ if (low == high)
+ return low;
+ }
+ }
+ }
+
+ /**
+ * Determine where to break the text in the given Segment, attempting to find
+ * a word boundary.
+ * @param s the Segment that holds the text
+ * @param metrics the font metrics used for calculating the break point
+ * @param x0 starting view location representing the start of the text
+ * @param x the target view location
+ * @param e the TabExpander used for expanding tabs (if this is null tabs
+ * are expanded to 1 space)
+ * @param startOffset the offset in the Document of the start of the text
+ * @return the offset at which we should break the text
+ */
+ public static final int getBreakLocation(Segment s, FontMetrics metrics,
+ int x0, int x, TabExpander e,
+ int startOffset)
+ {
+ int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset,
+ false);
+ int breakLoc = mark;
+ // If mark is equal to the end of the string, just use that position.
+ if (mark < s.count - 1)
+ {
+ for (int i = s.offset + mark; i >= s.offset; i--)
+ {
+ char ch = s.array[i];
+ if (ch < 256)
+ {
+ // For ASCII simply scan backwards for whitespace.
+ if (Character.isWhitespace(ch))
+ {
+ breakLoc = i - s.offset + 1;
+ break;
+ }
+ }
+ else
+ {
+ // Only query BreakIterator for complex chars.
+ BreakIterator bi = BreakIterator.getLineInstance();
+ bi.setText(s);
+ int pos = bi.preceding(i + 1);
+ if (pos > s.offset)
+ {
+ breakLoc = breakLoc - s.offset;
+ }
+ break;
+ }
+ }
+ }
+ return breakLoc;
+ }
+
+ /**
+ * Returns the paragraph element in the text component <code>c</code> at
+ * the specified location <code>offset</code>.
+ *
+ * @param c the text component
+ * @param offset the offset of the paragraph element to return
+ *
+ * @return the paragraph element at <code>offset</code>
+ */
+ public static final Element getParagraphElement(JTextComponent c, int offset)
+ {
+ Document doc = c.getDocument();
+ Element par = null;
+ if (doc instanceof StyledDocument)
+ {
+ StyledDocument styledDoc = (StyledDocument) doc;
+ par = styledDoc.getParagraphElement(offset);
+ }
+ else
+ {
+ Element root = c.getDocument().getDefaultRootElement();
+ int parIndex = root.getElementIndex(offset);
+ par = root.getElement(parIndex);
+ }
+ return par;
+ }
+
+ /**
+ * Returns the document position that is closest above to the specified x
+ * coordinate in the row containing <code>offset</code>.
+ *
+ * @param c the text component
+ * @param offset the offset
+ * @param x the x coordinate
+ *
+ * @return the document position that is closest above to the specified x
+ * coordinate in the row containing <code>offset</code>
+ *
+ * @throws BadLocationException if <code>offset</code> is not a valid offset
+ */
+ public static final int getPositionAbove(JTextComponent c, int offset, int x)
+ throws BadLocationException
+ {
+ int offs = getRowStart(c, offset);
+
+ if(offs == -1)
+ return -1;
+
+ // Effectively calculates the y value of the previous line.
+ Point pt = c.modelToView(offs-1).getLocation();
+
+ pt.x = x;
+
+ // Calculate a simple fitting offset.
+ offs = c.viewToModel(pt);
+
+ // Find out the real x positions of the calculated character and its
+ // neighbour.
+ int offsX = c.modelToView(offs).getLocation().x;
+ int offsXNext = c.modelToView(offs+1).getLocation().x;
+
+ // Chose the one which is nearer to us and return its offset.
+ if (Math.abs(offsX-x) <= Math.abs(offsXNext-x))
+ return offs;
+ else
+ return offs+1;
+ }
+
+ /**
+ * Returns the document position that is closest below to the specified x
+ * coordinate in the row containing <code>offset</code>.
+ *
+ * @param c the text component
+ * @param offset the offset
+ * @param x the x coordinate
+ *
+ * @return the document position that is closest above to the specified x
+ * coordinate in the row containing <code>offset</code>
+ *
+ * @throws BadLocationException if <code>offset</code> is not a valid offset
+ */
+ public static final int getPositionBelow(JTextComponent c, int offset, int x)
+ throws BadLocationException
+ {
+ int offs = getRowEnd(c, offset);
+
+ if(offs == -1)
+ return -1;
+
+ Point pt = null;
+
+ // Note: Some views represent the position after the last
+ // typed character others do not. Converting offset 3 in "a\nb"
+ // in a PlainView will return a valid rectangle while in a
+ // WrappedPlainView this will throw a BadLocationException.
+ // This behavior has been observed in the RI.
+ try
+ {
+ // Effectively calculates the y value of the next line.
+ pt = c.modelToView(offs+1).getLocation();
+ }
+ catch(BadLocationException ble)
+ {
+ return offset;
+ }
+
+ pt.x = x;
+
+ // Calculate a simple fitting offset.
+ offs = c.viewToModel(pt);
+
+ if (offs == c.getDocument().getLength())
+ return offs;
+
+ // Find out the real x positions of the calculated character and its
+ // neighbour.
+ int offsX = c.modelToView(offs).getLocation().x;
+ int offsXNext = c.modelToView(offs+1).getLocation().x;
+
+ // Chose the one which is nearer to us and return its offset.
+ if (Math.abs(offsX-x) <= Math.abs(offsXNext-x))
+ return offs;
+ else
+ return offs+1;
+ }
+
+ /** This is an internal helper method which is used by the
+ * <code>javax.swing.text</code> package. It simply delegates the
+ * call to a method with the same name on the <code>NavigationFilter</code>
+ * of the provided <code>JTextComponent</code> (if it has one) or its UI.
+ *
+ * If the underlying method throws a <code>BadLocationException</code> it
+ * will be swallowed and the initial offset is returned.
+ */
+ static int getNextVisualPositionFrom(JTextComponent t, int offset, int direction)
+ {
+ NavigationFilter nf = t.getNavigationFilter();
+
+ try
+ {
+ return (nf != null)
+ ? nf.getNextVisualPositionFrom(t,
+ offset,
+ Bias.Forward,
+ direction,
+ new Position.Bias[1])
+ : t.getUI().getNextVisualPositionFrom(t,
+ offset,
+ Bias.Forward,
+ direction,
+ new Position.Bias[1]);
+ }
+ catch (BadLocationException ble)
+ {
+ return offset;
+ }
+
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/View.java b/libjava/classpath/javax/swing/text/View.java
new file mode 100644
index 000000000..e3c795735
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/View.java
@@ -0,0 +1,881 @@
+/* View.java --
+ Copyright (C) 2002, 2004, 2005, 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.Container;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+
+public abstract class View implements SwingConstants
+{
+ public static final int BadBreakWeight = 0;
+ public static final int ExcellentBreakWeight = 2000;
+ public static final int ForcedBreakWeight = 3000;
+ public static final int GoodBreakWeight = 1000;
+
+ public static final int X_AXIS = 0;
+ public static final int Y_AXIS = 1;
+
+ private Element elt;
+ private View parent;
+
+ /**
+ * Creates a new <code>View</code> instance.
+ *
+ * @param elem an <code>Element</code> value
+ */
+ public View(Element elem)
+ {
+ elt = elem;
+ }
+
+ public abstract void paint(Graphics g, Shape s);
+
+ /**
+ * Sets the parent for this view. This is the first method that is beeing
+ * called on a view to setup the view hierarchy. This is also the last method
+ * beeing called when the view is disconnected from the view hierarchy, in
+ * this case <code>parent</code> is null.
+ *
+ * If <code>parent</code> is <code>null</code>, a call to this method also
+ * calls <code>setParent</code> on the children, thus disconnecting them from
+ * the view hierarchy. That means that super must be called when this method
+ * is overridden.
+ *
+ * @param parent the parent to set, <code>null</code> when this view is
+ * beeing disconnected from the view hierarchy
+ */
+ public void setParent(View parent)
+ {
+ if (parent == null)
+ {
+ int numChildren = getViewCount();
+ for (int i = 0; i < numChildren; i++)
+ {
+ View child = getView(i);
+ // It is important that we only reset the parent on views that
+ // actually belong to us. In FlowView the child may already be
+ // reparented.
+ if (child.getParent() == this)
+ child.setParent(null);
+ }
+ }
+
+ this.parent = parent;
+ }
+
+ public View getParent()
+ {
+ return parent;
+ }
+
+ public Container getContainer()
+ {
+ View parent = getParent();
+ if (parent == null)
+ return null;
+ else
+ return parent.getContainer();
+ }
+
+ public Document getDocument()
+ {
+ return getElement().getDocument();
+ }
+
+ public Element getElement()
+ {
+ return elt;
+ }
+
+ /**
+ * Returns the preferred span along the specified axis. Normally the view is
+ * rendered with the span returned here if that is possible.
+ *
+ * @param axis the axis
+ *
+ * @return the preferred span along the specified axis
+ */
+ public abstract float getPreferredSpan(int axis);
+
+ /**
+ * Returns the resize weight of this view. A value of <code>0</code> or less
+ * means this view is not resizeable. Positive values make the view
+ * resizeable. The default implementation returns <code>0</code>
+ * unconditionally.
+ *
+ * @param axis the axis
+ *
+ * @return the resizability of this view along the specified axis
+ */
+ public int getResizeWeight(int axis)
+ {
+ return 0;
+ }
+
+ /**
+ * Returns the maximum span along the specified axis. The default
+ * implementation will forward to
+ * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)}
+ * returns a value > 0, in which case this returns {@link Integer#MIN_VALUE}.
+ *
+ * @param axis the axis
+ *
+ * @return the maximum span along the specified axis
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float max = Integer.MAX_VALUE;
+ if (getResizeWeight(axis) <= 0)
+ max = getPreferredSpan(axis);
+ return max;
+ }
+
+ /**
+ * Returns the minimum span along the specified axis. The default
+ * implementation will forward to
+ * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)}
+ * returns a value > 0, in which case this returns <code>0</code>.
+ *
+ * @param axis the axis
+ *
+ * @return the minimum span along the specified axis
+ */
+ public float getMinimumSpan(int axis)
+ {
+ float min = 0;
+ if (getResizeWeight(axis) <= 0)
+ min = getPreferredSpan(axis);
+ return min;
+ }
+
+ public void setSize(float width, float height)
+ {
+ // The default implementation does nothing.
+ }
+
+ /**
+ * Returns the alignment of this view along the baseline of the parent view.
+ * An alignment of <code>0.0</code> will align this view with the left edge
+ * along the baseline, an alignment of <code>0.5</code> will align it
+ * centered to the baseline, an alignment of <code>1.0</code> will align
+ * the right edge along the baseline.
+ *
+ * The default implementation returns 0.5 unconditionally.
+ *
+ * @param axis the axis
+ *
+ * @return the alignment of this view along the parents baseline for the
+ * specified axis
+ */
+ public float getAlignment(int axis)
+ {
+ return 0.5f;
+ }
+
+ public AttributeSet getAttributes()
+ {
+ return getElement().getAttributes();
+ }
+
+ public boolean isVisible()
+ {
+ return true;
+ }
+
+ public int getViewCount()
+ {
+ return 0;
+ }
+
+ public View getView(int index)
+ {
+ return null;
+ }
+
+ public ViewFactory getViewFactory()
+ {
+ View parent = getParent();
+ return parent != null ? parent.getViewFactory() : null;
+ }
+
+ /**
+ * Replaces a couple of child views with new child views. If
+ * <code>length == 0</code> then this is a simple insertion, if
+ * <code>views == null</code> this only removes some child views.
+ *
+ * @param offset the offset at which to replace
+ * @param length the number of child views to be removed
+ * @param views the new views to be inserted, may be <code>null</code>
+ */
+ public void replace(int offset, int length, View[] views)
+ {
+ // Default implementation does nothing.
+ }
+
+ public void insert(int offset, View view)
+ {
+ View[] array = { view };
+ replace(offset, 1, array);
+ }
+
+ public void append(View view)
+ {
+ View[] array = { view };
+ int offset = getViewCount();
+ replace(offset, 0, array);
+ }
+
+ public void removeAll()
+ {
+ replace(0, getViewCount(), null);
+ }
+
+ public void remove(int index)
+ {
+ replace(index, 1, null);
+ }
+
+ public View createFragment(int p0, int p1)
+ {
+ // The default implementation doesn't support fragmentation.
+ return this;
+ }
+
+ public int getStartOffset()
+ {
+ return getElement().getStartOffset();
+ }
+
+ public int getEndOffset()
+ {
+ return getElement().getEndOffset();
+ }
+
+ public Shape getChildAllocation(int index, Shape a)
+ {
+ return null;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public int getViewIndex(float x, float y, Shape allocation)
+ {
+ return -1;
+ }
+
+ /**
+ * @since 1.4
+ */
+ public String getToolTipText(float x, float y, Shape allocation)
+ {
+ int index = getViewIndex(x, y, allocation);
+
+ String text = null;
+ if (index >= 0)
+ {
+ allocation = getChildAllocation(index, allocation);
+ Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation
+ : allocation.getBounds();
+ if (r.contains(x, y))
+ text = getView(index).getToolTipText(x, y, allocation);
+ }
+ return text;
+ }
+
+ /**
+ * @since 1.3
+ */
+ public Graphics getGraphics()
+ {
+ return getContainer().getGraphics();
+ }
+
+ public void preferenceChanged(View child, boolean width, boolean height)
+ {
+ View p = getParent();
+ if (p != null)
+ p.preferenceChanged(this, width, height);
+ }
+
+ public int getBreakWeight(int axis, float pos, float len)
+ {
+ int weight = BadBreakWeight;
+ if (len > getPreferredSpan(axis))
+ weight = GoodBreakWeight;
+ return weight;
+ }
+
+ public View breakView(int axis, int offset, float pos, float len)
+ {
+ return this;
+ }
+
+ /**
+ * @since 1.3
+ */
+ public int getViewIndex(int pos, Position.Bias b)
+ {
+ return -1;
+ }
+
+ /**
+ * Receive notification about an insert update to the text model.
+ *
+ * The default implementation of this method does the following:
+ * <ul>
+ * <li>Call {@link #updateChildren} if the element that this view is
+ * responsible for has changed. This makes sure that the children can
+ * correctly represent the model.<li>
+ * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
+ * the child views.<li>
+ * <li>Call {@link #updateLayout}. Gives the view a chance to either
+ * repair its layout, reschedule layout or do nothing at all.</li>
+ * </ul>
+ *
+ * @param ev the DocumentEvent that describes the change
+ * @param shape the shape of the view
+ * @param vf the ViewFactory for creating child views
+ */
+ public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
+ {
+ if (getViewCount() > 0)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ if (! updateChildren(ec, ev, vf))
+ ec = null;
+ }
+ forwardUpdate(ec, ev, shape, vf);
+ updateLayout(ec, ev, shape);
+ }
+ }
+
+ /**
+ * Receive notification about a remove update to the text model.
+ *
+ * The default implementation of this method does the following:
+ * <ul>
+ * <li>Call {@link #updateChildren} if the element that this view is
+ * responsible for has changed. This makes sure that the children can
+ * correctly represent the model.<li>
+ * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
+ * the child views.<li>
+ * <li>Call {@link #updateLayout}. Gives the view a chance to either
+ * repair its layout, reschedule layout or do nothing at all.</li>
+ * </ul>
+ *
+ * @param ev the DocumentEvent that describes the change
+ * @param shape the shape of the view
+ * @param vf the ViewFactory for creating child views
+ */
+ public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ if (! updateChildren(ec, ev, vf))
+ ec = null;
+ }
+ forwardUpdate(ec, ev, shape, vf);
+ updateLayout(ec, ev, shape);
+ }
+
+ /**
+ * Receive notification about a change update to the text model.
+ *
+ * The default implementation of this method does the following:
+ * <ul>
+ * <li>Call {@link #updateChildren} if the element that this view is
+ * responsible for has changed. This makes sure that the children can
+ * correctly represent the model.<li>
+ * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
+ * the child views.<li>
+ * <li>Call {@link #updateLayout}. Gives the view a chance to either
+ * repair its layout, reschedule layout or do nothing at all.</li>
+ * </ul>
+ *
+ * @param ev the DocumentEvent that describes the change
+ * @param shape the shape of the view
+ * @param vf the ViewFactory for creating child views
+ */
+ public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
+ {
+ if (getViewCount() > 0)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ if (! updateChildren(ec, ev, vf))
+ ec = null;
+ }
+ forwardUpdate(ec, ev, shape, vf);
+ updateLayout(ec, ev, shape);
+ }
+ }
+
+ /**
+ * Updates the list of children that is returned by {@link #getView}
+ * and {@link #getViewCount}.
+ *
+ * Element that are specified as beeing added in the ElementChange record are
+ * assigned a view for using the ViewFactory. Views of Elements that
+ * are specified as beeing removed are removed from the list.
+ *
+ * @param ec the ElementChange record that describes the change of the
+ * element
+ * @param ev the DocumentEvent describing the change of the document model
+ * @param vf the ViewFactory to use for creating new views
+ *
+ * @return whether or not the child views represent the child elements of
+ * the element that this view is responsible for. Some views may
+ * create views that are responsible only for parts of the element
+ * that they are responsible for and should then return false.
+ *
+ * @since 1.3
+ */
+ protected boolean updateChildren(DocumentEvent.ElementChange ec,
+ DocumentEvent ev,
+ ViewFactory vf)
+ {
+ Element[] added = ec.getChildrenAdded();
+ Element[] removed = ec.getChildrenRemoved();
+ int index = ec.getIndex();
+
+ View[] newChildren = null;
+ if (added != null)
+ {
+ newChildren = new View[added.length];
+ for (int i = 0; i < added.length; ++i)
+ newChildren[i] = vf.create(added[i]);
+ }
+ int numRemoved = removed != null ? removed.length : 0;
+ replace(index, numRemoved, newChildren);
+
+ return true;
+ }
+
+ /**
+ * Forwards the DocumentEvent to child views that need to get notified
+ * of the change to the model. This calles {@link #forwardUpdateToView}
+ * for each View that must be forwarded to.
+ *
+ * If <code>ec</code> is not <code>null</code> (this means there have been
+ * structural changes to the element that this view is responsible for) this
+ * method should recognize this and don't notify newly added child views.
+ *
+ * @param ec the ElementChange describing the element changes (may be
+ * <code>null</code> if there were no changes)
+ * @param ev the DocumentEvent describing the changes to the model
+ * @param shape the current allocation of the view
+ * @param vf the ViewFactory used to create new Views
+ *
+ * @since 1.3
+ */
+ protected void forwardUpdate(DocumentEvent.ElementChange ec,
+ DocumentEvent ev, Shape shape, ViewFactory vf)
+ {
+ int count = getViewCount();
+ if (count > 0)
+ {
+ // Determine start index.
+ int startOffset = ev.getOffset();
+ int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
+
+ // For REMOVE events we have to forward the event to the last element,
+ // for the case that an Element has been removed that represente
+ // the offset.
+ if (startIndex == -1 && ev.getType() == DocumentEvent.EventType.REMOVE
+ && startOffset >= getEndOffset())
+ {
+ startIndex = getViewCount() - 1;
+ }
+
+ // When startIndex is on a view boundary, forward event to the
+ // previous view too.
+ if (startIndex >= 0)
+ {
+ View v = getView(startIndex);
+ if (v != null)
+ {
+ if (v.getStartOffset() == startOffset && startOffset > 0)
+ startIndex = Math.max(0, startIndex - 1);
+ }
+ }
+ startIndex = Math.max(0, startIndex);
+
+ // Determine end index.
+ int endIndex = startIndex;
+ if (ev.getType() != DocumentEvent.EventType.REMOVE)
+ {
+ endIndex = getViewIndex(startOffset + ev.getLength(),
+ Position.Bias.Forward);
+ if (endIndex < 0)
+ endIndex = getViewCount() - 1;
+ }
+
+ // Determine hole that comes from added elements (we don't forward
+ // the event to newly added views.
+ int startAdded = endIndex + 1;
+ int endAdded = startAdded;
+ Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
+ if (added != null && added.length > 0)
+ {
+ startAdded = ec.getIndex();
+ endAdded = startAdded + added.length - 1;
+ }
+
+ // Forward event to all views between startIndex and endIndex,
+ // and leave out all views in the hole.
+ for (int i = startIndex; i <= endIndex; i++)
+ {
+ // Skip newly added child views.
+ if (! (i >= startAdded && i <= endAdded))
+ {
+ View child = getView(i);
+ if (child != null)
+ {
+ Shape childAlloc = getChildAllocation(i, shape);
+ forwardUpdateToView(child, ev, childAlloc, vf);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Forwards an update event to the given child view. This calls
+ * {@link #insertUpdate}, {@link #removeUpdate} or {@link #changedUpdate},
+ * depending on the type of document event.
+ *
+ * @param view the View to forward the event to
+ * @param ev the DocumentEvent to forward
+ * @param shape the current allocation of the View
+ * @param vf the ViewFactory used to create new Views
+ *
+ * @since 1.3
+ */
+ protected void forwardUpdateToView(View view, DocumentEvent ev, Shape shape,
+ ViewFactory vf)
+ {
+ DocumentEvent.EventType type = ev.getType();
+ if (type == DocumentEvent.EventType.INSERT)
+ view.insertUpdate(ev, shape, vf);
+ else if (type == DocumentEvent.EventType.REMOVE)
+ view.removeUpdate(ev, shape, vf);
+ else if (type == DocumentEvent.EventType.CHANGE)
+ view.changedUpdate(ev, shape, vf);
+ }
+
+ /**
+ * Updates the layout.
+ *
+ * @param ec the ElementChange that describes the changes to the element
+ * @param ev the DocumentEvent that describes the changes to the model
+ * @param shape the current allocation for this view
+ *
+ * @since 1.3
+ */
+ protected void updateLayout(DocumentEvent.ElementChange ec,
+ DocumentEvent ev, Shape shape)
+ {
+ if (ec != null && shape != null)
+ {
+ preferenceChanged(null, true, true);
+ Container c = getContainer();
+ if (c != null)
+ c.repaint();
+ }
+ }
+
+ /**
+ * Maps a position in the document into the coordinate space of the View.
+ * The output rectangle usually reflects the font height but has a width
+ * of zero.
+ *
+ * @param pos the position of the character in the model
+ * @param a the area that is occupied by the view
+ * @param b either {@link Position.Bias#Forward} or
+ * {@link Position.Bias#Backward} depending on the preferred
+ * direction bias. If <code>null</code> this defaults to
+ * <code>Position.Bias.Forward</code>
+ *
+ * @return a rectangle that gives the location of the document position
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>pos</code> is invalid
+ * @throws IllegalArgumentException if b is not one of the above listed
+ * valid values
+ */
+ public abstract Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException;
+
+ /**
+ * Maps a region in the document into the coordinate space of the View.
+ *
+ * @param p1 the beginning position inside the document
+ * @param b1 the direction bias for the beginning position
+ * @param p2 the end position inside the document
+ * @param b2 the direction bias for the end position
+ * @param a the area that is occupied by the view
+ *
+ * @return a rectangle that gives the span of the document region
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
+ * invalid
+ * @throws IllegalArgumentException if b1 or b2 is not one of the above
+ * listed valid values
+ */
+ public Shape modelToView(int p1, Position.Bias b1,
+ int p2, Position.Bias b2, Shape a)
+ throws BadLocationException
+ {
+ if (b1 != Position.Bias.Forward && b1 != Position.Bias.Backward)
+ throw new IllegalArgumentException
+ ("b1 must be either Position.Bias.Forward or Position.Bias.Backward");
+ if (b2 != Position.Bias.Forward && b2 != Position.Bias.Backward)
+ throw new IllegalArgumentException
+ ("b2 must be either Position.Bias.Forward or Position.Bias.Backward");
+
+ Shape s1 = modelToView(p1, a, b1);
+ // Special case for p2 == end index.
+ Shape s2;
+ if (p2 != getEndOffset())
+ {
+ s2 = modelToView(p2, a, b2);
+ }
+ else
+ {
+ try
+ {
+ s2 = modelToView(p2, a, b2);
+ }
+ catch (BadLocationException ex)
+ {
+ // Assume the end rectangle to be at the right edge of the
+ // view.
+ Rectangle aRect = a instanceof Rectangle ? (Rectangle) a
+ : a.getBounds();
+ s2 = new Rectangle(aRect.x + aRect.width - 1, aRect.y, 1,
+ aRect.height);
+ }
+ }
+
+ // Need to modify the rectangle, so we create a copy in all cases.
+ Rectangle r1 = s1.getBounds();
+ Rectangle r2 = s2 instanceof Rectangle ? (Rectangle) s2
+ : s2.getBounds();
+
+ // For multiline view, let the resulting rectangle span the whole view.
+ if (r1.y != r2.y)
+ {
+ Rectangle aRect = a instanceof Rectangle ? (Rectangle) a
+ : a.getBounds();
+ r1.x = aRect.x;
+ r1.width = aRect.width;
+ }
+
+ return SwingUtilities.computeUnion(r2.x, r2.y, r2.width, r2.height, r1);
+ }
+
+ /**
+ * Maps a position in the document into the coordinate space of the View.
+ * The output rectangle usually reflects the font height but has a width
+ * of zero.
+ *
+ * This method is deprecated and calls
+ * {@link #modelToView(int, Position.Bias, int, Position.Bias, Shape)} with
+ * a bias of {@link Position.Bias#Forward}.
+ *
+ * @param pos the position of the character in the model
+ * @param a the area that is occupied by the view
+ *
+ * @return a rectangle that gives the location of the document position
+ * inside the view coordinate space
+ *
+ * @throws BadLocationException if <code>pos</code> is invalid
+ *
+ * @deprecated Use {@link #modelToView(int, Shape, Position.Bias)} instead.
+ */
+ public Shape modelToView(int pos, Shape a) throws BadLocationException
+ {
+ return modelToView(pos, a, Position.Bias.Forward);
+ }
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model.
+ *
+ * @param x the x coordinate in the view space
+ * @param y the y coordinate in the view space
+ * @param a the allocation of this <code>View</code>
+ * @param b the bias to use
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code>
+ */
+ public abstract int viewToModel(float x, float y, Shape a, Position.Bias[] b);
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model. This method is deprecated and only there for
+ * compatibility.
+ *
+ * @param x the x coordinate in the view space
+ * @param y the y coordinate in the view space
+ * @param a the allocation of this <code>View</code>
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code>
+ *
+ * @deprecated Use {@link #viewToModel(float, float, Shape, Position.Bias[])}
+ * instead.
+ */
+ public int viewToModel(float x, float y, Shape a)
+ {
+ Position.Bias[] biasRet = new Position.Bias[1];
+ biasRet[0] = Position.Bias.Forward;
+ return viewToModel(x, y, a, biasRet);
+ }
+
+ /**
+ * Dumps the complete View hierarchy. This method can be used for debugging
+ * purposes.
+ */
+ protected void dump()
+ {
+ // Climb up the hierarchy to the parent.
+ View parent = getParent();
+ if (parent != null)
+ parent.dump();
+ else
+ dump(0);
+ }
+
+ /**
+ * Dumps the view hierarchy below this View with the specified indentation
+ * level.
+ *
+ * @param indent the indentation level to be used for this view
+ */
+ void dump(int indent)
+ {
+ for (int i = 0; i < indent; ++i)
+ System.out.print('.');
+ System.out.println(this + "(" + getStartOffset() + "," + getEndOffset() + ": " + getElement());
+
+ int count = getViewCount();
+ for (int i = 0; i < count; ++i)
+ getView(i).dump(indent + 1);
+ }
+
+ /**
+ * Returns the document position that is (visually) nearest to the given
+ * document position <code>pos</code> in the given direction <code>d</code>.
+ *
+ * @param pos the document position
+ * @param b the bias for <code>pos</code>
+ * @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}
+ * @param biasRet an array of {@link Position.Bias} that can hold at least
+ * one element, which is filled with the bias of the return position
+ * on method exit
+ *
+ * @return the document position that is (visually) nearest to the given
+ * document position <code>pos</code> in the given direction
+ * <code>d</code>
+ *
+ * @throws BadLocationException if <code>pos</code> is not a valid offset in
+ * the document model
+ * @throws IllegalArgumentException if <code>d</code> is not a valid direction
+ */
+ public int getNextVisualPositionFrom(int pos, Position.Bias b,
+ Shape a, int d,
+ Position.Bias[] biasRet)
+ throws BadLocationException
+ {
+ int ret = pos;
+ Rectangle r;
+ View parent;
+
+ switch (d)
+ {
+ case EAST:
+ // TODO: take component orientation into account?
+ // Note: If pos is below zero the implementation will return
+ // pos + 1 regardless of whether that value is a correct offset
+ // in the document model. However this is what the RI does.
+ ret = Math.min(pos + 1, getEndOffset());
+ break;
+ case WEST:
+ // TODO: take component orientation into account?
+ ret = Math.max(pos - 1, getStartOffset());
+ break;
+ case NORTH:
+ // Try to find a suitable offset by examining the area above.
+ parent = getParent();
+ r = parent.modelToView(pos, a, b).getBounds();
+ ret = parent.viewToModel(r.x, r.y - 1, a, biasRet);
+ break;
+ case SOUTH:
+ // Try to find a suitable offset by examining the area below.
+ parent = getParent();
+ r = parent.modelToView(pos, a, b).getBounds();
+ ret = parent.viewToModel(r.x + r.width, r.y + r.height, a, biasRet);
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal value for d");
+ }
+
+ return ret;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/ViewFactory.java b/libjava/classpath/javax/swing/text/ViewFactory.java
new file mode 100644
index 000000000..cb57bd801
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/ViewFactory.java
@@ -0,0 +1,50 @@
+/* ViewFactory.java --
+ Copyright (C) 2002, 2004 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;
+
+public interface ViewFactory
+{
+ /**
+ * Creates a view for a given element.
+ *
+ * @param elem them element to create view for
+ *
+ * @return a new created view
+ */
+ View create(Element elem);
+}
diff --git a/libjava/classpath/javax/swing/text/WrappedPlainView.java b/libjava/classpath/javax/swing/text/WrappedPlainView.java
new file mode 100644
index 000000000..f2a6c92d8
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/WrappedPlainView.java
@@ -0,0 +1,795 @@
+/* WrappedPlainView.java --
+ Copyright (C) 2005, 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.Color;
+import java.awt.Container;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.Position.Bias;
+
+/**
+ * @author Anthony Balkissoon abalkiss at redhat dot com
+ *
+ */
+public class WrappedPlainView extends BoxView implements TabExpander
+{
+ /** The color for selected text **/
+ Color selectedColor;
+
+ /** The color for unselected text **/
+ Color unselectedColor;
+
+ /** The color for disabled components **/
+ Color disabledColor;
+
+ /**
+ * Stores the font metrics. This is package private to avoid synthetic
+ * accessor method.
+ */
+ FontMetrics metrics;
+
+ /** Whether or not to wrap on word boundaries **/
+ boolean wordWrap;
+
+ /** A ViewFactory that creates WrappedLines **/
+ ViewFactory viewFactory = new WrappedLineCreator();
+
+ /** The start of the selected text **/
+ int selectionStart;
+
+ /** The end of the selected text **/
+ int selectionEnd;
+
+ /** The height of the line (used while painting) **/
+ int lineHeight;
+
+ /**
+ * The base offset for tab calculations.
+ */
+ private int tabBase;
+
+ /**
+ * The tab size.
+ */
+ private int tabSize;
+
+ /**
+ * The instance returned by {@link #getLineBuffer()}.
+ */
+ private transient Segment lineBuffer;
+
+ public WrappedPlainView (Element elem)
+ {
+ this (elem, false);
+ }
+
+ public WrappedPlainView (Element elem, boolean wordWrap)
+ {
+ super (elem, Y_AXIS);
+ this.wordWrap = wordWrap;
+ }
+
+ /**
+ * Provides access to the Segment used for retrievals from the Document.
+ * @return the Segment.
+ */
+ protected final Segment getLineBuffer()
+ {
+ if (lineBuffer == null)
+ lineBuffer = new Segment();
+ return lineBuffer;
+ }
+
+ /**
+ * Returns the next tab stop position after a given reference position.
+ *
+ * This implementation ignores the <code>tabStop</code> argument.
+ *
+ * @param x the current x position in pixels
+ * @param tabStop the position within the text stream that the tab occured at
+ */
+ public float nextTabStop(float x, int tabStop)
+ {
+ int next = (int) x;
+ if (tabSize != 0)
+ {
+ int numTabs = ((int) x - tabBase) / tabSize;
+ next = tabBase + (numTabs + 1) * tabSize;
+ }
+ return next;
+ }
+
+ /**
+ * Returns the tab size for the Document based on
+ * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is
+ * not defined
+ *
+ * @return the tab size.
+ */
+ protected int getTabSize()
+ {
+ Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
+ if (tabSize == null)
+ return 8;
+ return ((Integer)tabSize).intValue();
+ }
+
+ /**
+ * Draws a line of text, suppressing white space at the end and expanding
+ * tabs. Calls drawSelectedText and drawUnselectedText.
+ * @param p0 starting document position to use
+ * @param p1 ending document position to use
+ * @param g graphics context
+ * @param x starting x position
+ * @param y starting y position
+ */
+ protected void drawLine(int p0, int p1, Graphics g, int x, int y)
+ {
+ try
+ {
+ // We have to draw both selected and unselected text. There are
+ // several cases:
+ // - entire range is unselected
+ // - entire range is selected
+ // - start of range is selected, end of range is unselected
+ // - start of range is unselected, end of range is selected
+ // - middle of range is selected, start and end of range is unselected
+
+ // entire range unselected:
+ if ((selectionStart == selectionEnd) ||
+ (p0 > selectionEnd || p1 < selectionStart))
+ drawUnselectedText(g, x, y, p0, p1);
+
+ // entire range selected
+ else if (p0 >= selectionStart && p1 <= selectionEnd)
+ drawSelectedText(g, x, y, p0, p1);
+
+ // start of range selected, end of range unselected
+ else if (p0 >= selectionStart)
+ {
+ x = drawSelectedText(g, x, y, p0, selectionEnd);
+ drawUnselectedText(g, x, y, selectionEnd, p1);
+ }
+
+ // start of range unselected, end of range selected
+ else if (selectionStart > p0 && selectionEnd > p1)
+ {
+ x = drawUnselectedText(g, x, y, p0, selectionStart);
+ drawSelectedText(g, x, y, selectionStart, p1);
+ }
+
+ // middle of range selected
+ else if (selectionStart > p0)
+ {
+ x = drawUnselectedText(g, x, y, p0, selectionStart);
+ x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
+ drawUnselectedText(g, x, y, selectionEnd, p1);
+ }
+ }
+ catch (BadLocationException ble)
+ {
+ // shouldn't happen
+ }
+ }
+
+ /**
+ * Renders the range of text as selected text. Just paints the text
+ * in the color specified by the host component. Assumes the highlighter
+ * will render the selected background.
+ * @param g the graphics context
+ * @param x the starting X coordinate
+ * @param y the starting Y coordinate
+ * @param p0 the starting model location
+ * @param p1 the ending model location
+ * @return the X coordinate of the end of the text
+ * @throws BadLocationException if the given range is invalid
+ */
+ protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ g.setColor(selectedColor);
+ Segment segment = getLineBuffer();
+ getDocument().getText(p0, p1 - p0, segment);
+ return Utilities.drawTabbedText(segment, x, y, g, this, p0);
+ }
+
+ /**
+ * Renders the range of text as normal unhighlighted text.
+ * @param g the graphics context
+ * @param x the starting X coordinate
+ * @param y the starting Y coordinate
+ * @param p0 the starting model location
+ * @param p1 the end model location
+ * @return the X location of the end off the range
+ * @throws BadLocationException if the range given is invalid
+ */
+ protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ JTextComponent textComponent = (JTextComponent) getContainer();
+ if (textComponent.isEnabled())
+ g.setColor(unselectedColor);
+ else
+ g.setColor(disabledColor);
+
+ Segment segment = getLineBuffer();
+ getDocument().getText(p0, p1 - p0, segment);
+ return Utilities.drawTabbedText(segment, x, y, g, this, p0);
+ }
+
+ /**
+ * Loads the children to initiate the view. Called by setParent.
+ * Creates a WrappedLine for each child Element.
+ */
+ protected void loadChildren (ViewFactory f)
+ {
+ Element root = getElement();
+ int numChildren = root.getElementCount();
+ if (numChildren == 0)
+ return;
+
+ View[] children = new View[numChildren];
+ for (int i = 0; i < numChildren; i++)
+ children[i] = new WrappedLine(root.getElement(i));
+ replace(0, 0, children);
+ }
+
+ /**
+ * Calculates the break position for the text between model positions
+ * p0 and p1. Will break on word boundaries or character boundaries
+ * depending on the break argument given in construction of this
+ * WrappedPlainView. Used by the nested WrappedLine class to determine
+ * when to start the next logical line.
+ * @param p0 the start model position
+ * @param p1 the end model position
+ * @return the model position at which to break the text
+ */
+ protected int calculateBreakPosition(int p0, int p1)
+ {
+ Segment s = new Segment();
+ try
+ {
+ getDocument().getText(p0, p1 - p0, s);
+ }
+ catch (BadLocationException ex)
+ {
+ assert false : "Couldn't load text";
+ }
+ int width = getWidth();
+ int pos;
+ if (wordWrap)
+ pos = p0 + Utilities.getBreakLocation(s, metrics, tabBase,
+ tabBase + width, this, p0);
+ else
+ pos = p0 + Utilities.getTabbedTextOffset(s, metrics, tabBase,
+ tabBase + width, this, p0,
+ false);
+ return pos;
+ }
+
+ void updateMetrics()
+ {
+ Container component = getContainer();
+ metrics = component.getFontMetrics(component.getFont());
+ tabSize = getTabSize()* metrics.charWidth('m');
+ }
+
+ /**
+ * Determines the preferred span along the given axis. Implemented to
+ * cache the font metrics and then call the super classes method.
+ */
+ public float getPreferredSpan (int axis)
+ {
+ updateMetrics();
+ return super.getPreferredSpan(axis);
+ }
+
+ /**
+ * Determines the minimum span along the given axis. Implemented to
+ * cache the font metrics and then call the super classes method.
+ */
+ public float getMinimumSpan (int axis)
+ {
+ updateMetrics();
+ return super.getMinimumSpan(axis);
+ }
+
+ /**
+ * Determines the maximum span along the given axis. Implemented to
+ * cache the font metrics and then call the super classes method.
+ */
+ public float getMaximumSpan (int axis)
+ {
+ updateMetrics();
+ return super.getMaximumSpan(axis);
+ }
+
+ /**
+ * Called when something was inserted. Overridden so that
+ * the view factory creates WrappedLine views.
+ */
+ public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f)
+ {
+ // Update children efficiently.
+ updateChildren(e, a);
+
+ // Notify children.
+ Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
+ : null;
+ View v = getViewAtPosition(e.getOffset(), r);
+ if (v != null)
+ v.insertUpdate(e, r, f);
+ }
+
+ /**
+ * Called when something is removed. Overridden so that
+ * the view factory creates WrappedLine views.
+ */
+ public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f)
+ {
+ // Update children efficiently.
+ updateChildren(e, a);
+
+ // Notify children.
+ Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
+ : null;
+ View v = getViewAtPosition(e.getOffset(), r);
+ if (v != null)
+ v.removeUpdate(e, r, f);
+ }
+
+ /**
+ * Called when the portion of the Document that this View is responsible
+ * for changes. Overridden so that the view factory creates
+ * WrappedLine views.
+ */
+ public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f)
+ {
+ // Update children efficiently.
+ updateChildren(e, a);
+ }
+
+ /**
+ * Helper method. Updates the child views in response to
+ * insert/remove/change updates. This is here to be a little more efficient
+ * than the BoxView implementation.
+ *
+ * @param ev the document event
+ * @param a the shape
+ */
+ private void updateChildren(DocumentEvent ev, Shape a)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ Element[] removed = ec.getChildrenRemoved();
+ Element[] added = ec.getChildrenAdded();
+ View[] addedViews = new View[added.length];
+ for (int i = 0; i < added.length; i++)
+ addedViews[i] = new WrappedLine(added[i]);
+ replace(ec.getIndex(), removed.length, addedViews);
+ if (a != null)
+ {
+ preferenceChanged(null, true, true);
+ getContainer().repaint();
+ }
+ }
+ updateMetrics();
+ }
+
+ class WrappedLineCreator implements ViewFactory
+ {
+ // Creates a new WrappedLine
+ public View create(Element elem)
+ {
+ return new WrappedLine(elem);
+ }
+ }
+
+ /**
+ * Renders the <code>Element</code> that is associated with this
+ * <code>View</code>. Caches the metrics and then calls
+ * super.paint to paint all the child views.
+ *
+ * @param g the <code>Graphics</code> context to render to
+ * @param a the allocated region for the <code>Element</code>
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ tabBase = r.x;
+
+ JTextComponent comp = (JTextComponent)getContainer();
+ // Ensure metrics are up-to-date.
+ updateMetrics();
+
+ selectionStart = comp.getSelectionStart();
+ selectionEnd = comp.getSelectionEnd();
+
+ selectedColor = comp.getSelectedTextColor();
+ unselectedColor = comp.getForeground();
+ disabledColor = comp.getDisabledTextColor();
+ selectedColor = comp.getSelectedTextColor();
+ lineHeight = metrics.getHeight();
+ g.setFont(comp.getFont());
+
+ super.paint(g, a);
+ }
+
+ /**
+ * Sets the size of the View. Implemented to update the metrics
+ * and then call super method.
+ */
+ public void setSize (float width, float height)
+ {
+ updateMetrics();
+ if (width != getWidth())
+ preferenceChanged(null, true, true);
+ super.setSize(width, height);
+ }
+
+ class WrappedLine extends View
+ {
+ /** Used to cache the number of lines for this View **/
+ int numLines = 1;
+
+ public WrappedLine(Element elem)
+ {
+ super(elem);
+ }
+
+ /**
+ * Renders this (possibly wrapped) line using the given Graphics object
+ * and on the given rendering surface.
+ */
+ public void paint(Graphics g, Shape s)
+ {
+ Rectangle rect = s.getBounds();
+
+ int end = getEndOffset();
+ int currStart = getStartOffset();
+ int currEnd;
+ int count = 0;
+
+ // Determine layered highlights.
+ Container c = getContainer();
+ LayeredHighlighter lh = null;
+ JTextComponent tc = null;
+ if (c instanceof JTextComponent)
+ {
+ tc = (JTextComponent) c;
+ Highlighter h = tc.getHighlighter();
+ if (h instanceof LayeredHighlighter)
+ lh = (LayeredHighlighter) h;
+ }
+
+ while (currStart < end)
+ {
+ currEnd = calculateBreakPosition(currStart, end);
+
+ // Paint layered highlights, if any.
+ if (lh != null)
+ {
+ // Exclude trailing newline in last line.
+ if (currEnd == end)
+ lh.paintLayeredHighlights(g, currStart, currEnd - 1, s, tc,
+ this);
+ else
+ lh.paintLayeredHighlights(g, currStart, currEnd, s, tc, this);
+
+ }
+ drawLine(currStart, currEnd, g, rect.x, rect.y + metrics.getAscent());
+
+ rect.y += lineHeight;
+ if (currEnd == currStart)
+ currStart ++;
+ else
+ currStart = currEnd;
+
+ count++;
+
+ }
+
+ if (count != numLines)
+ {
+ numLines = count;
+ preferenceChanged(this, false, true);
+ }
+
+ }
+
+ /**
+ * Calculates the number of logical lines that the Element
+ * needs to be displayed and updates the variable numLines
+ * accordingly.
+ */
+ private int determineNumLines()
+ {
+ int nLines = 0;
+ int end = getEndOffset();
+ for (int i = getStartOffset(); i < end;)
+ {
+ nLines++;
+ // careful: check that there's no off-by-one problem here
+ // depending on which position calculateBreakPosition returns
+ int breakPoint = calculateBreakPosition(i, end);
+
+ if (breakPoint == i)
+ i = breakPoint + 1;
+ else
+ i = breakPoint;
+ }
+ return nLines;
+ }
+
+ /**
+ * Determines the preferred span for this view along the given axis.
+ *
+ * @param axis the axis (either X_AXIS or Y_AXIS)
+ *
+ * @return the preferred span along the given axis.
+ * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS
+ */
+ public float getPreferredSpan(int axis)
+ {
+ if (axis == X_AXIS)
+ return getWidth();
+ else if (axis == Y_AXIS)
+ {
+ if (metrics == null)
+ updateMetrics();
+ return numLines * metrics.getHeight();
+ }
+
+ throw new IllegalArgumentException("Invalid axis for getPreferredSpan: "
+ + axis);
+ }
+
+ /**
+ * Provides a mapping from model space to view space.
+ *
+ * @param pos the position in the model
+ * @param a the region into which the view is rendered
+ * @param b the position bias (forward or backward)
+ *
+ * @return a box in view space that represents the given position
+ * in model space
+ * @throws BadLocationException if the given model position is invalid
+ */
+ public Shape modelToView(int pos, Shape a, Bias b)
+ throws BadLocationException
+ {
+ Rectangle rect = a.getBounds();
+
+ // Throwing a BadLocationException is an observed behavior of the RI.
+ if (rect.isEmpty())
+ throw new BadLocationException("Unable to calculate view coordinates "
+ + "when allocation area is empty.", pos);
+
+ Segment s = getLineBuffer();
+ int lineHeight = metrics.getHeight();
+
+ // Return a rectangle with width 1 and height equal to the height
+ // of the text
+ rect.height = lineHeight;
+ rect.width = 1;
+
+ int currLineStart = getStartOffset();
+ int end = getEndOffset();
+
+ if (pos < currLineStart || pos >= end)
+ throw new BadLocationException("invalid offset", pos);
+
+ while (true)
+ {
+ int currLineEnd = calculateBreakPosition(currLineStart, end);
+ // If pos is between currLineStart and currLineEnd then just find
+ // the width of the text from currLineStart to pos and add that
+ // to rect.x
+ if (pos >= currLineStart && pos < currLineEnd)
+ {
+ try
+ {
+ getDocument().getText(currLineStart, pos - currLineStart, s);
+ }
+ catch (BadLocationException ble)
+ {
+ // Shouldn't happen
+ }
+ rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x,
+ WrappedPlainView.this,
+ currLineStart);
+ return rect;
+ }
+ // Increment rect.y so we're checking the next logical line
+ rect.y += lineHeight;
+
+ // Increment currLineStart to the model position of the start
+ // of the next logical line
+ if (currLineEnd == currLineStart)
+ currLineStart = end;
+ else
+ currLineStart = currLineEnd;
+ }
+
+ }
+
+ /**
+ * Provides a mapping from view space to model space.
+ *
+ * @param x the x coordinate in view space
+ * @param y the y coordinate in view space
+ * @param a the region into which the view is rendered
+ * @param b the position bias (forward or backward)
+ *
+ * @return the location in the model that best represents the
+ * given point in view space
+ */
+ public int viewToModel(float x, float y, Shape a, Bias[] b)
+ {
+ Segment s = getLineBuffer();
+ Rectangle rect = a.getBounds();
+ int currLineStart = getStartOffset();
+
+ // Although calling modelToView with the last possible offset will
+ // cause a BadLocationException in CompositeView it is allowed
+ // to return that offset in viewToModel.
+ int end = getEndOffset();
+
+ int lineHeight = metrics.getHeight();
+ if (y < rect.y)
+ return currLineStart;
+
+ if (y > rect.y + rect.height)
+ return end - 1;
+
+ // Note: rect.x and rect.width do not represent the width of painted
+ // text but the area where text *may* be painted. This means the width
+ // is most of the time identical to the component's width.
+
+ while (currLineStart != end)
+ {
+ int currLineEnd = calculateBreakPosition(currLineStart, end);
+
+ // If we're at the right y-position that means we're on the right
+ // logical line and we should look for the character
+ if (y >= rect.y && y < rect.y + lineHeight)
+ {
+ try
+ {
+ getDocument().getText(currLineStart, currLineEnd - currLineStart, s);
+ }
+ catch (BadLocationException ble)
+ {
+ // Shouldn't happen
+ }
+
+ int offset = Utilities.getTabbedTextOffset(s, metrics, rect.x,
+ (int) x,
+ WrappedPlainView.this,
+ currLineStart);
+ // If the calculated offset is the end of the line (in the
+ // document (= start of the next line) return the preceding
+ // offset instead. This makes sure that clicking right besides
+ // the last character in a line positions the cursor after the
+ // last character and not in the beginning of the next line.
+ return (offset == currLineEnd) ? offset - 1 : offset;
+ }
+ // Increment rect.y so we're checking the next logical line
+ rect.y += lineHeight;
+
+ // Increment currLineStart to the model position of the start
+ // of the next logical line.
+ currLineStart = currLineEnd;
+
+ }
+
+ return end;
+ }
+
+ /**
+ * <p>This method is called from insertUpdate and removeUpdate.</p>
+ *
+ * <p>If the number of lines in the document has changed, just repaint
+ * the whole thing (note, could improve performance by not repainting
+ * anything above the changes). If the number of lines hasn't changed,
+ * just repaint the given Rectangle.</p>
+ *
+ * <p>Note that the <code>Rectangle</code> argument may be <code>null</code>
+ * when the allocation area is empty.</code>
+ *
+ * @param a the Rectangle to repaint if the number of lines hasn't changed
+ */
+ void updateDamage (Rectangle a)
+ {
+ int nLines = determineNumLines();
+ if (numLines != nLines)
+ {
+ numLines = nLines;
+ preferenceChanged(this, false, true);
+ getContainer().repaint();
+ }
+ else if (a != null)
+ getContainer().repaint(a.x, a.y, a.width, a.height);
+ }
+
+ /**
+ * This method is called when something is inserted into the Document
+ * that this View is displaying.
+ *
+ * @param changes the DocumentEvent for the changes.
+ * @param a the allocation of the View
+ * @param f the ViewFactory used to rebuild
+ */
+ public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ updateDamage(r);
+ }
+
+ /**
+ * This method is called when something is removed from the Document
+ * that this View is displaying.
+ *
+ * @param changes the DocumentEvent for the changes.
+ * @param a the allocation of the View
+ * @param f the ViewFactory used to rebuild
+ */
+ public void removeUpdate (DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ // Note: This method is not called when characters from the
+ // end of the document are removed. The reason for this
+ // can be found in the implementation of View.forwardUpdate:
+ // The document event will denote offsets which do not exist
+ // any more, getViewIndex() will therefore return -1 and this
+ // makes View.forwardUpdate() skip this method call.
+ // However this seems to cause no trouble and as it reduces the
+ // number of method calls it can stay this way.
+
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ updateDamage(r);
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/ZoneView.java b/libjava/classpath/javax/swing/text/ZoneView.java
new file mode 100644
index 000000000..6cabc6c20
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/ZoneView.java
@@ -0,0 +1,442 @@
+/* ZoneView.java -- An effective BoxView subclass
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Shape;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+import javax.swing.event.DocumentEvent;
+
+/**
+ * A View implementation that delays loading of sub views until they are
+ * needed for display or internal transformations. This can be used for
+ * editors that need to handle large documents more effectivly than the
+ * standard {@link BoxView}.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ *
+ * @since 1.3
+ */
+public class ZoneView
+ extends BoxView
+{
+
+ /**
+ * The default zone view implementation. The specs suggest that this is
+ * a subclass of AsyncBoxView, so do we.
+ */
+ static class Zone
+ extends AsyncBoxView
+ {
+ /**
+ * The start position for this zone.
+ */
+ private Position p0;
+
+ /**
+ * The end position for this zone.
+ */
+ private Position p1;
+
+ /**
+ * Creates a new Zone for the specified element, start and end positions.
+ *
+ * @param el the element
+ * @param pos0 the start position
+ * @param pos1 the end position
+ * @param axis the major axis
+ */
+ Zone(Element el, Position pos0, Position pos1, int axis)
+ {
+ super(el, axis);
+ p0 = pos0;
+ p1 = pos1;
+ }
+
+ /**
+ * Returns the start offset of the zone.
+ *
+ * @return the start offset of the zone
+ */
+ public int getStartOffset()
+ {
+ return p0.getOffset();
+ }
+
+ /**
+ * Returns the end offset of the zone.
+ *
+ * @return the end offset of the zone
+ */
+ public int getEndOffset()
+ {
+ return p1.getOffset();
+ }
+ }
+
+ /**
+ * The maximumZoneSize.
+ */
+ private int maximumZoneSize;
+
+ /**
+ * The maximum number of loaded zones.
+ */
+ private int maxZonesLoaded;
+
+ /**
+ * A queue of loaded zones. When the number of loaded zones exceeds the
+ * maximum number of zones, the oldest zone(s) get unloaded.
+ */
+ private LinkedList loadedZones;
+
+ /**
+ * Creates a new ZoneView for the specified element and axis.
+ *
+ * @param element the element for which to create a ZoneView
+ * @param axis the major layout axis for the box
+ */
+ public ZoneView(Element element, int axis)
+ {
+ super(element, axis);
+ maximumZoneSize = 8192;
+ maxZonesLoaded = 3;
+ loadedZones = new LinkedList();
+ }
+
+ /**
+ * Sets the maximum zone size. Note that zones might still become larger
+ * then the size specified when a singe child view is larger for itself,
+ * because zones are formed on child view boundaries.
+ *
+ * @param size the maximum zone size to set
+ *
+ * @see #getMaximumZoneSize()
+ */
+ public void setMaximumZoneSize(int size)
+ {
+ maximumZoneSize = size;
+ }
+
+ /**
+ * Returns the maximum zone size. Note that zones might still become larger
+ * then the size specified when a singe child view is larger for itself,
+ * because zones are formed on child view boundaries.
+ *
+ * @return the maximum zone size
+ *
+ * @see #setMaximumZoneSize(int)
+ */
+ public int getMaximumZoneSize()
+ {
+ return maximumZoneSize;
+ }
+
+ /**
+ * Sets the maximum number of zones that are allowed to be loaded at the
+ * same time. If the new number of allowed zones is smaller then the
+ * previous settings, this unloads all zones the aren't allowed to be
+ * loaded anymore.
+ *
+ * @param num the number of zones allowed to be loaded at the same time
+ *
+ * @throws IllegalArgumentException if <code>num &lt;= 0</code>
+ *
+ * @see #getMaxZonesLoaded()
+ */
+ public void setMaxZonesLoaded(int num)
+ {
+ if (num < 1)
+ throw new IllegalArgumentException("Illegal number of zones");
+ maxZonesLoaded = num;
+ unloadOldestZones();
+ }
+
+ /**
+ * Returns the number of zones that are allowed to be loaded.
+ *
+ * @return the number of zones that are allowed to be loaded
+ *
+ * @see #setMaxZonesLoaded(int)
+ */
+ public int getMaxZonesLoaded()
+ {
+ return maxZonesLoaded;
+ }
+
+ /**
+ * Gets called after a zone has been loaded. This unloads the oldest zone(s)
+ * when the maximum number of zones is reached.
+ *
+ * @param zone the zone that has been loaded
+ */
+ protected void zoneWasLoaded(View zone)
+ {
+ loadedZones.addLast(zone);
+ unloadOldestZones();
+ }
+
+ /**
+ * This unloads the specified zone. This is implemented to simply remove
+ * all child views from that zone.
+ *
+ * @param zone the zone to be unloaded
+ */
+ protected void unloadZone(View zone)
+ {
+ zone.removeAll();
+ }
+
+ /**
+ * Returns <code>true</code> when the specified zone is loaded,
+ * <code>false</code> otherwise. The default implementation checks if
+ * the zone view has child elements.
+ *
+ * @param zone the zone view to check
+ *
+ * @return <code>true</code> when the specified zone is loaded,
+ * <code>false</code> otherwise
+ */
+ protected boolean isZoneLoaded(View zone)
+ {
+ return zone.getViewCount() > 0;
+ }
+
+ /**
+ * Creates a zone for the specified range. Subclasses can override this
+ * to provide a custom implementation for the zones.
+ *
+ * @param p0 the start of the range
+ * @param p1 the end of the range
+ *
+ * @return the zone
+ */
+ protected View createZone(int p0, int p1)
+ {
+ Document doc = getDocument();
+ Position pos0 = null;
+ Position pos1 = null;
+ try
+ {
+ pos0 = doc.createPosition(p0);
+ pos1 = doc.createPosition(p1);
+ }
+ catch (BadLocationException ex)
+ {
+ assert false : "Must not happen";
+ }
+ Zone zone = new Zone(getElement(), pos0, pos1, getAxis());
+ return zone;
+ }
+
+ // --------------------------------------------------------------------------
+ // CompositeView methods.
+ // --------------------------------------------------------------------------
+
+ /**
+ * Overridden to not load all the child views. This methods creates
+ * initial zones without actually loading them.
+ *
+ * @param vf not used
+ */
+ protected void loadChildren(ViewFactory vf)
+ {
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ append(createZone(p0, p1));
+ checkZoneAt(p0);
+ }
+
+ /**
+ * Returns the index of the child view at the document position
+ * <code>pos</code>.
+ *
+ * This overrides the CompositeView implementation because the ZoneView does
+ * not provide a one to one mapping from Elements to Views.
+ *
+ * @param pos the document position
+ *
+ * @return the index of the child view at the document position
+ * <code>pos</code>
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ int index = -1;
+ boolean found = false;
+ if (pos >= getStartOffset() && pos <= getEndOffset())
+ {
+ int upper = getViewCount() - 1;
+ int lower = 0;
+ index = (upper - lower) / 2 + lower;
+ int bias = 0;
+ do
+ {
+ View child = getView(index);
+ int childStart = child.getStartOffset();
+ int childEnd = child.getEndOffset();
+ if (pos >= childStart && pos < childEnd)
+ found = true;
+ else if (pos < childStart)
+ {
+ upper = index;
+ bias = -1;
+ }
+ else if (pos >= childEnd)
+ {
+ lower = index;
+ bias = 1;
+ }
+ if (! found)
+ {
+ int newIndex = (upper - lower) / 2 + lower;
+ if (newIndex == index)
+ index = newIndex + bias;
+ else
+ index = newIndex;
+ }
+ } while (upper != lower && ! found);
+ }
+ // If no child view actually covers the specified offset, reset index to
+ // -1.
+ if (! found)
+ index = -1;
+ return index;
+ }
+
+ // --------------------------------------------------------------------------
+ // View methods.
+ // --------------------------------------------------------------------------
+
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ // TODO: Implement this.
+ }
+
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
+ {
+ // TODO: Implement this.
+ }
+
+ protected boolean updateChildren(DocumentEvent.ElementChange ec,
+ DocumentEvent e, ViewFactory vf)
+ {
+ // TODO: Implement this.
+ return false;
+ }
+
+ // --------------------------------------------------------------------------
+ // Internal helper methods.
+ // --------------------------------------------------------------------------
+
+ /**
+ * A helper method to unload the oldest zones when there are more loaded
+ * zones then allowed.
+ */
+ private void unloadOldestZones()
+ {
+ int maxZones = getMaxZonesLoaded();
+ while (loadedZones.size() > maxZones)
+ {
+ View zone = (View) loadedZones.removeFirst();
+ unloadZone(zone);
+ }
+ }
+
+ /**
+ * Checks if the zone view at position <code>pos</code> should be split
+ * (its size is greater than maximumZoneSize) and tries to split it.
+ *
+ * @param pos the document position to check
+ */
+ private void checkZoneAt(int pos)
+ {
+ int viewIndex = getViewIndexAtPosition(pos); //, Position.Bias.Forward);
+ View view = getView(viewIndex);
+ int p0 = view.getStartOffset();
+ int p1 = view.getEndOffset();
+ if (p1 - p0 > maximumZoneSize)
+ splitZone(viewIndex, p0, p1);
+ }
+
+ /**
+ * Tries to break the view at the specified index and inside the specified
+ * range into pieces that are acceptable with respect to the maximum zone
+ * size.
+ *
+ * @param index the index of the view to split
+ * @param p0 the start offset
+ * @param p1 the end offset
+ */
+ private void splitZone(int index, int p0, int p1)
+ {
+ ArrayList newZones = new ArrayList();
+ int p = p0;
+ do
+ {
+ p0 = p;
+ p = Math.min(getPreferredZoneEnd(p0), p1);
+ newZones.add(createZone(p0, p));
+ } while (p < p1);
+ View[] newViews = new View[newZones.size()];
+ newViews = (View[]) newZones.toArray(newViews);
+ replace(index, 1, newViews);
+ }
+
+ /**
+ * Calculates the positions at which a zone split is performed. This
+ * tries to create zones sized close to half the maximum zone size.
+ *
+ * @param start the start offset
+ *
+ * @return the preferred end offset
+ */
+ private int getPreferredZoneEnd(int start)
+ {
+ Element el = getElement();
+ int index = el.getElementIndex(start + (maximumZoneSize / 2));
+ Element child = el.getElement(index);
+ int p0 = child.getStartOffset();
+ int p1 = child.getEndOffset();
+ int end = p1;
+ if (p0 - start > maximumZoneSize && p0 > start)
+ end = p0;
+ return end;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/BRView.java b/libjava/classpath/javax/swing/text/html/BRView.java
new file mode 100644
index 000000000..6f465c9d1
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/BRView.java
@@ -0,0 +1,70 @@
+/* BRView.java -- HTML BR tag view
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.text.Element;
+
+/**
+ * Handled the HTML BR tag.
+ */
+class BRView
+ extends InlineView
+{
+ /**
+ * Creates the new BR view.
+ *
+ * @param elem the HTML element, representing the view.
+ */
+ public BRView(Element elem)
+ {
+ super(elem);
+ }
+
+ /**
+ * Always return ForcedBreakWeight for the X_AXIS, BadBreakWeight for the
+ * Y_AXIS.
+ */
+ public int getBreakWeight(int axis, float pos, float len)
+ {
+ if (axis == X_AXIS)
+ return ForcedBreakWeight;
+ else
+ return super.getBreakWeight(axis, pos, len);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/BlockView.java b/libjava/classpath/javax/swing/text/html/BlockView.java
new file mode 100644
index 000000000..1c3397126
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/BlockView.java
@@ -0,0 +1,721 @@
+/* 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 gnu.javax.swing.text.html.css.Length;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.util.HashMap;
+
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+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 <langel@redhat.com>
+ */
+public class BlockView extends BoxView
+{
+
+ /**
+ * Stores information about child positioning according to the
+ * CSS attributes position, left, right, top and bottom.
+ */
+ private static class PositionInfo
+ {
+ // TODO: Use enums when available.
+
+ /**
+ * Static positioning. This is the default and is thus rarely really
+ * used.
+ */
+ static final int STATIC = 0;
+
+ /**
+ * Relative positioning. The box is teaked relative to its static
+ * computed bounds.
+ */
+ static final int RELATIVE = 1;
+
+ /**
+ * Absolute positioning. The box is moved relative to the parent's box.
+ */
+ static final int ABSOLUTE = 2;
+
+ /**
+ * Like ABSOLUTE, with some fixation against the viewport (not yet
+ * implemented).
+ */
+ static final int FIXED = 3;
+
+ /**
+ * The type according to the constants of this class.
+ */
+ int type;
+
+ /**
+ * The left constraint, null if not set.
+ */
+ Length left;
+
+ /**
+ * The right constraint, null if not set.
+ */
+ Length right;
+
+ /**
+ * The top constraint, null if not set.
+ */
+ Length top;
+
+ /**
+ * The bottom constraint, null if not set.
+ */
+ Length bottom;
+
+ /**
+ * Creates a new PositionInfo object.
+ *
+ * @param typ the type to set
+ * @param l the left constraint
+ * @param r the right constraint
+ * @param t the top constraint
+ * @param b the bottom constraint
+ */
+ PositionInfo(int typ, Length l, Length r, Length t, Length b)
+ {
+ type = typ;
+ left = l;
+ right = r;
+ top = t;
+ bottom = b;
+ }
+ }
+
+ /**
+ * The attributes for this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The box painter for this view.
+ *
+ * This is package private because the TableView needs access to it.
+ */
+ StyleSheet.BoxPainter painter;
+
+ /**
+ * The width and height as specified in the stylesheet, null if not
+ * specified. The first value is the X_AXIS, the second the Y_AXIS. You
+ * can index this directly by the X_AXIS and Y_AXIS constants.
+ */
+ private Length[] cssSpans;
+
+ /**
+ * Stores additional CSS layout information.
+ */
+ private HashMap positionInfo;
+
+ /**
+ * Creates a new view that represents an html box.
+ * This can be used for a number of elements.
+ *
+ * @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);
+ cssSpans = new Length[2];
+ positionInfo = new HashMap();
+ }
+
+ /**
+ * Creates the parent view for this. It is called before
+ * any other methods, if the parent view is working properly.
+ * 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)
+ {
+ if (r == null)
+ r = new SizeRequirements();
+
+ if (setCSSSpan(r, axis))
+ {
+ // If we have set the span from CSS, then we need to adjust
+ // the margins.
+ SizeRequirements parent = super.calculateMajorAxisRequirements(axis,
+ null);
+ int margin = axis == X_AXIS ? getLeftInset() + getRightInset()
+ : getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ constrainSize(axis, r, parent);
+ }
+ else
+ r = super.calculateMajorAxisRequirements(axis, r);
+ return r;
+ }
+
+ /**
+ * Calculates the requirements along the minor axis.
+ * This is implemented to call the superclass and then
+ * 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)
+ {
+ if (r == null)
+ r = new SizeRequirements();
+
+ if (setCSSSpan(r, axis))
+ {
+ // If we have set the span from CSS, then we need to adjust
+ // the margins.
+ SizeRequirements parent = super.calculateMinorAxisRequirements(axis,
+ null);
+ int margin = axis == X_AXIS ? getLeftInset() + getRightInset()
+ : getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ constrainSize(axis, r, parent);
+ }
+ else
+ r = super.calculateMinorAxisRequirements(axis, r);
+
+ // Apply text alignment if appropriate.
+ if (axis == X_AXIS)
+ {
+ Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null)
+ {
+ String al = o.toString().trim();
+ if (al.equals("center"))
+ r.alignment = 0.5f;
+ else if (al.equals("right"))
+ r.alignment = 1.0f;
+ else
+ r.alignment = 0.0f;
+ }
+ }
+ return r;
+ }
+
+ /**
+ * Sets the span on the SizeRequirements object according to the
+ * according CSS span value, when it is set.
+ *
+ * @param r the size requirements
+ * @param axis the axis
+ *
+ * @return <code>true</code> when the CSS span has been set,
+ * <code>false</code> otherwise
+ */
+ private boolean setCSSSpan(SizeRequirements r, int axis)
+ {
+ boolean ret = false;
+ Length span = cssSpans[axis];
+ // We can't set relative CSS spans here because we don't know
+ // yet about the allocated span. Instead we use the view's
+ // normal requirements.
+ if (span != null && ! span.isPercentage())
+ {
+ r.minimum = (int) span.getValue();
+ r.preferred = (int) span.getValue();
+ r.maximum = (int) span.getValue();
+ ret = true;
+ }
+ return ret;
+ }
+
+ /**
+ * Constrains the <code>r</code> requirements according to
+ * <code>min</code>.
+ *
+ * @param axis the axis
+ * @param r the requirements to constrain
+ * @param min the constraining requirements
+ */
+ private void constrainSize(int axis, SizeRequirements r,
+ SizeRequirements min)
+ {
+ if (min.minimum > r.minimum)
+ {
+ r.minimum = min.minimum;
+ r.preferred = min.minimum;
+ r.maximum = Math.max(r.maximum, min.maximum);
+ }
+ }
+
+ /**
+ * Lays out the box along the minor axis (the axis that is
+ * perpendicular to the axis that it represents). The results
+ * 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)
+ {
+ int viewCount = getViewCount();
+ for (int i = 0; i < viewCount; i++)
+ {
+ View view = getView(i);
+ int min = (int) view.getMinimumSpan(axis);
+ int max;
+ // Handle CSS span value of child.
+ Length length = cssSpans[axis];
+ if (length != null)
+ {
+ min = Math.max((int) length.getValue(targetSpan), min);
+ max = min;
+ }
+ else
+ max = (int) view.getMaximumSpan(axis);
+
+ if (max < targetSpan)
+ {
+ // Align child.
+ float align = view.getAlignment(axis);
+ offsets[i] = (int) ((targetSpan - max) * align);
+ spans[i] = max;
+ }
+ else
+ {
+ offsets[i] = 0;
+ spans[i] = Math.max(min, targetSpan);
+ }
+
+ // Adjust according to CSS position info.
+ positionView(targetSpan, axis, i, offsets, spans);
+ }
+ }
+
+ /**
+ * Overridden to perform additional CSS layout (absolute/relative
+ * positioning).
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis,
+ int[] offsets, int[] spans)
+ {
+ super.layoutMajorAxis(targetSpan, axis, offsets, spans);
+
+ // Adjust according to CSS position info.
+ int viewCount = getViewCount();
+ for (int i = 0; i < viewCount; i++)
+ {
+ positionView(targetSpan, axis, i, offsets, spans);
+ }
+ }
+
+ /**
+ * Positions a view according to any additional CSS constraints.
+ *
+ * @param targetSpan the target span
+ * @param axis the axis
+ * @param i the index of the view
+ * @param offsets the offsets get placed here
+ * @param spans the spans get placed here
+ */
+ private void positionView(int targetSpan, int axis, int i, int[] offsets,
+ int[] spans)
+ {
+ View view = getView(i);
+ PositionInfo pos = (PositionInfo) positionInfo.get(view);
+ if (pos != null)
+ {
+ int p0 = -1;
+ int p1 = -1;
+ if (axis == X_AXIS)
+ {
+ Length l = pos.left;
+ if (l != null)
+ p0 = (int) l.getValue(targetSpan);
+ l = pos.right;
+ if (l != null)
+ p1 = (int) l.getValue(targetSpan);
+ }
+ else
+ {
+ Length l = pos.top;
+ if (l != null)
+ p0 = (int) l.getValue(targetSpan);
+ l = pos.bottom;
+ if (l != null)
+ p1 = (int) l.getValue(targetSpan);
+ }
+ if (pos.type == PositionInfo.ABSOLUTE
+ || pos.type == PositionInfo.FIXED)
+ {
+ if (p0 != -1)
+ {
+ offsets[i] = p0;
+ if (p1 != -1)
+ {
+ // Overrides computed width. (Possibly overconstrained
+ // when the width attribute was set too.)
+ spans[i] = targetSpan - p1 - offsets[i];
+ }
+ }
+ else if (p1 != -1)
+ {
+ // Preserve any computed width.
+ offsets[i] = targetSpan - p1 - spans[i];
+ }
+ }
+ else if (pos.type == PositionInfo.RELATIVE)
+ {
+ if (p0 != -1)
+ {
+ offsets[i] += p0;
+ if (p1 != -1)
+ {
+ // Overrides computed width. (Possibly overconstrained
+ // when the width attribute was set too.)
+ spans[i] = spans[i] - p0 - p1 - offsets[i];
+ }
+ }
+ else if (p1 != -1)
+ {
+ // Preserve any computed width.
+ offsets[i] -= p1;
+ }
+ }
+ }
+ }
+
+ /**
+ * Paints using the given graphics configuration and shape.
+ * This delegates to the css box painter to paint the
+ * 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 = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+
+ // Debug output. Shows blocks in green rectangles.
+ // g.setColor(Color.GREEN);
+ // g.drawRect(rect.x, rect.y, rect.width, rect.height);
+
+ painter.paint(g, rect.x, rect.y, rect.width, rect.height, this);
+ super.paint(g, a);
+ }
+
+ /**
+ * Fetches the attributes to use when painting.
+ *
+ * @return the attributes of this model.
+ */
+ public AttributeSet getAttributes()
+ {
+ if (attributes == null)
+ attributes = getStyleSheet().getViewAttributes(this);
+ return attributes;
+ }
+
+ /**
+ * 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 super.getAlignment(axis);
+ if (axis == Y_AXIS)
+ {
+ if (getViewCount() == 0)
+ return 0.0F;
+ float prefHeight = getPreferredSpan(Y_AXIS);
+ View first = getView(0);
+ float firstRowHeight = first.getPreferredSpan(Y_AXIS);
+ return prefHeight != 0 ? (firstRowHeight * first.getAlignment(Y_AXIS))
+ / prefHeight
+ : 0;
+ }
+ throw new IllegalArgumentException("Invalid Axis");
+ }
+
+ /**
+ * 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()
+ {
+ // Fetch attributes.
+ StyleSheet ss = getStyleSheet();
+ attributes = ss.getViewAttributes(this);
+
+ // Fetch painter.
+ painter = ss.getBoxPainter(attributes);
+
+ // Update insets.
+ if (attributes != null)
+ {
+ setInsets((short) painter.getInset(TOP, this),
+ (short) painter.getInset(LEFT, this),
+ (short) painter.getInset(BOTTOM, this),
+ (short) painter.getInset(RIGHT, this));
+ }
+
+ // Fetch width and height.
+ float emBase = ss.getEMBase(attributes);
+ float exBase = ss.getEXBase(attributes);
+ cssSpans[X_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.WIDTH);
+ if (cssSpans[X_AXIS] != null)
+ cssSpans[X_AXIS].setFontBases(emBase, exBase);
+ cssSpans[Y_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.HEIGHT);
+ if (cssSpans[Y_AXIS] != null)
+ cssSpans[Y_AXIS].setFontBases(emBase, exBase);
+ }
+
+ /**
+ * Gets the default style sheet.
+ *
+ * @return the style sheet
+ */
+ protected StyleSheet getStyleSheet()
+ {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Overridden to fetch additional CSS layout information.
+ */
+ public void replace(int offset, int length, View[] views)
+ {
+ // First remove unneeded stuff.
+ for (int i = 0; i < length; i++)
+ {
+ View child = getView(i + offset);
+ positionInfo.remove(child);
+ }
+
+ // Call super to actually replace the views.
+ super.replace(offset, length, views);
+
+ // Now fetch the position infos for the new views.
+ for (int i = 0; i < views.length; i++)
+ {
+ fetchLayoutInfo(views[i]);
+ }
+ }
+
+ /**
+ * Fetches and stores the layout info for the specified view.
+ *
+ * @param view the view for which the layout info is stored
+ */
+ private void fetchLayoutInfo(View view)
+ {
+ AttributeSet atts = view.getAttributes();
+ Object o = atts.getAttribute(CSS.Attribute.POSITION);
+ if (o != null && o instanceof String && ! o.equals("static"))
+ {
+ int type;
+ if (o.equals("relative"))
+ type = PositionInfo.RELATIVE;
+ else if (o.equals("absolute"))
+ type = PositionInfo.ABSOLUTE;
+ else if (o.equals("fixed"))
+ type = PositionInfo.FIXED;
+ else
+ type = PositionInfo.STATIC;
+
+ if (type != PositionInfo.STATIC)
+ {
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ Length left = (Length) atts.getAttribute(CSS.Attribute.LEFT);
+ if (left != null)
+ left.setFontBases(emBase, exBase);
+ Length right = (Length) atts.getAttribute(CSS.Attribute.RIGHT);
+ if (right != null)
+ right.setFontBases(emBase, exBase);
+ Length top = (Length) atts.getAttribute(CSS.Attribute.TOP);
+ if (top != null)
+ top.setFontBases(emBase, exBase);
+ Length bottom = (Length) atts.getAttribute(CSS.Attribute.BOTTOM);
+ if (bottom != null)
+ bottom.setFontBases(emBase, exBase);
+ if (left != null || right != null || top != null || bottom != null)
+ {
+ PositionInfo pos = new PositionInfo(type, left, right, top,
+ bottom);
+ positionInfo.put(view, pos);
+ }
+ }
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/CSS.java b/libjava/classpath/javax/swing/text/html/CSS.java
new file mode 100644
index 000000000..0a77bdff9
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/CSS.java
@@ -0,0 +1,736 @@
+/* CSS.java -- Provides CSS attributes
+ Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text.html;
+
+import gnu.javax.swing.text.html.css.BorderStyle;
+import gnu.javax.swing.text.html.css.BorderWidth;
+import gnu.javax.swing.text.html.css.CSSColor;
+import gnu.javax.swing.text.html.css.FontSize;
+import gnu.javax.swing.text.html.css.FontStyle;
+import gnu.javax.swing.text.html.css.FontWeight;
+import gnu.javax.swing.text.html.css.Length;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import javax.swing.text.MutableAttributeSet;
+
+/**
+ * Provides CSS attributes to be used by the HTML view classes. The constants
+ * defined here are used as keys for text attributes for use in
+ * {@link javax.swing.text.AttributeSet}s of {@link javax.swing.text.Element}s.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class CSS implements Serializable
+{
+ /**
+ * Returns an array of all CSS attributes.
+ *
+ * @return All available CSS.Attribute objects.
+ */
+ public static CSS.Attribute[] getAllAttributeKeys()
+ {
+ Object[] src = Attribute.attributeMap.values().toArray();
+ CSS.Attribute[] dst = new CSS.Attribute[ src.length ];
+ System.arraycopy(src, 0, dst, 0, src.length);
+ return dst;
+ }
+
+ /**
+ * Returns an a given CSS attribute.
+ *
+ * @param name - The name of the attribute.
+ * @return The CSS attribute with the given name, or <code>null</code> if
+ * no attribute with that name exists.
+ */
+ public static CSS.Attribute getAttribute(String name)
+ {
+ return (CSS.Attribute)Attribute.attributeMap.get( name );
+ }
+
+ public static final class Attribute
+ {
+ /**
+ * The CSS attribute 'background'.
+ */
+ public static final Attribute BACKGROUND =
+ new Attribute("background", false, null);
+
+ /**
+ * The CSS attribute 'background-attachment'.
+ */
+ public static final Attribute BACKGROUND_ATTACHMENT =
+ new Attribute("background-attachment", false, "scroll");
+
+ /**
+ * The CSS attribute 'background-color'.
+ */
+ public static final Attribute BACKGROUND_COLOR =
+ new Attribute("background-color", false, "transparent");
+
+ /**
+ * The CSS attribute 'background-image'.
+ */
+ public static final Attribute BACKGROUND_IMAGE =
+ new Attribute("background-image", false, "none");
+
+ /**
+ * The CSS attribute 'background-position'.
+ */
+ public static final Attribute BACKGROUND_POSITION =
+ new Attribute("background-position", false, null);
+
+ /**
+ * The CSS attribute 'background-repeat'.
+ */
+ public static final Attribute BACKGROUND_REPEAT =
+ new Attribute("background-repeat", false, "repeat");
+
+ /**
+ * The CSS attribute 'border'.
+ */
+ public static final Attribute BORDER = new Attribute("border", false, null);
+
+ /**
+ * The CSS attribute 'border-bottom'.
+ */
+ public static final Attribute BORDER_BOTTOM =
+ new Attribute("border-bottom", false, null);
+
+ /**
+ * The CSS attribute 'border-bottom-width'.
+ */
+ public static final Attribute BORDER_BOTTOM_WIDTH =
+ new Attribute("border-bottom-width", false, "medium");
+
+ /**
+ * The CSS attribute 'border-color'.
+ */
+ public static final Attribute BORDER_COLOR =
+ new Attribute("border-color", false, "black");
+
+ /**
+ * The CSS attribute 'border-left'.
+ */
+ public static final Attribute BORDER_LEFT =
+ new Attribute("border-left", false, null);
+
+ /**
+ * The CSS attribute 'border-left-width'.
+ */
+ public static final Attribute BORDER_LEFT_WIDTH =
+ new Attribute("border-left-width", false, "medium");
+
+ /**
+ * The CSS attribute 'border-right'.
+ */
+ public static final Attribute BORDER_RIGHT =
+ new Attribute("border-right", false, null);
+
+ /**
+ * The CSS attribute 'border-right-width'.
+ */
+ public static final Attribute BORDER_RIGHT_WIDTH =
+ new Attribute("border-right-width", false, "medium");
+
+ /**
+ * The CSS attribute 'border-style'.
+ */
+ public static final Attribute BORDER_STYLE =
+ new Attribute("border-style", false, "none");
+
+ /**
+ * The CSS attribute 'border-top'.
+ */
+ public static final Attribute BORDER_TOP =
+ new Attribute("border-top", false, null);
+
+ /**
+ * The CSS attribute 'border-top-width'.
+ */
+ public static final Attribute BORDER_TOP_WIDTH =
+ new Attribute("border-top-width", false, "medium");
+
+ /**
+ * The CSS attribute 'border-width'.
+ */
+ public static final Attribute BORDER_WIDTH =
+ new Attribute("border-width", false, "medium");
+
+ /**
+ * The CSS attribute 'clear'.
+ */
+ public static final Attribute CLEAR = new Attribute("clear", false, "none");
+
+ /**
+ * The CSS attribute 'color'.
+ */
+ public static final Attribute COLOR = new Attribute("color", true, "black");
+
+ /**
+ * The CSS attribute 'display'.
+ */
+ public static final Attribute DISPLAY =
+ new Attribute("display", false, "block");
+
+ /**
+ * The CSS attribute 'float'.
+ */
+ public static final Attribute FLOAT = new Attribute("float", false, "none");
+
+ /**
+ * The CSS attribute 'font'.
+ */
+ public static final Attribute FONT = new Attribute("font", true, null);
+
+ /**
+ * The CSS attribute 'font-family'.
+ */
+ public static final Attribute FONT_FAMILY =
+ new Attribute("font-family", true, null);
+
+ /**
+ * The CSS attribute 'font-size'.
+ */
+ public static final Attribute FONT_SIZE =
+ new Attribute("font-size", true, "medium");
+
+ /**
+ * The CSS attribute 'font-style'.
+ */
+ public static final Attribute FONT_STYLE =
+ new Attribute("font-style", true, "normal");
+
+ /**
+ * The CSS attribute 'font-variant'.
+ */
+ public static final Attribute FONT_VARIANT =
+ new Attribute("font-variant", true, "normal");
+
+ /**
+ * The CSS attribute 'font-weight'.
+ */
+ public static final Attribute FONT_WEIGHT =
+ new Attribute("font-weight", true, "normal");
+
+ /**
+ * The CSS attribute 'height'.
+ */
+ public static final Attribute HEIGHT =
+ new Attribute("height", false, "auto");
+
+ /**
+ * The CSS attribute 'letter-spacing'.
+ */
+ public static final Attribute LETTER_SPACING =
+ new Attribute("letter-spacing", true, "normal");
+
+ /**
+ * The CSS attribute 'line-height'.
+ */
+ public static final Attribute LINE_HEIGHT =
+ new Attribute("line-height", true, "normal");
+
+ /**
+ * The CSS attribute 'list-style'.
+ */
+ public static final Attribute LIST_STYLE =
+ new Attribute("list-style", true, null);
+
+ /**
+ * The CSS attribute 'list-style-image'.
+ */
+ public static final Attribute LIST_STYLE_IMAGE =
+ new Attribute("list-style-image", true, "none");
+
+ /**
+ * The CSS attribute 'list-style-position'.
+ */
+ public static final Attribute LIST_STYLE_POSITION =
+ new Attribute("list-style-position", true, "outside");
+
+ /**
+ * The CSS attribute 'list-style-type'.
+ */
+ public static final Attribute LIST_STYLE_TYPE =
+ new Attribute("list-style-type", true, "disc");
+
+ /**
+ * The CSS attribute 'margin'.
+ */
+ public static final Attribute MARGIN = new Attribute("margin", false, null);
+
+ /**
+ * The CSS attribute 'margin-bottom'.
+ */
+ public static final Attribute MARGIN_BOTTOM =
+ new Attribute("margin-bottom", false, "0");
+
+ /**
+ * The CSS attribute 'margin-left'.
+ */
+ public static final Attribute MARGIN_LEFT =
+ new Attribute("margin-left", false, "0");
+
+ /**
+ * The CSS attribute 'margin-right'.
+ */
+ public static final Attribute MARGIN_RIGHT =
+ new Attribute("margin-right", false, "0");
+
+ /**
+ * The CSS attribute 'margin-top'.
+ */
+ public static final Attribute MARGIN_TOP =
+ new Attribute("margin-top", false, "0");
+
+ /**
+ * The CSS attribute 'padding'.
+ */
+ public static final Attribute PADDING =
+ new Attribute("padding", false, null);
+
+ /**
+ * The CSS attribute 'padding-bottom'.
+ */
+ public static final Attribute PADDING_BOTTOM =
+ new Attribute("padding-bottom", false, "0");
+
+ /**
+ * The CSS attribute 'padding-left'.
+ */
+ public static final Attribute PADDING_LEFT =
+ new Attribute("padding-left", false, "0");
+
+ /**
+ * The CSS attribute 'padding-right'.
+ */
+ public static final Attribute PADDING_RIGHT =
+ new Attribute("padding-right", false, "0");
+
+ /**
+ * The CSS attribute 'padding-top'.
+ */
+ public static final Attribute PADDING_TOP =
+ new Attribute("padding-top", false, "0");
+
+ /**
+ * The CSS attribute 'text-align'.
+ */
+ public static final Attribute TEXT_ALIGN =
+ new Attribute("text-align", true, null);
+
+ /**
+ * The CSS attribute 'text-decoration'.
+ */
+ public static final Attribute TEXT_DECORATION =
+ new Attribute("text-decoration", true, "none");
+
+ /**
+ * The CSS attribute 'text-indent'.
+ */
+ public static final Attribute TEXT_INDENT =
+ new Attribute("text-indent", true, "0");
+
+ /**
+ * The CSS attribute 'text-transform'.
+ */
+ public static final Attribute TEXT_TRANSFORM =
+ new Attribute("text-transform", true, "none");
+
+ /**
+ * The CSS attribute 'vertical-align'.
+ */
+ public static final Attribute VERTICAL_ALIGN =
+ new Attribute("vertical-align", false, "baseline");
+
+ /**
+ * The CSS attribute 'white-space'.
+ */
+ public static final Attribute WHITE_SPACE =
+ new Attribute("white-space", true, "normal");
+
+ /**
+ * The CSS attribute 'width'.
+ */
+ public static final Attribute WIDTH =
+ new Attribute("width", false, "auto");
+
+ /**
+ * The CSS attribute 'word-spacing'.
+ */
+ public static final Attribute WORD_SPACING =
+ new Attribute("word-spacing", true, "normal");
+
+ // Some GNU Classpath specific extensions.
+ static final Attribute BORDER_TOP_STYLE =
+ new Attribute("border-top-style", false, null);
+ static final Attribute BORDER_BOTTOM_STYLE =
+ new Attribute("border-bottom-style", false, null);
+ static final Attribute BORDER_LEFT_STYLE =
+ new Attribute("border-left-style", false, null);
+ static final Attribute BORDER_RIGHT_STYLE =
+ new Attribute("border-right-style", false, null);
+ static final Attribute BORDER_TOP_COLOR =
+ new Attribute("border-top-color", false, null);
+ static final Attribute BORDER_BOTTOM_COLOR =
+ new Attribute("border-bottom-color", false, null);
+ static final Attribute BORDER_LEFT_COLOR =
+ new Attribute("border-left-color", false, null);
+ static final Attribute BORDER_RIGHT_COLOR =
+ new Attribute("border-right-color", false, null);
+ static final Attribute BORDER_SPACING =
+ new Attribute("border-spacing", false, null);
+ static final Attribute POSITION =
+ new Attribute("position", false, null);
+ static final Attribute LEFT =
+ new Attribute("left", false, null);
+ static final Attribute RIGHT =
+ new Attribute("right", false, null);
+ static final Attribute TOP =
+ new Attribute("top", false, null);
+ static final Attribute BOTTOM =
+ new Attribute("bottom", false, null);
+
+ /**
+ * The attribute string.
+ */
+ String attStr;
+
+ /**
+ * Indicates if this attribute should be inherited from it's parent or
+ * not.
+ */
+ boolean isInherited;
+
+ /**
+ * A default value for this attribute if one exists, otherwise null.
+ */
+ String defaultValue;
+
+ /**
+ * A HashMap of all attributes.
+ */
+ static HashMap attributeMap;
+
+ /**
+ * Creates a new Attribute instance with the specified values.
+ *
+ * @param attr the attribute string
+ * @param inherited if the attribute should be inherited or not
+ * @param def a default value; may be <code>null</code>
+ */
+ Attribute(String attr, boolean inherited, String def)
+ {
+ attStr = attr;
+ isInherited = inherited;
+ defaultValue = def;
+ if( attributeMap == null)
+ attributeMap = new HashMap();
+ attributeMap.put( attr, this );
+ }
+
+ /**
+ * Returns the string representation of this attribute as specified
+ * in the CSS specification.
+ */
+ public String toString()
+ {
+ return attStr;
+ }
+
+ /**
+ * Returns <code>true</code> if the attribute should be inherited from
+ * the parent, <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if the attribute should be inherited from
+ * the parent, <code>false</code> otherwise
+ */
+ public boolean isInherited()
+ {
+ return isInherited;
+ }
+
+ /**
+ * Returns the default value of this attribute if one exists,
+ * <code>null</code> otherwise.
+ *
+ * @return the default value of this attribute if one exists,
+ * <code>null</code> otherwise
+ */
+ public String getDefaultValue()
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Maps attribute values (String) to some converter class, based on the
+ * key.
+ *
+ * @param att the key
+ * @param v the value
+ *
+ * @return the wrapped value
+ */
+ static Object getValue(Attribute att, String v)
+ {
+ Object o;
+ if (att == Attribute.FONT_SIZE)
+ o = new FontSize(v);
+ else if (att == Attribute.FONT_WEIGHT)
+ o = new FontWeight(v);
+ else if (att == Attribute.FONT_STYLE)
+ o = new FontStyle(v);
+ else if (att == Attribute.COLOR || att == Attribute.BACKGROUND_COLOR
+ || att == Attribute.BORDER_COLOR
+ || att == Attribute.BORDER_TOP_COLOR
+ || att == Attribute.BORDER_BOTTOM_COLOR
+ || att == Attribute.BORDER_LEFT_COLOR
+ || att == Attribute.BORDER_RIGHT_COLOR)
+ o = new CSSColor(v);
+ else if (att == Attribute.MARGIN || att == Attribute.MARGIN_BOTTOM
+ || att == Attribute.MARGIN_LEFT || att == Attribute.MARGIN_RIGHT
+ || att == Attribute.MARGIN_TOP || att == Attribute.WIDTH
+ || att == Attribute.HEIGHT
+ || att == Attribute.PADDING || att == Attribute.PADDING_BOTTOM
+ || att == Attribute.PADDING_LEFT || att == Attribute.PADDING_RIGHT
+ || att == Attribute.PADDING_TOP
+ || att == Attribute.LEFT || att == Attribute.RIGHT
+ || att == Attribute.TOP || att == Attribute.BOTTOM)
+ o = new Length(v);
+ else if (att == Attribute.BORDER_WIDTH || att == Attribute.BORDER_TOP_WIDTH
+ || att == Attribute.BORDER_LEFT_WIDTH
+ || att == Attribute.BORDER_RIGHT_WIDTH
+ || att == Attribute.BORDER_BOTTOM_WIDTH)
+ o = new BorderWidth(v);
+ else
+ o = v;
+ return o;
+ }
+
+ static void addInternal(MutableAttributeSet atts, Attribute a, String v)
+ {
+ if (a == Attribute.BACKGROUND)
+ parseBackgroundShorthand(atts, v);
+ else if (a == Attribute.PADDING)
+ parsePaddingShorthand(atts, v);
+ else if (a == Attribute.MARGIN)
+ parseMarginShorthand(atts, v);
+ else if (a == Attribute.BORDER || a == Attribute.BORDER_LEFT
+ || a == Attribute.BORDER_RIGHT || a == Attribute.BORDER_TOP
+ || a == Attribute.BORDER_BOTTOM)
+ parseBorderShorthand(atts, v, a);
+ }
+
+ /**
+ * Parses the background shorthand and translates it to more specific
+ * background attributes.
+ *
+ * @param atts the attributes
+ * @param v the value
+ */
+ private static void parseBackgroundShorthand(MutableAttributeSet atts,
+ String v)
+ {
+ StringTokenizer tokens = new StringTokenizer(v, " ");
+ while (tokens.hasMoreElements())
+ {
+ String token = tokens.nextToken();
+ if (CSSColor.isValidColor(token))
+ atts.addAttribute(Attribute.BACKGROUND_COLOR,
+ new CSSColor(token));
+ }
+ }
+
+ /**
+ * Parses the padding shorthand and translates to the specific padding
+ * values.
+ *
+ * @param atts the attributes
+ * @param v the actual value
+ */
+ private static void parsePaddingShorthand(MutableAttributeSet atts, String v)
+ {
+ StringTokenizer tokens = new StringTokenizer(v, " ");
+ int numTokens = tokens.countTokens();
+ if (numTokens == 1)
+ {
+ Length l = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l);
+ atts.addAttribute(Attribute.PADDING_LEFT, l);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l);
+ atts.addAttribute(Attribute.PADDING_TOP, l);
+ }
+ else if (numTokens == 2)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l1);
+ atts.addAttribute(Attribute.PADDING_TOP, l1);
+ atts.addAttribute(Attribute.PADDING_LEFT, l2);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l2);
+ }
+ else if (numTokens == 3)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_TOP, l1);
+ atts.addAttribute(Attribute.PADDING_LEFT, l2);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l2);
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l3);
+ }
+ else
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ Length l4 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.PADDING_TOP, l1);
+ atts.addAttribute(Attribute.PADDING_RIGHT, l2);
+ atts.addAttribute(Attribute.PADDING_BOTTOM, l3);
+ atts.addAttribute(Attribute.PADDING_LEFT, l4);
+ }
+ }
+
+ /**
+ * Parses the margin shorthand and translates to the specific margin
+ * values.
+ *
+ * @param atts the attributes
+ * @param v the actual value
+ */
+ private static void parseMarginShorthand(MutableAttributeSet atts, String v)
+ {
+ StringTokenizer tokens = new StringTokenizer(v, " ");
+ int numTokens = tokens.countTokens();
+ if (numTokens == 1)
+ {
+ Length l = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l);
+ atts.addAttribute(Attribute.MARGIN_TOP, l);
+ }
+ else if (numTokens == 2)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l1);
+ atts.addAttribute(Attribute.MARGIN_TOP, l1);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l2);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l2);
+ }
+ else if (numTokens == 3)
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_TOP, l1);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l2);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l2);
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l3);
+ }
+ else
+ {
+ Length l1 = new Length(tokens.nextToken());
+ Length l2 = new Length(tokens.nextToken());
+ Length l3 = new Length(tokens.nextToken());
+ Length l4 = new Length(tokens.nextToken());
+ atts.addAttribute(Attribute.MARGIN_TOP, l1);
+ atts.addAttribute(Attribute.MARGIN_RIGHT, l2);
+ atts.addAttribute(Attribute.MARGIN_BOTTOM, l3);
+ atts.addAttribute(Attribute.MARGIN_LEFT, l4);
+ }
+ }
+
+ /**
+ * Parses the CSS border shorthand attribute and translates it to the
+ * more specific border attributes.
+ *
+ * @param atts the attribute
+ * @param value the value
+ */
+ private static void parseBorderShorthand(MutableAttributeSet atts,
+ String value, Attribute cssAtt)
+ {
+ StringTokenizer tokens = new StringTokenizer(value, " ");
+ while (tokens.hasMoreTokens())
+ {
+ String token = tokens.nextToken();
+ if (BorderStyle.isValidStyle(token))
+ {
+ if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_LEFT_STYLE, token);
+ if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_RIGHT_STYLE, token);
+ if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_BOTTOM_STYLE, token);
+ if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_TOP_STYLE, token);
+ }
+ else if (BorderWidth.isValid(token))
+ {
+ BorderWidth w = new BorderWidth(token);
+ if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_LEFT_WIDTH, w);
+ if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_RIGHT_WIDTH, w);
+ if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_BOTTOM_WIDTH, w);
+ if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_TOP_WIDTH, w);
+ }
+ else if (CSSColor.isValidColor(token))
+ {
+ CSSColor c = new CSSColor(token);
+ if (cssAtt == Attribute.BORDER_LEFT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_LEFT_COLOR, c);
+ if (cssAtt == Attribute.BORDER_RIGHT || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_RIGHT_COLOR, c);
+ if (cssAtt == Attribute.BORDER_BOTTOM || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_BOTTOM_COLOR, c);
+ if (cssAtt == Attribute.BORDER_TOP || cssAtt == Attribute.BORDER)
+ atts.addAttribute(Attribute.BORDER_TOP_COLOR, c);
+ }
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/CSSBorder.java b/libjava/classpath/javax/swing/text/html/CSSBorder.java
new file mode 100644
index 000000000..23fcdccc6
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/CSSBorder.java
@@ -0,0 +1,421 @@
+/* CSSBorder.java -- A border for rendering CSS border styles
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import gnu.javax.swing.text.html.css.BorderWidth;
+import gnu.javax.swing.text.html.css.CSSColor;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+
+import javax.swing.border.Border;
+import javax.swing.text.AttributeSet;
+
+/**
+ * A border implementation to render CSS border styles.
+ */
+class CSSBorder
+ implements Border
+{
+
+ /**
+ * The CSS border styles.
+ */
+
+ private static final int STYLE_NOT_SET = -1;
+ private static final int STYLE_NONE = 0;
+ private static final int STYLE_HIDDEN = 1;
+ private static final int STYLE_DOTTED = 2;
+ private static final int STYLE_DASHED = 3;
+ private static final int STYLE_SOLID = 4;
+ private static final int STYLE_DOUBLE = 5;
+ private static final int STYLE_GROOVE = 6;
+ private static final int STYLE_RIDGE = 7;
+ private static final int STYLE_INSET = 8;
+ private static final int STYLE_OUTSET = 9;
+
+ /**
+ * The left insets.
+ */
+ private int left;
+
+ /**
+ * The right insets.
+ */
+ private int right;
+
+ /**
+ * The top insets.
+ */
+ private int top;
+
+ /**
+ * The bottom insets.
+ */
+ private int bottom;
+
+ /**
+ * The border style on the left.
+ */
+ private int leftStyle;
+
+ /**
+ * The border style on the right.
+ */
+ private int rightStyle;
+
+ /**
+ * The border style on the top.
+ */
+ private int topStyle;
+
+ /**
+ * The color for the top border.
+ */
+ private Color topColor;
+
+ /**
+ * The color for the bottom border.
+ */
+ private Color bottomColor;
+
+ /**
+ * The color for the left border.
+ */
+ private Color leftColor;
+
+ /**
+ * The color for the right border.
+ */
+ private Color rightColor;
+
+ /**
+ * The border style on the bottom.
+ */
+ private int bottomStyle;
+
+ /**
+ * Creates a new CSS border and fetches its attributes from the specified
+ * attribute set.
+ *
+ * @param atts the attribute set that contains the border spec
+ */
+ CSSBorder(AttributeSet atts, StyleSheet ss)
+ {
+ // Determine the border styles.
+ int style = getBorderStyle(atts, CSS.Attribute.BORDER_STYLE);
+ if (style == STYLE_NOT_SET)
+ style = STYLE_NONE; // Default to none.
+ topStyle = bottomStyle = leftStyle = rightStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_TOP_STYLE);
+ if (style != STYLE_NOT_SET)
+ topStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_BOTTOM_STYLE);
+ if (style != STYLE_NOT_SET)
+ bottomStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_LEFT_STYLE);
+ if (style != STYLE_NOT_SET)
+ leftStyle = style;
+ style = getBorderStyle(atts, CSS.Attribute.BORDER_RIGHT_STYLE);
+ if (style != STYLE_NOT_SET)
+ rightStyle = style;
+
+ // Determine the border colors.
+ Color color = getBorderColor(atts, CSS.Attribute.BORDER_COLOR);
+ if (color == null)
+ color = Color.BLACK;
+ topColor = bottomColor = leftColor = rightColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_TOP_COLOR);
+ if (color != null)
+ topColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_BOTTOM_COLOR);
+ if (color != null)
+ bottomColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_LEFT_COLOR);
+ if (color != null)
+ leftColor = color;
+ color = getBorderColor(atts, CSS.Attribute.BORDER_RIGHT_COLOR);
+ if (color != null)
+ rightColor = color;
+
+ // Determine the border widths.
+ int width = getBorderWidth(atts, CSS.Attribute.BORDER_WIDTH, ss);
+ if (width == -1)
+ width = 0;
+ top = bottom = left = right = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_TOP_WIDTH, ss);
+ if (width >= 0)
+ top = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_BOTTOM_WIDTH, ss);
+ if (width >= 0)
+ bottom = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_LEFT_WIDTH, ss);
+ if (width >= 0)
+ left = width;
+ width = getBorderWidth(atts, CSS.Attribute.BORDER_RIGHT_WIDTH, ss);
+ if (width >= 0)
+ right = width;
+ }
+
+ /**
+ * Determines the border style for a given CSS attribute.
+ *
+ * @param atts the attribute set
+ * @param key the CSS key
+ *
+ * @return the border style according to the constants defined in this class
+ */
+ private int getBorderStyle(AttributeSet atts, CSS.Attribute key)
+ {
+ int style = STYLE_NOT_SET;
+ Object o = atts.getAttribute(key);
+ if (o != null)
+ {
+ String cssStyle = o.toString();
+ if (cssStyle.equals("none"))
+ style = STYLE_NONE;
+ else if (cssStyle.equals("hidden"))
+ style = STYLE_HIDDEN;
+ else if (cssStyle.equals("dotted"))
+ style = STYLE_DOTTED;
+ else if (cssStyle.equals("dashed"))
+ style = STYLE_DASHED;
+ else if (cssStyle.equals("solid"))
+ style = STYLE_SOLID;
+ else if (cssStyle.equals("double"))
+ style = STYLE_DOUBLE;
+ else if (cssStyle.equals("groove"))
+ style = STYLE_GROOVE;
+ else if (cssStyle.equals("ridge"))
+ style = STYLE_RIDGE;
+ else if (cssStyle.equals("inset"))
+ style = STYLE_INSET;
+ else if (cssStyle.equals("outset"))
+ style = STYLE_OUTSET;
+ }
+ return style;
+ }
+
+ /**
+ * Determines the border color for the specified key.
+ *
+ * @param atts the attribute set from which to fetch the color
+ * @param key the CSS key
+ *
+ * @return the border color
+ */
+ private Color getBorderColor(AttributeSet atts, CSS.Attribute key)
+ {
+ Object o = atts.getAttribute(key);
+ Color color = null;
+ if (o instanceof CSSColor)
+ {
+ CSSColor cssColor = (CSSColor) o;
+ color = cssColor.getValue();
+ }
+ return color;
+ }
+
+ /**
+ * Returns the width for the specified key.
+ *
+ * @param atts the attributes to fetch the width from
+ * @param key the CSS key
+ *
+ * @return the width, or -1 of none has been set
+ */
+ private int getBorderWidth(AttributeSet atts, CSS.Attribute key,
+ StyleSheet ss)
+ {
+ int width = -1;
+ Object o = atts.getAttribute(key);
+ if (o instanceof BorderWidth)
+ {
+ BorderWidth w = (BorderWidth) o;
+ w.setFontBases(ss.getEMBase(atts), ss.getEXBase(atts));
+ width = (int) ((BorderWidth) o).getValue();
+ }
+ return width;
+ }
+
+ /**
+ * Returns the border insets.
+ */
+ public Insets getBorderInsets(Component c)
+ {
+ return new Insets(top, left, bottom, right);
+ }
+
+ /**
+ * CSS borders are generally opaque so return true here.
+ */
+ public boolean isBorderOpaque()
+ {
+ return true;
+ }
+
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height)
+ {
+ // Top border.
+ paintBorderLine(g, x, y + top / 2, x + width, y + top / 2, topStyle, top,
+ topColor, false);
+ // Left border.
+ paintBorderLine(g, x + left / 2, y, x + left / 2, y + height, leftStyle,
+ left, leftColor, true);
+ // Bottom border.
+ paintBorderLine(g, x, y + height - bottom / 2, x + width,
+ y + height - bottom / 2, topStyle, bottom, bottomColor,
+ false);
+ // Right border.
+ paintBorderLine(g, x + width - right / 2, y, x + width - right / 2,
+ y + height, topStyle, right, rightColor, true);
+
+ }
+
+ private void paintBorderLine(Graphics g, int x1, int y1, int x2, int y2,
+ int style, int width, Color color,
+ boolean vertical)
+ {
+ switch (style)
+ {
+ case STYLE_DOTTED:
+ paintDottedLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_DASHED:
+ paintDashedLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_SOLID:
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_DOUBLE:
+ paintDoubleLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_GROOVE:
+ paintGrooveLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_RIDGE:
+ paintRidgeLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_OUTSET:
+ paintOutsetLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_INSET:
+ paintInsetLine(g, x1, y1, x2, y2, width, color, vertical);
+ break;
+ case STYLE_NONE:
+ case STYLE_HIDDEN:
+ default:
+ // Nothing to do in these cases.
+ }
+ }
+
+ private void paintDottedLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintDashedLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintSolidLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ int x = Math.min(x1, x2);
+ int y = Math.min(y1, y1);
+ int w = Math.abs(x2 - x1);
+ int h = Math.abs(y2 - y1);
+ if (vertical)
+ {
+ w = width;
+ x -= width / 2;
+ }
+ else
+ {
+ h = width;
+ y -= width / 2;
+ }
+ g.setColor(color);
+ g.fillRect(x, y, w, h);
+ }
+
+ private void paintDoubleLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintGrooveLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintRidgeLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintOutsetLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+ private void paintInsetLine(Graphics g, int x1, int y1, int x2, int y2,
+ int width, Color color, boolean vertical)
+ {
+ // FIXME: Implement this.
+ paintSolidLine(g, x1, y1, x2, y2, width, color, vertical);
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/CSSParser.java b/libjava/classpath/javax/swing/text/html/CSSParser.java
new file mode 100644
index 000000000..5024c7b59
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/CSSParser.java
@@ -0,0 +1,561 @@
+/* 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;
+
+ /**
+ * 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()
+ {
+ 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;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java
new file mode 100644
index 000000000..bc7c36f4b
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/FormSubmitEvent.java
@@ -0,0 +1,123 @@
+/* FormSubmitEvent.java -- Event fired on form submit
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.net.URL;
+
+import javax.swing.text.Element;
+
+/**
+ * The event fired on form submit.
+ *
+ * @since 1.5
+ */
+public class FormSubmitEvent
+ extends HTMLFrameHyperlinkEvent
+{
+
+ // FIXME: Use enums when available.
+ /**
+ * The submit method.
+ */
+ public static class MethodType
+ {
+ /**
+ * Indicates a form submit with HTTP method POST.
+ */
+ public static final MethodType POST = new MethodType();
+
+ /**
+ * Indicates a form submit with HTTP method GET.
+ */
+ public static final MethodType GET = new MethodType();
+
+ private MethodType()
+ {
+ }
+ }
+
+ /**
+ * The submit method.
+ */
+ private MethodType method;
+
+ /**
+ * The actual submit data.
+ */
+ private String data;
+
+ /**
+ * Creates a new FormSubmitEvent.
+ *
+ * @param source the source
+ * @param type the type of hyperlink update
+ * @param url the action url
+ * @param el the associated element
+ * @param target the target attribute
+ * @param m the submit method
+ * @param d the submit data
+ */
+ FormSubmitEvent(Object source, EventType type, URL url, Element el,
+ String target, MethodType m, String d)
+ {
+ super(source, type, url, el, target);
+ method = m;
+ data = d;
+ }
+
+ /**
+ * Returns the submit data.
+ *
+ * @return the submit data
+ */
+ public String getData()
+ {
+ return data;
+ }
+
+ /**
+ * Returns the HTTP submit method.
+ *
+ * @return the HTTP submit method
+ */
+ public MethodType getMethod()
+ {
+ return method;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/FormView.java b/libjava/classpath/javax/swing/text/html/FormView.java
new file mode 100644
index 000000000..61c568f02
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/FormView.java
@@ -0,0 +1,870 @@
+/* FormView.java -- A view for a variety of HTML form elements
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+
+import javax.swing.ButtonModel;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JEditorPane;
+import javax.swing.JList;
+import javax.swing.JPasswordField;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.ComponentView;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.ElementIterator;
+import javax.swing.text.StyleConstants;
+
+/**
+ * A View that renders HTML form elements like buttons and input fields.
+ * This is implemented as a {@link ComponentView} that creates different Swing
+ * component depending on the type and setting of the different form elements.
+ *
+ * Namely, this view creates the following components:
+ * <table>
+ * <tr><th>Element type</th><th>Swing component</th></tr>
+ * <tr><td>input, button</td><td>JButton</td></tr>
+ * <tr><td>input, checkbox</td><td>JButton</td></tr>
+ * <tr><td>input, image</td><td>JButton</td></tr>
+ * <tr><td>input, password</td><td>JButton</td></tr>
+ * <tr><td>input, radio</td><td>JButton</td></tr>
+ * <tr><td>input, reset</td><td>JButton</td></tr>
+ * <tr><td>input, submit</td><td>JButton</td></tr>
+ * <tr><td>input, text</td><td>JButton</td></tr>
+ * <tr><td>select, size > 1 or with multiple attribute</td>
+ * <td>JList in JScrollPane</td></tr>
+ * <tr><td>select, size unspecified or == 1</td><td>JComboBox</td></tr>
+ * <tr><td>textarea, text</td><td>JTextArea in JScrollPane</td></tr>
+ * <tr><td>input, file</td><td>JTextField</td></tr>
+ * </table>
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class FormView
+ extends ComponentView
+ implements ActionListener
+{
+
+ protected class MouseEventListener
+ extends MouseAdapter
+ {
+ /**
+ * Creates a new <code>MouseEventListener</code>.
+ */
+ protected MouseEventListener()
+ {
+ // Nothing to do here.
+ }
+
+ public void mouseReleased(MouseEvent ev)
+ {
+ String data = getImageData(ev.getPoint());
+ imageSubmit(data);
+ }
+ }
+
+ /**
+ * Actually submits the form data.
+ */
+ private class SubmitThread
+ extends Thread
+ {
+ /**
+ * The submit data.
+ */
+ private String data;
+
+ /**
+ * Creates a new SubmitThread.
+ *
+ * @param d the submit data
+ */
+ SubmitThread(String d)
+ {
+ data = d;
+ }
+
+ /**
+ * Actually performs the submit.
+ */
+ public void run()
+ {
+ if (data.length() > 0)
+ {
+ final String method = getMethod();
+ final URL actionURL = getActionURL();
+ final String target = getTarget();
+ URLConnection conn;
+ final JEditorPane editor = (JEditorPane) getContainer();
+ final HTMLDocument doc = (HTMLDocument) editor.getDocument();
+ HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit();
+ if (kit.isAutoFormSubmission())
+ {
+ try
+ {
+ final URL url;
+ if (method != null && method.equals("post"))
+ {
+ // Perform POST.
+ url = actionURL;
+ conn = url.openConnection();
+ postData(conn, data);
+ }
+ else
+ {
+ // Default to GET.
+ url = new URL(actionURL + "?" + data);
+ }
+ Runnable loadDoc = new Runnable()
+ {
+ public void run()
+ {
+ if (doc.isFrameDocument())
+ {
+ editor.fireHyperlinkUpdate(createSubmitEvent(method,
+ actionURL,
+ target));
+ }
+ else
+ {
+ try
+ {
+ editor.setPage(url);
+ }
+ catch (IOException ex)
+ {
+ // Oh well.
+ ex.printStackTrace();
+ }
+ }
+ }
+ };
+ SwingUtilities.invokeLater(loadDoc);
+ }
+ catch (MalformedURLException ex)
+ {
+ ex.printStackTrace();
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ else
+ {
+ editor.fireHyperlinkUpdate(createSubmitEvent(method,actionURL,
+ target));
+ }
+ }
+ }
+
+ /**
+ * Determines the submit method.
+ *
+ * @return the submit method
+ */
+ private String getMethod()
+ {
+ AttributeSet formAtts = getFormAttributes();
+ String method = null;
+ if (formAtts != null)
+ {
+ method = (String) formAtts.getAttribute(HTML.Attribute.METHOD);
+ }
+ return method;
+ }
+
+ /**
+ * Determines the action URL.
+ *
+ * @return the action URL
+ */
+ private URL getActionURL()
+ {
+ AttributeSet formAtts = getFormAttributes();
+ HTMLDocument doc = (HTMLDocument) getElement().getDocument();
+ URL url = doc.getBase();
+ if (formAtts != null)
+ {
+ String action =
+ (String) formAtts.getAttribute(HTML.Attribute.ACTION);
+ if (action != null)
+ {
+ try
+ {
+ url = new URL(url, action);
+ }
+ catch (MalformedURLException ex)
+ {
+ url = null;
+ }
+ }
+ }
+ return url;
+ }
+
+ /**
+ * Fetches the target attribute.
+ *
+ * @return the target attribute or _self if none is present
+ */
+ private String getTarget()
+ {
+ AttributeSet formAtts = getFormAttributes();
+ String target = null;
+ if (formAtts != null)
+ {
+ target = (String) formAtts.getAttribute(HTML.Attribute.TARGET);
+ if (target != null)
+ target = target.toLowerCase();
+ }
+ if (target == null)
+ target = "_self";
+ return target;
+ }
+
+ /**
+ * Posts the form data over the specified connection.
+ *
+ * @param conn the connection
+ */
+ private void postData(URLConnection conn, String data)
+ {
+ conn.setDoOutput(true);
+ PrintWriter out = null;
+ try
+ {
+ out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
+ out.print(data);
+ out.flush();
+ }
+ catch (IOException ex)
+ {
+ // Deal with this!
+ ex.printStackTrace();
+ }
+ finally
+ {
+ if (out != null)
+ out.close();
+ }
+ }
+
+ /**
+ * Determines the attributes from the relevant form tag.
+ *
+ * @return the attributes from the relevant form tag, <code>null</code>
+ * when there is no form tag
+ */
+ private AttributeSet getFormAttributes()
+ {
+ AttributeSet atts = null;
+ Element form = getFormElement();
+ if (form != null)
+ atts = form.getAttributes();
+ return atts;
+ }
+
+ /**
+ * Creates the submit event that should be fired.
+ *
+ * This is package private to avoid accessor methods.
+ *
+ * @param method the submit method
+ * @param actionURL the action URL
+ * @param target the target
+ *
+ * @return the submit event
+ */
+ FormSubmitEvent createSubmitEvent(String method, URL actionURL,
+ String target)
+ {
+ FormSubmitEvent.MethodType m = "post".equals(method)
+ ? FormSubmitEvent.MethodType.POST
+ : FormSubmitEvent.MethodType.GET;
+ return new FormSubmitEvent(FormView.this,
+ HyperlinkEvent.EventType.ACTIVATED,
+ actionURL, getElement(), target, m, data);
+ }
+ }
+
+ /**
+ * If the value attribute of an <code>&lt;input type=&quot;submit&quot;&gt>
+ * tag is not specified, then this string is used.
+ *
+ * @deprecated As of JDK1.3 the value is fetched from the UIManager property
+ * <code>FormView.submitButtonText</code>.
+ */
+ public static final String SUBMIT =
+ UIManager.getString("FormView.submitButtonText");
+
+ /**
+ * If the value attribute of an <code>&lt;input type=&quot;reset&quot;&gt>
+ * tag is not specified, then this string is used.
+ *
+ * @deprecated As of JDK1.3 the value is fetched from the UIManager property
+ * <code>FormView.resetButtonText</code>.
+ */
+ public static final String RESET =
+ UIManager.getString("FormView.resetButtonText");
+
+ /**
+ * If this is true, the maximum size is set to the preferred size.
+ */
+ private boolean maxIsPreferred;
+
+ /**
+ * Creates a new <code>FormView</code>.
+ *
+ * @param el the element that is displayed by this view.
+ */
+ public FormView(Element el)
+ {
+ super(el);
+ }
+
+ /**
+ * Creates the correct AWT component for rendering the form element.
+ */
+ protected Component createComponent()
+ {
+ Component comp = null;
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ Object tag = atts.getAttribute(StyleConstants.NameAttribute);
+ Object model = atts.getAttribute(StyleConstants.ModelAttribute);
+ if (tag.equals(HTML.Tag.INPUT))
+ {
+ String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
+ if (type.equals("button"))
+ {
+ String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ JButton b = new JButton(value);
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
+ }
+ else if (type.equals("checkbox"))
+ {
+ if (model instanceof ResetableToggleButtonModel)
+ {
+ ResetableToggleButtonModel m =
+ (ResetableToggleButtonModel) model;
+ JCheckBox c = new JCheckBox();
+ c.setModel(m);
+ comp = c;
+ maxIsPreferred = true;
+ }
+ }
+ else if (type.equals("image"))
+ {
+ String src = (String) atts.getAttribute(HTML.Attribute.SRC);
+ JButton b;
+ try
+ {
+ URL base = ((HTMLDocument) el.getDocument()).getBase();
+ URL srcURL = new URL(base, src);
+ ImageIcon icon = new ImageIcon(srcURL);
+ b = new JButton(icon);
+ }
+ catch (MalformedURLException ex)
+ {
+ b = new JButton(src);
+ }
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
+ }
+ else if (type.equals("password"))
+ {
+ int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
+ -1);
+ JTextField tf = new JPasswordField();
+ if (size > 0)
+ tf.setColumns(size);
+ else
+ tf.setColumns(20);
+ if (model != null)
+ tf.setDocument((Document) model);
+ tf.addActionListener(this);
+ comp = tf;
+ maxIsPreferred = true;
+ }
+ else if (type.equals("radio"))
+ {
+ if (model instanceof ResetableToggleButtonModel)
+ {
+ ResetableToggleButtonModel m =
+ (ResetableToggleButtonModel) model;
+ JRadioButton c = new JRadioButton();
+ c.setModel(m);
+ comp = c;
+ maxIsPreferred = true;
+ }
+ }
+ else if (type.equals("reset"))
+ {
+ String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = UIManager.getString("FormView.resetButtonText");
+ JButton b = new JButton(value);
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
+ }
+ else if (type.equals("submit"))
+ {
+ String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = UIManager.getString("FormView.submitButtonText");
+ JButton b = new JButton(value);
+ if (model != null)
+ {
+ b.setModel((ButtonModel) model);
+ b.addActionListener(this);
+ }
+ comp = b;
+ maxIsPreferred = true;
+ }
+ else if (type.equals("text"))
+ {
+ int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
+ -1);
+ JTextField tf = new JTextField();
+ if (size > 0)
+ tf.setColumns(size);
+ else
+ tf.setColumns(20);
+ if (model != null)
+ tf.setDocument((Document) model);
+ tf.addActionListener(this);
+ comp = tf;
+ maxIsPreferred = true;
+ }
+ }
+ else if (tag == HTML.Tag.TEXTAREA)
+ {
+ JTextArea textArea = new JTextArea((Document) model);
+ int rows = HTML.getIntegerAttributeValue(atts, HTML.Attribute.ROWS, 1);
+ textArea.setRows(rows);
+ int cols = HTML.getIntegerAttributeValue(atts, HTML.Attribute.COLS, 20);
+ textArea.setColumns(cols);
+ maxIsPreferred = true;
+ comp = new JScrollPane(textArea,
+ JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+ JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else if (tag == HTML.Tag.SELECT)
+ {
+ if (model instanceof SelectListModel)
+ {
+ SelectListModel slModel = (SelectListModel) model;
+ JList list = new JList(slModel);
+ int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
+ 1);
+ list.setVisibleRowCount(size);
+ list.setSelectionModel(slModel.getSelectionModel());
+ comp = new JScrollPane(list);
+ }
+ else if (model instanceof SelectComboBoxModel)
+ {
+ SelectComboBoxModel scbModel = (SelectComboBoxModel) model;
+ comp = new JComboBox(scbModel);
+ }
+ maxIsPreferred = true;
+ }
+ return comp;
+ }
+
+ /**
+ * Determines the maximum span for this view on the specified axis.
+ *
+ * @param axis the axis along which to determine the span
+ *
+ * @return the maximum span for this view on the specified axis
+ *
+ * @throws IllegalArgumentException if the axis is invalid
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float span;
+ if (maxIsPreferred)
+ span = getPreferredSpan(axis);
+ else
+ span = super.getMaximumSpan(axis);
+ return span;
+ }
+
+ /**
+ * Processes an action from the Swing component.
+ *
+ * If the action comes from a submit button, the form is submitted by calling
+ * {@link #submitData}. In the case of a reset button, the form is reset to
+ * the original state. If the action comes from a password or text field,
+ * then the input focus is transferred to the next input element in the form,
+ * unless this text/password field is the last one, in which case the form
+ * is submitted.
+ *
+ * @param ev the action event
+ */
+ public void actionPerformed(ActionEvent ev)
+ {
+ Element el = getElement();
+ Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute);
+ if (tag.equals(HTML.Tag.INPUT))
+ {
+ AttributeSet atts = el.getAttributes();
+ String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
+ if (type.equals("submit"))
+ submitData(getFormData());
+ else if (type.equals("reset"))
+ resetForm();
+ }
+ // FIXME: Implement the remaining actions.
+ }
+
+ /**
+ * Submits the form data. A separate thread is created to do the
+ * transmission.
+ *
+ * @param data the form data
+ */
+ protected void submitData(String data)
+ {
+ SubmitThread submitThread = new SubmitThread(data);
+ submitThread.start();
+ }
+
+ /**
+ * Submits the form data in response to a click on a
+ * <code>&lt;input type=&quot;image&quot;&gt;</code> element.
+ *
+ * @param imageData the mouse click coordinates
+ */
+ protected void imageSubmit(String imageData)
+ {
+ // FIXME: Implement this.
+ }
+
+ /**
+ * Determines the image data that should be submitted in response to a
+ * mouse click on a image. This is either 'x=<p.x>&y=<p.y>' if the name
+ * attribute of the element is null or '' or
+ * <name>.x=<p.x>&<name>.y=<p.y>' when the name attribute is not empty.
+ *
+ * @param p the coordinates of the mouseclick
+ */
+ String getImageData(Point p)
+ {
+ String name = (String) getElement().getAttributes()
+ .getAttribute(HTML.Attribute.NAME);
+ String data;
+ if (name == null || name.equals(""))
+ {
+ data = "x=" + p.x + "&y=" + p.y;
+ }
+ else
+ {
+ data = name + ".x=" + p.x + "&" + name + ".y=" + p.y;
+ }
+ return data;
+ }
+
+ /**
+ * Determines and returns the enclosing form element if there is any.
+ *
+ * This is package private to avoid accessor methods.
+ *
+ * @return the enclosing form element, or <code>null</code> if there is no
+ * enclosing form element
+ */
+ Element getFormElement()
+ {
+ Element form = null;
+ Element el = getElement();
+ while (el != null && form == null)
+ {
+ AttributeSet atts = el.getAttributes();
+ if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FORM)
+ form = el;
+ else
+ el = el.getParentElement();
+ }
+ return form;
+ }
+
+ /**
+ * Determines the form data that is about to be submitted.
+ *
+ * @return the form data
+ */
+ private String getFormData()
+ {
+ Element form = getFormElement();
+ StringBuilder b = new StringBuilder();
+ if (form != null)
+ {
+ ElementIterator i = new ElementIterator(form);
+ Element next;
+ while ((next = i.next()) != null)
+ {
+ if (next.isLeaf())
+ {
+ AttributeSet atts = next.getAttributes();
+ String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
+ if (type != null && type.equals("submit")
+ && next != getElement())
+ {
+ // Skip this. This is not the actual submit trigger.
+ }
+ else if (type == null || ! type.equals("image"))
+ {
+ getElementFormData(next, b);
+ }
+ }
+ }
+ }
+ return b.toString();
+ }
+
+ /**
+ * Fetches the form data from the specified element and appends it to
+ * the data string.
+ *
+ * @param el the element from which to fetch form data
+ * @param b the data string
+ */
+ private void getElementFormData(Element el, StringBuilder b)
+ {
+ AttributeSet atts = el.getAttributes();
+ String name = (String) atts.getAttribute(HTML.Attribute.NAME);
+ if (name != null)
+ {
+ String value = null;
+ HTML.Tag tag = (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute);
+ if (tag == HTML.Tag.SELECT)
+ {
+ getSelectData(atts, b);
+ }
+ else
+ {
+ if (tag == HTML.Tag.INPUT)
+ value = getInputFormData(atts);
+ else if (tag == HTML.Tag.TEXTAREA)
+ value = getTextAreaData(atts);
+ if (name != null && value != null)
+ {
+ addData(b, name, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Fetches form data from select boxes.
+ *
+ * @param atts the attributes of the element
+ *
+ * @param b the form data string to append to
+ */
+ private void getSelectData(AttributeSet atts, StringBuilder b)
+ {
+ String name = (String) atts.getAttribute(HTML.Attribute.NAME);
+ if (name != null)
+ {
+ Object m = atts.getAttribute(StyleConstants.ModelAttribute);
+ if (m instanceof SelectListModel)
+ {
+ SelectListModel sl = (SelectListModel) m;
+ ListSelectionModel lsm = sl.getSelectionModel();
+ for (int i = 0; i < sl.getSize(); i++)
+ {
+ if (lsm.isSelectedIndex(i))
+ {
+ Option o = (Option) sl.getElementAt(i);
+ addData(b, name, o.getValue());
+ }
+ }
+ }
+ else if (m instanceof SelectComboBoxModel)
+ {
+ SelectComboBoxModel scb = (SelectComboBoxModel) m;
+ Option o = (Option) scb.getSelectedItem();
+ if (o != null)
+ addData(b, name, o.getValue());
+ }
+ }
+ }
+
+ /**
+ * Fetches form data from a textarea.
+ *
+ * @param atts the attributes
+ *
+ * @return the form data
+ */
+ private String getTextAreaData(AttributeSet atts)
+ {
+ Document doc = (Document) atts.getAttribute(StyleConstants.ModelAttribute);
+ String data;
+ try
+ {
+ data = doc.getText(0, doc.getLength());
+ }
+ catch (BadLocationException ex)
+ {
+ data = null;
+ }
+ return data;
+ }
+
+ /**
+ * Fetches form data from an input tag.
+ *
+ * @param atts the attributes from which to fetch the data
+ *
+ * @return the field value
+ */
+ private String getInputFormData(AttributeSet atts)
+ {
+ String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
+ Object model = atts.getAttribute(StyleConstants.ModelAttribute);
+ String value = null;
+ if (type.equals("text") || type.equals("password"))
+ {
+ Document doc = (Document) model;
+ try
+ {
+ value = doc.getText(0, doc.getLength());
+ }
+ catch (BadLocationException ex)
+ {
+ // Sigh.
+ assert false;
+ }
+ }
+ else if (type.equals("hidden") || type.equals("submit"))
+ {
+ value = (String) atts.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = "";
+ }
+ // TODO: Implement the others. radio, checkbox and file.
+ return value;
+ }
+
+ /**
+ * Actually adds the specified data to the string. It URL encodes
+ * the name and value and handles separation of the fields.
+ *
+ * @param b the string at which the form data to be added
+ * @param name the name of the field
+ * @param value the value
+ */
+ private void addData(StringBuilder b, String name, String value)
+ {
+ if (b.length() > 0)
+ b.append('&');
+ String encName = URLEncoder.encode(name);
+ b.append(encName);
+ b.append('=');
+ String encValue = URLEncoder.encode(value);
+ b.append(encValue);
+ }
+
+ /**
+ * Resets the form data to their initial state.
+ */
+ private void resetForm()
+ {
+ Element form = getFormElement();
+ if (form != null)
+ {
+ ElementIterator iter = new ElementIterator(form);
+ Element next;
+ while ((next = iter.next()) != null)
+ {
+ if (next.isLeaf())
+ {
+ AttributeSet atts = next.getAttributes();
+ Object m = atts.getAttribute(StyleConstants.ModelAttribute);
+ if (m instanceof ResetableModel)
+ ((ResetableModel) m).reset();
+ }
+ }
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/FrameSetView.java b/libjava/classpath/javax/swing/text/html/FrameSetView.java
new file mode 100644
index 000000000..e3252d79c
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/FrameSetView.java
@@ -0,0 +1,274 @@
+/* FrameSetView.java -- Implements HTML frameset
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.StringTokenizer;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BoxView;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+
+/**
+ * Implements HTML framesets. This is implemented as a vertical box that
+ * holds the rows of the frameset. Each row is again a horizontal box that
+ * holds the actual columns.
+ */
+public class FrameSetView
+ extends BoxView
+{
+
+ /**
+ * A row of a frameset.
+ */
+ private class FrameSetRow
+ extends BoxView
+ {
+ private int row;
+ FrameSetRow(Element el, int r)
+ {
+ super(el, X_AXIS);
+ row = r;
+ }
+
+ protected void loadChildren(ViewFactory f)
+ {
+ // Load the columns here.
+ Element el = getElement();
+ View[] columns = new View[numViews[X_AXIS]];
+ int offset = row * numViews[X_AXIS];
+ for (int c = 0; c < numViews[X_AXIS]; c++)
+ {
+ Element child = el.getElement(offset + c);
+ columns[c] = f.create(child);
+ }
+ replace(0, 0, columns);
+ }
+
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ int numRows = numViews[X_AXIS];
+ int[] abs = absolute[X_AXIS];
+ int[] rel = relative[X_AXIS];
+ int[] perc = percent[X_AXIS];
+ layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc);
+ }
+ }
+
+ /**
+ * Holds the absolute layout information for the views along one axis. The
+ * indices are absolute[axis][index], where axis is either X_AXIS (columns)
+ * or Y_AXIS (rows). Rows or columns that don't have absolute layout have
+ * a -1 in this array.
+ */
+ int[][] absolute;
+
+ /**
+ * Holds the relative (*) layout information for the views along one axis.
+ * The indices are relative[axis][index], where axis is either X_AXIS
+ * (columns) or Y_AXIS (rows). Rows or columns that don't have relative
+ * layout have a Float.NaN in this array.
+ */
+ int[][] relative;
+
+ /**
+ * Holds the relative (%) layout information for the views along one axis.
+ * The indices are relative[axis][index], where axis is either X_AXIS
+ * (columns) or Y_AXIS (rows). Rows or columns that don't have relative
+ * layout have a Float.NaN in this array.
+ *
+ * The percentage is divided by 100 so that we hold the actual fraction here.
+ */
+ int[][] percent;
+
+ /**
+ * The number of children in each direction.
+ */
+ int[] numViews;
+
+ FrameSetView(Element el)
+ {
+ super(el, Y_AXIS);
+ numViews = new int[2];
+ absolute = new int[2][];
+ relative = new int[2][];
+ percent = new int[2][];
+ }
+
+ /**
+ * Loads the children and places them inside the grid.
+ */
+ protected void loadChildren(ViewFactory f)
+ {
+ parseRowsCols();
+ // Set up the rows.
+ View[] rows = new View[numViews[Y_AXIS]];
+ for (int r = 0; r < numViews[Y_AXIS]; r++)
+ {
+ rows[r] = new FrameSetRow(getElement(), r);
+ }
+ replace(0, 0, rows);
+ }
+
+ /**
+ * Parses the rows and cols attributes and sets up the layout info.
+ */
+ private void parseRowsCols()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ String cols = (String) atts.getAttribute(HTML.Attribute.COLS);
+ if (cols == null) // Defaults to '100%' when not specified.
+ cols = "100%";
+ parseLayout(cols, X_AXIS);
+ String rows = (String) atts.getAttribute(HTML.Attribute.ROWS);
+ if (rows == null) // Defaults to '100%' when not specified.
+ rows = "100%";
+ parseLayout(rows, Y_AXIS);
+ }
+
+ /**
+ * Parses the cols or rows attribute and places the layout info in the
+ * appropriate arrays.
+ *
+ * @param att the attributes to parse
+ * @param axis the axis
+ */
+ private void parseLayout(String att, int axis)
+ {
+ StringTokenizer tokens = new StringTokenizer(att, ",");
+ numViews[axis] = tokens.countTokens();
+ absolute[axis] = new int[numViews[axis]];
+ relative[axis] = new int[numViews[axis]];
+ percent[axis] = new int[numViews[axis]];
+ for (int index = 0; tokens.hasMoreTokens(); index++)
+ {
+ String token = tokens.nextToken();
+ int p = token.indexOf('%');
+ int s = token.indexOf('*');
+ if (p != -1)
+ {
+ // Percent value.
+ String number = token.substring(0, p);
+ try
+ {
+ percent[axis][index] = Integer.parseInt(number);
+ }
+ catch (NumberFormatException ex)
+ {
+ // Leave value as 0 then.
+ }
+ }
+ else if (s != -1)
+ {
+ // Star relative value.
+ String number = token.substring(0, s);
+ try
+ {
+ relative[axis][index] = Integer.parseInt(number);
+ }
+ catch (NumberFormatException ex)
+ {
+ // Leave value as 0 then.
+ }
+ }
+ else
+ {
+ // Absolute value.
+ try
+ {
+ absolute[axis][index] = Integer.parseInt(token);
+ }
+ catch (NumberFormatException ex)
+ {
+ // Leave value as 0 then.
+ }
+ }
+ }
+ }
+
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ int numRows = numViews[Y_AXIS];
+ int[] abs = absolute[Y_AXIS];
+ int[] rel = relative[Y_AXIS];
+ int[] perc = percent[Y_AXIS];
+ layoutViews(targetSpan, axis, offsets, spans, numRows, abs, rel, perc);
+ }
+
+ void layoutViews(int targetSpan, int axis, int[] offsets, int[] spans,
+ int numViews, int[] abs, int[] rel, int[] perc)
+ {
+ // We need two passes. In the first pass we layout the absolute and
+ // percent values and accumulate the needed space. In the second pass
+ // the relative values are distributed and the offsets are set.
+ int total = 0;
+ int relTotal = 0;
+ for (int i = 0; i < numViews; i++)
+ {
+ if (abs[i] > 0)
+ {
+ spans[i] = abs[i];
+ total += spans[i];
+ }
+ else if (perc[i] > 0)
+ {
+ spans[i] = (targetSpan * perc[i]) / 100;
+ total += spans[i];
+ }
+ else if (rel[i] > 0)
+ {
+ relTotal += rel[i];
+ }
+ }
+ int offs = 0;
+ for (int i = 0; i < numViews; i++)
+ {
+ if (relTotal > 0 && rel[i] > 0)
+ {
+ spans[i] = targetSpan * (rel[i] / relTotal);
+ }
+ offsets[i] = offs;
+ offs += spans[i];
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/FrameView.java b/libjava/classpath/javax/swing/text/html/FrameView.java
new file mode 100644
index 000000000..cd4e44a98
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/FrameView.java
@@ -0,0 +1,233 @@
+/* FrameView.java -- Renders HTML frame tags
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.JEditorPane;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.ComponentView;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+
+/**
+ * A view that is responsible for rendering HTML frame tags.
+ * This is accomplished by a specialized {@link ComponentView}
+ * that embeds a JEditorPane with an own document.
+ */
+class FrameView
+ extends ComponentView
+ implements HyperlinkListener
+{
+
+ /**
+ * Creates a new FrameView for the specified element.
+ *
+ * @param el the element for the view
+ */
+ FrameView(Element el)
+ {
+ super(el);
+ }
+
+ /**
+ * Creates the element that will be embedded in the view.
+ * This will be a JEditorPane with the appropriate content set.
+ *
+ * @return the element that will be embedded in the view
+ */
+ protected Component createComponent()
+ {
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ JEditorPane html = new JEditorPane();
+ html.addHyperlinkListener(this);
+ URL base = ((HTMLDocument) el.getDocument()).getBase();
+ String srcAtt = (String) atts.getAttribute(HTML.Attribute.SRC);
+ if (srcAtt != null && ! srcAtt.equals(""))
+ {
+ try
+ {
+ URL page = new URL(base, srcAtt);
+ html.setPage(page);
+ ((HTMLDocument) html.getDocument()).setFrameDocument(true);
+ }
+ catch (MalformedURLException ex)
+ {
+ // Leave page empty.
+ }
+ catch (IOException ex)
+ {
+ // Leave page empty.
+ }
+ }
+ return html;
+ }
+
+ /**
+ * Catches hyperlink events on that frame's editor and forwards it to
+ * the outermost editorpane.
+ */
+ public void hyperlinkUpdate(HyperlinkEvent event)
+ {
+ JEditorPane outer = getTopEditorPane();
+ if (outer != null)
+ {
+ if (event instanceof HTMLFrameHyperlinkEvent)
+ {
+ HTMLFrameHyperlinkEvent hfhe = (HTMLFrameHyperlinkEvent) event;
+ if (hfhe.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
+ {
+ String target = hfhe.getTarget();
+ if (event instanceof FormSubmitEvent)
+ {
+ handleFormSubmitEvent(hfhe, outer, target);
+ }
+ else // No FormSubmitEvent.
+ {
+ handleHyperlinkEvent(hfhe, outer, target);
+ }
+ }
+ }
+ else
+ {
+ // Simply forward this event.
+ outer.fireHyperlinkUpdate(event);
+ }
+ }
+ }
+
+ /**
+ * Handles normal hyperlink events.
+ *
+ * @param event the event
+ * @param outer the top editor
+ * @param target the target
+ */
+ private void handleHyperlinkEvent(HyperlinkEvent event,
+ JEditorPane outer, String target)
+ {
+ if (target.equals("_top"))
+ {
+ try
+ {
+ outer.setPage(event.getURL());
+ }
+ catch (IOException ex)
+ {
+ // Well...
+ ex.printStackTrace();
+ }
+ }
+ if (! outer.isEditable())
+ {
+ outer.fireHyperlinkUpdate
+ (new HTMLFrameHyperlinkEvent(outer,
+ event.getEventType(),
+ event.getURL(),
+ event.getDescription(),
+ getElement(),
+ target));
+ }
+ }
+
+ /**
+ * Handles form submit events.
+ *
+ * @param event the event
+ * @param outer the top editor
+ * @param target the target
+ */
+ private void handleFormSubmitEvent(HTMLFrameHyperlinkEvent event,
+ JEditorPane outer,
+ String target)
+ {
+ HTMLEditorKit kit = (HTMLEditorKit) outer.getEditorKit();
+ if (kit != null && kit.isAutoFormSubmission())
+ {
+ if (target.equals("_top"))
+ {
+ try
+ {
+ outer.setPage(event.getURL());
+ }
+ catch (IOException ex)
+ {
+ // Well...
+ ex.printStackTrace();
+ }
+ }
+ else
+ {
+ HTMLDocument doc =
+ (HTMLDocument) outer.getDocument();
+ doc.processHTMLFrameHyperlinkEvent(event);
+ }
+ }
+ else
+ {
+ outer.fireHyperlinkUpdate(event);
+ }
+ }
+
+ /**
+ * Determines the topmost editor in a nested frameset.
+ *
+ * @return the topmost editor in a nested frameset
+ */
+ private JEditorPane getTopEditorPane()
+ {
+ View parent = getParent();
+ View top = null;
+ while (parent != null)
+ {
+ if (parent instanceof FrameSetView)
+ top = parent;
+ }
+ JEditorPane editor = null;
+ if (top != null)
+ editor = (JEditorPane) top.getContainer();
+ return editor;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/HRuleView.java b/libjava/classpath/javax/swing/text/html/HRuleView.java
new file mode 100644
index 000000000..9be1efff3
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/HRuleView.java
@@ -0,0 +1,189 @@
+/* HRuleView.java -- Horizontal dash in HTML documents.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.text.Element;
+import javax.swing.text.View;
+
+/**
+ * Represents the long horizontal separating dash that can be inserted into the
+ * HTML documents with HR tag.
+ */
+class HRuleView extends InlineView
+{
+ /**
+ * The null view, indicating, that nothing should be painted ahead the
+ * breaking point.
+ */
+ View nullView;
+
+ /**
+ * The height of the horizontal dash area.
+ */
+ static int HEIGHT = 4;
+
+ /**
+ * The imaginary invisible view that stays after end of line after the
+ * breaking procedure. It occupies on character.
+ */
+ class Beginning extends NullView
+ {
+ /**
+ * The break offset that becomes the views start offset.
+ */
+ int breakOffset;
+
+ /**
+ * Return the end offset that is always one char after the break offset.
+ */
+ public int getEndOffset()
+ {
+ return breakOffset + 1;
+ }
+
+ /**
+ * Return the start offset that has been passed in a constructor.
+ */
+ public int getStartOffset()
+ {
+ return breakOffset;
+ }
+
+ /**
+ * Create the new instance of this view.
+ *
+ * @param element the element (inherited from the HR view)
+ * @param offset the position where the HR view has been broken
+ */
+ public Beginning(Element element, int offset)
+ {
+ super(element);
+ breakOffset = offset;
+ }
+ }
+
+ /**
+ * Creates the new HR view.
+ */
+ public HRuleView(Element element)
+ {
+ super(element);
+ }
+
+ /**
+ * Returns the ForcedBreakWeight for the vertical axis, indicating, the the
+ * view must be broken to be displayed correctly. The horizontal dash is
+ * not breakeable along the Y axis.
+ */
+ public int getBreakWeight(int axis, float pos, float len)
+ {
+ if (axis == X_AXIS && ((getEndOffset() - getStartOffset()) > 1))
+ return ForcedBreakWeight;
+ else
+ return BadBreakWeight;
+ }
+
+ /**
+ * Draws the double line, upped black and the lower light gray.
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ Rectangle bounds = a.getBounds();
+
+ int x = bounds.x;
+ int y = bounds.y;
+
+ int w = bounds.x + bounds.width;
+
+ // We move "half pixel up" from the actual horizontal position -
+ // this will be rounded to the closest actual int co-ordinate.
+ int h = bounds.y + (int) Math.round(bounds.height * 0.5 - 0.5);
+
+ g.setColor(Color.black);
+ g.drawLine(x, y++, w, h++);
+ g.setColor(Color.lightGray);
+ g.drawLine(x, y, w, h);
+ }
+
+ /**
+ * Break the view into this view and the invisible imaginary view that
+ * stays on the end of line that is broken by HR dash. The view is broken
+ * only if its length is longer than one (the two characters are expected
+ * in the initial length).
+ */
+ public View breakView(int axis, int offset, float pos, float len)
+ {
+ if (getEndOffset() - getStartOffset() > 1)
+ return new Beginning(getElement(), offset);
+ else
+ return this;
+ }
+
+ /**
+ * Returns the width of the container for the horizontal axis and the
+ * thickness of the dash area for the vertical axis.
+ */
+ public float getMaximumSpan(int axis)
+ {
+ if (axis == X_AXIS)
+ {
+ Component container = getContainer();
+ if (container != null)
+ return getContainer().getWidth();
+ else
+ return 640;
+ }
+ else
+ return HEIGHT;
+ }
+
+ /**
+ * Returns the same values as {@link #getMaximumSpan(int)}
+ */
+ public float getPreferredSpan(int axis)
+ {
+ return getMaximumSpan(axis);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/HTML.java b/libjava/classpath/javax/swing/text/html/HTML.java
new file mode 100644
index 000000000..93c05daa2
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/HTML.java
@@ -0,0 +1,1253 @@
+/* HTML.java -- HTML document tag constants
+ Copyright (C) 2002 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.Serializable;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.swing.text.AttributeSet;
+
+/**
+ * HTML attribute and tag definitions.
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ */
+public class HTML
+{
+ /**
+ * Represents a HTML attribute.
+ */
+ public static final class Attribute
+ {
+ /**
+ * The action attribute
+ */
+ public static final Attribute ACTION = new Attribute("action");
+
+ /**
+ * The align attribute
+ */
+ public static final Attribute ALIGN = new Attribute("align");
+
+ /**
+ * The alink attribute
+ */
+ public static final Attribute ALINK = new Attribute("alink");
+
+ /**
+ * The alt attribute
+ */
+ public static final Attribute ALT = new Attribute("alt");
+
+ /**
+ * The archive attribute
+ */
+ public static final Attribute ARCHIVE = new Attribute("archive");
+
+ /**
+ * The background attribute
+ */
+ public static final Attribute BACKGROUND = new Attribute("background");
+
+ /**
+ * The bgcolor attribute
+ */
+ public static final Attribute BGCOLOR = new Attribute("bgcolor");
+
+ /**
+ * The border attribute
+ */
+ public static final Attribute BORDER = new Attribute("border");
+
+ /**
+ * The cellpadding attribute
+ */
+ public static final Attribute CELLPADDING = new Attribute("cellpadding");
+
+ /**
+ * The cellspacing attribute
+ */
+ public static final Attribute CELLSPACING = new Attribute("cellspacing");
+
+ /**
+ * The checked attribute
+ */
+ public static final Attribute CHECKED = new Attribute("checked");
+
+ /**
+ * The class attribute
+ */
+ public static final Attribute CLASS = new Attribute("class");
+
+ /**
+ * The classid attribute
+ */
+ public static final Attribute CLASSID = new Attribute("classid");
+
+ /**
+ * The clear attribute
+ */
+ public static final Attribute CLEAR = new Attribute("clear");
+
+ /**
+ * The code attribute
+ */
+ public static final Attribute CODE = new Attribute("code");
+
+ /**
+ * The codebase attribute
+ */
+ public static final Attribute CODEBASE = new Attribute("codebase");
+
+ /**
+ * The codetype attribute
+ */
+ public static final Attribute CODETYPE = new Attribute("codetype");
+
+ /**
+ * The color attribute
+ */
+ public static final Attribute COLOR = new Attribute("color");
+
+ /**
+ * The cols attribute
+ */
+ public static final Attribute COLS = new Attribute("cols");
+
+ /**
+ * The colspan attribute
+ */
+ public static final Attribute COLSPAN = new Attribute("colspan");
+
+ /**
+ * The comment attribute
+ */
+ public static final Attribute COMMENT = new Attribute("comment");
+
+ /**
+ * The compact attribute
+ */
+ public static final Attribute COMPACT = new Attribute("compact");
+
+ /**
+ * The content attribute
+ */
+ public static final Attribute CONTENT = new Attribute("content");
+
+ /**
+ * The coords attribute
+ */
+ public static final Attribute COORDS = new Attribute("coords");
+
+ /**
+ * The data attribute
+ */
+ public static final Attribute DATA = new Attribute("data");
+
+ /**
+ * The declare attribute
+ */
+ public static final Attribute DECLARE = new Attribute("declare");
+
+ /**
+ * The dir attribute
+ */
+ public static final Attribute DIR = new Attribute("dir");
+
+ /**
+ * The dummy attribute
+ */
+ public static final Attribute DUMMY = new Attribute("dummy");
+
+ /**
+ * The enctype attribute
+ */
+ public static final Attribute ENCTYPE = new Attribute("enctype");
+
+ /**
+ * The endtag attribute
+ */
+ public static final Attribute ENDTAG = new Attribute("endtag");
+
+ /**
+ * The face attribute
+ */
+ public static final Attribute FACE = new Attribute("face");
+
+ /**
+ * The frameborder attribute
+ */
+ public static final Attribute FRAMEBORDER = new Attribute("frameborder");
+
+ /**
+ * The halign attribute
+ */
+ public static final Attribute HALIGN = new Attribute("halign");
+
+ /**
+ * The height attribute
+ */
+ public static final Attribute HEIGHT = new Attribute("height");
+
+ /**
+ * The href attribute
+ */
+ public static final Attribute HREF = new Attribute("href");
+
+ /**
+ * The hspace attribute
+ */
+ public static final Attribute HSPACE = new Attribute("hspace");
+
+ /**
+ * The http-equiv attribute
+ */
+ public static final Attribute HTTPEQUIV = new Attribute("http-equiv");
+
+ /**
+ * The id attribute
+ */
+ public static final Attribute ID = new Attribute("id");
+
+ /**
+ * The ismap attribute
+ */
+ public static final Attribute ISMAP = new Attribute("ismap");
+
+ /**
+ * The lang attribute
+ */
+ public static final Attribute LANG = new Attribute("lang");
+
+ /**
+ * The language attribute
+ */
+ public static final Attribute LANGUAGE = new Attribute("language");
+
+ /**
+ * The link attribute
+ */
+ public static final Attribute LINK = new Attribute("link");
+
+ /**
+ * The lowsrc attribute
+ */
+ public static final Attribute LOWSRC = new Attribute("lowsrc");
+
+ /**
+ * The marginheight attribute
+ */
+ public static final Attribute MARGINHEIGHT = new Attribute("marginheight");
+
+ /**
+ * The marginwidth attribute
+ */
+ public static final Attribute MARGINWIDTH = new Attribute("marginwidth");
+
+ /**
+ * The maxlength attribute
+ */
+ public static final Attribute MAXLENGTH = new Attribute("maxlength");
+
+ /**
+ * The media attribute
+ */
+ static final Attribute MEDIA = new Attribute("media");
+
+ /**
+ * The method attribute
+ */
+ public static final Attribute METHOD = new Attribute("method");
+
+ /**
+ * The multiple attribute
+ */
+ public static final Attribute MULTIPLE = new Attribute("multiple");
+
+ /**
+ * The n attribute
+ */
+ public static final Attribute N = new Attribute("n");
+
+ /**
+ * The name attribute
+ */
+ public static final Attribute NAME = new Attribute("name");
+
+ /**
+ * The nohref attribute
+ */
+ public static final Attribute NOHREF = new Attribute("nohref");
+
+ /**
+ * The noresize attribute
+ */
+ public static final Attribute NORESIZE = new Attribute("noresize");
+
+ /**
+ * The noshade attribute
+ */
+ public static final Attribute NOSHADE = new Attribute("noshade");
+
+ /**
+ * The nowrap attribute
+ */
+ public static final Attribute NOWRAP = new Attribute("nowrap");
+
+ /**
+ * The prompt attribute
+ */
+ public static final Attribute PROMPT = new Attribute("prompt");
+
+ /**
+ * The rel attribute
+ */
+ public static final Attribute REL = new Attribute("rel");
+
+ /**
+ * The rev attribute
+ */
+ public static final Attribute REV = new Attribute("rev");
+
+ /**
+ * The rows attribute
+ */
+ public static final Attribute ROWS = new Attribute("rows");
+
+ /**
+ * The rowspan attribute
+ */
+ public static final Attribute ROWSPAN = new Attribute("rowspan");
+
+ /**
+ * The scrolling attribute
+ */
+ public static final Attribute SCROLLING = new Attribute("scrolling");
+
+ /**
+ * The selected attribute
+ */
+ public static final Attribute SELECTED = new Attribute("selected");
+
+ /**
+ * The shape attribute
+ */
+ public static final Attribute SHAPE = new Attribute("shape");
+
+ /**
+ * The shapes attribute
+ */
+ public static final Attribute SHAPES = new Attribute("shapes");
+
+ /**
+ * The size attribute
+ */
+ public static final Attribute SIZE = new Attribute("size");
+
+ /**
+ * The src attribute
+ */
+ public static final Attribute SRC = new Attribute("src");
+
+ /**
+ * The standby attribute
+ */
+ public static final Attribute STANDBY = new Attribute("standby");
+
+ /**
+ * The start attribute
+ */
+ public static final Attribute START = new Attribute("start");
+
+ /**
+ * The style attribute
+ */
+ public static final Attribute STYLE = new Attribute("style");
+
+ /**
+ * The target attribute
+ */
+ public static final Attribute TARGET = new Attribute("target");
+
+ /**
+ * The text attribute
+ */
+ public static final Attribute TEXT = new Attribute("text");
+
+ /**
+ * The title attribute
+ */
+ public static final Attribute TITLE = new Attribute("title");
+
+ /**
+ * The type attribute
+ */
+ public static final Attribute TYPE = new Attribute("type");
+
+ /**
+ * The usemap attribute
+ */
+ public static final Attribute USEMAP = new Attribute("usemap");
+
+ /**
+ * The valign attribute
+ */
+ public static final Attribute VALIGN = new Attribute("valign");
+
+ /**
+ * The value attribute
+ */
+ public static final Attribute VALUE = new Attribute("value");
+
+ /**
+ * The valuetype attribute
+ */
+ public static final Attribute VALUETYPE = new Attribute("valuetype");
+
+ /**
+ * The version attribute
+ */
+ public static final Attribute VERSION = new Attribute("version");
+
+ /**
+ * The vlink attribute
+ */
+ public static final Attribute VLINK = new Attribute("vlink");
+
+ /**
+ * The vspace attribute
+ */
+ public static final Attribute VSPACE = new Attribute("vspace");
+
+ /**
+ * The width attribute
+ */
+ public static final Attribute WIDTH = new Attribute("width");
+
+ /**
+ * This is used to reflect the pseudo class for the a tag.
+ */
+ static final Attribute PSEUDO_CLASS = new Attribute("_pseudo");
+
+ /**
+ * This is used to reflect the dynamic class for the a tag.
+ */
+ static final Attribute DYNAMIC_CLASS = new Attribute("_dynamic");
+
+ /**
+ * The attribute name.
+ */
+ private final String name;
+
+ /**
+ * Creates the attribute with the given name.
+ */
+ private Attribute(String a_name)
+ {
+ name = a_name;
+ }
+
+ /**
+ * Returns the attribute name. The names of the built-in attributes
+ * are always returned in lowercase.
+ */
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * Return an array of all attributes, declared in the HTML.Attribute
+ * class. WARNING: attributes are the only public fields,
+ * expected in this class.
+ */
+ static Attribute[] getAllAttributes()
+ {
+ Field[] f = Attribute.class.getFields();
+ Attribute[] attrs = new Attribute[ f.length ];
+ Field x;
+ int p = 0;
+ Attribute a;
+
+ for (int i = 0; i < f.length; i++)
+ {
+ x = f [ i ];
+
+ if ((x.getModifiers() & Modifier.STATIC) != 0)
+ {
+ if (x.getType().equals(Attribute.class))
+ {
+ try
+ {
+ a = (Attribute) x.get(null);
+ attrs [ p++ ] = a;
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace(System.err);
+ throw new Error("This should never happen, report a bug");
+ }
+ }
+ }
+ }
+
+ return attrs;
+ }
+ }
+
+ /**
+ * Represents a HTML tag.
+ */
+ public static class Tag
+ {
+ /**
+ * The &lt;a&gt; tag
+ */
+ public static final Tag A = new Tag("a");
+
+ /**
+ * The &lt;address&gt; tag
+ */
+ public static final Tag ADDRESS = new Tag("address");
+
+ /**
+ * The &lt;applet&gt; tag
+ */
+ public static final Tag APPLET = new Tag("applet");
+
+ /**
+ * The &lt;area&gt; tag
+ */
+ public static final Tag AREA = new Tag("area");
+
+ /**
+ * The &lt;b&gt; tag
+ */
+ public static final Tag B = new Tag("b");
+
+ /**
+ * The &lt;base&gt; tag
+ */
+ public static final Tag BASE = new Tag("base");
+
+ /**
+ * The &lt;basefont&gt; tag
+ */
+ public static final Tag BASEFONT = new Tag("basefont");
+
+ /**
+ * The &lt;big&gt; tag
+ */
+ public static final Tag BIG = new Tag("big");
+
+ /**
+ * The &lt;blockquote&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag BLOCKQUOTE = new Tag("blockquote", BREAKS | BLOCK);
+
+ /**
+ * The &lt;body&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag BODY = new Tag("body", BREAKS | BLOCK);
+
+ /**
+ * The &lt;br&gt; tag , breaks flow.
+ */
+ public static final Tag BR = new Tag("br", BREAKS);
+
+ /**
+ * The &lt;caption&gt; tag
+ */
+ public static final Tag CAPTION = new Tag("caption");
+
+ /**
+ * The &lt;center&gt; tag , breaks flow.
+ */
+ public static final Tag CENTER = new Tag("center", BREAKS);
+
+ /**
+ * The &lt;cite&gt; tag
+ */
+ public static final Tag CITE = new Tag("cite");
+
+ /**
+ * The &lt;code&gt; tag
+ */
+ public static final Tag CODE = new Tag("code");
+
+ /**
+ * The &lt;dd&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag DD = new Tag("dd", BREAKS | BLOCK);
+
+ /**
+ * The &lt;dfn&gt; tag
+ */
+ public static final Tag DFN = new Tag("dfn");
+
+ /**
+ * The &lt;dir&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag DIR = new Tag("dir", BREAKS | BLOCK);
+
+ /**
+ * The &lt;div&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag DIV = new Tag("div", BREAKS | BLOCK);
+
+ /**
+ * The &lt;dl&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag DL = new Tag("dl", BREAKS | BLOCK);
+
+ /**
+ * The &lt;dt&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag DT = new Tag("dt", BREAKS | BLOCK);
+
+ /**
+ * The &lt;em&gt; tag
+ */
+ public static final Tag EM = new Tag("em");
+
+ /**
+ * The &lt;font&gt; tag
+ */
+ public static final Tag FONT = new Tag("font");
+
+ /**
+ * The &lt;form&gt; tag , breaks flow.
+ */
+ public static final Tag FORM = new Tag("form", BREAKS);
+
+ /**
+ * The &lt;frame&gt; tag
+ */
+ public static final Tag FRAME = new Tag("frame");
+
+ /**
+ * The &lt;frameset&gt; tag
+ */
+ public static final Tag FRAMESET = new Tag("frameset");
+
+ /**
+ * The &lt;h1&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag H1 = new Tag("h1", BREAKS | BLOCK);
+
+ /**
+ * The &lt;h2&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag H2 = new Tag("h2", BREAKS | BLOCK);
+
+ /**
+ * The &lt;h3&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag H3 = new Tag("h3", BREAKS | BLOCK);
+
+ /**
+ * The &lt;h4&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag H4 = new Tag("h4", BREAKS | BLOCK);
+
+ /**
+ * The &lt;h5&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag H5 = new Tag("h5", BREAKS | BLOCK);
+
+ /**
+ * The &lt;h6&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag H6 = new Tag("h6", BREAKS | BLOCK);
+
+ /**
+ * The &lt;head&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag HEAD = new Tag("head", BREAKS | BLOCK);
+
+ /**
+ * The &lt;hr&gt; tag , breaks flow.
+ */
+ public static final Tag HR = new Tag("hr", BREAKS);
+
+ /**
+ * The &lt;html&gt; tag , breaks flow.
+ */
+ public static final Tag HTML = new Tag("html", BREAKS);
+
+ /**
+ * The &lt;i&gt; tag
+ */
+ public static final Tag I = new Tag("i");
+
+ /**
+ * The &lt;img&gt; tag
+ */
+ public static final Tag IMG = new Tag("img");
+
+ /**
+ * The &lt;input&gt; tag
+ */
+ public static final Tag INPUT = new Tag("input");
+
+ /**
+ * The &lt;isindex&gt; tag , breaks flow.
+ */
+ public static final Tag ISINDEX = new Tag("isindex", BREAKS);
+
+ /**
+ * The &lt;kbd&gt; tag
+ */
+ public static final Tag KBD = new Tag("kbd");
+
+ /**
+ * The &lt;li&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag LI = new Tag("li", BREAKS | BLOCK);
+
+ /**
+ * The &lt;link&gt; tag
+ */
+ public static final Tag LINK = new Tag("link");
+
+ /**
+ * The &lt;map&gt; tag
+ */
+ public static final Tag MAP = new Tag("map");
+
+ /**
+ * The &lt;menu&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag MENU = new Tag("menu", BREAKS | BLOCK);
+
+ /**
+ * The &lt;meta&gt; tag
+ */
+ public static final Tag META = new Tag("meta");
+
+ /**
+ * The &lt;nobr&gt; tag
+ */
+ static final Tag NOBR = new Tag("nobr");
+
+ /**
+ * The &lt;noframes&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag NOFRAMES = new Tag("noframes", BREAKS | BLOCK);
+
+ /**
+ * The &lt;object&gt; tag
+ */
+ public static final Tag OBJECT = new Tag("object");
+
+ /**
+ * The &lt;ol&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag OL = new Tag("ol", BREAKS | BLOCK);
+
+ /**
+ * The &lt;option&gt; tag
+ */
+ public static final Tag OPTION = new Tag("option");
+
+ /**
+ * The &lt;p&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag P = new Tag("p", BREAKS | BLOCK);
+
+ /**
+ * The &lt;param&gt; tag
+ */
+ public static final Tag PARAM = new Tag("param");
+
+ /**
+ * The &lt;pre&gt; tag , breaks flow, block tag, preformatted.
+ */
+ public static final Tag PRE = new Tag("pre", BREAKS | BLOCK | PREFORMATTED);
+
+ /**
+ * The &lt;s&gt; tag
+ */
+ public static final Tag S = new Tag("s");
+
+ /**
+ * The &lt;samp&gt; tag
+ */
+ public static final Tag SAMP = new Tag("samp");
+
+ /**
+ * The &lt;script&gt; tag
+ */
+ public static final Tag SCRIPT = new Tag("script");
+
+ /**
+ * The &lt;select&gt; tag
+ */
+ public static final Tag SELECT = new Tag("select");
+
+ /**
+ * The &lt;small&gt; tag
+ */
+ public static final Tag SMALL = new Tag("small");
+
+ /**
+ * The &lt;span&gt; tag
+ */
+ public static final Tag SPAN = new Tag("span");
+
+ /**
+ * The &lt;strike&gt; tag
+ */
+ public static final Tag STRIKE = new Tag("strike");
+
+ /**
+ * The &lt;strong&gt; tag
+ */
+ public static final Tag STRONG = new Tag("strong");
+
+ /**
+ * The &lt;style&gt; tag
+ */
+ public static final Tag STYLE = new Tag("style");
+
+ /**
+ * The &lt;sub&gt; tag
+ */
+ public static final Tag SUB = new Tag("sub");
+
+ /**
+ * The &lt;sup&gt; tag
+ */
+ public static final Tag SUP = new Tag("sup");
+
+ /**
+ * The &lt;table&gt; tag , block tag.
+ */
+ public static final Tag TABLE = new Tag("table", BLOCK);
+
+ /**
+ * The &lt;td&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag TD = new Tag("td", BREAKS | BLOCK);
+
+ /**
+ * The &lt;textarea&gt; tag , preformatted.
+ */
+ public static final Tag TEXTAREA = new Tag("textarea", PREFORMATTED);
+
+ /**
+ * The &lt;th&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag TH = new Tag("th", BREAKS | BLOCK);
+
+ /**
+ * The &lt;title&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag TITLE = new Tag("title", BREAKS | BLOCK);
+
+ /**
+ * The &lt;tr&gt; tag , block tag.
+ */
+ public static final Tag TR = new Tag("tr", BLOCK);
+
+ /**
+ * The &lt;tt&gt; tag
+ */
+ public static final Tag TT = new Tag("tt");
+
+ /**
+ * The &lt;u&gt; tag
+ */
+ public static final Tag U = new Tag("u");
+
+ /**
+ * The &lt;ul&gt; tag , breaks flow, block tag.
+ */
+ public static final Tag UL = new Tag("ul", BREAKS | BLOCK);
+
+ /**
+ * The &lt;var&gt; tag
+ */
+ public static final Tag VAR = new Tag("var");
+
+ /* Special tags */
+
+ /**
+ * Total number of syntetic tags, delared in the Tag class.
+ * This must be adjusted if the new synthetic tags are declared.
+ * Otherwise the HTML.getAllTags() will not work as expected.
+ */
+ private static final int TOTAL_SYNTHETIC_TAGS = 3;
+
+ /**
+ * All comments are labeled with this tag.
+ * This tag is not included into the array, returned by getAllTags().
+ * toString() returns 'comment'. HTML reader synthesizes this tag.
+ */
+ public static final Tag COMMENT = new Tag("comment", SYNTHETIC);
+
+ /**
+ * All text content is labeled with this tag.
+ * This tag is not included into the array, returned by getAllTags().
+ * toString() returns 'content'. HTML reader synthesizes this tag.
+ */
+ public static final Tag CONTENT = new Tag("content", SYNTHETIC);
+
+ /**
+ * All text content must be in a paragraph element.
+ * If a paragraph didn't exist when content was encountered,
+ * a paragraph is manufactured.
+ * toString() returns 'p-implied'. HTML reader synthesizes this tag.
+ */
+ public static final Tag IMPLIED = new Tag("p-implied", SYNTHETIC);
+ final String name;
+ final int flags;
+
+ /**
+ * Create the unitialised instance of HTML.Tag.
+ *
+ * The {@link #breaksFlow()}, {@link #isBlock()}
+ * and {@link #isPreformatted()} will always return false.
+ * The {@link #toString()} will return <code>null</code>.
+ *
+ * @since 1.3
+ */
+ public Tag()
+ {
+ name = null;
+ flags = 0;
+ }
+
+ /**
+ * Creates a new Tag with the specified id, and with causesBreak
+ * and isBlock set to false.
+ */
+ protected Tag(String id)
+ {
+ name = id;
+ flags = 0;
+ }
+
+ /**
+ * Creates a new Tag with the specified tag name and
+ * causesBreak and isBlock properties.
+ */
+ protected Tag(String id, boolean causesBreak, boolean isBlock)
+ {
+ int f = 0;
+
+ if (causesBreak)
+ {
+ f |= BREAKS;
+ }
+
+ if (isBlock)
+ {
+ f |= BLOCK;
+ }
+
+ flags = f;
+ name = id;
+ }
+
+ /**
+ * Create a tag taking flags.
+ */
+ Tag(String id, int a_flags)
+ {
+ name = id;
+ flags = a_flags;
+ }
+
+ /**
+ * Returns true if this tag is a block tag, which is a tag used to
+ * add structure to a document.
+ */
+ public boolean isBlock()
+ {
+ return (flags & BLOCK) != 0;
+ }
+
+ /**
+ * Returns true if this tag is pre-formatted, which is true if
+ * the tag is either PRE or TEXTAREA
+ */
+ public boolean isPreformatted()
+ {
+ return (flags & PREFORMATTED) != 0;
+ }
+
+ /**
+ * Returns true if this tag causes a line break to the flow of text
+ */
+ public boolean breaksFlow()
+ {
+ return (flags & BREAKS) != 0;
+ }
+
+ /**
+ * Returns the tag name. The names of the built-in tags are always
+ * returned in lowercase.
+ */
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * Return an array of HTML tags, declared in HTML.Tag class.
+ * WARNING: This method expects that the Tags are the only
+ * public fields declared in the Tag class.
+ */
+ static Tag[] getAllTags()
+ {
+ Field[] f = Tag.class.getFields();
+ Field x;
+
+ // The syntetic tags are not included.
+ Tag[] tags = new Tag[ f.length - TOTAL_SYNTHETIC_TAGS ];
+ int p = 0;
+ Tag t;
+
+ for (int i = 0; i < f.length; i++)
+ {
+ x = f [ i ];
+
+ if ((x.getModifiers() & Modifier.STATIC) != 0)
+ {
+ if (x.getType().equals(Tag.class))
+ {
+ try
+ {
+ t = (Tag) x.get(null);
+
+ if (!t.isSyntetic())
+ {
+ tags [ p++ ] = t;
+ }
+ }
+ catch (IllegalAccessException ex)
+ {
+ unexpected(ex);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ unexpected(ex);
+ }
+ }
+ }
+ }
+
+ return tags;
+ }
+
+ /**
+ * Returns true for tags, generated by the html reader
+ * (COMMENT, CONTENT and IMPLIED).
+ */
+ boolean isSyntetic()
+ {
+ return (flags & SYNTHETIC) != 0;
+ }
+
+ private static void unexpected(Exception ex)
+ throws Error
+ {
+ throw new Error("This should never happen, report a bug", ex);
+ }
+ }
+
+ /**
+ * Represents an unknown HTML tag.
+ * @author Mark Wielaard (mark@klomp.org)
+ */
+ public static class UnknownTag
+ extends Tag
+ implements Serializable
+ {
+ private static final long serialVersionUID = -1534369342247250625L;
+
+ /**
+ * Creates a new UnknownTag with the specified name
+ * @param name The tag name.
+ *
+ */
+ public UnknownTag(String name)
+ {
+ super(name);
+ }
+ }
+
+ /**
+ * This value is returned for attributes without value that have no
+ * default value defined in the DTD.
+ */
+ public static final String NULL_ATTRIBUTE_VALUE = "#DEFAULT";
+
+ /* Package level html tag flags */
+ static final int BREAKS = 1;
+ static final int BLOCK = 2;
+ static final int PREFORMATTED = 4;
+ static final int SYNTHETIC = 8;
+ private static Map<String,Tag> tagMap;
+ private static Map<String,Attribute> attrMap;
+
+ /**
+ * The public constructor (does nothing). It it seldom required to have
+ * an instance of this class, because all public fields and methods
+ * are static.
+ */
+ public HTML()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Returns the set of the recognized HTML attributes.
+ */
+ public static HTML.Attribute[] getAllAttributeKeys()
+ {
+ return Attribute.getAllAttributes();
+ }
+
+ /**
+ * Returns the set of actual HTML tags that are recognized by
+ * the default HTML reader. The returned array does not include the
+ * COMMENT, CONTENT and IMPLIED tags.
+ */
+ public static HTML.Tag[] getAllTags()
+ {
+ return Tag.getAllTags();
+ }
+
+ /**
+ * Returns an htl attribute constant for the given attribute name.
+ * @param attName the attribute name, case insensitive
+ */
+ public static Attribute getAttributeKey(String attName)
+ {
+ if (attrMap == null)
+ {
+ // Create the map on demand.
+ attrMap = new TreeMap<String,Attribute>();
+
+ Attribute[] attrs = getAllAttributeKeys();
+
+ for (int i = 0; i < attrs.length; i++)
+ {
+ attrMap.put(attrs [ i ].toString(), attrs [ i ]);
+ }
+ }
+
+ return attrMap.get(attName.toLowerCase());
+ }
+
+ /**
+ * Searches the value of given attribute in the provided set.
+ * If the value is found (String type expected), tries to parse it as
+ * an integer value. If succeded, returns the obtained integer value.
+ *
+ * For example:<p><code>
+ * SimpleAttributeSet ase = new SimpleAttributeSet();
+ * ase.addAttribute(HTML.getAttributeKey("size"),"222");
+ * System.out.println(
+ * HTML.getIntegerAttributeValue
+ * (ase, HTML.getAttributeKey("size"), 333)); // prints "222"
+ * System.out.println(
+ * HTML.getIntegerAttributeValue
+ * (ase, HTML.getAttributeKey("width"), 333)); // prints "333".
+ * </code></p>
+ *
+ *
+ * @param set The attribute set to search in. If the set contains the
+ * given attribute, it must by a type of String.
+ * @param attribute The html attribute to search in
+ * @param defaultValue The value that is returned if the attribute is not
+ * found in the given set or if the NumberFormatException was thrown
+ * during the parsing.
+ */
+ public static int getIntegerAttributeValue(AttributeSet set,
+ HTML.Attribute attribute,
+ int defaultValue
+ )
+ {
+ Object v = set.getAttribute(attribute);
+
+ if (v == null)
+ {
+ return defaultValue;
+ }
+
+ try
+ {
+ return Integer.parseInt(v.toString().trim());
+ }
+ catch (Exception ex)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns a HTML tag constant for the given HTML attribute name.
+ * If the tag is unknown, the null is returned.
+ * @param tagName the tag name, case insensitive
+ */
+ public static Tag getTag(String tagName)
+ {
+ if (tagMap == null)
+ {
+ // Create the mao on demand.
+ tagMap = new TreeMap<String,Tag>();
+
+ Tag[] tags = getAllTags();
+
+ for (int i = 0; i < tags.length; i++)
+ {
+ tagMap.put(tags [ i ].toString(), tags [ i ]);
+ }
+ }
+
+ return tagMap.get(tagName.toLowerCase());
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java
new file mode 100644
index 000000000..9545be4e8
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java
@@ -0,0 +1,2298 @@
+/* HTMLDocument.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 gnu.classpath.NotImplementedException;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Stack;
+import java.util.Vector;
+
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultButtonModel;
+import javax.swing.JEditorPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.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.PlainDocument;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.html.HTML.Tag;
+
+/**
+ * Represents the HTML document that is constructed by defining the text and
+ * other components (images, buttons, etc) in HTML language. This class can
+ * becomes the default document for {@link JEditorPane} after setting its
+ * content type to "text/html". HTML document also serves as an intermediate
+ * data structure when it is needed to parse HTML and then obtain the content of
+ * the certain types of tags. This class also has methods for modifying the HTML
+ * content.
+ *
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ * @author Anthony Balkissoon (abalkiss@redhat.com)
+ * @author Lillian Angel (langel@redhat.com)
+ */
+public class HTMLDocument extends DefaultStyledDocument
+{
+ /** A key for document properies. The value for the key is
+ * a Vector of Strings of comments not found in the body.
+ */
+ public static final String AdditionalComments = "AdditionalComments";
+ URL baseURL = null;
+ boolean preservesUnknownTags = true;
+ int tokenThreshold = Integer.MAX_VALUE;
+ HTMLEditorKit.Parser parser;
+
+ /**
+ * Indicates whether this document is inside a frame or not.
+ */
+ private boolean frameDocument;
+
+ /**
+ * Package private to avoid accessor methods.
+ */
+ String baseTarget;
+
+ /**
+ * Constructs an HTML document using the default buffer size and a default
+ * StyleSheet.
+ */
+ public HTMLDocument()
+ {
+ this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
+ }
+
+ /**
+ * 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)
+ {
+ super(c, 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) getAttributeContext();
+ }
+
+ /**
+ * This method creates a root element for the new document.
+ *
+ * @return the new default root
+ */
+ protected AbstractElement createDefaultRoot()
+ {
+ AbstractDocument.AttributeContext ctx = getAttributeContext();
+
+ // Create html element.
+ AttributeSet atts = ctx.getEmptySet();
+ atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.HTML);
+ BranchElement html = (BranchElement) createBranchElement(null, atts);
+
+ // Create body element.
+ atts = ctx.getEmptySet();
+ atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.BODY);
+ BranchElement body = (BranchElement) createBranchElement(html, atts);
+ html.replace(0, 0, new Element[] { body });
+
+ // Create p element.
+ atts = ctx.getEmptySet();
+ atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.P);
+ BranchElement p = (BranchElement) createBranchElement(body, atts);
+ body.replace(0, 0, new Element[] { p });
+
+ // Create an empty leaf element.
+ atts = ctx.getEmptySet();
+ atts = ctx.addAttribute(atts, StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ Element leaf = createLeafElement(p, atts, 0, 1);
+ p.replace(0, 0, new Element[]{ leaf });
+
+ return html;
+ }
+
+ /**
+ * 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)
+ {
+ return new RunElement(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)
+ {
+ return new BlockElement(parent, a);
+ }
+
+ /**
+ * 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.
+ * This is the document's URL if the document was loaded from a URL.
+ * If a <code>base</code> tag is found, it will be used.
+ * @return the base URL
+ */
+ public URL getBase()
+ {
+ return baseURL;
+ }
+
+ /**
+ * Sets the location against which to resolve relative URLs.
+ * @param u the new base URL
+ */
+ public void setBase(URL u)
+ {
+ baseURL = u;
+ getStyleSheet().setBase(u);
+ }
+
+ /**
+ * Returns whether or not the parser preserves unknown HTML tags.
+ * @return true if the parser preserves unknown tags
+ */
+ public boolean getPreservesUnknownTags()
+ {
+ return preservesUnknownTags;
+ }
+
+ /**
+ * Sets the behaviour of the parser when it encounters unknown HTML tags.
+ * @param preservesTags true if the parser should preserve unknown tags.
+ */
+ public void setPreservesUnknownTags(boolean preservesTags)
+ {
+ preservesUnknownTags = preservesTags;
+ }
+
+ /**
+ * An iterator to iterate through LeafElements in the document.
+ */
+ class LeafIterator extends Iterator
+ {
+ HTML.Tag tag;
+ HTMLDocument doc;
+ ElementIterator it;
+
+ public LeafIterator (HTML.Tag t, HTMLDocument d)
+ {
+ doc = d;
+ tag = t;
+ it = new ElementIterator(doc);
+ }
+
+ /**
+ * Return the attributes for the tag associated with this iteartor
+ * @return the AttributeSet
+ */
+ public AttributeSet getAttributes()
+ {
+ if (it.current() != null)
+ return it.current().getAttributes();
+ return null;
+ }
+
+ /**
+ * Get the end of the range for the current occurrence of the tag
+ * being defined and having the same attributes.
+ * @return the end of the range
+ */
+ public int getEndOffset()
+ {
+ if (it.current() != null)
+ return it.current().getEndOffset();
+ return -1;
+ }
+
+ /**
+ * Get the start of the range for the current occurrence of the tag
+ * being defined and having the same attributes.
+ * @return the start of the range (-1 if it can't be found).
+ */
+
+ public int getStartOffset()
+ {
+ if (it.current() != null)
+ return it.current().getStartOffset();
+ return -1;
+ }
+
+ /**
+ * Advance the iterator to the next LeafElement .
+ */
+ public void next()
+ {
+ it.next();
+ while (it.current()!= null && !it.current().isLeaf())
+ it.next();
+ }
+
+ /**
+ * Indicates whether or not the iterator currently represents an occurrence
+ * of the tag.
+ * @return true if the iterator currently represents an occurrence of the
+ * tag.
+ */
+ public boolean isValid()
+ {
+ return it.current() != null;
+ }
+
+ /**
+ * Type of tag for this iterator.
+ */
+ public Tag getTag()
+ {
+ return tag;
+ }
+
+ }
+
+ public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event)
+ {
+ String target = event.getTarget();
+ Element el = event.getSourceElement();
+ URL url = event.getURL();
+ if (target.equals("_self"))
+ {
+ updateFrame(el, url);
+ }
+ else if (target.equals("_parent"))
+ {
+ updateFrameSet(el.getParentElement(), url);
+ }
+ else
+ {
+ Element targetFrame = findFrame(target);
+ if (targetFrame != null)
+ updateFrame(targetFrame, url);
+ }
+ }
+
+ /**
+ * Finds the named frame inside this document.
+ *
+ * @param target the name to look for
+ *
+ * @return the frame if there is a matching frame, <code>null</code>
+ * otherwise
+ */
+ private Element findFrame(String target)
+ {
+ ElementIterator i = new ElementIterator(this);
+ Element next = null;
+ while ((next = i.next()) != null)
+ {
+ AttributeSet atts = next.getAttributes();
+ if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FRAME)
+ {
+ String name = (String) atts.getAttribute(HTML.Attribute.NAME);
+ if (name != null && name.equals(target))
+ break;
+ }
+ }
+ return next;
+ }
+
+ /**
+ * Updates the frame that is represented by the specified element to
+ * refer to the specified URL.
+ *
+ * @param el the element
+ * @param url the new url
+ */
+ private void updateFrame(Element el, URL url)
+ {
+ try
+ {
+ writeLock();
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(el.getStartOffset(), 1,
+ DocumentEvent.EventType.CHANGE);
+ AttributeSet elAtts = el.getAttributes();
+ AttributeSet copy = elAtts.copyAttributes();
+ MutableAttributeSet matts = (MutableAttributeSet) elAtts;
+ ev.addEdit(new AttributeUndoableEdit(el, copy, false));
+ matts.removeAttribute(HTML.Attribute.SRC);
+ matts.addAttribute(HTML.Attribute.SRC, url.toString());
+ ev.end();
+ fireChangedUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ /**
+ * Updates the frameset that is represented by the specified element
+ * to create a frame that refers to the specified URL.
+ *
+ * @param el the element
+ * @param url the url
+ */
+ private void updateFrameSet(Element el, URL url)
+ {
+ int start = el.getStartOffset();
+ int end = el.getEndOffset();
+
+ StringBuilder html = new StringBuilder();
+ html.append("<frame");
+ if (url != null)
+ {
+ html.append(" src=\"");
+ html.append(url.toString());
+ html.append("\"");
+ }
+ html.append('>');
+ if (getParser() == null)
+ setParser(new HTMLEditorKit().getParser());
+ try
+ {
+ setOuterHTML(el, html.toString());
+ }
+ catch (BadLocationException ex)
+ {
+ ex.printStackTrace();
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Gets an iterator for the given HTML.Tag.
+ * @param t the requested HTML.Tag
+ * @return the Iterator
+ */
+ public HTMLDocument.Iterator getIterator (HTML.Tag t)
+ {
+ return new HTMLDocument.LeafIterator(t, this);
+ }
+
+ /**
+ * An iterator over a particular type of tag.
+ */
+ public abstract static class Iterator
+ {
+ /**
+ * Return the attribute set for this tag.
+ * @return the <code>AttributeSet</code> (null if none found).
+ */
+ public abstract AttributeSet getAttributes();
+
+ /**
+ * Get the end of the range for the current occurrence of the tag
+ * being defined and having the same attributes.
+ * @return the end of the range
+ */
+ public abstract int getEndOffset();
+
+ /**
+ * Get the start of the range for the current occurrence of the tag
+ * being defined and having the same attributes.
+ * @return the start of the range (-1 if it can't be found).
+ */
+ public abstract int getStartOffset();
+
+ /**
+ * Move the iterator forward.
+ */
+ public abstract void next();
+
+ /**
+ * Indicates whether or not the iterator currently represents an occurrence
+ * of the tag.
+ * @return true if the iterator currently represents an occurrence of the
+ * tag.
+ */
+ public abstract boolean isValid();
+
+ /**
+ * Type of tag this iterator represents.
+ * @return the tag.
+ */
+ public abstract HTML.Tag getTag();
+ }
+
+ public class BlockElement extends AbstractDocument.BranchElement
+ {
+ public BlockElement (Element parent, AttributeSet a)
+ {
+ super(parent, a);
+ }
+
+ /**
+ * Gets the resolving parent. Since HTML attributes are not
+ * inherited at the model level, this returns null.
+ */
+ public AttributeSet getResolveParent()
+ {
+ return null;
+ }
+
+ /**
+ * Gets the name of the element.
+ *
+ * @return the name of the element if it exists, null otherwise.
+ */
+ public String getName()
+ {
+ Object tag = getAttribute(StyleConstants.NameAttribute);
+ String name = null;
+ if (tag != null)
+ name = tag.toString();
+ if (name == null)
+ name = super.getName();
+ return name;
+ }
+ }
+
+ /**
+ * 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()
+ {
+ Object tag = getAttribute(StyleConstants.NameAttribute);
+ String name = null;
+ if (tag != null)
+ name = tag.toString();
+ if (name == null)
+ name = super.getName();
+ return name;
+ }
+
+ /**
+ * 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
+ {
+ /**
+ * The maximum token threshold. We don't grow it larger than this.
+ */
+ private static final int MAX_THRESHOLD = 10000;
+
+ /**
+ * The threshold growth factor.
+ */
+ private static final int GROW_THRESHOLD = 5;
+
+ /**
+ * Holds the current character attribute set *
+ */
+ protected MutableAttributeSet charAttr = new SimpleAttributeSet();
+
+ protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
+
+ /**
+ * The parse stack. It holds the current element tree path.
+ */
+ private Stack<HTML.Tag> parseStack = new Stack<HTML.Tag>();
+
+ /**
+ * A stack for character attribute sets *
+ */
+ Stack charAttrStack = new Stack();
+
+ /** A mapping between HTML.Tag objects and the actions that handle them **/
+ HashMap tagToAction;
+
+ /** Tells us whether we've received the '</html>' tag yet **/
+ boolean endHTMLEncountered = false;
+
+ /**
+ * Related to the constructor with explicit insertTag
+ */
+ int popDepth;
+
+ /**
+ * Related to the constructor with explicit insertTag
+ */
+ int pushDepth;
+
+ /**
+ * Related to the constructor with explicit insertTag
+ */
+ int offset;
+
+ /**
+ * The tag (inclusve), after that the insertion should start.
+ */
+ HTML.Tag insertTag;
+
+ /**
+ * This variable becomes true after the insert tag has been encountered.
+ */
+ boolean insertTagEncountered;
+
+
+ /** A temporary variable that helps with the printing out of debug information **/
+ boolean debug = false;
+
+ /**
+ * This is true when we are inside a pre tag.
+ */
+ boolean inPreTag = false;
+
+ /**
+ * This is true when we are inside a style tag. This will add text
+ * content inside this style tag beeing parsed as CSS.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ boolean inStyleTag = false;
+
+ /**
+ * This is true when we are inside a &lt;textarea&gt; tag. Any text
+ * content will then be added to the text area.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ boolean inTextArea = false;
+
+ /**
+ * This contains all stylesheets that are somehow read, either
+ * via embedded style tags, or via linked stylesheets. The
+ * elements will be String objects containing a stylesheet each.
+ */
+ ArrayList styles;
+
+ /**
+ * The document model for a textarea.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ ResetablePlainDocument textAreaDocument;
+
+ /**
+ * The current model of a select tag. Can be a ComboBoxModel or a
+ * ListModel depending on the type of the select box.
+ */
+ Object selectModel;
+
+ /**
+ * The current option beeing read.
+ */
+ Option option;
+
+ /**
+ * The current number of options in the current select model.
+ */
+ int numOptions;
+
+ /**
+ * The current button groups mappings.
+ */
+ HashMap buttonGroups;
+
+ /**
+ * The token threshold. This gets increased while loading.
+ */
+ private int threshold;
+
+ public class TagAction
+ {
+ /**
+ * 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();
+
+ // Initialize with link pseudo class.
+ if (t == HTML.Tag.A)
+ a.addAttribute(HTML.Attribute.PSEUDO_CLASS, "link");
+
+ // Just add the attributes in <code>a</code>.
+ 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();
+ }
+ }
+
+ /**
+ * Processes elements that make up forms: &lt;input&gt;, &lt;textarea&gt;,
+ * &lt;select&gt; and &lt;option&gt;.
+ */
+ 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)
+ {
+ if (t == HTML.Tag.INPUT)
+ {
+ String type = (String) a.getAttribute(HTML.Attribute.TYPE);
+ if (type == null)
+ {
+ type = "text"; // Default to 'text' when nothing was specified.
+ a.addAttribute(HTML.Attribute.TYPE, type);
+ }
+ setModel(type, a);
+ }
+ else if (t == HTML.Tag.TEXTAREA)
+ {
+ inTextArea = true;
+ textAreaDocument = new ResetablePlainDocument();
+ a.addAttribute(StyleConstants.ModelAttribute, textAreaDocument);
+ }
+ else if (t == HTML.Tag.SELECT)
+ {
+ int size = HTML.getIntegerAttributeValue(a, HTML.Attribute.SIZE,
+ 1);
+ boolean multi = a.getAttribute(HTML.Attribute.MULTIPLE) != null;
+ if (size > 1 || multi)
+ {
+ SelectListModel m = new SelectListModel();
+ if (multi)
+ m.getSelectionModel().setSelectionMode(ListSelectionModel
+ .MULTIPLE_INTERVAL_SELECTION);
+ selectModel = m;
+ }
+ else
+ {
+ selectModel = new SelectComboBoxModel();
+ }
+ a.addAttribute(StyleConstants.ModelAttribute, selectModel);
+ }
+ if (t == HTML.Tag.OPTION)
+ {
+ option = new Option(a);
+ if (selectModel instanceof SelectListModel)
+ {
+ SelectListModel m = (SelectListModel) selectModel;
+ m.addElement(option);
+ if (option.isSelected())
+ {
+ m.getSelectionModel().addSelectionInterval(numOptions,
+ numOptions);
+ m.addInitialSelection(numOptions);
+ }
+ }
+ else if (selectModel instanceof SelectComboBoxModel)
+ {
+ SelectComboBoxModel m = (SelectComboBoxModel) selectModel;
+ m.addElement(option);
+ if (option.isSelected())
+ {
+ m.setSelectedItem(option);
+ m.setInitialSelection(option);
+ }
+ }
+ numOptions++;
+ }
+ else
+ {
+ // Build the element.
+ super.start(t, a);
+ }
+ }
+
+ /**
+ * Called when an end tag is seen for one of the types of tags associated
+ * with this Action.
+ */
+ public void end(HTML.Tag t)
+ {
+ if (t == HTML.Tag.OPTION)
+ {
+ option = null;
+ }
+ else
+ {
+ if (t == HTML.Tag.TEXTAREA)
+ {
+ inTextArea = false;
+ }
+ else if (t == HTML.Tag.SELECT)
+ {
+ selectModel = null;
+ numOptions = 0;
+ }
+ // Finish the element.
+ super.end(t);
+ }
+ }
+
+ private void setModel(String type, MutableAttributeSet attrs)
+ {
+ if (type.equals("submit") || type.equals("reset")
+ || type.equals("image"))
+ {
+ // Create button.
+ attrs.addAttribute(StyleConstants.ModelAttribute,
+ new DefaultButtonModel());
+ }
+ else if (type.equals("text") || type.equals("password"))
+ {
+ String text = (String) attrs.getAttribute(HTML.Attribute.VALUE);
+ ResetablePlainDocument doc = new ResetablePlainDocument();
+ if (text != null)
+ {
+ doc.setInitialText(text);
+ try
+ {
+ doc.insertString(0, text, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't happen.
+ assert false;
+ }
+ }
+ attrs.addAttribute(StyleConstants.ModelAttribute, doc);
+ }
+ else if (type.equals("file"))
+ {
+ attrs.addAttribute(StyleConstants.ModelAttribute,
+ new PlainDocument());
+ }
+ else if (type.equals("checkbox") || type.equals("radio"))
+ {
+ ResetableToggleButtonModel model =
+ new ResetableToggleButtonModel();
+ if (attrs.getAttribute(HTML.Attribute.SELECTED) != null)
+ {
+ model.setSelected(true);
+ model.setInitial(true);
+ }
+ if (type.equals("radio"))
+ {
+ String name = (String) attrs.getAttribute(HTML.Attribute.NAME);
+ if (name != null)
+ {
+ if (buttonGroups == null)
+ buttonGroups = new HashMap();
+ ButtonGroup group = (ButtonGroup) buttonGroups.get(name);
+ if (group == null)
+ {
+ group = new ButtonGroup();
+ buttonGroups.put(name, group);
+ }
+ model.setGroup(group);
+ }
+ }
+ attrs.addAttribute(StyleConstants.ModelAttribute, model);
+ }
+ }
+ }
+
+ /**
+ * Called for form tags.
+ */
+ class FormTagAction
+ extends BlockAction
+ {
+ /**
+ * Clears the button group mapping.
+ */
+ public void end(HTML.Tag t)
+ {
+ super.end(t);
+ buttonGroups = null;
+ }
+ }
+
+ /**
+ * This action indicates that the content between starting and closing HTML
+ * elements (like script - /script) should not be visible. The content is
+ * still inserted and can be accessed when iterating the HTML document. The
+ * parser will only fire
+ * {@link javax.swing.text.html.HTMLEditorKit.ParserCallback#handleText} for
+ * the hidden tags, regardless from that html tags the hidden section may
+ * contain.
+ */
+ 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)
+ {
+ 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)
+ {
+ blockClose(t);
+ }
+ }
+
+ /**
+ * Handles &lt;isindex&gt; tags.
+ */
+ 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)
+ {
+ blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+ addSpecialElement(t, a);
+ blockClose(HTML.Tag.IMPLIED);
+ }
+ }
+
+ 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)
+ {
+ 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)
+ {
+ super.end(t);
+ }
+ }
+
+ /**
+ * This action is performed when a &lt;pre&gt; tag is parsed.
+ */
+ 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)
+ {
+ inPreTag = true;
+ blockOpen(t, a);
+ a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
+ blockOpen(HTML.Tag.IMPLIED, 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)
+ {
+ blockClose(HTML.Tag.IMPLIED);
+ inPreTag = false;
+ blockClose(t);
+ }
+ }
+
+ /**
+ * Inserts the elements that are represented by ths single tag with
+ * attributes (only). The closing tag, even if present, mut follow
+ * immediately after the starting tag without providing any additional
+ * information. Hence the {@link TagAction#end} method need not be
+ * overridden and still does nothing.
+ */
+ public class SpecialAction extends TagAction
+ {
+ /**
+ * The functionality is delegated to {@link HTMLReader#addSpecialElement}
+ */
+ public void start(HTML.Tag t, MutableAttributeSet a)
+ {
+ addSpecialElement(t, a);
+ }
+ }
+
+ 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)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+
+ /**
+ * Called when an end tag is seen for one of the types of tags associated
+ * with this Action.
+ */
+ public void end(HTML.Tag t)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+ }
+
+ /**
+ * Converts HTML tags to CSS attributes.
+ */
+ class ConvertAction
+ extends TagAction
+ {
+
+ public void start(HTML.Tag tag, MutableAttributeSet atts)
+ {
+ pushCharacterStyle();
+ charAttr.addAttribute(tag, atts.copyAttributes());
+ StyleSheet styleSheet = getStyleSheet();
+ // TODO: Add other tags here.
+ if (tag == HTML.Tag.FONT)
+ {
+ String color = (String) atts.getAttribute(HTML.Attribute.COLOR);
+ if (color != null)
+ styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
+ String face = (String) atts.getAttribute(HTML.Attribute.FACE);
+ if (face != null)
+ styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY,
+ face);
+ String size = (String) atts.getAttribute(HTML.Attribute.SIZE);
+ if (size != null)
+ styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE,
+ size);
+ }
+ }
+
+ public void end(HTML.Tag tag)
+ {
+ popCharacterStyle();
+ }
+ }
+
+ class BaseAction extends TagAction
+ {
+ /**
+ * 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)
+ {
+ baseTarget = (String) a.getAttribute(HTML.Attribute.TARGET);
+ }
+ }
+
+ 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)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ 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)
+ {
+ // We read in all the stylesheets that are embedded or referenced
+ // inside the header.
+ if (styles != null)
+ {
+ int numStyles = styles.size();
+ for (int i = 0; i < numStyles; i++)
+ {
+ String style = (String) styles.get(i);
+ getStyleSheet().addRule(style);
+ }
+ }
+ super.end(t);
+ }
+ }
+
+ class LinkAction extends HiddenAction
+ {
+ /**
+ * This method is called when a start tag is seen for one of the types
+ * of tags associated with this Action.
+ */
+ public void start(HTML.Tag t, MutableAttributeSet a)
+ {
+ super.start(t, a);
+ String type = (String) a.getAttribute(HTML.Attribute.TYPE);
+ if (type == null)
+ type = "text/css";
+ if (type.equals("text/css"))
+ {
+ String rel = (String) a.getAttribute(HTML.Attribute.REL);
+ String media = (String) a.getAttribute(HTML.Attribute.MEDIA);
+ String title = (String) a.getAttribute(HTML.Attribute.TITLE);
+ if (media == null)
+ media = "all";
+ else
+ media = media.toLowerCase();
+ if (rel != null)
+ {
+ rel = rel.toLowerCase();
+ if ((media.indexOf("all") != -1
+ || media.indexOf("screen") != -1)
+ && (rel.equals("stylesheet")))
+ {
+ String href = (String) a.getAttribute(HTML.Attribute.HREF);
+ URL url = null;
+ try
+ {
+ url = new URL(baseURL, href);
+ }
+ catch (MalformedURLException ex)
+ {
+ try
+ {
+ url = new URL(href);
+ }
+ catch (MalformedURLException ex2)
+ {
+ url = null;
+ }
+ }
+ if (url != null)
+ {
+ try
+ {
+ getStyleSheet().importStyleSheet(url);
+ }
+ catch (Exception ex)
+ {
+ // Don't let exceptions and runtime exceptions
+ // in CSS parsing disprupt the HTML parsing
+ // process. But inform the user/developer
+ // on the console about it.
+ ex.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ 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)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+
+ /**
+ * Called when an end tag is seen for one of the types of tags associated
+ * with this Action.
+ */
+ public void end(HTML.Tag t)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+ }
+
+ 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)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+
+ /**
+ * Called when an end tag is seen for one of the types of tags associated
+ * with this Action.
+ */
+ public void end(HTML.Tag t)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+ }
+
+ 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)
+ {
+ inStyleTag = true;
+ }
+
+ /**
+ * Called when an end tag is seen for one of the types of tags associated
+ * with this Action.
+ */
+ public void end(HTML.Tag t)
+ {
+ inStyleTag = false;
+ }
+ }
+
+ 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)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+
+ /**
+ * Called when an end tag is seen for one of the types of tags associated
+ * with this Action.
+ */
+ public void end(HTML.Tag t)
+ throws NotImplementedException
+ {
+ // FIXME: Implement.
+ }
+ }
+
+ public HTMLReader(int offset)
+ {
+ this (offset, 0, 0, null);
+ }
+
+ public HTMLReader(int offset, int popDepth, int pushDepth,
+ HTML.Tag insertTag)
+ {
+ this.insertTag = insertTag;
+ this.offset = offset;
+ this.popDepth = popDepth;
+ this.pushDepth = pushDepth;
+ threshold = getTokenThreshold();
+ 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();
+
+ ConvertAction convertAction = new ConvertAction();
+ 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, convertAction);
+ tagToAction.put(HTML.Tag.FORM, new FormTagAction());
+ tagToAction.put(HTML.Tag.FRAME, specialAction);
+ tagToAction.put(HTML.Tag.FRAMESET, blockAction);
+ tagToAction.put(HTML.Tag.H1, paragraphAction);
+ 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.copyAttributes());
+ }
+
+ /**
+ * 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
+ {
+ flushImpl();
+ }
+
+ /**
+ * Flushes the buffer and handle partial inserts.
+ *
+ */
+ private void flushImpl()
+ throws BadLocationException
+ {
+ int oldLen = getLength();
+ int size = parseBuffer.size();
+ ElementSpec[] elems = new ElementSpec[size];
+ parseBuffer.copyInto(elems);
+ if (oldLen == 0)
+ create(elems);
+ else
+ insert(offset, elems);
+ parseBuffer.removeAllElements();
+ offset += getLength() - oldLen;
+ }
+
+ /**
+ * This method is called by the parser to indicate a block of
+ * text was encountered. Should insert the text appropriately.
+ *
+ * @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 (shouldInsert() && data != null && data.length > 0)
+ {
+ if (inTextArea)
+ textAreaContent(data);
+ else if (inPreTag)
+ preContent(data);
+ else if (option != null)
+ option.setLabel(new String(data));
+ else if (inStyleTag)
+ {
+ if (styles == null)
+ styles = new ArrayList();
+ styles.add(new String(data));
+ }
+ else
+ addContent(data, 0, data.length);
+
+ }
+ }
+
+ /**
+ * Checks if the HTML tag should be inserted. The tags before insert tag (if
+ * specified) are not inserted. Also, the tags after the end of the html are
+ * not inserted.
+ *
+ * @return true if the tag should be inserted, false otherwise.
+ */
+ private boolean shouldInsert()
+ {
+ return ! endHTMLEncountered
+ && (insertTagEncountered || insertTag == null);
+ }
+
+ /**
+ * 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)
+ {
+ if (t == insertTag)
+ insertTagEncountered = true;
+
+ if (shouldInsert())
+ {
+ 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)
+ {
+ if (shouldInsert())
+ {
+ 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)
+ {
+ if (shouldInsert())
+ {
+ // If this is the </html> 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)
+ {
+ if (t == insertTag)
+ insertTagEncountered = true;
+
+ if (shouldInsert())
+ {
+ 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.
+ }
+
+ /**
+ * 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)
+ {
+ try
+ {
+ int offset = textAreaDocument.getLength();
+ String text = new String(data);
+ textAreaDocument.setInitialText(text);
+ textAreaDocument.insertString(offset, text, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // Must not happen as we insert at a model location that we
+ // got from the document itself.
+ assert false;
+ }
+ }
+
+ /**
+ * Adds the given text that was encountered in a <PRE> element.
+ * This adds synthesized lines to hold the text runs.
+ *
+ * @param data the text
+ */
+ protected void preContent(char[] data)
+ {
+ int start = 0;
+ for (int i = 0; i < data.length; i++)
+ {
+ if (data[i] == '\n')
+ {
+ addContent(data, start, i - start + 1);
+ blockClose(HTML.Tag.IMPLIED);
+ MutableAttributeSet atts = new SimpleAttributeSet();
+ atts.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
+ blockOpen(HTML.Tag.IMPLIED, atts);
+ start = i + 1;
+ }
+ }
+ if (start < data.length)
+ {
+ // Add remaining last line.
+ addContent(data, start, data.length - start);
+ }
+ }
+
+ /**
+ * 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)
+ {
+ if (inImpliedParagraph())
+ blockClose(HTML.Tag.IMPLIED);
+
+ // Push the new tag on top of the stack.
+ parseStack.push(t);
+
+ DefaultStyledDocument.ElementSpec element;
+
+ AbstractDocument.AttributeContext ctx = getAttributeContext();
+ AttributeSet copy = attr.copyAttributes();
+ copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t);
+ element = new DefaultStyledDocument.ElementSpec(copy,
+ DefaultStyledDocument.ElementSpec.StartTagType);
+ parseBuffer.addElement(element);
+ }
+
+ /**
+ * Returns true when we are currently inside a paragraph, either
+ * a real one or an implied, false otherwise.
+ *
+ * @return
+ */
+ private boolean inParagraph()
+ {
+ boolean inParagraph = false;
+ if (! parseStack.isEmpty())
+ {
+ HTML.Tag top = parseStack.peek();
+ inParagraph = top == HTML.Tag.P || top == HTML.Tag.IMPLIED;
+ }
+ return inParagraph;
+ }
+
+ private boolean inImpliedParagraph()
+ {
+ boolean inParagraph = false;
+ if (! parseStack.isEmpty())
+ {
+ HTML.Tag top = parseStack.peek();
+ inParagraph = top == HTML.Tag.IMPLIED;
+ }
+ return inParagraph;
+ }
+
+ /**
+ * 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)
+ {
+ DefaultStyledDocument.ElementSpec element;
+
+ if (inImpliedParagraph() && t != HTML.Tag.IMPLIED)
+ blockClose(HTML.Tag.IMPLIED);
+
+ // Pull the token from the stack.
+ if (! parseStack.isEmpty()) // Just to be sure.
+ parseStack.pop();
+
+ // If the previous tag is a start tag then we insert a synthetic
+ // content tag.
+ DefaultStyledDocument.ElementSpec prev;
+ prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec)
+ parseBuffer.get(parseBuffer.size() - 1) : null;
+ if (prev != null &&
+ prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
+ {
+ addContent(new char[]{' '}, 0, 1);
+ }
+
+ element = new DefaultStyledDocument.ElementSpec(null,
+ DefaultStyledDocument.ElementSpec.EndTagType);
+ parseBuffer.addElement(element);
+ }
+
+ /**
+ * 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)
+ {
+ if (generateImpliedPIfNecessary && ! inParagraph())
+ {
+ blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+ }
+
+ AbstractDocument.AttributeContext ctx = getAttributeContext();
+ DefaultStyledDocument.ElementSpec element;
+ AttributeSet attributes = null;
+
+ // Copy the attribute set, don't use the same object because
+ // it may change
+ if (charAttr != null)
+ attributes = charAttr.copyAttributes();
+ else
+ attributes = ctx.getEmptySet();
+ attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ element = new DefaultStyledDocument.ElementSpec(attributes,
+ DefaultStyledDocument.ElementSpec.ContentType,
+ data, offs, length);
+
+ // Add the element to the buffer
+ parseBuffer.addElement(element);
+
+ if (parseBuffer.size() > threshold)
+ {
+ if (threshold <= MAX_THRESHOLD)
+ threshold *= GROW_THRESHOLD;
+ try
+ {
+ flushImpl();
+ }
+ 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)
+ {
+ if (t != HTML.Tag.FRAME && ! inParagraph())
+ {
+ blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+ }
+
+ a.addAttribute(StyleConstants.NameAttribute, t);
+
+ // The two spaces are required because some special elements like HR
+ // must be broken. At least two characters are needed to break into the
+ // two parts.
+ DefaultStyledDocument.ElementSpec spec =
+ new DefaultStyledDocument.ElementSpec(a.copyAttributes(),
+ DefaultStyledDocument.ElementSpec.ContentType,
+ new char[] {' '}, 0, 1 );
+ parseBuffer.add(spec);
+ }
+
+ }
+
+ /**
+ * 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 reader for the parser to use when inserting the HTML fragment into
+ * the document. Checks if the parser is present, sets the parent in the
+ * element stack and removes any actions for BODY (it can be only one body in
+ * a HTMLDocument).
+ *
+ * @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
+ * @param parent the element that will be the parent in the document. HTML
+ * parsing includes checks for the parent, so it must be available.
+ * @return - the reader
+ * @throws IllegalStateException if the parsert is not set.
+ */
+ public HTMLEditorKit.ParserCallback getInsertingReader(int pos, int popDepth,
+ int pushDepth,
+ HTML.Tag insertTag,
+ final Element parent)
+ throws IllegalStateException
+ {
+ if (parser == null)
+ throw new IllegalStateException("Parser has not been set");
+
+ HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth, insertTag)
+ {
+ /**
+ * Ignore BODY.
+ */
+ public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos)
+ {
+ if (t != HTML.Tag.BODY)
+ super.handleStartTag(t, a, pos);
+ }
+
+ /**
+ * Ignore BODY.
+ */
+ public void handleEndTag(HTML.Tag t, int pos)
+ {
+ if (t != HTML.Tag.BODY)
+ super.handleEndTag(t, pos);
+ }
+ };
+
+ return reader;
+ }
+
+ /**
+ * 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 (for instance, &lt;p id
+ * ='my paragraph &gt;'). If it is not found, null is returned. The HTML tag,
+ * having this attribute, is not checked by this method and can be any. The
+ * method is not thread-safe.
+ *
+ * @param attrId - the value of the attribute id to look for
+ * @return the element that has the given id.
+ */
+ public Element getElement(String attrId)
+ {
+ return getElement(getDefaultRootElement(), HTML.Attribute.ID,
+ 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");
+
+ int start = elem.getStartOffset();
+ int end = elem.getEndOffset();
+
+ HTMLEditorKit.ParserCallback reader = getInsertingReader(
+ end, 0, 0, HTML.Tag.BODY, elem);
+
+ // TODO charset
+ getParser().parse(new StringReader(htmlText), reader, true);
+
+ // Remove the previous content
+ remove(start, end - start);
+ }
+
+ /**
+ * 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
+ {
+ // Remove the current element:
+ int start = elem.getStartOffset();
+ int end = elem.getEndOffset();
+
+ remove(start, end-start);
+
+ HTMLEditorKit.ParserCallback reader = getInsertingReader(
+ start, 0, 0, HTML.Tag.BODY, elem);
+
+ // TODO charset
+ getParser().parse(new StringReader(htmlText), reader, true);
+ }
+
+ /**
+ * 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
+ {
+ HTMLEditorKit.ParserCallback reader = getInsertingReader(
+ elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+ // TODO charset
+ getParser().parse(new StringReader(htmlText), reader, true);
+ }
+
+ /**
+ * 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
+ {
+ HTMLEditorKit.ParserCallback reader = getInsertingReader(
+ elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+ // TODO charset
+ getParser().parse(new StringReader(htmlText), reader, true);
+
+ }
+
+ /**
+ * 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
+ {
+ HTMLEditorKit.ParserCallback reader = getInsertingReader(
+ elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+ // TODO charset
+ getParser().parse(new StringReader(htmlText), reader, true);
+ }
+
+ /**
+ * 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
+ {
+ HTMLEditorKit.ParserCallback reader = getInsertingReader(
+ elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+ // TODO charset
+ getParser().parse(new StringReader(htmlText), reader, true);
+ }
+
+ /**
+ * Overridden to tag content with the synthetic HTML.Tag.CONTENT
+ * tag.
+ */
+ protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att)
+ {
+ if (att == null)
+ {
+ SimpleAttributeSet sas = new SimpleAttributeSet();
+ sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
+ att = sas;
+ }
+ super.insertUpdate(evt, att);
+ }
+
+ /**
+ * Returns <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise
+ */
+ boolean isFrameDocument()
+ {
+ return frameDocument;
+ }
+
+ /**
+ * Set <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise.
+ *
+ * @param frameDoc <code>true</code> when this document is inside a frame,
+ * <code>false</code> otherwise
+ */
+ void setFrameDocument(boolean frameDoc)
+ {
+ frameDocument = frameDoc;
+ }
+
+ /**
+ * Returns the target that is specified in the base tag, if this is the case.
+ *
+ * @return the target that is specified in the base tag, if this is the case
+ */
+ String getBaseTarget()
+ {
+ return baseTarget;
+ }
+
+ /**
+ * Updates the A tag's pseudo class value in response to a hyperlink
+ * action.
+ *
+ * @param el the corresponding element
+ * @param value the new value
+ */
+ void updateSpecialClass(Element el, HTML.Attribute cl, String value)
+ {
+ try
+ {
+ writeLock();
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(el.getStartOffset(), 1,
+ DocumentEvent.EventType.CHANGE);
+ AttributeSet elAtts = el.getAttributes();
+ AttributeSet anchorAtts = (AttributeSet) elAtts.getAttribute(HTML.Tag.A);
+ if (anchorAtts != null)
+ {
+ AttributeSet copy = elAtts.copyAttributes();
+ StyleSheet ss = getStyleSheet();
+ if (value != null)
+ {
+ anchorAtts = ss.addAttribute(anchorAtts, cl, value);
+ }
+ else
+ {
+ anchorAtts = ss.removeAttribute(anchorAtts, cl);
+ }
+ MutableAttributeSet matts = (MutableAttributeSet) elAtts;
+ ev.addEdit(new AttributeUndoableEdit(el, copy, false));
+ matts.removeAttribute(HTML.Tag.A);
+ matts.addAttribute(HTML.Tag.A, anchorAtts);
+ ev.end();
+ fireChangedUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+ }
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
new file mode 100644
index 000000000..e3505d3c2
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/HTMLEditorKit.java
@@ -0,0 +1,1520 @@
+/* HTMLEditorKit.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.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.Cursor;
+import java.awt.Point;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.accessibility.Accessible;
+import javax.accessibility.AccessibleContext;
+
+import javax.swing.Action;
+import javax.swing.JEditorPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.EditorKit;
+import javax.swing.text.Element;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyledDocument;
+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;
+
+/* Move these imports here after javax.swing.text.html to make it compile
+ with jikes. */
+import gnu.javax.swing.text.html.parser.GnuParserDelegator;
+import gnu.javax.swing.text.html.parser.HTML_401F;
+
+/**
+ * @author Lillian Angel (langel at redhat dot com)
+ */
+public class HTMLEditorKit
+ extends StyledEditorKit
+ implements Serializable, Cloneable, Accessible
+{
+
+ /**
+ * Fires the hyperlink events on the associated component
+ * when needed.
+ */
+ public static class LinkController
+ extends MouseAdapter
+ implements MouseMotionListener, Serializable
+ {
+
+ /**
+ * The element of the last anchor tag.
+ */
+ private Element lastAnchorElement;
+
+ /**
+ * 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)
+ {
+ JEditorPane editor = (JEditorPane) e.getSource();
+ if (! editor.isEditable() && SwingUtilities.isLeftMouseButton(e))
+ {
+ Point loc = e.getPoint();
+ int pos = editor.viewToModel(loc);
+ if (pos >= 0)
+ activateLink(pos, editor, e.getX(), e.getY());
+ }
+ }
+
+ /**
+ * Dispatched when the mouse is dragged on a component.
+ *
+ * @param e - the mouse event.
+ */
+ public void mouseDragged(MouseEvent e)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Dispatched when the mouse cursor has moved into the component.
+ *
+ * @param e - the mouse event.
+ */
+ public void mouseMoved(MouseEvent e)
+ {
+ JEditorPane editor = (JEditorPane) e.getSource();
+ HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit();
+ if (! editor.isEditable())
+ {
+ Document doc = editor.getDocument();
+ if (doc instanceof HTMLDocument)
+ {
+ Cursor newCursor = kit.getDefaultCursor();
+ HTMLDocument htmlDoc = (HTMLDocument) doc;
+ Point loc = e.getPoint();
+ int pos = editor.viewToModel(loc);
+ Element el = htmlDoc.getCharacterElement(pos);
+ if (pos < el.getStartOffset() || pos >= el.getEndOffset())
+ el = null;
+ if (el != null)
+ {
+ AttributeSet aAtts = (AttributeSet)
+ el.getAttributes().getAttribute(HTML.Tag.A);
+ if (aAtts != null)
+ {
+ if (el != lastAnchorElement)
+ {
+ if (lastAnchorElement != null)
+ htmlDoc.updateSpecialClass(lastAnchorElement,
+ HTML.Attribute.DYNAMIC_CLASS,
+ null);
+ lastAnchorElement = el;
+ htmlDoc.updateSpecialClass(el,
+ HTML.Attribute.DYNAMIC_CLASS,
+ "hover");
+ }
+ newCursor = kit.getLinkCursor();
+ }
+ else
+ {
+ if (lastAnchorElement != null)
+ htmlDoc.updateSpecialClass(lastAnchorElement,
+ HTML.Attribute.DYNAMIC_CLASS,
+ null);
+ lastAnchorElement = null;
+ }
+ }
+ else
+ {
+ if (lastAnchorElement != null)
+ htmlDoc.updateSpecialClass(lastAnchorElement,
+ HTML.Attribute.DYNAMIC_CLASS,
+ null);
+ lastAnchorElement = null;
+ }
+ if (editor.getCursor() != newCursor)
+ {
+ editor.setCursor(newCursor);
+ }
+ }
+ }
+ }
+
+ /**
+ * If the given position represents a link, then linkActivated is called
+ * on the JEditorPane.
+ *
+ * @param pos the position
+ * @param editor the editor pane
+ */
+ protected void activateLink(int pos, JEditorPane editor)
+ {
+ activateLink(pos, editor);
+ }
+
+ private void activateLink(int pos, JEditorPane editor, int x, int y)
+ {
+ // TODO: This is here for future extension for mapped links support.
+ // For the time beeing we implement simple hyperlinks.
+ Document doc = editor.getDocument();
+ if (doc instanceof HTMLDocument)
+ {
+ HTMLDocument htmlDoc = (HTMLDocument) doc;
+ Element el = htmlDoc.getCharacterElement(pos);
+ AttributeSet atts = el.getAttributes();
+ AttributeSet anchorAtts =
+ (AttributeSet) atts.getAttribute(HTML.Tag.A);
+ String href = null;
+ if (anchorAtts != null)
+ {
+ href = (String) anchorAtts.getAttribute(HTML.Attribute.HREF);
+ htmlDoc.updateSpecialClass(el, HTML.Attribute.PSEUDO_CLASS,
+ "visited");
+ }
+ else
+ {
+ // TODO: Implement link maps here.
+ }
+ HyperlinkEvent event = null;
+ if (href != null)
+ event = createHyperlinkEvent(editor, htmlDoc, href,
+ anchorAtts, el);
+ if (event != null)
+ editor.fireHyperlinkUpdate(event);
+ }
+
+ }
+
+ /**
+ * Creates a HyperlinkEvent for the specified href and anchor if
+ * possible. If for some reason this won't work, return null.
+ *
+ * @param editor the editor
+ * @param doc the document
+ * @param href the href link
+ * @param anchor the anchor
+ * @param el the element
+ *
+ * @return the hyperlink event, or <code>null</code> if we couldn't
+ * create one
+ */
+ private HyperlinkEvent createHyperlinkEvent(JEditorPane editor,
+ HTMLDocument doc,
+ String href,
+ AttributeSet anchor,
+ Element el)
+ {
+ URL url;
+ try
+ {
+ URL base = doc.getBase();
+ url = new URL(base, href);
+
+ }
+ catch (MalformedURLException ex)
+ {
+ url = null;
+ }
+ HyperlinkEvent ev;
+ if (doc.isFrameDocument())
+ {
+ String target = null;
+ if (anchor != null)
+ target = (String) anchor.getAttribute(HTML.Attribute.TARGET);
+ if (target == null || target.equals(""))
+ target = doc.getBaseTarget();
+ if (target == null || target.equals(""))
+ target = "_self";
+ ev = new HTMLFrameHyperlinkEvent(editor,
+ HyperlinkEvent.EventType.ACTIVATED,
+ url, href, el, target);
+ }
+ else
+ {
+ ev = new HyperlinkEvent(editor, HyperlinkEvent.EventType.ACTIVATED,
+ url, href, el);
+ }
+ return ev;
+ }
+ }
+
+ /**
+ * 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)
+ {
+ insertAtBoundry(editor, doc, offset, insertElement,
+ html, parentTag, addTag);
+ }
+
+ /**
+ * 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)
+ {
+ Element parent = insertElement;
+ Element el;
+ // Find common parent element.
+ if (offset > 0 || insertElement == null)
+ {
+ el = doc.getDefaultRootElement();
+ while (el != null && el.getStartOffset() != offset
+ && ! el.isLeaf())
+ el = el.getElement(el.getElementIndex(offset));
+ parent = el != null ? el.getParentElement() : null;
+ }
+ if (parent != null)
+ {
+ int pops = 0;
+ int pushes = 0;
+ if (offset == 0 && insertElement != null)
+ {
+ el = parent;
+ while (el != null && ! el.isLeaf())
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pops++;
+ }
+ }
+ else
+ {
+ el = parent;
+ offset--;
+ while (el != null && ! el.isLeaf())
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pops++;
+ }
+ el = parent;
+ offset++;
+ while (el != null && el != insertElement)
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pushes++;
+ }
+ }
+ pops = Math.max(0, pops - 1);
+ insertHTML(editor, doc, offset, html, pops, pushes, addTag);
+ }
+ }
+
+ /**
+ * Inserts the HTML.
+ *
+ * @param ae - the action performed
+ */
+ public void actionPerformed(ActionEvent ae)
+ {
+ JEditorPane source = getEditor(ae);
+ if (source != null)
+ {
+ HTMLDocument d = getHTMLDocument(source);
+ int offset = source.getSelectionStart();
+ int length = d.getLength();
+ boolean inserted = true;
+ if (! tryInsert(source, d, offset, parentTag, addTag))
+ {
+ inserted = tryInsert(source, d, offset, alternateParentTag,
+ alternateAddTag);
+ }
+ if (inserted)
+ adjustSelection(source, d, offset, length);
+ }
+ }
+
+ /**
+ * Tries to insert the html chunk to the specified <code>addTag</code>.
+ *
+ * @param pane the editor
+ * @param doc the document
+ * @param offset the offset at which to insert
+ * @param tag the tag at which to insert
+ * @param addTag the add tag
+ *
+ * @return <code>true</code> when the html has been inserted successfully,
+ * <code>false</code> otherwise
+ */
+ private boolean tryInsert(JEditorPane pane, HTMLDocument doc, int offset,
+ HTML.Tag tag, HTML.Tag addTag)
+ {
+ boolean inserted = false;
+ Element el = findElementMatchingTag(doc, offset, tag);
+ if (el != null && el.getStartOffset() == offset)
+ {
+ insertAtBoundary(pane, doc, offset, el, html, tag, addTag);
+ inserted = true;
+ }
+ else if (offset > 0)
+ {
+ int depth = elementCountToTag(doc, offset - 1, tag);
+ if (depth != -1)
+ {
+ insertHTML(pane, doc, offset, html, depth, 0, addTag);
+ inserted = true;
+ }
+ }
+ return inserted;
+ }
+
+ /**
+ * Adjusts the selection after an insertion has been performed.
+ *
+ * @param pane the editor pane
+ * @param doc the document
+ * @param offset the insert offset
+ * @param oldLen the old document length
+ */
+ private void adjustSelection(JEditorPane pane, HTMLDocument doc,
+ int offset, int oldLen)
+ {
+ int newLen = doc.getLength();
+ if (newLen != oldLen && offset < newLen)
+ {
+ if (offset > 0)
+ {
+ String text;
+ try
+ {
+ text = doc.getText(offset - 1, 1);
+ }
+ catch (BadLocationException ex)
+ {
+ text = null;
+ }
+ if (text != null && text.length() > 0
+ && text.charAt(0) == '\n')
+ {
+ pane.select(offset, offset);
+ }
+ else
+ {
+ pane.select(offset + 1, offset + 1);
+ }
+ }
+ else
+ {
+ pane.select(1, 1);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 <code>Element</code>s that are supported.
+ */
+ public static class HTMLFactory
+ implements ViewFactory
+ {
+
+ /**
+ * Constructor
+ */
+ public HTMLFactory()
+ {
+ // Do Nothing here.
+ }
+
+ /**
+ * Creates a {@link View} for the specified <code>Element</code>.
+ *
+ * @param element the <code>Element</code> to create a <code>View</code>
+ * for
+ * @return the <code>View</code> for the specified <code>Element</code>
+ * or <code>null</code> if the type of <code>element</code> 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 == HTML.Tag.IMPLIED || tag == HTML.Tag.P
+ || tag == HTML.Tag.H1 || tag == HTML.Tag.H2
+ || tag == HTML.Tag.H3 || tag == HTML.Tag.H4
+ || tag == HTML.Tag.H5 || tag == HTML.Tag.H6
+ || tag == HTML.Tag.DT)
+ view = new ParagraphView(element);
+ else if (tag == HTML.Tag.LI || tag == HTML.Tag.DL
+ || tag == HTML.Tag.DD || tag == HTML.Tag.BODY
+ || tag == HTML.Tag.HTML || tag == HTML.Tag.CENTER
+ || tag == HTML.Tag.DIV
+ || tag == HTML.Tag.BLOCKQUOTE
+ || tag == HTML.Tag.PRE
+ || tag == HTML.Tag.FORM
+ // Misplaced TD and TH tags get mapped as vertical block.
+ // Note that correctly placed tags get mapped in TableView.
+ || tag == HTML.Tag.TD || tag == HTML.Tag.TH)
+ view = new BlockView(element, View.Y_AXIS);
+ else if (tag == HTML.Tag.TR)
+ // Misplaced TR tags get mapped as horizontal blocks.
+ // Note that correctly placed tags get mapped in TableView.
+ view = new BlockView(element, View.X_AXIS);
+ else if (tag == HTML.Tag.IMG)
+ view = new ImageView(element);
+
+ else if (tag == HTML.Tag.CONTENT)
+ view = new InlineView(element);
+ else if (tag == HTML.Tag.HEAD)
+ view = new NullView(element);
+ else if (tag == HTML.Tag.TABLE)
+ view = new javax.swing.text.html.TableView(element);
+ else if (tag == HTML.Tag.HR)
+ view = new HRuleView(element);
+ else if (tag == HTML.Tag.BR)
+ view = new BRView(element);
+ else if (tag == HTML.Tag.INPUT || tag == HTML.Tag.SELECT
+ || tag == HTML.Tag.TEXTAREA)
+ view = new FormView(element);
+
+ else if (tag == HTML.Tag.MENU || tag == HTML.Tag.DIR
+ || tag == HTML.Tag.UL || tag == HTML.Tag.OL)
+ view = new ListView(element);
+ else if (tag == HTML.Tag.FRAMESET)
+ view = new FrameSetView(element);
+ else if (tag == HTML.Tag.FRAME)
+ view = new FrameView(element);
+ else if (tag == HTML.Tag.OBJECT)
+ view = new ObjectView(element);
+ }
+ if (view == null)
+ {
+ view = new NullView(element);
+ }
+ return view;
+ }
+ }
+
+ /**
+ * The abstract HTML parser declaration.
+ */
+ public abstract static class Parser
+ {
+ /**
+ * Parse the HTML text, calling various methods of the provided callback
+ * in response to the occurence of the corresponding HTML constructions.
+ * @param reader The reader to read the source HTML from.
+ * @param callback The callback to receive information about the parsed
+ * HTML structures
+ * @param ignoreCharSet If true, the parser ignores all charset information
+ * that may be present in HTML documents.
+ * @throws IOException, normally if the reader throws one.
+ */
+ public abstract void parse(Reader reader, ParserCallback callback,
+ boolean ignoreCharSet) throws IOException;
+ }
+
+ /**
+ * The "hook" that receives all information about the HTML document
+ * structure while parsing it. The methods are invoked by parser
+ * and should be normally overridden.
+ */
+ public static class ParserCallback
+ {
+ /**
+ * If the tag does not occurs in the html stream directly, but
+ * is supposed by parser, the tag attribute set contains this additional
+ * attribute, having value Boolean.True.
+ */
+ 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
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Handle HTML comment, present in the given position.
+ * @param comment the comment
+ * @position the position of the comment in the text being parsed.
+ */
+ public void handleComment(char[] comment, int position)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Notifies about the character sequences, used to separate lines in
+ * this document. The parser calls this method after it finishes
+ * parsing the document, but before flush().
+ * @param end_of_line The "end of line sequence", one of: \r or \n or \r\n.
+ */
+ public void handleEndOfLineString(String end_of_line)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * The method is called when the HTML closing tag ((like &lt;/table&gt;)
+ * is found or if the parser concludes that the one should be present
+ * in the current position.
+ * @param tag The tag being handled
+ * @param position the tag position in the text being parsed.
+ */
+ public void handleEndTag(HTML.Tag tag, int position)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Handle the error.
+ * @param message The message, explaining the error.
+ * @param position The starting position of the fragment that has caused
+ * the error in the html document being parsed.
+ */
+ public void handleError(String message, int position)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Handle the tag with no content, like &lt;br&gt;. The method is
+ * called for the elements that, in accordance with the current DTD,
+ * has an empty content.
+ * @param tag The tag being handled.
+ * @param position The tag position in the text being parsed.
+ */
+ public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes,
+ int position)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * The method is called when the HTML opening tag ((like &lt;table&gt;)
+ * is found or if the parser concludes that the one should be present
+ * in the current position.
+ * @param tag The tag being handled
+ * @param position The tag position in the text being parsed
+ */
+ public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes,
+ int position)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Handle the text section.
+ * @param text A section text.
+ * @param position The text position in the HTML document text being parsed.
+ */
+ public void handleText(char[] text, int position)
+ {
+ // Nothing to do here.
+ }
+ }
+
+ /**
+ * Use serialVersionUID (v1.4) for interoperability.
+ */
+ private static final long serialVersionUID = 8751997116710384592L;
+
+ /**
+ * Default cascading stylesheed file ("default.css").
+ */
+ public static final String DEFAULT_CSS = "default.css";
+
+ /**
+ * The <b>bold</b> action identifier.
+ */
+ public static final String BOLD_ACTION = "html-bold-action";
+
+ /**
+ * The <i>italic</i> action identifier.
+ */
+ public static final String ITALIC_ACTION = "html-italic-action";
+
+ /**
+ * The <font color="#FF0000">color</font> action indentifier
+ * (passing the color as an argument).
+ */
+ public static final String COLOR_ACTION = "html-color-action";
+
+ /**
+ * The <font size="+1">increase</font> font action identifier.
+ */
+ public static final String FONT_CHANGE_BIGGER = "html-font-bigger";
+
+ /**
+ * The <font size="-1">decrease</font> font action identifier.
+ */
+ public static final String FONT_CHANGE_SMALLER = "html-font-smaller";
+
+ /**
+ * Align images at the bottom.
+ */
+ public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom";
+
+ /**
+ * Align images at the middle.
+ */
+ public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle";
+
+ /**
+ * Align images at the top.
+ */
+ public static final String IMG_ALIGN_TOP = "html-image-align-top";
+
+ /**
+ * Align images at the border.
+ */
+ public static final String IMG_BORDER = "html-image-border";
+
+ /**
+ * The "logical style" action identifier, passing that style as parameter.
+ */
+ public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action";
+
+ /**
+ * The "ident paragraph left" action.
+ */
+ public static final String PARA_INDENT_LEFT = "html-para-indent-left";
+
+ /**
+ * The "ident paragraph right" action.
+ */
+ public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
+
+ /**
+ * Actions for HTML
+ */
+ private static final Action[] defaultActions =
+ {
+ new InsertHTMLTextAction("InsertTable",
+ "<table border=1><tr><td></td></tr></table>",
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertTableRow",
+ "<table border=1><tr><td></td></tr></table>",
+ HTML.Tag.TABLE, HTML.Tag.TR,
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertTableCell",
+ "<table border=1><tr><td></td></tr></table>",
+ HTML.Tag.TR, HTML.Tag.TD,
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertUnorderedList",
+ "<ul><li></li></ul>",
+ HTML.Tag.BODY, HTML.Tag.UL),
+ new InsertHTMLTextAction("InsertUnorderedListItem",
+ "<ul><li></li></ul>",
+ HTML.Tag.UL, HTML.Tag.LI,
+ HTML.Tag.BODY, HTML.Tag.UL),
+ new InsertHTMLTextAction("InsertOrderedList",
+ "<ol><li></li></ol>",
+ HTML.Tag.BODY, HTML.Tag.OL),
+ new InsertHTMLTextAction("InsertOrderedListItem",
+ "<ol><li></li></ol>",
+ HTML.Tag.OL, HTML.Tag.LI,
+ HTML.Tag.BODY, HTML.Tag.OL),
+ new InsertHTMLTextAction("InsertPre",
+ "<pre></pre>", HTML.Tag.BODY, HTML.Tag.PRE)
+ // TODO: The reference impl has an InsertHRAction too.
+ };
+
+ /**
+ * The current style sheet.
+ */
+ private 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.
+ */
+ private LinkController linkController;
+
+ /** The content type */
+ String contentType = "text/html";
+
+ /** The input attributes defined by default.css */
+ MutableAttributeSet inputAttributes;
+
+ /** The editor pane used. */
+ JEditorPane editorPane;
+
+ /**
+ * Whether or not the editor kit handles form submissions.
+ *
+ * @see #isAutoFormSubmission()
+ * @see #setAutoFormSubmission(boolean)
+ */
+ private boolean autoFormSubmission;
+
+ /**
+ * Constructs an HTMLEditorKit, creates a StyleContext, and loads the style sheet.
+ */
+ public HTMLEditorKit()
+ {
+ linkController = new LinkController();
+ autoFormSubmission = true;
+ }
+
+ /**
+ * 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.
+ *
+ * @return the model
+ */
+ public Document createDefaultDocument()
+ {
+ // Protect the shared stylesheet.
+ StyleSheet styleSheet = getStyleSheet();
+ StyleSheet ss = new StyleSheet();
+ ss.addStyleSheet(styleSheet);
+
+ HTMLDocument document = new HTMLDocument(ss);
+ document.setParser(getParser());
+ document.setAsynchronousLoadPriority(4);
+ document.setTokenThreshold(100);
+ return document;
+ }
+
+ /**
+ * 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()
+ {
+ if (parser == null)
+ {
+ parser = new GnuParserDelegator(HTML_401F.getInstance());
+ }
+ 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 = 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);
+ if (editorPane != null)
+ 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)
+ {
+ HTMLWriter writer = new HTMLWriter(out, (HTMLDocument) doc, pos, len);
+ writer.write();
+ }
+ else if (doc instanceof StyledDocument)
+ {
+ MinimalHTMLWriter writer = new MinimalHTMLWriter(out,
+ (StyledDocument) doc,
+ pos, len);
+ writer.write();
+ }
+ else
+ super.write(out, doc, pos, len);
+ }
+
+ /**
+ * 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
+ HTMLEditorKit copy = (HTMLEditorKit) super.clone();
+ copy.linkController = new LinkController();
+ return copy;
+ }
+
+ /**
+ * 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);
+ c.addMouseListener(linkController);
+ c.addMouseMotionListener(linkController);
+ editorPane = c;
+ }
+
+ /**
+ * 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(linkController);
+ c.removeMouseMotionListener(linkController);
+ 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)
+ {
+ try
+ {
+ styleSheet = new StyleSheet();
+ Class<?> c = HTMLEditorKit.class;
+ InputStream in = c.getResourceAsStream(DEFAULT_CSS);
+ InputStreamReader r = new InputStreamReader(in);
+ styleSheet.loadRules(r, null);
+ r.close();
+ }
+ catch (IOException ex)
+ {
+ throw new RuntimeException("No style available.", ex);
+ }
+ }
+ 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;
+ }
+
+ /**
+ * Returns <code>true</code> when forms should be automatically submitted
+ * by the editor kit. Set this to <code>false</code> when you want to
+ * intercept form submission. In this case you'd want to listen for
+ * hyperlink events on the document and handle FormSubmitEvents specially.
+ *
+ * The default is <code>true</code>.
+ *
+ * @return <code>true</code> when forms should be automatically submitted
+ * by the editor kit, <code>false</code> otherwise
+ *
+ * @since 1.5
+ *
+ * @see #setAutoFormSubmission(boolean)
+ * @see FormSubmitEvent
+ */
+ public boolean isAutoFormSubmission()
+ {
+ return autoFormSubmission;
+ }
+
+ /**
+ * Sets whether or not the editor kit should automatically submit forms.
+ *
+ * @param auto <code>true</code> when the editor kit should handle form
+ * submission, <code>false</code> otherwise
+ *
+ * @since 1.5
+ *
+ * @see #isAutoFormSubmission()
+ */
+ public void setAutoFormSubmission(boolean auto)
+ {
+ autoFormSubmission = auto;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java b/libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java
new file mode 100644
index 000000000..e146965d7
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/HTMLFrameHyperlinkEvent.java
@@ -0,0 +1,130 @@
+/* HTMLFrameHyperlinkEvent.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.net.URL;
+
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.text.Element;
+
+/**
+ * HTMLFrameHyperlinkEvent transfers information about the link that was
+ * activated in a frame.
+ *
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public class HTMLFrameHyperlinkEvent extends HyperlinkEvent
+{
+ private final String target_frame;
+
+ /**
+ * Creates a new hypertext link event.
+ *
+ * @param source The object this link is associated to.
+ * @param type The type of event.
+ * @param url The URL this link pointing too.
+ * @param element The element in the document representing the anchor.
+ * @param frame - the Frame to display the document in.
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL url,
+ Element element, String frame)
+ {
+ super(source, type, url, frame, element);
+ target_frame = frame;
+ }
+
+ /**
+ * Creates a new hypertext link event.
+ *
+ * @param source The object this link is associated to.
+ * @param type The type of event.
+ * @param url The URL this link pointing too.
+ * @param frame - the Frame to display the document in.
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL url,
+ String frame)
+ {
+ super(source, type, url, frame);
+ target_frame = frame;
+ }
+
+ /**
+ * Creates a new hypertext link event.
+ *
+ * @param source The object this link is associated to.
+ * @param type The type of event.
+ * @param url The URL this link pointing too.
+ * @param description The description for this link.
+ * @param element The element in the document representing the anchor.
+ * @param frame - the Frame to display the document in.
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL url,
+ String description, Element element,
+ String frame)
+ {
+ super(source, type, url, description, element);
+ target_frame = frame;
+ }
+
+ /**
+ * Creates a new hypertext link event.
+ *
+ * @param source The object this link is associated to.
+ * @param type The type of event.
+ * @param url The URL this link pointing too.
+ * @param description The description for this link.
+ * @param frame - the Frame to display the document in.
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL url,
+ String description, String frame)
+ {
+ super(source, type, url, description);
+ target_frame = frame;
+ }
+
+ /**
+ * Gets the string, passed as the target frame identifier.
+ *
+ * @return the target for the link.
+ */
+ public String getTarget()
+ {
+ return target_frame;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/HTMLWriter.java b/libjava/classpath/javax/swing/text/html/HTMLWriter.java
new file mode 100644
index 000000000..55d25ccbb
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/HTMLWriter.java
@@ -0,0 +1,1088 @@
+/* HTMLWriter.java --
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text.html;
+
+import gnu.java.lang.CPStringBuilder;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import java.util.Enumeration;
+import java.util.HashSet;
+
+import javax.swing.ComboBoxModel;
+
+import javax.swing.text.AbstractWriter;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
+
+import javax.swing.text.html.HTML;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.Option;
+
+/**
+ * HTMLWriter,
+ * A Writer for HTMLDocuments.
+ *
+ * @author David Fu (fchoong at netbeans.jp)
+ */
+
+public class HTMLWriter
+ extends AbstractWriter
+{
+ /**
+ * We keep a reference of the writer passed by the construct.
+ */
+ private Writer outWriter = null;
+
+ /**
+ * We keep a reference of the HTMLDocument passed by the construct.
+ */
+ private HTMLDocument htmlDoc = null;
+
+ /**
+ * Used to keep track of which embedded has been written out.
+ */
+ private HashSet<HTML.Tag> openEmbeddedTagHashSet = null;
+
+ private String new_line_str = "" + NEWLINE;
+
+ private char[] html_entity_char_arr = {'<', '>', '&', '"'};
+
+ private String[] html_entity_escape_str_arr = {"&lt;", "&gt;", "&amp;",
+ "&quot;"};
+
+ // variables used to output Html Fragment
+ private int doc_pos = -1;
+ private int doc_len = -1;
+ private int doc_offset_remaining = -1;
+ private int doc_len_remaining = -1;
+ private HashSet<Element> htmlFragmentParentHashSet = null;
+ private Element startElem = null;
+ private Element endElem = null;
+ private boolean fg_pass_start_elem = false;
+ private boolean fg_pass_end_elem = false;
+
+ /**
+ * Constructs a HTMLWriter.
+ *
+ * @param writer writer to write output to
+ * @param doc the HTMLDocument to output
+ */
+ public HTMLWriter(Writer writer, HTMLDocument doc)
+ {
+ super(writer, doc);
+ outWriter = writer;
+ htmlDoc = doc;
+ openEmbeddedTagHashSet = new HashSet<HTML.Tag>();
+ } // public HTMLWriter(Writer writer, HTMLDocument doc)
+
+ /**
+ * Constructs a HTMLWriter which outputs a Html Fragment.
+ *
+ * @param writer <code>Writer</code> to write output to
+ * @param doc the <code>javax.swing.text.html.HTMLDocument</code>
+ * to output
+ * @param pos position to start outputing the document
+ * @param len amount to output the document
+ */
+ public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len)
+ {
+ super(writer, doc, pos, len);
+ outWriter = writer;
+ htmlDoc = doc;
+ openEmbeddedTagHashSet = new HashSet<HTML.Tag>();
+
+ doc_pos = pos;
+ doc_offset_remaining = pos;
+ doc_len = len;
+ doc_len_remaining = len;
+ htmlFragmentParentHashSet = new HashSet<Element>();
+ } // public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len)
+
+ /**
+ * Call this method to start outputing HTML.
+ *
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ public void write()
+ throws IOException, BadLocationException
+ {
+ Element rootElem = htmlDoc.getDefaultRootElement();
+
+ if (doc_pos == -1 && doc_len == -1)
+ {
+ // Normal traversal.
+ traverse(rootElem);
+ } // if(doc_pos == -1 && doc_len == -1)
+ else
+ {
+ // Html fragment traversal.
+ if (doc_pos == -1 || doc_len == -1)
+ throw new BadLocationException("Bad Location("
+ + doc_pos + ", " + doc_len + ")", doc_pos);
+
+ startElem = htmlDoc.getCharacterElement(doc_pos);
+
+ int start_offset = startElem.getStartOffset();
+
+ // Positions before start_offset will not be traversed, and thus
+ // will not be counted.
+ if (start_offset > 0)
+ doc_offset_remaining = doc_offset_remaining - start_offset;
+
+ Element tempParentElem = startElem;
+
+ while ((tempParentElem = tempParentElem.getParentElement()) != null)
+ {
+ if (!htmlFragmentParentHashSet.contains(tempParentElem))
+ htmlFragmentParentHashSet.add(tempParentElem);
+ } // while((tempParentElem = tempParentElem.getParentElement())
+ // != null)
+
+ // NOTE: 20061030 - fchoong - the last index should not be included.
+ endElem = htmlDoc.getCharacterElement(doc_pos + doc_len - 1);
+
+ tempParentElem = endElem;
+
+ while ((tempParentElem = tempParentElem.getParentElement()) != null)
+ {
+ if (!htmlFragmentParentHashSet.contains(tempParentElem))
+ htmlFragmentParentHashSet.add(tempParentElem);
+ } // while((tempParentElem = tempParentElem.getParentElement())
+ // != null)
+
+ traverseHtmlFragment(rootElem);
+
+ } // else
+
+ // NOTE: close out remaining open embeded tags.
+ HTML.Tag[] tag_arr =
+ openEmbeddedTagHashSet.toArray(new HTML.Tag[openEmbeddedTagHashSet.size()]);
+
+ for (int i = 0; i < tag_arr.length; i++)
+ {
+ writeRaw("</" + tag_arr[i].toString() + ">");
+ } // for(int i = 0; i < tag_arr.length; i++)
+
+ } // public void write() throws IOException, BadLocationException
+
+ /**
+ * Writes all the attributes in the attrSet, except for attrbutes with
+ * keys of <code>javax.swing.text.html.HTML.Tag</code>,
+ * <code>javax.swing.text.StyleConstants</code> or
+ * <code>javax.swing.text.html.HTML.Attribute.ENDTAG</code>.
+ *
+ * @param attrSet attrSet to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void writeAttributes(AttributeSet attrSet)
+ throws IOException
+ {
+ Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ // HTML.Attribute.ENDTAG is an instance, not a class.
+ if (!((key instanceof HTML.Tag) || (key instanceof StyleConstants)
+ || (key == HTML.Attribute.ENDTAG)))
+ {
+ if (key == HTML.Attribute.SELECTED)
+ writeRaw(" selected");
+ else if (key == HTML.Attribute.CHECKED)
+ writeRaw(" checked");
+ else
+ writeRaw(" " + key + "=\"" + value + "\"");
+ } // if(!((key instanceof HTML.Tag) || (key instanceof
+ // StyleConstants) || (key == HTML.Attribute.ENDTAG)))
+ } // while(attrNameEnum.hasMoreElements())
+
+ } // protected void writeAttributes(AttributeSet attrSet) throws IOException
+
+ /**
+ * Writes out an empty tag. i.e. a tag without any child elements.
+ *
+ * @param paramElem the element to output as an empty tag
+ *
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void emptyTag(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ String elem_name = paramElem.getName();
+ AttributeSet attrSet = paramElem.getAttributes();
+
+ writeRaw("<" + elem_name);
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ if (isBlockTag(attrSet))
+ {
+ writeRaw("</" + elem_name + ">");
+ } // if(isBlockTag(attrSet))
+
+ } // protected void emptyTag(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Determines if it is a block tag or not.
+ *
+ * @param attrSet the attrSet of the element
+ *
+ * @return <code>true</code> if it is a block tag
+ * <code>false</code> if it is a not block tag
+ */
+ protected boolean isBlockTag(AttributeSet attrSet)
+ {
+ return ((HTML.Tag)
+ attrSet.getAttribute(StyleConstants.NameAttribute)).isBlock();
+ } // protected boolean isBlockTag(AttributeSet attrSet)
+
+ /**
+ * Writes out a start tag. Synthesized elements are skipped.
+ *
+ * @param paramElem the element to output as a start tag
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void startTag(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ // NOTE: Sysnthesized elements do no call this method at all.
+ String elem_name = paramElem.getName();
+ AttributeSet attrSet = paramElem.getAttributes();
+
+ indent();
+ writeRaw("<" + elem_name);
+ writeAttributes(attrSet);
+ writeRaw(">");
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+ incrIndent();
+
+ } // protected void startTag(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Writes out the contents of a textarea.
+ *
+ * @param attrSet the attrSet of the element to output as a text area
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void textAreaContent(AttributeSet attrSet)
+ throws IOException, BadLocationException
+ {
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+ indent();
+ writeRaw("<textarea");
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ Document tempDocument =
+ (Document) attrSet.getAttribute(StyleConstants.ModelAttribute);
+
+ writeRaw(tempDocument.getText(0, tempDocument.getLength()));
+ indent();
+ writeRaw("</textarea>");
+
+ } // protected void textAreaContent(AttributeSet attrSet)
+ // throws IOException, BadLocationException
+
+ /**
+ * Writes out text, within the appropriate range if it is specified.
+ *
+ * @param paramElem the element to output as a text
+ * @throws IOException on any I/O exceptions
+ * @throws BadLocationException if a pos is not a valid position in the
+ * html doc element
+ */
+ protected void text(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ int offset = paramElem.getStartOffset();
+ int len = paramElem.getEndOffset() - paramElem.getStartOffset();
+ String txt_value = htmlDoc.getText(offset, len);
+
+ writeContent(txt_value);
+
+ } // protected void text(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Writes out the contents of a select element.
+ *
+ * @param attrSet the attrSet of the element to output as a select box
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void selectContent(AttributeSet attrSet)
+ throws IOException
+ {
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+ indent();
+ writeRaw("<select");
+ writeAttributes(attrSet);
+ writeRaw(">");
+ incrIndent();
+ writeLineSeparator(); // extra formatting to look more like the RI.
+
+ ComboBoxModel comboBoxModel =
+ (ComboBoxModel) attrSet.getAttribute(StyleConstants.ModelAttribute);
+
+ for (int i = 0; i < comboBoxModel.getSize(); i++)
+ {
+ writeOption((Option) comboBoxModel.getElementAt(i));
+ } // for(int i = 0; i < comboBoxModel.getSize(); i++)
+
+ decrIndent();
+ indent();
+ writeRaw("</select>");
+
+ } // protected void selectContent(AttributeSet attrSet) throws IOException
+
+ /**
+ * Writes out the contents of an option element.
+ *
+ * @param option the option object to output as a select option
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void writeOption(Option option)
+ throws IOException
+ {
+ indent();
+ writeRaw("<option");
+ writeAttributes(option.getAttributes());
+ writeRaw(">");
+
+ writeContent(option.getLabel());
+
+ writeRaw("</option>");
+ writeLineSeparator(); // extra formatting to look more like the RI.
+
+ } // protected void writeOption(Option option) throws IOException
+
+ /**
+ * Writes out an end tag.
+ *
+ * @param paramElem the element to output as an end tag
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void endTag(Element paramElem)
+ throws IOException
+ {
+ String elem_name = paramElem.getName();
+
+ //writeLineSeparator(); // Extra formatting to look more like the RI.
+ decrIndent();
+ indent();
+ writeRaw("</" + elem_name + ">");
+ writeLineSeparator(); // Extra formatting to look more like the RI.
+
+ } // protected void endTag(Element paramElem) throws IOException
+
+ /**
+ * Writes out the comment.
+ *
+ * @param paramElem the element to output as a comment
+ */
+ protected void comment(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ AttributeSet attrSet = paramElem.getAttributes();
+
+ String comment_str = (String) attrSet.getAttribute(HTML.Attribute.COMMENT);
+
+ writeRaw("<!--" + comment_str + "-->");
+
+ } // protected void comment(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Determines if element is a synthesized
+ * <code>javax.swing.text.Element</code> or not.
+ *
+ * @param element the element to test
+ *
+ * @return <code>true</code> if it is a synthesized element,
+ * <code>false</code> if it is a not synthesized element
+ */
+ protected boolean synthesizedElement(Element element)
+ {
+ AttributeSet attrSet = element.getAttributes();
+ Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
+
+ if (tagType == HTML.Tag.CONTENT || tagType == HTML.Tag.COMMENT
+ || tagType == HTML.Tag.IMPLIED)
+ return true;
+ else
+ return false;
+ } // protected boolean synthesizedElement(Element element)
+
+ /**
+ * Determines if
+ * <code>javax.swing.text.StyleConstants.NameAttribute</code>
+ * matches tag or not.
+ *
+ * @param attrSet the <code>javax.swing.text.AttributeSet</code> of
+ * element to be matched
+ * @param tag the HTML.Tag to match
+ *
+ * @return <code>true</code> if it matches,
+ * <code>false</code> if it does not match
+ */
+ protected boolean matchNameAttribute(AttributeSet attrSet, HTML.Tag tag)
+ {
+ Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
+
+ if (tagType == tag)
+ return true;
+ else
+ return false;
+ } // protected boolean matchNameAttribute(AttributeSet attrSet,
+ // HTML.Tag tag)
+
+ /**
+ * Writes out an embedded tag. The tags not already in
+ * openEmbededTagHashSet will written out.
+ *
+ * @param attrSet the <code>javax.swing.text.AttributeSet</code> of
+ * the element to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void writeEmbeddedTags(AttributeSet attrSet)
+ throws IOException
+ {
+ Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ if (key instanceof HTML.Tag)
+ {
+ if (!openEmbeddedTagHashSet.contains(key))
+ {
+ writeRaw("<" + key);
+ writeAttributes((AttributeSet) value);
+ writeRaw(">");
+ openEmbeddedTagHashSet.add((HTML.Tag) key);
+ } // if(!openEmbededTagHashSet.contains(key))
+ } // if(key instanceof HTML.Tag)
+ } // while(attrNameEnum.hasMoreElements())
+
+ } // protected void writeEmbeddedTags(AttributeSet attrSet)
+ // throws IOException
+
+ /**
+ * Closes out an unwanted embedded tag. The tags from the
+ * openEmbededTagHashSet not found in attrSet will be written out.
+ *
+ * @param attrSet the AttributeSet of the element to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
+ throws IOException
+ {
+ HTML.Tag[] tag_arr =
+ openEmbeddedTagHashSet.toArray(new HTML.Tag[openEmbeddedTagHashSet.size()]);
+
+ for (int i = 0; i < tag_arr.length; i++)
+ {
+ HTML.Tag key = tag_arr[i];
+
+ if (!attrSet.isDefined(key))
+ {
+ writeRaw("</" + key.toString() + ">");
+ openEmbeddedTagHashSet.remove(key);
+ } // if(!attrSet.isDefined(key))
+ } // for(int i = 0; i < tag_arr.length; i++)
+
+ } // protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
+ // throws IOException
+
+ /**
+ * Writes out a line separator. Overwrites the parent to write out a new
+ * line.
+ *
+ * @throws IOException on any I/O exceptions.
+ */
+ protected void writeLineSeparator()
+ throws IOException
+ {
+ writeRaw(new_line_str);
+ } // protected void writeLineSeparator() throws IOException
+
+ /**
+ * Write to the writer. Character entites such as &lt;, &gt;
+ * are escaped appropriately.
+ *
+ * @param chars char array to write out
+ * @param off offset
+ * @param len length
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ protected void output(char[] chars, int off, int len)
+ throws IOException
+ {
+ CPStringBuilder strBuffer = new CPStringBuilder();
+
+ for (int i = 0; i < chars.length; i++)
+ {
+ if (isCharHtmlEntity(chars[i]))
+ strBuffer.append(escapeCharHtmlEntity(chars[i]));
+ else
+ strBuffer.append(chars[i]);
+ } // for(int i = 0; i < chars.length; i++)
+
+ writeRaw(strBuffer.toString());
+
+ } // protected void output(char[] chars, int off, int len)
+ // throws IOException
+
+ //-------------------------------------------------------------------------
+ // private methods
+
+ /**
+ * The main method used to traverse through the elements.
+ *
+ * @param paramElem element to traverse
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void traverse(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ Element currElem = paramElem;
+
+ AttributeSet attrSet = currElem.getAttributes();
+
+ closeOutUnwantedEmbeddedTags(attrSet);
+
+ // handle the tag
+ if (synthesizedElement(paramElem))
+ {
+ if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ {
+ writeEmbeddedTags(attrSet);
+ text(currElem);
+ } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ {
+ comment(currElem);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverse(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+ } // if(child_elem_count > 0)
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ } // if(synthesizedElement(paramElem))
+ else
+ {
+ // NOTE: 20061030 - fchoong - title is treated specially here.
+ // based on RI behavior.
+ if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ {
+ boolean fg_is_end_tag = false;
+ Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ if (key == HTML.Attribute.ENDTAG && value.equals("true"))
+ fg_is_end_tag = true;
+ } // while(attrNameEnum.hasMoreElements())
+
+ if (fg_is_end_tag)
+ writeRaw("</title>");
+ else
+ {
+ indent();
+ writeRaw("<title>");
+
+ String title_str =
+ (String) htmlDoc.getProperty(HTMLDocument.TitleProperty);
+
+ if (title_str != null)
+ writeContent(title_str);
+
+ } // else
+ } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.PRE))
+ {
+ // We pursue more stringent formating here.
+ attrSet = paramElem.getAttributes();
+
+ indent();
+ writeRaw("<pre");
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ int child_elem_count = currElem.getElementCount();
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverse(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ writeRaw("</pre>");
+
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ {
+ selectContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ {
+ textAreaContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ else
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ startTag(currElem);
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverse(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ endTag(currElem);
+
+ } // if(child_elem_count > 0)
+ else
+ {
+ emptyTag(currElem);
+ } // else
+ } // else
+ } // else
+
+ } // private void traverse(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * The method used to traverse through a html fragment.
+ *
+ * @param paramElem element to traverse
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void traverseHtmlFragment(Element paramElem)
+ throws IOException, BadLocationException
+ {
+ // NOTE: This method is similar to traverse(Element paramElem)
+ Element currElem = paramElem;
+
+ boolean fg_is_fragment_parent_elem = false;
+ boolean fg_is_start_and_end_elem = false;
+
+ if (htmlFragmentParentHashSet.contains(paramElem))
+ fg_is_fragment_parent_elem = true;
+
+ if (paramElem == startElem)
+ fg_pass_start_elem = true;
+
+ if (paramElem == startElem && paramElem == endElem)
+ fg_is_start_and_end_elem = true;
+
+ AttributeSet attrSet = currElem.getAttributes();
+
+ closeOutUnwantedEmbeddedTags(attrSet);
+
+ if (fg_is_fragment_parent_elem || (fg_pass_start_elem
+ && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
+ {
+ // handle the tag
+ if (synthesizedElement(paramElem))
+ {
+ if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ {
+ writeEmbeddedTags(attrSet);
+
+ int content_offset = paramElem.getStartOffset();
+ int content_length = currElem.getEndOffset() - content_offset;
+
+ if (doc_offset_remaining > 0)
+ {
+ if (content_length > doc_offset_remaining)
+ {
+ int split_len = content_length;
+
+ split_len = split_len - doc_offset_remaining;
+
+ if (split_len > doc_len_remaining)
+ split_len = doc_len_remaining;
+
+ // we need to split it.
+ String txt_value = htmlDoc.getText(content_offset
+ + doc_offset_remaining, split_len);
+
+ writeContent(txt_value);
+
+ doc_offset_remaining = 0; // the offset is used up.
+ doc_len_remaining = doc_len_remaining - split_len;
+ } // if(content_length > doc_offset_remaining)
+ else
+ {
+ // doc_offset_remaining is greater than the entire
+ // length of content
+ doc_offset_remaining = doc_offset_remaining
+ - content_length;
+ } // else
+ } // if(doc_offset_remaining > 0)
+ else if (content_length <= doc_len_remaining)
+ {
+ // we can fit the entire content.
+ text(currElem);
+ doc_len_remaining = doc_len_remaining - content_length;
+ } // else if(content_length <= doc_len_remaining)
+ else
+ {
+ // we need to split it.
+ String txt_value = htmlDoc.getText(content_offset,
+ doc_len_remaining);
+
+ writeContent(txt_value);
+
+ doc_len_remaining = 0;
+ } // else
+
+ } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ {
+ comment(currElem);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverseHtmlFragment(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+ } // if(child_elem_count > 0)
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
+ } // if(synthesizedElement(paramElem))
+ else
+ {
+ // NOTE: 20061030 - fchoong - the isLeaf() condition seems to
+ // generate the closest behavior to the RI.
+ if (paramElem.isLeaf())
+ {
+ if (doc_offset_remaining > 0)
+ {
+ doc_offset_remaining--;
+ } // if(doc_offset_remaining > 0)
+ else if (doc_len_remaining > 0)
+ {
+ doc_len_remaining--;
+ } // else if(doc_len_remaining > 0)
+ } // if(paramElem.isLeaf())
+
+ // NOTE: 20061030 - fchoong - title is treated specially here.
+ // based on RI behavior.
+ if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ {
+ boolean fg_is_end_tag = false;
+ Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ if (key == HTML.Attribute.ENDTAG && value.equals("true"))
+ fg_is_end_tag = true;
+ } // while(attrNameEnum.hasMoreElements())
+
+ if (fg_is_end_tag)
+ writeRaw("</title>");
+ else
+ {
+ indent();
+ writeRaw("<title>");
+
+ String title_str =
+ (String) htmlDoc.getProperty(HTMLDocument.TitleProperty);
+
+ if (title_str != null)
+ writeContent(title_str);
+
+ } // else
+ } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.PRE))
+ {
+ // We pursue more stringent formating here.
+ attrSet = paramElem.getAttributes();
+
+ indent();
+ writeRaw("<pre");
+ writeAttributes(attrSet);
+ writeRaw(">");
+
+ int child_elem_count = currElem.getElementCount();
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverseHtmlFragment(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ writeRaw("</pre>");
+
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
+ else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ {
+ selectContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
+ else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ {
+ textAreaContent(attrSet);
+ } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
+ else
+ {
+ int child_elem_count = currElem.getElementCount();
+
+ if (child_elem_count > 0)
+ {
+ startTag(currElem);
+
+ for (int i = 0; i < child_elem_count; i++)
+ {
+ Element childElem = paramElem.getElement(i);
+
+ traverseHtmlFragment(childElem);
+
+ } // for(int i = 0; i < child_elem_count; i++)
+
+ endTag(currElem);
+
+ } // if(child_elem_count > 0)
+ else
+ {
+ emptyTag(currElem);
+ } // else
+ } // else
+ } // else
+
+ } // if(fg_is_fragment_parent_elem || (fg_pass_start_elem
+ // && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
+
+ if (paramElem == endElem)
+ fg_pass_end_elem = true;
+
+ } // private void traverseHtmlFragment(Element paramElem)
+ // throws IOException, BadLocationException
+
+ /**
+ * Write to the writer without any modifications.
+ *
+ * @param param_str the str to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void writeRaw(String param_str)
+ throws IOException
+ {
+ super.output(param_str.toCharArray(), 0, param_str.length());
+ } // private void writeRaw(char[] chars, int off, int len)
+ // throws IOException
+
+ /**
+ * Write to the writer, escaping HTML character entitie where neccessary.
+ *
+ * @param param_str the str to write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void writeContent(String param_str)
+ throws IOException
+ {
+ char[] str_char_arr = param_str.toCharArray();
+
+ if (hasHtmlEntity(param_str))
+ output(str_char_arr, 0, str_char_arr.length);
+ else
+ super.output(str_char_arr, 0, str_char_arr.length);
+
+ } // private void writeContent(String param_str) throws IOException
+
+ /**
+ * Use this for debugging. Writes out all attributes regardless of type.
+ *
+ * @param attrSet the <code>javax.swing.text.AttributeSet</code> to
+ * write out
+ *
+ * @throws IOException on any I/O exceptions
+ */
+ private void writeAllAttributes(AttributeSet attrSet)
+ throws IOException
+ {
+ Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
+
+ while (attrNameEnum.hasMoreElements())
+ {
+ Object key = attrNameEnum.nextElement();
+ Object value = attrSet.getAttribute(key);
+
+ writeRaw(" " + key + "=\"" + value + "\"");
+ writeRaw(" " + key.getClass().toString() + "=\""
+ + value.getClass().toString() + "\"");
+ } // while(attrNameEnum.hasMoreElements())
+
+ } // private void writeAllAttributes(AttributeSet attrSet)
+ // throws IOException
+
+ /**
+ * Tests if the str contains any html entities.
+ *
+ * @param param_str the str to test
+ *
+ * @return <code>true</code> if it has a html entity
+ * <code>false</code> if it does not have a html entity
+ */
+ private boolean hasHtmlEntity(String param_str)
+ {
+ boolean ret_bool = false;
+
+ for (int i = 0; i < html_entity_char_arr.length; i++)
+ {
+ if (param_str.indexOf(html_entity_char_arr[i]) != -1)
+ {
+ ret_bool = true;
+ break;
+ } // if(param_str.indexOf(html_entity_char_arr[i]) != -1)
+ } // for(int i = 0; i < html_entity_char_arr.length; i++)
+
+ return ret_bool;
+ } // private boolean hasHtmlEntity(String param_str)
+
+ /**
+ * Tests if the char is a html entities.
+ *
+ * @param param_char the char to test
+ *
+ * @return <code>true</code> if it is a html entity
+ * <code>false</code> if it is not a html entity.
+ */
+ private boolean isCharHtmlEntity(char param_char)
+ {
+ boolean ret_bool = false;
+
+ for (int i = 0; i < html_entity_char_arr.length; i++)
+ {
+ if (param_char == html_entity_char_arr[i])
+ {
+ ret_bool = true;
+ break;
+ } // if(param_char == html_entity_char_arr[i])
+ } // for(int i = 0; i < html_entity_char_arr.length; i++)
+
+ return ret_bool;
+ } // private boolean hasHtmlEntity(String param_str)
+
+ /**
+ * Escape html entities.
+ *
+ * @param param_char the char to escape
+ *
+ * @return escaped html entity. Original char is returned as a str if is
+ * is not a html entity
+ */
+ private String escapeCharHtmlEntity(char param_char)
+ {
+ String ret_str = "" + param_char;
+
+ for (int i = 0; i < html_entity_char_arr.length; i++)
+ {
+ if (param_char == html_entity_char_arr[i])
+ {
+ ret_str = html_entity_escape_str_arr[i];
+ break;
+ } // if(param_char == html_entity_char_arr[i])
+ } // for(int i = 0; i < html_entity_char_arr.length; i++)
+
+ return ret_str;
+ } // private String escapeCharHtmlEntity(char param_char)
+
+} // public class HTMLWriter extends AbstractWriter
diff --git a/libjava/classpath/javax/swing/text/html/ImageView.java b/libjava/classpath/javax/swing/text/html/ImageView.java
new file mode 100644
index 000000000..bdbe9b3bb
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ImageView.java
@@ -0,0 +1,591 @@
+package javax.swing.text.html;
+
+import gnu.javax.swing.text.html.ImageViewIconFactory;
+import gnu.javax.swing.text.html.css.Length;
+
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Toolkit;
+import java.awt.image.ImageObserver;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.Icon;
+import javax.swing.SwingUtilities;
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+import javax.swing.text.Position.Bias;
+import javax.swing.text.html.HTML.Attribute;
+
+/**
+ * A view, representing a single image, represented by the HTML IMG tag.
+ *
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ */
+public class ImageView extends View
+{
+ /**
+ * Tracks image loading state and performs the necessary layout updates.
+ */
+ class Observer
+ implements ImageObserver
+ {
+
+ public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height)
+ {
+ boolean widthChanged = false;
+ if ((flags & ImageObserver.WIDTH) != 0 && spans[X_AXIS] == null)
+ widthChanged = true;
+ boolean heightChanged = false;
+ if ((flags & ImageObserver.HEIGHT) != 0 && spans[Y_AXIS] == null)
+ heightChanged = true;
+ if (widthChanged || heightChanged)
+ safePreferenceChanged(ImageView.this, widthChanged, heightChanged);
+ boolean ret = (flags & ALLBITS) != 0;
+ return ret;
+ }
+
+ }
+
+ /**
+ * True if the image loads synchronuosly (on demand). By default, the image
+ * loads asynchronuosly.
+ */
+ boolean loadOnDemand;
+
+ /**
+ * The image icon, wrapping the image,
+ */
+ Image image;
+
+ /**
+ * The image state.
+ */
+ byte imageState = MediaTracker.LOADING;
+
+ /**
+ * True when the image needs re-loading, false otherwise.
+ */
+ private boolean reloadImage;
+
+ /**
+ * True when the image properties need re-loading, false otherwise.
+ */
+ private boolean reloadProperties;
+
+ /**
+ * True when the width is set as CSS/HTML attribute.
+ */
+ private boolean haveWidth;
+
+ /**
+ * True when the height is set as CSS/HTML attribute.
+ */
+ private boolean haveHeight;
+
+ /**
+ * True when the image is currently loading.
+ */
+ private boolean loading;
+
+ /**
+ * The current width of the image.
+ */
+ private int width;
+
+ /**
+ * The current height of the image.
+ */
+ private int height;
+
+ /**
+ * Our ImageObserver for tracking the loading state.
+ */
+ private ImageObserver observer;
+
+ /**
+ * The CSS width and height.
+ *
+ * Package private to avoid synthetic accessor methods.
+ */
+ Length[] spans;
+
+ /**
+ * The cached attributes.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * Creates the image view that represents the given element.
+ *
+ * @param element the element, represented by this image view.
+ */
+ public ImageView(Element element)
+ {
+ super(element);
+ spans = new Length[2];
+ observer = new Observer();
+ reloadProperties = true;
+ reloadImage = true;
+ loadOnDemand = false;
+ }
+
+ /**
+ * Load or reload the image. This method initiates the image reloading. After
+ * the image is ready, the repaint event will be scheduled. The current image,
+ * if it already exists, will be discarded.
+ */
+ private void reloadImage()
+ {
+ loading = true;
+ reloadImage = false;
+ haveWidth = false;
+ haveHeight = false;
+ image = null;
+ width = 0;
+ height = 0;
+ try
+ {
+ loadImage();
+ updateSize();
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ /**
+ * Get the image alignment. This method works handling standart alignment
+ * attributes in the HTML IMG tag (align = top bottom middle left right).
+ * Depending from the parameter, either horizontal or vertical alingment
+ * information is returned.
+ *
+ * @param axis -
+ * either X_AXIS or Y_AXIS
+ */
+ public float getAlignment(int axis)
+ {
+ AttributeSet attrs = getAttributes();
+ Object al = attrs.getAttribute(Attribute.ALIGN);
+
+ // Default is top left aligned.
+ if (al == null)
+ return 0.0f;
+
+ String align = al.toString();
+
+ if (axis == View.X_AXIS)
+ {
+ if (align.equals("middle"))
+ return 0.5f;
+ else if (align.equals("left"))
+ return 0.0f;
+ else if (align.equals("right"))
+ return 1.0f;
+ else
+ return 0.0f;
+ }
+ else if (axis == View.Y_AXIS)
+ {
+ if (align.equals("middle"))
+ return 0.5f;
+ else if (align.equals("top"))
+ return 0.0f;
+ else if (align.equals("bottom"))
+ return 1.0f;
+ else
+ return 0.0f;
+ }
+ else
+ throw new IllegalArgumentException("axis " + axis);
+ }
+
+ /**
+ * Get the text that should be shown as the image replacement and also as the
+ * image tool tip text. The method returns the value of the attribute, having
+ * the name {@link Attribute#ALT}. If there is no such attribute, the image
+ * name from the url is returned. If the URL is not available, the empty
+ * string is returned.
+ */
+ public String getAltText()
+ {
+ Object rt = getAttributes().getAttribute(Attribute.ALT);
+ if (rt != null)
+ return rt.toString();
+ else
+ {
+ URL u = getImageURL();
+ if (u == null)
+ return "";
+ else
+ return u.getFile();
+ }
+ }
+
+ /**
+ * Returns the combination of the document and the style sheet attributes.
+ */
+ public AttributeSet getAttributes()
+ {
+ if (attributes == null)
+ attributes = getStyleSheet().getViewAttributes(this);
+ return attributes;
+ }
+
+ /**
+ * Get the image to render. May return null if the image is not yet loaded.
+ */
+ public Image getImage()
+ {
+ updateState();
+ return image;
+ }
+
+ /**
+ * Get the URL location of the image to render. If this method returns null,
+ * the "no image" icon is rendered instead. By defaul, url must be present as
+ * the "src" property of the IMG tag. If it is missing, null is returned and
+ * the "no image" icon is rendered.
+ *
+ * @return the URL location of the image to render.
+ */
+ public URL getImageURL()
+ {
+ Element el = getElement();
+ String src = (String) el.getAttributes().getAttribute(Attribute.SRC);
+ URL url = null;
+ if (src != null)
+ {
+ URL base = ((HTMLDocument) getDocument()).getBase();
+ try
+ {
+ url = new URL(base, src);
+ }
+ catch (MalformedURLException ex)
+ {
+ // Return null.
+ }
+ }
+ return url;
+ }
+
+ /**
+ * Get the icon that should be displayed while the image is loading and hence
+ * not yet available.
+ *
+ * @return an icon, showing a non broken sheet of paper with image.
+ */
+ public Icon getLoadingImageIcon()
+ {
+ return ImageViewIconFactory.getLoadingImageIcon();
+ }
+
+ /**
+ * Get the image loading strategy.
+ *
+ * @return false (default) if the image is loaded when the view is
+ * constructed, true if the image is only loaded on demand when
+ * rendering.
+ */
+ public boolean getLoadsSynchronously()
+ {
+ return loadOnDemand;
+ }
+
+ /**
+ * Get the icon that should be displayed when the image is not available.
+ *
+ * @return an icon, showing a broken sheet of paper with image.
+ */
+ public Icon getNoImageIcon()
+ {
+ return ImageViewIconFactory.getNoImageIcon();
+ }
+
+ /**
+ * Get the preferred span of the image along the axis. The image size is first
+ * requested to the attributes {@link Attribute#WIDTH} and
+ * {@link Attribute#HEIGHT}. If they are missing, and the image is already
+ * loaded, the image size is returned. If there are no attributes, and the
+ * image is not loaded, zero is returned.
+ *
+ * @param axis -
+ * either X_AXIS or Y_AXIS
+ * @return either width of height of the image, depending on the axis.
+ */
+ public float getPreferredSpan(int axis)
+ {
+ Image image = getImage();
+
+ if (axis == View.X_AXIS)
+ {
+ if (spans[axis] != null)
+ return spans[axis].getValue();
+ else if (image != null)
+ return image.getWidth(getContainer());
+ else
+ return getNoImageIcon().getIconWidth();
+ }
+ else if (axis == View.Y_AXIS)
+ {
+ if (spans[axis] != null)
+ return spans[axis].getValue();
+ else if (image != null)
+ return image.getHeight(getContainer());
+ else
+ return getNoImageIcon().getIconHeight();
+ }
+ else
+ throw new IllegalArgumentException("axis " + axis);
+ }
+
+ /**
+ * Get the associated style sheet from the document.
+ *
+ * @return the associated style sheet.
+ */
+ protected StyleSheet getStyleSheet()
+ {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Get the tool tip text. This is overridden to return the value of the
+ * {@link #getAltText()}. The parameters are ignored.
+ *
+ * @return that is returned by getAltText().
+ */
+ public String getToolTipText(float x, float y, Shape shape)
+ {
+ return getAltText();
+ }
+
+ /**
+ * Paints the image or one of the two image state icons. The image is resized
+ * to the shape bounds. If there is no image available, the alternative text
+ * is displayed besides the image state icon.
+ *
+ * @param g
+ * the Graphics, used for painting.
+ * @param bounds
+ * the bounds of the region where the image or replacing icon must be
+ * painted.
+ */
+ public void paint(Graphics g, Shape bounds)
+ {
+ updateState();
+ Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds
+ : bounds.getBounds();
+ Image image = getImage();
+ if (image != null)
+ {
+ g.drawImage(image, r.x, r.y, r.width, r.height, observer);
+ }
+ else
+ {
+ Icon icon = getNoImageIcon();
+ if (icon != null)
+ icon.paintIcon(getContainer(), g, r.x, r.y);
+ }
+ }
+
+ /**
+ * Set if the image should be loaded only when needed (synchronuosly). By
+ * default, the image loads asynchronuosly. If the image is not yet ready, the
+ * icon, returned by the {@link #getLoadingImageIcon()}, is displayed.
+ */
+ public void setLoadsSynchronously(boolean load_on_demand)
+ {
+ loadOnDemand = load_on_demand;
+ }
+
+ /**
+ * Update all cached properties from the attribute set, returned by the
+ * {@link #getAttributes}.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ AttributeSet atts = getAttributes();
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ spans[X_AXIS] = (Length) atts.getAttribute(CSS.Attribute.WIDTH);
+ if (spans[X_AXIS] != null)
+ {
+ spans[X_AXIS].setFontBases(emBase, exBase);
+ }
+ spans[Y_AXIS] = (Length) atts.getAttribute(CSS.Attribute.HEIGHT);
+ if (spans[Y_AXIS] != null)
+ {
+ spans[Y_AXIS].setFontBases(emBase, exBase);
+ }
+ }
+
+ /**
+ * Maps the picture co-ordinates into the image position in the model. As the
+ * image is not divideable, this is currently implemented always to return the
+ * start offset.
+ */
+ public int viewToModel(float x, float y, Shape shape, Bias[] bias)
+ {
+ return getStartOffset();
+ }
+
+ /**
+ * This is currently implemented always to return the area of the image view,
+ * as the image is not divideable by character positions.
+ *
+ * @param pos character position
+ * @param area of the image view
+ * @param bias bias
+ *
+ * @return the shape, where the given character position should be mapped.
+ */
+ public Shape modelToView(int pos, Shape area, Bias bias)
+ throws BadLocationException
+ {
+ return area;
+ }
+
+ /**
+ * Starts loading the image asynchronuosly. If the image must be loaded
+ * synchronuosly instead, the {@link #setLoadsSynchronously} must be
+ * called before calling this method. The passed parameters are not used.
+ */
+ public void setSize(float width, float height)
+ {
+ updateState();
+ // TODO: Implement this when we have an alt view for the alt=... attribute.
+ }
+
+ /**
+ * This makes sure that the image and properties have been loaded.
+ */
+ private void updateState()
+ {
+ if (reloadImage)
+ reloadImage();
+ if (reloadProperties)
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Actually loads the image.
+ */
+ private void loadImage()
+ {
+ URL src = getImageURL();
+ Image newImage = null;
+ if (src != null)
+ {
+ // Call getImage(URL) to allow the toolkit caching of that image URL.
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ newImage = tk.getImage(src);
+ tk.prepareImage(newImage, -1, -1, observer);
+ if (newImage != null && getLoadsSynchronously())
+ {
+ // Load image synchronously.
+ MediaTracker tracker = new MediaTracker(getContainer());
+ tracker.addImage(newImage, 0);
+ try
+ {
+ tracker.waitForID(0);
+ }
+ catch (InterruptedException ex)
+ {
+ Thread.interrupted();
+ }
+
+ }
+ }
+ image = newImage;
+ }
+
+ /**
+ * Updates the size parameters of the image.
+ */
+ private void updateSize()
+ {
+ int newW = 0;
+ int newH = 0;
+ Image newIm = getImage();
+ if (newIm != null)
+ {
+ // Fetch width.
+ Length l = spans[X_AXIS];
+ if (l != null)
+ {
+ newW = (int) l.getValue();
+ haveWidth = true;
+ }
+ else
+ {
+ newW = newIm.getWidth(observer);
+ }
+ // Fetch height.
+ l = spans[Y_AXIS];
+ if (l != null)
+ {
+ newH = (int) l.getValue();
+ haveHeight = true;
+ }
+ else
+ {
+ newW = newIm.getWidth(observer);
+ }
+ // Go and trigger loading.
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if (haveWidth || haveHeight)
+ tk.prepareImage(newIm, width, height, observer);
+ else
+ tk.prepareImage(newIm, -1, -1, observer);
+ }
+ }
+
+ /**
+ * Calls preferenceChanged from the event dispatch thread and within
+ * a read lock to protect us from threading issues.
+ *
+ * @param v the view
+ * @param width true when the width changed
+ * @param height true when the height changed
+ */
+ void safePreferenceChanged(final View v, final boolean width,
+ final boolean height)
+ {
+ if (SwingUtilities.isEventDispatchThread())
+ {
+ Document doc = getDocument();
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ preferenceChanged(v, width, height);
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ }
+ else
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ safePreferenceChanged(v, width, height);
+ }
+ });
+ }
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/InlineView.java b/libjava/classpath/javax/swing/text/html/InlineView.java
new file mode 100644
index 000000000..5376c6b9d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/InlineView.java
@@ -0,0 +1,307 @@
+/* InlineView.java -- Renders HTML content
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.FontMetrics;
+import java.awt.Shape;
+import java.text.BreakIterator;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.LabelView;
+import javax.swing.text.Segment;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+
+/**
+ * Renders HTML content (identified by {@link HTML.Tag#CONTENT}). This is
+ * basically a {@link LabelView} that is adjusted to understand styles defined
+ * by stylesheets.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class InlineView
+ extends LabelView
+{
+
+ /**
+ * The attributes used by this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The span of the longest word in this view.
+ *
+ * @see #getLongestWord()
+ */
+ private float longestWord;
+
+ /**
+ * Indicates if we may wrap or not.
+ */
+ private boolean nowrap;
+
+ /**
+ * Creates a new <code>InlineView</code> that renders the specified element.
+ *
+ * @param element the element for this view
+ */
+ public InlineView(Element element)
+ {
+ super(element);
+ }
+
+ /**
+ * Receives notification that something was inserted into the document in
+ * a location that this view is responsible for.
+ *
+ * @param e the document event
+ * @param a the current allocation of this view
+ * @param f the view factory for creating new views
+ *
+ * @since 1.5
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ // FIXME: What to do here?
+ super.insertUpdate(e, a, f);
+ }
+
+ /**
+ * Receives notification that something was removed from the document in
+ * a location that this view is responsible for.
+ *
+ * @param e the document event
+ * @param a the current allocation of this view
+ * @param f the view factory for creating new views
+ *
+ * @since 1.5
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ // FIXME: What to do here?
+ super.removeUpdate(e, a, f);
+ }
+
+ /**
+ * Receives notification that attributes have changed in the document in
+ * a location that this view is responsible for. This calls
+ * {@link #setPropertiesFromAttributes}.
+ *
+ * @param e the document event
+ * @param a the current allocation of this view
+ * @param f the view factory for creating new views
+ *
+ * @since 1.5
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.changedUpdate(e, a, f);
+ StyleSheet ss = getStyleSheet();
+ attributes = ss.getViewAttributes(this);
+ preferenceChanged(null, true, true);
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Returns the attributes that are used for rendering. This is implemented
+ * to multiplex the attributes specified in the model with a stylesheet.
+ *
+ * @return the attributes that are used for rendering
+ */
+ public AttributeSet getAttributes()
+ {
+ if (attributes == null)
+ {
+ StyleSheet ss = getStyleSheet();
+ attributes = ss.getViewAttributes(this);
+ }
+ return attributes;
+ }
+
+
+ public int getBreakWeight(int axis, float pos, float len)
+ {
+ int weight;
+ if (nowrap)
+ weight = BadBreakWeight;
+ else
+ weight = super.getBreakWeight(axis, pos, len);
+ return weight;
+ }
+
+ public View breakView(int axis, int offset, float pos, float len)
+ {
+ // FIXME: Implement this.
+ return super.breakView(axis, offset, pos, len);
+ }
+
+ /**
+ * Loads the character style properties from the stylesheet.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ super.setPropertiesFromAttributes();
+ AttributeSet atts = getAttributes();
+ Object o = atts.getAttribute(CSS.Attribute.TEXT_DECORATION);
+
+ // Check for underline.
+ boolean b = false;
+ if (o != null && o.toString().contains("underline"))
+ b = true;
+ setUnderline(b);
+
+ // Check for line-through.
+ b = false;
+ if (o != null && o.toString().contains("line-through"))
+ b = true;
+ setStrikeThrough(b);
+
+ // Check for vertical alignment (subscript/superscript).
+ o = atts.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
+
+ // Subscript.
+ b = false;
+ if (o != null && o.toString().contains("sub"))
+ b = true;
+ setSubscript(b);
+
+ // Superscript.
+ b = false;
+ if (o != null && o.toString().contains("sup"))
+ b = true;
+ setSuperscript(b);
+
+ // Fetch nowrap setting.
+ o = atts.getAttribute(CSS.Attribute.WHITE_SPACE);
+ if (o != null && o.equals("nowrap"))
+ nowrap = true;
+ else
+ nowrap = false;
+ }
+
+ /**
+ * Returns the stylesheet used by this view. This returns the stylesheet
+ * of the <code>HTMLDocument</code> that is rendered by this view.
+ *
+ * @return the stylesheet used by this view
+ */
+ protected StyleSheet getStyleSheet()
+ {
+ Document doc = getDocument();
+ StyleSheet styleSheet = null;
+ if (doc instanceof HTMLDocument)
+ styleSheet = ((HTMLDocument) doc).getStyleSheet();
+ return styleSheet;
+ }
+
+ /**
+ * Returns the minimum span for the specified axis. This returns the
+ * width of the longest word for the X axis and the super behaviour for
+ * the Y axis. This is a slight deviation from the reference implementation.
+ * IMO this should improve rendering behaviour so that an InlineView never
+ * gets smaller than the longest word in it.
+ */
+ public float getMinimumSpan(int axis)
+ {
+ float min = super.getMinimumSpan(axis);
+ if (axis == X_AXIS)
+ min = Math.max(getLongestWord(), min);
+ return min;
+ }
+
+ /**
+ * Returns the span of the longest word in this view.
+ *
+ * @return the span of the longest word in this view
+ */
+ private float getLongestWord()
+ {
+ if (longestWord == -1)
+ longestWord = calculateLongestWord();
+ return longestWord;
+ }
+
+ /**
+ * Calculates the span of the longest word in this view.
+ *
+ * @return the span of the longest word in this view
+ */
+ private float calculateLongestWord()
+ {
+ float span = 0;
+ try
+ {
+ Document doc = getDocument();
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ Segment s = new Segment();
+ doc.getText(p0, p1 - p0, s);
+ BreakIterator iter = BreakIterator.getWordInstance();
+ iter.setText(s);
+ int wordStart = p0;
+ int wordEnd = p0;
+ int start = iter.first();
+ for (int end = iter.next(); end != BreakIterator.DONE;
+ start = end, end = iter.next())
+ {
+ if ((end - start) > (wordEnd - wordStart))
+ {
+ wordStart = start;
+ wordEnd = end;
+ }
+ }
+ if (wordEnd - wordStart > 0)
+ {
+ FontMetrics fm = getFontMetrics();
+ int offset = s.offset + wordStart - s.getBeginIndex();
+ span = fm.charsWidth(s.array, offset, wordEnd - wordStart);
+ }
+ }
+ catch (BadLocationException ex)
+ {
+ // Return 0.
+ }
+ return span;
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/ListView.java b/libjava/classpath/javax/swing/text/html/ListView.java
new file mode 100644
index 000000000..f05051a4d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ListView.java
@@ -0,0 +1,128 @@
+/* ListView.java --
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text.html;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.text.Element;
+
+/**
+ * A View to render HTML lists, like the <code>&lt;ul&gt;</code> and
+ * <code>&lt;ol&gt;</code> tags.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class ListView
+ extends BlockView
+{
+
+ /**
+ * The painter used to paint the list items.
+ */
+ private StyleSheet.ListPainter painter;
+
+ /**
+ * Creates a new <code>ListView</code> for the specified element.
+ *
+ * @param el the element to create a list view for
+ */
+ public ListView(Element el)
+ {
+ super(el, Y_AXIS);
+ }
+
+ /**
+ * Returns the alignment of this view along the specified axis.
+ *
+ * This returns <code>0.5</code> unconditionally.
+ *
+ * @param axis the axis
+ *
+ * @return the alignment of this view along the specified axis
+ */
+ public float getAlignment(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException("Illegal axis parameter: " + axis);
+
+ return 0.5F;
+ }
+
+ /**
+ * Paints the <code>ListView</code>.
+ *
+ * @param g the graphics context to use for painting
+ * @param allocation the allocation given to this view
+ */
+ public void paint(Graphics g, Shape allocation)
+ {
+ super.paint(g, allocation);
+ }
+
+ /**
+ * Paints the child with the specified index into the specified allocation.
+ *
+ * This implementation forwards to the list painter fetched from the
+ * {@link StyleSheet} and then calls
+ * <code>super.paintChild(g, a, index)</code>.
+ *
+ * @param g the graphics context to use
+ * @param a the allocation for the child
+ * @param index the child index
+ */
+ protected void paintChild(Graphics g, Rectangle a, int index)
+ {
+ painter.paint(g, a.x, a.y, a.width, a.height, this, index);
+ super.paintChild(g, a, index);
+ }
+
+ /**
+ * Fetches this view's properties from the style attributes of this view's
+ * element.
+ *
+ * This forwards to super and then fetches a {@link StyleSheet.ListPainter}
+ * from the stylesheet suitable for painting the list.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ super.setPropertiesFromAttributes();
+ painter = getStyleSheet().getListPainter(getAttributes());
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java b/libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java
new file mode 100644
index 000000000..3dddfc3de
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/MinimalHTMLWriter.java
@@ -0,0 +1,453 @@
+/* MinimalHTMLWriter.java --
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.swing.text.html;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.AbstractWriter;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Element;
+import javax.swing.text.ElementIterator;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.Style;
+import javax.swing.text.StyledDocument;
+import java.io.Writer;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Enumeration;
+import java.awt.Color;
+
+/**
+ * MinimalHTMLWriter,
+ * A minimal AbstractWriter implementation for HTML.
+ *
+ * @author Sven de Marothy
+ */
+public class MinimalHTMLWriter extends AbstractWriter
+{
+ private StyledDocument doc;
+ private Deque<String> tagStack;
+ private boolean inFontTag = false;
+
+ /**
+ * Constructs a MinimalHTMLWriter.
+ * @param w - a Writer, for output.
+ * @param doc - the document
+ */
+ public MinimalHTMLWriter(Writer w, StyledDocument doc)
+ {
+ super(w, doc);
+ this.doc = doc;
+ tagStack = new ArrayDeque<String>();
+ }
+
+ /**
+ * Constructs a MinimalHTMLWriter.
+ * @param w - a Writer, for output.
+ * @param doc - the document
+ * @param pos - start position
+ * @param len - length
+ */
+ public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len)
+ {
+ super(w, doc, pos, len);
+ this.doc = doc;
+ tagStack = new ArrayDeque<String>();
+ }
+
+ /**
+ * Starts a span tag.
+ */
+ protected void startFontTag(String style) throws IOException
+ {
+ if( inFontTag() )
+ endOpenTags();
+ writeStartTag("<span style=\""+style+"\">");
+ inFontTag = true;
+ }
+
+ /**
+ * Returns whether the writer is within two span tags.
+ */
+ protected boolean inFontTag()
+ {
+ return inFontTag;
+ }
+
+ /**
+ * Ends a span tag.
+ */
+ protected void endFontTag() throws IOException
+ {
+ writeEndTag("</span>");
+ inFontTag = false;
+ }
+
+ /**
+ * Write the entire HTML document.
+ */
+ public synchronized void write() throws IOException, BadLocationException
+ {
+ writeStartTag("<html>");
+ writeHeader();
+ writeBody();
+ writeEndTag("</html>");
+ }
+
+ /**
+ * Write a start tag and increment the indent.
+ */
+ protected void writeStartTag(String tag) throws IOException
+ {
+ indent();
+ write(tag+NEWLINE);
+ incrIndent();
+ }
+
+ /**
+ * Write an ending tag and decrement the indent.
+ */
+ protected void writeEndTag(String endTag) throws IOException
+ {
+ decrIndent();
+ indent();
+ write(endTag+NEWLINE);
+ }
+
+ /**
+ * Write the HTML header.
+ */
+ protected void writeHeader() throws IOException
+ {
+ writeStartTag("<head>");
+ writeStartTag("<style>");
+ writeStartTag("<!--");
+ writeStyles();
+ writeEndTag("-->");
+ writeEndTag("</style>");
+ writeEndTag("</head>");
+ }
+
+ /**
+ * Write a paragraph start tag.
+ */
+ protected void writeStartParagraph(Element elem) throws IOException
+ {
+ indent();
+ write("<p class=default>"+NEWLINE); // FIXME: Class value = ?
+ incrIndent();
+ }
+
+ /**
+ * Write a paragraph end tag, closes any other open tags.
+ */
+ protected void writeEndParagraph() throws IOException
+ {
+ endOpenTags();
+ writeEndTag("</p>");
+ }
+
+ /**
+ * Writes the body of the HTML document.
+ */
+ protected void writeBody() throws IOException, BadLocationException
+ {
+ writeStartTag("<body>");
+
+ ElementIterator ei = getElementIterator();
+ Element e = ei.first();
+ boolean inParagraph = false;
+ do
+ {
+ if( e.isLeaf() )
+ {
+ boolean hasNL = (getText(e).indexOf(NEWLINE) != -1);
+ if( !inParagraph && hasText( e ) )
+ {
+ writeStartParagraph(e);
+ inParagraph = true;
+ }
+
+ if( hasText( e ) )
+ writeContent(e, true);
+
+ if( hasNL && inParagraph )
+ {
+ writeEndParagraph();
+ inParagraph = false;
+ }
+ else
+ endOpenTags();
+ }
+ }
+ while((e = ei.next()) != null);
+
+ writeEndTag("</body>");
+ }
+
+ protected void text(Element elem) throws IOException, BadLocationException
+ {
+ write( getText(elem).trim() );
+ }
+
+ /**
+ * Write bold, indent and underline tags.
+ */
+ protected void writeHTMLTags(AttributeSet attr) throws IOException
+ {
+ if(attr.getAttribute(StyleConstants.Bold) != null)
+ if(((Boolean)attr.getAttribute(StyleConstants.Bold)).booleanValue())
+ {
+ write("<b>");
+ tagStack.push("</b>");
+ }
+ if(attr.getAttribute(StyleConstants.Italic) != null)
+ if(((Boolean)attr.getAttribute(StyleConstants.Italic)).booleanValue())
+ {
+ write("<i>");
+ tagStack.push("</i>");
+ }
+ if(attr.getAttribute(StyleConstants.Underline) != null)
+ if(((Boolean)attr.getAttribute(StyleConstants.Underline)).booleanValue())
+ {
+ write("<u>");
+ tagStack.push("</u>");
+ }
+ }
+
+ /**
+ * Returns whether the element contains text or not.
+ */
+ protected boolean isText(Element elem)
+ {
+ return (elem.getEndOffset() != elem.getStartOffset());
+ }
+
+ /**
+ * Writes the content of an element.
+ */
+ protected void writeContent(Element elem, boolean needsIndenting)
+ throws IOException, BadLocationException
+ {
+ writeNonHTMLAttributes(elem.getAttributes());
+ if(needsIndenting)
+ indent();
+ writeHTMLTags(elem.getAttributes());
+ if( isText(elem) )
+ text(elem);
+ else
+ writeLeaf(elem);
+
+ endOpenTags();
+ }
+
+ /**
+ * Writes a non-text leaf element.
+ */
+ protected void writeLeaf(Element e) throws IOException
+ {
+ // NOTE: Haven't tested if this is correct.
+ if(e.getName().equals(StyleConstants.IconElementName))
+ writeImage(e);
+ else
+ writeComponent(e);
+ }
+
+ /**
+ * Write the HTML attributes which do not have tag equivalents,
+ * e.g. attributes other than bold/italic/underlined.
+ */
+ protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException
+ {
+ String style = "";
+
+ // Alignment? Background?
+
+ if( StyleConstants.getForeground(attr) != null )
+ style = style + "color: " +
+ getColor(StyleConstants.getForeground(attr)) + "; ";
+
+ style = style + "font-size: "+StyleConstants.getFontSize(attr)+"pt; ";
+ style = style + "font-family: "+StyleConstants.getFontFamily(attr);
+
+ startFontTag(style);
+ }
+
+ /**
+ * Write the styles used.
+ */
+ protected void writeStyles() throws IOException
+ {
+ if(doc instanceof DefaultStyledDocument)
+ {
+ Enumeration<?> styles = ((DefaultStyledDocument)doc).getStyleNames();
+ while(styles.hasMoreElements())
+ writeStyle(doc.getStyle((String)styles.nextElement()));
+ }
+ else
+ { // What else to do here?
+ Style s = doc.getStyle("default");
+ if(s != null)
+ writeStyle( s );
+ }
+ }
+
+ /**
+ * Write a set of attributes.
+ */
+ protected void writeAttributes(AttributeSet attr) throws IOException
+ {
+ Enumeration<?> attribs = attr.getAttributeNames();
+ while(attribs.hasMoreElements())
+ {
+ Object attribName = attribs.nextElement();
+ String name = attribName.toString();
+ String output = getAttribute(name, attr.getAttribute(attribName));
+ if( output != null )
+ {
+ indent();
+ write( output + NEWLINE );
+ }
+ }
+ }
+
+ /**
+ * Deliberately unimplemented, handles component elements.
+ */
+ protected void writeComponent(Element elem) throws IOException
+ {
+ }
+
+ /**
+ * Deliberately unimplemented.
+ * Writes StyleConstants.IconElementName elements.
+ */
+ protected void writeImage(Element elem) throws IOException
+ {
+ }
+
+ // -------------------- Private methods. --------------------------------
+
+ /**
+ * Write a single style attribute
+ */
+ private String getAttribute(String name, Object a) throws IOException
+ {
+ if(name.equals("foreground"))
+ return "foreground:"+getColor((Color)a)+";";
+ if(name.equals("background"))
+ return "background:"+getColor((Color)a)+";";
+ if(name.equals("italic"))
+ return "italic:"+(((Boolean)a).booleanValue() ? "italic;" : ";");
+ if(name.equals("bold"))
+ return "bold:"+(((Boolean)a).booleanValue() ? "bold;" : "normal;");
+ if(name.equals("family"))
+ return "family:" + a + ";";
+ if(name.equals("size"))
+ {
+ int size = ((Integer)a).intValue();
+ int htmlSize;
+ if( size > 24 )
+ htmlSize = 7;
+ else if( size > 18 )
+ htmlSize = 6;
+ else if( size > 14 )
+ htmlSize = 5;
+ else if( size > 12 )
+ htmlSize = 4;
+ else if( size > 10 )
+ htmlSize = 3;
+ else if( size > 8 )
+ htmlSize = 2;
+ else
+ htmlSize = 1;
+
+ return "size:" + htmlSize + ";";
+ }
+
+ return null;
+ }
+
+ /**
+ * Stupid that Color doesn't have a method for this.
+ */
+ private String getColor(Color c)
+ {
+ String r = "00" + Integer.toHexString(c.getRed());
+ r = r.substring(r.length() - 2);
+ String g = "00" + Integer.toHexString(c.getGreen());
+ g = g.substring(g.length() - 2);
+ String b = "00" + Integer.toHexString(c.getBlue());
+ b = b.substring(b.length() - 2);
+ return "#" + r + g + b;
+ }
+
+ /**
+ * Empty the stack of open tags
+ */
+ private void endOpenTags() throws IOException
+ {
+ while(tagStack.size() > 0)
+ write(tagStack.pop());
+
+ if( inFontTag() )
+ {
+ write(""+NEWLINE);
+ endFontTag();
+ }
+ }
+
+ /**
+ * Output a single style
+ */
+ private void writeStyle(Style s) throws IOException
+ {
+ if( s == null )
+ return;
+
+ writeStartTag("p."+s.getName()+" {");
+ writeAttributes(s);
+ writeEndTag("}");
+ }
+
+ private boolean hasText(Element e) throws BadLocationException
+ {
+ return (getText(e).trim().length() > 0);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java
new file mode 100644
index 000000000..e7fa9f373
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/MultiAttributeSet.java
@@ -0,0 +1,213 @@
+/* MultiAttributeSet.java -- Multiplexes between a set of AttributeSets
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+
+/**
+ * An AttributeSet impl that multiplexes between a set of other AttributeSets.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+class MultiAttributeSet
+ implements AttributeSet
+{
+
+ /**
+ * The Enumeration for the multiplexed names.
+ */
+ private class MultiNameEnumeration
+ implements Enumeration<Object>
+ {
+ /**
+ * The index of the current AttributeSet.
+ */
+ private int index;
+
+ /**
+ * The names Enumeration of the current AttributeSet.
+ */
+ private Enumeration<?> current;
+
+ /**
+ * Creates a new instance.
+ */
+ MultiNameEnumeration()
+ {
+ index = 0;
+ current = multi[0].getAttributeNames();
+ }
+
+ public boolean hasMoreElements()
+ {
+ return current.hasMoreElements() || index < multi.length - 1;
+ }
+
+ public Object nextElement()
+ {
+ if (! current.hasMoreElements())
+ {
+ if (index < multi.length - 1)
+ {
+ index++;
+ current = multi[index].getAttributeNames();
+ }
+ else
+ throw new NoSuchElementException();
+ }
+ return current.nextElement();
+ }
+
+ }
+
+ /**
+ * The AttributeSets to multiplex.
+ */
+ AttributeSet[] multi;
+
+ /**
+ * Provided for subclasses that need to initialize via {@link #init}.
+ */
+ MultiAttributeSet()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param m the AttributeSets to multiplex
+ */
+ MultiAttributeSet(AttributeSet[] m)
+ {
+ init(m);
+ }
+
+ /**
+ * Provided for subclasses to initialize the attribute set.
+ *
+ * @param m the attributes to multiplex
+ */
+ void init(AttributeSet[] m)
+ {
+ multi = m;
+ }
+
+ public boolean containsAttribute(Object name, Object value)
+ {
+ boolean ret = false;
+ for (int i = 0; i < multi.length && ret == false; i++)
+ {
+ if (multi[i].containsAttribute(name, value))
+ ret = true;
+ }
+ return ret;
+ }
+
+ public boolean containsAttributes(AttributeSet attributes)
+ {
+ boolean ret = true;
+ Enumeration<?> e = attributes.getAttributeNames();
+ while (ret && e.hasMoreElements())
+ {
+ Object key = e.nextElement();
+ ret = attributes.getAttribute(key).equals(getAttribute(key));
+ }
+ return ret;
+ }
+
+ public AttributeSet copyAttributes()
+ {
+ SimpleAttributeSet copy = new SimpleAttributeSet();
+ for (int i = 0; i < multi.length; i++)
+ {
+ copy.addAttributes(multi[i]);
+ }
+ return copy;
+ }
+
+ public Object getAttribute(Object key)
+ {
+ Object ret = null;
+ for (int i = 0; i < multi.length && ret == null; i++)
+ {
+ ret = multi[i].getAttribute(key);
+ }
+ return ret;
+ }
+
+ public int getAttributeCount()
+ {
+ int n = 0;
+ for (int i = 0; i < multi.length; i++)
+ {
+ n += multi[i].getAttributeCount();
+ }
+ return n;
+ }
+
+ public Enumeration<?> getAttributeNames()
+ {
+ return new MultiNameEnumeration();
+ }
+
+ public AttributeSet getResolveParent()
+ {
+ return null;
+ }
+
+ public boolean isDefined(Object attrName)
+ {
+ boolean ret = false;
+ for (int i = 0; i < multi.length && ! ret; i++)
+ ret = multi[i].isDefined(attrName);
+ return ret;
+ }
+
+ public boolean isEqual(AttributeSet attr)
+ {
+ return getAttributeCount() == attr.getAttributeCount()
+ && containsAttributes(attr);
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/MultiStyle.java b/libjava/classpath/javax/swing/text/html/MultiStyle.java
new file mode 100644
index 000000000..eb6215035
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/MultiStyle.java
@@ -0,0 +1,136 @@
+/* MultiStyle.java -- Multiplexes between several Styles
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+
+import javax.swing.event.ChangeListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.Style;
+
+/**
+ * A Style implementation that is able to multiplex between several other
+ * Styles. This is used for CSS style resolving.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+class MultiStyle
+ extends MultiAttributeSet
+ implements Style
+{
+
+ // FIXME: Fix the implementation to also return attributes that
+ // are added to this style, etc. However, this is not really needed
+ // now for CSS, but would be nice for correctness.
+
+ /**
+ * The name of the style.
+ */
+ private String name;
+
+ /**
+ * The attributes added to this style.
+ */
+ private SimpleAttributeSet attributes;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param n the name
+ * @param m the styles to multiplex
+ */
+ public MultiStyle(String n, AttributeSet[] m)
+ {
+ super(m);
+ name = n;
+ attributes = new SimpleAttributeSet();
+ }
+
+ /**
+ * Returns the name of the style.
+ *
+ * @return the name of the style
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ public void addChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement.
+ }
+
+ public void removeChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement.
+ }
+
+ public void addAttribute(Object name, Object value)
+ {
+ attributes.addAttribute(name, value);
+ }
+
+ public void addAttributes(AttributeSet atts)
+ {
+ attributes.addAttributes(atts);
+ }
+
+ public void removeAttribute(Object name)
+ {
+ attributes.removeAttribute(name);
+ }
+
+ public void removeAttributes(Enumeration<?> names)
+ {
+ attributes.removeAttribute(names);
+ }
+
+ public void removeAttributes(AttributeSet atts)
+ {
+ attributes.removeAttribute(atts);
+ }
+
+ public void setResolveParent(AttributeSet parent)
+ {
+ // TODO: Implement.
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/NullView.java b/libjava/classpath/javax/swing/text/html/NullView.java
new file mode 100644
index 000000000..86ce0c10f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/NullView.java
@@ -0,0 +1,102 @@
+/* NullView.java -- A dummy view that renders nothing
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Graphics;
+import java.awt.Shape;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+import javax.swing.text.Position.Bias;
+
+/**
+ * A dummy view that renders nothing. This is used for invisible HTML elements
+ * like <head>.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+class NullView
+ extends View
+{
+
+ /**
+ * Creates a new NullView.
+ *
+ * @param elem the element
+ */
+ public NullView(Element elem)
+ {
+ super(elem);
+ }
+
+ /**
+ * Does nothing.
+ */
+ public void paint(Graphics g, Shape s)
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Returns zero for both directions.
+ */
+ public float getPreferredSpan(int axis)
+ {
+ return 0;
+ }
+
+ /**
+ * Returns the allocation of this view, which should be empty anyway.
+ */
+ public Shape modelToView(int pos, Shape a, Bias b)
+ throws BadLocationException
+ {
+ return a;
+ }
+
+ /**
+ * Returns the start offset of the element.
+ */
+ public int viewToModel(float x, float y, Shape a, Bias[] b)
+ {
+ return getElement().getStartOffset();
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/ObjectView.java b/libjava/classpath/javax/swing/text/html/ObjectView.java
new file mode 100644
index 000000000..9d900441b
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ObjectView.java
@@ -0,0 +1,110 @@
+/* ObjectView.java -- A view for HTML object tags
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Component;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.ComponentView;
+import javax.swing.text.Element;
+
+/**
+ * A view for HTML <code>&lt;object&gt;</code> tags.
+ *
+ * This is a {@link ComponentView} that creates special components depending
+ * on the object specification. If the object tag has a classid attribute, then
+ * this view will try to load the class specified by this attribute using the
+ * classloader that loaded the associated document. If the class could be
+ * loaded, an instance is created and the type narrowed to {@link Component}.
+ *
+ * It is also possible to set bean properties on the created component using
+ * nested <code>&lt;param&gt;</code> tags. For example:
+ * <pre>
+ * <object classid="javax.swing.JLabel">
+ * <param name="text" value="sample text">
+ * </object>
+ * </pre>
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class ObjectView extends ComponentView
+{
+
+ /**
+ * Creates a new <code>ObjectView</code>.
+ *
+ * @param el the element for which to create a view
+ */
+ public ObjectView(Element el)
+ {
+ super(el);
+ }
+
+ /**
+ * Creates a component based on the specification in the element of this
+ * view. See the class description for details.
+ */
+ protected Component createComponent()
+ {
+ Component comp = null;
+ Element el = getElement();
+ AttributeSet atts = el.getAttributes();
+ String classId = (String) atts.getAttribute("classid");
+ try
+ {
+ Class<?> objectClass = Class.forName(classId);
+ Object instance = objectClass.newInstance();
+ comp = (Component) instance;
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // Ignored.
+ }
+ catch (IllegalAccessException ex)
+ {
+ // Ignored.
+ }
+ catch (InstantiationException ex)
+ {
+ // Ignored.
+ }
+ // FIXME: Handle param tags and set bean properties accordingly.
+ return comp;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/Option.java b/libjava/classpath/javax/swing/text/html/Option.java
new file mode 100644
index 000000000..18d5c2bd8
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/Option.java
@@ -0,0 +1,159 @@
+/* Option.java -- Value class for <option> list model elements
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.text.AttributeSet;
+
+/**
+ * Value class for the combobox model that renders <code>&lt;option&gt;</code>
+ * elements.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class Option
+{
+
+ /**
+ * The attributes of the <option> tag.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The label.
+ */
+ private String label;
+
+ /**
+ * The selected state of this label.
+ */
+ private boolean selected;
+
+ /**
+ * Creates a new <code>Option</code> instance that uses the specified
+ * tag attributes.
+ *
+ * @param attr the attributes to use
+ */
+ public Option(AttributeSet attr)
+ {
+ // Protect the attribute set.
+ attributes = attr.copyAttributes();
+ label = null;
+ selected = attr.getAttribute(HTML.Attribute.SELECTED) != null;
+ }
+
+ /**
+ * Sets the label to use for this <code>&lt;option&gt;</code> tag.
+ *
+ * @param l the label to set
+ */
+ public void setLabel(String l)
+ {
+ label = l;
+ }
+
+ /**
+ * Returns the label of this <code>&lt;option&gt;</code> tag.
+ *
+ * @return the label of this <code>&lt;option&gt;</code> tag
+ */
+ public String getLabel()
+ {
+ return label;
+ }
+
+ /**
+ * Returns the attributes used to render this <code>&lt;option&gt;</code>
+ * tag.
+ *
+ * @return the attributes used to render this <code>&lt;option&gt;</code> tag
+ */
+ public AttributeSet getAttributes()
+ {
+ return attributes;
+ }
+
+ /**
+ * Returns a string representation of this <code>&lt;option&gt;</code> tag.
+ * This returns the <code>label</code> property.
+ *
+ * @return a string representation of this <code>&lt;option&gt;</code> tag
+ */
+ public String toString()
+ {
+ return label;
+ }
+
+ /**
+ * Sets the selected state of this <code>&lt;option&gt;</code> tag.
+ *
+ * @param s the selected state to set
+ */
+ protected void setSelection(boolean s)
+ {
+ selected = s;
+ }
+
+ /**
+ * Returns <code>true</code> when this option is selected, <code>false</code>
+ * otherwise.
+ *
+ * @return <code>true</code> when this option is selected, <code>false</code>
+ * otherwise
+ */
+ public boolean isSelected()
+ {
+ return selected;
+ }
+
+ /**
+ * Returns the string associated with the <code>value</code> attribute or
+ * the label, if no such attribute is specified.
+ *
+ * @return the string associated with the <code>value</code> attribute or
+ * the label, if no such attribute is specified
+ */
+ public String getValue()
+ {
+ String value = (String) attributes.getAttribute(HTML.Attribute.VALUE);
+ if (value == null)
+ value = label;
+ return value;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/ParagraphView.java b/libjava/classpath/javax/swing/text/html/ParagraphView.java
new file mode 100644
index 000000000..cab8edb28
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ParagraphView.java
@@ -0,0 +1,322 @@
+/* ParagraphView.java -- Renders a paragraph in HTML
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import gnu.javax.swing.text.html.css.Length;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.SizeRequirements;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.View;
+
+/**
+ * Renders a paragraph in HTML. This is a subclass of
+ * {@link javax.swing.text.ParagraphView} with some adjustments for
+ * understanding stylesheets.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class ParagraphView
+ extends javax.swing.text.ParagraphView
+{
+
+ /**
+ * The attributes used by this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The stylesheet's box painter.
+ */
+ private StyleSheet.BoxPainter painter;
+
+ /**
+ * The width as specified in the stylesheet or null if not specified.
+ */
+ private Length cssWidth;
+
+ /**
+ * The height as specified in the stylesheet or null if not specified.
+ */
+ private Length cssHeight;
+
+ /**
+ * Creates a new ParagraphView for the specified element.
+ *
+ * @param element the element
+ */
+ public ParagraphView(Element element)
+ {
+ super(element);
+ }
+
+ /**
+ * Sets the parent of this view. This is implemented to call the parent
+ * functionality and then trigger {@link #setPropertiesFromAttributes} in
+ * order to load the stylesheet attributes.
+ *
+ * @param parent the parent view to set
+ */
+ public void setParent(View parent)
+ {
+ super.setParent(parent);
+ if (parent != null)
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Returns the attributes used by this view. This is implemented to multiplex
+ * the attributes of the model with the attributes of the stylesheet.
+ */
+ public AttributeSet getAttributes()
+ {
+ if (attributes == null)
+ {
+ attributes = getStyleSheet().getViewAttributes(this);
+ }
+ return attributes;
+ }
+
+ /**
+ * Loads the visual properties of the ParagraphView from the element's
+ * attributes and the stylesheet of the HTML document.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ super.setPropertiesFromAttributes();
+
+ // Fetch CSS attributes.
+ attributes = getAttributes();
+ if (attributes != null)
+ {
+ super.setPropertiesFromAttributes();
+ Object o = attributes.getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null)
+ {
+ String align = o.toString();
+ if (align.equals("left"))
+ setJustification(StyleConstants.ALIGN_LEFT);
+ else if (align.equals("right"))
+ setJustification(StyleConstants.ALIGN_RIGHT);
+ else if (align.equals("center"))
+ setJustification(StyleConstants.ALIGN_CENTER);
+ else if (align.equals("justify"))
+ setJustification(StyleConstants.ALIGN_JUSTIFIED);
+ }
+
+ // Fetch StyleSheet's box painter.
+ painter = getStyleSheet().getBoxPainter(attributes);
+ setInsets((short) painter.getInset(TOP, this),
+ (short) painter.getInset(LEFT, this),
+ (short) painter.getInset(BOTTOM, this),
+ (short) painter.getInset(RIGHT, this));
+
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(attributes);
+ float exBase = ss.getEXBase(attributes);
+ cssWidth = (Length) attributes.getAttribute(CSS.Attribute.WIDTH);
+ if (cssWidth != null)
+ cssWidth.setFontBases(emBase, exBase);
+ cssHeight = (Length) attributes.getAttribute(CSS.Attribute.WIDTH);
+ if (cssHeight != null)
+ cssHeight.setFontBases(emBase, exBase);
+ }
+ }
+
+ /**
+ * Returns the stylesheet used by this view.
+ *
+ * @return the stylesheet used by this view
+ */
+ protected StyleSheet getStyleSheet()
+ {
+ Document doc = getDocument();
+ StyleSheet styleSheet = null;
+ if (doc instanceof HTMLDocument)
+ styleSheet = ((HTMLDocument) doc).getStyleSheet();
+ return styleSheet;
+ }
+
+ /**
+ * Calculates the minor axis requirements of this view. This is implemented
+ * to return the super class'es requirements and modifies the minimumSpan
+ * slightly so that it is not smaller than the length of the longest word.
+ *
+ * @param axis the axis
+ * @param r the SizeRequirements object to be used as return parameter;
+ * if <code>null</code> a new one will be created
+ *
+ * @return the requirements along the minor layout axis
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ r = super.calculateMinorAxisRequirements(axis, r);
+ if (! setCSSSpan(r, axis))
+ {
+ int margin = axis == X_AXIS ? getLeftInset() + getRightInset()
+ : getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ }
+ return r;
+ }
+
+ /**
+ * Sets the span on the SizeRequirements object according to the
+ * according CSS span value, when it is set.
+ *
+ * @param r the size requirements
+ * @param axis the axis
+ *
+ * @return <code>true</code> when the CSS span has been set,
+ * <code>false</code> otherwise
+ */
+ private boolean setCSSSpan(SizeRequirements r, int axis)
+ {
+ boolean ret = false;
+ if (axis == X_AXIS)
+ {
+ if (cssWidth != null && ! cssWidth.isPercentage())
+ {
+ r.minimum = (int) cssWidth.getValue();
+ r.preferred = (int) cssWidth.getValue();
+ r.maximum = (int) cssWidth.getValue();
+ ret = true;
+ }
+ }
+ else
+ {
+ if (cssHeight != null && ! cssWidth.isPercentage())
+ {
+ r.minimum = (int) cssHeight.getValue();
+ r.preferred = (int) cssHeight.getValue();
+ r.maximum = (int) cssHeight.getValue();
+ ret = true;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Determines if this view is visible or not. If none of the children is
+ * visible and the only visible child is the break that ends the paragraph,
+ * this paragraph is not considered to be visible.
+ *
+ * @return the visibility of this paragraph
+ */
+ public boolean isVisible()
+ {
+ // FIXME: Implement the above specified behaviour.
+ return super.isVisible();
+ }
+
+ /**
+ * Paints this view. This paints the box using the stylesheet's
+ * box painter for this view and delegates to the super class paint()
+ * afterwards.
+ *
+ * @param g the graphics object
+ * @param a the current allocation of this view
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ if (a != null)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ painter.paint(g, r.x, r.y, r.width, r.height, this);
+ }
+ super.paint(g, a);
+ }
+
+ /**
+ * Returns the preferred span of this view. If this view is not visible,
+ * we return <code>0</code>, otherwise the super class is called.
+ *
+ * @param axis the axis
+ *
+ * @return the preferred span of this view
+ */
+ public float getPreferredSpan(int axis)
+ {
+ float span = 0;
+ if (isVisible())
+ span = super.getPreferredSpan(axis);
+ return span;
+ }
+
+ /**
+ * Returns the minimum span of this view. If this view is not visible,
+ * we return <code>0</code>, otherwise the super class is called.
+ *
+ * @param axis the axis
+ *
+ * @return the minimum span of this view
+ */
+ public float getMinimumSpan(int axis)
+ {
+ float span = 0;
+ if (isVisible())
+ span = super.getMinimumSpan(axis);
+ return span;
+ }
+
+ /**
+ * Returns the maximum span of this view. If this view is not visible,
+ * we return <code>0</code>, otherwise the super class is called.
+ *
+ * @param axis the axis
+ *
+ * @return the maximum span of this view
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float span = 0;
+ if (isVisible())
+ span = super.getMaximumSpan(axis);
+ return span;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/ResetableModel.java b/libjava/classpath/javax/swing/text/html/ResetableModel.java
new file mode 100644
index 000000000..17f65b97d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ResetableModel.java
@@ -0,0 +1,50 @@
+/* ResetableModel.java -- Form models that can be resetted
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+/**
+ * Form models that can be resetted implement this.
+ */
+interface ResetableModel
+{
+ /**
+ * Resets the model.
+ */
+ void reset();
+}
diff --git a/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java
new file mode 100644
index 000000000..6177f9b86
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ResetablePlainDocument.java
@@ -0,0 +1,82 @@
+/* ResetablePlainDocument.java -- A plain document for use in the HTML renderer
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+/**
+ * A PlainDocument that can be resetted.
+ */
+class ResetablePlainDocument
+ extends PlainDocument
+ implements ResetableModel
+{
+ /**
+ * The initial text.
+ */
+ private String initial;
+
+ /**
+ * Stores the initial text.
+ *
+ * @param text the initial text
+ */
+ void setInitialText(String text)
+ {
+ initial = text;
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ try
+ {
+ replace(0, getLength(), initial, null);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't happen.
+ assert false;
+ }
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java
new file mode 100644
index 000000000..637ece151
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ResetableToggleButtonModel.java
@@ -0,0 +1,70 @@
+/* ResetableToggleButtonModel.java -- A toggle button model with reset support
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.JToggleButton.ToggleButtonModel;
+
+class ResetableToggleButtonModel
+ extends ToggleButtonModel
+ implements ResetableModel
+{
+
+ /**
+ * The initial state.
+ */
+ private boolean initial;
+
+ /**
+ * Sets the initial selection value.
+ *
+ * @param state the initial value
+ */
+ public void setInitial(boolean state)
+ {
+ initial = state;
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ setSelected(initial);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java
new file mode 100644
index 000000000..999746413
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/SelectComboBoxModel.java
@@ -0,0 +1,84 @@
+/* SelectComboBoxModel.java -- A special ComboBoxModel for use in HTML renderer
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import javax.swing.DefaultComboBoxModel;
+
+/**
+ * A special ComboBoxModel that supports storing the initial value so that
+ * the combobox can be resetted later.
+ */
+class SelectComboBoxModel
+ extends DefaultComboBoxModel
+ implements ResetableModel
+{
+
+ /**
+ * The initial selection.
+ */
+ private Option initial;
+
+ /**
+ * Sets the initial selection.
+ *
+ * @param option the initial selection
+ */
+ void setInitialSelection(Option option)
+ {
+ initial = option;
+ }
+
+ /**
+ * Returns the initial selection.
+ *
+ * @return the initial selection
+ */
+ Option getInitialSelection()
+ {
+ return initial;
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ setSelectedItem(initial);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/SelectListModel.java b/libjava/classpath/javax/swing/text/html/SelectListModel.java
new file mode 100644
index 000000000..23bfaa11b
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/SelectListModel.java
@@ -0,0 +1,106 @@
+/* OptionListModel.java -- A special ListModel for use in the HTML renderer
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.BitSet;
+
+import javax.swing.DefaultListModel;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.ListSelectionModel;
+
+/**
+ * A special list model that encapsulates its selection model and supports
+ * storing of the initial value so that it can be resetted.
+ */
+class SelectListModel
+ extends DefaultListModel
+ implements ResetableModel
+{
+ /**
+ * The selection model.
+ */
+ private DefaultListSelectionModel selectionModel;
+
+ /**
+ * The initial selection.
+ */
+ private BitSet initialSelection;
+
+ /**
+ * Creates a new SelectListModel.
+ */
+ SelectListModel()
+ {
+ selectionModel = new DefaultListSelectionModel();
+ initialSelection = new BitSet();
+ }
+
+ /**
+ * Sets the initial selection.
+ *
+ * @param init the initial selection
+ */
+ void addInitialSelection(int init)
+ {
+ initialSelection.set(init);
+ }
+
+ /**
+ * Resets the model.
+ */
+ public void reset()
+ {
+ selectionModel.clearSelection();
+ for (int i = initialSelection.size(); i >= 0; i--)
+ {
+ if (initialSelection.get(i))
+ selectionModel.addSelectionInterval(i, i);
+ }
+ }
+
+ /**
+ * Returns the associated selection model.
+ *
+ * @return the associated selection model
+ */
+ ListSelectionModel getSelectionModel()
+ {
+ return selectionModel;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/StyleSheet.java b/libjava/classpath/javax/swing/text/html/StyleSheet.java
new file mode 100644
index 000000000..5cf015bc5
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/StyleSheet.java
@@ -0,0 +1,1455 @@
+/* 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 gnu.javax.swing.text.html.css.BorderWidth;
+import gnu.javax.swing.text.html.css.CSSColor;
+import gnu.javax.swing.text.html.css.CSSParser;
+import gnu.javax.swing.text.html.css.CSSParserCallback;
+import gnu.javax.swing.text.html.css.FontSize;
+import gnu.javax.swing.text.html.css.FontStyle;
+import gnu.javax.swing.text.html.css.FontWeight;
+import gnu.javax.swing.text.html.css.Length;
+import gnu.javax.swing.text.html.css.Selector;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.geom.Rectangle2D;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.border.Border;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.Element;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import javax.swing.text.View;
+
+
+/**
+ * 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
+{
+
+ /**
+ * Parses CSS stylesheets using the parser in gnu/javax/swing/html/css.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ class CSSStyleSheetParserCallback
+ implements CSSParserCallback
+ {
+ /**
+ * The current styles.
+ */
+ private CSSStyle[] styles;
+
+ /**
+ * The precedence of the stylesheet to be parsed.
+ */
+ private int precedence;
+
+ /**
+ * Creates a new CSS parser. This parser parses a CSS stylesheet with
+ * the specified precedence.
+ *
+ * @param prec the precedence, according to the constants defined in
+ * CSSStyle
+ */
+ CSSStyleSheetParserCallback(int prec)
+ {
+ precedence = prec;
+ }
+
+ /**
+ * Called at the beginning of a statement.
+ *
+ * @param sel the selector
+ */
+ public void startStatement(Selector[] sel)
+ {
+ styles = new CSSStyle[sel.length];
+ for (int i = 0; i < sel.length; i++)
+ styles[i] = new CSSStyle(precedence, sel[i]);
+ }
+
+ /**
+ * Called at the end of a statement.
+ */
+ public void endStatement()
+ {
+ for (int i = 0; i < styles.length; i++)
+ css.add(styles[i]);
+ styles = null;
+ }
+
+ /**
+ * Called when a declaration is parsed.
+ *
+ * @param property the property
+ * @param value the value
+ */
+ public void declaration(String property, String value)
+ {
+ CSS.Attribute cssAtt = CSS.getAttribute(property);
+ Object val = CSS.getValue(cssAtt, value);
+ for (int i = 0; i < styles.length; i++)
+ {
+ CSSStyle style = styles[i];
+ CSS.addInternal(style, cssAtt, value);
+ if (cssAtt != null)
+ style.addAttribute(cssAtt, val);
+ }
+ }
+
+ }
+
+ /**
+ * Represents a style that is defined by a CSS rule.
+ */
+ private class CSSStyle
+ extends SimpleAttributeSet
+ implements Style, Comparable<CSSStyle>
+ {
+
+ static final int PREC_UA = 0;
+ static final int PREC_NORM = 100000;
+ static final int PREC_AUTHOR_NORMAL = 200000;
+ static final int PREC_AUTHOR_IMPORTANT = 300000;
+ static final int PREC_USER_IMPORTANT = 400000;
+
+ /**
+ * The priority of this style when matching CSS selectors.
+ */
+ private int precedence;
+
+ /**
+ * The selector for this rule.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ Selector selector;
+
+ CSSStyle(int prec, Selector sel)
+ {
+ precedence = prec;
+ selector = sel;
+ }
+
+ public String getName()
+ {
+ // TODO: Implement this for correctness.
+ return null;
+ }
+
+ public void addChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement this for correctness.
+ }
+
+ public void removeChangeListener(ChangeListener listener)
+ {
+ // TODO: Implement this for correctness.
+ }
+
+ /**
+ * Sorts the rule according to the style's precedence and the
+ * selectors specificity.
+ */
+ public int compareTo(CSSStyle other)
+ {
+ return other.precedence + other.selector.getSpecificity()
+ - precedence - selector.getSpecificity();
+ }
+
+ }
+
+ /** The base URL */
+ URL base;
+
+ /** Base font size (int) */
+ int baseFontSize;
+
+ /**
+ * The linked style sheets stored.
+ */
+ private ArrayList<StyleSheet> linked;
+
+ /**
+ * Maps element names (selectors) to AttributSet (the corresponding style
+ * information).
+ */
+ ArrayList<CSSStyle> css = new ArrayList<CSSStyle>();
+
+ /**
+ * Maps selectors to their resolved styles.
+ */
+ private HashMap<String,Style> resolvedStyles;
+
+ /**
+ * Constructs a StyleSheet.
+ */
+ public StyleSheet()
+ {
+ super();
+ baseFontSize = 4; // Default font size from CSS
+ resolvedStyles = new HashMap<String,Style>();
+ }
+
+ /**
+ * 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)
+ {
+ // Create list of the element and all of its parents, starting
+ // with the bottommost element.
+ ArrayList<Element> path = new ArrayList<Element>();
+ Element el;
+ AttributeSet atts;
+ for (el = e; el != null; el = el.getParentElement())
+ path.add(el);
+
+ // Create fully qualified selector.
+ StringBuilder selector = new StringBuilder();
+ int count = path.size();
+ // We append the actual element after this loop.
+ for (int i = count - 1; i > 0; i--)
+ {
+ el = path.get(i);
+ atts = el.getAttributes();
+ Object name = atts.getAttribute(StyleConstants.NameAttribute);
+ selector.append(name.toString());
+ if (atts.isDefined(HTML.Attribute.ID))
+ {
+ selector.append('#');
+ selector.append(atts.getAttribute(HTML.Attribute.ID));
+ }
+ if (atts.isDefined(HTML.Attribute.CLASS))
+ {
+ selector.append('.');
+ selector.append(atts.getAttribute(HTML.Attribute.CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS));
+ }
+ selector.append(' ');
+ }
+ selector.append(t.toString());
+ el = path.get(0);
+ atts = el.getAttributes();
+ // For leaf elements, we have to fetch the tag specific attributes.
+ if (el.isLeaf())
+ {
+ Object o = atts.getAttribute(t);
+ if (o instanceof AttributeSet)
+ atts = (AttributeSet) o;
+ else
+ atts = null;
+ }
+ if (atts != null)
+ {
+ if (atts.isDefined(HTML.Attribute.ID))
+ {
+ selector.append('#');
+ selector.append(atts.getAttribute(HTML.Attribute.ID));
+ }
+ if (atts.isDefined(HTML.Attribute.CLASS))
+ {
+ selector.append('.');
+ selector.append(atts.getAttribute(HTML.Attribute.CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.DYNAMIC_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.DYNAMIC_CLASS));
+ }
+ if (atts.isDefined(HTML.Attribute.PSEUDO_CLASS))
+ {
+ selector.append(':');
+ selector.append(atts.getAttribute(HTML.Attribute.PSEUDO_CLASS));
+ }
+ }
+ return getResolvedStyle(selector.toString(), path, t);
+ }
+
+ /**
+ * Fetches a resolved style. If there is no resolved style for the
+ * specified selector, the resolve the style using
+ * {@link #resolveStyle(String, List, HTML.Tag)}.
+ *
+ * @param selector the selector for which to resolve the style
+ * @param path the Element path, used in the resolving algorithm
+ * @param tag the tag for which to resolve
+ *
+ * @return the resolved style
+ */
+ private Style getResolvedStyle(String selector, List<Element> path, HTML.Tag tag)
+ {
+ Style style = resolvedStyles.get(selector);
+ if (style == null)
+ style = resolveStyle(selector, path, tag);
+ return style;
+ }
+
+ /**
+ * Resolves a style. This creates arrays that hold the tag names,
+ * class and id attributes and delegates the work to
+ * {@link #resolveStyle(String, String[], List<Map<String,String>>)}.
+ *
+ * @param selector the selector
+ * @param path the Element path
+ * @param tag the tag
+ *
+ * @return the resolved style
+ */
+ private Style resolveStyle(String selector, List<Element> path, HTML.Tag tag)
+ {
+ int count = path.size();
+ String[] tags = new String[count];
+ List<Map<String,String>> attributes =
+ new ArrayList<Map<String,String>>(count);
+ for (int i = 0; i < count; i++)
+ {
+ Element el = path.get(i);
+ AttributeSet atts = el.getAttributes();
+ if (i == 0 && el.isLeaf())
+ {
+ Object o = atts.getAttribute(tag);
+ if (o instanceof AttributeSet)
+ atts = (AttributeSet) o;
+ else
+ atts = null;
+ }
+ if (atts != null)
+ {
+ HTML.Tag t =
+ (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute);
+ if (t != null)
+ tags[i] = t.toString();
+ else
+ tags[i] = null;
+ attributes.set(i, attributeSetToMap(atts));
+ }
+ else
+ {
+ tags[i] = null;
+ }
+ }
+ tags[0] = tag.toString();
+ return resolveStyle(selector, tags, attributes);
+ }
+
+ /**
+ * Performs style resolving.
+ *
+ * @param selector the selector
+ * @param tags the tags
+ * @param attributes the attributes of the tags
+ *
+ * @return the resolved style
+ */
+ private Style resolveStyle(String selector, String[] tags,
+ List<Map<String,String>> attributes)
+ {
+ // FIXME: This style resolver is not correct. But it works good enough for
+ // the default.css.
+ ArrayList<CSSStyle> styles = new ArrayList<CSSStyle>();
+ for (CSSStyle style : css)
+ {
+ if (style.selector.matches(tags, attributes))
+ styles.add(style);
+ }
+
+ // Add styles from linked stylesheets.
+ if (linked != null)
+ {
+ for (int i = linked.size() - 1; i >= 0; i--)
+ {
+ StyleSheet ss = linked.get(i);
+ for (int j = ss.css.size() - 1; j >= 0; j--)
+ {
+ CSSStyle style = ss.css.get(j);
+ if (style.selector.matches(tags, attributes))
+ styles.add(style);
+ }
+ }
+ }
+
+ // Sort selectors.
+ Collections.sort(styles);
+ Style[] styleArray = styles.toArray(new Style[styles.size()]);
+ Style resolved = new MultiStyle(selector, styleArray);
+ resolvedStyles.put(selector, resolved);
+ return resolved;
+ }
+
+ /**
+ * Gets the rule that best matches the selector. selector is a space
+ * separated String of element names. The attributes of the returned
+ * 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)
+ {
+ CSSStyle best = null;
+ for (Iterator<CSSStyle> i = css.iterator(); i.hasNext();)
+ {
+ CSSStyle style = i.next();
+ if (style.compareTo(best) < 0)
+ best = style;
+ }
+ return best;
+ }
+
+ /**
+ * Adds a set of rules to the sheet. The rules are expected to be in valid
+ * CSS format. This is called as a result of parsing a <style> tag
+ *
+ * @param rule - the rule to add to the sheet
+ */
+ public void addRule(String rule)
+ {
+ CSSStyleSheetParserCallback cb =
+ new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
+ // FIXME: Handle ref.
+ StringReader in = new StringReader(rule);
+ CSSParser parser = new CSSParser(in, cb);
+ try
+ {
+ parser.parse();
+ }
+ catch (IOException ex)
+ {
+ // Shouldn't happen. And if, then don't let it bork the outside code.
+ }
+ // Clean up resolved styles cache so that the new styles are recognized
+ // on next stylesheet request.
+ resolvedStyles.clear();
+ }
+
+ /**
+ * Translates a CSS declaration into an AttributeSet. This is called
+ * as a result of encountering an HTML style attribute.
+ *
+ * @param decl - the declaration to get
+ * @return the AttributeSet representing the declaration
+ */
+ public AttributeSet getDeclaration(String decl)
+ {
+ if (decl == null)
+ return SimpleAttributeSet.EMPTY;
+ // FIXME: Not implemented.
+ return null;
+ }
+
+ /**
+ * Loads a set of rules that have been specified in terms of CSS grammar.
+ * If there are any conflicts with existing rules, the new rule is added.
+ *
+ * @param in - the stream to read the CSS grammar from.
+ * @param ref - the reference URL. It is the location of the stream, it may
+ * be null. All relative URLs specified in the stream will be based upon this
+ * parameter.
+ * @throws IOException - For any IO error while reading
+ */
+ public void loadRules(Reader in, URL ref)
+ throws IOException
+ {
+ CSSStyleSheetParserCallback cb =
+ new CSSStyleSheetParserCallback(CSSStyle.PREC_UA);
+ // FIXME: Handle ref.
+ CSSParser parser = new CSSParser(in, cb);
+ parser.parse();
+ }
+
+ /**
+ * Gets a set of attributes to use in the view. This is a set of
+ * attributes that can be used for View.getAttributes
+ *
+ * @param v - the view to get the set for
+ * @return the AttributeSet to use in the view.
+ */
+ public AttributeSet getViewAttributes(View v)
+ {
+ return new ViewAttributeSet(v, this);
+ }
+
+ /**
+ * Removes a style previously added.
+ *
+ * @param nm - the name of the style to remove
+ */
+ public void removeStyle(String nm)
+ {
+ // FIXME: Not implemented.
+ super.removeStyle(nm);
+ }
+
+ /**
+ * Adds the rules from ss to those of the receiver. ss's rules will
+ * override the old rules. An added StyleSheet will never override the rules
+ * of the receiving style sheet.
+ *
+ * @param ss - the new StyleSheet.
+ */
+ public void addStyleSheet(StyleSheet ss)
+ {
+ if (linked == null)
+ linked = new ArrayList<StyleSheet>();
+ linked.add(ss);
+ }
+
+ /**
+ * Removes ss from those of the receiver
+ *
+ * @param ss - the StyleSheet to remove.
+ */
+ public void removeStyleSheet(StyleSheet ss)
+ {
+ if (linked != null)
+ {
+ linked.remove(ss);
+ }
+ }
+
+ /**
+ * Returns an array of the linked StyleSheets. May return null.
+ *
+ * @return - An array of the linked StyleSheets.
+ */
+ public StyleSheet[] getStyleSheets()
+ {
+ StyleSheet[] linkedSS;
+ if (linked != null)
+ {
+ linkedSS = new StyleSheet[linked.size()];
+ linkedSS = linked.toArray(linkedSS);
+ }
+ else
+ {
+ linkedSS = null;
+ }
+ return linkedSS;
+ }
+
+ /**
+ * Imports a style sheet from the url. The rules are directly added to the
+ * receiver. This is usually called when a <link> tag is resolved in an
+ * HTML document.
+ *
+ * @param url the URL to import the StyleSheet from
+ */
+ public void importStyleSheet(URL url)
+ {
+ try
+ {
+ InputStream in = url.openStream();
+ Reader r = new BufferedReader(new InputStreamReader(in));
+ CSSStyleSheetParserCallback cb =
+ new CSSStyleSheetParserCallback(CSSStyle.PREC_AUTHOR_NORMAL);
+ CSSParser parser = new CSSParser(r, cb);
+ parser.parse();
+ }
+ catch (IOException ex)
+ {
+ // We can't do anything about it I guess.
+ }
+ }
+
+ /**
+ * Sets the base url. All import statements that are relative, will be
+ * relative to base.
+ *
+ * @param base -
+ * the base URL.
+ */
+ public void setBase(URL base)
+ {
+ this.base = base;
+ }
+
+ /**
+ * Gets the base url.
+ *
+ * @return - the base
+ */
+ public URL getBase()
+ {
+ return base;
+ }
+
+ /**
+ * Adds a CSS attribute to the given set.
+ *
+ * @param attr - the attribute set
+ * @param key - the attribute to add
+ * @param value - the value of the key
+ */
+ public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
+ String value)
+ {
+ Object val = CSS.getValue(key, value);
+ CSS.addInternal(attr, key, value);
+ attr.addAttribute(key, val);
+ }
+
+ /**
+ * Adds a CSS attribute to the given set.
+ * This method parses the value argument from HTML based on key.
+ * Returns true if it finds a valid value for the given key,
+ * and false otherwise.
+ *
+ * @param attr - the attribute set
+ * @param key - the attribute to add
+ * @param value - the value of the key
+ * @return true if a valid value was found.
+ */
+ public boolean addCSSAttributeFromHTML(MutableAttributeSet attr, CSS.Attribute key,
+ String value)
+ {
+ // FIXME: Need to parse value from HTML based on key.
+ attr.addAttribute(key, value);
+ return attr.containsAttribute(key, value);
+ }
+
+ /**
+ * Converts a set of HTML attributes to an equivalent set of CSS attributes.
+ *
+ * @param htmlAttrSet - the set containing the HTML attributes.
+ * @return the set of CSS attributes
+ */
+ public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet)
+ {
+ AttributeSet cssAttr = htmlAttrSet.copyAttributes();
+
+ // The HTML align attribute maps directly to the CSS text-align attribute.
+ Object o = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.TEXT_ALIGN, o);
+
+ // The HTML width attribute maps directly to CSS width.
+ o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH,
+ new Length(o.toString()));
+
+ // The HTML height attribute maps directly to CSS height.
+ o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT,
+ new Length(o.toString()));
+
+ o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap");
+
+ // Map cellspacing attr of tables to CSS border-spacing.
+ o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING,
+ new Length(o.toString()));
+
+ // For table cells and headers, fetch the cellpadding value from the
+ // parent table and set it as CSS padding attribute.
+ HTML.Tag tag = (HTML.Tag)
+ htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
+ if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH)
+ && htmlAttrSet instanceof Element)
+ {
+ Element el = (Element) htmlAttrSet;
+ AttributeSet tableAttrs = el.getParentElement().getParentElement()
+ .getAttributes();
+ o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING);
+ if (o != null)
+ {
+ Length l = new Length(o.toString());
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_BOTTOM, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_LEFT, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_RIGHT, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING_TOP, l);
+ }
+ o = tableAttrs.getAttribute(HTML.Attribute.BORDER);
+ cssAttr = translateBorder(cssAttr, o);
+ }
+
+ // Translate border attribute.
+ o = cssAttr.getAttribute(HTML.Attribute.BORDER);
+ cssAttr = translateBorder(cssAttr, o);
+
+ // TODO: Add more mappings.
+ return cssAttr;
+ }
+
+ /**
+ * Translates a HTML border attribute to a corresponding set of CSS
+ * attributes.
+ *
+ * @param cssAttr the original set of CSS attributes to add to
+ * @param o the value of the border attribute
+ *
+ * @return the new set of CSS attributes
+ */
+ private AttributeSet translateBorder(AttributeSet cssAttr, Object o)
+ {
+ if (o != null)
+ {
+ BorderWidth l = new BorderWidth(o.toString());
+ if (l.getValue() > 0)
+ {
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_WIDTH, l);
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_STYLE,
+ "solid");
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_COLOR,
+ new CSSColor("black"));
+ }
+ }
+ return cssAttr;
+ }
+
+ /**
+ * Adds an attribute to the given set and returns a new set. This is implemented
+ * to convert StyleConstants attributes to CSS before forwarding them to the superclass.
+ * The StyleConstants attribute do not have corresponding CSS entry, the attribute
+ * is stored (but will likely not be used).
+ *
+ * @param old - the old set
+ * @param key - the non-null attribute key
+ * @param value - the attribute value
+ * @return the updated set
+ */
+ public AttributeSet addAttribute(AttributeSet old, Object key,
+ Object value)
+ {
+ // FIXME: Not implemented.
+ return super.addAttribute(old, key, value);
+ }
+
+ /**
+ * Adds a set of attributes to the element. If any of these attributes are
+ * StyleConstants, they will be converted to CSS before forwarding to the
+ * superclass.
+ *
+ * @param old - the old set
+ * @param attr - the attributes to add
+ * @return the updated attribute set
+ */
+ public AttributeSet addAttributes(AttributeSet old, AttributeSet attr)
+ {
+ // FIXME: Not implemented.
+ return super.addAttributes(old, attr);
+ }
+
+ /**
+ * Removes an attribute from the set. If the attribute is a
+ * StyleConstants, it will be converted to CSS before forwarding to the
+ * superclass.
+ *
+ * @param old - the old set
+ * @param key - the non-null attribute key
+ * @return the updated set
+ */
+ public AttributeSet removeAttribute(AttributeSet old, Object key)
+ {
+ // FIXME: Not implemented.
+ return super.removeAttribute(old, key);
+ }
+
+ /**
+ * Removes an attribute from the set. If any of the attributes are
+ * StyleConstants, they will be converted to CSS before forwarding to the
+ * superclass.
+ *
+ * @param old - the old set
+ * @param attrs - the attributes to remove
+ * @return the updated set
+ */
+ public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs)
+ {
+ // FIXME: Not implemented.
+ return super.removeAttributes(old, attrs);
+ }
+
+ /**
+ * Removes a set of attributes for the element. If any of the attributes is a
+ * StyleConstants, they will be converted to CSS before forwarding to the
+ * superclass.
+ *
+ * @param old - the old attribute set
+ * @param names - the attribute names
+ * @return the update attribute set
+ */
+ public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names)
+ {
+ // FIXME: Not implemented.
+ return super.removeAttributes(old, names);
+ }
+
+ /**
+ * Creates a compact set of attributes that might be shared. This is a hook
+ * for subclasses that want to change the behaviour of SmallAttributeSet.
+ *
+ * @param a - the set of attributes to be represented in the compact form.
+ * @return the set of attributes created
+ */
+ protected StyleContext.SmallAttributeSet createSmallAttributeSet(AttributeSet a)
+ {
+ return super.createSmallAttributeSet(a);
+ }
+
+ /**
+ * Creates a large set of attributes. This set is not shared. This is a hook
+ * for subclasses that want to change the behaviour of the larger attribute
+ * storage format.
+ *
+ * @param a - the set of attributes to be represented in the larger form.
+ * @return the large set of attributes.
+ */
+ protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
+ {
+ return super.createLargeAttributeSet(a);
+ }
+
+ /**
+ * Gets the font to use for the given set.
+ *
+ * @param a - the set to get the font for.
+ * @return the font for the set
+ */
+ public Font getFont(AttributeSet a)
+ {
+ int realSize = getFontSize(a);
+
+ // Decrement size for subscript and superscript.
+ Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
+ if (valign != null)
+ {
+ String v = valign.toString();
+ if (v.contains("sup") || v.contains("sub"))
+ realSize -= 2;
+ }
+
+ // TODO: Convert font family.
+ String family = "SansSerif";
+
+ int style = Font.PLAIN;
+ FontWeight weight = (FontWeight) a.getAttribute(CSS.Attribute.FONT_WEIGHT);
+ if (weight != null)
+ style |= weight.getValue();
+ FontStyle fStyle = (FontStyle) a.getAttribute(CSS.Attribute.FONT_STYLE);
+ if (fStyle != null)
+ style |= fStyle.getValue();
+ return new Font(family, style, realSize);
+ }
+
+ /**
+ * Determines the EM base value based on the specified attributes.
+ *
+ * @param atts the attibutes
+ *
+ * @return the EM base value
+ */
+ float getEMBase(AttributeSet atts)
+ {
+ Font font = getFont(atts);
+ FontRenderContext ctx = new FontRenderContext(null, false, false);
+ Rectangle2D bounds = font.getStringBounds("M", ctx);
+ return (float) bounds.getWidth();
+ }
+
+ /**
+ * Determines the EX base value based on the specified attributes.
+ *
+ * @param atts the attibutes
+ *
+ * @return the EX base value
+ */
+ float getEXBase(AttributeSet atts)
+ {
+ Font font = getFont(atts);
+ FontRenderContext ctx = new FontRenderContext(null, false, false);
+ Rectangle2D bounds = font.getStringBounds("x", ctx);
+ return (float) bounds.getHeight();
+ }
+
+ /**
+ * Resolves the fontsize for a given set of attributes.
+ *
+ * @param atts the attributes
+ *
+ * @return the resolved font size
+ */
+ private int getFontSize(AttributeSet atts)
+ {
+ int size = 12;
+ if (atts.isDefined(CSS.Attribute.FONT_SIZE))
+ {
+ FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE);
+ if (fs.isRelative())
+ {
+ int parSize = 12;
+ AttributeSet resolver = atts.getResolveParent();
+ if (resolver != null)
+ parSize = getFontSize(resolver);
+ size = fs.getValue(parSize);
+ }
+ else
+ {
+ size = fs.getValue();
+ }
+ }
+ else
+ {
+ AttributeSet resolver = atts.getResolveParent();
+ if (resolver != null)
+ size = getFontSize(resolver);
+ }
+ return size;
+ }
+
+ /**
+ * Takes a set of attributes and turns it into a foreground
+ * color specification. This is used to specify things like, brigher, more hue
+ * etc.
+ *
+ * @param a - the set to get the foreground color for
+ * @return the foreground color for the set
+ */
+ public Color getForeground(AttributeSet a)
+ {
+ CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.COLOR);
+ Color color = null;
+ if (c != null)
+ color = c.getValue();
+ return color;
+ }
+
+ /**
+ * Takes a set of attributes and turns it into a background
+ * color specification. This is used to specify things like, brigher, more hue
+ * etc.
+ *
+ * @param a - the set to get the background color for
+ * @return the background color for the set
+ */
+ public Color getBackground(AttributeSet a)
+ {
+ CSSColor c = (CSSColor) a.getAttribute(CSS.Attribute.BACKGROUND_COLOR);
+ Color color = null;
+ if (c != null)
+ color = c.getValue();
+ return color;
+ }
+
+ /**
+ * Gets the box formatter to use for the given set of CSS attributes.
+ *
+ * @param a - the given set
+ * @return the box formatter
+ */
+ public BoxPainter getBoxPainter(AttributeSet a)
+ {
+ return new BoxPainter(a, this);
+ }
+
+ /**
+ * Gets the list formatter to use for the given set of CSS attributes.
+ *
+ * @param a - the given set
+ * @return the list formatter
+ */
+ public ListPainter getListPainter(AttributeSet a)
+ {
+ return new ListPainter(a, this);
+ }
+
+ /**
+ * Sets the base font size between 1 and 7.
+ *
+ * @param sz - the new font size for the base.
+ */
+ public void setBaseFontSize(int sz)
+ {
+ if (sz <= 7 && sz >= 1)
+ baseFontSize = sz;
+ }
+
+ /**
+ * Sets the base font size from the String. It can either identify
+ * a specific font size (between 1 and 7) or identify a relative
+ * font size such as +1 or -2.
+ *
+ * @param size - the new font size as a String.
+ */
+ public void setBaseFontSize(String size)
+ {
+ size = size.trim();
+ int temp = 0;
+ try
+ {
+ if (size.length() == 2)
+ {
+ int i = new Integer(size.substring(1)).intValue();
+ if (size.startsWith("+"))
+ temp = baseFontSize + i;
+ else if (size.startsWith("-"))
+ temp = baseFontSize - i;
+ }
+ else if (size.length() == 1)
+ temp = new Integer(size.substring(0)).intValue();
+
+ if (temp <= 7 && temp >= 1)
+ baseFontSize = temp;
+ }
+ catch (NumberFormatException nfe)
+ {
+ // Do nothing here
+ }
+ }
+
+ /**
+ * TODO
+ *
+ * @param pt - TODO
+ * @return TODO
+ */
+ public static int getIndexOfSize(float pt)
+ {
+ // FIXME: Not implemented.
+ return 0;
+ }
+
+ /**
+ * Gets the point size, given a size index.
+ *
+ * @param index - the size index
+ * @return the point size.
+ */
+ public float getPointSize(int index)
+ {
+ // FIXME: Not implemented.
+ return 0;
+ }
+
+ /**
+ * Given the string of the size, returns the point size value.
+ *
+ * @param size - the string representation of the size.
+ * @return - the point size value.
+ */
+ public float getPointSize(String size)
+ {
+ // FIXME: Not implemented.
+ return 0;
+ }
+
+ /**
+ * Convert the color string represenation into java.awt.Color. The valid
+ * values are like "aqua" , "#00FFFF" or "rgb(1,6,44)".
+ *
+ * @param colorName the color to convert.
+ * @return the matching java.awt.color
+ */
+ public Color stringToColor(String colorName)
+ {
+ return CSSColor.convertValue(colorName);
+ }
+
+ /**
+ * This class carries out some of the duties of CSS formatting. This enables views
+ * to present the CSS formatting while not knowing how the CSS values are cached.
+ *
+ * This object is reponsible for the insets of a View and making sure
+ * the background is maintained according to the CSS attributes.
+ *
+ * @author Lillian Angel (langel@redhat.com)
+ */
+ public static class BoxPainter extends Object implements Serializable
+ {
+
+ /**
+ * The left inset.
+ */
+ private float leftInset;
+
+ /**
+ * The right inset.
+ */
+ private float rightInset;
+
+ /**
+ * The top inset.
+ */
+ private float topInset;
+
+ /**
+ * The bottom inset.
+ */
+ private float bottomInset;
+
+ /**
+ * The border of the box.
+ */
+ private Border border;
+
+ private float leftPadding;
+ private float rightPadding;
+ private float topPadding;
+ private float bottomPadding;
+
+ /**
+ * The background color.
+ */
+ private Color background;
+
+ /**
+ * Package-private constructor.
+ *
+ * @param as - AttributeSet for painter
+ */
+ BoxPainter(AttributeSet as, StyleSheet ss)
+ {
+ float emBase = ss.getEMBase(as);
+ float exBase = ss.getEXBase(as);
+ // Fetch margins.
+ Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ leftInset = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ rightInset = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.MARGIN_TOP);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ topInset = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.MARGIN_BOTTOM);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ bottomInset = l.getValue();
+ }
+
+ // Fetch padding.
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ leftPadding = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ rightPadding = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ topPadding = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM);
+ if (l != null)
+ {
+ l.setFontBases(emBase, exBase);
+ bottomPadding = l.getValue();
+ }
+
+ // Determine border.
+ border = new CSSBorder(as, ss);
+
+ // Determine background.
+ background = ss.getBackground(as);
+
+ }
+
+
+ /**
+ * Gets the inset needed on a given side to account for the margin, border
+ * and padding.
+ *
+ * @param size - the size of the box to get the inset for. View.TOP, View.LEFT,
+ * View.BOTTOM or View.RIGHT.
+ * @param v - the view making the request. This is used to get the AttributeSet,
+ * amd may be used to resolve percentage arguments.
+ * @return the inset
+ * @throws IllegalArgumentException - for an invalid direction.
+ */
+ public float getInset(int size, View v)
+ {
+ float inset;
+ switch (size)
+ {
+ case View.TOP:
+ inset = topInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).top;
+ inset += topPadding;
+ break;
+ case View.BOTTOM:
+ inset = bottomInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).bottom;
+ inset += bottomPadding;
+ break;
+ case View.LEFT:
+ inset = leftInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).left;
+ inset += leftPadding;
+ break;
+ case View.RIGHT:
+ inset = rightInset;
+ if (border != null)
+ inset += border.getBorderInsets(null).right;
+ inset += rightPadding;
+ break;
+ default:
+ inset = 0.0F;
+ }
+ return inset;
+ }
+
+ /**
+ * Paints the CSS box according to the attributes given. This should
+ * paint the border, padding and background.
+ *
+ * @param g - the graphics configuration
+ * @param x - the x coordinate
+ * @param y - the y coordinate
+ * @param w - the width of the allocated area
+ * @param h - the height of the allocated area
+ * @param v - the view making the request
+ */
+ public void paint(Graphics g, float x, float y, float w, float h, View v)
+ {
+ int inX = (int) (x + leftInset);
+ int inY = (int) (y + topInset);
+ int inW = (int) (w - leftInset - rightInset);
+ int inH = (int) (h - topInset - bottomInset);
+ if (background != null)
+ {
+ g.setColor(background);
+ g.fillRect(inX, inY, inW, inH);
+ }
+ if (border != null)
+ {
+ border.paintBorder(null, g, inX, inY, inW, inH);
+ }
+ }
+ }
+
+ /**
+ * This class carries out some of the CSS list formatting duties. Implementations
+ * of this class enable views to present the CSS formatting while not knowing anything
+ * about how the CSS values are being cached.
+ *
+ * @author Lillian Angel (langel@redhat.com)
+ */
+ public static class ListPainter implements Serializable
+ {
+
+ /**
+ * Attribute set for painter
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The associated style sheet.
+ */
+ private StyleSheet styleSheet;
+
+ /**
+ * The bullet type.
+ */
+ private String type;
+
+ /**
+ * Package-private constructor.
+ *
+ * @param as - AttributeSet for painter
+ */
+ ListPainter(AttributeSet as, StyleSheet ss)
+ {
+ attributes = as;
+ styleSheet = ss;
+ type = (String) as.getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
+ }
+
+ /**
+ * Cached rectangle re-used in the paint method below.
+ */
+ private final Rectangle tmpRect = new Rectangle();
+
+ /**
+ * Paints the CSS list decoration according to the attributes given.
+ *
+ * @param g - the graphics configuration
+ * @param x - the x coordinate
+ * @param y - the y coordinate
+ * @param w - the width of the allocated area
+ * @param h - the height of the allocated area
+ * @param v - the view making the request
+ * @param item - the list item to be painted >=0.
+ */
+ public void paint(Graphics g, float x, float y, float w, float h, View v,
+ int item)
+ {
+ // FIXME: This is a very simplistic list rendering. We still need
+ // to implement different bullet types (see type field) and custom
+ // bullets via images.
+ View itemView = v.getView(item);
+ AttributeSet viewAtts = itemView.getAttributes();
+ Object tag = viewAtts.getAttribute(StyleConstants.NameAttribute);
+ // Only paint something here when the child view is an LI tag
+ // and the calling view is some of the list tags then).
+ if (tag != null && tag == HTML.Tag.LI)
+ {
+ g.setColor(Color.BLACK);
+ int centerX = (int) (x - 12);
+ int centerY = -1;
+ // For paragraphs (almost all cases) center bullet vertically
+ // in the middle of the first line.
+ tmpRect.setBounds((int) x, (int) y, (int) w, (int) h);
+ if (itemView.getViewCount() > 0)
+ {
+ View v1 = itemView.getView(0);
+ if (v1 instanceof ParagraphView && v1.getViewCount() > 0)
+ {
+ Shape a1 = itemView.getChildAllocation(0, tmpRect);
+ Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1
+ : a1.getBounds();
+ ParagraphView par = (ParagraphView) v1;
+ Shape a = par.getChildAllocation(0, r1);
+ if (a != null)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a
+ : a.getBounds();
+ centerY = (int) (r.height / 2 + r.y);
+ }
+ }
+ }
+ if (centerY == -1)
+ {
+ centerY =(int) (h / 2 + y);
+ }
+ g.fillOval(centerX - 3, centerY - 3, 6, 6);
+ }
+ }
+ }
+
+ /**
+ * Converts an AttributeSet to a Map. This is used for CSS resolving.
+ *
+ * @param atts the attributes to convert
+ *
+ * @return the converted map
+ */
+ private Map<String,String> attributeSetToMap(AttributeSet atts)
+ {
+ HashMap<String,String> map = new HashMap<String,String>();
+ Enumeration<?> keys = atts.getAttributeNames();
+ while (keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = atts.getAttribute(key);
+ map.put(key.toString(), value.toString());
+ }
+ return map;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/TableView.java b/libjava/classpath/javax/swing/text/html/TableView.java
new file mode 100644
index 000000000..2ad1b6d38
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/TableView.java
@@ -0,0 +1,974 @@
+/* TableView.java -- A table view for HTML tables
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import gnu.javax.swing.text.html.css.Length;
+
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+
+/**
+ * A view implementation that renders HTML tables.
+ *
+ * This is basically a vertical BoxView that contains the rows of the table
+ * and the rows are horizontal BoxViews that contain the actual columns.
+ */
+class TableView
+ extends BlockView
+ implements ViewFactory
+{
+
+ /**
+ * Represents a single table row.
+ */
+ class RowView
+ extends BlockView
+ {
+ /**
+ * Has true at column positions where an above row's cell overlaps into
+ * this row.
+ */
+ boolean[] overlap;
+
+ /**
+ * Stores the row index of this row.
+ */
+ int rowIndex;
+
+ /**
+ * Creates a new RowView.
+ *
+ * @param el the element for the row view
+ */
+ RowView(Element el)
+ {
+ super(el, X_AXIS);
+ }
+
+ public void replace(int offset, int len, View[] views)
+ {
+ gridValid = false;
+ super.replace(offset, len, views);
+ }
+
+ /**
+ * Overridden to make rows not resizable along the Y axis.
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float span;
+ if (axis == Y_AXIS)
+ span = super.getPreferredSpan(axis);
+ else
+ span = Integer.MAX_VALUE;
+ return span;
+ }
+
+ public float getMinimumSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = totalColumnRequirements.minimum;
+ else
+ span = super.getMinimumSpan(axis);
+ return span;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = totalColumnRequirements.preferred;
+ else
+ span = super.getPreferredSpan(axis);
+ return span;
+ }
+
+ /**
+ * Calculates the overall size requirements for the row along the
+ * major axis. This will be the sum of the column requirements.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ if (r == null)
+ r = new SizeRequirements();
+ int adjust = (columnRequirements.length + 1) * cellSpacing;
+ r.minimum = totalColumnRequirements.minimum + adjust;
+ r.preferred = totalColumnRequirements.preferred + adjust;
+ r.maximum = totalColumnRequirements.maximum + adjust;
+ r.alignment = 0.0F;
+ return r;
+ }
+
+ /**
+ * Lays out the columns in this row.
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
+ int spans[])
+ {
+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+
+ // Adjust columns that have rowSpan > 1.
+ int numCols = getViewCount();
+ for (int i = 0; i < numCols; i++)
+ {
+ View v = getView(i);
+ if (v instanceof CellView)
+ {
+ CellView cell = (CellView) v;
+ if (cell.rowSpan > 1)
+ {
+ for (int r = 1; r < cell.rowSpan; r++)
+ {
+ spans[i] += TableView.this.getSpan(axis, rowIndex + r);
+ spans[i] += cellSpacing;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Lays out the columns in this row.
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int spans[])
+ {
+ updateGrid();
+ int realColumn = 0;
+ int colCount = getViewCount();
+ for (int i = 0; i < numColumns;)
+ {
+ if (! overlap[i] && realColumn < colCount)
+ {
+ View v = getView(realColumn);
+ if (v instanceof CellView)
+ {
+ CellView cv = (CellView) v;
+ offsets[realColumn] = columnOffsets[i];
+ spans[realColumn] = 0;
+ for (int j = 0; j < cv.colSpan; j++, i++)
+ {
+ spans[realColumn] += columnSpans[i];
+ if (j < cv.colSpan - 1)
+ spans[realColumn] += cellSpacing;
+ }
+ }
+ realColumn++;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ }
+ }
+
+ /**
+ * A view that renders HTML table cells (TD and TH tags).
+ */
+ class CellView
+ extends BlockView
+ {
+
+ /**
+ * The number of columns that this view spans.
+ */
+ int colSpan;
+
+ /**
+ * The number of rows that this cell spans.
+ */
+ int rowSpan;
+
+ /**
+ * Creates a new CellView for the specified element.
+ *
+ * @param el the element for which to create the colspan
+ */
+ CellView(Element el)
+ {
+ super(el, Y_AXIS);
+ }
+
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ r.maximum = Integer.MAX_VALUE;
+ return r;
+ }
+
+ /**
+ * Overridden to fetch the columnSpan attibute.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ super.setPropertiesFromAttributes();
+ colSpan = 1;
+ AttributeSet atts = getAttributes();
+ Object o = atts.getAttribute(HTML.Attribute.COLSPAN);
+ if (o != null)
+ {
+ try
+ {
+ colSpan = Integer.parseInt(o.toString());
+ }
+ catch (NumberFormatException ex)
+ {
+ // Couldn't parse the colspan, assume 1.
+ colSpan = 1;
+ }
+ }
+ rowSpan = 1;
+ o = atts.getAttribute(HTML.Attribute.ROWSPAN);
+ if (o != null)
+ {
+ try
+ {
+ rowSpan = Integer.parseInt(o.toString());
+ }
+ catch (NumberFormatException ex)
+ {
+ // Couldn't parse the colspan, assume 1.
+ rowSpan = 1;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * The attributes of this view.
+ */
+ private AttributeSet attributes;
+
+ /**
+ * The column requirements.
+ *
+ * Package private to avoid accessor methods.
+ */
+ SizeRequirements[] columnRequirements;
+
+ /**
+ * The overall requirements across all columns.
+ *
+ * Package private to avoid accessor methods.
+ */
+ SizeRequirements totalColumnRequirements;
+
+ /**
+ * The column layout, offsets.
+ *
+ * Package private to avoid accessor methods.
+ */
+ int[] columnOffsets;
+
+ /**
+ * The column layout, spans.
+ *
+ * Package private to avoid accessor methods.
+ */
+ int[] columnSpans;
+
+ /**
+ * The widths of the columns that have been explicitly specified.
+ */
+ Length[] columnWidths;
+
+ /**
+ * The total number of columns.
+ */
+ int numColumns;
+
+ /**
+ * The table width.
+ */
+ private Length width;
+
+ /**
+ * Indicates if the grid setup is ok.
+ */
+ boolean gridValid = false;
+
+ /**
+ * Additional space that is added _between_ table cells.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ int cellSpacing;
+
+ /**
+ * A cached Rectangle object for reuse in paint().
+ */
+ private Rectangle tmpRect;
+
+ /**
+ * Creates a new HTML table view for the specified element.
+ *
+ * @param el the element for the table view
+ */
+ public TableView(Element el)
+ {
+ super(el, Y_AXIS);
+ totalColumnRequirements = new SizeRequirements();
+ tmpRect = new Rectangle();
+ }
+
+ /**
+ * Implementation of the ViewFactory interface for creating the
+ * child views correctly.
+ */
+ public View create(Element elem)
+ {
+ View view = null;
+ AttributeSet atts = elem.getAttributes();
+ Object name = atts.getAttribute(StyleConstants.NameAttribute);
+ AttributeSet pAtts = elem.getParentElement().getAttributes();
+ Object pName = pAtts.getAttribute(StyleConstants.NameAttribute);
+
+ if (name == HTML.Tag.TR && pName == HTML.Tag.TABLE)
+ view = new RowView(elem);
+ else if ((name == HTML.Tag.TD || name == HTML.Tag.TH)
+ && pName == HTML.Tag.TR)
+ view = new CellView(elem);
+ else if (name == HTML.Tag.CAPTION)
+ view = new ParagraphView(elem);
+ else
+ {
+ // If we haven't mapped the element, then fall back to the standard
+ // view factory.
+ View parent = getParent();
+ if (parent != null)
+ {
+ ViewFactory vf = parent.getViewFactory();
+ if (vf != null)
+ view = vf.create(elem);
+ }
+ }
+ return view;
+ }
+
+ /**
+ * Returns this object as view factory so that we get our TR, TD, TH
+ * and CAPTION subelements created correctly.
+ */
+ public ViewFactory getViewFactory()
+ {
+ return this;
+ }
+
+ /**
+ * Returns the attributes of this view. This is overridden to provide
+ * the attributes merged with the CSS stuff.
+ */
+ public AttributeSet getAttributes()
+ {
+ if (attributes == null)
+ attributes = getStyleSheet().getViewAttributes(this);
+ return attributes;
+ }
+
+ /**
+ * Returns the stylesheet associated with this view.
+ *
+ * @return the stylesheet associated with this view
+ */
+ protected StyleSheet getStyleSheet()
+ {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Overridden to calculate the size requirements according to the
+ * columns distribution.
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ updateGrid();
+ calculateColumnRequirements();
+
+ // Calculate the horizontal requirements according to the superclass.
+ // This will return the maximum of the row's widths.
+ r = super.calculateMinorAxisRequirements(axis, r);
+
+ // Try to set the CSS width if it fits.
+ if (width != null)
+ {
+ int w = (int) width.getValue();
+ if (r.minimum < w)
+ r.minimum = w;
+ }
+
+ // Adjust requirements when we have cell spacing.
+ int adjust = (columnRequirements.length + 1) * cellSpacing;
+ r.minimum += adjust;
+ r.preferred += adjust;
+
+ // Apply the alignment.
+ AttributeSet atts = getAttributes();
+ Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN);
+ r.alignment = 0.0F;
+ if (o != null)
+ {
+ String al = o.toString();
+ if (al.equals("left"))
+ r.alignment = 0.0F;
+ else if (al.equals("center"))
+ r.alignment = 0.5F;
+ else if (al.equals("right"))
+ r.alignment = 1.0F;
+ }
+
+ // Make it not resize in the horizontal direction.
+ r.maximum = r.preferred;
+ return r;
+ }
+
+ /**
+ * Overridden to perform the table layout before calling the super
+ * implementation.
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans)
+ {
+ updateGrid();
+
+ // Mark all rows as invalid along their minor axis to force correct
+ // layout of multi-row cells.
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View row = getView(i);
+ if (row instanceof RowView)
+ ((RowView) row).layoutChanged(axis);
+ }
+
+ layoutColumns(targetSpan);
+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+ }
+
+ /**
+ * Calculates the size requirements for the columns.
+ */
+ private void calculateColumnRequirements()
+ {
+ int numRows = getViewCount();
+ totalColumnRequirements.minimum = 0;
+ totalColumnRequirements.preferred = 0;
+ totalColumnRequirements.maximum = 0;
+
+ // In this first pass we find out a suitable total width to fit in
+ // all columns of all rows.
+ for (int r = 0; r < numRows; r++)
+ {
+ View rowView = getView(r);
+ int numCols;
+ if (rowView instanceof RowView)
+ numCols = ((RowView) rowView).getViewCount();
+ else
+ numCols = 0;
+
+ // We collect the normal (non-relative) column requirements in the
+ // total variable and the relative requirements in the relTotal
+ // variable. In the end we create the maximum of both to get the
+ // real requirements.
+ SizeRequirements total = new SizeRequirements();
+ SizeRequirements relTotal = new SizeRequirements();
+ float totalPercent = 0.F;
+ int realCol = 0;
+ for (int c = 0; c < numCols; c++)
+ {
+ View v = rowView.getView(c);
+ if (v instanceof CellView)
+ {
+ CellView cellView = (CellView) v;
+ int colSpan = cellView.colSpan;
+ if (colSpan > 1)
+ {
+ int cellMin = (int) cellView.getMinimumSpan(X_AXIS);
+ int cellPref = (int) cellView.getPreferredSpan(X_AXIS);
+ int cellMax = (int) cellView.getMaximumSpan(X_AXIS);
+ int currentMin = 0;
+ int currentPref = 0;
+ long currentMax = 0;
+ for (int i = 0; i < colSpan; i++)
+ {
+ SizeRequirements req = columnRequirements[realCol];
+ currentMin += req.minimum;
+ currentPref += req.preferred;
+ currentMax += req.maximum;
+ }
+ int deltaMin = cellMin - currentMin;
+ int deltaPref = cellPref - currentPref;
+ int deltaMax = (int) (cellMax - currentMax);
+ // Distribute delta.
+ for (int i = 0; i < colSpan; i++)
+ {
+ SizeRequirements req = columnRequirements[realCol];
+ if (deltaMin > 0)
+ req.minimum += deltaMin / colSpan;
+ if (deltaPref > 0)
+ req.preferred += deltaPref / colSpan;
+ if (deltaMax > 0)
+ req.maximum += deltaMax / colSpan;
+ if (columnWidths[realCol] == null
+ || ! columnWidths[realCol].isPercentage())
+ {
+ total.minimum += req.minimum;
+ total.preferred += req.preferred;
+ total.maximum += req.maximum;
+ }
+ else
+ {
+ relTotal.minimum =
+ Math.max(relTotal.minimum,
+ (int) (req.minimum
+ * columnWidths[realCol].getValue()));
+ relTotal.preferred =
+ Math.max(relTotal.preferred,
+ (int) (req.preferred
+ * columnWidths[realCol].getValue()));
+ relTotal.maximum =
+ Math.max(relTotal.maximum,
+ (int) (req.maximum
+ * columnWidths[realCol].getValue()));
+ totalPercent += columnWidths[realCol].getValue();
+ }
+ }
+ realCol += colSpan;
+ }
+ else
+ {
+ // Shortcut for colSpan == 1.
+ SizeRequirements req = columnRequirements[realCol];
+ req.minimum = Math.max(req.minimum,
+ (int) cellView.getMinimumSpan(X_AXIS));
+ req.preferred = Math.max(req.preferred,
+ (int) cellView.getPreferredSpan(X_AXIS));
+ req.maximum = Math.max(req.maximum,
+ (int) cellView.getMaximumSpan(X_AXIS));
+ if (columnWidths[realCol] == null
+ || ! columnWidths[realCol].isPercentage())
+ {
+ total.minimum += columnRequirements[realCol].minimum;
+ total.preferred +=
+ columnRequirements[realCol].preferred;
+ total.maximum += columnRequirements[realCol].maximum;
+ }
+ else
+ {
+ relTotal.minimum =
+ Math.max(relTotal.minimum,
+ (int) (req.minimum
+ / columnWidths[c].getValue()));
+ relTotal.preferred =
+ Math.max(relTotal.preferred,
+ (int) (req.preferred
+ / columnWidths[c].getValue()));
+ relTotal.maximum =
+ Math.max(relTotal.maximum,
+ (int) (req.maximum
+ / columnWidths[c].getValue()));
+ totalPercent += columnWidths[c].getValue();
+ }
+ realCol += 1;
+ }
+ }
+ }
+
+ // Update the total requirements as follows:
+ // 1. Multiply the absolute requirements with 1 - totalPercent. This
+ // gives the total requirements based on the wishes of the absolute
+ // cells.
+ // 2. Take the maximum of this value and the total relative
+ // requirements. Now we should have enough space for whatever cell
+ // in this column.
+ // 3. Take the maximum of this value and the previous maximum value.
+ total.minimum *= 1.F / (1.F - totalPercent);
+ total.preferred *= 1.F / (1.F - totalPercent);
+ total.maximum *= 1.F / (1.F - totalPercent);
+
+ int rowTotalMin = Math.max(total.minimum, relTotal.minimum);
+ int rowTotalPref = Math.max(total.preferred, relTotal.preferred);
+ int rowTotalMax = Math.max(total.maximum, relTotal.maximum);
+ totalColumnRequirements.minimum =
+ Math.max(totalColumnRequirements.minimum, rowTotalMin);
+ totalColumnRequirements.preferred =
+ Math.max(totalColumnRequirements.preferred, rowTotalPref);
+ totalColumnRequirements.maximum =
+ Math.max(totalColumnRequirements.maximum, rowTotalMax);
+ }
+
+ // Now we know what we want and can fix up the actual relative
+ // column requirements.
+ int numCols = columnRequirements.length;
+ for (int i = 0; i < numCols; i++)
+ {
+ if (columnWidths[i] != null)
+ {
+ columnRequirements[i].minimum = (int)
+ columnWidths[i].getValue(totalColumnRequirements.minimum);
+ columnRequirements[i].preferred = (int)
+ columnWidths[i].getValue(totalColumnRequirements.preferred);
+ columnRequirements[i].maximum = (int)
+ columnWidths[i].getValue(totalColumnRequirements.maximum);
+ }
+ }
+ }
+
+ /**
+ * Lays out the columns.
+ *
+ * @param targetSpan the target span into which the table is laid out
+ */
+ private void layoutColumns(int targetSpan)
+ {
+ // Set the spans to the preferred sizes. Determine the space
+ // that we have to adjust the sizes afterwards.
+ long sumPref = 0;
+ int n = columnRequirements.length;
+ for (int i = 0; i < n; i++)
+ {
+ SizeRequirements col = columnRequirements[i];
+ if (columnWidths[i] != null)
+ columnSpans[i] = (int) columnWidths[i].getValue(targetSpan);
+ else
+ columnSpans[i] = col.preferred;
+ sumPref += columnSpans[i];
+ }
+
+ // Try to adjust the spans so that we fill the targetSpan.
+ // For adjustments we have to use the targetSpan minus the cumulated
+ // cell spacings.
+ long diff = targetSpan - (n + 1) * cellSpacing - sumPref;
+ float factor = 0.0F;
+ int[] diffs = null;
+ if (diff != 0)
+ {
+ long total = 0;
+ diffs = new int[n];
+ for (int i = 0; i < n; i++)
+ {
+ // Only adjust the width if we haven't set a column width here.
+ if (columnWidths[i] == null)
+ {
+ SizeRequirements col = columnRequirements[i];
+ int span;
+ if (diff < 0)
+ {
+ span = col.minimum;
+ diffs[i] = columnSpans[i] - span;
+ }
+ else
+ {
+ span = col.maximum;
+ diffs[i] = span - columnSpans[i];
+ }
+ total += span;
+ }
+ else
+ total += columnSpans[i];
+ }
+
+ float maxAdjust = Math.abs(total - sumPref);
+ factor = diff / maxAdjust;
+ factor = Math.min(factor, 1.0F);
+ factor = Math.max(factor, -1.0F);
+ }
+
+ // Actually perform adjustments.
+ int totalOffs = cellSpacing;
+ for (int i = 0; i < n; i++)
+ {
+ columnOffsets[i] = totalOffs;
+ if (diff != 0)
+ {
+ float adjust = factor * diffs[i];
+ columnSpans[i] += Math.round(adjust);
+ }
+ // Avoid overflow here.
+ totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i]
+ + (long) cellSpacing, Integer.MAX_VALUE);
+ }
+ }
+
+ /**
+ * Updates the arrays that contain the row and column data in response
+ * to a change to the table structure.
+ *
+ * Package private to avoid accessor methods.
+ */
+ void updateGrid()
+ {
+ if (! gridValid)
+ {
+ AttributeSet atts = getAttributes();
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ int maxColumns = 0;
+ int numRows = getViewCount();
+ for (int r = 0; r < numRows; r++)
+ {
+ View rowView = getView(r);
+ int numCols = 0;
+ if (rowView instanceof RowView)
+ {
+ int numCells = ((RowView) rowView).getViewCount();
+ for (int i = 0; i < numCells; i++)
+ {
+ View v = rowView.getView(i);
+ if (v instanceof CellView)
+ numCols += ((CellView) v).colSpan;
+ }
+ }
+ maxColumns = Math.max(numCols, maxColumns);
+ }
+ numColumns = maxColumns;
+ columnWidths = new Length[maxColumns];
+ int[] rowSpans = new int[maxColumns];
+ for (int r = 0; r < numRows; r++)
+ {
+ View view = getView(r);
+ if (view instanceof RowView)
+ {
+ RowView rowView = (RowView) view;
+ rowView.rowIndex = r;
+ rowView.overlap = new boolean[maxColumns];
+ int colIndex = 0;
+ int colCount = rowView.getViewCount();
+ for (int c = 0; c < maxColumns;)
+ {
+ if (rowSpans[c] > 0)
+ {
+ rowSpans[c]--;
+ rowView.overlap[c] = true;
+ c++;
+ }
+ else if (colIndex < colCount)
+ {
+ View v = rowView.getView(colIndex);
+ colIndex++;
+ if (v instanceof CellView)
+ {
+ CellView cv = (CellView) v;
+ Object o =
+ cv.getAttributes().getAttribute(CSS.Attribute.WIDTH);
+ if (o != null && columnWidths[c] == null
+ && o instanceof Length)
+ {
+ columnWidths[c]= (Length) o;
+ columnWidths[c].setFontBases(emBase, exBase);
+ }
+ int rs = cv.rowSpan - 1;
+ for (int col = cv.colSpan - 1; col >= 0; col--)
+ {
+ rowSpans[c] = rs;
+ c++;
+ }
+ }
+ }
+ else
+ {
+ c++;
+ }
+ }
+ }
+ }
+ columnRequirements = new SizeRequirements[maxColumns];
+ for (int i = 0; i < maxColumns; i++)
+ columnRequirements[i] = new SizeRequirements();
+ columnOffsets = new int[maxColumns];
+ columnSpans = new int[maxColumns];
+
+ gridValid = true;
+ }
+ }
+
+ /**
+ * Overridden to restrict the table width to the preferred size.
+ */
+ public float getMaximumSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = super.getPreferredSpan(axis);
+ else
+ span = super.getMaximumSpan(axis);
+ return span;
+ }
+
+ /**
+ * Overridden to fetch the CSS attributes when view gets connected.
+ */
+ public void setParent(View parent)
+ {
+ super.setParent(parent);
+ if (parent != null)
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Fetches CSS and HTML layout attributes.
+ */
+ protected void setPropertiesFromAttributes()
+ {
+ super.setPropertiesFromAttributes();
+
+ // Fetch and parse cell spacing.
+ AttributeSet atts = getAttributes();
+ StyleSheet ss = getStyleSheet();
+ float emBase = ss.getEMBase(atts);
+ float exBase = ss.getEXBase(atts);
+ Object o = atts.getAttribute(CSS.Attribute.BORDER_SPACING);
+ if (o != null && o instanceof Length)
+ {
+ Length l = (Length) o;
+ l.setFontBases(emBase, exBase);
+ cellSpacing = (int) l.getValue();
+ }
+ o = atts.getAttribute(CSS.Attribute.WIDTH);
+ if (o != null && o instanceof Length)
+ {
+ width = (Length) o;
+ width.setFontBases(emBase, exBase);
+ }
+ }
+
+ /**
+ * Overridden to adjust for cellSpacing.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ int adjust = (getViewCount() + 1) * cellSpacing;
+ r.minimum += adjust;
+ r.preferred += adjust;
+ r.maximum += adjust;
+ return r;
+ }
+
+ /**
+ * Overridden to adjust for cellSpacing.
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int spans[])
+ {
+ // Mark all rows as invalid along their minor axis to force correct
+ // layout of multi-row cells.
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View row = getView(i);
+ if (row instanceof RowView)
+ ((RowView) row).layoutChanged(axis);
+ }
+
+ int adjust = (getViewCount() + 1) * cellSpacing;
+ super.layoutMajorAxis(targetSpan - adjust, axis, offsets, spans);
+ for (int i = 0; i < offsets.length; i++)
+ {
+ offsets[i] += (i + 1) * cellSpacing;
+ }
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.insertUpdate(e, a, this);
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.removeUpdate(e, a, this);
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.changedUpdate(e, a, this);
+ }
+
+ public void replace(int offset, int len, View[] views)
+ {
+ gridValid = false;
+ super.replace(offset, len, views);
+ }
+
+ /**
+ * We can't use the super class's paint() method because it might cut
+ * off multi-row children. Instead we trigger painting for all rows
+ * and let the rows sort out what to paint and what not.
+ */
+ public void paint(Graphics g, Shape a)
+ {
+ Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ painter.paint(g, rect.x, rect.y, rect.width, rect.height, this);
+ int nRows = getViewCount();
+ Rectangle inside = getInsideAllocation(a);
+ for (int r = 0; r < nRows; r++)
+ {
+ tmpRect.setBounds(inside);
+ childAllocation(r, tmpRect);
+ paintChild(g, tmpRect, r);
+ }
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java
new file mode 100644
index 000000000..fb57872ce
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/ViewAttributeSet.java
@@ -0,0 +1,163 @@
+/* ViewAttributeSet.java -- The AttributeSet used by HTML views
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text.html;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.Element;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.View;
+
+/**
+ * An AttributeSet implemenation that is used by the HTML views. This
+ * AttributeSet is created by StyleSheet.getViewAttributes() and combines
+ * the following attributes:
+ * - The original attributes of the View's element.
+ * - Any translated (HTML->CSS) attributes, as returned by
+ * StyleSheet.translateHTMLToCS().
+ * - CSS Styles as resolved by the CSS stylesheet.
+ *
+ * In addition to that, it resolves attributes to the parent views, if
+ * a CSS attribute is requested that is inheritable.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+class ViewAttributeSet
+ extends MultiAttributeSet
+{
+
+ /**
+ * The view for which we are the AttributeSet.
+ */
+ private View view;
+
+ /**
+ * The stylesheet to use.
+ */
+ private StyleSheet styleSheet;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param v the view for which to do the AttributeSet
+ */
+ ViewAttributeSet(View v, StyleSheet ss)
+ {
+ styleSheet = ss;
+ view = v;
+ ArrayList<AttributeSet> atts = new ArrayList<AttributeSet>();
+
+ Element el = v.getElement();
+ AttributeSet elAtts = el.getAttributes();
+ AttributeSet htmlAtts = styleSheet.translateHTMLToCSS(elAtts);
+ if (htmlAtts.getAttributeCount() > 0)
+ atts.add(htmlAtts);
+
+ if (el.isLeaf())
+ {
+ Enumeration<?> n = elAtts.getAttributeNames();
+ while (n.hasMoreElements())
+ {
+ Object key = n.nextElement();
+ if (key instanceof HTML.Tag)
+ {
+ AttributeSet rule = styleSheet.getRule((HTML.Tag) key, el);
+ if (rule != null)
+ atts.add(rule);
+ }
+ }
+ }
+ else
+ {
+ HTML.Tag tag =
+ (HTML.Tag) elAtts.getAttribute(StyleConstants.NameAttribute);
+ AttributeSet rule = styleSheet.getRule(tag, el);
+ if (rule != null)
+ atts.add(rule);
+ }
+
+ AttributeSet[] atts1 = new AttributeSet[atts.size()];
+ atts1 = atts.toArray(atts1);
+ init(atts1);
+ }
+
+ /**
+ * Fetches the attribute for the specific ckey. If the attribute
+ * can't be found and the key is a CSS.Attribute that is inherited,
+ * then the attribute is looked up in the resolve parent.
+ */
+ public Object getAttribute(Object key)
+ {
+ Object val = super.getAttribute(key);
+ if (val == null)
+ {
+ // Didn't find value. If the key is a CSS.Attribute, and is
+ // inherited, then ask the resolve parent.
+ if (key instanceof CSS.Attribute)
+ {
+ CSS.Attribute cssKey = (CSS.Attribute) key;
+ if (cssKey.isInherited())
+ {
+ AttributeSet resolveParent = getResolveParent();
+ if (resolveParent != null)
+ val = resolveParent.getAttribute(cssKey);
+ }
+ }
+ }
+ return val;
+ }
+
+ /**
+ * Returns the resolve parent of this AttributeSet. This is the AttributeSet
+ * returned by the parent view if available.
+ */
+ public AttributeSet getResolveParent()
+ {
+ AttributeSet parent = null;
+ if (view != null)
+ {
+ View parentView = view.getParent();
+ if (parentView != null)
+ parent = parentView.getAttributes();
+ }
+ return parent;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/package.html b/libjava/classpath/javax/swing/text/html/package.html
new file mode 100644
index 000000000..c7e774428
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/package.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!-- package.html - describes classes in javax.swing.text.html package.
+ Copyright (C) 2002 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. -->
+
+<html>
+<head><title>GNU Classpath - javax.swing.text.html</title></head>
+
+<body>
+<p> Provides supporting classes for web browsers,
+ web robots, web page content analysers, web editors and
+ other applications applications working with Hypertext
+ Markup Language (HTML).
+</p>
+
+</body>
+</html>
diff --git a/libjava/classpath/javax/swing/text/html/parser/AttributeList.java b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java
new file mode 100644
index 000000000..a943f056d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/AttributeList.java
@@ -0,0 +1,294 @@
+/* AttributeList.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.parser;
+
+import gnu.javax.swing.text.html.parser.support.gnuStringIntMapper;
+
+import java.io.Serializable;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * <p>
+ * Stores the attribute information, obtained by parsing SGML (DTD) tag
+ * <code>&lt;!ATTLIST .. &gt;</code></p>
+ * <p>
+ * Elements can have a associated named properties (attributes) having the
+ * assigned values. The element start tag can have any number of attribute
+ * value pairs, separated by spaces. They can appear in any order.
+ * SGML requires you to delimit the attribute values using either double (")
+ * or single (') quotation marks. In HTML, it is possible
+ * (but not recommended) to specify the value of an attribute without
+ * quotation marks. Such attribute value may only contain
+ * letters, digits, hyphens (-) and periods (.) .
+ * </p>
+ * <p>
+ * The <code>AttributeList</code> defines a single attribute that additionally
+ * has a pointer referencing the possible subsequent attribute.
+ * The whole structure is just a simple linked list, storing all attributes of
+ * some <code>Element</code>.
+ * Use the <code>getNext()</code> method repeatedly to see all attributes in
+ * the list.
+ * </p>
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public final class AttributeList
+ implements DTDConstants, Serializable
+{
+ /** Maps between type names and they string values. */
+ private static final gnuStringIntMapper mapper =
+ new gnuStringIntMapper()
+ {
+ protected void create()
+ {
+ add("CDATA", DTDConstants.CDATA);
+ add("ENTITY", DTDConstants.ENTITY);
+ add("ENTITIES", DTDConstants.ENTITIES);
+ add("ID", DTDConstants.ID);
+ add("IDREF", DTDConstants.IDREF);
+ add("IDREFS", DTDConstants.IDREFS);
+ add("NAME", DTDConstants.NAME);
+ add("NAMES", DTDConstants.NAMES);
+ add("NMTOKEN", DTDConstants.NMTOKEN);
+ add("NMTOKENS", DTDConstants.NMTOKENS);
+ add("NOTATION", DTDConstants.NOTATION);
+ add("NUMBER", DTDConstants.NUMBER);
+ add("NUMBERS", DTDConstants.NUMBERS);
+ add("NUTOKEN", DTDConstants.NUTOKEN);
+ add("NUTOKENS", DTDConstants.NUTOKENS);
+ }
+ };
+
+ /** Use serialVersionUID for interoperability. */
+ private static final long serialVersionUID = -1361214058742015233L;
+
+ /**
+ * The value of ( = pointer to ) the next attribute in the linked list,
+ * storing all attributes of some Element. Contains null for the
+ * last attribute.
+ */
+ public AttributeList next;
+
+ /**
+ * The name of the attribute. The attribute names are case insensitive.
+ */
+ public String name;
+
+ /**
+ * The default value of this attribute. Equals to null if no default value
+ * is specified.
+ */
+ public String value;
+
+ /**
+ * The explicit set of the allowed values of this attribute. Equals to
+ * null, if this parameter was not specified.
+ * Values, defined in DTD, are case insensitive.
+ */
+ public Vector<?> values;
+
+ /**
+ * The modifier of this attribute. This field contains one of the
+ * following DTD constants:
+ * <ul>
+ * <li> REQUIRED if the attribute value is always required,</li>
+ * <li> IMPLIED if the user agent must supply the default value itself,</li>
+ * <li> FIXED if the attribute value is fixed to some value and cannot
+ * be changed.</li>
+ * <li> DEFAULT if the attribute default value has been supplied.</li>
+ * <li> CURRENT the value that at any point in the document is
+ * the last value supplied for that element. A value is required to be
+ * supplied for the first* occurrence of an element</li>
+ * <li> CONREF specifies the IDREF value of
+ * the reference to content in another location of the document.
+ * The element with this attribute is empty, the content from
+ * that another location must be used instead.</li>
+ * </ul>
+ */
+ public int modifier;
+
+ /**
+ * The type of the attribute. The possible values of this field
+ * (NUMBER, NAME, ID, CDATA and so on) are defined in DTDConstants.
+ */
+ public int type;
+
+ /**
+ * Creates the attribute with the given name, initializing other fields
+ * to the default values ( 0 and null ).
+ *
+ * @param a_name The name of the attribute.
+ */
+ public AttributeList(String a_name)
+ {
+ name = a_name;
+ }
+
+ /**
+ * Creates the attribute with the given properties.
+ * @param a_name The name of the attribute
+ * @param a_type The type of the attribute. The possible values are defined
+ * in <code> DTDConstants</code>.
+ * @param a_modifier The modifier of this attribute. The possible values
+ * are defined in <code> DTDConstants</code>.
+ * @param a_default The default value of this attribute
+ * @param allowed_values The explicit set of the allowed values of
+ * this attribute
+ * @param a_next The value of the subsequent instance of the AttributeList,
+ * representing the next attribute definition for the same element.
+ * Equals to null for the last attribute definition.
+ */
+ public AttributeList(String a_name, int a_type, int a_modifier,
+ String a_default, Vector<?> allowed_values,
+ AttributeList a_next
+ )
+ {
+ this(a_name);
+ type = a_type;
+ modifier = a_modifier;
+ value = a_default;
+ values = allowed_values;
+ next = a_next;
+ }
+
+ /**
+ * Get the modifier of this attribute. This field contains one of the
+ * following DTD constants:
+ * <ul>
+ * <li> REQUIRED if the attribute value is always required,</li>
+ * <li> IMPLIED if the user agent must supply the default value itself,</li>
+ * <li> FIXED if the attribute value is fixed to some value and cannot
+ * be changed.</li>
+ * <li> DEFAULT if the attribute default value has been supplied.</li>
+ * <li> CURRENT the value that at any point in the document is
+ * the last value supplied for that element. A value is required to be
+ * supplied for the first* occurrence of an element</li>
+ * <li> CONREF specifies the IDREF value of
+ * the reference to content in another location of the document.
+ * The element with this attribute is empty, the content from
+ * that another location must be used instead.</li>
+ * </ul>
+ */
+ public int getModifier()
+ {
+ return modifier;
+ }
+
+ /**
+ * Get the name of the attribute.
+ * The value is returned as it was supplied to a
+ * constructor, preserving the character case.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Get the value of ( = pointer to ) the next attribute in the linked list,
+ * storing all attributes of some Element. Contains null for the
+ * last attribute.
+ */
+ public AttributeList getNext()
+ {
+ return next;
+ }
+
+ /**
+ * Get the type of the attribute. The possible values of this field
+ * (NUMBER, NAME, ID, CDATA and so on) are defined in DTDConstants.
+ */
+ public int getType()
+ {
+ return type;
+ }
+
+ /**
+ * Get the default value of this attribute.
+ */
+ public String getValue()
+ {
+ return value;
+ }
+
+ /**
+ * Get the allowed values of this attribute.
+ */
+ public Enumeration<?> getValues()
+ {
+ return (values != null) ? values.elements() : null;
+ }
+
+ /**
+ * Converts a string value, representing a valid SGLM attribute type,
+ * into the corresponding value, defined in DTDConstants.
+ * @param typeName the name of the type (character case is ignored).
+ * @return a value from DTDConstants or DTDConstants.ANY if the
+ * string is not representing a known type. The known attribute types
+ * in this implementation are CDATA, ENTITY, ENTITIES, ID, IDREF, IDREFS,
+ * NAME, NAMES, NMTOKEN, NMTOKENS, NOTATION, NUMBER, NUMBERS, NUTOKEN and
+ * NUTOKENS.
+ * @throws NullPointerException if the passed parameter is null.
+ */
+ public static int name2type(String typeName)
+ {
+ return mapper.get(typeName.toUpperCase());
+ }
+
+ /**
+ * Returns the attribute name.
+ */
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * Converts a value from DTDConstants into the string representation.
+ * @param type - an integer value of the public static integer field,
+ * defined in the DTDConstants class.
+ * @return a corresponding SGML DTD keyword (UPPERCASE) or null if there
+ * are no attribute type constant having the given value.
+ */
+ public static String type2name(int type)
+ {
+ return mapper.get(type);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/ContentModel.java b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java
new file mode 100644
index 000000000..d5c4418de
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/ContentModel.java
@@ -0,0 +1,223 @@
+/* ContentModel.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.parser;
+
+import gnu.javax.swing.text.html.parser.models.transformer;
+
+import java.io.Serializable;
+
+import java.util.Vector;
+
+/**
+ * A representation of the element content. The instances of this class
+ * can be arranged into the linked list, representing a BNF expression.
+ * The content model is constructed as a branched tree structure in the
+ * following way:
+ * <pre>
+ * a = new ContentModel('+', A, null); // a reprensents A+
+ * b = new ContentModel('&amp;', B, a); // b represents B &amp; A+
+ * c = new ContentModel('*', b, null); // c represents ( B &amp; A+) *
+ * d = new ContentModel('|', new ContentModel('*', A, null),
+ * new ContentModel('?', B, null)); // d represents ( A* | B? )
+ * </pre>
+ * where the valid operations are:
+ * <ul>
+ * <li><code>E* </code> E occurs zero or more times</li>
+ * <li><code>E+ </code> E occurs one or more times</li>
+ * <li><code>E? </code> E occurs once or not atl all</li>
+ * <li><code>A,B</code> A occurs before B</li>
+ * <li><code>A|B</code> both A and B are permitted in any order.
+ * The '|' alone does not permit the repetetive occurence of A or B
+ * (use <code>(A|B)*</code>.</li>
+ * <li><code>A&amp;B</code> both A and B must occur once (in any order)</li>
+ * </ul>
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public final class ContentModel
+ implements Serializable
+{
+ /** Use serialVersionUID for interoperability. */
+ private static final long serialVersionUID = -1130825523866321257L;
+
+ /**
+ * The next content model model ( = pointer to the next element of
+ * the linked list) for the binary expression (',','&amp;' or '|'). Null
+ * for the last element in the list.
+ */
+ public ContentModel next;
+
+ /**
+ * The document content, containing either Element or the enclosed
+ * content model (that would be in the parentheses in BNF expression).
+ */
+ public Object content;
+
+ /**
+ * Specifies the BNF operation between this node and the node,
+ * stored in the field <code>next</code> (or for this node, if it is
+ * an unary operation.
+ */
+ public int type;
+
+ /**
+ * Create a content model initializing all fields to default values.
+ */
+ public ContentModel()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Create a content model, consisting of the single element.
+ * Examples:
+ *<code>
+ * a = new ContentModel('+', A, null); // a reprensents A+
+ * b = new ContentModel('&amp;', B, a); // b represents B &amp; A+
+ * c = new ContentModel('*', b, null); // c represents ( B &amp; A+) *
+ * d = new ContentModel('|', A,
+ * new ContentModel('?',b, null);
+ * // d represents
+ * </code>
+ */
+ public ContentModel(Element a_content)
+ {
+ content = a_content;
+ }
+
+ /**
+ * Create a content model, involving expression of the given type.
+ * @param a_type The expression operation type ('*','?' or '+'
+ * @param a_content The content for that the expression is applied.
+ */
+ public ContentModel(int a_type, ContentModel a_content)
+ {
+ content = a_content;
+ type = a_type;
+ }
+
+ /**
+ * Create a content model, involving binary expression of the given type.
+ * @param a_type The expression operation type ( ',', '|' or '&amp;').
+ * @param a_content The content of the left part of the expression.
+ * @param a_next The content model, representing the right part of the
+ * expression.
+ */
+ public ContentModel(int a_type, Object a_content, ContentModel a_next)
+ {
+ content = a_content;
+ type = a_type;
+ next = a_next;
+ }
+
+ /**
+ * Adds all list elements to the given vector, ignoring the
+ * operations between the elements. The old vector values are not
+ * discarded.
+ * @param elements - a vector to add the values to.
+ */
+ public void getElements(Vector<Element> elements)
+ {
+ ContentModel c = this;
+
+ while (c != null)
+ {
+ // FIXME: correct?
+ if (c.content instanceof Element)
+ elements.add((Element) c.content);
+ c = c.next;
+ }
+ }
+
+ /**
+ * Checks if the content model matches an empty input stream.
+ * The empty content is created using SGML DTD keyword EMPTY.
+ * The empty model is a model with the content field equal to null.
+ *
+ * @return true if the content field is equal to null.
+ */
+ public boolean empty()
+ {
+ return content == null;
+ }
+
+ /**
+ * Get the element, stored in the <code>next.content</code>.
+ * The method is programmed as the part of the standard API, but not
+ * used in this implementation.
+ * @return the value of the field <code>next</code>.
+ */
+ public Element first()
+ {
+ return (Element) next.content;
+ }
+
+ /**
+ * Checks if this object can potentially be the first token in the
+ * ContenModel list. The method is programmed as the part of the
+ * standard API, but not used in this implementation.
+ */
+ public boolean first(Object token)
+ {
+ ContentModel c = this;
+ while (c.next != null)
+ {
+ if (c.content != null && c.content.toString().equals(token.toString()) &&
+ c.type != ','
+ )
+
+ // Agree if the operation with the preceeding element
+ // is not the comma operation.
+ return true;
+ c = c.next;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a string representation (an expression) of this content model.
+ * The expression has BNF-like syntax, except the absence of the
+ * unary operator is additionally indicated by " ' ". It is
+ * advisable to check the created models for correctness using this
+ * method.
+ */
+ public String toString()
+ {
+ return transformer.transform(this).toString();
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/DTD.java b/libjava/classpath/javax/swing/text/html/parser/DTD.java
new file mode 100644
index 000000000..09b50fee7
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/DTD.java
@@ -0,0 +1,609 @@
+/* DTD.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.parser;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.BitSet;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * <p>Representation or the SGML DTD document.
+ * Provides basis for describing a syntax of the
+ * HTML documents. The fields of this class are NOT initialized in
+ * constructor. You need to do this separately before passing this data
+ * structure to the HTML parser. The subclasses with the fields, pre-
+ * initialized, for example, for HTML 4.01, can be available only between
+ * the implementation specific classes
+ * ( for example, {@link gnu.javax.swing.text.html.parser.HTML_401F }
+ * in this implementation).</p>
+ * <p>
+ * If you need more information about SGML DTD documents,
+ * the author suggests to read SGML tutorial on
+ * <a href="http://www.w3.org/TR/WD-html40-970708/intro/sgmltut.html"
+ * >http://www.w3.org/TR/WD-html40-970708/intro/sgmltut.html</a>.
+ * We also recommend Goldfarb C.F (1991) <i>The SGML Handbook</i>,
+ * Oxford University Press, 688 p, ISBN: 0198537379.
+ * </p>
+ * <p>
+ * Warning: the html, head and other tag fields will only be automatically
+ * assigned if the VM has the correctly implemented reflection mechanism.
+ * As these fields are not used anywhere in the implementation, not
+ * exception will be thrown in the opposite case.
+ * </p>
+ *
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public class DTD
+ implements DTDConstants
+{
+ /**
+ * The version of the persistent data format.
+ * @specnote This was made <code>final</code> in 1.5.
+ */
+ public static final int FILE_VERSION = 1;
+
+ /**
+ * The table of existing available DTDs.
+ */
+ static Hashtable<String,DTD> dtdHash = new Hashtable<String,DTD>();
+
+ /**
+ * The applet element for this DTD.
+ */
+ public Element applet;
+
+ /**
+ * The base element for this DTD.
+ */
+ public Element base;
+
+ /**
+ * The body element for this DTD.
+ */
+ public Element body;
+
+ /**
+ * The head element for this DTD.
+ */
+ public Element head;
+
+ /**
+ * The html element for this DTD.
+ */
+ public Element html;
+
+ /**
+ * The isindex element of for this DTD.
+ */
+ public Element isindex;
+
+ /**
+ * The meta element for this DTD.
+ */
+ public Element meta;
+
+ /**
+ * The p element for this DTD.
+ */
+ public Element p;
+
+ /**
+ * The param element for this DTD.
+ */
+ public Element param;
+
+ /**
+ * The pcdata for this DTD.
+ */
+ public Element pcdata;
+
+ /**
+ * The title element for this DTD.
+ */
+ public Element title;
+
+ /**
+ * The element for accessing all DTD elements by name.
+ */
+ public Hashtable<String,Element> elementHash =
+ new Hashtable<String,Element>();
+
+ /**
+ * The entity table for accessing all DTD entities by name.
+ */
+ public Hashtable<Object, Entity> entityHash = new Hashtable<Object, Entity>();
+
+ /**
+ * The name of this DTD.
+ */
+ public String name;
+
+ /**
+ * Contains all elements in this DTD. The
+ * javax.swing.text.html.parser.Element#index field of all elements
+ * in this vector is set to the element position in this vector.
+ */
+ public Vector<Element> elements = new Vector<Element>();
+
+ /** Create a new DTD with the specified name. */
+ protected DTD(String a_name)
+ {
+ name = a_name;
+ }
+
+ /** Get this DTD by name. The current implementation
+ * only looks in the internal table of DTD documents. If no corresponding
+ * entry is found, the new entry is created, placed into
+ * the table and returned. */
+ public static DTD getDTD(String name)
+ throws IOException
+ {
+ DTD d = dtdHash.get(name);
+
+ if (d == null)
+ {
+ d = new DTD(name);
+ dtdHash.put(d.name, d);
+ }
+
+ return d;
+ }
+
+ /**
+ * Get the element by the element name. If the element is not yet
+ * defined, it is newly created and placed into the element table.
+ * If the element name matches (ingoring case) a public non static
+ * element field in this class, this field is assigned to the value
+ * of the newly created element.
+ */
+ public Element getElement(String element_name)
+ {
+ return newElement(element_name);
+ }
+
+ /**
+ * Get the element by the value of its
+ * {@link javax.swing.text.html.parser.Element#index} field.
+ */
+ public Element getElement(int index)
+ {
+ return elements.get(index);
+ }
+
+ /**
+ * Get the entity with the given identifier.
+ * @param id that can be returned by
+ * {@link javax.swing.text.html.parser.Entity#name2type(String an_entity)}
+ * @return The entity from this DTD or null if there is no entity with
+ * such id or such entity is not present in the table of this instance.
+ */
+ public Entity getEntity(int id)
+ {
+ String name = Entity.mapper.get(id);
+
+ if (name != null)
+ return entityHash.get(name);
+ else
+ return null;
+ }
+
+ /**
+ * Get the named entity by its name.
+ */
+ public Entity getEntity(String entity_name)
+ {
+ return entityHash.get(entity_name);
+ }
+
+ /**
+ * Get the name of this instance of DTD
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Creates, adds into the entity table and returns the
+ * character entity like <code>&amp;lt;</code>
+ * (means '<code>&lt;</code>' );
+ * @param name The entity name (without heading &amp; and closing ;)
+ * @param type The entity type
+ * @param character The entity value (single character)
+ * @return The created entity
+ */
+ public Entity defEntity(String name, int type, int character)
+ {
+ Entity e = newEntity(name, type);
+ e.data = new char[] { (char) character };
+ return e;
+ }
+
+ /**
+ * Define the attributes for the element with the given name.
+ * If the element is not exist, it is created.
+ * @param forElement
+ * @param attributes
+ */
+ public void defineAttributes(String forElement, AttributeList attributes)
+ {
+ Element e = elementHash.get(forElement.toLowerCase());
+
+ if (e == null)
+ e = newElement(forElement);
+
+ e.atts = attributes;
+ }
+
+ /**
+ * Defines the element and adds it to the element table. Sets the
+ * <code>Element.index</code> field to the value, unique for this
+ * instance of DTD. If the element with the given name already exists,
+ * replaces all other its settings by the method argument values.
+ * @param name the name of the element
+ * @param type the type of the element
+ * @param headless true if the element needs no starting tag
+ * (should not occur in HTML).
+ * @param tailless true if the element needs no ending tag (like
+ * <code>&lt;hr&gt;</code>
+ * @param content the element content
+ * @param exclusions the set of elements that must not occur inside
+ * this element. The <code>Element.index</code> value defines which
+ * bit in this bitset corresponds to that element.
+ * @param inclusions the set of elements that can occur inside this
+ * element. the <code>Element.index</code> value defines which
+ * bit in this bitset corresponds to that element.
+ * @param attributes the element attributes.
+ * @return the newly defined element.
+ */
+ public Element defineElement(String name, int type, boolean headless,
+ boolean tailless, ContentModel content,
+ BitSet exclusions, BitSet inclusions,
+ AttributeList attributes
+ )
+ {
+ Element e = newElement(name);
+ e.type = type;
+ e.oStart = headless;
+ e.oEnd = tailless;
+ e.content = content;
+ e.exclusions = exclusions;
+ e.inclusions = inclusions;
+ e.atts = attributes;
+
+ return e;
+ }
+
+ /**
+ * Creates, intializes and adds to the entity table the new
+ * entity.
+ * @param name the name of the entity
+ * @param type the type of the entity
+ * @param data the data section of the entity
+ * @return the created entity
+ */
+ public Entity defineEntity(String name, int type, char[] data)
+ {
+ Entity e = newEntity(name, type);
+ e.data = data;
+
+ return e;
+ }
+
+ /** Place this DTD into the DTD table. */
+ public static void putDTDHash(String name, DTD dtd)
+ {
+ dtdHash.put(name, dtd);
+ }
+
+ /**
+ * <p>Reads DTD from an archived format. This format is not standardized
+ * and differs between implementations.</p><p> This implementation
+ * reads and defines all entities and elements using
+ * ObjectInputStream. The elements and entities can be written into the
+ * stream in any order. The objects other than elements and entities
+ * are ignored.</p>
+ * @param stream A data stream to read from.
+ * @throws java.io.IOException If one is thrown by the input stream
+ */
+ public void read(DataInputStream stream)
+ throws java.io.IOException
+ {
+ ObjectInputStream oi = new ObjectInputStream(stream);
+ Object def;
+ try
+ {
+ while (true)
+ {
+ def = oi.readObject();
+ if (def instanceof Element)
+ {
+ Element e = (Element) def;
+ elementHash.put(e.name.toLowerCase(), e);
+ assignField(e);
+ }
+ else if (def instanceof Entity)
+ {
+ Entity e = (Entity) def;
+ entityHash.put(e.name, e);
+ }
+ }
+ }
+ catch (ClassNotFoundException ex)
+ {
+ throw new IOException(ex.getMessage());
+ }
+ catch (EOFException ex)
+ {
+ // ok EOF
+ }
+ }
+
+ /**
+ * Returns the name of this instance of DTD.
+ */
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * Creates and returns new attribute (not an attribute list).
+ * @param name the name of this attribute
+ * @param type the type of this attribute (FIXED, IMPLIED or
+ * REQUIRED from <code>DTDConstants</code>).
+ * @param modifier the modifier of this attribute
+ * @param default_value the default value of this attribute
+ * @param allowed_values the allowed values of this attribute. The multiple
+ * possible values in this parameter are supposed to be separated by
+ * '|', same as in SGML DTD <code>&lt;!ATTLIST </code>tag. This parameter
+ * can be null if no list of allowed values is specified.
+ * @param atts the previous attribute of this element. This is
+ * placed to the field
+ * {@link javax.swing.text.html.parser.AttributeList#next },
+ * creating a linked list.
+ * @return The attributes.
+ */
+ protected AttributeList defAttributeList(String name, int type, int modifier,
+ String default_value,
+ String allowed_values,
+ AttributeList atts
+ )
+ {
+ AttributeList al = new AttributeList(name);
+ al.modifier = modifier;
+ al.value = default_value;
+ al.next = atts;
+
+ if (allowed_values != null)
+ {
+ StringTokenizer st = new StringTokenizer(allowed_values, " \t|");
+ Vector<String> v = new Vector<String>(st.countTokens());
+
+ while (st.hasMoreTokens())
+ v.add(st.nextToken());
+
+ al.values = v;
+ }
+
+ return al;
+ }
+
+ /**
+ * Creates a new content model.
+ * @param type specifies the BNF operation for this content model.
+ * The valid operations are documented in the
+ * {@link javax.swing.text.html.parser.ContentModel#type }.
+ * @param content the content of this content model
+ * @param next if the content model is specified by BNF-like
+ * expression, contains the rest of this expression.
+ * @return The newly created content model.
+ */
+ protected ContentModel defContentModel(int type, Object content,
+ ContentModel next
+ )
+ {
+ ContentModel model = new ContentModel();
+ model.type = type;
+ model.next = next;
+ model.content = content;
+
+ return model;
+ }
+
+ /**
+ * Defines a new element and adds it to the element table.
+ * If the element alredy exists,
+ * overrides it settings with the specified values.
+ * @param name the name of the new element
+ * @param type the type of the element
+ * @param headless true if the element needs no starting tag
+ * @param tailless true if the element needs no closing tag
+ * @param content the element content.
+ * @param exclusions the elements that must be excluded from the
+ * content of this element, in all levels of the hierarchy.
+ * @param inclusions the elements that can be included as the
+ * content of this element.
+ * @param attributes the element attributes.
+ * @return the created or updated element.
+ */
+ protected Element defElement(String name, int type, boolean headless,
+ boolean tailless, ContentModel content,
+ String[] exclusions, String[] inclusions,
+ AttributeList attributes
+ )
+ {
+ // compute the bit sets
+ BitSet exclude = bitSet(exclusions);
+ BitSet include = bitSet(inclusions);
+
+ Element e =
+ defineElement(name, type, headless, tailless, content, exclude, include,
+ attributes
+ );
+
+ return e;
+ }
+
+ /**
+ * Creates, intializes and adds to the entity table the new
+ * entity.
+ * @param name the name of the entity
+ * @param type the type of the entity
+ * @param data the data section of the entity
+ * @return the created entity
+ */
+ protected Entity defEntity(String name, int type, String data)
+ {
+ Entity e = newEntity(name, type);
+ e.data = data.toCharArray();
+
+ return e;
+ }
+
+ private void assignField(Element e)
+ {
+ String element_name = e.name;
+ try
+ {
+ // Assign the field via reflection.
+ Field f = getClass().getField(element_name.toLowerCase());
+ if ((f.getModifiers() & Modifier.PUBLIC) != 0)
+ if ((f.getModifiers() & Modifier.STATIC) == 0)
+ if (f.getType().isAssignableFrom(e.getClass()))
+ f.set(this, e);
+ }
+ catch (IllegalAccessException ex)
+ {
+ unexpected(ex);
+ }
+ catch (NoSuchFieldException ex)
+ {
+ // This is ok.
+ }
+
+ // Some virtual machines may still lack the proper
+ // implementation of reflection. As the tag fields
+ // are not used anywhere in this implementation,
+ // (and this class is also rarely used by the end user),
+ // it may be better not to crash everything by throwing an error
+ // for each case when the HTML parsing is required.
+ catch (Throwable t)
+ {
+ // This VM has no reflection mechanism implemented!
+ if (t instanceof OutOfMemoryError)
+ throw (Error) t;
+ }
+ }
+
+ /**
+ * Create the bit set for this array of elements.
+ * The unknown elements are automatically defined and added
+ * to the element table.
+ * @param elements
+ * @return The bit set.
+ */
+ private BitSet bitSet(String[] elements)
+ {
+ BitSet b = new BitSet();
+
+ for (int i = 0; i < elements.length; i++)
+ {
+ Element e = getElement(elements [ i ]);
+
+ if (e == null)
+ e = newElement(elements [ i ]);
+
+ b.set(e.index);
+ }
+
+ return b;
+ }
+
+ /**
+ * Find the element with the given name in the element table.
+ * If not find, create a new element with this name and add to the
+ * table.
+ * @param name the name of the element
+ * @return the found or created element.
+ */
+ private Element newElement(String name)
+ {
+ Element e = elementHash.get(name.toLowerCase());
+
+ if (e == null)
+ {
+ e = new Element();
+ e.name = name;
+ e.index = elements.size();
+ elements.add(e);
+ elementHash.put(e.name.toLowerCase(), e);
+ assignField(e);
+ }
+ return e;
+ }
+
+ /**
+ * Creates and adds to the element table the entity with an
+ * unitialized data section. Used internally.
+ * @param name the name of the entity
+ * @param type the type of the entity, a bitwise combination
+ * of GENERAL, PARAMETER, SYSTEM and PUBLIC.
+ *
+ * @return the created entity
+ */
+ private Entity newEntity(String name, int type)
+ {
+ Entity e = new Entity(name, type, null);
+ entityHash.put(e.name, e);
+ return e;
+ }
+
+ private void unexpected(Exception ex)
+ {
+ throw new Error("This should never happen, report a bug", ex);
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/DTDConstants.java b/libjava/classpath/javax/swing/text/html/parser/DTDConstants.java
new file mode 100644
index 000000000..75e7afb4d
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/DTDConstants.java
@@ -0,0 +1,292 @@
+/* DTDConstants.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.parser;
+
+/**
+ * <p>This class defines the SGML basic types, used for describing HTML 4.01
+ * at <a href="http://www.w3.org/TR/html4/types.html"
+ * >http://www.w3.org/TR/html4/types.html</a>. Not all constants,
+ * defined here, are actually used in HTML 4.01 SGML specification. Some others
+ * are defined just as part of the required implementation.
+ * </p>
+ * <p>
+ * If you need more information about SGML DTD documents,
+ * the author suggests to read SGML tutorial on
+ * <a href="http://www.w3.org/TR/WD-html40-970708/intro/sgmltut.html"
+ * >http://www.w3.org/TR/WD-html40-970708/intro/sgmltut.html</a>.
+ * We also recommend Goldfarb C.F (1991) <i>The SGML Handbook</i>,
+ * Oxford University Press, 688 p, ISBN: 0198537379.
+ * </p>
+ *
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public interface DTDConstants
+{
+ /* ----- The data types, used in HTML 4.01 SGML definition: ---- */
+
+ /**
+ * The CDATA (Character data) constant, specifes the content model,
+ * consisting of characters only. In SGML for HTML 4.01, the character
+ * entities must be replaced by characters, the line feeds must be
+ * ignored and any number of the subsequent carriage returns or tabs
+ * must be replaced by a single space.
+ */
+ int CDATA = 1;
+
+ /**
+ * The EMPTY constant, means the element with no content.
+ */
+ int EMPTY = 17;
+
+ /**
+ * The ID constant, means that the token is the unique identifier.
+ * This identifier can be referenced by attribute with value of IDREF.
+ * The identifier must begin with letter, followed by any number of
+ * letters, digits, hyphens, underscores, colons and periods.
+ */
+ int ID = 4;
+
+ /**
+ * The IDREF constant, specifies reference to a valid ID within
+ * the document.
+ */
+ int IDREF = 5;
+
+ /**
+ * The IDREFS constant, a space separated list of IDREFs
+ */
+ int IDREFS = 6;
+
+ /**
+ * The NAME constant, means the token that
+ * must begin with letter, followed by any number of
+ * letters, digits, hyphens, underscores, colons and periods.
+ */
+ int NAME = 7;
+
+ /**
+ * The NAMES constant, specifies a space separated of NAMEs.
+ */
+ int NAMES = 8;
+
+ /**
+ * The NMTOKEN constant, specifies the attribute, consisting of
+ * characters that can be either digits or alphabetic characters).
+ */
+ int NMTOKEN = 9;
+
+ /**
+ * The NMTOKENS constant, specifies a list of NMTOKENs.
+ */
+ int NMTOKENS = 10;
+
+ /**
+ * The NOTATION constant, a previously defined data type.
+ */
+ int NOTATION = 11;
+
+ /**
+ * The NUMBER constant (means that the attribute consists of at least
+ * one decimal digit).
+ */
+ int NUMBER = 12;
+
+ /**
+ * The NUMBERS constant, specifies a space separated list of NUMBERs.
+ */
+ int NUMBERS = 13;
+
+ /**
+ * The NUTOKEN constant.
+ */
+ int NUTOKEN = 14;
+
+ /**
+ * The NUTOKENS constant.
+ */
+ int NUTOKENS = 15;
+
+ /* -------
+ The entity scope constants.
+ As these four constants are combined with the bitwise OR,
+ they are defined in the hexadecimal notation.
+ The reason of setting the two bits at once (for PUBLIC and SYSTEM)
+ is probably historical. ----- */
+
+ /**
+ * The PUBLIC constant, specifies the public entity. The PUBLIC entities
+ * are assumed to be known to many systems so that a full declaration
+ * need not be transmitted. For example,
+ * &lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN"&gt;
+ */
+ int PUBLIC = 0xA;
+
+ /**
+ * The SYSTEM constant, specifies the system entitiy. The system entities
+ * are assumed to be known but require the clear identifer
+ * (like the file path), where they can be found in the system.
+ * For example, <code>
+ * &lt;DOCTYPE html SYSTEM "/path/to/file.dtd"&gt; </code>.
+ */
+ int SYSTEM = 0x11;
+
+ /**
+ * The PARAMETER constant, specifies that entity is only valid
+ * inside SGML DTD scope.
+ */
+ int PARAMETER = 0x40000;
+
+ /**
+ * The GENERAL constant, specifies theat the entity is valid in the
+ * whole HTML document scope.
+ */
+ int GENERAL = 0x10000;
+
+ /* ---- The constants, defining if the element attribute is required,
+ fixed or implied. ---- */
+
+ /**
+ * The attribute modifier #REQUIRED constant, indicates that the
+ * value must be supplied.
+ */
+ int REQUIRED = 2;
+
+ /**
+ * The attribute modifier #FIXED constant, means that the attribute has
+ * the fixed value that cannot be changed.
+ */
+ int FIXED = 1;
+
+ /**
+ * The attribute modifier #IMPLIED constant,
+ * indicating that for this attribute the user agent must provide
+ * the value itself.
+ */
+ int IMPLIED = 5;
+
+ /**
+ * The attribute modifier #CURRENT constant, specifies the value
+ * that at any point in the document is the last value supplied for
+ * that element. A value is required to be supplied for the first
+ * occurrence of an element
+ */
+ int CURRENT = 3;
+
+ /**
+ * The attribute modifier #CONREF constant, specifies the IDREF value of
+ * the reference to content in another location of the document.
+ * The element with this attribute is empty, the content from
+ * that another location must be used instead.
+ */
+ int CONREF = 4;
+
+ /* ----- Constants, defining if the element
+ start and end tags are required. ---- */
+
+ /**
+ * The STARTTAG, meaning that the element needs a starting tag.
+ */
+ int STARTTAG = 13;
+
+ /**
+ * The ENDTAG constant, meaning that the element needs a closing tag.
+ */
+ int ENDTAG = 14;
+
+ /* ----- Other constants: ----- */
+
+ /**
+ * The ANY constant, specifies
+ * an attribute, consisting from arbitrary characters.
+ */
+ int ANY = 19;
+
+ /**
+ * The DEFAULT constant, specifies the default value.
+ */
+ int DEFAULT = 131072;
+
+ /**
+ * The ENTITIES constant (list of ENTITYes)
+ */
+ int ENTITIES = 3;
+
+ /**
+ * The ENTITY constant, meaning the numeric or symbolic name of some
+ * HTML data.
+ */
+ int ENTITY = 2;
+
+ /**
+ * The MD constant.
+ */
+ int MD = 16;
+
+ /**
+ * The MODEL constant.
+ */
+ int MODEL = 18;
+
+ /**
+ * The MS constant.
+ */
+ int MS = 15;
+
+ /**
+ * The PI (Processing Instruction) constant, specifies a processing
+ * instruction. Processing instructions are used to embed information
+ * intended for specific applications.
+ */
+ int PI = 12;
+
+ /**
+ * The RCDATA constant (Entity References and Character Data), specifies
+ * the content model, consisting of characters AND entities. The
+ * "&lt;" is threated as an ordinary character, but
+ * "<code>&amp;name;</code>" still means the general entity with
+ * the given name.
+ */
+ int RCDATA = 16;
+
+ /**
+ * The SDATA constant. Means that the value contains the entity name
+ * and the replacement value of a character entity reference.
+ */
+ int SDATA = 11;
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java
new file mode 100644
index 000000000..f717d69cb
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/DocumentParser.java
@@ -0,0 +1,268 @@
+/* DocumentParser.java -- A parser for HTML documents.
+ 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.parser;
+
+import javax.swing.text.html.parser.Parser;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.html.HTMLEditorKit;
+
+/**
+ * <p>A simple error-tolerant HTML parser that uses a DTD document
+ * to access data on the possible tokens, arguments and syntax.</p>
+ * <p> The parser reads an HTML content from a Reader and calls various
+ * notifying methods (which should be overridden in a subclass)
+ * when tags or data are encountered.</p>
+ * <p>Some HTML elements need no opening or closing tags. The
+ * task of this parser is to invoke the tag handling methods also when
+ * the tags are not explicitly specified and must be supposed using
+ * information, stored in the DTD.
+ * For example, parsing the document
+ * <p>&lt;table&gt;&lt;tr&gt;&lt;td&gt;a&lt;td&gt;b&lt;td&gt;c&lt;/tr&gt; <br>
+ * will invoke exactly the handling methods exactly in the same order
+ * (and with the same parameters) as if parsing the document: <br>
+ * <em>&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;table&gt;&lt;
+ * tbody&gt;</em>&lt;tr&gt;&lt;td&gt;a<em>&lt;/td&gt;</em>&lt;td&gt;b<em>
+ * &lt;/td&gt;</em>&lt;td&gt;c<em>&lt;/td&gt;&lt;/tr&gt;</em>&lt;
+ * <em>/tbody&gt;&lt;/table&gt;&lt;/body&gt;&lt;/html&gt;</em></p>
+ * (supposed tags are given in italics). The parser also supports
+ * obsolete elements of HTML syntax.<p>
+ * </p>
+ * In this implementation, DocumentParser is directly derived from its
+ * ancestor without changes of functionality.
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public class DocumentParser
+ extends Parser
+ implements DTDConstants
+{
+ /**
+ * The enclosed working parser class.
+ */
+ private class gnuParser
+ extends gnu.javax.swing.text.html.parser.support.Parser
+ {
+ private gnuParser(DTD d)
+ {
+ super(d);
+ }
+
+ protected final void handleComment(char[] comment)
+ {
+ parser.handleComment(comment);
+ callBack.handleComment(comment, hTag.where.startPosition);
+ }
+
+ protected final void handleEmptyTag(TagElement tag)
+ throws javax.swing.text.ChangedCharSetException
+ {
+ parser.handleEmptyTag(tag);
+ callBack.handleSimpleTag(tag.getHTMLTag(), getAttributes(),
+ hTag.where.startPosition
+ );
+ }
+
+ protected final void handleEndTag(TagElement tag)
+ {
+ parser.handleEndTag(tag);
+ callBack.handleEndTag(tag.getHTMLTag(), hTag.where.startPosition);
+ }
+
+ protected final void handleError(int line, String message)
+ {
+ parser.handleError(line, message);
+ callBack.handleError(message, hTag.where.startPosition);
+ }
+
+ protected final void handleStartTag(TagElement tag)
+ {
+ parser.handleStartTag(tag);
+ SimpleAttributeSet attributes = gnu.getAttributes();
+
+ if (tag.fictional())
+ attributes.addAttribute(HTMLEditorKit.ParserCallback.IMPLIED,
+ Boolean.TRUE
+ );
+
+ callBack.handleStartTag(tag.getHTMLTag(), attributes,
+ hTag.where.startPosition
+ );
+ }
+
+ protected final void handleText(char[] text)
+ {
+ parser.handleText(text);
+ callBack.handleText(text, hTag.where.startPosition);
+ }
+
+ DTD getDTD()
+ {
+ return dtd;
+ }
+ }
+
+ /**
+ * This field is used to access the identically named
+ * methods of the outer class.
+ * This is package-private to avoid an accessor method.
+ */
+ DocumentParser parser = this;
+
+ /**
+ * The callback.
+ * This is package-private to avoid an accessor method.
+ */
+ HTMLEditorKit.ParserCallback callBack;
+
+ /**
+ * The reference to the working class of HTML parser that is
+ * actually used to parse the document.
+ * This is package-private to avoid an accessor method.
+ */
+ gnuParser gnu;
+
+ /**
+ * Creates a new parser that uses the given DTD to access data on the
+ * possible tokens, arguments and syntax. There is no single - step way
+ * to get a default DTD; you must either refer to the implementation -
+ * specific packages, write your own DTD or obtain the working instance
+ * of parser in other way, for example, by calling
+ * {@link javax.swing.text.html.HTMLEditorKit#getParser()}.
+ *
+ * @param a_dtd a DTD to use.
+ */
+ public DocumentParser(DTD a_dtd)
+ {
+ super(a_dtd);
+ gnu = new gnuParser(a_dtd);
+ }
+
+ /**
+ * Parses the HTML document, calling methods of the provided
+ * callback. This method must be multithread - safe.
+ * @param reader The reader to read the HTML document from
+ * @param aCallback The callback that is notifyed about the presence
+ * of HTML elements in the document.
+ * @param ignoreCharSet If thrue, any charset changes during parsing
+ * are ignored.
+ * @throws java.io.IOException
+ */
+ public void parse(Reader reader, HTMLEditorKit.ParserCallback aCallback,
+ boolean ignoreCharSet
+ )
+ throws IOException
+ {
+ callBack = aCallback;
+ gnu.parse(reader);
+
+ callBack.handleEndOfLineString(gnu.getEndOfLineSequence());
+ try
+ {
+ callBack.flush();
+ }
+ catch (BadLocationException ex)
+ {
+ // Convert this into the supported type of exception.
+ throw new IOException(ex.getMessage());
+ }
+ }
+
+ /**
+ * Handle HTML comment. The default method returns without action.
+ * @param comment the comment being handled
+ */
+ protected void handleComment(char[] comment)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * Handle the tag with no content, like &lt;br&gt;. The method is
+ * called for the elements that, in accordance with the current DTD,
+ * has an empty content.
+ * @param tag the tag being handled.
+ * @throws javax.swing.text.ChangedCharSetException
+ */
+ protected void handleEmptyTag(TagElement tag)
+ throws javax.swing.text.ChangedCharSetException
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * The method is called when the HTML closing tag ((like &lt;/table&gt;)
+ * is found or if the parser concludes that the one should be present
+ * in the current position.
+ * @param tag The tag being handled
+ */
+ protected void handleEndTag(TagElement tag)
+ {
+ // This default implementation does nothing.
+ }
+
+ /* Handle error that has occured in the given line. */
+ protected void handleError(int line, String message)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * The method is called when the HTML opening tag ((like &lt;table&gt;)
+ * is found or if the parser concludes that the one should be present
+ * in the current position.
+ * @param tag The tag being handled
+ */
+ protected void handleStartTag(TagElement tag)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * Handle the text section.
+ * @param text a section text.
+ */
+ protected void handleText(char[] text)
+ {
+ // This default implementation does nothing.
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/Element.java b/libjava/classpath/javax/swing/text/html/parser/Element.java
new file mode 100644
index 000000000..c07c07f54
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/Element.java
@@ -0,0 +1,317 @@
+/* Element.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.parser;
+
+import gnu.javax.swing.text.html.parser.support.gnuStringIntMapper;
+
+import java.io.Serializable;
+
+import java.util.BitSet;
+
+/**
+ * <p>
+ * Stores the element information, obtained by parsing SGML DTD
+ * tag <code>&lt;!ELEMENT .. &gt;</code>. This class has no public
+ * constructor and can only be instantiated using the
+ * {@link javax.swing.text.html.parser.DTD } methods</p>
+ *
+ * <p>SGML defines elements that represent structures or
+ * behavior. An element typically consists of a start tag, content, and an
+ * end tag. Hence the elements are not tags. The HTML 4.0 definition specifies
+ * that some elements are not required to have the end tags. Also, some
+ * HTML elements (like <code>&lt;hr&gt;</code>) have no content. Element names
+ * are case sensitive.</p>
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public final class Element
+ implements DTDConstants, Serializable
+{
+ /**
+ * Package level mapper between type names and they string values.
+ */
+ static final gnuStringIntMapper mapper =
+ new gnuStringIntMapper()
+ {
+ protected void create()
+ {
+ add("CDATA", DTDConstants.CDATA);
+ add("RCDATA", DTDConstants.RCDATA);
+ add("EMPTY", DTDConstants.EMPTY);
+ add("ANY", DTDConstants.ANY);
+ }
+ };
+
+ /** Use serialVersionUID for interoperability. */
+ private static final long serialVersionUID = -6717939384601675586L;
+
+ /**
+ * The element attributes.
+ */
+ public AttributeList atts;
+
+ /**
+ * Contains refernces to elements that must NOT occur inside this element,
+ * at any level of hierarchy.
+ */
+ public BitSet exclusions;
+
+ /**
+ * Contains refernces to elements that must CAN occur inside this element,
+ * at any level of hierarchy.
+ */
+ public BitSet inclusions;
+
+ /**
+ * The content model, defining elements, entities and DTD text
+ * that may/may not occur inside this element.
+ */
+ public ContentModel content;
+
+ /**
+ * A field to store additional user data for this Element.
+ */
+ public Object data;
+
+ /**
+ * The element name.
+ */
+ public String name;
+
+ /**
+ * True is this element need not to have the closing tag, false
+ * otherwise. The HTML 4.0 definition specifies
+ * that some elements (like <code>&lt;hr&gt;</code>are
+ * not required to have the end tags.
+ */
+ public boolean oEnd;
+
+ /**
+ * True is this element need not to have the starting tag, false
+ * otherwise. The HTML 4.0 definition specifies
+ * that some elements (like <code>&lt;head&gt;</code> or
+ * <code>&lt;body&gt;</code>) are
+ * not required to have the start tags.
+
+ */
+ public boolean oStart;
+
+ /**
+ * This field contains the unique integer identifier of this Element,
+ * used to refer the element (more exactly, the element flag)
+ * in <code>inclusions</code> and <code>exclusions</code> bit set.
+ */
+ public int index;
+
+ /**
+ * The element type, containing value, defined in DTDConstants.
+ * In this implementation, the element type can be
+ * CDATA, RCDATA, EMPTY or ANY.
+ */
+ public int type;
+
+ /**
+ * The default constructor must have package level access in this
+ * class. Use DTD.defineElement(..) to create an element when required.
+ */
+ Element()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Converts the string representation of the element type
+ * into its unique integer identifier, defined in DTDConstants.
+ * @param a_type A name of the type
+ * @return DTDConstants.CDATA, DTDConstants.RCDATA, DTDConstants.EMPTY,
+ * DTDConstants.ANY or null if the type name is not
+ * "CDATA", "RCDATA", "EMPTY" or "ANY". This function is case sensitive.
+ * @throws NullPointerException if <code>a_type</code> is null.
+ */
+ public static int name2type(String a_type)
+ {
+ return mapper.get(a_type);
+ }
+
+ /**
+ * Get the element attribute by name.
+ * @param attribute the attribute name, case insensitive.
+ * @return the correspoding attribute of this element. The class,
+ * for storing as attribute list, as a single attribute, is used to
+ * store a single attribute in this case.
+ * @throws NullPointerException if the attribute name is null.
+ */
+ public AttributeList getAttribute(String attribute)
+ {
+ AttributeList a = atts;
+
+ while (a != null && !attribute.equalsIgnoreCase(a.name))
+ a = a.next;
+
+ return a;
+ }
+
+ /**
+ * Get the element attribute by its value.
+ * @param a_value the attribute value, case insensitive.
+ * @return the correspoding attribute of this element. The class,
+ * for storing as attribute list, as a single attribute, is used to
+ * store a single attribute in this case. If there are several
+ * attributes with the same value, there is no garranty, which one
+ * is returned.
+ */
+ public AttributeList getAttributeByValue(String a_value)
+ {
+ AttributeList a = atts;
+
+ if (a_value == null)
+ {
+ while (a != null)
+ {
+ if (a.value == null)
+ return a;
+
+ a = a.next;
+ }
+ }
+ else
+ {
+ while (a != null)
+ {
+ if (a.value != null && a_value.equalsIgnoreCase(a.value))
+ return a;
+
+ a = a.next;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all attributes of this document as an attribute list.
+ * @return The attribute list.
+ */
+ public AttributeList getAttributes()
+ {
+ return atts;
+ }
+
+ /**
+ * Get the content model, defining elements, entities and DTD text
+ * that may/may not occur inside this element.
+ */
+ public ContentModel getContent()
+ {
+ return content;
+ }
+
+ /**
+ * Returns true for the element with no content.
+ * Empty elements are defined with the SGML DTD keyword "EMPTY".
+ * @return true if content model field (content) method is equal to
+ * null or its method empty() returns true.
+ */
+ public boolean isEmpty()
+ {
+ return content == null || content.empty();
+ }
+
+ /**
+ * Get the unique integer identifier of this Element,
+ * used to refer the element (more exactly, the element flag)
+ * in <code>inclusions</code> and <code>exclusions</code> bit set.
+ * WARNING: This value may not be the same between different
+ * implementations.
+ */
+ public int getIndex()
+ {
+ return index;
+ }
+
+ /**
+ * Get the element name.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Get the element type.
+ * @return one of the values, defined DTDConstants.
+ * In this implementation, the element type can be
+ * CDATA, RCDATA, EMPTY or ANY.
+ */
+ public int getType()
+ {
+ return type;
+ }
+
+ /**
+ * True is this element need not to have the starting tag, false
+ * otherwise.s element need not to have the closing tag, false
+ * otherwise. The HTML 4.0 definition specifies
+ * that some elements (like <code>&lt;hr&gt;</code>are
+ * not required to have the end tags.
+ */
+ public boolean omitEnd()
+ {
+ return oEnd;
+ }
+
+ /**
+ * True is this element need not to have the closing tag, false
+ * otherwise. The HTML 4.0 definition specifies
+ * that some elements (like <code>&lt;head&gt;</code> or
+ * <code>&lt;body&gt;</code>) are
+ * not required to have the start tags.
+ */
+ public boolean omitStart()
+ {
+ return oStart;
+ }
+
+ /**
+ * Returns the name of this element.
+ */
+ public String toString()
+ {
+ return name;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/Entity.java b/libjava/classpath/javax/swing/text/html/parser/Entity.java
new file mode 100644
index 000000000..d40fb94f3
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/Entity.java
@@ -0,0 +1,183 @@
+/* Entity.java -- Stores information, obtained by parsing SGML DTL
+ * &lt;!ENTITY % .. &gt; tag
+ 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.parser;
+
+import gnu.javax.swing.text.html.parser.support.gnuStringIntMapper;
+
+/**
+ * <p>Stores information, obtained by parsing SGML DTL
+ * &lt;!ENTITY % .. &gt; tag.</p>
+ * <p>
+ * The entity defines some kind of macro that can be used elsewhere in
+ * the document.
+ * When the macro is referred to by the name in the DTD, it is expanded into
+ * a string
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public final class Entity
+ implements DTDConstants
+{
+ /**
+ * Package level mapper between type names and they string values.
+ */
+ final static gnuStringIntMapper mapper =
+ new gnuStringIntMapper()
+ {
+ protected void create()
+ {
+ add("ANY", DTDConstants.ANY);
+ add("CDATA", DTDConstants.CDATA);
+ add("PUBLIC", DTDConstants.PUBLIC);
+ add("SDATA", DTDConstants.SDATA);
+ add("PI", DTDConstants.PI);
+ add("STARTTAG", DTDConstants.STARTTAG);
+ add("ENDTAG", DTDConstants.ENDTAG);
+ add("MS", DTDConstants.MS);
+ add("MD", DTDConstants.MD);
+ add("SYSTEM", DTDConstants.SYSTEM);
+ }
+ };
+
+ /**
+ * The entity name.
+ */
+ public String name;
+
+ /**
+ * The entity data
+ */
+ public char[] data;
+
+ /**
+ * The entity type.
+ */
+ public int type;
+
+ /**
+ * String representation of the entity data.
+ */
+ private String sdata;
+
+ /**
+ * Create a new entity
+ * @param a_name the entity name
+ * @param a_type the entity type
+ * @param a_data the data replacing the entity reference
+ */
+ public Entity(String a_name, int a_type, char[] a_data)
+ {
+ name = a_name;
+ type = a_type;
+ data = a_data;
+ }
+
+ /**
+ * Converts a given string to the corresponding entity type.
+ * @return a value, defined in DTDConstants (one of
+ * PUBLIC, CDATA, SDATA, PI, STARTTAG, ENDTAG, MS, MD, SYSTEM)
+ * or CDATA if the parameter is not a valid entity type.
+ */
+ public static int name2type(String an_entity)
+ {
+ int r = mapper.get(an_entity);
+ return (r == 0) ? DTDConstants.CDATA : r;
+ }
+
+ /**
+ * Get the entity data.
+ */
+ public char[] getData()
+ {
+ return data;
+ }
+
+ /**
+ * Returns true for general entities. Each general entity can be
+ * referenced as <code>&entity-name;</code>. Such entities are
+ * defined by the SGML DTD tag
+ * <code>&lt;!ENTITY <i>name</i> "<i>value</i>"></code>. The general
+ * entities can be used anywhere in the document.
+ */
+ public boolean isGeneral()
+ {
+ return (type & DTDConstants.GENERAL) != 0;
+ }
+
+ /**
+ * Get the entity name.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Returns true for parameter entities. Each parameter entity can be
+ * referenced as <code>&entity-name;</code>. Such entities are
+ * defined by the SGML DTD tag
+ * <code>&lt;!ENTITY % <i>name</i> "<i>value</i>"></code>. The parameter
+ * entities can be used only in SGML context.
+ */
+ public boolean isParameter()
+ {
+ return (type & DTDConstants.PARAMETER) != 0;
+ }
+
+ /**
+ * Returns a data as String
+ */
+ public String getString()
+ {
+ if (sdata == null)
+ sdata = new String(data);
+
+ return sdata;
+ }
+
+ /**
+ * Get the entity type.
+ * @return the value of the {@link #type}.
+ */
+ public int getType()
+ {
+ return type;
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/Parser.java b/libjava/classpath/javax/swing/text/html/parser/Parser.java
new file mode 100644
index 000000000..f3faa2524
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/Parser.java
@@ -0,0 +1,446 @@
+/* Parser.java -- HTML parser
+ 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.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.swing.text.ChangedCharSetException;
+import javax.swing.text.SimpleAttributeSet;
+
+/*
+ * FOR DEVELOPERS: To avoid regression, please run the package test
+ * textsuite/javax.swing.text.html.parser/AllParserTests after your
+ * modifications.
+ */
+
+/**
+ * <p>A simple error-tolerant HTML parser that uses a DTD document
+ * to access data on the possible tokens, arguments and syntax.</p>
+ * <p> The parser reads an HTML content from a Reader and calls various
+ * notifying methods (which should be overridden in a subclass)
+ * when tags or data are encountered.</p>
+ * <p>Some HTML elements need no opening or closing tags. The
+ * task of this parser is to invoke the tag handling methods also when
+ * the tags are not explicitly specified and must be supposed using
+ * information, stored in the DTD.
+ * For example, parsing the document
+ * <p>&lt;table&gt;&lt;tr&gt;&lt;td&gt;a&lt;td&gt;b&lt;td&gt;c&lt;/tr&gt; <br>
+ * will invoke exactly the handling methods exactly in the same order
+ * (and with the same parameters) as if parsing the document: <br>
+ * <em>&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;table&gt;&lt;
+ * tbody&gt;</em>&lt;tr&gt;&lt;td&gt;a<em>&lt;/td&gt;</em>&lt;td&gt;b<em>
+ * &lt;/td&gt;</em>&lt;td&gt;c<em>&lt;/td&gt;&lt;/tr&gt;</em>&lt;
+ * <em>/tbody&gt;&lt;/table&gt;&lt;/body&gt;&lt;/html&gt;</em></p>
+ * (supposed tags are given in italics). The parser also supports
+ * obsolete elements of HTML syntax.<p>
+ * </p>
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public class Parser
+ implements DTDConstants
+{
+ /**
+ * The document template description that will be used to parse the documents.
+ */
+ protected DTD dtd;
+
+ /**
+ * The value of this field determines whether or not the Parser will be
+ * strict in enforcing SGML compatibility. The default value is false,
+ * stating that the parser should do everything to parse and get at least
+ * some information even from the incorrectly written HTML input.
+ */
+ protected boolean strict;
+
+ /**
+ * The package level reference to the working HTML parser in this
+ * implementation.
+ */
+ final gnu.javax.swing.text.html.parser.support.Parser gnu;
+
+ /**
+ * Creates a new parser that uses the given DTD to access data on the
+ * possible tokens, arguments and syntax. There is no single - step way
+ * to get a default DTD; you must either refer to the implementation -
+ * specific packages, write your own DTD or obtain the working instance
+ * of parser in other way, for example, by calling
+ * {@link javax.swing.text.html.HTMLEditorKit#getParser() }.
+ * @param a_dtd A DTD to use.
+ */
+ public Parser(DTD a_dtd)
+ {
+ dtd = a_dtd;
+
+ final Parser j = this;
+
+ gnu =
+ new gnu.javax.swing.text.html.parser.support.Parser(dtd)
+ {
+ protected final void handleComment(char[] comment)
+ {
+ j.handleComment(comment);
+ }
+
+ protected final void handleEOFInComment()
+ {
+ j.handleEOFInComment();
+ }
+
+ protected final void handleEmptyTag(TagElement tag)
+ throws javax.swing.text.ChangedCharSetException
+ {
+ j.handleEmptyTag(tag);
+ }
+
+ protected final void handleStartTag(TagElement tag)
+ {
+ j.handleStartTag(tag);
+ }
+
+ protected final void handleEndTag(TagElement tag)
+ {
+ j.handleEndTag(tag);
+ }
+
+ protected final void handleError(int line, String message)
+ {
+ j.handleError(line, message);
+ }
+
+ protected final void handleText(char[] text)
+ {
+ j.handleText(text);
+ }
+
+ protected final void handleTitle(char[] title)
+ {
+ j.handleTitle(title);
+ }
+
+ protected final void markFirstTime(Element element)
+ {
+ j.markFirstTime(element);
+ }
+
+ protected final void startTag(TagElement tag)
+ throws ChangedCharSetException
+ {
+ j.startTag(tag);
+ }
+
+ protected final void endTag(boolean omitted)
+ {
+ j.endTag(omitted);
+ }
+
+ protected TagElement makeTag(Element element)
+ {
+ return j.makeTag(element);
+ }
+
+ protected TagElement makeTag(Element element, boolean isSupposed)
+ {
+ return j.makeTag(element, isSupposed);
+ }
+ };
+ }
+
+ /**
+ * Parse the HTML text, calling various methods in response to the
+ * occurence of the corresponding HTML constructions.
+ * @param reader The reader to read the source HTML from.
+ * @throws IOException If the reader throws one.
+ */
+ public synchronized void parse(Reader reader)
+ throws IOException
+ {
+ gnu.parse(reader);
+ }
+
+ /**
+ * Parses DTD markup declaration. Currently returns without action.
+ * @return null.
+ * @throws java.io.IOException
+ */
+ public String parseDTDMarkup()
+ throws IOException
+ {
+ return gnu.parseDTDMarkup();
+ }
+
+ /**
+ * Parse DTD document declarations. Currently only parses the document
+ * type declaration markup.
+ * @param strBuff
+ * @return true if this is a valid DTD markup declaration.
+ * @throws IOException
+ */
+ protected boolean parseMarkupDeclarations(StringBuffer strBuff)
+ throws IOException
+ {
+ return gnu.parseMarkupDeclarations(strBuff);
+ }
+
+ /**
+ * Get the attributes of the current tag.
+ * @return The attribute set, representing the attributes of the current tag.
+ */
+ protected SimpleAttributeSet getAttributes()
+ {
+ return gnu.getAttributes();
+ }
+
+ /**
+ * Get the number of the document line being parsed.
+ * @return The current line.
+ */
+ protected int getCurrentLine()
+ {
+ return gnu.hTag.where.beginLine;
+ }
+
+ /**
+ * Get the current position in the document being parsed.
+ * @return The current position.
+ */
+ protected int getCurrentPos()
+ {
+ return gnu.hTag.where.startPosition;
+ }
+
+ /**
+ * The method is called when the HTML end (closing) tag is found or if
+ * the parser concludes that the one should be present in the
+ * current position. The method is called immediatly
+ * before calling the handleEndTag().
+ * @param omitted True if the tag is no actually present in the document,
+ * but is supposed by the parser (like &lt;/html&gt; at the end of the
+ * document).
+ */
+ protected void endTag(boolean omitted)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * Invokes the error handler. The default method in this implementation
+ * finally delegates the call to handleError, also providing the number of the
+ * current line.
+ */
+ protected void error(String msg)
+ {
+ gnu.error(msg);
+ }
+
+ /**
+ * Invokes the error handler. The default method in this implementation
+ * finally delegates the call to error (msg+": '"+invalid+"'").
+ */
+ protected void error(String msg, String invalid)
+ {
+ gnu.error(msg, invalid);
+ }
+
+ /**
+ * Invokes the error handler. The default method in this implementation
+ * finally delegates the call to error (parm1+" "+ parm2+" "+ parm3).
+ */
+ protected void error(String parm1, String parm2, String parm3)
+ {
+ gnu.error(parm1, parm2, parm3);
+ }
+
+ /**
+ * Invokes the error handler. The default method in this implementation
+ * finally delegates the call to error
+ * (parm1+" "+ parm2+" "+ parm3+" "+ parm4).
+ */
+ protected void error(String parm1, String parm2, String parm3, String parm4)
+ {
+ gnu.error(parm1, parm2, parm3, parm4);
+ }
+
+ /**
+ * In this implementation, this is never called and returns without action.
+ */
+ protected void flushAttributes()
+ {
+ gnu.flushAttributes();
+ }
+
+ /**
+ * Handle HTML comment. The default method returns without action.
+ * @param comment The comment being handled
+ */
+ protected void handleComment(char[] comment)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * This is additionally called in when the HTML content terminates
+ * without closing the HTML comment. This can only happen if the
+ * HTML document contains errors (for example, the closing --;gt is
+ * missing. The default method calls the error handler.
+ */
+ protected void handleEOFInComment()
+ {
+ gnu.error("Unclosed comment");
+ }
+
+ /**
+ * Handle the tag with no content, like &lt;br&gt;. The method is
+ * called for the elements that, in accordance with the current DTD,
+ * has an empty content.
+ * @param tag The tag being handled.
+ * @throws javax.swing.text.ChangedCharSetException
+ */
+ protected void handleEmptyTag(TagElement tag)
+ throws ChangedCharSetException
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * The method is called when the HTML closing tag ((like &lt;/table&gt;)
+ * is found or if the parser concludes that the one should be present
+ * in the current position.
+ * @param tag The tag being handled
+ */
+ protected void handleEndTag(TagElement tag)
+ {
+ // This default implementation does nothing.
+ }
+
+ /* Handle error that has occured in the given line. */
+ protected void handleError(int line, String message)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * The method is called when the HTML opening tag ((like &lt;table&gt;)
+ * is found or if the parser concludes that the one should be present
+ * in the current position.
+ * @param tag The tag being handled
+ */
+ protected void handleStartTag(TagElement tag)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * Handle the text section.
+ * <p> For non-preformatted section, the parser replaces
+ * \t, \r and \n by spaces and then multiple spaces
+ * by a single space. Additionaly, all whitespace around
+ * tags is discarded.
+ * </p>
+ * <p> For pre-formatted text (inside TEXAREA and PRE), the parser preserves
+ * all tabs and spaces, but removes <b>one</b> bounding \r, \n or \r\n,
+ * if it is present. Additionally, it replaces each occurence of \r or \r\n
+ * by a single \n.</p>
+ *
+ * @param text A section text.
+ */
+ protected void handleText(char[] text)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * Handle HTML &lt;title&gt; tag. This method is invoked when
+ * both title starting and closing tags are already behind.
+ * The passed argument contains the concatenation of all
+ * title text sections.
+ * @param title The title text.
+ */
+ protected void handleTitle(char[] title)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * Constructs the tag from the given element. In this implementation,
+ * this is defined, but never called.
+ * @param element the base element of the tag.
+ * @return the tag
+ */
+ protected TagElement makeTag(Element element)
+ {
+ return makeTag(element, false);
+ }
+
+ /**
+ * Constructs the tag from the given element.
+ * @param element the tag base {@link javax.swing.text.html.parser.Element}
+ * @param isSupposed true if the tag is not actually present in the
+ * html input, but the parser supposes that it should to occur in
+ * the current location.
+ * @return the tag
+ */
+ protected TagElement makeTag(Element element, boolean isSupposed)
+ {
+ return new TagElement(element, isSupposed);
+ }
+
+ /**
+ * This is called when the tag, representing the given element,
+ * occurs first time in the document.
+ * @param element
+ */
+ protected void markFirstTime(Element element)
+ {
+ // This default implementation does nothing.
+ }
+
+ /**
+ * The method is called when the HTML opening tag ((like &lt;table&gt;)
+ * is found or if the parser concludes that the one should be present
+ * in the current position. The method is called immediately before
+ * calling the handleStartTag.
+ * @param tag The tag
+ */
+ protected void startTag(TagElement tag)
+ throws ChangedCharSetException
+ {
+ // This default implementation does nothing.
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java
new file mode 100644
index 000000000..cdd339b8f
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/ParserDelegator.java
@@ -0,0 +1,207 @@
+/* ParserDelegator.java -- Delegator for ParserDocument.
+ 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.parser;
+
+import gnu.javax.swing.text.html.parser.HTML_401F;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.HTMLEditorKit.ParserCallback;
+
+/**
+ * This class instantiates and starts the working instance of
+ * html parser, being responsible for providing the default DTD.
+ *
+ * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
+ */
+public class ParserDelegator
+ extends javax.swing.text.html.HTMLEditorKit.Parser
+ implements Serializable
+{
+ private class gnuParser
+ extends gnu.javax.swing.text.html.parser.support.Parser
+ {
+ private static final long serialVersionUID = 1;
+
+ private gnuParser(DTD d)
+ {
+ super(d);
+ }
+
+ protected final void handleComment(char[] comment)
+ {
+ callBack.handleComment(comment, hTag.where.startPosition);
+ }
+
+ protected final void handleEmptyTag(TagElement tag)
+ throws javax.swing.text.ChangedCharSetException
+ {
+ callBack.handleSimpleTag(tag.getHTMLTag(), getAttributes(),
+ hTag.where.startPosition
+ );
+ }
+
+ protected final void handleEndTag(TagElement tag)
+ {
+ callBack.handleEndTag(tag.getHTMLTag(), hTag.where.startPosition);
+ }
+
+ protected final void handleError(int line, String message)
+ {
+ callBack.handleError(message, hTag.where.startPosition);
+ }
+
+ protected final void handleStartTag(TagElement tag)
+ {
+ SimpleAttributeSet attributes = gnu.getAttributes();
+
+ if (tag.fictional())
+ attributes.addAttribute(ParserCallback.IMPLIED, Boolean.TRUE);
+
+ callBack.handleStartTag(tag.getHTMLTag(), attributes,
+ hTag.where.startPosition
+ );
+ }
+
+ protected final void handleText(char[] text)
+ {
+ callBack.handleText(text, hTag.where.startPosition);
+ }
+
+ DTD getDTD()
+ {
+ // Accessing the inherited gnu.javax.swing.text.html.parser.support.Parser
+ // field. super. is a workaround, required to support JDK1.3's javac.
+ return super.dtd;
+ }
+ }
+
+ /**
+ * Use serialVersionUID for interoperability.
+ */
+ private static final long serialVersionUID = -1276686502624777206L;
+
+ private static DTD dtd = HTML_401F.getInstance();
+
+ /**
+ * The callback.
+ * This is package-private to avoid an accessor method.
+ */
+ HTMLEditorKit.ParserCallback callBack;
+
+ /**
+ * The reference to the working class of HTML parser that is
+ * actually used to parse the document.
+ * This is package-private to avoid an accessor method.
+ */
+ gnuParser gnu;
+
+ /**
+ * Parses the HTML document, calling methods of the provided
+ * callback. This method must be multithread - safe.
+ * @param reader The reader to read the HTML document from
+ * @param a_callback The callback that is notifyed about the presence
+ * of HTML elements in the document.
+ * @param ignoreCharSet If thrue, any charset changes during parsing
+ * are ignored.
+ * @throws java.io.IOException
+ */
+ public void parse(Reader reader, HTMLEditorKit.ParserCallback a_callback,
+ boolean ignoreCharSet
+ )
+ throws IOException
+ {
+ callBack = a_callback;
+
+ if (gnu == null || !dtd.equals(gnu.getDTD()))
+ {
+ gnu = new gnuParser(dtd);
+ }
+
+ gnu.parse(reader);
+
+ callBack.handleEndOfLineString(gnu.getEndOfLineSequence());
+ try
+ {
+ callBack.flush();
+ }
+ catch (BadLocationException ex)
+ {
+ // Convert this into the supported type of exception.
+ throw new IOException(ex.getMessage());
+ }
+ }
+
+ /**
+ * Calling this method instructs that, if not specified directly,
+ * the documents will be parsed using the default
+ * DTD of the implementation.
+ */
+ protected static void setDefaultDTD()
+ {
+ dtd = HTML_401F.getInstance();
+ }
+
+ /**
+ * Registers the user - written DTD under the given name, also
+ * making it default for the subsequent parsings. This has effect on
+ * all subsequent calls to the parse(...) . If you need to specify
+ * your DTD locally, simply {@link javax.swing.text.html.parser.Parser}
+ * instead.
+ * @param a_dtd The DTD that will be used to parse documents by this class.
+ * @param name The name of this DTD.
+ * @return No standard is specified on which instance of DTD must be
+ * returned by this method, and it is recommended to leave the returned
+ * value without consideration. This implementation returns the DTD
+ * that was previously set as the default DTD, or the implementations
+ * default DTD if none was set.
+ */
+ protected static DTD createDTD(DTD a_dtd, String name)
+ {
+ DTD.putDTDHash(name, a_dtd);
+
+ DTD dtd_prev = dtd;
+ dtd = a_dtd;
+ return dtd_prev;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/TagElement.java b/libjava/classpath/javax/swing/text/html/parser/TagElement.java
new file mode 100644
index 000000000..4558b15eb
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/TagElement.java
@@ -0,0 +1,142 @@
+/* TagElement.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.parser;
+
+import javax.swing.text.html.HTML;
+
+/**
+ * The SGML element, defining a single html tag.
+ * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
+ */
+public class TagElement
+{
+ /**
+ * The Element the tag was constructed from.
+ */
+ private final Element element;
+
+ /**
+ * The coresponding HTML tag, assigned once in constructor.
+ */
+ private final HTML.Tag tag;
+
+ /**
+ * The 'fictional' flag.
+ */
+ private final boolean fictional;
+
+ /**
+ * Creates the html tag element from the defintion, stored in the
+ * given element. Sets the flag 'fictional' to false.
+ * @param an_element
+ */
+ public TagElement(Element an_element)
+ {
+ this(an_element, false);
+ }
+
+ /**
+ * Creates the html tag element from the defintion, stored in the
+ * given element, setting the flag 'fictional' to the given value.
+ */
+ public TagElement(Element an_element, boolean is_fictional)
+ {
+ element = an_element;
+ fictional = is_fictional;
+
+ HTML.Tag t = HTML.getTag(element.getName());
+
+ if (t != null)
+ tag = t;
+ else
+ tag = new HTML.UnknownTag(element.getName());
+ }
+
+ /**
+ * Get the element from that the tag was constructed.
+ */
+ public Element getElement()
+ {
+ return element;
+ }
+
+ /**
+ * Get the corresponding HTML tag. This is either one of the
+ * pre-defined HTML tags or the instance of the UnknownTag with the
+ * element name.
+ */
+ public HTML.Tag getHTMLTag()
+ {
+ return tag;
+ }
+
+ /**
+ * Calls isPreformatted() for the corresponding html tag and returns
+ * the obtained value.
+ */
+ public boolean isPreformatted()
+ {
+ return tag.isPreformatted();
+ }
+
+ /**
+ * Calls breaksFlow() for the corresponding html tag and returns
+ * the obtained value.
+ */
+ public boolean breaksFlow()
+ {
+ return tag.breaksFlow();
+ }
+
+ /**
+ * Get the value of the flag 'fictional'.
+ */
+ public boolean fictional()
+ {
+ return fictional;
+ }
+
+ /**
+ * Returns string representation of this object.
+ */
+ public String toString()
+ {
+ return getElement() + (fictional ? "?" : "");
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/html/parser/package.html b/libjava/classpath/javax/swing/text/html/parser/package.html
new file mode 100644
index 000000000..5d5157fb2
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/html/parser/package.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!-- package.html - describes classes in javax.swing.text.html package.
+ Copyright (C) 2002 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. -->
+
+<html>
+<head><title>GNU Classpath - javax.swing.text.html.parser</title></head>
+
+<body>
+<p> Provides the DTD driven for web browsers,
+ web robots, web page content analysers, web editors and
+ other applications applications working with Hypertext
+ Markup Language (HTML).
+</p>
+
+</body>
+</html>
diff --git a/libjava/classpath/javax/swing/text/package.html b/libjava/classpath/javax/swing/text/package.html
new file mode 100644
index 000000000..5db555d88
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/package.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!-- package.html - describes classes in javax.swing.text package.
+ Copyright (C) 2002 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. -->
+
+<html>
+<head><title>GNU Classpath - javax.swing.text</title></head>
+
+<body>
+<p>Provides core text classes and interfaces representing models and views
+used by the text components for display and editing of text.</p>
+</body>
+</html>
diff --git a/libjava/classpath/javax/swing/text/rtf/ControlWordToken.java b/libjava/classpath/javax/swing/text/rtf/ControlWordToken.java
new file mode 100644
index 000000000..7008f0fd4
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/ControlWordToken.java
@@ -0,0 +1,86 @@
+/* ControlWordToken.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.rtf;
+
+/**
+ * A special {@link Token} that represents a control word in RTF like
+ * '\deff0' where 'deff' is the name of the control word and '0' is an
+ * optional parameter.
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ */
+class ControlWordToken extends Token
+{
+
+ /**
+ * The name of the control word.
+ */
+ public String name;
+
+ /**
+ * The optional parameter of the control word. Absence of a parameter is
+ * expressed through Integer.MIN_VALUE.
+ */
+ public int param;
+
+ /**
+ * Constructs a new ControlWordToken with the specified name and without
+ * a parameter.
+ *
+ * @param name the name of the control word
+ */
+ public ControlWordToken(String name)
+ {
+ this(name, Integer.MIN_VALUE);
+ }
+
+
+ /**
+ * Constructs a new ControlWordToken with the specified name and parameter.
+ *
+ * @param name the name of the control word
+ */
+ public ControlWordToken(String name, int param)
+ {
+ super(Token.CONTROL_WORD);
+ this.name = name;
+ this.param = param;
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/rtf/RTFEditorKit.java b/libjava/classpath/javax/swing/text/rtf/RTFEditorKit.java
new file mode 100644
index 000000000..b2ebe3dd1
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/RTFEditorKit.java
@@ -0,0 +1,114 @@
+/* RTFEditorKit.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.rtf;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.StyledEditorKit;
+
+/**
+ * Provides support for RTF data for use in
+ * {@link javax.swing.JEditorPane}s.
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ */
+public class RTFEditorKit
+ extends StyledEditorKit
+{
+
+ /**
+ * Constructs a new RTFEditorKit.
+ */
+ public RTFEditorKit()
+ {
+ super();
+ }
+
+ /**
+ * Returns the MIME content type. In the case of RTFEditorKit this is
+ * &apos;text/rtf&apos;
+ *
+ * @return the MIME content type for RTFEditorKit
+ */
+ public String getContentType()
+ {
+ return "text/rtf";
+ }
+
+ /**
+ * Reads RTF data from <code>stream</code> into <code>doc</code> at the
+ * specified position <code>pos</code>.
+ *
+ * @param stream the {@link InputStream} from where we read RTF data
+ * @param doc the {@link Document} into which we read the RTF data
+ * @param pos the position where to start
+ *
+ * @throws IOException if an IO error occurs
+ * @throws BadLocationException if the position is not valid
+ */
+ public void read(InputStream stream, Document doc, int pos)
+ throws IOException, BadLocationException
+ {
+ RTFParser parser = new RTFParser(stream, doc, pos);
+ parser.parse();
+ }
+
+
+ /**
+ * Reads RTF data from <code>reader</code> into <code>doc</code> at the
+ * specified position <code>pos</code>.
+ *
+ * @param reader the {@link Reader} from where we read RTF data
+ * @param doc the {@link Document} into which we read the RTF data
+ * @param pos the position where to start
+ *
+ * @throws IOException if an IO error occurs
+ * @throws BadLocationException if the position is not valid
+ */
+ public void read(Reader reader, Document doc, int pos)
+ throws IOException, BadLocationException
+ {
+ RTFParser parser = new RTFParser(reader, doc, pos);
+ parser.parse();
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/rtf/RTFParseException.java b/libjava/classpath/javax/swing/text/rtf/RTFParseException.java
new file mode 100644
index 000000000..2a9c64f05
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/RTFParseException.java
@@ -0,0 +1,65 @@
+/* RTFParseException.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.rtf;
+
+/**
+ * Indicates a parsing error during RTF processing.
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ */
+class RTFParseException
+ extends RuntimeException
+{
+ /**
+ * Constructs a new RTFParseException without message.
+ */
+ public RTFParseException()
+ {
+ super();
+ }
+
+ /**
+ * Constructs a new RTFParseException with the specified message.
+ */
+ public RTFParseException(String message)
+ {
+ super(message);
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/rtf/RTFParser.java b/libjava/classpath/javax/swing/text/rtf/RTFParser.java
new file mode 100644
index 000000000..3306056ff
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/RTFParser.java
@@ -0,0 +1,203 @@
+/* RTFParser.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.rtf;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+
+/**
+ * Parses an RTF file into a {@link Document}. The parser utilizes
+ * {@link RTFScanner}.
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ */
+class RTFParser
+{
+
+ /**
+ * Our scanner.
+ */
+ private RTFScanner scanner;
+
+ /**
+ * The document into which we parse.
+ */
+ private Document doc;
+
+ /**
+ * The current position.
+ */
+ private int pos;
+
+ /**
+ * Constructs a new RTFParser for the specified document and position,
+ * without initializing the scanner. This is only used internally.
+ *
+ * @param doc the {@link Document} into which we should parse
+ * @param pos the position to start
+ */
+ private RTFParser(Document doc, int pos)
+ {
+ this.doc = doc;
+ this.pos = pos;
+ }
+
+ /**
+ * Constructs a new RTFParser for the specified <code>stream</code>.
+ *
+ * @param stream the stream from which we parse
+ * @param doc the {@link Document} into which we should parse
+ * @param pos the position to start
+ */
+ public RTFParser(InputStream stream, Document doc, int pos)
+ {
+ this(doc, pos);
+ scanner = new RTFScanner(stream);
+ }
+
+ /**
+ * Constructs a new RTFParser for the specified <code>reader</code>.
+ *
+ * @param reader the reader from which we parse
+ * @param doc the {@link Document} into which we should parse
+ * @param pos the position to start
+ */
+ public RTFParser(Reader reader, Document doc, int pos)
+ {
+ this(doc, pos);
+ scanner = new RTFScanner(reader);
+ }
+
+ /**
+ * Returns the {@link Document} in which we parsed the RTF data.
+ *
+ * @return the {@link Document} in which we parsed the RTF data
+ */
+ public Document getDocument()
+ {
+ return doc;
+ }
+
+ /**
+ * Starts the parsing process.
+ */
+ public void parse()
+ throws IOException, BadLocationException
+ {
+ parseFile();
+ }
+
+ /**
+ * The parse rules for &lt;file&gt;.
+ */
+ private void parseFile()
+ throws IOException, BadLocationException
+ {
+ Token t1 = scanner.readToken();
+ if (t1.type != Token.LCURLY)
+ throw new RTFParseException("expected left curly braces");
+
+ parseHeader();
+ parseDocument();
+
+ Token t2 = scanner.peekToken();
+ if (t2.type == Token.RCURLY)
+ {
+ // Eat the token.
+ scanner.readToken();
+ }
+ else
+ {
+ // Ignore this for maximum robustness when file is broken.
+ System.err.println("RTF warning: expected right curly braces");
+ }
+
+ }
+
+ /**
+ * The parse rules for &lt;header&gt;.
+ *
+ * TODO: implement this properly
+ */
+ private void parseHeader()
+ //throws IOException, BadLocationException
+ {
+ // TODO add parse rules here
+ }
+
+
+ /**
+ * The parse rules for &lt;document&gt;.
+ *
+ * TODO: implement this properly
+ */
+ private void parseDocument()
+ throws IOException, BadLocationException
+ {
+ // !!! TODO !!!
+ // This simply emits every TEXT Token as text to the document
+ // which is plain stupid
+
+ boolean eof = false;
+
+ do {
+ Token token = scanner.readToken();
+ switch (token.type)
+ {
+ case Token.TEXT:
+ TextToken textToken = (TextToken) token;
+ doc.insertString(pos, textToken.text, null);
+ pos += textToken.text.length();
+ break;
+ case Token.EOF:
+ eof = true;
+ break;
+ default:
+ // FIXME
+ break;
+ }
+ } while (!eof);
+
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/rtf/RTFScanner.java b/libjava/classpath/javax/swing/text/rtf/RTFScanner.java
new file mode 100644
index 000000000..2eca159c4
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/RTFScanner.java
@@ -0,0 +1,294 @@
+/* RTFScanner.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.rtf;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+/**
+ * Provides a scanner that scans an {@link InputStream} for tokens of the
+ * RTF syntax.
+ *
+ * This scanner is based upon the RTF specification 1.6
+ * available at:
+ *
+ * <a
+ * href="http://msdn.microsoft.com/library/en-us/dnrtfspec/html/rtfspec.asp">
+ * RTF specification at MSDN</a>
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ */
+class RTFScanner
+{
+
+ /**
+ * The reader from which we read the RTF data.
+ */
+ private Reader in;
+
+ /**
+ * This is used to constuct strings from the read in chars.
+ */
+ private StringBuffer buffer;
+
+ /**
+ * Lookahead token.
+ */
+ private Token lastToken;
+
+ /**
+ * Constructs a new RTFScanner without initializing the {@link Reader}.
+ */
+ private RTFScanner()
+ {
+ buffer = new StringBuffer();
+ }
+
+ /**
+ * Constructs a new RTFScanner for the given {@link InputStream}.
+ * The stream is wrapped into an {@link InputStreamReader} and if it's
+ * not yet buffered then the Reader is wrapped in a {@link BufferedReader}
+ *
+ * @param stream the {@link InputStream} to read RTF data from
+ */
+ public RTFScanner(InputStream stream)
+ {
+ this();
+ InputStreamReader reader = new InputStreamReader(stream);
+ in = new BufferedReader(reader);
+ }
+
+ /**
+ * Constructs a new RTFScanner for the given {@link Reader}.
+ *
+ * If the reader is not an instance of {@link BufferedReader} then it
+ * is wrapped into a BufferedReader.
+ *
+ * @param reader the {@link BufferedReader} to read RTF data from
+ */
+ public RTFScanner(Reader reader)
+ {
+ this();
+ if (reader instanceof BufferedReader)
+ {
+ in = reader;
+ }
+ else
+ {
+ in = new BufferedReader(reader);
+ }
+ }
+
+ /**
+ * Reads in the next {@link Token} from the stream.
+ *
+ * @return the read {@link Token}
+ *
+ * @throws IOException if the underlying stream has problems
+ */
+ private Token readTokenImpl()
+ throws IOException
+ {
+ Token token = null;
+
+ int c = in.read();
+ switch(c)
+ {
+ case -1:
+ token = new Token(Token.EOF);
+ break;
+
+ case '{':
+ token = new Token(Token.LCURLY);
+ break;
+
+ case '}':
+ token = new Token(Token.RCURLY);
+ break;
+
+ case '\\':
+ buffer.delete(0, buffer.length());
+ buffer.append((char) c);
+ token = readControlWord();
+ break;
+
+ default:
+ buffer.delete(0, buffer.length());
+ buffer.append((char) c);
+ token = readText();
+ break;
+ }
+
+ return token;
+ }
+
+ Token peekToken()
+ throws IOException
+ {
+ lastToken = readTokenImpl();
+ return lastToken;
+ }
+
+ Token readToken()
+ throws IOException
+ {
+ Token token;
+ if (lastToken != null)
+ {
+ token = lastToken;
+ lastToken = null;
+ }
+ else
+ token = readTokenImpl();
+ return token;
+ }
+
+ /**
+ * Reads in a control word and optional parameter.
+ *
+ * @return the read in control word as {@link ControlWordToken}
+ *
+ * @throws IOException if the underlying stream has problems
+ */
+ private Token readControlWord()
+ throws IOException
+ {
+ // this flag indicates if we are still reading the name or are already
+ // in the parameter
+ boolean readingName = true;
+ String name = null;
+ String param = null;
+
+ while (true)
+ {
+ in.mark(1);
+ int c = in.read();
+
+ // check for 'a'..'z'
+ if (readingName && (c >= 'a') && (c <= 'z'))
+ {
+ buffer.append((char) c);
+ }
+ else if ((c >= '0') && (c <= '9'))
+ {
+ // if the last char was in the name, then finish reading the name
+ if (readingName)
+ {
+ name = buffer.toString();
+ buffer.delete(0, buffer.length());
+ readingName = false;
+ }
+ buffer.append((char) c);
+ }
+ else
+ {
+ // if we were in the name, then finish this
+ if (readingName)
+ {
+ name = buffer.toString();
+ }
+ // otherwise finish the parameter
+ else
+ {
+ param = buffer.toString();
+ }
+
+ // clear up
+ buffer.delete(0, buffer.length());
+ // reset input buffer to last char
+ in.reset();
+ // break while loop
+ break;
+ }
+ }
+
+ ControlWordToken token = null;
+
+ if (param == null)
+ token = new ControlWordToken(name);
+ else
+ token =new ControlWordToken(name, Integer.parseInt(param));
+
+ return token;
+
+ }
+
+ /**
+ * Reads in a block of text.
+ *
+ * @return the token for the text
+ */
+ private Token readText()
+ throws IOException
+ {
+
+ boolean readingText = true;
+ while (readingText)
+ {
+ in.mark(1);
+ int c = in.read();
+ switch(c)
+ {
+ case '\\':
+ case '{':
+ case '}':
+ case -1:
+ readingText = false;
+ in.reset();
+ break;
+
+ default:
+ buffer.append((char) c);
+ break;
+ }
+
+ }
+
+ String text = buffer.toString();
+ Token token = new TextToken(text);
+
+ buffer.delete(0, buffer.length());
+
+ return token;
+
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/rtf/TextToken.java b/libjava/classpath/javax/swing/text/rtf/TextToken.java
new file mode 100644
index 000000000..2d6d527d1
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/TextToken.java
@@ -0,0 +1,65 @@
+/* TextToken.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.rtf;
+
+/**
+ * A special {@link Token} that represents a piece of text in RTF.
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ */
+class TextToken extends Token
+{
+
+ /**
+ * The text.
+ */
+ public String text;
+
+ /**
+ * Constructs a new TextToken with the specified textual data.
+ *
+ * @param text the text for this token
+ */
+ public TextToken(String text)
+ {
+ super(Token.TEXT);
+ this.text = text;
+ }
+
+}
diff --git a/libjava/classpath/javax/swing/text/rtf/Token.java b/libjava/classpath/javax/swing/text/rtf/Token.java
new file mode 100644
index 000000000..7d5adaaf0
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/Token.java
@@ -0,0 +1,91 @@
+/* Token.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.rtf;
+
+/**
+ * Represents a simple token that the RTFScanner can read. A simple
+ * only has a type (like LCURLY or RCURLY). More complex tokens may
+ * attach data to the token.
+ *
+ * @author Roman Kennke (roman@ontographics.com)
+ */
+class Token
+{
+
+ /**
+ * This special type inidicates the end of the input stream.
+ */
+ public static final int EOF = -1;
+
+ /**
+ * A left curly brace '{'.
+ */
+ public static final int LCURLY = 1;
+
+ /**
+ * A right curly brace '}'.
+ */
+ public static final int RCURLY = 2;
+
+ /**
+ * A control word like '\rtf1'. Tokens with this type are represented
+ * through the subclass {@link ControlWordToken}.
+ */
+ public static final int CONTROL_WORD = 3;
+
+ /**
+ * A token that contains text. This is represented through the subclass
+ * {@link TextToken}.
+ */
+ public static final int TEXT = 4;
+
+
+ /** The token type. */
+ public int type;
+
+ /**
+ * Constructs a new Token with the specified type.
+ *
+ * @param type the Token type
+ */
+ public Token(int type)
+ {
+ this.type = type;
+ }
+}
diff --git a/libjava/classpath/javax/swing/text/rtf/package.html b/libjava/classpath/javax/swing/text/rtf/package.html
new file mode 100644
index 000000000..c695aef6c
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/rtf/package.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!-- package.html - describes classes in javax.swing.text.rtf package.
+ 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. -->
+
+<html>
+<head><title>GNU Classpath - javax.swing.text.rtf</title></head>
+
+<body>
+<p>Provides support for Rich Text Format (RTF) data to be used with the
+{@link JEditorPane} component.
+</p>
+
+</body>
+</html>