1 /*
   2  * Copyright (c) 1997, 2008, 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;
  26 
  27 import java.awt.*;
  28 import java.awt.event.ActionEvent;
  29 
  30 import java.io.ObjectOutputStream;
  31 import java.io.ObjectInputStream;
  32 import java.io.IOException;
  33 
  34 import javax.swing.text.*;
  35 import javax.swing.event.*;
  36 import javax.swing.plaf.*;
  37 
  38 /**
  39  * A text component that can be marked up with attributes that are
  40  * represented graphically.
  41  * You can find how-to information and examples of using text panes in
  42  * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/text.html">Using Text Components</a>,
  43  * a section in <em>The Java Tutorial.</em>
  44  *
  45  * <p>
  46  * This component models paragraphs
  47  * that are composed of runs of character level attributes.  Each
  48  * paragraph may have a logical style attached to it which contains
  49  * the default attributes to use if not overridden by attributes set
  50  * on the paragraph or character run.  Components and images may
  51  * be embedded in the flow of text.
  52  *
  53  * <dl>
  54  * <dt><b><font size=+1>Newlines</font></b>
  55  * <dd>
  56  * For a discussion on how newlines are handled, see
  57  * <a href="text/DefaultEditorKit.html">DefaultEditorKit</a>.
  58  * </dl>
  59  *
  60  * <p>
  61  * <strong>Warning:</strong> Swing is not thread safe. For more
  62  * information see <a
  63  * href="package-summary.html#threading">Swing's Threading
  64  * Policy</a>.
  65  * <p>
  66  * <strong>Warning:</strong>
  67  * Serialized objects of this class will not be compatible with
  68  * future Swing releases. The current serialization support is
  69  * appropriate for short term storage or RMI between applications running
  70  * the same version of Swing.  As of 1.4, support for long term storage
  71  * of all JavaBeans&trade;
  72  * has been added to the <code>java.beans</code> package.
  73  * Please see {@link java.beans.XMLEncoder}.
  74  *
  75  * @beaninfo
  76  *   attribute: isContainer true
  77  * description: A text component that can be marked up with attributes that are graphically represented.
  78  *
  79  * @author  Timothy Prinzing
  80  * @see javax.swing.text.StyledEditorKit
  81  */
  82 public class JTextPane extends JEditorPane {
  83 
  84     /**
  85      * Creates a new <code>JTextPane</code>.  A new instance of
  86      * <code>StyledEditorKit</code> is
  87      * created and set, and the document model set to <code>null</code>.
  88      */
  89     public JTextPane() {
  90         super();
  91         EditorKit editorKit = createDefaultEditorKit();
  92         String contentType = editorKit.getContentType();
  93         if (contentType != null
  94             && getEditorKitClassNameForContentType(contentType) ==
  95                  defaultEditorKitMap.get(contentType)) {
  96             setEditorKitForContentType(contentType, editorKit);
  97         }
  98         setEditorKit(editorKit);
  99     }
 100 
 101     /**
 102      * Creates a new <code>JTextPane</code>, with a specified document model.
 103      * A new instance of <code>javax.swing.text.StyledEditorKit</code>
 104      *  is created and set.
 105      *
 106      * @param doc the document model
 107      */
 108     public JTextPane(StyledDocument doc) {
 109         this();
 110         setStyledDocument(doc);
 111     }
 112 
 113     /**
 114      * Returns the class ID for the UI.
 115      *
 116      * @return the string "TextPaneUI"
 117      *
 118      * @see JComponent#getUIClassID
 119      * @see UIDefaults#getUI
 120      */
 121     public String getUIClassID() {
 122         return uiClassID;
 123     }
 124 
 125     /**
 126      * Associates the editor with a text document.  This
 127      * must be a <code>StyledDocument</code>.
 128      *
 129      * @param doc  the document to display/edit
 130      * @exception IllegalArgumentException  if <code>doc</code> can't
 131      *   be narrowed to a <code>StyledDocument</code> which is the
 132      *   required type of model for this text component
 133      */
 134     public void setDocument(Document doc) {
 135         if (doc instanceof StyledDocument) {
 136             super.setDocument(doc);
 137         } else {
 138             throw new IllegalArgumentException("Model must be StyledDocument");
 139         }
 140     }
 141 
 142     /**
 143      * Associates the editor with a text document.
 144      * The currently registered factory is used to build a view for
 145      * the document, which gets displayed by the editor.
 146      *
 147      * @param doc  the document to display/edit
 148      */
 149     public void setStyledDocument(StyledDocument doc) {
 150         super.setDocument(doc);
 151     }
 152 
 153     /**
 154      * Fetches the model associated with the editor.
 155      *
 156      * @return the model
 157      */
 158     public StyledDocument getStyledDocument() {
 159         return (StyledDocument) getDocument();
 160     }
 161 
 162     /**
 163      * Replaces the currently selected content with new content
 164      * represented by the given string.  If there is no selection
 165      * this amounts to an insert of the given text.  If there
 166      * is no replacement text this amounts to a removal of the
 167      * current selection.  The replacement text will have the
 168      * attributes currently defined for input at the point of
 169      * insertion.  If the document is not editable, beep and return.
 170      *
 171      * @param content  the content to replace the selection with
 172      */
 173     @Override
 174     public void replaceSelection(String content) {
 175         replaceSelection(content, true);
 176     }
 177 
 178     private void replaceSelection(String content, boolean checkEditable) {
 179         if (checkEditable && !isEditable()) {
 180             UIManager.getLookAndFeel().provideErrorFeedback(JTextPane.this);
 181             return;
 182         }
 183         Document doc = getStyledDocument();
 184         if (doc != null) {
 185             try {
 186                 Caret caret = getCaret();
 187                 boolean composedTextSaved = saveComposedText(caret.getDot());
 188                 int p0 = Math.min(caret.getDot(), caret.getMark());
 189                 int p1 = Math.max(caret.getDot(), caret.getMark());
 190                 AttributeSet attr = getInputAttributes().copyAttributes();
 191                 if (doc instanceof AbstractDocument) {
 192                     ((AbstractDocument)doc).replace(p0, p1 - p0, content,attr);
 193                 }
 194                 else {
 195                     if (p0 != p1) {
 196                         doc.remove(p0, p1 - p0);
 197                     }
 198                     if (content != null && content.length() > 0) {
 199                         doc.insertString(p0, content, attr);
 200                     }
 201                 }
 202                 if (composedTextSaved) {
 203                     restoreComposedText();
 204                 }
 205             } catch (BadLocationException e) {
 206                 UIManager.getLookAndFeel().provideErrorFeedback(JTextPane.this);
 207             }
 208         }
 209     }
 210 
 211     /**
 212      * Inserts a component into the document as a replacement
 213      * for the currently selected content.  If there is no
 214      * selection the component is effectively inserted at the
 215      * current position of the caret.  This is represented in
 216      * the associated document as an attribute of one character
 217      * of content.
 218      * <p>
 219      * The component given is the actual component used by the
 220      * JTextPane.  Since components cannot be a child of more than
 221      * one container, this method should not be used in situations
 222      * where the model is shared by text components.
 223      * <p>
 224      * The component is placed relative to the text baseline
 225      * according to the value returned by
 226      * <code>Component.getAlignmentY</code>.  For Swing components
 227      * this value can be conveniently set using the method
 228      * <code>JComponent.setAlignmentY</code>.  For example, setting
 229      * a value of <code>0.75</code> will cause 75 percent of the
 230      * component to be above the baseline, and 25 percent of the
 231      * component to be below the baseline.
 232      *
 233      * @param c    the component to insert
 234      */
 235     public void insertComponent(Component c) {
 236         MutableAttributeSet inputAttributes = getInputAttributes();
 237         inputAttributes.removeAttributes(inputAttributes);
 238         StyleConstants.setComponent(inputAttributes, c);
 239         replaceSelection(" ", false);
 240         inputAttributes.removeAttributes(inputAttributes);
 241     }
 242 
 243     /**
 244      * Inserts an icon into the document as a replacement
 245      * for the currently selected content.  If there is no
 246      * selection the icon is effectively inserted at the
 247      * current position of the caret.  This is represented in
 248      * the associated document as an attribute of one character
 249      * of content.
 250      *
 251      * @param g    the icon to insert
 252      * @see Icon
 253      */
 254     public void insertIcon(Icon g) {
 255         MutableAttributeSet inputAttributes = getInputAttributes();
 256         inputAttributes.removeAttributes(inputAttributes);
 257         StyleConstants.setIcon(inputAttributes, g);
 258         replaceSelection(" ", false);
 259         inputAttributes.removeAttributes(inputAttributes);
 260     }
 261 
 262     /**
 263      * Adds a new style into the logical style hierarchy.  Style attributes
 264      * resolve from bottom up so an attribute specified in a child
 265      * will override an attribute specified in the parent.
 266      *
 267      * @param nm   the name of the style (must be unique within the
 268      *   collection of named styles).  The name may be <code>null</code>
 269      *   if the style is unnamed, but the caller is responsible
 270      *   for managing the reference returned as an unnamed style can't
 271      *   be fetched by name.  An unnamed style may be useful for things
 272      *   like character attribute overrides such as found in a style
 273      *   run.
 274      * @param parent the parent style.  This may be <code>null</code>
 275      *   if unspecified
 276      *   attributes need not be resolved in some other style.
 277      * @return the new <code>Style</code>
 278      */
 279     public Style addStyle(String nm, Style parent) {
 280         StyledDocument doc = getStyledDocument();
 281         return doc.addStyle(nm, parent);
 282     }
 283 
 284     /**
 285      * Removes a named non-<code>null</code> style previously added to
 286      * the document.
 287      *
 288      * @param nm  the name of the style to remove
 289      */
 290     public void removeStyle(String nm) {
 291         StyledDocument doc = getStyledDocument();
 292         doc.removeStyle(nm);
 293     }
 294 
 295     /**
 296      * Fetches a named non-<code>null</code> style previously added.
 297      *
 298      * @param nm  the name of the style
 299      * @return the <code>Style</code>
 300      */
 301     public Style getStyle(String nm) {
 302         StyledDocument doc = getStyledDocument();
 303         return doc.getStyle(nm);
 304     }
 305 
 306     /**
 307      * Sets the logical style to use for the paragraph at the
 308      * current caret position.  If attributes aren't explicitly set
 309      * for character and paragraph attributes they will resolve
 310      * through the logical style assigned to the paragraph, which
 311      * in term may resolve through some hierarchy completely
 312      * independent of the element hierarchy in the document.
 313      *
 314      * @param s  the logical style to assign to the paragraph,
 315      *          or <code>null</code> for no style
 316      */
 317     public void setLogicalStyle(Style s) {
 318         StyledDocument doc = getStyledDocument();
 319         doc.setLogicalStyle(getCaretPosition(), s);
 320     }
 321 
 322     /**
 323      * Fetches the logical style assigned to the paragraph represented
 324      * by the current position of the caret, or <code>null</code>.
 325      *
 326      * @return the <code>Style</code>
 327      */
 328     public Style getLogicalStyle() {
 329         StyledDocument doc = getStyledDocument();
 330         return doc.getLogicalStyle(getCaretPosition());
 331     }
 332 
 333     /**
 334      * Fetches the character attributes in effect at the
 335      * current location of the caret, or <code>null</code>.
 336      *
 337      * @return the attributes, or <code>null</code>
 338      */
 339     public AttributeSet getCharacterAttributes() {
 340         StyledDocument doc = getStyledDocument();
 341         Element run = doc.getCharacterElement(getCaretPosition());
 342         if (run != null) {
 343             return run.getAttributes();
 344         }
 345         return null;
 346     }
 347 
 348     /**
 349      * Applies the given attributes to character
 350      * content.  If there is a selection, the attributes
 351      * are applied to the selection range.  If there
 352      * is no selection, the attributes are applied to
 353      * the input attribute set which defines the attributes
 354      * for any new text that gets inserted.
 355      *
 356      * @param attr the attributes
 357      * @param replace if true, then replace the existing attributes first
 358      */
 359     public void setCharacterAttributes(AttributeSet attr, boolean replace) {
 360         int p0 = getSelectionStart();
 361         int p1 = getSelectionEnd();
 362         if (p0 != p1) {
 363             StyledDocument doc = getStyledDocument();
 364             doc.setCharacterAttributes(p0, p1 - p0, attr, replace);
 365         } else {
 366             MutableAttributeSet inputAttributes = getInputAttributes();
 367             if (replace) {
 368                 inputAttributes.removeAttributes(inputAttributes);
 369             }
 370             inputAttributes.addAttributes(attr);
 371         }
 372     }
 373 
 374     /**
 375      * Fetches the current paragraph attributes in effect
 376      * at the location of the caret, or <code>null</code> if none.
 377      *
 378      * @return the attributes
 379      */
 380     public AttributeSet getParagraphAttributes() {
 381         StyledDocument doc = getStyledDocument();
 382         Element paragraph = doc.getParagraphElement(getCaretPosition());
 383         if (paragraph != null) {
 384             return paragraph.getAttributes();
 385         }
 386         return null;
 387     }
 388 
 389     /**
 390      * Applies the given attributes to paragraphs.  If
 391      * there is a selection, the attributes are applied
 392      * to the paragraphs that intersect the selection.
 393      * If there is no selection, the attributes are applied
 394      * to the paragraph at the current caret position.
 395      *
 396      * @param attr the non-<code>null</code> attributes
 397      * @param replace if true, replace the existing attributes first
 398      */
 399     public void setParagraphAttributes(AttributeSet attr, boolean replace) {
 400         int p0 = getSelectionStart();
 401         int p1 = getSelectionEnd();
 402         StyledDocument doc = getStyledDocument();
 403         doc.setParagraphAttributes(p0, p1 - p0, attr, replace);
 404     }
 405 
 406     /**
 407      * Gets the input attributes for the pane.
 408      *
 409      * @return the attributes
 410      */
 411     public MutableAttributeSet getInputAttributes() {
 412         return getStyledEditorKit().getInputAttributes();
 413     }
 414 
 415     /**
 416      * Gets the editor kit.
 417      *
 418      * @return the editor kit
 419      */
 420     protected final StyledEditorKit getStyledEditorKit() {
 421         return (StyledEditorKit) getEditorKit();
 422     }
 423 
 424     /**
 425      * @see #getUIClassID
 426      * @see #readObject
 427      */
 428     private static final String uiClassID = "TextPaneUI";
 429 
 430 
 431     /**
 432      * See <code>readObject</code> and <code>writeObject</code> in
 433      * <code>JComponent</code> for more
 434      * information about serialization in Swing.
 435      *
 436      * @param s the output stream
 437      */
 438     private void writeObject(ObjectOutputStream s) throws IOException {
 439         s.defaultWriteObject();
 440         if (getUIClassID().equals(uiClassID)) {
 441             byte count = JComponent.getWriteObjCounter(this);
 442             JComponent.setWriteObjCounter(this, --count);
 443             if (count == 0 && ui != null) {
 444                 ui.installUI(this);
 445             }
 446         }
 447     }
 448 
 449 
 450     // --- JEditorPane ------------------------------------
 451 
 452     /**
 453      * Creates the <code>EditorKit</code> to use by default.  This
 454      * is implemented to return <code>javax.swing.text.StyledEditorKit</code>.
 455      *
 456      * @return the editor kit
 457      */
 458     protected EditorKit createDefaultEditorKit() {
 459         return new StyledEditorKit();
 460     }
 461 
 462     /**
 463      * Sets the currently installed kit for handling
 464      * content.  This is the bound property that
 465      * establishes the content type of the editor.
 466      *
 467      * @param kit the desired editor behavior
 468      * @exception IllegalArgumentException if kit is not a
 469      *          <code>StyledEditorKit</code>
 470      */
 471     public final void setEditorKit(EditorKit kit) {
 472         if (kit instanceof StyledEditorKit) {
 473             super.setEditorKit(kit);
 474         } else {
 475             throw new IllegalArgumentException("Must be StyledEditorKit");
 476         }
 477     }
 478 
 479     /**
 480      * Returns a string representation of this <code>JTextPane</code>.
 481      * This method
 482      * is intended to be used only for debugging purposes, and the
 483      * content and format of the returned string may vary between
 484      * implementations. The returned string may be empty but may not
 485      * be <code>null</code>.
 486      *
 487      * @return  a string representation of this <code>JTextPane</code>
 488      */
 489     protected String paramString() {
 490         return super.paramString();
 491     }
 492 
 493 }