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