mirror of
https://github.com/autc04/Retro68.git
synced 2024-11-24 23:32:06 +00:00
1223 lines
35 KiB
Java
1223 lines
35 KiB
Java
|
/* JEditorPane.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;
|
||
|
|
||
|
import java.awt.Container;
|
||
|
import java.awt.Dimension;
|
||
|
import java.io.BufferedInputStream;
|
||
|
import java.io.FilterInputStream;
|
||
|
import java.io.IOException;
|
||
|
import java.io.InputStream;
|
||
|
import java.io.InputStreamReader;
|
||
|
import java.io.Reader;
|
||
|
import java.io.StringReader;
|
||
|
import java.net.MalformedURLException;
|
||
|
import java.net.URL;
|
||
|
import java.net.URLConnection;
|
||
|
import java.util.HashMap;
|
||
|
|
||
|
import javax.accessibility.AccessibleContext;
|
||
|
import javax.accessibility.AccessibleHyperlink;
|
||
|
import javax.accessibility.AccessibleHypertext;
|
||
|
import javax.accessibility.AccessibleStateSet;
|
||
|
import javax.accessibility.AccessibleText;
|
||
|
import javax.swing.event.HyperlinkEvent;
|
||
|
import javax.swing.event.HyperlinkListener;
|
||
|
import javax.swing.plaf.TextUI;
|
||
|
import javax.swing.text.AbstractDocument;
|
||
|
import javax.swing.text.BadLocationException;
|
||
|
import javax.swing.text.DefaultEditorKit;
|
||
|
import javax.swing.text.Document;
|
||
|
import javax.swing.text.EditorKit;
|
||
|
import javax.swing.text.Element;
|
||
|
import javax.swing.text.JTextComponent;
|
||
|
import javax.swing.text.View;
|
||
|
import javax.swing.text.ViewFactory;
|
||
|
import javax.swing.text.WrappedPlainView;
|
||
|
import javax.swing.text.html.HTML;
|
||
|
import javax.swing.text.html.HTMLDocument;
|
||
|
import javax.swing.text.html.HTMLEditorKit;
|
||
|
|
||
|
/**
|
||
|
* A powerful text editor component that can handle different types of
|
||
|
* content.
|
||
|
*
|
||
|
* The JEditorPane text component is driven by an instance of
|
||
|
* {@link EditorKit}. The editor kit is responsible for providing
|
||
|
* a default {@link Document} implementation, a mechanism for loading
|
||
|
* and saving documents of its supported content type and providing
|
||
|
* a set of {@link Action}s for manipulating the content.
|
||
|
*
|
||
|
* By default the following content types are supported:
|
||
|
* <ul>
|
||
|
* <li><code>text/plain</code>: Plain text, handled by
|
||
|
* {@link javax.swing.text.DefaultEditorKit}.</li>
|
||
|
* <li><code>text/html</code>: HTML 4.0 styled text, handled by
|
||
|
* {@link javax.swing.text.html.HTMLEditorKit}.</li>
|
||
|
* <li><code>text/rtf</code>: RTF text, handled by
|
||
|
* {@link javax.swing.text.rtf.RTFEditorKit}.</li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @author original author unknown
|
||
|
* @author Roman Kennke (roman@kennke.org)
|
||
|
* @author Anthony Balkissoon abalkiss at redhat dot com
|
||
|
*/
|
||
|
public class JEditorPane extends JTextComponent
|
||
|
{
|
||
|
/**
|
||
|
* Provides accessibility support for <code>JEditorPane</code>.
|
||
|
*
|
||
|
* @author Roman Kennke (kennke@aicas.com)
|
||
|
*/
|
||
|
protected class AccessibleJEditorPane extends AccessibleJTextComponent
|
||
|
{
|
||
|
|
||
|
/**
|
||
|
* Creates a new <code>AccessibleJEditorPane</code> object.
|
||
|
*/
|
||
|
protected AccessibleJEditorPane()
|
||
|
{
|
||
|
super();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a description of this <code>AccessibleJEditorPane</code>. If
|
||
|
* this property is not set, then this returns the content-type of the
|
||
|
* editor pane.
|
||
|
*
|
||
|
* @return a description of this AccessibleJEditorPane
|
||
|
*/
|
||
|
public String getAccessibleDescription()
|
||
|
{
|
||
|
String descr = super.getAccessibleDescription();
|
||
|
if (descr == null)
|
||
|
return getContentType();
|
||
|
else
|
||
|
return descr;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the accessible state of this <code>AccessibleJEditorPane</code>.
|
||
|
*
|
||
|
* @return the accessible state of this <code>AccessibleJEditorPane</code>
|
||
|
*/
|
||
|
public AccessibleStateSet getAccessibleStateSet()
|
||
|
{
|
||
|
AccessibleStateSet state = super.getAccessibleStateSet();
|
||
|
// TODO: Figure out what state must be added here to the super's state.
|
||
|
return state;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Provides accessibility support for <code>JEditorPane</code>s, when the
|
||
|
* editor kit is an instance of {@link HTMLEditorKit}.
|
||
|
*
|
||
|
* @author Roman Kennke (kennke@aicas.com)
|
||
|
*/
|
||
|
protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane
|
||
|
{
|
||
|
/**
|
||
|
* Returns the accessible text of the <code>JEditorPane</code>. This will
|
||
|
* be an instance of
|
||
|
* {@link JEditorPaneAccessibleHypertextSupport}.
|
||
|
*
|
||
|
* @return the accessible text of the <code>JEditorPane</code>
|
||
|
*/
|
||
|
public AccessibleText getAccessibleText()
|
||
|
{
|
||
|
return new JEditorPaneAccessibleHypertextSupport();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is the accessible text that is returned by
|
||
|
* {@link AccessibleJEditorPaneHTML#getAccessibleText()}.
|
||
|
*
|
||
|
* @author Roman Kennke (kennke@aicas.com)
|
||
|
*/
|
||
|
protected class JEditorPaneAccessibleHypertextSupport
|
||
|
extends AccessibleJEditorPane implements AccessibleHypertext
|
||
|
{
|
||
|
|
||
|
/**
|
||
|
* Creates a new JEditorPaneAccessibleHypertextSupport object.
|
||
|
*/
|
||
|
public JEditorPaneAccessibleHypertextSupport()
|
||
|
{
|
||
|
super();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The accessible representation of a HTML link.
|
||
|
*
|
||
|
* @author Roman Kennke (kennke@aicas.com)
|
||
|
*/
|
||
|
public class HTMLLink extends AccessibleHyperlink
|
||
|
{
|
||
|
|
||
|
/**
|
||
|
* The element in the document that represents the link.
|
||
|
*/
|
||
|
Element element;
|
||
|
|
||
|
/**
|
||
|
* Creates a new <code>HTMLLink</code>.
|
||
|
*
|
||
|
* @param el the link element
|
||
|
*/
|
||
|
public HTMLLink(Element el)
|
||
|
{
|
||
|
this.element = el;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns <code>true</code> if this <code>HTMLLink</code> is still
|
||
|
* valid. A <code>HTMLLink</code> can become invalid when the document
|
||
|
* changes.
|
||
|
*
|
||
|
* @return <code>true</code> if this <code>HTMLLink</code> is still
|
||
|
* valid
|
||
|
*/
|
||
|
public boolean isValid()
|
||
|
{
|
||
|
// I test here if the element at our element's start offset is the
|
||
|
// same as the element in the document at this offset. If this is true,
|
||
|
// I consider the link valid, if not, then this link no longer
|
||
|
// represented by this HTMLLink and therefor invalid.
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
return doc.getCharacterElement(element.getStartOffset()) == element;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the number of AccessibleActions in this link object. In
|
||
|
* general, link have 1 AccessibleAction associated with them. There are
|
||
|
* special cases where links can have multiple actions associated, like
|
||
|
* in image maps.
|
||
|
*
|
||
|
* @return the number of AccessibleActions in this link object
|
||
|
*/
|
||
|
public int getAccessibleActionCount()
|
||
|
{
|
||
|
// TODO: Implement the special cases.
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Performs the specified action on the link object. This ususally means
|
||
|
* activating the link.
|
||
|
*
|
||
|
* @return <code>true</code> if the action has been performed
|
||
|
* successfully, <code>false</code> otherwise
|
||
|
*/
|
||
|
public boolean doAccessibleAction(int i)
|
||
|
{
|
||
|
String href = (String) element.getAttributes().getAttribute("href");
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
try
|
||
|
{
|
||
|
URL url = new URL(doc.getBase(), href);
|
||
|
setPage(url);
|
||
|
String desc = doc.getText(element.getStartOffset(),
|
||
|
element.getEndOffset() - element.getStartOffset());
|
||
|
HyperlinkEvent ev =
|
||
|
new HyperlinkEvent(JEditorPane.this,
|
||
|
HyperlinkEvent.EventType.ACTIVATED, url, desc,
|
||
|
element);
|
||
|
fireHyperlinkUpdate(ev);
|
||
|
return true;
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the description of the action at action index <code>i</code>.
|
||
|
* This method returns the text within the element associated with this
|
||
|
* link.
|
||
|
*
|
||
|
* @param i the action index
|
||
|
*
|
||
|
* @return the description of the action at action index <code>i</code>
|
||
|
*/
|
||
|
public String getAccessibleActionDescription(int i)
|
||
|
{
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
try
|
||
|
{
|
||
|
return doc.getText(element.getStartOffset(),
|
||
|
element.getEndOffset() - element.getStartOffset());
|
||
|
}
|
||
|
catch (BadLocationException ex)
|
||
|
{
|
||
|
throw (AssertionError)
|
||
|
new AssertionError("BadLocationException must not be thrown "
|
||
|
+ "here.")
|
||
|
.initCause(ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an {@link URL} object, that represents the action at action
|
||
|
* index <code>i</code>.
|
||
|
*
|
||
|
* @param i the action index
|
||
|
*
|
||
|
* @return an {@link URL} object, that represents the action at action
|
||
|
* index <code>i</code>
|
||
|
*/
|
||
|
public Object getAccessibleActionObject(int i)
|
||
|
{
|
||
|
String href = (String) element.getAttributes().getAttribute("href");
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
try
|
||
|
{
|
||
|
URL url = new URL(doc.getBase(), href);
|
||
|
return url;
|
||
|
}
|
||
|
catch (MalformedURLException ex)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an object that represents the link anchor. For examples, if
|
||
|
* the link encloses a string, then a <code>String</code> object is
|
||
|
* returned, if the link encloses an <img> tag, then an
|
||
|
* <code>ImageIcon</code> object is returned.
|
||
|
*
|
||
|
* @return an object that represents the link anchor
|
||
|
*/
|
||
|
public Object getAccessibleActionAnchor(int i)
|
||
|
{
|
||
|
// TODO: This is only the String case. Implement all cases.
|
||
|
return getAccessibleActionDescription(i);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the start index of the hyperlink element.
|
||
|
*
|
||
|
* @return the start index of the hyperlink element
|
||
|
*/
|
||
|
public int getStartIndex()
|
||
|
{
|
||
|
return element.getStartOffset();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the end index of the hyperlink element.
|
||
|
*
|
||
|
* @return the end index of the hyperlink element
|
||
|
*/
|
||
|
public int getEndIndex()
|
||
|
{
|
||
|
return element.getEndOffset();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the number of hyperlinks in the document.
|
||
|
*
|
||
|
* @return the number of hyperlinks in the document
|
||
|
*/
|
||
|
public int getLinkCount()
|
||
|
{
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
|
||
|
int count = 0;
|
||
|
while (linkIter.isValid())
|
||
|
{
|
||
|
count++;
|
||
|
linkIter.next();
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the <code>i</code>-th hyperlink in the document or
|
||
|
* <code>null</code> if there is no hyperlink with the specified index.
|
||
|
*
|
||
|
* @param i the index of the hyperlink to return
|
||
|
*
|
||
|
* @return the <code>i</code>-th hyperlink in the document or
|
||
|
* <code>null</code> if there is no hyperlink with the specified
|
||
|
* index
|
||
|
*/
|
||
|
public AccessibleHyperlink getLink(int i)
|
||
|
{
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
|
||
|
int count = 0;
|
||
|
while (linkIter.isValid())
|
||
|
{
|
||
|
count++;
|
||
|
if (count == i)
|
||
|
break;
|
||
|
linkIter.next();
|
||
|
}
|
||
|
if (linkIter.isValid())
|
||
|
{
|
||
|
int offset = linkIter.getStartOffset();
|
||
|
// TODO: I fetch the element for the link via getCharacterElement().
|
||
|
// I am not sure that this is correct, maybe we must use
|
||
|
// getParagraphElement()?
|
||
|
Element el = doc.getCharacterElement(offset);
|
||
|
HTMLLink link = new HTMLLink(el);
|
||
|
return link;
|
||
|
}
|
||
|
else
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the index of the link element at the character position
|
||
|
* <code>c</code> within the document, or <code>-1</code> if there is no
|
||
|
* link at the specified position.
|
||
|
*
|
||
|
* @param c the character index from which to fetch the link index
|
||
|
*
|
||
|
* @return the index of the link element at the character position
|
||
|
* <code>c</code> within the document, or <code>-1</code> if there
|
||
|
* is no link at the specified position
|
||
|
*/
|
||
|
public int getLinkIndex(int c)
|
||
|
{
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
|
||
|
int count = 0;
|
||
|
while (linkIter.isValid())
|
||
|
{
|
||
|
if (linkIter.getStartOffset() <= c && linkIter.getEndOffset() > c)
|
||
|
break;
|
||
|
count++;
|
||
|
linkIter.next();
|
||
|
}
|
||
|
if (linkIter.isValid())
|
||
|
return count;
|
||
|
else
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the link text of the link at index <code>i</code>, or
|
||
|
* <code>null</code>, if there is no link at the specified position.
|
||
|
*
|
||
|
* @param i the index of the link
|
||
|
*
|
||
|
* @return the link text of the link at index <code>i</code>, or
|
||
|
* <code>null</code>, if there is no link at the specified
|
||
|
* position
|
||
|
*/
|
||
|
public String getLinkText(int i)
|
||
|
{
|
||
|
HTMLDocument doc = (HTMLDocument) getDocument();
|
||
|
HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
|
||
|
int count = 0;
|
||
|
while (linkIter.isValid())
|
||
|
{
|
||
|
count++;
|
||
|
if (count == i)
|
||
|
break;
|
||
|
linkIter.next();
|
||
|
}
|
||
|
if (linkIter.isValid())
|
||
|
{
|
||
|
int offset = linkIter.getStartOffset();
|
||
|
// TODO: I fetch the element for the link via getCharacterElement().
|
||
|
// I am not sure that this is correct, maybe we must use
|
||
|
// getParagraphElement()?
|
||
|
Element el = doc.getCharacterElement(offset);
|
||
|
try
|
||
|
{
|
||
|
String text = doc.getText(el.getStartOffset(),
|
||
|
el.getEndOffset() - el.getStartOffset());
|
||
|
return text;
|
||
|
}
|
||
|
catch (BadLocationException ex)
|
||
|
{
|
||
|
throw (AssertionError)
|
||
|
new AssertionError("BadLocationException must not be thrown "
|
||
|
+ "here.")
|
||
|
.initCause(ex);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Used to store a mapping for content-type to editor kit class.
|
||
|
*/
|
||
|
private static class EditorKitMapping
|
||
|
{
|
||
|
/**
|
||
|
* The classname of the editor kit.
|
||
|
*/
|
||
|
String className;
|
||
|
|
||
|
/**
|
||
|
* The classloader with which the kit is to be loaded.
|
||
|
*/
|
||
|
ClassLoader classLoader;
|
||
|
|
||
|
/**
|
||
|
* Creates a new EditorKitMapping object.
|
||
|
*
|
||
|
* @param cn the classname
|
||
|
* @param cl the classloader
|
||
|
*/
|
||
|
EditorKitMapping(String cn, ClassLoader cl)
|
||
|
{
|
||
|
className = cn;
|
||
|
classLoader = cl;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An EditorKit used for plain text. This is the default editor kit for
|
||
|
* JEditorPanes.
|
||
|
*
|
||
|
* @author Roman Kennke (kennke@aicas.com)
|
||
|
*/
|
||
|
private static class PlainEditorKit extends DefaultEditorKit
|
||
|
{
|
||
|
|
||
|
/**
|
||
|
* Returns a ViewFactory that supplies WrappedPlainViews.
|
||
|
*/
|
||
|
public ViewFactory getViewFactory()
|
||
|
{
|
||
|
return new ViewFactory()
|
||
|
{
|
||
|
public View create(Element el)
|
||
|
{
|
||
|
return new WrappedPlainView(el);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A special stream that can be cancelled.
|
||
|
*/
|
||
|
private class PageStream
|
||
|
extends FilterInputStream
|
||
|
{
|
||
|
/**
|
||
|
* True when the stream has been cancelled, false otherwise.
|
||
|
*/
|
||
|
private boolean cancelled;
|
||
|
|
||
|
protected PageStream(InputStream in)
|
||
|
{
|
||
|
super(in);
|
||
|
cancelled = false;
|
||
|
}
|
||
|
|
||
|
private void checkCancelled()
|
||
|
throws IOException
|
||
|
{
|
||
|
if (cancelled)
|
||
|
throw new IOException("Stream has been cancelled");
|
||
|
}
|
||
|
|
||
|
void cancel()
|
||
|
{
|
||
|
cancelled = true;
|
||
|
}
|
||
|
|
||
|
public int read()
|
||
|
throws IOException
|
||
|
{
|
||
|
checkCancelled();
|
||
|
return super.read();
|
||
|
}
|
||
|
|
||
|
public int read(byte[] b, int off, int len)
|
||
|
throws IOException
|
||
|
{
|
||
|
checkCancelled();
|
||
|
return super.read(b, off, len);
|
||
|
}
|
||
|
|
||
|
public long skip(long n)
|
||
|
throws IOException
|
||
|
{
|
||
|
checkCancelled();
|
||
|
return super.skip(n);
|
||
|
}
|
||
|
|
||
|
public int available()
|
||
|
throws IOException
|
||
|
{
|
||
|
checkCancelled();
|
||
|
return super.available();
|
||
|
}
|
||
|
|
||
|
public void reset()
|
||
|
throws IOException
|
||
|
{
|
||
|
checkCancelled();
|
||
|
super.reset();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The thread that loads documents asynchronously.
|
||
|
*/
|
||
|
private class PageLoader
|
||
|
implements Runnable
|
||
|
{
|
||
|
private Document doc;
|
||
|
private PageStream in;
|
||
|
private URL old;
|
||
|
URL page;
|
||
|
PageLoader(Document doc, InputStream in, URL old, URL page)
|
||
|
{
|
||
|
this.doc = doc;
|
||
|
this.in = new PageStream(in);
|
||
|
this.old = old;
|
||
|
this.page = page;
|
||
|
}
|
||
|
|
||
|
public void run()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
read(in, doc);
|
||
|
}
|
||
|
catch (IOException ex)
|
||
|
{
|
||
|
UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (SwingUtilities.isEventDispatchThread())
|
||
|
firePropertyChange("page", old, page);
|
||
|
else
|
||
|
{
|
||
|
SwingUtilities.invokeLater(new Runnable()
|
||
|
{
|
||
|
public void run()
|
||
|
{
|
||
|
firePropertyChange("page", old, page);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void cancel()
|
||
|
{
|
||
|
in.cancel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static final long serialVersionUID = 3140472492599046285L;
|
||
|
|
||
|
private EditorKit editorKit;
|
||
|
|
||
|
boolean focus_root;
|
||
|
|
||
|
/**
|
||
|
* Maps content-types to editor kit instances.
|
||
|
*/
|
||
|
static HashMap editorKits;
|
||
|
|
||
|
// A mapping between content types and registered EditorKit types
|
||
|
static HashMap registerMap;
|
||
|
|
||
|
static
|
||
|
{
|
||
|
registerMap = new HashMap();
|
||
|
editorKits = new HashMap();
|
||
|
registerEditorKitForContentType("application/rtf",
|
||
|
"javax.swing.text.rtf.RTFEditorKit");
|
||
|
registerEditorKitForContentType("text/plain",
|
||
|
"javax.swing.JEditorPane$PlainEditorKit");
|
||
|
registerEditorKitForContentType("text/html",
|
||
|
"javax.swing.text.html.HTMLEditorKit");
|
||
|
registerEditorKitForContentType("text/rtf",
|
||
|
"javax.swing.text.rtf.RTFEditorKit");
|
||
|
|
||
|
}
|
||
|
|
||
|
// A mapping between content types and used EditorKits
|
||
|
HashMap editorMap;
|
||
|
|
||
|
/**
|
||
|
* The currently loading stream, if any.
|
||
|
*/
|
||
|
private PageLoader loader;
|
||
|
|
||
|
public JEditorPane()
|
||
|
{
|
||
|
init();
|
||
|
setEditorKit(createDefaultEditorKit());
|
||
|
}
|
||
|
|
||
|
public JEditorPane(String url) throws IOException
|
||
|
{
|
||
|
this(new URL(url));
|
||
|
}
|
||
|
|
||
|
public JEditorPane(String type, String text)
|
||
|
{
|
||
|
init();
|
||
|
setEditorKit(createEditorKitForContentType(type));
|
||
|
setText(text);
|
||
|
}
|
||
|
|
||
|
public JEditorPane(URL url) throws IOException
|
||
|
{
|
||
|
init();
|
||
|
setEditorKit(createEditorKitForContentType("text/html"));
|
||
|
setPage(url);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called by the constructors to set up the default bindings for content
|
||
|
* types and EditorKits.
|
||
|
*/
|
||
|
void init()
|
||
|
{
|
||
|
editorMap = new HashMap();
|
||
|
}
|
||
|
|
||
|
protected EditorKit createDefaultEditorKit()
|
||
|
{
|
||
|
return new PlainEditorKit();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates and returns an EditorKit that is appropriate for the given
|
||
|
* content type. This is created using the default recognized types
|
||
|
* plus any EditorKit types that have been registered.
|
||
|
*
|
||
|
* @see #registerEditorKitForContentType(String, String)
|
||
|
* @see #registerEditorKitForContentType(String, String, ClassLoader)
|
||
|
* @param type the content type
|
||
|
* @return an EditorKit for use with the given content type
|
||
|
*/
|
||
|
public static EditorKit createEditorKitForContentType(String type)
|
||
|
{
|
||
|
// Try cached instance.
|
||
|
EditorKit e = (EditorKit) editorKits.get(type);
|
||
|
if (e == null)
|
||
|
{
|
||
|
EditorKitMapping m = (EditorKitMapping) registerMap.get(type);
|
||
|
if (m != null)
|
||
|
{
|
||
|
String className = m.className;
|
||
|
ClassLoader loader = m.classLoader;
|
||
|
try
|
||
|
{
|
||
|
e = (EditorKit) loader.loadClass(className).newInstance();
|
||
|
}
|
||
|
catch (Exception e2)
|
||
|
{
|
||
|
// The reference implementation returns null when class is not
|
||
|
// loadable or instantiatable.
|
||
|
}
|
||
|
}
|
||
|
// Cache this for later retrieval.
|
||
|
if (e != null)
|
||
|
editorKits.put(type, e);
|
||
|
}
|
||
|
return e;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sends a given <code>HyperlinkEvent</code> to all registered listeners.
|
||
|
*
|
||
|
* @param event the event to send
|
||
|
*/
|
||
|
public void fireHyperlinkUpdate(HyperlinkEvent event)
|
||
|
{
|
||
|
HyperlinkListener[] listeners = getHyperlinkListeners();
|
||
|
|
||
|
for (int index = 0; index < listeners.length; ++index)
|
||
|
listeners[index].hyperlinkUpdate(event);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the accessible context associated with this editor pane.
|
||
|
*
|
||
|
* @return the accessible context associated with this editor pane
|
||
|
*/
|
||
|
public AccessibleContext getAccessibleContext()
|
||
|
{
|
||
|
if (accessibleContext == null)
|
||
|
{
|
||
|
if (getEditorKit() instanceof HTMLEditorKit)
|
||
|
accessibleContext = new AccessibleJEditorPaneHTML();
|
||
|
else
|
||
|
accessibleContext = new AccessibleJEditorPane();
|
||
|
}
|
||
|
return accessibleContext;
|
||
|
}
|
||
|
|
||
|
public final String getContentType()
|
||
|
{
|
||
|
return getEditorKit().getContentType();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the EditorKit. If there is no EditorKit set this method
|
||
|
* calls createDefaultEditorKit() and setEditorKit() first.
|
||
|
*/
|
||
|
public EditorKit getEditorKit()
|
||
|
{
|
||
|
if (editorKit == null)
|
||
|
setEditorKit(createDefaultEditorKit());
|
||
|
return editorKit;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the class name of the EditorKit associated with the given
|
||
|
* content type.
|
||
|
*
|
||
|
* @since 1.3
|
||
|
* @param type the content type
|
||
|
* @return the class name of the EditorKit associated with this content type
|
||
|
*/
|
||
|
public static String getEditorKitClassNameForContentType(String type)
|
||
|
{
|
||
|
EditorKitMapping m = (EditorKitMapping) registerMap.get(type);
|
||
|
String kitName = m != null ? m.className : null;
|
||
|
return kitName;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the EditorKit to use for the given content type. If an
|
||
|
* EditorKit has been explicitly set via
|
||
|
* <code>setEditorKitForContentType</code>
|
||
|
* then it will be returned. Otherwise an attempt will be made to create
|
||
|
* an EditorKit from the default recognzied content types or any
|
||
|
* EditorKits that have been registered. If none can be created, a
|
||
|
* PlainEditorKit is created.
|
||
|
*
|
||
|
* @see #registerEditorKitForContentType(String, String)
|
||
|
* @see #registerEditorKitForContentType(String, String, ClassLoader)
|
||
|
* @param type the content type
|
||
|
* @return an appropriate EditorKit for the given content type
|
||
|
*/
|
||
|
public EditorKit getEditorKitForContentType(String type)
|
||
|
{
|
||
|
// First check if an EditorKit has been explicitly set.
|
||
|
EditorKit e = (EditorKit) editorMap.get(type);
|
||
|
// Then check to see if we can create one.
|
||
|
if (e == null)
|
||
|
{
|
||
|
e = createEditorKitForContentType(type);
|
||
|
if (e != null)
|
||
|
setEditorKitForContentType(type, e);
|
||
|
}
|
||
|
// Otherwise default to PlainEditorKit.
|
||
|
if (e == null)
|
||
|
e = createDefaultEditorKit();
|
||
|
return e;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the preferred size for the JEditorPane. This is implemented to
|
||
|
* return the super's preferred size, unless one of
|
||
|
* {@link #getScrollableTracksViewportHeight()} or
|
||
|
* {@link #getScrollableTracksViewportWidth()} returns <code>true</code>,
|
||
|
* in which case the preferred width and/or height is replaced by the UI's
|
||
|
* minimum size.
|
||
|
*
|
||
|
* @return the preferred size for the JEditorPane
|
||
|
*/
|
||
|
public Dimension getPreferredSize()
|
||
|
{
|
||
|
Dimension pref = super.getPreferredSize();
|
||
|
Container parent = getParent();
|
||
|
if (parent instanceof JViewport)
|
||
|
{
|
||
|
JViewport vp = (JViewport) getParent();
|
||
|
TextUI ui = getUI();
|
||
|
Dimension min = null;
|
||
|
if (! getScrollableTracksViewportWidth())
|
||
|
{
|
||
|
min = ui.getMinimumSize(this);
|
||
|
int vpWidth = vp.getWidth();
|
||
|
if (vpWidth != 0 && vpWidth < min.width)
|
||
|
pref.width = min.width;
|
||
|
}
|
||
|
if (! getScrollableTracksViewportHeight())
|
||
|
{
|
||
|
if (min == null)
|
||
|
min = ui.getMinimumSize(this);
|
||
|
int vpHeight = vp.getHeight();
|
||
|
if (vpHeight != 0 && vpHeight < min.height)
|
||
|
pref.height = min.height;
|
||
|
}
|
||
|
}
|
||
|
return pref;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns <code>true</code> when a Viewport should force the height of
|
||
|
* this component to match the viewport height. This is implemented to return
|
||
|
* <code>true</code> when the parent is an instance of JViewport and
|
||
|
* the viewport height > the UI's minimum height.
|
||
|
*
|
||
|
* @return <code>true</code> when a Viewport should force the height of
|
||
|
* this component to match the viewport height
|
||
|
*/
|
||
|
public boolean getScrollableTracksViewportHeight()
|
||
|
{
|
||
|
// Tests show that this returns true when the parent is a JViewport
|
||
|
// and has a height > minimum UI height.
|
||
|
Container parent = getParent();
|
||
|
int height = parent.getHeight();
|
||
|
TextUI ui = getUI();
|
||
|
return parent instanceof JViewport
|
||
|
&& height >= ui.getMinimumSize(this).height
|
||
|
&& height <= ui.getMaximumSize(this).height;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns <code>true</code> when a Viewport should force the width of
|
||
|
* this component to match the viewport width. This is implemented to return
|
||
|
* <code>true</code> when the parent is an instance of JViewport and
|
||
|
* the viewport width > the UI's minimum width.
|
||
|
*
|
||
|
* @return <code>true</code> when a Viewport should force the width of
|
||
|
* this component to match the viewport width
|
||
|
*/
|
||
|
public boolean getScrollableTracksViewportWidth()
|
||
|
{
|
||
|
// Tests show that this returns true when the parent is a JViewport
|
||
|
// and has a width > minimum UI width.
|
||
|
Container parent = getParent();
|
||
|
return parent != null && parent instanceof JViewport
|
||
|
&& parent.getWidth() > getUI().getMinimumSize(this).width;
|
||
|
}
|
||
|
|
||
|
public URL getPage()
|
||
|
{
|
||
|
return loader != null ? loader.page : null;
|
||
|
}
|
||
|
|
||
|
protected InputStream getStream(URL page)
|
||
|
throws IOException
|
||
|
{
|
||
|
URLConnection conn = page.openConnection();
|
||
|
// Try to detect the content type of the stream data.
|
||
|
String type = conn.getContentType();
|
||
|
if (type != null)
|
||
|
setContentType(type);
|
||
|
InputStream stream = conn.getInputStream();
|
||
|
return new BufferedInputStream(stream);
|
||
|
}
|
||
|
|
||
|
public String getText()
|
||
|
{
|
||
|
return super.getText();
|
||
|
}
|
||
|
|
||
|
public String getUIClassID()
|
||
|
{
|
||
|
return "EditorPaneUI";
|
||
|
}
|
||
|
|
||
|
public boolean isFocusCycleRoot()
|
||
|
{
|
||
|
return focus_root;
|
||
|
}
|
||
|
|
||
|
protected String paramString()
|
||
|
{
|
||
|
return "JEditorPane";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method initializes from a stream.
|
||
|
*/
|
||
|
public void read(InputStream in, Object desc) throws IOException
|
||
|
{
|
||
|
EditorKit kit = getEditorKit();
|
||
|
if (kit instanceof HTMLEditorKit && desc instanceof HTMLDocument)
|
||
|
{
|
||
|
HTMLDocument doc = (HTMLDocument) desc;
|
||
|
setDocument(doc);
|
||
|
try
|
||
|
{
|
||
|
InputStreamReader reader = new InputStreamReader(in);
|
||
|
kit.read(reader, doc, 0);
|
||
|
}
|
||
|
catch (BadLocationException ex)
|
||
|
{
|
||
|
assert false : "BadLocationException must not be thrown here.";
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Reader inRead = new InputStreamReader(in);
|
||
|
super.read(inRead, desc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Establishes a binding between type and classname. This enables
|
||
|
* us to create an EditorKit later for the given content type.
|
||
|
*
|
||
|
* @param type the content type
|
||
|
* @param classname the name of the class that is associated with this
|
||
|
* content type
|
||
|
*/
|
||
|
public static void registerEditorKitForContentType(String type,
|
||
|
String classname)
|
||
|
{
|
||
|
registerEditorKitForContentType(type, classname,
|
||
|
Thread.currentThread().getContextClassLoader());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Establishes the default bindings of type to classname.
|
||
|
*/
|
||
|
public static void registerEditorKitForContentType(String type,
|
||
|
String classname,
|
||
|
ClassLoader loader)
|
||
|
{
|
||
|
registerMap.put(type, new EditorKitMapping(classname, loader));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Replaces the currently selected content with new content represented
|
||
|
* by the given string.
|
||
|
*/
|
||
|
public void replaceSelection(String content)
|
||
|
{
|
||
|
// TODO: Implement this properly.
|
||
|
super.replaceSelection(content);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Scrolls the view to the given reference location (that is, the value
|
||
|
* returned by the UL.getRef method for the URL being displayed).
|
||
|
*/
|
||
|
public void scrollToReference(String reference)
|
||
|
{
|
||
|
// TODO: Implement this properly.
|
||
|
}
|
||
|
|
||
|
public final void setContentType(String type)
|
||
|
{
|
||
|
// Strip off content type parameters.
|
||
|
int paramIndex = type.indexOf(';');
|
||
|
if (paramIndex > -1)
|
||
|
{
|
||
|
// TODO: Handle character encoding.
|
||
|
type = type.substring(0, paramIndex).trim();
|
||
|
}
|
||
|
if (editorKit != null
|
||
|
&& editorKit.getContentType().equals(type))
|
||
|
return;
|
||
|
|
||
|
EditorKit kit = getEditorKitForContentType(type);
|
||
|
|
||
|
if (kit != null)
|
||
|
setEditorKit(kit);
|
||
|
}
|
||
|
|
||
|
public void setEditorKit(EditorKit newValue)
|
||
|
{
|
||
|
if (editorKit == newValue)
|
||
|
return;
|
||
|
|
||
|
if (editorKit != null)
|
||
|
editorKit.deinstall(this);
|
||
|
|
||
|
EditorKit oldValue = editorKit;
|
||
|
editorKit = newValue;
|
||
|
|
||
|
if (editorKit != null)
|
||
|
{
|
||
|
editorKit.install(this);
|
||
|
setDocument(editorKit.createDefaultDocument());
|
||
|
}
|
||
|
|
||
|
firePropertyChange("editorKit", oldValue, newValue);
|
||
|
invalidate();
|
||
|
repaint();
|
||
|
// Reset the accessibleContext since this depends on the editorKit.
|
||
|
accessibleContext = null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Explicitly sets an EditorKit to be used for the given content type.
|
||
|
* @param type the content type
|
||
|
* @param k the EditorKit to use for the given content type
|
||
|
*/
|
||
|
public void setEditorKitForContentType(String type, EditorKit k)
|
||
|
{
|
||
|
editorMap.put(type, k);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the current URL being displayed.
|
||
|
*/
|
||
|
public void setPage(String url) throws IOException
|
||
|
{
|
||
|
setPage(new URL(url));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the current URL being displayed.
|
||
|
*/
|
||
|
public void setPage(URL page) throws IOException
|
||
|
{
|
||
|
if (page == null)
|
||
|
throw new IOException("invalid url");
|
||
|
|
||
|
URL old = getPage();
|
||
|
// Only reload if the URL doesn't point to the same file.
|
||
|
// This is not the same as equals because there might be different
|
||
|
// URLs on the same file with different anchors.
|
||
|
if (old == null || ! old.sameFile(page))
|
||
|
{
|
||
|
InputStream in = getStream(page);
|
||
|
if (editorKit != null)
|
||
|
{
|
||
|
Document doc = editorKit.createDefaultDocument();
|
||
|
doc.putProperty(Document.StreamDescriptionProperty, page);
|
||
|
|
||
|
if (loader != null)
|
||
|
loader.cancel();
|
||
|
loader = new PageLoader(doc, in, old, page);
|
||
|
|
||
|
int prio = -1;
|
||
|
if (doc instanceof AbstractDocument)
|
||
|
{
|
||
|
AbstractDocument aDoc = (AbstractDocument) doc;
|
||
|
prio = aDoc.getAsynchronousLoadPriority();
|
||
|
}
|
||
|
if (prio >= 0)
|
||
|
{
|
||
|
// Load asynchronously.
|
||
|
setDocument(doc);
|
||
|
Thread loadThread = new Thread(loader,
|
||
|
"JEditorPane.PageLoader");
|
||
|
loadThread.setDaemon(true);
|
||
|
loadThread.setPriority(prio);
|
||
|
loadThread.start();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Load synchronously.
|
||
|
loader.run();
|
||
|
setDocument(doc);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the text of the JEditorPane. The argument <code>t</code>
|
||
|
* is expected to be in the format of the current EditorKit. This removes
|
||
|
* the content of the current document and uses the EditorKit to read in the
|
||
|
* new text. This allows the EditorKit to handle the String rather than just
|
||
|
* inserting in plain text.
|
||
|
*
|
||
|
* @param t the text to display in this JEditorPane
|
||
|
*/
|
||
|
public void setText(String t)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
// Remove the current content.
|
||
|
Document doc = getDocument();
|
||
|
doc.remove(0, doc.getLength());
|
||
|
if (t == null || t.equals(""))
|
||
|
return;
|
||
|
|
||
|
// Let the EditorKit read the text into the Document.
|
||
|
getEditorKit().read(new StringReader(t), doc, 0);
|
||
|
}
|
||
|
catch (BadLocationException ble)
|
||
|
{
|
||
|
// TODO: Don't know what to do here.
|
||
|
}
|
||
|
catch (IOException ioe)
|
||
|
{
|
||
|
// TODO: Don't know what to do here.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a <code>HyperlinkListener</code> object to this editor pane.
|
||
|
*
|
||
|
* @param listener the listener to add
|
||
|
*/
|
||
|
public void addHyperlinkListener(HyperlinkListener listener)
|
||
|
{
|
||
|
listenerList.add(HyperlinkListener.class, listener);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes a <code>HyperlinkListener</code> object to this editor pane.
|
||
|
*
|
||
|
* @param listener the listener to remove
|
||
|
*/
|
||
|
public void removeHyperlinkListener(HyperlinkListener listener)
|
||
|
{
|
||
|
listenerList.remove(HyperlinkListener.class, listener);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns all added <code>HyperlinkListener</code> objects.
|
||
|
*
|
||
|
* @return array of listeners
|
||
|
*
|
||
|
* @since 1.4
|
||
|
*/
|
||
|
public HyperlinkListener[] getHyperlinkListeners()
|
||
|
{
|
||
|
return (HyperlinkListener[]) getListeners(HyperlinkListener.class);
|
||
|
}
|
||
|
}
|