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                            ACTIVATED, u, href, element);
865         } else {
866         String JavaDoc target = (anchor != null) ?
867             (String JavaDoc)anchor.getAttribute(HTML.Attribute.TARGET) : null;
868         if ((target == null) || (target.equals(""))) {
869             target = hdoc.getBaseTarget();
870         }
871         if ((target == null) || (target.equals(""))) {
872             target = "_self";
873         }
874         linkEvent = new HTMLFrameHyperlinkEvent JavaDoc(html, HyperlinkEvent.
875                  EventType.ACTIVATED, u, href, element, target);
876         }
877         return linkEvent;
878     }
879
880     void fireEvents(JEditorPane editor, HTMLDocument JavaDoc doc, String JavaDoc href,
881                         Element lastElem) {
882         if (this.href != null) {
883         // fire an exited event on the old link
884
URL JavaDoc u;
885         try {
886             u = new URL JavaDoc(doc.getBase(), this.href);
887         } catch (MalformedURLException JavaDoc m) {
888             u = null;
889         }
890         HyperlinkEvent exit = new HyperlinkEvent(editor,
891                      HyperlinkEvent.EventType.EXITED, u, this.href,
892                                  lastElem);
893         editor.fireHyperlinkUpdate(exit);
894         }
895         if (href != null) {
896         // fire an entered event on the new link
897
URL JavaDoc u;
898         try {
899             u = new URL JavaDoc(doc.getBase(), href);
900         } catch (MalformedURLException JavaDoc m) {
901             u = null;
902         }
903         HyperlinkEvent entered = new HyperlinkEvent(editor,
904                         HyperlinkEvent.EventType.ENTERED,
905                         u, href, curElem);
906         editor.fireHyperlinkUpdate(entered);
907         }
908     }
909     }
910
911     /**
912      * Interface to be supported by the parser. This enables
913      * providing a different parser while reusing some of the
914      * implementation provided by this editor kit.
915      */

916     public static abstract class Parser {
917     /**
918      * Parse the given stream and drive the given callback
919      * with the results of the parse. This method should
920      * be implemented to be thread-safe.
921      */

922     public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
923
924     }
925
926     /**
927      * The result of parsing drives these callback methods.
928      * The open and close actions should be balanced. The
929      * <code>flush</code> method will be the last method
930      * called, to give the receiver a chance to flush any
931      * pending data into the document.
932      * <p>Refer to DocumentParser, the default parser used, for further
933      * information on the contents of the AttributeSets, the positions, and
934      * other info.
935      *
936      * @see javax.swing.text.html.parser.DocumentParser
937      */

938     public static class ParserCallback {
939     /**
940      * This is passed as an attribute in the attributeset to indicate
941      * the element is implied eg, the string '&lt;&gt;foo&lt;\t&gt;'
942      * contains an implied html element and an implied body element.
943      *
944      * @since 1.3
945      */

946     public static final Object JavaDoc IMPLIED = "_implied_";
947
948
949         public void flush() throws BadLocationException {
950     }
951
952         public void handleText(char[] data, int pos) {
953     }
954
955         public void handleComment(char[] data, int pos) {
956     }
957
958     public void handleStartTag(HTML.Tag JavaDoc t, MutableAttributeSet a, int pos) {
959     }
960
961     public void handleEndTag(HTML.Tag JavaDoc t, int pos) {
962     }
963
964     public void handleSimpleTag(HTML.Tag JavaDoc t, MutableAttributeSet a, int pos) {
965     }
966
967     public void handleError(String JavaDoc errorMsg, int pos){
968     }
969
970     /**
971      * This is invoked after the stream has been parsed, but before
972      * <code>flush</code>. <code>eol</code> will be one of \n, \r
973      * or \r\n, which ever is encountered the most in parsing the
974      * stream.
975      *
976      * @since 1.3
977      */

978     public void handleEndOfLineString(String JavaDoc eol) {
979     }
980     }
981
982     /**
983      * A factory to build views for HTML. The following
984      * table describes what this factory will build by
985      * default.
986      *
987      * <table summary="Describes the tag and view created by this factory by default">
988      * <tr>
989      * <th align=left>Tag<th align=left>View created
990      * </tr><tr>
991      * <td>HTML.Tag.CONTENT<td>InlineView
992      * </tr><tr>
993      * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView
994      * </tr><tr>
995      * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView
996      * </tr><tr>
997      * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView
998      * </tr><tr>
999      * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView
1000     * </tr><tr>
1001     * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView
1002     * </tr><tr>
1003     * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView
1004     * </tr><tr>
1005     * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView
1006     * </tr><tr>
1007     * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView
1008     * </tr><tr>
1009     * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView
1010     * </tr><tr>
1011     * <td>HTML.Tag.MENU<td>ListView
1012     * </tr><tr>
1013     * <td>HTML.Tag.DIR<td>ListView
1014     * </tr><tr>
1015     * <td>HTML.Tag.UL<td>ListView
1016     * </tr><tr>
1017     * <td>HTML.Tag.OL<td>ListView
1018     * </tr><tr>
1019     * <td>HTML.Tag.LI<td>BlockView
1020     * </tr><tr>
1021     * <td>HTML.Tag.DL<td>BlockView
1022     * </tr><tr>
1023     * <td>HTML.Tag.DD<td>BlockView
1024     * </tr><tr>
1025     * <td>HTML.Tag.BODY<td>BlockView
1026     * </tr><tr>
1027     * <td>HTML.Tag.HTML<td>BlockView
1028     * </tr><tr>
1029     * <td>HTML.Tag.CENTER<td>BlockView
1030     * </tr><tr>
1031     * <td>HTML.Tag.DIV<td>BlockView
1032     * </tr><tr>
1033     * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1034     * </tr><tr>
1035     * <td>HTML.Tag.PRE<td>BlockView
1036     * </tr><tr>
1037     * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1038     * </tr><tr>
1039     * <td>HTML.Tag.PRE<td>BlockView
1040     * </tr><tr>
1041     * <td>HTML.Tag.IMG<td>ImageView
1042     * </tr><tr>
1043     * <td>HTML.Tag.HR<td>HRuleView
1044     * </tr><tr>
1045     * <td>HTML.Tag.BR<td>BRView
1046     * </tr><tr>
1047     * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView
1048     * </tr><tr>
1049     * <td>HTML.Tag.INPUT<td>FormView
1050     * </tr><tr>
1051     * <td>HTML.Tag.SELECT<td>FormView
1052     * </tr><tr>
1053     * <td>HTML.Tag.TEXTAREA<td>FormView
1054     * </tr><tr>
1055     * <td>HTML.Tag.OBJECT<td>ObjectView
1056     * </tr><tr>
1057     * <td>HTML.Tag.FRAMESET<td>FrameSetView
1058     * </tr><tr>
1059     * <td>HTML.Tag.FRAME<td>FrameView
1060     * </tr>
1061     * </table>
1062     */

