KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > html > HTMLEditorKit


1 /*
2  * @(#)HTMLEditorKit.java 1.131 04/05/18
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text.html;
8
9 import java.lang.reflect.Method JavaDoc;
10 import java.awt.*;
11 import java.awt.event.*;
12 import java.io.*;
13 import java.net.MalformedURLException JavaDoc;
14 import java.net.URL JavaDoc;
15 import javax.swing.text.*;
16 import javax.swing.*;
17 import javax.swing.border.*;
18 import javax.swing.event.*;
19 import javax.swing.plaf.TextUI JavaDoc;
20 import java.util.*;
21 import javax.accessibility.*;
22 import java.lang.ref.*;
23
24 /**
25  * The Swing JEditorPane text component supports different kinds
26  * of content via a plug-in mechanism called an EditorKit. Because
27  * HTML is a very popular format of content, some support is provided
28  * by default. The default support is provided by this class, which
29  * supports HTML version 3.2 (with some extensions), and is migrating
30  * toward version 4.0.
31  * The <applet> tag is not supported, but some support is provided
32  * for the <object> tag.
33  * <p>
34  * There are several goals of the HTML EditorKit provided, that have
35  * an effect upon the way that HTML is modeled. These
36  * have influenced its design in a substantial way.
37  * <dl>
38  * <p>
39  * <dt>
40  * Support editing
41  * <dd>
42  * It might seem fairly obvious that a plug-in for JEditorPane
43  * should provide editing support, but that fact has several
44  * design considerations. There are a substantial number of HTML
45  * documents that don't properly conform to an HTML specification.
46  * These must be normalized somewhat into a correct form if one
47  * is to edit them. Additionally, users don't like to be presented
48  * with an excessive amount of structure editing, so using traditional
49  * text editing gestures is preferred over using the HTML structure
50  * exactly as defined in the HTML document.
51  * <p>
52  * The modeling of HTML is provided by the class <code>HTMLDocument</code>.
53  * Its documention describes the details of how the HTML is modeled.
54  * The editing support leverages heavily off of the text package.
55  * <p>
56  * <dt>
57  * Extendable/Scalable
58  * <dd>
59  * To maximize the usefulness of this kit, a great deal of effort
60  * has gone into making it extendable. These are some of the
61  * features.
62  * <ol>
63  * <li>
64  * The parser is replacable. The default parser is the Hot Java
65  * parser which is DTD based. A different DTD can be used, or an
66  * entirely different parser can be used. To change the parser,
67  * reimplement the getParser method. The default parser is
68  * dynamically loaded when first asked for, so the class files
69  * will never be loaded if an alternative parser is used. The
70  * default parser is in a separate package called parser below
71  * this package.
72  * <li>
73  * The parser drives the ParserCallback, which is provided by
74  * HTMLDocument. To change the callback, subclass HTMLDocument
75  * and reimplement the createDefaultDocument method to return
76  * document that produces a different reader. The reader controls
77  * how the document is structured. Although the Document provides
78  * HTML support by default, there is nothing preventing support of
79  * non-HTML tags that result in alternative element structures.
80  * <li>
81  * The default view of the models are provided as a hierarchy of
82  * View implementations, so one can easily customize how a particular
83  * element is displayed or add capabilities for new kinds of elements
84  * by providing new View implementations. The default set of views
85  * are provided by the <code>HTMLFactory</code> class. This can
86  * be easily changed by subclassing or replacing the HTMLFactory
87  * and reimplementing the getViewFactory method to return the alternative
88  * factory.
89  * <li>
90  * The View implementations work primarily off of CSS attributes,
91  * which are kept in the views. This makes it possible to have
92  * multiple views mapped over the same model that appear substantially
93  * different. This can be especially useful for printing. For
94  * most HTML attributes, the HTML attributes are converted to CSS
95  * attributes for display. This helps make the View implementations
96  * more general purpose
97  * </ol>
98  * <p>
99  * <dt>
100  * Asynchronous Loading
101  * <dd>
102  * Larger documents involve a lot of parsing and take some time
103  * to load. By default, this kit produces documents that will be
104  * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
105  * This is controlled by a property on the document. The method
106  * <a HREF="#createDefaultDocument">createDefaultDocument</a> can
107  * be overriden to change this. The batching of work is done
108  * by the <code>HTMLDocument.HTMLReader</code> class. The actual
109  * work is done by the <code>DefaultStyledDocument</code> and
110  * <code>AbstractDocument</code> classes in the text package.
111  * <p>
112  * <dt>
113  * Customization from current LAF
114  * <dd>
115  * HTML provides a well known set of features without exactly
116  * specifying the display characteristics. Swing has a theme
117  * mechanism for its look-and-feel implementations. It is desirable
118  * for the look-and-feel to feed display characteristics into the
119  * HTML views. An user with poor vision for example would want
120  * high contrast and larger than typical fonts.
121  * <p>
122  * The support for this is provided by the <code>StyleSheet</code>
123  * class. The presentation of the HTML can be heavily influenced
124  * by the setting of the StyleSheet property on the EditorKit.
125  * <p>
126  * <dt>
127  * Not lossy
128  * <dd>
129  * An EditorKit has the ability to be read and save documents.
130  * It is generally the most pleasing to users if there is no loss
131  * of data between the two operation. The policy of the HTMLEditorKit
132  * will be to store things not recognized or not necessarily visible
133  * so they can be subsequently written out. The model of the HTML document
134  * should therefore contain all information discovered while reading the
135  * document. This is constrained in some ways by the need to support
136  * editing (i.e. incorrect documents sometimes must be normalized).
137  * The guiding principle is that information shouldn't be lost, but
138  * some might be synthesized to produce a more correct model or it might
139  * be rearranged.
140  * </dl>
141  *
142  * @author Timothy Prinzing
143  * @version 1.131 05/18/04
144  */

