1 /* 2 * Copyright (c) 1997, 2014, 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 * <dt> 59 * Support editing 60 * <dd> 61 * It might seem fairly obvious that a plug-in for JEditorPane 62 * should provide editing support, but that fact has several 63 * design considerations. There are a substantial number of HTML 64 * documents that don't properly conform to an HTML specification. 65 * These must be normalized somewhat into a correct form if one 66 * is to edit them. Additionally, users don't like to be presented 67 * with an excessive amount of structure editing, so using traditional 68 * text editing gestures is preferred over using the HTML structure 69 * exactly as defined in the HTML document. 70 * <p> 71 * The modeling of HTML is provided by the class <code>HTMLDocument</code>. 72 * Its documentation describes the details of how the HTML is modeled. 73 * The editing support leverages heavily off of the text package. 74 * 75 * <dt> 76 * Extendable/Scalable 77 * <dd> 78 * To maximize the usefulness of this kit, a great deal of effort 79 * has gone into making it extendable. These are some of the 80 * features. 81 * <ol> 82 * <li> 83 * The parser is replaceable. The default parser is the Hot Java 84 * parser which is DTD based. A different DTD can be used, or an 85 * entirely different parser can be used. To change the parser, 86 * reimplement the getParser method. The default parser is 87 * dynamically loaded when first asked for, so the class files 88 * will never be loaded if an alternative parser is used. The 89 * default parser is in a separate package called parser below 90 * this package. 91 * <li> 92 * The parser drives the ParserCallback, which is provided by 93 * HTMLDocument. To change the callback, subclass HTMLDocument 94 * and reimplement the createDefaultDocument method to return 95 * document that produces a different reader. The reader controls 96 * how the document is structured. Although the Document provides 97 * HTML support by default, there is nothing preventing support of 98 * non-HTML tags that result in alternative element structures. 99 * <li> 100 * The default view of the models are provided as a hierarchy of 101 * View implementations, so one can easily customize how a particular 102 * element is displayed or add capabilities for new kinds of elements 103 * by providing new View implementations. The default set of views 104 * are provided by the <code>HTMLFactory</code> class. This can 105 * be easily changed by subclassing or replacing the HTMLFactory 106 * and reimplementing the getViewFactory method to return the alternative 107 * factory. 108 * <li> 109 * The View implementations work primarily off of CSS attributes, 110 * which are kept in the views. This makes it possible to have 111 * multiple views mapped over the same model that appear substantially 112 * different. This can be especially useful for printing. For 113 * most HTML attributes, the HTML attributes are converted to CSS 114 * attributes for display. This helps make the View implementations 115 * more general purpose 116 * </ol> 117 * 118 * <dt> 119 * Asynchronous Loading 120 * <dd> 121 * Larger documents involve a lot of parsing and take some time 122 * to load. By default, this kit produces documents that will be 123 * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>. 124 * This is controlled by a property on the document. The method 125 * {@link #createDefaultDocument createDefaultDocument} can 126 * be overriden to change this. The batching of work is done 127 * by the <code>HTMLDocument.HTMLReader</code> class. The actual 128 * work is done by the <code>DefaultStyledDocument</code> and 129 * <code>AbstractDocument</code> classes in the text package. 130 * 131 * <dt> 132 * Customization from current LAF 133 * <dd> 134 * HTML provides a well known set of features without exactly 135 * specifying the display characteristics. Swing has a theme 136 * mechanism for its look-and-feel implementations. It is desirable 137 * for the look-and-feel to feed display characteristics into the 138 * HTML views. An user with poor vision for example would want 139 * high contrast and larger than typical fonts. 140 * <p> 141 * The support for this is provided by the <code>StyleSheet</code> 142 * class. The presentation of the HTML can be heavily influenced 143 * by the setting of the StyleSheet property on the EditorKit. 144 * 145 * <dt> 146 * Not lossy 147 * <dd> 148 * An EditorKit has the ability to be read and save documents. 149 * It is generally the most pleasing to users if there is no loss 150 * of data between the two operation. The policy of the HTMLEditorKit 151 * will be to store things not recognized or not necessarily visible 152 * so they can be subsequently written out. The model of the HTML document 153 * should therefore contain all information discovered while reading the 154 * document. This is constrained in some ways by the need to support 155 * editing (i.e. incorrect documents sometimes must be normalized). 156 * The guiding principle is that information shouldn't be lost, but 157 * some might be synthesized to produce a more correct model or it might 158 * be rearranged. 159 * </dl> 160 * 161 * @author Timothy Prinzing 162 */ 163 @SuppressWarnings("serial") // Same-version serialization only 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(), 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 @SuppressWarnings("serial") // Same-version serialization only 645 public static class LinkController extends MouseAdapter implements MouseMotionListener, Serializable { 646 private Element curElem = null; 647 /** 648 * If true, the current element (curElem) represents an image. 649 */ 650 private boolean curElemImage = false; 651 private String href = null; 652 /** This is used by viewToModel to avoid allocing a new array each 653 * time. */ 654 private transient Position.Bias[] bias = new Position.Bias[1]; 655 /** 656 * Current offset. 657 */ 658 private int curOffset; 659 660 /** 661 * Called for a mouse click event. 662 * If the component is read-only (ie a browser) then 663 * the clicked event is used to drive an attempt to 664 * follow the reference specified by a link. 665 * 666 * @param e the mouse event 667 * @see MouseListener#mouseClicked 668 */ 669 public void mouseClicked(MouseEvent e) { 670 JEditorPane editor = (JEditorPane) e.getSource(); 671 672 if (! editor.isEditable() && editor.isEnabled() && 673 SwingUtilities.isLeftMouseButton(e)) { 674 Point pt = new Point(e.getX(), e.getY()); 675 int pos = editor.viewToModel(pt); 676 if (pos >= 0) { 677 activateLink(pos, editor, e); 678 } 679 } 680 } 681 682 // ignore the drags 683 public void mouseDragged(MouseEvent e) { 684 } 685 686 // track the moving of the mouse. 687 public void mouseMoved(MouseEvent e) { 688 JEditorPane editor = (JEditorPane) e.getSource(); 689 if (!editor.isEnabled()) { 690 return; 691 } 692 693 HTMLEditorKit kit = (HTMLEditorKit)editor.getEditorKit(); 694 boolean adjustCursor = true; 695 Cursor newCursor = kit.getDefaultCursor(); 696 if (!editor.isEditable()) { 697 Point pt = new Point(e.getX(), e.getY()); 698 int pos = editor.getUI().viewToModel(editor, pt, bias); 699 if (bias[0] == Position.Bias.Backward && pos > 0) { 700 pos--; 701 } 702 if (pos >= 0 &&(editor.getDocument() instanceof HTMLDocument)){ 703 HTMLDocument hdoc = (HTMLDocument)editor.getDocument(); 704 Element elem = hdoc.getCharacterElement(pos); 705 if (!doesElementContainLocation(editor, elem, pos, 706 e.getX(), e.getY())) { 707 elem = null; 708 } 709 if (curElem != elem || curElemImage) { 710 Element lastElem = curElem; 711 curElem = elem; 712 String href = null; 713 curElemImage = false; 714 if (elem != null) { 715 AttributeSet a = elem.getAttributes(); 716 AttributeSet anchor = (AttributeSet)a. 717 getAttribute(HTML.Tag.A); 718 if (anchor == null) { 719 curElemImage = (a.getAttribute(StyleConstants. 720 NameAttribute) == HTML.Tag.IMG); 721 if (curElemImage) { 722 href = getMapHREF(editor, hdoc, elem, a, 723 pos, e.getX(), e.getY()); 724 } 725 } 726 else { 727 href = (String)anchor.getAttribute 728 (HTML.Attribute.HREF); 729 } 730 } 731 732 if (href != this.href) { 733 // reference changed, fire event(s) 734 fireEvents(editor, hdoc, href, lastElem, e); 735 this.href = href; 736 if (href != null) { 737 newCursor = kit.getLinkCursor(); 738 } 739 } 740 else { 741 adjustCursor = false; 742 } 743 } 744 else { 745 adjustCursor = false; 746 } 747 curOffset = pos; 748 } 749 } 750 if (adjustCursor && editor.getCursor() != newCursor) { 751 editor.setCursor(newCursor); 752 } 753 } 754 755 /** 756 * Returns a string anchor if the passed in element has a 757 * USEMAP that contains the passed in location. 758 */ 759 private String getMapHREF(JEditorPane html, HTMLDocument hdoc, 760 Element elem, AttributeSet attr, int offset, 761 int x, int y) { 762 Object useMap = attr.getAttribute(HTML.Attribute.USEMAP); 763 if (useMap != null && (useMap instanceof String)) { 764 Map m = hdoc.getMap((String)useMap); 765 if (m != null && offset < hdoc.getLength()) { 766 Rectangle bounds; 767 TextUI ui = html.getUI(); 768 try { 769 Shape lBounds = ui.modelToView(html, offset, 770 Position.Bias.Forward); 771 Shape rBounds = ui.modelToView(html, offset + 1, 772 Position.Bias.Backward); 773 bounds = lBounds.getBounds(); 774 bounds.add((rBounds instanceof Rectangle) ? 775 (Rectangle)rBounds : rBounds.getBounds()); 776 } catch (BadLocationException ble) { 777 bounds = null; 778 } 779 if (bounds != null) { 780 AttributeSet area = m.getArea(x - bounds.x, 781 y - bounds.y, 782 bounds.width, 783 bounds.height); 784 if (area != null) { 785 return (String)area.getAttribute(HTML.Attribute. 786 HREF); 787 } 788 } 789 } 790 } 791 return null; 792 } 793 794 /** 795 * Returns true if the View representing <code>e</code> contains 796 * the location <code>x</code>, <code>y</code>. <code>offset</code> 797 * gives the offset into the Document to check for. 798 */ 799 private boolean doesElementContainLocation(JEditorPane editor, 800 Element e, int offset, 801 int x, int y) { 802 if (e != null && offset > 0 && e.getStartOffset() == offset) { 803 try { 804 TextUI ui = editor.getUI(); 805 Shape s1 = ui.modelToView(editor, offset, 806 Position.Bias.Forward); 807 if (s1 == null) { 808 return false; 809 } 810 Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle)s1 : 811 s1.getBounds(); 812 Shape s2 = ui.modelToView(editor, e.getEndOffset(), 813 Position.Bias.Backward); 814 if (s2 != null) { 815 Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle)s2 : 816 s2.getBounds(); 817 r1.add(r2); 818 } 819 return r1.contains(x, y); 820 } catch (BadLocationException ble) { 821 } 822 } 823 return true; 824 } 825 826 /** 827 * Calls linkActivated on the associated JEditorPane 828 * if the given position represents a link.<p>This is implemented 829 * to forward to the method with the same name, but with the following 830 * args both == -1. 831 * 832 * @param pos the position 833 * @param editor the editor pane 834 */ 835 protected void activateLink(int pos, JEditorPane editor) { 836 activateLink(pos, editor, null); 837 } 838 839 /** 840 * Calls linkActivated on the associated JEditorPane 841 * if the given position represents a link. If this was the result 842 * of a mouse click, <code>x</code> and 843 * <code>y</code> will give the location of the mouse, otherwise 844 * they will be {@literal <} 0. 845 * 846 * @param pos the position 847 * @param html the editor pane 848 */ 849 void activateLink(int pos, JEditorPane html, MouseEvent mouseEvent) { 850 Document doc = html.getDocument(); 851 if (doc instanceof HTMLDocument) { 852 HTMLDocument hdoc = (HTMLDocument) doc; 853 Element e = hdoc.getCharacterElement(pos); 854 AttributeSet a = e.getAttributes(); 855 AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A); 856 HyperlinkEvent linkEvent = null; 857 String description; 858 int x = -1; 859 int y = -1; 860 861 if (mouseEvent != null) { 862 x = mouseEvent.getX(); 863 y = mouseEvent.getY(); 864 } 865 866 if (anchor == null) { 867 href = getMapHREF(html, hdoc, e, a, pos, x, y); 868 } 869 else { 870 href = (String)anchor.getAttribute(HTML.Attribute.HREF); 871 } 872 873 if (href != null) { 874 linkEvent = createHyperlinkEvent(html, hdoc, href, anchor, 875 e, mouseEvent); 876 } 877 if (linkEvent != null) { 878 html.fireHyperlinkUpdate(linkEvent); 879 } 880 } 881 } 882 883 /** 884 * Creates and returns a new instance of HyperlinkEvent. If 885 * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent 886 * will be created. 887 */ 888 HyperlinkEvent createHyperlinkEvent(JEditorPane html, 889 HTMLDocument hdoc, String href, 890 AttributeSet anchor, 891 Element element, 892 MouseEvent mouseEvent) { 893 URL u; 894 try { 895 URL base = hdoc.getBase(); 896 u = new URL(base, href); 897 // Following is a workaround for 1.2, in which 898 // new URL("file://...", "#...") causes the filename to 899 // be lost. 900 if (href != null && "file".equals(u.getProtocol()) && 901 href.startsWith("#")) { 902 String baseFile = base.getFile(); 903 String newFile = u.getFile(); 904 if (baseFile != null && newFile != null && 905 !newFile.startsWith(baseFile)) { 906 u = new URL(base, baseFile + href); 907 } 908 } 909 } catch (MalformedURLException m) { 910 u = null; 911 } 912 HyperlinkEvent linkEvent; 913 914 if (!hdoc.isFrameDocument()) { 915 linkEvent = new HyperlinkEvent( 916 html, HyperlinkEvent.EventType.ACTIVATED, u, href, 917 element, mouseEvent); 918 } else { 919 String target = (anchor != null) ? 920 (String)anchor.getAttribute(HTML.Attribute.TARGET) : null; 921 if ((target == null) || (target.equals(""))) { 922 target = hdoc.getBaseTarget(); 923 } 924 if ((target == null) || (target.equals(""))) { 925 target = "_self"; 926 } 927 linkEvent = new HTMLFrameHyperlinkEvent( 928 html, HyperlinkEvent.EventType.ACTIVATED, u, href, 929 element, mouseEvent, target); 930 } 931 return linkEvent; 932 } 933 934 void fireEvents(JEditorPane editor, HTMLDocument doc, String href, 935 Element lastElem, MouseEvent mouseEvent) { 936 if (this.href != null) { 937 // fire an exited event on the old link 938 URL u; 939 try { 940 u = new URL(doc.getBase(), this.href); 941 } catch (MalformedURLException m) { 942 u = null; 943 } 944 HyperlinkEvent exit = new HyperlinkEvent(editor, 945 HyperlinkEvent.EventType.EXITED, u, this.href, 946 lastElem, mouseEvent); 947 editor.fireHyperlinkUpdate(exit); 948 } 949 if (href != null) { 950 // fire an entered event on the new link 951 URL u; 952 try { 953 u = new URL(doc.getBase(), href); 954 } catch (MalformedURLException m) { 955 u = null; 956 } 957 HyperlinkEvent entered = new HyperlinkEvent(editor, 958 HyperlinkEvent.EventType.ENTERED, 959 u, href, curElem, mouseEvent); 960 editor.fireHyperlinkUpdate(entered); 961 } 962 } 963 } 964 965 /** 966 * Interface to be supported by the parser. This enables 967 * providing a different parser while reusing some of the 968 * implementation provided by this editor kit. 969 */ 970 public static abstract class Parser { 971 /** 972 * Parse the given stream and drive the given callback 973 * with the results of the parse. This method should 974 * be implemented to be thread-safe. 975 */ 976 public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException; 977 978 } 979 980 /** 981 * The result of parsing drives these callback methods. 982 * The open and close actions should be balanced. The 983 * <code>flush</code> method will be the last method 984 * called, to give the receiver a chance to flush any 985 * pending data into the document. 986 * <p>Refer to DocumentParser, the default parser used, for further 987 * information on the contents of the AttributeSets, the positions, and 988 * other info. 989 * 990 * @see javax.swing.text.html.parser.DocumentParser 991 */ 992 public static class ParserCallback { 993 /** 994 * This is passed as an attribute in the attributeset to indicate 995 * the element is implied eg, the string '<>foo<\t>' 996 * contains an implied html element and an implied body element. 997 * 998 * @since 1.3 999 */ 1000 public static final Object IMPLIED = "_implied_"; 1001 1002 1003 public void flush() throws BadLocationException { 1004 } 1005 1006 public void handleText(char[] data, int pos) { 1007 } 1008 1009 public void handleComment(char[] data, int pos) { 1010 } 1011 1012 public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) { 1013 } 1014 1015 public void handleEndTag(HTML.Tag t, int pos) { 1016 } 1017 1018 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) { 1019 } 1020 1021 public void handleError(String errorMsg, int pos){ 1022 } 1023 1024 /** 1025 * This is invoked after the stream has been parsed, but before 1026 * <code>flush</code>. <code>eol</code> will be one of \n, \r 1027 * or \r\n, which ever is encountered the most in parsing the 1028 * stream. 1029 * 1030 * @since 1.3 1031 */ 1032 public void handleEndOfLineString(String eol) { 1033 } 1034 } 1035 1036 /** 1037 * A factory to build views for HTML. The following 1038 * table describes what this factory will build by 1039 * default. 1040 * 1041 * <table summary="Describes the tag and view created by this factory by default"> 1042 * <tr> 1043 * <th align=left>Tag<th align=left>View created 1044 * </tr><tr> 1045 * <td>HTML.Tag.CONTENT<td>InlineView 1046 * </tr><tr> 1047 * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView 1048 * </tr><tr> 1049 * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView 1050 * </tr><tr> 1051 * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView 1052 * </tr><tr> 1053 * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView 1054 * </tr><tr> 1055 * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView 1056 * </tr><tr> 1057 * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView 1058 * </tr><tr> 1059 * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView 1060 * </tr><tr> 1061 * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView 1062 * </tr><tr> 1063 * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView 1064 * </tr><tr> 1065 * <td>HTML.Tag.MENU<td>ListView 1066 * </tr><tr> 1067 * <td>HTML.Tag.DIR<td>ListView 1068 * </tr><tr> 1069 * <td>HTML.Tag.UL<td>ListView 1070 * </tr><tr> 1071 * <td>HTML.Tag.OL<td>ListView 1072 * </tr><tr> 1073 * <td>HTML.Tag.LI<td>BlockView 1074 * </tr><tr> 1075 * <td>HTML.Tag.DL<td>BlockView 1076 * </tr><tr> 1077 * <td>HTML.Tag.DD<td>BlockView 1078 * </tr><tr> 1079 * <td>HTML.Tag.BODY<td>BlockView 1080 * </tr><tr> 1081 * <td>HTML.Tag.HTML<td>BlockView 1082 * </tr><tr> 1083 * <td>HTML.Tag.CENTER<td>BlockView 1084 * </tr><tr> 1085 * <td>HTML.Tag.DIV<td>BlockView 1086 * </tr><tr> 1087 * <td>HTML.Tag.BLOCKQUOTE<td>BlockView 1088 * </tr><tr> 1089 * <td>HTML.Tag.PRE<td>BlockView 1090 * </tr><tr> 1091 * <td>HTML.Tag.BLOCKQUOTE<td>BlockView 1092 * </tr><tr> 1093 * <td>HTML.Tag.PRE<td>BlockView 1094 * </tr><tr> 1095 * <td>HTML.Tag.IMG<td>ImageView 1096 * </tr><tr> 1097 * <td>HTML.Tag.HR<td>HRuleView 1098 * </tr><tr> 1099 * <td>HTML.Tag.BR<td>BRView 1100 * </tr><tr> 1101 * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView 1102 * </tr><tr> 1103 * <td>HTML.Tag.INPUT<td>FormView 1104 * </tr><tr> 1105 * <td>HTML.Tag.SELECT<td>FormView 1106 * </tr><tr> 1107 * <td>HTML.Tag.TEXTAREA<td>FormView 1108 * </tr><tr> 1109 * <td>HTML.Tag.OBJECT<td>ObjectView 1110 * </tr><tr> 1111 * <td>HTML.Tag.FRAMESET<td>FrameSetView 1112 * </tr><tr> 1113 * <td>HTML.Tag.FRAME<td>FrameView 1114 * </tr> 1115 * </table> 1116 */ 1117 public static class HTMLFactory implements ViewFactory { 1118 1119 /** 1120 * Creates a view from an element. 1121 * 1122 * @param elem the element 1123 * @return the view 1124 */ 1125 public View create(Element elem) { 1126 AttributeSet attrs = elem.getAttributes(); 1127 Object elementName = 1128 attrs.getAttribute(AbstractDocument.ElementNameAttribute); 1129 Object o = (elementName != null) ? 1130 null : attrs.getAttribute(StyleConstants.NameAttribute); 1131 if (o instanceof HTML.Tag) { 1132 HTML.Tag kind = (HTML.Tag) o; 1133 if (kind == HTML.Tag.CONTENT) { 1134 return new InlineView(elem); 1135 } else if (kind == HTML.Tag.IMPLIED) { 1136 String ws = (String) elem.getAttributes().getAttribute( 1137 CSS.Attribute.WHITE_SPACE); 1138 if ((ws != null) && ws.equals("pre")) { 1139 return new LineView(elem); 1140 } 1141 return new javax.swing.text.html.ParagraphView(elem); 1142 } else if ((kind == HTML.Tag.P) || 1143 (kind == HTML.Tag.H1) || 1144 (kind == HTML.Tag.H2) || 1145 (kind == HTML.Tag.H3) || 1146 (kind == HTML.Tag.H4) || 1147 (kind == HTML.Tag.H5) || 1148 (kind == HTML.Tag.H6) || 1149 (kind == HTML.Tag.DT)) { 1150 // paragraph 1151 return new javax.swing.text.html.ParagraphView(elem); 1152 } else if ((kind == HTML.Tag.MENU) || 1153 (kind == HTML.Tag.DIR) || 1154 (kind == HTML.Tag.UL) || 1155 (kind == HTML.Tag.OL)) { 1156 return new ListView(elem); 1157 } else if (kind == HTML.Tag.BODY) { 1158 return new BodyBlockView(elem); 1159 } else if (kind == HTML.Tag.HTML) { 1160 return new BlockView(elem, View.Y_AXIS); 1161 } else if ((kind == HTML.Tag.LI) || 1162 (kind == HTML.Tag.CENTER) || 1163 (kind == HTML.Tag.DL) || 1164 (kind == HTML.Tag.DD) || 1165 (kind == HTML.Tag.DIV) || 1166 (kind == HTML.Tag.BLOCKQUOTE) || 1167 (kind == HTML.Tag.PRE) || 1168 (kind == HTML.Tag.FORM)) { 1169 // vertical box 1170 return new BlockView(elem, View.Y_AXIS); 1171 } else if (kind == HTML.Tag.NOFRAMES) { 1172 return new NoFramesView(elem, View.Y_AXIS); 1173 } else if (kind==HTML.Tag.IMG) { 1174 return new ImageView(elem); 1175 } else if (kind == HTML.Tag.ISINDEX) { 1176 return new IsindexView(elem); 1177 } else if (kind == HTML.Tag.HR) { 1178 return new HRuleView(elem); 1179 } else if (kind == HTML.Tag.BR) { 1180 return new BRView(elem); 1181 } else if (kind == HTML.Tag.TABLE) { 1182 return new javax.swing.text.html.TableView(elem); 1183 } else if ((kind == HTML.Tag.INPUT) || 1184 (kind == HTML.Tag.SELECT) || 1185 (kind == HTML.Tag.TEXTAREA)) { 1186 return new FormView(elem); 1187 } else if (kind == HTML.Tag.OBJECT) { 1188 return new ObjectView(elem); 1189 } else if (kind == HTML.Tag.FRAMESET) { 1190 if (elem.getAttributes().isDefined(HTML.Attribute.ROWS)) { 1191 return new FrameSetView(elem, View.Y_AXIS); 1192 } else if (elem.getAttributes().isDefined(HTML.Attribute.COLS)) { 1193 return new FrameSetView(elem, View.X_AXIS); 1194 } 1195 throw new RuntimeException("Can't build a" + kind + ", " + elem + ":" + 1196 "no ROWS or COLS defined."); 1197 } else if (kind == HTML.Tag.FRAME) { 1198 return new FrameView(elem); 1199 } else if (kind instanceof HTML.UnknownTag) { 1200 return new HiddenTagView(elem); 1201 } else if (kind == HTML.Tag.COMMENT) { 1202 return new CommentView(elem); 1203 } else if (kind == HTML.Tag.HEAD) { 1204 // Make the head never visible, and never load its 1205 // children. For Cursor positioning, 1206 // getNextVisualPositionFrom is overriden to always return 1207 // the end offset of the element. 1208 return new BlockView(elem, View.X_AXIS) { 1209 public float getPreferredSpan(int axis) { 1210 return 0; 1211 } 1212 public float getMinimumSpan(int axis) { 1213 return 0; 1214 } 1215 public float getMaximumSpan(int axis) { 1216 return 0; 1217 } 1218 protected void loadChildren(ViewFactory f) { 1219 } 1220 public Shape modelToView(int pos, Shape a, 1221 Position.Bias b) throws BadLocationException { 1222 return a; 1223 } 1224 public int getNextVisualPositionFrom(int pos, 1225 Position.Bias b, Shape a, 1226 int direction, Position.Bias[] biasRet) { 1227 return getElement().getEndOffset(); 1228 } 1229 }; 1230 } else if ((kind == HTML.Tag.TITLE) || 1231 (kind == HTML.Tag.META) || 1232 (kind == HTML.Tag.LINK) || 1233 (kind == HTML.Tag.STYLE) || 1234 (kind == HTML.Tag.SCRIPT) || 1235 (kind == HTML.Tag.AREA) || 1236 (kind == HTML.Tag.MAP) || 1237 (kind == HTML.Tag.PARAM) || 1238 (kind == HTML.Tag.APPLET)) { 1239 return new HiddenTagView(elem); 1240 } 1241 } 1242 // If we get here, it's either an element we don't know about 1243 // or something from StyledDocument that doesn't have a mapping to HTML. 1244 String nm = (elementName != null) ? (String)elementName : 1245 elem.getName(); 1246 if (nm != null) { 1247 if (nm.equals(AbstractDocument.ContentElementName)) { 1248 return new LabelView(elem); 1249 } else if (nm.equals(AbstractDocument.ParagraphElementName)) { 1250 return new ParagraphView(elem); 1251 } else if (nm.equals(AbstractDocument.SectionElementName)) { 1252 return new BoxView(elem, View.Y_AXIS); 1253 } else if (nm.equals(StyleConstants.ComponentElementName)) { 1254 return new ComponentView(elem); 1255 } else if (nm.equals(StyleConstants.IconElementName)) { 1256 return new IconView(elem); 1257 } 1258 } 1259 1260 // default to text display 1261 return new LabelView(elem); 1262 } 1263 1264 static class BodyBlockView extends BlockView implements ComponentListener { 1265 public BodyBlockView(Element elem) { 1266 super(elem,View.Y_AXIS); 1267 } 1268 // reimplement major axis requirements to indicate that the 1269 // block is flexible for the body element... so that it can 1270 // be stretched to fill the background properly. 1271 protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) { 1272 r = super.calculateMajorAxisRequirements(axis, r); 1273 r.maximum = Integer.MAX_VALUE; 1274 return r; 1275 } 1276 1277 protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { 1278 Container container = getContainer(); 1279 Container parentContainer; 1280 if (container != null 1281 && (container instanceof javax.swing.JEditorPane) 1282 && (parentContainer = container.getParent()) != null 1283 && (parentContainer instanceof javax.swing.JViewport)) { 1284 JViewport viewPort = (JViewport)parentContainer; 1285 if (cachedViewPort != null) { 1286 JViewport cachedObject = cachedViewPort.get(); 1287 if (cachedObject != null) { 1288 if (cachedObject != viewPort) { 1289 cachedObject.removeComponentListener(this); 1290 } 1291 } else { 1292 cachedViewPort = null; 1293 } 1294 } 1295 if (cachedViewPort == null) { 1296 viewPort.addComponentListener(this); 1297 cachedViewPort = new WeakReference<JViewport>(viewPort); 1298 } 1299 1300 componentVisibleWidth = viewPort.getExtentSize().width; 1301 if (componentVisibleWidth > 0) { 1302 Insets insets = container.getInsets(); 1303 viewVisibleWidth = componentVisibleWidth - insets.left - getLeftInset(); 1304 //try to use viewVisibleWidth if it is smaller than targetSpan 1305 targetSpan = Math.min(targetSpan, viewVisibleWidth); 1306 } 1307 } else { 1308 if (cachedViewPort != null) { 1309 JViewport cachedObject = cachedViewPort.get(); 1310 if (cachedObject != null) { 1311 cachedObject.removeComponentListener(this); 1312 } 1313 cachedViewPort = null; 1314 } 1315 } 1316 super.layoutMinorAxis(targetSpan, axis, offsets, spans); 1317 } 1318 1319 public void setParent(View parent) { 1320 //if parent == null unregister component listener 1321 if (parent == null) { 1322 if (cachedViewPort != null) { 1323 Object cachedObject; 1324 if ((cachedObject = cachedViewPort.get()) != null) { 1325 ((JComponent)cachedObject).removeComponentListener(this); 1326 } 1327 cachedViewPort = null; 1328 } 1329 } 1330 super.setParent(parent); 1331 } 1332 1333 public void componentResized(ComponentEvent e) { 1334 if ( !(e.getSource() instanceof JViewport) ) { 1335 return; 1336 } 1337 JViewport viewPort = (JViewport)e.getSource(); 1338 if (componentVisibleWidth != viewPort.getExtentSize().width) { 1339 Document doc = getDocument(); 1340 if (doc instanceof AbstractDocument) { 1341 AbstractDocument document = (AbstractDocument)getDocument(); 1342 document.readLock(); 1343 try { 1344 layoutChanged(X_AXIS); 1345 preferenceChanged(null, true, true); 1346 } finally { 1347 document.readUnlock(); 1348 } 1349 1350 } 1351 } 1352 } 1353 public void componentHidden(ComponentEvent e) { 1354 } 1355 public void componentMoved(ComponentEvent e) { 1356 } 1357 public void componentShown(ComponentEvent e) { 1358 } 1359 /* 1360 * we keep weak reference to viewPort if and only if BodyBoxView is listening for ComponentEvents 1361 * only in that case cachedViewPort is not equal to null. 1362 * we need to keep this reference in order to remove BodyBoxView from viewPort listeners. 1363 * 1364 */ 1365 private Reference<JViewport> cachedViewPort = null; 1366 private boolean isListening = false; 1367 private int viewVisibleWidth = Integer.MAX_VALUE; 1368 private int componentVisibleWidth = Integer.MAX_VALUE; 1369 } 1370 1371 } 1372 1373 // --- Action implementations ------------------------------ 1374 1375 /** The bold action identifier 1376 */ 1377 public static final String BOLD_ACTION = "html-bold-action"; 1378 /** The italic action identifier 1379 */ 1380 public static final String ITALIC_ACTION = "html-italic-action"; 1381 /** The paragraph left indent action identifier 1382 */ 1383 public static final String PARA_INDENT_LEFT = "html-para-indent-left"; 1384 /** The paragraph right indent action identifier 1385 */ 1386 public static final String PARA_INDENT_RIGHT = "html-para-indent-right"; 1387 /** The font size increase to next value action identifier 1388 */ 1389 public static final String FONT_CHANGE_BIGGER = "html-font-bigger"; 1390 /** The font size decrease to next value action identifier 1391 */ 1392 public static final String FONT_CHANGE_SMALLER = "html-font-smaller"; 1393 /** The Color choice action identifier 1394 The color is passed as an argument 1395 */ 1396 public static final String COLOR_ACTION = "html-color-action"; 1397 /** The logical style choice action identifier 1398 The logical style is passed in as an argument 1399 */ 1400 public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action"; 1401 /** 1402 * Align images at the top. 1403 */ 1404 public static final String IMG_ALIGN_TOP = "html-image-align-top"; 1405 1406 /** 1407 * Align images in the middle. 1408 */ 1409 public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle"; 1410 1411 /** 1412 * Align images at the bottom. 1413 */ 1414 public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom"; 1415 1416 /** 1417 * Align images at the border. 1418 */ 1419 public static final String IMG_BORDER = "html-image-border"; 1420 1421 1422 /** HTML used when inserting tables. */ 1423 private static final String INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>"; 1424 1425 /** HTML used when inserting unordered lists. */ 1426 private static final String INSERT_UL_HTML = "<ul><li></li></ul>"; 1427 1428 /** HTML used when inserting ordered lists. */ 1429 private static final String INSERT_OL_HTML = "<ol><li></li></ol>"; 1430 1431 /** HTML used when inserting hr. */ 1432 private static final String INSERT_HR_HTML = "<hr>"; 1433 1434 /** HTML used when inserting pre. */ 1435 private static final String INSERT_PRE_HTML = "<pre></pre>"; 1436 1437 private static final NavigateLinkAction nextLinkAction = 1438 new NavigateLinkAction("next-link-action"); 1439 1440 private static final NavigateLinkAction previousLinkAction = 1441 new NavigateLinkAction("previous-link-action"); 1442 1443 private static final ActivateLinkAction activateLinkAction = 1444 new ActivateLinkAction("activate-link-action"); 1445 1446 private static final Action[] defaultActions = { 1447 new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML, 1448 HTML.Tag.BODY, HTML.Tag.TABLE), 1449 new InsertHTMLTextAction("InsertTableRow", INSERT_TABLE_HTML, 1450 HTML.Tag.TABLE, HTML.Tag.TR, 1451 HTML.Tag.BODY, HTML.Tag.TABLE), 1452 new InsertHTMLTextAction("InsertTableDataCell", INSERT_TABLE_HTML, 1453 HTML.Tag.TR, HTML.Tag.TD, 1454 HTML.Tag.BODY, HTML.Tag.TABLE), 1455 new InsertHTMLTextAction("InsertUnorderedList", INSERT_UL_HTML, 1456 HTML.Tag.BODY, HTML.Tag.UL), 1457 new InsertHTMLTextAction("InsertUnorderedListItem", INSERT_UL_HTML, 1458 HTML.Tag.UL, HTML.Tag.LI, 1459 HTML.Tag.BODY, HTML.Tag.UL), 1460 new InsertHTMLTextAction("InsertOrderedList", INSERT_OL_HTML, 1461 HTML.Tag.BODY, HTML.Tag.OL), 1462 new InsertHTMLTextAction("InsertOrderedListItem", INSERT_OL_HTML, 1463 HTML.Tag.OL, HTML.Tag.LI, 1464 HTML.Tag.BODY, HTML.Tag.OL), 1465 new InsertHRAction(), 1466 new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML, 1467 HTML.Tag.BODY, HTML.Tag.PRE), 1468 nextLinkAction, previousLinkAction, activateLinkAction, 1469 1470 new BeginAction(beginAction, false), 1471 new BeginAction(selectionBeginAction, true) 1472 }; 1473 1474 // link navigation support 1475 private boolean foundLink = false; 1476 private int prevHypertextOffset = -1; 1477 private Object linkNavigationTag; 1478 1479 1480 /** 1481 * An abstract Action providing some convenience methods that may 1482 * be useful in inserting HTML into an existing document. 1483 * <p>NOTE: None of the convenience methods obtain a lock on the 1484 * document. If you have another thread modifying the text these 1485 * methods may have inconsistent behavior, or return the wrong thing. 1486 */ 1487 @SuppressWarnings("serial") // Superclass is not serializable across versions 1488 public static abstract class HTMLTextAction extends StyledTextAction { 1489 public HTMLTextAction(String name) { 1490 super(name); 1491 } 1492 1493 /** 1494 * @return HTMLDocument of <code>e</code>. 1495 */ 1496 protected HTMLDocument getHTMLDocument(JEditorPane e) { 1497 Document d = e.getDocument(); 1498 if (d instanceof HTMLDocument) { 1499 return (HTMLDocument) d; 1500 } 1501 throw new IllegalArgumentException("document must be HTMLDocument"); 1502 } 1503 1504 /** 1505 * @return HTMLEditorKit for <code>e</code>. 1506 */ 1507 protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) { 1508 EditorKit k = e.getEditorKit(); 1509 if (k instanceof HTMLEditorKit) { 1510 return (HTMLEditorKit) k; 1511 } 1512 throw new IllegalArgumentException("EditorKit must be HTMLEditorKit"); 1513 } 1514 1515 /** 1516 * Returns an array of the Elements that contain <code>offset</code>. 1517 * The first elements corresponds to the root. 1518 */ 1519 protected Element[] getElementsAt(HTMLDocument doc, int offset) { 1520 return getElementsAt(doc.getDefaultRootElement(), offset, 0); 1521 } 1522 1523 /** 1524 * Recursive method used by getElementsAt. 1525 */ 1526 private Element[] getElementsAt(Element parent, int offset, 1527 int depth) { 1528 if (parent.isLeaf()) { 1529 Element[] retValue = new Element[depth + 1]; 1530 retValue[depth] = parent; 1531 return retValue; 1532 } 1533 Element[] retValue = getElementsAt(parent.getElement 1534 (parent.getElementIndex(offset)), offset, depth + 1); 1535 retValue[depth] = parent; 1536 return retValue; 1537 } 1538 1539 /** 1540 * Returns number of elements, starting at the deepest leaf, needed 1541 * to get to an element representing <code>tag</code>. This will 1542 * return -1 if no elements is found representing <code>tag</code>, 1543 * or 0 if the parent of the leaf at <code>offset</code> represents 1544 * <code>tag</code>. 1545 */ 1546 protected int elementCountToTag(HTMLDocument doc, int offset, 1547 HTML.Tag tag) { 1548 int depth = -1; 1549 Element e = doc.getCharacterElement(offset); 1550 while (e != null && e.getAttributes().getAttribute 1551 (StyleConstants.NameAttribute) != tag) { 1552 e = e.getParentElement(); 1553 depth++; 1554 } 1555 if (e == null) { 1556 return -1; 1557 } 1558 return depth; 1559 } 1560 1561 /** 1562 * Returns the deepest element at <code>offset</code> matching 1563 * <code>tag</code>. 1564 */ 1565 protected Element findElementMatchingTag(HTMLDocument doc, int offset, 1566 HTML.Tag tag) { 1567 Element e = doc.getDefaultRootElement(); 1568 Element lastMatch = null; 1569 while (e != null) { 1570 if (e.getAttributes().getAttribute 1571 (StyleConstants.NameAttribute) == tag) { 1572 lastMatch = e; 1573 } 1574 e = e.getElement(e.getElementIndex(offset)); 1575 } 1576 return lastMatch; 1577 } 1578 } 1579 1580 1581 /** 1582 * InsertHTMLTextAction can be used to insert an arbitrary string of HTML 1583 * into an existing HTML document. At least two HTML.Tags need to be 1584 * supplied. The first Tag, parentTag, identifies the parent in 1585 * the document to add the elements to. The second tag, addTag, 1586 * identifies the first tag that should be added to the document as 1587 * seen in the HTML string. One important thing to remember, is that 1588 * the parser is going to generate all the appropriate tags, even if 1589 * they aren't in the HTML string passed in.<p> 1590 * For example, lets say you wanted to create an action to insert 1591 * a table into the body. The parentTag would be HTML.Tag.BODY, 1592 * addTag would be HTML.Tag.TABLE, and the string could be something 1593 * like <table><tr><td></td></tr></table>. 1594 * <p>There is also an option to supply an alternate parentTag and 1595 * addTag. These will be checked for if there is no parentTag at 1596 * offset. 1597 */ 1598 @SuppressWarnings("serial") // Superclass is not serializable across versions 1599 public static class InsertHTMLTextAction extends HTMLTextAction { 1600 public InsertHTMLTextAction(String name, String html, 1601 HTML.Tag parentTag, HTML.Tag addTag) { 1602 this(name, html, parentTag, addTag, null, null); 1603 } 1604 1605 public InsertHTMLTextAction(String name, String html, 1606 HTML.Tag parentTag, 1607 HTML.Tag addTag, 1608 HTML.Tag alternateParentTag, 1609 HTML.Tag alternateAddTag) { 1610 this(name, html, parentTag, addTag, alternateParentTag, 1611 alternateAddTag, true); 1612 } 1613 1614 /* public */ 1615 InsertHTMLTextAction(String name, String html, 1616 HTML.Tag parentTag, 1617 HTML.Tag addTag, 1618 HTML.Tag alternateParentTag, 1619 HTML.Tag alternateAddTag, 1620 boolean adjustSelection) { 1621 super(name); 1622 this.html = html; 1623 this.parentTag = parentTag; 1624 this.addTag = addTag; 1625 this.alternateParentTag = alternateParentTag; 1626 this.alternateAddTag = alternateAddTag; 1627 this.adjustSelection = adjustSelection; 1628 } 1629 1630 /** 1631 * A cover for HTMLEditorKit.insertHTML. If an exception it 1632 * thrown it is wrapped in a RuntimeException and thrown. 1633 */ 1634 protected void insertHTML(JEditorPane editor, HTMLDocument doc, 1635 int offset, String html, int popDepth, 1636 int pushDepth, HTML.Tag addTag) { 1637 try { 1638 getHTMLEditorKit(editor).insertHTML(doc, offset, html, 1639 popDepth, pushDepth, 1640 addTag); 1641 } catch (IOException ioe) { 1642 throw new RuntimeException("Unable to insert: " + ioe); 1643 } catch (BadLocationException ble) { 1644 throw new RuntimeException("Unable to insert: " + ble); 1645 } 1646 } 1647 1648 /** 1649 * This is invoked when inserting at a boundary. It determines 1650 * the number of pops, and then the number of pushes that need 1651 * to be performed, and then invokes insertHTML. 1652 * @since 1.3 1653 */ 1654 protected void insertAtBoundary(JEditorPane editor, HTMLDocument doc, 1655 int offset, Element insertElement, 1656 String html, HTML.Tag parentTag, 1657 HTML.Tag addTag) { 1658 insertAtBoundry(editor, doc, offset, insertElement, html, 1659 parentTag, addTag); 1660 } 1661 1662 /** 1663 * This is invoked when inserting at a boundary. It determines 1664 * the number of pops, and then the number of pushes that need 1665 * to be performed, and then invokes insertHTML. 1666 * @deprecated As of Java 2 platform v1.3, use insertAtBoundary 1667 */ 1668 @Deprecated 1669 protected void insertAtBoundry(JEditorPane editor, HTMLDocument doc, 1670 int offset, Element insertElement, 1671 String html, HTML.Tag parentTag, 1672 HTML.Tag addTag) { 1673 // Find the common parent. 1674 Element e; 1675 Element commonParent; 1676 boolean isFirst = (offset == 0); 1677 1678 if (offset > 0 || insertElement == null) { 1679 e = doc.getDefaultRootElement(); 1680 while (e != null && e.getStartOffset() != offset && 1681 !e.isLeaf()) { 1682 e = e.getElement(e.getElementIndex(offset)); 1683 } 1684 commonParent = (e != null) ? e.getParentElement() : null; 1685 } 1686 else { 1687 // If inserting at the origin, the common parent is the 1688 // insertElement. 1689 commonParent = insertElement; 1690 } 1691 if (commonParent != null) { 1692 // Determine how many pops to do. 1693 int pops = 0; 1694 int pushes = 0; 1695 if (isFirst && insertElement != null) { 1696 e = commonParent; 1697 while (e != null && !e.isLeaf()) { 1698 e = e.getElement(e.getElementIndex(offset)); 1699 pops++; 1700 } 1701 } 1702 else { 1703 e = commonParent; 1704 offset--; 1705 while (e != null && !e.isLeaf()) { 1706 e = e.getElement(e.getElementIndex(offset)); 1707 pops++; 1708 } 1709 1710 // And how many pushes 1711 e = commonParent; 1712 offset++; 1713 while (e != null && e != insertElement) { 1714 e = e.getElement(e.getElementIndex(offset)); 1715 pushes++; 1716 } 1717 } 1718 pops = Math.max(0, pops - 1); 1719 1720 // And insert! 1721 insertHTML(editor, doc, offset, html, pops, pushes, addTag); 1722 } 1723 } 1724 1725 /** 1726 * If there is an Element with name <code>tag</code> at 1727 * <code>offset</code>, this will invoke either insertAtBoundary 1728 * or <code>insertHTML</code>. This returns true if there is 1729 * a match, and one of the inserts is invoked. 1730 */ 1731 /*protected*/ 1732 boolean insertIntoTag(JEditorPane editor, HTMLDocument doc, 1733 int offset, HTML.Tag tag, HTML.Tag addTag) { 1734 Element e = findElementMatchingTag(doc, offset, tag); 1735 if (e != null && e.getStartOffset() == offset) { 1736 insertAtBoundary(editor, doc, offset, e, html, 1737 tag, addTag); 1738 return true; 1739 } 1740 else if (offset > 0) { 1741 int depth = elementCountToTag(doc, offset - 1, tag); 1742 if (depth != -1) { 1743 insertHTML(editor, doc, offset, html, depth, 0, addTag); 1744 return true; 1745 } 1746 } 1747 return false; 1748 } 1749 1750 /** 1751 * Called after an insertion to adjust the selection. 1752 */ 1753 /* protected */ 1754 void adjustSelection(JEditorPane pane, HTMLDocument doc, 1755 int startOffset, int oldLength) { 1756 int newLength = doc.getLength(); 1757 if (newLength != oldLength && startOffset < newLength) { 1758 if (startOffset > 0) { 1759 String text; 1760 try { 1761 text = doc.getText(startOffset - 1, 1); 1762 } catch (BadLocationException ble) { 1763 text = null; 1764 } 1765 if (text != null && text.length() > 0 && 1766 text.charAt(0) == '\n') { 1767 pane.select(startOffset, startOffset); 1768 } 1769 else { 1770 pane.select(startOffset + 1, startOffset + 1); 1771 } 1772 } 1773 else { 1774 pane.select(1, 1); 1775 } 1776 } 1777 } 1778 1779 /** 1780 * Inserts the HTML into the document. 1781 * 1782 * @param ae the event 1783 */ 1784 public void actionPerformed(ActionEvent ae) { 1785 JEditorPane editor = getEditor(ae); 1786 if (editor != null) { 1787 HTMLDocument doc = getHTMLDocument(editor); 1788 int offset = editor.getSelectionStart(); 1789 int length = doc.getLength(); 1790 boolean inserted; 1791 // Try first choice 1792 if (!insertIntoTag(editor, doc, offset, parentTag, addTag) && 1793 alternateParentTag != null) { 1794 // Then alternate. 1795 inserted = insertIntoTag(editor, doc, offset, 1796 alternateParentTag, 1797 alternateAddTag); 1798 } 1799 else { 1800 inserted = true; 1801 } 1802 if (adjustSelection && inserted) { 1803 adjustSelection(editor, doc, offset, length); 1804 } 1805 } 1806 } 1807 1808 /** HTML to insert. */ 1809 protected String html; 1810 /** Tag to check for in the document. */ 1811 protected HTML.Tag parentTag; 1812 /** Tag in HTML to start adding tags from. */ 1813 protected HTML.Tag addTag; 1814 /** Alternate Tag to check for in the document if parentTag is 1815 * not found. */ 1816 protected HTML.Tag alternateParentTag; 1817 /** Alternate tag in HTML to start adding tags from if parentTag 1818 * is not found and alternateParentTag is found. */ 1819 protected HTML.Tag alternateAddTag; 1820 /** True indicates the selection should be adjusted after an insert. */ 1821 boolean adjustSelection; 1822 } 1823 1824 1825 /** 1826 * InsertHRAction is special, at actionPerformed time it will determine 1827 * the parent HTML.Tag based on the paragraph element at the selection 1828 * start. 1829 */ 1830 @SuppressWarnings("serial") // Superclass is not serializable across versions 1831 static class InsertHRAction extends InsertHTMLTextAction { 1832 InsertHRAction() { 1833 super("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null, null, 1834 false); 1835 } 1836 1837 /** 1838 * Inserts the HTML into the document. 1839 * 1840 * @param ae the event 1841 */ 1842 public void actionPerformed(ActionEvent ae) { 1843 JEditorPane editor = getEditor(ae); 1844 if (editor != null) { 1845 HTMLDocument doc = getHTMLDocument(editor); 1846 int offset = editor.getSelectionStart(); 1847 Element paragraph = doc.getParagraphElement(offset); 1848 if (paragraph.getParentElement() != null) { 1849 parentTag = (HTML.Tag)paragraph.getParentElement(). 1850 getAttributes().getAttribute 1851 (StyleConstants.NameAttribute); 1852 super.actionPerformed(ae); 1853 } 1854 } 1855 } 1856 1857 } 1858 1859 /* 1860 * Returns the object in an AttributeSet matching a key 1861 */ 1862 static private Object getAttrValue(AttributeSet attr, HTML.Attribute key) { 1863 Enumeration names = attr.getAttributeNames(); 1864 while (names.hasMoreElements()) { 1865 Object nextKey = names.nextElement(); 1866 Object nextVal = attr.getAttribute(nextKey); 1867 if (nextVal instanceof AttributeSet) { 1868 Object value = getAttrValue((AttributeSet)nextVal, key); 1869 if (value != null) { 1870 return value; 1871 } 1872 } else if (nextKey == key) { 1873 return nextVal; 1874 } 1875 } 1876 return null; 1877 } 1878 1879 /* 1880 * Action to move the focus on the next or previous hypertext link 1881 * or object. TODO: This method relies on support from the 1882 * javax.accessibility package. The text package should support 1883 * keyboard navigation of text elements directly. 1884 */ 1885 @SuppressWarnings("serial") // Superclass is not serializable across versions 1886 static class NavigateLinkAction extends TextAction implements CaretListener { 1887 1888 private static final FocusHighlightPainter focusPainter = 1889 new FocusHighlightPainter(null); 1890 private final boolean focusBack; 1891 1892 /* 1893 * Create this action with the appropriate identifier. 1894 */ 1895 public NavigateLinkAction(String actionName) { 1896 super(actionName); 1897 focusBack = "previous-link-action".equals(actionName); 1898 } 1899 1900 /** 1901 * Called when the caret position is updated. 1902 * 1903 * @param e the caret event 1904 */ 1905 public void caretUpdate(CaretEvent e) { 1906 Object src = e.getSource(); 1907 if (src instanceof JTextComponent) { 1908 JTextComponent comp = (JTextComponent) src; 1909 HTMLEditorKit kit = getHTMLEditorKit(comp); 1910 if (kit != null && kit.foundLink) { 1911 kit.foundLink = false; 1912 // TODO: The AccessibleContext for the editor should register 1913 // as a listener for CaretEvents and forward the events to 1914 // assistive technologies listening for such events. 1915 comp.getAccessibleContext().firePropertyChange( 1916 AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET, 1917 Integer.valueOf(kit.prevHypertextOffset), 1918 Integer.valueOf(e.getDot())); 1919 } 1920 } 1921 } 1922 1923 /* 1924 * The operation to perform when this action is triggered. 1925 */ 1926 public void actionPerformed(ActionEvent e) { 1927 JTextComponent comp = getTextComponent(e); 1928 if (comp == null || comp.isEditable()) { 1929 return; 1930 } 1931 1932 Document doc = comp.getDocument(); 1933 HTMLEditorKit kit = getHTMLEditorKit(comp); 1934 if (doc == null || kit == null) { 1935 return; 1936 } 1937 1938 // TODO: Should start successive iterations from the 1939 // current caret position. 1940 ElementIterator ei = new ElementIterator(doc); 1941 int currentOffset = comp.getCaretPosition(); 1942 int prevStartOffset = -1; 1943 int prevEndOffset = -1; 1944 1945 // highlight the next link or object after the current caret position 1946 Element nextElement; 1947 while ((nextElement = ei.next()) != null) { 1948 String name = nextElement.getName(); 1949 AttributeSet attr = nextElement.getAttributes(); 1950 1951 Object href = getAttrValue(attr, HTML.Attribute.HREF); 1952 if (!(name.equals(HTML.Tag.OBJECT.toString())) && href == null) { 1953 continue; 1954 } 1955 1956 int elementOffset = nextElement.getStartOffset(); 1957 if (focusBack) { 1958 if (elementOffset >= currentOffset && 1959 prevStartOffset >= 0) { 1960 1961 kit.foundLink = true; 1962 comp.setCaretPosition(prevStartOffset); 1963 moveCaretPosition(comp, kit, prevStartOffset, 1964 prevEndOffset); 1965 kit.prevHypertextOffset = prevStartOffset; 1966 return; 1967 } 1968 } else { // focus forward 1969 if (elementOffset > currentOffset) { 1970 1971 kit.foundLink = true; 1972 comp.setCaretPosition(elementOffset); 1973 moveCaretPosition(comp, kit, elementOffset, 1974 nextElement.getEndOffset()); 1975 kit.prevHypertextOffset = elementOffset; 1976 return; 1977 } 1978 } 1979 prevStartOffset = nextElement.getStartOffset(); 1980 prevEndOffset = nextElement.getEndOffset(); 1981 } 1982 if (focusBack && prevStartOffset >= 0) { 1983 kit.foundLink = true; 1984 comp.setCaretPosition(prevStartOffset); 1985 moveCaretPosition(comp, kit, prevStartOffset, prevEndOffset); 1986 kit.prevHypertextOffset = prevStartOffset; 1987 } 1988 } 1989 1990 /* 1991 * Moves the caret from mark to dot 1992 */ 1993 private void moveCaretPosition(JTextComponent comp, HTMLEditorKit kit, 1994 int mark, int dot) { 1995 Highlighter h = comp.getHighlighter(); 1996 if (h != null) { 1997 int p0 = Math.min(dot, mark); 1998 int p1 = Math.max(dot, mark); 1999 try { 2000 if (kit.linkNavigationTag != null) { 2001 h.changeHighlight(kit.linkNavigationTag, p0, p1); 2002 } else { 2003 kit.linkNavigationTag = 2004 h.addHighlight(p0, p1, focusPainter); 2005 } 2006 } catch (BadLocationException e) { 2007 } 2008 } 2009 } 2010 2011 private HTMLEditorKit getHTMLEditorKit(JTextComponent comp) { 2012 if (comp instanceof JEditorPane) { 2013 EditorKit kit = ((JEditorPane) comp).getEditorKit(); 2014 if (kit instanceof HTMLEditorKit) { 2015 return (HTMLEditorKit) kit; 2016 } 2017 } 2018 return null; 2019 } 2020 2021 /** 2022 * A highlight painter that draws a one-pixel border around 2023 * the highlighted area. 2024 */ 2025 static class FocusHighlightPainter extends 2026 DefaultHighlighter.DefaultHighlightPainter { 2027 2028 FocusHighlightPainter(Color color) { 2029 super(color); 2030 } 2031 2032 /** 2033 * Paints a portion of a highlight. 2034 * 2035 * @param g the graphics context 2036 * @param offs0 the starting model offset ≥ 0 2037 * @param offs1 the ending model offset ≥ offs1 2038 * @param bounds the bounding box of the view, which is not 2039 * necessarily the region to paint. 2040 * @param c the editor 2041 * @param view View painting for 2042 * @return region in which drawing occurred 2043 */ 2044 public Shape paintLayer(Graphics g, int offs0, int offs1, 2045 Shape bounds, JTextComponent c, View view) { 2046 2047 Color color = getColor(); 2048 2049 if (color == null) { 2050 g.setColor(c.getSelectionColor()); 2051 } 2052 else { 2053 g.setColor(color); 2054 } 2055 if (offs0 == view.getStartOffset() && 2056 offs1 == view.getEndOffset()) { 2057 // Contained in view, can just use bounds. 2058 Rectangle alloc; 2059 if (bounds instanceof Rectangle) { 2060 alloc = (Rectangle)bounds; 2061 } 2062 else { 2063 alloc = bounds.getBounds(); 2064 } 2065 g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height); 2066 return alloc; 2067 } 2068 else { 2069 // Should only render part of View. 2070 try { 2071 // --- determine locations --- 2072 Shape shape = view.modelToView(offs0, Position.Bias.Forward, 2073 offs1,Position.Bias.Backward, 2074 bounds); 2075 Rectangle r = (shape instanceof Rectangle) ? 2076 (Rectangle)shape : shape.getBounds(); 2077 g.drawRect(r.x, r.y, r.width - 1, r.height); 2078 return r; 2079 } catch (BadLocationException e) { 2080 // can't render 2081 } 2082 } 2083 // Only if exception 2084 return null; 2085 } 2086 } 2087 } 2088 2089 /* 2090 * Action to activate the hypertext link that has focus. 2091 * TODO: This method relies on support from the 2092 * javax.accessibility package. The text package should support 2093 * keyboard navigation of text elements directly. 2094 */ 2095 @SuppressWarnings("serial") // Superclass is not serializable across versions 2096 static class ActivateLinkAction extends TextAction { 2097 2098 /** 2099 * Create this action with the appropriate identifier. 2100 */ 2101 public ActivateLinkAction(String actionName) { 2102 super(actionName); 2103 } 2104 2105 /* 2106 * activates the hyperlink at offset 2107 */ 2108 private void activateLink(String href, HTMLDocument doc, 2109 JEditorPane editor, int offset) { 2110 try { 2111 URL page = 2112 (URL)doc.getProperty(Document.StreamDescriptionProperty); 2113 URL url = new URL(page, href); 2114 HyperlinkEvent linkEvent = new HyperlinkEvent 2115 (editor, HyperlinkEvent.EventType. 2116 ACTIVATED, url, url.toExternalForm(), 2117 doc.getCharacterElement(offset)); 2118 editor.fireHyperlinkUpdate(linkEvent); 2119 } catch (MalformedURLException m) { 2120 } 2121 } 2122 2123 /* 2124 * Invokes default action on the object in an element 2125 */ 2126 private void doObjectAction(JEditorPane editor, Element elem) { 2127 View view = getView(editor, elem); 2128 if (view != null && view instanceof ObjectView) { 2129 Component comp = ((ObjectView)view).getComponent(); 2130 if (comp != null && comp instanceof Accessible) { 2131 AccessibleContext ac = comp.getAccessibleContext(); 2132 if (ac != null) { 2133 AccessibleAction aa = ac.getAccessibleAction(); 2134 if (aa != null) { 2135 aa.doAccessibleAction(0); 2136 } 2137 } 2138 } 2139 } 2140 } 2141 2142 /* 2143 * Returns the root view for a document 2144 */ 2145 private View getRootView(JEditorPane editor) { 2146 return editor.getUI().getRootView(editor); 2147 } 2148 2149 /* 2150 * Returns a view associated with an element 2151 */ 2152 private View getView(JEditorPane editor, Element elem) { 2153 Object lock = lock(editor); 2154 try { 2155 View rootView = getRootView(editor); 2156 int start = elem.getStartOffset(); 2157 if (rootView != null) { 2158 return getView(rootView, elem, start); 2159 } 2160 return null; 2161 } finally { 2162 unlock(lock); 2163 } 2164 } 2165 2166 private View getView(View parent, Element elem, int start) { 2167 if (parent.getElement() == elem) { 2168 return parent; 2169 } 2170 int index = parent.getViewIndex(start, Position.Bias.Forward); 2171 2172 if (index != -1 && index < parent.getViewCount()) { 2173 return getView(parent.getView(index), elem, start); 2174 } 2175 return null; 2176 } 2177 2178 /* 2179 * If possible acquires a lock on the Document. If a lock has been 2180 * obtained a key will be retured that should be passed to 2181 * <code>unlock</code>. 2182 */ 2183 private Object lock(JEditorPane editor) { 2184 Document document = editor.getDocument(); 2185 2186 if (document instanceof AbstractDocument) { 2187 ((AbstractDocument)document).readLock(); 2188 return document; 2189 } 2190 return null; 2191 } 2192 2193 /* 2194 * Releases a lock previously obtained via <code>lock</code>. 2195 */ 2196 private void unlock(Object key) { 2197 if (key != null) { 2198 ((AbstractDocument)key).readUnlock(); 2199 } 2200 } 2201 2202 /* 2203 * The operation to perform when this action is triggered. 2204 */ 2205 public void actionPerformed(ActionEvent e) { 2206 2207 JTextComponent c = getTextComponent(e); 2208 if (c.isEditable() || !(c instanceof JEditorPane)) { 2209 return; 2210 } 2211 JEditorPane editor = (JEditorPane)c; 2212 2213 Document d = editor.getDocument(); 2214 if (d == null || !(d instanceof HTMLDocument)) { 2215 return; 2216 } 2217 HTMLDocument doc = (HTMLDocument)d; 2218 2219 ElementIterator ei = new ElementIterator(doc); 2220 int currentOffset = editor.getCaretPosition(); 2221 2222 // invoke the next link or object action 2223 String urlString = null; 2224 String objString = null; 2225 Element currentElement; 2226 while ((currentElement = ei.next()) != null) { 2227 String name = currentElement.getName(); 2228 AttributeSet attr = currentElement.getAttributes(); 2229 2230 Object href = getAttrValue(attr, HTML.Attribute.HREF); 2231 if (href != null) { 2232 if (currentOffset >= currentElement.getStartOffset() && 2233 currentOffset <= currentElement.getEndOffset()) { 2234 2235 activateLink((String)href, doc, editor, currentOffset); 2236 return; 2237 } 2238 } else if (name.equals(HTML.Tag.OBJECT.toString())) { 2239 Object obj = getAttrValue(attr, HTML.Attribute.CLASSID); 2240 if (obj != null) { 2241 if (currentOffset >= currentElement.getStartOffset() && 2242 currentOffset <= currentElement.getEndOffset()) { 2243 2244 doObjectAction(editor, currentElement); 2245 return; 2246 } 2247 } 2248 } 2249 } 2250 } 2251 } 2252 2253 private static int getBodyElementStart(JTextComponent comp) { 2254 Element rootElement = comp.getDocument().getRootElements()[0]; 2255 for (int i = 0; i < rootElement.getElementCount(); i++) { 2256 Element currElement = rootElement.getElement(i); 2257 if("body".equals(currElement.getName())) { 2258 return currElement.getStartOffset(); 2259 } 2260 } 2261 return 0; 2262 } 2263 2264 /* 2265 * Move the caret to the beginning of the document. 2266 * @see DefaultEditorKit#beginAction 2267 * @see HTMLEditorKit#getActions 2268 */ 2269 @SuppressWarnings("serial") // Superclass is not serializable across versions 2270 static class BeginAction extends TextAction { 2271 2272 /* Create this object with the appropriate identifier. */ 2273 BeginAction(String nm, boolean select) { 2274 super(nm); 2275 this.select = select; 2276 } 2277 2278 /** The operation to perform when this action is triggered. */ 2279 public void actionPerformed(ActionEvent e) { 2280 JTextComponent target = getTextComponent(e); 2281 int bodyStart = getBodyElementStart(target); 2282 2283 if (target != null) { 2284 if (select) { 2285 target.moveCaretPosition(bodyStart); 2286 } else { 2287 target.setCaretPosition(bodyStart); 2288 } 2289 } 2290 } 2291 2292 private boolean select; 2293 } 2294 }