300 }
301
302 /**
303 * Removes a hyperlink listener.
304 *
305 * @param listener the listener
306 */
307 public synchronized void removeHyperlinkListener(HyperlinkListener listener) {
308 listenerList.remove(HyperlinkListener.class, listener);
309 }
310
311 /**
312 * Returns an array of all the <code>HyperLinkListener</code>s added
313 * to this JEditorPane with addHyperlinkListener().
314 *
315 * @return all of the <code>HyperLinkListener</code>s added or an empty
316 * array if no listeners have been added
317 * @since 1.4
318 */
319 public synchronized HyperlinkListener[] getHyperlinkListeners() {
320 return (HyperlinkListener[])listenerList.getListeners(
321 HyperlinkListener.class);
322 }
323
324 /**
325 * Notifies all listeners that have registered interest for
326 * notification on this event type. This is normally called
327 * by the currently installed <code>EditorKit</code> if a content type
328 * that supports hyperlinks is currently active and there
329 * was activity with a link. The listener list is processed
330 * last to first.
331 *
332 * @param e the event
333 * @see EventListenerList
334 */
335 public void fireHyperlinkUpdate(HyperlinkEvent e) {
336 // Guaranteed to return a non-null array
337 Object[] listeners = listenerList.getListenerList();
338 // Process the listeners last to first, notifying
339 // those that are interested in this event
340 for (int i = listeners.length-2; i>=0; i-=2) {
341 if (listeners[i]==HyperlinkListener.class) {
479 // Have to scroll after painted.
480 SwingUtilities.invokeLater(new Runnable() {
481 public void run() {
482 scrollToReference(reference);
483 }
484 });
485 }
486 getDocument().putProperty(Document.StreamDescriptionProperty, page);
487 }
488 firePropertyChange("page", loaded, page);
489 }
490
491 /**
492 * Create model and initialize document properties from page properties.
493 */
494 private Document initializeModel(EditorKit kit, URL page) {
495 Document doc = kit.createDefaultDocument();
496 if (pageProperties != null) {
497 // transfer properties discovered in stream to the
498 // document property collection.
499 for (Enumeration e = pageProperties.keys(); e.hasMoreElements() ;) {
500 Object key = e.nextElement();
501 doc.putProperty(key, pageProperties.get(key));
502 }
503 pageProperties.clear();
504 }
505 if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
506 doc.putProperty(Document.StreamDescriptionProperty, page);
507 }
508 return doc;
509 }
510
511 /**
512 * Return load priority for the document or -1 if priority not supported.
513 */
514 private int getAsynchronousLoadPriority(Document doc) {
515 return (doc instanceof AbstractDocument ?
516 ((AbstractDocument) doc).getAsynchronousLoadPriority() : -1);
517 }
518
519 /**
520 * This method initializes from a stream. If the kit is
815 try {
816 SwingUtilities.invokeAndWait(new Runnable() {
817 public void run() {
818 handleConnectionProperties(conn);
819 }
820 });
821 } catch (InterruptedException e) {
822 throw new RuntimeException(e);
823 } catch (InvocationTargetException e) {
824 throw new RuntimeException(e);
825 }
826 }
827 return conn.getInputStream();
828 }
829
830 /**
831 * Handle URL connection properties (most notably, content type).
832 */
833 private void handleConnectionProperties(URLConnection conn) {
834 if (pageProperties == null) {
835 pageProperties = new Hashtable();
836 }
837 String type = conn.getContentType();
838 if (type != null) {
839 setContentType(type);
840 pageProperties.put("content-type", type);
841 }
842 pageProperties.put(Document.StreamDescriptionProperty, conn.getURL());
843 String enc = conn.getContentEncoding();
844 if (enc != null) {
845 pageProperties.put("content-encoding", enc);
846 }
847 }
848
849 private Object getPostData() {
850 return getDocument().getProperty(PostDataProperty);
851 }
852
853 private void handlePostData(HttpURLConnection conn, Object postData)
854 throws IOException {
855 conn.setDoOutput(true);
1029 if (type.toLowerCase().startsWith("text/")) {
1030 setCharsetFromContentTypeParameters(paramList);
1031 }
1032 }
1033 if ((kit == null) || (! type.equals(kit.getContentType()))
1034 || !isUserSetEditorKit) {
1035 EditorKit k = getEditorKitForContentType(type);
1036 if (k != null && k != kit) {
1037 setEditorKit(k);
1038 isUserSetEditorKit = false;
1039 }
1040 }
1041
1042 }
1043
1044 /**
1045 * This method gets the charset information specified as part
1046 * of the content type in the http header information.
1047 */
1048 private void setCharsetFromContentTypeParameters(String paramlist) {
1049 String charset = null;
1050 try {
1051 // paramlist is handed to us with a leading ';', strip it.
1052 int semi = paramlist.indexOf(';');
1053 if (semi > -1 && semi < paramlist.length()-1) {
1054 paramlist = paramlist.substring(semi + 1);
1055 }
1056
1057 if (paramlist.length() > 0) {
1058 // parse the paramlist into attr-value pairs & get the
1059 // charset pair's value
1060 HeaderParser hdrParser = new HeaderParser(paramlist);
1061 charset = hdrParser.findValue("charset");
1062 if (charset != null) {
1063 putClientProperty("charset", charset);
1064 }
1065 }
1066 }
1067 catch (IndexOutOfBoundsException e) {
1068 // malformed parameter list, use charset we have
1069 }
1120 * Fetches the editor kit to use for the given type
1121 * of content. This is called when a type is requested
1122 * that doesn't match the currently installed type.
1123 * If the component doesn't have an <code>EditorKit</code> registered
1124 * for the given type, it will try to create an
1125 * <code>EditorKit</code> from the default <code>EditorKit</code> registry.
1126 * If that fails, a <code>PlainEditorKit</code> is used on the
1127 * assumption that all text documents can be represented
1128 * as plain text.
1129 * <p>
1130 * This method can be reimplemented to use some
1131 * other kind of type registry. This can
1132 * be reimplemented to use the Java Activation
1133 * Framework, for example.
1134 *
1135 * @param type the non-<code>null</code> content type
1136 * @return the editor kit
1137 */
1138 public EditorKit getEditorKitForContentType(String type) {
1139 if (typeHandlers == null) {
1140 typeHandlers = new Hashtable(3);
1141 }
1142 EditorKit k = (EditorKit) typeHandlers.get(type);
1143 if (k == null) {
1144 k = createEditorKitForContentType(type);
1145 if (k != null) {
1146 setEditorKitForContentType(type, k);
1147 }
1148 }
1149 if (k == null) {
1150 k = createDefaultEditorKit();
1151 }
1152 return k;
1153 }
1154
1155 /**
1156 * Directly sets the editor kit to use for the given type. A
1157 * look-and-feel implementation might use this in conjunction
1158 * with <code>createEditorKitForContentType</code> to install handlers for
1159 * content types with a look-and-feel bias.
1160 *
1161 * @param type the non-<code>null</code> content type
1162 * @param k the editor kit to be set
1163 */
1164 public void setEditorKitForContentType(String type, EditorKit k) {
1165 if (typeHandlers == null) {
1166 typeHandlers = new Hashtable(3);
1167 }
1168 typeHandlers.put(type, k);
1169 }
1170
1171 /**
1172 * Replaces the currently selected content with new content
1173 * represented by the given string. If there is no selection
1174 * this amounts to an insert of the given text. If there
1175 * is no replacement text (i.e. the content string is empty
1176 * or <code>null</code>) this amounts to a removal of the
1177 * current selection. The replacement text will have the
1178 * attributes currently defined for input. If the component is not
1179 * editable, beep and return.
1180 * <p>
1181 * This method is thread safe, although most Swing methods
1182 * are not. Please see
1183 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
1184 * to Use Threads</A> for more information.
1185 *
1186 * @param content the content to replace the selection with. This
1221 }
1222
1223 /**
1224 * Creates a handler for the given type from the default registry
1225 * of editor kits. The registry is created if necessary. If the
1226 * registered class has not yet been loaded, an attempt
1227 * is made to dynamically load the prototype of the kit for the
1228 * given type. If the type was registered with a <code>ClassLoader</code>,
1229 * that <code>ClassLoader</code> will be used to load the prototype.
1230 * If there was no registered <code>ClassLoader</code>,
1231 * <code>Class.forName</code> will be used to load the prototype.
1232 * <p>
1233 * Once a prototype <code>EditorKit</code> instance is successfully
1234 * located, it is cloned and the clone is returned.
1235 *
1236 * @param type the content type
1237 * @return the editor kit, or <code>null</code> if there is nothing
1238 * registered for the given type
1239 */
1240 public static EditorKit createEditorKitForContentType(String type) {
1241 EditorKit k = null;
1242 Hashtable kitRegistry = getKitRegisty();
1243 k = (EditorKit) kitRegistry.get(type);
1244 if (k == null) {
1245 // try to dynamically load the support
1246 String classname = (String) getKitTypeRegistry().get(type);
1247 ClassLoader loader = (ClassLoader) getKitLoaderRegistry().get(type);
1248 try {
1249 Class c;
1250 if (loader != null) {
1251 c = loader.loadClass(classname);
1252 } else {
1253 // Will only happen if developer has invoked
1254 // registerEditorKitForContentType(type, class, null).
1255 c = Class.forName(classname, true, Thread.currentThread().
1256 getContextClassLoader());
1257 }
1258 k = (EditorKit) c.newInstance();
1259 kitRegistry.put(type, k);
1260 } catch (Throwable e) {
1261 k = null;
1262 }
1263 }
1264
1265 // create a copy of the prototype or null if there
1266 // is no prototype.
1267 if (k != null) {
1284 */
1285 public static void registerEditorKitForContentType(String type, String classname) {
1286 registerEditorKitForContentType(type, classname,Thread.currentThread().
1287 getContextClassLoader());
1288 }
1289
1290 /**
1291 * Establishes the default bindings of <code>type</code> to
1292 * <code>classname</code>.
1293 * The class will be dynamically loaded later when actually
1294 * needed using the given <code>ClassLoader</code>,
1295 * and can be safely changed
1296 * before attempted uses to avoid loading unwanted classes.
1297 *
1298 * @param type the non-<code>null</code> content type
1299 * @param classname the class to load later
1300 * @param loader the <code>ClassLoader</code> to use to load the name
1301 */
1302 public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) {
1303 getKitTypeRegistry().put(type, classname);
1304 getKitLoaderRegistry().put(type, loader);
1305 getKitRegisty().remove(type);
1306 }
1307
1308 /**
1309 * Returns the currently registered <code>EditorKit</code>
1310 * class name for the type <code>type</code>.
1311 *
1312 * @param type the non-<code>null</code> content type
1313 *
1314 * @since 1.3
1315 */
1316 public static String getEditorKitClassNameForContentType(String type) {
1317 return (String)getKitTypeRegistry().get(type);
1318 }
1319
1320 private static Hashtable getKitTypeRegistry() {
1321 loadDefaultKitsIfNecessary();
1322 return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
1323 }
1324
1325 private static Hashtable getKitLoaderRegistry() {
1326 loadDefaultKitsIfNecessary();
1327 return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
1328 }
1329
1330 private static Hashtable getKitRegisty() {
1331 Hashtable ht = (Hashtable)SwingUtilities.appContextGet(kitRegistryKey);
1332 if (ht == null) {
1333 ht = new Hashtable(3);
1334 SwingUtilities.appContextPut(kitRegistryKey, ht);
1335 }
1336 return ht;
1337 }
1338
1339 /**
1340 * This is invoked every time the registries are accessed. Loading
1341 * is done this way instead of via a static as the static is only
1342 * called once when running in plugin resulting in the entries only
1343 * appearing in the first applet.
1344 */
1345 private static void loadDefaultKitsIfNecessary() {
1346 if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
1347 synchronized(defaultEditorKitMap) {
1348 if (defaultEditorKitMap.size() == 0) {
1349 defaultEditorKitMap.put("text/plain",
1350 "javax.swing.JEditorPane$PlainEditorKit");
1566 if (count == 0 && ui != null) {
1567 ui.installUI(this);
1568 }
1569 }
1570 }
1571
1572 // --- variables ---------------------------------------
1573
1574 /**
1575 * Stream currently loading asynchronously (potentially cancelable).
1576 * Access to this variable should be synchronized.
1577 */
1578 PageStream loading;
1579
1580 /**
1581 * Current content binding of the editor.
1582 */
1583 private EditorKit kit;
1584 private boolean isUserSetEditorKit;
1585
1586 private Hashtable pageProperties;
1587
1588 /** Should be kept in sync with javax.swing.text.html.FormView counterpart. */
1589 final static String PostDataProperty = "javax.swing.JEditorPane.postdata";
1590
1591 /**
1592 * Table of registered type handlers for this editor.
1593 */
1594 private Hashtable typeHandlers;
1595
1596 /*
1597 * Private AppContext keys for this class's static variables.
1598 */
1599 private static final Object kitRegistryKey = new Object(); // JEditorPane.kitRegistry
1600 private static final Object kitTypeRegistryKey = new Object(); // JEditorPane.kitTypeRegistry
1601 private static final Object kitLoaderRegistryKey = new Object(); // JEditorPane.kitLoaderRegistry
1602
1603 /**
1604 * @see #getUIClassID
1605 * @see #readObject
1606 */
1607 private static final String uiClassID = "EditorPaneUI";
1608
1609
1610 /**
1611 * Key for a client property used to indicate whether
1612 * <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units">
1613 * w3c compliant</a> length units are used for html rendering.
1614 * <p>
1964 * Get the index with the hypertext document at which this
1965 * link begins
1966 *
1967 * @return index of start of link
1968 */
1969 public int getStartIndex() {
1970 return element.getStartOffset();
1971 }
1972
1973 /**
1974 * Get the index with the hypertext document at which this
1975 * link ends
1976 *
1977 * @return index of end of link
1978 */
1979 public int getEndIndex() {
1980 return element.getEndOffset();
1981 }
1982 }
1983
1984 private class LinkVector extends Vector {
1985 public int baseElementIndex(Element e) {
1986 HTMLLink l;
1987 for (int i = 0; i < elementCount; i++) {
1988 l = (HTMLLink) elementAt(i);
1989 if (l.element == e) {
1990 return i;
1991 }
1992 }
1993 return -1;
1994 }
1995 }
1996
1997 LinkVector hyperlinks;
1998 boolean linksValid = false;
1999
2000 /**
2001 * Build the private table mapping links to locations in the text
2002 */
2003 private void buildLinkTable() {
2004 hyperlinks.removeAllElements();
2005 Document d = JEditorPane.this.getDocument();
2006 if (d != null) {
2007 ElementIterator ei = new ElementIterator(d);
2008 Element e;
2080
2081 // don't need to verify that it's an HREF element; if
2082 // not, then it won't be in the hyperlinks Vector, and
2083 // so indexOf will return -1 in any case
2084 return hyperlinks.baseElementIndex(e);
2085 }
2086
2087 /**
2088 * Returns the index into an array of hyperlinks that
2089 * index. If there is no hyperlink at this index, it returns
2090 * null.
2091 *
2092 * @param linkIndex into the set of hyperlinks for this hypertext doc.
2093 * @return string representation of the hyperlink
2094 */
2095 public AccessibleHyperlink getLink(int linkIndex) {
2096 if (linksValid == false) {
2097 buildLinkTable();
2098 }
2099 if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
2100 return (AccessibleHyperlink) hyperlinks.elementAt(linkIndex);
2101 } else {
2102 return null;
2103 }
2104 }
2105
2106 /**
2107 * Returns the contiguous text within the document that
2108 * is associated with this hyperlink.
2109 *
2110 * @param linkIndex into the set of hyperlinks for this hypertext doc.
2111 * @return the contiguous text sharing the link at this index
2112 */
2113 public String getLinkText(int linkIndex) {
2114 if (linksValid == false) {
2115 buildLinkTable();
2116 }
2117 Element e = (Element) hyperlinks.elementAt(linkIndex);
2118 if (e != null) {
2119 Document d = JEditorPane.this.getDocument();
2120 if (d != null) {
|
300 }
301
302 /**
303 * Removes a hyperlink listener.
304 *
305 * @param listener the listener
306 */
307 public synchronized void removeHyperlinkListener(HyperlinkListener listener) {
308 listenerList.remove(HyperlinkListener.class, listener);
309 }
310
311 /**
312 * Returns an array of all the <code>HyperLinkListener</code>s added
313 * to this JEditorPane with addHyperlinkListener().
314 *
315 * @return all of the <code>HyperLinkListener</code>s added or an empty
316 * array if no listeners have been added
317 * @since 1.4
318 */
319 public synchronized HyperlinkListener[] getHyperlinkListeners() {
320 return listenerList.getListeners(javax.swing.event.HyperlinkListener.class);
321 }
322
323 /**
324 * Notifies all listeners that have registered interest for
325 * notification on this event type. This is normally called
326 * by the currently installed <code>EditorKit</code> if a content type
327 * that supports hyperlinks is currently active and there
328 * was activity with a link. The listener list is processed
329 * last to first.
330 *
331 * @param e the event
332 * @see EventListenerList
333 */
334 public void fireHyperlinkUpdate(HyperlinkEvent e) {
335 // Guaranteed to return a non-null array
336 Object[] listeners = listenerList.getListenerList();
337 // Process the listeners last to first, notifying
338 // those that are interested in this event
339 for (int i = listeners.length-2; i>=0; i-=2) {
340 if (listeners[i]==HyperlinkListener.class) {
478 // Have to scroll after painted.
479 SwingUtilities.invokeLater(new Runnable() {
480 public void run() {
481 scrollToReference(reference);
482 }
483 });
484 }
485 getDocument().putProperty(Document.StreamDescriptionProperty, page);
486 }
487 firePropertyChange("page", loaded, page);
488 }
489
490 /**
491 * Create model and initialize document properties from page properties.
492 */
493 private Document initializeModel(EditorKit kit, URL page) {
494 Document doc = kit.createDefaultDocument();
495 if (pageProperties != null) {
496 // transfer properties discovered in stream to the
497 // document property collection.
498 for (Enumeration<String> e = pageProperties.keys(); e.hasMoreElements() ;) {
499 String key = e.nextElement();
500 doc.putProperty(key, pageProperties.get(key));
501 }
502 pageProperties.clear();
503 }
504 if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
505 doc.putProperty(Document.StreamDescriptionProperty, page);
506 }
507 return doc;
508 }
509
510 /**
511 * Return load priority for the document or -1 if priority not supported.
512 */
513 private int getAsynchronousLoadPriority(Document doc) {
514 return (doc instanceof AbstractDocument ?
515 ((AbstractDocument) doc).getAsynchronousLoadPriority() : -1);
516 }
517
518 /**
519 * This method initializes from a stream. If the kit is
814 try {
815 SwingUtilities.invokeAndWait(new Runnable() {
816 public void run() {
817 handleConnectionProperties(conn);
818 }
819 });
820 } catch (InterruptedException e) {
821 throw new RuntimeException(e);
822 } catch (InvocationTargetException e) {
823 throw new RuntimeException(e);
824 }
825 }
826 return conn.getInputStream();
827 }
828
829 /**
830 * Handle URL connection properties (most notably, content type).
831 */
832 private void handleConnectionProperties(URLConnection conn) {
833 if (pageProperties == null) {
834 pageProperties = new Hashtable<String, Object>();
835 }
836 String type = conn.getContentType();
837 if (type != null) {
838 setContentType(type);
839 pageProperties.put("content-type", type);
840 }
841 pageProperties.put(Document.StreamDescriptionProperty, conn.getURL());
842 String enc = conn.getContentEncoding();
843 if (enc != null) {
844 pageProperties.put("content-encoding", enc);
845 }
846 }
847
848 private Object getPostData() {
849 return getDocument().getProperty(PostDataProperty);
850 }
851
852 private void handlePostData(HttpURLConnection conn, Object postData)
853 throws IOException {
854 conn.setDoOutput(true);
1028 if (type.toLowerCase().startsWith("text/")) {
1029 setCharsetFromContentTypeParameters(paramList);
1030 }
1031 }
1032 if ((kit == null) || (! type.equals(kit.getContentType()))
1033 || !isUserSetEditorKit) {
1034 EditorKit k = getEditorKitForContentType(type);
1035 if (k != null && k != kit) {
1036 setEditorKit(k);
1037 isUserSetEditorKit = false;
1038 }
1039 }
1040
1041 }
1042
1043 /**
1044 * This method gets the charset information specified as part
1045 * of the content type in the http header information.
1046 */
1047 private void setCharsetFromContentTypeParameters(String paramlist) {
1048 String charset;
1049 try {
1050 // paramlist is handed to us with a leading ';', strip it.
1051 int semi = paramlist.indexOf(';');
1052 if (semi > -1 && semi < paramlist.length()-1) {
1053 paramlist = paramlist.substring(semi + 1);
1054 }
1055
1056 if (paramlist.length() > 0) {
1057 // parse the paramlist into attr-value pairs & get the
1058 // charset pair's value
1059 HeaderParser hdrParser = new HeaderParser(paramlist);
1060 charset = hdrParser.findValue("charset");
1061 if (charset != null) {
1062 putClientProperty("charset", charset);
1063 }
1064 }
1065 }
1066 catch (IndexOutOfBoundsException e) {
1067 // malformed parameter list, use charset we have
1068 }
1119 * Fetches the editor kit to use for the given type
1120 * of content. This is called when a type is requested
1121 * that doesn't match the currently installed type.
1122 * If the component doesn't have an <code>EditorKit</code> registered
1123 * for the given type, it will try to create an
1124 * <code>EditorKit</code> from the default <code>EditorKit</code> registry.
1125 * If that fails, a <code>PlainEditorKit</code> is used on the
1126 * assumption that all text documents can be represented
1127 * as plain text.
1128 * <p>
1129 * This method can be reimplemented to use some
1130 * other kind of type registry. This can
1131 * be reimplemented to use the Java Activation
1132 * Framework, for example.
1133 *
1134 * @param type the non-<code>null</code> content type
1135 * @return the editor kit
1136 */
1137 public EditorKit getEditorKitForContentType(String type) {
1138 if (typeHandlers == null) {
1139 typeHandlers = new Hashtable<String, EditorKit>(3);
1140 }
1141 EditorKit k = typeHandlers.get(type);
1142 if (k == null) {
1143 k = createEditorKitForContentType(type);
1144 if (k != null) {
1145 setEditorKitForContentType(type, k);
1146 }
1147 }
1148 if (k == null) {
1149 k = createDefaultEditorKit();
1150 }
1151 return k;
1152 }
1153
1154 /**
1155 * Directly sets the editor kit to use for the given type. A
1156 * look-and-feel implementation might use this in conjunction
1157 * with <code>createEditorKitForContentType</code> to install handlers for
1158 * content types with a look-and-feel bias.
1159 *
1160 * @param type the non-<code>null</code> content type
1161 * @param k the editor kit to be set
1162 */
1163 public void setEditorKitForContentType(String type, EditorKit k) {
1164 if (typeHandlers == null) {
1165 typeHandlers = new Hashtable<String, EditorKit>(3);
1166 }
1167 typeHandlers.put(type, k);
1168 }
1169
1170 /**
1171 * Replaces the currently selected content with new content
1172 * represented by the given string. If there is no selection
1173 * this amounts to an insert of the given text. If there
1174 * is no replacement text (i.e. the content string is empty
1175 * or <code>null</code>) this amounts to a removal of the
1176 * current selection. The replacement text will have the
1177 * attributes currently defined for input. If the component is not
1178 * editable, beep and return.
1179 * <p>
1180 * This method is thread safe, although most Swing methods
1181 * are not. Please see
1182 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
1183 * to Use Threads</A> for more information.
1184 *
1185 * @param content the content to replace the selection with. This
1220 }
1221
1222 /**
1223 * Creates a handler for the given type from the default registry
1224 * of editor kits. The registry is created if necessary. If the
1225 * registered class has not yet been loaded, an attempt
1226 * is made to dynamically load the prototype of the kit for the
1227 * given type. If the type was registered with a <code>ClassLoader</code>,
1228 * that <code>ClassLoader</code> will be used to load the prototype.
1229 * If there was no registered <code>ClassLoader</code>,
1230 * <code>Class.forName</code> will be used to load the prototype.
1231 * <p>
1232 * Once a prototype <code>EditorKit</code> instance is successfully
1233 * located, it is cloned and the clone is returned.
1234 *
1235 * @param type the content type
1236 * @return the editor kit, or <code>null</code> if there is nothing
1237 * registered for the given type
1238 */
1239 public static EditorKit createEditorKitForContentType(String type) {
1240 Hashtable<String, EditorKit> kitRegistry = getKitRegisty();
1241 EditorKit k = kitRegistry.get(type);
1242 if (k == null) {
1243 // try to dynamically load the support
1244 String classname = getKitTypeRegistry().get(type);
1245 ClassLoader loader = getKitLoaderRegistry().get(type);
1246 try {
1247 Class c;
1248 if (loader != null) {
1249 c = loader.loadClass(classname);
1250 } else {
1251 // Will only happen if developer has invoked
1252 // registerEditorKitForContentType(type, class, null).
1253 c = Class.forName(classname, true, Thread.currentThread().
1254 getContextClassLoader());
1255 }
1256 k = (EditorKit) c.newInstance();
1257 kitRegistry.put(type, k);
1258 } catch (Throwable e) {
1259 k = null;
1260 }
1261 }
1262
1263 // create a copy of the prototype or null if there
1264 // is no prototype.
1265 if (k != null) {
1282 */
1283 public static void registerEditorKitForContentType(String type, String classname) {
1284 registerEditorKitForContentType(type, classname,Thread.currentThread().
1285 getContextClassLoader());
1286 }
1287
1288 /**
1289 * Establishes the default bindings of <code>type</code> to
1290 * <code>classname</code>.
1291 * The class will be dynamically loaded later when actually
1292 * needed using the given <code>ClassLoader</code>,
1293 * and can be safely changed
1294 * before attempted uses to avoid loading unwanted classes.
1295 *
1296 * @param type the non-<code>null</code> content type
1297 * @param classname the class to load later
1298 * @param loader the <code>ClassLoader</code> to use to load the name
1299 */
1300 public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) {
1301 getKitTypeRegistry().put(type, classname);
1302 if (loader != null) {
1303 getKitLoaderRegistry().put(type, loader);
1304 } else {
1305 getKitLoaderRegistry().remove(type);
1306 }
1307 getKitRegisty().remove(type);
1308 }
1309
1310 /**
1311 * Returns the currently registered <code>EditorKit</code>
1312 * class name for the type <code>type</code>.
1313 *
1314 * @param type the non-<code>null</code> content type
1315 *
1316 * @since 1.3
1317 */
1318 public static String getEditorKitClassNameForContentType(String type) {
1319 return getKitTypeRegistry().get(type);
1320 }
1321
1322 private static Hashtable<String, String> getKitTypeRegistry() {
1323 loadDefaultKitsIfNecessary();
1324 return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
1325 }
1326
1327 private static Hashtable<String, ClassLoader> getKitLoaderRegistry() {
1328 loadDefaultKitsIfNecessary();
1329 return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
1330 }
1331
1332 private static Hashtable<String, EditorKit> getKitRegisty() {
1333 Hashtable ht = (Hashtable)SwingUtilities.appContextGet(kitRegistryKey);
1334 if (ht == null) {
1335 ht = new Hashtable(3);
1336 SwingUtilities.appContextPut(kitRegistryKey, ht);
1337 }
1338 return ht;
1339 }
1340
1341 /**
1342 * This is invoked every time the registries are accessed. Loading
1343 * is done this way instead of via a static as the static is only
1344 * called once when running in plugin resulting in the entries only
1345 * appearing in the first applet.
1346 */
1347 private static void loadDefaultKitsIfNecessary() {
1348 if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
1349 synchronized(defaultEditorKitMap) {
1350 if (defaultEditorKitMap.size() == 0) {
1351 defaultEditorKitMap.put("text/plain",
1352 "javax.swing.JEditorPane$PlainEditorKit");
1568 if (count == 0 && ui != null) {
1569 ui.installUI(this);
1570 }
1571 }
1572 }
1573
1574 // --- variables ---------------------------------------
1575
1576 /**
1577 * Stream currently loading asynchronously (potentially cancelable).
1578 * Access to this variable should be synchronized.
1579 */
1580 PageStream loading;
1581
1582 /**
1583 * Current content binding of the editor.
1584 */
1585 private EditorKit kit;
1586 private boolean isUserSetEditorKit;
1587
1588 private Hashtable<String, Object> pageProperties;
1589
1590 /** Should be kept in sync with javax.swing.text.html.FormView counterpart. */
1591 final static String PostDataProperty = "javax.swing.JEditorPane.postdata";
1592
1593 /**
1594 * Table of registered type handlers for this editor.
1595 */
1596 private Hashtable<String, EditorKit> typeHandlers;
1597
1598 /*
1599 * Private AppContext keys for this class's static variables.
1600 */
1601 private static final Object kitRegistryKey = new Object(); // JEditorPane.kitRegistry
1602 private static final Object kitTypeRegistryKey = new Object(); // JEditorPane.kitTypeRegistry
1603 private static final Object kitLoaderRegistryKey = new Object(); // JEditorPane.kitLoaderRegistry
1604
1605 /**
1606 * @see #getUIClassID
1607 * @see #readObject
1608 */
1609 private static final String uiClassID = "EditorPaneUI";
1610
1611
1612 /**
1613 * Key for a client property used to indicate whether
1614 * <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units">
1615 * w3c compliant</a> length units are used for html rendering.
1616 * <p>
1966 * Get the index with the hypertext document at which this
1967 * link begins
1968 *
1969 * @return index of start of link
1970 */
1971 public int getStartIndex() {
1972 return element.getStartOffset();
1973 }
1974
1975 /**
1976 * Get the index with the hypertext document at which this
1977 * link ends
1978 *
1979 * @return index of end of link
1980 */
1981 public int getEndIndex() {
1982 return element.getEndOffset();
1983 }
1984 }
1985
1986 private class LinkVector extends Vector<HTMLLink> {
1987 public int baseElementIndex(Element e) {
1988 HTMLLink l;
1989 for (int i = 0; i < elementCount; i++) {
1990 l = elementAt(i);
1991 if (l.element == e) {
1992 return i;
1993 }
1994 }
1995 return -1;
1996 }
1997 }
1998
1999 LinkVector hyperlinks;
2000 boolean linksValid = false;
2001
2002 /**
2003 * Build the private table mapping links to locations in the text
2004 */
2005 private void buildLinkTable() {
2006 hyperlinks.removeAllElements();
2007 Document d = JEditorPane.this.getDocument();
2008 if (d != null) {
2009 ElementIterator ei = new ElementIterator(d);
2010 Element e;
2082
2083 // don't need to verify that it's an HREF element; if
2084 // not, then it won't be in the hyperlinks Vector, and
2085 // so indexOf will return -1 in any case
2086 return hyperlinks.baseElementIndex(e);
2087 }
2088
2089 /**
2090 * Returns the index into an array of hyperlinks that
2091 * index. If there is no hyperlink at this index, it returns
2092 * null.
2093 *
2094 * @param linkIndex into the set of hyperlinks for this hypertext doc.
2095 * @return string representation of the hyperlink
2096 */
2097 public AccessibleHyperlink getLink(int linkIndex) {
2098 if (linksValid == false) {
2099 buildLinkTable();
2100 }
2101 if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
2102 return hyperlinks.elementAt(linkIndex);
2103 } else {
2104 return null;
2105 }
2106 }
2107
2108 /**
2109 * Returns the contiguous text within the document that
2110 * is associated with this hyperlink.
2111 *
2112 * @param linkIndex into the set of hyperlinks for this hypertext doc.
2113 * @return the contiguous text sharing the link at this index
2114 */
2115 public String getLinkText(int linkIndex) {
2116 if (linksValid == false) {
2117 buildLinkTable();
2118 }
2119 Element e = (Element) hyperlinks.elementAt(linkIndex);
2120 if (e != null) {
2121 Document d = JEditorPane.this.getDocument();
2122 if (d != null) {
|