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