1063    public static class HTMLFactory implements ViewFactory {
1064    
1065    /**
1066     * Creates a view from an element.
1067     *
1068     * @param elem the element
1069     * @return the view
1070     */

1071        public View create(Element elem) {
1072        Object JavaDoc o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
1073        if (o instanceof HTML.Tag JavaDoc) {
1074        HTML.Tag JavaDoc kind = (HTML.Tag JavaDoc) o;
1075        if (kind == HTML.Tag.CONTENT) {
1076            return new InlineView JavaDoc(elem);
1077        } else if (kind == HTML.Tag.IMPLIED) {
1078            String JavaDoc ws = (String JavaDoc) elem.getAttributes().getAttribute(
1079            CSS.Attribute.WHITE_SPACE);
1080            if ((ws != null) && ws.equals("pre")) {
1081            return new LineView JavaDoc(elem);
1082            }
1083            return new javax.swing.text.html.ParagraphView JavaDoc(elem);
1084        } else if ((kind == HTML.Tag.P) ||
1085               (kind == HTML.Tag.H1) ||
1086               (kind == HTML.Tag.H2) ||
1087               (kind == HTML.Tag.H3) ||
1088               (kind == HTML.Tag.H4) ||
1089               (kind == HTML.Tag.H5) ||
1090               (kind == HTML.Tag.H6) ||
1091               (kind == HTML.Tag.DT)) {
1092            // paragraph
1093
return new javax.swing.text.html.ParagraphView JavaDoc(elem);
1094        } else if ((kind == HTML.Tag.MENU) ||
1095               (kind == HTML.Tag.DIR) ||
1096               (kind == HTML.Tag.UL) ||
1097               (kind == HTML.Tag.OL)) {
1098            return new ListView JavaDoc(elem);
1099        } else if (kind == HTML.Tag.BODY) {
1100            return new BodyBlockView(elem);
1101        } else if (kind == HTML.Tag.HTML) {
1102            return new BlockView JavaDoc(elem, View.Y_AXIS);
1103        } else if ((kind == HTML.Tag.LI) ||
1104               (kind == HTML.Tag.CENTER) ||
1105               (kind == HTML.Tag.DL) ||
1106               (kind == HTML.Tag.DD) ||
1107               (kind == HTML.Tag.DIV) ||
1108               (kind == HTML.Tag.BLOCKQUOTE) ||
1109               (kind == HTML.Tag.PRE) ||
1110                           (kind == HTML.Tag.FORM)) {
1111            // vertical box
1112
return new BlockView JavaDoc(elem, View.Y_AXIS);
1113        } else if (kind == HTML.Tag.NOFRAMES) {
1114            return new NoFramesView JavaDoc(elem, View.Y_AXIS);
1115        } else if (kind==HTML.Tag.IMG) {
1116            return new ImageView JavaDoc(elem);
1117        } else if (kind == HTML.Tag.ISINDEX) {
1118            return new IsindexView JavaDoc(elem);
1119        } else if (kind == HTML.Tag.HR) {
1120            return new HRuleView JavaDoc(elem);
1121        } else if (kind == HTML.Tag.BR) {
1122            return new BRView JavaDoc(elem);
1123        } else if (kind == HTML.Tag.TABLE) {
1124            return new javax.swing.text.html.TableView JavaDoc(elem);
1125        } else if ((kind == HTML.Tag.INPUT) ||
1126               (kind == HTML.Tag.SELECT) ||
1127               (kind == HTML.Tag.TEXTAREA)) {
1128            return new FormView JavaDoc(elem);
1129        } else if (kind == HTML.Tag.OBJECT) {
1130            return new ObjectView JavaDoc(elem);
1131        } else if (kind == HTML.Tag.FRAMESET) {
1132                     if (elem.getAttributes().isDefined(HTML.Attribute.ROWS)) {
1133                         return new FrameSetView JavaDoc(elem, View.Y_AXIS);
1134                     } else if (elem.getAttributes().isDefined(HTML.Attribute.COLS)) {
1135                         return new FrameSetView JavaDoc(elem, View.X_AXIS);
1136                     }
1137                     throw new RuntimeException JavaDoc("Can't build a" + kind + ", " + elem + ":" +
1138                                     "no ROWS or COLS defined.");
1139                } else if (kind == HTML.Tag.FRAME) {
1140            return new FrameView JavaDoc(elem);
1141                } else if (kind instanceof HTML.UnknownTag JavaDoc) {
1142            return new HiddenTagView JavaDoc(elem);
1143        } else if (kind == HTML.Tag.COMMENT) {
1144            return new CommentView JavaDoc(elem);
1145        } else if (kind == HTML.Tag.HEAD) {
1146            // Make the head never visible, and never load its
1147
// children. For Cursor positioning,
1148
// getNextVisualPositionFrom is overriden to always return
1149
// the end offset of the element.
1150
return new BlockView JavaDoc(elem, View.X_AXIS) {
1151            public float getPreferredSpan(int axis) {
1152                return 0;
1153            }
1154            public float getMinimumSpan(int axis) {
1155                return 0;
1156            }
1157            public float getMaximumSpan(int axis) {
1158                return 0;
1159            }
1160            protected void loadChildren(ViewFactory f) {
1161            }
1162                        public Shape modelToView(int pos, Shape a,
1163                               Position.Bias b) throws BadLocationException {
1164                            return a;
1165                        }
1166            public int getNextVisualPositionFrom(int pos,
1167                     Position.Bias b, Shape a,
1168                     int direction, Position.Bias[] biasRet) {
1169                return getElement().getEndOffset();
1170            }
1171            };
1172        } else if ((kind == HTML.Tag.TITLE) ||
1173               (kind == HTML.Tag.META) ||
1174               (kind == HTML.Tag.LINK) ||
1175               (kind == HTML.Tag.STYLE) ||
1176               (kind == HTML.Tag.SCRIPT) ||
1177               (kind == HTML.Tag.AREA) ||
1178               (kind == HTML.Tag.MAP) ||
1179               (kind == HTML.Tag.PARAM) ||
1180               (kind == HTML.Tag.APPLET)) {
1181            return new HiddenTagView JavaDoc(elem);
1182        }
1183        }
1184        // If we get here, it's either an element we don't know about
1185
// or something from StyledDocument that doesn't have a mapping to HTML.
1186
String JavaDoc nm = elem.getName();
1187        if (nm != null) {
1188        if (nm.equals(AbstractDocument.ContentElementName)) {
1189            return new LabelView(elem);
1190        } else if (nm.equals(AbstractDocument.ParagraphElementName)) {
1191            return new ParagraphView JavaDoc(elem);
1192        } else if (nm.equals(AbstractDocument.SectionElementName)) {
1193            return new BoxView(elem, View.Y_AXIS);
1194        } else if (nm.equals(StyleConstants.ComponentElementName)) {
1195            return new ComponentView(elem);
1196        } else if (nm.equals(StyleConstants.IconElementName)) {
1197            return new IconView(elem);
1198        }
1199        }
1200    
1201        // default to text display
1202
return new LabelView(elem);
1203    }
1204
1205    static class BodyBlockView extends BlockView JavaDoc implements ComponentListener {
1206        public BodyBlockView(Element elem) {
1207        super(elem,View.Y_AXIS);
1208        }
1209        // reimplement major axis requirements to indicate that the
1210
// block is flexible for the body element... so that it can
1211
// be stretched to fill the background properly.
1212
protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
1213        r = super.calculateMajorAxisRequirements(axis, r);
1214        r.maximum = Integer.MAX_VALUE;
1215        return r;
1216        }
1217
1218        protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
1219        Container container = getContainer();
1220        Container parentContainer;
1221        if (container != null
1222            && (container instanceof javax.swing.JEditorPane JavaDoc)
1223            && (parentContainer = container.getParent()) != null
1224            && (parentContainer instanceof javax.swing.JViewport JavaDoc)) {
1225            JViewport viewPort = (JViewport)parentContainer;
1226            Object JavaDoc cachedObject;
1227            if (cachedViewPort != null) {
1228            if ((cachedObject = cachedViewPort.get()) != null) {
1229                if (cachedObject != viewPort) {
1230                ((JComponent)cachedObject).removeComponentListener(this);
1231                }
1232            } else {
1233                cachedViewPort = null;
1234            }
1235            }
1236            if (cachedViewPort == null) {
1237            viewPort.addComponentListener(this);
1238            cachedViewPort = new WeakReference(viewPort);
1239            }
1240
1241            componentVisibleWidth = viewPort.getExtentSize().width;
1242                    if (componentVisibleWidth > 0) {
1243            Insets insets = container.getInsets();
1244            viewVisibleWidth = componentVisibleWidth - insets.left - getLeftInset();
1245            //try to use viewVisibleWidth if it is smaller than targetSpan
1246
targetSpan = Math.min(targetSpan, viewVisibleWidth);
1247            }
1248        } else {
1249            if (cachedViewPort != null) {
1250            Object JavaDoc cachedObject;
1251            if ((cachedObject = cachedViewPort.get()) != null) {
1252                ((JComponent)cachedObject).removeComponentListener(this);
1253            }
1254            cachedViewPort = null;
1255            }
1256        }
1257                super.layoutMinorAxis(targetSpan, axis, offsets, spans);
1258        }
1259
1260        public void setParent(View parent) {
1261        //if parent == null unregister component listener
1262
if (parent == null) {
1263            if (cachedViewPort != null) {
1264            Object JavaDoc cachedObject;
1265            if ((cachedObject = cachedViewPort.get()) != null) {
1266                ((JComponent)cachedObject).removeComponentListener(this);
1267            }
1268            cachedViewPort = null;
1269            }
1270        }
1271        super.setParent(parent);
1272        }
1273
1274        public void componentResized(ComponentEvent e) {
1275        if ( !(e.getSource() instanceof JViewport) ) {
1276            return;
1277        }
1278        JViewport viewPort = (JViewport)e.getSource();
1279        if (componentVisibleWidth != viewPort.getExtentSize().width) {
1280            Document doc = getDocument();
1281            if (doc instanceof AbstractDocument) {
1282            AbstractDocument document = (AbstractDocument)getDocument();
1283            document.readLock();
1284            try {
1285                layoutChanged(X_AXIS);
1286                preferenceChanged(null, true, true);
1287            } finally {
1288                document.readUnlock();
1289            }
1290            
1291            }
1292        }
1293        }
1294        public void componentHidden(ComponentEvent e) {
1295        }
1296        public void componentMoved(ComponentEvent e) {
1297        }
1298        public void componentShown(ComponentEvent e) {
1299        }
1300        /*
1301             * we keep weak reference to viewPort if and only if BodyBoxView is listening for ComponentEvents
1302         * only in that case cachedViewPort is not equal to null.
1303         * we need to keep this reference in order to remove BodyBoxView from viewPort listeners.
1304         *
1305         */

1306        private Reference cachedViewPort = null;
1307        private boolean isListening = false;
1308        private int viewVisibleWidth = Integer.MAX_VALUE;
1309        private int componentVisibleWidth = Integer.MAX_VALUE;
1310    }
1311    
1312    }
1313    
1314    // --- Action implementations ------------------------------
1315

1316/** The bold action identifier
1317*/

1318    public static final String JavaDoc BOLD_ACTION = "html-bold-action";
1319/** The italic action identifier
1320*/

1321    public static final String JavaDoc ITALIC_ACTION = "html-italic-action";
1322/** The paragraph left indent action identifier
1323*/

1324    public static final String JavaDoc PARA_INDENT_LEFT = "html-para-indent-left";
1325/** The paragraph right indent action identifier
1326*/

1327    public static final String JavaDoc PARA_INDENT_RIGHT = "html-para-indent-right";
1328/** The font size increase to next value action identifier
1329*/

1330    public static final String JavaDoc FONT_CHANGE_BIGGER = "html-font-bigger";
1331/** The font size decrease to next value action identifier
1332*/

1333    public static final String JavaDoc FONT_CHANGE_SMALLER = "html-font-smaller";
1334/** The Color choice action identifier
1335     The color is passed as an argument
1336*/

1337    public static final String JavaDoc COLOR_ACTION = "html-color-action";
1338/** The logical style choice action identifier
1339     The logical style is passed in as an argument
1340*/

