001    /* JTextPane.java -- A powerful text widget supporting styled text
002       Copyright (C) 2002, 2004, 2005 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;
040    
041    import java.awt.Component;
042    
043    import javax.swing.text.AbstractDocument;
044    import javax.swing.text.AttributeSet;
045    import javax.swing.text.BadLocationException;
046    import javax.swing.text.Caret;
047    import javax.swing.text.Document;
048    import javax.swing.text.EditorKit;
049    import javax.swing.text.Element;
050    import javax.swing.text.MutableAttributeSet;
051    import javax.swing.text.SimpleAttributeSet;
052    import javax.swing.text.Style;
053    import javax.swing.text.StyleConstants;
054    import javax.swing.text.StyledDocument;
055    import javax.swing.text.StyledEditorKit;
056    
057    /**
058     * A powerful text component that supports styled content as well as
059     * embedding images and components. It is entirely based on a
060     * {@link StyledDocument} content model and a {@link StyledEditorKit}.
061     *
062     * @author Roman Kennke (roman@kennke.org)
063     * @author Andrew Selkirk
064     */
065    public class JTextPane
066      extends JEditorPane
067    {
068      /**
069       * Creates a new <code>JTextPane</code> with a <code>null</code> document.
070       */
071      public JTextPane()
072      {
073        super();
074      }
075    
076      /**
077       * Creates a new <code>JTextPane</code> and sets the specified
078       * <code>document</code>.
079       *
080       * @param document the content model to use
081       */
082      public JTextPane(StyledDocument document)
083      {
084        this();
085        setStyledDocument(document);
086      }
087    
088      /**
089       * Returns the UI class ID. This is <code>TextPaneUI</code>.
090       *
091       * @return <code>TextPaneUI</code>
092       */
093      public String getUIClassID()
094      {
095        return "TextPaneUI";
096      }
097    
098      /**
099       * Sets the content model for this <code>JTextPane</code>.
100       * <code>JTextPane</code> can only be used with {@link StyledDocument}s,
101       * if you try to set a different type of <code>Document</code>, an
102       * <code>IllegalArgumentException</code> is thrown.
103       *
104       * @param document the content model to set
105       *
106       * @throws IllegalArgumentException if <code>document</code> is not an
107       *         instance of <code>StyledDocument</code>
108       *
109       * @see #setStyledDocument
110       */
111      public void setDocument(Document document)
112      {
113        if (document != null && !(document instanceof StyledDocument))
114          throw new IllegalArgumentException
115            ("JTextPane can only handle StyledDocuments");
116    
117        setStyledDocument((StyledDocument) document);
118      }
119    
120      /**
121       * Returns the {@link StyledDocument} that is the content model for
122       * this <code>JTextPane</code>. This is a typed wrapper for
123       * {@link #getDocument()}.
124       *
125       * @return the content model of this <code>JTextPane</code>
126       */
127      public StyledDocument getStyledDocument()
128      {
129        return (StyledDocument) super.getDocument();
130      }
131    
132      /**
133       * Sets the content model for this <code>JTextPane</code>.
134       *
135       * @param document the content model to set
136       */
137      public void setStyledDocument(StyledDocument document)
138      {
139        super.setDocument(document);
140      }
141    
142      /**
143       * Replaces the currently selected text with the specified
144       * <code>content</code>. If there is no selected text, this results
145       * in a simple insertion at the current caret position. If there is
146       * no <code>content</code> specified, this results in the selection
147       * beeing deleted.
148       *
149       * @param content the text with which the selection is replaced
150       */
151      public void replaceSelection(String content)
152      {
153        Caret caret = getCaret();
154        StyledDocument doc = getStyledDocument();
155        AttributeSet a = getInputAttributes().copyAttributes();
156        if (doc == null)
157          return;
158    
159        int dot = caret.getDot();
160        int mark = caret.getMark();
161    
162        int p0 = Math.min (dot, mark);
163        int p1 = Math.max (dot, mark);
164    
165        try
166          {
167            if (doc instanceof AbstractDocument)
168              ((AbstractDocument)doc).replace(p0, p1 - p0, content, a);
169            else
170              {
171                // Remove selected text.
172                if (dot != mark)
173                  doc.remove(p0, p1 - p0);
174                // Insert new text.
175                if (content != null && content.length() > 0)
176                  doc.insertString(p0, content, a);
177              }
178          }
179        catch (BadLocationException e)
180          {
181            throw new AssertionError
182              ("No BadLocationException should be thrown here");      
183          }
184      }
185    
186      /**
187       * Inserts an AWT or Swing component into the text at the current caret
188       * position.
189       *
190       * @param component the component to be inserted
191       */
192      public void insertComponent(Component component)
193      {
194        SimpleAttributeSet atts = new SimpleAttributeSet();
195        atts.addAttribute(StyleConstants.ComponentAttribute, component);
196        atts.addAttribute(StyleConstants.NameAttribute,
197                          StyleConstants.ComponentElementName);
198        try
199          {
200            getDocument().insertString(getCaret().getDot(), " ", atts);
201          }
202        catch (BadLocationException ex)
203          {
204            AssertionError err = new AssertionError("Unexpected bad location");
205            err.initCause(ex);
206            throw err;
207          }
208      }
209    
210      /**
211       * Inserts an <code>Icon</code> into the text at the current caret position.
212       *
213       * @param icon the <code>Icon</code> to be inserted
214       */
215      public void insertIcon(Icon icon)
216      {
217        MutableAttributeSet inputAtts = getInputAttributes();
218        inputAtts.removeAttributes(inputAtts);
219        StyleConstants.setIcon(inputAtts, icon);
220        replaceSelection(" ");
221        inputAtts.removeAttributes(inputAtts);
222      }
223    
224      /**
225       * Adds a style into the style hierarchy. Unspecified style attributes
226       * can be resolved in the <code>parent</code> style, if one is specified.
227       *
228       * While it is legal to add nameless styles (<code>nm == null</code),
229       * you must be aware that the client application is then responsible
230       * for managing the style hierarchy, since unnamed styles cannot be
231       * looked up by their name.
232       *
233       * @param nm the name of the style or <code>null</code> if the style should
234       *           be unnamed
235       * @param parent the parent in which unspecified style attributes are
236       *           resolved, or <code>null</code> if that is not necessary
237       *
238       * @return the newly created <code>Style</code>
239       */
240      public Style addStyle(String nm, Style parent)
241      {
242        return getStyledDocument().addStyle(nm, parent);
243      }
244    
245      /**
246       * Removes a named <code>Style</code> from the style hierarchy.
247       *
248       * @param nm the name of the <code>Style</code> to be removed
249       */
250      public void removeStyle(String nm)
251      {
252        getStyledDocument().removeStyle(nm);
253      }
254    
255      /**
256       * Looks up and returns a named <code>Style</code>.
257       *
258       * @param nm the name of the <code>Style</code>
259       *
260       * @return the found <code>Style</code> of <code>null</code> if no such
261       *         <code>Style</code> exists
262       */
263      public Style getStyle(String nm)
264      {
265        return getStyledDocument().getStyle(nm);
266      }
267    
268      /**
269       * Returns the logical style of the paragraph at the current caret position.
270       *
271       * @return the logical style of the paragraph at the current caret position
272       */
273      public Style getLogicalStyle()
274      {
275        return getStyledDocument().getLogicalStyle(getCaretPosition());
276      }
277    
278      /**
279       * Sets the logical style for the paragraph at the current caret position.
280       *
281       * @param style the style to set for the current paragraph
282       */
283      public void setLogicalStyle(Style style)
284      {
285        getStyledDocument().setLogicalStyle(getCaretPosition(), style);
286      }
287    
288      /**
289       * Returns the text attributes for the character at the current caret
290       * position.
291       *
292       * @return the text attributes for the character at the current caret
293       *         position
294       */
295      public AttributeSet getCharacterAttributes()
296      {
297        StyledDocument doc = getStyledDocument();
298        Element el = doc.getCharacterElement(getCaretPosition());
299        return el.getAttributes();
300      }
301    
302      /**
303       * Sets text attributes for the current selection. If there is no selection
304       * the text attributes are applied to newly inserted text
305       *
306       * @param attribute the text attributes to set
307       * @param replace if <code>true</code>, the attributes of the current
308       *     selection are overridden, otherwise they are merged
309       *
310       * @see #getInputAttributes
311       */
312      public void setCharacterAttributes(AttributeSet attribute,
313                                         boolean replace)
314      {
315        int dot = getCaret().getDot();
316        int start = getSelectionStart();
317        int end = getSelectionEnd();
318        if (start == dot && end == dot)
319          // There is no selection, update insertAttributes instead
320          {
321            MutableAttributeSet inputAttributes =
322              getStyledEditorKit().getInputAttributes();
323            if (replace)
324              inputAttributes.removeAttributes(inputAttributes);
325            inputAttributes.addAttributes(attribute);
326          }
327        else
328          getStyledDocument().setCharacterAttributes(start, end - start, attribute,
329                                                     replace);
330      }
331    
332      /**
333       * Returns the text attributes of the paragraph at the current caret
334       * position.
335       *
336       * @return the attributes of the paragraph at the current caret position
337       */
338      public AttributeSet getParagraphAttributes()
339      {
340        StyledDocument doc = getStyledDocument();
341        Element el = doc.getParagraphElement(getCaretPosition());
342        return el.getAttributes();
343      }
344    
345      /**
346       * Sets text attributes for the paragraph at the current selection.
347       * If there is no selection the text attributes are applied to
348       * the paragraph at the current caret position.
349       *
350       * @param attribute the text attributes to set
351       * @param replace if <code>true</code>, the attributes of the current
352       *     selection are overridden, otherwise they are merged
353       */
354      public void setParagraphAttributes(AttributeSet attribute,
355                                         boolean replace)
356      {
357        // TODO
358      }
359    
360      /**
361       * Returns the attributes that are applied to newly inserted text.
362       * This is a {@link MutableAttributeSet}, so you can easily modify these
363       * attributes.
364       *
365       * @return the attributes that are applied to newly inserted text
366       */
367      public MutableAttributeSet getInputAttributes()
368      {
369        return getStyledEditorKit().getInputAttributes();
370      }
371    
372      /**
373       * Returns the {@link StyledEditorKit} that is currently used by this
374       * <code>JTextPane</code>.
375       *
376       * @return the current <code>StyledEditorKit</code> of this
377       *         <code>JTextPane</code>
378       */
379      protected final StyledEditorKit getStyledEditorKit()
380      {
381        return (StyledEditorKit) getEditorKit();
382      }
383    
384      /**
385       * Creates the default {@link EditorKit} that is used in
386       * <code>JTextPane</code>s. This is an instance of {@link StyledEditorKit}.
387       *
388       * @return the default {@link EditorKit} that is used in
389       *         <code>JTextPane</code>s
390       */
391      protected EditorKit createDefaultEditorKit()
392      {
393        return new StyledEditorKit();
394      }
395    
396      /**
397       * Sets the {@link EditorKit} to use for this <code>JTextPane</code>.
398       * <code>JTextPane</code>s can only handle {@link StyledEditorKit}s,
399       * if client programs try to set a different type of <code>EditorKit</code>
400       * then an IllegalArgumentException is thrown
401       *
402       * @param editor the <code>EditorKit</code> to set
403       *
404       * @throws IllegalArgumentException if <code>editor</code> is no
405       *         <code>StyledEditorKit</code>
406       */
407      public final void setEditorKit(EditorKit editor)
408      {
409        if (!(editor instanceof StyledEditorKit))
410          throw new IllegalArgumentException
411            ("JTextPanes can only handle StyledEditorKits");
412        super.setEditorKit(editor);
413      }
414    
415      /**
416       * Returns a param string that can be used for debugging.
417       *
418       * @return a param string that can be used for debugging.
419       */
420      protected String paramString()
421      {
422        return super.paramString(); // TODO
423      }
424    }