/* StyledEditorKit.java --
Copyright (C) 2002, 2004 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package javax.swing.text;
import java.awt.Color;
import java.awt.event.ActionEvent;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
/**
* An {@link EditorKit} that supports editing styled text.
*
* @author Andrew Selkirk
* @author Roman Kennke (roman@kennke.org)
*/
public class StyledEditorKit extends DefaultEditorKit
{
/** The serialVersionUID. */
private static final long serialVersionUID = 7002391892985555948L;
/**
* Toggles the underline attribute for the selected text.
*/
public static class UnderlineAction extends StyledEditorKit.StyledTextAction
{
/**
* Creates an instance of UnderlineAction
.
*/
public UnderlineAction()
{
super("font-underline");
}
/**
* Performs the action.
*
* @param event the ActionEvent
that describes the action
*/
public void actionPerformed(ActionEvent event)
{
JEditorPane editor = getEditor(event);
StyledDocument doc = getStyledDocument(editor);
Element el = doc.getCharacterElement(editor.getSelectionStart());
boolean isUnderline = StyleConstants.isUnderline(el.getAttributes());
SimpleAttributeSet atts = new SimpleAttributeSet();
StyleConstants.setUnderline(atts, ! isUnderline);
setCharacterAttributes(editor, atts, false);
}
}
/**
* Toggles the italic attribute for the selected text.
*/
public static class ItalicAction extends StyledEditorKit.StyledTextAction
{
/**
* Creates an instance of ItalicAction
.
*/
public ItalicAction()
{
super("font-italic");
}
/**
* Performs the action.
*
* @param event the ActionEvent
that describes the action
*/
public void actionPerformed(ActionEvent event)
{
JEditorPane editor = getEditor(event);
StyledDocument doc = getStyledDocument(editor);
Element el = doc.getCharacterElement(editor.getSelectionStart());
boolean isItalic = StyleConstants.isItalic(el.getAttributes());
SimpleAttributeSet atts = new SimpleAttributeSet();
StyleConstants.setItalic(atts, ! isItalic);
setCharacterAttributes(editor, atts, false);
}
}
/**
* Toggles the bold attribute for the selected text.
*/
public static class BoldAction extends StyledEditorKit.StyledTextAction
{
/**
* Creates an instance of BoldAction
.
*/
public BoldAction()
{
super("font-bold");
}
/**
* Performs the action.
*
* @param event the ActionEvent
that describes the action
*/
public void actionPerformed(ActionEvent event)
{
JEditorPane editor = getEditor(event);
StyledDocument doc = getStyledDocument(editor);
Element el = doc.getCharacterElement(editor.getSelectionStart());
boolean isBold = StyleConstants.isBold(el.getAttributes());
SimpleAttributeSet atts = new SimpleAttributeSet();
StyleConstants.setBold(atts, ! isBold);
setCharacterAttributes(editor, atts, false);
}
}
/**
* Sets the alignment attribute on the selected text.
*/
public static class AlignmentAction extends StyledEditorKit.StyledTextAction
{
/**
* The aligment to set.
*/
private int a;
/**
* Creates a new instance of AlignmentAction
to set the
* alignment to a
.
*
* @param nm the name of the Action
* @param a the alignment to set
*/
public AlignmentAction(String nm, int a)
{
super(nm);
this.a = a;
}
/**
* Performs the action.
*
* @param event the ActionEvent
that describes the action
*/
public void actionPerformed(ActionEvent event)
{
SimpleAttributeSet atts = new SimpleAttributeSet();
StyleConstants.setAlignment(atts, a);
setParagraphAttributes(getEditor(event), atts, false);
}
}
/**
* Sets the foreground color attribute on the selected text.
*/
public static class ForegroundAction extends StyledEditorKit.StyledTextAction
{
/**
* The foreground color to set.
*/
private Color fg;
/**
* Creates a new instance of ForegroundAction
to set the
* foreground color to fg
.
*
* @param nm the name of the Action
* @param fg the foreground color to set
*/
public ForegroundAction(String nm, Color fg)
{
super(nm);
this.fg = fg;
}
/**
* Performs the action.
*
* @param event the ActionEvent
that describes the action
*/
public void actionPerformed(ActionEvent event)
{
SimpleAttributeSet atts = new SimpleAttributeSet();
StyleConstants.setForeground(atts, fg);
setCharacterAttributes(getEditor(event), atts, false);
}
}
/**
* Sets the font size attribute on the selected text.
*/
public static class FontSizeAction extends StyledEditorKit.StyledTextAction
{
/**
* The font size to set.
*/
private int size;
/**
* Creates a new instance of FontSizeAction
to set the
* font size to size
.
*
* @param nm the name of the Action
* @param size the font size to set
*/
public FontSizeAction(String nm, int size)
{
super(nm);
this.size = size;
}
/**
* Performs the action.
*
* @param event the ActionEvent
that describes the action
*/
public void actionPerformed(ActionEvent event)
{
SimpleAttributeSet atts = new SimpleAttributeSet();
StyleConstants.setFontSize(atts, size);
setCharacterAttributes(getEditor(event), atts, false);
}
}
/**
* Sets the font family attribute on the selected text.
*/
public static class FontFamilyAction extends StyledEditorKit.StyledTextAction
{
/**
* The font family to set.
*/
private String family;
/**
* Creates a new instance of FontFamilyAction
to set the
* font family to family
.
*
* @param nm the name of the Action
* @param family the font family to set
*/
public FontFamilyAction(String nm, String family)
{
super(nm);
this.family = family;
}
/**
* Performs the action.
*
* @param event the ActionEvent
that describes the action
*/
public void actionPerformed(ActionEvent event)
{
SimpleAttributeSet atts = new SimpleAttributeSet();
StyleConstants.setFontFamily(atts, family);
setCharacterAttributes(getEditor(event), atts, false);
}
}
/**
* The abstract superclass of all styled TextActions. This class
* provides some useful methods to manipulate the text attributes.
*/
public abstract static class StyledTextAction extends TextAction
{
/**
* Creates a new instance of StyledTextAction
.
*
* @param nm the name of the StyledTextAction
*/
public StyledTextAction(String nm)
{
super(nm);
}
/**
* Returns the JEditorPane
component from which the
* ActionEvent
originated.
*
* @param event the ActionEvent
* @return the JEditorPane
component from which the
* ActionEvent
originated
*/
protected final JEditorPane getEditor(ActionEvent event)
{
return (JEditorPane) getTextComponent(event);
}
/**
* Sets the specified character attributes on the currently selected
* text of editor
. If editor
does not have
* a selection, then the attributes are used as input attributes
* for newly inserted content.
*
* @param editor the JEditorPane
component
* @param atts the text attributes to set
* @param replace if true
the current attributes of the
* selection are replaces, otherwise they are merged
*/
protected final void setCharacterAttributes(JEditorPane editor,
AttributeSet atts,
boolean replace)
{
int p0 = editor.getSelectionStart();
int p1 = editor.getSelectionEnd();
if (p0 != p1)
{
StyledDocument doc = getStyledDocument(editor);
doc.setCharacterAttributes(p0, p1 - p0, atts, replace);
}
// Update input attributes.
StyledEditorKit kit = getStyledEditorKit(editor);
MutableAttributeSet inputAtts = kit.getInputAttributes();
if (replace)
{
inputAtts.removeAttributes(inputAtts);
}
inputAtts.addAttributes(atts);
}
/**
* Returns the {@link StyledDocument} that is used by editor
.
*
* @param editor the JEditorPane
from which to get the
* StyledDocument
*
* @return the {@link StyledDocument} that is used by editor
*/
protected final StyledDocument getStyledDocument(JEditorPane editor)
{
Document doc = editor.getDocument();
if (!(doc instanceof StyledDocument))
throw new AssertionError("The Document for StyledEditorKits is "
+ "expected to be a StyledDocument.");
return (StyledDocument) doc;
}
/**
* Returns the {@link StyledEditorKit} that is used by editor
.
*
* @param editor the JEditorPane
from which to get the
* StyledEditorKit
*
* @return the {@link StyledEditorKit} that is used by editor
*/
protected final StyledEditorKit getStyledEditorKit(JEditorPane editor)
{
EditorKit kit = editor.getEditorKit();
if (!(kit instanceof StyledEditorKit))
throw new AssertionError("The EditorKit for StyledDocuments is "
+ "expected to be a StyledEditorKit.");
return (StyledEditorKit) kit;
}
/**
* Sets the specified character attributes on the paragraph that
* contains the currently selected
* text of editor
. If editor
does not have
* a selection, then the attributes are set on the paragraph that
* contains the current caret position.
*
* @param editor the JEditorPane
component
* @param atts the text attributes to set
* @param replace if true
the current attributes of the
* selection are replaces, otherwise they are merged
*/
protected final void setParagraphAttributes(JEditorPane editor,
AttributeSet atts,
boolean replace)
{
Document doc = editor.getDocument();
if (doc instanceof StyledDocument)
{
StyledDocument styleDoc = (StyledDocument) editor.getDocument();
EditorKit kit = editor.getEditorKit();
if (!(kit instanceof StyledEditorKit))
{
StyledEditorKit styleKit = (StyledEditorKit) kit;
int start = editor.getSelectionStart();
int end = editor.getSelectionEnd();
int dot = editor.getCaret().getDot();
if (start == dot && end == dot)
{
// If there is no selection, then we only update the
// input attributes.
MutableAttributeSet inputAttributes =
styleKit.getInputAttributes();
inputAttributes.addAttributes(atts);
}
else
styleDoc.setParagraphAttributes(start, end, atts, replace);
}
else
throw new AssertionError("The EditorKit for StyledTextActions "
+ "is expected to be a StyledEditorKit");
}
else
throw new AssertionError("The Document for StyledTextActions is "
+ "expected to be a StyledDocument.");
}
}
/**
* A {@link ViewFactory} that is able to create {@link View}s for
* the Element
s that are supported by
* StyledEditorKit
, namely the following types of Elements:
*
*
Element
.
*
* @param element the Element
to create a View
* for
* @return the View
for the specified Element
* or null
if the type of element
is
* not supported
*/
public View create(Element element)
{
String name = element.getName();
View view = null;
if (name.equals(AbstractDocument.ContentElementName))
view = new LabelView(element);
else if (name.equals(AbstractDocument.ParagraphElementName))
view = new ParagraphView(element);
else if (name.equals(AbstractDocument.SectionElementName))
view = new BoxView(element, View.Y_AXIS);
else if (name.equals(StyleConstants.ComponentElementName))
view = new ComponentView(element);
else if (name.equals(StyleConstants.IconElementName))
view = new IconView(element);
else
throw new AssertionError("Unknown Element type: "
+ element.getClass().getName() + " : "
+ name);
return view;
}
}
/**
* Keeps track of the caret position and updates the currentRun
* Element
and the inputAttributes
.
*/
class CaretTracker
implements CaretListener
{
/**
* Notifies an update of the caret position.
*
* @param ev the event for the caret update
*/
public void caretUpdate(CaretEvent ev)
{
Object source = ev.getSource();
if (!(source instanceof JTextComponent))
throw new AssertionError("CaretEvents are expected to come from a"
+ "JTextComponent.");
JTextComponent text = (JTextComponent) source;
Document doc = text.getDocument();
if (!(doc instanceof StyledDocument))
throw new AssertionError("The Document used by StyledEditorKits is"
+ "expected to be a StyledDocument");
StyledDocument styleDoc = (StyledDocument) doc;
currentRun = styleDoc.getCharacterElement(ev.getDot());
createInputAttributes(currentRun, inputAttributes);
}
}
/**
* Stores the Element
at the current caret position. This
* is updated by {@link CaretTracker}.
*/
Element currentRun;
/**
* The current input attributes. This is updated by {@link CaretTracker}.
*/
MutableAttributeSet inputAttributes;
/**
* The CaretTracker that keeps track of the current input attributes, and
* the current character run Element.
*/
CaretTracker caretTracker;
/**
* The ViewFactory for StyledEditorKits.
*/
StyledViewFactory viewFactory;
/**
* Creates a new instance of StyledEditorKit
.
*/
public StyledEditorKit()
{
inputAttributes = new SimpleAttributeSet();
}
/**
* Creates an exact copy of this StyledEditorKit
.
*
* @return an exact copy of this StyledEditorKit
*/
public Object clone()
{
StyledEditorKit clone = (StyledEditorKit) super.clone();
// FIXME: Investigate which fields must be copied.
return clone;
}
/**
* Returns the Action
s supported by this {@link EditorKit}.
* This includes the {@link BoldAction}, {@link ItalicAction} and
* {@link UnderlineAction} as well as the Action
s supported
* by {@link DefaultEditorKit}.
*
* The other Action
s of StyledEditorKit
are not
* returned here, since they require a parameter and thus custom
* instantiation.
*
* @return the Action
s supported by this {@link EditorKit}
*/
public Action[] getActions()
{
Action[] actions1 = super.getActions();
Action[] myActions = new Action[] {
new FontSizeAction("font-size-8", 8),
new FontSizeAction("font-size-10", 10),
new FontSizeAction("font-size-12", 12),
new FontSizeAction("font-size-14", 14),
new FontSizeAction("font-size-16", 16),
new FontSizeAction("font-size-18", 18),
new FontSizeAction("font-size-24", 24),
new FontSizeAction("font-size-36", 36),
new FontSizeAction("font-size-48", 48),
new FontFamilyAction("font-family-Serif", "Serif"),
new FontFamilyAction("font-family-Monospaced", "Monospaced"),
new FontFamilyAction("font-family-SansSerif", "SansSerif"),
new AlignmentAction("left-justify", StyleConstants.ALIGN_LEFT),
new AlignmentAction("center-justify", StyleConstants.ALIGN_CENTER),
new AlignmentAction("right-justify", StyleConstants.ALIGN_RIGHT),
new BoldAction(),
new ItalicAction(),
new UnderlineAction()
};
return TextAction.augmentList(actions1, myActions);
}
/**
* Returns the current input attributes. These are automatically set on
* any newly inserted content, if not specified otherwise.
*
* @return the current input attributes
*/
public MutableAttributeSet getInputAttributes()
{
return inputAttributes;
}
/**
* Returns the {@link Element} that represents the character run at the
* current caret position.
*
* @return the {@link Element} that represents the character run at the
* current caret position
*/
public Element getCharacterAttributeRun()
{
return currentRun;
}
/**
* Creates the default {@link Document} supported by this
* EditorKit
. This is an instance of
* {@link DefaultStyledDocument} in this case but may be overridden by
* subclasses.
*
* @return an instance of DefaultStyledDocument
*/
public Document createDefaultDocument()
{
return new DefaultStyledDocument();
}
/**
* Installs this EditorKit
on the specified {@link JEditorPane}.
* This basically involves setting up required listeners on the
* JEditorPane
.
*
* @param component the JEditorPane
to install this
* EditorKit
on
*/
public void install(JEditorPane component)
{
CaretTracker tracker = new CaretTracker();
component.addCaretListener(tracker);
}
/**
* Deinstalls this EditorKit
from the specified
* {@link JEditorPane}. This basically involves removing all listeners from
* JEditorPane
that have been set up by this
* EditorKit
.
*
* @param component the JEditorPane
from which to deinstall this
* EditorKit
*/
public void deinstall(JEditorPane component)
{
CaretTracker t = caretTracker;
if (t != null)
component.removeCaretListener(t);
caretTracker = null;
}
/**
* Returns a {@link ViewFactory} that is able to create {@link View}s
* for {@link Element}s that are supported by this EditorKit
,
* namely the following types of Element
s:
*
* EditorKit
*/
public ViewFactory getViewFactory()
{
if (viewFactory == null)
viewFactory = new StyledViewFactory();
return viewFactory;
}
/**
* Copies the text attributes from element
to set
.
* This is called everytime when the caret position changes to keep
* track of the current input attributes. The attributes in set
* are cleaned before adding the attributes of element
.
*
* This method filters out attributes for element names, Icon
s
* and Component
s.
*
* @param element the Element
from which to copy the text
* attributes
* @param set the inputAttributes to copy the attributes to
*/
protected void createInputAttributes(Element element,
MutableAttributeSet set)
{
// FIXME: Filter out component, icon and element name attributes.
set.removeAttributes(set);
set.addAttributes(element.getAttributes());
}
}