145 public class HTMLEditorKit extends StyledEditorKit implements Accessible {
146
147     private JEditorPane theEditor;
148    
149     /**
150      * Constructs an HTMLEditorKit, creates a StyleContext,
151      * and loads the style sheet.
152      */

153     public HTMLEditorKit() {
154
155     }
156
157     /**
158      * Get the MIME type of the data that this
159      * kit represents support for. This kit supports
160      * the type <code>text/html</code>.
161      *
162      * @return the type
163      */

164     public String JavaDoc getContentType() {
165     return "text/html";
166     }
167
168     /**
169      * Fetch a factory that is suitable for producing
170      * views of any models that are produced by this
171      * kit.
172      *
173      * @return the factory
174      */

175     public ViewFactory getViewFactory() {
176     return defaultFactory;
177     }
178
179     /**
180      * Create an uninitialized text storage model
181      * that is appropriate for this type of editor.
182      *
183      * @return the model
184      */

185     public Document createDefaultDocument() {
186     StyleSheet JavaDoc styles = getStyleSheet();
187     StyleSheet JavaDoc ss = new StyleSheet JavaDoc();
188
189     ss.addStyleSheet(styles);
190
191     HTMLDocument JavaDoc doc = new HTMLDocument JavaDoc(ss);
192     doc.setParser(getParser());
193     doc.setAsynchronousLoadPriority(4);
194     doc.setTokenThreshold(100);
195     return doc;
196     }
197
198     /**
199      * Inserts content from the given stream. If <code>doc</code> is
200      * an instance of HTMLDocument, this will read
201      * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
202      * the body Element, if you do not insert into the body an exception will
203      * be thrown. When inserting into a non-empty document all tags outside
204      * of the body (head, title) will be dropped.
205      *
206      * @param in the stream to read from
207      * @param doc the destination for the insertion
208      * @param pos the location in the document to place the
209      * content
210      * @exception IOException on any I/O error
211      * @exception BadLocationException if pos represents an invalid
212      * location within the document
213      * @exception RuntimeException (will eventually be a BadLocationException)
214      * if pos is invalid
215      */

216     public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
217
218     if (doc instanceof HTMLDocument JavaDoc) {
219         HTMLDocument JavaDoc hdoc = (HTMLDocument JavaDoc) doc;
220         Parser p = getParser();
221         if (p == null) {
222         throw new IOException("Can't load parser");
223         }
224         if (pos > doc.getLength()) {
225         throw new BadLocationException("Invalid location", pos);
226         }
227
228         ParserCallback receiver = hdoc.getReader(pos);
229         Boolean JavaDoc ignoreCharset = (Boolean JavaDoc)doc.getProperty("IgnoreCharsetDirective");
230         p.parse(in, receiver, (ignoreCharset == null) ? false : ignoreCharset.booleanValue());
231         receiver.flush();
232     } else {
233         super.read(in, doc, pos);
234     }
235     }
236
237     /**
238      * Inserts HTML into an existing document.
239      *
240      * @param doc the document to insert into
241      * @param offset the offset to insert HTML at
242      * @param popDepth the number of ElementSpec.EndTagTypes to generate before
243      * inserting
244      * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
245      * of ElementSpec.JoinNextDirection that should be generated
246      * before inserting, but after the end tags have been generated
247      * @param insertTag the first tag to start inserting into document
248      * @exception RuntimeException (will eventually be a BadLocationException)
249      * if pos is invalid
250      */

