/*
* $Id$
*
* Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.appframework;
import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.Caret;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.jdesktop.application.Action;
import org.jdesktop.application.ApplicationActionMap;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.application.ProxyActions;
import org.jdesktop.beans.AbstractBean;
/**
*/
public class StyledTextActions extends AbstractBean {
private static boolean installed;
private final ApplicationContext context;
private final CaretListener textComponentCaretListener;
private final PropertyChangeListener textComponentPCL;
private final String markerActionKey = "StyledTextActions.markerAction";
private final javax.swing.Action markerAction;
private boolean boldEnabled = false;
private boolean boldSelected = false;
private boolean italicEnabled = false;
private boolean italicSelected = false;
private boolean underlineEnabled = false;
private boolean underlineSelected = false;
//TODO justification actions
// private boolean leftJustifyEnabled = false;
// private boolean centerJustifyEnabled = false;
// private boolean rightJustifyEnabled = false;
// private boolean fullJustifyEnabled = false;
@SuppressWarnings("serial")
StyledTextActions(ApplicationContext context) {
this.context = context;
markerAction = new javax.swing.AbstractAction() {
public void actionPerformed(ActionEvent e) {
}
};
textComponentCaretListener = new TextComponentCaretListener();
textComponentPCL = new TextComponentPCL();
installProxyActions();
}
public static synchronized void install(final ApplicationContext context) {
//TODO this should be one per context
if (!installed) {
context.addPropertyChangeListener("focusOwner", new PropertyChangeListener() {
private StyledTextActions textActions = new StyledTextActions(context);
public void propertyChange(PropertyChangeEvent evt) {
textActions.updateFocusOwner((JComponent) evt.getOldValue(),
(JComponent) evt.getNewValue());
}
});
installed = true;
}
}
private ApplicationContext getContext() {
return context;
}
/* Called by the KeyboardFocus PropertyChangeListener in ApplicationContext,
* before any other focus-change related work is done.
*/
public void updateFocusOwner(JComponent oldOwner, JComponent newOwner) {
if (oldOwner instanceof JTextComponent) {
JTextComponent text = (JTextComponent) oldOwner;
text.removeCaretListener(textComponentCaretListener);
text.removePropertyChangeListener(textComponentPCL);
}
if (newOwner instanceof JTextComponent) {
JTextComponent text = (JTextComponent) newOwner;
maybeInstallTextActions(text);
updateTextActions(text);
text.addCaretListener(textComponentCaretListener);
text.addPropertyChangeListener(textComponentPCL);
} else if (newOwner == null) {
setBoldEnabled(false);
setBoldSelected(false);
}
}
private final class TextComponentCaretListener implements CaretListener {
public void caretUpdate(CaretEvent e) {
updateTextActions((JTextComponent) (e.getSource()));
}
}
private final class TextComponentPCL implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if ((propertyName == null) || "editable".equals(propertyName)) {
updateTextActions((JTextComponent) (e.getSource()));
}
}
}
@ProxyActions({"bold", "italic", "underline"})
public static final class ProxyActionsContainer {
//exists to provide proxy action cover for the actions in the outer class
}
//TODO hack around loosing actions from map
private ApplicationActionMap main;
private void installProxyActions() {
ProxyActionsContainer pac = new ProxyActionsContainer();
ApplicationActionMap aam = getContext().getActionMap(pac);
main = getContext().getActionMap();
for (Object key : aam.allKeys()) {
main.put(key, aam.get(key));
}
}
private void updateTextActions(JTextComponent text) {
if (text.getDocument() instanceof StyledDocument) {
StyledDocument document = (StyledDocument) text.getDocument();
boolean editable = text.isEditable();
Caret caret = text.getCaret();
int dot = caret.getDot();
dot = dot > 0 ? dot - 1 : dot;
Element elem = document.getCharacterElement(dot);
AttributeSet set = elem.getAttributes();
setBoldEnabled(editable);
setBoldSelected(StyleConstants.isBold(set));
setItalicEnabled(editable);
setItalicSelected(StyleConstants.isItalic(set));
setUnderlineEnabled(editable);
setUnderlineSelected(StyleConstants.isUnderline(set));
} else {
setBoldEnabled(false);
setBoldSelected(false);
setItalicEnabled(false);
setItalicSelected(false);
setUnderlineEnabled(false);
setUnderlineSelected(false);
}
}
// TBD: what if text.getActionMap is null, or if it's parent isn't the UI-installed actionMap
private void maybeInstallTextActions(JTextComponent text) {
ActionMap actionMap = text.getActionMap();
if (actionMap.get(markerActionKey) == null) {
actionMap.put(markerActionKey, markerAction);
ActionMap textActions = getContext().getActionMap(getClass(), this);
for (Object key : textActions.keys()) {
actionMap.put(key, textActions.get(key));
}
}
}
/* This method lifted from JTextComponent.java
*/
private int getCurrentEventModifiers() {
int modifiers = 0;
AWTEvent currentEvent = EventQueue.getCurrentEvent();
if (currentEvent instanceof InputEvent) {
modifiers = ((InputEvent) currentEvent).getModifiers();
} else if (currentEvent instanceof ActionEvent) {
modifiers = ((ActionEvent) currentEvent).getModifiers();
}
return modifiers;
}
private void invokeTextAction(JTextComponent text, String actionName) {
ActionMap actionMap = text.getActionMap().getParent();
long eventTime = EventQueue.getMostRecentEventTime();
int eventMods = getCurrentEventModifiers();
ActionEvent actionEvent =
new ActionEvent(text, ActionEvent.ACTION_PERFORMED, actionName, eventTime, eventMods);
actionMap.get(actionName).actionPerformed(actionEvent);
}
/**
* @return the boldEnabled
*/
public boolean isBoldEnabled() {
return boldEnabled;
}
/**
* @param boldEnabled the boldEnabled to set
*/
public void setBoldEnabled(boolean boldEnabled) {
boolean oldValue = isBoldEnabled();
this.boldEnabled = boldEnabled;
firePropertyChange("boldEnabled", oldValue, isBoldEnabled());
}
/**
* @return the boldSelected
*/
public boolean isBoldSelected() {
return boldSelected;
}
/**
* @param boldSelected the boldSelected to set
*/
public void setBoldSelected(boolean boldSelected) {
boolean oldValue = isBoldSelected();
this.boldSelected = boldSelected;
firePropertyChange("boldSelected", oldValue, isBoldSelected());
}
@Action(enabledProperty = "boldEnabled", selectedProperty = "boldSelected")
public void bold(ActionEvent e) {
Object src = e.getSource();
if (src instanceof JTextComponent) {
invokeTextAction((JTextComponent) src, "font-bold");
}
}
/**
* @return the boldEnabled
*/
public boolean isItalicEnabled() {
return italicEnabled;
}
/**
* @param italicEnabled the boldEnabled to set
*/
public void setItalicEnabled(boolean italicEnabled) {
boolean oldValue = isItalicEnabled();
this.italicEnabled = italicEnabled;
firePropertyChange("italicEnabled", oldValue, isItalicEnabled());
}
/**
* @return the boldSelected
*/
public boolean isItalicSelected() {
return italicSelected;
}
/**
* @param italicSelected the boldSelected to set
*/
public void setItalicSelected(boolean italicSelected) {
boolean oldValue = isItalicSelected();
this.italicSelected = italicSelected;
firePropertyChange("italicSelected", oldValue, isItalicSelected());
}
@Action(enabledProperty = "italicEnabled", selectedProperty = "italicSelected")
public void italic(ActionEvent e) {
Object src = e.getSource();
if (src instanceof JTextComponent) {
invokeTextAction((JTextComponent) src, "font-italic");
}
}
/**
* @return the underlineEnabled
*/
public boolean isUnderlineEnabled() {
return underlineEnabled;
}
/**
* @param underlineEnabled the underlineEnabled to set
*/
public void setUnderlineEnabled(boolean underlineEnabled) {
boolean oldValue = isUnderlineEnabled();
this.underlineEnabled = underlineEnabled;
firePropertyChange("underlineEnabled", oldValue, isUnderlineEnabled());
}
/**
* @return the underlineSelected
*/
public boolean isUnderlineSelected() {
return underlineSelected;
}
/**
* @param underlineSelected the underlineSelected to set
*/
public void setUnderlineSelected(boolean underlineSelected) {
boolean oldValue = isUnderlineSelected();
this.underlineSelected = underlineSelected;
firePropertyChange("underlineSelected", oldValue, isUnderlineSelected());
}
@Action(enabledProperty = "underlineEnabled", selectedProperty = "underlineSelected")
public void underline(ActionEvent e) {
Object src = e.getSource();
if (src instanceof JTextComponent) {
invokeTextAction((JTextComponent) src, "font-underline");
}
}
}