1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing;
  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>Newlines</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  * @since 1.2
  82  */
  83 @SuppressWarnings("serial") // Same-version serialization only
  84 public class JTextPane extends JEditorPane {
  85 
  86     /**
  87      * Creates a new <code>JTextPane</code>.  A new instance of
  88      * <code>StyledEditorKit</code> is
  89      * created and set, and the document model set to <code>null</code>.
  90      */
  91     public JTextPane() {
  92         super();
  93         EditorKit editorKit = createDefaultEditorKit();
  94         String contentType = editorKit.getContentType();
  95         if (contentType != null
  96             && getEditorKitClassNameForContentType(contentType) ==
  97                  defaultEditorKitMap.get(contentType)) {
  98             setEditorKitForContentType(contentType, editorKit);
  99         }
 100         setEditorKit(editorKit);
 101     }
 102 
 103     /**
 104      * Creates a new <code>JTextPane</code>, with a specified document model.
 105      * A new instance of <code>javax.swing.text.StyledEditorKit</code>
 106      *  is created and set.
 107      *
 108      * @param doc the document model
 109      */
 110     public JTextPane(StyledDocument doc) {
 111         this();
 112         setStyledDocument(doc);
 113     }
 114 
 115     /**
 116      * Returns the class ID for the UI.
 117      *
 118      * @return the string "TextPaneUI"
 119      *
 120      * @see JComponent#getUIClassID
 121      * @see UIDefaults#getUI
 122      */
 123     public String getUIClassID() {
 124         return uiClassID;
 125     }
 126 
 127     /**
 128      * Associates the editor with a text document.  This
 129      * must be a <code>StyledDocument</code>.
 130      *
 131      * @param doc  the document to display/edit
 132      * @exception IllegalArgumentException  if <code>doc</code> can't
 133      *   be narrowed to a <code>StyledDocument</code> which is the
 134      *   required type of model for this text component
 135      */
 136     public void setDocument(Document doc) {
 137         if (doc instanceof StyledDocument) {
 138             super.setDocument(doc);
 139         } else {
 140             throw new IllegalArgumentException("Model must be StyledDocument");
 141         }
 142     }
 143 
 144     /**
 145      * Associates the editor with a text document.
 146      * The currently registered factory is used to build a view for
 147      * the document, which gets displayed by the editor.
 148      *
 149      * @param doc  the document to display/edit
 150      */
 151     public void setStyledDocument(StyledDocument doc) {
 152         super.setDocument(doc);
 153     }
 154 
 155     /**
 156      * Fetches the model associated with the editor.
 157      *
 158      * @return the model
 159      */
 160     public StyledDocument getStyledDocument() {
 161         return (StyledDocument) getDocument();
 162     }
 163 
 164     /**
 165      * Replaces the currently selected content with new content
 166      * represented by the given string.  If there is no selection
 167      * this amounts to an insert of the given text.  If there
 168      * is no replacement text this amounts to a removal of the
 169      * current selection.  The replacement text will have the
 170      * attributes currently defined for input at the point of
 171      * insertion.  If the document is not editable, beep and return.
 172      *
 173      * @param content  the content to replace the selection with
 174      */
 175     @Override
 176     public void replaceSelection(String content) {
 177         replaceSelection(content, true);
 178     }
 179 
 180     private void replaceSelection(String content, boolean checkEditable) {
 181         if (checkEditable && !isEditable()) {
 182             UIManager.getLookAndFeel().provideErrorFeedback(JTextPane.this);
 183             return;
 184         }
 185         Document doc = getStyledDocument();
 186         if (doc != null) {
 187             try {
 188                 Caret caret = getCaret();
 189                 boolean composedTextSaved = saveComposedText(caret.getDot());
 190                 int p0 = Math.min(caret.getDot(), caret.getMark());
 191                 int p1 = Math.max(caret.getDot(), caret.getMark());
 192                 AttributeSet attr = getInputAttributes().copyAttributes();
 193                 if (doc instanceof AbstractDocument) {
 194                     ((AbstractDocument)doc).replace(p0, p1 - p0, content,attr);
 195                 }
 196                 else {
 197                     if (p0 != p1) {
 198                         doc.remove(p0, p1 - p0);
 199                     }
 200                     if (content != null && content.length() > 0) {
 201                         doc.insertString(p0, content, attr);
 202                     }
 203                 }
 204                 if (composedTextSaved) {
 205                     restoreComposedText();
 206                 }
 207             } catch (BadLocationException e) {
 208                 UIManager.getLookAndFeel().provideErrorFeedback(JTextPane.this);
 209             }
 210         }
 211     }
 212 
 213     /**
 214      * Inserts a component into the document as a replacement
 215      * for the currently selected content.  If there is no
 216      * selection the component is effectively inserted at the
 217      * current position of the caret.  This is represented in
 218      * the associated document as an attribute of one character
 219      * of content.
 220      * <p>
 221      * The component given is the actual component used by the
 222      * JTextPane.  Since components cannot be a child of more than
 223      * one container, this method should not be used in situations
 224      * where the model is shared by text components.
 225      * <p>
 226      * The component is placed relative to the text baseline
 227      * according to the value returned by
 228      * <code>Component.getAlignmentY</code>.  For Swing components
 229      * this value can be conveniently set using the method
 230      * <code>JComponent.setAlignmentY</code>.  For example, setting
 231      * a value of <code>0.75</code> will cause 75 percent of the
 232      * component to be above the baseline, and 25 percent of the
 233      * component to be below the baseline.
 234      *
 235      * @param c    the component to insert
 236      */
 237     public void insertComponent(Component c) {
 238         MutableAttributeSet inputAttributes = getInputAttributes();
 239         inputAttributes.removeAttributes(inputAttributes);
 240         StyleConstants.setComponent(inputAttributes, c);
 241         replaceSelection(" ", false);
 242         inputAttributes.removeAttributes(inputAttributes);
 243     }
 244 
 245     /**
 246      * Inserts an icon into the document as a replacement
 247      * for the currently selected content.  If there is no
 248      * selection the icon is effectively inserted at the
 249      * current position of the caret.  This is represented in
 250      * the associated document as an attribute of one character
 251      * of content.
 252      *
 253      * @param g    the icon to insert
 254      * @see Icon
 255      */
 256     public void insertIcon(Icon g) {
 257         MutableAttributeSet inputAttributes = getInputAttributes();
 258         inputAttributes.removeAttributes(inputAttributes);
 259         StyleConstants.setIcon(inputAttributes, g);
 260         replaceSelection(" ", false);
 261         inputAttributes.removeAttributes(inputAttributes);
 262     }
 263 
 264     /**
 265      * Adds a new style into the logical style hierarchy.  Style attributes
 266      * resolve from bottom up so an attribute specified in a child
 267      * will override an attribute specified in the parent.
 268      *
 269      * @param nm   the name of the style (must be unique within the
 270      *   collection of named styles).  The name may be <code>null</code>
 271      *   if the style is unnamed, but the caller is responsible
 272      *   for managing the reference returned as an unnamed style can't
 273      *   be fetched by name.  An unnamed style may be useful for things
 274      *   like character attribute overrides such as found in a style
 275      *   run.
 276      * @param parent the parent style.  This may be <code>null</code>
 277      *   if unspecified
 278      *   attributes need not be resolved in some other style.
 279      * @return the new <code>Style</code>
 280      */
 281     public Style addStyle(String nm, Style parent) {
 282         StyledDocument doc = getStyledDocument();
 283         return doc.addStyle(nm, parent);
 284     }
 285 
 286     /**
 287      * Removes a named non-<code>null</code> style previously added to
 288      * the document.
 289      *
 290      * @param nm  the name of the style to remove
 291      */
 292     public void removeStyle(String nm) {
 293         StyledDocument doc = getStyledDocument();
 294         doc.removeStyle(nm);
 295     }
 296 
 297     /**
 298      * Fetches a named non-<code>null</code> style previously added.
 299      *
 300      * @param nm  the name of the style
 301      * @return the <code>Style</code>
 302      */
 303     public Style getStyle(String nm) {
 304         StyledDocument doc = getStyledDocument();
 305         return doc.getStyle(nm);
 306     }
 307 
 308     /**
 309      * Sets the logical style to use for the paragraph at the
 310      * current caret position.  If attributes aren't explicitly set
 311      * for character and paragraph attributes they will resolve
 312      * through the logical style assigned to the paragraph, which
 313      * in term may resolve through some hierarchy completely
 314      * independent of the element hierarchy in the document.
 315      *
 316      * @param s  the logical style to assign to the paragraph,
 317      *          or <code>null</code> for no style
 318      */
 319     public void setLogicalStyle(Style s) {
 320         StyledDocument doc = getStyledDocument();
 321         doc.setLogicalStyle(getCaretPosition(), s);
 322     }
 323 
 324     /**
 325      * Fetches the logical style assigned to the paragraph represented
 326      * by the current position of the caret, or <code>null</code>.
 327      *
 328      * @return the <code>Style</code>
 329      */
 330     public Style getLogicalStyle() {
 331         StyledDocument doc = getStyledDocument();
 332         return doc.getLogicalStyle(getCaretPosition());
 333     }
 334 
 335     /**
 336      * Fetches the character attributes in effect at the
 337      * current location of the caret, or <code>null</code>.
 338      *
 339      * @return the attributes, or <code>null</code>
 340      */
 341     public AttributeSet getCharacterAttributes() {
 342         StyledDocument doc = getStyledDocument();
 343         Element run = doc.getCharacterElement(getCaretPosition());
 344         if (run != null) {
 345             return run.getAttributes();
 346         }
 347         return null;
 348     }
 349 
 350     /**
 351      * Applies the given attributes to character
 352      * content.  If there is a selection, the attributes
 353      * are applied to the selection range.  If there
 354      * is no selection, the attributes are applied to
 355      * the input attribute set which defines the attributes
 356      * for any new text that gets inserted.
 357      *
 358      * @param attr the attributes
 359      * @param replace if true, then replace the existing attributes first
 360      */
 361     public void setCharacterAttributes(AttributeSet attr, boolean replace) {
 362         int p0 = getSelectionStart();
 363         int p1 = getSelectionEnd();
 364         if (p0 != p1) {
 365             StyledDocument doc = getStyledDocument();
 366             doc.setCharacterAttributes(p0, p1 - p0, attr, replace);
 367         } else {
 368             MutableAttributeSet inputAttributes = getInputAttributes();
 369             if (replace) {
 370                 inputAttributes.removeAttributes(inputAttributes);
 371             }
 372             inputAttributes.addAttributes(attr);
 373         }
 374     }
 375 
 376     /**
 377      * Fetches the current paragraph attributes in effect
 378      * at the location of the caret, or <code>null</code> if none.
 379      *
 380      * @return the attributes
 381      */
 382     public AttributeSet getParagraphAttributes() {
 383         StyledDocument doc = getStyledDocument();
 384         Element paragraph = doc.getParagraphElement(getCaretPosition());
 385         if (paragraph != null) {
 386             return paragraph.getAttributes();
 387         }
 388         return null;
 389     }
 390 
 391     /**
 392      * Applies the given attributes to paragraphs.  If
 393      * there is a selection, the attributes are applied
 394      * to the paragraphs that intersect the selection.
 395      * If there is no selection, the attributes are applied
 396      * to the paragraph at the current caret position.
 397      *
 398      * @param attr the non-<code>null</code> attributes
 399      * @param replace if true, replace the existing attributes first
 400      */
 401     public void setParagraphAttributes(AttributeSet attr, boolean replace) {
 402         int p0 = getSelectionStart();
 403         int p1 = getSelectionEnd();
 404         StyledDocument doc = getStyledDocument();
 405         doc.setParagraphAttributes(p0, p1 - p0, attr, replace);
 406     }
 407 
 408     /**
 409      * Gets the input attributes for the pane.
 410      *
 411      * @return the attributes
 412      */
 413     public MutableAttributeSet getInputAttributes() {
 414         return getStyledEditorKit().getInputAttributes();
 415     }
 416 
 417     /**
 418      * Gets the editor kit.
 419      *
 420      * @return the editor kit
 421      */
 422     protected final StyledEditorKit getStyledEditorKit() {
 423         return (StyledEditorKit) getEditorKit();
 424     }
 425 
 426     /**
 427      * @see #getUIClassID
 428      * @see #readObject
 429      */
 430     private static final String uiClassID = "TextPaneUI";
 431 
 432 
 433     /**
 434      * See <code>readObject</code> and <code>writeObject</code> in
 435      * <code>JComponent</code> for more
 436      * information about serialization in Swing.
 437      *
 438      * @param s the output stream
 439      */
 440     private void writeObject(ObjectOutputStream s) throws IOException {
 441         s.defaultWriteObject();
 442         if (getUIClassID().equals(uiClassID)) {
 443             byte count = JComponent.getWriteObjCounter(this);
 444             JComponent.setWriteObjCounter(this, --count);
 445             if (count == 0 && ui != null) {
 446                 ui.installUI(this);
 447             }
 448         }
 449     }
 450 
 451 
 452     // --- JEditorPane ------------------------------------
 453 
 454     /**
 455      * Creates the <code>EditorKit</code> to use by default.  This
 456      * is implemented to return <code>javax.swing.text.StyledEditorKit</code>.
 457      *
 458      * @return the editor kit
 459      */
 460     protected EditorKit createDefaultEditorKit() {
 461         return new StyledEditorKit();
 462     }
 463 
 464     /**
 465      * Sets the currently installed kit for handling
 466      * content.  This is the bound property that
 467      * establishes the content type of the editor.
 468      *
 469      * @param kit the desired editor behavior
 470      * @exception IllegalArgumentException if kit is not a
 471      *          <code>StyledEditorKit</code>
 472      */
 473     public final void setEditorKit(EditorKit kit) {
 474         if (kit instanceof StyledEditorKit) {
 475             super.setEditorKit(kit);
 476         } else {
 477             throw new IllegalArgumentException("Must be StyledEditorKit");
 478         }
 479     }
 480 
 481     /**
 482      * Returns a string representation of this <code>JTextPane</code>.
 483      * This method
 484      * is intended to be used only for debugging purposes, and the
 485      * content and format of the returned string may vary between
 486      * implementations. The returned string may be empty but may not
 487      * be <code>null</code>.
 488      *
 489      * @return  a string representation of this <code>JTextPane</code>
 490      */
 491     protected String paramString() {
 492         return super.paramString();
 493     }
 494 
 495 }