All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.awt.Actions Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.openide.awt;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.NbBundle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.actions.Closable;
import org.netbeans.api.actions.Editable;
import org.netbeans.api.actions.Openable;
import org.netbeans.api.actions.Printable;
import org.netbeans.api.actions.Viewable;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.ContextAwareAction;
import org.openide.util.ImageUtilities;
import org.openide.util.LookupListener;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.actions.BooleanStateAction;
import org.openide.util.actions.SystemAction;


/** Supporting class for manipulation with menu and toolbar presenters.
*
* @author   Jaroslav Tulach
*/
public class Actions {
    /**
     * Action may {@link Action#putValue} this value to indicate that, if not enabled,
     * it should not be visible at all. Presenters may honour the request by removing
     * action's presenter from the UI.
     */
    public static final String ACTION_VALUE_VISIBLE = "openide.awt.actionVisible"; // NOI18N
    
    /**
     * Key for {@link Action#getValue} to indicate that the action should be presented
     * as toggle, if possible. Presenters may create checkbox item, toggle button etc.
     * This is to avoid accessing the {@link Action#SELECTED_KEY} actual value during
     * presenter construction, as evaluation may be expensive.
     * @since 7.71
     */
    public static final String ACTION_VALUE_TOGGLE = "openide.awt.actionToggle"; // NOI18N
    
    /**
     * @deprecated should not be used
     */
    @Deprecated
    public Actions() {}

    /**
     * Make sure an icon is not null, so that e.g. menu items for javax.swing.Action's
     * with no specified icon are correctly aligned. SystemAction already does this so
     * that is not affected.
     */
    private static Icon nonNullIcon(Icon i) {
        return null;

        /*if (i != null) {
            return i;
        } else {
            if (BLANK_ICON == null) {
                BLANK_ICON = new ImageIcon(Utilities.loadImage("org/openide/resources/actions/empty.gif", true)); // NOI18N
            }
            return BLANK_ICON;
        }*/
    }

    /** Method that finds the keydescription assigned to this action.
    * @param action action to find key for
    * @return the text representing the key or null if  there is no text assigned
    */
    public static String findKey(SystemAction action) {
        return findKey((Action) action);
    }

    /** Same method as above, but works just with plain actions.
     */
    private static String findKey(Action action) {
        if (action == null) {
            return null;
        }

        KeyStroke stroke = (KeyStroke) action.getValue(Action.ACCELERATOR_KEY);

        if (stroke == null) {
            return null;
        }

        return keyStrokeToString(stroke);
    }

    // Based on com.formdev.flatlaf.FlatMenuItemRenderer.getMacOSModifiersExText
    private static String getMacOSModifiersExText(int modifiersEx) {
        /* Use the proper MacOS convention, which uses single-character modifier symbols, in the
        order below, without "+" or space separators. */
        StringBuilder buf = new StringBuilder();
        if ((modifiersEx & InputEvent.CTRL_DOWN_MASK) != 0) {
            buf.append('\u2303'); // MacOS "control key" symbol.
        }
        if ((modifiersEx & (InputEvent.ALT_DOWN_MASK | InputEvent.ALT_GRAPH_DOWN_MASK)) != 0) {
            buf.append('\u2325'); // MacOS "option key" symbol.
        }
        if ((modifiersEx & InputEvent.SHIFT_DOWN_MASK) != 0) {
            buf.append('\u21E7'); // MacOS "shift key" symbol.
        }
        if ((modifiersEx & InputEvent.META_DOWN_MASK) != 0) {
            buf.append('\u2318'); // MacOS "command key" symbol.
        }
        return buf.toString();
    }

    /**
     * Creates nice textual representation of KeyStroke.
     * Modifiers and an actual key label are concated per the platform-specific convention
     * @param stroke the KeyStroke to get description of
     * @return String describing the KeyStroke
     */
    public static String keyStrokeToString( KeyStroke stroke ) {
        boolean macOS = Utilities.isMac();
        String modifText = macOS
                ? getMacOSModifiersExText(stroke.getModifiers())
                : InputEvent.getModifiersExText(stroke.getModifiers());
        String keyText = (stroke.getKeyCode() == KeyEvent.VK_UNDEFINED) ?
            String.valueOf(stroke.getKeyChar()) : getKeyText(stroke.getKeyCode());
        if (!modifText.isEmpty()) {
            if (macOS) {
                return modifText + keyText;
            } else {
                return modifText + '+' + keyText;
            }
        } else {
            return keyText;
        }
    }

    /** @return slight modification of what KeyEvent.getKeyText() returns.
     *  The numpad Left, Right, Down, Up get extra result.
     */
    private static String getKeyText(int keyCode) {
        String ret = KeyEvent.getKeyText(keyCode);
        if (ret != null) {
            switch (keyCode) {
                case KeyEvent.VK_KP_DOWN:
                    ret = prefixNumpad(ret, KeyEvent.VK_DOWN);
                    break;
                case KeyEvent.VK_KP_LEFT:
                    ret = prefixNumpad(ret, KeyEvent.VK_LEFT);
                    break;
                case KeyEvent.VK_KP_RIGHT:
                    ret = prefixNumpad(ret, KeyEvent.VK_RIGHT);
                    break;
                case KeyEvent.VK_KP_UP:
                    ret = prefixNumpad(ret, KeyEvent.VK_UP);
                    break;
            }
        }
        return ret;
    }

    private static String prefixNumpad(String key, int nonNumpadCode) {
        final String REPLACABLE_PREFIX = "KP_";
        final String usePrefix = NbBundle.getMessage(Actions.class, "key-prefix-numpad");
        final String nonNumpadName = KeyEvent.getKeyText(nonNumpadCode);
        if (key.equals(nonNumpadName)) {
            /* AWT's name for the key does not distinguish the numpad vs. non-numpad version of the
            key; add our "Numpad-" prefix. */
            return usePrefix + key;
        } else if (key.startsWith(REPLACABLE_PREFIX)) {
            /* AWT's name for the numpad key uses the somewhat confusing "KP_" prefix (e.g.
            "KP_LEFT"); use our own preferred prefix instead (e.g. "Numpad-LEFT"). */
            return usePrefix + key.substring(REPLACABLE_PREFIX.length());
        } else {
            /* AWT is using some other convention to disambiguate the numpad vs. non-numpad version
            of the key. Use AWT's name in this case. */
            return key;
        }
    }

    /** Attaches menu item to an action.
    * @param item menu item
    * @param action action
    * @param popup create popup or menu item
     * @deprecated Use {@link #connect(JMenuItem, Action, boolean)} instead.
    */
    @Deprecated
    public static void connect(JMenuItem item, SystemAction action, boolean popup) {
        connect(item, (Action) action, popup);
    }