251     public void insertHTML(HTMLDocument JavaDoc doc, int offset, String JavaDoc html,
252                int popDepth, int pushDepth,
253                HTML.Tag JavaDoc insertTag) throws
254                    BadLocationException, IOException {
255     Parser p = getParser();
256     if (p == null) {
257         throw new IOException("Can't load parser");
258     }
259     if (offset > doc.getLength()) {
260         throw new BadLocationException("Invalid location", offset);
261     }
262
263     ParserCallback receiver = doc.getReader(offset, popDepth, pushDepth,
264                         insertTag);
265     Boolean JavaDoc ignoreCharset = (Boolean JavaDoc)doc.getProperty
266                             ("IgnoreCharsetDirective");
267     p.parse(new StringReader(html), receiver, (ignoreCharset == null) ?
268         false : ignoreCharset.booleanValue());
269     receiver.flush();
270     }
271
272     /**
273      * Write content from a document to the given stream
274      * in a format appropriate for this kind of content handler.
275      *
276      * @param out the stream to write to
277      * @param doc the source for the write
278      * @param pos the location in the document to fetch the
279      * content
280      * @param len the amount to write out
281      * @exception IOException on any I/O error
282      * @exception BadLocationException if pos represents an invalid
283      * location within the document
284      */

285     public void write(Writer out, Document doc, int pos, int len)
286     throws IOException, BadLocationException {
287
288     if (doc instanceof HTMLDocument JavaDoc) {
289         HTMLWriter JavaDoc w = new HTMLWriter JavaDoc(out, (HTMLDocument JavaDoc)doc, pos, len);
290         w.write();
291     } else if (doc instanceof StyledDocument) {
292         MinimalHTMLWriter JavaDoc w = new MinimalHTMLWriter JavaDoc(out, (StyledDocument)doc, pos, len);
293         w.write();
294     } else {
295         super.write(out, doc, pos, len);
296     }
297     }
298
299     /**
300      * Called when the kit is being installed into the
301      * a JEditorPane.
302      *
303      * @param c the JEditorPane
304      */

305     public void install(JEditorPane c) {
306     c.addMouseListener(linkHandler);
307         c.addMouseMotionListener(linkHandler);
308     c.addCaretListener(nextLinkAction);
309     super.install(c);
310         theEditor = c;
311     }
312
313     /**
314      * Called when the kit is being removed from the
315      * JEditorPane. This is used to unregister any
316      * listeners that were attached.
317      *
318      * @param c the JEditorPane
319      */

