/**
* Distribution License:
* BibleDesktop is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 2 or later
* as published by the Free Software Foundation. This program 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.
*
* The License is available on the internet at:
* http://www.gnu.org/copyleft/gpl.html
* or by writing to:
* Free Software Foundation, Inc.
* 59 Temple Place - Suite 330
* Boston, MA 02111-1307, USA
*
* © CrossWire Bible Society, 2005 - 2016
*/
package org.crosswire.common.swing;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.KeyStroke;
import javax.swing.event.EventListenerList;
import org.crosswire.common.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A CrossWire Action is a generic extension of AbstractAction, that adds
* LARGE_ICON to Action and also forwards the Action to its listeners after
* modifying the ActionEvent to include the ACTION_COMMAND_KEY.
*
* @see gnu.gpl.License The GNU General Public License for details.
* @author DM Smith
*/
public class CWAction extends AbstractAction {
/**
* The icon to display when a large one is needed. This is still not part of
* Java as of 1.5. Now it is with Java 1.6!
*/
public static final String LARGE_ICON = "LargeIcon";
/**
* The tooltip to display. This is an alias for SHORT_DESCRIPTION. The
* creator and user of a CWAction is to store and retrieve
* SHORT_DESCRIPTION.
*/
public static final String TOOL_TIP = "ToolTip";
/**
* Set or clear, using null, the icon on this action.
* @param icon the small icon to set
* @return this action
*/
public CWAction setLargeIcon(Icon icon) {
putValue(LARGE_ICON, icon);
return this;
}
public CWAction setLargeIcon(String iconPath) {
return setLargeIcon(GuiUtil.getIcon(iconPath));
}
public CWAction setTooltip(String tooltip) {
putValue(Action.SHORT_DESCRIPTION, tooltip);
return this;
}
/**
* Set or clear, using null, the icon on this action.
* @param icon the small icon to set
* @return this action
*/
public CWAction setSmallIcon(Icon icon) {
putValue(SMALL_ICON, icon);
return this;
}
public CWAction setSmallIcon(String iconPath) {
return setSmallIcon(GuiUtil.getIcon(iconPath));
}
/**
* Set the accelerator key from spec. If the spec is invalid it is logged and ignored.
* @param acceleratorSpec
* @return this action
*/
public CWAction setAccelerator(String acceleratorSpec) {
putValue(Action.ACCELERATOR_KEY, getAccelerator(acceleratorSpec));
return this;
}
/**
* Set enabled either true or false on this action.
*
* @param newEnabled the desired state
* @return this action
*/
public CWAction enable(boolean newEnabled) {
setEnabled(newEnabled);
return this;
}
/**
* Create a clone of this action and attache the listener. If
* no listener is supplied, the action is not cloned.
*
* @param listener the listener for the action
* @return a cloned action with the listener attached or the current action
*/
public CWAction setListener(ActionListener listener) {
CWAction action = this;
if (listener != null) {
action = action.clone();
action.addActionListener(listener);
}
return action;
}
/**
* Forwards the ActionEvent to the registered listener.
*
* @param evt
* ActionEvent
*/
@Override
public void actionPerformed(ActionEvent evt) {
if (listeners != null) {
Object[] listenerList = listeners.getListenerList();
// Recreate the ActionEvent and stuff the value of the
// ACTION_COMMAND_KEY
ActionEvent e = new ActionEvent(evt.getSource(), evt.getID(), (String) getValue(Action.ACTION_COMMAND_KEY));
for (int i = 0; i <= listenerList.length - 2; i += 2) {
((ActionListener) listenerList[i + 1]).actionPerformed(e);
}
}
}
/**
* Adds a listener for Action events.
*
* @param listener
* ActionListener
to add
*/
public void addActionListener(ActionListener listener) {
if (listeners == null) {
listeners = new EventListenerList();
}
listeners.add(ActionListener.class, listener);
}
/**
* Remove an ActionListener
*
* @param listener
* ActionListener
to remove
*/
public void removeActionListener(ActionListener listener) {
if (listeners == null) {
return;
}
listeners.remove(ActionListener.class, listener);
}
/**
* String representation of this object suitable for debugging
*
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Name:");
sb.append((String) getValue(Action.NAME));
sb.append("\n Desc:");
sb.append((String) getValue(Action.SHORT_DESCRIPTION));
sb.append("\n ActionCommandKey:");
sb.append((String) getValue(Action.ACTION_COMMAND_KEY));
sb.append("\n Enabled:");
sb.append(isEnabled());
sb.append("\n ObjectID:");
sb.append(System.identityHashCode(this));
sb.append('\n');
return sb.toString();
}
/**
* Create a clone that does not copy the listeners. These CWActions need to
* have listeners added to be meaningful.
*
* @see javax.swing.AbstractAction#clone()
*/
@Override
public CWAction clone() {
CWAction action = null;
try {
action = (CWAction) super.clone();
action.listeners = null;
} catch (CloneNotSupportedException e) {
assert false : e;
}
return action;
}
/**
* Convert the string to a valid Accelerator (that is a KeyStroke)
*/
private KeyStroke getAccelerator(String acceleratorSpec) {
KeyStroke accelerator = null;
if (acceleratorSpec != null && acceleratorSpec.length() > 0) {
try {
accelerator = getKeyStroke(acceleratorSpec);
} catch (NumberFormatException nfe) {
log.warn("Could not parse integer for accelerator of action", nfe);
}
}
return accelerator;
}
/**
*
*/
private KeyStroke getKeyStroke(String acceleratorSpec) throws NumberFormatException {
int keyModifier = 0;
int key = 0;
String[] parts = StringUtil.splitAll(acceleratorSpec, ',');
for (int j = 0; j < parts.length; j++) {
String part = parts[j].trim();
if ("ctrl".equalsIgnoreCase(part)) {
// use this so MacOS users are happy
// It will map to the CMD key on Mac; CTRL otherwise.
keyModifier |= Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
} else if ("shift".equalsIgnoreCase(part)) {
keyModifier |= InputEvent.SHIFT_DOWN_MASK;
} else if ("alt".equalsIgnoreCase(part)) {
keyModifier |= InputEvent.ALT_DOWN_MASK;
} else if (part.startsWith("0x")) {
key = Integer.parseInt(part.substring(2), 16);
} else if (part.length() == 1) {
key = part.charAt(0);
}
}
return KeyStroke.getKeyStroke(key, keyModifier);
}
private EventListenerList listeners;
/**
* The log stream
*/
private static final Logger log = LoggerFactory.getLogger(CWAction.class);
/**
* Serialization ID
*/
private static final long serialVersionUID = 3258416148742484276L;
}