Frames | No Frames |
1: /* StyledEditorKit.java -- 2: Copyright (C) 2002, 2004 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import java.awt.Color; 42: import java.awt.event.ActionEvent; 43: 44: import javax.swing.Action; 45: import javax.swing.JEditorPane; 46: import javax.swing.event.CaretEvent; 47: import javax.swing.event.CaretListener; 48: 49: /** 50: * An {@link EditorKit} that supports editing styled text. 51: * 52: * @author Andrew Selkirk 53: * @author Roman Kennke (roman@kennke.org) 54: */ 55: public class StyledEditorKit extends DefaultEditorKit 56: { 57: /** The serialVersionUID. */ 58: private static final long serialVersionUID = 7002391892985555948L; 59: 60: /** 61: * Toggles the underline attribute for the selected text. 62: */ 63: public static class UnderlineAction extends StyledEditorKit.StyledTextAction 64: { 65: /** 66: * Creates an instance of <code>UnderlineAction</code>. 67: */ 68: public UnderlineAction() 69: { 70: super("font-underline"); 71: } 72: 73: /** 74: * Performs the action. 75: * 76: * @param event the <code>ActionEvent</code> that describes the action 77: */ 78: public void actionPerformed(ActionEvent event) 79: { 80: JEditorPane editor = getEditor(event); 81: StyledDocument doc = getStyledDocument(editor); 82: Element el = doc.getCharacterElement(editor.getSelectionStart()); 83: boolean isUnderline = StyleConstants.isUnderline(el.getAttributes()); 84: SimpleAttributeSet atts = new SimpleAttributeSet(); 85: StyleConstants.setUnderline(atts, ! isUnderline); 86: setCharacterAttributes(editor, atts, false); 87: } 88: } 89: 90: /** 91: * Toggles the italic attribute for the selected text. 92: */ 93: public static class ItalicAction extends StyledEditorKit.StyledTextAction 94: { 95: /** 96: * Creates an instance of <code>ItalicAction</code>. 97: */ 98: public ItalicAction() 99: { 100: super("font-italic"); 101: } 102: 103: /** 104: * Performs the action. 105: * 106: * @param event the <code>ActionEvent</code> that describes the action 107: */ 108: public void actionPerformed(ActionEvent event) 109: { 110: JEditorPane editor = getEditor(event); 111: StyledDocument doc = getStyledDocument(editor); 112: Element el = doc.getCharacterElement(editor.getSelectionStart()); 113: boolean isItalic = StyleConstants.isItalic(el.getAttributes()); 114: SimpleAttributeSet atts = new SimpleAttributeSet(); 115: StyleConstants.setItalic(atts, ! isItalic); 116: setCharacterAttributes(editor, atts, false); 117: } 118: } 119: 120: /** 121: * Toggles the bold attribute for the selected text. 122: */ 123: public static class BoldAction extends StyledEditorKit.StyledTextAction 124: { 125: /** 126: * Creates an instance of <code>BoldAction</code>. 127: */ 128: public BoldAction() 129: { 130: super("font-bold"); 131: } 132: 133: /** 134: * Performs the action. 135: * 136: * @param event the <code>ActionEvent</code> that describes the action 137: */ 138: public void actionPerformed(ActionEvent event) 139: { 140: JEditorPane editor = getEditor(event); 141: StyledDocument doc = getStyledDocument(editor); 142: Element el = doc.getCharacterElement(editor.getSelectionStart()); 143: boolean isBold = StyleConstants.isBold(el.getAttributes()); 144: SimpleAttributeSet atts = new SimpleAttributeSet(); 145: StyleConstants.setItalic(atts, ! isBold); 146: setCharacterAttributes(editor, atts, false); 147: } 148: } 149: 150: /** 151: * Sets the alignment attribute on the selected text. 152: */ 153: public static class AlignmentAction extends StyledEditorKit.StyledTextAction 154: { 155: /** 156: * The aligment to set. 157: */ 158: private int a; 159: 160: /** 161: * Creates a new instance of <code>AlignmentAction</code> to set the 162: * alignment to <code>a</code>. 163: * 164: * @param nm the name of the Action 165: * @param a the alignment to set 166: */ 167: public AlignmentAction(String nm, int a) 168: { 169: super(nm); 170: this.a = a; 171: } 172: 173: /** 174: * Performs the action. 175: * 176: * @param event the <code>ActionEvent</code> that describes the action 177: */ 178: public void actionPerformed(ActionEvent event) 179: { 180: SimpleAttributeSet atts = new SimpleAttributeSet(); 181: StyleConstants.setAlignment(atts, a); 182: setParagraphAttributes(getEditor(event), atts, false); 183: } 184: } 185: 186: /** 187: * Sets the foreground color attribute on the selected text. 188: */ 189: public static class ForegroundAction extends StyledEditorKit.StyledTextAction 190: { 191: /** 192: * The foreground color to set. 193: */ 194: private Color fg; 195: 196: /** 197: * Creates a new instance of <code>ForegroundAction</code> to set the 198: * foreground color to <code>fg</code>. 199: * 200: * @param nm the name of the Action 201: * @param fg the foreground color to set 202: */ 203: public ForegroundAction(String nm, Color fg) 204: { 205: super(nm); 206: this.fg = fg; 207: } 208: 209: /** 210: * Performs the action. 211: * 212: * @param event the <code>ActionEvent</code> that describes the action 213: */ 214: public void actionPerformed(ActionEvent event) 215: { 216: SimpleAttributeSet atts = new SimpleAttributeSet(); 217: StyleConstants.setForeground(atts, fg); 218: setCharacterAttributes(getEditor(event), atts, false); 219: } 220: } 221: 222: /** 223: * Sets the font size attribute on the selected text. 224: */ 225: public static class FontSizeAction extends StyledEditorKit.StyledTextAction 226: { 227: /** 228: * The font size to set. 229: */ 230: private int size; 231: 232: /** 233: * Creates a new instance of <code>FontSizeAction</code> to set the 234: * font size to <code>size</code>. 235: * 236: * @param nm the name of the Action 237: * @param size the font size to set 238: */ 239: public FontSizeAction(String nm, int size) 240: { 241: super(nm); 242: this.size = size; 243: } 244: 245: /** 246: * Performs the action. 247: * 248: * @param event the <code>ActionEvent</code> that describes the action 249: */ 250: public void actionPerformed(ActionEvent event) 251: { 252: SimpleAttributeSet atts = new SimpleAttributeSet(); 253: StyleConstants.setFontSize(atts, size); 254: setCharacterAttributes(getEditor(event), atts, false); 255: } 256: } 257: 258: /** 259: * Sets the font family attribute on the selected text. 260: */ 261: public static class FontFamilyAction extends StyledEditorKit.StyledTextAction 262: { 263: /** 264: * The font family to set. 265: */ 266: private String family; 267: 268: /** 269: * Creates a new instance of <code>FontFamilyAction</code> to set the 270: * font family to <code>family</code>. 271: * 272: * @param nm the name of the Action 273: * @param family the font family to set 274: */ 275: public FontFamilyAction(String nm, String family) 276: { 277: super(nm); 278: this.family = family; 279: } 280: 281: /** 282: * Performs the action. 283: * 284: * @param event the <code>ActionEvent</code> that describes the action 285: */ 286: public void actionPerformed(ActionEvent event) 287: { 288: SimpleAttributeSet atts = new SimpleAttributeSet(); 289: StyleConstants.setFontFamily(atts, family); 290: setCharacterAttributes(getEditor(event), atts, false); 291: } 292: } 293: 294: /** 295: * The abstract superclass of all styled TextActions. This class 296: * provides some useful methods to manipulate the text attributes. 297: */ 298: public abstract static class StyledTextAction extends TextAction 299: { 300: /** 301: * Creates a new instance of <code>StyledTextAction</code>. 302: * 303: * @param nm the name of the <code>StyledTextAction</code> 304: */ 305: public StyledTextAction(String nm) 306: { 307: super(nm); 308: } 309: 310: /** 311: * Returns the <code>JEditorPane</code> component from which the 312: * <code>ActionEvent</code> originated. 313: * 314: * @param event the <code>ActionEvent</code> 315: * @return the <code>JEditorPane</code> component from which the 316: * <code>ActionEvent</code> originated 317: */ 318: protected final JEditorPane getEditor(ActionEvent event) 319: { 320: return (JEditorPane) getTextComponent(event); 321: } 322: 323: /** 324: * Sets the specified character attributes on the currently selected 325: * text of <code>editor</code>. If <code>editor</code> does not have 326: * a selection, then the attributes are used as input attributes 327: * for newly inserted content. 328: * 329: * @param editor the <code>JEditorPane</code> component 330: * @param atts the text attributes to set 331: * @param replace if <code>true</code> the current attributes of the 332: * selection are replaces, otherwise they are merged 333: */ 334: protected final void setCharacterAttributes(JEditorPane editor, 335: AttributeSet atts, 336: boolean replace) 337: { 338: Document doc = editor.getDocument(); 339: if (doc instanceof StyledDocument) 340: { 341: StyledDocument styleDoc = (StyledDocument) editor.getDocument(); 342: EditorKit kit = editor.getEditorKit(); 343: if (!(kit instanceof StyledEditorKit)) 344: { 345: StyledEditorKit styleKit = (StyledEditorKit) kit; 346: int start = editor.getSelectionStart(); 347: int end = editor.getSelectionEnd(); 348: int dot = editor.getCaret().getDot(); 349: if (start == dot && end == dot) 350: { 351: // If there is no selection, then we only update the 352: // input attributes. 353: MutableAttributeSet inputAttributes = 354: styleKit.getInputAttributes(); 355: inputAttributes.addAttributes(atts); 356: } 357: else 358: styleDoc.setCharacterAttributes(start, end, atts, replace); 359: } 360: else 361: throw new AssertionError("The EditorKit for StyledTextActions " 362: + "is expected to be a StyledEditorKit"); 363: } 364: else 365: throw new AssertionError("The Document for StyledTextActions is " 366: + "expected to be a StyledDocument."); 367: } 368: 369: /** 370: * Returns the {@link StyledDocument} that is used by <code>editor</code>. 371: * 372: * @param editor the <code>JEditorPane</code> from which to get the 373: * <code>StyledDocument</code> 374: * 375: * @return the {@link StyledDocument} that is used by <code>editor</code> 376: */ 377: protected final StyledDocument getStyledDocument(JEditorPane editor) 378: { 379: Document doc = editor.getDocument(); 380: if (!(doc instanceof StyledDocument)) 381: throw new AssertionError("The Document for StyledEditorKits is " 382: + "expected to be a StyledDocument."); 383: 384: return (StyledDocument) doc; 385: } 386: 387: /** 388: * Returns the {@link StyledEditorKit} that is used by <code>editor</code>. 389: * 390: * @param editor the <code>JEditorPane</code> from which to get the 391: * <code>StyledEditorKit</code> 392: * 393: * @return the {@link StyledEditorKit} that is used by <code>editor</code> 394: */ 395: protected final StyledEditorKit getStyledEditorKit(JEditorPane editor) 396: { 397: EditorKit kit = editor.getEditorKit(); 398: if (!(kit instanceof StyledEditorKit)) 399: throw new AssertionError("The EditorKit for StyledDocuments is " 400: + "expected to be a StyledEditorKit."); 401: 402: return (StyledEditorKit) kit; 403: } 404: 405: /** 406: * Sets the specified character attributes on the paragraph that 407: * contains the currently selected 408: * text of <code>editor</code>. If <code>editor</code> does not have 409: * a selection, then the attributes are set on the paragraph that 410: * contains the current caret position. 411: * 412: * @param editor the <code>JEditorPane</code> component 413: * @param atts the text attributes to set 414: * @param replace if <code>true</code> the current attributes of the 415: * selection are replaces, otherwise they are merged 416: */ 417: protected final void setParagraphAttributes(JEditorPane editor, 418: AttributeSet atts, 419: boolean replace) 420: { 421: Document doc = editor.getDocument(); 422: if (doc instanceof StyledDocument) 423: { 424: StyledDocument styleDoc = (StyledDocument) editor.getDocument(); 425: EditorKit kit = editor.getEditorKit(); 426: if (!(kit instanceof StyledEditorKit)) 427: { 428: StyledEditorKit styleKit = (StyledEditorKit) kit; 429: int start = editor.getSelectionStart(); 430: int end = editor.getSelectionEnd(); 431: int dot = editor.getCaret().getDot(); 432: if (start == dot && end == dot) 433: { 434: // If there is no selection, then we only update the 435: // input attributes. 436: MutableAttributeSet inputAttributes = 437: styleKit.getInputAttributes(); 438: inputAttributes.addAttributes(atts); 439: } 440: else 441: styleDoc.setParagraphAttributes(start, end, atts, replace); 442: } 443: else 444: throw new AssertionError("The EditorKit for StyledTextActions " 445: + "is expected to be a StyledEditorKit"); 446: } 447: else 448: throw new AssertionError("The Document for StyledTextActions is " 449: + "expected to be a StyledDocument."); 450: } 451: } 452: 453: /** 454: * A {@link ViewFactory} that is able to create {@link View}s for 455: * the <code>Element</code>s that are supported by 456: * <code>StyledEditorKit</code>, namely the following types of Elements: 457: * 458: * <ul> 459: * <li>{@link AbstractDocument#ContentElementName}</li> 460: * <li>{@link AbstractDocument#ParagraphElementName}</li> 461: * <li>{@link AbstractDocument#SectionElementName}</li> 462: * <li>{@link StyleConstants#ComponentElementName}</li> 463: * <li>{@link StyleConstants#IconElementName}</li> 464: * </ul> 465: */ 466: static class StyledViewFactory 467: implements ViewFactory 468: { 469: /** 470: * Creates a {@link View} for the specified <code>Element</code>. 471: * 472: * @param element the <code>Element</code> to create a <code>View</code> 473: * for 474: * @return the <code>View</code> for the specified <code>Element</code> 475: * or <code>null</code> if the type of <code>element</code> is 476: * not supported 477: */ 478: public View create(Element element) 479: { 480: String name = element.getName(); 481: View view = null; 482: if (name.equals(AbstractDocument.ContentElementName)) 483: view = new LabelView(element); 484: else if (name.equals(AbstractDocument.ParagraphElementName)) 485: view = new ParagraphView(element); 486: else if (name.equals(AbstractDocument.SectionElementName)) 487: view = new BoxView(element, View.Y_AXIS); 488: else if (name.equals(StyleConstants.ComponentElementName)) 489: view = new ComponentView(element); 490: else if (name.equals(StyleConstants.IconElementName)) 491: view = new IconView(element); 492: else 493: throw new AssertionError("Unknown Element type: " 494: + element.getClass().getName() + " : " 495: + name); 496: return view; 497: } 498: } 499: 500: /** 501: * Keeps track of the caret position and updates the currentRun 502: * <code>Element</code> and the <code>inputAttributes</code>. 503: */ 504: class CaretTracker 505: implements CaretListener 506: { 507: /** 508: * Notifies an update of the caret position. 509: * 510: * @param ev the event for the caret update 511: */ 512: public void caretUpdate(CaretEvent ev) 513: { 514: Object source = ev.getSource(); 515: if (!(source instanceof JTextComponent)) 516: throw new AssertionError("CaretEvents are expected to come from a" 517: + "JTextComponent."); 518: 519: JTextComponent text = (JTextComponent) source; 520: Document doc = text.getDocument(); 521: if (!(doc instanceof StyledDocument)) 522: throw new AssertionError("The Document used by StyledEditorKits is" 523: + "expected to be a StyledDocument"); 524: 525: StyledDocument styleDoc = (StyledDocument) doc; 526: currentRun = styleDoc.getCharacterElement(ev.getDot()); 527: createInputAttributes(currentRun, inputAttributes); 528: } 529: } 530: 531: /** 532: * Stores the <code>Element</code> at the current caret position. This 533: * is updated by {@link CaretTracker}. 534: */ 535: Element currentRun; 536: 537: /** 538: * The current input attributes. This is updated by {@link CaretTracker}. 539: */ 540: MutableAttributeSet inputAttributes; 541: 542: /** 543: * The CaretTracker that keeps track of the current input attributes, and 544: * the current character run Element. 545: */ 546: CaretTracker caretTracker; 547: 548: /** 549: * The ViewFactory for StyledEditorKits. 550: */ 551: StyledViewFactory viewFactory; 552: 553: /** 554: * Creates a new instance of <code>StyledEditorKit</code>. 555: */ 556: public StyledEditorKit() 557: { 558: inputAttributes = new SimpleAttributeSet(); 559: } 560: 561: /** 562: * Creates an exact copy of this <code>StyledEditorKit</code>. 563: * 564: * @return an exact copy of this <code>StyledEditorKit</code> 565: */ 566: public Object clone() 567: { 568: StyledEditorKit clone = (StyledEditorKit) super.clone(); 569: // FIXME: Investigate which fields must be copied. 570: return clone; 571: } 572: 573: /** 574: * Returns the <code>Action</code>s supported by this {@link EditorKit}. 575: * This includes the {@link BoldAction}, {@link ItalicAction} and 576: * {@link UnderlineAction} as well as the <code>Action</code>s supported 577: * by {@link DefaultEditorKit}. 578: * 579: * The other <code>Action</code>s of <code>StyledEditorKit</code> are not 580: * returned here, since they require a parameter and thus custom 581: * instantiation. 582: * 583: * @return the <code>Action</code>s supported by this {@link EditorKit} 584: */ 585: public Action[] getActions() 586: { 587: Action[] actions1 = super.getActions(); 588: Action[] myActions = new Action[] { 589: new FontSizeAction("font-size-8", 8), 590: new FontSizeAction("font-size-10", 10), 591: new FontSizeAction("font-size-12", 12), 592: new FontSizeAction("font-size-14", 14), 593: new FontSizeAction("font-size-16", 16), 594: new FontSizeAction("font-size-18", 18), 595: new FontSizeAction("font-size-24", 24), 596: new FontSizeAction("font-size-36", 36), 597: new FontSizeAction("font-size-48", 48), 598: new FontFamilyAction("font-family-Serif", "Serif"), 599: new FontFamilyAction("font-family-Monospaced", "Monospaced"), 600: new FontFamilyAction("font-family-SansSerif", "SansSerif"), 601: new AlignmentAction("left-justify", StyleConstants.ALIGN_LEFT), 602: new AlignmentAction("center-justify", StyleConstants.ALIGN_CENTER), 603: new AlignmentAction("right-justify", StyleConstants.ALIGN_RIGHT), 604: new BoldAction(), 605: new ItalicAction(), 606: new UnderlineAction() 607: }; 608: return TextAction.augmentList(actions1, myActions); 609: } 610: 611: /** 612: * Returns the current input attributes. These are automatically set on 613: * any newly inserted content, if not specified otherwise. 614: * 615: * @return the current input attributes 616: */ 617: public MutableAttributeSet getInputAttributes() 618: { 619: return inputAttributes; 620: } 621: 622: /** 623: * Returns the {@link Element} that represents the character run at the 624: * current caret position. 625: * 626: * @return the {@link Element} that represents the character run at the 627: * current caret position 628: */ 629: public Element getCharacterAttributeRun() 630: { 631: return currentRun; 632: } 633: 634: /** 635: * Creates the default {@link Document} supported by this 636: * <code>EditorKit</code>. This is an instance of 637: * {@link DefaultStyledDocument} in this case but may be overridden by 638: * subclasses. 639: * 640: * @return an instance of <code>DefaultStyledDocument</code> 641: */ 642: public Document createDefaultDocument() 643: { 644: return new DefaultStyledDocument(); 645: } 646: 647: /** 648: * Installs this <code>EditorKit</code> on the specified {@link JEditorPane}. 649: * This basically involves setting up required listeners on the 650: * <code>JEditorPane</code>. 651: * 652: * @param component the <code>JEditorPane</code> to install this 653: * <code>EditorKit</code> on 654: */ 655: public void install(JEditorPane component) 656: { 657: CaretTracker tracker = new CaretTracker(); 658: component.addCaretListener(tracker); 659: } 660: 661: /** 662: * Deinstalls this <code>EditorKit</code> from the specified 663: * {@link JEditorPane}. This basically involves removing all listeners from 664: * <code>JEditorPane</code> that have been set up by this 665: * <code>EditorKit</code>. 666: * 667: * @param component the <code>JEditorPane</code> from which to deinstall this 668: * <code>EditorKit</code> 669: */ 670: public void deinstall(JEditorPane component) 671: { 672: CaretTracker t = caretTracker; 673: if (t != null) 674: component.removeCaretListener(t); 675: caretTracker = null; 676: } 677: 678: /** 679: * Returns a {@link ViewFactory} that is able to create {@link View}s 680: * for {@link Element}s that are supported by this <code>EditorKit</code>, 681: * namely the following types of <code>Element</code>s: 682: * 683: * <ul> 684: * <li>{@link AbstractDocument#ContentElementName}</li> 685: * <li>{@link AbstractDocument#ParagraphElementName}</li> 686: * <li>{@link AbstractDocument#SectionElementName}</li> 687: * <li>{@link StyleConstants#ComponentElementName}</li> 688: * <li>{@link StyleConstants#IconElementName}</li> 689: * </ul> 690: * 691: * @return a {@link ViewFactory} that is able to create {@link View}s 692: * for {@link Element}s that are supported by this <code>EditorKit</code> 693: */ 694: public ViewFactory getViewFactory() 695: { 696: if (viewFactory == null) 697: viewFactory = new StyledViewFactory(); 698: return viewFactory; 699: } 700: 701: /** 702: * Copies the text attributes from <code>element</code> to <code>set</code>. 703: * This is called everytime when the caret position changes to keep 704: * track of the current input attributes. The attributes in <code>set</code> 705: * are cleaned before adding the attributes of <code>element</code>. 706: * 707: * This method filters out attributes for element names, <code>Icon</code>s 708: * and <code>Component</code>s. 709: * 710: * @param element the <code>Element</code> from which to copy the text 711: * attributes 712: * @param set the inputAttributes to copy the attributes to 713: */ 714: protected void createInputAttributes(Element element, 715: MutableAttributeSet set) 716: { 717: // FIXME: Filter out component, icon and element name attributes. 718: set.removeAttributes(set); 719: set.addAttributes(element.getAttributes()); 720: } 721: }