1341    public static final String JavaDoc LOGICAL_STYLE_ACTION = "html-logical-style-action";
1342    /**
1343     * Align images at the top.
1344     */

1345    public static final String JavaDoc IMG_ALIGN_TOP = "html-image-align-top";
1346
1347    /**
1348     * Align images in the middle.
1349     */

1350    public static final String JavaDoc IMG_ALIGN_MIDDLE = "html-image-align-middle";
1351
1352    /**
1353     * Align images at the bottom.
1354     */

1355    public static final String JavaDoc IMG_ALIGN_BOTTOM = "html-image-align-bottom";
1356
1357    /**
1358     * Align images at the border.
1359     */

1360    public static final String JavaDoc IMG_BORDER = "html-image-border";
1361
1362
1363    /** HTML used when inserting tables. */
1364    private static final String JavaDoc INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>";
1365
1366    /** HTML used when inserting unordered lists. */
1367    private static final String JavaDoc INSERT_UL_HTML = "<ul><li></li></ul>";
1368
1369    /** HTML used when inserting ordered lists. */
1370    private static final String JavaDoc INSERT_OL_HTML = "<ol><li></li></ol>";
1371
1372    /** HTML used when inserting hr. */
1373    private static final String JavaDoc INSERT_HR_HTML = "<hr>";
1374
1375    /** HTML used when inserting pre. */
1376    private static final String JavaDoc INSERT_PRE_HTML = "<pre></pre>";
1377
1378    private static NavigateLinkAction nextLinkAction =
1379    new NavigateLinkAction("next-link-action");
1380
1381    private static NavigateLinkAction previousLinkAction =
1382    new NavigateLinkAction("previous-link-action");
1383
1384    private static ActivateLinkAction activateLinkAction =
1385    new ActivateLinkAction("activate-link-action");
1386    
1387    private static final Action[] defaultActions = {
1388    new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML,
1389                 HTML.Tag.BODY, HTML.Tag.TABLE),
1390    new InsertHTMLTextAction("InsertTableRow", INSERT_TABLE_HTML,
1391                 HTML.Tag.TABLE, HTML.Tag.TR,
1392                 HTML.Tag.BODY, HTML.Tag.TABLE),
1393    new InsertHTMLTextAction("InsertTableDataCell", INSERT_TABLE_HTML,
1394                 HTML.Tag.TR, HTML.Tag.TD,
1395                 HTML.Tag.BODY, HTML.Tag.TABLE),
1396    new InsertHTMLTextAction("InsertUnorderedList", INSERT_UL_HTML,
1397                 HTML.Tag.BODY, HTML.Tag.UL),
1398    new InsertHTMLTextAction("InsertUnorderedListItem", INSERT_UL_HTML,
1399                 HTML.Tag.UL, HTML.Tag.LI,
1400                 HTML.Tag.BODY, HTML.Tag.UL),
1401    new InsertHTMLTextAction("InsertOrderedList", INSERT_OL_HTML,
1402                 HTML.Tag.BODY, HTML.Tag.OL),
1403    new InsertHTMLTextAction("InsertOrderedListItem", INSERT_OL_HTML,
1404                 HTML.Tag.OL, HTML.Tag.LI,
1405                 HTML.Tag.BODY, HTML.Tag.OL),
1406    new InsertHRAction(),
1407    new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML,
1408                 HTML.Tag.BODY, HTML.Tag.PRE),
1409    nextLinkAction, previousLinkAction, activateLinkAction
1410    };
1411
1412
1413    /**
1414     * An abstract Action providing some convenience methods that may
1415     * be useful in inserting HTML into an existing document.
1416     * <p>NOTE: None of the convenience methods obtain a lock on the
1417     * document. If you have another thread modifying the text these
1418     * methods may have inconsistent behavior, or return the wrong thing.
1419     */

1420    public static abstract class HTMLTextAction extends StyledTextAction {
1421    public HTMLTextAction(String JavaDoc name) {
1422        super(name);
1423    }
1424
1425    /**
1426     * @return HTMLDocument of <code>e</code>.
1427     */

1428    protected HTMLDocument JavaDoc getHTMLDocument(JEditorPane e) {
1429        Document d = e.getDocument();
1430        if (d instanceof HTMLDocument JavaDoc) {
1431        return (HTMLDocument JavaDoc) d;
1432        }
1433        throw new IllegalArgumentException JavaDoc("document must be HTMLDocument");
1434    }
1435
1436    /**
1437     * @return HTMLEditorKit for <code>e</code>.
1438     */

1439        protected HTMLEditorKit JavaDoc getHTMLEditorKit(JEditorPane e) {
1440        EditorKit k = e.getEditorKit();
1441        if (k instanceof HTMLEditorKit JavaDoc) {
1442        return (HTMLEditorKit JavaDoc) k;
1443        }
1444        throw new IllegalArgumentException JavaDoc("EditorKit must be HTMLEditorKit");
1445    }
1446
1447    /**
1448     * Returns an array of the Elements that contain <code>offset</code>.
1449     * The first elements corresponds to the root.
1450     */

1451    protected Element[] getElementsAt(HTMLDocument JavaDoc doc, int offset) {
1452        return getElementsAt(doc.getDefaultRootElement(), offset, 0);
1453    }
1454
1455    /**
1456     * Recursive method used by getElementsAt.
1457     */

1458    private Element[] getElementsAt(Element parent, int offset,
1459                    int depth) {
1460        if (parent.isLeaf()) {
1461        Element[] retValue = new Element[depth + 1];
1462        retValue[depth] = parent;
1463        return retValue;
1464        }
1465        Element[] retValue = getElementsAt(parent.getElement
1466              (parent.getElementIndex(offset)), offset, depth + 1);
1467        retValue[depth] = parent;
1468        return retValue;
1469    }
1470
1471    /**
1472     * Returns number of elements, starting at the deepest leaf, needed
1473     * to get to an element representing <code>tag</code>. This will
1474     * return -1 if no elements is found representing <code>tag</code>,
1475     * or 0 if the parent of the leaf at <code>offset</code> represents
1476     * <code>tag</code>.
1477     */

1478    protected int elementCountToTag(HTMLDocument JavaDoc doc, int offset,
1479                    HTML.Tag JavaDoc tag) {
1480        int depth = -1;
1481        Element e = doc.getCharacterElement(offset);
1482        while (e != null && e.getAttributes().getAttribute
1483           (StyleConstants.NameAttribute) != tag) {
1484        e = e.getParentElement();
1485        depth++;
1486        }
1487        if (e == null) {
1488        return -1;
1489        }
1490        return depth;
1491    }
1492
1493    /**
1494     * Returns the deepest element at <code>offset</code> matching
1495     * <code>tag</code>.
1496     */

1497    protected Element findElementMatchingTag(HTMLDocument JavaDoc doc, int offset,
1498                         HTML.Tag JavaDoc tag) {
1499        Element e = doc.getDefaultRootElement();
1500        Element lastMatch = null;
1501        while (e != null) {
1502        if (e.getAttributes().getAttribute
1503           (StyleConstants.NameAttribute) == tag) {
1504            lastMatch = e;
1505        }
1506        e = e.getElement(e.getElementIndex(offset));
1507        }
1508        return lastMatch;
1509    }
1510    }
1511
1512
1513    /**
1514     * InsertHTMLTextAction can be used to insert an arbitrary string of HTML
1515     * into an existing HTML document. At least two HTML.Tags need to be
1516     * supplied. The first Tag, parentTag, identifies the parent in
1517     * the document to add the elements to. The second tag, addTag,
1518     * identifies the first tag that should be added to the document as
1519     * seen in the HTML string. One important thing to remember, is that
1520     * the parser is going to generate all the appropriate tags, even if
1521     * they aren't in the HTML string passed in.<p>
1522     * For example, lets say you wanted to create an action to insert
1523     * a table into the body. The parentTag would be HTML.Tag.BODY,
1524     * addTag would be HTML.Tag.TABLE, and the string could be something
1525     * like &lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;.
1526     * <p>There is also an option to supply an alternate parentTag and
1527     * addTag. These will be checked for if there is no parentTag at
1528     * offset.
1529     */