320     public void deinstall(JEditorPane c) {
321     c.removeMouseListener(linkHandler);
322         c.removeMouseMotionListener(linkHandler);
323     c.removeCaretListener(nextLinkAction);
324     super.deinstall(c);
325         theEditor = null;
326     }
327
328     /**
329      * Default Cascading Style Sheet file that sets
330      * up the tag views.
331      */

332     public static final String JavaDoc DEFAULT_CSS = "default.css";
333
334     /**
335      * Set the set of styles to be used to render the various
336      * HTML elements. These styles are specified in terms of
337      * CSS specifications. Each document produced by the kit
338      * will have a copy of the sheet which it can add the
339      * document specific styles to. By default, the StyleSheet
340      * specified is shared by all HTMLEditorKit instances.
341      * This should be reimplemented to provide a finer granularity
342      * if desired.
343      */

344     public void setStyleSheet(StyleSheet JavaDoc s) {
345     defaultStyles = s;
346     }
347
348     /**
349      * Get the set of styles currently being used to render the
350      * HTML elements. By default the resource specified by
351      * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
352      * instances.
353      */

354     public StyleSheet JavaDoc getStyleSheet() {
355     if (defaultStyles == null) {
356         defaultStyles = new StyleSheet JavaDoc();
357         try {
358         InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
359         Reader r = new BufferedReader(
360                 new InputStreamReader(is, "ISO-8859-1"));
361         defaultStyles.loadRules(r, null);
362         r.close();
363         } catch (Throwable JavaDoc e) {
364         // on error we simply have no styles... the html
365
// will look mighty wrong but still function.
366
}
367     }
368     return defaultStyles;
369     }
370     
371     /**
372      * Fetch a resource relative to the HTMLEditorKit classfile.
373      * If this is called on 1.2 the loading will occur under the
374      * protection of a doPrivileged call to allow the HTMLEditorKit
375      * to function when used in an applet.
376      *
377      * @param name the name of the resource, relative to the
378      * HTMLEditorKit class
379      * @return a stream representing the resource
380      */

381     static InputStream getResourceAsStream(String JavaDoc name) {
382     try {
383             return ResourceLoader.getResourceAsStream(name);
384     } catch (Throwable JavaDoc e) {
385         // If the class doesn't exist or we have some other
386
// problem we just try to call getResourceAsStream directly.
387
return HTMLEditorKit JavaDoc.class.getResourceAsStream(name);
388     }
389     }
390
391     /**
392      * Fetches the command list for the editor. This is
393      * the list of commands supported by the superclass
394      * augmented by the collection of commands defined
395      * locally for style operations.
396      *
397      * @return the command list
398      */

399     public Action[] getActions() {
400     return TextAction.augmentList(super.getActions(), this.defaultActions);
401     }
402
403     /**
404      * Copies the key/values in <code>element</code>s AttributeSet into
405      * <code>set</code>. This does not copy component, icon, or element
406      * names attributes. Subclasses may wish to refine what is and what
407      * isn't copied here. But be sure to first remove all the attributes that
408      * are in <code>set</code>.<p>
409      * This is called anytime the caret moves over a different location.
410      *
411      */

