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 }