1530    public static class InsertHTMLTextAction extends HTMLTextAction {
1531    public InsertHTMLTextAction(String JavaDoc name, String JavaDoc html,
1532                    HTML.Tag JavaDoc parentTag, HTML.Tag JavaDoc addTag) {
1533        this(name, html, parentTag, addTag, null, null);
1534    }
1535
1536    public InsertHTMLTextAction(String JavaDoc name, String JavaDoc html,
1537                    HTML.Tag JavaDoc parentTag,
1538                    HTML.Tag JavaDoc addTag,
1539                    HTML.Tag JavaDoc alternateParentTag,
1540                    HTML.Tag JavaDoc alternateAddTag) {
1541        this(name, html, parentTag, addTag, alternateParentTag,
1542         alternateAddTag, true);
1543    }
1544
1545    /* public */
1546    InsertHTMLTextAction(String JavaDoc name, String JavaDoc html,
1547                    HTML.Tag JavaDoc parentTag,
1548                    HTML.Tag JavaDoc addTag,
1549                    HTML.Tag JavaDoc alternateParentTag,
1550                    HTML.Tag JavaDoc alternateAddTag,
1551                    boolean adjustSelection) {
1552        super(name);
1553        this.html = html;
1554        this.parentTag = parentTag;
1555        this.addTag = addTag;
1556        this.alternateParentTag = alternateParentTag;
1557        this.alternateAddTag = alternateAddTag;
1558        this.adjustSelection = adjustSelection;
1559    }
1560
1561    /**
1562     * A cover for HTMLEditorKit.insertHTML. If an exception it
1563     * thrown it is wrapped in a RuntimeException and thrown.
1564     */

1565    protected void insertHTML(JEditorPane editor, HTMLDocument JavaDoc doc,
1566                  int offset, String JavaDoc html, int popDepth,
1567                  int pushDepth, HTML.Tag JavaDoc addTag) {
1568        try {
1569        getHTMLEditorKit(editor).insertHTML(doc, offset, html,
1570                            popDepth, pushDepth,
1571                            addTag);
1572        } catch (IOException ioe) {
1573        throw new RuntimeException JavaDoc("Unable to insert: " + ioe);
1574        } catch (BadLocationException ble) {
1575        throw new RuntimeException JavaDoc("Unable to insert: " + ble);
1576        }
1577    }
1578
1579    /**
1580     * This is invoked when inserting at a boundary. It determines
1581     * the number of pops, and then the number of pushes that need
1582     * to be performed, and then invokes insertHTML.
1583     * @since 1.3
1584     */

1585    protected void insertAtBoundary(JEditorPane editor, HTMLDocument JavaDoc doc,
1586                    int offset, Element insertElement,
1587                    String JavaDoc html, HTML.Tag JavaDoc parentTag,
1588                    HTML.Tag JavaDoc addTag) {
1589        insertAtBoundry(editor, doc, offset, insertElement, html,
1590                parentTag, addTag);
1591    }
1592
1593    /**
1594     * This is invoked when inserting at a boundary. It determines
1595     * the number of pops, and then the number of pushes that need
1596     * to be performed, and then invokes insertHTML.
1597     * @deprecated As of Java 2 platform v1.3, use insertAtBoundary
1598     */

1599        @Deprecated JavaDoc
1600    protected void insertAtBoundry(JEditorPane editor, HTMLDocument JavaDoc doc,
1601                       int offset, Element insertElement,
1602                       String JavaDoc html, HTML.Tag JavaDoc parentTag,
1603                       HTML.Tag JavaDoc addTag) {
1604        // Find the common parent.
1605
Element e;
1606        Element commonParent;
1607        boolean isFirst = (offset == 0);
1608
1609        if (offset > 0 || insertElement == null) {
1610        e = doc.getDefaultRootElement();
1611        while (e != null && e.getStartOffset() != offset &&
1612               !e.isLeaf()) {
1613            e = e.getElement(e.getElementIndex(offset));
1614        }
1615        commonParent = (e != null) ? e.getParentElement() : null;
1616        }
1617        else {
1618        // If inserting at the origin, the common parent is the
1619
// insertElement.
1620
commonParent = insertElement;
1621        }
1622        if (commonParent != null) {
1623        // Determine how many pops to do.
1624
int pops = 0;
1625        int pushes = 0;
1626        if (isFirst && insertElement != null) {
1627            e = commonParent;
1628            while (e != null && !e.isLeaf()) {
1629            e = e.getElement(e.getElementIndex(offset));
1630            pops++;
1631            }
1632        }
1633        else {
1634            e = commonParent;
1635            offset--;
1636            while (e != null && !e.isLeaf()) {
1637            e = e.getElement(e.getElementIndex(offset));
1638            pops++;
1639            }
1640
1641            // And how many pushes
1642
e = commonParent;
1643            offset++;
1644            while (e != null && e != insertElement) {
1645            e = e.getElement(e.getElementIndex(offset));
1646            pushes++;
1647            }
1648        }
1649        pops = Math.max(0, pops - 1);
1650
1651        // And insert!
1652
insertHTML(editor, doc, offset, html, pops, pushes, addTag);
1653        }
1654    }
1655
1656    /**
1657     * If there is an Element with name <code>tag</code> at
1658     * <code>offset</code>, this will invoke either insertAtBoundary
1659     * or <code>insertHTML</code>. This returns true if there is
1660     * a match, and one of the inserts is invoked.
1661     */

1662    /*protected*/
1663    boolean insertIntoTag(JEditorPane editor, HTMLDocument JavaDoc doc,
1664                  int offset, HTML.Tag JavaDoc tag, HTML.Tag JavaDoc addTag) {
1665        Element e = findElementMatchingTag(doc, offset, tag);
1666        if (e != null && e.getStartOffset() == offset) {
1667        insertAtBoundary(editor, doc, offset, e, html,
1668                 tag, addTag);
1669        return true;
1670        }
1671        else if (offset > 0) {
1672        int depth = elementCountToTag(doc, offset - 1, tag);
1673        if (depth != -1) {
1674            insertHTML(editor, doc, offset, html, depth, 0, addTag);
1675            return true;
1676        }
1677        }
1678        return false;
1679    }
1680
1681    /**
1682     * Called after an insertion to adjust the selection.
1683     */

1684    /* protected */
1685    void adjustSelection(JEditorPane pane, HTMLDocument JavaDoc doc,
1686                 int startOffset, int oldLength) {
1687        int newLength = doc.getLength();
1688        if (newLength != oldLength && startOffset < newLength) {
1689        if (startOffset > 0) {
1690            String JavaDoc text;
1691            try {
1692            text = doc.getText(startOffset - 1, 1);
1693            } catch (BadLocationException ble) {
1694            text = null;
1695            }
1696            if (text != null && text.length() > 0 &&
1697            text.charAt(0) == '\n') {
1698            pane.select(startOffset, startOffset);
1699            }
1700            else {
1701            pane.select(startOffset + 1, startOffset + 1);
1702            }
1703        }
1704        else {
1705            pane.select(1, 1);
1706        }
1707        }
1708    }
1709
1710        /**
1711         * Inserts the HTML into the document.
1712         *
1713         * @param ae the event
1714         */

1715        public void actionPerformed(ActionEvent ae) {
1716        JEditorPane editor = getEditor(ae);
1717        if (editor != null) {
1718        HTMLDocument JavaDoc doc = getHTMLDocument(editor);
1719        int offset = editor.getSelectionStart();
1720        int length = doc.getLength();
1721        boolean inserted;
1722        // Try first choice
1723
if (!insertIntoTag(editor, doc, offset, parentTag, addTag) &&
1724            alternateParentTag != null) {
1725            // Then alternate.
1726
inserted = insertIntoTag(editor, doc, offset,
1727                         alternateParentTag,
1728                         alternateAddTag);
1729        }
1730        else {
1731            inserted = true;
1732        }
1733        if (adjustSelection && inserted) {
1734            adjustSelection(editor, doc, offset, length);
1735        }
1736        }
1737    }
1738
1739    /** HTML to insert. */
1740    protected String JavaDoc html;
1741    /** Tag to check for in the document. */
1742    protected HTML.Tag JavaDoc parentTag;
1743    /** Tag in HTML to start adding tags from. */
1744    protected HTML.Tag JavaDoc addTag;
1745    /** Alternate Tag to check for in the document if parentTag is
1746     * not found. */

1747    protected HTML.Tag JavaDoc alternateParentTag;
1748    /** Alternate tag in HTML to start adding tags from if parentTag
1749     * is not found and alternateParentTag is found. */

1750    protected HTML.Tag JavaDoc alternateAddTag;
1751    /** True indicates the selection should be adjusted after an insert. */
1752    boolean adjustSelection;
1753    }
1754
1755
1756    /**
1757     * InsertHRAction is special, at actionPerformed time it will determine
1758     * the parent HTML.Tag based on the paragraph element at the selection
1759     * start.
1760     */

