/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.util.actions; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.text.MessageFormat; import java.io.*; import java.net.URL; import java.util.*; import java.util.List; import javax.swing.*; import org.openide.ErrorManager; import org.openide.awt.JPopupMenuPlus; import org.openide.util.*; /* not relevant here --jglick * The actions system allows connection between action * "invokers" and an action "performer", where in some cases the * performer of the action can be the action class itself, while in * other cases it can be a class that implements the ActionPerformer * interface and is registered at the action via setActionPerformer. */ /** * The common predecessor of callable actions in the IDE. *
* Also implements the Swing {@link Action} to enable use * with the Swing action model. *
An action class is a singleton, i.e. should generally contain no instance state. * Rather, subclassing and use of abstract protected methods should be used * to create variants of the action. *
While it is possible to subclass this class directly--for example, if your "action"
* is really a placeholder for a popup menu that shows other actions--most people will
* prefer to use one of the subclasses, which are more convenient.
*
* @author Ian Formanek, Jaroslav Tulach
*/
public abstract class SystemAction extends SharedClassObject
implements Action {
/** Name of property indicating whether or not the action is enabled. */
public static final String PROP_ENABLED = "enabled"; // NOI18N
/** Name of property for the action's display icon. */
public static final String PROP_ICON = "icon"; // NOI18N
/** Name of property for the action's display icon, if textual. */
private static final String PROP_ICON_TEXTUAL = "iconTextual"; // NOI18N
/** serialVersionUID */
static final long serialVersionUID = 7131978192935797459L;
/** Obtain a singleton instance of the action with a specified class.
* If there already is a instance then it is returned, otherwise
* a new one is created.
*
* @param actionClass the class of the action to find
* @return the singleton action instance
* @exception ClassCastException if the class is not SystemAction
* @exception IllegalArgumentException if the instance cannot be created
*/
public static SystemAction get (Class actionClass) {
return (SystemAction)findObject (actionClass, true);
}
/** Get a human presentable name of the action.
* This may be
* presented as an item in a menu.
*
Using the normal menu presenters, an included ampersand
* before a letter will be treated as the name of a mnemonic.
* @return the name of the action
*/
public abstract String getName ();
/** Get a help context for the action.
* @return the help context for this action
*/
public abstract HelpCtx getHelpCtx ();
/** Test whether the action is currently enabled.
* @return true
if so
*/
public boolean isEnabled() {
return getProperty (PROP_ENABLED).equals (Boolean.TRUE);
}
/** Set whether the action should be enabled.
* @param value true
to enable it
*/
public void setEnabled(boolean value) {
putProperty (PROP_ENABLED, value ? Boolean.TRUE : Boolean.FALSE, true);
}
/** Set a property in the singleton. This property is common for all instances
* of the same class.
*
* @param name the name of the property
* @param value the value
*/
public final void putValue (String name, Object value) {
putProperty (name, value, true);
// Could handle putValue (SMALL_ICON, ImageIcon icon) but not
// really that important.
}
/** Get a property in the singleton. Values are shared among all instances of the same class.
* The special tokens {@link Action#NAME} and {@link Action#SMALL_ICON} are also recognized
* and delegated to {@link #getName} and {@link #getIcon}, resp.
* @param name the name of the property
* @return the value
*/
public final Object getValue (String name) {
Object val = getProperty (name);
if (val == null) {
if (NAME.equals (name))
val = getName ();
else if (SMALL_ICON.equals (name))
val = getIcon ();
}
return val;
}
/** Actually perform the action.
* Specified in {@link java.awt.event.ActionListener#actionPerformed}.
*
In some cases, the implementation may have an empty body, * if the presenters handle the performing of the action in a different way * than by calling this method. *
When run in the normal way from the action manager (e.g. as
* part of a standard menu or toolbar presenter), the action body can
* block and take time, but needs to explicitly ask to enter the AWT
* event thread if doing any GUI work. See the Threading Models document
* in API documentation for details.
* @param ev the event triggering the action
*/
public abstract void actionPerformed (java.awt.event.ActionEvent ev);
/** Initialize the action.
* The default implementation just enabled it.
*/
protected void initialize () {
putProperty (PROP_ENABLED, Boolean.TRUE);
super.initialize ();
}
/** Indicate whether action state should be cleared after the last action of this class is deleted.
* @return false
in the default implementation
*/
protected boolean clearSharedData () {
return false;
}
/** Set the action's display icon.
* @param icon the icon
*/
public final void setIcon (Icon icon) {
putProperty (PROP_ICON, icon, true);
putProperty (PROP_ICON_TEXTUAL, icon);
}
/** Get the action's display icon.
* @return the icon
* @throws IllegalStateException if an icon could not be created
*/
public final Icon getIcon () {
return getIcon (false);
}
/*nbif compat
public final void setIcon (ImageIcon icon) {
setIcon ((Icon) icon);
}
public final ImageIcon g3t1c0n () {
Icon i = getIcon (false);
if (i instanceof ImageIcon) {
return ((ImageIcon) i);
} else {
// [PENDING] could try to translate Icon -> ImageIcon somehow,
// but I have no idea how to do this (paint it, take Component
// graphics, load the image data somehow??)
return FixedIcon.create (SystemAction.class.getResource ("/org/openide/resources/actions/empty.gif")); // NOI18N
}
}
/*nbend*/
/** Get the action's display icon, possibly creating a text label.
* @param createLabel if true
, create a textual icon if otherwise there
* would be none; if false
, create a blank icon
* @return an icon
* @throws IllegalStateException if an icon could not be created
*/
public final Icon getIcon (boolean createLabel) {
synchronized (getLock ()) {
Icon img = (Icon) getProperty (createLabel ? PROP_ICON_TEXTUAL : PROP_ICON);
if (img == null) {
// create the icon from the resource
String resName = iconResource ();
if (resName != null) {
// [PENDING] use nbresloc: protocol instead.
// Even better, permit returning a URL from iconResource instead of
// a resource name, and use it directly, with nbresloc as the default.
URL url = getClass ().getResource (resName);
if (url == null) {
throw new IllegalStateException (MessageFormat.format(NbBundle.getBundle ("org.openide.util.Bundle").getString("MSG_FMT_IconNotFound"), new Object [] {getClass ().getName (), iconResource () }));
}
img = new ImageIcon(url);
putProperty (PROP_ICON, img);
putProperty (PROP_ICON_TEXTUAL, img);
} else {
if (createLabel) {
String text = getName ();
if (text.endsWith ("...")) text = text.substring (0, text.length () - 3); // NOI18N
img = new ComponentIcon (new JLabel (Utilities.replaceString (text.trim (), "&", ""))); // NOI18N
putProperty (PROP_ICON_TEXTUAL, img);
} else {
img = new ImageIcon(SystemAction.class.getResource ("/org/openide/resources/actions/empty.gif")); // NOI18N
putProperty (PROP_ICON, img);
}
}
}
return img;
}
}
/** Specify the proper resource name for the action's icon.
* May be overridden by subclasses.
* Typically this should be a 16x16 color GIF.
* @return the resource name for the icon, e.g. /com/mycom/mymodule/myIcon.gif
; or null
to make a text label
*/
protected String iconResource () {
return null;
}
/** Create the default toolbar representation of an array of actions.
* Null items in the array will add a separator to the toolbar.
*
* @param actions actions to show in the generated toolbar
* @return a toolbar instance displaying them
*/
public static JToolBar createToolbarPresenter (SystemAction[] actions) {
JToolBar p = new JToolBar ();
int i, k = actions.length;
for (i = 0; i < k; i++) {
if (actions [i] == null)
p.addSeparator();
else
if (actions [i] instanceof Presenter.Toolbar)
p.add (((Presenter.Toolbar)actions [i]).getToolbarPresenter ());
}
return p;
}
/** Concatenate two arrays of actions.
* @param actions1 first array of actions to link
* @param actions1 second array of actions to link
* @return an array of both sets of actions in the same order
*/
public static SystemAction[] linkActions (SystemAction[] actions1, SystemAction[] actions2) {
List l = new Vector (Arrays.asList (actions1));
l.addAll (Arrays.asList (actions2));
return (SystemAction[]) l.toArray (actions1);
}
/** Create the default popup menu representation of an array of actions.
* @param actions actions to show in the generated menu
* @return a popup menu displaying them
*/
public static JPopupMenu createPopupMenu(SystemAction []actions) {
boolean addSeparator = false;
JPopupMenu popupMenu = new JPopupMenuPlus();
JMenuItem item;
for(int i = 0; i < actions.length; i++) {
if (actions[i] == null) {
addSeparator = popupMenu.getComponentCount() > 0;
continue;
}
if (actions[i] instanceof Presenter.Popup) {
item = ((Presenter.Popup)actions[i]).getPopupPresenter ();
} else {
item = new JMenuItem (actions[i].getName ());
item.setEnabled(false);
}
if(addSeparator) {
popupMenu.addSeparator ();
addSeparator = false;
}
popupMenu.add (item);
}
return popupMenu;
}
/** Icon based on a component (such as a text label).
* Just draws that component as an image.
*/
private static class ComponentIcon extends ImageIcon {
private JComponent comp;
private BufferedImage image;
/** Create an icon.
* @param comp a component, which must be unattached to a container
* and should not be used for other purposes
*/
public ComponentIcon (JComponent comp) {
if (comp.getParent () != null) throw new IllegalArgumentException ();
this.comp = comp;
Dimension size = comp.getPreferredSize ();
// Careful! If you have e.g. a JLabel with empty text, width = 0 => exceptions.
// Must make sure it is at least a reasonable size.
comp.setSize (Math.max (size.width, 16), Math.max (size.height, 16));
}
protected void loadImage (Image i) {
}
public void paintIcon (Component c, Graphics g, int x, int y) {
// When enabled, tracks color choices of container:
comp.setBackground (c.getBackground ());
comp.setForeground (c.getForeground ());
Graphics clip = g.create (x, y, getIconWidth (), getIconHeight ());
comp.paint (clip);
}
public int getIconWidth () {
return comp.getWidth ();
}
public int getIconHeight () {
return comp.getHeight ();
}
// Needed because GrayFilter (used for disabled icons) calls this directly,
// rather than going through the Icon interface.
public Image getImage () {
if (image == null) {
image = new BufferedImage (getIconWidth (), getIconHeight (), BufferedImage.TYPE_INT_ARGB);
// [PENDING] this is obviously ugly, but how should we decide what is the
// default fg for the Main Window toolbar area? Background is irrelevant,
// since we use alpha channel. But have to guess at the foreground.
comp.setForeground (Color.black);
comp.paint (image.getGraphics ());
}
return image;
}
}
}