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.setBold(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: int p0 = editor.getSelectionStart(); 339: int p1 = editor.getSelectionEnd(); 340: if (p0 != p1) 341: { 342: StyledDocument doc = getStyledDocument(editor); 343: doc.setCharacterAttributes(p0, p1 - p0, atts, replace); 344: } 345: // Update input attributes. 346: StyledEditorKit kit = getStyledEditorKit(editor); 347: MutableAttributeSet inputAtts = kit.getInputAttributes(); 348: if (replace) 349: { 350: inputAtts.removeAttributes(inputAtts); 351: } 352: inputAtts.addAttributes(atts); 353: } 354: 355: /** 356: * Returns the {@link StyledDocument} that is used by <code>editor</code>. 357: * 358: * @param editor the <code>JEditorPane</code> from which to get the 359: * <code>StyledDocument</code> 360: * 361: * @return the {@link StyledDocument} that is used by <code>editor</code> 362: */ 363: protected final StyledDocument getStyledDocument(JEditorPane editor) 364: { 365: Document doc = editor.getDocument(); 366: if (!(doc instanceof StyledDocument)) 367: throw new AssertionError("The Document for StyledEditorKits is " 368: + "expected to be a StyledDocument."); 369: 370: return (StyledDocument) doc; 371: } 372: 373: /** 374: * Returns the {@link StyledEditorKit} that is used by <code>editor</code>. 375: * 376: * @param editor the <code>JEditorPane</code> from which to get the 377: * <code>StyledEditorKit</code> 378: * 379: * @return the {@link StyledEditorKit} that is used by <code>editor</code> 380: */ 381: protected final StyledEditorKit getStyledEditorKit(JEditorPane editor) 382: { 383: EditorKit kit = editor.getEditorKit(); 384: if (!(kit instanceof StyledEditorKit)) 385: throw new AssertionError("The EditorKit for StyledDocuments is " 386: + "expected to be a StyledEditorKit."); 387: 388: return (StyledEditorKit) kit; 389: } 390: 391: /** 392: * Sets the specified character attributes on the paragraph that 393: * contains the currently selected 394: * text of <code>editor</code>. If <code>editor</code> does not have 395: * a selection, then the attributes are set on the paragraph that 396: * contains the current caret position. 397: * 398: * @param editor the <code>JEditorPane</code> component 399: * @param atts the text attributes to set 400: * @param replace if <code>true</code> the current attributes of the 401: * selection are replaces, otherwise they are merged 402: */ 403: protected final void setParagraphAttributes(JEditorPane editor, 404: AttributeSet atts, 405: boolean replace) 406: { 407: Document doc = editor.getDocument(); 408: if (doc instanceof StyledDocument) 409: { 410: StyledDocument styleDoc = (StyledDocument) editor.getDocument(); 411: EditorKit kit = editor.getEditorKit(); 412: if (!(kit instanceof StyledEditorKit)) 413: { 414: StyledEditorKit styleKit = (StyledEditorKit) kit; 415: int start = editor.getSelectionStart(); 416: int end = editor.getSelectionEnd(); 417: int dot = editor.getCaret().getDot(); 418: if (start == dot && end == dot) 419: { 420: // If there is no selection, then we only update the 421: // input attributes. 422: MutableAttributeSet inputAttributes = 423: styleKit.getInputAttributes(); 424: inputAttributes.addAttributes(atts); 425: } 426: else 427: styleDoc.setParagraphAttributes(start, end, atts, replace); 428: } 429: else 430: throw new AssertionError("The EditorKit for StyledTextActions " 431: + "is expected to be a StyledEditorKit"); 432: } 433: else 434: throw new AssertionError("The Document for StyledTextActions is " 435: + "expected to be a StyledDocument."); 436: } 437: } 438: 439: /** 440: * A {@link ViewFactory} that is able to create {@link View}s for 441: * the <code>Element</code>s that are supported by 442: * <code>StyledEditorKit</code>, namely the following types of Elements: 443: * 444: * <ul> 445: * <li>{@link AbstractDocument#ContentElementName}</li> 446: * <li>{@link AbstractDocument#ParagraphElementName}</li> 447: * <li>{@link AbstractDocument#SectionElementName}</li> 448: * <li>{@link StyleConstants#ComponentElementName}</li> 449: * <li>{@link StyleConstants#IconElementName}</li> 450: * </ul> 451: */ 452: static class StyledViewFactory 453: implements ViewFactory 454: { 455: /** 456: * Creates a {@link View} for the specified <code>Element</code>. 457: * 458: * @param element the <code>Element</code> to create a <code>View</code> 459: * for 460: * @return the <code>View</code> for the specified <code>Element</code> 461: * or <code>null</code> if the type of <code>element</code> is 462: * not supported 463: */ 464: public View create(Element element) 465: { 466: String name = element.getName(); 467: View view = null; 468: if (name.equals(AbstractDocument.ContentElementName)) 469: view = new LabelView(element); 470: else if (name.equals(AbstractDocument.ParagraphElementName)) 471: view = new ParagraphView(element); 472: else if (name.equals(AbstractDocument.SectionElementName)) 473: view = new BoxView(element, View.Y_AXIS); 474: else if (name.equals(StyleConstants.ComponentElementName)) 475: view = new ComponentView(element); 476: else if (name.equals(StyleConstants.IconElementName)) 477: view = new IconView(element); 478: else 479: throw new AssertionError("Unknown Element type: " 480: + element.getClass().getName() + " : " 481: + name); 482: return view; 483: } 484: } 485: 486: /** 487: * Keeps track of the caret position and updates the currentRun 488: * <code>Element</code> and the <code>inputAttributes</code>. 489: */ 490: class CaretTracker 491: implements CaretListener 492: { 493: /** 494: * Notifies an update of the caret position. 495: * 496: * @param ev the event for the caret update 497: */ 498: public void caretUpdate(CaretEvent ev) 499: { 500: Object source = ev.getSource(); 501: if (!(source instanceof JTextComponent)) 502: throw new AssertionError("CaretEvents are expected to come from a" 503: + "JTextComponent."); 504: 505: JTextComponent text = (JTextComponent) source; 506: Document doc = text.getDocument(); 507: if (!(doc instanceof StyledDocument)) 508: throw new AssertionError("The Document used by StyledEditorKits is" 509: + "expected to be a StyledDocument"); 510: 511: StyledDocument styleDoc = (StyledDocument) doc; 512: currentRun = styleDoc.getCharacterElement(ev.getDot()); 513: createInputAttributes(currentRun, inputAttributes); 514: } 515: } 516: 517: /** 518: * Stores the <code>Element</code> at the current caret position. This 519: * is updated by {@link CaretTracker}. 520: */ 521: Element currentRun; 522: 523: /** 524: * The current input attributes. This is updated by {@link CaretTracker}. 525: */ 526: MutableAttributeSet inputAttributes; 527: 528: /** 529: * The CaretTracker that keeps track of the current input attributes, and 530: * the current character run Element. 531: */ 532: CaretTracker caretTracker; 533: 534: /** 535: * The ViewFactory for StyledEditorKits. 536: */ 537: StyledViewFactory viewFactory; 538: 539: /** 540: * Creates a new instance of <code>StyledEditorKit</code>. 541: */ 542: public StyledEditorKit() 543: { 544: inputAttributes = new SimpleAttributeSet(); 545: } 546: 547: /** 548: * Creates an exact copy of this <code>StyledEditorKit</code>. 549: * 550: * @return an exact copy of this <code>StyledEditorKit</code> 551: */ 552: public Object clone() 553: { 554: StyledEditorKit clone = (StyledEditorKit) super.clone(); 555: // FIXME: Investigate which fields must be copied. 556: return clone; 557: } 558: 559: /** 560: * Returns the <code>Action</code>s supported by this {@link EditorKit}. 561: * This includes the {@link BoldAction}, {@link ItalicAction} and 562: * {@link UnderlineAction} as well as the <code>Action</code>s supported 563: * by {@link DefaultEditorKit}. 564: * 565: * The other <code>Action</code>s of <code>StyledEditorKit</code> are not 566: * returned here, since they require a parameter and thus custom 567: * instantiation. 568: * 569: * @return the <code>Action</code>s supported by this {@link EditorKit} 570: */ 571: public Action[] getActions() 572: { 573: Action[] actions1 = super.getActions(); 574: Action[] myActions = new Action[] { 575: new FontSizeAction("font-size-8", 8), 576: new FontSizeAction("font-size-10", 10), 577: new FontSizeAction("font-size-12", 12), 578: new FontSizeAction("font-size-14", 14), 579: new FontSizeAction("font-size-16", 16), 580: new FontSizeAction("font-size-18", 18), 581: new FontSizeAction("font-size-24", 24), 582: new FontSizeAction("font-size-36", 36), 583: new FontSizeAction("font-size-48", 48), 584: new FontFamilyAction("font-family-Serif", "Serif"), 585: new FontFamilyAction("font-family-Monospaced", "Monospaced"), 586: new FontFamilyAction("font-family-SansSerif", "SansSerif"), 587: new AlignmentAction("left-justify", StyleConstants.ALIGN_LEFT), 588: new AlignmentAction("center-justify", StyleConstants.ALIGN_CENTER), 589: new AlignmentAction("right-justify", StyleConstants.ALIGN_RIGHT), 590: new BoldAction(), 591: new ItalicAction(), 592: new UnderlineAction() 593: }; 594: return TextAction.augmentList(actions1, myActions); 595: } 596: 597: /** 598: * Returns the current input attributes. These are automatically set on 599: * any newly inserted content, if not specified otherwise. 600: * 601: * @return the current input attributes 602: */ 603: public MutableAttributeSet getInputAttributes() 604: { 605: return inputAttributes; 606: } 607: 608: /** 609: * Returns the {@link Element} that represents the character run at the 610: * current caret position. 611: * 612: * @return the {@link Element} that represents the character run at the 613: * current caret position 614: */ 615: public Element getCharacterAttributeRun() 616: { 617: return currentRun; 618: } 619: 620: /** 621: * Creates the default {@link Document} supported by this 622: * <code>EditorKit</code>. This is an instance of 623: * {@link DefaultStyledDocument} in this case but may be overridden by 624: * subclasses. 625: * 626: * @return an instance of <code>DefaultStyledDocument</code> 627: */ 628: public Document createDefaultDocument() 629: { 630: return new DefaultStyledDocument(); 631: } 632: 633: /** 634: * Installs this <code>EditorKit</code> on the specified {@link JEditorPane}. 635: * This basically involves setting up required listeners on the 636: * <code>JEditorPane</code>. 637: * 638: * @param component the <code>JEditorPane</code> to install this 639: * <code>EditorKit</code> on 640: */ 641: public void install(JEditorPane component) 642: { 643: CaretTracker tracker = new CaretTracker(); 644: component.addCaretListener(tracker); 645: } 646: 647: /** 648: * Deinstalls this <code>EditorKit</code> from the specified 649: * {@link JEditorPane}. This basically involves removing all listeners from 650: * <code>JEditorPane</code> that have been set up by this 651: * <code>EditorKit</code>. 652: * 653: * @param component the <code>JEditorPane</code> from which to deinstall this 654: * <code>EditorKit</code> 655: */ 656: public void deinstall(JEditorPane component) 657: { 658: CaretTracker t = caretTracker; 659: if (t != null) 660: component.removeCaretListener(t); 661: caretTracker = null; 662: } 663: 664: /** 665: * Returns a {@link ViewFactory} that is able to create {@link View}s 666: * for {@link Element}s that are supported by this <code>EditorKit</code>, 667: * namely the following types of <code>Element</code>s: 668: * 669: * <ul> 670: * <li>{@link AbstractDocument#ContentElementName}</li> 671: * <li>{@link AbstractDocument#ParagraphElementName}</li> 672: * <li>{@link AbstractDocument#SectionElementName}</li> 673: * <li>{@link StyleConstants#ComponentElementName}</li> 674: * <li>{@link StyleConstants#IconElementName}</li> 675: * </ul> 676: * 677: * @return a {@link ViewFactory} that is able to create {@link View}s 678: * for {@link Element}s that are supported by this <code>EditorKit</code> 679: */ 680: public ViewFactory getViewFactory() 681: { 682: if (viewFactory == null) 683: viewFactory = new StyledViewFactory(); 684: return viewFactory; 685: } 686: 687: /** 688: * Copies the text attributes from <code>element</code> to <code>set</code>. 689: * This is called everytime when the caret position changes to keep 690: * track of the current input attributes. The attributes in <code>set</code> 691: * are cleaned before adding the attributes of <code>element</code>. 692: * 693: * This method filters out attributes for element names, <code>Icon</code>s 694: * and <code>Component</code>s. 695: * 696: * @param element the <code>Element</code> from which to copy the text 697: * attributes 698: * @param set the inputAttributes to copy the attributes to 699: */ 700: protected void createInputAttributes(Element element, 701: MutableAttributeSet set) 702: { 703: // FIXME: Filter out component, icon and element name attributes. 704: set.removeAttributes(set); 705: set.addAttributes(element.getAttributes()); 706: } 707: }