1761    static class InsertHRAction extends InsertHTMLTextAction {
1762    InsertHRAction() {
1763        super("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null, null,
1764          false);
1765    }
1766
1767        /**
1768         * Inserts the HTML into the document.
1769         *
1770         * @param ae the event
1771         */

1772        public void actionPerformed(ActionEvent ae) {
1773        JEditorPane editor = getEditor(ae);
1774        if (editor != null) {
1775        HTMLDocument JavaDoc doc = getHTMLDocument(editor);
1776        int offset = editor.getSelectionStart();
1777        Element paragraph = doc.getParagraphElement(offset);
1778        if (paragraph.getParentElement() != null) {
1779            parentTag = (HTML.Tag JavaDoc)paragraph.getParentElement().
1780                          getAttributes().getAttribute
1781                          (StyleConstants.NameAttribute);
1782            super.actionPerformed(ae);
1783        }
1784        }
1785    }
1786    
1787    }
1788
1789    /*
1790     * Returns the object in an AttributeSet matching a key
1791     */

1792    static private Object JavaDoc getAttrValue(AttributeSet attr, HTML.Attribute JavaDoc key) {
1793    Enumeration names = attr.getAttributeNames();
1794    while (names.hasMoreElements()) {
1795        Object JavaDoc nextKey = names.nextElement();
1796        Object JavaDoc nextVal = attr.getAttribute(nextKey);
1797        if (nextVal instanceof AttributeSet) {
1798        Object JavaDoc value = getAttrValue((AttributeSet)nextVal, key);
1799        if (value != null) {
1800            return value;
1801        }
1802        } else if (nextKey == key) {
1803        return nextVal;
1804        }
1805    }
1806    return null;
1807    }
1808
1809    /*
1810     * Action to move the focus on the next or previous hypertext link
1811     * or object. TODO: This method relies on support from the
1812     * javax.accessibility package. The text package should support
1813     * keyboard navigation of text elements directly.
1814     */

