GNU Classpath (0.18) | ||
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: import java.beans.PropertyChangeEvent; 44: import java.beans.PropertyChangeListener; 45: import java.io.Serializable; 46: 47: import javax.swing.Action; 48: import javax.swing.JEditorPane; 49: import javax.swing.JTextPane; 50: import javax.swing.event.CaretEvent; 51: import javax.swing.event.CaretListener; 52: 53: /** 54: * An {@link EditorKit} that supports editing styled text. 55: * 56: * @author Andrew Selkirk 57: * @author Roman Kennke (roman@kennke.org) 58: */ 59: public class StyledEditorKit extends DefaultEditorKit 60: { 61: /** The serialVersionUID. */ 62: private static final long serialVersionUID = 7002391892985555948L; 63: 64: /** 65: * Toggles the underline attribute for the selected text. 66: */ 67: public static class UnderlineAction extends StyledEditorKit.StyledTextAction 68: { 69: /** 70: * Creates an instance of <code>UnderlineAction</code>. 71: */ 72: public UnderlineAction() 73: { 74: super("TODO"); // TODO: Figure out name for this action. 75: } 76: 77: /** 78: * Performs the action. 79: * 80: * @param event the <code>ActionEvent</code> that describes the action 81: */ 82: public void actionPerformed(ActionEvent event) 83: { 84: JEditorPane editor = getEditor(event); 85: StyledDocument doc = getStyledDocument(editor); 86: Element el = doc.getCharacterElement(editor.getSelectionStart()); 87: boolean isUnderline = StyleConstants.isUnderline(el.getAttributes()); 88: SimpleAttributeSet atts = new SimpleAttributeSet(); 89: StyleConstants.setUnderline(atts, ! isUnderline); 90: setCharacterAttributes(editor, atts, false); 91: } 92: } 93: 94: /** 95: * Toggles the italic attribute for the selected text. 96: */ 97: public static class ItalicAction extends StyledEditorKit.StyledTextAction 98: { 99: /** 100: * Creates an instance of <code>ItalicAction</code>. 101: */ 102: public ItalicAction() 103: { 104: super("TODO"); // TODO: Figure out correct name of this Action. 105: } 106: 107: /** 108: * Performs the action. 109: * 110: * @param event the <code>ActionEvent</code> that describes the action 111: */ 112: public void actionPerformed(ActionEvent event) 113: { 114: JEditorPane editor = getEditor(event); 115: StyledDocument doc = getStyledDocument(editor); 116: Element el = doc.getCharacterElement(editor.getSelectionStart()); 117: boolean isItalic = StyleConstants.isItalic(el.getAttributes()); 118: SimpleAttributeSet atts = new SimpleAttributeSet(); 119: StyleConstants.setItalic(atts, ! isItalic); 120: setCharacterAttributes(editor, atts, false); 121: } 122: } 123: 124: /** 125: * Toggles the bold attribute for the selected text. 126: */ 127: public static class BoldAction extends StyledEditorKit.StyledTextAction 128: { 129: /** 130: * Creates an instance of <code>BoldAction</code>. 131: */ 132: public BoldAction() 133: { 134: super("TODO"); // TODO: Figure out correct name of this Action. 135: } 136: 137: /** 138: * Performs the action. 139: * 140: * @param event the <code>ActionEvent</code> that describes the action 141: */ 142: public void actionPerformed(ActionEvent event) 143: { 144: JEditorPane editor = getEditor(event); 145: StyledDocument doc = getStyledDocument(editor); 146: Element el = doc.getCharacterElement(editor.getSelectionStart()); 147: boolean isBold = StyleConstants.isBold(el.getAttributes()); 148: SimpleAttributeSet atts = new SimpleAttributeSet(); 149: StyleConstants.setItalic(atts, ! isBold); 150: setCharacterAttributes(editor, atts, false); 151: } 152: } 153: 154: /** 155: * Sets the alignment attribute on the selected text. 156: */ 157: public static class AlignmentAction extends StyledEditorKit.StyledTextAction 158: { 159: /** 160: * The aligment to set. 161: */ 162: private int a; 163: 164: /** 165: * Creates a new instance of <code>AlignmentAction</code> to set the 166: * alignment to <code>a</code>. 167: * 168: * @param nm the name of the Action 169: * @param a the alignment to set 170: */ 171: public AlignmentAction(String nm, int a) 172: { 173: super(nm); 174: this.a = a; 175: } 176: 177: /** 178: * Performs the action. 179: * 180: * @param event the <code>ActionEvent</code> that describes the action 181: */ 182: public void actionPerformed(ActionEvent event) 183: { 184: SimpleAttributeSet atts = new SimpleAttributeSet(); 185: StyleConstants.setAlignment(atts, a); 186: setParagraphAttributes(getEditor(event), atts, false); 187: } 188: } 189: 190: /** 191: * Sets the foreground color attribute on the selected text. 192: */ 193: public static class ForegroundAction extends StyledEditorKit.StyledTextAction 194: { 195: /** 196: * The foreground color to set. 197: */ 198: private Color fg; 199: 200: /** 201: * Creates a new instance of <code>ForegroundAction</code> to set the 202: * foreground color to <code>fg</code>. 203: * 204: * @param nm the name of the Action 205: * @param fg the foreground color to set 206: */ 207: public ForegroundAction(String nm, Color fg) 208: { 209: super(nm); 210: this.fg = fg; 211: } 212: 213: /** 214: * Performs the action. 215: * 216: * @param event the <code>ActionEvent</code> that describes the action 217: */ 218: public void actionPerformed(ActionEvent event) 219: { 220: SimpleAttributeSet atts = new SimpleAttributeSet(); 221: StyleConstants.setForeground(atts, fg); 222: setCharacterAttributes(getEditor(event), atts, false); 223: } 224: } 225: 226: /** 227: * Sets the font size attribute on the selected text. 228: */ 229: public static class FontSizeAction extends StyledEditorKit.StyledTextAction 230: { 231: /** 232: * The font size to set. 233: */ 234: private int size; 235: 236: /** 237: * Creates a new instance of <code>FontSizeAction</code> to set the 238: * font size to <code>size</code>. 239: * 240: * @param nm the name of the Action 241: * @param size the font size to set 242: */ 243: public FontSizeAction(String nm, int size) 244: { 245: super(nm); 246: this.size = size; 247: } 248: 249: /** 250: * Performs the action. 251: * 252: * @param event the <code>ActionEvent</code> that describes the action 253: */ 254: public void actionPerformed(ActionEvent event) 255: { 256: SimpleAttributeSet atts = new SimpleAttributeSet(); 257: StyleConstants.setFontSize(atts, size); 258: setCharacterAttributes(getEditor(event), atts, false); 259: } 260: } 261: 262: /** 263: * Sets the font family attribute on the selected text. 264: */ 265: public static class FontFamilyAction extends StyledEditorKit.StyledTextAction 266: { 267: /** 268: * The font family to set. 269: */ 270: private String family; 271: 272: /** 273: * Creates a new instance of <code>FontFamilyAction</code> to set the 274: * font family to <code>family</code>. 275: * 276: * @param nm the name of the Action 277: * @param family the font family to set 278: */ 279: public FontFamilyAction(String nm, String family) 280: { 281: super(nm); 282: this.family = family; 283: } 284: 285: /** 286: * Performs the action. 287: * 288: * @param event the <code>ActionEvent</code> that describes the action 289: */ 290: public void actionPerformed(ActionEvent event) 291: { 292: SimpleAttributeSet atts = new SimpleAttributeSet(); 293: StyleConstants.setFontFamily(atts, family); 294: setCharacterAttributes(getEditor(event), atts, false); 295: } 296: } 297: 298: /** 299: * The abstract superclass of all styled TextActions. This class 300: * provides some useful methods to manipulate the text attributes. 301: */ 302: public abstract static class StyledTextAction extends TextAction 303: { 304: /** 305: * Creates a new instance of <code>StyledTextAction</code>. 306: * 307: * @param nm the name of the <code>StyledTextAction</code> 308: */ 309: public StyledTextAction(String nm) 310: { 311: super(nm); 312: } 313: 314: /** 315: * Returns the <code>JEditorPane</code> component from which the 316: * <code>ActionEvent</code> originated. 317: * 318: * @param event the <code>ActionEvent</code> 319: * @return the <code>JEditorPane</code> component from which the 320: * <code>ActionEvent</code> originated 321: */ 322: protected final JEditorPane getEditor(ActionEvent event) 323: { 324: return (JEditorPane) getTextComponent(event); 325: } 326: 327: /** 328: * Sets the specified character attributes on the currently selected 329: * text of <code>editor</code>. If <code>editor</code> does not have 330: * a selection, then the attributes are used as input attributes 331: * for newly inserted content. 332: * 333: * @param editor the <code>JEditorPane</code> component 334: * @param atts the text attributes to set 335: * @param replace if <code>true</code> the current attributes of the 336: * selection are replaces, otherwise they are merged 337: */ 338: protected final void setCharacterAttributes(JEditorPane editor, 339: AttributeSet atts, 340: boolean replace) 341: { 342: Document doc = editor.getDocument(); 343: if (doc instanceof StyledDocument) 344: { 345: StyledDocument styleDoc = (StyledDocument) editor.getDocument(); 346: EditorKit kit = editor.getEditorKit(); 347: if (!(kit instanceof StyledEditorKit)) 348: { 349: StyledEditorKit styleKit = (StyledEditorKit) kit; 350: int start = editor.getSelectionStart(); 351: int end = editor.getSelectionEnd(); 352: int dot = editor.getCaret().getDot(); 353: if (start == dot && end == dot) 354: { 355: // If there is no selection, then we only update the 356: // input attributes. 357: MutableAttributeSet inputAttributes = 358: styleKit.getInputAttributes(); 359: inputAttributes.addAttributes(atts); 360: } 361: else 362: styleDoc.setCharacterAttributes(start, end, atts, replace); 363: } 364: else 365: throw new AssertionError("The EditorKit for StyledTextActions " 366: + "is expected to be a StyledEditorKit"); 367: } 368: else 369: throw new AssertionError("The Document for StyledTextActions is " 370: + "expected to be a StyledDocument."); 371: } 372: 373: /** 374: * Returns the {@link StyledDocument} that is used by <code>editor</code>. 375: * 376: * @param editor the <code>JEditorPane</code> from which to get the 377: * <code>StyledDocument</code> 378: * 379: * @return the {@link StyledDocument} that is used by <code>editor</code> 380: */ 381: protected final StyledDocument getStyledDocument(JEditorPane editor) 382: { 383: Document doc = editor.getDocument(); 384: if (!(doc instanceof StyledDocument)) 385: throw new AssertionError("The Document for StyledEditorKits is " 386: + "expected to be a StyledDocument."); 387: 388: return (StyledDocument) doc; 389: } 390: 391: /** 392: * Returns the {@link StyledEditorKit} that is used by <code>editor</code>. 393: * 394: * @param editor the <code>JEditorPane</code> from which to get the 395: * <code>StyledEditorKit</code> 396: * 397: * @return the {@link StyledEditorKit} that is used by <code>editor</code> 398: */ 399: protected final StyledEditorKit getStyledEditorKit(JEditorPane editor) 400: { 401: EditorKit kit = editor.getEditorKit(); 402: if (!(kit instanceof StyledEditorKit)) 403: throw new AssertionError("The EditorKit for StyledDocuments is " 404: + "expected to be a StyledEditorKit."); 405: 406: return (StyledEditorKit) kit; 407: } 408: 409: /** 410: * Sets the specified character attributes on the paragraph that 411: * contains the currently selected 412: * text of <code>editor</code>. If <code>editor</code> does not have 413: * a selection, then the attributes are set on the paragraph that 414: * contains the current caret position. 415: * 416: * @param editor the <code>JEditorPane</code> component 417: * @param atts the text attributes to set 418: * @param replace if <code>true</code> the current attributes of the 419: * selection are replaces, otherwise they are merged 420: */ 421: protected final void setParagraphAttributes(JEditorPane editor, 422: AttributeSet atts, 423: boolean replace) 424: { 425: Document doc = editor.getDocument(); 426: if (doc instanceof StyledDocument) 427: { 428: StyledDocument styleDoc = (StyledDocument) editor.getDocument(); 429: EditorKit kit = editor.getEditorKit(); 430: if (!(kit instanceof StyledEditorKit)) 431: { 432: StyledEditorKit styleKit = (StyledEditorKit) kit; 433: int start = editor.getSelectionStart(); 434: int end = editor.getSelectionEnd(); 435: int dot = editor.getCaret().getDot(); 436: if (start == dot && end == dot) 437: { 438: // If there is no selection, then we only update the 439: // input attributes. 440: MutableAttributeSet inputAttributes = 441: styleKit.getInputAttributes(); 442: inputAttributes.addAttributes(atts); 443: } 444: else 445: styleDoc.setParagraphAttributes(start, end, atts, replace); 446: } 447: else 448: throw new AssertionError("The EditorKit for StyledTextActions " 449: + "is expected to be a StyledEditorKit"); 450: } 451: else 452: throw new AssertionError("The Document for StyledTextActions is " 453: + "expected to be a StyledDocument."); 454: } 455: } 456: 457: /** 458: * A {@link ViewFactory} that is able to create {@link View}s for 459: * the <code>Element</code>s that are supported by 460: * <code>StyledEditorKit</code>, namely the following types of Elements: 461: * 462: * <ul> 463: * <li>{@link AbstractDocument.ContentElementName}</li> 464: * <li>{@link AbstractDocument.ParagraphElementName}</li> 465: * <li>{@link AbstractDocument.SectionElementName}</li> 466: * <li>{@link StyleContext.ComponentElementName}</li> 467: * <li>{@link StyleContext.IconElementName}</li> 468: * </ul> 469: */ 470: static class StyledViewFactory 471: implements ViewFactory 472: { 473: /** 474: * Creates a {@link View} for the specified <code>Element</code>. 475: * 476: * @param element the <code>Element</code> to create a <code>View</code> 477: * for 478: * @return the <code>View</code> for the specified <code>Element</code> 479: * or <code>null</code> if the type of <code>element</code> is 480: * not supported 481: */ 482: public View create(Element element) 483: { 484: String name = element.getName(); 485: View view = null; 486: if (name.equals(AbstractDocument.ContentElementName)) 487: view = new LabelView(element); 488: else if (name.equals(AbstractDocument.ParagraphElementName)) 489: view = new ParagraphView(element); 490: else if (name.equals(AbstractDocument.SectionElementName)) 491: view = new BoxView(element, View.Y_AXIS); 492: else if (name.equals(StyleConstants.ComponentElementName)) 493: view = new ComponentView(element); 494: else if (name.equals(StyleConstants.IconElementName)) 495: view = new IconView(element); 496: else 497: throw new AssertionError("Unknown Element type: " 498: + element.getClass().getName() + " : " 499: + name); 500: return view; 501: } 502: } 503: 504: /** 505: * Keeps track of the caret position and updates the currentRun 506: * <code>Element</code> and the <code>inputAttributes</code>. 507: */ 508: class CaretTracker 509: implements CaretListener 510: { 511: /** 512: * Notifies an update of the caret position. 513: * 514: * @param ev the event for the caret update 515: */ 516: public void caretUpdate(CaretEvent ev) 517: { 518: Object source = ev.getSource(); 519: if (!(source instanceof JTextComponent)) 520: throw new AssertionError("CaretEvents are expected to come from a" 521: + "JTextComponent."); 522: 523: JTextComponent text = (JTextComponent) source; 524: Document doc = text.getDocument(); 525: if (!(doc instanceof StyledDocument)) 526: throw new AssertionError("The Document used by StyledEditorKits is" 527: + "expected to be a StyledDocument"); 528: 529: StyledDocument styleDoc = (StyledDocument) doc; 530: currentRun = styleDoc.getCharacterElement(ev.getDot()); 531: createInputAttributes(currentRun, inputAttributes); 532: } 533: } 534: 535: /** 536: * Stores the <code>Element</code> at the current caret position. This 537: * is updated by {@link CaretTracker}. 538: */ 539: Element currentRun; 540: 541: /** 542: * The current input attributes. This is updated by {@link CaretTracker}. 543: */ 544: MutableAttributeSet inputAttributes; 545: 546: /** 547: * The CaretTracker that keeps track of the current input attributes, and 548: * the current character run Element. 549: */ 550: CaretTracker caretTracker; 551: 552: /** 553: * The ViewFactory for StyledEditorKits. 554: */ 555: StyledViewFactory viewFactory; 556: 557: /** 558: * Creates a new instance of <code>StyledEditorKit</code>. 559: */ 560: public StyledEditorKit() 561: { 562: inputAttributes = new SimpleAttributeSet(); 563: } 564: 565: /** 566: * Creates an exact copy of this <code>StyledEditorKit</code>. 567: * 568: * @return an exact copy of this <code>StyledEditorKit</code> 569: */ 570: public Object clone() 571: { 572: StyledEditorKit clone = (StyledEditorKit) super.clone(); 573: // FIXME: Investigate which fields must be copied. 574: return clone; 575: } 576: 577: /** 578: * Returns the <code>Action</code>s supported by this {@link EditorKit}. 579: * This includes the {@link BoldAction}, {@link ItalicAction} and 580: * {@link UnderlineAction} as well as the <code>Action</code>s supported 581: * by {@link DefaultEditorKit}. 582: * 583: * The other <code>Action</code>s of <code>StyledEditorKit</code> are not 584: * returned here, since they require a parameter and thus custom 585: * instantiation. 586: * 587: * @return the <code>Action</code>s supported by this {@link EditorKit} 588: */ 589: public Action[] getActions() 590: { 591: Action[] actions1 = super.getActions(); 592: Action[] myActions = new Action[] { new BoldAction(), new ItalicAction(), 593: new UnderlineAction() }; 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 StyleContext.ComponentElementName}</li> 674: * <li>{@link StyleContext.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: AttributeSet atts = element.getAttributes(); 704: set.removeAttributes(set); 705: // FIXME: Filter out component, icon and element name attributes. 706: set.addAttributes(atts); 707: } 708: }
GNU Classpath (0.18) |