History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/html/HTMLEditorKit.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/text/html/HTMLEditorKit.java+2288−0
@@ -0,0 +1,2288 @@
1+/*
2+ * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+package javax.swing.text.html;
26+
27+import sun.awt.AppContext;
28+
29+import java.lang.reflect.Method;
30+import java.awt.*;
31+import java.awt.event.*;
32+import java.io.*;
33+import java.net.MalformedURLException;
34+import java.net.URL;
35+import javax.swing.text.*;
36+import javax.swing.*;
37+import javax.swing.border.*;
38+import javax.swing.event.*;
39+import javax.swing.plaf.TextUI;
40+import java.util.*;
41+import javax.accessibility.*;
42+import java.lang.ref.*;
43+
44+/**
45+ * The Swing JEditorPane text component supports different kinds
46+ * of content via a plug-in mechanism called an EditorKit. Because
47+ * HTML is a very popular format of content, some support is provided
48+ * by default. The default support is provided by this class, which
49+ * supports HTML version 3.2 (with some extensions), and is migrating
50+ * toward version 4.0.
51+ * The <applet> tag is not supported, but some support is provided
52+ * for the <object> tag.
53+ * <p>
54+ * There are several goals of the HTML EditorKit provided, that have
55+ * an effect upon the way that HTML is modeled. These
56+ * have influenced its design in a substantial way.
57+ * <dl>
58+ * <p>
59+ * <dt>
60+ * Support editing
61+ * <dd>
62+ * It might seem fairly obvious that a plug-in for JEditorPane
63+ * should provide editing support, but that fact has several
64+ * design considerations. There are a substantial number of HTML
65+ * documents that don't properly conform to an HTML specification.
66+ * These must be normalized somewhat into a correct form if one
67+ * is to edit them. Additionally, users don't like to be presented
68+ * with an excessive amount of structure editing, so using traditional
69+ * text editing gestures is preferred over using the HTML structure
70+ * exactly as defined in the HTML document.
71+ * <p>
72+ * The modeling of HTML is provided by the class <code>HTMLDocument</code>.
73+ * Its documention describes the details of how the HTML is modeled.
74+ * The editing support leverages heavily off of the text package.
75+ * <p>
76+ * <dt>
77+ * Extendable/Scalable
78+ * <dd>
79+ * To maximize the usefulness of this kit, a great deal of effort
80+ * has gone into making it extendable. These are some of the
81+ * features.
82+ * <ol>
83+ * <li>
84+ * The parser is replacable. The default parser is the Hot Java
85+ * parser which is DTD based. A different DTD can be used, or an
86+ * entirely different parser can be used. To change the parser,
87+ * reimplement the getParser method. The default parser is
88+ * dynamically loaded when first asked for, so the class files
89+ * will never be loaded if an alternative parser is used. The
90+ * default parser is in a separate package called parser below
91+ * this package.
92+ * <li>
93+ * The parser drives the ParserCallback, which is provided by
94+ * HTMLDocument. To change the callback, subclass HTMLDocument
95+ * and reimplement the createDefaultDocument method to return
96+ * document that produces a different reader. The reader controls
97+ * how the document is structured. Although the Document provides
98+ * HTML support by default, there is nothing preventing support of
99+ * non-HTML tags that result in alternative element structures.
100+ * <li>
101+ * The default view of the models are provided as a hierarchy of
102+ * View implementations, so one can easily customize how a particular
103+ * element is displayed or add capabilities for new kinds of elements
104+ * by providing new View implementations. The default set of views
105+ * are provided by the <code>HTMLFactory</code> class. This can
106+ * be easily changed by subclassing or replacing the HTMLFactory
107+ * and reimplementing the getViewFactory method to return the alternative
108+ * factory.
109+ * <li>
110+ * The View implementations work primarily off of CSS attributes,
111+ * which are kept in the views. This makes it possible to have
112+ * multiple views mapped over the same model that appear substantially
113+ * different. This can be especially useful for printing. For
114+ * most HTML attributes, the HTML attributes are converted to CSS
115+ * attributes for display. This helps make the View implementations
116+ * more general purpose
117+ * </ol>
118+ * <p>
119+ * <dt>
120+ * Asynchronous Loading
121+ * <dd>
122+ * Larger documents involve a lot of parsing and take some time
123+ * to load. By default, this kit produces documents that will be
124+ * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
125+ * This is controlled by a property on the document. The method
126+ * {@link #createDefaultDocument createDefaultDocument} can
127+ * be overriden to change this. The batching of work is done
128+ * by the <code>HTMLDocument.HTMLReader</code> class. The actual
129+ * work is done by the <code>DefaultStyledDocument</code> and
130+ * <code>AbstractDocument</code> classes in the text package.
131+ * <p>
132+ * <dt>
133+ * Customization from current LAF
134+ * <dd>
135+ * HTML provides a well known set of features without exactly
136+ * specifying the display characteristics. Swing has a theme
137+ * mechanism for its look-and-feel implementations. It is desirable
138+ * for the look-and-feel to feed display characteristics into the
139+ * HTML views. An user with poor vision for example would want
140+ * high contrast and larger than typical fonts.
141+ * <p>
142+ * The support for this is provided by the <code>StyleSheet</code>
143+ * class. The presentation of the HTML can be heavily influenced
144+ * by the setting of the StyleSheet property on the EditorKit.
145+ * <p>
146+ * <dt>
147+ * Not lossy
148+ * <dd>
149+ * An EditorKit has the ability to be read and save documents.
150+ * It is generally the most pleasing to users if there is no loss
151+ * of data between the two operation. The policy of the HTMLEditorKit
152+ * will be to store things not recognized or not necessarily visible
153+ * so they can be subsequently written out. The model of the HTML document
154+ * should therefore contain all information discovered while reading the
155+ * document. This is constrained in some ways by the need to support
156+ * editing (i.e. incorrect documents sometimes must be normalized).
157+ * The guiding principle is that information shouldn't be lost, but
158+ * some might be synthesized to produce a more correct model or it might
159+ * be rearranged.
160+ * </dl>
161+ *
162+ * @author Timothy Prinzing
163+ */
164+public class HTMLEditorKit extends StyledEditorKit implements Accessible {
165+
166+ private JEditorPane theEditor;
167+
168+ /**
169+ * Constructs an HTMLEditorKit, creates a StyleContext,
170+ * and loads the style sheet.
171+ */
172+ public HTMLEditorKit() {
173+
174+ }
175+
176+ /**
177+ * Get the MIME type of the data that this
178+ * kit represents support for. This kit supports
179+ * the type <code>text/html</code>.
180+ *
181+ * @return the type
182+ */
183+ public String getContentType() {
184+ return "text/html";
185+ }
186+
187+ /**
188+ * Fetch a factory that is suitable for producing
189+ * views of any models that are produced by this
190+ * kit.
191+ *
192+ * @return the factory
193+ */
194+ public ViewFactory getViewFactory() {
195+ return defaultFactory;
196+ }
197+
198+ /**
199+ * Create an uninitialized text storage model
200+ * that is appropriate for this type of editor.
201+ *
202+ * @return the model
203+ */
204+ public Document createDefaultDocument() {
205+ StyleSheet styles = getStyleSheet();
206+ StyleSheet ss = new StyleSheet();
207+
208+ ss.addStyleSheet(styles);
209+
210+ HTMLDocument doc = new HTMLDocument(ss);
211+ doc.setParser(getParser());
212+ doc.setAsynchronousLoadPriority(4);
213+ doc.setTokenThreshold(100);
214+ return doc;
215+ }
216+
217+ /**
218+ * Try to get an HTML parser from the document. If no parser is set for
219+ * the document, return the editor kit's default parser. It is an error
220+ * if no parser could be obtained from the editor kit.
221+ */
222+ private Parser ensureParser(HTMLDocument doc) throws IOException {
223+ Parser p = doc.getParser();
224+ if (p == null) {
225+ p = getParser();
226+ }
227+ if (p == null) {
228+ throw new IOException("Can't load parser");
229+ }
230+ return p;
231+ }
232+
233+ /**
234+ * Inserts content from the given stream. If <code>doc</code> is
235+ * an instance of HTMLDocument, this will read
236+ * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
237+ * the body Element, if you do not insert into the body an exception will
238+ * be thrown. When inserting into a non-empty document all tags outside
239+ * of the body (head, title) will be dropped.
240+ *
241+ * @param in the stream to read from
242+ * @param doc the destination for the insertion
243+ * @param pos the location in the document to place the
244+ * content
245+ * @exception IOException on any I/O error
246+ * @exception BadLocationException if pos represents an invalid
247+ * location within the document
248+ * @exception RuntimeException (will eventually be a BadLocationException)
249+ * if pos is invalid
250+ */
251+ public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
252+
253+ if (doc instanceof HTMLDocument) {
254+ HTMLDocument hdoc = (HTMLDocument) doc;
255+ if (pos > doc.getLength()) {
256+ throw new BadLocationException("Invalid location", pos);
257+ }
258+
259+ Parser p = ensureParser(hdoc);
260+ ParserCallback receiver = hdoc.getReader(pos);
261+ Boolean ignoreCharset = (Boolean)doc.getProperty("IgnoreCharsetDirective");
262+ p.parse(in, receiver, (ignoreCharset == null) ? false : ignoreCharset.booleanValue());
263+ receiver.flush();
264+ } else {
265+ super.read(in, doc, pos);
266+ }
267+ }
268+
269+ /**
270+ * Inserts HTML into an existing document.
271+ *
272+ * @param doc the document to insert into
273+ * @param offset the offset to insert HTML at
274+ * @param popDepth the number of ElementSpec.EndTagTypes to generate before
275+ * inserting
276+ * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
277+ * of ElementSpec.JoinNextDirection that should be generated
278+ * before inserting, but after the end tags have been generated
279+ * @param insertTag the first tag to start inserting into document
280+ * @exception RuntimeException (will eventually be a BadLocationException)
281+ * if pos is invalid
282+ */
283+ public void insertHTML(HTMLDocument doc, int offset, String html,
284+ int popDepth, int pushDepth,
285+ HTML.Tag insertTag) throws
286+ BadLocationException, IOException {
287+ if (offset > doc.getLength()) {
288+ throw new BadLocationException("Invalid location", offset);
289+ }
290+
291+ Parser p = ensureParser(doc);
292+ ParserCallback receiver = doc.getReader(offset, popDepth, pushDepth,
293+ insertTag);
294+ Boolean ignoreCharset = (Boolean)doc.getProperty
295+ ("IgnoreCharsetDirective");
296+ p.parse(new StringReader(html), receiver, (ignoreCharset == null) ?
297+ false : ignoreCharset.booleanValue());
298+ receiver.flush();
299+ }
300+
301+ /**
302+ * Write content from a document to the given stream
303+ * in a format appropriate for this kind of content handler.
304+ *
305+ * @param out the stream to write to
306+ * @param doc the source for the write
307+ * @param pos the location in the document to fetch the
308+ * content
309+ * @param len the amount to write out
310+ * @exception IOException on any I/O error
311+ * @exception BadLocationException if pos represents an invalid
312+ * location within the document
313+ */
314+ public void write(Writer out, Document doc, int pos, int len)
315+ throws IOException, BadLocationException {
316+
317+ if (doc instanceof HTMLDocument) {
318+ HTMLWriter w = new HTMLWriter(out, (HTMLDocument)doc, pos, len);
319+ w.write();
320+ } else if (doc instanceof StyledDocument) {
321+ MinimalHTMLWriter w = new MinimalHTMLWriter(out, (StyledDocument)doc, pos, len);
322+ w.write();
323+ } else {
324+ super.write(out, doc, pos, len);
325+ }
326+ }
327+
328+ /**
329+ * Called when the kit is being installed into the
330+ * a JEditorPane.
331+ *
332+ * @param c the JEditorPane
333+ */
334+ public void install(JEditorPane c) {
335+ c.addMouseListener(linkHandler);
336+ c.addMouseMotionListener(linkHandler);
337+ c.addCaretListener(nextLinkAction);
338+ super.install(c);
339+ theEditor = c;
340+ }
341+
342+ /**
343+ * Called when the kit is being removed from the
344+ * JEditorPane. This is used to unregister any
345+ * listeners that were attached.
346+ *
347+ * @param c the JEditorPane
348+ */
349+ public void deinstall(JEditorPane c) {
350+ c.removeMouseListener(linkHandler);
351+ c.removeMouseMotionListener(linkHandler);
352+ c.removeCaretListener(nextLinkAction);
353+ super.deinstall(c);
354+ theEditor = null;
355+ }
356+
357+ /**
358+ * Default Cascading Style Sheet file that sets
359+ * up the tag views.
360+ */
361+ public static final String DEFAULT_CSS = "default.css";
362+
363+ /**
364+ * Set the set of styles to be used to render the various
365+ * HTML elements. These styles are specified in terms of
366+ * CSS specifications. Each document produced by the kit
367+ * will have a copy of the sheet which it can add the
368+ * document specific styles to. By default, the StyleSheet
369+ * specified is shared by all HTMLEditorKit instances.
370+ * This should be reimplemented to provide a finer granularity
371+ * if desired.
372+ */
373+ public void setStyleSheet(StyleSheet s) {
374+ if (s == null) {
375+ AppContext.getAppContext().remove(DEFAULT_STYLES_KEY);
376+ } else {
377+ AppContext.getAppContext().put(DEFAULT_STYLES_KEY, s);
378+ }
379+ }
380+
381+ /**
382+ * Get the set of styles currently being used to render the
383+ * HTML elements. By default the resource specified by
384+ * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
385+ * instances.
386+ */
387+ public StyleSheet getStyleSheet() {
388+ AppContext appContext = AppContext.getAppContext();
389+ StyleSheet defaultStyles = (StyleSheet) appContext.get(DEFAULT_STYLES_KEY);
390+
391+ if (defaultStyles == null) {
392+ defaultStyles = new StyleSheet();
393+ appContext.put(DEFAULT_STYLES_KEY, defaultStyles);
394+ try {
395+ InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
396+ Reader r = new BufferedReader(
397+ new InputStreamReader(is, "ISO-8859-1"));
398+ defaultStyles.loadRules(r, null);
399+ r.close();
400+ } catch (Throwable e) {
401+ // on error we simply have no styles... the html
402+ // will look mighty wrong but still function.
403+ }
404+ }
405+ return defaultStyles;
406+ }
407+
408+ /**
409+ * Fetch a resource relative to the HTMLEditorKit classfile.
410+ * If this is called on 1.2 the loading will occur under the
411+ * protection of a doPrivileged call to allow the HTMLEditorKit
412+ * to function when used in an applet.
413+ *
414+ * @param name the name of the resource, relative to the
415+ * HTMLEditorKit class
416+ * @return a stream representing the resource
417+ */
418+ static InputStream getResourceAsStream(String name) {
419+ try {
420+ return ResourceLoader.getResourceAsStream(name);
421+ } catch (Throwable e) {
422+ // If the class doesn't exist or we have some other
423+ // problem we just try to call getResourceAsStream directly.
424+ return HTMLEditorKit.class.getResourceAsStream(name);
425+ }
426+ }
427+
428+ /**
429+ * Fetches the command list for the editor. This is
430+ * the list of commands supported by the superclass
431+ * augmented by the collection of commands defined
432+ * locally for style operations.
433+ *
434+ * @return the command list
435+ */
436+ public Action[] getActions() {
437+ return TextAction.augmentList(super.getActions(), this.defaultActions);
438+ }
439+
440+ /**
441+ * Copies the key/values in <code>element</code>s AttributeSet into
442+ * <code>set</code>. This does not copy component, icon, or element
443+ * names attributes. Subclasses may wish to refine what is and what
444+ * isn't copied here. But be sure to first remove all the attributes that
445+ * are in <code>set</code>.<p>
446+ * This is called anytime the caret moves over a different location.
447+ *
448+ */
449+ protected void createInputAttributes(Element element,
450+ MutableAttributeSet set) {
451+ set.removeAttributes(set);
452+ set.addAttributes(element.getAttributes());
453+ set.removeAttribute(StyleConstants.ComposedTextAttribute);
454+
455+ Object o = set.getAttribute(StyleConstants.NameAttribute);
456+ if (o instanceof HTML.Tag) {
457+ HTML.Tag tag = (HTML.Tag)o;
458+ // PENDING: we need a better way to express what shouldn't be
459+ // copied when editing...
460+ if(tag == HTML.Tag.IMG) {
461+ // Remove the related image attributes, src, width, height
462+ set.removeAttribute(HTML.Attribute.SRC);
463+ set.removeAttribute(HTML.Attribute.HEIGHT);
464+ set.removeAttribute(HTML.Attribute.WIDTH);
465+ set.addAttribute(StyleConstants.NameAttribute,
466+ HTML.Tag.CONTENT);
467+ }
468+ else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
469+ // Don't copy HRs or BRs either.
470+ set.addAttribute(StyleConstants.NameAttribute,
471+ HTML.Tag.CONTENT);
472+ }
473+ else if (tag == HTML.Tag.COMMENT) {
474+ // Don't copy COMMENTs either
475+ set.addAttribute(StyleConstants.NameAttribute,
476+ HTML.Tag.CONTENT);
477+ set.removeAttribute(HTML.Attribute.COMMENT);
478+ }
479+ else if (tag == HTML.Tag.INPUT) {
480+ // or INPUT either
481+ set.addAttribute(StyleConstants.NameAttribute,
482+ HTML.Tag.CONTENT);
483+ set.removeAttribute(HTML.Tag.INPUT);
484+ }
485+ else if (tag instanceof HTML.UnknownTag) {
486+ // Don't copy unknowns either:(
487+ set.addAttribute(StyleConstants.NameAttribute,
488+ HTML.Tag.CONTENT);
489+ set.removeAttribute(HTML.Attribute.ENDTAG);
490+ }
491+ }
492+ }
493+
494+ /**
495+ * Gets the input attributes used for the styled
496+ * editing actions.
497+ *
498+ * @return the attribute set
499+ */
500+ public MutableAttributeSet getInputAttributes() {
501+ if (input == null) {
502+ input = getStyleSheet().addStyle(null, null);
503+ }
504+ return input;
505+ }
506+
507+ /**
508+ * Sets the default cursor.
509+ *
510+ * @since 1.3
511+ */
512+ public void setDefaultCursor(Cursor cursor) {
513+ defaultCursor = cursor;
514+ }
515+
516+ /**
517+ * Returns the default cursor.
518+ *
519+ * @since 1.3
520+ */
521+ public Cursor getDefaultCursor() {
522+ return defaultCursor;
523+ }
524+
525+ /**
526+ * Sets the cursor to use over links.
527+ *
528+ * @since 1.3
529+ */
530+ public void setLinkCursor(Cursor cursor) {
531+ linkCursor = cursor;
532+ }
533+
534+ /**
535+ * Returns the cursor to use over hyper links.
536+ * @since 1.3
537+ */
538+ public Cursor getLinkCursor() {
539+ return linkCursor;
540+ }
541+
542+ /**
543+ * Indicates whether an html form submission is processed automatically
544+ * or only <code>FormSubmitEvent</code> is fired.
545+ *
546+ * @return true if html form submission is processed automatically,
547+ * false otherwise.
548+ *
549+ * @see #setAutoFormSubmission
550+ * @since 1.5
551+ */
552+ public boolean isAutoFormSubmission() {
553+ return isAutoFormSubmission;
554+ }
555+
556+ /**
557+ * Specifies if an html form submission is processed
558+ * automatically or only <code>FormSubmitEvent</code> is fired.
559+ * By default it is set to true.
560+ *
561+ * @see #isAutoFormSubmission()
562+ * @see FormSubmitEvent
563+ * @since 1.5
564+ */
565+ public void setAutoFormSubmission(boolean isAuto) {
566+ isAutoFormSubmission = isAuto;
567+ }
568+
569+ /**
570+ * Creates a copy of the editor kit.
571+ *
572+ * @return the copy
573+ */
574+ public Object clone() {
575+ HTMLEditorKit o = (HTMLEditorKit)super.clone();
576+ if (o != null) {
577+ o.input = null;
578+ o.linkHandler = new LinkController();
579+ }
580+ return o;
581+ }
582+
583+ /**
584+ * Fetch the parser to use for reading HTML streams.
585+ * This can be reimplemented to provide a different
586+ * parser. The default implementation is loaded dynamically
587+ * to avoid the overhead of loading the default parser if
588+ * it's not used. The default parser is the HotJava parser
589+ * using an HTML 3.2 DTD.
590+ */
591+ protected Parser getParser() {
592+ if (defaultParser == null) {
593+ try {
594+ Class c = Class.forName("javax.swing.text.html.parser.ParserDelegator");
595+ defaultParser = (Parser) c.newInstance();
596+ } catch (Throwable e) {
597+ }
598+ }
599+ return defaultParser;
600+ }
601+
602+ // ----- Accessibility support -----
603+ private AccessibleContext accessibleContext;
604+
605+ /**
606+ * returns the AccessibleContext associated with this editor kit
607+ *
608+ * @return the AccessibleContext associated with this editor kit
609+ * @since 1.4
610+ */
611+ public AccessibleContext getAccessibleContext() {
612+ if (theEditor == null) {
613+ return null;
614+ }
615+ if (accessibleContext == null) {
616+ AccessibleHTML a = new AccessibleHTML(theEditor);
617+ accessibleContext = a.getAccessibleContext();
618+ }
619+ return accessibleContext;
620+ }
621+
622+ // --- variables ------------------------------------------
623+
624+ private static final Cursor MoveCursor = Cursor.getPredefinedCursor
625+ (Cursor.HAND_CURSOR);
626+ private static final Cursor DefaultCursor = Cursor.getPredefinedCursor
627+ (Cursor.DEFAULT_CURSOR);
628+
629+ /** Shared factory for creating HTML Views. */
630+ private static final ViewFactory defaultFactory = new HTMLFactory();
631+
632+ MutableAttributeSet input;
633+ private static final Object DEFAULT_STYLES_KEY = new Object();
634+ private LinkController linkHandler = new LinkController();
635+ private static Parser defaultParser = null;
636+ private Cursor defaultCursor = DefaultCursor;
637+ private Cursor linkCursor = MoveCursor;
638+ private boolean isAutoFormSubmission = true;
639+
640+ /**
641+ * Class to watch the associated component and fire
642+ * hyperlink events on it when appropriate.
643+ */
644+ public static class LinkController extends MouseAdapter implements MouseMotionListener, Serializable {
645+ private Element curElem = null;
646+ /**
647+ * If true, the current element (curElem) represents an image.
648+ */
649+ private boolean curElemImage = false;
650+ private String href = null;
651+ /** This is used by viewToModel to avoid allocing a new array each
652+ * time. */
653+ private transient Position.Bias[] bias = new Position.Bias[1];
654+ /**
655+ * Current offset.
656+ */
657+ private int curOffset;
658+
659+ /**
660+ * Called for a mouse click event.
661+ * If the component is read-only (ie a browser) then
662+ * the clicked event is used to drive an attempt to
663+ * follow the reference specified by a link.
664+ *
665+ * @param e the mouse event
666+ * @see MouseListener#mouseClicked
667+ */
668+ public void mouseClicked(MouseEvent e) {
669+ JEditorPane editor = (JEditorPane) e.getSource();
670+
671+ if (! editor.isEditable() && editor.isEnabled() &&
672+ SwingUtilities.isLeftMouseButton(e)) {
673+ Point pt = new Point(e.getX(), e.getY());
674+ int pos = editor.viewToModel(pt);
675+ if (pos >= 0) {
676+ activateLink(pos, editor, e);
677+ }
678+ }
679+ }
680+
681+ // ignore the drags
682+ public void mouseDragged(MouseEvent e) {
683+ }
684+
685+ // track the moving of the mouse.
686+ public void mouseMoved(MouseEvent e) {
687+ JEditorPane editor = (JEditorPane) e.getSource();
688+ if (!editor.isEnabled()) {
689+ return;
690+ }
691+
692+ HTMLEditorKit kit = (HTMLEditorKit)editor.getEditorKit();
693+ boolean adjustCursor = true;
694+ Cursor newCursor = kit.getDefaultCursor();
695+ if (!editor.isEditable()) {
696+ Point pt = new Point(e.getX(), e.getY());
697+ int pos = editor.getUI().viewToModel(editor, pt, bias);
698+ if (bias[0] == Position.Bias.Backward && pos > 0) {
699+ pos--;
700+ }
701+ if (pos >= 0 &&(editor.getDocument() instanceof HTMLDocument)){
702+ HTMLDocument hdoc = (HTMLDocument)editor.getDocument();
703+ Element elem = hdoc.getCharacterElement(pos);
704+ if (!doesElementContainLocation(editor, elem, pos,
705+ e.getX(), e.getY())) {
706+ elem = null;
707+ }
708+ if (curElem != elem || curElemImage) {
709+ Element lastElem = curElem;
710+ curElem = elem;
711+ String href = null;
712+ curElemImage = false;
713+ if (elem != null) {
714+ AttributeSet a = elem.getAttributes();
715+ AttributeSet anchor = (AttributeSet)a.
716+ getAttribute(HTML.Tag.A);
717+ if (anchor == null) {
718+ curElemImage = (a.getAttribute(StyleConstants.
719+ NameAttribute) == HTML.Tag.IMG);
720+ if (curElemImage) {
721+ href = getMapHREF(editor, hdoc, elem, a,
722+ pos, e.getX(), e.getY());
723+ }
724+ }
725+ else {
726+ href = (String)anchor.getAttribute
727+ (HTML.Attribute.HREF);
728+ }
729+ }
730+
731+ if (href != this.href) {
732+ // reference changed, fire event(s)
733+ fireEvents(editor, hdoc, href, lastElem, e);
734+ this.href = href;
735+ if (href != null) {
736+ newCursor = kit.getLinkCursor();
737+ }
738+ }
739+ else {
740+ adjustCursor = false;
741+ }
742+ }
743+ else {
744+ adjustCursor = false;
745+ }
746+ curOffset = pos;
747+ }
748+ }
749+ if (adjustCursor && editor.getCursor() != newCursor) {
750+ editor.setCursor(newCursor);
751+ }
752+ }
753+
754+ /**
755+ * Returns a string anchor if the passed in element has a
756+ * USEMAP that contains the passed in location.
757+ */
758+ private String getMapHREF(JEditorPane html, HTMLDocument hdoc,
759+ Element elem, AttributeSet attr, int offset,
760+ int x, int y) {
761+ Object useMap = attr.getAttribute(HTML.Attribute.USEMAP);
762+ if (useMap != null && (useMap instanceof String)) {
763+ Map m = hdoc.getMap((String)useMap);
764+ if (m != null && offset < hdoc.getLength()) {
765+ Rectangle bounds;
766+ TextUI ui = html.getUI();
767+ try {
768+ Shape lBounds = ui.modelToView(html, offset,
769+ Position.Bias.Forward);
770+ Shape rBounds = ui.modelToView(html, offset + 1,
771+ Position.Bias.Backward);
772+ bounds = lBounds.getBounds();
773+ bounds.add((rBounds instanceof Rectangle) ?
774+ (Rectangle)rBounds : rBounds.getBounds());
775+ } catch (BadLocationException ble) {
776+ bounds = null;
777+ }
778+ if (bounds != null) {
779+ AttributeSet area = m.getArea(x - bounds.x,
780+ y - bounds.y,
781+ bounds.width,
782+ bounds.height);
783+ if (area != null) {
784+ return (String)area.getAttribute(HTML.Attribute.
785+ HREF);
786+ }
787+ }
788+ }
789+ }
790+ return null;
791+ }
792+
793+ /**
794+ * Returns true if the View representing <code>e</code> contains
795+ * the location <code>x</code>, <code>y</code>. <code>offset</code>
796+ * gives the offset into the Document to check for.
797+ */
798+ private boolean doesElementContainLocation(JEditorPane editor,
799+ Element e, int offset,
800+ int x, int y) {
801+ if (e != null && offset > 0 && e.getStartOffset() == offset) {
802+ try {
803+ TextUI ui = editor.getUI();
804+ Shape s1 = ui.modelToView(editor, offset,
805+ Position.Bias.Forward);
806+ if (s1 == null) {
807+ return false;
808+ }
809+ Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle)s1 :
810+ s1.getBounds();
811+ Shape s2 = ui.modelToView(editor, e.getEndOffset(),
812+ Position.Bias.Backward);
813+ if (s2 != null) {
814+ Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle)s2 :
815+ s2.getBounds();
816+ r1.add(r2);
817+ }
818+ return r1.contains(x, y);
819+ } catch (BadLocationException ble) {
820+ }
821+ }
822+ return true;
823+ }
824+
825+ /**
826+ * Calls linkActivated on the associated JEditorPane
827+ * if the given position represents a link.<p>This is implemented
828+ * to forward to the method with the same name, but with the following
829+ * args both == -1.
830+ *
831+ * @param pos the position
832+ * @param editor the editor pane
833+ */
834+ protected void activateLink(int pos, JEditorPane editor) {
835+ activateLink(pos, editor, null);
836+ }
837+
838+ /**
839+ * Calls linkActivated on the associated JEditorPane
840+ * if the given position represents a link. If this was the result
841+ * of a mouse click, <code>x</code> and
842+ * <code>y</code> will give the location of the mouse, otherwise
843+ * they will be < 0.
844+ *
845+ * @param pos the position
846+ * @param html the editor pane
847+ */
848+ void activateLink(int pos, JEditorPane html, MouseEvent mouseEvent) {
849+ Document doc = html.getDocument();
850+ if (doc instanceof HTMLDocument) {
851+ HTMLDocument hdoc = (HTMLDocument) doc;
852+ Element e = hdoc.getCharacterElement(pos);
853+ AttributeSet a = e.getAttributes();
854+ AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A);
855+ HyperlinkEvent linkEvent = null;
856+ String description;
857+ int x = -1;
858+ int y = -1;
859+
860+ if (mouseEvent != null) {
861+ x = mouseEvent.getX();
862+ y = mouseEvent.getY();
863+ }
864+
865+ if (anchor == null) {
866+ href = getMapHREF(html, hdoc, e, a, pos, x, y);
867+ }
868+ else {
869+ href = (String)anchor.getAttribute(HTML.Attribute.HREF);
870+ }
871+
872+ if (href != null) {
873+ linkEvent = createHyperlinkEvent(html, hdoc, href, anchor,
874+ e, mouseEvent);
875+ }
876+ if (linkEvent != null) {
877+ html.fireHyperlinkUpdate(linkEvent);
878+ }
879+ }
880+ }
881+
882+ /**
883+ * Creates and returns a new instance of HyperlinkEvent. If
884+ * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
885+ * will be created.
886+ */
887+ HyperlinkEvent createHyperlinkEvent(JEditorPane html,
888+ HTMLDocument hdoc, String href,
889+ AttributeSet anchor,
890+ Element element,
891+ MouseEvent mouseEvent) {
892+ URL u;
893+ try {
894+ URL base = hdoc.getBase();
895+ u = new URL(base, href);
896+ // Following is a workaround for 1.2, in which
897+ // new URL("file://...", "#...") causes the filename to
898+ // be lost.
899+ if (href != null && "file".equals(u.getProtocol()) &&
900+ href.startsWith("#")) {
901+ String baseFile = base.getFile();
902+ String newFile = u.getFile();
903+ if (baseFile != null && newFile != null &&
904+ !newFile.startsWith(baseFile)) {
905+ u = new URL(base, baseFile + href);
906+ }
907+ }
908+ } catch (MalformedURLException m) {
909+ u = null;
910+ }
911+ HyperlinkEvent linkEvent;
912+
913+ if (!hdoc.isFrameDocument()) {
914+ linkEvent = new HyperlinkEvent(
915+ html, HyperlinkEvent.EventType.ACTIVATED, u, href,
916+ element, mouseEvent);
917+ } else {
918+ String target = (anchor != null) ?
919+ (String)anchor.getAttribute(HTML.Attribute.TARGET) : null;
920+ if ((target == null) || (target.equals(""))) {
921+ target = hdoc.getBaseTarget();
922+ }
923+ if ((target == null) || (target.equals(""))) {
924+ target = "_self";
925+ }
926+ linkEvent = new HTMLFrameHyperlinkEvent(
927+ html, HyperlinkEvent.EventType.ACTIVATED, u, href,
928+ element, mouseEvent, target);
929+ }
930+ return linkEvent;
931+ }
932+
933+ void fireEvents(JEditorPane editor, HTMLDocument doc, String href,
934+ Element lastElem, MouseEvent mouseEvent) {
935+ if (this.href != null) {
936+ // fire an exited event on the old link
937+ URL u;
938+ try {
939+ u = new URL(doc.getBase(), this.href);
940+ } catch (MalformedURLException m) {
941+ u = null;
942+ }
943+ HyperlinkEvent exit = new HyperlinkEvent(editor,
944+ HyperlinkEvent.EventType.EXITED, u, this.href,
945+ lastElem, mouseEvent);
946+ editor.fireHyperlinkUpdate(exit);
947+ }
948+ if (href != null) {
949+ // fire an entered event on the new link
950+ URL u;
951+ try {
952+ u = new URL(doc.getBase(), href);
953+ } catch (MalformedURLException m) {
954+ u = null;
955+ }
956+ HyperlinkEvent entered = new HyperlinkEvent(editor,
957+ HyperlinkEvent.EventType.ENTERED,
958+ u, href, curElem, mouseEvent);
959+ editor.fireHyperlinkUpdate(entered);
960+ }
961+ }
962+ }
963+
964+ /**
965+ * Interface to be supported by the parser. This enables
966+ * providing a different parser while reusing some of the
967+ * implementation provided by this editor kit.
968+ */
969+ public static abstract class Parser {
970+ /**
971+ * Parse the given stream and drive the given callback
972+ * with the results of the parse. This method should
973+ * be implemented to be thread-safe.
974+ */
975+ public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
976+
977+ }
978+
979+ /**
980+ * The result of parsing drives these callback methods.
981+ * The open and close actions should be balanced. The
982+ * <code>flush</code> method will be the last method
983+ * called, to give the receiver a chance to flush any
984+ * pending data into the document.
985+ * <p>Refer to DocumentParser, the default parser used, for further
986+ * information on the contents of the AttributeSets, the positions, and
987+ * other info.
988+ *
989+ * @see javax.swing.text.html.parser.DocumentParser
990+ */
991+ public static class ParserCallback {
992+ /**
993+ * This is passed as an attribute in the attributeset to indicate
994+ * the element is implied eg, the string '&lt;&gt;foo&lt;\t&gt;'
995+ * contains an implied html element and an implied body element.
996+ *
997+ * @since 1.3
998+ */
999+ public static final Object IMPLIED = "_implied_";
1000+
1001+
1002+ public void flush() throws BadLocationException {
1003+ }
1004+
1005+ public void handleText(char[] data, int pos) {
1006+ }
1007+
1008+ public void handleComment(char[] data, int pos) {
1009+ }
1010+
1011+ public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
1012+ }
1013+
1014+ public void handleEndTag(HTML.Tag t, int pos) {
1015+ }
1016+
1017+ public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
1018+ }
1019+
1020+ public void handleError(String errorMsg, int pos){
1021+ }
1022+
1023+ /**
1024+ * This is invoked after the stream has been parsed, but before
1025+ * <code>flush</code>. <code>eol</code> will be one of \n, \r
1026+ * or \r\n, which ever is encountered the most in parsing the
1027+ * stream.
1028+ *
1029+ * @since 1.3
1030+ */
1031+ public void handleEndOfLineString(String eol) {
1032+ }
1033+ }
1034+
1035+ /**
1036+ * A factory to build views for HTML. The following
1037+ * table describes what this factory will build by
1038+ * default.
1039+ *
1040+ * <table summary="Describes the tag and view created by this factory by default">
1041+ * <tr>
1042+ * <th align=left>Tag<th align=left>View created
1043+ * </tr><tr>
1044+ * <td>HTML.Tag.CONTENT<td>InlineView
1045+ * </tr><tr>
1046+ * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView
1047+ * </tr><tr>
1048+ * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView
1049+ * </tr><tr>
1050+ * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView
1051+ * </tr><tr>
1052+ * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView
1053+ * </tr><tr>
1054+ * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView
1055+ * </tr><tr>
1056+ * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView
1057+ * </tr><tr>
1058+ * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView
1059+ * </tr><tr>
1060+ * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView
1061+ * </tr><tr>
1062+ * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView
1063+ * </tr><tr>
1064+ * <td>HTML.Tag.MENU<td>ListView
1065+ * </tr><tr>
1066+ * <td>HTML.Tag.DIR<td>ListView
1067+ * </tr><tr>
1068+ * <td>HTML.Tag.UL<td>ListView
1069+ * </tr><tr>
1070+ * <td>HTML.Tag.OL<td>ListView
1071+ * </tr><tr>
1072+ * <td>HTML.Tag.LI<td>BlockView
1073+ * </tr><tr>
1074+ * <td>HTML.Tag.DL<td>BlockView
1075+ * </tr><tr>
1076+ * <td>HTML.Tag.DD<td>BlockView
1077+ * </tr><tr>
1078+ * <td>HTML.Tag.BODY<td>BlockView
1079+ * </tr><tr>
1080+ * <td>HTML.Tag.HTML<td>BlockView
1081+ * </tr><tr>
1082+ * <td>HTML.Tag.CENTER<td>BlockView
1083+ * </tr><tr>
1084+ * <td>HTML.Tag.DIV<td>BlockView
1085+ * </tr><tr>
1086+ * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1087+ * </tr><tr>
1088+ * <td>HTML.Tag.PRE<td>BlockView
1089+ * </tr><tr>
1090+ * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1091+ * </tr><tr>
1092+ * <td>HTML.Tag.PRE<td>BlockView
1093+ * </tr><tr>
1094+ * <td>HTML.Tag.IMG<td>ImageView
1095+ * </tr><tr>
1096+ * <td>HTML.Tag.HR<td>HRuleView
1097+ * </tr><tr>
1098+ * <td>HTML.Tag.BR<td>BRView
1099+ * </tr><tr>
1100+ * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView
1101+ * </tr><tr>
1102+ * <td>HTML.Tag.INPUT<td>FormView
1103+ * </tr><tr>
1104+ * <td>HTML.Tag.SELECT<td>FormView
1105+ * </tr><tr>
1106+ * <td>HTML.Tag.TEXTAREA<td>FormView
1107+ * </tr><tr>
1108+ * <td>HTML.Tag.OBJECT<td>ObjectView
1109+ * </tr><tr>
1110+ * <td>HTML.Tag.FRAMESET<td>FrameSetView
1111+ * </tr><tr>
1112+ * <td>HTML.Tag.FRAME<td>FrameView
1113+ * </tr>
1114+ * </table>
1115+ */
1116+ public static class HTMLFactory implements ViewFactory {
1117+
1118+ /**
1119+ * Creates a view from an element.
1120+ *
1121+ * @param elem the element
1122+ * @return the view
1123+ */
1124+ public View create(Element elem) {
1125+ AttributeSet attrs = elem.getAttributes();
1126+ Object elementName =
1127+ attrs.getAttribute(AbstractDocument.ElementNameAttribute);
1128+ Object o = (elementName != null) ?
1129+ null : attrs.getAttribute(StyleConstants.NameAttribute);
1130+ if (o instanceof HTML.Tag) {
1131+ HTML.Tag kind = (HTML.Tag) o;
1132+ if (kind == HTML.Tag.CONTENT) {
1133+ return new InlineView(elem);
1134+ } else if (kind == HTML.Tag.IMPLIED) {
1135+ String ws = (String) elem.getAttributes().getAttribute(
1136+ CSS.Attribute.WHITE_SPACE);
1137+ if ((ws != null) && ws.equals("pre")) {
1138+ return new LineView(elem);
1139+ }
1140+ return new javax.swing.text.html.ParagraphView(elem);
1141+ } else if ((kind == HTML.Tag.P) ||
1142+ (kind == HTML.Tag.H1) ||
1143+ (kind == HTML.Tag.H2) ||
1144+ (kind == HTML.Tag.H3) ||
1145+ (kind == HTML.Tag.H4) ||
1146+ (kind == HTML.Tag.H5) ||
1147+ (kind == HTML.Tag.H6) ||
1148+ (kind == HTML.Tag.DT)) {
1149+ // paragraph
1150+ return new javax.swing.text.html.ParagraphView(elem);
1151+ } else if ((kind == HTML.Tag.MENU) ||
1152+ (kind == HTML.Tag.DIR) ||
1153+ (kind == HTML.Tag.UL) ||
1154+ (kind == HTML.Tag.OL)) {
1155+ return new ListView(elem);
1156+ } else if (kind == HTML.Tag.BODY) {
1157+ return new BodyBlockView(elem);
1158+ } else if (kind == HTML.Tag.HTML) {
1159+ return new BlockView(elem, View.Y_AXIS);
1160+ } else if ((kind == HTML.Tag.LI) ||
1161+ (kind == HTML.Tag.CENTER) ||
1162+ (kind == HTML.Tag.DL) ||
1163+ (kind == HTML.Tag.DD) ||
1164+ (kind == HTML.Tag.DIV) ||
1165+ (kind == HTML.Tag.BLOCKQUOTE) ||
1166+ (kind == HTML.Tag.PRE) ||
1167+ (kind == HTML.Tag.FORM)) {
1168+ // vertical box
1169+ return new BlockView(elem, View.Y_AXIS);
1170+ } else if (kind == HTML.Tag.NOFRAMES) {
1171+ return new NoFramesView(elem, View.Y_AXIS);
1172+ } else if (kind==HTML.Tag.IMG) {
1173+ return new ImageView(elem);
1174+ } else if (kind == HTML.Tag.ISINDEX) {
1175+ return new IsindexView(elem);
1176+ } else if (kind == HTML.Tag.HR) {
1177+ return new HRuleView(elem);
1178+ } else if (kind == HTML.Tag.BR) {
1179+ return new BRView(elem);
1180+ } else if (kind == HTML.Tag.TABLE) {
1181+ return new javax.swing.text.html.TableView(elem);
1182+ } else if ((kind == HTML.Tag.INPUT) ||
1183+ (kind == HTML.Tag.SELECT) ||
1184+ (kind == HTML.Tag.TEXTAREA)) {
1185+ return new FormView(elem);
1186+ } else if (kind == HTML.Tag.OBJECT) {
1187+ return new ObjectView(elem);
1188+ } else if (kind == HTML.Tag.FRAMESET) {
1189+ if (elem.getAttributes().isDefined(HTML.Attribute.ROWS)) {
1190+ return new FrameSetView(elem, View.Y_AXIS);
1191+ } else if (elem.getAttributes().isDefined(HTML.Attribute.COLS)) {
1192+ return new FrameSetView(elem, View.X_AXIS);
1193+ }
1194+ throw new RuntimeException("Can't build a" + kind + ", " + elem + ":" +
1195+ "no ROWS or COLS defined.");
1196+ } else if (kind == HTML.Tag.FRAME) {
1197+ return new FrameView(elem);
1198+ } else if (kind instanceof HTML.UnknownTag) {
1199+ return new HiddenTagView(elem);
1200+ } else if (kind == HTML.Tag.COMMENT) {
1201+ return new CommentView(elem);
1202+ } else if (kind == HTML.Tag.HEAD) {
1203+ // Make the head never visible, and never load its
1204+ // children. For Cursor positioning,
1205+ // getNextVisualPositionFrom is overriden to always return
1206+ // the end offset of the element.
1207+ return new BlockView(elem, View.X_AXIS) {
1208+ public float getPreferredSpan(int axis) {
1209+ return 0;
1210+ }
1211+ public float getMinimumSpan(int axis) {
1212+ return 0;
1213+ }
1214+ public float getMaximumSpan(int axis) {
1215+ return 0;
1216+ }
1217+ protected void loadChildren(ViewFactory f) {
1218+ }
1219+ public Shape modelToView(int pos, Shape a,
1220+ Position.Bias b) throws BadLocationException {
1221+ return a;
1222+ }
1223+ public int getNextVisualPositionFrom(int pos,
1224+ Position.Bias b, Shape a,
1225+ int direction, Position.Bias[] biasRet) {
1226+ return getElement().getEndOffset();
1227+ }
1228+ };
1229+ } else if ((kind == HTML.Tag.TITLE) ||
1230+ (kind == HTML.Tag.META) ||
1231+ (kind == HTML.Tag.LINK) ||
1232+ (kind == HTML.Tag.STYLE) ||
1233+ (kind == HTML.Tag.SCRIPT) ||
1234+ (kind == HTML.Tag.AREA) ||
1235+ (kind == HTML.Tag.MAP) ||
1236+ (kind == HTML.Tag.PARAM) ||
1237+ (kind == HTML.Tag.APPLET)) {
1238+ return new HiddenTagView(elem);
1239+ }
1240+ }
1241+ // If we get here, it's either an element we don't know about
1242+ // or something from StyledDocument that doesn't have a mapping to HTML.
1243+ String nm = (elementName != null) ? (String)elementName :
1244+ elem.getName();
1245+ if (nm != null) {
1246+ if (nm.equals(AbstractDocument.ContentElementName)) {
1247+ return new LabelView(elem);
1248+ } else if (nm.equals(AbstractDocument.ParagraphElementName)) {
1249+ return new ParagraphView(elem);
1250+ } else if (nm.equals(AbstractDocument.SectionElementName)) {
1251+ return new BoxView(elem, View.Y_AXIS);
1252+ } else if (nm.equals(StyleConstants.ComponentElementName)) {
1253+ return new ComponentView(elem);
1254+ } else if (nm.equals(StyleConstants.IconElementName)) {
1255+ return new IconView(elem);
1256+ }
1257+ }
1258+
1259+ // default to text display
1260+ return new LabelView(elem);
1261+ }
1262+
1263+ static class BodyBlockView extends BlockView implements ComponentListener {
1264+ public BodyBlockView(Element elem) {
1265+ super(elem,View.Y_AXIS);
1266+ }
1267+ // reimplement major axis requirements to indicate that the
1268+ // block is flexible for the body element... so that it can
1269+ // be stretched to fill the background properly.
1270+ protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
1271+ r = super.calculateMajorAxisRequirements(axis, r);
1272+ r.maximum = Integer.MAX_VALUE;
1273+ return r;
1274+ }
1275+
1276+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
1277+ Container container = getContainer();
1278+ Container parentContainer;
1279+ if (container != null
1280+ && (container instanceof javax.swing.JEditorPane)
1281+ && (parentContainer = container.getParent()) != null
1282+ && (parentContainer instanceof javax.swing.JViewport)) {
1283+ JViewport viewPort = (JViewport)parentContainer;
1284+ if (cachedViewPort != null) {
1285+ JViewport cachedObject = cachedViewPort.get();
1286+ if (cachedObject != null) {
1287+ if (cachedObject != viewPort) {
1288+ cachedObject.removeComponentListener(this);
1289+ }
1290+ } else {
1291+ cachedViewPort = null;
1292+ }
1293+ }
1294+ if (cachedViewPort == null) {
1295+ viewPort.addComponentListener(this);
1296+ cachedViewPort = new WeakReference<JViewport>(viewPort);
1297+ }
1298+
1299+ componentVisibleWidth = viewPort.getExtentSize().width;
1300+ if (componentVisibleWidth > 0) {
1301+ Insets insets = container.getInsets();
1302+ viewVisibleWidth = componentVisibleWidth - insets.left - getLeftInset();
1303+ //try to use viewVisibleWidth if it is smaller than targetSpan
1304+ targetSpan = Math.min(targetSpan, viewVisibleWidth);
1305+ }
1306+ } else {
1307+ if (cachedViewPort != null) {
1308+ JViewport cachedObject = cachedViewPort.get();
1309+ if (cachedObject != null) {
1310+ cachedObject.removeComponentListener(this);
1311+ }
1312+ cachedViewPort = null;
1313+ }
1314+ }
1315+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
1316+ }
1317+
1318+ public void setParent(View parent) {
1319+ //if parent == null unregister component listener
1320+ if (parent == null) {
1321+ if (cachedViewPort != null) {
1322+ Object cachedObject;
1323+ if ((cachedObject = cachedViewPort.get()) != null) {
1324+ ((JComponent)cachedObject).removeComponentListener(this);
1325+ }
1326+ cachedViewPort = null;
1327+ }
1328+ }
1329+ super.setParent(parent);
1330+ }
1331+
1332+ public void componentResized(ComponentEvent e) {
1333+ if ( !(e.getSource() instanceof JViewport) ) {
1334+ return;
1335+ }
1336+ JViewport viewPort = (JViewport)e.getSource();
1337+ if (componentVisibleWidth != viewPort.getExtentSize().width) {
1338+ Document doc = getDocument();
1339+ if (doc instanceof AbstractDocument) {
1340+ AbstractDocument document = (AbstractDocument)getDocument();
1341+ document.readLock();
1342+ try {
1343+ layoutChanged(X_AXIS);
1344+ preferenceChanged(null, true, true);
1345+ } finally {
1346+ document.readUnlock();
1347+ }
1348+
1349+ }
1350+ }
1351+ }
1352+ public void componentHidden(ComponentEvent e) {
1353+ }
1354+ public void componentMoved(ComponentEvent e) {
1355+ }
1356+ public void componentShown(ComponentEvent e) {
1357+ }
1358+ /*
1359+ * we keep weak reference to viewPort if and only if BodyBoxView is listening for ComponentEvents
1360+ * only in that case cachedViewPort is not equal to null.
1361+ * we need to keep this reference in order to remove BodyBoxView from viewPort listeners.
1362+ *
1363+ */
1364+ private Reference<JViewport> cachedViewPort = null;
1365+ private boolean isListening = false;
1366+ private int viewVisibleWidth = Integer.MAX_VALUE;
1367+ private int componentVisibleWidth = Integer.MAX_VALUE;
1368+ }
1369+
1370+ }
1371+
1372+ // --- Action implementations ------------------------------
1373+
1374+/** The bold action identifier
1375+*/
1376+ public static final String BOLD_ACTION = "html-bold-action";
1377+/** The italic action identifier
1378+*/
1379+ public static final String ITALIC_ACTION = "html-italic-action";
1380+/** The paragraph left indent action identifier
1381+*/
1382+ public static final String PARA_INDENT_LEFT = "html-para-indent-left";
1383+/** The paragraph right indent action identifier
1384+*/
1385+ public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
1386+/** The font size increase to next value action identifier
1387+*/
1388+ public static final String FONT_CHANGE_BIGGER = "html-font-bigger";
1389+/** The font size decrease to next value action identifier
1390+*/
1391+ public static final String FONT_CHANGE_SMALLER = "html-font-smaller";
1392+/** The Color choice action identifier
1393+ The color is passed as an argument
1394+*/
1395+ public static final String COLOR_ACTION = "html-color-action";
1396+/** The logical style choice action identifier
1397+ The logical style is passed in as an argument
1398+*/
1399+ public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action";
1400+ /**
1401+ * Align images at the top.
1402+ */
1403+ public static final String IMG_ALIGN_TOP = "html-image-align-top";
1404+
1405+ /**
1406+ * Align images in the middle.
1407+ */
1408+ public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle";
1409+
1410+ /**
1411+ * Align images at the bottom.
1412+ */
1413+ public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom";
1414+
1415+ /**
1416+ * Align images at the border.
1417+ */
1418+ public static final String IMG_BORDER = "html-image-border";
1419+
1420+
1421+ /** HTML used when inserting tables. */
1422+ private static final String INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>";
1423+
1424+ /** HTML used when inserting unordered lists. */
1425+ private static final String INSERT_UL_HTML = "<ul><li></li></ul>";
1426+
1427+ /** HTML used when inserting ordered lists. */
1428+ private static final String INSERT_OL_HTML = "<ol><li></li></ol>";
1429+
1430+ /** HTML used when inserting hr. */
1431+ private static final String INSERT_HR_HTML = "<hr>";
1432+
1433+ /** HTML used when inserting pre. */
1434+ private static final String INSERT_PRE_HTML = "<pre></pre>";
1435+
1436+ private static final NavigateLinkAction nextLinkAction =
1437+ new NavigateLinkAction("next-link-action");
1438+
1439+ private static final NavigateLinkAction previousLinkAction =
1440+ new NavigateLinkAction("previous-link-action");
1441+
1442+ private static final ActivateLinkAction activateLinkAction =
1443+ new ActivateLinkAction("activate-link-action");
1444+
1445+ private static final Action[] defaultActions = {
1446+ new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML,
1447+ HTML.Tag.BODY, HTML.Tag.TABLE),
1448+ new InsertHTMLTextAction("InsertTableRow", INSERT_TABLE_HTML,
1449+ HTML.Tag.TABLE, HTML.Tag.TR,
1450+ HTML.Tag.BODY, HTML.Tag.TABLE),
1451+ new InsertHTMLTextAction("InsertTableDataCell", INSERT_TABLE_HTML,
1452+ HTML.Tag.TR, HTML.Tag.TD,
1453+ HTML.Tag.BODY, HTML.Tag.TABLE),
1454+ new InsertHTMLTextAction("InsertUnorderedList", INSERT_UL_HTML,
1455+ HTML.Tag.BODY, HTML.Tag.UL),
1456+ new InsertHTMLTextAction("InsertUnorderedListItem", INSERT_UL_HTML,
1457+ HTML.Tag.UL, HTML.Tag.LI,
1458+ HTML.Tag.BODY, HTML.Tag.UL),
1459+ new InsertHTMLTextAction("InsertOrderedList", INSERT_OL_HTML,
1460+ HTML.Tag.BODY, HTML.Tag.OL),
1461+ new InsertHTMLTextAction("InsertOrderedListItem", INSERT_OL_HTML,
1462+ HTML.Tag.OL, HTML.Tag.LI,
1463+ HTML.Tag.BODY, HTML.Tag.OL),
1464+ new InsertHRAction(),
1465+ new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML,
1466+ HTML.Tag.BODY, HTML.Tag.PRE),
1467+ nextLinkAction, previousLinkAction, activateLinkAction,
1468+
1469+ new BeginAction(beginAction, false),
1470+ new BeginAction(selectionBeginAction, true)
1471+ };
1472+
1473+ // link navigation support
1474+ private boolean foundLink = false;
1475+ private int prevHypertextOffset = -1;
1476+ private Object linkNavigationTag;
1477+
1478+
1479+ /**
1480+ * An abstract Action providing some convenience methods that may
1481+ * be useful in inserting HTML into an existing document.
1482+ * <p>NOTE: None of the convenience methods obtain a lock on the
1483+ * document. If you have another thread modifying the text these
1484+ * methods may have inconsistent behavior, or return the wrong thing.
1485+ */
1486+ public static abstract class HTMLTextAction extends StyledTextAction {
1487+ public HTMLTextAction(String name) {
1488+ super(name);
1489+ }
1490+
1491+ /**
1492+ * @return HTMLDocument of <code>e</code>.
1493+ */
1494+ protected HTMLDocument getHTMLDocument(JEditorPane e) {
1495+ Document d = e.getDocument();
1496+ if (d instanceof HTMLDocument) {
1497+ return (HTMLDocument) d;
1498+ }
1499+ throw new IllegalArgumentException("document must be HTMLDocument");
1500+ }
1501+
1502+ /**
1503+ * @return HTMLEditorKit for <code>e</code>.
1504+ */
1505+ protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
1506+ EditorKit k = e.getEditorKit();
1507+ if (k instanceof HTMLEditorKit) {
1508+ return (HTMLEditorKit) k;
1509+ }
1510+ throw new IllegalArgumentException("EditorKit must be HTMLEditorKit");
1511+ }
1512+
1513+ /**
1514+ * Returns an array of the Elements that contain <code>offset</code>.
1515+ * The first elements corresponds to the root.
1516+ */
1517+ protected Element[] getElementsAt(HTMLDocument doc, int offset) {
1518+ return getElementsAt(doc.getDefaultRootElement(), offset, 0);
1519+ }
1520+
1521+ /**
1522+ * Recursive method used by getElementsAt.
1523+ */
1524+ private Element[] getElementsAt(Element parent, int offset,
1525+ int depth) {
1526+ if (parent.isLeaf()) {
1527+ Element[] retValue = new Element[depth + 1];
1528+ retValue[depth] = parent;
1529+ return retValue;
1530+ }
1531+ Element[] retValue = getElementsAt(parent.getElement
1532+ (parent.getElementIndex(offset)), offset, depth + 1);
1533+ retValue[depth] = parent;
1534+ return retValue;
1535+ }
1536+
1537+ /**
1538+ * Returns number of elements, starting at the deepest leaf, needed
1539+ * to get to an element representing <code>tag</code>. This will
1540+ * return -1 if no elements is found representing <code>tag</code>,
1541+ * or 0 if the parent of the leaf at <code>offset</code> represents
1542+ * <code>tag</code>.
1543+ */
1544+ protected int elementCountToTag(HTMLDocument doc, int offset,
1545+ HTML.Tag tag) {
1546+ int depth = -1;
1547+ Element e = doc.getCharacterElement(offset);
1548+ while (e != null && e.getAttributes().getAttribute
1549+ (StyleConstants.NameAttribute) != tag) {
1550+ e = e.getParentElement();
1551+ depth++;
1552+ }
1553+ if (e == null) {
1554+ return -1;
1555+ }
1556+ return depth;
1557+ }
1558+
1559+ /**
1560+ * Returns the deepest element at <code>offset</code> matching
1561+ * <code>tag</code>.
1562+ */
1563+ protected Element findElementMatchingTag(HTMLDocument doc, int offset,
1564+ HTML.Tag tag) {
1565+ Element e = doc.getDefaultRootElement();
1566+ Element lastMatch = null;
1567+ while (e != null) {
1568+ if (e.getAttributes().getAttribute
1569+ (StyleConstants.NameAttribute) == tag) {
1570+ lastMatch = e;
1571+ }
1572+ e = e.getElement(e.getElementIndex(offset));
1573+ }
1574+ return lastMatch;
1575+ }
1576+ }
1577+
1578+
1579+ /**
1580+ * InsertHTMLTextAction can be used to insert an arbitrary string of HTML
1581+ * into an existing HTML document. At least two HTML.Tags need to be
1582+ * supplied. The first Tag, parentTag, identifies the parent in
1583+ * the document to add the elements to. The second tag, addTag,
1584+ * identifies the first tag that should be added to the document as
1585+ * seen in the HTML string. One important thing to remember, is that
1586+ * the parser is going to generate all the appropriate tags, even if
1587+ * they aren't in the HTML string passed in.<p>
1588+ * For example, lets say you wanted to create an action to insert
1589+ * a table into the body. The parentTag would be HTML.Tag.BODY,
1590+ * addTag would be HTML.Tag.TABLE, and the string could be something
1591+ * like &lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;.
1592+ * <p>There is also an option to supply an alternate parentTag and
1593+ * addTag. These will be checked for if there is no parentTag at
1594+ * offset.
1595+ */
1596+ public static class InsertHTMLTextAction extends HTMLTextAction {
1597+ public InsertHTMLTextAction(String name, String html,
1598+ HTML.Tag parentTag, HTML.Tag addTag) {
1599+ this(name, html, parentTag, addTag, null, null);
1600+ }
1601+
1602+ public InsertHTMLTextAction(String name, String html,
1603+ HTML.Tag parentTag,
1604+ HTML.Tag addTag,
1605+ HTML.Tag alternateParentTag,
1606+ HTML.Tag alternateAddTag) {
1607+ this(name, html, parentTag, addTag, alternateParentTag,
1608+ alternateAddTag, true);
1609+ }
1610+
1611+ /* public */
1612+ InsertHTMLTextAction(String name, String html,
1613+ HTML.Tag parentTag,
1614+ HTML.Tag addTag,
1615+ HTML.Tag alternateParentTag,
1616+ HTML.Tag alternateAddTag,
1617+ boolean adjustSelection) {
1618+ super(name);
1619+ this.html = html;
1620+ this.parentTag = parentTag;
1621+ this.addTag = addTag;
1622+ this.alternateParentTag = alternateParentTag;
1623+ this.alternateAddTag = alternateAddTag;
1624+ this.adjustSelection = adjustSelection;
1625+ }
1626+
1627+ /**
1628+ * A cover for HTMLEditorKit.insertHTML. If an exception it
1629+ * thrown it is wrapped in a RuntimeException and thrown.
1630+ */
1631+ protected void insertHTML(JEditorPane editor, HTMLDocument doc,
1632+ int offset, String html, int popDepth,
1633+ int pushDepth, HTML.Tag addTag) {
1634+ try {
1635+ getHTMLEditorKit(editor).insertHTML(doc, offset, html,
1636+ popDepth, pushDepth,
1637+ addTag);
1638+ } catch (IOException ioe) {
1639+ throw new RuntimeException("Unable to insert: " + ioe);
1640+ } catch (BadLocationException ble) {
1641+ throw new RuntimeException("Unable to insert: " + ble);
1642+ }
1643+ }
1644+
1645+ /**
1646+ * This is invoked when inserting at a boundary. It determines
1647+ * the number of pops, and then the number of pushes that need
1648+ * to be performed, and then invokes insertHTML.
1649+ * @since 1.3
1650+ */
1651+ protected void insertAtBoundary(JEditorPane editor, HTMLDocument doc,
1652+ int offset, Element insertElement,
1653+ String html, HTML.Tag parentTag,
1654+ HTML.Tag addTag) {
1655+ insertAtBoundry(editor, doc, offset, insertElement, html,
1656+ parentTag, addTag);
1657+ }
1658+
1659+ /**
1660+ * This is invoked when inserting at a boundary. It determines
1661+ * the number of pops, and then the number of pushes that need
1662+ * to be performed, and then invokes insertHTML.
1663+ * @deprecated As of Java 2 platform v1.3, use insertAtBoundary
1664+ */
1665+ @Deprecated
1666+ protected void insertAtBoundry(JEditorPane editor, HTMLDocument doc,
1667+ int offset, Element insertElement,
1668+ String html, HTML.Tag parentTag,
1669+ HTML.Tag addTag) {
1670+ // Find the common parent.
1671+ Element e;
1672+ Element commonParent;
1673+ boolean isFirst = (offset == 0);
1674+
1675+ if (offset > 0 || insertElement == null) {
1676+ e = doc.getDefaultRootElement();
1677+ while (e != null && e.getStartOffset() != offset &&
1678+ !e.isLeaf()) {
1679+ e = e.getElement(e.getElementIndex(offset));
1680+ }
1681+ commonParent = (e != null) ? e.getParentElement() : null;
1682+ }
1683+ else {
1684+ // If inserting at the origin, the common parent is the
1685+ // insertElement.
1686+ commonParent = insertElement;
1687+ }
1688+ if (commonParent != null) {
1689+ // Determine how many pops to do.
1690+ int pops = 0;
1691+ int pushes = 0;
1692+ if (isFirst && insertElement != null) {
1693+ e = commonParent;
1694+ while (e != null && !e.isLeaf()) {
1695+ e = e.getElement(e.getElementIndex(offset));
1696+ pops++;
1697+ }
1698+ }
1699+ else {
1700+ e = commonParent;
1701+ offset--;
1702+ while (e != null && !e.isLeaf()) {
1703+ e = e.getElement(e.getElementIndex(offset));
1704+ pops++;
1705+ }
1706+
1707+ // And how many pushes
1708+ e = commonParent;
1709+ offset++;
1710+ while (e != null && e != insertElement) {
1711+ e = e.getElement(e.getElementIndex(offset));
1712+ pushes++;
1713+ }
1714+ }
1715+ pops = Math.max(0, pops - 1);
1716+
1717+ // And insert!
1718+ insertHTML(editor, doc, offset, html, pops, pushes, addTag);
1719+ }
1720+ }
1721+
1722+ /**
1723+ * If there is an Element with name <code>tag</code> at
1724+ * <code>offset</code>, this will invoke either insertAtBoundary
1725+ * or <code>insertHTML</code>. This returns true if there is
1726+ * a match, and one of the inserts is invoked.
1727+ */
1728+ /*protected*/
1729+ boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
1730+ int offset, HTML.Tag tag, HTML.Tag addTag) {
1731+ Element e = findElementMatchingTag(doc, offset, tag);
1732+ if (e != null && e.getStartOffset() == offset) {
1733+ insertAtBoundary(editor, doc, offset, e, html,
1734+ tag, addTag);
1735+ return true;
1736+ }
1737+ else if (offset > 0) {
1738+ int depth = elementCountToTag(doc, offset - 1, tag);
1739+ if (depth != -1) {
1740+ insertHTML(editor, doc, offset, html, depth, 0, addTag);
1741+ return true;
1742+ }
1743+ }
1744+ return false;
1745+ }
1746+
1747+ /**
1748+ * Called after an insertion to adjust the selection.
1749+ */
1750+ /* protected */
1751+ void adjustSelection(JEditorPane pane, HTMLDocument doc,
1752+ int startOffset, int oldLength) {
1753+ int newLength = doc.getLength();
1754+ if (newLength != oldLength && startOffset < newLength) {
1755+ if (startOffset > 0) {
1756+ String text;
1757+ try {
1758+ text = doc.getText(startOffset - 1, 1);
1759+ } catch (BadLocationException ble) {
1760+ text = null;
1761+ }
1762+ if (text != null && text.length() > 0 &&
1763+ text.charAt(0) == '\n') {
1764+ pane.select(startOffset, startOffset);
1765+ }
1766+ else {
1767+ pane.select(startOffset + 1, startOffset + 1);
1768+ }
1769+ }
1770+ else {
1771+ pane.select(1, 1);
1772+ }
1773+ }
1774+ }
1775+
1776+ /**
1777+ * Inserts the HTML into the document.
1778+ *
1779+ * @param ae the event
1780+ */
1781+ public void actionPerformed(ActionEvent ae) {
1782+ JEditorPane editor = getEditor(ae);
1783+ if (editor != null) {
1784+ HTMLDocument doc = getHTMLDocument(editor);
1785+ int offset = editor.getSelectionStart();
1786+ int length = doc.getLength();
1787+ boolean inserted;
1788+ // Try first choice
1789+ if (!insertIntoTag(editor, doc, offset, parentTag, addTag) &&
1790+ alternateParentTag != null) {
1791+ // Then alternate.
1792+ inserted = insertIntoTag(editor, doc, offset,
1793+ alternateParentTag,
1794+ alternateAddTag);
1795+ }
1796+ else {
1797+ inserted = true;
1798+ }
1799+ if (adjustSelection && inserted) {
1800+ adjustSelection(editor, doc, offset, length);
1801+ }
1802+ }
1803+ }
1804+
1805+ /** HTML to insert. */
1806+ protected String html;
1807+ /** Tag to check for in the document. */
1808+ protected HTML.Tag parentTag;
1809+ /** Tag in HTML to start adding tags from. */
1810+ protected HTML.Tag addTag;
1811+ /** Alternate Tag to check for in the document if parentTag is
1812+ * not found. */
1813+ protected HTML.Tag alternateParentTag;
1814+ /** Alternate tag in HTML to start adding tags from if parentTag
1815+ * is not found and alternateParentTag is found. */
1816+ protected HTML.Tag alternateAddTag;
1817+ /** True indicates the selection should be adjusted after an insert. */
1818+ boolean adjustSelection;
1819+ }
1820+
1821+
1822+ /**
1823+ * InsertHRAction is special, at actionPerformed time it will determine
1824+ * the parent HTML.Tag based on the paragraph element at the selection
1825+ * start.
1826+ */
1827+ static class InsertHRAction extends InsertHTMLTextAction {
1828+ InsertHRAction() {
1829+ super("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null, null,
1830+ false);
1831+ }
1832+
1833+ /**
1834+ * Inserts the HTML into the document.
1835+ *
1836+ * @param ae the event
1837+ */
1838+ public void actionPerformed(ActionEvent ae) {
1839+ JEditorPane editor = getEditor(ae);
1840+ if (editor != null) {
1841+ HTMLDocument doc = getHTMLDocument(editor);
1842+ int offset = editor.getSelectionStart();
1843+ Element paragraph = doc.getParagraphElement(offset);
1844+ if (paragraph.getParentElement() != null) {
1845+ parentTag = (HTML.Tag)paragraph.getParentElement().
1846+ getAttributes().getAttribute
1847+ (StyleConstants.NameAttribute);
1848+ super.actionPerformed(ae);
1849+ }
1850+ }
1851+ }
1852+
1853+ }
1854+
1855+ /*
1856+ * Returns the object in an AttributeSet matching a key
1857+ */
1858+ static private Object getAttrValue(AttributeSet attr, HTML.Attribute key) {
1859+ Enumeration names = attr.getAttributeNames();
1860+ while (names.hasMoreElements()) {
1861+ Object nextKey = names.nextElement();
1862+ Object nextVal = attr.getAttribute(nextKey);
1863+ if (nextVal instanceof AttributeSet) {
1864+ Object value = getAttrValue((AttributeSet)nextVal, key);
1865+ if (value != null) {
1866+ return value;
1867+ }
1868+ } else if (nextKey == key) {
1869+ return nextVal;
1870+ }
1871+ }
1872+ return null;
1873+ }
1874+
1875+ /*
1876+ * Action to move the focus on the next or previous hypertext link
1877+ * or object. TODO: This method relies on support from the
1878+ * javax.accessibility package. The text package should support
1879+ * keyboard navigation of text elements directly.
1880+ */
1881+ static class NavigateLinkAction extends TextAction implements CaretListener {
1882+
1883+ private static final FocusHighlightPainter focusPainter =
1884+ new FocusHighlightPainter(null);
1885+ private final boolean focusBack;
1886+
1887+ /*
1888+ * Create this action with the appropriate identifier.
1889+ */
1890+ public NavigateLinkAction(String actionName) {
1891+ super(actionName);
1892+ focusBack = "previous-link-action".equals(actionName);
1893+ }
1894+
1895+ /**
1896+ * Called when the caret position is updated.
1897+ *
1898+ * @param e the caret event
1899+ */
1900+ public void caretUpdate(CaretEvent e) {
1901+ Object src = e.getSource();
1902+ if (src instanceof JTextComponent) {
1903+ JTextComponent comp = (JTextComponent) src;
1904+ HTMLEditorKit kit = getHTMLEditorKit(comp);
1905+ if (kit != null && kit.foundLink) {
1906+ kit.foundLink = false;
1907+ // TODO: The AccessibleContext for the editor should register
1908+ // as a listener for CaretEvents and forward the events to
1909+ // assistive technologies listening for such events.
1910+ comp.getAccessibleContext().firePropertyChange(
1911+ AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET,
1912+ Integer.valueOf(kit.prevHypertextOffset),
1913+ Integer.valueOf(e.getDot()));
1914+ }
1915+ }
1916+ }
1917+
1918+ /*
1919+ * The operation to perform when this action is triggered.
1920+ */
1921+ public void actionPerformed(ActionEvent e) {
1922+ JTextComponent comp = getTextComponent(e);
1923+ if (comp == null || comp.isEditable()) {
1924+ return;
1925+ }
1926+
1927+ Document doc = comp.getDocument();
1928+ HTMLEditorKit kit = getHTMLEditorKit(comp);
1929+ if (doc == null || kit == null) {
1930+ return;
1931+ }
1932+
1933+ // TODO: Should start successive iterations from the
1934+ // current caret position.
1935+ ElementIterator ei = new ElementIterator(doc);
1936+ int currentOffset = comp.getCaretPosition();
1937+ int prevStartOffset = -1;
1938+ int prevEndOffset = -1;
1939+
1940+ // highlight the next link or object after the current caret position
1941+ Element nextElement;
1942+ while ((nextElement = ei.next()) != null) {
1943+ String name = nextElement.getName();
1944+ AttributeSet attr = nextElement.getAttributes();
1945+
1946+ Object href = getAttrValue(attr, HTML.Attribute.HREF);
1947+ if (!(name.equals(HTML.Tag.OBJECT.toString())) && href == null) {
1948+ continue;
1949+ }
1950+
1951+ int elementOffset = nextElement.getStartOffset();
1952+ if (focusBack) {
1953+ if (elementOffset >= currentOffset &&
1954+ prevStartOffset >= 0) {
1955+
1956+ kit.foundLink = true;
1957+ comp.setCaretPosition(prevStartOffset);
1958+ moveCaretPosition(comp, kit, prevStartOffset,
1959+ prevEndOffset);
1960+ kit.prevHypertextOffset = prevStartOffset;
1961+ return;
1962+ }
1963+ } else { // focus forward
1964+ if (elementOffset > currentOffset) {
1965+
1966+ kit.foundLink = true;
1967+ comp.setCaretPosition(elementOffset);
1968+ moveCaretPosition(comp, kit, elementOffset,
1969+ nextElement.getEndOffset());
1970+ kit.prevHypertextOffset = elementOffset;
1971+ return;
1972+ }
1973+ }
1974+ prevStartOffset = nextElement.getStartOffset();
1975+ prevEndOffset = nextElement.getEndOffset();
1976+ }
1977+ if (focusBack && prevStartOffset >= 0) {
1978+ kit.foundLink = true;
1979+ comp.setCaretPosition(prevStartOffset);
1980+ moveCaretPosition(comp, kit, prevStartOffset, prevEndOffset);
1981+ kit.prevHypertextOffset = prevStartOffset;
1982+ }
1983+ }
1984+
1985+ /*
1986+ * Moves the caret from mark to dot
1987+ */
1988+ private void moveCaretPosition(JTextComponent comp, HTMLEditorKit kit,
1989+ int mark, int dot) {
1990+ Highlighter h = comp.getHighlighter();
1991+ if (h != null) {
1992+ int p0 = Math.min(dot, mark);
1993+ int p1 = Math.max(dot, mark);
1994+ try {
1995+ if (kit.linkNavigationTag != null) {
1996+ h.changeHighlight(kit.linkNavigationTag, p0, p1);
1997+ } else {
1998+ kit.linkNavigationTag =
1999+ h.addHighlight(p0, p1, focusPainter);
2000+ }
2001+ } catch (BadLocationException e) {
2002+ }
2003+ }
2004+ }
2005+
2006+ private HTMLEditorKit getHTMLEditorKit(JTextComponent comp) {
2007+ if (comp instanceof JEditorPane) {
2008+ EditorKit kit = ((JEditorPane) comp).getEditorKit();
2009+ if (kit instanceof HTMLEditorKit) {
2010+ return (HTMLEditorKit) kit;
2011+ }
2012+ }
2013+ return null;
2014+ }
2015+
2016+ /**
2017+ * A highlight painter that draws a one-pixel border around
2018+ * the highlighted area.
2019+ */
2020+ static class FocusHighlightPainter extends
2021+ DefaultHighlighter.DefaultHighlightPainter {
2022+
2023+ FocusHighlightPainter(Color color) {
2024+ super(color);
2025+ }
2026+
2027+ /**
2028+ * Paints a portion of a highlight.
2029+ *
2030+ * @param g the graphics context
2031+ * @param offs0 the starting model offset >= 0
2032+ * @param offs1 the ending model offset >= offs1
2033+ * @param bounds the bounding box of the view, which is not
2034+ * necessarily the region to paint.
2035+ * @param c the editor
2036+ * @param view View painting for
2037+ * @return region in which drawing occurred
2038+ */
2039+ public Shape paintLayer(Graphics g, int offs0, int offs1,
2040+ Shape bounds, JTextComponent c, View view) {
2041+
2042+ Color color = getColor();
2043+
2044+ if (color == null) {
2045+ g.setColor(c.getSelectionColor());
2046+ }
2047+ else {
2048+ g.setColor(color);
2049+ }
2050+ if (offs0 == view.getStartOffset() &&
2051+ offs1 == view.getEndOffset()) {
2052+ // Contained in view, can just use bounds.
2053+ Rectangle alloc;
2054+ if (bounds instanceof Rectangle) {
2055+ alloc = (Rectangle)bounds;
2056+ }
2057+ else {
2058+ alloc = bounds.getBounds();
2059+ }
2060+ g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height);
2061+ return alloc;
2062+ }
2063+ else {
2064+ // Should only render part of View.
2065+ try {
2066+ // --- determine locations ---
2067+ Shape shape = view.modelToView(offs0, Position.Bias.Forward,
2068+ offs1,Position.Bias.Backward,
2069+ bounds);
2070+ Rectangle r = (shape instanceof Rectangle) ?
2071+ (Rectangle)shape : shape.getBounds();
2072+ g.drawRect(r.x, r.y, r.width - 1, r.height);
2073+ return r;
2074+ } catch (BadLocationException e) {
2075+ // can't render
2076+ }
2077+ }
2078+ // Only if exception
2079+ return null;
2080+ }
2081+ }
2082+ }
2083+
2084+ /*
2085+ * Action to activate the hypertext link that has focus.
2086+ * TODO: This method relies on support from the
2087+ * javax.accessibility package. The text package should support
2088+ * keyboard navigation of text elements directly.
2089+ */
2090+ static class ActivateLinkAction extends TextAction {
2091+
2092+ /**
2093+ * Create this action with the appropriate identifier.
2094+ */
2095+ public ActivateLinkAction(String actionName) {
2096+ super(actionName);
2097+ }
2098+
2099+ /*
2100+ * activates the hyperlink at offset
2101+ */
2102+ private void activateLink(String href, HTMLDocument doc,
2103+ JEditorPane editor, int offset) {
2104+ try {
2105+ URL page =
2106+ (URL)doc.getProperty(Document.StreamDescriptionProperty);
2107+ URL url = new URL(page, href);
2108+ HyperlinkEvent linkEvent = new HyperlinkEvent
2109+ (editor, HyperlinkEvent.EventType.
2110+ ACTIVATED, url, url.toExternalForm(),
2111+ doc.getCharacterElement(offset));
2112+ editor.fireHyperlinkUpdate(linkEvent);
2113+ } catch (MalformedURLException m) {
2114+ }
2115+ }
2116+
2117+ /*
2118+ * Invokes default action on the object in an element
2119+ */
2120+ private void doObjectAction(JEditorPane editor, Element elem) {
2121+ View view = getView(editor, elem);
2122+ if (view != null && view instanceof ObjectView) {
2123+ Component comp = ((ObjectView)view).getComponent();
2124+ if (comp != null && comp instanceof Accessible) {
2125+ AccessibleContext ac = comp.getAccessibleContext();
2126+ if (ac != null) {
2127+ AccessibleAction aa = ac.getAccessibleAction();
2128+ if (aa != null) {
2129+ aa.doAccessibleAction(0);
2130+ }
2131+ }
2132+ }
2133+ }
2134+ }
2135+
2136+ /*
2137+ * Returns the root view for a document
2138+ */
2139+ private View getRootView(JEditorPane editor) {
2140+ return editor.getUI().getRootView(editor);
2141+ }
2142+
2143+ /*
2144+ * Returns a view associated with an element
2145+ */
2146+ private View getView(JEditorPane editor, Element elem) {
2147+ Object lock = lock(editor);
2148+ try {
2149+ View rootView = getRootView(editor);
2150+ int start = elem.getStartOffset();
2151+ if (rootView != null) {
2152+ return getView(rootView, elem, start);
2153+ }
2154+ return null;
2155+ } finally {
2156+ unlock(lock);
2157+ }
2158+ }
2159+
2160+ private View getView(View parent, Element elem, int start) {
2161+ if (parent.getElement() == elem) {
2162+ return parent;
2163+ }
2164+ int index = parent.getViewIndex(start, Position.Bias.Forward);
2165+
2166+ if (index != -1 && index < parent.getViewCount()) {
2167+ return getView(parent.getView(index), elem, start);
2168+ }
2169+ return null;
2170+ }
2171+
2172+ /*
2173+ * If possible acquires a lock on the Document. If a lock has been
2174+ * obtained a key will be retured that should be passed to
2175+ * <code>unlock</code>.
2176+ */
2177+ private Object lock(JEditorPane editor) {
2178+ Document document = editor.getDocument();
2179+
2180+ if (document instanceof AbstractDocument) {
2181+ ((AbstractDocument)document).readLock();
2182+ return document;
2183+ }
2184+ return null;
2185+ }
2186+
2187+ /*
2188+ * Releases a lock previously obtained via <code>lock</code>.
2189+ */
2190+ private void unlock(Object key) {
2191+ if (key != null) {
2192+ ((AbstractDocument)key).readUnlock();
2193+ }
2194+ }
2195+
2196+ /*
2197+ * The operation to perform when this action is triggered.
2198+ */
2199+ public void actionPerformed(ActionEvent e) {
2200+
2201+ JTextComponent c = getTextComponent(e);
2202+ if (c.isEditable() || !(c instanceof JEditorPane)) {
2203+ return;
2204+ }
2205+ JEditorPane editor = (JEditorPane)c;
2206+
2207+ Document d = editor.getDocument();
2208+ if (d == null || !(d instanceof HTMLDocument)) {
2209+ return;
2210+ }
2211+ HTMLDocument doc = (HTMLDocument)d;
2212+
2213+ ElementIterator ei = new ElementIterator(doc);
2214+ int currentOffset = editor.getCaretPosition();
2215+
2216+ // invoke the next link or object action
2217+ String urlString = null;
2218+ String objString = null;
2219+ Element currentElement;
2220+ while ((currentElement = ei.next()) != null) {
2221+ String name = currentElement.getName();
2222+ AttributeSet attr = currentElement.getAttributes();
2223+
2224+ Object href = getAttrValue(attr, HTML.Attribute.HREF);
2225+ if (href != null) {
2226+ if (currentOffset >= currentElement.getStartOffset() &&
2227+ currentOffset <= currentElement.getEndOffset()) {
2228+
2229+ activateLink((String)href, doc, editor, currentOffset);
2230+ return;
2231+ }
2232+ } else if (name.equals(HTML.Tag.OBJECT.toString())) {
2233+ Object obj = getAttrValue(attr, HTML.Attribute.CLASSID);
2234+ if (obj != null) {
2235+ if (currentOffset >= currentElement.getStartOffset() &&
2236+ currentOffset <= currentElement.getEndOffset()) {
2237+
2238+ doObjectAction(editor, currentElement);
2239+ return;
2240+ }
2241+ }
2242+ }
2243+ }
2244+ }
2245+ }
2246+
2247+ private static int getBodyElementStart(JTextComponent comp) {
2248+ Element rootElement = comp.getDocument().getRootElements()[0];
2249+ for (int i = 0; i < rootElement.getElementCount(); i++) {
2250+ Element currElement = rootElement.getElement(i);
2251+ if("body".equals(currElement.getName())) {
2252+ return currElement.getStartOffset();
2253+ }
2254+ }
2255+ return 0;
2256+ }
2257+
2258+ /*
2259+ * Move the caret to the beginning of the document.
2260+ * @see DefaultEditorKit#beginAction
2261+ * @see HTMLEditorKit#getActions
2262+ */
2263+
2264+ static class BeginAction extends TextAction {
2265+
2266+ /* Create this object with the appropriate identifier. */
2267+ BeginAction(String nm, boolean select) {
2268+ super(nm);
2269+ this.select = select;
2270+ }
2271+
2272+ /** The operation to perform when this action is triggered. */
2273+ public void actionPerformed(ActionEvent e) {
2274+ JTextComponent target = getTextComponent(e);
2275+ int bodyStart = getBodyElementStart(target);
2276+
2277+ if (target != null) {
2278+ if (select) {
2279+ target.moveCaretPosition(bodyStart);
2280+ } else {
2281+ target.setCaretPosition(bodyStart);
2282+ }
2283+ }
2284+ }
2285+
2286+ private boolean select;
2287+ }
2288+}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t