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}