|
Java example source code file (JEditorPane.java)
This example Java source code file (JEditorPane.java) is included in the alvinalexander.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
Learn more about this Java project at its project page.
The JEditorPane.java Java example source code
/*
* Copyright (c) 1997, 2013, 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 sun.swing.SwingUtilities2;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import java.io.*;
import java.util.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.text.html.*;
import javax.accessibility.*;
/**
* A text component to edit various kinds of content.
* You can find how-to information and examples of using editor panes in
* <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/text.html">Using Text Components,
* a section in <em>The Java Tutorial.
*
* <p>
* This component uses implementations of the
* <code>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 <code>EditorKit currently
* installed. If the content is set to a new URL, its type is used
* to determine the <code>EditorKit that should be used to
* load the content.
* <p>
* By default, the following types of content are known:
* <dl>
* <dt>text/plain
* <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 that produces a wrapped plain text view.
* <dt>text/html
* <dd>HTML text. The kit used in this case is the class
* <code>javax.swing.text.html.HTMLEditorKit
* which provides HTML 3.2 support.
* <dt>text/rtf
* <dd>RTF text. The kit used in this case is the class
* <code>javax.swing.text.rtf.RTFEditorKit
* 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 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. 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 property
* on <code>HTMLDocument is set.
* In this case the current <code>EditorKit 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
* 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 will generate
* hyperlink events if the <code>JEditorPane is not editable
* (<code>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.
* <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 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) 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.
* <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.read operation throw a
* <code>ChangedCharSetException which will
* be caught. The read is then restarted with a new Reader that uses
* the character set specified in the <code>ChangedCharSetException
* (which is an <code>IOException).
* </ol>
* <p>
* <dl>
* <dt>Newlines
* <dd>
* For a discussion on how newlines are handled, see
* <a href="text/DefaultEditorKit.html">DefaultEditorKit.
* </dl>
*
* <p>
* <strong>Warning: Swing is not thread safe. For more
* information see <a
* href="package-summary.html#threading">Swing's Threading
* Policy</a>.
* <p>
* <strong>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 <code>java.beans package.
* Please see {@link java.beans.XMLEncoder}.
*
* @beaninfo
* attribute: isContainer false
* description: A text component to edit various types of content.
*
* @author Timothy Prinzing
*/
public class JEditorPane extends JTextComponent {
/**
* Creates a new <code>JEditorPane.
* The document model is set to <code>null.
*/
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 <code>JEditorPane based on a specified URL for input.
*
* @param initialPage the URL
* @exception IOException if the URL is <code>null
* or cannot be accessed
*/
public JEditorPane(URL initialPage) throws IOException {
this();
setPage(initialPage);
}
/**
* Creates a <code>JEditorPane based on a string containing
* a URL specification.
*
* @param url the URL
* @exception IOException if the URL is <code>null or
* cannot be accessed
*/
public JEditorPane(String url) throws IOException {
this();
setPage(url);
}
/**
* Creates a <code>JEditorPane that has been initialized
* to the given text. This is a convenience constructor that calls the
* <code>setContentType and setText methods.
*
* @param type mime type of the given text
* @param text the text to initialize with; may be <code>null
* @exception NullPointerException if the <code>type parameter
* is <code>null
*/
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.
*
* @param listener the listener
*/
public synchronized void addHyperlinkListener(HyperlinkListener listener) {
listenerList.add(HyperlinkListener.class, 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 <code>HyperLinkListeners added
* to this JEditorPane with addHyperlinkListener().
*
* @return all of the <code>HyperLinkListeners added or an empty
* array if no listeners have been added
* @since 1.4
*/
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 <code>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.
*
* @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-<code>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 <code>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:
*
* <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 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.
* If the <code>Document is of type
* <code>AbstractDocument and has a value returned by
* <code>AbstractDocument.getAsynchronousLoadPriority
* 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, which
* is bound and will fire a property change event. If an
* <code>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.
* <p>
* If the document is loaded asynchronously, the document
* will be installed into the editor immediately using a
* call to <code>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 <code>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.
*
* @param page the URL of the page
* @exception IOException for a <code>null or invalid
* page specification, or exception from the stream being read
* @see #getPage
* @beaninfo
* description: the URL used to set content
* bound: true
* expert: true
*/
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.
*/
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.
*/
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 <code>HTMLEditorKit, and the
* <code>desc parameter is an HTMLDocument ,
* then it invokes the <code>HTMLEditorKit 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 <code>EditorKit to initiate a
* read. In the case where a <code>ChangedCharSetException
* is thrown this exception will contain the new CharSet.
* Therefore the <code>read 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);
}
try {
String charset = (String) getClientProperty("charset");
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.
*/
class PageLoader extends SwingWorker<URL, Object> {
/**
* 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.
*/
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
*/
InputStream in;
/**
* 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 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.
*/
Document doc;
}
/**
* Fetches a stream for the given URL, which is about to
* be loaded by the <code>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.
* <p>
* This method is expected to have the the side effect of
* establishing the content type, and therefore setting the
* appropriate <code>EditorKit 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 so that relative
* URL's can be properly resolved.
*
* @param page the URL of the page
*/
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).
*/
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 <code>UL.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
* <code>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.
*
* @param reference the named location to scroll to
*/
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 <code>null, and relative URL's will not be
* resolved.
*
* @return the URL, or <code>null if none
*/
public URL getPage() {
return (URL) getDocument().getProperty(Document.StreamDescriptionProperty);
}
/**
* Sets the current URL being displayed.
*
* @param url the URL for display
* @exception IOException for a <code>null 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.
*
* @return the string "EditorPaneUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
public String getUIClassID() {
return uiClassID;
}
/**
* Creates the default editor kit (<code>PlainEditorKit) 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.
* <code>createDefaultEditorKit 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 <code>EditorKit.
*
* @return the content type, <code>null 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 <code>getEditorKitForContentType,
* and then <code>setEditorKit if an editor kit can
* be successfully located. This is mostly convenience method
* that can be used as an alternative to calling
* <code>setEditorKit 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.
* For example if the type is specified as
* <code>text/html; charset=EUC-JP the content
* will be loaded using the <code>EditorKit registered for
* <code>text/html and the Reader provided to
* the <code>EditorKit to load unicode into the document will
* use the <code>EUC-JP charset for translating
* to unicode. If the type is not recognized, the content
* will be loaded using the <code>EditorKit registered
* for plain text, <code>text/plain.
*
* @param type the non-<code>null mime type for the content editing
* support
* @see #getContentType
* @beaninfo
* description: the type of content
* @throws NullPointerException if the <code>type parameter
* is <code>null
*/
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.
*/
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-<code>null,
* the new kit is installed, and a default document created for it.
* A <code>PropertyChange event ("editorKit") is always fired when
* <code>setEditorKit is called.
* <p>
* <em>NOTE: This has the side effect of changing the model,
* because the <code>EditorKit is the source of how a
* particular type
* of content is modeled. This method will cause <code>setDocument
* to be called on behalf of the caller to ensure integrity
* of the internal state.</em>
*
* @param kit the desired editor behavior
* @see #getEditorKit
* @beaninfo
* description: the currently installed kit for handling content
* bound: true
* expert: true
*/
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 <code>EditorKit registered
* for the given type, it will try to create an
* <code>EditorKit from the default EditorKit registry.
* If that fails, a <code>PlainEditorKit 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 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 <code>createEditorKitForContentType to install handlers for
* content types with a look-and-feel bias.
*
* @param type the non-<code>null 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 <code>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.
*
* @param content the content to replace the selection with. This
* value can be <code>null
*/
@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 <code>ClassLoader,
* that <code>ClassLoader will be used to load the prototype.
* If there was no registered <code>ClassLoader,
* <code>Class.forName will be used to load the prototype.
* <p>
* Once a prototype <code>EditorKit instance is successfully
* located, it is cloned and the clone is returned.
*
* @param type the content type
* @return the editor kit, or <code>null if there is nothing
* registered for the given type
*/
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) {
c = loader.loadClass(classname);
} else {
// Will only happen if developer has invoked
// registerEditorKitForContentType(type, class, null).
c = Class.forName(classname, true, Thread.currentThread().
getContextClassLoader());
}
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 <code>type to
* <code>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
* <code>EditorKit will be loaded with Class.forName
* when registered with this method.
*
* @param type the non-<code>null 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 <code>type to
* <code>classname.
* The class will be dynamically loaded later when actually
* needed using the given <code>ClassLoader,
* and can be safely changed
* before attempted uses to avoid loading unwanted classes.
*
* @param type the non-<code>null content type
* @param classname the class to load later
* @param loader the <code>ClassLoader to use to load the name
*/
public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) {
getKitTypeRegistry().put(type, classname);
getKitLoaderRegistry().put(type, loader);
getKitRegisty().remove(type);
}
/**
* Returns the currently registered <code>EditorKit
* class name for the type <code>type.
*
* @param type the non-<code>null content type
*
* @since 1.3
*/
public static String getEditorKitClassNameForContentType(String type) {
return getKitTypeRegistry().get(type);
}
private static Hashtable<String, String> getKitTypeRegistry() {
loadDefaultKitsIfNecessary();
return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
}
private static Hashtable<String, ClassLoader> getKitLoaderRegistry() {
loadDefaultKitsIfNecessary();
return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
}
private static Hashtable<String, EditorKit> getKitRegisty() {
Hashtable 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.
*/
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 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 <code>JEditorPane.
* The preferred size for <code>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 <em>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.
*
* @return a <code>Dimension 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 <code>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 <code>text/html
* 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. 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(), and replace the
* existing <code>Document with the new one. You are then assured the
* previous <code>Document 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 the old
* text will be deleted
* @see #getText
* @beaninfo
* 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 <code>TextComponent
* in terms of the
* content type of this editor. If an exception is thrown while
* attempting to retrieve the text, <code>null will be returned.
* This is implemented to call <code>JTextComponent.write with
* a <code>StringWriter.
*
* @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
* <code>Scrollable to match the width of the viewport.
*
* @return true if a viewport should force the Scrollables width to
* match its own, false otherwise
*/
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
* <code>Scrollable to match the height of the viewport.
*
* @return true if a viewport should force the
* <code>Scrollable's height to match its own,
* false otherwise
*/
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 <code>readObject and writeObject in
* <code>JComponent 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.
*/
private EditorKit kit;
private boolean isUserSetEditorKit;
private Hashtable<String, Object> pageProperties;
/** Should be kept in sync with javax.swing.text.html.FormView counterpart. */
final static String PostDataProperty = "javax.swing.JEditorPane.postdata";
/**
* 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 #getUIClassID
* @see #readObject
*/
private static final String uiClassID = "EditorPaneUI";
/**
* 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.
*
* @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.
* <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.
*
* @since 1.5
*/
public static final String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
static final Map<String, String> defaultEditorKitMap = new HashMap(0);
/**
* Returns a string representation of this <code>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 <code>null.
*
* @return a string representation of this <code>JEditorPane
*/
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.
*
* @return an AccessibleJEditorPane that serves as the
* AccessibleContext of this JEditorPane
*/
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
* <code>JEditorPane class. It provides an implementation of the
* Java Accessibility API appropriate to editor pane user-interface
* elements.
* <p>
* <strong>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 <code>java.beans package.
* Please see {@link java.beans.XMLEncoder}.
*/
protected class AccessibleJEditorPane extends AccessibleJTextComponent {
/**
* Gets the accessibleDescription property of this object. If this
* property isn't set, returns the content type of this
* <code>JEditorPane instead (e.g. "plain/text", "html/text").
*
* @return the localized description of the object; <code>null
* 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.
*
* @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 <code>AccessibleHypertext,
* and is used in instances where the <code>EditorKit
* installed in this <code>JEditorPane is an instance of
* <code>HTMLEditorKit.
* <p>
* <strong>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 <code>java.beans package.
* Please see {@link java.beans.XMLEncoder}.
*/
protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane {
private AccessibleContext accessibleContext;
public AccessibleText getAccessibleText() {
return new JEditorPaneAccessibleHypertextSupport();
}
protected AccessibleJEditorPaneHTML () {
HTMLEditorKit kit = (HTMLEditorKit)JEditorPane.this.getEditorKit();
accessibleContext = kit.getAccessibleContext();
}
/**
* 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.
*
* @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.
*
* @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
* <code>AccessibleJEditorPaneHTML.getAccessibleText.
*
* Provides support for <code>AccessibleHypertext in case
* there is an HTML document being displayed in this
* <code>JEditorPane.
*
*/
protected class JEditorPaneAccessibleHypertextSupport
extends AccessibleJEditorPane implements AccessibleHypertext {
public class HTMLLink extends AccessibleHyperlink {
Element 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).
*
* @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.
*
* @return the zero-based number of Actions in this object
*/
public int getAccessibleActionCount() {
return 1;
}
/**
* Perform the specified Action on the object
*
* @param i zero-based index of actions
* @return true if the 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.
*
* @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.
*
* @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://www.sun.com/access">Accessibility
* this method would return a String containing the text:
* 'Accessibility'.
*
* 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
*
* @return index of start of link
*/
public int getStartIndex() {
return element.getStartOffset();
}
/**
* 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
*/
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
*/
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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @return the view factory
*/
public ViewFactory getViewFactory() {
return this;
}
/**
* 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.
*/
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.
*/
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.
*/
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.
*
* @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;
}
}
}
}
Other Java examples (source code examples)
Here is a short list of links related to this Java JEditorPane.java source code file:
|