/* * 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; } } }