![JAR search and dependency download from the Maven repository](/logo.png)
org.openide.awt.MenuBar Maven / Gradle / Ivy
/*
* 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-2004 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.awt;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.*;
import java.awt.event.KeyEvent;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.KeyStroke;
import org.openide.ErrorManager;
import org.openide.loaders.*;
import org.openide.cookies.InstanceCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.Repository;
import org.openide.nodes.*;
import org.openide.util.Utilities;
import org.openide.util.actions.Presenter;
import org.openide.util.*;
/** An extended version of swing's JMenuBar. This menubar can
* load its content from the folder where its "disk image" is stored.
* Moreover, menu is Externalizable
to restore its persistent
* state with minimal storage expensiveness.
*
* The MenuBar recognizes following objects in the folder:
* - subfolders - they're turned into top-level JMenu instances
*
- instances of
Component
- they're added directly
* to the menubar.
* - instances of
Presenter.Toolbar
- their toolbar presenter
* is added to the menubar.
*
* before OpenAPI version 3.2, only subfolders were recognized.
*
* In subfolders the following objects are recognized and added to submenus:
* - nested subfolders - they're turned into submenus
*
- instances of
Presenter.Menu
* - instances of
JMenuItem
* - instances of
JSeparator
* - instances of
Action
* - executable
DataObject
s
*
*
* @author David Peroutka, Dafe Simonek, Petr Nejedly
*/
public class MenuBar extends JMenuBar implements Externalizable {
/** the folder which represents and loads content of the menubar */
private MenuBarFolder menuBarFolder;
static final long serialVersionUID =-4721949937356581268L;
/** Don't call this constructor or this class will not get
* initialized properly. This constructor is only for externalization.
*/
public MenuBar() {
super();
}
/** Creates a new MenuBar
from given folder.
* @param folder The folder from which to create the content of the menubar.
* If the parameter is null, default menu folder is obtained.
*/
public MenuBar(DataFolder folder) {
this();
setBorder (javax.swing.BorderFactory.createEmptyBorder());
DataFolder theFolder = folder;
if (theFolder == null) {
FileObject fo = Repository.getDefault().getDefaultFileSystem().findResource("Menu");
if (fo == null) throw new IllegalStateException("No Menu/"); // NOI18N
theFolder = DataFolder.findFolder(fo);
}
startLoading(theFolder);
if(folder != null) {
getAccessibleContext().setAccessibleDescription(folder.getName());
}
}
public void addImpl (Component c, Object constraint, int idx) {
//Issue 17559, Apple's screen menu bar implementation blindly casts
//added components as instances of JMenu. Silently ignore any non-menu
//items on Mac if the screen menu flag is true.
if (Utilities.getOperatingSystem() == Utilities.OS_MAC &&
Boolean.getBoolean ("apple.laf.useScreenMenuBar")) { //NOI18N
if (!(c instanceof JMenu)) {
return;
}
}
super.addImpl (c, constraint, idx);
}
/**
* Overridden to handle mac conversion from Alt to Ctrl and vice versa so
* Alt can be used as the compose character on international keyboards.
*/
protected boolean processKeyBinding(KeyStroke ks,
KeyEvent e,
int condition,
boolean pressed) {
if (Utilities.getOperatingSystem() == Utilities.OS_MAC) {
int mods = e.getModifiers();
boolean isCtrl = (mods & KeyEvent.CTRL_MASK) != 0;
boolean isAlt = (mods & KeyEvent.ALT_MASK) != 0;
if (isAlt) {
return false;
}
if (isAlt && !isCtrl) {
mods = mods & ~ KeyEvent.ALT_MASK;
mods = mods & ~ KeyEvent.ALT_DOWN_MASK;
mods |= KeyEvent.CTRL_MASK;
mods |= KeyEvent.CTRL_DOWN_MASK;
} else if (!isAlt && isCtrl) {
mods = mods & ~ KeyEvent.CTRL_MASK;
mods = mods & ~ KeyEvent.CTRL_DOWN_MASK;
mods |= KeyEvent.ALT_MASK;
mods |= KeyEvent.ALT_DOWN_MASK;
} else if (!isAlt && !isCtrl) {
return super.processKeyBinding (ks, e, condition, pressed);
}
KeyEvent newEvent = new MarkedKeyEvent ((Component) e.getSource(), e.getID(),
e.getWhen(), mods, e.getKeyCode(), e.getKeyChar(),
e.getKeyLocation());
KeyStroke newStroke = e.getID() == KeyEvent.KEY_TYPED ?
KeyStroke.getKeyStroke (ks.getKeyChar(), mods) :
KeyStroke.getKeyStroke (ks.getKeyCode(), mods,
!ks.isOnKeyRelease());
boolean result = super.processKeyBinding (newStroke, newEvent,
condition, pressed);
if (newEvent.isConsumed()) {
e.consume();
}
return result;
} else {
return super.processKeyBinding (ks, e, condition, pressed);
}
}
/** Blocks until the menubar is completely created. */
public void waitFinished () {
menuBarFolder.instanceFinished();
}
/** Saves the contents of this object to the specified stream.
*
* @exception IOException Includes any I/O exceptions that may occur
*/
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(menuBarFolder.getFolder());
}
/**
* Restores contents of this object from the specified stream.
*
* @exception ClassNotFoundException If the class for an object being
* restored cannot be found.
*/
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
startLoading((DataFolder)in.readObject());
}
/** Starts loading of this menu from menu folder */
void startLoading (final DataFolder folder) {
menuBarFolder = new MenuBarFolder(folder);
}
/** Convert an array of instance cookies to instances, adds them
* to given list.
* @param arr array of instance cookies
* @param list list to add created objects to
*/
static void allInstances (InstanceCookie[] arr, java.util.List list) {
ErrorManager err = ErrorManager.getDefault();
Exception ex = null;
for (int i = 0; i < arr.length; i++) {
Exception newEx = null;
try {
Object o = arr[i].instanceCreate();
list.add (o);
} catch (ClassNotFoundException e) {
newEx = e;
} catch (IOException e) {
newEx = e;
}
if (newEx != null) {
ErrorManager.Annotation[] anns = err.findAnnotations(newEx);
if (anns == null || anns.length == 0) {
// if the exception is not annotated, assign it low
// priority
err.annotate(newEx, err.INFORMATIONAL, null, null, null, null);
}
err.copyAnnotation(newEx, ex);
ex = newEx;
}
}
// if there was an exception => notify it
if (ex != null) {
err.notify (ex);
}
}
/** This class can be used to fill the content of given
* MenuBar
from the given DataFolder
.
*/
private final class MenuBarFolder extends FolderInstance {
/** List of the components this FolderInstance manages. */
private ArrayList managed = new ArrayList();
/** Creates a new menubar folder on the specified DataFolder
.
* @param folder a DataFolder
to work with
*/
public MenuBarFolder (final DataFolder folder) {
super(folder);
recreate ();
}
/** Removes the components added by this FolderInstance from the MenuBar.
* Called when menu is refreshed. */
private void cleanUp() {
for (Iterator it = managed.iterator(); it.hasNext(); ) {
MenuBar.this.remove((Component)it.next());
}
managed.clear();
}
/** Adds the component to the MenuBar after the last added one */
private void addComponent (Component c) {
MenuBar.this.add(c, managed.size());
managed.add(c);
}
/** Full name of the data folder's primary file separated by dots.
* @return the name
*/
public String instanceName () {
return MenuBar.class.getName();
}
/** Returns the root class of all objects.
* @return MenuBar.class
*/
public Class instanceClass () {
return MenuBar.class;
}
/** Accepts only cookies that can provide a Component
* or a Presenter.Toolbar
.
* @param cookie the instance cookie to test
* @return true if the cookie is accepted.
*/
protected InstanceCookie acceptCookie(InstanceCookie cookie)
throws IOException, ClassNotFoundException {
Class cls = cookie.instanceClass();
boolean is =
Component.class.isAssignableFrom(cls) ||
Presenter.Toolbar.class.isAssignableFrom(cls) ||
Action.class.isAssignableFrom(cls);
return is ? cookie : null;
}
/** Returns an InstanceCookie
of a JMenu
* for the specified DataFolder
.
*
* @param df a DataFolder
to create the cookie for
* @return an InstanceCookie
for the specified folder
*/
protected InstanceCookie acceptFolder (DataFolder df) {
return new InstanceSupport.Instance(new LazyMenu(df, false));
}
/** Updates the MenuBar
represented by this folder.
*
* @param cookies array of instance cookies for the folder
* @return the updated MenuBar
representee
*/
protected Object createInstance(InstanceCookie[] cookies)
throws IOException, ClassNotFoundException {
final LinkedList ll = new LinkedList();
allInstances(cookies, ll);
final MenuBar mb = MenuBar.this;
cleanUp(); //remove the stuff we've added last time
// fill with new content
Iterator it = ll.iterator();
while (it.hasNext()) {
Component component = convertToComponent(it.next());
if (component != null) {
addComponent(component);
}
}
mb.validate();
mb.repaint();
return mb;
}
private Component convertToComponent(final Object obj) {
Component retVal = null;
if (obj instanceof Component) {
retVal = (Component)obj;
} else {
if (obj instanceof Presenter.Toolbar) {
retVal = ((Presenter.Toolbar)obj).getToolbarPresenter();
if (obj instanceof JButton) { // tune the presenter a bit
((JButton)obj).setBorderPainted(false);
}
} else if (obj instanceof Action) {
Action a = (Action) obj;
JButton button = new JButton();
Actions.connect(button, a);
retVal = button;
}
}
return retVal;
}
/** For outer class access to the data folder */
DataFolder getFolder () {
return folder;
}
/** Recreate the instance in AWT thread. */
protected Task postCreationTask (Runnable run) {
return new AWTTask (run);
}
}
/**
* A marker class to allow different processing of remapped key events
* on mac - allows them to be recognized by LazyMenu.
*/
private static final class MarkedKeyEvent extends KeyEvent {
public MarkedKeyEvent (Component c, int id,
long when, int mods, int code, char kchar,
int loc) {
super(c, id, when, mods, code, kchar, loc);
}
}
/** Menu based on the folder content whith lazy items creation. */
private static class LazyMenu extends JMenuPlus implements NodeListener, Runnable {
DataFolder master;
boolean icon;
MenuFolder slave;
/** Constructor. */
public LazyMenu(final DataFolder df, boolean icon) {
master = df;
this.icon = icon;
// Listen for changes in Node's DisplayName/Icon
Node n = master.getNodeDelegate ();
n.addNodeListener (org.openide.nodes.NodeOp.weakNodeListener (this, n));
updateProps();
}
protected boolean processKeyBinding(KeyStroke ks,
KeyEvent e,
int condition,
boolean pressed) {
if (Utilities.getOperatingSystem() == Utilities.OS_MAC) {
int mods = e.getModifiers();
boolean isCtrl = (mods & KeyEvent.CTRL_MASK) != 0;
boolean isAlt = (mods & KeyEvent.ALT_MASK) != 0;
if (isAlt && (e instanceof MarkedKeyEvent)) {
mods = mods & ~ KeyEvent.CTRL_MASK;
mods = mods & ~ KeyEvent.CTRL_DOWN_MASK;
mods |= KeyEvent.ALT_MASK;
mods |= KeyEvent.ALT_DOWN_MASK;
KeyEvent newEvent = new MarkedKeyEvent (
(Component) e.getSource(), e.getID(),
e.getWhen(), mods, e.getKeyCode(), e.getKeyChar(),
e.getKeyLocation());
KeyStroke newStroke = e.getID() == KeyEvent.KEY_TYPED ?
KeyStroke.getKeyStroke (ks.getKeyChar(), mods) :
KeyStroke.getKeyStroke (ks.getKeyCode(), mods,
!ks.isOnKeyRelease());
boolean result = super.processKeyBinding (newStroke,
newEvent, condition, pressed);
if (newEvent.isConsumed()) {
e.consume();
}
return result;
} else if (!isAlt) {
return super.processKeyBinding (ks, e, condition, pressed);
} else {
return false;
}
} else {
return super.processKeyBinding (ks, e, condition, pressed);
}
}
private void updateProps() {
// set the text and be aware of mnemonics
Node n = master.getNodeDelegate ();
Actions.setMenuText(this, n.getDisplayName (), true);
if (icon) setIcon (new ImageIcon (
n.getIcon (java.beans.BeanInfo.ICON_COLOR_16x16)));
}
/** Update the properties. Exported via Runnable interface so it
* can be rescheduled. */
public void run() {
updateProps();
}
/** If the display name changes, than change the name of the menu.*/
public void propertyChange (java.beans.PropertyChangeEvent ev) {
if (
Node.PROP_DISPLAY_NAME.equals (ev.getPropertyName ()) ||
Node.PROP_NAME.equals (ev.getPropertyName ()) ||
Node.PROP_ICON.equals (ev.getPropertyName ())
) {
// update the properties in AWT queue
if (EventQueue.isDispatchThread ()) {
updateProps(); // do the update synchronously
} else {
EventQueue.invokeLater (this);
}
}
}
// The rest of the NodeListener implementation
public void childrenAdded (NodeMemberEvent ev) {}
public void childrenRemoved (NodeMemberEvent ev) {}
public void childrenReordered(NodeReorderEvent ev) {}
public void nodeDestroyed (NodeEvent ev) {}
/** Overrides superclass method to lazy create popup. */
public JPopupMenu getPopupMenu() {
doInitialize();
return super.getPopupMenu();
}
private void doInitialize() {
if(slave == null) {
slave = new MenuFolder(); // will do the tracking
slave.waitFinished();
}
}
/** This class can be used to update a JMenu
instance
* from the given DataFolder
.
*/
private class MenuFolder extends FolderInstance {
/**
* Start tracking the content of the master folder.
* It will cause initial update of the Menu
*/
public MenuFolder () {
super(master);
recreate ();
}
/** The name of the menu
* @return the name
*/
public String instanceName () {
return LazyMenu.class.getName();
}
/** Returns the class of represented menu.
* @return JMenu.class
*/
public Class instanceClass () {
return JMenu.class;
}
/** If no instance cookie, tries to create execution action on the
* data object.
*/
protected InstanceCookie acceptDataObject (DataObject dob) {
InstanceCookie ic = super.acceptDataObject (dob);
if (ic == null) {
JMenuItem item = ExecBridge.createMenuItem (dob);
return item != null ? new InstanceSupport.Instance (item) : null;
} else {
return ic;
}
}
/**
* Accepts only cookies that can provide Menu
.
* @param cookie an InstanceCookie
to test
* @return true if the cookie can provide accepted instances
*/
protected InstanceCookie acceptCookie(InstanceCookie cookie)
throws IOException, ClassNotFoundException {
// [pnejedly] Don't try to optimize this by InstanceCookie.Of
// It will load the classes few ms later from instanceCreate
// anyway and more instanceOf calls take longer
Class c = cookie.instanceClass();
boolean is =
Presenter.Menu.class.isAssignableFrom (c) ||
JMenuItem.class.isAssignableFrom (c) ||
JSeparator.class.isAssignableFrom (c) ||
Action.class.isAssignableFrom (c);
return is ? cookie : null;
}
/**
* Returns a Menu.Folder
cookie for the specified
* DataFolder
.
* @param df a DataFolder
to create the cookie for
* @return a Menu.Folder
for the specified folder
*/
protected InstanceCookie acceptFolder(DataFolder df) {
return new InstanceSupport.Instance(new LazyMenu(df, true));
}
/** Updates the JMenu
represented by this folder.
* @param cookies array of instance cookies for the folder
* @return the updated JMenu
representee
*/
protected Object createInstance(InstanceCookie[] cookies)
throws IOException, ClassNotFoundException {
JMenu m = LazyMenu.this;
//synchronized (this) { // see #15917 - attachment from 2001/09/27
LinkedList cInstances = new LinkedList();
allInstances (cookies, cInstances);
m.removeAll();
// #11848, #13013. Enablement should be set immediatelly,
// popup will be created on-demand.
// m.setEnabled(!cInstances.isEmpty());
// TODO: fill it with empty sign instead
if(cInstances.isEmpty()) {
JMenuItem item = new JMenuItem(
NbBundle.getMessage(DataObject.class, "CTL_EmptyMenu"));
item.setEnabled(false);
m.add(item);
}
// clear first - refresh the menu's content
boolean addSeparator = false;
// sync to prevent from concurrent modifications of
// cookie instances list
Iterator it = cInstances.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof Presenter.Menu) {
obj = ((Presenter.Menu)obj).getMenuPresenter();
}
if (obj instanceof JMenuItem) {
if(addSeparator) {
m.addSeparator();
addSeparator = false;
}
m.add((JMenuItem)obj);
} else if (obj instanceof JSeparator) {
addSeparator = getMenuComponentCount() > 0;
} else if (obj instanceof Action) {
if(addSeparator) {
m.addSeparator();
addSeparator = false;
}
Action a = (Action)obj;
JMenuItem item = new JMenuItem ();
Actions.connect (item, a, false);
m.add (item);
}
}
return m;
}
/** Recreate the instance in AWT thread.
*/
protected Task postCreationTask (Runnable run) {
return new AWTTask (run);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy