/* * 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.awt; import java.awt.*; import java.awt.event.*; import java.beans.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.Keymap; import org.openide.ErrorManager; import org.openide.util.Mutex; import org.openide.util.actions.*; import org.openide.util.HelpCtx; import org.openide.util.WeakListener; import javax.swing.Action; /** Supporting class for manipulation with menu and toolbar presenters. * * @author Jaroslav Tulach */ public class Actions extends Object { /** Top manager listener listens on TopManager.PROP_GLOBAL_KEYMAP, initialized * lazily */ private static Object GLOBAL_KEYMAP; /** 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) { Keymap map = (Keymap)org.openide.util.Lookup.getDefault().lookup(Keymap.class); if (map == null) { return null; } KeyStroke[] arr = map.getKeyStrokesForAction (action); if (arr.length == 0) { return null; } KeyStroke accelerator = arr[0]; int modifiers = accelerator.getModifiers(); String acceleratorText = ""; // NOI18N if (modifiers > 0) { acceleratorText = KeyEvent.getKeyModifiersText(modifiers); acceleratorText += "+"; // NOI18N } else if (accelerator.getKeyCode() == KeyEvent.VK_UNDEFINED) { return ""; // NOI18N } acceleratorText += KeyEvent.getKeyText(accelerator.getKeyCode()); return acceleratorText; } /** Attaches menu item to an action. * @param item menu item * @param action action * @param popup create popup or menu item */ public static void connect (JMenuItem item, SystemAction action, boolean popup) { connect (item, (Action)action, popup); } /** Attaches menu item to an action. * @param item menu item * @param action action * @param popup create popup or menu item */ static void connect (JMenuItem item, Action action, boolean popup) { Bridge b = new MenuBridge (item, action, popup); // Would make more sense to defer this until addNotify, but for some reason (why?) // if you do that, various menus start out compacted and poorly painted. b.updateState (null); } /** Attaches checkbox menu item to boolean state action. * @param item menu item * @param action action * @param popup create popup or menu item */ public static void connect (JCheckBoxMenuItem item, BooleanStateAction action, boolean popup) { Bridge b = new CheckMenuBridge (item, action, popup); b.updateState (null); } /** Connects buttons to action. * @param button the button * @param action the action */ public static void connect (AbstractButton button, SystemAction action) { connect (button, (Action)action); } /** Connects buttons to action. * @param button the button * @param action the action */ static void connect (AbstractButton button, Action action) { Bridge b = new ButtonBridge (button, action); b.updateState (null); } /** 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.updateState (null); } /** Adds a change listener to TopManager's keymap. */ private static void addKeymapListener (PropertyChangeListener l) { if (GLOBAL_KEYMAP == null) { synchronized (Actions.class) { if (GLOBAL_KEYMAP == null) { try { Class c = Class.forName("org.openide.awt.Actions$GlobalKeymapL"); // NOI18N GLOBAL_KEYMAP = (PropertyChangeSupport)c.newInstance(); } catch (Exception e) { // any object just do not ask again GLOBAL_KEYMAP = new Object (); } catch (LinkageError e) { GLOBAL_KEYMAP = new Object (); } } } } if (GLOBAL_KEYMAP instanceof PropertyChangeSupport) { ((PropertyChangeSupport)GLOBAL_KEYMAP).addPropertyChangeListener ( WeakListener.propertyChange(l, GLOBAL_KEYMAP) ); } } /** Find key strokes for action. * @param action * @return array of keystrokes that invoke that action */ private static KeyStroke[] getKeyStrokesForAction (Action action) { Keymap map = (Keymap)org.openide.util.Lookup.getDefault().lookup(Keymap.class); if (map != null) { return map.getKeyStrokesForAction (action); } else { return new KeyStroke[0]; } } /** 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. */ public static void setMenuText(AbstractButton item, String text, boolean useMnemonic) { // #17664. Handle null text also. if(text == null) { item.setText(null); return; } int i = text.indexOf('&'); String newText = text; if (i < 0) { item.setText(text); if (useMnemonic) { item.setMnemonic (0); } } else { if (i == (text.length() - 1)) { //Ampersand is last characted, use first character as shortcut item.setText(text.substring(0, i)); if (useMnemonic) { item.setMnemonic (0); } } else { item.setText(text.substring(0, i) + text.substring(i + 1)); if (useMnemonic) { char ch = text.charAt(i + 1); if ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z') || (ch>='0' && ch<='9')) { // it's latin character or arabic digit, // setting it as mnemonics item.setMnemonic(ch); } else { char latinChar = getLatinKeyboardChar(ch); item.setMnemonic (latinChar); setDisplayedMnemonicIndex(item, i, latinChar); } } } } } /** 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
/** Gets the latin symbol, which corresponds
* to some non-Latin symbol on the localized keyboard.
* The search is done via lookup of Resource bundle
* for pairs having the form, e.g. MNEMONIC_\u0424=A
.
* @param localeChar non-Latin character to be used as mnemonic
* @return character on latin keyboard, corresponding to the locale character.
*/
private static char getLatinKeyboardChar(char localeChar) {
try {
// associated should be a latin character, arabic digit
// or US-keyboard punctuation
return org.openide.util.NbBundle.getBundle(Actions.class)
.getString("MNEMONIC_" + localeChar).charAt(0); // NOI18N
} catch (java.util.MissingResourceException ex) {
// correspondence not found, returning the character itself
return localeChar;
}
}
/** holds instance of Actions14 or plain object if not on JDK1.4 */
private static Object actions14;
/** Wrapper for the
* AbstractButton.setDisplayedMnemonicIndex
method.
*
* There is a special support listening on changes in the action and
* if such change occures, updateState method is called to
* reflect it.
*/
private static abstract class Bridge extends Object
implements PropertyChangeListener {
/** component to work with */
protected JComponent comp;
/** action to associate */
protected Action action;
/** @param comp component
* @param action the action
*/
public Bridge (JComponent comp, Action action) {
this.comp = comp;
this.action = action;
// visibility listener
Bridge.this.comp.addPropertyChangeListener(new VisL());
if (Bridge.this.comp.isShowing ()) {
addNotify ();
}
// listener on keys used for this object
addKeymapListener (this);
// 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 ());
}
/** Attaches listener to given action */
public void addNotify () {
action.addPropertyChangeListener (this);
updateState (null);
}
/** Remove the listener */
public void removeNotify () {
action.removePropertyChangeListener (this);
}
/** @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) {
// do in EventQueue
Mutex.EVENT.readAccess (new Runnable () {
public void run () {
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 {
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 {
/** the button */
protected AbstractButton button;
public ButtonBridge (AbstractButton button, Action action) {
super (button, action);
button.addActionListener (this);
this.button = button;
}
/** @param changedProperty the name of property that has changed
* or null if it is not known
*/
public void updateState (String changedProperty) {
boolean didToolTip = false;
if (changedProperty == null || changedProperty.equals ("enabled")) { // NOI18N
button.setEnabled (action.isEnabled ());
}
if (changedProperty == null || changedProperty.equals ("icon")) { // NOI18N
if (action instanceof SystemAction) {
SystemAction sa = (SystemAction)action;
button.setIcon (sa.getIcon (useTextIcons ()));
} else {
Object i = action.getValue (Action.SMALL_ICON);
if (i instanceof Icon) {
button.setIcon ((Icon)i);
}
}
}
if (changedProperty == null || changedProperty.equals (Action.SHORT_DESCRIPTION)) {
String shortDesc = (String) action.getValue (Action.SHORT_DESCRIPTION);
if (shortDesc != null && !shortDesc.equals (action.getValue (Action.NAME))) {
button.setToolTipText (shortDesc);
didToolTip = true;
}
}
if (! didToolTip && (changedProperty == null ||
changedProperty.equals ("globalKeymap"))) { // NOI18N
String tip = findKey (action);
String an = cutAmpersand ((String)action.getValue (Action.NAME));
if (tip == null || tip.equals("")) { // NOI18N
button.setToolTipText(an);
} else {
button.setToolTipText(org.openide.util.NbBundle.getMessage(
Actions.class, "FMT_ButtonHint", an, 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;
}
/**
*/
public void actionPerformed(final java.awt.event.ActionEvent ev) {
invokeAction(action, ev);
}
}
/** Utility method for invoking actions in separate thread. Note:
* it uses reflection because it should work without
* the rest of the IDE classes.
*/
static void invokeAction(Action a, ActionEvent ev) {
Throwable t = null;
try {
Class c = Class.forName("org.openide.actions.ActionManager"); // NOI18N
Object o = org.openide.util.Lookup.getDefault ().lookup(c);
if (o != null) {
// lookup has found the instance
// use reflection now
java.lang.reflect.Method m = c.getMethod("invokeAction", // NOI18N
new Class[] {
javax.swing.Action.class,
java.awt.event.ActionEvent.class });
m.invoke(o, new Object[] { a, ev } );
// everything went ok -->
return;
}
}
// exceptions from forName:
catch (ClassNotFoundException x) { }
catch (ExceptionInInitializerError x) { }
catch (LinkageError x) { }
// exceptions from getMethod:
catch (SecurityException x) { t = x; }
catch (NoSuchMethodException x) { t = x;}
// exceptions from invoke
catch (IllegalAccessException x) { t = x;}
catch (IllegalArgumentException x) { t = x;}
catch (java.lang.reflect.InvocationTargetException x) {
t = x;
}
if (t != null) {
ErrorManager.getDefault ().notify(ErrorManager.INFORMATIONAL, t);
}
// something went wrong --> invoke the action directly
a.actionPerformed(ev);
}
/** Bridge for button and boolean action.
*/
private static class BooleanButtonBridge extends ButtonBridge {
public BooleanButtonBridge (AbstractButton button, BooleanStateAction action) {
super (button, action);
}
/** @param changedProperty the name of property that has changed
* or null if it is not known
*/
public void updateState (String changedProperty) {
super.updateState (changedProperty);
if (changedProperty == null || changedProperty.equals (BooleanStateAction.PROP_BOOLEAN_STATE)) {
button.setSelected (((BooleanStateAction)action).getBooleanState ());
}
}
}
/** 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);
}
}
/** @param changedProperty the name of property that has changed
* or null if it is not known
*/
public void updateState (String changedProperty) {
if (changedProperty == null || changedProperty.equals ("enabled")) { // NOI18N
button.setEnabled (action.isEnabled ());
}
if (changedProperty == null || !changedProperty.equals ("accelerator")) { // NOI18N
updateKey ((JMenuItem)comp, action);
}
if (!popup) {
if (changedProperty == null || changedProperty.equals ("icon")) { // NOI18N
Object i = action.getValue (Action.SMALL_ICON);
if (i instanceof Icon) {
button.setIcon ((Icon)i);
}
}
}
if (changedProperty == null || changedProperty.equals ("name")) { // NOI18N
Object s = action.getValue (Action.NAME);
if (s instanceof String) {
setMenuText (((JMenuItem)comp), (String)s, !popup);
}
}
}
// Not actually used:
protected boolean useTextIcons () {
return false;
}
}
/** Check menu item bridge.
*/
private static final class CheckMenuBridge extends BooleanButtonBridge {
/** is popup or menu */
private boolean popup;
/** Popup menu */
public CheckMenuBridge (JCheckBoxMenuItem item, BooleanStateAction action, boolean popup) {
super (item, action);
this.popup = popup;
if (popup) {
prepareMargins (item, action);
}
}
/** @param changedProperty the name of property that has changed
* or null if it is not known
*/
public void updateState (String changedProperty) {
super.updateState (changedProperty);
if (changedProperty == null || !changedProperty.equals ("accelerator")) { // NOI18N
updateKey ((JMenuItem)comp, action);
}
if (changedProperty == null || changedProperty.equals ("name")) { // NOI18N
Object s = action.getValue (Action.NAME);
if (s instanceof String) {
setMenuText (((JMenuItem)comp), (String)s, !popup);
}
}
}
protected boolean useTextIcons () {
return false;
}
}
/** Sub menu bridge.
*/
private static final class SubMenuBridge extends MenuBridge
implements ChangeListener, Runnable {
/** model to obtain subitems from */
private SubMenuModel model;
/** submenu */
private SubMenu menu;
/** Constructor.
*/
public SubMenuBridge (SubMenu item, SystemAction action, SubMenuModel model, boolean popup) {
super (item, action, popup);
prepareMargins (item, action);
menu = item;
this.model = model;
}
public void addNotify () {
super.addNotify ();
model.addChangeListener (this);
generateSubMenu ();
}
public void removeNotify () {
model.removeChangeListener (this);
super.removeNotify ();
}
/** Called when model changes. Regenerates the model.
*/
public void stateChanged (ChangeEvent ev) {
// transfers the execution into AWT event thread
org.openide.util.Mutex.EVENT.readAccess (this);
}
/** Called only in AWT event thread. Safe to work with component hiearachy.
*/
public void run () {
// change in keys or in submenu model
generateSubMenu ();
}
/** Regenerates the menu
*/
private void generateSubMenu() {
boolean shouldUpdate = false;
try {
menu.removeAll ();
int cnt = model.getCount ();
if (cnt != menu.previousCount) {
// update UI
shouldUpdate = true;
}
// in all cases remeber the previous
menu.previousCount = cnt;
// remove if there is an previous listener
if (menu.oneItemListener != null) {
menu.removeActionListener(menu.oneItemListener);
}
if (cnt == 0) {
// menu disabled
menu.setEnabled (false);
if (menu.oneItemListener != null) {
menu.removeActionListener (menu.oneItemListener);
menu.oneItemListener = null;
}
return;
} else {
menu.setEnabled (true);
// go on
}
if (cnt == 1) {
// generate without submenu
menu.addActionListener(menu.oneItemListener = new ISubActionListener(0, model));
HelpCtx help = model.getHelpCtx (0);
associateHelp (menu, help == null ? findHelp (action) : help);
} else {
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 = menu.getComponentCount() > 0;
} else {
if(addSeparator) {
menu.addSeparator();
addSeparator = false;
}
// if (shortcut == null)
// (Dafe) changed to support mnemonics in item labels
JMenuItem item = new JMenuItem();
Actions.setMenuText(item, label, true);
// 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);
menu.add(item);
}
}
associateHelp (menu, findHelp (action));
}
} finally {
if (shouldUpdate) {
menu.updateUI ();
}
}
}
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 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);
}
}
}
//
// Methods for configuration of MenuItems
//
/** Method to prepare the margins and text positions.
*/
static void prepareMargins (JMenuItem item, Action action) {
Insets margin = item.getMargin ();
margin.left = 0;
item.setMargin(margin);
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 SubMenu || !(item instanceof JMenu)) {
if (item instanceof SubMenu && !((SubMenu)item).useAccel()) {
item.setAccelerator (null);
} else {
KeyStroke[] arr = getKeyStrokesForAction (action);
if (arr.length != 0) {
// assign the key
item.setAccelerator (arr[0]);
} else {
item.setAccelerator (null);
}
}
}
}
//
//
// The presenter classes
//
//
/** Actions.MenuItem extends the java.awt.MenuItem and adds a connection to Corona
* system actions. The ActMenuItem processes the MenuEvents itself and
* calls the action.performAction() method.
* It also tracks the enabled state of the action and reflects it as its
* visual enabled state.
*
*/
public static class MenuItem extends javax.swing.JMenuItem {
static final long serialVersionUID =-21757335363267194L;
/** Constructs a new ActMenuItem with the specified label
* and no keyboard shortcut and connects it to the given SystemAction.
* @param action the action to which this menu item should be connected
* @param label a string label for the check box menu item,
* or null for an unlabeled menu item.
* @param showIcon if true, the menu item has an icon of the action
* @param useMnemonic if true, the menu try to find mnemonic in action label
*/
public MenuItem (SystemAction aAction, boolean useMnemonic) {
Actions.connect (this, aAction, !useMnemonic);
}
}
/** CheckboxMenuItem extends the java.awt.CheckboxMenuItem and adds
* a connection to Corona 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 {
static final long serialVersionUID =6190621106981774043L;
/** Constructs a new ActCheckboxMenuItem with the specified label
* and connects it to the given BooleanStateAction.
* @param action the action to which this menu item should be connected
* @param label a string label for the check box menu item,
* or null for an unlabeled menu item.
* @param useMnemonic if true, the menu try to find mnemonic in action label
*/
public CheckboxMenuItem (BooleanStateAction aAction, boolean useMnemonic) {
Actions.connect (this, aAction, !useMnemonic);
}
}
/** Component shown in toolbar, representing an action.
*
*/
public static class ToolbarButton extends org.openide.awt.ToolbarButton {
static final long serialVersionUID =6564434578524381134L;
public ToolbarButton (SystemAction 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 LayoutManager
*/
public Dimension getMaximumSize() {
return this.getPreferredSize ();
}
public Dimension getMinimumSize() {
return this.getPreferredSize ();
}
}
/** The Component for BooleeanState action that is to be shown
* in a toolbar.
*
*/
public static class ToolbarToggleButton extends org.openide.awt.ToolbarToggleButton {
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 LayoutManager
*/
public Dimension getMaximumSize() {
return this.getPreferredSize ();
}
public Dimension getMinimumSize() {
return this.getPreferredSize ();
}
}
/** 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.
*/
public static interface SubMenuModel {
/** @return count of the submenu items. */
public int getCount();
/** Gets label for specific index
* @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
* @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);
}
/** SubMenu provides easy way of displaying submenu items based on
* SubMenuModel.
*/
public static class SubMenu extends org.openide.awt.JMenuPlus {
/** number of previous sub items */
int previousCount = -1;
/** listener to remove from this menu or null
*/
ActionListener oneItemListener;
/** The keystroke which acts as the menu's accelerator.
* This menu can have an accelerator! */
private KeyStroke accelerator;
/** The model of the submenu used in menuitem generation */
private SubMenuModel subModel;
/** 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 action the action to which this menu item should be connected
* @param label a string label for the check box menu item,
* or null for an unlabeled menu item.
* @param support the support for the menu items
*/
public SubMenu(SystemAction aAction, SubMenuModel model) {
this (aAction, model, true);
}
static final long serialVersionUID =-4446966671302959091L;
/** 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 action the action to which this menu item should be connected
* @param label a string label for the check box menu item,
* or null for an unlabeled menu item.
* @param support the support for the menu items
*/
public SubMenu(SystemAction aAction, SubMenuModel model, boolean popup) {
subModel = model;
new SubMenuBridge (this, aAction, model, popup).updateState (null);
}
// XXX Overriding processKeyBinding is not a nice solution, used as
// a last resort here to fix the bug.
// #9331. Missed accelerator for Paste action.
/** Overrides superclass method.
* If it has accelerator delegates processing of it to the first item. */
protected boolean processKeyBinding(
KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
// If it is as accelerator process the doClick binding to the
// first sub-item.
if(ks.equals(accelerator)) {
// Use first item if there is one.
Component[] cs = getMenuComponents();
if(cs.length > 0 && cs[0] instanceof JComponent) {
JComponent comp = (JComponent)cs[0];
ActionMap am = comp.getActionMap();
if(am != null && comp.isEnabled()) {
Action action = am.get("doClick"); // NOI18N
if (action != null) {
return SwingUtilities.notifyAction(
action, ks, e, comp, e.getModifiers());
}
}
return false;
}
}
return super.processKeyBinding(ks, e, condition, pressed);
}
// XXX #11048. Ugly patch.
// This works for the cases when this menu is in 'menu'
// (not popup), the popup is handled by NbPopupMenuUI hack. This same
// method for popup wouldn't work since NbPopupMenuUI automatically
// passes focus to sub-menu.
/** Overrides superclass method. Adds a hack for KB menu invokation
* when this JMenu
needs to act like JMenuItem
. */
public void processKeyEvent(KeyEvent e, MenuElement[] path, MenuSelectionManager m) {
if(getMenuComponentCount() <= 1
&& java.util.Arrays.equals(
path, MenuSelectionManager.defaultManager().getSelectedPath())
&& (e.getKeyCode() == KeyEvent.VK_ENTER
|| e.getKeyCode() == KeyEvent.VK_SPACE)
) {
ActionListener ac = oneItemListener;
if(ac != null) {
m.setSelectedPath(new MenuElement[0]);
ac.actionPerformed(new ActionEvent(e.getSource(), 0, null));
return;
}
}
super.processKeyEvent(e, path, m);
}
/** Request for either MenuUI or MenuItemUI if the only one subitem should not
* use submenu.
*/
public String getUIClassID () {
if (previousCount == 0) {
return "MenuItemUI"; // NOI18N
}
return previousCount == 1 ? "MenuItemUI" : "MenuUI"; // NOI18N
}
boolean useAccel() {
return subModel.getCount() <= 1;
}
/** Overrides superclass method to be able to have an accelerator. */
public void setAccelerator(KeyStroke keyStroke) {
KeyStroke oldAccelerator = accelerator;
this.accelerator = keyStroke;
firePropertyChange("accelerator", oldAccelerator, accelerator); // NOI18N
}
/** Overrides superclass method to be able to have an accelerator. */
public KeyStroke getAccelerator() {
return this.accelerator;
}
public void menuSelectionChanged(boolean isIncluded) {
if (previousCount <= 1)
setArmed(isIncluded); // JMenuItem behaviour
else
super.menuSelectionChanged(isIncluded);
}
/** Menu cannot be selected when it represents MenuItem.
*/
public void setSelected (boolean s) {
// disabled menu cannot be selected
if (isEnabled () || !s) {
super.setSelected (s);
}
}
/** Seting menu to disabled also sets the item as not selected
*/
public void setEnabled (boolean e) {
super.setEnabled (e);
if (!e) {
super.setSelected (false);
}
}
public void doClick(int pressTime) {
if (!isEnabled ()) {
// do nothing if not enabled
return;
}
if (oneItemListener != null) {
oneItemListener.actionPerformed (null);
} else {
super.doClick (pressTime);
}
}
}
/** Inner class that listens on property change of top manager and
* if PROP_GLOBAL_KEYMAP is fired, then fires to own listeners
* in AWT thread.
*/
static final class GlobalKeymapL extends PropertyChangeSupport
implements PropertyChangeListener, Runnable {
GlobalKeymapL () {
super (org.openide.TopManager.getDefault());
org.openide.TopManager.getDefault ().addPropertyChangeListener (this);
}
public void propertyChange(PropertyChangeEvent ev) {
if (org.openide.TopManager.PROP_GLOBAL_KEYMAP == ev.getPropertyName ()) {
Mutex.EVENT.readAccess (this);
}
}
public void run () {
firePropertyChange (org.openide.TopManager.PROP_GLOBAL_KEYMAP, null, null);
}
}
}