001    /* StyledEditorKit.java --
002       Copyright (C) 2002, 2004 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import java.awt.Color;
042    import java.awt.event.ActionEvent;
043    
044    import javax.swing.Action;
045    import javax.swing.JEditorPane;
046    import javax.swing.event.CaretEvent;
047    import 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     */
055    public 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    }