412     protected void createInputAttributes(Element element,
413                      MutableAttributeSet set) {
414     set.removeAttributes(set);
415     set.addAttributes(element.getAttributes());
416     set.removeAttribute(StyleConstants.ComposedTextAttribute);
417
418     Object JavaDoc o = set.getAttribute(StyleConstants.NameAttribute);
419     if (o instanceof HTML.Tag JavaDoc) {
420         HTML.Tag JavaDoc tag = (HTML.Tag JavaDoc)o;
421         // PENDING: we need a better way to express what shouldn't be
422
// copied when editing...
423
if(tag == HTML.Tag.IMG) {
424         // Remove the related image attributes, src, width, height
425
set.removeAttribute(HTML.Attribute.SRC);
426         set.removeAttribute(HTML.Attribute.HEIGHT);
427         set.removeAttribute(HTML.Attribute.WIDTH);
428         set.addAttribute(StyleConstants.NameAttribute,
429                  HTML.Tag.CONTENT);
430         }
431         else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
432         // Don't copy HRs or BRs either.
433
set.addAttribute(StyleConstants.NameAttribute,
434                  HTML.Tag.CONTENT);
435         }
436         else if (tag == HTML.Tag.COMMENT) {
437         // Don't copy COMMENTs either
438
set.addAttribute(StyleConstants.NameAttribute,
439                  HTML.Tag.CONTENT);
440         set.removeAttribute(HTML.Attribute.COMMENT);
441         }
442         else if (tag == HTML.Tag.INPUT) {
443         // or INPUT either
444
set.addAttribute(StyleConstants.NameAttribute,
445                  HTML.Tag.CONTENT);
446         set.removeAttribute(HTML.Tag.INPUT);
447         }
448         else if (tag instanceof HTML.UnknownTag JavaDoc) {
449         // Don't copy unknowns either:(
450
set.addAttribute(StyleConstants.NameAttribute,
451                  HTML.Tag.CONTENT);
452         set.removeAttribute(HTML.Attribute.ENDTAG);
453         }
454     }
455     }
456
457     /**
458      * Gets the input attributes used for the styled
459      * editing actions.
460      *
461      * @return the attribute set
462      */

463     public MutableAttributeSet getInputAttributes() {
464     if (input == null) {
465         input = getStyleSheet().addStyle(null, null);
466     }
467     return input;
468     }
469
470     /**
471      * Sets the default cursor.
472      *
473      * @since 1.3
474      */

475     public void setDefaultCursor(Cursor cursor) {
476     defaultCursor = cursor;
477     }
478
479     /**
480      * Returns the default cursor.
481      *
482      * @since 1.3
483      */

484     public Cursor getDefaultCursor() {
485     return defaultCursor;
486     }
487
488     /**
489      * Sets the cursor to use over links.
490      *
491      * @since 1.3
492      */

493     public void setLinkCursor(Cursor cursor) {
494     linkCursor = cursor;
495     }
496
497     /**
498      * Returns the cursor to use over hyper links.
499      */

500     public Cursor getLinkCursor() {
501     return linkCursor;
502     }
503
504     /**
505      * Indicates whether an html form submission is processed automatically
506      * or only <code>FormSubmitEvent</code> is fired.
507      *
508      * @return true if html form submission is processed automatically,
509      * false otherwise.
510      *
511      * @see #setAutoFormSubmission
512      * @since 1.5
513      */

514     public boolean isAutoFormSubmission() {
515         return isAutoFormSubmission;
516     }
517
518     /**
519      * Specifies if an html form submission is processed
520      * automatically or only <code>FormSubmitEvent</code> is fired.
521      * By default it is set to true.
522      *
523      * @see #isAutoFormSubmission
524      * @see FormSubmitEvent
525      * @since 1.5
526      */

527     public void setAutoFormSubmission(boolean isAuto) {
528         isAutoFormSubmission = isAuto;
529     }
530
531     /**
532      * Creates a copy of the editor kit.
533      *
534      * @return the copy
535      */

536     public Object JavaDoc clone() {
537     HTMLEditorKit JavaDoc o = (HTMLEditorKit JavaDoc)super.clone();
538         if (o != null) {
539             o.input = null;
540             o.linkHandler = new LinkController();
541         }
542     return o;
543     }
544
545     /**
546      * Fetch the parser to use for reading HTML streams.
547      * This can be reimplemented to provide a different
548      * parser. The default implementation is loaded dynamically
549      * to avoid the overhead of loading the default parser if
550      * it's not used. The default parser is the HotJava parser
551      * using an HTML 3.2 DTD.
552      */