1815    static class NavigateLinkAction extends TextAction
1816        implements CaretListener {
1817
1818    private static int prevHypertextOffset = -1;
1819    private static boolean foundLink = false;
1820    private FocusHighlightPainter focusPainter =
1821        new FocusHighlightPainter(null);
1822    private Object JavaDoc selectionTag;
1823    private boolean focusBack = false;
1824
1825        /*
1826         * Create this action with the appropriate identifier.
1827         */

1828        public NavigateLinkAction(String JavaDoc actionName) {
1829            super(actionName);
1830        if ("previous-link-action".equals(actionName)) {
1831        focusBack = true;
1832        }
1833        }
1834    
1835    /**
1836     * Called when the caret position is updated.
1837     *
1838     * @param e the caret event
1839     */

1840    public void caretUpdate(CaretEvent e) {
1841        if (foundLink) {
1842        foundLink = false;
1843        // TODO: The AccessibleContext for the editor should register
1844
// as a listener for CaretEvents and forward the events to
1845
// assistive technologies listening for such events.
1846
Object JavaDoc src = e.getSource();
1847        if (src instanceof JTextComponent) {
1848            ((JTextComponent)src).getAccessibleContext().firePropertyChange(
1849                        AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET,
1850                new Integer JavaDoc(prevHypertextOffset),
1851                new Integer JavaDoc(e.getDot()));
1852        }
1853        }
1854    }
1855
1856        /*
1857     * The operation to perform when this action is triggered.
1858     */

1859        public void actionPerformed(ActionEvent e) {
1860            JTextComponent comp = getTextComponent(e);
1861        if (comp == null || comp.isEditable()) {
1862        return;
1863        }
1864        Document doc = comp.getDocument();
1865        if (doc == null) {
1866        return;
1867        }
1868        // TODO: Should start successive iterations from the
1869
// current caret position.
1870
ElementIterator ei = new ElementIterator(doc);
1871
1872        int currentOffset = comp.getCaretPosition();
1873        int prevStartOffset = -1;
1874        int prevEndOffset = -1;
1875        
1876        // highlight the next link or object after the current caret position
1877
Element nextElement = null;
1878        while ((nextElement = ei.next()) != null) {
1879        String JavaDoc name = nextElement.getName();
1880        AttributeSet attr = nextElement.getAttributes();
1881        
1882        Object JavaDoc href = getAttrValue(attr, HTML.Attribute.HREF);
1883        if (!(name.equals(HTML.Tag.OBJECT.toString())) && href == null) {
1884            continue;
1885        }
1886        
1887        int elementOffset = nextElement.getStartOffset();
1888        if (focusBack) {
1889            if (elementOffset >= currentOffset &&
1890            prevStartOffset >= 0) {
1891
1892            foundLink = true;
1893            comp.setCaretPosition(prevStartOffset);
1894            moveCaretPosition(comp, prevStartOffset,
1895                      prevEndOffset);
1896            prevHypertextOffset = prevStartOffset;
1897            return;
1898            }
1899        } else { // focus forward
1900
if (elementOffset > currentOffset) {
1901
1902            foundLink = true;
1903            comp.setCaretPosition(elementOffset);
1904            moveCaretPosition(comp, elementOffset,
1905                      nextElement.getEndOffset());
1906            prevHypertextOffset = elementOffset;
1907            return;
1908            }
1909        }
1910        prevStartOffset = nextElement.getStartOffset();
1911        prevEndOffset = nextElement.getEndOffset();
1912        }
1913            if (focusBack && prevStartOffset >= 0) {
1914                foundLink = true;
1915                comp.setCaretPosition(prevStartOffset);
1916                moveCaretPosition(comp, prevStartOffset,
1917                                  prevEndOffset);
1918                prevHypertextOffset = prevStartOffset;
1919                return;
1920            }
1921        }
1922    
1923    /*
1924     * Moves the caret from mark to dot
1925     */

1926    private void moveCaretPosition(JTextComponent comp, int mark, int dot) {
1927        Highlighter h = comp.getHighlighter();
1928        if (h != null) {
1929        int p0 = Math.min(dot, mark);
1930        int p1 = Math.max(dot, mark);
1931        try {
1932            if (selectionTag != null) {
1933            h.changeHighlight(selectionTag, p0, p1);
1934            } else {
1935            Highlighter.HighlightPainter p = focusPainter;
1936            selectionTag = h.addHighlight(p0, p1, p);
1937            }
1938        } catch (BadLocationException e) {
1939        }
1940        }
1941    }
1942
1943    /**
1944     * A highlight painter that draws a one-pixel border around
1945     * the highlighted area.
1946     */

1947    class FocusHighlightPainter extends
1948        DefaultHighlighter.DefaultHighlightPainter {
1949
1950        FocusHighlightPainter(Color color) {
1951        super(color);
1952        }
1953        
1954        /**
1955         * Paints a portion of a highlight.
1956         *
1957         * @param g the graphics context
1958         * @param offs0 the starting model offset >= 0
1959         * @param offs1 the ending model offset >= offs1
1960         * @param bounds the bounding box of the view, which is not
1961         * necessarily the region to paint.
1962         * @param c the editor
1963         * @param view View painting for
1964         * @return region in which drawing occurred
1965         */

1966        public Shape paintLayer(Graphics g, int offs0, int offs1,
1967                    Shape bounds, JTextComponent c, View view) {
1968        
1969        Color color = getColor();
1970        
1971        if (color == null) {
1972            g.setColor(c.getSelectionColor());
1973        }
1974        else {
1975            g.setColor(color);
1976        }
1977        if (offs0 == view.getStartOffset() &&
1978            offs1 == view.getEndOffset()) {
1979            // Contained in view, can just use bounds.
1980
Rectangle alloc;
1981            if (bounds instanceof Rectangle) {
1982            alloc = (Rectangle)bounds;
1983            }
1984            else {
1985            alloc = bounds.getBounds();
1986            }
1987            g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height);
1988            return alloc;
1989        }
1990        else {
1991            // Should only render part of View.
1992
try {
1993            // --- determine locations ---
1994
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
1995                               offs1,Position.Bias.Backward,
1996                               bounds);
1997            Rectangle r = (shape instanceof Rectangle) ?
1998                (Rectangle)shape : shape.getBounds();
1999            g.drawRect(r.x, r.y, r.width - 1, r.height);
2000            return r;
2001            } catch (BadLocationException e) {
2002            // can't render
2003
}
2004        }
2005        // Only if exception
2006
return null;
2007        }
2008    }
2009    }
2010
2011    /*
2012     * Action to activate the hypertext link that has focus.
2013     * TODO: This method relies on support from the
2014     * javax.accessibility package. The text package should support
2015     * keyboard navigation of text elements directly.
2016     */

