001/* JTextPane.java -- A powerful text widget supporting styled text
002   Copyright (C) 2002, 2004, 2005 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;
040
041import java.awt.Component;
042
043import javax.swing.text.AbstractDocument;
044import javax.swing.text.AttributeSet;
045import javax.swing.text.BadLocationException;
046import javax.swing.text.Caret;
047import javax.swing.text.Document;
048import javax.swing.text.EditorKit;
049import javax.swing.text.Element;
050import javax.swing.text.MutableAttributeSet;
051import javax.swing.text.SimpleAttributeSet;
052import javax.swing.text.Style;
053import javax.swing.text.StyleConstants;
054import javax.swing.text.StyledDocument;
055import 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 */
065public 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}