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 */
25package javax.swing.text.html;
26
27import sun.awt.AppContext;
28
29import java.lang.reflect.Method;
30import java.awt.*;
31import java.awt.event.*;
32import java.io.*;
33import java.net.MalformedURLException;
34import java.net.URL;
35import javax.swing.text.*;
36import javax.swing.*;
37import javax.swing.border.*;
38import javax.swing.event.*;
39import javax.swing.plaf.TextUI;
40import java.util.*;
41import javax.accessibility.*;
42import 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 */
164public 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