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™ 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 }