    /** Attaches menu item to an action.
     * You can supply an alternative implementation
     * for this method by implementing method
     * {@link ButtonActionConnector#connect(JMenuItem, Action, boolean)} and
     * registering an instance of {@link ButtonActionConnector} in the
     * default lookup. 
     * 

* Since version 7.1 the action can also provide properties * "menuText" and "popupText" if one wants to use other text on the JMenuItem * than the name * of the action taken from Action.NAME. The popupText is checked only if the * popup parameter is true and takes the biggest precedence. The menuText is * tested everytime and takes precedence over standard Action.NAME *

* By default icons are not visible in popup menus. This can be configured * via branding. * * @param item menu item * @param action action * @param popup create popup or menu item * @since 3.29 */ public static void connect(JMenuItem item, Action action, boolean popup) { for (ButtonActionConnector bac : buttonActionConnectors()) { if (bac.connect(item, action, popup)) { return; } } Bridge b; if ((item instanceof JCheckBoxMenuItem) && (action.getValue(Actions.ACTION_VALUE_TOGGLE) != null)) { b = new CheckMenuBridge((JCheckBoxMenuItem)item, action, popup); } else { b = new MenuBridge(item, action, popup); } b.prepare(); if (item instanceof Actions.MenuItem) { ((Actions.MenuItem)item).setBridge(b); } item.putClientProperty(DynamicMenuContent.HIDE_WHEN_DISABLED, action.getValue(DynamicMenuContent.HIDE_WHEN_DISABLED)); } /** Attaches checkbox menu item to boolean state action. * @param item menu item * @param action action * @param popup create popup or menu item * @deprecated Please use {@link #connect(javax.swing.JCheckBoxMenuItem, javax.swing.Action, boolean)}. * Have your action to implement properly {@link Action#getValue} for {@link Action#SELECTED_KEY} */ @Deprecated public static void connect(JCheckBoxMenuItem item, BooleanStateAction action, boolean popup) { Bridge b = new CheckMenuBridge(item, action, popup); b.prepare(); } /** Attaches checkbox menu item to boolean state action. The presenter connects to the * {@link Action#SELECTED_KEY} action value * * @param item menu item * @param action action * @param popup create popup or menu item * @since 7.71 */ public static void connect(JCheckBoxMenuItem item, Action action, boolean popup) { Bridge b = new CheckMenuBridge(item, action, popup); b.prepare(); } /** Connects buttons to action. * @param button the button * @param action the action * @deprecated Use {@link #connect(AbstractButton, Action)} instead. */ @Deprecated public static void connect(AbstractButton button, SystemAction action) { connect(button, (Action) action); } /** Connects buttons to action. If the action supplies value for "iconBase" * key from getValue(String) with a path to icons, the methods set*Icon * will be called on the * button with loaded icons using the iconBase. E.g. if the value for "iconBase" * is "com/mycompany/myIcon.gif" then the following images are tried *

    *
  • setIcon with "com/mycompany/myIcon.gif"
  • *
  • setPressedIcon with "com/mycompany/myIcon_pressed.gif"
  • *
  • setDisabledIcon with "com/mycompany/myIcon_disabled.gif"
  • *
  • setRolloverIcon with "com/mycompany/myIcon_rollover.gif"
  • *
  • setSelectedIcon with "com/mycompany/myIcon_selected.gif"
  • *
  • setRolloverSelectedIcon with "com/mycompany/myIcon_rolloverSelected.gif"
  • *
  • setDisabledSelectedIcon with "com/mycompany/myIcon_disabledSelected.gif"
  • *
* SystemAction has special support for iconBase - please check * {@link SystemAction#iconResource} for more details. * You can supply an alternative implementation * for this method by implementing method * {@link ButtonActionConnector#connect(AbstractButton, Action)} and * registering an instance of {@link ButtonActionConnector} in the * default lookup. * @param button the button * @param action the action * @since 3.29 * @since 7.32 for set*SelectedIcon */ public static void connect(AbstractButton button, Action action) { for (ButtonActionConnector bac : buttonActionConnectors()) { if (bac.connect(button, action)) { return; } } Bridge b; if (action instanceof BooleanStateAction) { b = new BooleanButtonBridge(button, (BooleanStateAction)action); } else if (action.getValue(Actions.ACTION_VALUE_TOGGLE) != null) { b = new BooleanButtonBridge(button, action); } else { b = new ButtonBridge(button, action); } b.prepare(); button.putClientProperty(DynamicMenuContent.HIDE_WHEN_DISABLED, action.getValue(DynamicMenuContent.HIDE_WHEN_DISABLED)); } /** Connects buttons to action. * @param button the button * @param action the action */ public static void connect(AbstractButton button, BooleanStateAction action) { Bridge b = new BooleanButtonBridge(button, action); b.prepare(); } /** Sets the text for the menu item or other subclass of AbstractButton. * Cut from the name '&' char. * @param item AbstractButton * @param text new label * @param useMnemonic if true and '&' char found in new text, next char is used * as Mnemonic. * @deprecated Use either {@link AbstractButton#setText} or {@link Mnemonics#setLocalizedText(AbstractButton, String)} as appropriate. */ @Deprecated public static void setMenuText(AbstractButton item, String text, boolean useMnemonic) { String msg = NbBundle.getMessage(Actions.class, "USE_MNEMONICS"); // NOI18N if ("always".equals(msg)) { // NOI18N useMnemonic = true; } else if ("never".equals(msg)) { // NOI18N useMnemonic = false; } else { assert "default".equals(msg); // NOI18N } if (useMnemonic) { Mnemonics.setLocalizedText(item, text); } else { item.setText(cutAmpersand(text)); } } /** * Removes an ampersand from a text string; commonly used to strip out unneeded mnemonics. * Replaces the first occurence of &? by ? or (&?? by the empty string * where ? is a wildcard for any character. * &? is a shortcut in English locale. * (&?) is a shortcut in Japanese locale. * Used to remove shortcuts from workspace names (or similar) when shortcuts are not supported. *

The current implementation behaves in the same way regardless of locale. * In case of a conflict it would be necessary to change the * behavior based on the current locale. * @param text a localized label that may have mnemonic information in it * @return string without first & if there was any */ public static String cutAmpersand(String text) { // XXX should this also be deprecated by something in Mnemonics? if( null == text ) return null; int i; String result = text; /* First check of occurence of '(&'. If not found check * for '&' itself. * If '(&' is found then remove '(&??'. */ i = text.indexOf("(&"); // NOI18N if ((i >= 0) && ((i + 3) < text.length()) && /* #31093 */ (text.charAt(i + 3) == ')')) { // NOI18N result = text.substring(0, i) + text.substring(i + 4); } else { //Sequence '(&?)' not found look for '&' itself i = text.indexOf('&'); if (i < 0) { //No ampersand result = text; } else if (i == (text.length() - 1)) { //Ampersand is last character, wrong shortcut but we remove it anyway result = text.substring(0, i); } else { //Remove ampersand from middle of string //Is ampersand followed by space? If yes do not remove it. if (" ".equals(text.substring(i + 1, i + 2))) { result = text; } else { result = text.substring(0, i) + text.substring(i + 1); } } } return result; } // // Factories // /** Creates new action which is always enabled. Rather than using this method * directly, use {@link ActionRegistration} annotation: *

     * {@link ActionRegistration @ActionRegistration}(displayName="#key")
     * {@link ActionID @ActionID}(id="your.pkg.action.id", category="Tools")
     * public final class Always implements {@link ActionListener} {
     *   public Always() {
     *   }
     *   public void actionPerformed({@link ActionEvent} e) {
     *    // your code
     *   }
     * }
     * 
* This method can also be used from * XML Layer * directly by following XML definition: *
     * <file name="your-pkg-action-id.instance">
     *   <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
     *   <attr name="delegate" methodvalue="your.pkg.YourAction.factoryMethod"/>
     *   <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
     *   <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
     *   <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
     *   <!-- if desired: <attr name="asynchronous" boolvalue="true"/> -->
     * </file>
     * 
* In case the "delegate" is not just {@link ActionListener}, but also * {@link Action}, the returned action acts as a lazy proxy - it defers initialization * of the action itself, but as soon as it is created, it delegates all queries * to it. This way one can create an action that looks statically enabled, and as soon * as user really uses it, it becomes active - it can change its name, it can * change its enabled state, etc. * * * @param delegate the task to perform when action is invoked * @param displayName the name of the action * @param iconBase the location to the actions icon * @param noIconInMenu true if this icon shall not have an item in menu * @since 7.3 */ public static Action alwaysEnabled( ActionListener delegate, String displayName, String iconBase, boolean noIconInMenu ) { HashMap map = new HashMap(); map.put("delegate", delegate); // NOI18N map.put("displayName", displayName); // NOI18N map.put("iconBase", iconBase); // NOI18N map.put("noIconInMenu", noIconInMenu); // NOI18N return alwaysEnabled(map); } // for use from layers static Action alwaysEnabled(Map map) { return AlwaysEnabledAction.create(map); } /** Creates action which represents a boolean value in {@link java.util.prefs.Preferences}. * When added to a menu the action is presented as a JCheckBox. * This method can also be used from * XML Layer * directly by following XML definition: *
     * <file name="your-pkg-action-id.instance">
     *   <attr name="preferencesNode" methodvalue="method-returning-Preferences-instance" or
     *                                   methodvalue="method-returning-Lookup-that-contains-Preferences-instance" or
     *                                   stringvalue="see below for the preferencesNode parameter description"
     * />
     *   <attr name="preferencesKey" stringvalue="preferences-key-name"/>
     *   <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.checkbox"/>
     *   <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
     *   <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
     *   <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
     *   <!-- if desired: <attr name="asynchronous" boolvalue="true"/> -->
     * </file>
     * 
* * @param preferencesNode It's one of: *
    *
  • Absolute path to preferences node under NbPreferences.root().
  • *
  • "system:" followed by absolute path to preferences node under Preferences.systemRoot().
  • *
  • "user:" followed by absolute path to preferences node under Preferences.userRoot().
  • *
* @param preferencesKey name of the preferences key. * @param displayName the name of the action * @param iconBase the location to the actions icon * @param noIconInMenu true if this icon shall not have an item in menu * @since 7.17 */ public static Action checkbox( String preferencesNode, String preferencesKey, String displayName, String iconBase, boolean noIconInMenu ) { HashMap map = new HashMap(); map.put("preferencesNode", preferencesNode); // NOI18N map.put("preferencesKey", preferencesKey); // NOI18N map.put("displayName", displayName); // NOI18N map.put("iconBase", iconBase); // NOI18N map.put("noIconInMenu", noIconInMenu); // NOI18N return checkbox(map); } // for use from layers static Action checkbox(Map map) { return AlwaysEnabledAction.create(map); } /** Creates new "callback" action. Such action has an assigned key * which is used to find proper delegate in {@link ActionMap} of currently * active component. You can use {@link ActionRegistration} annotation to * register your action: *
     * {@link ActionRegistration @ActionRegistration}(displayName="#Key", key="KeyInActionMap")
     * {@link ActionID @ActionID}(category="Tools", id = "action.pkg.ClassName")
     * public final class Fallback implements {@link ActionListener} {
     *   public void actionPerformed({@link ActionEvent} e) {
     *    // your code
     *   }
     * }
     * 
* If you want to create callback action without any fallback implementation, * you can annotate any string constant: *
     * {@link ActionRegistration @ActionRegistration}(displayName = "#Key")
     * {@link ActionID @ActionID}(category = "Edit", id = "my.field.action")
     * public static final String ACTION_MAP_KEY = "KeyInActionMap";
     * 
*

* This action can be lazily declared in a * * layer file using following XML snippet: *

     * <file name="action-pkg-ClassName.instance">
     *   <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.callback"/>
     *   <attr name="key" stringvalue="KeyInActionMap"/>
     *   <attr name="surviveFocusChange" boolvalue="false"/> <!-- defaults to false -->
     *   <attr name="fallback" newvalue="action.pkg.DefaultAction"/> <!-- may be missing -->
     *   <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
     *   <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
     *   <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
     *   <!-- if desired: <attr name="asynchronous" boolvalue="true"/> -->
     * </file>
     * 
* * * @param key the key to search for in an {@link ActionMap} * @param surviveFocusChange true to remember action provided by previously * active component even some other component is currently active * @param fallback action to delegate to when no key found. Use null * to make the action disabled if delegate assigned to key is missing * @param displayName localized name of the action (including ampersand) * @param iconBase the location to the action icon * @param noIconInMenu true if this icon shall not have an item in menu * @return creates new action associated with given key * @since 7.10 */ public static ContextAwareAction callback( String key, Action fallback, boolean surviveFocusChange, String displayName, String iconBase, boolean noIconInMenu ) { Map map = new HashMap(); map.put("key", key); // NOI18N map.put("surviveFocusChange", surviveFocusChange); // NOI18N map.put("fallback", fallback); // NOI18N map.put("displayName", displayName); // NOI18N map.put("iconBase", iconBase); // NOI18N map.put("noIconInMenu", noIconInMenu); // NOI18N return callback(map); } static ContextAwareAction callback(Map fo) { return GeneralAction.callback(fo); } /** Creates new "context" action, an action that observes the current * selection for a given type and enables if instances of given type are * present. Common interfaces to watch for include {@link Openable}, * {@link Editable}, {@link Closable}, {@link Viewable}, and any interfaces * defined and exposed by various other APIs. Use {@link ActionRegistration} * annotation to register your action:: *
     * {@link ActionRegistration @ActionRegistration}(displayName="#Key")
     * {@link ActionID @ActionID}(category="Tools", id = "action.pkg.YourClass")
     * public final class YourClass implements {@link ActionListener} {
     *    Openable context;
     *
     *    public YourClass(Openable context) {
     *      this.context = context;
     *    }
     *
     *    public void actionPerformed({@link ActionEvent} ev) {
     *       // do something with context
     *    }
     * }
     * 
* In case you are interested in creating multi selection action, just * change parameters of your constructor: *
     * {@link ActionRegistration @ActionRegistration}(displayName="#Key")
     * {@link ActionID @ActionID}(category="Tools", id = "action.pkg.YourClass")
     * public final class YourClass implements {@link ActionListener} {
     *    List<Openable> context;
     *
     *    public YourClass(List<Openable> context) {
     *      this.context = context;
     *    }
     *
     *    public void actionPerformed({@link ActionEvent} ev) {
     *       // do something with context
     *    }
     * }
     * 
*

* Actions of this kind can be declared in * * layer file using following XML snippet: *

     * <file name="action-pkg-ClassName.instance">
     *   <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
     *   <attr name="type" stringvalue="org.netbeans.api.actions.Openable"/>
     *   <attr name="selectionType" stringvalue="ANY"/> <-- or EXACTLY_ONE -->
     *   <attr name="delegate" newvalue="action.pkg.YourAction"/>
     * 
     *   <!--
     *      Similar registration like in case of "callback" action.
     *      May be missing completely:
     *   -->
     *   <attr name="key" stringvalue="KeyInActionMap"/>
     *   <attr name="surviveFocusChange" boolvalue="false"/> 
     *   <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
     *   <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
     *   <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
     *   <!-- if desired: <attr name="asynchronous" boolvalue="true"/> -->
     * </file>
     * 
* In the previous case there has to be a class with public default constructor * named action.pkg.YourAction. It has to implement * {@link ContextAwareAction} interface. Its {@link ContextAwareAction#createContextAwareInstance(org.openide.util.Lookup)} * method is called when the action is invoked. The passed in {@link Lookup} * contains instances of the type interface, actionPerformed * is then called on the returned clone. *

* Alternatively one can use support for simple dependency injection by * using following attributes: *

     * <file name="action-pkg-ClassName.instance">
     *   <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
     *   <attr name="type" stringvalue="org.netbeans.api.actions.Openable"/>
     *   <attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
     *   <attr name="selectionType" stringvalue="EXACTLY_ONE"/>
     *   <attr name="injectable" stringvalue="pkg.YourClass"/>
     *   <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
     *   <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
     *   <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
     * </file>
     * 
* where pkg.YourClass is defined with public constructor taking * type: *
     * public final class YourClass implements ActionListener {
     *    Openable context;
     *
     *    public YourClass(Openable context) {
     *      this.context = context;
     *    }
     *
     *    public void actionPerformed(ActionEvent ev) {
     *       // do something with context
     *    }
     * }
     * 
* The instance of this class is created when the action is invoked and * its constructor is fed with the instance of type inside * the active context. actionPerformed method is called then. *

* To create action that handled multiselection * one can use following XML snippet: *

     * <file name="action-pkg-ClassName.instance">
     *   <attr name="type" stringvalue="org.netbeans.api.actions.Openable"/>
     *   <attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
     *   <attr name="selectionType" stringvalue="ANY"/>
     *   <attr name="injectable" stringvalue="pkg.YourClass"/>
     *   <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
     *   <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/>
     *   <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> -->
     *   <!-- since 7.33: <attr name="context" newvalue="org.my.own.LookupImpl"/> -->
     * </file>
     * 
* Now the constructor of YourClass needs to have following * form: *
     * public final class YourClass implements ActionListener {
     *    List<Openable> context;
     *
     *    public YourClass(List<Openable> context) {
     *      this.context = context;
     *    }
     *  }
     * 
*

* Further attributes are defined to control action's enabled and checked state. * Attributes which control enable state are prefixed by "{@code enableOn}". Attributes * controlling checked state have prefix "{@code checkedOn}": *


     * <file name="action-pkg-ClassName.instance">
     *   <!-- Enable on certain type in Lookup -->
     *   <attr name="enableOnType" stringvalue="qualified.type.name"/>
     * 
     *   <!-- Monitor specific property in that type -->
     *   <attr name="enableOnProperty" stringvalue="propertyName"/>
     * 
     *   <!-- The property value, which corresponds to enabled action.
     *           Values "#null" and "#non-null" are treated specially.
     *   -->
     *   <attr name="enableOnValue" stringvalue="propertyName"/>
     * 
     *   <!-- Name of custom listener interface -->
     *   <attr name="enableOnChangeListener" stringvalue="qualifier.listener.interface"/>
     * 
     *   <!-- Name of listener method that triggers state re-evaluation  -->
     *   <attr name="enableOnMethod" stringvalue="methodName"/>
     * 
     *   <!-- Delegate to the action instance for final decision -->
     *   <attr name="enableOnActionProperty" stringvalue="actionPropertyName"/>
     * 
     *   <!-- ... -->
     * 
     * </file>
     * 
     * 
* * @param type the object to seek for in the active context * @param single shall there be just one or multiple instances of the object * @param surviveFocusChange shall the action remain enabled and act on * previous selection even if no selection is currently in context? * @param delegate action to call when this action is invoked * @param key alternatively an action can be looked up in action map * (see {@link Actions#callback(java.lang.String, javax.swing.Action, boolean, java.lang.String, java.lang.String, boolean)}) * @param displayName localized name of the action (including ampersand) * @param iconBase the location to the action icon * @param noIconInMenu true if this icon shall not have an item in menu * @return new instance of context aware action watching for type * @since 7.10 */ public static ContextAwareAction context( Class type, boolean single, boolean surviveFocusChange, ContextAwareAction delegate, String key, String displayName, String iconBase, boolean noIconInMenu ) { Map map = new HashMap(); map.put("key", key); // NOI18N map.put("surviveFocusChange", surviveFocusChange); // NOI18N map.put("delegate", delegate); // NOI18N map.put("type", type); // NOI18N map.put("selectionType", single ? ContextSelection.EXACTLY_ONE : ContextSelection.ANY); map.put("displayName", displayName); // NOI18N map.put("iconBase", iconBase); // NOI18N map.put("noIconInMenu", noIconInMenu); // NOI18N return GeneralAction.context(map, true); } static Action context(Map fo) { Object context = fo.get("context"); if (context instanceof Lookup) { Lookup lkp = (Lookup)context; return GeneralAction.bindContext(fo, lkp); } else { return GeneralAction.context(fo); } } static ContextAction.Performer inject(final Map fo) { Object t = fo.get("selectionType"); // NOI18N if (ContextSelection.EXACTLY_ONE.toString().equals(t)) { return new InjectorExactlyOne(fo); } if (ContextSelection.ANY.toString().equals(t)) { return new InjectorAny(fo); } throw new IllegalStateException("no selectionType parameter in " + fo); // NOI18N } static ContextAction.Performer performer(final Map fo) { String type = (String)fo.get("type"); if (type.equals(Openable.class.getName())) { return new ActionDefaultPerfomer(0); } if (type.equals(Viewable.class.getName())) { return new ActionDefaultPerfomer(1); } if (type.equals(Editable.class.getName())) { return new ActionDefaultPerfomer(2); } if (type.equals(Closable.class.getName())) { return new ActionDefaultPerfomer(3); } if (type.equals(Printable.class.getName())) { return new ActionDefaultPerfomer(4); } throw new IllegalStateException(type); } /** * Locates a specific action programmatically. * The action will typically have been registered using {@link ActionRegistration}. *

Normally an {@link ActionReference} will suffice to insert the action * into various UI elements (typically using {@link Utilities#actionsForPath}), * but in special circumstances you may need to find a single known action. * This method is just a shortcut for using {@link FileUtil#getConfigObject} * with the correct arguments, plus using {@link AcceleratorBinding#setAccelerator}. * @param category as in {@link ActionID#category} * @param id as in {@link ActionID#id} * @return the action registered under that ID, or null * @throws IllegalArgumentException if a corresponding {@link ActionID} would have been rejected * @since 7.42 */ public static Action forID(String category, String id) throws IllegalArgumentException { // copied from ActionProcessor: if (category.startsWith("Actions/")) { throw new IllegalArgumentException("category should not start with Actions/: " + category); } if (!FQN.matcher(id).matches()) { throw new IllegalArgumentException("id must be valid fully qualified name: " + id); } String path = "Actions/" + category + "/" + id.replace('.', '-') + ".instance"; Action a = FileUtil.getConfigObject(path, Action.class); if (a == null) { return null; } FileObject def = FileUtil.getConfigFile(path); if (def != null) { AcceleratorBinding.setAccelerator(a, def); } return a; } private static final String IDENTIFIER = "(?:\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)"; // NOI18N private static final Pattern FQN = Pattern.compile(IDENTIFIER + "(?:[.]" + IDENTIFIER + ")*"); // NOI18N /** Extracts help from action. */ private static HelpCtx findHelp(Action a) { if (a instanceof HelpCtx.Provider) { return ((HelpCtx.Provider) a).getHelpCtx(); } else { return HelpCtx.DEFAULT_HELP; } } // #40824 - when the text changes, it's too late to update in JmenuPlus.popup.show() (which triggers the updateState() in the MenuBridge). // check JmenuPlus.setPopupMenuVisible() static void prepareMenuBridgeItemsInContainer(Container c) { Component[] comps = c.getComponents(); for (int i = 0; i < comps.length; i++) { if (comps[i] instanceof JComponent) { JComponent cop = (JComponent) comps[i]; MenuBridge bridge = (MenuBridge) cop.getClientProperty("menubridgeresizehack"); if (bridge != null) { bridge.updateState(null); } } } } // // Methods for configuration of MenuItems // /** Method to prepare the margins and text positions. */ static void prepareMargins(JMenuItem item, Action action) { item.setHorizontalTextPosition(JMenuItem.RIGHT); item.setHorizontalAlignment(JMenuItem.LEFT); } /** Updates value of the key * @param item item to update * @param action the action to update */ static void updateKey(JMenuItem item, Action action) { if (!(item instanceof JMenu)) { item.setAccelerator((KeyStroke) action.getValue(Action.ACCELERATOR_KEY)); } } /** Interface for the creating Actions.SubMenu. It provides the methods for * all items in submenu: name shortcut and perform method. Also has methods * for notification of changes of the model. * @deprecated used by deprecated {@link SubMenu} */ @Deprecated public static interface SubMenuModel { /** @return count of the submenu items. */ public int getCount(); /** Gets label for specific index * @param index of the submenu item * @return label for this menu item (or null for a separator) */ public String getLabel(int index); /** Gets shortcut for specific index * @index of the submenu item * @return menushortcut for this menu item */ // public MenuShortcut getMenuShortcut(int index); /** Get context help for the specified item. * This can be used to associate help with individual items. * You may return null to just use the context help for * the associated system action (if any). * Note that only help IDs will work, not URLs. * @return the context help, or null */ public HelpCtx getHelpCtx(int index); /** Perform the action on the specific index * @param index of the submenu item which should be performed */ public void performActionAt(int index); /** Adds change listener for changes of the model. */ public void addChangeListener(ChangeListener l); /** Removes change listener for changes of the model. */ public void removeChangeListener(ChangeListener l); } /** Listener on showing/hiding state of the component. * Is attached to menu or toolbar item in prepareXXX methods and * method addNotify is called when the item is showing and * the method removeNotify is called when the item is hidding. *

* There is a special support listening on changes in the action and * if such change occures, updateState method is called to * reflect it. */ private abstract static class Bridge extends Object implements PropertyChangeListener { /** component to work with */ protected JComponent comp; /** action to associate */ protected Action action; private final PropertyChangeListener actionL; /** @param comp component * @param action the action */ public Bridge(JComponent comp, Action action) { if(comp == null || action == null) { throw new IllegalArgumentException( "None of the arguments can be null: comp=" + comp + //NOI18N ", action=" + action); // NOI18N } this.comp = comp; this.action = action; actionL = WeakListeners.propertyChange(this, action); // associate context help, if applicable // [PENDING] probably belongs in ButtonBridge.updateState to make it dynamic HelpCtx help = findHelp(action); if ((help != null) && !help.equals(HelpCtx.DEFAULT_HELP) && (help.getHelpID() != null)) { HelpCtx.setHelpIDString(comp, help.getHelpID()); } } protected void prepare() { comp.addPropertyChangeListener(new VisL()); if (comp.isShowing()) { addNotify(); } else { updateState(null); } } /** Attaches listener to given action */ final void addNotify() { action.addPropertyChangeListener(actionL); updateState(null); } /** Remove the listener */ final void removeNotify() { action.removePropertyChangeListener(actionL); } /** @param changedProperty the name of property that has changed * or null if it is not known */ public abstract void updateState(String changedProperty); /** Listener to changes of some properties. * Multicast - reacts to keymap changes and ancestor changes * together. */ public void propertyChange(final PropertyChangeEvent ev) { //assert EventQueue.isDispatchThread(); if (!EventQueue.isDispatchThread()) { new IllegalStateException("This must happen in the event thread!").printStackTrace(); } updateState(ev.getPropertyName()); } // Must be separate from general PCL, because otherwise // SystemAction.PROP_ENABLED -> updateState("enabled") -> // button.setEnabled(...) -> JButton.PROP_ENABLED -> // updateState("enabled") -> button.setEnabled(same) private class VisL implements PropertyChangeListener { VisL() { } public void propertyChange(final PropertyChangeEvent ev) { if ("ancestor".equals(ev.getPropertyName())) { // ancestor change - decide if parent is null or not if (ev.getNewValue() != null) { addNotify(); } else { removeNotify(); } } } } } /** Bridge between an action and button. */ private static class ButtonBridge extends Bridge implements ActionListener { /** UI logger to notify about invocation of an action */ private static Logger UILOG = Logger.getLogger("org.netbeans.ui.actions"); // NOI18N /** the button */ protected AbstractButton button; public ButtonBridge(AbstractButton button, Action action) { super(button, action); button.addActionListener(action); this.button = button; button.addActionListener(this); } public void actionPerformed(ActionEvent ev) { LogRecord rec = new LogRecord(Level.FINER, "UI_ACTION_BUTTON_PRESS"); // NOI18N rec.setParameters(new Object[] { button, button.getClass().getName(), action, action.getClass().getName(), action.getValue(Action.NAME) }); rec.setResourceBundle(NbBundle.getBundle(Actions.class)); rec.setResourceBundleName(Actions.class.getPackage().getName() + ".Bundle"); // NOI18N rec.setLoggerName(UILOG.getName()); UILOG.log(rec); } protected void updateButtonIcon() { Object i = null; Object base = action.getValue("iconBase"); // NOI18N boolean useSmallIcon = true; Object prop = button.getClientProperty("PreferredIconSize"); //NOI18N if (prop instanceof Integer) { if (((Integer) prop).intValue() == 24) { useSmallIcon = false; } } if (action instanceof SystemAction) { if (base instanceof String) { String b = (String) base; ImageIcon imgIcon = loadImage(b, useSmallIcon, null); if (imgIcon != null) { i = imgIcon; button.setIcon(imgIcon); button.setDisabledIcon(ImageUtilities.createDisabledIcon(imgIcon)); } else { SystemAction sa = (SystemAction) action; i = sa.getIcon(useTextIcons()); button.setIcon((Icon) i); button.setDisabledIcon(ImageUtilities.createDisabledIcon((Icon) i)); } } else { SystemAction sa = (SystemAction) action; i = sa.getIcon(useTextIcons()); button.setIcon((Icon) i); button.setDisabledIcon(ImageUtilities.createDisabledIcon((Icon) i)); } } else { //Try to get icon from iconBase for non SystemAction action if (base instanceof String) { String b = (String) base; ImageIcon imgIcon = loadImage(b, useSmallIcon, null); // NOI18N if (imgIcon != null) { i = imgIcon; button.setIcon((Icon) i); button.setDisabledIcon(ImageUtilities.createDisabledIcon(imgIcon)); } else { i = action.getValue(Action.SMALL_ICON); if (i instanceof Icon) { button.setIcon((Icon) i); button.setDisabledIcon(ImageUtilities.createDisabledIcon((Icon) i)); } else { button.setIcon(nonNullIcon(null)); } } } else { i = action.getValue(Action.SMALL_ICON); if (i instanceof Icon) { button.setIcon((Icon) i); button.setDisabledIcon(ImageUtilities.createDisabledIcon((Icon) i)); } else { button.setIcon(nonNullIcon(null)); } } } if (base instanceof String) { String b = (String) base; ImageIcon imgIcon = null; if (i == null) { // even for regular icon imgIcon = loadImage(b, useSmallIcon, null); if (imgIcon != null) { button.setIcon(imgIcon); } i = imgIcon; } ImageIcon pImgIcon = loadImage(b, useSmallIcon, "_pressed"); // NOI18N if (pImgIcon != null) { button.setPressedIcon(pImgIcon); } ImageIcon rImgIcon = loadImage(b, useSmallIcon, "_rollover"); // NOI18N if (rImgIcon != null) { button.setRolloverIcon(rImgIcon); } ImageIcon dImgIcon = loadImage(b, useSmallIcon, "_disabled"); // NOI18N if (dImgIcon != null) { button.setDisabledIcon(dImgIcon); } else if (imgIcon != null) { button.setDisabledIcon(ImageUtilities.createDisabledIcon(imgIcon)); } ImageIcon sImgIcon = loadImage(b, useSmallIcon, "_selected"); // NOI18N if (sImgIcon != null) { button.setSelectedIcon(sImgIcon); } sImgIcon = loadImage(b, useSmallIcon, "_rolloverSelected"); // NOI18N if (sImgIcon != null) { button.setRolloverSelectedIcon(sImgIcon); } sImgIcon = loadImage(b, useSmallIcon, "_disabledSelected"); // NOI18N if (sImgIcon != null) { button.setDisabledSelectedIcon(sImgIcon); } } } static ImageIcon loadImage(String iconBase, boolean useSmallIcon, String suffix) { if (!useSmallIcon) { String bigBase = insertBeforeSuffix(iconBase, "24"); // NOI18N ImageIcon icon = ImageUtilities.loadImageIcon(insertBeforeSuffix(bigBase, suffix), true); if (icon != null) { return icon; } } return ImageUtilities.loadImageIcon(insertBeforeSuffix(iconBase, suffix), true); // NOI18N } static String insertBeforeSuffix(String path, String toInsert) { if (toInsert == null) { return path; } String withoutSuffix = path; String suffix = ""; // NOI18N if (path.lastIndexOf('.') >= 0) { withoutSuffix = path.substring(0, path.lastIndexOf('.')); suffix = path.substring(path.lastIndexOf('.'), path.length()); } return withoutSuffix + toInsert + suffix; } /** @param changedProperty the name of property that has changed * or null if it is not known */ public void updateState(String changedProperty) { // note: "enabled" (== SA.PROP_ENABLED) hardcoded in AbstractAction if ((changedProperty == null) || changedProperty.equals(SystemAction.PROP_ENABLED)) { button.setEnabled(action.isEnabled()); } if ( (changedProperty == null) || changedProperty.equals(SystemAction.PROP_ICON) || changedProperty.equals(Action.SMALL_ICON) || changedProperty.equals("iconBase") ) { // NOI18N updateButtonIcon(); } if ( (changedProperty == null) || changedProperty.equals(Action.ACCELERATOR_KEY) || (changedProperty.equals(Action.NAME) && (action.getValue(Action.SHORT_DESCRIPTION) == null)) || changedProperty.equals(Action.SHORT_DESCRIPTION) ) { String tip = findKey(action); String toolTip = (String) action.getValue(Action.SHORT_DESCRIPTION); if (toolTip == null) { toolTip = (String) action.getValue(Action.NAME); toolTip = (toolTip == null) ? "" : cutAmpersand(toolTip); } if ((tip == null) || tip.equals("")) { // NOI18N button.setToolTipText(toolTip); } else { button.setToolTipText( org.openide.util.NbBundle.getMessage(Actions.class, "FMT_ButtonHint", toolTip, tip) ); } } if ( button instanceof javax.accessibility.Accessible && ((changedProperty == null) || changedProperty.equals(Action.NAME)) ) { button.getAccessibleContext().setAccessibleName((String) action.getValue(Action.NAME)); } } /** Should textual icons be used when lacking a real icon? * In the default implementation, true. * @return true if so */ protected boolean useTextIcons() { return true; } } /** Bridge for button and boolean action. */ private static class BooleanButtonBridge extends ButtonBridge { private final BooleanStateAction stateAction; private final PropertyChangeListener bsaL; public BooleanButtonBridge(AbstractButton button, BooleanStateAction bsa) { super(button, bsa); this.stateAction = bsa; if (bsa != null && bsa != action) { bsaL = WeakListeners.propertyChange(this, BooleanStateAction.PROP_BOOLEAN_STATE, bsa); bsa.addPropertyChangeListener(bsaL); } else { bsaL = null; } } public BooleanButtonBridge(AbstractButton button, Action action) { super(button, action); this.stateAction = null; this.bsaL = null; } /** @param changedProperty the name of property that has changed * or null if it is not known */ @Override public void updateState(String changedProperty) { super.updateState(changedProperty); if ((changedProperty == null) || changedProperty.equals(BooleanStateAction.PROP_BOOLEAN_STATE) || (bsaL == null && changedProperty.equals(Action.SELECTED_KEY))) { button.setSelected(getBooleanState()); } } protected boolean getBooleanState() { if (action instanceof AlwaysEnabledAction.CheckBox) { return ((AlwaysEnabledAction.CheckBox)action).isPreferencesSelected(); } return stateAction != null ? stateAction.getBooleanState() : Boolean.TRUE.equals(action.getValue(Action.SELECTED_KEY)); } } /** Menu item bridge. */ private static class MenuBridge extends ButtonBridge { /** behave like menu or popup */ private boolean popup; /** Constructor. * @param popup pop-up menu */ public MenuBridge(JMenuItem item, Action action, boolean popup) { super(item, action); this.popup = popup; if (popup) { prepareMargins(item, action); } else { // #40824 hack item.putClientProperty("menubridgeresizehack", this); // #40824 hack end. } } protected @Override void prepare() { if (popup) { // popups generally get no hierarchy events, yet we need to listen to other changes addNotify(); } else { super.prepare(); } } /** @param changedProperty the name of property that has changed * or null if it is not known */ @Override public void updateState(String changedProperty) { if (this.button == null) { this.button = (AbstractButton) this.comp; } if ((changedProperty == null) || changedProperty.equals(SystemAction.PROP_ENABLED)) { button.setEnabled(action.isEnabled()); } if ((changedProperty == null) || !changedProperty.equals(Action.ACCELERATOR_KEY)) { updateKey((JMenuItem) comp, action); } if (!popup) { if ( (changedProperty == null) || changedProperty.equals(SystemAction.PROP_ICON) || changedProperty.equals(Action.SMALL_ICON) || changedProperty.equals("iconBase") ) { // NOI18N updateButtonIcon(); } } if ((changedProperty == null) || changedProperty.equals(Action.NAME)) { Object s = null; boolean useMnemonic = true; if (popup) { s = action.getValue("popupText"); // NOI18N } if (s == null) { s = action.getValue("menuText"); // NOI18N useMnemonic = !popup; } if (s == null) { s = action.getValue(Action.NAME); useMnemonic = !popup; } if (s instanceof String) { setMenuText(((JMenuItem) comp), (String) s, useMnemonic); //System.out.println("Menu item: " + s); //System.out.println("Action class: " + action.getClass()); } } } @Override protected void updateButtonIcon() { Object i = null; Object obj = action.getValue("noIconInMenu"); //NOI18N Object base = action.getValue("iconBase"); // NOI18N if (Boolean.TRUE.equals(obj)) { //button.setIcon(nonNullIcon(null)); return; } if (action instanceof SystemAction) { SystemAction sa = (SystemAction) action; i = sa.getIcon(useTextIcons()); if( i != null ) { button.setIcon((Icon) i); button.setDisabledIcon(ImageUtilities.createDisabledIcon((Icon) i)); } } else { if (base == null) { i = action.getValue(Action.SMALL_ICON); if (i instanceof Icon) { button.setIcon((Icon) i); button.setDisabledIcon(ImageUtilities.createDisabledIcon((Icon) i)); } else { //button.setIcon(nonNullIcon(null)); } } } if (base instanceof String) { String b = (String) base; ImageIcon imgIcon = null; if (i == null) { // even for regular icon imgIcon = ImageUtilities.loadImageIcon(b, true); if (imgIcon != null) { button.setIcon(imgIcon); button.setDisabledIcon(ImageUtilities.createDisabledIcon(imgIcon)); } } ImageIcon pImgIcon = ImageUtilities.loadImageIcon(insertBeforeSuffix(b, "_pressed"), true); // NOI18N if (pImgIcon != null) { button.setPressedIcon(pImgIcon); } ImageIcon rImgIcon = ImageUtilities.loadImageIcon(insertBeforeSuffix(b, "_rollover"), true); // NOI18N if (rImgIcon != null) { button.setRolloverIcon(rImgIcon); } ImageIcon dImgIcon = ImageUtilities.loadImageIcon(insertBeforeSuffix(b, "_disabled"), true); // NOI18N if (dImgIcon != null) { button.setDisabledIcon(dImgIcon); } else if (imgIcon != null) { button.setDisabledIcon(ImageUtilities.createDisabledIcon(imgIcon)); } } } @Override protected boolean useTextIcons() { return false; } } /** Check menu item bridge. */ private static final class CheckMenuBridge extends BooleanButtonBridge { /** is popup or menu */ private boolean popup; private boolean hasOwnIcon = false; /** Popup menu */ public CheckMenuBridge(JCheckBoxMenuItem item, BooleanStateAction bsa, boolean popup) { super(item, bsa); init(item, popup); } public CheckMenuBridge(JCheckBoxMenuItem item, Action action, boolean popup) { super(item, action); init(item, popup); } private void init(JCheckBoxMenuItem item, boolean popup) { this.popup = popup; if (popup) { prepareMargins(item, action); } Object base = action.getValue("iconBase"); //NOI18N Object i = null; if (action instanceof SystemAction) { i = action.getValue(SystemAction.PROP_ICON); } else { i = action.getValue(Action.SMALL_ICON); } hasOwnIcon = (base != null) || (i != null); } /** @param changedProperty the name of property that has changed * or null if it is not known */ @Override public void updateState(String changedProperty) { super.updateState(changedProperty); if ((changedProperty == null) || !changedProperty.equals(Action.ACCELERATOR_KEY)) { updateKey((JMenuItem) comp, action); } if ((changedProperty == null) || changedProperty.equals(Action.NAME)) { Object s = action.getValue(Action.NAME); if (s instanceof String) { setMenuText(((JMenuItem) comp), (String) s, true); } } } @Override protected void updateButtonIcon() { if (hasOwnIcon) { super.updateButtonIcon(); return; } if (!popup) { button.setIcon(ImageUtilities.loadImageIcon("org/openide/resources/actions/empty.gif", true)); // NOI18N } } @Override protected boolean useTextIcons() { return false; } } /** The class that listens to the menu item selections and forwards it to the * action class via the performAction() method. */ private static class ISubActionListener implements java.awt.event.ActionListener { int index; SubMenuModel support; public ISubActionListener(int index, SubMenuModel support) { this.index = index; this.support = support; } /** called when a user clicks on this menu item */ public void actionPerformed(ActionEvent e) { support.performActionAt(index); } } /** Sub menu bridge 2. */ @Deprecated private static final class SubMenuBridge extends MenuBridge implements ChangeListener, DynamicMenuContent { /** model to obtain subitems from */ private SubMenuModel model; private List currentOnes; private JMenuItem single; private JMenu multi; /** Constructor. */ public SubMenuBridge(JMenuItem one, JMenu more, Action action, SubMenuModel model, boolean popup) { super(one, action, popup); single = one; multi = more; setMenuText(multi, (String)action.getValue(Action.NAME), popup); prepareMargins(one, action); prepareMargins(more, action); currentOnes = new ArrayList(); this.model = model; } /** Called when model changes. Regenerates the model. */ public void stateChanged(ChangeEvent ev) { //assert EventQueue.isDispatchThread(); if (!EventQueue.isDispatchThread()) { new IllegalStateException("This must happen in the event thread!").printStackTrace(); } // change in keys or in submenu model // checkVisibility(); } @Override public void updateState(String changedProperty) { super.updateState(changedProperty); // checkVisibility(); } public JComponent[] getMenuPresenters() { return synchMenuPresenters(null); } public JComponent[] synchMenuPresenters(JComponent[] items) { currentOnes.clear(); int cnt = model.getCount(); if (cnt == 0) { updateState(null); currentOnes.add(single); // menu disabled single.setEnabled(false); } else if (cnt == 1) { updateState(null); currentOnes.add(single); single.setEnabled(action.isEnabled()); // generate without submenu HelpCtx help = model.getHelpCtx(0); associateHelp(single, (help == null) ? findHelp(action) : help); } else { currentOnes.add(multi); multi.removeAll(); //TODO Mnemonics.setLocalizedText(multi, (String)action.getValue(Action.NAME)); boolean addSeparator = false; int count = model.getCount(); for (int i = 0; i < count; i++) { String label = model.getLabel(i); // MenuShortcut shortcut = support.getMenuShortcut(i); if (label == null) { addSeparator = multi.getItemCount() > 0; } else { if (addSeparator) { multi.addSeparator(); addSeparator = false; } // if (shortcut == null) // (Dafe) changed to support mnemonics in item labels JMenuItem item = new JMenuItem(); Mnemonics.setLocalizedText(item, label); // attach the shortcut to the first item if (i == 0) { updateKey(item, action); } item.addActionListener(new ISubActionListener(i, model)); HelpCtx help = model.getHelpCtx(i); associateHelp(item, (help == null) ? findHelp(action) : help); multi.add(item); } associateHelp(multi, findHelp(action)); } multi.setEnabled(true); } return currentOnes.toArray(new JMenuItem[currentOnes.size()]); } private void associateHelp(JComponent comp, HelpCtx help) { if ((help != null) && !help.equals(HelpCtx.DEFAULT_HELP) && (help.getHelpID() != null)) { HelpCtx.setHelpIDString(comp, help.getHelpID()); } else { HelpCtx.setHelpIDString(comp, null); } } } // // // The presenter classes // // /** * Extension of Swing menu item with connection to * system actions. */ public static class MenuItem extends javax.swing.JMenuItem implements DynamicMenuContent { static final long serialVersionUID = -21757335363267194L; private Actions.Bridge bridge; /** Constructs a new menu item with the specified label * and no keyboard shortcut and connects it to the given SystemAction. * @param aAction the action to which this menu item should be connected * @param useMnemonic if true, the menu try to find mnemonic in action label */ public MenuItem(SystemAction aAction, boolean useMnemonic) { Actions.connect(this, aAction, !useMnemonic); } /** Constructs a new menu item with the specified label * and no keyboard shortcut and connects it to the given SystemAction. * @param aAction the action to which this menu item should be connected * @param useMnemonic if true, the menu try to find mnemonic in action label */ public MenuItem(Action aAction, boolean useMnemonic) { Actions.connect(this, aAction, !useMnemonic); } void setBridge(Actions.Bridge br) { bridge = br; } public JComponent[] synchMenuPresenters(JComponent[] items) { if (bridge != null) { bridge.updateState(null); } return getMenuPresenters(); } public JComponent[] getMenuPresenters() { return new JComponent[] {this}; } } /** CheckboxMenuItem extends the java.awt.CheckboxMenuItem and adds * a connection to boolean state actions. The ActCheckboxMenuItem * processes the ItemEvents itself and calls the action.seBooleanState() method. * It also tracks the enabled and boolean state of the action and reflects it * as its visual enabled/check state. * * @author Ian Formanek, Jan Jancura */ public static class CheckboxMenuItem extends javax.swing.JCheckBoxMenuItem { private static final long serialVersionUID = 6190621106981774043L; /** Constructs a new ActCheckboxMenuItem with the specified label * and connects it to the given BooleanStateAction. * @param aAction the action to which this menu item should be connected * @param useMnemonic if true, the menu try to find mnemonic in action label * @deprecated use {@link #CheckboxMenuItem(javax.swing.Action, boolean)}. * Have your action to implement properly {@link Action#getValue} for {@link Action#SELECTED_KEY} */ @Deprecated public CheckboxMenuItem(BooleanStateAction aAction, boolean useMnemonic) { Actions.connect(this, aAction, !useMnemonic); } /** Constructs a new ActCheckboxMenuItem with the specified label * and connects it to the given Action and its {@link Action#SELECTED_KEY} * value. * * @param aAction the action to which this menu item should be connected * @param useMnemonic if true, the menu try to find mnemonic in action label * @since 7.71 */ public CheckboxMenuItem(Action aAction, boolean useMnemonic) { Actions.connect(this, aAction, !useMnemonic); } } /** Component shown in toolbar, representing an action. * @deprecated extends deprecated ToolbarButton */ @Deprecated public static class ToolbarButton extends org.openide.awt.ToolbarButton { private static final long serialVersionUID = 6564434578524381134L; public ToolbarButton(SystemAction aAction) { super(null); Actions.connect(this, aAction); } public ToolbarButton(Action aAction) { super(null); Actions.connect(this, aAction); } /** * Gets the maximum size of this component. * @return A dimension object indicating this component's maximum size. * @see #getMinimumSize * @see #getPreferredSize * @see java.awt.LayoutManager */ @Override public Dimension getMaximumSize() { return this.getPreferredSize(); } @Override public Dimension getMinimumSize() { return this.getPreferredSize(); } } /** The Component for BooleeanState action that is to be shown * in a toolbar. * * @deprecated extends deprecated ToolbarToggleButton */ @Deprecated public static class ToolbarToggleButton extends org.openide.awt.ToolbarToggleButton { private static final long serialVersionUID = -4783163952526348942L; /** Constructs a new ActToolbarToggleButton for specified action */ public ToolbarToggleButton(BooleanStateAction aAction) { super(null, false); Actions.connect(this, aAction); } /** * Gets the maximum size of this component. * @return A dimension object indicating this component's maximum size. * @see #getMinimumSize * @see #getPreferredSize * @see java.awt.LayoutManager */ @Override public Dimension getMaximumSize() { return this.getPreferredSize(); } @Override public Dimension getMinimumSize() { return this.getPreferredSize(); } } /** SubMenu provides easy way of displaying submenu items based on * SubMenuModel. * @deprecated Extends deprecated {@link JMenuPlus}. Instead create a regular {@link JMenu} and add items to it (or use {@link DynamicMenuContent}). */ @Deprecated public static class SubMenu extends JMenuPlus implements DynamicMenuContent { private static final long serialVersionUID = -4446966671302959091L; private SubMenuBridge bridge; /** Constructs a new ActMenuItem with the specified label * and no keyboard shortcut and connects it to the given SystemAction. * No icon is used by default. * @param aAction the action to which this menu item should be connected * @param model the support for the menu items */ public SubMenu(SystemAction aAction, SubMenuModel model) { this(aAction, model, true); } /** Constructs a new ActMenuItem with the specified label * and no keyboard shortcut and connects it to the given SystemAction. * No icon is used by default. * @param aAction the action to which this menu item should be connected * @param model the support for the menu items * @param popup whether this is a popup menu */ public SubMenu(SystemAction aAction, SubMenuModel model, boolean popup) { this((Action) aAction, model, popup); } /** Constructs a new ActMenuItem with the specified label * and no keyboard shortcut and connects it to the given SystemAction. * No icon is used by default. * @param aAction the action to which this menu item should be connected * @param model the support for the menu items * @param popup whether this is a popup menu */ public SubMenu(Action aAction, SubMenuModel model, boolean popup) { bridge = new SubMenuBridge(new JMenuItem(), this, aAction, model, popup); bridge.prepare(); } public JComponent[] getMenuPresenters() { return bridge.getMenuPresenters(); } public JComponent[] synchMenuPresenters(JComponent[] items) { return bridge.synchMenuPresenters(items); } } /** * SPI for supplying alternative implementation of connection between actions and presenters. * The implementations * of this interface are being looked up in the default lookup. * If there is no implemenation in the lookup the default implementation * is used. * @see Lookup#getDefault() * @since org.openide.awt 6.9 */ public interface ButtonActionConnector { /** * Connects the action to the supplied button. * @return true if the connection was successful and no * further actions are needed. If false is returned the * default connect implementation is called */ boolean connect(AbstractButton button, Action action); /** * Connects the action to the supplied JMenuItem. * @return true if the connection was successful and no * further actions are needed. If false is returned the * default connect implementation is called */ boolean connect(JMenuItem item, Action action, boolean popup); } private static final ButtonActionConnectorGetter GET = new ButtonActionConnectorGetter(); private static Collection buttonActionConnectors() { return GET.all(); } private static final class ButtonActionConnectorGetter implements LookupListener { private final Lookup.Result result; private Collection all; ButtonActionConnectorGetter() { result = Lookup.getDefault().lookupResult(ButtonActionConnector.class); result.addLookupListener(this); resultChanged(null); } final Collection all() { return all; } @Override public void resultChanged(LookupEvent ev) { all = result.allInstances(); all.iterator().hasNext(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy