/*
* 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-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.awt;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
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.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.<P>
* Moreover, menu is <code>Externalizable</code> to restore its persistent
* state with minimal storage expensiveness.
*
* The MenuBar recognizes following objects in the folder: <UL>
* <LI>subfolders - they're turned into top-level JMenu instances
* <LI>instances of <CODE>Component</CODE> - they're added directly
* to the menubar.
* <LI>instances of <CODE>Presenter.Toolbar</CODE> - their toolbar presenter
* is added to the menubar.
* </UL>
* before OpenAPI version 3.2, only subfolders were recognized.
*
* <P>In subfolders the following objects are recognized and added to submenus:<UL>
* <LI>nested subfolders - they're turned into submenus
* <LI>instances of <CODE>Presenter.Menu</CODE>
* <LI>instances of <CODE>JMenuItem</CODE>
* <LI>instances of <CODE>JSeparator</CODE>
* <LI>instances of <CODE>Action</CODE>
* <LI>executable <CODE>DataObject</CODE>s
* </UL>
*
* @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 <code>MenuBar</code> 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) {
super();
// PENDING(david) -- setBorder(new MenuBorder());
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());
}
}
/** 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) {
org.openide.ErrorManager err = org.openide.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) {
if (err.findAnnotations(newEx) == null) {
// 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
* <code>MenuBar</code> from the given <code>DataFolder</code>.
*/
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 <code>DataFolder</code>.
* @param folder a <code>DataFolder</code> 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 <code>Component</code>
* or a <code>Presenter.Toolbar</code>.
* @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();
return Component.class.isAssignableFrom(cls) ||
Presenter.Toolbar.class.isAssignableFrom(cls)
? cookie : null;
}
/** Returns an <code>InstanceCookie</code> of a JMenu
* for the specified <code>DataFolder</code>.
*
* @param df a <code>DataFolder</code> to create the cookie for
* @return an <code>InstanceCookie</code> for the specified folder
*/
protected InstanceCookie acceptFolder (DataFolder df) {
return new InstanceSupport.Instance(new LazyMenu(df, false));
}
/** Updates the <code>MenuBar</code> represented by this folder.
*
* @param cookies array of instance cookies for the folder
* @return the updated <code>MenuBar</code> 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()) {
Object obj = it.next ();
if (obj instanceof Presenter.Toolbar) {
obj = ((Presenter.Toolbar)obj).getToolbarPresenter();
if (obj instanceof JButton) { // tune the presenter a bit
((JButton)obj).setBorderPainted(false);
}
}
if (obj instanceof Component) addComponent((Component) obj);
}
mb.validate();
mb.repaint();
return mb;
}
/** 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);
}
}
/** 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 (WeakListener.node (this, n));
updateProps();
}
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 <code>JMenu</code> instance
* from the given <code>DataFolder</code>.
*/
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 <code>Menu</code>.
* @param cookie an <code>InstanceCookie</code> 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 <code>Menu.Folder</code> cookie for the specified
* <code>DataFolder</code>.
* @param df a <code>DataFolder</code> to create the cookie for
* @return a <code>Menu.Folder</code> for the specified folder
*/
protected InstanceCookie acceptFolder(DataFolder df) {
return new InstanceSupport.Instance(new LazyMenu(df, true));
}
/** Updates the <code>JMenu</code> represented by this folder.
* @param cookies array of instance cookies for the folder
* @return the updated <code>JMenu</code> 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(MenuBar.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);
}
}
}
}