001/* StyledEditorKit.java -- 002 Copyright (C) 2002, 2004 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.text; 040 041import java.awt.Color; 042import java.awt.event.ActionEvent; 043 044import javax.swing.Action; 045import javax.swing.JEditorPane; 046import javax.swing.event.CaretEvent; 047import javax.swing.event.CaretListener; 048 049/** 050 * An {@link EditorKit} that supports editing styled text. 051 * 052 * @author Andrew Selkirk 053 * @author Roman Kennke (roman@kennke.org) 054 */ 055public class StyledEditorKit extends DefaultEditorKit 056{ 057 /** The serialVersionUID. */ 058 private static final long serialVersionUID = 7002391892985555948L; 059 060 /** 061 * Toggles the underline attribute for the selected text. 062 */ 063 public static class UnderlineAction extends StyledEditorKit.StyledTextAction 064 { 065 /** 066 * Creates an instance of <code>UnderlineAction</code>. 067 */ 068 public UnderlineAction() 069 { 070 super("font-underline"); 071 } 072 073 /** 074 * Performs the action. 075 * 076 * @param event the <code>ActionEvent</code> that describes the action 077 */ 078 public void actionPerformed(ActionEvent event) 079 { 080 JEditorPane editor = getEditor(event); 081 StyledDocument doc = getStyledDocument(editor); 082 Element el = doc.getCharacterElement(editor.getSelectionStart()); 083 boolean isUnderline = StyleConstants.isUnderline(el.getAttributes()); 084 SimpleAttributeSet atts = new SimpleAttributeSet(); 085 StyleConstants.setUnderline(atts, ! isUnderline); 086 setCharacterAttributes(editor, atts, false); 087 } 088 } 089 090 /** 091 * Toggles the italic attribute for the selected text. 092 */ 093 public static class ItalicAction extends StyledEditorKit.StyledTextAction 094 { 095 /** 096 * Creates an instance of <code>ItalicAction</code>. 097 */ 098 public ItalicAction() 099 { 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}