553     protected Parser getParser() {
554     if (defaultParser == null) {
555         try {
556                 Class JavaDoc c = Class.forName("javax.swing.text.html.parser.ParserDelegator");
557                 defaultParser = (Parser) c.newInstance();
558         } catch (Throwable JavaDoc e) {
559         }
560     }
561     return defaultParser;
562     }
563
564     // ----- Accessibility support -----
565
private AccessibleContext accessibleContext;
566
567     /**
568      * returns the AccessibleContext associated with this editor kit
569      *
570      * @return the AccessibleContext associated with this editor kit
571      * @since 1.4
572      */

573     public AccessibleContext getAccessibleContext() {
574     if (theEditor == null) {
575         return null;
576     }
577     if (accessibleContext == null) {
578         AccessibleHTML JavaDoc a = new AccessibleHTML JavaDoc(theEditor);
579         accessibleContext = a.getAccessibleContext();
580     }
581     return accessibleContext;
582     }
583
584     // --- variables ------------------------------------------
585

586     private static final Cursor MoveCursor = Cursor.getPredefinedCursor
587                                 (Cursor.HAND_CURSOR);
588     private static final Cursor DefaultCursor = Cursor.getPredefinedCursor
589                                 (Cursor.DEFAULT_CURSOR);
590
591     /** Shared factory for creating HTML Views. */
592     private static final ViewFactory defaultFactory = new HTMLFactory();
593
594     MutableAttributeSet input;
595     private static StyleSheet JavaDoc defaultStyles = null;
596     private LinkController linkHandler = new LinkController();
597     private static Parser defaultParser = null;
598     private Cursor defaultCursor = DefaultCursor;
599     private Cursor linkCursor = MoveCursor;
600     private boolean isAutoFormSubmission = true;
601
602     /**
603      * Class to watch the associated component and fire
604      * hyperlink events on it when appropriate.
605      */

