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