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