2017    static class ActivateLinkAction extends TextAction {
2018
2019        /**
2020         * Create this action with the appropriate identifier.
2021         */

2022        public ActivateLinkAction(String JavaDoc actionName) {
2023            super(actionName);
2024        }
2025
2026    /*
2027     * activates the hyperlink at offset
2028     */

2029    private void activateLink(String JavaDoc href, HTMLDocument JavaDoc doc,
2030                  JEditorPane editor, int offset) {
2031        try {
2032        URL JavaDoc page =
2033            (URL JavaDoc)doc.getProperty(Document.StreamDescriptionProperty);
2034        URL JavaDoc url = new URL JavaDoc(page, href);
2035        HyperlinkEvent linkEvent = new HyperlinkEvent
2036            (editor, HyperlinkEvent.EventType.
2037             ACTIVATED, url, url.toExternalForm(),
2038             doc.getCharacterElement(offset));
2039        editor.fireHyperlinkUpdate(linkEvent);
2040        } catch (MalformedURLException JavaDoc m) {
2041        }
2042    }
2043
2044    /*
2045     * Invokes default action on the object in an element
2046     */

2047    private void doObjectAction(JEditorPane editor, Element elem) {
2048        View view = getView(editor, elem);
2049        if (view != null && view instanceof ObjectView JavaDoc) {
2050        Component comp = ((ObjectView JavaDoc)view).getComponent();
2051        if (comp != null && comp instanceof Accessible) {
2052            AccessibleContext ac = ((Accessible)comp).getAccessibleContext();
2053            if (ac != null) {
2054            AccessibleAction aa = ac.getAccessibleAction();
2055            if (aa != null) {
2056                aa.doAccessibleAction(0);
2057            }
2058            }
2059        }
2060        }
2061    }
2062    
2063    /*
2064     * Returns the root view for a document
2065     */

2066    private View getRootView(JEditorPane editor) {
2067        return editor.getUI().getRootView(editor);
2068    }
2069
2070        /*
2071     * Returns a view associated with an element
2072         */

2073        private View getView(JEditorPane editor, Element elem) {
2074            Object JavaDoc lock = lock(editor);
2075            try {
2076                View rootView = getRootView(editor);
2077                int start = elem.getStartOffset();
2078                if (rootView != null) {
2079                    return getView(rootView, elem, start);
2080                }
2081                return null;
2082            } finally {
2083                unlock(lock);
2084            }
2085        }
2086
2087    private View getView(View parent, Element elem, int start) {
2088            if (parent.getElement() == elem) {
2089                return parent;
2090            }
2091            int index = parent.getViewIndex(start, Position.Bias.Forward);
2092        
2093            if (index != -1 && index < parent.getViewCount()) {
2094                return getView(parent.getView(index), elem, start);
2095            }
2096            return null;
2097        }
2098
2099    /*
2100     * If possible acquires a lock on the Document. If a lock has been
2101     * obtained a key will be retured that should be passed to
2102     * <code>unlock</code>.
2103     */

2104    private Object JavaDoc lock(JEditorPane editor) {
2105        Document document = editor.getDocument();
2106        
2107        if (document instanceof AbstractDocument) {
2108        ((AbstractDocument)document).readLock();
2109        return document;
2110        }
2111        return null;
2112    }
2113    
2114    /*
2115     * Releases a lock previously obtained via <code>lock</code>.
2116     */

2117    private void unlock(Object JavaDoc key) {
2118        if (key != null) {
2119        ((AbstractDocument)key).readUnlock();
2120        }
2121    }
2122
2123        /*
2124     * The operation to perform when this action is triggered.
2125     */

2126        public void actionPerformed(ActionEvent e) {
2127
2128            JTextComponent c = getTextComponent(e);
2129        if (c.isEditable() || !(c instanceof JEditorPane)) {
2130        return;
2131        }
2132        JEditorPane editor = (JEditorPane)c;
2133        
2134        Document d = editor.getDocument();
2135        if (d == null || !(d instanceof HTMLDocument JavaDoc)) {
2136        return;
2137        }
2138        HTMLDocument JavaDoc doc = (HTMLDocument JavaDoc)d;
2139
2140        ElementIterator ei = new ElementIterator(doc);
2141        int currentOffset = editor.getCaretPosition();
2142
2143        // invoke the next link or object action
2144
String JavaDoc urlString = null;
2145        String JavaDoc objString = null;
2146        Element currentElement = null;
2147        while ((currentElement = ei.next()) != null) {
2148        String JavaDoc name = currentElement.getName();
2149        AttributeSet attr = currentElement.getAttributes();
2150        
2151        Object JavaDoc href = getAttrValue(attr, HTML.Attribute.HREF);
2152        if (href != null) {
2153            if (currentOffset >= currentElement.getStartOffset() &&
2154            currentOffset <= currentElement.getEndOffset()) {
2155            
2156            activateLink((String JavaDoc)href, doc, editor, currentOffset);
2157            return;
2158            }
2159        } else if (name.equals(HTML.Tag.OBJECT.toString())) {
2160            Object JavaDoc obj = getAttrValue(attr, HTML.Attribute.CLASSID);
2161            if (obj != null) {
2162            if (currentOffset >= currentElement.getStartOffset() &&
2163                currentOffset <= currentElement.getEndOffset()) {
2164                
2165                doObjectAction(editor, currentElement);
2166                return;
2167            }
2168            }
2169        }
2170        }
2171    }
2172    }
2173}
2174
Popular Tags