606     public static class LinkController extends MouseAdapter implements MouseMotionListener, Serializable {
607         private Element curElem = null;
608         /**
609          * If true, the current element (curElem) represents an image.
610          */

611         private boolean curElemImage = false;
612     private String JavaDoc href = null;
613     /** This is used by viewToModel to avoid allocing a new array each
614      * time. */

615     private Position.Bias[] bias = new Position.Bias[1];
616         /**
617          * Current offset.
618          */

619         private int curOffset;
620
621     /**
622          * Called for a mouse click event.
623      * If the component is read-only (ie a browser) then
624      * the clicked event is used to drive an attempt to
625      * follow the reference specified by a link.
626      *
627      * @param e the mouse event
628      * @see MouseListener#mouseClicked
629      */

630         public void mouseClicked(MouseEvent e) {
631         JEditorPane editor = (JEditorPane) e.getSource();
632
633         if (! editor.isEditable() && SwingUtilities.isLeftMouseButton(e)) {
634         Point pt = new Point(e.getX(), e.getY());
635         int pos = editor.viewToModel(pt);
636         if (pos >= 0) {
637             activateLink(pos, editor, e.getX(), e.getY());
638         }
639         }
640     }
641
642         // ignore the drags
643
public void mouseDragged(MouseEvent e) {
644         }
645
646         // track the moving of the mouse.
647
public void mouseMoved(MouseEvent e) {
648             JEditorPane editor = (JEditorPane) e.getSource();
649         HTMLEditorKit JavaDoc kit = (HTMLEditorKit JavaDoc)editor.getEditorKit();
650         boolean adjustCursor = true;
651         Cursor newCursor = kit.getDefaultCursor();
652             if (!editor.isEditable()) {
653                 Point pt = new Point(e.getX(), e.getY());
654                 int pos = editor.getUI().viewToModel(editor, pt, bias);
655         if (bias[0] == Position.Bias.Backward && pos > 0) {
656             pos--;
657         }
658                 if (pos >= 0 &&(editor.getDocument() instanceof HTMLDocument JavaDoc)){
659                     HTMLDocument JavaDoc hdoc = (HTMLDocument JavaDoc)editor.getDocument();
660                     Element elem = hdoc.getCharacterElement(pos);
661                     if (!doesElementContainLocation(editor, elem, pos,
662                                                     e.getX(), e.getY())) {
663                         elem = null;
664                     }
665                     if (curElem != elem || curElemImage) {
666                         Element lastElem = curElem;
667                         curElem = elem;
668                         String JavaDoc href = null;
669                         curElemImage = false;
670                         if (elem != null) {
671                             AttributeSet a = elem.getAttributes();
672                             AttributeSet anchor = (AttributeSet)a.
673                                                    getAttribute(HTML.Tag.A);
674                             if (anchor == null) {
675                                 curElemImage = (a.getAttribute(StyleConstants.
676                                             NameAttribute) == HTML.Tag.IMG);
677                                 if (curElemImage) {
678                                     href = getMapHREF(editor, hdoc, elem, a,
679                                                       pos, e.getX(), e.getY());
680                                 }
681                             }
682                             else {
683                                 href = (String JavaDoc)anchor.getAttribute
684                                     (HTML.Attribute.HREF);
685                             }
686                         }
687
688                         if (href != this.href) {
689                             // reference changed, fire event(s)
690
fireEvents(editor, hdoc, href, lastElem);
691                             this.href = href;
692                             if (href != null) {
693                                 newCursor = kit.getLinkCursor();
694                             }
695                         }
696                         else {
697                             adjustCursor = false;
698                         }
699                     }
700                     else {
701                         adjustCursor = false;
702                     }
703                     curOffset = pos;
704                 }
705             }
706         if (adjustCursor && editor.getCursor() != newCursor) {
707         editor.setCursor(newCursor);
708         }
709         }
710
711         /**
712          * Returns a string anchor if the passed in element has a
713          * USEMAP that contains the passed in location.
714          */

715         private String JavaDoc getMapHREF(JEditorPane html, HTMLDocument JavaDoc hdoc,
716                                   Element elem, AttributeSet attr, int offset,
717                                   int x, int y) {
718             Object JavaDoc useMap = attr.getAttribute(HTML.Attribute.USEMAP);
719             if (useMap != null && (useMap instanceof String JavaDoc)) {
720                 Map JavaDoc m = hdoc.getMap((String JavaDoc)useMap);
721                 if (m != null && offset < hdoc.getLength()) {
722                     Rectangle bounds;
723                     TextUI JavaDoc ui = html.getUI();
724                     try {
725                         Shape lBounds = ui.modelToView(html, offset,
726                                                    Position.Bias.Forward);
727                         Shape rBounds = ui.modelToView(html, offset + 1,
728                                                    Position.Bias.Backward);
729                         bounds = lBounds.getBounds();
730                         bounds.add((rBounds instanceof Rectangle) ?
731                                     (Rectangle)rBounds : rBounds.getBounds());
732                     } catch (BadLocationException ble) {
733                         bounds = null;
734                     }
735                     if (bounds != null) {
736                         AttributeSet area = m.getArea(x - bounds.x,
737                                                       y - bounds.y,
738                                                       bounds.width,
739                                                       bounds.height);
740                         if (area != null) {
741                             return (String JavaDoc)area.getAttribute(HTML.Attribute.
742                                                              HREF);
743                         }
744                     }
745                 }
746             }
747             return null;
748         }
749
750         /**
751          * Returns true if the View representing <code>e</code> contains
752          * the location <code>x</code>, <code>y</code>. <code>offset</code>
753          * gives the offset into the Document to check for.
754          */

755         private boolean doesElementContainLocation(JEditorPane editor,
756                                                    Element e, int offset,
757                                                    int x, int y) {
758             if (e != null && offset > 0 && e.getStartOffset() == offset) {
759                 try {
760                     TextUI JavaDoc ui = editor.getUI();
761                     Shape s1 = ui.modelToView(editor, offset,
762                                               Position.Bias.Forward);
763             if (s1 == null) {
764             return false;
765             }
766                     Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle)s1 :
767                                     s1.getBounds();
768                     Shape s2 = ui.modelToView(editor, e.getEndOffset(),
769                                               Position.Bias.Backward);
770             if (s2 != null) {
771             Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle)s2 :
772                                     s2.getBounds();
773             r1.add(r2);
774             }
775                     return r1.contains(x, y);
776                 } catch (BadLocationException ble) {
777                 }
778             }
779             return true;
780         }
781
782     /**
783      * Calls linkActivated on the associated JEditorPane
784      * if the given position represents a link.<p>This is implemented
785      * to forward to the method with the same name, but with the following
786      * args both == -1.
787          *
788          * @param pos the position
789          * @param editor the editor pane
790      */

