/*
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing;
import java.awt.*;
import java.beans.JavaBean;
import java.beans.BeanProperty;
import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import java.io.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.text.html.*;
import javax.accessibility.*;
import sun.reflect.misc.ReflectUtil;
A text component to edit various kinds of content.
You can find how-to information and examples of using editor panes in
Using Text Components,
a section in The Java Tutorial.
This component uses implementations of the
EditorKit
to accomplish its behavior. It effectively
morphs into the proper kind of text editor for the kind
of content it is given. The content type that editor is bound
to at any given time is determined by the EditorKit
currently
installed. If the content is set to a new URL, its type is used
to determine the EditorKit
that should be used to
load the content.
By default, the following types of content are known:
- text/plain
- Plain text, which is the default the type given isn't
recognized. The kit used in this case is an extension of
DefaultEditorKit
that produces a wrapped plain text view.
- text/html
- HTML text. The kit used in this case is the class
javax.swing.text.html.HTMLEditorKit
which provides HTML 3.2 support.
- text/rtf
- RTF text. The kit used in this case is the class
javax.swing.text.rtf.RTFEditorKit
which provides a limited support of the Rich Text Format.
There are several ways to load content into this component.
- The
setText
method can be used to initialize the component from a string. In this case the current EditorKit
will be used, and the content type will be
expected to be of this type.
- The
read
method can be used to initialize the component from a Reader
. Note that if the content type is HTML,
relative references (e.g. for things like images) can't be resolved
unless the <base> tag is used or the Base property
on HTMLDocument
is set.
In this case the current EditorKit
will be used,
and the content type will be expected to be of this type.
- The
setPage
method can be used to initialize the component from a URL. In this case, the content type will be determined from the URL, and the registered EditorKit
for that content type will be set.
Some kinds of content may provide hyperlink support by generating
hyperlink events. The HTML EditorKit
will generate
hyperlink events if the JEditorPane
is not editable
(JEditorPane.setEditable(false);
has been called).
If HTML frames are embedded in the document, the typical response would be
to change a portion of the current document. The following code
fragment is a possible hyperlink listener implementation, that treats
HTML frame events specially, and simply displays any other activated
hyperlinks.
class Hyperactive implements HyperlinkListener {
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
JEditorPane pane = (JEditorPane) e.getSource();
if (e instanceof HTMLFrameHyperlinkEvent) {
HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
HTMLDocument doc = (HTMLDocument)pane.getDocument();
doc.processHTMLFrameHyperlinkEvent(evt);
} else {
try {
pane.setPage(e.getURL());
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}
For information on customizing how text/html is rendered please see W3C_LENGTH_UNITS
and HONOR_DISPLAY_PROPERTIES
Culturally dependent information in some documents is handled through
a mechanism called character encoding. Character encoding is an
unambiguous mapping of the members of a character set (letters, ideographs,
digits, symbols, or control functions) to specific numeric code values. It
represents the way the file is stored. Example character encodings are
ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is
passed to an user agent (JEditorPane
) it is converted to
the document character set (ISO-10646 aka Unicode).
There are multiple ways to get a character set mapping to happen
with JEditorPane
.
- One way is to specify the character set as a parameter of the MIME type. This will be established by a call to the
setContentType
method. If the content is loaded by the setPage
method the content type will have been set according to the specification of the URL. It the file is loaded directly, the content type would be expected to have been set prior to loading. -
Another way the character set can be specified is in the document itself.
This requires reading the document prior to determining the character set
that is desired. To handle this, it is expected that the
EditorKit
.read operation throw a
ChangedCharSetException
which will
be caught. The read is then restarted with a new Reader that uses
the character set specified in the ChangedCharSetException
(which is an IOException
).
- Newlines
-
For a discussion on how newlines are handled, see
DefaultEditorKit.
Warning: Swing is not thread safe. For more
information see Swing's Threading
Policy.
Warning:
Serialized objects of this class will not be compatible with
future Swing releases. The current serialization support is
appropriate for short term storage or RMI between applications running
the same version of Swing. As of 1.4, support for long term storage
of all JavaBeans™
has been added to the java.beans
package. Please see XMLEncoder
.
Author: Timothy Prinzing Since: 1.2
/**
* A text component to edit various kinds of content.
* You can find how-to information and examples of using editor panes in
* <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/text.html">Using Text Components</a>,
* a section in <em>The Java Tutorial.</em>
*
* <p>
* This component uses implementations of the
* <code>EditorKit</code> to accomplish its behavior. It effectively
* morphs into the proper kind of text editor for the kind
* of content it is given. The content type that editor is bound
* to at any given time is determined by the <code>EditorKit</code> currently
* installed. If the content is set to a new URL, its type is used
* to determine the <code>EditorKit</code> that should be used to
* load the content.
* <p>
* By default, the following types of content are known:
* <dl>
* <dt><b>text/plain</b>
* <dd>Plain text, which is the default the type given isn't
* recognized. The kit used in this case is an extension of
* <code>DefaultEditorKit</code> that produces a wrapped plain text view.
* <dt><b>text/html</b>
* <dd>HTML text. The kit used in this case is the class
* <code>javax.swing.text.html.HTMLEditorKit</code>
* which provides HTML 3.2 support.
* <dt><b>text/rtf</b>
* <dd>RTF text. The kit used in this case is the class
* <code>javax.swing.text.rtf.RTFEditorKit</code>
* which provides a limited support of the Rich Text Format.
* </dl>
* <p>
* There are several ways to load content into this component.
* <ol>
* <li>
* The {@link #setText setText} method can be used to initialize
* the component from a string. In this case the current
* <code>EditorKit</code> will be used, and the content type will be
* expected to be of this type.
* <li>
* The {@link #read read} method can be used to initialize the
* component from a <code>Reader</code>. Note that if the content type is HTML,
* relative references (e.g. for things like images) can't be resolved
* unless the <base> tag is used or the <em>Base</em> property
* on <code>HTMLDocument</code> is set.
* In this case the current <code>EditorKit</code> will be used,
* and the content type will be expected to be of this type.
* <li>
* The {@link #setPage setPage} method can be used to initialize
* the component from a URL. In this case, the content type will be
* determined from the URL, and the registered <code>EditorKit</code>
* for that content type will be set.
* </ol>
* <p>
* Some kinds of content may provide hyperlink support by generating
* hyperlink events. The HTML <code>EditorKit</code> will generate
* hyperlink events if the <code>JEditorPane</code> is <em>not editable</em>
* (<code>JEditorPane.setEditable(false);</code> has been called).
* If HTML frames are embedded in the document, the typical response would be
* to change a portion of the current document. The following code
* fragment is a possible hyperlink listener implementation, that treats
* HTML frame events specially, and simply displays any other activated
* hyperlinks.
* <pre>
class Hyperactive implements HyperlinkListener {
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
JEditorPane pane = (JEditorPane) e.getSource();
if (e instanceof HTMLFrameHyperlinkEvent) {
HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
HTMLDocument doc = (HTMLDocument)pane.getDocument();
doc.processHTMLFrameHyperlinkEvent(evt);
} else {
try {
pane.setPage(e.getURL());
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}
* </pre>
* <p>
* For information on customizing how <b>text/html</b> is rendered please see
* {@link #W3C_LENGTH_UNITS} and {@link #HONOR_DISPLAY_PROPERTIES}
* <p>
* Culturally dependent information in some documents is handled through
* a mechanism called character encoding. Character encoding is an
* unambiguous mapping of the members of a character set (letters, ideographs,
* digits, symbols, or control functions) to specific numeric code values. It
* represents the way the file is stored. Example character encodings are
* ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is
* passed to an user agent (<code>JEditorPane</code>) it is converted to
* the document character set (ISO-10646 aka Unicode).
* <p>
* There are multiple ways to get a character set mapping to happen
* with <code>JEditorPane</code>.
* <ol>
* <li>
* One way is to specify the character set as a parameter of the MIME
* type. This will be established by a call to the
* {@link #setContentType setContentType} method. If the content
* is loaded by the {@link #setPage setPage} method the content
* type will have been set according to the specification of the URL.
* It the file is loaded directly, the content type would be expected to
* have been set prior to loading.
* <li>
* Another way the character set can be specified is in the document itself.
* This requires reading the document prior to determining the character set
* that is desired. To handle this, it is expected that the
* <code>EditorKit</code>.read operation throw a
* <code>ChangedCharSetException</code> which will
* be caught. The read is then restarted with a new Reader that uses
* the character set specified in the <code>ChangedCharSetException</code>
* (which is an <code>IOException</code>).
* </ol>
*
* <dl>
* <dt><b>Newlines</b>
* <dd>
* For a discussion on how newlines are handled, see
* <a href="text/DefaultEditorKit.html">DefaultEditorKit</a>.
* </dl>
*
* <p>
* <strong>Warning:</strong> Swing is not thread safe. For more
* information see <a
* href="package-summary.html#threading">Swing's Threading
* Policy</a>.
* <p>
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @author Timothy Prinzing
* @since 1.2
*/
@JavaBean(defaultProperty = "UIClassID", description = "A text component to edit various types of content.")
@SwingContainer(false)
@SuppressWarnings("serial") // Same-version serialization only
public class JEditorPane extends JTextComponent {
Creates a new JEditorPane
.
The document model is set to null
.
/**
* Creates a new <code>JEditorPane</code>.
* The document model is set to <code>null</code>.
*/
public JEditorPane() {
super();
setFocusCycleRoot(true);
setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
public Component getComponentAfter(Container focusCycleRoot,
Component aComponent) {
if (focusCycleRoot != JEditorPane.this ||
(!isEditable() && getComponentCount() > 0)) {
return super.getComponentAfter(focusCycleRoot,
aComponent);
} else {
Container rootAncestor = getFocusCycleRootAncestor();
return (rootAncestor != null)
? rootAncestor.getFocusTraversalPolicy().
getComponentAfter(rootAncestor,
JEditorPane.this)
: null;
}
}
public Component getComponentBefore(Container focusCycleRoot,
Component aComponent) {
if (focusCycleRoot != JEditorPane.this ||
(!isEditable() && getComponentCount() > 0)) {
return super.getComponentBefore(focusCycleRoot,
aComponent);
} else {
Container rootAncestor = getFocusCycleRootAncestor();
return (rootAncestor != null)
? rootAncestor.getFocusTraversalPolicy().
getComponentBefore(rootAncestor,
JEditorPane.this)
: null;
}
}
public Component getDefaultComponent(Container focusCycleRoot)
{
return (focusCycleRoot != JEditorPane.this ||
(!isEditable() && getComponentCount() > 0))
? super.getDefaultComponent(focusCycleRoot)
: null;
}
protected boolean accept(Component aComponent) {
return (aComponent != JEditorPane.this)
? super.accept(aComponent)
: false;
}
});
LookAndFeel.installProperty(this,
"focusTraversalKeysForward",
JComponent.
getManagingFocusForwardTraversalKeys());
LookAndFeel.installProperty(this,
"focusTraversalKeysBackward",
JComponent.
getManagingFocusBackwardTraversalKeys());
}
Creates a JEditorPane
based on a specified URL for input.
Params: - initialPage – the URL
Throws: - IOException – if the URL is
null
or cannot be accessed
/**
* Creates a <code>JEditorPane</code> based on a specified URL for input.
*
* @param initialPage the URL
* @exception IOException if the URL is <code>null</code>
* or cannot be accessed
*/
public JEditorPane(URL initialPage) throws IOException {
this();
setPage(initialPage);
}
Creates a JEditorPane
based on a string containing
a URL specification.
Params: - url – the URL
Throws: - IOException – if the URL is
null
or
cannot be accessed
/**
* Creates a <code>JEditorPane</code> based on a string containing
* a URL specification.
*
* @param url the URL
* @exception IOException if the URL is <code>null</code> or
* cannot be accessed
*/
public JEditorPane(String url) throws IOException {
this();
setPage(url);
}
Creates a JEditorPane
that has been initialized
to the given text. This is a convenience constructor that calls the
setContentType
and setText
methods.
Params: - type – mime type of the given text
- text – the text to initialize with; may be
null
Throws: - NullPointerException – if the
type
parameter
is null
/**
* Creates a <code>JEditorPane</code> that has been initialized
* to the given text. This is a convenience constructor that calls the
* <code>setContentType</code> and <code>setText</code> methods.
*
* @param type mime type of the given text
* @param text the text to initialize with; may be <code>null</code>
* @exception NullPointerException if the <code>type</code> parameter
* is <code>null</code>
*/
public JEditorPane(String type, String text) {
this();
setContentType(type);
setText(text);
}
Adds a hyperlink listener for notification of any changes, for example
when a link is selected and entered.
Params: - listener – the listener
/**
* Adds a hyperlink listener for notification of any changes, for example
* when a link is selected and entered.
*
* @param listener the listener
*/
public synchronized void addHyperlinkListener(HyperlinkListener listener) {
listenerList.add(HyperlinkListener.class, listener);
}
Removes a hyperlink listener.
Params: - listener – the listener
/**
* Removes a hyperlink listener.
*
* @param listener the listener
*/
public synchronized void removeHyperlinkListener(HyperlinkListener listener) {
listenerList.remove(HyperlinkListener.class, listener);
}
Returns an array of all the HyperLinkListener
s added
to this JEditorPane with addHyperlinkListener().
Returns: all of the HyperLinkListener
s added or an empty
array if no listeners have been added Since: 1.4
/**
* Returns an array of all the <code>HyperLinkListener</code>s added
* to this JEditorPane with addHyperlinkListener().
*
* @return all of the <code>HyperLinkListener</code>s added or an empty
* array if no listeners have been added
* @since 1.4
*/
@BeanProperty(bound = false)
public synchronized HyperlinkListener[] getHyperlinkListeners() {
return listenerList.getListeners(javax.swing.event.HyperlinkListener.class);
}
Notifies all listeners that have registered interest for
notification on this event type. This is normally called
by the currently installed EditorKit
if a content type
that supports hyperlinks is currently active and there
was activity with a link. The listener list is processed
last to first.
Params: - e – the event
See Also:
/**
* Notifies all listeners that have registered interest for
* notification on this event type. This is normally called
* by the currently installed <code>EditorKit</code> if a content type
* that supports hyperlinks is currently active and there
* was activity with a link. The listener list is processed
* last to first.
*
* @param e the event
* @see EventListenerList
*/
public void fireHyperlinkUpdate(HyperlinkEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==HyperlinkListener.class) {
((HyperlinkListener)listeners[i+1]).hyperlinkUpdate(e);
}
}
}
Sets the current URL being displayed. The content type of the
pane is set, and if the editor kit for the pane is
non-null
, then
a new default document is created and the URL is read into it.
If the URL contains and reference location, the location will
be scrolled to by calling the scrollToReference
method. If the desired URL is the one currently being displayed,
the document will not be reloaded. To force a document
reload it is necessary to clear the stream description property
of the document. The following code shows how this can be done:
Document doc = jEditorPane.getDocument();
doc.putProperty(Document.StreamDescriptionProperty, null);
If the desired URL is not the one currently being
displayed, the getStream
method is called to
give subclasses control over the stream provided.
This may load either synchronously or asynchronously
depending upon the document returned by the EditorKit
.
If the Document
is of type
AbstractDocument
and has a value returned by
AbstractDocument.getAsynchronousLoadPriority
that is greater than or equal to zero, the page will be
loaded on a separate thread using that priority.
If the document is loaded synchronously, it will be
filled in with the stream prior to being installed into
the editor with a call to setDocument
, which
is bound and will fire a property change event. If an
IOException
is thrown the partially loaded
document will
be discarded and neither the document or page property
change events will be fired. If the document is
successfully loaded and installed, a view will be
built for it by the UI which will then be scrolled if
necessary, and then the page property change event
will be fired.
If the document is loaded asynchronously, the document
will be installed into the editor immediately using a
call to setDocument
which will fire a
document property change event, then a thread will be
created which will begin doing the actual loading.
In this case, the page property change event will not be
fired by the call to this method directly, but rather will be
fired when the thread doing the loading has finished.
It will also be fired on the event-dispatch thread.
Since the calling thread can not throw an IOException
in the event of failure on the other thread, the page
property change event will be fired when the other
thread is done whether the load was successful or not.
Params: - page – the URL of the page
Throws: - IOException – for a
null
or invalid
page specification, or exception from the stream being read
See Also:
/**
* Sets the current URL being displayed. The content type of the
* pane is set, and if the editor kit for the pane is
* non-<code>null</code>, then
* a new default document is created and the URL is read into it.
* If the URL contains and reference location, the location will
* be scrolled to by calling the <code>scrollToReference</code>
* method. If the desired URL is the one currently being displayed,
* the document will not be reloaded. To force a document
* reload it is necessary to clear the stream description property
* of the document. The following code shows how this can be done:
*
* <pre>
* Document doc = jEditorPane.getDocument();
* doc.putProperty(Document.StreamDescriptionProperty, null);
* </pre>
*
* If the desired URL is not the one currently being
* displayed, the <code>getStream</code> method is called to
* give subclasses control over the stream provided.
* <p>
* This may load either synchronously or asynchronously
* depending upon the document returned by the <code>EditorKit</code>.
* If the <code>Document</code> is of type
* <code>AbstractDocument</code> and has a value returned by
* <code>AbstractDocument.getAsynchronousLoadPriority</code>
* that is greater than or equal to zero, the page will be
* loaded on a separate thread using that priority.
* <p>
* If the document is loaded synchronously, it will be
* filled in with the stream prior to being installed into
* the editor with a call to <code>setDocument</code>, which
* is bound and will fire a property change event. If an
* <code>IOException</code> is thrown the partially loaded
* document will
* be discarded and neither the document or page property
* change events will be fired. If the document is
* successfully loaded and installed, a view will be
* built for it by the UI which will then be scrolled if
* necessary, and then the page property change event
* will be fired.
* <p>
* If the document is loaded asynchronously, the document
* will be installed into the editor immediately using a
* call to <code>setDocument</code> which will fire a
* document property change event, then a thread will be
* created which will begin doing the actual loading.
* In this case, the page property change event will not be
* fired by the call to this method directly, but rather will be
* fired when the thread doing the loading has finished.
* It will also be fired on the event-dispatch thread.
* Since the calling thread can not throw an <code>IOException</code>
* in the event of failure on the other thread, the page
* property change event will be fired when the other
* thread is done whether the load was successful or not.
*
* @param page the URL of the page
* @exception IOException for a <code>null</code> or invalid
* page specification, or exception from the stream being read
* @see #getPage
*/
@BeanProperty(expert = true, description
= "the URL used to set content")
public void setPage(URL page) throws IOException {
if (page == null) {
throw new IOException("invalid url");
}
URL loaded = getPage();
// reset scrollbar
if (!page.equals(loaded) && page.getRef() == null) {
scrollRectToVisible(new Rectangle(0,0,1,1));
}
boolean reloaded = false;
Object postData = getPostData();
if ((loaded == null) || !loaded.sameFile(page) || (postData != null)) {
// different url or POST method, load the new content
int p = getAsynchronousLoadPriority(getDocument());
if (p < 0) {
// open stream synchronously
InputStream in = getStream(page);
if (kit != null) {
Document doc = initializeModel(kit, page);
// At this point, one could either load up the model with no
// view notifications slowing it down (i.e. best synchronous
// behavior) or set the model and start to feed it on a separate
// thread (best asynchronous behavior).
p = getAsynchronousLoadPriority(doc);
if (p >= 0) {
// load asynchronously
setDocument(doc);
synchronized(this) {
pageLoader = new PageLoader(doc, in, loaded, page);
pageLoader.execute();
}
return;
}
read(in, doc);
setDocument(doc);
reloaded = true;
}
} else {
// we may need to cancel background loading
if (pageLoader != null) {
pageLoader.cancel(true);
}
// Do everything in a background thread.
// Model initialization is deferred to that thread, too.
pageLoader = new PageLoader(null, null, loaded, page);
pageLoader.execute();
return;
}
}
final String reference = page.getRef();
if (reference != null) {
if (!reloaded) {
scrollToReference(reference);
}
else {
// Have to scroll after painted.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
scrollToReference(reference);
}
});
}
getDocument().putProperty(Document.StreamDescriptionProperty, page);
}
firePropertyChange("page", loaded, page);
}
Create model and initialize document properties from page properties.
/**
* Create model and initialize document properties from page properties.
*/
private Document initializeModel(EditorKit kit, URL page) {
Document doc = kit.createDefaultDocument();
if (pageProperties != null) {
// transfer properties discovered in stream to the
// document property collection.
for (Enumeration<String> e = pageProperties.keys(); e.hasMoreElements() ;) {
String key = e.nextElement();
doc.putProperty(key, pageProperties.get(key));
}
pageProperties.clear();
}
if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
doc.putProperty(Document.StreamDescriptionProperty, page);
}
return doc;
}
Return load priority for the document or -1 if priority not supported.
/**
* Return load priority for the document or -1 if priority not supported.
*/
private int getAsynchronousLoadPriority(Document doc) {
return (doc instanceof AbstractDocument ?
((AbstractDocument) doc).getAsynchronousLoadPriority() : -1);
}
This method initializes from a stream. If the kit is
set to be of type HTMLEditorKit
, and the
desc
parameter is an HTMLDocument
,
then it invokes the HTMLEditorKit
to initiate
the read. Otherwise it calls the superclass
method which loads the model as plain text.
Params: - in – the stream from which to read
- desc – an object describing the stream
Throws: - IOException – as thrown by the stream being
used to initialize
See Also:
/**
* This method initializes from a stream. If the kit is
* set to be of type <code>HTMLEditorKit</code>, and the
* <code>desc</code> parameter is an <code>HTMLDocument</code>,
* then it invokes the <code>HTMLEditorKit</code> to initiate
* the read. Otherwise it calls the superclass
* method which loads the model as plain text.
*
* @param in the stream from which to read
* @param desc an object describing the stream
* @exception IOException as thrown by the stream being
* used to initialize
* @see JTextComponent#read
* @see #setDocument
*/
public void read(InputStream in, Object desc) throws IOException {
if (desc instanceof HTMLDocument &&
kit instanceof HTMLEditorKit) {
HTMLDocument hdoc = (HTMLDocument) desc;
setDocument(hdoc);
read(in, hdoc);
} else {
String charset = (String) getClientProperty("charset");
Reader r = (charset != null) ? new InputStreamReader(in, charset) :
new InputStreamReader(in);
super.read(r, desc);
}
}
This method invokes the EditorKit
to initiate a
read. In the case where a ChangedCharSetException
is thrown this exception will contain the new CharSet.
Therefore the read
operation
is then restarted after building a new Reader with the new charset.
Params: - in – the inputstream to use
- doc – the document to load
/**
* This method invokes the <code>EditorKit</code> to initiate a
* read. In the case where a <code>ChangedCharSetException</code>
* is thrown this exception will contain the new CharSet.
* Therefore the <code>read</code> operation
* is then restarted after building a new Reader with the new charset.
*
* @param in the inputstream to use
* @param doc the document to load
*
*/
void read(InputStream in, Document doc) throws IOException {
if (! Boolean.TRUE.equals(doc.getProperty("IgnoreCharsetDirective"))) {
final int READ_LIMIT = 1024 * 10;
in = new BufferedInputStream(in, READ_LIMIT);
in.mark(READ_LIMIT);
}
String charset = (String) getClientProperty("charset");
try(Reader r = (charset != null) ? new InputStreamReader(in, charset) :
new InputStreamReader(in)) {
kit.read(r, doc, 0);
} catch (BadLocationException e) {
throw new IOException(e.getMessage());
} catch (ChangedCharSetException changedCharSetException) {
String charSetSpec = changedCharSetException.getCharSetSpec();
if (changedCharSetException.keyEqualsCharSet()) {
putClientProperty("charset", charSetSpec);
} else {
setCharsetFromContentTypeParameters(charSetSpec);
}
try {
in.reset();
} catch (IOException exception) {
//mark was invalidated
in.close();
URL url = (URL)doc.getProperty(Document.StreamDescriptionProperty);
if (url != null) {
URLConnection conn = url.openConnection();
in = conn.getInputStream();
} else {
//there is nothing we can do to recover stream
throw changedCharSetException;
}
}
try {
doc.remove(0, doc.getLength());
} catch (BadLocationException e) {}
doc.putProperty("IgnoreCharsetDirective", Boolean.valueOf(true));
read(in, doc);
}
}
Loads a stream into the text document model.
/**
* Loads a stream into the text document model.
*/
class PageLoader extends SwingWorker<URL, Object> {
Construct an asynchronous page loader.
/**
* Construct an asynchronous page loader.
*/
PageLoader(Document doc, InputStream in, URL old, URL page) {
this.in = in;
this.old = old;
this.page = page;
this.doc = doc;
}
Try to load the document, then scroll the view
to the reference (if specified). When done, fire
a page property change event.
/**
* Try to load the document, then scroll the view
* to the reference (if specified). When done, fire
* a page property change event.
*/
protected URL doInBackground() {
boolean pageLoaded = false;
try {
if (in == null) {
in = getStream(page);
if (kit == null) {
// We received document of unknown content type.
UIManager.getLookAndFeel().
provideErrorFeedback(JEditorPane.this);
return old;
}
}
if (doc == null) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
doc = initializeModel(kit, page);
setDocument(doc);
}
});
} catch (InvocationTargetException ex) {
UIManager.getLookAndFeel().provideErrorFeedback(
JEditorPane.this);
return old;
} catch (InterruptedException ex) {
UIManager.getLookAndFeel().provideErrorFeedback(
JEditorPane.this);
return old;
}
}
read(in, doc);
URL page = (URL) doc.getProperty(Document.StreamDescriptionProperty);
String reference = page.getRef();
if (reference != null) {
// scroll the page if necessary, but do it on the
// event thread... that is the only guarantee that
// modelToView can be safely called.
Runnable callScrollToReference = new Runnable() {
public void run() {
URL u = (URL) getDocument().getProperty
(Document.StreamDescriptionProperty);
String ref = u.getRef();
scrollToReference(ref);
}
};
SwingUtilities.invokeLater(callScrollToReference);
}
pageLoaded = true;
} catch (IOException ioe) {
UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
} finally {
if (pageLoaded) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JEditorPane.this.firePropertyChange("page", old, page);
}
});
}
}
return (pageLoaded ? page : old);
}
The stream to load the document with
/**
* The stream to load the document with
*/
InputStream in;
URL of the old page that was replaced (for the property change event)
/**
* URL of the old page that was replaced (for the property change event)
*/
URL old;
URL of the page being loaded (for the property change event)
/**
* URL of the page being loaded (for the property change event)
*/
URL page;
The Document instance to load into. This is cached in case a
new Document is created between the time the thread this is created
and run.
/**
* The Document instance to load into. This is cached in case a
* new Document is created between the time the thread this is created
* and run.
*/
Document doc;
}
Fetches a stream for the given URL, which is about to
be loaded by the setPage
method. By
default, this simply opens the URL and returns the
stream. This can be reimplemented to do useful things
like fetch the stream from a cache, monitor the progress
of the stream, etc.
This method is expected to have the side effect of
establishing the content type, and therefore setting the
appropriate EditorKit
to use for loading the stream.
If this the stream was an http connection, redirects
will be followed and the resulting URL will be set as
the Document.StreamDescriptionProperty
so that relative
URL's can be properly resolved.
Params: - page – the URL of the page
Throws: - IOException – if an I/O problem occurs
Returns: a stream for the URL which is about to be loaded
/**
* Fetches a stream for the given URL, which is about to
* be loaded by the <code>setPage</code> method. By
* default, this simply opens the URL and returns the
* stream. This can be reimplemented to do useful things
* like fetch the stream from a cache, monitor the progress
* of the stream, etc.
* <p>
* This method is expected to have the side effect of
* establishing the content type, and therefore setting the
* appropriate <code>EditorKit</code> to use for loading the stream.
* <p>
* If this the stream was an http connection, redirects
* will be followed and the resulting URL will be set as
* the <code>Document.StreamDescriptionProperty</code> so that relative
* URL's can be properly resolved.
*
* @param page the URL of the page
* @return a stream for the URL which is about to be loaded
* @throws IOException if an I/O problem occurs
*/
protected InputStream getStream(URL page) throws IOException {
final URLConnection conn = page.openConnection();
if (conn instanceof HttpURLConnection) {
HttpURLConnection hconn = (HttpURLConnection) conn;
hconn.setInstanceFollowRedirects(false);
Object postData = getPostData();
if (postData != null) {
handlePostData(hconn, postData);
}
int response = hconn.getResponseCode();
boolean redirect = (response >= 300 && response <= 399);
/*
* In the case of a redirect, we want to actually change the URL
* that was input to the new, redirected URL
*/
if (redirect) {
String loc = conn.getHeaderField("Location");
if (loc.startsWith("http", 0)) {
page = new URL(loc);
} else {
page = new URL(page, loc);
}
return getStream(page);
}
}
// Connection properties handler should be forced to run on EDT,
// as it instantiates the EditorKit.
if (SwingUtilities.isEventDispatchThread()) {
handleConnectionProperties(conn);
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
handleConnectionProperties(conn);
}
});
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
return conn.getInputStream();
}
Handle URL connection properties (most notably, content type).
/**
* Handle URL connection properties (most notably, content type).
*/
private void handleConnectionProperties(URLConnection conn) {
if (pageProperties == null) {
pageProperties = new Hashtable<String, Object>();
}
String type = conn.getContentType();
if (type != null) {
setContentType(type);
pageProperties.put("content-type", type);
}
pageProperties.put(Document.StreamDescriptionProperty, conn.getURL());
String enc = conn.getContentEncoding();
if (enc != null) {
pageProperties.put("content-encoding", enc);
}
}
private Object getPostData() {
return getDocument().getProperty(PostDataProperty);
}
private void handlePostData(HttpURLConnection conn, Object postData)
throws IOException {
conn.setDoOutput(true);
DataOutputStream os = null;
try {
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
os = new DataOutputStream(conn.getOutputStream());
os.writeBytes((String) postData);
} finally {
if (os != null) {
os.close();
}
}
}
Scrolls the view to the given reference location
(that is, the value returned by the URL.getRef
method for the URL being displayed). By default, this
method only knows how to locate a reference in an
HTMLDocument. The implementation calls the
scrollRectToVisible
method to
accomplish the actual scrolling. If scrolling to a
reference location is needed for document types other
than HTML, this method should be reimplemented.
This method will have no effect if the component
is not visible.
Params: - reference – the named location to scroll to
/**
* Scrolls the view to the given reference location
* (that is, the value returned by the <code>URL.getRef</code>
* method for the URL being displayed). By default, this
* method only knows how to locate a reference in an
* HTMLDocument. The implementation calls the
* <code>scrollRectToVisible</code> method to
* accomplish the actual scrolling. If scrolling to a
* reference location is needed for document types other
* than HTML, this method should be reimplemented.
* This method will have no effect if the component
* is not visible.
*
* @param reference the named location to scroll to
*/
@SuppressWarnings("deprecation")
public void scrollToReference(String reference) {
Document d = getDocument();
if (d instanceof HTMLDocument) {
HTMLDocument doc = (HTMLDocument) d;
HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A);
for (; iter.isValid(); iter.next()) {
AttributeSet a = iter.getAttributes();
String nm = (String) a.getAttribute(HTML.Attribute.NAME);
if ((nm != null) && nm.equals(reference)) {
// found a matching reference in the document.
try {
int pos = iter.getStartOffset();
Rectangle r = modelToView(pos);
if (r != null) {
// the view is visible, scroll it to the
// center of the current visible area.
Rectangle vis = getVisibleRect();
//r.y -= (vis.height / 2);
r.height = vis.height;
scrollRectToVisible(r);
setCaretPosition(pos);
}
} catch (BadLocationException ble) {
UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
}
}
}
}
}
Gets the current URL being displayed. If a URL was
not specified in the creation of the document, this
will return null
, and relative URL's will not be
resolved.
Returns: the URL, or null
if none
/**
* Gets the current URL being displayed. If a URL was
* not specified in the creation of the document, this
* will return <code>null</code>, and relative URL's will not be
* resolved.
*
* @return the URL, or <code>null</code> if none
*/
public URL getPage() {
return (URL) getDocument().getProperty(Document.StreamDescriptionProperty);
}
Sets the current URL being displayed.
Params: - url – the URL for display
Throws: - IOException – for a
null
or invalid URL
specification
/**
* Sets the current URL being displayed.
*
* @param url the URL for display
* @exception IOException for a <code>null</code> or invalid URL
* specification
*/
public void setPage(String url) throws IOException {
if (url == null) {
throw new IOException("invalid url");
}
URL page = new URL(url);
setPage(page);
}
Gets the class ID for the UI.
See Also: Returns: the string "EditorPaneUI"
/**
* Gets the class ID for the UI.
*
* @return the string "EditorPaneUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
@BeanProperty(bound = false)
public String getUIClassID() {
return uiClassID;
}
Creates the default editor kit (PlainEditorKit
) for when
the component is first created.
Returns: the editor kit
/**
* Creates the default editor kit (<code>PlainEditorKit</code>) for when
* the component is first created.
*
* @return the editor kit
*/
protected EditorKit createDefaultEditorKit() {
return new PlainEditorKit();
}
Fetches the currently installed kit for handling content.
createDefaultEditorKit
is called to set up a default
if necessary.
Returns: the editor kit
/**
* Fetches the currently installed kit for handling content.
* <code>createDefaultEditorKit</code> is called to set up a default
* if necessary.
*
* @return the editor kit
*/
public EditorKit getEditorKit() {
if (kit == null) {
kit = createDefaultEditorKit();
isUserSetEditorKit = false;
}
return kit;
}
Gets the type of content that this editor
is currently set to deal with. This is
defined to be the type associated with the
currently installed EditorKit
.
Returns: the content type, null
if no editor kit set
/**
* Gets the type of content that this editor
* is currently set to deal with. This is
* defined to be the type associated with the
* currently installed <code>EditorKit</code>.
*
* @return the content type, <code>null</code> if no editor kit set
*/
public final String getContentType() {
return (kit != null) ? kit.getContentType() : null;
}
Sets the type of content that this editor
handles. This calls getEditorKitForContentType
,
and then setEditorKit
if an editor kit can
be successfully located. This is mostly convenience method
that can be used as an alternative to calling
setEditorKit
directly.
If there is a charset definition specified as a parameter
of the content type specification, it will be used when
loading input streams using the associated EditorKit
.
For example if the type is specified as
text/html; charset=EUC-JP
the content
will be loaded using the EditorKit
registered for
text/html
and the Reader provided to
the EditorKit
to load unicode into the document will
use the EUC-JP
charset for translating
to unicode. If the type is not recognized, the content
will be loaded using the EditorKit
registered
for plain text, text/plain
.
Params: - type – the non-
null
mime type for the content editing
support
Throws: - NullPointerException – if the
type
parameter
is null
See Also:
/**
* Sets the type of content that this editor
* handles. This calls <code>getEditorKitForContentType</code>,
* and then <code>setEditorKit</code> if an editor kit can
* be successfully located. This is mostly convenience method
* that can be used as an alternative to calling
* <code>setEditorKit</code> directly.
* <p>
* If there is a charset definition specified as a parameter
* of the content type specification, it will be used when
* loading input streams using the associated <code>EditorKit</code>.
* For example if the type is specified as
* <code>text/html; charset=EUC-JP</code> the content
* will be loaded using the <code>EditorKit</code> registered for
* <code>text/html</code> and the Reader provided to
* the <code>EditorKit</code> to load unicode into the document will
* use the <code>EUC-JP</code> charset for translating
* to unicode. If the type is not recognized, the content
* will be loaded using the <code>EditorKit</code> registered
* for plain text, <code>text/plain</code>.
*
* @param type the non-<code>null</code> mime type for the content editing
* support
* @see #getContentType
* @throws NullPointerException if the <code>type</code> parameter
* is <code>null</code>
*/
@BeanProperty(bound = false, description
= "the type of content")
public final void setContentType(String type) {
// The type could have optional info is part of it,
// for example some charset info. We need to strip that
// of and save it.
int parm = type.indexOf(';');
if (parm > -1) {
// Save the paramList.
String paramList = type.substring(parm);
// update the content type string.
type = type.substring(0, parm).trim();
if (type.toLowerCase().startsWith("text/")) {
setCharsetFromContentTypeParameters(paramList);
}
}
if ((kit == null) || (! type.equals(kit.getContentType()))
|| !isUserSetEditorKit) {
EditorKit k = getEditorKitForContentType(type);
if (k != null && k != kit) {
setEditorKit(k);
isUserSetEditorKit = false;
}
}
}
This method gets the charset information specified as part
of the content type in the http header information.
/**
* This method gets the charset information specified as part
* of the content type in the http header information.
*/
private void setCharsetFromContentTypeParameters(String paramlist) {
String charset;
try {
// paramlist is handed to us with a leading ';', strip it.
int semi = paramlist.indexOf(';');
if (semi > -1 && semi < paramlist.length()-1) {
paramlist = paramlist.substring(semi + 1);
}
if (paramlist.length() > 0) {
// parse the paramlist into attr-value pairs & get the
// charset pair's value
HeaderParser hdrParser = new HeaderParser(paramlist);
charset = hdrParser.findValue("charset");
if (charset != null) {
putClientProperty("charset", charset);
}
}
}
catch (IndexOutOfBoundsException e) {
// malformed parameter list, use charset we have
}
catch (NullPointerException e) {
// malformed parameter list, use charset we have
}
catch (Exception e) {
// malformed parameter list, use charset we have; but complain
System.err.println("JEditorPane.getCharsetFromContentTypeParameters failed on: " + paramlist);
e.printStackTrace();
}
}
Sets the currently installed kit for handling
content. This is the bound property that
establishes the content type of the editor.
Any old kit is first deinstalled, then if kit is
non-null
,
the new kit is installed, and a default document created for it.
A PropertyChange
event ("editorKit") is always fired when
setEditorKit
is called.
NOTE: This has the side effect of changing the model,
because the EditorKit
is the source of how a
particular type
of content is modeled. This method will cause setDocument
to be called on behalf of the caller to ensure integrity
of the internal state.
Params: - kit – the desired editor behavior
See Also:
/**
* Sets the currently installed kit for handling
* content. This is the bound property that
* establishes the content type of the editor.
* Any old kit is first deinstalled, then if kit is
* non-<code>null</code>,
* the new kit is installed, and a default document created for it.
* A <code>PropertyChange</code> event ("editorKit") is always fired when
* <code>setEditorKit</code> is called.
* <p>
* <em>NOTE: This has the side effect of changing the model,
* because the <code>EditorKit</code> is the source of how a
* particular type
* of content is modeled. This method will cause <code>setDocument</code>
* to be called on behalf of the caller to ensure integrity
* of the internal state.</em>
*
* @param kit the desired editor behavior
* @see #getEditorKit
*/
@BeanProperty(expert = true, description
= "the currently installed kit for handling content")
public void setEditorKit(EditorKit kit) {
EditorKit old = this.kit;
isUserSetEditorKit = true;
if (old != null) {
old.deinstall(this);
}
this.kit = kit;
if (this.kit != null) {
this.kit.install(this);
setDocument(this.kit.createDefaultDocument());
}
firePropertyChange("editorKit", old, kit);
}
Fetches the editor kit to use for the given type
of content. This is called when a type is requested
that doesn't match the currently installed type.
If the component doesn't have an EditorKit
registered
for the given type, it will try to create an
EditorKit
from the default EditorKit
registry.
If that fails, a PlainEditorKit
is used on the
assumption that all text documents can be represented
as plain text.
This method can be reimplemented to use some
other kind of type registry. This can
be reimplemented to use the Java Activation
Framework, for example.
Params: - type – the non-
null
content type
Returns: the editor kit
/**
* Fetches the editor kit to use for the given type
* of content. This is called when a type is requested
* that doesn't match the currently installed type.
* If the component doesn't have an <code>EditorKit</code> registered
* for the given type, it will try to create an
* <code>EditorKit</code> from the default <code>EditorKit</code> registry.
* If that fails, a <code>PlainEditorKit</code> is used on the
* assumption that all text documents can be represented
* as plain text.
* <p>
* This method can be reimplemented to use some
* other kind of type registry. This can
* be reimplemented to use the Java Activation
* Framework, for example.
*
* @param type the non-<code>null</code> content type
* @return the editor kit
*/
public EditorKit getEditorKitForContentType(String type) {
if (typeHandlers == null) {
typeHandlers = new Hashtable<String, EditorKit>(3);
}
EditorKit k = typeHandlers.get(type);
if (k == null) {
k = createEditorKitForContentType(type);
if (k != null) {
setEditorKitForContentType(type, k);
}
}
if (k == null) {
k = createDefaultEditorKit();
}
return k;
}
Directly sets the editor kit to use for the given type. A
look-and-feel implementation might use this in conjunction
with createEditorKitForContentType
to install handlers for
content types with a look-and-feel bias.
Params: - type – the non-
null
content type - k – the editor kit to be set
/**
* Directly sets the editor kit to use for the given type. A
* look-and-feel implementation might use this in conjunction
* with <code>createEditorKitForContentType</code> to install handlers for
* content types with a look-and-feel bias.
*
* @param type the non-<code>null</code> content type
* @param k the editor kit to be set
*/
public void setEditorKitForContentType(String type, EditorKit k) {
if (typeHandlers == null) {
typeHandlers = new Hashtable<String, EditorKit>(3);
}
typeHandlers.put(type, k);
}
Replaces the currently selected content with new content
represented by the given string. If there is no selection
this amounts to an insert of the given text. If there
is no replacement text (i.e. the content string is empty
or null
) this amounts to a removal of the
current selection. The replacement text will have the
attributes currently defined for input. If the component is not
editable, beep and return.
Params: - content – the content to replace the selection with. This
value can be
null
/**
* Replaces the currently selected content with new content
* represented by the given string. If there is no selection
* this amounts to an insert of the given text. If there
* is no replacement text (i.e. the content string is empty
* or <code>null</code>) this amounts to a removal of the
* current selection. The replacement text will have the
* attributes currently defined for input. If the component is not
* editable, beep and return.
*
* @param content the content to replace the selection with. This
* value can be <code>null</code>
*/
@Override
public void replaceSelection(String content) {
if (! isEditable()) {
UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
return;
}
EditorKit kit = getEditorKit();
if(kit instanceof StyledEditorKit) {
try {
Document doc = getDocument();
Caret caret = getCaret();
boolean composedTextSaved = saveComposedText(caret.getDot());
int p0 = Math.min(caret.getDot(), caret.getMark());
int p1 = Math.max(caret.getDot(), caret.getMark());
if (doc instanceof AbstractDocument) {
((AbstractDocument)doc).replace(p0, p1 - p0, content,
((StyledEditorKit)kit).getInputAttributes());
}
else {
if (p0 != p1) {
doc.remove(p0, p1 - p0);
}
if (content != null && content.length() > 0) {
doc.insertString(p0, content, ((StyledEditorKit)kit).
getInputAttributes());
}
}
if (composedTextSaved) {
restoreComposedText();
}
} catch (BadLocationException e) {
UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
}
}
else {
super.replaceSelection(content);
}
}
Creates a handler for the given type from the default registry
of editor kits. The registry is created if necessary. If the
registered class has not yet been loaded, an attempt
is made to dynamically load the prototype of the kit for the
given type. If the type was registered with a ClassLoader
,
that ClassLoader
will be used to load the prototype.
If there was no registered ClassLoader
,
Class.forName
will be used to load the prototype.
Once a prototype EditorKit
instance is successfully
located, it is cloned and the clone is returned.
Params: - type – the content type
Returns: the editor kit, or null
if there is nothing
registered for the given type
/**
* Creates a handler for the given type from the default registry
* of editor kits. The registry is created if necessary. If the
* registered class has not yet been loaded, an attempt
* is made to dynamically load the prototype of the kit for the
* given type. If the type was registered with a <code>ClassLoader</code>,
* that <code>ClassLoader</code> will be used to load the prototype.
* If there was no registered <code>ClassLoader</code>,
* <code>Class.forName</code> will be used to load the prototype.
* <p>
* Once a prototype <code>EditorKit</code> instance is successfully
* located, it is cloned and the clone is returned.
*
* @param type the content type
* @return the editor kit, or <code>null</code> if there is nothing
* registered for the given type
*/
@SuppressWarnings("deprecation")
public static EditorKit createEditorKitForContentType(String type) {
Hashtable<String, EditorKit> kitRegistry = getKitRegisty();
EditorKit k = kitRegistry.get(type);
if (k == null) {
// try to dynamically load the support
String classname = getKitTypeRegistry().get(type);
ClassLoader loader = getKitLoaderRegistry().get(type);
try {
Class<?> c;
if (loader != null) {
ReflectUtil.checkPackageAccess(classname);
c = loader.loadClass(classname);
} else {
// Will only happen if developer has invoked
// registerEditorKitForContentType(type, class, null).
c = SwingUtilities.loadSystemClass(classname);
}
k = (EditorKit) c.newInstance();
kitRegistry.put(type, k);
} catch (Throwable e) {
k = null;
}
}
// create a copy of the prototype or null if there
// is no prototype.
if (k != null) {
return (EditorKit) k.clone();
}
return null;
}
Establishes the default bindings of type
to
classname
.
The class will be dynamically loaded later when actually
needed, and can be safely changed before attempted uses
to avoid loading unwanted classes. The prototype
EditorKit
will be loaded with Class.forName
when registered with this method.
Params: - type – the non-
null
content type - classname – the class to load later
/**
* Establishes the default bindings of <code>type</code> to
* <code>classname</code>.
* The class will be dynamically loaded later when actually
* needed, and can be safely changed before attempted uses
* to avoid loading unwanted classes. The prototype
* <code>EditorKit</code> will be loaded with <code>Class.forName</code>
* when registered with this method.
*
* @param type the non-<code>null</code> content type
* @param classname the class to load later
*/
public static void registerEditorKitForContentType(String type, String classname) {
registerEditorKitForContentType(type, classname,Thread.currentThread().
getContextClassLoader());
}
Establishes the default bindings of type
to
classname
.
The class will be dynamically loaded later when actually
needed using the given ClassLoader
,
and can be safely changed
before attempted uses to avoid loading unwanted classes.
Params: - type – the non-
null
content type - classname – the class to load later
- loader – the
ClassLoader
to use to load the name
/**
* Establishes the default bindings of <code>type</code> to
* <code>classname</code>.
* The class will be dynamically loaded later when actually
* needed using the given <code>ClassLoader</code>,
* and can be safely changed
* before attempted uses to avoid loading unwanted classes.
*
* @param type the non-<code>null</code> content type
* @param classname the class to load later
* @param loader the <code>ClassLoader</code> to use to load the name
*/
public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) {
getKitTypeRegistry().put(type, classname);
if (loader != null) {
getKitLoaderRegistry().put(type, loader);
} else {
getKitLoaderRegistry().remove(type);
}
getKitRegisty().remove(type);
}
Returns the currently registered EditorKit
class name for the type type
. Params: - type – the non-
null
content type
Returns: a String
containing the EditorKit
class name for type
Since: 1.3
/**
* Returns the currently registered {@code EditorKit} class name for the
* type {@code type}.
*
* @param type the non-{@code null} content type
* @return a {@code String} containing the {@code EditorKit} class name
* for {@code type}
* @since 1.3
*/
public static String getEditorKitClassNameForContentType(String type) {
return getKitTypeRegistry().get(type);
}
private static Hashtable<String, String> getKitTypeRegistry() {
loadDefaultKitsIfNecessary();
@SuppressWarnings("unchecked")
Hashtable<String, String> tmp =
(Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
return tmp;
}
private static Hashtable<String, ClassLoader> getKitLoaderRegistry() {
loadDefaultKitsIfNecessary();
@SuppressWarnings("unchecked")
Hashtable<String, ClassLoader> tmp =
(Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
return tmp;
}
private static Hashtable<String, EditorKit> getKitRegisty() {
@SuppressWarnings("unchecked")
Hashtable<String, EditorKit> ht =
(Hashtable)SwingUtilities.appContextGet(kitRegistryKey);
if (ht == null) {
ht = new Hashtable<>(3);
SwingUtilities.appContextPut(kitRegistryKey, ht);
}
return ht;
}
This is invoked every time the registries are accessed. Loading
is done this way instead of via a static as the static is only
called once when running in plugin resulting in the entries only
appearing in the first applet.
/**
* This is invoked every time the registries are accessed. Loading
* is done this way instead of via a static as the static is only
* called once when running in plugin resulting in the entries only
* appearing in the first applet.
*/
private static void loadDefaultKitsIfNecessary() {
if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
synchronized(defaultEditorKitMap) {
if (defaultEditorKitMap.size() == 0) {
defaultEditorKitMap.put("text/plain",
"javax.swing.JEditorPane$PlainEditorKit");
defaultEditorKitMap.put("text/html",
"javax.swing.text.html.HTMLEditorKit");
defaultEditorKitMap.put("text/rtf",
"javax.swing.text.rtf.RTFEditorKit");
defaultEditorKitMap.put("application/rtf",
"javax.swing.text.rtf.RTFEditorKit");
}
}
Hashtable<Object, Object> ht = new Hashtable<>();
SwingUtilities.appContextPut(kitTypeRegistryKey, ht);
ht = new Hashtable<>();
SwingUtilities.appContextPut(kitLoaderRegistryKey, ht);
for (String key : defaultEditorKitMap.keySet()) {
registerEditorKitForContentType(key,defaultEditorKitMap.get(key));
}
}
}
// --- java.awt.Component methods --------------------------
Returns the preferred size for the JEditorPane
.
The preferred size for JEditorPane
is slightly altered
from the preferred size of the superclass. If the size
of the viewport has become smaller than the minimum size
of the component, the scrollable definition for tracking
width or height will turn to false. The default viewport
layout will give the preferred size, and that is not desired
in the case where the scrollable is tracking. In that case
the normal preferred size is adjusted to the
minimum size. This allows things like HTML tables to
shrink down to their minimum size and then be laid out at
their minimum size, refusing to shrink any further.
Returns: a Dimension
containing the preferred size
/**
* Returns the preferred size for the <code>JEditorPane</code>.
* The preferred size for <code>JEditorPane</code> is slightly altered
* from the preferred size of the superclass. If the size
* of the viewport has become smaller than the minimum size
* of the component, the scrollable definition for tracking
* width or height will turn to false. The default viewport
* layout will give the preferred size, and that is not desired
* in the case where the scrollable is tracking. In that case
* the <em>normal</em> preferred size is adjusted to the
* minimum size. This allows things like HTML tables to
* shrink down to their minimum size and then be laid out at
* their minimum size, refusing to shrink any further.
*
* @return a <code>Dimension</code> containing the preferred size
*/
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
JViewport port = (JViewport) parent;
TextUI ui = getUI();
int prefWidth = d.width;
int prefHeight = d.height;
if (! getScrollableTracksViewportWidth()) {
int w = port.getWidth();
Dimension min = ui.getMinimumSize(this);
if (w != 0 && w < min.width) {
// Only adjust to min if we have a valid size
prefWidth = min.width;
}
}
if (! getScrollableTracksViewportHeight()) {
int h = port.getHeight();
Dimension min = ui.getMinimumSize(this);
if (h != 0 && h < min.height) {
// Only adjust to min if we have a valid size
prefHeight = min.height;
}
}
if (prefWidth != d.width || prefHeight != d.height) {
d = new Dimension(prefWidth, prefHeight);
}
}
return d;
}
// --- JTextComponent methods -----------------------------
Sets the text of this TextComponent
to the specified
content,
which is expected to be in the format of the content type of
this editor. For example, if the type is set to text/html
the string should be specified in terms of HTML.
This is implemented to remove the contents of the current document,
and replace them by parsing the given string using the current
EditorKit
. This gives the semantics of the
superclass by not changing
out the model, while supporting the content type currently set on
this component. The assumption is that the previous content is
relatively
small, and that the previous content doesn't have side effects.
Both of those assumptions can be violated and cause undesirable results.
To avoid this, create a new document,
getEditorKit().createDefaultDocument()
, and replace the
existing Document
with the new one. You are then assured the
previous Document
won't have any lingering state.
-
Leaving the existing model in place means that the old view will be
torn down, and a new view created, where replacing the document would
avoid the tear down of the old view.
-
Some formats (such as HTML) can install things into the document that
can influence future contents. HTML can have style information embedded
that would influence the next content installed unexpectedly.
An alternative way to load this component with a string would be to
create a StringReader and call the read method. In this case the model
would be replaced after it was initialized with the contents of the
string.
Params: - t – the new text to be set; if
null
the old
text will be deleted
See Also:
/**
* Sets the text of this <code>TextComponent</code> to the specified
* content,
* which is expected to be in the format of the content type of
* this editor. For example, if the type is set to <code>text/html</code>
* the string should be specified in terms of HTML.
* <p>
* This is implemented to remove the contents of the current document,
* and replace them by parsing the given string using the current
* <code>EditorKit</code>. This gives the semantics of the
* superclass by not changing
* out the model, while supporting the content type currently set on
* this component. The assumption is that the previous content is
* relatively
* small, and that the previous content doesn't have side effects.
* Both of those assumptions can be violated and cause undesirable results.
* To avoid this, create a new document,
* <code>getEditorKit().createDefaultDocument()</code>, and replace the
* existing <code>Document</code> with the new one. You are then assured the
* previous <code>Document</code> won't have any lingering state.
* <ol>
* <li>
* Leaving the existing model in place means that the old view will be
* torn down, and a new view created, where replacing the document would
* avoid the tear down of the old view.
* <li>
* Some formats (such as HTML) can install things into the document that
* can influence future contents. HTML can have style information embedded
* that would influence the next content installed unexpectedly.
* </ol>
* <p>
* An alternative way to load this component with a string would be to
* create a StringReader and call the read method. In this case the model
* would be replaced after it was initialized with the contents of the
* string.
*
* @param t the new text to be set; if <code>null</code> the old
* text will be deleted
* @see #getText
*/
@BeanProperty(bound = false, description
= "the text of this component")
public void setText(String t) {
try {
Document doc = getDocument();
doc.remove(0, doc.getLength());
if (t == null || t.equals("")) {
return;
}
Reader r = new StringReader(t);
EditorKit kit = getEditorKit();
kit.read(r, doc, 0);
} catch (IOException ioe) {
UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
} catch (BadLocationException ble) {
UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
}
}
Returns the text contained in this TextComponent
in terms of the
content type of this editor. If an exception is thrown while
attempting to retrieve the text, null
will be returned.
This is implemented to call JTextComponent.write
with
a StringWriter
.
See Also: Returns: the text
/**
* Returns the text contained in this <code>TextComponent</code>
* in terms of the
* content type of this editor. If an exception is thrown while
* attempting to retrieve the text, <code>null</code> will be returned.
* This is implemented to call <code>JTextComponent.write</code> with
* a <code>StringWriter</code>.
*
* @return the text
* @see #setText
*/
public String getText() {
String txt;
try {
StringWriter buf = new StringWriter();
write(buf);
txt = buf.toString();
} catch (IOException ioe) {
txt = null;
}
return txt;
}
// --- Scrollable ----------------------------------------
Returns true if a viewport should always force the width of this
Scrollable
to match the width of the viewport.
Returns: true if a viewport should force the Scrollables width to
match its own, false otherwise
/**
* Returns true if a viewport should always force the width of this
* <code>Scrollable</code> to match the width of the viewport.
*
* @return true if a viewport should force the Scrollables width to
* match its own, false otherwise
*/
@BeanProperty(bound = false)
public boolean getScrollableTracksViewportWidth() {
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
JViewport port = (JViewport) parent;
TextUI ui = getUI();
int w = port.getWidth();
Dimension min = ui.getMinimumSize(this);
Dimension max = ui.getMaximumSize(this);
if ((w >= min.width) && (w <= max.width)) {
return true;
}
}
return false;
}
Returns true if a viewport should always force the height of this
Scrollable
to match the height of the viewport.
Returns: true if a viewport should force the
Scrollable
's height to match its own,
false otherwise
/**
* Returns true if a viewport should always force the height of this
* <code>Scrollable</code> to match the height of the viewport.
*
* @return true if a viewport should force the
* <code>Scrollable</code>'s height to match its own,
* false otherwise
*/
@BeanProperty(bound = false)
public boolean getScrollableTracksViewportHeight() {
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
JViewport port = (JViewport) parent;
TextUI ui = getUI();
int h = port.getHeight();
Dimension min = ui.getMinimumSize(this);
if (h >= min.height) {
Dimension max = ui.getMaximumSize(this);
if (h <= max.height) {
return true;
}
}
}
return false;
}
// --- Serialization ------------------------------------
See readObject
and writeObject
in
JComponent
for more
information about serialization in Swing.
/**
* See <code>readObject</code> and <code>writeObject</code> in
* <code>JComponent</code> for more
* information about serialization in Swing.
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
if (getUIClassID().equals(uiClassID)) {
byte count = JComponent.getWriteObjCounter(this);
JComponent.setWriteObjCounter(this, --count);
if (count == 0 && ui != null) {
ui.installUI(this);
}
}
}
// --- variables ---------------------------------------
private SwingWorker<URL, Object> pageLoader;
Current content binding of the editor.
/**
* Current content binding of the editor.
*/
private EditorKit kit;
private boolean isUserSetEditorKit;
private Hashtable<String, Object> pageProperties;
Should be kept in sync with javax.swing.text.html.FormView counterpart. /** Should be kept in sync with javax.swing.text.html.FormView counterpart. */
static final String PostDataProperty = "javax.swing.JEditorPane.postdata";
Table of registered type handlers for this editor.
/**
* Table of registered type handlers for this editor.
*/
private Hashtable<String, EditorKit> typeHandlers;
/*
* Private AppContext keys for this class's static variables.
*/
private static final Object kitRegistryKey =
new StringBuffer("JEditorPane.kitRegistry");
private static final Object kitTypeRegistryKey =
new StringBuffer("JEditorPane.kitTypeRegistry");
private static final Object kitLoaderRegistryKey =
new StringBuffer("JEditorPane.kitLoaderRegistry");
See Also: - getUIClassID
- JTextComponent.readObject
/**
* @see #getUIClassID
* @see #readObject
*/
private static final String uiClassID = "EditorPaneUI";
Key for a client property used to indicate whether
w3c compliant length units are used for html rendering.
By default this is not enabled; to enable it set the client property
with this name to Boolean.TRUE
.
Since: 1.5
/**
* Key for a client property used to indicate whether
* <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units">
* w3c compliant</a> length units are used for html rendering.
* <p>
* By default this is not enabled; to enable
* it set the client {@link #putClientProperty property} with this name
* to <code>Boolean.TRUE</code>.
*
* @since 1.5
*/
public static final String W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits";
Key for a client property used to indicate whether
the default font and foreground color from the component are
used if a font or foreground color is not specified in the styled
text.
The default varies based on the look and feel; to enable it set the client property
with this name to Boolean.TRUE
.
Since: 1.5
/**
* Key for a client property used to indicate whether
* the default font and foreground color from the component are
* used if a font or foreground color is not specified in the styled
* text.
* <p>
* The default varies based on the look and feel;
* to enable it set the client {@link #putClientProperty property} with
* this name to <code>Boolean.TRUE</code>.
*
* @since 1.5
*/
public static final String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
static final Map<String, String> defaultEditorKitMap = new HashMap<String, String>(0);
Returns a string representation of this JEditorPane
.
This method
is intended to be used only for debugging purposes, and the
content and format of the returned string may vary between
implementations. The returned string may be empty but may not
be null
.
Returns: a string representation of this JEditorPane
/**
* Returns a string representation of this <code>JEditorPane</code>.
* This method
* is intended to be used only for debugging purposes, and the
* content and format of the returned string may vary between
* implementations. The returned string may be empty but may not
* be <code>null</code>.
*
* @return a string representation of this <code>JEditorPane</code>
*/
protected String paramString() {
String kitString = (kit != null ?
kit.toString() : "");
String typeHandlersString = (typeHandlers != null ?
typeHandlers.toString() : "");
return super.paramString() +
",kit=" + kitString +
",typeHandlers=" + typeHandlersString;
}
/////////////////
// Accessibility support
////////////////
Gets the AccessibleContext associated with this JEditorPane.
For editor panes, the AccessibleContext takes the form of an
AccessibleJEditorPane.
A new AccessibleJEditorPane instance is created if necessary.
Returns: an AccessibleJEditorPane that serves as the
AccessibleContext of this JEditorPane
/**
* Gets the AccessibleContext associated with this JEditorPane.
* For editor panes, the AccessibleContext takes the form of an
* AccessibleJEditorPane.
* A new AccessibleJEditorPane instance is created if necessary.
*
* @return an AccessibleJEditorPane that serves as the
* AccessibleContext of this JEditorPane
*/
@BeanProperty(bound = false)
public AccessibleContext getAccessibleContext() {
if (getEditorKit() instanceof HTMLEditorKit) {
if (accessibleContext == null || accessibleContext.getClass() !=
AccessibleJEditorPaneHTML.class) {
accessibleContext = new AccessibleJEditorPaneHTML();
}
} else if (accessibleContext == null || accessibleContext.getClass() !=
AccessibleJEditorPane.class) {
accessibleContext = new AccessibleJEditorPane();
}
return accessibleContext;
}
This class implements accessibility support for the
JEditorPane
class. It provides an implementation of the
Java Accessibility API appropriate to editor pane user-interface
elements.
Warning:
Serialized objects of this class will not be compatible with
future Swing releases. The current serialization support is
appropriate for short term storage or RMI between applications running
the same version of Swing. As of 1.4, support for long term storage
of all JavaBeans™
has been added to the java.beans
package. Please see XMLEncoder
.
/**
* This class implements accessibility support for the
* <code>JEditorPane</code> class. It provides an implementation of the
* Java Accessibility API appropriate to editor pane user-interface
* elements.
* <p>
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*/
@SuppressWarnings("serial") // Same-version serialization only
protected class AccessibleJEditorPane extends AccessibleJTextComponent {
Gets the accessibleDescription property of this object. If this
property isn't set, returns the content type of this
JEditorPane
instead (e.g. "plain/text", "html/text").
See Also: Returns: the localized description of the object; null
if this object does not have a description
/**
* Gets the accessibleDescription property of this object. If this
* property isn't set, returns the content type of this
* <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
*
* @return the localized description of the object; <code>null</code>
* if this object does not have a description
*
* @see #setAccessibleName
*/
public String getAccessibleDescription() {
String description = accessibleDescription;
// fallback to client property
if (description == null) {
description = (String)getClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY);
}
if (description == null) {
description = JEditorPane.this.getContentType();
}
return description;
}
Gets the state set of this object.
See Also: Returns: an instance of AccessibleStateSet describing the states
of the object
/**
* Gets the state set of this object.
*
* @return an instance of AccessibleStateSet describing the states
* of the object
* @see AccessibleStateSet
*/
public AccessibleStateSet getAccessibleStateSet() {
AccessibleStateSet states = super.getAccessibleStateSet();
states.add(AccessibleState.MULTI_LINE);
return states;
}
}
This class provides support for AccessibleHypertext
,
and is used in instances where the EditorKit
installed in this JEditorPane
is an instance of
HTMLEditorKit
.
Warning:
Serialized objects of this class will not be compatible with
future Swing releases. The current serialization support is
appropriate for short term storage or RMI between applications running
the same version of Swing. As of 1.4, support for long term storage
of all JavaBeans™
has been added to the java.beans
package. Please see XMLEncoder
.
/**
* This class provides support for <code>AccessibleHypertext</code>,
* and is used in instances where the <code>EditorKit</code>
* installed in this <code>JEditorPane</code> is an instance of
* <code>HTMLEditorKit</code>.
* <p>
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*/
@SuppressWarnings("serial") // Same-version serialization only
protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane {
private AccessibleContext accessibleContext;
Returns the accessible text.
Returns: the accessible text
/**
* Returns the accessible text.
* @return the accessible text
*/
public AccessibleText getAccessibleText() {
return new JEditorPaneAccessibleHypertextSupport();
}
Constructs an AccessibleJEditorPaneHTML
. /**
* Constructs an {@code AccessibleJEditorPaneHTML}.
*/
protected AccessibleJEditorPaneHTML () {
HTMLEditorKit kit = (HTMLEditorKit)JEditorPane.this.getEditorKit();
accessibleContext = kit.getAccessibleContext();
}
Returns the number of accessible children of the object.
Returns: the number of accessible children of the object.
/**
* Returns the number of accessible children of the object.
*
* @return the number of accessible children of the object.
*/
public int getAccessibleChildrenCount() {
if (accessibleContext != null) {
return accessibleContext.getAccessibleChildrenCount();
} else {
return 0;
}
}
Returns the specified Accessible child of the object. The Accessible
children of an Accessible object are zero-based, so the first child
of an Accessible child is at index 0, the second child is at index 1,
and so on.
Params: - i – zero-based index of child
See Also: Returns: the Accessible child of the object
/**
* Returns the specified Accessible child of the object. The Accessible
* children of an Accessible object are zero-based, so the first child
* of an Accessible child is at index 0, the second child is at index 1,
* and so on.
*
* @param i zero-based index of child
* @return the Accessible child of the object
* @see #getAccessibleChildrenCount
*/
public Accessible getAccessibleChild(int i) {
if (accessibleContext != null) {
return accessibleContext.getAccessibleChild(i);
} else {
return null;
}
}
Returns the Accessible child, if one exists, contained at the local
coordinate Point.
Params: - p – The point relative to the coordinate system of this object.
Returns: the Accessible, if it exists, at the specified location;
otherwise null
/**
* Returns the Accessible child, if one exists, contained at the local
* coordinate Point.
*
* @param p The point relative to the coordinate system of this object.
* @return the Accessible, if it exists, at the specified location;
* otherwise null
*/
public Accessible getAccessibleAt(Point p) {
if (accessibleContext != null && p != null) {
try {
AccessibleComponent acomp =
accessibleContext.getAccessibleComponent();
if (acomp != null) {
return acomp.getAccessibleAt(p);
} else {
return null;
}
} catch (IllegalComponentStateException e) {
return null;
}
} else {
return null;
}
}
}
What's returned by
AccessibleJEditorPaneHTML.getAccessibleText
.
Provides support for AccessibleHypertext
in case
there is an HTML document being displayed in this
JEditorPane
.
/**
* What's returned by
* <code>AccessibleJEditorPaneHTML.getAccessibleText</code>.
*
* Provides support for <code>AccessibleHypertext</code> in case
* there is an HTML document being displayed in this
* <code>JEditorPane</code>.
*
*/
protected class JEditorPaneAccessibleHypertextSupport
extends AccessibleJEditorPane implements AccessibleHypertext {
An HTML link.
/**
* An HTML link.
*/
public class HTMLLink extends AccessibleHyperlink {
Element element;
Constructs a HTMLLink
. Params: - e – the element
/**
* Constructs a {@code HTMLLink}.
* @param e the element
*/
public HTMLLink(Element e) {
element = e;
}
Since the document a link is associated with may have
changed, this method returns whether this Link is valid
anymore (with respect to the document it references).
Returns: a flag indicating whether this link is still valid with
respect to the AccessibleHypertext it belongs to
/**
* Since the document a link is associated with may have
* changed, this method returns whether this Link is valid
* anymore (with respect to the document it references).
*
* @return a flag indicating whether this link is still valid with
* respect to the AccessibleHypertext it belongs to
*/
public boolean isValid() {
return JEditorPaneAccessibleHypertextSupport.this.linksValid;
}
Returns the number of accessible actions available in this Link
If there are more than one, the first one is NOT considered the
"default" action of this LINK object (e.g. in an HTML imagemap).
In general, links will have only one AccessibleAction in them.
Returns: the zero-based number of Actions in this object
/**
* Returns the number of accessible actions available in this Link
* If there are more than one, the first one is NOT considered the
* "default" action of this LINK object (e.g. in an HTML imagemap).
* In general, links will have only one AccessibleAction in them.
*
* @return the zero-based number of Actions in this object
*/
public int getAccessibleActionCount() {
return 1;
}
Perform the specified Action on the object
Params: - i – zero-based index of actions
See Also: Returns: true if the action was performed; else false.
/**
* Perform the specified Action on the object
*
* @param i zero-based index of actions
* @return true if the action was performed; else false.
* @see #getAccessibleActionCount
*/
public boolean doAccessibleAction(int i) {
if (i == 0 && isValid() == true) {
URL u = (URL) getAccessibleActionObject(i);
if (u != null) {
HyperlinkEvent linkEvent =
new HyperlinkEvent(JEditorPane.this, HyperlinkEvent.EventType.ACTIVATED, u);
JEditorPane.this.fireHyperlinkUpdate(linkEvent);
return true;
}
}
return false; // link invalid or i != 0
}
Return a String description of this particular
link action. The string returned is the text
within the document associated with the element
which contains this link.
Params: - i – zero-based index of the actions
See Also: Returns: a String description of the action
/**
* Return a String description of this particular
* link action. The string returned is the text
* within the document associated with the element
* which contains this link.
*
* @param i zero-based index of the actions
* @return a String description of the action
* @see #getAccessibleActionCount
*/
public String getAccessibleActionDescription(int i) {
if (i == 0 && isValid() == true) {
Document d = JEditorPane.this.getDocument();
if (d != null) {
try {
return d.getText(getStartIndex(),
getEndIndex() - getStartIndex());
} catch (BadLocationException exception) {
return null;
}
}
}
return null;
}
Returns a URL object that represents the link.
Params: - i – zero-based index of the actions
See Also: Returns: an URL representing the HTML link itself
/**
* Returns a URL object that represents the link.
*
* @param i zero-based index of the actions
* @return an URL representing the HTML link itself
* @see #getAccessibleActionCount
*/
public Object getAccessibleActionObject(int i) {
if (i == 0 && isValid() == true) {
AttributeSet as = element.getAttributes();
AttributeSet anchor =
(AttributeSet) as.getAttribute(HTML.Tag.A);
String href = (anchor != null) ?
(String) anchor.getAttribute(HTML.Attribute.HREF) : null;
if (href != null) {
URL u;
try {
u = new URL(JEditorPane.this.getPage(), href);
} catch (MalformedURLException m) {
u = null;
}
return u;
}
}
return null; // link invalid or i != 0
}
Return an object that represents the link anchor,
as appropriate for that link.
E.g. from HTML:
<a href="http://openjdk.java.net">OpenJDK</a>
this method would return a String containing the text:
'OpenJDK'.
Similarly, from this HTML:
<a HREF="#top"><img src="top-hat.gif" alt="top hat"></a>
this might return the object ImageIcon("top-hat.gif", "top hat");
Params: - i – zero-based index of the actions
See Also: Returns: an Object representing the hypertext anchor
/**
* Return an object that represents the link anchor,
* as appropriate for that link.
* <p>
* E.g. from HTML:
* <a href="http://openjdk.java.net">OpenJDK</a>
* this method would return a String containing the text:
* 'OpenJDK'.
* <p>
* Similarly, from this HTML:
* <a HREF="#top"><img src="top-hat.gif" alt="top hat"></a>
* this might return the object ImageIcon("top-hat.gif", "top hat");
*
* @param i zero-based index of the actions
* @return an Object representing the hypertext anchor
* @see #getAccessibleActionCount
*/
public Object getAccessibleActionAnchor(int i) {
return getAccessibleActionDescription(i);
}
Get the index with the hypertext document at which this
link begins
Returns: index of start of link
/**
* Get the index with the hypertext document at which this
* link begins
*
* @return index of start of link
*/
public int getStartIndex() {
return element.getStartOffset();
}
Get the index with the hypertext document at which this
link ends
Returns: index of end of link
/**
* Get the index with the hypertext document at which this
* link ends
*
* @return index of end of link
*/
public int getEndIndex() {
return element.getEndOffset();
}
}
private class LinkVector extends Vector<HTMLLink> {
public int baseElementIndex(Element e) {
HTMLLink l;
for (int i = 0; i < elementCount; i++) {
l = elementAt(i);
if (l.element == e) {
return i;
}
}
return -1;
}
}
LinkVector hyperlinks;
boolean linksValid = false;
Build the private table mapping links to locations in the text
/**
* Build the private table mapping links to locations in the text
*/
private void buildLinkTable() {
hyperlinks.removeAllElements();
Document d = JEditorPane.this.getDocument();
if (d != null) {
ElementIterator ei = new ElementIterator(d);
Element e;
AttributeSet as;
AttributeSet anchor;
String href;
while ((e = ei.next()) != null) {
if (e.isLeaf()) {
as = e.getAttributes();
anchor = (AttributeSet) as.getAttribute(HTML.Tag.A);
href = (anchor != null) ?
(String) anchor.getAttribute(HTML.Attribute.HREF) : null;
if (href != null) {
hyperlinks.addElement(new HTMLLink(e));
}
}
}
}
linksValid = true;
}
Make one of these puppies
/**
* Make one of these puppies
*/
public JEditorPaneAccessibleHypertextSupport() {
hyperlinks = new LinkVector();
Document d = JEditorPane.this.getDocument();
if (d != null) {
d.addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent theEvent) {
linksValid = false;
}
public void insertUpdate(DocumentEvent theEvent) {
linksValid = false;
}
public void removeUpdate(DocumentEvent theEvent) {
linksValid = false;
}
});
}
}
Returns the number of links within this hypertext doc.
Returns: number of links in this hypertext doc.
/**
* Returns the number of links within this hypertext doc.
*
* @return number of links in this hypertext doc.
*/
public int getLinkCount() {
if (linksValid == false) {
buildLinkTable();
}
return hyperlinks.size();
}
Returns the index into an array of hyperlinks that
is associated with this character index, or -1 if there
is no hyperlink associated with this index.
Params: - charIndex – index within the text
Returns: index into the set of hyperlinks for this hypertext doc.
/**
* Returns the index into an array of hyperlinks that
* is associated with this character index, or -1 if there
* is no hyperlink associated with this index.
*
* @param charIndex index within the text
* @return index into the set of hyperlinks for this hypertext doc.
*/
public int getLinkIndex(int charIndex) {
if (linksValid == false) {
buildLinkTable();
}
Element e = null;
Document doc = JEditorPane.this.getDocument();
if (doc != null) {
for (e = doc.getDefaultRootElement(); ! e.isLeaf(); ) {
int index = e.getElementIndex(charIndex);
e = e.getElement(index);
}
}
// don't need to verify that it's an HREF element; if
// not, then it won't be in the hyperlinks Vector, and
// so indexOf will return -1 in any case
return hyperlinks.baseElementIndex(e);
}
Returns the index into an array of hyperlinks that
index. If there is no hyperlink at this index, it returns
null.
Params: - linkIndex – into the set of hyperlinks for this hypertext doc.
Returns: string representation of the hyperlink
/**
* Returns the index into an array of hyperlinks that
* index. If there is no hyperlink at this index, it returns
* null.
*
* @param linkIndex into the set of hyperlinks for this hypertext doc.
* @return string representation of the hyperlink
*/
public AccessibleHyperlink getLink(int linkIndex) {
if (linksValid == false) {
buildLinkTable();
}
if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
return hyperlinks.elementAt(linkIndex);
} else {
return null;
}
}
Returns the contiguous text within the document that
is associated with this hyperlink.
Params: - linkIndex – into the set of hyperlinks for this hypertext doc.
Returns: the contiguous text sharing the link at this index
/**
* Returns the contiguous text within the document that
* is associated with this hyperlink.
*
* @param linkIndex into the set of hyperlinks for this hypertext doc.
* @return the contiguous text sharing the link at this index
*/
public String getLinkText(int linkIndex) {
if (linksValid == false) {
buildLinkTable();
}
Element e = (Element) hyperlinks.elementAt(linkIndex);
if (e != null) {
Document d = JEditorPane.this.getDocument();
if (d != null) {
try {
return d.getText(e.getStartOffset(),
e.getEndOffset() - e.getStartOffset());
} catch (BadLocationException exception) {
return null;
}
}
}
return null;
}
}
static class PlainEditorKit extends DefaultEditorKit implements ViewFactory {
Fetches a factory that is suitable for producing
views of any models that are produced by this
kit. The default is to have the UI produce the
factory, so this method has no implementation.
Returns: the view factory
/**
* Fetches a factory that is suitable for producing
* views of any models that are produced by this
* kit. The default is to have the UI produce the
* factory, so this method has no implementation.
*
* @return the view factory
*/
public ViewFactory getViewFactory() {
return this;
}
Creates a view from the given structural element of a
document.
Params: - elem – the piece of the document to build a view of
See Also: Returns: the view
/**
* Creates a view from the given structural element of a
* document.
*
* @param elem the piece of the document to build a view of
* @return the view
* @see View
*/
public View create(Element elem) {
Document doc = elem.getDocument();
Object i18nFlag
= doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
// build a view that support bidi
return createI18N(elem);
} else {
return new WrappedPlainView(elem);
}
}
View createI18N(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new PlainParagraph(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)){
return new BoxView(elem, View.Y_AXIS);
}
}
return null;
}
Paragraph for representing plain-text lines that support
bidirectional text.
/**
* Paragraph for representing plain-text lines that support
* bidirectional text.
*/
static class PlainParagraph extends javax.swing.text.ParagraphView {
PlainParagraph(Element elem) {
super(elem);
layoutPool = new LogicalView(elem);
layoutPool.setParent(this);
}
protected void setPropertiesFromAttributes() {
Component c = getContainer();
if ((c != null)
&& (! c.getComponentOrientation().isLeftToRight()))
{
setJustification(StyleConstants.ALIGN_RIGHT);
} else {
setJustification(StyleConstants.ALIGN_LEFT);
}
}
Fetch the constraining span to flow against for
the given child index.
/**
* Fetch the constraining span to flow against for
* the given child index.
*/
public int getFlowSpan(int index) {
Component c = getContainer();
if (c instanceof JTextArea) {
JTextArea area = (JTextArea) c;
if (! area.getLineWrap()) {
// no limit if unwrapped
return Integer.MAX_VALUE;
}
}
return super.getFlowSpan(index);
}
protected SizeRequirements calculateMinorAxisRequirements(int axis,
SizeRequirements r)
{
SizeRequirements req
= super.calculateMinorAxisRequirements(axis, r);
Component c = getContainer();
if (c instanceof JTextArea) {
JTextArea area = (JTextArea) c;
if (! area.getLineWrap()) {
// min is pref if unwrapped
req.minimum = req.preferred;
}
}
return req;
}
This class can be used to represent a logical view for
a flow. It keeps the children updated to reflect the state
of the model, gives the logical child views access to the
view hierarchy, and calculates a preferred span. It doesn't
do any rendering, layout, or model/view translation.
/**
* This class can be used to represent a logical view for
* a flow. It keeps the children updated to reflect the state
* of the model, gives the logical child views access to the
* view hierarchy, and calculates a preferred span. It doesn't
* do any rendering, layout, or model/view translation.
*/
static class LogicalView extends CompositeView {
LogicalView(Element elem) {
super(elem);
}
protected int getViewIndexAtPosition(int pos) {
Element elem = getElement();
if (elem.getElementCount() > 0) {
return elem.getElementIndex(pos);
}
return 0;
}
protected boolean
updateChildren(DocumentEvent.ElementChange ec,
DocumentEvent e, ViewFactory f)
{
return false;
}
protected void loadChildren(ViewFactory f) {
Element elem = getElement();
if (elem.getElementCount() > 0) {
super.loadChildren(f);
} else {
View v = new GlyphView(elem);
append(v);
}
}
public float getPreferredSpan(int axis) {
if( getViewCount() != 1 )
throw new Error("One child view is assumed.");
View v = getView(0);
//((GlyphView)v).setGlyphPainter(null);
return v.getPreferredSpan(axis);
}
Forward the DocumentEvent to the given child view. This
is implemented to reparent the child to the logical view
(the children may have been parented by a row in the flow
if they fit without breaking) and then execute the
superclass behavior.
Params: - v – the child view to forward the event to.
- e – the change information from the associated document
- a – the current allocation of the view
- f – the factory to use to rebuild if the view has
children
See Also: Since: 1.3
/**
* Forward the DocumentEvent to the given child view. This
* is implemented to reparent the child to the logical view
* (the children may have been parented by a row in the flow
* if they fit without breaking) and then execute the
* superclass behavior.
*
* @param v the child view to forward the event to.
* @param e the change information from the associated document
* @param a the current allocation of the view
* @param f the factory to use to rebuild if the view has
* children
* @see #forwardUpdate
* @since 1.3
*/
protected void forwardUpdateToView(View v, DocumentEvent e,
Shape a, ViewFactory f) {
v.setParent(this);
super.forwardUpdateToView(v, e, a, f);
}
// The following methods don't do anything useful, they
// simply keep the class from being abstract.
public void paint(Graphics g, Shape allocation) {
}
protected boolean isBefore(int x, int y, Rectangle alloc) {
return false;
}
protected boolean isAfter(int x, int y, Rectangle alloc) {
return false;
}
protected View getViewAtPoint(int x, int y, Rectangle alloc) {
return null;
}
protected void childAllocation(int index, Rectangle a) {
}
}
}
}
/* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
* sensibly:
* From a String like: 'timeout=15, max=5'
* create an array of Strings:
* { {"timeout", "15"},
* {"max", "5"}
* }
* From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
* create one like (no quotes in literal):
* { {"basic", null},
* {"realm", "FuzzFace"}
* {"foo", "Biz Bar Baz"}
* }
* keys are converted to lower case, vals are left as is....
*
* author Dave Brown
*/
static class HeaderParser {
/* table of key/val pairs - maxes out at 10!!!!*/
String raw;
String[][] tab;
public HeaderParser(String raw) {
this.raw = raw;
tab = new String[10][2];
parse();
}
private void parse() {
if (raw != null) {
raw = raw.trim();
char[] ca = raw.toCharArray();
int beg = 0, end = 0, i = 0;
boolean inKey = true;
boolean inQuote = false;
int len = ca.length;
while (end < len) {
char c = ca[end];
if (c == '=') { // end of a key
tab[i][0] = new String(ca, beg, end-beg).toLowerCase();
inKey = false;
end++;
beg = end;
} else if (c == '\"') {
if (inQuote) {
tab[i++][1]= new String(ca, beg, end-beg);
inQuote=false;
do {
end++;
} while (end < len && (ca[end] == ' ' || ca[end] == ','));
inKey=true;
beg=end;
} else {
inQuote=true;
end++;
beg=end;
}
} else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
if (inQuote) {
end++;
continue;
} else if (inKey) {
tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase();
} else {
tab[i++][1] = (new String(ca, beg, end-beg));
}
while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
end++;
}
inKey = true;
beg = end;
} else {
end++;
}
}
// get last key/val, if any
if (--end > beg) {
if (!inKey) {
if (ca[end] == '\"') {
tab[i++][1] = (new String(ca, beg, end-beg));
} else {
tab[i++][1] = (new String(ca, beg, end-beg+1));
}
} else {
tab[i][0] = (new String(ca, beg, end-beg+1)).toLowerCase();
}
} else if (end == beg) {
if (!inKey) {
if (ca[end] == '\"') {
tab[i++][1] = String.valueOf(ca[end-1]);
} else {
tab[i++][1] = String.valueOf(ca[end]);
}
} else {
tab[i][0] = String.valueOf(ca[end]).toLowerCase();
}
}
}
}
public String findKey(int i) {
if (i < 0 || i > 10)
return null;
return tab[i][0];
}
public String findValue(int i) {
if (i < 0 || i > 10)
return null;
return tab[i][1];
}
public String findValue(String key) {
return findValue(key, null);
}
public String findValue(String k, String Default) {
if (k == null)
return Default;
k = k.toLowerCase();
for (int i = 0; i < 10; ++i) {
if (tab[i][0] == null) {
return Default;
} else if (k.equals(tab[i][0])) {
return tab[i][1];
}
}
return Default;
}
public int findInt(String k, int Default) {
try {
return Integer.parseInt(findValue(k, String.valueOf(Default)));
} catch (Throwable t) {
return Default;
}
}
}
}