791     protected void activateLink(int pos, JEditorPane editor) {
792         activateLink(pos, editor, -1, -1);
793     }
794
795     /**
796      * Calls linkActivated on the associated JEditorPane
797      * if the given position represents a link. If this was the result
798      * of a mouse click, <code>x</code> and
799      * <code>y</code> will give the location of the mouse, otherwise
800      * they will be < 0.
801          *
802          * @param pos the position
803          * @param html the editor pane
804      */

805         void activateLink(int pos, JEditorPane html, int x, int y) {
806         Document doc = html.getDocument();
807         if (doc instanceof HTMLDocument JavaDoc) {
808         HTMLDocument JavaDoc hdoc = (HTMLDocument JavaDoc) doc;
809         Element e = hdoc.getCharacterElement(pos);
810         AttributeSet a = e.getAttributes();
811         AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A);
812         HyperlinkEvent linkEvent = null;
813                 String JavaDoc description;
814
815                 if (anchor == null) {
816                     href = getMapHREF(html, hdoc, e, a, pos, x, y);
817                 }
818                 else {
819                     href = (String JavaDoc)anchor.getAttribute(HTML.Attribute.HREF);
820                 }
821
822         if (href != null) {
823             linkEvent = createHyperlinkEvent(html, hdoc, href, anchor,
824                                                      e);
825         }
826         if (linkEvent != null) {
827             html.fireHyperlinkUpdate(linkEvent);
828         }
829         }
830     }
831
832     /**
833      * Creates and returns a new instance of HyperlinkEvent. If
834      * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
835      * will be created.
836      */

837     HyperlinkEvent createHyperlinkEvent(JEditorPane html,
838                         HTMLDocument JavaDoc hdoc, String JavaDoc href,
839                         AttributeSet anchor,
840                                             Element element) {
841         URL JavaDoc u;
842         try {
843         URL JavaDoc base = hdoc.getBase();
844         u = new URL JavaDoc(base, href);
845         // Following is a workaround for 1.2, in which
846
// new URL("file://...", "#...") causes the filename to
847
// be lost.
848
if (href != null && "file".equals(u.getProtocol()) &&
849             href.startsWith("#")) {
850             String JavaDoc baseFile = base.getFile();
851             String JavaDoc newFile = u.getFile();
852             if (baseFile != null && newFile != null &&
853             !newFile.startsWith(baseFile)) {
854             u = new URL JavaDoc(base, baseFile + href);
855             }
856         }
857         } catch (MalformedURLException JavaDoc m) {
858         u = null;
859         }
860         HyperlinkEvent linkEvent = null;
861
862         if (!hdoc.isFrameDocument()) {
863         linkEvent = new HyperlinkEvent(html, HyperlinkEvent.EventType.
864